Commit 4f8cd05a authored by Biju Das's avatar Biju Das Committed by Mark Brown

ASoC: sh: rz-ssi: Add full duplex support

Add full duplex support, to support simultaneous
playback/record on the same ssi channel.
Signed-off-by: default avatarBiju Das <biju.das.jz@bp.renesas.com>
Link: https://patch.msgid.link/20240715092322.119879-1-biju.das.jz@bp.renesas.comSigned-off-by: default avatarMark Brown <broonie@kernel.org>
parent b3f35bae
...@@ -52,6 +52,7 @@ ...@@ -52,6 +52,7 @@
#define SSIFCR_RIE BIT(2) #define SSIFCR_RIE BIT(2)
#define SSIFCR_TFRST BIT(1) #define SSIFCR_TFRST BIT(1)
#define SSIFCR_RFRST BIT(0) #define SSIFCR_RFRST BIT(0)
#define SSIFCR_FIFO_RST (SSIFCR_TFRST | SSIFCR_RFRST)
#define SSIFSR_TDC_MASK 0x3f #define SSIFSR_TDC_MASK 0x3f
#define SSIFSR_TDC_SHIFT 24 #define SSIFSR_TDC_SHIFT 24
...@@ -130,6 +131,14 @@ struct rz_ssi_priv { ...@@ -130,6 +131,14 @@ struct rz_ssi_priv {
bool lrckp_fsync_fall; /* LR clock polarity (SSICR.LRCKP) */ bool lrckp_fsync_fall; /* LR clock polarity (SSICR.LRCKP) */
bool bckp_rise; /* Bit clock polarity (SSICR.BCKP) */ bool bckp_rise; /* Bit clock polarity (SSICR.BCKP) */
bool dma_rt; bool dma_rt;
/* Full duplex communication support */
struct {
unsigned int rate;
unsigned int channels;
unsigned int sample_width;
unsigned int sample_bits;
} hw_params_cache;
}; };
static void rz_ssi_dma_complete(void *data); static void rz_ssi_dma_complete(void *data);
...@@ -208,6 +217,11 @@ static bool rz_ssi_stream_is_valid(struct rz_ssi_priv *ssi, ...@@ -208,6 +217,11 @@ static bool rz_ssi_stream_is_valid(struct rz_ssi_priv *ssi,
return ret; return ret;
} }
static inline bool rz_ssi_is_stream_running(struct rz_ssi_stream *strm)
{
return strm->substream && strm->running;
}
static void rz_ssi_stream_init(struct rz_ssi_stream *strm, static void rz_ssi_stream_init(struct rz_ssi_stream *strm,
struct snd_pcm_substream *substream) struct snd_pcm_substream *substream)
{ {
...@@ -303,13 +317,53 @@ static int rz_ssi_clk_setup(struct rz_ssi_priv *ssi, unsigned int rate, ...@@ -303,13 +317,53 @@ static int rz_ssi_clk_setup(struct rz_ssi_priv *ssi, unsigned int rate,
return 0; return 0;
} }
static void rz_ssi_set_idle(struct rz_ssi_priv *ssi)
{
int timeout;
/* Disable irqs */
rz_ssi_reg_mask_setl(ssi, SSICR, SSICR_TUIEN | SSICR_TOIEN |
SSICR_RUIEN | SSICR_ROIEN, 0);
rz_ssi_reg_mask_setl(ssi, SSIFCR, SSIFCR_TIE | SSIFCR_RIE, 0);
/* Clear all error flags */
rz_ssi_reg_mask_setl(ssi, SSISR,
(SSISR_TOIRQ | SSISR_TUIRQ | SSISR_ROIRQ |
SSISR_RUIRQ), 0);
/* Wait for idle */
timeout = 100;
while (--timeout) {
if (rz_ssi_reg_readl(ssi, SSISR) & SSISR_IIRQ)
break;
udelay(1);
}
if (!timeout)
dev_info(ssi->dev, "timeout waiting for SSI idle\n");
/* Hold FIFOs in reset */
rz_ssi_reg_mask_setl(ssi, SSIFCR, 0,
SSIFCR_TFRST | SSIFCR_RFRST);
}
static int rz_ssi_start(struct rz_ssi_priv *ssi, struct rz_ssi_stream *strm) static int rz_ssi_start(struct rz_ssi_priv *ssi, struct rz_ssi_stream *strm)
{ {
bool is_play = rz_ssi_stream_is_play(ssi, strm->substream); bool is_play = rz_ssi_stream_is_play(ssi, strm->substream);
bool is_full_duplex;
u32 ssicr, ssifcr; u32 ssicr, ssifcr;
is_full_duplex = rz_ssi_is_stream_running(&ssi->playback) ||
rz_ssi_is_stream_running(&ssi->capture);
ssicr = rz_ssi_reg_readl(ssi, SSICR); ssicr = rz_ssi_reg_readl(ssi, SSICR);
ssifcr = rz_ssi_reg_readl(ssi, SSIFCR) & ~0xF; ssifcr = rz_ssi_reg_readl(ssi, SSIFCR);
if (!is_full_duplex) {
ssifcr &= ~0xF;
} else {
rz_ssi_reg_mask_setl(ssi, SSICR, SSICR_TEN | SSICR_REN, 0);
rz_ssi_set_idle(ssi);
ssifcr &= ~SSIFCR_FIFO_RST;
}
/* FIFO interrupt thresholds */ /* FIFO interrupt thresholds */
if (rz_ssi_is_dma_enabled(ssi)) if (rz_ssi_is_dma_enabled(ssi))
...@@ -322,10 +376,14 @@ static int rz_ssi_start(struct rz_ssi_priv *ssi, struct rz_ssi_stream *strm) ...@@ -322,10 +376,14 @@ static int rz_ssi_start(struct rz_ssi_priv *ssi, struct rz_ssi_stream *strm)
/* enable IRQ */ /* enable IRQ */
if (is_play) { if (is_play) {
ssicr |= SSICR_TUIEN | SSICR_TOIEN; ssicr |= SSICR_TUIEN | SSICR_TOIEN;
ssifcr |= SSIFCR_TIE | SSIFCR_RFRST; ssifcr |= SSIFCR_TIE;
if (!is_full_duplex)
ssifcr |= SSIFCR_RFRST;
} else { } else {
ssicr |= SSICR_RUIEN | SSICR_ROIEN; ssicr |= SSICR_RUIEN | SSICR_ROIEN;
ssifcr |= SSIFCR_RIE | SSIFCR_TFRST; ssifcr |= SSIFCR_RIE;
if (!is_full_duplex)
ssifcr |= SSIFCR_TFRST;
} }
rz_ssi_reg_writel(ssi, SSICR, ssicr); rz_ssi_reg_writel(ssi, SSICR, ssicr);
...@@ -337,7 +395,11 @@ static int rz_ssi_start(struct rz_ssi_priv *ssi, struct rz_ssi_stream *strm) ...@@ -337,7 +395,11 @@ static int rz_ssi_start(struct rz_ssi_priv *ssi, struct rz_ssi_stream *strm)
SSISR_RUIRQ), 0); SSISR_RUIRQ), 0);
strm->running = 1; strm->running = 1;
ssicr |= is_play ? SSICR_TEN : SSICR_REN; if (is_full_duplex)
ssicr |= SSICR_TEN | SSICR_REN;
else
ssicr |= is_play ? SSICR_TEN : SSICR_REN;
rz_ssi_reg_writel(ssi, SSICR, ssicr); rz_ssi_reg_writel(ssi, SSICR, ssicr);
return 0; return 0;
...@@ -345,10 +407,12 @@ static int rz_ssi_start(struct rz_ssi_priv *ssi, struct rz_ssi_stream *strm) ...@@ -345,10 +407,12 @@ static int rz_ssi_start(struct rz_ssi_priv *ssi, struct rz_ssi_stream *strm)
static int rz_ssi_stop(struct rz_ssi_priv *ssi, struct rz_ssi_stream *strm) static int rz_ssi_stop(struct rz_ssi_priv *ssi, struct rz_ssi_stream *strm)
{ {
int timeout;
strm->running = 0; strm->running = 0;
if (rz_ssi_is_stream_running(&ssi->playback) ||
rz_ssi_is_stream_running(&ssi->capture))
return 0;
/* Disable TX/RX */ /* Disable TX/RX */
rz_ssi_reg_mask_setl(ssi, SSICR, SSICR_TEN | SSICR_REN, 0); rz_ssi_reg_mask_setl(ssi, SSICR, SSICR_TEN | SSICR_REN, 0);
...@@ -356,30 +420,7 @@ static int rz_ssi_stop(struct rz_ssi_priv *ssi, struct rz_ssi_stream *strm) ...@@ -356,30 +420,7 @@ static int rz_ssi_stop(struct rz_ssi_priv *ssi, struct rz_ssi_stream *strm)
if (rz_ssi_is_dma_enabled(ssi)) if (rz_ssi_is_dma_enabled(ssi))
dmaengine_terminate_async(strm->dma_ch); dmaengine_terminate_async(strm->dma_ch);
/* Disable irqs */ rz_ssi_set_idle(ssi);
rz_ssi_reg_mask_setl(ssi, SSICR, SSICR_TUIEN | SSICR_TOIEN |
SSICR_RUIEN | SSICR_ROIEN, 0);
rz_ssi_reg_mask_setl(ssi, SSIFCR, SSIFCR_TIE | SSIFCR_RIE, 0);
/* Clear all error flags */
rz_ssi_reg_mask_setl(ssi, SSISR,
(SSISR_TOIRQ | SSISR_TUIRQ | SSISR_ROIRQ |
SSISR_RUIRQ), 0);
/* Wait for idle */
timeout = 100;
while (--timeout) {
if (rz_ssi_reg_readl(ssi, SSISR) & SSISR_IIRQ)
break;
udelay(1);
}
if (!timeout)
dev_info(ssi->dev, "timeout waiting for SSI idle\n");
/* Hold FIFOs in reset */
rz_ssi_reg_mask_setl(ssi, SSIFCR, 0,
SSIFCR_TFRST | SSIFCR_RFRST);
return 0; return 0;
} }
...@@ -512,66 +553,90 @@ static int rz_ssi_pio_send(struct rz_ssi_priv *ssi, struct rz_ssi_stream *strm) ...@@ -512,66 +553,90 @@ static int rz_ssi_pio_send(struct rz_ssi_priv *ssi, struct rz_ssi_stream *strm)
static irqreturn_t rz_ssi_interrupt(int irq, void *data) static irqreturn_t rz_ssi_interrupt(int irq, void *data)
{ {
struct rz_ssi_stream *strm = NULL; struct rz_ssi_stream *strm_playback = NULL;
struct rz_ssi_stream *strm_capture = NULL;
struct rz_ssi_priv *ssi = data; struct rz_ssi_priv *ssi = data;
u32 ssisr = rz_ssi_reg_readl(ssi, SSISR); u32 ssisr = rz_ssi_reg_readl(ssi, SSISR);
if (ssi->playback.substream) if (ssi->playback.substream)
strm = &ssi->playback; strm_playback = &ssi->playback;
else if (ssi->capture.substream) if (ssi->capture.substream)
strm = &ssi->capture; strm_capture = &ssi->capture;
else
if (!strm_playback && !strm_capture)
return IRQ_HANDLED; /* Left over TX/RX interrupt */ return IRQ_HANDLED; /* Left over TX/RX interrupt */
if (irq == ssi->irq_int) { /* error or idle */ if (irq == ssi->irq_int) { /* error or idle */
if (ssisr & SSISR_TUIRQ) bool is_stopped = false;
strm->uerr_num++; int i, count;
if (ssisr & SSISR_TOIRQ)
strm->oerr_num++; if (rz_ssi_is_dma_enabled(ssi))
if (ssisr & SSISR_RUIRQ) count = 4;
strm->uerr_num++; else
if (ssisr & SSISR_ROIRQ) count = 1;
strm->oerr_num++;
if (ssisr & (SSISR_RUIRQ | SSISR_ROIRQ | SSISR_TUIRQ | SSISR_TOIRQ))
if (ssisr & (SSISR_TUIRQ | SSISR_TOIRQ | SSISR_RUIRQ | is_stopped = true;
SSISR_ROIRQ)) {
/* Error handling */ if (ssi->capture.substream && is_stopped) {
/* You must reset (stop/restart) after each interrupt */ if (ssisr & SSISR_RUIRQ)
rz_ssi_stop(ssi, strm); strm_capture->uerr_num++;
if (ssisr & SSISR_ROIRQ)
/* Clear all flags */ strm_capture->oerr_num++;
rz_ssi_reg_mask_setl(ssi, SSISR, SSISR_TOIRQ |
SSISR_TUIRQ | SSISR_ROIRQ | rz_ssi_stop(ssi, strm_capture);
SSISR_RUIRQ, 0);
/* Add/remove more data */
strm->transfer(ssi, strm);
/* Resume */
rz_ssi_start(ssi, strm);
} }
if (ssi->playback.substream && is_stopped) {
if (ssisr & SSISR_TUIRQ)
strm_playback->uerr_num++;
if (ssisr & SSISR_TOIRQ)
strm_playback->oerr_num++;
rz_ssi_stop(ssi, strm_playback);
}
/* Clear all flags */
rz_ssi_reg_mask_setl(ssi, SSISR, SSISR_TOIRQ | SSISR_TUIRQ |
SSISR_ROIRQ | SSISR_RUIRQ, 0);
/* Add/remove more data */
if (ssi->capture.substream && is_stopped) {
for (i = 0; i < count; i++)
strm_capture->transfer(ssi, strm_capture);
}
if (ssi->playback.substream && is_stopped) {
for (i = 0; i < count; i++)
strm_playback->transfer(ssi, strm_playback);
}
/* Resume */
if (ssi->playback.substream && is_stopped)
rz_ssi_start(ssi, &ssi->playback);
if (ssi->capture.substream && is_stopped)
rz_ssi_start(ssi, &ssi->capture);
} }
if (!strm->running) if (!rz_ssi_is_stream_running(&ssi->playback) &&
!rz_ssi_is_stream_running(&ssi->capture))
return IRQ_HANDLED; return IRQ_HANDLED;
/* tx data empty */ /* tx data empty */
if (irq == ssi->irq_tx) if (irq == ssi->irq_tx && rz_ssi_is_stream_running(&ssi->playback))
strm->transfer(ssi, &ssi->playback); strm_playback->transfer(ssi, &ssi->playback);
/* rx data full */ /* rx data full */
if (irq == ssi->irq_rx) { if (irq == ssi->irq_rx && rz_ssi_is_stream_running(&ssi->capture)) {
strm->transfer(ssi, &ssi->capture); strm_capture->transfer(ssi, &ssi->capture);
rz_ssi_reg_mask_setl(ssi, SSIFSR, SSIFSR_RDF, 0); rz_ssi_reg_mask_setl(ssi, SSIFSR, SSIFSR_RDF, 0);
} }
if (irq == ssi->irq_rt) { if (irq == ssi->irq_rt) {
struct snd_pcm_substream *substream = strm->substream; if (ssi->playback.substream) {
strm_playback->transfer(ssi, &ssi->playback);
if (rz_ssi_stream_is_play(ssi, substream)) {
strm->transfer(ssi, &ssi->playback);
} else { } else {
strm->transfer(ssi, &ssi->capture); strm_capture->transfer(ssi, &ssi->capture);
rz_ssi_reg_mask_setl(ssi, SSIFSR, SSIFSR_RDF, 0); rz_ssi_reg_mask_setl(ssi, SSIFSR, SSIFSR_RDF, 0);
} }
} }
...@@ -731,9 +796,12 @@ static int rz_ssi_dai_trigger(struct snd_pcm_substream *substream, int cmd, ...@@ -731,9 +796,12 @@ static int rz_ssi_dai_trigger(struct snd_pcm_substream *substream, int cmd,
switch (cmd) { switch (cmd) {
case SNDRV_PCM_TRIGGER_START: case SNDRV_PCM_TRIGGER_START:
/* Soft Reset */ /* Soft Reset */
rz_ssi_reg_mask_setl(ssi, SSIFCR, 0, SSIFCR_SSIRST); if (!rz_ssi_is_stream_running(&ssi->playback) &&
rz_ssi_reg_mask_setl(ssi, SSIFCR, SSIFCR_SSIRST, 0); !rz_ssi_is_stream_running(&ssi->capture)) {
udelay(5); rz_ssi_reg_mask_setl(ssi, SSIFCR, 0, SSIFCR_SSIRST);
rz_ssi_reg_mask_setl(ssi, SSIFCR, SSIFCR_SSIRST, 0);
udelay(5);
}
rz_ssi_stream_init(strm, substream); rz_ssi_stream_init(strm, substream);
...@@ -824,14 +892,41 @@ static int rz_ssi_dai_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) ...@@ -824,14 +892,41 @@ static int rz_ssi_dai_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
return 0; return 0;
} }
static bool rz_ssi_is_valid_hw_params(struct rz_ssi_priv *ssi, unsigned int rate,
unsigned int channels,
unsigned int sample_width,
unsigned int sample_bits)
{
if (ssi->hw_params_cache.rate != rate ||
ssi->hw_params_cache.channels != channels ||
ssi->hw_params_cache.sample_width != sample_width ||
ssi->hw_params_cache.sample_bits != sample_bits)
return false;
return true;
}
static void rz_ssi_cache_hw_params(struct rz_ssi_priv *ssi, unsigned int rate,
unsigned int channels,
unsigned int sample_width,
unsigned int sample_bits)
{
ssi->hw_params_cache.rate = rate;
ssi->hw_params_cache.channels = channels;
ssi->hw_params_cache.sample_width = sample_width;
ssi->hw_params_cache.sample_bits = sample_bits;
}
static int rz_ssi_dai_hw_params(struct snd_pcm_substream *substream, static int rz_ssi_dai_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params, struct snd_pcm_hw_params *params,
struct snd_soc_dai *dai) struct snd_soc_dai *dai)
{ {
struct rz_ssi_priv *ssi = snd_soc_dai_get_drvdata(dai); struct rz_ssi_priv *ssi = snd_soc_dai_get_drvdata(dai);
struct rz_ssi_stream *strm = rz_ssi_stream_get(ssi, substream);
unsigned int sample_bits = hw_param_interval(params, unsigned int sample_bits = hw_param_interval(params,
SNDRV_PCM_HW_PARAM_SAMPLE_BITS)->min; SNDRV_PCM_HW_PARAM_SAMPLE_BITS)->min;
unsigned int channels = params_channels(params); unsigned int channels = params_channels(params);
unsigned int rate = params_rate(params);
if (sample_bits != 16) { if (sample_bits != 16) {
dev_err(ssi->dev, "Unsupported sample width: %d\n", dev_err(ssi->dev, "Unsupported sample width: %d\n",
...@@ -845,8 +940,20 @@ static int rz_ssi_dai_hw_params(struct snd_pcm_substream *substream, ...@@ -845,8 +940,20 @@ static int rz_ssi_dai_hw_params(struct snd_pcm_substream *substream,
return -EINVAL; return -EINVAL;
} }
return rz_ssi_clk_setup(ssi, params_rate(params), if (rz_ssi_is_stream_running(&ssi->playback) ||
params_channels(params)); rz_ssi_is_stream_running(&ssi->capture)) {
if (rz_ssi_is_valid_hw_params(ssi, rate, channels,
strm->sample_width, sample_bits))
return 0;
dev_err(ssi->dev, "Full duplex needs same HW params\n");
return -EINVAL;
}
rz_ssi_cache_hw_params(ssi, rate, channels, strm->sample_width,
sample_bits);
return rz_ssi_clk_setup(ssi, rate, channels);
} }
static const struct snd_soc_dai_ops rz_ssi_dai_ops = { static const struct snd_soc_dai_ops rz_ssi_dai_ops = {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment