diff options
author | Shengjiu Wang <b02247@freescale.com> | 2014-04-14 15:19:54 +0800 |
---|---|---|
committer | Nitin Garg <nitin.garg@freescale.com> | 2015-01-15 21:17:58 -0600 |
commit | 963e1bef81207531d9bac95d16607b603d750a91 (patch) | |
tree | df981eb5bf746b7ddd046f2861e611676cea1fa8 /sound | |
parent | 43541a8742429f78368e8a430a43c8555dafe3b5 (diff) |
ENGR00307835-3 ASoC: fsl: implement the ESAI xrun handler.
When esai xrun happened, there is possibility of channel swap. So ESAI
need to be reset.
Signed-off-by: Shengjiu Wang <b02247@freescale.com>
(cherry picked from commit 440fbeee5bba4d4356a392ff35fe262774c48ea1)
Diffstat (limited to 'sound')
-rw-r--r-- | sound/soc/fsl/fsl_esai.c | 111 | ||||
-rw-r--r-- | sound/soc/fsl/imx-pcm-dma.c | 42 |
2 files changed, 152 insertions, 1 deletions
diff --git a/sound/soc/fsl/fsl_esai.c b/sound/soc/fsl/fsl_esai.c index 878a24de6e8a..cf193c08af15 100644 --- a/sound/soc/fsl/fsl_esai.c +++ b/sound/soc/fsl/fsl_esai.c @@ -49,6 +49,7 @@ struct fsl_esai { struct snd_dmaengine_dai_dma_data dma_params_rx; struct snd_dmaengine_dai_dma_data dma_params_tx; + struct snd_pcm_substream *substream[2]; struct platform_device *pdev; struct regmap *regmap; struct clk *coreclk; @@ -497,6 +498,8 @@ static int fsl_esai_startup(struct snd_pcm_substream *substream, ESAI_xCCR_xDC_MASK, ESAI_xCCR_xDC(2)); } + esai_priv->substream[substream->stream] = substream; + return 0; err_fsysclk: @@ -560,6 +563,8 @@ static void fsl_esai_shutdown(struct snd_pcm_substream *substream, { struct fsl_esai *esai_priv = snd_soc_dai_get_drvdata(dai); + esai_priv->substream[substream->stream] = NULL; + if (!IS_ERR(esai_priv->fsysclk)) clk_disable_unprepare(esai_priv->fsysclk); if (!IS_ERR(esai_priv->extalclk)) @@ -726,6 +731,107 @@ static struct regmap_config fsl_esai_regmap_config = { .writeable_reg = fsl_esai_writeable_reg, }; +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_priv = snd_soc_dai_get_drvdata(cpu_dai); + u32 saisr; + + regmap_read(esai_priv->regmap, REG_ESAI_SAISR, &saisr); + + return saisr & (ESAI_SAISR_TUE | ESAI_SAISR_ROE) ; +} + +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_priv = snd_soc_dai_get_drvdata(cpu_dai); + u32 saisr; + + if (stop) { + stop_lock_stream(esai_priv->substream[0]); + stop_lock_stream(esai_priv->substream[1]); + } + + + 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); + + if (stop) { + start_unlock_stream(esai_priv->substream[1]); + start_unlock_stream(esai_priv->substream[0]); + } +} + static int fsl_esai_probe(struct platform_device *pdev) { struct device_node *np = pdev->dev.of_node; @@ -817,6 +923,11 @@ static int fsl_esai_probe(struct platform_device *pdev) esai_priv->dma_params_tx.addr = res->start + REG_ESAI_ETDR; esai_priv->dma_params_rx.addr = res->start + REG_ESAI_ERDR; + esai_priv->dma_params_tx.check_xrun = fsl_esai_check_xrun; + esai_priv->dma_params_rx.check_xrun = fsl_esai_check_xrun; + esai_priv->dma_params_tx.device_reset = fsl_esai_reset; + esai_priv->dma_params_rx.device_reset = fsl_esai_reset; + esai_priv->synchronous = of_property_read_bool(np, "fsl,esai-synchronous"); diff --git a/sound/soc/fsl/imx-pcm-dma.c b/sound/soc/fsl/imx-pcm-dma.c index 1fc01ed3279d..cb1eba73342a 100644 --- a/sound/soc/fsl/imx-pcm-dma.c +++ b/sound/soc/fsl/imx-pcm-dma.c @@ -48,9 +48,49 @@ static const struct snd_pcm_hardware imx_pcm_hardware = { .fifo_size = 0, }; +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) + return ret; + + snd_dmaengine_pcm_set_config_from_dai_data(substream, dma_data, + slave_config); + + return 0; + +} + static const struct snd_dmaengine_pcm_config imx_dmaengine_pcm_config = { .pcm_hardware = &imx_pcm_hardware, - .prepare_slave_config = snd_dmaengine_pcm_prepare_slave_config, + .prepare_slave_config = imx_pcm_dma_prepare_slave_config, .compat_filter_fn = filter, .prealloc_buffer_size = IMX_DEFAULT_DMABUF_SIZE, }; |