[PATCH] ASoC: fsl_esai: fix the channel swap issue after xrun

From: S.j. Wang
Date: Thu May 16 2019 - 23:11:40 EST


There is chip errata ERR008000, the reference doc is
(https://www.nxp.com/docs/en/errata/IMX6DQCE.pdf),

The issue is "While using ESAI transmit or receive and
an underrun/overrun happens, channel swap may occur.
The only recovery mechanism is to reset the ESAI."

In this commit add a tasklet to handle reset of ESAI
after xrun happens

Signed-off-by: Shengjiu Wang <shengjiu.wang@xxxxxxx>
---
sound/soc/fsl/fsl_esai.c | 166 +++++++++++++++++++++++++++++++++++++++
1 file changed, 166 insertions(+)

diff --git a/sound/soc/fsl/fsl_esai.c b/sound/soc/fsl/fsl_esai.c
index 10d2210c91ef..149972894c95 100644
--- a/sound/soc/fsl/fsl_esai.c
+++ b/sound/soc/fsl/fsl_esai.c
@@ -52,17 +52,20 @@ struct fsl_esai {
struct clk *extalclk;
struct clk *fsysclk;
struct clk *spbaclk;
+ struct tasklet_struct task;
u32 fifo_depth;
u32 slot_width;
u32 slots;
u32 tx_mask;
u32 rx_mask;
+ u32 tx_channels;
u32 hck_rate[2];
u32 sck_rate[2];
bool hck_dir[2];
bool sck_div[2];
bool slave_mode;
bool synchronous;
+ bool reset_at_xrun;
char name[32];
};

@@ -71,8 +74,14 @@ static irqreturn_t esai_isr(int irq, void *devid)
struct fsl_esai *esai_priv = (struct fsl_esai *)devid;
struct platform_device *pdev = esai_priv->pdev;
u32 esr;
+ u32 saisr;

regmap_read(esai_priv->regmap, REG_ESAI_ESR, &esr);
+ regmap_read(esai_priv->regmap, REG_ESAI_SAISR, &saisr);
+
+ if ((saisr & (ESAI_SAISR_TUE | ESAI_SAISR_ROE))
+ && esai_priv->reset_at_xrun)
+ tasklet_schedule(&esai_priv->task);

if (esr & ESAI_ESR_TINIT_MASK)
dev_dbg(&pdev->dev, "isr: Transmission Initialized\n");
@@ -552,6 +561,9 @@ static int fsl_esai_trigger(struct snd_pcm_substream *substream, int cmd,
u32 pins = DIV_ROUND_UP(channels, esai_priv->slots);
u32 mask;

+ if (tx)
+ esai_priv->tx_channels = channels;
+
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
case SNDRV_PCM_TRIGGER_RESUME:
@@ -585,10 +597,16 @@ static int fsl_esai_trigger(struct snd_pcm_substream *substream, int cmd,
regmap_update_bits(esai_priv->regmap, REG_ESAI_xSMA(tx),
ESAI_xSMA_xS_MASK, ESAI_xSMA_xS(mask));

+ regmap_update_bits(esai_priv->regmap, REG_ESAI_xCR(tx),
+ ESAI_xCR_xEIE_MASK, ESAI_xCR_xEIE);
+
break;
case SNDRV_PCM_TRIGGER_SUSPEND:
case SNDRV_PCM_TRIGGER_STOP:
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ regmap_update_bits(esai_priv->regmap, REG_ESAI_xCR(tx),
+ ESAI_xCR_xEIE_MASK, 0);
+
regmap_update_bits(esai_priv->regmap, REG_ESAI_xCR(tx),
tx ? ESAI_xCR_TE_MASK : ESAI_xCR_RE_MASK, 0);
regmap_update_bits(esai_priv->regmap, REG_ESAI_xSMA(tx),
@@ -618,6 +636,145 @@ static const struct snd_soc_dai_ops fsl_esai_dai_ops = {
.set_tdm_slot = fsl_esai_set_dai_tdm_slot,
};

+static void fsl_esai_reset(unsigned long arg)
+{
+ struct fsl_esai *esai_priv = (struct fsl_esai *)arg;
+ u32 saisr;
+ u32 tsma, tsmb, rsma, rsmb, tcr, rcr, tfcr, rfcr;
+ int i;
+
+ /*
+ * stop the tx & rx
+ */
+ regmap_read(esai_priv->regmap, REG_ESAI_TSMA, &tsma);
+ regmap_read(esai_priv->regmap, REG_ESAI_TSMB, &tsmb);
+ regmap_read(esai_priv->regmap, REG_ESAI_RSMA, &rsma);
+ regmap_read(esai_priv->regmap, REG_ESAI_RSMB, &rsmb);
+
+ regmap_read(esai_priv->regmap, REG_ESAI_TCR, &tcr);
+ regmap_read(esai_priv->regmap, REG_ESAI_RCR, &rcr);
+
+ regmap_read(esai_priv->regmap, REG_ESAI_TFCR, &tfcr);
+ regmap_read(esai_priv->regmap, REG_ESAI_RFCR, &rfcr);
+
+ regmap_update_bits(esai_priv->regmap, REG_ESAI_TCR,
+ ESAI_xCR_xEIE_MASK | ESAI_xCR_TE_MASK, 0);
+ regmap_update_bits(esai_priv->regmap, REG_ESAI_RCR,
+ ESAI_xCR_xEIE_MASK | ESAI_xCR_RE_MASK, 0);
+
+ regmap_update_bits(esai_priv->regmap, REG_ESAI_TSMA,
+ ESAI_xSMA_xS_MASK, 0);
+ regmap_update_bits(esai_priv->regmap, REG_ESAI_TSMB,
+ ESAI_xSMB_xS_MASK, 0);
+ regmap_update_bits(esai_priv->regmap, REG_ESAI_RSMA,
+ ESAI_xSMA_xS_MASK, 0);
+ regmap_update_bits(esai_priv->regmap, REG_ESAI_RSMB,
+ ESAI_xSMB_xS_MASK, 0);
+
+ regmap_update_bits(esai_priv->regmap, REG_ESAI_TFCR,
+ ESAI_xFCR_xFR | ESAI_xFCR_xFEN, ESAI_xFCR_xFR);
+ regmap_update_bits(esai_priv->regmap, REG_ESAI_TFCR,
+ ESAI_xFCR_xFR, 0);
+
+ regmap_update_bits(esai_priv->regmap, REG_ESAI_RFCR,
+ ESAI_xFCR_xFR | ESAI_xFCR_xFEN, ESAI_xFCR_xFR);
+ regmap_update_bits(esai_priv->regmap, REG_ESAI_RFCR,
+ ESAI_xFCR_xFR, 0);
+
+ /*
+ * reset the esai, and restore the registers
+ */
+ regmap_update_bits(esai_priv->regmap, REG_ESAI_ECR,
+ ESAI_ECR_ESAIEN_MASK | ESAI_ECR_ERST_MASK,
+ ESAI_ECR_ESAIEN | ESAI_ECR_ERST);
+ regmap_update_bits(esai_priv->regmap, REG_ESAI_ECR,
+ ESAI_ECR_ESAIEN_MASK | ESAI_ECR_ERST_MASK,
+ ESAI_ECR_ESAIEN);
+
+ regmap_update_bits(esai_priv->regmap, REG_ESAI_TCR,
+ ESAI_xCR_xPR_MASK,
+ ESAI_xCR_xPR);
+ regmap_update_bits(esai_priv->regmap, REG_ESAI_RCR,
+ ESAI_xCR_xPR_MASK,
+ ESAI_xCR_xPR);
+
+ regmap_update_bits(esai_priv->regmap, REG_ESAI_PRRC,
+ ESAI_PRRC_PDC_MASK, 0);
+ regmap_update_bits(esai_priv->regmap, REG_ESAI_PCRC,
+ ESAI_PCRC_PC_MASK, 0);
+
+ /*
+ * Add fifo reset here, because the regcache_sync will
+ * write one more data to ETDR.
+ * Which will cause channel shift.
+ */
+ regmap_update_bits(esai_priv->regmap, REG_ESAI_TFCR,
+ ESAI_xFCR_xFR_MASK, ESAI_xFCR_xFR);
+ regmap_update_bits(esai_priv->regmap, REG_ESAI_RFCR,
+ ESAI_xFCR_xFR_MASK, ESAI_xFCR_xFR);
+
+ regcache_mark_dirty(esai_priv->regmap);
+ regcache_sync(esai_priv->regmap);
+
+ regmap_update_bits(esai_priv->regmap, REG_ESAI_TFCR,
+ ESAI_xFCR_xFR_MASK, 0);
+ regmap_update_bits(esai_priv->regmap, REG_ESAI_RFCR,
+ ESAI_xFCR_xFR_MASK, 0);
+
+ regmap_update_bits(esai_priv->regmap, REG_ESAI_TCR,
+ ESAI_xCR_xPR_MASK, 0);
+ regmap_update_bits(esai_priv->regmap, REG_ESAI_RCR,
+ ESAI_xCR_xPR_MASK, 0);
+
+ regmap_update_bits(esai_priv->regmap, REG_ESAI_PRRC,
+ ESAI_PRRC_PDC_MASK,
+ ESAI_PRRC_PDC(ESAI_GPIO));
+ regmap_update_bits(esai_priv->regmap, REG_ESAI_PCRC,
+ ESAI_PCRC_PC_MASK,
+ ESAI_PCRC_PC(ESAI_GPIO));
+
+ regmap_read(esai_priv->regmap, REG_ESAI_SAISR, &saisr);
+
+ /*
+ * restart tx / rx, if they already enabled
+ */
+
+ regmap_update_bits(esai_priv->regmap, REG_ESAI_TFCR,
+ ESAI_xFCR_xFEN_MASK, tfcr & ESAI_xFCR_xFEN);
+
+ regmap_update_bits(esai_priv->regmap, REG_ESAI_RFCR,
+ ESAI_xFCR_xFEN_MASK, rfcr & ESAI_xFCR_xFEN);
+
+ /* Write initial words reqiured by ESAI as normal procedure */
+ for (i = 0; i < esai_priv->tx_channels; i++)
+ regmap_write(esai_priv->regmap, REG_ESAI_ETDR, 0x0);
+
+ regmap_update_bits(esai_priv->regmap, REG_ESAI_TCR,
+ ESAI_xCR_TE_MASK,
+ ESAI_xCR_TE_MASK & tcr);
+ regmap_update_bits(esai_priv->regmap, REG_ESAI_RCR,
+ ESAI_xCR_RE_MASK,
+ ESAI_xCR_RE_MASK & rcr);
+
+ regmap_update_bits(esai_priv->regmap, REG_ESAI_TSMB,
+ ESAI_xSMB_xS_MASK,
+ ESAI_xSMB_xS_MASK & tsmb);
+ regmap_update_bits(esai_priv->regmap, REG_ESAI_TSMA,
+ ESAI_xSMA_xS_MASK,
+ ESAI_xSMA_xS_MASK & tsma);
+ regmap_update_bits(esai_priv->regmap, REG_ESAI_RSMB,
+ ESAI_xSMB_xS_MASK,
+ ESAI_xSMB_xS_MASK & rsmb);
+ regmap_update_bits(esai_priv->regmap, REG_ESAI_RSMA,
+ ESAI_xSMA_xS_MASK,
+ ESAI_xSMA_xS_MASK & rsma);
+
+ regmap_update_bits(esai_priv->regmap, REG_ESAI_TCR,
+ ESAI_xCR_xEIE_MASK, ESAI_xCR_xEIE & tcr);
+ regmap_update_bits(esai_priv->regmap, REG_ESAI_RCR,
+ ESAI_xCR_xEIE_MASK, ESAI_xCR_xEIE & rcr);
+}
+
static int fsl_esai_dai_probe(struct snd_soc_dai *dai)
{
struct fsl_esai *esai_priv = snd_soc_dai_get_drvdata(dai);
@@ -787,6 +944,10 @@ static int fsl_esai_probe(struct platform_device *pdev)
esai_priv->pdev = pdev;
snprintf(esai_priv->name, sizeof(esai_priv->name), "%pOFn", np);

+ if (of_device_is_compatible(np, "fsl,vf610-esai") ||
+ of_device_is_compatible(np, "fsl,imx35-esai"))
+ esai_priv->reset_at_xrun = true;
+
/* Get the addresses and IRQ */
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
regs = devm_ioremap_resource(&pdev->dev, res);
@@ -899,6 +1060,8 @@ static int fsl_esai_probe(struct platform_device *pdev)
return ret;
}

+ tasklet_init(&esai_priv->task, fsl_esai_reset, (unsigned long)esai_priv);
+
pm_runtime_enable(&pdev->dev);

regcache_cache_only(esai_priv->regmap, true);
@@ -912,7 +1075,10 @@ static int fsl_esai_probe(struct platform_device *pdev)

static int fsl_esai_remove(struct platform_device *pdev)
{
+ struct fsl_esai *esai_priv = platform_get_drvdata(pdev);
+
pm_runtime_disable(&pdev->dev);
+ tasklet_kill(&esai_priv->task);

return 0;
}
--
2.21.0