summaryrefslogtreecommitdiff
path: root/sound
diff options
context:
space:
mode:
authorShengjiu Wang <b02247@freescale.com>2014-04-14 15:19:54 +0800
committerNitin Garg <nitin.garg@freescale.com>2015-01-15 21:17:58 -0600
commit963e1bef81207531d9bac95d16607b603d750a91 (patch)
treedf981eb5bf746b7ddd046f2861e611676cea1fa8 /sound
parent43541a8742429f78368e8a430a43c8555dafe3b5 (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.c111
-rw-r--r--sound/soc/fsl/imx-pcm-dma.c42
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,
};