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..75d5e4cf876cb2 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; diff --git a/sound/soc/soc-pcm.c b/sound/soc/soc-pcm.c index ef8ccf52798eb6..dc589211775b10 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; @@ -2502,6 +2510,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 +2519,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-common-ops.c b/sound/soc/sof/intel/hda-common-ops.c index aa91f2b6fd657b..f65b0a34fe3b6d 100644 --- a/sound/soc/sof/intel/hda-common-ops.c +++ b/sound/soc/sof/intel/hda-common-ops.c @@ -54,6 +54,7 @@ const struct snd_sof_dsp_ops sof_hda_common_ops = { .pcm_hw_params = hda_dsp_pcm_hw_params, .pcm_hw_free = hda_dsp_stream_hw_free, .pcm_trigger = hda_dsp_pcm_trigger, + .pcm_set_sdw_ignore_suspend = hda_dsp_set_sdw_ignore_suspend, .pcm_pointer = hda_dsp_pcm_pointer, .pcm_ack = hda_dsp_pcm_ack, diff --git a/sound/soc/sof/intel/hda-dai.c b/sound/soc/sof/intel/hda-dai.c index 7a8875a885ab34..0090f689f48417 100644 --- a/sound/soc/sof/intel/hda-dai.c +++ b/sound/soc/sof/intel/hda-dai.c @@ -336,21 +336,33 @@ static int __maybe_unused hda_dai_trigger(struct snd_pcm_substream *substream, i 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_RESUME || cmd == SNDRV_PCM_TRIGGER_START) && + hda_stream->suspend_ignored) { hda_stream->suspend_ignored = false; return 0; } + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE && + snd_soc_substream_to_rtd(substream)->dai_link->no_pcm && + cmd == SNDRV_PCM_TRIGGER_STOP) + hda_dsp_set_sdw_ignore_suspend(sdev, substream, false); + /* * Set the suspend_ignored flag for BE which is used with * D0I3-compatible streams used for WoV to keep the firmware pipeline * running. */ - if (cmd == SNDRV_PCM_TRIGGER_SUSPEND && - sdev->system_suspend_target == SOF_SUSPEND_S0IX && - hda_dai_has_wov_fe(sdev, substream)) { - hda_stream->suspend_ignored = true; - return 0; + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE && + snd_soc_substream_to_rtd(substream)->dai_link->no_pcm && + cmd == SNDRV_PCM_TRIGGER_SUSPEND) { + bool ignore_suspend = sdev->system_suspend_target == SOF_SUSPEND_S0IX && + hda_dai_has_wov_fe(sdev, substream); + + hda_dsp_set_sdw_ignore_suspend(sdev, substream, ignore_suspend); + if (ignore_suspend) { + hda_stream->suspend_ignored = true; + return 0; + } } if (ops->pre_trigger) { diff --git a/sound/soc/sof/intel/hda.c b/sound/soc/sof/intel/hda.c index e1815bfe358b5d..6bbae46800e8b0 100644 --- a/sound/soc/sof/intel/hda.c +++ b/sound/soc/sof/intel/hda.c @@ -58,6 +58,61 @@ 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 void hda_dsp_set_sdw_be_ignore_suspend(struct snd_sof_dev *sdev, + struct snd_soc_pcm_runtime *be, + int dir, bool ignore_suspend) +{ + struct snd_soc_dai *dai; + int i; + + for_each_rtd_cpu_dais(be, i, dai) { + struct snd_soc_dapm_widget *w; + struct snd_sof_widget *swidget; + struct snd_sof_dai *sof_dai; + struct sdw_stream_runtime *sdw_stream; + + w = snd_soc_dai_get_widget(dai, dir); + if (!w || !w->dobj.private) + continue; + + swidget = w->dobj.private; + sof_dai = swidget->private; + if (!sof_dai || sof_dai->type != SOF_DAI_INTEL_ALH) + continue; + + sdw_stream = snd_soc_dai_get_stream(dai, dir); + if (!IS_ERR_OR_NULL(sdw_stream)) { + dev_dbg(sdev->dev, + "WoV-state: BE=%s target=%d ignore_suspend=%d->%d sdw_state=%d\n", + be->dai_link->name, sdev->system_suspend_target, + sdw_stream->ignore_suspend, ignore_suspend, sdw_stream->state); + sdw_stream->ignore_suspend = ignore_suspend; + } + } +} + +void hda_dsp_set_sdw_ignore_suspend(struct snd_sof_dev *sdev, + struct snd_pcm_substream *substream, + bool ignore_suspend) +{ + struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); + struct snd_soc_dpcm *be_link; + int dir = substream->stream; + + if (dir != SNDRV_PCM_STREAM_CAPTURE) + return; + + if (rtd->dai_link->no_pcm) { + hda_dsp_set_sdw_be_ignore_suspend(sdev, rtd, dir, ignore_suspend); + return; + } + + snd_soc_dpcm_mutex_assert_held(rtd); + + for_each_dpcm_be(rtd, dir, be_link) + hda_dsp_set_sdw_be_ignore_suspend(sdev, be_link->be, dir, ignore_suspend); +} + static int sdw_params_stream(struct device *dev, struct sdw_intel_stream_params_data *params_data) { @@ -416,6 +471,10 @@ static inline bool hda_dsp_sdw_check_mic_privacy_irq(struct snd_sof_dev *sdev) static inline void hda_dsp_sdw_process_mic_privacy(struct snd_sof_dev *sdev) { } +void hda_dsp_set_sdw_ignore_suspend(struct snd_sof_dev *sdev, + struct snd_pcm_substream *substream, + bool ignore_suspend) { } + #endif /* IS_ENABLED(CONFIG_SND_SOC_SOF_INTEL_SOUNDWIRE) */ /* pre fw run operations */ diff --git a/sound/soc/sof/intel/hda.h b/sound/soc/sof/intel/hda.h index bc11519427649f..1baed0b706b7da 100644 --- a/sound/soc/sof/intel/hda.h +++ b/sound/soc/sof/intel/hda.h @@ -680,6 +680,9 @@ int hda_dsp_stream_hw_free(struct snd_sof_dev *sdev, struct snd_pcm_substream *substream); int hda_dsp_pcm_trigger(struct snd_sof_dev *sdev, struct snd_pcm_substream *substream, int cmd); +void hda_dsp_set_sdw_ignore_suspend(struct snd_sof_dev *sdev, + struct snd_pcm_substream *substream, + bool ignore_suspend); snd_pcm_uframes_t hda_dsp_pcm_pointer(struct snd_sof_dev *sdev, struct snd_pcm_substream *substream); int hda_dsp_pcm_ack(struct snd_sof_dev *sdev, struct snd_pcm_substream *substream); diff --git a/sound/soc/sof/ops.h b/sound/soc/sof/ops.h index 19605de680a319..57014aadc849e9 100644 --- a/sound/soc/sof/ops.h +++ b/sound/soc/sof/ops.h @@ -544,6 +544,15 @@ snd_sof_pcm_platform_trigger(struct snd_sof_dev *sdev, return 0; } +static inline void +snd_sof_pcm_platform_set_sdw_ignore_suspend(struct snd_sof_dev *sdev, + struct snd_pcm_substream *substream, + bool ignore_suspend) +{ + if (sof_ops(sdev) && sof_ops(sdev)->pcm_set_sdw_ignore_suspend) + sof_ops(sdev)->pcm_set_sdw_ignore_suspend(sdev, substream, ignore_suspend); +} + /* Firmware loading. If the fw_filename argument is NULL, the firmware file set in the * default profile will be loaded. Otherwise, it must contain the full path to the file. */ diff --git a/sound/soc/sof/pcm.c b/sound/soc/sof/pcm.c index 0161c0ae8bbdb4..ec6ee6584d4bc0 100644 --- a/sound/soc/sof/pcm.c +++ b/sound/soc/sof/pcm.c @@ -66,6 +66,153 @@ 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); + 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); + sof_pcm_update_widget_ignore_suspend(sdev, stream, widget, &ret); + if (ret) + break; + } + if (ret) + sof_pcm_release_widget_ignore_suspend(sdev, stream); + snd_soc_dapm_mutex_unlock(rtd->card); + snd_soc_dapm_dai_free_widgets(&connected_widgets); + if (ret) + break; + } + + if (!ret) + 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, @@ -259,6 +406,54 @@ static int sof_pcm_stream_free(struct snd_sof_dev *sdev, return err; } +void sof_pcm_update_sdw_ignore_suspend(struct snd_sof_dev *sdev) +{ + struct snd_soc_card *card; + struct snd_sof_pcm *spcm; + + if (!sdev->component || !sof_ops(sdev)->pcm_set_sdw_ignore_suspend) + return; + + card = sdev->component->card; + if (!card || !snd_soc_card_is_instantiated(card)) + return; + + snd_soc_dpcm_mutex_lock(card); + /* Clear every connected BE before applying the union of active WoV routes. */ + list_for_each_entry(spcm, &sdev->pcm_list, list) { + struct snd_pcm_substream *substream = + spcm->stream[SNDRV_PCM_STREAM_CAPTURE].substream; + + if (substream) + snd_sof_pcm_platform_set_sdw_ignore_suspend(sdev, substream, false); + } + + if (sdev->system_suspend_target != SOF_SUSPEND_S0IX) + goto out; + + list_for_each_entry(spcm, &sdev->pcm_list, list) { + struct snd_sof_pcm_stream *stream = &spcm->stream[SNDRV_PCM_STREAM_CAPTURE]; + struct snd_pcm_substream *substream = stream->substream; + unsigned long flags; + + if (!substream || !stream->d0i3_compatible || + stream->dsp_max_burst_size_in_ms > 1) + continue; + + snd_pcm_stream_lock_irqsave(substream, flags); + if (substream->runtime && (snd_pcm_running(substream) || stream->suspend_ignored)) { + dev_dbg(sdev->dev, + "WoV-state: PM retain PCM=%s target=%d suspended=%d\n", + spcm->pcm.caps[SNDRV_PCM_STREAM_CAPTURE].name, + sdev->system_suspend_target, stream->suspend_ignored); + snd_sof_pcm_platform_set_sdw_ignore_suspend(sdev, substream, true); + } + snd_pcm_stream_unlock_irqrestore(substream, flags); + } +out: + snd_soc_dpcm_mutex_unlock(card); +} + int sof_pcm_free_all_streams(struct snd_sof_dev *sdev) { struct snd_pcm_substream *substream; @@ -303,6 +498,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 */ @@ -421,7 +618,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 +628,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 +647,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 +829,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/pm.c b/sound/soc/sof/pm.c index b5d239b12e59d2..54ee21c32bd95b 100644 --- a/sound/soc/sof/pm.c +++ b/sound/soc/sof/pm.c @@ -388,10 +388,10 @@ int snd_sof_prepare(struct device *dev) */ if (sdev->fw_state == SOF_FW_CRASHED || sdev->fw_state == SOF_FW_BOOT_FAILED) - return 0; + goto update_sdw; if (!desc->use_acpi_target_states) - return 0; + goto update_sdw; #if defined(CONFIG_ACPI) switch (acpi_target_system_state()) { @@ -414,6 +414,10 @@ int snd_sof_prepare(struct device *dev) } #endif +update_sdw: + /* PRE-ordered capture backends receive SUSPEND before their frontends. */ + sof_pcm_update_sdw_ignore_suspend(sdev); + return 0; } EXPORT_SYMBOL(snd_sof_prepare); @@ -423,5 +427,7 @@ void snd_sof_complete(struct device *dev) struct snd_sof_dev *sdev = dev_get_drvdata(dev); sdev->system_suspend_target = SOF_SUSPEND_NONE; + /* Clear the completed or aborted suspend decision after controller resume. */ + sof_pcm_update_sdw_ignore_suspend(sdev); } EXPORT_SYMBOL(snd_sof_complete); diff --git a/sound/soc/sof/sof-audio.h b/sound/soc/sof/sof-audio.h index 10b41ab18b7990..2d19e119afe377 100644 --- a/sound/soc/sof/sof-audio.h +++ b/sound/soc/sof/sof-audio.h @@ -683,6 +683,7 @@ static inline void snd_sof_compr_init_elapsed_work(struct work_struct *work) { } int sof_pcm_dai_link_fixup(struct snd_soc_pcm_runtime *rtd, struct snd_pcm_hw_params *params); /* PM */ +void sof_pcm_update_sdw_ignore_suspend(struct snd_sof_dev *sdev); bool snd_sof_stream_suspend_ignored(struct snd_sof_dev *sdev); bool snd_sof_dsp_only_d0i3_compatible_stream_active(struct snd_sof_dev *sdev); diff --git a/sound/soc/sof/sof-priv.h b/sound/soc/sof/sof-priv.h index 83e4d83cc27cbb..620de872b00bb4 100644 --- a/sound/soc/sof/sof-priv.h +++ b/sound/soc/sof/sof-priv.h @@ -252,6 +252,10 @@ struct snd_sof_dsp_ops { int (*pcm_trigger)(struct snd_sof_dev *sdev, struct snd_pcm_substream *substream, int cmd); /* optional */ + /* set connected SoundWire BE retention during system PM prepare/complete */ + void (*pcm_set_sdw_ignore_suspend)(struct snd_sof_dev *sdev, + struct snd_pcm_substream *substream, + bool ignore_suspend); /* optional */ /* host stream pointer */ snd_pcm_uframes_t (*pcm_pointer)(struct snd_sof_dev *sdev, @@ -407,6 +411,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 +664,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;