ENGR00307835-3 ASoC: fsl: implement the ESAI xrun handler.
authorShengjiu Wang <b02247@freescale.com>
Mon, 14 Apr 2014 07:19:54 +0000 (15:19 +0800)
committerNitin Garg <nitin.garg@freescale.com>
Wed, 16 Apr 2014 13:58:20 +0000 (08:58 -0500)
When esai xrun happened, there is possibility of channel swap. So ESAI
need to be reset.

Signed-off-by: Shengjiu Wang <b02247@freescale.com>
sound/soc/fsl/fsl_esai.c
sound/soc/fsl/fsl_esai.h
sound/soc/fsl/imx-pcm-dma.c

index 6f6adcec7d41397a17585cc67480113ed12aac2a..762ac316fa32281f869a63a62bd73732519e4f18 100644 (file)
 #define IMX_ESAI_NET            (1 << 0)
 #define IMX_ESAI_SYN            (1 << 1)
 
+static inline void write_esai_mask(u32 __iomem *addr, u32 clear, u32 set)
+{
+       u32 val = readl(addr);
+       val = (val & ~clear) | set;
+       writel(val, addr);
+}
+
 static int fsl_esai_set_dai_sysclk(struct snd_soc_dai *cpu_dai,
                                   int clk_id, unsigned int freq, int dir)
 {
@@ -308,6 +315,8 @@ static int fsl_esai_startup(struct snd_pcm_substream *substream,
        clk_enable(esai->clk);
        clk_prepare_enable(esai->dmaclk);
 
+       esai->substream[substream->stream] = substream;
+
        ESAI_DUMP();
        return 0;
 }
@@ -463,6 +472,8 @@ static void fsl_esai_shutdown(struct snd_pcm_substream *substream,
                writel(0, esai->base + ESAI_PCRC);
        }
 
+       esai->substream[substream->stream] = NULL;
+
        clk_disable_unprepare(esai->dmaclk);
        clk_disable(esai->clk);
 }
@@ -578,6 +589,126 @@ static const struct snd_soc_component_driver fsl_esai_component = {
        .name           = "fsl-esai",
 };
 
+static bool fsl_esai_check_xrun(struct snd_pcm_substream *substream)
+{
+       struct snd_soc_pcm_runtime *rtd = substream->private_data;
+       struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+       struct fsl_esai *esai = snd_soc_dai_get_drvdata(cpu_dai);
+       u32 saisr = readl(esai->base + ESAI_SAISR);
+
+       return saisr & (ESAI_SAISR_TUE | ESAI_SAISR_ROE) ;
+}
+
+static int store_reg(struct snd_soc_dai *cpu_dai)
+{
+       struct fsl_esai *esai = snd_soc_dai_get_drvdata(cpu_dai);
+
+       esai->reg_cache[0] = readl(esai->base + ESAI_ECR);
+       esai->reg_cache[2] = readl(esai->base + ESAI_TFCR);
+       esai->reg_cache[4] = readl(esai->base + ESAI_RFCR);
+       esai->reg_cache[8] = readl(esai->base + ESAI_SAICR);
+       esai->reg_cache[9] = readl(esai->base + ESAI_TCR);
+       esai->reg_cache[10] = readl(esai->base + ESAI_TCCR);
+       esai->reg_cache[11] = readl(esai->base + ESAI_RCR);
+       esai->reg_cache[12] = readl(esai->base + ESAI_RCCR);
+       esai->reg_cache[13] = readl(esai->base + ESAI_TSMA);
+       esai->reg_cache[14] = readl(esai->base + ESAI_TSMB);
+       esai->reg_cache[15] = readl(esai->base + ESAI_RSMA);
+       esai->reg_cache[16] = readl(esai->base + ESAI_RSMB);
+       return 0;
+}
+
+static int restore_reg(struct snd_soc_dai *cpu_dai)
+{
+       struct fsl_esai *esai = snd_soc_dai_get_drvdata(cpu_dai);
+
+       writel(esai->reg_cache[0], esai->base + ESAI_ECR);
+       writel(esai->reg_cache[2] & ~ESAI_TFCR_TFEN, esai->base + ESAI_TFCR);
+       writel(esai->reg_cache[4] & ~ESAI_RFCR_RFEN, esai->base + ESAI_RFCR);
+       writel(esai->reg_cache[8], esai->base + ESAI_SAICR);
+       writel(esai->reg_cache[9] & ~ESAI_TCR_TE(12), esai->base + ESAI_TCR);
+       writel(esai->reg_cache[10], esai->base + ESAI_TCCR);
+       writel(esai->reg_cache[11] & ~ESAI_RCR_RE(8), esai->base + ESAI_RCR);
+       writel(esai->reg_cache[12], esai->base + ESAI_RCCR);
+       writel(esai->reg_cache[13], esai->base + ESAI_TSMA);
+       writel(esai->reg_cache[14], esai->base + ESAI_TSMB);
+       writel(esai->reg_cache[15], esai->base + ESAI_RSMA);
+       writel(esai->reg_cache[16], esai->base + ESAI_RSMB);
+       return 0;
+}
+
+static int stop_lock_stream(struct snd_pcm_substream *substream)
+{
+       if (substream) {
+               snd_pcm_stream_lock_irq(substream);
+               if (substream->runtime->status->state == SNDRV_PCM_STATE_RUNNING)
+                       substream->ops->trigger(substream, SNDRV_PCM_TRIGGER_STOP);
+       }
+       return 0;
+}
+
+static int start_unlock_stream(struct snd_pcm_substream *substream)
+{
+       if (substream) {
+               if (substream->runtime->status->state == SNDRV_PCM_STATE_RUNNING)
+                       substream->ops->trigger(substream, SNDRV_PCM_TRIGGER_START);
+               snd_pcm_stream_unlock_irq(substream);
+       }
+       return 0;
+}
+
+/*
+ *Here is ESAI underrun reset step:
+ *1. Read "TUE" and got TUE=1
+ *2. stop DMA.
+ *3. stop ESAI TX section.
+ *4. Set the transmitter section individual reset "TPR=1"
+ *5. Reset the ESAI Transmit FIFO (set ESAI_TFCR[1]=1).
+ *6. Config the control registers ESAI_TCCR and ESAI_TCR.config the Transmit FIFO register.
+ *7. clear "TPR"
+ *8. read "TUE"
+ *9. Prefill ESAI TX FIFO.
+ *10.Start DMA.
+ *11 Enable the ESAI
+ */
+static void fsl_esai_reset(struct snd_pcm_substream *substream, bool stop)
+{
+       struct snd_soc_pcm_runtime *rtd = substream->private_data;
+       struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+       struct fsl_esai *esai = snd_soc_dai_get_drvdata(cpu_dai);
+       ESAI_DUMP();
+
+       if (stop) {
+               stop_lock_stream(esai->substream[0]);
+               stop_lock_stream(esai->substream[1]);
+       }
+
+       store_reg(cpu_dai);
+
+       writel(ESAI_ECR_ESAIEN | ESAI_ECR_ERST, esai->base + ESAI_ECR);
+       writel(ESAI_ECR_ESAIEN, esai->base + ESAI_ECR);
+
+       write_esai_mask(esai->base+ESAI_TCR, 0, ESAI_TCR_TPR);
+       write_esai_mask(esai->base+ESAI_RCR, 0, ESAI_RCR_RPR);
+
+       restore_reg(cpu_dai);
+
+       write_esai_mask(esai->base+ESAI_TCR, ESAI_TCR_TPR, 0);
+       write_esai_mask(esai->base+ESAI_RCR, ESAI_RCR_RPR, 0);
+
+       writel(ESAI_GPIO_ESAI, esai->base + ESAI_PRRC);
+       writel(ESAI_GPIO_ESAI, esai->base + ESAI_PCRC);
+
+       /* read "TUE" flag.*/
+       readl(esai->base + ESAI_SAISR);
+
+       if (stop) {
+               start_unlock_stream(esai->substream[1]);
+               start_unlock_stream(esai->substream[0]);
+       }
+       ESAI_DUMP();
+}
+
 static int fsl_esai_reg_init(struct fsl_esai *esai)
 {
        u32 xccr, slots = 2;
@@ -674,6 +805,11 @@ static int fsl_esai_probe(struct platform_device *pdev)
        esai->dma_params_tx.filter_data = &esai->filter_data_tx;
        esai->dma_params_rx.filter_data = &esai->filter_data_rx;
 
+       esai->dma_params_tx.check_xrun = fsl_esai_check_xrun;
+       esai->dma_params_rx.check_xrun = fsl_esai_check_xrun;
+       esai->dma_params_tx.device_reset = fsl_esai_reset;
+       esai->dma_params_rx.device_reset = fsl_esai_reset;
+
        ret = of_property_read_u32_array(pdev->dev.of_node,
                                "fsl,esai-dma-events", dma_events, 2);
        if (ret) {
index f0b6198a69a62fe3b8c7b2872a05c3e2b5ec9127..daf08064127717b1c7b5ef11dc9f40bfddf75baf 100644 (file)
@@ -1,7 +1,7 @@
 /*
  * imx-esai.h  --  ESAI driver header file for Freescale IMX
  *
- * Copyright 2008-2013 Freescale Semiconductor, Inc. All Rights Reserved.
+ * Copyright 2008-2014 Freescale Semiconductor, Inc. All Rights Reserved.
  */
 
 /*
 #define IMX_DAI_ESAI_TX 0x04
 #define IMX_DAI_ESAI_RX 0x08
 #define IMX_DAI_ESAI_TXRX (IMX_DAI_ESAI_TX | IMX_DAI_ESAI_RX)
+#define REG_CACHE_NUM  20
 
 struct fsl_esai {
        struct clk *clk;
@@ -328,8 +329,10 @@ struct fsl_esai {
        struct snd_dmaengine_dai_dma_data dma_params_tx;
        struct imx_dma_data filter_data_tx;
        struct imx_dma_data filter_data_rx;
+       struct snd_pcm_substream *substream[2];
 
        char name[32];
+       u32 reg_cache[REG_CACHE_NUM];
 };
 
 #endif
index 33752da05482084003f7e59480b948a15f642a3f..aa74383daa829f8d881ed8317140b59df418b42f 100644 (file)
@@ -94,14 +94,34 @@ static void imx_pcm_dma_set_config_from_dai_data(
        }
 }
 
+static void imx_pcm_dma_complete(void *arg)
+{
+       struct snd_pcm_substream *substream = arg;
+       struct snd_soc_pcm_runtime *rtd = substream->private_data;
+       struct dmaengine_pcm_runtime_data *prtd = substream->runtime->private_data;
+       struct snd_dmaengine_dai_dma_data *dma_data;
+
+       prtd->pos += snd_pcm_lib_period_bytes(substream);
+       if (prtd->pos >= snd_pcm_lib_buffer_bytes(substream))
+               prtd->pos = 0;
+
+       snd_pcm_period_elapsed(substream);
+
+       dma_data = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream);
+       if (dma_data->check_xrun && dma_data->check_xrun(substream))
+               dma_data->device_reset(substream, 1);
+}
+
 static int imx_pcm_dma_prepare_slave_config(struct snd_pcm_substream *substream,
        struct snd_pcm_hw_params *params, struct dma_slave_config *slave_config)
 {
        struct snd_soc_pcm_runtime *rtd = substream->private_data;
        struct snd_dmaengine_dai_dma_data *dma_data;
+       struct dmaengine_pcm_runtime_data *prtd = substream->runtime->private_data;
        int ret;
 
        dma_data = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream);
+       prtd->callback = imx_pcm_dma_complete;
 
        ret = snd_hwparams_to_dma_slave_config(substream, params, slave_config);
        if (ret)