Commit 335302db authored by Mark Brown's avatar Mark Brown

ASoC: SOF: Fixes for Intel HD-Audio DMA stopping

Merge series from Kai Vehmanen <kai.vehmanen@linux.intel.com>:

	Implement an updated programming sequence to handle DMA stop for Intel
	HD-Audio DMA.

	The new flow is only used if the firmware is sufficiently new to
	support the feature. SOF1.9.2 is the first release with the updated
	flow. The kernel changes are backwards compatible with old firmware
	releases. Likewise new firmware releases will work with old kernel.

	Series reviewed originally at:
	https://github.com/thesofproject/linux/pull/3167
parents 4dcddadf 69acac56
......@@ -52,12 +52,25 @@
#define SOF_DAI_FMT_INV_MASK 0x0f00
#define SOF_DAI_FMT_CLOCK_PROVIDER_MASK 0xf000
/* DAI_CONFIG flags */
#define SOF_DAI_CONFIG_FLAGS_MASK 0x3
#define SOF_DAI_CONFIG_FLAGS_NONE (0 << 0) /**< DAI_CONFIG sent without stage information */
#define SOF_DAI_CONFIG_FLAGS_HW_PARAMS (1 << 0) /**< DAI_CONFIG sent during hw_params stage */
#define SOF_DAI_CONFIG_FLAGS_HW_FREE (2 << 0) /**< DAI_CONFIG sent during hw_free stage */
#define SOF_DAI_CONFIG_FLAGS_RFU (3 << 0) /**< not used, reserved for future use */
/*
* DAI_CONFIG flags. The 4 LSB bits are used for the commands, HW_PARAMS, HW_FREE and PAUSE
* representing when the IPC is sent. The 4 MSB bits are used to add quirks along with the above
* commands.
*/
#define SOF_DAI_CONFIG_FLAGS_CMD_MASK 0xF
#define SOF_DAI_CONFIG_FLAGS_NONE 0 /**< DAI_CONFIG sent without stage information */
#define SOF_DAI_CONFIG_FLAGS_HW_PARAMS BIT(0) /**< DAI_CONFIG sent during hw_params stage */
#define SOF_DAI_CONFIG_FLAGS_HW_FREE BIT(1) /**< DAI_CONFIG sent during hw_free stage */
/**< DAI_CONFIG sent during pause trigger. Only available ABI 3.20 onwards */
#define SOF_DAI_CONFIG_FLAGS_PAUSE BIT(2)
#define SOF_DAI_CONFIG_FLAGS_QUIRK_SHIFT 4
#define SOF_DAI_CONFIG_FLAGS_QUIRK_MASK (0xF << SOF_DAI_CONFIG_FLAGS_QUIRK_SHIFT)
/*
* This should be used along with the SOF_DAI_CONFIG_FLAGS_HW_PARAMS to indicate that pipeline
* stop/pause and DAI DMA stop/pause should happen in two steps. This change is only available
* ABI 3.20 onwards.
*/
#define SOF_DAI_CONFIG_FLAGS_2_STEP_STOP BIT(0)
/** \brief Types of DAI */
enum sof_ipc_dai_type {
......
......@@ -197,9 +197,9 @@ static int hda_link_dai_widget_update(struct sof_intel_hda_stream *hda_stream,
/* set up/free DAI widget and send DAI_CONFIG IPC */
if (widget_setup)
return hda_ctrl_dai_widget_setup(w);
return hda_ctrl_dai_widget_setup(w, SOF_DAI_CONFIG_FLAGS_2_STEP_STOP);
return hda_ctrl_dai_widget_free(w);
return hda_ctrl_dai_widget_free(w, SOF_DAI_CONFIG_FLAGS_NONE);
}
static int hda_link_hw_params(struct snd_pcm_substream *substream,
......@@ -287,6 +287,36 @@ static int hda_link_pcm_prepare(struct snd_pcm_substream *substream,
dai);
}
static int hda_link_dai_config_pause_push_ipc(struct snd_soc_dapm_widget *w)
{
struct snd_sof_widget *swidget = w->dobj.private;
struct snd_soc_component *component = swidget->scomp;
struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component);
struct sof_ipc_dai_config *config;
struct snd_sof_dai *sof_dai;
struct sof_ipc_reply reply;
int ret;
sof_dai = swidget->private;
if (!sof_dai || !sof_dai->dai_config) {
dev_err(sdev->dev, "No config for DAI %s\n", w->name);
return -EINVAL;
}
config = &sof_dai->dai_config[sof_dai->current_config];
/* set PAUSE command flag */
config->flags = FIELD_PREP(SOF_DAI_CONFIG_FLAGS_CMD_MASK, SOF_DAI_CONFIG_FLAGS_PAUSE);
ret = sof_ipc_tx_message(sdev->ipc, config->hdr.cmd, config, config->hdr.size,
&reply, sizeof(reply));
if (ret < 0)
dev_err(sdev->dev, "DAI config for %s failed during pause push\n", w->name);
return ret;
}
static int hda_link_pcm_trigger(struct snd_pcm_substream *substream,
int cmd, struct snd_soc_dai *dai)
{
......@@ -312,6 +342,9 @@ static int hda_link_pcm_trigger(struct snd_pcm_substream *substream,
hda_stream = hstream_to_sof_hda_stream(link_dev);
dev_dbg(dai->dev, "In %s cmd=%d\n", __func__, cmd);
w = snd_soc_dai_get_widget(dai, substream->stream);
switch (cmd) {
case SNDRV_PCM_TRIGGER_RESUME:
/* set up hw_params */
......@@ -329,10 +362,7 @@ static int hda_link_pcm_trigger(struct snd_pcm_substream *substream,
break;
case SNDRV_PCM_TRIGGER_SUSPEND:
case SNDRV_PCM_TRIGGER_STOP:
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
w = dai->playback_widget;
else
w = dai->capture_widget;
snd_hdac_ext_link_stream_clear(link_dev);
/*
* free DAI widget during stop/suspend to keep widget use_count's balanced.
......@@ -347,10 +377,13 @@ static int hda_link_pcm_trigger(struct snd_pcm_substream *substream,
}
link_dev->link_prepared = 0;
fallthrough;
break;
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
snd_hdac_ext_link_stream_clear(link_dev);
ret = hda_link_dai_config_pause_push_ipc(w);
if (ret < 0)
return ret;
break;
default:
return -EINVAL;
......@@ -451,9 +484,9 @@ static int ssp_dai_setup_or_free(struct snd_pcm_substream *substream, struct snd
return 0;
if (setup)
return hda_ctrl_dai_widget_setup(w);
return hda_ctrl_dai_widget_setup(w, SOF_DAI_CONFIG_FLAGS_NONE);
return hda_ctrl_dai_widget_free(w);
return hda_ctrl_dai_widget_free(w, SOF_DAI_CONFIG_FLAGS_NONE);
}
static int ssp_dai_startup(struct snd_pcm_substream *substream,
......
......@@ -279,6 +279,45 @@ int hda_dsp_stream_put(struct snd_sof_dev *sdev, int direction, int stream_tag)
return 0;
}
static int hda_dsp_stream_reset(struct snd_sof_dev *sdev, struct hdac_stream *hstream)
{
int sd_offset = SOF_STREAM_SD_OFFSET(hstream);
int timeout = HDA_DSP_STREAM_RESET_TIMEOUT;
u32 val;
/* enter stream reset */
snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR, sd_offset, SOF_STREAM_SD_OFFSET_CRST,
SOF_STREAM_SD_OFFSET_CRST);
do {
val = snd_sof_dsp_read(sdev, HDA_DSP_HDA_BAR, sd_offset);
if (val & SOF_STREAM_SD_OFFSET_CRST)
break;
} while (--timeout);
if (timeout == 0) {
dev_err(sdev->dev, "timeout waiting for stream reset\n");
return -ETIMEDOUT;
}
timeout = HDA_DSP_STREAM_RESET_TIMEOUT;
/* exit stream reset and wait to read a zero before reading any other register */
snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR, sd_offset, SOF_STREAM_SD_OFFSET_CRST, 0x0);
/* wait for hardware to report that stream is out of reset */
udelay(3);
do {
val = snd_sof_dsp_read(sdev, HDA_DSP_HDA_BAR, sd_offset);
if ((val & SOF_STREAM_SD_OFFSET_CRST) == 0)
break;
} while (--timeout);
if (timeout == 0) {
dev_err(sdev->dev, "timeout waiting for stream to exit reset\n");
return -ETIMEDOUT;
}
return 0;
}
int hda_dsp_stream_trigger(struct snd_sof_dev *sdev,
struct hdac_ext_stream *stream, int cmd)
{
......@@ -436,9 +475,9 @@ int hda_dsp_stream_hw_params(struct snd_sof_dev *sdev,
struct hdac_bus *bus = sof_to_bus(sdev);
struct hdac_stream *hstream = &stream->hstream;
int sd_offset = SOF_STREAM_SD_OFFSET(hstream);
int ret, timeout = HDA_DSP_STREAM_RESET_TIMEOUT;
int ret;
u32 dma_start = SOF_HDA_SD_CTL_DMA_START;
u32 val, mask;
u32 mask;
u32 run;
if (!stream) {
......@@ -483,36 +522,9 @@ int hda_dsp_stream_hw_params(struct snd_sof_dev *sdev,
SOF_HDA_CL_DMA_SD_INT_MASK);
/* stream reset */
snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR, sd_offset, 0x1,
0x1);
udelay(3);
do {
val = snd_sof_dsp_read(sdev, HDA_DSP_HDA_BAR,
sd_offset);
if (val & 0x1)
break;
} while (--timeout);
if (timeout == 0) {
dev_err(sdev->dev, "error: stream reset failed\n");
return -ETIMEDOUT;
}
timeout = HDA_DSP_STREAM_RESET_TIMEOUT;
snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR, sd_offset, 0x1,
0x0);
/* wait for hardware to report that stream is out of reset */
udelay(3);
do {
val = snd_sof_dsp_read(sdev, HDA_DSP_HDA_BAR,
sd_offset);
if ((val & 0x1) == 0)
break;
} while (--timeout);
if (timeout == 0) {
dev_err(sdev->dev, "error: timeout waiting for stream reset\n");
return -ETIMEDOUT;
}
ret = hda_dsp_stream_reset(sdev, hstream);
if (ret < 0)
return ret;
if (hstream->posbuf)
*hstream->posbuf = 0;
......@@ -647,6 +659,11 @@ int hda_dsp_stream_hw_free(struct snd_sof_dev *sdev,
hstream);
struct hdac_bus *bus = sof_to_bus(sdev);
u32 mask = 0x1 << stream->index;
int ret;
ret = hda_dsp_stream_reset(sdev, stream);
if (ret < 0)
return ret;
spin_lock_irq(&bus->reg_lock);
/* couple host and link DMA if link DMA channel is idle */
......
......@@ -41,7 +41,7 @@
#define EXCEPT_MAX_HDR_SIZE 0x400
#define HDA_EXT_ROM_STATUS_SIZE 8
int hda_ctrl_dai_widget_setup(struct snd_soc_dapm_widget *w)
int hda_ctrl_dai_widget_setup(struct snd_soc_dapm_widget *w, unsigned int quirk_flags)
{
struct snd_sof_widget *swidget = w->dobj.private;
struct snd_soc_component *component = swidget->scomp;
......@@ -58,6 +58,13 @@ int hda_ctrl_dai_widget_setup(struct snd_soc_dapm_widget *w)
return -EINVAL;
}
/* DAI already configured, reset it before reconfiguring it */
if (sof_dai->configured) {
ret = hda_ctrl_dai_widget_free(w, SOF_DAI_CONFIG_FLAGS_NONE);
if (ret < 0)
return ret;
}
config = &sof_dai->dai_config[sof_dai->current_config];
/*
......@@ -71,8 +78,10 @@ int hda_ctrl_dai_widget_setup(struct snd_soc_dapm_widget *w)
return ret;
}
/* set HW_PARAMS flag */
config->flags = FIELD_PREP(SOF_DAI_CONFIG_FLAGS_MASK, SOF_DAI_CONFIG_FLAGS_HW_PARAMS);
/* set HW_PARAMS flag along with quirks */
config->flags = SOF_DAI_CONFIG_FLAGS_HW_PARAMS |
quirk_flags << SOF_DAI_CONFIG_FLAGS_QUIRK_SHIFT;
/* send DAI_CONFIG IPC */
ret = sof_ipc_tx_message(sdev->ipc, config->hdr.cmd, config, config->hdr.size,
......@@ -87,7 +96,7 @@ int hda_ctrl_dai_widget_setup(struct snd_soc_dapm_widget *w)
return 0;
}
int hda_ctrl_dai_widget_free(struct snd_soc_dapm_widget *w)
int hda_ctrl_dai_widget_free(struct snd_soc_dapm_widget *w, unsigned int quirk_flags)
{
struct snd_sof_widget *swidget = w->dobj.private;
struct snd_soc_component *component = swidget->scomp;
......@@ -110,8 +119,9 @@ int hda_ctrl_dai_widget_free(struct snd_soc_dapm_widget *w)
config = &sof_dai->dai_config[sof_dai->current_config];
/* set HW_FREE flag */
config->flags = FIELD_PREP(SOF_DAI_CONFIG_FLAGS_MASK, SOF_DAI_CONFIG_FLAGS_HW_FREE);
/* set HW_FREE flag along with any quirks */
config->flags = SOF_DAI_CONFIG_FLAGS_HW_FREE |
quirk_flags << SOF_DAI_CONFIG_FLAGS_QUIRK_SHIFT;
ret = sof_ipc_tx_message(sdev->ipc, config->hdr.cmd, config, config->hdr.size,
&reply, sizeof(reply));
......@@ -166,9 +176,9 @@ static int sdw_dai_config_ipc(struct snd_sof_dev *sdev,
config->alh.stream_id = alh_stream_id;
if (setup)
return hda_ctrl_dai_widget_setup(w);
return hda_ctrl_dai_widget_setup(w, SOF_DAI_CONFIG_FLAGS_NONE);
return hda_ctrl_dai_widget_free(w);
return hda_ctrl_dai_widget_free(w, SOF_DAI_CONFIG_FLAGS_NONE);
}
static int sdw_params_stream(struct device *dev,
......
......@@ -487,6 +487,8 @@ struct sof_intel_hda_stream {
(SOF_HDA_ADSP_SD_ENTRY_SIZE * ((s)->index) \
+ SOF_HDA_ADSP_LOADER_BASE)
#define SOF_STREAM_SD_OFFSET_CRST 0x1
/*
* DSP Core services.
*/
......@@ -737,7 +739,7 @@ int hda_pci_intel_probe(struct pci_dev *pci, const struct pci_device_id *pci_id)
struct snd_sof_dai;
struct sof_ipc_dai_config;
int hda_ctrl_dai_widget_setup(struct snd_soc_dapm_widget *w);
int hda_ctrl_dai_widget_free(struct snd_soc_dapm_widget *w);
int hda_ctrl_dai_widget_setup(struct snd_soc_dapm_widget *w, unsigned int quirk_flags);
int hda_ctrl_dai_widget_free(struct snd_soc_dapm_widget *w, unsigned int quirk_flags);
#endif
......@@ -107,6 +107,9 @@ int sof_pcm_dsp_pcm_free(struct snd_pcm_substream *substream, struct snd_sof_dev
struct sof_ipc_reply reply;
int ret;
if (!spcm->prepared[substream->stream])
return 0;
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;
......@@ -178,11 +181,9 @@ static int sof_pcm_hw_params(struct snd_soc_component *component,
* Handle repeated calls to hw_params() without free_pcm() in
* between. At least ALSA OSS emulation depends on this.
*/
if (spcm->prepared[substream->stream]) {
ret = sof_pcm_dsp_pcm_free(substream, sdev, spcm);
if (ret < 0)
return ret;
}
ret = sof_pcm_dsp_pcm_free(substream, sdev, spcm);
if (ret < 0)
return ret;
dev_dbg(component->dev, "pcm: hw params stream %d dir %d\n",
spcm->pcm.pcm_id, substream->stream);
......@@ -298,24 +299,26 @@ static int sof_pcm_hw_free(struct snd_soc_component *component,
dev_dbg(component->dev, "pcm: free stream %d dir %d\n",
spcm->pcm.pcm_id, substream->stream);
if (spcm->prepared[substream->stream]) {
ret = sof_pcm_dsp_pcm_free(substream, sdev, spcm);
if (ret < 0)
err = ret;
}
ret = sof_widget_list_free(sdev, spcm, substream->stream);
/* free PCM in the DSP */
ret = sof_pcm_dsp_pcm_free(substream, sdev, spcm);
if (ret < 0)
err = ret;
cancel_work_sync(&spcm->stream[substream->stream].period_elapsed_work);
/* stop DMA */
ret = snd_sof_pcm_platform_hw_free(sdev, substream);
if (ret < 0) {
dev_err(component->dev, "error: platform hw free failed\n");
err = ret;
}
/* free the DAPM widget list */
ret = sof_widget_list_free(sdev, spcm, substream->stream);
if (ret < 0)
err = ret;
cancel_work_sync(&spcm->stream[substream->stream].period_elapsed_work);
return err;
}
......@@ -466,13 +469,10 @@ static int sof_pcm_trigger(struct snd_soc_component *component,
/* free PCM if reset_hw_params is set and the STOP IPC is successful */
if (!ret && reset_hw_params) {
ret = sof_pcm_dsp_pcm_free(substream, sdev, spcm);
ret = sof_pcm_stream_free(sdev, substream, spcm, substream->stream,
free_widget_list);
if (ret < 0)
return ret;
/* free widget list only for SUSPEND trigger */
if (free_widget_list)
ret = sof_widget_list_free(sdev, spcm, substream->stream);
}
return ret;
......
......@@ -57,7 +57,7 @@ static int sof_dai_config_setup(struct snd_sof_dev *sdev, struct snd_sof_dai *da
}
/* set NONE flag to clear all previous settings */
config->flags = FIELD_PREP(SOF_DAI_CONFIG_FLAGS_MASK, SOF_DAI_CONFIG_FLAGS_NONE);
config->flags = SOF_DAI_CONFIG_FLAGS_NONE;
ret = sof_ipc_tx_message(sdev->ipc, config->hdr.cmd, config, config->hdr.size,
&reply, sizeof(reply));
......@@ -728,6 +728,31 @@ int sof_set_up_pipelines(struct snd_sof_dev *sdev, bool verify)
return 0;
}
int sof_pcm_stream_free(struct snd_sof_dev *sdev, struct snd_pcm_substream *substream,
struct snd_sof_pcm *spcm, int dir, bool free_widget_list)
{
int ret;
/* Send PCM_FREE IPC to reset pipeline */
ret = sof_pcm_dsp_pcm_free(substream, sdev, spcm);
if (ret < 0)
return ret;
/* stop the DMA */
ret = snd_sof_pcm_platform_hw_free(sdev, substream);
if (ret < 0)
return ret;
/* free widget list */
if (free_widget_list) {
ret = sof_widget_list_free(sdev, spcm, dir);
if (ret < 0)
dev_err(sdev->dev, "failed to free widgets during suspend\n");
}
return ret;
}
/*
* Free the PCM, its associated widgets and set the prepared flag to false for all PCMs that
* did not get suspended(ex: paused streams) so the widgets can be set up again during resume.
......@@ -751,15 +776,9 @@ static int sof_tear_down_left_over_pipelines(struct snd_sof_dev *sdev)
continue;
if (spcm->stream[dir].list) {
ret = sof_pcm_dsp_pcm_free(substream, sdev, spcm);
ret = sof_pcm_stream_free(sdev, substream, spcm, dir, true);
if (ret < 0)
return ret;
ret = sof_widget_list_free(sdev, spcm, dir);
if (ret < 0) {
dev_err(sdev->dev, "failed to free widgets during suspend\n");
return ret;
}
}
}
......
......@@ -267,4 +267,6 @@ int sof_widget_list_setup(struct snd_sof_dev *sdev, struct snd_sof_pcm *spcm, in
int sof_widget_list_free(struct snd_sof_dev *sdev, struct snd_sof_pcm *spcm, int dir);
int sof_pcm_dsp_pcm_free(struct snd_pcm_substream *substream, struct snd_sof_dev *sdev,
struct snd_sof_pcm *spcm);
int sof_pcm_stream_free(struct snd_sof_dev *sdev, struct snd_pcm_substream *substream,
struct snd_sof_pcm *spcm, int dir, bool free_widget_list);
#endif
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