Skip to content
Open
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 @@ -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);

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

/**
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-component.c
Original file line number Diff line number Diff line change
Expand Up @@ -1224,6 +1224,7 @@ int snd_soc_pcm_component_trigger(struct snd_pcm_substream *substream,

return ret;
}
EXPORT_SYMBOL_GPL(snd_soc_pcm_component_trigger);

int snd_soc_pcm_component_pm_runtime_get(struct snd_soc_pcm_runtime *rtd,
void *stream)
Expand Down
48 changes: 42 additions & 6 deletions sound/soc/soc-pcm.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Comment thread
Copilot marked this conversation as resolved.
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;
Expand Down Expand Up @@ -2413,6 +2421,21 @@ static int dpcm_dai_trigger_fe_be(struct snd_pcm_substream *substream,
fe->dai_link->name, cmd);

ret = soc_pcm_trigger(substream, cmd);
/*
* The PCM core undoes failed START and PAUSE_RELEASE triggers,
* but not RESUME because the stream is not running yet. Drop
* the BE reference taken for this FE so be_start stays balanced.
*/
if (ret < 0 && cmd == SNDRV_PCM_TRIGGER_RESUME) {
int rollback_ret;

rollback_ret = dpcm_be_dai_trigger(fe, substream->stream,
SNDRV_PCM_TRIGGER_SUSPEND);
if (rollback_ret < 0)
dev_err(fe->dev,
"failed to roll back BE resume after FE trigger failure: %d\n",
rollback_ret);
}
}
end:
return snd_soc_ret(fe->dev, ret, "trigger FE cmd: %d failed\n", cmd);
Expand Down Expand Up @@ -2502,6 +2525,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) {
Expand All @@ -2510,27 +2534,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;
}

/*
Expand Down
8 changes: 8 additions & 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 Expand Up @@ -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;

Expand All @@ -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
Expand Down
1 change: 1 addition & 0 deletions sound/soc/sof/intel/hda-common-ops.c
Original file line number Diff line number Diff line change
Expand Up @@ -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,

Expand Down
57 changes: 50 additions & 7 deletions sound/soc/sof/intel/hda-dai.c
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,31 @@ static bool hda_dai_has_wov_fe(struct snd_sof_dev *sdev, struct snd_pcm_substrea
return false;
}

/*
* snd_sof_prepare() already selected the WoV PCMs to keep active in S0ix and
* marked them suspend_ignored. Reuse that decision so the DSP and SoundWire
* sides of the BE always agree.
*/
static bool hda_dai_has_retained_wov_fe(struct snd_sof_dev *sdev,
struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
struct snd_soc_dpcm *dpcm;
struct snd_sof_pcm *spcm;

if (sdev->system_suspend_target != SOF_SUSPEND_S0IX ||
substream->stream != SNDRV_PCM_STREAM_CAPTURE)
return false;

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].suspend_ignored)
return true;
}

return false;
}

/*
* 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 +344,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,6 +356,12 @@ 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)
Expand All @@ -341,19 +373,25 @@ static int __maybe_unused hda_dai_trigger(struct snd_pcm_substream *substream, i
return 0;
}

if (cmd == SNDRV_PCM_TRIGGER_START && hda_stream->suspend_ignored) {
hda_stream->suspend_ignored = false;
if (hda_dai_has_wov_fe(sdev, substream))
return 0;
}

/*
* Set the suspend_ignored flag for BE which is used with
* D0I3-compatible streams used for WoV to keep the firmware pipeline
* running.
* Keep the BE firmware pipeline running for WoV PCMs retained by
* snd_sof_prepare(). The SoundWire ignore_suspend flag is managed by
* snd_sof_prepare()/snd_sof_complete() before any SUSPEND trigger.
*/
if (cmd == SNDRV_PCM_TRIGGER_SUSPEND &&
sdev->system_suspend_target == SOF_SUSPEND_S0IX &&
hda_dai_has_wov_fe(sdev, substream)) {
snd_soc_substream_to_rtd(substream)->dai_link->no_pcm &&
hda_dai_has_retained_wov_fe(sdev, substream)) {
hda_stream->suspend_ignored = true;
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 All @@ -365,7 +403,12 @@ static int __maybe_unused hda_dai_trigger(struct snd_pcm_substream *substream, i
return ret;
}

if (ops->post_trigger) {
/*
* IPC4 uses post_trigger to update local pipeline bookkeeping on STOP;
* IPC3's post_trigger sends IPC and must be skipped after a firmware crash.
*/
if (ops->post_trigger &&
(!skip_fw_ipc || sdev->pdata->ipc_type == SOF_IPC_TYPE_4)) {
ret = ops->post_trigger(sdev, dai, substream, cmd);
if (ret < 0)
return ret;
Expand Down
Loading
Loading