Skip to content
3 changes: 3 additions & 0 deletions drivers/soundwire/intel.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
3 changes: 3 additions & 0 deletions drivers/soundwire/intel_ace2x.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
9 changes: 9 additions & 0 deletions drivers/soundwire/intel_auxdevice.c
Original file line number Diff line number Diff line change
Expand Up @@ -650,6 +650,9 @@ static int __maybe_unused intel_suspend(struct device *dev)
return 0;
}

if (sdw_bus_ignore_suspend(bus))
return 0;
Comment on lines +653 to +654

/* Prevent runtime PM from racing with the code below. */
pm_runtime_disable(dev);

Expand Down Expand Up @@ -737,6 +740,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);
Expand Down
19 changes: 19 additions & 0 deletions drivers/soundwire/stream.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
3 changes: 3 additions & 0 deletions include/linux/soundwire/sdw.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
};

/**
Expand Down Expand Up @@ -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);
Expand Down
21 changes: 20 additions & 1 deletion sound/soc/sdw_utils/soc_sdw_utils.c
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand All @@ -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
Expand All @@ -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);
Comment thread
bardliao marked this conversation as resolved.
break;
default:
ret = -EINVAL;
break;
Expand Down Expand Up @@ -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");
Expand Down
1 change: 1 addition & 0 deletions sound/soc/soc-dapm.c
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
1 change: 1 addition & 0 deletions sound/soc/sof/core.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
4 changes: 4 additions & 0 deletions sound/soc/sof/intel/hda-dai-ops.c
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
71 changes: 65 additions & 6 deletions sound/soc/sof/intel/hda-dai.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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).
Expand All @@ -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) {
Expand All @@ -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;
}
Expand All @@ -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;
Expand Down Expand Up @@ -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);
Comment thread
bardliao marked this conversation as resolved.

return hda_dai_trigger(substream, cmd, cpu_dai);
}
EXPORT_SYMBOL_NS(sdw_hda_dai_trigger, "SND_SOC_SOF_INTEL_HDA_COMMON");
Expand Down
Loading
Loading