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

S.j. Wang shengjiu.wang at nxp.com
Fri May 17 13:09:22 AEST 2019


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 at 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;
 }
-- 
2.21.0



More information about the Linuxppc-dev mailing list