Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
18 changes: 18 additions & 0 deletions drivers/soundwire/stream.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
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
11 changes: 10 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
33 changes: 27 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)
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 @@ -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) {
Expand All @@ -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;
}

/*
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
50 changes: 49 additions & 1 deletion sound/soc/sof/intel/hda-dai.c
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,9 @@ hda_link_dma_cleanup(struct snd_pcm_substream *substream,
if (!hlink)
return -EINVAL;

hda_stream = hstream_to_sof_hda_stream(hext_stream);
hda_stream->suspend_ignored = false;

if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
stream_tag = hdac_stream(hext_stream)->stream_tag;
snd_hdac_ext_bus_link_clear_stream_id(hlink, stream_tag);
Expand All @@ -157,7 +160,6 @@ hda_link_dma_cleanup(struct snd_pcm_substream *substream,
hext_stream->link_prepared = 0;

/* free the host DMA channel reserved by hostless streams */
hda_stream = hstream_to_sof_hda_stream(hext_stream);
hda_stream->host_reserved = 0;

return 0;
Expand Down Expand Up @@ -280,6 +282,32 @@ static int __maybe_unused hda_dai_hw_params(struct snd_pcm_substream *substream,
return hda_dai_hw_params_data(substream, params, dai, &data, flags);
}

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);
struct snd_soc_dpcm *dpcm;
struct snd_sof_pcm *spcm;

if (substream->stream != SNDRV_PCM_STREAM_CAPTURE)
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.
*/
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))
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 @@ -289,6 +317,7 @@ static int __maybe_unused hda_dai_trigger(struct snd_pcm_substream *substream, i
{
const struct hda_dai_widget_dma_ops *ops = hda_dai_get_ops(substream, dai);
struct hdac_ext_stream *hext_stream;
struct sof_intel_hda_stream *hda_stream;
struct snd_sof_dev *sdev;
int ret;

Expand All @@ -305,6 +334,25 @@ static int __maybe_unused hda_dai_trigger(struct snd_pcm_substream *substream, i
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 || cmd == SNDRV_PCM_TRIGGER_START) &&
hda_stream->suspend_ignored) {
hda_stream->suspend_ignored = false;
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.
*/
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 (ops->pre_trigger) {
ret = ops->pre_trigger(sdev, dai, substream, cmd);
Expand Down
10 changes: 8 additions & 2 deletions sound/soc/sof/intel/hda-dsp.c
Original file line number Diff line number Diff line change
Expand Up @@ -683,9 +683,15 @@ static int hda_dsp_set_power_state(struct snd_sof_dev *sdev,
ret = hda_dsp_set_D0_state(sdev, target_state);
break;
case SOF_DSP_PM_D3:
/* The only allowed transition is: D0I0 -> D3 */
/*
* The only allowed transition is D0I0 -> D3. If the firmware
* crashed, the D0I3 -> D0I0 IPC cannot succeed, but the DSP can
* still be powered down directly.
*/
if (sdev->dsp_power_state.state == SOF_DSP_PM_D0 &&
sdev->dsp_power_state.substate == SOF_HDA_DSP_PM_D0I0) {
(sdev->dsp_power_state.substate == SOF_HDA_DSP_PM_D0I0 ||
sdev->fw_state == SOF_FW_CRASHED ||
sdev->fw_state == SOF_FW_BOOT_FAILED)) {
struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata;
const struct sof_intel_dsp_desc *chip = hda->desc;
int ret, j;
Expand Down
14 changes: 14 additions & 0 deletions sound/soc/sof/intel/hda-pcm.c
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,20 @@ int hda_dsp_pcm_open(struct snd_sof_dev *sdev,
spcm->stream[substream->stream].d0i3_compatible)
flags |= SOF_HDA_STREAM_DMI_L1_COMPATIBLE;

/*
* Set the RESUME supported flag for WoV streams. The core will ignore
* the trigger but applications must not try to restart the WoV stream
* due to not supported RESUME.
* WoV streams can be indetified by:
* They are capture streams and
* They have the d0i3_compatible flag set and
* They don't use Deep Buffer
*/
if (direction == SNDRV_PCM_STREAM_CAPTURE &&
spcm->stream[substream->stream].d0i3_compatible &&
spcm->stream[substream->stream].dsp_max_burst_size_in_ms <= 1)
runtime->hw.info |= SNDRV_PCM_INFO_RESUME;

dsp_stream = hda_dsp_stream_get(sdev, direction, flags);
if (!dsp_stream) {
dev_err(sdev->dev, "error: no stream available\n");
Expand Down
Loading