Files
Niranjan Dighe aaa19719df sound: safety-i2s: Add support for always-on rx/tx
Add support for keeping RX/TX always ON by using amixer commands
and to not disable clocks after playback/capture usecases finish.
This is to prevent A2B master from losing internal state.

Bug 3742979

Change-Id: I01dacaa3f54c98a7a14be101d8f0a315b006d7d8
Signed-off-by: Niranjan Dighe <ndighe@nvidia.com>
Reviewed-on: https://git-master.nvidia.com/r/c/linux-nv-oot/+/2966000
Reviewed-by: Uday Gupta <udayg@nvidia.com>
Reviewed-by: Laxman Dewangan <ldewangan@nvidia.com>
GVS: Gerrit_Virtual_Submit <buildbot_gerritrpt@nvidia.com>
Tested-by: Purvi Medawala <pmedawala@nvidia.com>
2023-10-17 18:44:37 -07:00

487 lines
12 KiB
C

// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2021-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
*/
#include <linux/kernel.h>
#include <linux/io.h>
#include <linux/delay.h>
#include "tegra_i2s_regs.h"
#include "tegra_i2s.h"
#include <asm-generic/delay.h>
static inline struct i2s_dev *i2s_inst(unsigned int id)
{
static struct i2s_dev *i2s;
if (!i2s)
i2s = safety_i2s_get_priv();
WARN_ON(id >= NUM_SAFETY_I2S_INST);
return &i2s[id];
}
#define I2S_BASE(id) ((i2s_inst(id))->base)
#ifdef SAFETY_I2S_DEBUG
void i2s_dump_all_regs(unsigned int id)
{
int i;
pr_alert("RX Registers:\n");
for (i = 0; i <= 0x2c; i += 4)
pr_alert("0x%08x = 0x%08x\n", 0x02450000 + id * 0x10000 + i,
readl(I2S_BASE(id) + i));
pr_alert("TX Registers:\n");
for (i = 0x80; i <= 0xb0; i += 4)
pr_alert("0x%08x = 0x%08x\n", 0x02450000 + id * 0x10000 + i,
readl(I2S_BASE(id) + i));
pr_alert("Common Registers:\n");
for (i = 0x100; i <= 0x120; i += 4)
pr_alert("0x%08x = 0x%08x\n", 0x02450000 + id * 0x10000 + i,
readl(I2S_BASE(id) + i));
}
#endif
static unsigned int is_i2s_enabled(unsigned int id)
{
unsigned int val;
val = readl(I2S_BASE(id) + T234_I2S_ENABLE);
return (val & T234_I2S_EN_MASK);
}
unsigned int i2s_enable(unsigned int id)
{
unsigned int val;
val = readl(I2S_BASE(id) + T234_I2S_ENABLE);
val |= T234_I2S_EN;
writel(val, I2S_BASE(id) + T234_I2S_ENABLE);
return 0;
}
unsigned int i2s_disable(unsigned int id)
{
unsigned int val;
val = readl(I2S_BASE(id) + T234_I2S_ENABLE);
val &= ~T234_I2S_EN;
writel(val, I2S_BASE(id) + T234_I2S_ENABLE);
return 0;
}
static unsigned int is_i2s_tx_enabled(unsigned int id)
{
unsigned int val;
val = readl(I2S_BASE(id) + T234_I2S_TX_ENABLE);
return (val & T234_I2S_EN_MASK);
}
unsigned int i2s_enable_tx(unsigned int id)
{
unsigned int val;
if (is_i2s_tx_enabled(id))
return 0;
val = readl(I2S_BASE(id) + T234_I2S_TX_ENABLE);
val |= T234_I2S_TX_EN;
writel(val, I2S_BASE(id) + T234_I2S_TX_ENABLE);
return 0;
}
static unsigned int is_i2s_rx_enabled(unsigned int id)
{
unsigned int val;
val = readl(I2S_BASE(id) + T234_I2S_RX_ENABLE);
return (val & T234_I2S_EN_MASK);
}
unsigned int i2s_enable_rx(unsigned int id)
{
unsigned int val;
if (is_i2s_rx_enabled(id))
return 0;
val = readl(I2S_BASE(id) + T234_I2S_RX_ENABLE);
val |= T234_I2S_RX_EN;
writel(val, I2S_BASE(id) + T234_I2S_RX_ENABLE);
return 0;
}
static unsigned int is_i2s_loopback_enabled(unsigned int id)
{
unsigned int val;
val = readl(I2S_BASE(id) + T234_I2S_CTRL);
return !!(val & T234_I2S_CTRL_LPBK_MASK);
}
unsigned int i2s_set_loopback(unsigned int id, unsigned int loopback_enable)
{
unsigned int val;
unsigned int enable;
unsigned int rx_enable;
unsigned int tx_enable;
if (loopback_enable == is_i2s_loopback_enabled(id)) {
pr_info("I2S%d already has loopback in %s state\n",
(id + 1), loopback_enable ? "enabled" : "disabled");
return 0;
}
/* I2S needs to be disabled before enabling Loopback */
enable = is_i2s_enabled(id);
if (enable) {
tx_enable = is_i2s_tx_enabled(id);
if (tx_enable)
i2s_disable_tx(id);
rx_enable = is_i2s_rx_enabled(id);
if (rx_enable)
i2s_disable_rx(id);
}
val = readl(I2S_BASE(id) + T234_I2S_CTRL);
val = loopback_enable ? (val | T234_I2S_CTRL_LPBK_EN) :
(val & ~T234_I2S_CTRL_LPBK_MASK);
writel(val, I2S_BASE(id) + T234_I2S_CTRL);
if (enable) {
if (rx_enable)
i2s_enable_rx(id);
if (tx_enable)
i2s_enable_tx(id);
}
pr_info("I2S%d loopback set to %s state\n",
(id + 1), loopback_enable ? "enabled" : "disabled");
return 0;
}
static int i2s_sw_reset(unsigned int id, int direction, int timeout)
{
unsigned int sw_reset_reg, sw_reset_mask;
unsigned int sw_reset_en, sw_reset_default;
unsigned int tx_fifo_ctrl, rx_fifo_ctrl, tx_ctrl, rx_ctrl, ctrl, val;
int wait = timeout;
tx_fifo_ctrl = readl(I2S_BASE(id) + T234_I2S_TX_FIFO_CTRL);
rx_fifo_ctrl = readl(I2S_BASE(id) + T234_I2S_RX_FIFO_CTRL);
tx_ctrl = readl(I2S_BASE(id) + T234_I2S_TX_CTRL);
rx_ctrl = readl(I2S_BASE(id) + T234_I2S_RX_CTRL);
ctrl = readl(I2S_BASE(id) + T234_I2S_CTRL);
if (direction == PCM_STREAM_CAPTURE) {
sw_reset_reg = T234_I2S_RX_SOFT_RESET;
sw_reset_mask = T234_I2S_RX_SOFT_RESET_MASK;
sw_reset_en = T234_I2S_RX_SOFT_RESET_EN;
sw_reset_default = T234_I2S_RX_SOFT_RESET_DEFAULT;
} else {
sw_reset_reg = T234_I2S_TX_SOFT_RESET;
sw_reset_mask = T234_I2S_TX_SOFT_RESET_MASK;
sw_reset_en = T234_I2S_TX_SOFT_RESET_EN;
sw_reset_default = T234_I2S_TX_SOFT_RESET_DEFAULT;
}
updatel(I2S_BASE(id) + sw_reset_reg, sw_reset_mask, sw_reset_en);
do {
val = readl(I2S_BASE(id) + sw_reset_reg);
wait--;
udelay(10);
if (!wait) {
pr_err("RESET bit not cleared yet\n");
return -1;
}
} while (val & sw_reset_mask);
updatel(I2S_BASE(id) + sw_reset_reg, sw_reset_mask, sw_reset_default);
writel(tx_fifo_ctrl, I2S_BASE(id) + T234_I2S_TX_FIFO_CTRL);
writel(rx_fifo_ctrl, I2S_BASE(id) + T234_I2S_RX_FIFO_CTRL);
writel(tx_ctrl, I2S_BASE(id) + T234_I2S_TX_CTRL);
writel(rx_ctrl, I2S_BASE(id) + T234_I2S_RX_CTRL);
writel(ctrl, I2S_BASE(id) + T234_I2S_CTRL);
return 0;
}
static int i2s_get_status(unsigned int id, int direction)
{
unsigned int status_reg;
status_reg = (direction == PCM_STREAM_CAPTURE) ?
T234_I2S_RX_STATUS :
T234_I2S_TX_STATUS;
return readl(I2S_BASE(id) + status_reg);
}
/* Should be called after disabling RX */
static int i2s_rx_stop(unsigned int id)
{
int dcnt = 10, ret;
int status;
/* wait until I2S RX ENABLE bit is cleared
* and FIFO becomes empty as the DMA is still on
*/
while (((status = i2s_get_status(id, PCM_STREAM_CAPTURE)) &
T234_I2S_RX_STATUS_ENABLED) && dcnt--)
udelay(10);
if (dcnt < 0 || !(status & T234_I2S_RX_STATUS_FIFO_EMPTY)) {
/* HW needs sw reset to make sure previous trans be clean */
ret = i2s_sw_reset(id, PCM_STREAM_CAPTURE, 0xffff);
if (ret) {
pr_err("Failed at I2S%d_RX sw reset\n", id + 7);
return ret;
}
}
return 0;
}
unsigned int i2s_disable_rx(unsigned int id)
{
unsigned int val;
if (i2s_inst(id)->config.rx_always_on)
return 0;
val = readl(I2S_BASE(id) + T234_I2S_RX_ENABLE);
val &= ~T234_I2S_RX_EN;
writel(val, I2S_BASE(id) + T234_I2S_RX_ENABLE);
i2s_rx_stop(id);
return 0;
}
/* Should be called after disabling TX */
static int i2s_tx_stop(unsigned int id)
{
int dcnt = 10, ret;
int status;
/* wait until I2S TX ENABLE bit is cleared */
while ((status = i2s_get_status(id, PCM_STREAM_PLAYBACK) &
T234_I2S_TX_STATUS_ENABLED) && dcnt--)
udelay(10);
if (dcnt < 0 || !(status & T234_I2S_TX_STATUS_FIFO_EMPTY)) {
/* HW needs sw reset to make sure previous trans be clean */
ret = i2s_sw_reset(id, PCM_STREAM_PLAYBACK, 0xffff);
if (ret) {
pr_err("Failed at I2S%d_TX sw reset\n", id + 7);
return ret;
}
}
return 0;
}
unsigned int i2s_disable_tx(unsigned int id)
{
unsigned int val;
if (i2s_inst(id)->config.tx_always_on)
return 0;
val = readl(I2S_BASE(id) + T234_I2S_TX_ENABLE);
val &= ~T234_I2S_TX_EN;
writel(val, I2S_BASE(id) + T234_I2S_TX_ENABLE);
i2s_tx_stop(id);
return 0;
}
int i2s_configure(unsigned int id, struct i2s_config *config)
{
unsigned int ctrl = 0, timing = 0, rxctrl = 0, txctrl = 0;
unsigned int slotctrl = 0, tx_slotctrl = 0, rx_slotctrl = 0;
unsigned int i2sclock, bitcnt;
unsigned int clock_trim = config->clock_trim;
unsigned int fifo_ctrl = 0, threshold = 0;
volatile void __iomem *i2s_base = I2S_BASE(id);
switch (config->mode) {
case I2S_FRAME_FORMAT_I2S:
if (config->clock_mode == I2S_MASTER)
ctrl |= T234_I2S_CTRL_MASTER_EN;
ctrl |= (config->fsync_width <<
T234_I2S_CTRL_FSYNC_WIDTH_SHIFT);
ctrl &= ~T234_I2S_CTRL_EDGE_CTRL_MASK;
if (config->edge_ctrl == I2S_CLK_NEG_EDGE)
ctrl |= T234_I2S_CTRL_EDGE_CTRL_NEG_EDGE;
/* LRCK_MODE */
ctrl &= ~T234_I2S_CTRL_FRAME_FORMAT_MASK;
ctrl &= ~T234_I2S_CTRL_LRCK_POLARITY_MASK;
if (config->clock_polarity == LRCK_HIGH)
ctrl |= T234_I2S_CTRL_LRCK_POLARITY_HIGH;
i2sclock = config->srate * config->bit_size * config->channels;
if (config->bclk_ratio != 0)
i2sclock *= config->bclk_ratio;
bitcnt = (i2sclock / config->srate) - 1;
if (i2sclock % (2 * config->srate))
timing |= T234_I2S_TIMING_NON_SYM_EN;
timing |= ((bitcnt >> 1) <<
T234_I2S_TIMING_CHANNEL_BIT_CNT_SHIFT);
ctrl |= (((config->bit_size >> 2) - 1) <<
T234_I2S_CTRL_BIT_SIZE_SHIFT);
rxctrl |= (config->offset <<
T234_I2S_RX_CTRL_DATA_OFFSET_SHIFT) &
T234_I2S_RX_CTRL_DATA_OFFSET_MASK;
txctrl |= (config->offset <<
T234_I2S_TX_CTRL_DATA_OFFSET_SHIFT) &
T234_I2S_TX_CTRL_DATA_OFFSET_MASK;
if (config->pcm_mask_bits) {
rxctrl |= ((config->pcm_mask_bits) <<
T234_I2S_RX_CTRL_MASK_BITS_SHIFT);
txctrl |= ((config->pcm_mask_bits) <<
T234_I2S_TX_CTRL_MASK_BITS_SHIFT);
}
txctrl &= ~T234_I2S_TX_CTRL_HIGHZ_CTRL_MASK;
if (config->highz_ctrl == 1)
txctrl |= T234_I2S_TX_CTRL_HIGHZ_CTRL_HIGHZ;
else if (config->highz_ctrl == 2)
txctrl |=
T234_I2S_TX_CTRL_HIGHZ_CTRL_HIGHZ_ON_HALF_BIT_CLK;
writel(timing, i2s_base + T234_I2S_TIMING);
break;
case I2S_FRAME_FORMAT_TDM:
if (config->clock_mode == I2S_MASTER)
ctrl |= T234_I2S_CTRL_MASTER_EN;
ctrl |= (config->fsync_width <<
T234_I2S_CTRL_FSYNC_WIDTH_SHIFT);
ctrl &= ~T234_I2S_CTRL_EDGE_CTRL_MASK;
if (config->edge_ctrl == I2S_CLK_NEG_EDGE)
ctrl |= T234_I2S_CTRL_EDGE_CTRL_NEG_EDGE;
ctrl &= ~T234_I2S_CTRL_FRAME_FORMAT_MASK;
ctrl |= T234_I2S_CTRL_FRAME_FORMAT_FSYNC_MODE;
ctrl &= ~T234_I2S_CTRL_LRCK_POLARITY_MASK;
if (config->clock_polarity == LRCK_HIGH)
ctrl |= T234_I2S_CTRL_LRCK_POLARITY_HIGH;
ctrl |= (((config->bit_size >> 2) - 1) <<
T234_I2S_CTRL_BIT_SIZE_SHIFT);
i2sclock = config->srate * config->bit_size * config->channels;
if (config->bclk_ratio != 0)
i2sclock *= config->bclk_ratio;
bitcnt = (i2sclock / config->srate) - 1;
if (i2sclock % (2 * config->srate))
timing |= T234_I2S_TIMING_NON_SYM_EN;
timing |= (bitcnt << T234_I2S_TIMING_CHANNEL_BIT_CNT_SHIFT);
rxctrl |= (config->offset <<
T234_I2S_RX_CTRL_DATA_OFFSET_SHIFT) &
T234_I2S_RX_CTRL_DATA_OFFSET_MASK;
txctrl |= (config->offset <<
T234_I2S_TX_CTRL_DATA_OFFSET_SHIFT) &
T234_I2S_TX_CTRL_DATA_OFFSET_MASK;
if (config->pcm_mask_bits) {
rxctrl |= ((config->pcm_mask_bits) <<
T234_I2S_RX_CTRL_MASK_BITS_SHIFT);
txctrl |= ((config->pcm_mask_bits) <<
T234_I2S_TX_CTRL_MASK_BITS_SHIFT);
}
txctrl &= ~T234_I2S_TX_CTRL_HIGHZ_CTRL_MASK;
if (config->highz_ctrl == 1)
txctrl |= T234_I2S_TX_CTRL_HIGHZ_CTRL_HIGHZ;
else if (config->highz_ctrl == 2)
txctrl |=
T234_I2S_TX_CTRL_HIGHZ_CTRL_HIGHZ_ON_HALF_BIT_CLK;
slotctrl |= ((config->total_slots - 1) <<
T234_I2S_SLOT_CTRL_TOTAL_SLOTS_SHIFT);
tx_slotctrl |= (config->tx_mask <<
T234_I2S_TX_SLOT_CTRL_SLOT_ENABLES_SHIFT);
rx_slotctrl |= (config->rx_mask <<
T234_I2S_RX_SLOT_CTRL_SLOT_ENABLES_SHIFT);
writel(timing, i2s_base + T234_I2S_TIMING);
break;
default:
return -1;
}
if (clock_trim > T234_I2S_SCLK_TRIM_SEL_MASK) {
pr_alert("Clock trim invalid\n");
return -1;
}
clock_trim = ((clock_trim & T234_I2S_SCLK_TRIM_SEL_MASK) <<
T234_I2S_SCLK_TRIM_SEL_SHIFT);
//TODO: Add proper register offset macros and masks etc.
fifo_ctrl = (((config->channels - 1) << 4) & (0xf << 4)) |
(((config->channels) << 16) & (0x7f << 16)) |
(1 << 24);
threshold = 0;
/* Overwrite the timing register only in the master mode.
* Setting it to 0x0 causes noise during slave mode playback.
* Setting the I2S control registers
*/
writel(ctrl, i2s_base + T234_I2S_CTRL);
writel(rxctrl, i2s_base + T234_I2S_RX_CTRL);
writel(txctrl, i2s_base + T234_I2S_TX_CTRL);
writel(slotctrl, i2s_base + T234_I2S_SLOT_CTRL);
writel(tx_slotctrl, i2s_base + T234_I2S_TX_SLOT_CTRL);
writel(rx_slotctrl, i2s_base + T234_I2S_RX_SLOT_CTRL);
writel(clock_trim, i2s_base + T234_I2S_CLK_TRIM);
writel(fifo_ctrl, i2s_base + T234_I2S_RX_FIFO_CTRL);
writel(fifo_ctrl, i2s_base + T234_I2S_TX_FIFO_CTRL);
writel(threshold, i2s_base + T234_I2S_TX_START_THRESHOLD);
i2s_enable(id);
return 0;
}