Commit 04c80277 authored by Kai Vehmanen's avatar Kai Vehmanen Committed by Mark Brown

ASoC: SOF: reset DMA state in prepare

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: default avatarKai Vehmanen <kai.vehmanen@linux.intel.com>
Signed-off-by: default avatarPierre-Louis Bossart <pierre-louis.bossart@linux.intel.com>
Link: https://lore.kernel.org/r/20190722141402.7194-3-pierre-louis.bossart@linux.intel.comSigned-off-by: default avatarMark Brown <broonie@kernel.org>
parent f1b1b9b1
...@@ -208,12 +208,11 @@ static int sof_pcm_hw_params(struct snd_pcm_substream *substream, ...@@ -208,12 +208,11 @@ static int sof_pcm_hw_params(struct snd_pcm_substream *substream,
if (ret < 0) if (ret < 0)
return ret; return ret;
spcm->prepared[substream->stream] = true;
/* save pcm hw_params */ /* save pcm hw_params */
memcpy(&spcm->params[substream->stream], params, sizeof(*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; return ret;
} }
...@@ -236,6 +235,9 @@ static int sof_pcm_hw_free(struct snd_pcm_substream *substream) ...@@ -236,6 +235,9 @@ static int sof_pcm_hw_free(struct snd_pcm_substream *substream)
if (!spcm) if (!spcm)
return -EINVAL; return -EINVAL;
if (!spcm->prepared[substream->stream])
return 0;
dev_dbg(sdev->dev, "pcm: free stream %d dir %d\n", spcm->pcm.pcm_id, dev_dbg(sdev->dev, "pcm: free stream %d dir %d\n", spcm->pcm.pcm_id,
substream->stream); substream->stream);
...@@ -258,6 +260,8 @@ static int sof_pcm_hw_free(struct snd_pcm_substream *substream) ...@@ -258,6 +260,8 @@ static int sof_pcm_hw_free(struct snd_pcm_substream *substream)
if (ret < 0) if (ret < 0)
dev_err(sdev->dev, "error: platform hw free failed\n"); dev_err(sdev->dev, "error: platform hw free failed\n");
spcm->prepared[substream->stream] = false;
return ret; return ret;
} }
...@@ -278,11 +282,7 @@ static int sof_pcm_prepare(struct snd_pcm_substream *substream) ...@@ -278,11 +282,7 @@ static int sof_pcm_prepare(struct snd_pcm_substream *substream)
if (!spcm) if (!spcm)
return -EINVAL; return -EINVAL;
/* if (spcm->prepared[substream->stream])
* 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])
return 0; return 0;
dev_dbg(sdev->dev, "pcm: prepare stream %d dir %d\n", spcm->pcm.pcm_id, 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) ...@@ -311,6 +311,7 @@ static int sof_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
struct snd_sof_pcm *spcm; struct snd_sof_pcm *spcm;
struct sof_ipc_stream stream; struct sof_ipc_stream stream;
struct sof_ipc_reply reply; struct sof_ipc_reply reply;
bool reset_hw_params = false;
int ret; int ret;
/* nothing to do for BE */ /* nothing to do for BE */
...@@ -351,6 +352,7 @@ static int sof_pcm_trigger(struct snd_pcm_substream *substream, int cmd) ...@@ -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_SUSPEND:
case SNDRV_PCM_TRIGGER_STOP: case SNDRV_PCM_TRIGGER_STOP:
stream.hdr.cmd |= SOF_IPC_STREAM_TRIG_STOP; stream.hdr.cmd |= SOF_IPC_STREAM_TRIG_STOP;
reset_hw_params = true;
break; break;
default: default:
dev_err(sdev->dev, "error: unhandled trigger cmd %d\n", cmd); 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) ...@@ -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, ret = sof_ipc_tx_message(sdev->ipc, stream.hdr.cmd, &stream,
sizeof(stream), &reply, sizeof(reply)); sizeof(stream), &reply, sizeof(reply));
if (ret < 0 || cmd != SNDRV_PCM_TRIGGER_SUSPEND) if (ret < 0 || !reset_hw_params)
return ret; return ret;
/* /*
* The hw_free op is usually called when the pcm stream is closed. * In case of stream is stopped, DSP must be reprogrammed upon
* Since the stream is not closed during suspend, the DSP needs to be * restart, so free PCM here.
* notified explicitly to free pcm to prevent errors upon resume.
*/ */
stream.hdr.size = sizeof(stream); stream.hdr.size = sizeof(stream);
stream.hdr.cmd = SOF_IPC_GLB_STREAM_MSG | SOF_IPC_STREAM_PCM_FREE; stream.hdr.cmd = SOF_IPC_GLB_STREAM_MSG | SOF_IPC_STREAM_PCM_FREE;
stream.comp_id = spcm->stream[substream->stream].comp_id; stream.comp_id = spcm->stream[substream->stream].comp_id;
spcm->prepared[substream->stream] = false;
/* send IPC to the DSP */ /* send IPC to the DSP */
return sof_ipc_tx_message(sdev->ipc, stream.hdr.cmd, &stream, 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) ...@@ -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.host_posn = 0;
spcm->stream[substream->stream].posn.dai_posn = 0; spcm->stream[substream->stream].posn.dai_posn = 0;
spcm->stream[substream->stream].substream = substream; spcm->stream[substream->stream].substream = substream;
spcm->prepared[substream->stream] = false;
ret = snd_sof_pcm_platform_open(sdev, substream); ret = snd_sof_pcm_platform_open(sdev, substream);
if (ret < 0) if (ret < 0)
......
...@@ -233,7 +233,7 @@ static int sof_set_hw_params_upon_resume(struct snd_sof_dev *sdev) ...@@ -233,7 +233,7 @@ static int sof_set_hw_params_upon_resume(struct snd_sof_dev *sdev)
state = substream->runtime->status->state; state = substream->runtime->status->state;
if (state == SNDRV_PCM_STATE_SUSPENDED) if (state == SNDRV_PCM_STATE_SUSPENDED)
spcm->hw_params_upon_resume[dir] = 1; spcm->prepared[dir] = false;
} }
} }
......
...@@ -297,7 +297,7 @@ struct snd_sof_pcm { ...@@ -297,7 +297,7 @@ struct snd_sof_pcm {
struct snd_sof_pcm_stream stream[2]; struct snd_sof_pcm_stream stream[2];
struct list_head list; /* list in sdev pcm list */ struct list_head list; /* list in sdev pcm list */
struct snd_pcm_hw_params params[2]; 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 */ /* ALSA SOF Kcontrol device */
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment