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@nxp.com --- 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; }