From c20cc217203e693c73e27fa9245ae88d1b6ff3a2 Mon Sep 17 00:00:00 2001 From: Bard Liao Date: Tue, 6 Oct 2026 21:56:12 +0800 Subject: [PATCH 01/10] soundwire: stream: add support for ignoring system suspend Some streams, such as those used for wake-on-voice, must remain active during system suspend. Add an ignore_suspend flag to sdw_stream_runtime and export sdw_bus_ignore_suspend() so bus drivers can check whether any stream on the bus requires it to remain active. Signed-off-by: Bard Liao --- drivers/soundwire/stream.c | 19 +++++++++++++++++++ include/linux/soundwire/sdw.h | 3 +++ 2 files changed, 22 insertions(+) diff --git a/drivers/soundwire/stream.c b/drivers/soundwire/stream.c index c158bf91e020f9..76e5c0369ca04f 100644 --- a/drivers/soundwire/stream.c +++ b/drivers/soundwire/stream.c @@ -1688,6 +1688,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 326db122bbe6ea..ac9755d11daa2d 100644 --- a/include/linux/soundwire/sdw.h +++ b/include/linux/soundwire/sdw.h @@ -978,6 +978,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; @@ -986,6 +987,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); From 16f5615b109c296d387dfaca6a39085332362b4d Mon Sep 17 00:00:00 2001 From: Bard Liao Date: Tue, 6 Oct 2026 22:06:50 +0800 Subject: [PATCH 02/10] soundwire: Intel: preserve buses for ignore-suspend streams Honor the SoundWire bus ignore-suspend state in the Intel drivers. Preserve the master across system suspend when a stream requires the bus to remain active. Signed-off-by: Bard Liao --- drivers/soundwire/intel.c | 3 +++ drivers/soundwire/intel_ace2x.c | 3 +++ drivers/soundwire/intel_auxdevice.c | 16 ++++++++++++++++ 3 files changed, 22 insertions(+) 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 5983fbc5725a26..5c1bb08ac9dba9 100644 --- a/drivers/soundwire/intel_auxdevice.c +++ b/drivers/soundwire/intel_auxdevice.c @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -650,6 +651,14 @@ static int __maybe_unused intel_suspend(struct device *dev) return 0; } + /* + * ignore_suspend is set by the PCM trigger for S0ix and may remain set + * if a later sleep transition does not issue another PCM trigger. + */ + if (pm_suspend_target_state == PM_SUSPEND_TO_IDLE && + sdw_bus_ignore_suspend(bus)) + return 0; + /* Prevent runtime PM from racing with the code below. */ pm_runtime_disable(dev); @@ -737,6 +746,13 @@ static int __maybe_unused intel_resume(struct device *dev) return 0; } + if (pm_suspend_target_state == PM_SUSPEND_TO_IDLE && + 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); From 35da46843f483dd00b3465145c62b6a14bbcb86a Mon Sep 17 00:00:00 2001 From: Bard Liao Date: Tue, 6 Oct 2026 21:49:39 +0800 Subject: [PATCH 03/10] ASoC: soc_sdw_utils: preserve streams that ignore suspend Do not disable a SoundWire stream on suspend when it is marked to remain active. If the stream is already enabled on resume, no further action is needed. Signed-off-by: Bard Liao --- sound/soc/sdw_utils/soc_sdw_utils.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/sound/soc/sdw_utils/soc_sdw_utils.c b/sound/soc/sdw_utils/soc_sdw_utils.c index 7a19de75f32b53..2072e3e215708c 100644 --- a/sound/soc/sdw_utils/soc_sdw_utils.c +++ b/sound/soc/sdw_utils/soc_sdw_utils.c @@ -1902,6 +1902,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 +1927,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; From 7029457c11abfd1c1033a4c104521725ce0b726e Mon Sep 17 00:00:00 2001 From: Bard Liao Date: Tue, 6 Oct 2026 22:10:20 +0800 Subject: [PATCH 04/10] ASoC: SOF: Intel: keep SoundWire streams active for D0i3-compatible PCMs Keep a SoundWire stream active during system suspend when it is used by a WoV stream. WoV streams can be identified by: They are capture streams and. They have the d0i3_compatible flag set and They don't use Deep Buffer. The decision is made in the BE SUSPEND trigger. The BE DAI trigger runs before the SoundWire link trigger, so the ignore_suspend flag of the SoundWire stream is updated before the link trigger decides whether to disable the stream. When a BE is shared by WoV and regular FEs, the BE only receives SUSPEND once the last FE is suspended, and all connected FEs are checked at that point regardless of their open or suspend order. A WoV PCM retained in a previous S0ix suspend stays in the SUSPENDED state and will not receive another SUSPEND trigger. If the system then enters a deeper suspend state, stop its FE and BE pipelines and release its DAPM retention at PM prepare time. Assisted-by: LLM Signed-off-by: Bard Liao Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- sound/soc/sof/intel/hda-dai.c | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/sound/soc/sof/intel/hda-dai.c b/sound/soc/sof/intel/hda-dai.c index 7a8875a885ab34..5dc489324d06fe 100644 --- a/sound/soc/sof/intel/hda-dai.c +++ b/sound/soc/sof/intel/hda-dai.c @@ -308,6 +308,27 @@ static bool hda_dai_has_wov_fe(struct snd_sof_dev *sdev, struct snd_pcm_substrea 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). @@ -691,6 +712,17 @@ 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 || + cmd == SNDRV_PCM_TRIGGER_START || cmd == SNDRV_PCM_TRIGGER_RESUME) + 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"); From 1054c396ccb703fca9efa6f9ccb574e4d544655e Mon Sep 17 00:00:00 2001 From: Bard Liao Date: Fri, 16 Oct 2026 16:10:41 +0800 Subject: [PATCH 05/10] ASoC: soc_sdw_utils: Handle enabled SoundWire streams Multiple FE streams can share a SoundWire BE, so prepare may arrive after another FE has enabled the shared stream. Skip sdw_prepare_stream() when the stream is already enabled. When hw_free is called for an enabled stream, disable it before depreparing. Propagate disable errors rather than attempting to deprepare an enabled stream. Signed-off-by: Bard Liao --- sound/soc/sdw_utils/soc_sdw_utils.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/sound/soc/sdw_utils/soc_sdw_utils.c b/sound/soc/sdw_utils/soc_sdw_utils.c index 2072e3e215708c..556284f48a8913 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"); @@ -2011,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"); From 0f90b0ba8f6043ded55f9c70d6ec2c0cade74ddd Mon Sep 17 00:00:00 2001 From: Bard Liao Date: Tue, 20 Oct 2026 08:05:19 +0800 Subject: [PATCH 06/10] ASoC: SOF: Intel: Handle retained HDA BE trigger recovery A SoundWire capture BE shared by regular and WoV FEs can remain running across S0ix. Its HDA suspend trigger sets suspend_ignored and skips the pipeline and link DMA teardown. Recovery must consume that flag without starting the retained hardware again. The first BE trigger after system resume is not necessarily RESUME. A regular capture FE can recover through prepare followed by START. Also, when another FE has already prepared the shared BE, DPCM translates the first RESUME into START rather than resuming an unprepared stream. Only handling RESUME in the retained-stream shortcut lets START fall through to the normal HDA trigger callbacks. This unnecessarily pauses and restarts the retained IPC4 pipeline, starts the link DMA again and increments the BE pipeline's started_count even though its existing running reference was preserved during suspend. It also leaves suspend_ignored set after recovery. Consume suspend_ignored on either START or RESUME and return without retriggering the retained hardware. DPCM still accounts for the recovering FE in be_start, while the HDA pipeline keeps its existing running reference until the last BE user stops. Ordinary START triggers are unchanged when suspend_ignored is false. The BE SUSPEND trigger also has to decide whether a WoV FE sharing the BE retains its pipeline. With IPC4 capture links the FE is triggered before the BE, so the FE retention flag may not be set yet and its DPCM state is only updated after the trigger completes. Treat a WoV FE as retaining when it is running, or when it was already suspended for S0ix, that is its PCM is SUSPENDED, still prepared and has suspend_ignored set. PREPARED or PAUSED PCMs are also moved to SUSPENDED without a SUSPEND trigger, so suspend_ignored is required to not retain the BE for an idle WoV FE. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: Bard Liao --- sound/soc/sof/intel/hda-dai-ops.c | 4 +++ sound/soc/sof/intel/hda-dai.c | 54 +++++++++++++++++++++++-------- 2 files changed, 45 insertions(+), 13 deletions(-) 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 5dc489324d06fe..42cad93afdb128 100644 --- a/sound/soc/sof/intel/hda-dai.c +++ b/sound/soc/sof/intel/hda-dai.c @@ -282,26 +282,46 @@ static int __maybe_unused hda_dai_hw_params(struct snd_pcm_substream *substream, return hda_dai_hw_params_data(substream, params, dai, &data, flags); } +/* + * The FE retention flag is set by the FE trigger, which may run after the BE trigger, so + * it is only used for an FE which is already suspended. A WoV FE which is retained is + * either running (its SUSPEND is being handled) or already suspended with + * suspend_ignored set and its PCM still prepared. PREPARED or PAUSED PCMs are also + * marked SUSPENDED without a SUSPEND trigger, so suspend_ignored is required for them. + * Its DPCM state is not updated until the trigger completes. + */ +static bool hda_dai_fe_retains(struct snd_sof_dev *sdev, struct snd_soc_pcm_runtime *fe) +{ + const int dir = SNDRV_PCM_STREAM_CAPTURE; + struct snd_pcm_substream *fe_substream; + struct snd_sof_pcm *spcm; + + spcm = snd_sof_find_spcm_dai(sdev->component, fe); + if (!spcm || !spcm->stream[dir].d0i3_compatible || + spcm->stream[dir].dsp_max_burst_size_in_ms > 1) + return false; + + if (fe->dpcm[dir].state == SND_SOC_DPCM_STATE_START) + return true; + + fe_substream = snd_soc_dpcm_get_substream(fe, dir); + + return spcm->stream[dir].suspend_ignored && spcm->prepared[dir] && + 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); struct snd_soc_dpcm *dpcm; - struct snd_sof_pcm *spcm; if (substream->stream != SNDRV_PCM_STREAM_CAPTURE) 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. - */ + /* An idle WoV FE may still be connected to the BE, only retain for an active one. */ 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)) + if (hda_dai_fe_retains(sdev, dpcm->fe)) return true; } @@ -340,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) { @@ -351,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; } @@ -374,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; From 4ef6e4909977ab9f0244db951affa1bbee4be223 Mon Sep 17 00:00:00 2001 From: Bard Liao Date: Thu, 5 Nov 2026 17:07:34 +0800 Subject: [PATCH 07/10] soundwire: intel: Revoke stream retention before deeper system sleep A WoV PCM can remain SUSPENDED after waking from S0ix until userspace recovers it. If the next sleep transition enters S3 or S4 first, ALSA does not send another SUSPEND trigger, leaving the SoundWire stream ENABLED with ignore_suspend set even though the hardware loses its configuration during the deeper transition. The controller suspend-target check alone does not invalidate the stream state. An ENABLED stream makes prepare and RESUME skip port configuration, so the stream must be disabled before powering down. Disable retained SoundWire streams and clear ignore_suspend in Intel link PM prepare when the target is not suspend-to-idle, before the controller and codecs suspend. Use each stream state, retention flag and sleep target to decide whether its Intel DAI needs reinitialization. Keep the component suspend callbacks limited to marking DAI state and preserve the S0ix retention path. Remember the streams disabled in prepare. If the suspend is aborted, re-enable them and restore ignore_suspend, either directly when prepare fails or from the complete callback. The record is dropped once the controller suspend starts. Signed-off-by: Bard Liao --- drivers/soundwire/cadence_master.h | 3 ++ drivers/soundwire/intel.c | 14 ++++-- drivers/soundwire/intel_ace2x.c | 14 ++++-- drivers/soundwire/intel_auxdevice.c | 76 ++++++++++++++++++++++++++++- 4 files changed, 98 insertions(+), 9 deletions(-) diff --git a/drivers/soundwire/cadence_master.h b/drivers/soundwire/cadence_master.h index f4e41a9ab16589..2cba93eea1a79a 100644 --- a/drivers/soundwire/cadence_master.h +++ b/drivers/soundwire/cadence_master.h @@ -89,6 +89,8 @@ struct sdw_cdns_stream_config { * @link_id: Master link id * @suspended: status set when suspended, to be used in .prepare * @paused: status set in .trigger, to be used in suspend + * @retention_revoked: stream retention was revoked in .prepare, to be restored if + * the system suspend is aborted * @direction: stream direction */ struct sdw_cdns_dai_runtime { @@ -100,6 +102,7 @@ struct sdw_cdns_dai_runtime { int link_id; bool suspended; bool paused; + bool retention_revoked; int direction; }; diff --git a/drivers/soundwire/intel.c b/drivers/soundwire/intel.c index 2064e64c2c5455..a159ca8d869a56 100644 --- a/drivers/soundwire/intel.c +++ b/drivers/soundwire/intel.c @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -952,14 +953,19 @@ static int intel_component_dais_suspend(struct snd_soc_component *component) for_each_component_dais(component, dai) { struct sdw_cdns *cdns = snd_soc_dai_get_drvdata(dai); struct sdw_cdns_dai_runtime *dai_runtime; + struct sdw_stream_runtime *stream; - if (sdw_bus_ignore_suspend(&cdns->bus)) + dai_runtime = cdns->dai_runtime_array[dai->id]; + if (!dai_runtime) continue; - dai_runtime = cdns->dai_runtime_array[dai->id]; + stream = dai_runtime->stream; + if (pm_suspend_target_state == PM_SUSPEND_TO_IDLE && + stream && stream->ignore_suspend && + stream->state == SDW_STREAM_ENABLED) + continue; - if (dai_runtime) - dai_runtime->suspended = true; + dai_runtime->suspended = true; } return 0; diff --git a/drivers/soundwire/intel_ace2x.c b/drivers/soundwire/intel_ace2x.c index 717531d737f586..39ffe722812aba 100644 --- a/drivers/soundwire/intel_ace2x.c +++ b/drivers/soundwire/intel_ace2x.c @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -951,14 +952,19 @@ static int intel_component_dais_suspend(struct snd_soc_component *component) for_each_component_dais(component, dai) { struct sdw_cdns *cdns = snd_soc_dai_get_drvdata(dai); struct sdw_cdns_dai_runtime *dai_runtime; + struct sdw_stream_runtime *stream; - if (sdw_bus_ignore_suspend(&cdns->bus)) + dai_runtime = cdns->dai_runtime_array[dai->id]; + if (!dai_runtime) continue; - dai_runtime = cdns->dai_runtime_array[dai->id]; + stream = dai_runtime->stream; + if (pm_suspend_target_state == PM_SUSPEND_TO_IDLE && + stream && stream->ignore_suspend && + stream->state == SDW_STREAM_ENABLED) + continue; - if (dai_runtime) - dai_runtime->suspended = true; + dai_runtime->suspended = true; } return 0; diff --git a/drivers/soundwire/intel_auxdevice.c b/drivers/soundwire/intel_auxdevice.c index 5c1bb08ac9dba9..ba937e03ca3f78 100644 --- a/drivers/soundwire/intel_auxdevice.c +++ b/drivers/soundwire/intel_auxdevice.c @@ -577,13 +577,40 @@ int intel_resume_child_device(struct device *dev, void *data) return 0; } +/* Drop the revoke record, and re-enable the streams if the suspend was aborted. */ +static void intel_retention_finish(struct device *dev, struct sdw_cdns *cdns, bool restore) +{ + int i, ret; + + for (i = 0; i < cdns->pcm.num_pdi; i++) { + struct sdw_cdns_dai_runtime *dai_runtime = cdns->dai_runtime_array[i]; + struct sdw_stream_runtime *stream; + + if (!dai_runtime || !dai_runtime->retention_revoked) + continue; + + dai_runtime->retention_revoked = false; + stream = dai_runtime->stream; + if (!restore || !stream) + continue; + + ret = sdw_enable_stream(stream); + if (ret) { + dev_err(dev, "failed to re-enable retained stream %s: %d\n", + stream->name, ret); + continue; + } + stream->ignore_suspend = true; + } +} + static int __maybe_unused intel_pm_prepare(struct device *dev) { struct sdw_cdns *cdns = dev_get_drvdata(dev); struct sdw_intel *sdw = cdns_to_intel(cdns); struct sdw_bus *bus = &cdns->bus; u32 clock_stop_quirks; - int ret; + int i, ret; if (bus->prop.hw_disabled || !sdw->startup_done) { dev_dbg(dev, "SoundWire master %d is disabled or not-started, ignoring\n", @@ -634,9 +661,52 @@ static int __maybe_unused intel_pm_prepare(struct device *dev) dev_err(dev, "%s: intel_resume_child_device failed: %d\n", __func__, ret); } + /* + * Revoke retention from an earlier S0ix before any controller or codec + * suspends. An already-suspended PCM will not issue another trigger. + */ + if (pm_suspend_target_state != PM_SUSPEND_TO_IDLE) { + for (i = 0; i < cdns->pcm.num_pdi; i++) { + struct sdw_cdns_dai_runtime *dai_runtime = cdns->dai_runtime_array[i]; + struct sdw_stream_runtime *stream; + + if (!dai_runtime || !dai_runtime->stream) + continue; + + stream = dai_runtime->stream; + if (!stream->ignore_suspend) + continue; + + if (stream->state == SDW_STREAM_ENABLED) { + ret = sdw_disable_stream(stream); + if (ret) { + dev_err(dev, "failed to disable retained stream %s: %d\n", + stream->name, ret); + /* .complete is not called if .prepare fails */ + intel_retention_finish(dev, cdns, true); + return ret; + } + dai_runtime->retention_revoked = true; + } + stream->ignore_suspend = false; + } + } + return 0; } +static void __maybe_unused intel_pm_complete(struct device *dev) +{ + struct sdw_cdns *cdns = dev_get_drvdata(dev); + struct sdw_intel *sdw = cdns_to_intel(cdns); + + if (cdns->bus.prop.hw_disabled || !sdw->startup_done) + return; + + /* intel_suspend() drops the record, so it is still set only if the suspend was aborted */ + intel_retention_finish(dev, cdns, true); +} + static int __maybe_unused intel_suspend(struct device *dev) { struct sdw_cdns *cdns = dev_get_drvdata(dev); @@ -651,6 +721,9 @@ static int __maybe_unused intel_suspend(struct device *dev) return 0; } + /* The suspend goes ahead, the streams are restarted by the PCM RESUME trigger. */ + intel_retention_finish(dev, cdns, false); + /* * ignore_suspend is set by the PCM trigger for S0ix and may remain set * if a later sleep transition does not issue another PCM trigger. @@ -875,6 +948,7 @@ static int __maybe_unused intel_resume_runtime(struct device *dev) static const struct dev_pm_ops intel_pm = { .prepare = intel_pm_prepare, + .complete = intel_pm_complete, SET_SYSTEM_SLEEP_PM_OPS(intel_suspend, intel_resume) SET_RUNTIME_PM_OPS(intel_suspend_runtime, intel_resume_runtime, NULL) }; From 3c426e090da11309a7ae89ae1ae33126dfab9576 Mon Sep 17 00:00:00 2001 From: Bard Liao Date: Thu, 5 Nov 2026 21:09:28 +0800 Subject: [PATCH 08/10] ASoC: SOF: Release retained FE and HDA state before deeper sleep A WoV PCM can remain SUSPENDED after waking from S0ix until userspace recovers it. If the next sleep transition enters S3 or S4 first, ALSA does not send another SUSPEND trigger. The FE and HDA BE can therefore keep suspend_ignored although the deeper transition resets the DSP and link DMA hardware. A retained FE pipeline still owes the STOP it skipped at SUSPEND. Its started_count stays non-zero, which makes the IPC4 RESET and later RUNNING transitions skip the pipeline. Clearing the flag before teardown would lose this information. Pay the skipped STOP in sof_pcm_stream_free() and clear the flag there. Include retained streams in sof_pcm_free_all_streams(), which only runs when the DSP is not staying in D0. If the STOP fails, still free the stream so the flag cannot outlive the firmware state. A retained stream that is dropped while SUSPENDED never receives a STOP trigger. PREPARE cannot tell this from a normal recovery because ALSA moves a SUSPENDED PCM to SETUP before calling it, and the pipeline is still running in both cases. Keep the stream in PREPARE and let START skip it. Clear the flag on STOP, PAUSE and close. The retained HDA BE has the same problem: its pipeline is still running and its started_count is non-zero, but the FE STOP does not touch it and its topology is freed before the DSP is suspended. Add a release_retained_streams DSP op and call it from sof_suspend() before the pipelines are torn down. The HDA implementation runs the whole BE SUSPEND sequence (pre_trigger, trigger and post_trigger), so the pipeline is paused, the link DMA stopped and started_count cleared before the FE resets the pipeline, and then clears the BE retention flag so subsequent BE triggers cannot mistake reset hardware for a still-running stream. Preserve the S0ix WoV retention path. Signed-off-by: Bard Liao --- sound/soc/sof/intel/hda-common-ops.c | 1 + sound/soc/sof/intel/hda-dai.c | 63 ++++++++++++++++++++++++++++ sound/soc/sof/intel/hda-dsp.c | 1 - sound/soc/sof/intel/hda.h | 1 + sound/soc/sof/ops.h | 7 ++++ sound/soc/sof/pcm.c | 27 ++++++++++-- sound/soc/sof/pm.c | 9 ++++ sound/soc/sof/sof-priv.h | 1 + 8 files changed, 105 insertions(+), 5 deletions(-) diff --git a/sound/soc/sof/intel/hda-common-ops.c b/sound/soc/sof/intel/hda-common-ops.c index aa91f2b6fd657b..b39ed71582cf7f 100644 --- a/sound/soc/sof/intel/hda-common-ops.c +++ b/sound/soc/sof/intel/hda-common-ops.c @@ -103,6 +103,7 @@ const struct snd_sof_dsp_ops sof_hda_common_ops = { .runtime_resume = hda_dsp_runtime_resume, .runtime_idle = hda_dsp_runtime_idle, .set_hw_params_upon_resume = hda_dsp_set_hw_params_upon_resume, + .release_retained_streams = hda_dsp_release_retained_streams, /* ALSA HW info flags */ .hw_info = SNDRV_PCM_INFO_MMAP | diff --git a/sound/soc/sof/intel/hda-dai.c b/sound/soc/sof/intel/hda-dai.c index 42cad93afdb128..1fc578e683e93f 100644 --- a/sound/soc/sof/intel/hda-dai.c +++ b/sound/soc/sof/intel/hda-dai.c @@ -812,6 +812,60 @@ static int hda_dai_suspend(struct hdac_bus *bus) return 0; } +/* + * A retained WoV BE gets no SUSPEND trigger when its PCM stays suspended into a + * D3 transition, so run the SUSPEND sequence here, before its pipeline is freed. + */ +static int hda_dai_release_retained_streams(struct snd_sof_dev *sdev) +{ + struct hdac_bus *bus = sof_to_bus(sdev); + bool skip_fw_ipc = sdev->fw_state != SOF_FW_BOOT_COMPLETE; + struct hdac_stream *s; + int ret; + + list_for_each_entry(s, &bus->stream_list, list) { + struct hdac_ext_stream *hext_stream = stream_to_hdac_ext_stream(s); + const struct hda_dai_widget_dma_ops *ops; + struct sof_intel_hda_stream *hda_stream; + struct snd_pcm_substream *substream; + struct snd_soc_dai *cpu_dai; + + hda_stream = hstream_to_sof_hda_stream(hext_stream); + substream = hext_stream->link_substream; + if (!hda_stream->suspend_ignored || !substream) + continue; + + cpu_dai = snd_soc_rtd_to_cpu(snd_soc_substream_to_rtd(substream), 0); + ops = hda_dai_get_ops(substream, cpu_dai); + if (!ops) + return -EINVAL; + + if (ops->pre_trigger && !skip_fw_ipc) { + ret = ops->pre_trigger(sdev, cpu_dai, substream, SNDRV_PCM_TRIGGER_SUSPEND); + if (ret < 0) + return ret; + } + + if (ops->trigger) { + ret = ops->trigger(sdev, cpu_dai, substream, SNDRV_PCM_TRIGGER_SUSPEND); + if (ret < 0) + return ret; + } + + if (ops->post_trigger) { + ret = ops->post_trigger(sdev, cpu_dai, substream, SNDRV_PCM_TRIGGER_SUSPEND); + if (ret < 0) + return ret; + } + + ret = hda_link_dma_cleanup(substream, hext_stream, cpu_dai, true); + if (ret < 0) + return ret; + } + + return 0; +} + static void ssp_set_dai_drv_ops(struct snd_sof_dev *sdev, struct snd_sof_dsp_ops *ops) { const struct sof_intel_dsp_desc *chip; @@ -1085,3 +1139,12 @@ int hda_dsp_dais_suspend(struct snd_sof_dev *sdev) return 0; } + +int hda_dsp_release_retained_streams(struct snd_sof_dev *sdev) +{ +#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_LINK) + return hda_dai_release_retained_streams(sdev); +#else + return 0; +#endif +} diff --git a/sound/soc/sof/intel/hda-dsp.c b/sound/soc/sof/intel/hda-dsp.c index ec49aeaa9bd7af..43a18ba4f6b785 100644 --- a/sound/soc/sof/intel/hda-dsp.c +++ b/sound/soc/sof/intel/hda-dsp.c @@ -852,7 +852,6 @@ static int hda_suspend(struct snd_sof_dev *sdev, bool runtime_suspend) hda_dsp_ctrl_ppcap_enable(sdev, false); hda_dsp_ctrl_ppcap_int_enable(sdev, false); skip_dsp: - /* disable hda bus irq and streams */ hda_dsp_ctrl_stop_chip(sdev); diff --git a/sound/soc/sof/intel/hda.h b/sound/soc/sof/intel/hda.h index bc11519427649f..b3ef9aa68d2d74 100644 --- a/sound/soc/sof/intel/hda.h +++ b/sound/soc/sof/intel/hda.h @@ -956,6 +956,7 @@ int hda_data_stream_cleanup(struct device *dev, struct snd_dma_buffer *dmab, /* common dai driver */ extern struct snd_soc_dai_driver skl_dai[]; int hda_dsp_dais_suspend(struct snd_sof_dev *sdev); +int hda_dsp_release_retained_streams(struct snd_sof_dev *sdev); /* * Platform Specific HW abstraction Ops. diff --git a/sound/soc/sof/ops.h b/sound/soc/sof/ops.h index 19605de680a319..d3f3c46b14d3ca 100644 --- a/sound/soc/sof/ops.h +++ b/sound/soc/sof/ops.h @@ -276,6 +276,13 @@ static inline int snd_sof_dsp_hw_params_upon_resume(struct snd_sof_dev *sdev) return 0; } +static inline int snd_sof_dsp_release_retained_streams(struct snd_sof_dev *sdev) +{ + if (sof_ops(sdev)->release_retained_streams) + return sof_ops(sdev)->release_retained_streams(sdev); + return 0; +} + static inline int snd_sof_dsp_set_clk(struct snd_sof_dev *sdev, u32 freq) { if (sof_ops(sdev)->set_clk) diff --git a/sound/soc/sof/pcm.c b/sound/soc/sof/pcm.c index 0161c0ae8bbdb4..79df47558a2fb9 100644 --- a/sound/soc/sof/pcm.c +++ b/sound/soc/sof/pcm.c @@ -216,6 +216,19 @@ static int sof_pcm_stream_free(struct snd_sof_dev *sdev, int err = 0; if (spcm->prepared[substream->stream]) { + if (spcm->stream[dir].suspend_ignored && pcm_ops && pcm_ops->trigger) { + ret = pcm_ops->trigger(sdev->component, substream, spcm, + SNDRV_PCM_TRIGGER_STOP, dir); + if (ret < 0) { + spcm_err(spcm, substream->stream, + "retained stream stop failed %d\n", ret); + err = ret; + } + if (!pcm_ops->platform_stop_during_hw_free) + snd_sof_pcm_platform_trigger(sdev, substream, + SNDRV_PCM_TRIGGER_STOP); + } + /* stop DMA first if needed */ if (pcm_ops && pcm_ops->platform_stop_during_hw_free) snd_sof_pcm_platform_trigger(sdev, substream, @@ -236,6 +249,8 @@ static int sof_pcm_stream_free(struct snd_sof_dev *sdev, spcm->pending_stop[substream->stream] = false; } + spcm->stream[dir].suspend_ignored = false; + /* reset the DMA */ ret = snd_sof_pcm_platform_hw_free(sdev, substream); if (ret < 0) { @@ -269,8 +284,7 @@ int sof_pcm_free_all_streams(struct snd_sof_dev *sdev) for_each_pcm_streams(dir) { substream = spcm->stream[dir].substream; - if (!substream || !substream->runtime || - spcm->stream[dir].suspend_ignored) + if (!substream || !substream->runtime) continue; if (spcm->stream[dir].list) { @@ -337,12 +351,12 @@ static int sof_pcm_prepare(struct snd_soc_component *component, spcm_dbg(spcm, substream->stream, "Entry: prepare\n"); if (spcm->prepared[substream->stream]) { + /* a retained stream is still running, nothing to rebuild */ if (!spcm->pending_stop[substream->stream]) return 0; /* - * this case should be reached in case of xruns where we absolutely - * want to free-up and reset all PCM/DMA resources + * Reset PCM/DMA resources after an xrun. */ ret = sof_pcm_stream_free(sdev, substream, spcm, substream->stream, true); if (ret < 0) @@ -499,6 +513,10 @@ static int sof_pcm_trigger(struct snd_soc_component *component, break; } + if (!ret && (cmd == SNDRV_PCM_TRIGGER_STOP || + cmd == SNDRV_PCM_TRIGGER_PAUSE_PUSH)) + spcm->stream[substream->stream].suspend_ignored = false; + /* free PCM if reset_hw_params is set and the STOP IPC is successful */ if (!ret && reset_hw_params) ret = sof_pcm_stream_free(sdev, substream, spcm, substream->stream, false); @@ -636,6 +654,7 @@ static int sof_pcm_close(struct snd_soc_component *component, */ } + spcm->stream[substream->stream].suspend_ignored = false; spcm->stream[substream->stream].substream = NULL; return 0; diff --git a/sound/soc/sof/pm.c b/sound/soc/sof/pm.c index b5d239b12e59d2..30f817a411f9da 100644 --- a/sound/soc/sof/pm.c +++ b/sound/soc/sof/pm.c @@ -243,6 +243,15 @@ static int sof_suspend(struct device *dev, bool runtime_suspend) if (runtime_suspend && !sof_ops(sdev)->runtime_suspend) return 0; + /* A retained stream must be stopped while its pipelines still exist. */ + if (!runtime_suspend && old_state == SOF_DSP_PM_D0 && target_state != SOF_DSP_PM_D0) { + ret = snd_sof_dsp_release_retained_streams(sdev); + if (ret < 0) { + dev_err(sdev->dev, "error: releasing retained streams %d\n", ret); + return ret; + } + } + /* we need to tear down pipelines only if the DSP hardware is * active, which happens for PCI devices. if the device is * suspended, it is brought back to full power and then diff --git a/sound/soc/sof/sof-priv.h b/sound/soc/sof/sof-priv.h index 83e4d83cc27cbb..1e3d13c469168f 100644 --- a/sound/soc/sof/sof-priv.h +++ b/sound/soc/sof/sof-priv.h @@ -319,6 +319,7 @@ struct snd_sof_dsp_ops { int (*runtime_resume)(struct snd_sof_dev *sof_dev); /* optional */ int (*runtime_idle)(struct snd_sof_dev *sof_dev); /* optional */ int (*set_hw_params_upon_resume)(struct snd_sof_dev *sdev); /* optional */ + int (*release_retained_streams)(struct snd_sof_dev *sdev); /* optional */ int (*set_power_state)(struct snd_sof_dev *sdev, const struct sof_dsp_power_state *target_state); /* optional */ From 8b4aba6de7a064dfc5d703b33200006f5566f3ca Mon Sep 17 00:00:00 2001 From: Bard Liao Date: Thu, 5 Nov 2026 13:40:17 +0800 Subject: [PATCH 09/10] ASoC: dapm: Honor FE ignore_suspend for shared BE A BE may be shared by multiple FEs. Some FEs need to remain active during system suspend while others do not. Keep a BE DAI endpoint active if it is connected to an active FE DAI with ignore_suspend set. This lets drivers mark only the FE DAI instead of retaining the shared BE for every connected FE. Signed-off-by: Bard Liao --- sound/soc/soc-dapm.c | 49 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 47 insertions(+), 2 deletions(-) diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c index 74be5e20949337..c28d9e1f68bf1a 100644 --- a/sound/soc/soc-dapm.c +++ b/sound/soc/soc-dapm.c @@ -36,6 +36,7 @@ #include #include #include +#include #include #include "soc-internal.h" @@ -1415,18 +1416,62 @@ static int dapm_new_dai_link(struct snd_soc_dapm_widget *w) * the ALSA card - when we are suspending the ALSA state for the card * is set to D3. */ +static bool dapm_be_has_active_ignore_suspend_fe(struct snd_soc_dapm_widget *widget) +{ + struct snd_soc_pcm_runtime *be; + struct snd_soc_dpcm *dpcm; + struct snd_soc_dai *be_dai, *fe_dai; + struct snd_soc_dapm_widget *fe_widget; + int i, j, stream; + + if (widget->id == snd_soc_dapm_dai_in) + stream = SNDRV_PCM_STREAM_PLAYBACK; + else if (widget->id == snd_soc_dapm_dai_out) + stream = SNDRV_PCM_STREAM_CAPTURE; + else + return false; + + /* DPCM client links are stable while the card is in system suspend. */ + for_each_card_rtds(widget->dapm->card, be) { + if (!be->dai_link->no_pcm) + continue; + + for_each_rtd_dais(be, i, be_dai) { + if (snd_soc_dai_get_widget(be_dai, stream) != widget) + continue; + + for_each_dpcm_fe(be, stream, dpcm) { + if (dpcm->state == SND_SOC_DPCM_LINK_STATE_FREE) + continue; + + for_each_rtd_cpu_dais(dpcm->fe, j, fe_dai) { + fe_widget = snd_soc_dai_get_widget(fe_dai, stream); + if (fe_widget && fe_widget->active && + fe_widget->ignore_suspend) + return true; + } + } + } + } + + return false; +} + static int dapm_suspend_check(struct snd_soc_dapm_widget *widget) { struct device *dev = snd_soc_dapm_to_dev(widget->dapm); int level = snd_power_get_state(widget->dapm->card->snd_card); + bool ignore_suspend; switch (level) { case SNDRV_CTL_POWER_D3hot: case SNDRV_CTL_POWER_D3cold: - if (widget->ignore_suspend) + ignore_suspend = widget->ignore_suspend || + dapm_be_has_active_ignore_suspend_fe(widget); + if (ignore_suspend) dev_dbg(dev, "ASoC: %s ignoring suspend\n", widget->name); - return widget->ignore_suspend; + return ignore_suspend; default: return 1; } From dcb174260bbe2563707ae00f1d1c734dee57150f Mon Sep 17 00:00:00 2001 From: Bard Liao Date: Thu, 5 Nov 2026 14:12:44 +0800 Subject: [PATCH 10/10] ASoC: SOF: Mark WoV DAPM widgets to ignore suspend Keep the DAPM capture path of a WoV stream powered during S0ix. Find the microphone endpoint from the FE capture path instead of assuming a card widget named DMIC, since cards may name it differently. Mark the FE DAI widget and the endpoint when the WoV stream is suspended for S0ix, so the lookup runs while the path is connected. If no endpoint is found, only an error is logged and the stream is still retained, so the FE and BE keep the same retention decision. Signed-off-by: Bard Liao --- sound/soc/sof/pcm.c | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/sound/soc/sof/pcm.c b/sound/soc/sof/pcm.c index 79df47558a2fb9..1c3ee8511b5e62 100644 --- a/sound/soc/sof/pcm.c +++ b/sound/soc/sof/pcm.c @@ -66,6 +66,37 @@ void snd_sof_pcm_period_elapsed(struct snd_pcm_substream *substream) } EXPORT_SYMBOL(snd_sof_pcm_period_elapsed); +static int sof_pcm_set_dai_ignore_suspend(struct snd_soc_pcm_runtime *rtd, int dir) +{ + struct snd_soc_dai *dai = snd_soc_rtd_to_cpu(rtd, 0); + struct snd_soc_dapm_widget *widget = snd_soc_dai_get_widget(dai, dir); + struct snd_soc_dapm_widget_list *list; + int i, ret; + + if (!widget) + return -ENODEV; + + ret = snd_soc_dapm_dai_get_connected_widgets(dai, dir, &list, NULL); + if (ret < 0) + return ret; + + ret = -ENODEV; + snd_soc_dapm_mutex_lock(rtd->card); + for (i = 0; i < list->num_widgets; i++) { + if (list->widgets[i]->is_ep & SND_SOC_DAPM_EP_SOURCE) { + list->widgets[i]->ignore_suspend = 1; + ret = 0; + } + } + if (!ret) + widget->ignore_suspend = 1; + snd_soc_dapm_mutex_unlock(rtd->card); + + snd_soc_dapm_dai_free_widgets(&list); + + 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, @@ -462,6 +493,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) { + ret = sof_pcm_set_dai_ignore_suspend(rtd, substream->stream); + if (ret < 0) + spcm_err(spcm, substream->stream, + "failed to retain DAPM widgets for WoV: %d\n", ret); + spcm->stream[substream->stream].suspend_ignored = true; return 0; }