mirror of
https://github.com/torvalds/linux.git
synced 2024-11-10 14:11:52 +00:00
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: Biju Das <biju.das.jz@bp.renesas.com> Link: https://patch.msgid.link/20240715092322.119879-1-biju.das.jz@bp.renesas.com Signed-off-by: Mark Brown <broonie@kernel.org>
This commit is contained in:
parent
b3f35bae68
commit
4f8cd05a43
@ -52,6 +52,7 @@
|
||||
#define SSIFCR_RIE BIT(2)
|
||||
#define SSIFCR_TFRST BIT(1)
|
||||
#define SSIFCR_RFRST BIT(0)
|
||||
#define SSIFCR_FIFO_RST (SSIFCR_TFRST | SSIFCR_RFRST)
|
||||
|
||||
#define SSIFSR_TDC_MASK 0x3f
|
||||
#define SSIFSR_TDC_SHIFT 24
|
||||
@ -130,6 +131,14 @@ struct rz_ssi_priv {
|
||||
bool lrckp_fsync_fall; /* LR clock polarity (SSICR.LRCKP) */
|
||||
bool bckp_rise; /* Bit clock polarity (SSICR.BCKP) */
|
||||
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);
|
||||
@ -208,6 +217,11 @@ static bool rz_ssi_stream_is_valid(struct rz_ssi_priv *ssi,
|
||||
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,
|
||||
struct snd_pcm_substream *substream)
|
||||
{
|
||||
@ -303,59 +317,10 @@ static int rz_ssi_clk_setup(struct rz_ssi_priv *ssi, unsigned int rate,
|
||||
return 0;
|
||||
}
|
||||
|
||||
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);
|
||||
u32 ssicr, ssifcr;
|
||||
|
||||
ssicr = rz_ssi_reg_readl(ssi, SSICR);
|
||||
ssifcr = rz_ssi_reg_readl(ssi, SSIFCR) & ~0xF;
|
||||
|
||||
/* FIFO interrupt thresholds */
|
||||
if (rz_ssi_is_dma_enabled(ssi))
|
||||
rz_ssi_reg_writel(ssi, SSISCR, 0);
|
||||
else
|
||||
rz_ssi_reg_writel(ssi, SSISCR,
|
||||
SSISCR_TDES(strm->fifo_sample_size / 2 - 1) |
|
||||
SSISCR_RDFS(0));
|
||||
|
||||
/* enable IRQ */
|
||||
if (is_play) {
|
||||
ssicr |= SSICR_TUIEN | SSICR_TOIEN;
|
||||
ssifcr |= SSIFCR_TIE | SSIFCR_RFRST;
|
||||
} else {
|
||||
ssicr |= SSICR_RUIEN | SSICR_ROIEN;
|
||||
ssifcr |= SSIFCR_RIE | SSIFCR_TFRST;
|
||||
}
|
||||
|
||||
rz_ssi_reg_writel(ssi, SSICR, ssicr);
|
||||
rz_ssi_reg_writel(ssi, SSIFCR, ssifcr);
|
||||
|
||||
/* Clear all error flags */
|
||||
rz_ssi_reg_mask_setl(ssi, SSISR,
|
||||
(SSISR_TOIRQ | SSISR_TUIRQ | SSISR_ROIRQ |
|
||||
SSISR_RUIRQ), 0);
|
||||
|
||||
strm->running = 1;
|
||||
ssicr |= is_play ? SSICR_TEN : SSICR_REN;
|
||||
rz_ssi_reg_writel(ssi, SSICR, ssicr);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rz_ssi_stop(struct rz_ssi_priv *ssi, struct rz_ssi_stream *strm)
|
||||
static void rz_ssi_set_idle(struct rz_ssi_priv *ssi)
|
||||
{
|
||||
int timeout;
|
||||
|
||||
strm->running = 0;
|
||||
|
||||
/* Disable TX/RX */
|
||||
rz_ssi_reg_mask_setl(ssi, SSICR, SSICR_TEN | SSICR_REN, 0);
|
||||
|
||||
/* Cancel all remaining DMA transactions */
|
||||
if (rz_ssi_is_dma_enabled(ssi))
|
||||
dmaengine_terminate_async(strm->dma_ch);
|
||||
|
||||
/* Disable irqs */
|
||||
rz_ssi_reg_mask_setl(ssi, SSICR, SSICR_TUIEN | SSICR_TOIEN |
|
||||
SSICR_RUIEN | SSICR_ROIEN, 0);
|
||||
@ -380,6 +345,82 @@ static int rz_ssi_stop(struct rz_ssi_priv *ssi, struct rz_ssi_stream *strm)
|
||||
/* 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)
|
||||
{
|
||||
bool is_play = rz_ssi_stream_is_play(ssi, strm->substream);
|
||||
bool is_full_duplex;
|
||||
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);
|
||||
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 */
|
||||
if (rz_ssi_is_dma_enabled(ssi))
|
||||
rz_ssi_reg_writel(ssi, SSISCR, 0);
|
||||
else
|
||||
rz_ssi_reg_writel(ssi, SSISCR,
|
||||
SSISCR_TDES(strm->fifo_sample_size / 2 - 1) |
|
||||
SSISCR_RDFS(0));
|
||||
|
||||
/* enable IRQ */
|
||||
if (is_play) {
|
||||
ssicr |= SSICR_TUIEN | SSICR_TOIEN;
|
||||
ssifcr |= SSIFCR_TIE;
|
||||
if (!is_full_duplex)
|
||||
ssifcr |= SSIFCR_RFRST;
|
||||
} else {
|
||||
ssicr |= SSICR_RUIEN | SSICR_ROIEN;
|
||||
ssifcr |= SSIFCR_RIE;
|
||||
if (!is_full_duplex)
|
||||
ssifcr |= SSIFCR_TFRST;
|
||||
}
|
||||
|
||||
rz_ssi_reg_writel(ssi, SSICR, ssicr);
|
||||
rz_ssi_reg_writel(ssi, SSIFCR, ssifcr);
|
||||
|
||||
/* Clear all error flags */
|
||||
rz_ssi_reg_mask_setl(ssi, SSISR,
|
||||
(SSISR_TOIRQ | SSISR_TUIRQ | SSISR_ROIRQ |
|
||||
SSISR_RUIRQ), 0);
|
||||
|
||||
strm->running = 1;
|
||||
if (is_full_duplex)
|
||||
ssicr |= SSICR_TEN | SSICR_REN;
|
||||
else
|
||||
ssicr |= is_play ? SSICR_TEN : SSICR_REN;
|
||||
|
||||
rz_ssi_reg_writel(ssi, SSICR, ssicr);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rz_ssi_stop(struct rz_ssi_priv *ssi, struct rz_ssi_stream *strm)
|
||||
{
|
||||
strm->running = 0;
|
||||
|
||||
if (rz_ssi_is_stream_running(&ssi->playback) ||
|
||||
rz_ssi_is_stream_running(&ssi->capture))
|
||||
return 0;
|
||||
|
||||
/* Disable TX/RX */
|
||||
rz_ssi_reg_mask_setl(ssi, SSICR, SSICR_TEN | SSICR_REN, 0);
|
||||
|
||||
/* Cancel all remaining DMA transactions */
|
||||
if (rz_ssi_is_dma_enabled(ssi))
|
||||
dmaengine_terminate_async(strm->dma_ch);
|
||||
|
||||
rz_ssi_set_idle(ssi);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -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)
|
||||
{
|
||||
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;
|
||||
u32 ssisr = rz_ssi_reg_readl(ssi, SSISR);
|
||||
|
||||
if (ssi->playback.substream)
|
||||
strm = &ssi->playback;
|
||||
else if (ssi->capture.substream)
|
||||
strm = &ssi->capture;
|
||||
else
|
||||
strm_playback = &ssi->playback;
|
||||
if (ssi->capture.substream)
|
||||
strm_capture = &ssi->capture;
|
||||
|
||||
if (!strm_playback && !strm_capture)
|
||||
return IRQ_HANDLED; /* Left over TX/RX interrupt */
|
||||
|
||||
if (irq == ssi->irq_int) { /* error or idle */
|
||||
if (ssisr & SSISR_TUIRQ)
|
||||
strm->uerr_num++;
|
||||
if (ssisr & SSISR_TOIRQ)
|
||||
strm->oerr_num++;
|
||||
if (ssisr & SSISR_RUIRQ)
|
||||
strm->uerr_num++;
|
||||
if (ssisr & SSISR_ROIRQ)
|
||||
strm->oerr_num++;
|
||||
bool is_stopped = false;
|
||||
int i, count;
|
||||
|
||||
if (ssisr & (SSISR_TUIRQ | SSISR_TOIRQ | SSISR_RUIRQ |
|
||||
SSISR_ROIRQ)) {
|
||||
/* Error handling */
|
||||
/* You must reset (stop/restart) after each interrupt */
|
||||
rz_ssi_stop(ssi, strm);
|
||||
if (rz_ssi_is_dma_enabled(ssi))
|
||||
count = 4;
|
||||
else
|
||||
count = 1;
|
||||
|
||||
/* Clear all flags */
|
||||
rz_ssi_reg_mask_setl(ssi, SSISR, SSISR_TOIRQ |
|
||||
SSISR_TUIRQ | SSISR_ROIRQ |
|
||||
SSISR_RUIRQ, 0);
|
||||
if (ssisr & (SSISR_RUIRQ | SSISR_ROIRQ | SSISR_TUIRQ | SSISR_TOIRQ))
|
||||
is_stopped = true;
|
||||
|
||||
/* Add/remove more data */
|
||||
strm->transfer(ssi, strm);
|
||||
if (ssi->capture.substream && is_stopped) {
|
||||
if (ssisr & SSISR_RUIRQ)
|
||||
strm_capture->uerr_num++;
|
||||
if (ssisr & SSISR_ROIRQ)
|
||||
strm_capture->oerr_num++;
|
||||
|
||||
/* Resume */
|
||||
rz_ssi_start(ssi, strm);
|
||||
rz_ssi_stop(ssi, strm_capture);
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
/* tx data empty */
|
||||
if (irq == ssi->irq_tx)
|
||||
strm->transfer(ssi, &ssi->playback);
|
||||
if (irq == ssi->irq_tx && rz_ssi_is_stream_running(&ssi->playback))
|
||||
strm_playback->transfer(ssi, &ssi->playback);
|
||||
|
||||
/* rx data full */
|
||||
if (irq == ssi->irq_rx) {
|
||||
strm->transfer(ssi, &ssi->capture);
|
||||
if (irq == ssi->irq_rx && rz_ssi_is_stream_running(&ssi->capture)) {
|
||||
strm_capture->transfer(ssi, &ssi->capture);
|
||||
rz_ssi_reg_mask_setl(ssi, SSIFSR, SSIFSR_RDF, 0);
|
||||
}
|
||||
|
||||
if (irq == ssi->irq_rt) {
|
||||
struct snd_pcm_substream *substream = strm->substream;
|
||||
|
||||
if (rz_ssi_stream_is_play(ssi, substream)) {
|
||||
strm->transfer(ssi, &ssi->playback);
|
||||
if (ssi->playback.substream) {
|
||||
strm_playback->transfer(ssi, &ssi->playback);
|
||||
} else {
|
||||
strm->transfer(ssi, &ssi->capture);
|
||||
strm_capture->transfer(ssi, &ssi->capture);
|
||||
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,
|
||||
switch (cmd) {
|
||||
case SNDRV_PCM_TRIGGER_START:
|
||||
/* Soft Reset */
|
||||
rz_ssi_reg_mask_setl(ssi, SSIFCR, 0, SSIFCR_SSIRST);
|
||||
rz_ssi_reg_mask_setl(ssi, SSIFCR, SSIFCR_SSIRST, 0);
|
||||
udelay(5);
|
||||
if (!rz_ssi_is_stream_running(&ssi->playback) &&
|
||||
!rz_ssi_is_stream_running(&ssi->capture)) {
|
||||
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);
|
||||
|
||||
@ -824,14 +892,41 @@ static int rz_ssi_dai_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
|
||||
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,
|
||||
struct snd_pcm_hw_params *params,
|
||||
struct snd_soc_dai *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,
|
||||
SNDRV_PCM_HW_PARAM_SAMPLE_BITS)->min;
|
||||
unsigned int channels = params_channels(params);
|
||||
unsigned int rate = params_rate(params);
|
||||
|
||||
if (sample_bits != 16) {
|
||||
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,
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return rz_ssi_clk_setup(ssi, params_rate(params),
|
||||
params_channels(params));
|
||||
if (rz_ssi_is_stream_running(&ssi->playback) ||
|
||||
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 = {
|
||||
|
Loading…
Reference in New Issue
Block a user