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..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); 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; 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, 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; }