diff --git a/drivers/soundwire/intel.c b/drivers/soundwire/intel.c index af65214836b4d0..a159ca8d869a56 100644 --- a/drivers/soundwire/intel.c +++ b/drivers/soundwire/intel.c @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -952,11 +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; dai_runtime = cdns->dai_runtime_array[dai->id]; + if (!dai_runtime) + continue; - if (dai_runtime) - dai_runtime->suspended = true; + stream = dai_runtime->stream; + if (pm_suspend_target_state == PM_SUSPEND_TO_IDLE && + stream && stream->ignore_suspend && + stream->state == SDW_STREAM_ENABLED) + continue; + + dai_runtime->suspended = true; } return 0; diff --git a/drivers/soundwire/intel.h b/drivers/soundwire/intel.h index 7a2e7e73ad632a..87f1203e74a3b7 100644 --- a/drivers/soundwire/intel.h +++ b/drivers/soundwire/intel.h @@ -78,6 +78,7 @@ struct sdw_intel { struct sdw_intel_link_res *link_res; bool startup_done; struct sdw_intel_bpt bpt_ctx; + bool suspend_bus_retained; #ifdef CONFIG_DEBUG_FS struct dentry *debugfs; #endif diff --git a/drivers/soundwire/intel_ace2x.c b/drivers/soundwire/intel_ace2x.c index 642b33ab552606..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,11 +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; dai_runtime = cdns->dai_runtime_array[dai->id]; + if (!dai_runtime) + continue; - if (dai_runtime) - dai_runtime->suspended = true; + stream = dai_runtime->stream; + if (pm_suspend_target_state == PM_SUSPEND_TO_IDLE && + stream && stream->ignore_suspend && + stream->state == SDW_STREAM_ENABLED) + continue; + + dai_runtime->suspended = true; } return 0; diff --git a/drivers/soundwire/intel_auxdevice.c b/drivers/soundwire/intel_auxdevice.c index 5983fbc5725a26..48ba160f0009f8 100644 --- a/drivers/soundwire/intel_auxdevice.c +++ b/drivers/soundwire/intel_auxdevice.c @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -576,13 +577,23 @@ int intel_resume_child_device(struct device *dev, void *data) return 0; } +static bool intel_ignore_suspend(struct sdw_intel *sdw) +{ + const struct sdw_intel_link_res *res = sdw->link_res; + + if (res->ops && res->ops->ignore_suspend) + return res->ops->ignore_suspend(res->dev); + + return pm_suspend_target_state == PM_SUSPEND_TO_IDLE; +} + 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, stream_error = 0; if (bus->prop.hw_disabled || !sdw->startup_done) { dev_dbg(dev, "SoundWire master %d is disabled or not-started, ignoring\n", @@ -591,6 +602,7 @@ static int __maybe_unused intel_pm_prepare(struct device *dev) } clock_stop_quirks = sdw->link_res->clock_stop_quirks; + sdw->suspend_bus_retained = false; if (pm_runtime_suspended(dev) && pm_runtime_suspended(dev->parent) && @@ -633,7 +645,38 @@ static int __maybe_unused intel_pm_prepare(struct device *dev) dev_err(dev, "%s: intel_resume_child_device failed: %d\n", __func__, ret); } - return 0; + /* + * Revoke retention from an earlier S0ix before any controller or codec + * suspends. An already-suspended PCM will not issue another trigger. + */ + if (!intel_ignore_suspend(sdw)) { + 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; + + stream->ignore_suspend = false; + dai_runtime->suspended = true; + + if (stream->state == SDW_STREAM_ENABLED) { + ret = sdw_disable_stream(stream); + if (ret) { + dev_err(dev, "failed to disable retained stream %s: %d; close and reopen PCM\n", + stream->name, ret); + if (!stream_error) + stream_error = ret; + } + } + } + } + + return stream_error; } static int __maybe_unused intel_suspend(struct device *dev) @@ -650,6 +693,15 @@ 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 (intel_ignore_suspend(sdw) && sdw_bus_ignore_suspend(bus)) { + sdw->suspend_bus_retained = true; + return 0; + } + /* Prevent runtime PM from racing with the code below. */ pm_runtime_disable(dev); @@ -679,6 +731,7 @@ static int __maybe_unused intel_suspend(struct device *dev) ret = sdw_intel_stop_bus(sdw, true, false); if (ret < 0) { dev_err(dev, "%s: cannot stop bus: %d\n", __func__, ret); + pm_runtime_enable(dev); return ret; } @@ -737,6 +790,12 @@ static int __maybe_unused intel_resume(struct device *dev) return 0; } + if (sdw->suspend_bus_retained) { + 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 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); diff --git a/include/linux/soundwire/sdw_intel.h b/include/linux/soundwire/sdw_intel.h index 9710f2dc04e298..697cc0357cffca 100644 --- a/include/linux/soundwire/sdw_intel.h +++ b/include/linux/soundwire/sdw_intel.h @@ -226,6 +226,7 @@ struct sdw_intel_ops { int (*free_stream)(struct device *dev, struct sdw_intel_stream_free_data *free_data); int (*trigger)(struct snd_pcm_substream *substream, int cmd, struct snd_soc_dai *dai); + bool (*ignore_suspend)(struct device *dev); }; /** diff --git a/sound/soc/sdw_utils/soc_sdw_utils.c b/sound/soc/sdw_utils/soc_sdw_utils.c index 7a19de75f32b53..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"); @@ -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-dapm.c b/sound/soc/soc-dapm.c index 74be5e20949337..436643567f6704 100644 --- a/sound/soc/soc-dapm.c +++ b/sound/soc/soc-dapm.c @@ -355,6 +355,7 @@ void snd_soc_dapm_mark_endpoints_dirty(struct snd_soc_card *card) snd_soc_dapm_mutex_unlock(card); } +EXPORT_SYMBOL_GPL(snd_soc_dapm_mark_endpoints_dirty); /* create a new dapm widget */ static inline struct snd_soc_dapm_widget *dapm_cnew_widget( diff --git a/sound/soc/sof/core.c b/sound/soc/sof/core.c index 1f38fe03e49395..37fca4c37d59bc 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); 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/intel/hda-dsp.c b/sound/soc/sof/intel/hda-dsp.c index ec49aeaa9bd7af..9dcc4c55554c3b 100644 --- a/sound/soc/sof/intel/hda-dsp.c +++ b/sound/soc/sof/intel/hda-dsp.c @@ -852,6 +852,22 @@ 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: + if (!runtime_suspend) { + struct hdac_stream *s; + + /* Do not carry WoV retention state across a system D3 transition. */ + list_for_each_entry(s, &bus->stream_list, list) { + struct sof_intel_hda_stream *hda_stream; + struct hdac_ext_stream *hext_stream; + + hext_stream = stream_to_hdac_ext_stream(s); + hda_stream = hstream_to_sof_hda_stream(hext_stream); + if (sdev->system_suspend_target != SOF_SUSPEND_S0IX && + hda_stream->suspend_ignored) + hext_stream->link_prepared = 0; + hda_stream->suspend_ignored = false; + } + } /* disable hda bus irq and streams */ hda_dsp_ctrl_stop_chip(sdev); diff --git a/sound/soc/sof/intel/hda.c b/sound/soc/sof/intel/hda.c index e1815bfe358b5d..787d975d4f1853 100644 --- a/sound/soc/sof/intel/hda.c +++ b/sound/soc/sof/intel/hda.c @@ -100,9 +100,18 @@ static int sdw_params_free(struct device *dev, struct sdw_intel_stream_free_data return 0; } +static bool sdw_ignore_suspend(struct device *dev) +{ + struct snd_sof_dev *sdev = dev_get_drvdata(dev); + + return sdev->system_suspend_target == SOF_SUSPEND_S0IX && + sdev->fw_state == SOF_FW_BOOT_COMPLETE; +} + struct sdw_intel_ops sdw_callback = { .params_stream = sdw_params_stream, .free_stream = sdw_params_free, + .ignore_suspend = sdw_ignore_suspend, }; static int sdw_ace2x_params_stream(struct device *dev, @@ -132,6 +141,7 @@ static struct sdw_intel_ops sdw_ace2x_callback = { .params_stream = sdw_ace2x_params_stream, .free_stream = sdw_ace2x_free_stream, .trigger = sdw_ace2x_trigger, + .ignore_suspend = sdw_ignore_suspend, }; static int hda_sdw_acpi_scan(struct snd_sof_dev *sdev) diff --git a/sound/soc/sof/ipc4-pcm.c b/sound/soc/sof/ipc4-pcm.c index cbf948d8479148..581a8c4284cb2f 100644 --- a/sound/soc/sof/ipc4-pcm.c +++ b/sound/soc/sof/ipc4-pcm.c @@ -19,11 +19,15 @@ * struct sof_ipc4_pcm_stream_priv - IPC4 specific private data * @time_info: pointer to time info struct if it is supported, otherwise NULL * @chain_dma_allocated: indicates the ChainDMA allocation state + * @started: this PCM holds a started reference on its FE-triggered pipelines + * @paused: this PCM holds a paused reference on its FE-triggered pipelines */ struct sof_ipc4_pcm_stream_priv { struct sof_ipc4_timestamp_info *time_info; bool chain_dma_allocated; + bool started; + bool paused; }; /* @@ -189,66 +193,73 @@ sof_ipc4_add_pipeline_to_trigger_list(struct snd_sof_dev *sdev, int state, } } -static void -sof_ipc4_update_pipeline_state(struct snd_sof_dev *sdev, int state, int cmd, - struct snd_sof_pipeline *spipe, - struct ipc4_pipeline_set_state_data *trigger_list) +static void sof_ipc4_set_stream_state(struct snd_sof_pcm_stream *stream, + bool started, bool paused) { - struct snd_sof_widget *pipe_widget = spipe->pipe_widget; - struct sof_ipc4_pipeline *pipeline = pipe_widget->private; + struct sof_ipc4_pcm_stream_priv *stream_priv = stream->private; + struct snd_sof_pcm_stream_pipeline_list *pipeline_list = &stream->pipeline_list; + int started_delta = (int)started - stream_priv->started; + int paused_delta = (int)paused - stream_priv->paused; int i; - if (pipeline->skip_during_fe_trigger && state != SOF_IPC4_PIPE_RESET) - return; + for (i = 0; i < pipeline_list->count; i++) { + struct snd_sof_pipeline *spipe = pipeline_list->pipelines[i]; + struct sof_ipc4_pipeline *pipeline = spipe->pipe_widget->private; - /* set state for pipeline if it was just triggered */ - for (i = 0; i < trigger_list->count; i++) { - if (trigger_list->pipeline_instance_ids[i] == pipe_widget->instance_id) { - pipeline->state = state; - break; + if (pipeline->skip_during_fe_trigger || pipeline->use_chain_dma) + continue; + + spipe->started_count += started_delta; + spipe->paused_count += paused_delta; + } + + stream_priv->started = started; + stream_priv->paused = paused; +} + +static void sof_ipc4_update_stream_state(int state, int cmd, + struct snd_sof_pcm_stream *stream, + struct ipc4_pipeline_set_state_data *trigger_list) +{ + struct sof_ipc4_pcm_stream_priv *stream_priv = stream->private; + struct snd_sof_pcm_stream_pipeline_list *pipeline_list = &stream->pipeline_list; + bool started = stream_priv->started; + bool paused = stream_priv->paused; + int i, trigger_index; + + for (i = 0; i < pipeline_list->count; i++) { + struct snd_sof_widget *pipe_widget = pipeline_list->pipelines[i]->pipe_widget; + struct sof_ipc4_pipeline *pipeline = pipe_widget->private; + + if (pipeline->skip_during_fe_trigger && state != SOF_IPC4_PIPE_RESET) + continue; + + for (trigger_index = 0; trigger_index < trigger_list->count; trigger_index++) { + if (trigger_list->pipeline_instance_ids[trigger_index] == + pipe_widget->instance_id) { + pipeline->state = state; + break; + } } } - switch (state) { - case SOF_IPC4_PIPE_PAUSED: - switch (cmd) { - case SNDRV_PCM_TRIGGER_PAUSE_PUSH: - /* - * increment paused_count if the PAUSED is the final state during - * the PAUSE trigger - */ - spipe->paused_count++; - break; - case SNDRV_PCM_TRIGGER_STOP: - case SNDRV_PCM_TRIGGER_SUSPEND: - /* - * decrement started_count if PAUSED is the final state during the - * STOP trigger - */ - spipe->started_count--; - break; - default: - break; + if (state == SOF_IPC4_PIPE_RUNNING) { + if (cmd == SNDRV_PCM_TRIGGER_START || cmd == SNDRV_PCM_TRIGGER_RESUME) { + started = true; + paused = false; } - break; - case SOF_IPC4_PIPE_RUNNING: - switch (cmd) { - case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: - /* decrement paused_count for RELEASE */ - spipe->paused_count--; - break; - case SNDRV_PCM_TRIGGER_START: - case SNDRV_PCM_TRIGGER_RESUME: - /* increment started_count for START/RESUME */ - spipe->started_count++; - break; - default: - break; + if (cmd == SNDRV_PCM_TRIGGER_PAUSE_RELEASE) + paused = false; + } else if (state == SOF_IPC4_PIPE_PAUSED) { + if (cmd == SNDRV_PCM_TRIGGER_PAUSE_PUSH) + paused = started; + if (cmd == SNDRV_PCM_TRIGGER_STOP || cmd == SNDRV_PCM_TRIGGER_SUSPEND) { + started = false; + paused = false; } - break; - default: - break; } + + sof_ipc4_set_stream_state(stream, started, paused); } /* @@ -401,6 +412,7 @@ static int sof_ipc4_trigger_pipelines(struct snd_soc_component *component, struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component); struct snd_sof_pcm_stream_pipeline_list *pipeline_list; struct sof_ipc4_fw_data *ipc4_data = sdev->private; + struct sof_ipc4_pcm_stream_priv *stream_priv = spcm->stream[dir].private; struct ipc4_pipeline_set_state_data *trigger_list; struct snd_sof_widget *pipe_widget; struct sof_ipc4_pipeline *pipeline; @@ -414,6 +426,9 @@ static int sof_ipc4_trigger_pipelines(struct snd_soc_component *component, pipeline_list = &spcm->stream[dir].pipeline_list; guard(mutex)(&ipc4_data->pipeline_state_mutex); + if (state == SOF_IPC4_PIPE_RESET) + sof_ipc4_set_stream_state(&spcm->stream[dir], false, false); + /* nothing to trigger if the list is empty */ if (!pipeline_list->pipelines || !pipeline_list->count) return 0; @@ -487,6 +502,10 @@ static int sof_ipc4_trigger_pipelines(struct snd_soc_component *component, } else for (i = 0; i < pipeline_list->count; i++) { + if (state == SOF_IPC4_PIPE_PAUSED && + (!stream_priv->started || stream_priv->paused)) + continue; + spipe = pipeline_list->pipelines[i]; sof_ipc4_add_pipeline_to_trigger_list(sdev, state, spipe, trigger_list, pipe_priority); @@ -495,7 +514,9 @@ static int sof_ipc4_trigger_pipelines(struct snd_soc_component *component, /* return if all pipelines are in the requested state already */ if (!trigger_list->count) { ret = 0; - goto free; + if (state == SOF_IPC4_PIPE_PAUSED) + goto update_paused_state; + goto update_state; } /* no need to pause before reset, EOS or before pause release */ @@ -526,11 +547,9 @@ static int sof_ipc4_trigger_pipelines(struct snd_soc_component *component, } /* update PAUSED state for all pipelines just triggered */ - for (i = 0; i < pipeline_list->count ; i++) { - spipe = pipeline_list->pipelines[i]; - sof_ipc4_update_pipeline_state(sdev, SOF_IPC4_PIPE_PAUSED, cmd, spipe, - trigger_list); - } +update_paused_state: + sof_ipc4_update_stream_state(SOF_IPC4_PIPE_PAUSED, cmd, + &spcm->stream[dir], trigger_list); /* return if this is the final state */ if (state == SOF_IPC4_PIPE_PAUSED) { @@ -569,10 +588,8 @@ static int sof_ipc4_trigger_pipelines(struct snd_soc_component *component, } /* update RUNNING/RESET state for all pipelines that were just triggered */ - for (i = 0; i < pipeline_list->count; i++) { - spipe = pipeline_list->pipelines[i]; - sof_ipc4_update_pipeline_state(sdev, state, cmd, spipe, trigger_list); - } +update_state: + sof_ipc4_update_stream_state(state, cmd, &spcm->stream[dir], trigger_list); free: kfree(trigger_list); @@ -615,8 +632,21 @@ static int sof_ipc4_pcm_hw_free(struct snd_soc_component *component, struct snd_pcm_substream *substream, struct snd_sof_pcm *spcm, int dir) { + struct sof_ipc4_pcm_stream_priv *stream_priv = spcm->stream[dir].private; + int ret = 0, reset_ret; + + if (stream_priv->started) { + ret = sof_ipc4_trigger_pipelines(component, substream, SOF_IPC4_PIPE_PAUSED, + SNDRV_PCM_TRIGGER_STOP, spcm, dir); + if (ret) + spcm_err(spcm, dir, "failed to stop PCM during hw_free: %d\n", ret); + } + /* command is not relevant with RESET, so just pass 0 */ - return sof_ipc4_trigger_pipelines(component, substream, SOF_IPC4_PIPE_RESET, 0, spcm, dir); + reset_ret = sof_ipc4_trigger_pipelines(component, substream, + SOF_IPC4_PIPE_RESET, 0, spcm, dir); + + return ret ? ret : reset_ret; } static int ipc4_ssp_dai_config_pcm_params_match(struct snd_sof_dev *sdev, diff --git a/sound/soc/sof/ipc4-topology.c b/sound/soc/sof/ipc4-topology.c index 8b13c97b348044..76be638cfe555e 100644 --- a/sound/soc/sof/ipc4-topology.c +++ b/sound/soc/sof/ipc4-topology.c @@ -3737,6 +3737,8 @@ static int sof_ipc4_widget_free(struct snd_sof_dev *sdev, struct snd_sof_widget swidget->heap_bytes = 0; swidget->stack_bytes = 0; pipeline->state = SOF_IPC4_PIPE_UNINITIALIZED; + swidget->spipe->started_count = 0; + swidget->spipe->paused_count = 0; ida_free(&pipeline_ida, swidget->instance_id); swidget->instance_id = -EINVAL; } else { diff --git a/sound/soc/sof/pcm.c b/sound/soc/sof/pcm.c index 0161c0ae8bbdb4..b801002d0bb4fd 100644 --- a/sound/soc/sof/pcm.c +++ b/sound/soc/sof/pcm.c @@ -66,6 +66,312 @@ void snd_sof_pcm_period_elapsed(struct snd_pcm_substream *substream) } EXPORT_SYMBOL(snd_sof_pcm_period_elapsed); +static void sof_pcm_free_ignore_suspend_widgets(struct list_head *widgets) +{ + struct snd_sof_ignore_suspend_widget *isw, *tmp; + + list_for_each_entry_safe(isw, tmp, widgets, list) { + list_del(&isw->list); + kfree(isw); + } +} + +static int sof_pcm_add_ignore_suspend_widget(struct list_head *widgets, + struct snd_soc_dapm_widget *dapm_widget) +{ + struct snd_sof_ignore_suspend_widget *isw; + + list_for_each_entry(isw, widgets, list) { + if (isw->widget == dapm_widget) + return 0; + } + + isw = kzalloc_obj(*isw); + if (!isw) + return -ENOMEM; + + isw->widget = dapm_widget; + list_add_tail(&isw->list, widgets); + + return 0; +} + +static struct snd_sof_ignore_suspend_widget * +sof_pcm_find_ignore_suspend_widget(struct snd_sof_dev *sdev, struct snd_soc_dapm_widget *widget) +{ + struct snd_sof_ignore_suspend_widget *isw; + + list_for_each_entry(isw, &sdev->ignore_suspend_widget_list, list) { + if (isw->widget == widget) + return isw; + } + + return NULL; +} + +static unsigned int sof_pcm_dir_to_ep(int dir) +{ + return SND_SOC_DAPM_DIR_TO_EP(dir == SNDRV_PCM_STREAM_PLAYBACK ? + SND_SOC_DAPM_DIR_OUT : SND_SOC_DAPM_DIR_IN); +} + +static int sof_pcm_init_ignore_suspend_widgets(struct snd_sof_dev *sdev, + struct snd_soc_card *card, int dir) +{ + enum snd_soc_dapm_type dai_id = dir == SNDRV_PCM_STREAM_PLAYBACK ? + snd_soc_dapm_dai_in : snd_soc_dapm_dai_out; + unsigned int ep = sof_pcm_dir_to_ep(dir); + struct snd_soc_dapm_widget *widget; + LIST_HEAD(widgets); + int ret = 0; + + snd_soc_dapm_mutex_lock(card); + for_each_card_widgets(card, widget) { + /* DAI widgets become endpoints when their streams start. */ + if (!(widget->is_ep & ep) && widget->id != dai_id) + continue; + + if (sof_pcm_find_ignore_suspend_widget(sdev, widget)) + continue; + + ret = sof_pcm_add_ignore_suspend_widget(&widgets, widget); + if (ret) + goto unlock; + } + list_splice_tail_init(&widgets, &sdev->ignore_suspend_widget_list); +unlock: + snd_soc_dapm_mutex_unlock(card); + sof_pcm_free_ignore_suspend_widgets(&widgets); + + return ret; +} + +static int sof_pcm_collect_ignore_suspend_widgets(struct snd_sof_dev *sdev, + struct snd_soc_pcm_runtime *rtd, + struct snd_sof_pcm *spcm, int dir, + struct list_head *widgets) +{ + enum snd_soc_dapm_type dai_id = dir == SNDRV_PCM_STREAM_PLAYBACK ? + snd_soc_dapm_dai_in : snd_soc_dapm_dai_out; + unsigned int ep = sof_pcm_dir_to_ep(dir); + 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, ret; + + 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); + return connected; + } + + widget = snd_soc_dai_get_widget(dai, dir); + if (widget && widget->is_ep) { + ret = sof_pcm_add_ignore_suspend_widget(widgets, widget); + if (ret) { + snd_soc_dapm_dai_free_widgets(&connected_widgets); + return ret; + } + } + + for_each_dapm_widgets(connected_widgets, i, widget) { + if (!(widget->is_ep & ep) && widget->id != dai_id) + continue; + + ret = sof_pcm_add_ignore_suspend_widget(widgets, widget); + if (ret) { + snd_soc_dapm_dai_free_widgets(&connected_widgets); + return ret; + } + } + + snd_soc_dapm_dai_free_widgets(&connected_widgets); + } + + return 0; +} + +static void sof_pcm_put_widget_ignore_suspend(struct snd_sof_ignore_suspend_widget *isw) +{ + if (WARN_ON_ONCE(!isw->refcount)) + return; + + if (--isw->refcount) + return; + + isw->widget->ignore_suspend = isw->initial_ignore_suspend; +} + +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 list_head *retained_widgets = &spcm->stream[dir].ignore_suspend_widgets; + struct snd_sof_ignore_suspend_widget *isw; + LIST_HEAD(widgets); + int ret = 0; + + if (!ignore_suspend && list_empty(retained_widgets)) + return 0; + + if (ignore_suspend) { + ret = sof_pcm_collect_ignore_suspend_widgets(sdev, rtd, spcm, dir, &widgets); + if (ret) + goto out; + } + + snd_soc_dapm_mutex_lock(rtd->card); + list_for_each_entry(isw, &widgets, list) { + struct snd_sof_ignore_suspend_widget *owned; + + owned = sof_pcm_find_ignore_suspend_widget(sdev, isw->widget); + if (WARN_ON_ONCE(!owned)) { + ret = -EINVAL; + goto unlock; + } + if (owned->refcount == UINT_MAX) { + ret = -EOVERFLOW; + goto unlock; + } + } + + list_for_each_entry(isw, &widgets, list) { + struct snd_sof_ignore_suspend_widget *owned; + + owned = sof_pcm_find_ignore_suspend_widget(sdev, isw->widget); + if (!owned->refcount) + owned->initial_ignore_suspend = owned->widget->ignore_suspend; + owned->refcount++; + owned->widget->ignore_suspend = 1; + dev_dbg(sdev->dev, "WoV PCM %s ignore suspend for widget %s\n", + spcm->pcm.caps[dir].name, isw->widget->name); + } + if (ignore_suspend) { + list_splice_tail_init(&widgets, retained_widgets); + } else { + list_for_each_entry(isw, retained_widgets, list) { + struct snd_sof_ignore_suspend_widget *owned; + + owned = sof_pcm_find_ignore_suspend_widget(sdev, isw->widget); + if (WARN_ON_ONCE(!owned)) + continue; + + sof_pcm_put_widget_ignore_suspend(owned); + } + } +unlock: + snd_soc_dapm_mutex_unlock(rtd->card); + if (ret) + goto out; + + if (!ignore_suspend) + sof_pcm_free_ignore_suspend_widgets(retained_widgets); + + snd_soc_dapm_mark_endpoints_dirty(rtd->card); +out: + sof_pcm_free_ignore_suspend_widgets(&widgets); + if (ret) + spcm_err(spcm, dir, "failed to retain WoV widgets: %d\n", ret); + + return ret; +} + +void snd_sof_pcm_complete_suspend(struct snd_sof_dev *sdev) +{ + struct snd_sof_pcm *spcm; + int dir; + + list_for_each_entry(spcm, &sdev->pcm_list, list) { + for_each_pcm_streams(dir) { + struct snd_pcm_substream *substream = spcm->stream[dir].substream; + struct snd_soc_pcm_runtime *rtd; + + if (!spcm->stream[dir].suspend_widgets_prepared) + continue; + + if (!spcm->stream[dir].suspend_ignored && !WARN_ON_ONCE(!substream)) { + rtd = snd_soc_substream_to_rtd(substream); + sof_pcm_set_widget_ignore_suspend(sdev, rtd, spcm, dir, false); + } + spcm->stream[dir].suspend_widgets_prepared = false; + } + } +} + +int snd_sof_pcm_prepare_suspend(struct snd_sof_dev *sdev) +{ + struct snd_sof_pcm *spcm; + int dir = SNDRV_PCM_STREAM_CAPTURE; + int ret; + + list_for_each_entry(spcm, &sdev->pcm_list, list) { + struct snd_pcm_substream *substream = spcm->stream[dir].substream; + struct snd_soc_pcm_runtime *rtd; + + if (spcm->stream[dir].suspend_ignored || + !spcm->stream[dir].d0i3_compatible || + spcm->stream[dir].dsp_max_burst_size_in_ms > 1 || + !substream || !substream->runtime || !snd_pcm_running(substream)) + continue; + + rtd = snd_soc_substream_to_rtd(substream); + ret = sof_pcm_set_widget_ignore_suspend(sdev, rtd, spcm, dir, true); + if (ret) { + spcm_err(spcm, dir, "failed to prepare WoV retention: %d; aborting suspend\n", + ret); + snd_sof_pcm_complete_suspend(sdev); + return ret; + } + spcm->stream[dir].suspend_widgets_prepared = true; + } + + return 0; +} + +int snd_sof_pcm_clear_suspend_ignored(struct snd_sof_dev *sdev) +{ + struct snd_sof_pcm *spcm; + int dir, ret = 0; + + list_for_each_entry(spcm, &sdev->pcm_list, list) { + for_each_pcm_streams(dir) { + struct snd_pcm_substream *substream; + struct snd_soc_pcm_runtime *rtd; + int stream_ret; + + if (!spcm->stream[dir].suspend_ignored) + continue; + + sdev->system_suspend_retention_revoked = true; + spcm->stream[dir].suspend_ignored = false; + substream = spcm->stream[dir].substream; + if (WARN_ON_ONCE(!substream)) { + stream_ret = -EINVAL; + } else { + rtd = snd_soc_substream_to_rtd(substream); + stream_ret = sof_pcm_set_widget_ignore_suspend(sdev, rtd, spcm, + dir, false); + } + if (stream_ret) { + spcm_err(spcm, dir, "failed to clear WoV retention: %d; close and reopen PCM\n", + stream_ret); + if (!ret) + ret = stream_ret; + } + } + } + + 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 +609,10 @@ static int sof_pcm_hw_free(struct snd_soc_component *component, spcm_dbg(spcm, substream->stream, "Entry: hw_free\n"); + if (spcm->stream[substream->stream].suspend_ignored) { + sof_pcm_set_widget_ignore_suspend(sdev, rtd, spcm, substream->stream, false); + spcm->stream[substream->stream].suspend_ignored = false; + } ret = sof_pcm_stream_free(sdev, substream, spcm, substream->stream, true); /* unprepare and free the list of DAPM widgets */ @@ -336,6 +646,16 @@ 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); + spcm->stream[dir].suspend_ignored = false; + } + if (spcm->prepared[substream->stream]) { if (!spcm->pending_stop[substream->stream]) return 0; @@ -421,6 +741,8 @@ static int sof_pcm_trigger(struct snd_soc_component *component, * not supported, no need to re-start streams that * remained enabled in D0ix. */ + sof_pcm_set_widget_ignore_suspend(sdev, rtd, spcm, + substream->stream, false); spcm->stream[substream->stream].suspend_ignored = false; return 0; } @@ -430,6 +752,8 @@ static int sof_pcm_trigger(struct snd_soc_component *component, break; case SNDRV_PCM_TRIGGER_RESUME: if (spcm->stream[substream->stream].suspend_ignored) { + sof_pcm_set_widget_ignore_suspend(sdev, rtd, spcm, + substream->stream, false); spcm->stream[substream->stream].suspend_ignored = false; return 0; } @@ -448,6 +772,13 @@ 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) { + if (spcm->stream[substream->stream].suspend_ignored) + return 0; + + if (!spcm->stream[substream->stream].suspend_widgets_prepared) { + spcm_err(spcm, substream->stream, "WoV widgets were not prepared for suspend\n"); + return -EINVAL; + } spcm->stream[substream->stream].suspend_ignored = true; return 0; } @@ -566,6 +897,14 @@ static int sof_pcm_open(struct snd_soc_component *component, spcm_dbg(spcm, substream->stream, "Entry: open\n"); + ret = sof_pcm_init_ignore_suspend_widgets(sdev, rtd->card, + substream->stream); + if (ret) { + spcm_err(spcm, substream->stream, + "failed to initialize WoV endpoint widgets: %d\n", ret); + return ret; + } + caps = &spcm->pcm.caps[substream->stream]; /* set runtime config */ @@ -614,7 +953,7 @@ static int sof_pcm_close(struct snd_soc_component *component, struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component); struct snd_sof_pcm *spcm; - int err; + int ret; /* nothing to do for BE */ if (rtd->dai_link->no_pcm) @@ -626,10 +965,14 @@ static int sof_pcm_close(struct snd_soc_component *component, spcm_dbg(spcm, substream->stream, "Entry: close\n"); - err = snd_sof_pcm_platform_close(sdev, substream); - if (err < 0) { + if (spcm->stream[substream->stream].suspend_ignored) { + sof_pcm_set_widget_ignore_suspend(sdev, rtd, spcm, substream->stream, false); + spcm->stream[substream->stream].suspend_ignored = false; + } + ret = snd_sof_pcm_platform_close(sdev, substream); + if (ret < 0) { spcm_err(spcm, substream->stream, - "platform pcm close failed %d\n", err); + "platform pcm close failed %d\n", ret); /* * keep going, no point in preventing the close * from happening @@ -808,6 +1151,10 @@ static int sof_pcm_probe(struct snd_soc_component *component) static void sof_pcm_remove(struct snd_soc_component *component) { + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component); + + sof_pcm_free_ignore_suspend_widgets(&sdev->ignore_suspend_widget_list); + /* remove topology */ snd_soc_tplg_component_remove(component); } diff --git a/sound/soc/sof/pm.c b/sound/soc/sof/pm.c index b5d239b12e59d2..2d210b26a5a94e 100644 --- a/sound/soc/sof/pm.c +++ b/sound/soc/sof/pm.c @@ -370,6 +370,10 @@ EXPORT_SYMBOL(snd_sof_resume); int snd_sof_suspend(struct device *dev) { + struct snd_sof_dev *sdev = dev_get_drvdata(dev); + + sdev->system_suspend_started = true; + return sof_suspend(dev, false); } EXPORT_SYMBOL(snd_sof_suspend); @@ -378,6 +382,10 @@ int snd_sof_prepare(struct device *dev) { struct snd_sof_dev *sdev = dev_get_drvdata(dev); const struct sof_dev_desc *desc = sdev->pdata->desc; + int ret; + + sdev->system_suspend_started = false; + sdev->system_suspend_retention_revoked = false; /* will suspend to S3 by default */ sdev->system_suspend_target = SOF_SUSPEND_S3; @@ -388,10 +396,10 @@ int snd_sof_prepare(struct device *dev) */ if (sdev->fw_state == SOF_FW_CRASHED || sdev->fw_state == SOF_FW_BOOT_FAILED) - return 0; + return snd_sof_pcm_clear_suspend_ignored(sdev); if (!desc->use_acpi_target_states) - return 0; + return snd_sof_pcm_clear_suspend_ignored(sdev); #if defined(CONFIG_ACPI) switch (acpi_target_system_state()) { @@ -414,7 +422,14 @@ int snd_sof_prepare(struct device *dev) } #endif - return 0; + if (sdev->system_suspend_target != SOF_SUSPEND_S0IX) + return snd_sof_pcm_clear_suspend_ignored(sdev); + + ret = snd_sof_pcm_prepare_suspend(sdev); + if (ret) + sdev->system_suspend_target = SOF_SUSPEND_NONE; + + return ret; } EXPORT_SYMBOL(snd_sof_prepare); @@ -422,6 +437,13 @@ void snd_sof_complete(struct device *dev) { struct snd_sof_dev *sdev = dev_get_drvdata(dev); + snd_sof_pcm_complete_suspend(sdev); + + if (sdev->system_suspend_retention_revoked && !sdev->system_suspend_started) + dev_err(sdev->dev, "system suspend aborted after revoking WoV retention; close and reopen PCM\n"); + + sdev->system_suspend_started = false; + sdev->system_suspend_retention_revoked = false; sdev->system_suspend_target = SOF_SUSPEND_NONE; } EXPORT_SYMBOL(snd_sof_complete); diff --git a/sound/soc/sof/sof-audio.h b/sound/soc/sof/sof-audio.h index 10b41ab18b7990..02f966eb7d0abf 100644 --- a/sound/soc/sof/sof-audio.h +++ b/sound/soc/sof/sof-audio.h @@ -346,6 +346,9 @@ struct snd_sof_pcm_stream { * active or not while suspending the stream */ bool suspend_ignored; + bool suspend_widgets_prepared; + /* Endpoint references acquired when retaining this stream across suspend. */ + struct list_head ignore_suspend_widgets; struct snd_sof_pcm_stream_pipeline_list pipeline_list; /* used by IPC implementation and core does not touch it */ @@ -640,6 +643,9 @@ struct snd_sof_pcm *snd_sof_find_spcm_comp(struct snd_soc_component *scomp, int *direction); void snd_sof_pcm_period_elapsed(struct snd_pcm_substream *substream); void snd_sof_pcm_init_elapsed_work(struct work_struct *work); +int snd_sof_pcm_clear_suspend_ignored(struct snd_sof_dev *sdev); +int snd_sof_pcm_prepare_suspend(struct snd_sof_dev *sdev); +void snd_sof_pcm_complete_suspend(struct snd_sof_dev *sdev); 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, struct snd_sof_platform_stream_params *platform_params, diff --git a/sound/soc/sof/sof-priv.h b/sound/soc/sof/sof-priv.h index 83e4d83cc27cbb..f21d4fd9e2944c 100644 --- a/sound/soc/sof/sof-priv.h +++ b/sound/soc/sof/sof-priv.h @@ -407,6 +407,14 @@ struct snd_sof_dfsentry { }; }; +/* DAPM endpoint or DAI widget tracked for WoV ignore_suspend management */ +struct snd_sof_ignore_suspend_widget { + struct snd_soc_dapm_widget *widget; + unsigned int refcount; + bool initial_ignore_suspend; + 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; @@ -600,6 +608,8 @@ struct snd_sof_dev { /* Intended power target of system suspend */ enum sof_system_suspend_state system_suspend_target; + bool system_suspend_started; + bool system_suspend_retention_revoked; /* DSP firmware boot */ wait_queue_head_t boot_wait; @@ -653,6 +663,8 @@ struct snd_sof_dev { struct list_head dai_list; struct list_head dai_link_list; struct list_head route_list; + /* All card DAPM endpoints/DAI widgets and their WoV ignore_suspend reference counts */ + struct list_head ignore_suspend_widget_list; struct snd_soc_component *component; u32 enabled_cores_mask; /* keep track of enabled cores */ bool led_present; diff --git a/sound/soc/sof/topology.c b/sound/soc/sof/topology.c index 750916b57def74..f91798b21f586f 100644 --- a/sound/soc/sof/topology.c +++ b/sound/soc/sof/topology.c @@ -1773,6 +1773,7 @@ static int sof_dai_load(struct snd_soc_component *scomp, int index, spcm->scomp = scomp; for_each_pcm_streams(stream) { + INIT_LIST_HEAD(&spcm->stream[stream].ignore_suspend_widgets); spcm->stream[stream].comp_id = COMP_ID_UNASSIGNED; if (pcm->compress) snd_sof_compr_init_elapsed_work(&spcm->stream[stream].period_elapsed_work);