OSDN Git Service

ASoC: SOF: reset DMA state in prepare
authorKai Vehmanen <kai.vehmanen@linux.intel.com>
Mon, 22 Jul 2019 14:13:43 +0000 (09:13 -0500)
committerMark Brown <broonie@kernel.org>
Tue, 23 Jul 2019 11:18:18 +0000 (12:18 +0100)
When application goes through SUSPEND/STOP->PREPARE->START
cycle, we should always reprogram the SOF device to start
DMA from a known state so that hw_ptr/appl_ptrs remain valid.
This is expected by ALSA core as it resets the buffer
state as part of prepare (see snd_pcm_do_prepare()).

Fix the issue by forcing reconfiguration of the FW with
STREAM_PCM_PARAMS in prepare(). Use combined logic to handle
prepare and the existing flow to reprogram hw-params after
system suspend.

Without the fix, first call to pcm pointer() will return
an invalid hw_ptr and application may immediately observe XRUN
status, unless "start_threshold" SW parameter is set to maximum
value by the application.

Signed-off-by: Kai Vehmanen <kai.vehmanen@linux.intel.com>
Signed-off-by: Pierre-Louis Bossart <pierre-louis.bossart@linux.intel.com>
Link: https://lore.kernel.org/r/20190722141402.7194-3-pierre-louis.bossart@linux.intel.com
Signed-off-by: Mark Brown <broonie@kernel.org>
sound/soc/sof/pcm.c
sound/soc/sof/pm.c
sound/soc/sof/sof-priv.h

index 334e9d5..3b8955e 100644 (file)
@@ -208,12 +208,11 @@ static int sof_pcm_hw_params(struct snd_pcm_substream *substream,
        if (ret < 0)
                return ret;
 
+       spcm->prepared[substream->stream] = true;
+
        /* save pcm hw_params */
        memcpy(&spcm->params[substream->stream], params, sizeof(*params));
 
-       /* clear hw_params_upon_resume flag */
-       spcm->hw_params_upon_resume[substream->stream] = 0;
-
        return ret;
 }
 
@@ -236,6 +235,9 @@ static int sof_pcm_hw_free(struct snd_pcm_substream *substream)
        if (!spcm)
                return -EINVAL;
 
+       if (!spcm->prepared[substream->stream])
+               return 0;
+
        dev_dbg(sdev->dev, "pcm: free stream %d dir %d\n", spcm->pcm.pcm_id,
                substream->stream);
 
@@ -258,6 +260,8 @@ static int sof_pcm_hw_free(struct snd_pcm_substream *substream)
        if (ret < 0)
                dev_err(sdev->dev, "error: platform hw free failed\n");
 
+       spcm->prepared[substream->stream] = false;
+
        return ret;
 }
 
@@ -278,11 +282,7 @@ static int sof_pcm_prepare(struct snd_pcm_substream *substream)
        if (!spcm)
                return -EINVAL;
 
-       /*
-        * check if hw_params needs to be set-up again.
-        * This is only needed when resuming from system sleep.
-        */
-       if (!spcm->hw_params_upon_resume[substream->stream])
+       if (spcm->prepared[substream->stream])
                return 0;
 
        dev_dbg(sdev->dev, "pcm: prepare stream %d dir %d\n", spcm->pcm.pcm_id,
@@ -311,6 +311,7 @@ static int sof_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
        struct snd_sof_pcm *spcm;
        struct sof_ipc_stream stream;
        struct sof_ipc_reply reply;
+       bool reset_hw_params = false;
        int ret;
 
        /* nothing to do for BE */
@@ -351,6 +352,7 @@ static int sof_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
        case SNDRV_PCM_TRIGGER_SUSPEND:
        case SNDRV_PCM_TRIGGER_STOP:
                stream.hdr.cmd |= SOF_IPC_STREAM_TRIG_STOP;
+               reset_hw_params = true;
                break;
        default:
                dev_err(sdev->dev, "error: unhandled trigger cmd %d\n", cmd);
@@ -363,17 +365,17 @@ static int sof_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
        ret = sof_ipc_tx_message(sdev->ipc, stream.hdr.cmd, &stream,
                                 sizeof(stream), &reply, sizeof(reply));
 
-       if (ret < 0 || cmd != SNDRV_PCM_TRIGGER_SUSPEND)
+       if (ret < 0 || !reset_hw_params)
                return ret;
 
        /*
-        * The hw_free op is usually called when the pcm stream is closed.
-        * Since the stream is not closed during suspend, the DSP needs to be
-        * notified explicitly to free pcm to prevent errors upon resume.
+        * In case of stream is stopped, DSP must be reprogrammed upon
+        * restart, so free PCM here.
         */
        stream.hdr.size = sizeof(stream);
        stream.hdr.cmd = SOF_IPC_GLB_STREAM_MSG | SOF_IPC_STREAM_PCM_FREE;
        stream.comp_id = spcm->stream[substream->stream].comp_id;
+       spcm->prepared[substream->stream] = false;
 
        /* send IPC to the DSP */
        return sof_ipc_tx_message(sdev->ipc, stream.hdr.cmd, &stream,
@@ -481,6 +483,7 @@ static int sof_pcm_open(struct snd_pcm_substream *substream)
        spcm->stream[substream->stream].posn.host_posn = 0;
        spcm->stream[substream->stream].posn.dai_posn = 0;
        spcm->stream[substream->stream].substream = substream;
+       spcm->prepared[substream->stream] = false;
 
        ret = snd_sof_pcm_platform_open(sdev, substream);
        if (ret < 0)
index 278abfd..48c6d78 100644 (file)
@@ -233,7 +233,7 @@ static int sof_set_hw_params_upon_resume(struct snd_sof_dev *sdev)
 
                        state = substream->runtime->status->state;
                        if (state == SNDRV_PCM_STATE_SUSPENDED)
-                               spcm->hw_params_upon_resume[dir] = 1;
+                               spcm->prepared[dir] = false;
                }
        }
 
index b8c0b2a..fa5cb7d 100644 (file)
@@ -297,7 +297,7 @@ struct snd_sof_pcm {
        struct snd_sof_pcm_stream stream[2];
        struct list_head list;  /* list in sdev pcm list */
        struct snd_pcm_hw_params params[2];
-       int hw_params_upon_resume[2]; /* set up hw_params upon resume */
+       bool prepared[2]; /* PCM_PARAMS set successfully */
 };
 
 /* ALSA SOF Kcontrol device */