From 8864bc16bd793d959a21f48d3da4fdba9f6b1196 Mon Sep 17 00:00:00 2001 From: Bard Liao Date: Tue, 6 Oct 2026 21:56:12 +0800 Subject: [PATCH 1/5] 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 | 18 ++++++++++++++++++ include/linux/soundwire/sdw.h | 3 +++ 2 files changed, 21 insertions(+) diff --git a/drivers/soundwire/stream.c b/drivers/soundwire/stream.c index b4f68356708fe4..d46ae22957789a 100644 --- a/drivers/soundwire/stream.c +++ b/drivers/soundwire/stream.c @@ -1679,6 +1679,24 @@ 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->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); From c7e79200f8df6a622934adb2a2e419f3db7c9073 Mon Sep 17 00:00:00 2001 From: Bard Liao Date: Tue, 6 Oct 2026 22:06:50 +0800 Subject: [PATCH 2/5] 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 | 9 +++++++++ 3 files changed, 15 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 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); From a5766305da5f4bf8949430a747b576227bee578b Mon Sep 17 00:00:00 2001 From: Bard Liao Date: Tue, 6 Oct 2026 21:49:39 +0800 Subject: [PATCH 3/5] 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 14aee10b40ba7c..e3ee8f385952f0 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 dd330e7d0a0d2ba40577c0650563429d30fdba2d Mon Sep 17 00:00:00 2001 From: Bard Liao Date: Tue, 6 Oct 2026 22:10:20 +0800 Subject: [PATCH 4/5] 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 indetified by: They are capture streams and. They have the d0i3_compatible flag set and They don't use Deep Buffer. Signed-off-by: Bard Liao --- sound/soc/sof/intel/hda.c | 56 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/sound/soc/sof/intel/hda.c b/sound/soc/sof/intel/hda.c index e1815bfe358b5d..40f401fae32472 100644 --- a/sound/soc/sof/intel/hda.c +++ b/sound/soc/sof/intel/hda.c @@ -58,6 +58,51 @@ static int sdw_clock_stop_quirks = SDW_INTEL_CLK_STOP_BUS_RESET; module_param(sdw_clock_stop_quirks, int, 0444); MODULE_PARM_DESC(sdw_clock_stop_quirks, "SOF SoundWire clock stop quirks"); +static bool sdw_be_ignore_suspend(struct snd_sof_dev *sdev, + struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *be = snd_soc_substream_to_rtd(substream); + struct snd_soc_dpcm *dpcm; + int dir = substream->stream; + + if (dir != SNDRV_PCM_STREAM_CAPTURE) + return false; + + for_each_dpcm_fe(be, dir, dpcm) { + struct snd_soc_pcm_runtime *fe = dpcm->fe; + struct snd_pcm_substream *fe_substream; + struct snd_sof_pcm *spcm; + + if (dpcm->state == SND_SOC_DPCM_LINK_STATE_FREE) + continue; + + fe_substream = snd_soc_dpcm_get_substream(fe, dir); + if (!fe_substream || !fe_substream->runtime) + continue; + + list_for_each_entry(spcm, &sdev->pcm_list, list) { + if (!strcmp(spcm->pcm.pcm_name, fe->dai_link->name)) + break; + } + + if (!list_entry_is_head(spcm, &sdev->pcm_list, list) && + spcm->stream[dir].d0i3_compatible && + spcm->stream[dir].dsp_max_burst_size_in_ms <= 1) + return true; + } + + return false; +} + +static void sdw_set_stream_ignore_suspend(struct snd_sof_dev *sdev, struct snd_soc_dai *dai, + struct snd_pcm_substream *substream) +{ + struct sdw_stream_runtime *sdw_stream = snd_soc_dai_get_stream(dai, substream->stream); + + if (!IS_ERR_OR_NULL(sdw_stream)) + sdw_stream->ignore_suspend = sdw_be_ignore_suspend(sdev, substream); +} + static int sdw_params_stream(struct device *dev, struct sdw_intel_stream_params_data *params_data) { @@ -70,6 +115,9 @@ static int sdw_params_stream(struct device *dev, d->name); return -EINVAL; } + + sdw_set_stream_ignore_suspend(widget_to_sdev(w), d, params_data->substream); + data.dai_index = (params_data->link_id << 8) | d->id; data.dai_data = params_data->alh_stream_id; data.dai_node_id = data.dai_data; @@ -108,6 +156,14 @@ struct sdw_intel_ops sdw_callback = { static int sdw_ace2x_params_stream(struct device *dev, struct sdw_intel_stream_params_data *params_data) { + struct snd_soc_dai *d = params_data->dai; + struct snd_soc_dapm_widget *w = snd_soc_dai_get_widget(d, params_data->substream->stream); + + if (!w) + return -EINVAL; + + sdw_set_stream_ignore_suspend(widget_to_sdev(w), d, params_data->substream); + return sdw_hda_dai_hw_params(params_data->substream, params_data->hw_params, params_data->dai, From 191e95318940c1845e5349678e989fe6478dfed2 Mon Sep 17 00:00:00 2001 From: Bard Liao Date: Fri, 2 Oct 2026 17:42:38 +0800 Subject: [PATCH 5/5] ASoC: SOF: preserve DAPM widgets for WoV PCMs The SOF DSP remains active for wake on voice streams, but DAPM may still power down widgets on the PCM path. Mark the connected endpoint widgets to ignore suspend so DAPM keeps the path powered. Signed-off-by: Bard Liao --- sound/soc/sof/pcm.c | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/sound/soc/sof/pcm.c b/sound/soc/sof/pcm.c index 42738f12fa3390..f3692085ece9fc 100644 --- a/sound/soc/sof/pcm.c +++ b/sound/soc/sof/pcm.c @@ -66,6 +66,41 @@ void snd_sof_pcm_period_elapsed(struct snd_pcm_substream *substream) } EXPORT_SYMBOL(snd_sof_pcm_period_elapsed); +static void 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_soc_dai *dai; + int j; + + 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) { + for_each_dapm_widgets(connected_widgets, i, widget) { + 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); + widget->ignore_suspend = ignore_suspend; + } + snd_soc_dapm_dai_free_widgets(&connected_widgets); + } else { + dev_dbg(sdev->dev, + "failed to enumerate D0i3-compatible PCM %s widgets: %d\n", + spcm->pcm.caps[dir].name, connected); + } + } +} + 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, @@ -422,6 +457,7 @@ static int sof_pcm_trigger(struct snd_soc_component *component, * 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; } @@ -437,6 +473,8 @@ static int sof_pcm_trigger(struct snd_soc_component *component, sdev->system_suspend_target == SOF_SUSPEND_S0IX && spcm->stream[substream->stream].d0i3_compatible) { spcm->stream[substream->stream].suspend_ignored = true; + /* Set ignore_suspend to the DAPM widgets */ + sof_pcm_set_widget_ignore_suspend(sdev, rtd, spcm, substream->stream, true); return 0; }