diff --git a/drivers/soundwire/intel.c b/drivers/soundwire/intel.c index af65214836b4d0..2064e64c2c5455 100644 --- a/drivers/soundwire/intel.c +++ b/drivers/soundwire/intel.c @@ -953,6 +953,9 @@ static int intel_component_dais_suspend(struct snd_soc_component *component) struct sdw_cdns *cdns = snd_soc_dai_get_drvdata(dai); struct sdw_cdns_dai_runtime *dai_runtime; + if (sdw_bus_ignore_suspend(&cdns->bus)) + continue; + dai_runtime = cdns->dai_runtime_array[dai->id]; if (dai_runtime) diff --git a/drivers/soundwire/intel_ace2x.c b/drivers/soundwire/intel_ace2x.c index 642b33ab552606..717531d737f586 100644 --- a/drivers/soundwire/intel_ace2x.c +++ b/drivers/soundwire/intel_ace2x.c @@ -952,6 +952,9 @@ static int intel_component_dais_suspend(struct snd_soc_component *component) struct sdw_cdns *cdns = snd_soc_dai_get_drvdata(dai); struct sdw_cdns_dai_runtime *dai_runtime; + if (sdw_bus_ignore_suspend(&cdns->bus)) + continue; + dai_runtime = cdns->dai_runtime_array[dai->id]; if (dai_runtime) diff --git a/drivers/soundwire/intel_auxdevice.c b/drivers/soundwire/intel_auxdevice.c index 901a71262094f9..fd12035c38ee3c 100644 --- a/drivers/soundwire/intel_auxdevice.c +++ b/drivers/soundwire/intel_auxdevice.c @@ -647,6 +647,9 @@ static int __maybe_unused intel_suspend(struct device *dev) return 0; } + if (sdw_bus_ignore_suspend(bus)) + return 0; + /* Prevent runtime PM from racing with the code below. */ pm_runtime_disable(dev); @@ -734,6 +737,12 @@ static int __maybe_unused intel_resume(struct device *dev) return 0; } + if (sdw_bus_ignore_suspend(bus)) { + dev_dbg(dev, "SoundWire master %d remained active during system suspend\n", + bus->link_id); + return 0; + } + ret = sdw_intel_link_power_up(sdw); if (ret) { dev_err(dev, "%s failed: %d\n", __func__, ret); diff --git a/drivers/soundwire/stream.c b/drivers/soundwire/stream.c index b4f68356708fe4..5965abde882911 100644 --- a/drivers/soundwire/stream.c +++ b/drivers/soundwire/stream.c @@ -1679,6 +1679,25 @@ int sdw_enable_stream(struct sdw_stream_runtime *stream) } EXPORT_SYMBOL(sdw_enable_stream); +bool sdw_bus_ignore_suspend(struct sdw_bus *bus) +{ + struct sdw_master_runtime *m_rt; + bool ret = false; + + mutex_lock(&bus->bus_lock); + list_for_each_entry(m_rt, &bus->m_rt_list, bus_node) { + if (m_rt->stream->state == SDW_STREAM_ENABLED && + m_rt->stream->ignore_suspend) { + ret = true; + break; + } + } + mutex_unlock(&bus->bus_lock); + + return ret; +} +EXPORT_SYMBOL(sdw_bus_ignore_suspend); + static int _sdw_disable_stream(struct sdw_stream_runtime *stream) { struct sdw_master_runtime *m_rt; diff --git a/include/linux/soundwire/sdw.h b/include/linux/soundwire/sdw.h index 1b03bfad2e6bbe..50960ab8baf3b0 100644 --- a/include/linux/soundwire/sdw.h +++ b/include/linux/soundwire/sdw.h @@ -976,6 +976,7 @@ struct sdw_stream_params { * @master_list: List of Master runtime(s) in this stream. * master_list can contain only one m_rt per Master instance * for a stream + * @ignore_suspend: stream keeps running during system suspend */ struct sdw_stream_runtime { const char *name; @@ -984,6 +985,7 @@ struct sdw_stream_runtime { enum sdw_stream_type type; int m_rt_count; struct list_head master_list; + bool ignore_suspend; }; /** @@ -1086,6 +1088,7 @@ int sdw_stream_remove_master(struct sdw_bus *bus, int sdw_startup_stream(void *sdw_substream); int sdw_prepare_stream(struct sdw_stream_runtime *stream); int sdw_enable_stream(struct sdw_stream_runtime *stream); +bool sdw_bus_ignore_suspend(struct sdw_bus *bus); int sdw_disable_stream(struct sdw_stream_runtime *stream); int sdw_deprepare_stream(struct sdw_stream_runtime *stream); void sdw_shutdown_stream(void *sdw_substream); diff --git a/sound/soc/sdw_utils/soc_sdw_utils.c b/sound/soc/sdw_utils/soc_sdw_utils.c index 14aee10b40ba7c..31e649fada688f 100644 --- a/sound/soc/sdw_utils/soc_sdw_utils.c +++ b/sound/soc/sdw_utils/soc_sdw_utils.c @@ -1880,6 +1880,9 @@ int asoc_sdw_prepare(struct snd_pcm_substream *substream) return PTR_ERR(sdw_stream); } + if (sdw_stream->state == SDW_STREAM_ENABLED) + return 0; + return sdw_prepare_stream(sdw_stream); } EXPORT_SYMBOL_NS(asoc_sdw_prepare, "SND_SOC_SDW_UTILS"); @@ -1902,6 +1905,10 @@ int asoc_sdw_trigger(struct snd_pcm_substream *substream, int cmd) switch (cmd) { case SNDRV_PCM_TRIGGER_RESUME: + if (sdw_stream->state == SDW_STREAM_ENABLED) { + ret = 0; + break; + } /* * The peripherals lose their port configuration when the * controller is power-gated during system suspend, and an @@ -1923,10 +1930,12 @@ int asoc_sdw_trigger(struct snd_pcm_substream *substream, int cmd) break; case SNDRV_PCM_TRIGGER_PAUSE_PUSH: - case SNDRV_PCM_TRIGGER_SUSPEND: case SNDRV_PCM_TRIGGER_STOP: ret = sdw_disable_stream(sdw_stream); break; + case SNDRV_PCM_TRIGGER_SUSPEND: + ret = sdw_stream->ignore_suspend ? 0 : sdw_disable_stream(sdw_stream); + break; default: ret = -EINVAL; break; @@ -2005,6 +2014,16 @@ int asoc_sdw_hw_free(struct snd_pcm_substream *substream) return PTR_ERR(sdw_stream); } + if (sdw_stream->state == SDW_STREAM_ENABLED) { + int ret; + + ret = sdw_disable_stream(sdw_stream); + if (ret) { + dev_err(rtd->dev, "failed to disable SoundWire stream: %d\n", ret); + return ret; + } + } + return sdw_deprepare_stream(sdw_stream); } EXPORT_SYMBOL_NS(asoc_sdw_hw_free, "SND_SOC_SDW_UTILS"); diff --git a/sound/soc/soc-component.c b/sound/soc/soc-component.c index 463ea227cac819..44fd2315b17443 100644 --- a/sound/soc/soc-component.c +++ b/sound/soc/soc-component.c @@ -1224,6 +1224,7 @@ int snd_soc_pcm_component_trigger(struct snd_pcm_substream *substream, return ret; } +EXPORT_SYMBOL_GPL(snd_soc_pcm_component_trigger); int snd_soc_pcm_component_pm_runtime_get(struct snd_soc_pcm_runtime *rtd, void *stream) diff --git a/sound/soc/soc-pcm.c b/sound/soc/soc-pcm.c index ef8ccf52798eb6..629d4b9e324d08 100644 --- a/sound/soc/soc-pcm.c +++ b/sound/soc/soc-pcm.c @@ -2266,14 +2266,22 @@ int dpcm_be_dai_trigger(struct snd_soc_pcm_runtime *fe, int stream, be->dpcm[stream].state = SND_SOC_DPCM_STATE_START; break; case SNDRV_PCM_TRIGGER_RESUME: - if ((be->dpcm[stream].state != SND_SOC_DPCM_STATE_SUSPEND)) + /* + * Another FE may already have prepared or restarted the BE. + * Count this FE as well, but only trigger the first user. + */ + if (be->dpcm[stream].state != SND_SOC_DPCM_STATE_SUSPEND && + be->dpcm[stream].state != SND_SOC_DPCM_STATE_PREPARE && + be->dpcm[stream].state != SND_SOC_DPCM_STATE_START) goto next; be->dpcm[stream].be_start++; if (be->dpcm[stream].be_start != 1) goto next; - ret = soc_pcm_trigger(be_substream, cmd); + ret = soc_pcm_trigger(be_substream, + be->dpcm[stream].state == SND_SOC_DPCM_STATE_PREPARE ? + SNDRV_PCM_TRIGGER_START : cmd); if (ret) { be->dpcm[stream].be_start--; goto next; @@ -2413,6 +2421,21 @@ static int dpcm_dai_trigger_fe_be(struct snd_pcm_substream *substream, fe->dai_link->name, cmd); ret = soc_pcm_trigger(substream, cmd); + /* + * The PCM core undoes failed START and PAUSE_RELEASE triggers, + * but not RESUME because the stream is not running yet. Drop + * the BE reference taken for this FE so be_start stays balanced. + */ + if (ret < 0 && cmd == SNDRV_PCM_TRIGGER_RESUME) { + int rollback_ret; + + rollback_ret = dpcm_be_dai_trigger(fe, substream->stream, + SNDRV_PCM_TRIGGER_SUSPEND); + if (rollback_ret < 0) + dev_err(fe->dev, + "failed to roll back BE resume after FE trigger failure: %d\n", + rollback_ret); + } } end: return snd_soc_ret(fe->dev, ret, "trigger FE cmd: %d failed\n", cmd); @@ -2502,6 +2525,7 @@ static int dpcm_fe_dai_trigger(struct snd_pcm_substream *substream, int cmd) int dpcm_be_dai_prepare(struct snd_soc_pcm_runtime *fe, int stream) { struct snd_soc_dpcm *dpcm; + unsigned long flags = 0; int ret = 0; for_each_dpcm_be(fe, stream, dpcm) { @@ -2510,27 +2534,39 @@ int dpcm_be_dai_prepare(struct snd_soc_pcm_runtime *fe, int stream) struct snd_pcm_substream *be_substream = snd_soc_dpcm_get_substream(be, stream); + /* + * Non-atomic prepare callbacks may sleep. Serialize them with + * BE triggers using the stream mutex, not an atomic stream lock. + */ + if (be_substream->pcm->nonatomic) + snd_pcm_stream_lock_irqsave_nested(be_substream, flags); + /* is this op for this BE ? */ if (!snd_soc_dpcm_can_be_update(fe, be, stream)) - continue; + goto next; if (!snd_soc_dpcm_can_be_prepared(fe, be, stream)) - continue; + goto next; if ((be->dpcm[stream].state != SND_SOC_DPCM_STATE_HW_PARAMS) && (be->dpcm[stream].state != SND_SOC_DPCM_STATE_STOP) && (be->dpcm[stream].state != SND_SOC_DPCM_STATE_SUSPEND) && (be->dpcm[stream].state != SND_SOC_DPCM_STATE_PAUSED)) - continue; + goto next; dev_dbg(be->dev, "ASoC: prepare BE %s\n", be->dai_link->name); ret = __soc_pcm_prepare(be, be_substream); if (ret < 0) - break; + goto next; be->dpcm[stream].state = SND_SOC_DPCM_STATE_PREPARE; +next: + if (be_substream->pcm->nonatomic) + snd_pcm_stream_unlock_irqrestore(be_substream, flags); + if (ret < 0) + break; } /* diff --git a/sound/soc/sof/core.c b/sound/soc/sof/core.c index 1f38fe03e49395..49520db269e0ca 100644 --- a/sound/soc/sof/core.c +++ b/sound/soc/sof/core.c @@ -690,6 +690,7 @@ int snd_sof_device_probe(struct device *dev, struct snd_sof_pdata *plat_data) INIT_LIST_HEAD(&sdev->dai_list); INIT_LIST_HEAD(&sdev->dai_link_list); INIT_LIST_HEAD(&sdev->route_list); + INIT_LIST_HEAD(&sdev->ignore_suspend_widget_list); INIT_LIST_HEAD(&sdev->ipc_client_list); INIT_LIST_HEAD(&sdev->ipc_rx_handler_list); INIT_LIST_HEAD(&sdev->fw_state_handler_list); @@ -751,6 +752,7 @@ int snd_sof_device_remove(struct device *dev) { struct snd_sof_dev *sdev = dev_get_drvdata(dev); struct snd_sof_pdata *pdata = sdev->pdata; + struct snd_sof_ignore_suspend_widget *isw, *tmp; int ret; bool aborted = false; @@ -770,6 +772,12 @@ int snd_sof_device_remove(struct device *dev) */ snd_sof_machine_unregister(sdev, pdata); + /* topology is unloaded by now, free any leftover ignore_suspend widget entries */ + list_for_each_entry_safe(isw, tmp, &sdev->ignore_suspend_widget_list, list) { + list_del(&isw->list); + kfree(isw); + } + /* * Balance the runtime pm usage count in case we are faced with an * exception and we forcably prevented D3 power state to preserve diff --git a/sound/soc/sof/intel/hda-dai-ops.c b/sound/soc/sof/intel/hda-dai-ops.c index f0be42048db33c..c002c234c45797 100644 --- a/sound/soc/sof/intel/hda-dai-ops.c +++ b/sound/soc/sof/intel/hda-dai-ops.c @@ -580,6 +580,10 @@ static int hda_ipc3_post_trigger(struct snd_sof_dev *sdev, struct snd_soc_dai *c { struct snd_soc_dapm_widget *w = snd_soc_dai_get_widget(cpu_dai, substream->stream); + if (sdev->fw_state != SOF_FW_BOOT_COMPLETE && + (cmd == SNDRV_PCM_TRIGGER_STOP || cmd == SNDRV_PCM_TRIGGER_SUSPEND)) + return 0; + switch (cmd) { case SNDRV_PCM_TRIGGER_SUSPEND: case SNDRV_PCM_TRIGGER_STOP: diff --git a/sound/soc/sof/intel/hda-dai.c b/sound/soc/sof/intel/hda-dai.c index 7a8875a885ab34..f9f9d8c6212591 100644 --- a/sound/soc/sof/intel/hda-dai.c +++ b/sound/soc/sof/intel/hda-dai.c @@ -282,6 +282,25 @@ static int __maybe_unused hda_dai_hw_params(struct snd_pcm_substream *substream, return hda_dai_hw_params_data(substream, params, dai, &data, flags); } +/* + * A retained WoV FE that is being resumed has already consumed its + * suspend_ignored flag when the FE is triggered before the BE, but its + * DPCM state is not updated until the trigger completes. Its PCM is still + * SUSPENDED during the trigger. + */ +static bool hda_dai_fe_is_resuming(struct snd_soc_pcm_runtime *fe) +{ + struct snd_pcm_substream *fe_substream; + + if (fe->dpcm[SNDRV_PCM_STREAM_CAPTURE].runtime_update != SND_SOC_DPCM_UPDATE_FE) + return false; + + fe_substream = snd_soc_dpcm_get_substream(fe, SNDRV_PCM_STREAM_CAPTURE); + + return fe_substream && fe_substream->runtime && + fe_substream->runtime->state == SNDRV_PCM_STATE_SUSPENDED; +} + static bool hda_dai_has_wov_fe(struct snd_sof_dev *sdev, struct snd_pcm_substream *substream) { struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); @@ -292,22 +311,44 @@ static bool hda_dai_has_wov_fe(struct snd_sof_dev *sdev, struct snd_pcm_substrea return false; /* - * Only retain the BE for a connected WoV capture FE that is running - * or has already ignored its suspend trigger. An idle WoV FE may - * still be connected to the BE. + * Only retain the BE for a connected WoV capture FE that is running, + * has already ignored its suspend trigger or is being resumed. An idle + * WoV FE may still be connected to the BE. */ for_each_dpcm_fe(rtd, SNDRV_PCM_STREAM_CAPTURE, dpcm) { spcm = snd_sof_find_spcm_dai(sdev->component, dpcm->fe); if (spcm && spcm->stream[SNDRV_PCM_STREAM_CAPTURE].d0i3_compatible && spcm->stream[SNDRV_PCM_STREAM_CAPTURE].dsp_max_burst_size_in_ms <= 1 && (dpcm->fe->dpcm[SNDRV_PCM_STREAM_CAPTURE].state == SND_SOC_DPCM_STATE_START || - spcm->stream[SNDRV_PCM_STREAM_CAPTURE].suspend_ignored)) + spcm->stream[SNDRV_PCM_STREAM_CAPTURE].suspend_ignored || + hda_dai_fe_is_resuming(dpcm->fe))) return true; } return false; } +static void hda_dai_set_sdw_ignore_suspend(struct snd_soc_dai *dai, int dir, + bool ignore_suspend) +{ + struct snd_soc_dapm_widget *w = snd_soc_dai_get_widget(dai, dir); + struct sdw_stream_runtime *sdw_stream; + struct snd_sof_widget *swidget; + struct snd_sof_dai *sof_dai; + + if (!w || !w->dobj.private) + return; + + swidget = w->dobj.private; + sof_dai = swidget->private; + if (!sof_dai || sof_dai->type != SOF_DAI_INTEL_ALH) + return; + + sdw_stream = snd_soc_dai_get_stream(dai, dir); + if (!IS_ERR_OR_NULL(sdw_stream)) + sdw_stream->ignore_suspend = ignore_suspend; +} + /* * In contrast to IPC3, the dai trigger in IPC4 mixes pipeline state changes * (over IPC channel) and DMA state change (direct host register changes). @@ -319,6 +360,7 @@ static int __maybe_unused hda_dai_trigger(struct snd_pcm_substream *substream, i struct hdac_ext_stream *hext_stream; struct sof_intel_hda_stream *hda_stream; struct snd_sof_dev *sdev; + bool skip_fw_ipc; int ret; if (!ops) { @@ -330,13 +372,20 @@ static int __maybe_unused hda_dai_trigger(struct snd_pcm_substream *substream, i dai->name, substream->stream); sdev = dai_to_sdev(substream, dai); + /* + * Firmware IPC is unavailable after a crash, but STOP/SUSPEND must + * still clean up the host-side DMA resources below. + */ + skip_fw_ipc = sdev->fw_state != SOF_FW_BOOT_COMPLETE && + (cmd == SNDRV_PCM_TRIGGER_STOP || cmd == SNDRV_PCM_TRIGGER_SUSPEND); hext_stream = ops->get_hext_stream(sdev, dai, substream); if (!hext_stream) return -EINVAL; hda_stream = hstream_to_sof_hda_stream(hext_stream); - if (cmd == SNDRV_PCM_TRIGGER_RESUME && hda_stream->suspend_ignored) { + if ((cmd == SNDRV_PCM_TRIGGER_START || cmd == SNDRV_PCM_TRIGGER_RESUME) && + hda_stream->suspend_ignored) { hda_stream->suspend_ignored = false; return 0; } @@ -353,7 +402,7 @@ static int __maybe_unused hda_dai_trigger(struct snd_pcm_substream *substream, i return 0; } - if (ops->pre_trigger) { + if (ops->pre_trigger && !skip_fw_ipc) { ret = ops->pre_trigger(sdev, dai, substream, cmd); if (ret < 0) return ret; @@ -691,6 +740,16 @@ EXPORT_SYMBOL_NS(sdw_hda_dai_hw_free, "SND_SOC_SOF_INTEL_HDA_COMMON"); int sdw_hda_dai_trigger(struct snd_pcm_substream *substream, int cmd, struct snd_soc_dai *cpu_dai) { + struct snd_sof_dev *sdev = dai_to_sdev(substream, cpu_dai); + bool retain = false; + + if (cmd == SNDRV_PCM_TRIGGER_SUSPEND) + retain = sdev->system_suspend_target == SOF_SUSPEND_S0IX && + hda_dai_has_wov_fe(sdev, substream); + + if (cmd == SNDRV_PCM_TRIGGER_SUSPEND || cmd == SNDRV_PCM_TRIGGER_STOP) + hda_dai_set_sdw_ignore_suspend(cpu_dai, substream->stream, retain); + return hda_dai_trigger(substream, cmd, cpu_dai); } EXPORT_SYMBOL_NS(sdw_hda_dai_trigger, "SND_SOC_SOF_INTEL_HDA_COMMON"); diff --git a/sound/soc/sof/pcm.c b/sound/soc/sof/pcm.c index 0161c0ae8bbdb4..c081f17ab1e8d8 100644 --- a/sound/soc/sof/pcm.c +++ b/sound/soc/sof/pcm.c @@ -66,6 +66,163 @@ void snd_sof_pcm_period_elapsed(struct snd_pcm_substream *substream) } EXPORT_SYMBOL(snd_sof_pcm_period_elapsed); +static void sof_pcm_update_widget_ignore_suspend(struct snd_sof_dev *sdev, + struct snd_sof_pcm_stream *stream, + struct snd_soc_dapm_widget *widget, + int *ret) +{ + struct snd_sof_ignore_suspend_widget *isw; + bool owned = false; + + list_for_each_entry(isw, &sdev->ignore_suspend_widget_list, list) { + if (isw->widget == widget) { + owned = true; + + if (isw->stream == stream) + return; + } + } + + /* Do not take ownership of a widget already held by another subsystem. */ + if (widget->ignore_suspend && !owned) + return; + + isw = kzalloc_obj(*isw); + if (!isw) { + *ret = -ENOMEM; + return; + } + + isw->widget = widget; + isw->stream = stream; + list_add_tail(&isw->list, &sdev->ignore_suspend_widget_list); + widget->ignore_suspend = 1; +} + +static void sof_pcm_release_widget_ignore_suspend(struct snd_sof_dev *sdev, + struct snd_sof_pcm_stream *stream) +{ + struct snd_sof_ignore_suspend_widget *isw, *tmp; + + list_for_each_entry_safe(isw, tmp, &sdev->ignore_suspend_widget_list, list) { + struct snd_soc_dapm_widget *widget = isw->widget; + bool owned = false; + + if (isw->stream != stream) + continue; + + list_del(&isw->list); + kfree(isw); + + list_for_each_entry(isw, &sdev->ignore_suspend_widget_list, list) { + if (isw->widget == widget) { + owned = true; + break; + } + } + + if (!owned) + widget->ignore_suspend = 0; + } +} + +static void sof_pcm_refresh_dapm_paths(struct snd_sof_dev *sdev, + struct snd_soc_pcm_runtime *rtd, + struct snd_sof_pcm *spcm, int dir) +{ + struct snd_soc_dai *dai; + int j; + + for_each_rtd_cpu_dais(rtd, j, dai) { + struct snd_soc_dapm_widget_list *connected_widgets; + int connected; + + connected = snd_soc_dapm_dai_get_connected_widgets(dai, dir, + &connected_widgets, + NULL); + if (connected >= 0) { + snd_soc_dapm_dai_free_widgets(&connected_widgets); + } else { + dev_dbg(sdev->dev, + "failed to refresh D0i3-compatible PCM %s paths: %d\n", + spcm->pcm.caps[dir].name, connected); + } + } +} + +static int sof_pcm_set_widget_ignore_suspend(struct snd_sof_dev *sdev, + struct snd_soc_pcm_runtime *rtd, + struct snd_sof_pcm *spcm, int dir, + bool ignore_suspend) +{ + struct snd_sof_pcm_stream *stream = &spcm->stream[dir]; + struct snd_soc_dai *dai; + int j, ret = 0; + + if (!ignore_suspend) { + snd_soc_dapm_mutex_lock(rtd->card); + sof_pcm_release_widget_ignore_suspend(sdev, stream); + snd_soc_dapm_mutex_unlock(rtd->card); + stream->suspend_ignored = false; + sof_pcm_refresh_dapm_paths(sdev, rtd, spcm, dir); + return 0; + } + + for_each_rtd_cpu_dais(rtd, j, dai) { + struct snd_soc_dapm_widget_list *connected_widgets; + struct snd_soc_dapm_widget *widget; + int connected, i; + + connected = snd_soc_dapm_dai_get_connected_widgets(dai, dir, + &connected_widgets, + NULL); + if (connected < 0) { + dev_dbg(sdev->dev, + "failed to enumerate D0i3-compatible PCM %s widgets: %d\n", + spcm->pcm.caps[dir].name, connected); + ret = connected; + break; + } + + snd_soc_dapm_mutex_lock(rtd->card); + widget = snd_soc_dai_get_widget(dai, dir); + if (widget && widget->is_ep) + sof_pcm_update_widget_ignore_suspend(sdev, stream, widget, &ret); + + for_each_dapm_widgets(connected_widgets, i, widget) { + if (ret) + break; + if (!widget->is_ep) + continue; + + dev_dbg(sdev->dev, + "WoV PCM %s ignore suspend for widget %s\n", + spcm->pcm.caps[dir].name, widget->name); + sof_pcm_update_widget_ignore_suspend(sdev, stream, widget, &ret); + if (ret) + break; + } + snd_soc_dapm_mutex_unlock(rtd->card); + snd_soc_dapm_dai_free_widgets(&connected_widgets); + if (ret) + break; + } + + if (ret) { + snd_soc_dapm_mutex_lock(rtd->card); + sof_pcm_release_widget_ignore_suspend(sdev, stream); + snd_soc_dapm_mutex_unlock(rtd->card); + stream->suspend_ignored = false; + } else { + stream->suspend_ignored = true; + } + + /* Recalculate endpoint caches using the updated ignore_suspend flags. */ + sof_pcm_refresh_dapm_paths(sdev, rtd, spcm, dir); + + return ret; +} + int sof_pcm_setup_connected_widgets(struct snd_sof_dev *sdev, struct snd_soc_pcm_runtime *rtd, struct snd_sof_pcm *spcm, struct snd_pcm_hw_params *params, @@ -303,6 +460,8 @@ static int sof_pcm_hw_free(struct snd_soc_component *component, spcm_dbg(spcm, substream->stream, "Entry: hw_free\n"); + sof_pcm_set_widget_ignore_suspend(sdev, rtd, spcm, + substream->stream, false); ret = sof_pcm_stream_free(sdev, substream, spcm, substream->stream, true); /* unprepare and free the list of DAPM widgets */ @@ -336,6 +495,14 @@ static int sof_pcm_prepare(struct snd_soc_component *component, spcm_dbg(spcm, substream->stream, "Entry: prepare\n"); + /* + * A DROP on a suspended PCM does not issue STOP. Clear the frontend's + * retained state before a later prepare/start sequence can consume it. + * The backend will check for another retained WoV FE before skipping START. + */ + if (spcm->stream[dir].suspend_ignored) + sof_pcm_set_widget_ignore_suspend(sdev, rtd, spcm, dir, false); + if (spcm->prepared[substream->stream]) { if (!spcm->pending_stop[substream->stream]) return 0; @@ -421,7 +588,8 @@ static int sof_pcm_trigger(struct snd_soc_component *component, * not supported, no need to re-start streams that * remained enabled in D0ix. */ - spcm->stream[substream->stream].suspend_ignored = false; + sof_pcm_set_widget_ignore_suspend(sdev, rtd, spcm, + substream->stream, false); return 0; } @@ -430,7 +598,8 @@ static int sof_pcm_trigger(struct snd_soc_component *component, break; case SNDRV_PCM_TRIGGER_RESUME: if (spcm->stream[substream->stream].suspend_ignored) { - spcm->stream[substream->stream].suspend_ignored = false; + sof_pcm_set_widget_ignore_suspend(sdev, rtd, spcm, + substream->stream, false); return 0; } spcm_err(spcm, substream->stream, "RESUME trigger is not supported\n"); @@ -448,7 +617,11 @@ static int sof_pcm_trigger(struct snd_soc_component *component, substream->stream == SNDRV_PCM_STREAM_CAPTURE && spcm->stream[substream->stream].d0i3_compatible && spcm->stream[substream->stream].dsp_max_burst_size_in_ms <= 1) { - spcm->stream[substream->stream].suspend_ignored = true; + /* Set ignore_suspend to the DAPM widgets */ + ret = sof_pcm_set_widget_ignore_suspend(sdev, rtd, spcm, + substream->stream, true); + if (ret < 0) + return ret; return 0; } /* On suspend the DMA must be stopped in DSPless mode */ @@ -626,6 +799,8 @@ static int sof_pcm_close(struct snd_soc_component *component, spcm_dbg(spcm, substream->stream, "Entry: close\n"); + sof_pcm_set_widget_ignore_suspend(sdev, rtd, spcm, substream->stream, false); + err = snd_sof_pcm_platform_close(sdev, substream); if (err < 0) { spcm_err(spcm, substream->stream, diff --git a/sound/soc/sof/sof-priv.h b/sound/soc/sof/sof-priv.h index 83e4d83cc27cbb..15abf8f9b6e6f6 100644 --- a/sound/soc/sof/sof-priv.h +++ b/sound/soc/sof/sof-priv.h @@ -407,6 +407,13 @@ struct snd_sof_dfsentry { }; }; +/* DAPM widget whose ignore_suspend flag was set by a WoV PCM */ +struct snd_sof_ignore_suspend_widget { + struct snd_soc_dapm_widget *widget; + struct snd_sof_pcm_stream *stream; + struct list_head list; +}; + /* Debug mapping for any DSP memory or registers that can used for debug */ struct snd_sof_debugfs_map { const char *name; @@ -653,6 +660,8 @@ struct snd_sof_dev { struct list_head dai_list; struct list_head dai_link_list; struct list_head route_list; + /* DAPM widgets marked ignore_suspend by WoV PCMs */ + struct list_head ignore_suspend_widget_list; struct snd_soc_component *component; u32 enabled_cores_mask; /* keep track of enabled cores */ bool led_present;