dai: correct the order for DAI and DMA start/stop

To stop/suspend an active DMA channel:
1. Stop the DMA service request at the peripheral first (stop the DAI);
2. Disable the hardware service request on the appropriate DMA channel.

For start/resume:
1. Enable the DMA service request on the appropriate channel;
2. Enable the DMA service request at the peripheral (enable DAI).

When the start/stop order for DMA and DAI is different, on multiple
start/stop runs for playback or record or combined, we get an
underrun/overflow.
That's because the DAI makes a DMA request, before the DMA channel is
enabled.

Some platforms cannot just simple disable DMA channel during
the transfer, because it will hang the whole DMA controller.
Therefore, for DMA_SUSPEND_DRAIN, stop the DMA first
and let the DAI drain the FIFO in order to stop the channel
as soon as possible.

Fixes: #3809

Signed-off-by: Iuliana Prodan <iuliana.prodan@nxp.com>
This commit is contained in:
Iuliana Prodan 2021-02-16 12:38:40 +02:00 committed by Daniel Baluta
parent fa35ee00f2
commit 1fa1001e24
1 changed files with 15 additions and 2 deletions

View File

@ -651,11 +651,11 @@ static int dai_comp_trigger_internal(struct comp_dev *dev, int cmd)
/* only start the DAI if we are not XRUN handling */
if (dd->xrun == 0) {
/* start the DAI */
dai_trigger(dd->dai, cmd, dev->direction);
ret = dma_start(dd->chan);
if (ret < 0)
return ret;
/* start the DAI */
dai_trigger(dd->dai, cmd, dev->direction);
} else {
dd->xrun = 0;
}
@ -695,8 +695,21 @@ static int dai_comp_trigger_internal(struct comp_dev *dev, int cmd)
COMPILER_FALLTHROUGH;
case COMP_TRIGGER_STOP:
comp_dbg(dev, "dai_comp_trigger_internal(), STOP");
/*
* Some platforms cannot just simple disable
* DMA channel during the transfer,
* because it will hang the whole DMA controller.
* Therefore, stop the DMA first and let the DAI
* drain the FIFO in order to stop the channel
* as soon as possible.
*/
#if CONFIG_DMA_SUSPEND_DRAIN
ret = dma_stop(dd->chan);
dai_trigger(dd->dai, cmd, dev->direction);
#else
dai_trigger(dd->dai, cmd, dev->direction);
ret = dma_stop(dd->chan);
#endif
break;
case COMP_TRIGGER_PAUSE:
comp_dbg(dev, "dai_comp_trigger_internal(), PAUSE");