Skip to content
Open
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
107 changes: 100 additions & 7 deletions sound/soc/sof/intel/hda-dai-ops.c
Original file line number Diff line number Diff line change
Expand Up @@ -356,6 +356,64 @@ static struct hdac_ext_link *sdw_get_hlink(struct snd_sof_dev *sdev,
return hdac_bus_eml_sdw_get_hlink(bus);
}

/*
* For aggregate DAIs (num_cpus > 1), each CPU DAI owns its own independently
* allocated link DMA (its own hdac_ext_stream / PPLC register block, see
* hda_link_stream_assign()). The firmware pipeline's RUNNING IPC starts all
* of the aggregate's physical links on the DSP side in one shot, so it must
* not be sent until every one of those link DMAs has actually been armed on
* the host side. Only one CPU DAI in the aggregate typically owns a real
* firmware pipeline object (see is_aggregated_dai() in sof-audio.c), so this
* check is intentionally independent of any per-DAI pipeline/spipe
* association -- it only cares about the host-side link DMA state.
*/
static bool hda_ipc4_all_link_dmas_running(struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
struct snd_soc_dai *dai;
int i;

for_each_rtd_cpu_dais(rtd, i, dai) {
struct hdac_ext_stream *hext_stream;

hext_stream = snd_soc_dai_get_dma_data(dai, substream);
if (!hext_stream ||
!(readl(hext_stream->pplc_addr + AZX_REG_PPLCCTL) & AZX_PPLCCTL_RUN))
return false;
}

return true;
}

/*
* For aggregate DAIs (num_cpus > 1), only one CPU DAI actually owns a
* firmware pipeline object -- see is_aggregated_dai() in sof-audio.c. The
* other member(s) have their own widget, but their pipe_widget->instance_id
* stays unset since their pipeline is never set up in firmware. Since ASoC
* triggers each CPU DAI of the aggregate sequentially and the owning DAI is
* not guaranteed to be triggered last, search the aggregate's sibling CPU
* DAIs for the one that does own a valid pipeline, so its state transition
* can be completed regardless of which DAI is currently being triggered.
*/
static struct snd_sof_widget *
hda_ipc4_find_owning_pipe_widget(struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
struct snd_soc_dai *dai;
int i;

for_each_rtd_cpu_dais(rtd, i, dai) {
struct snd_soc_dapm_widget *w = snd_soc_dai_get_widget(dai,
substream->stream);
struct snd_sof_widget *sw = w ? w->dobj.private : NULL;

if (sw && sw->spipe->pipe_widget->instance_id >= 0)
return sw;
Comment on lines +406 to +411

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it is granted to be never NULL, no need for the check, not even in case of DSPless mode, when this path is not even reached.

}

return NULL;
}

static int hda_ipc4_pre_trigger(struct snd_sof_dev *sdev, struct snd_soc_dai *cpu_dai,
struct snd_pcm_substream *substream, int cmd)
{
Expand All @@ -369,10 +427,16 @@ static int hda_ipc4_pre_trigger(struct snd_sof_dev *sdev, struct snd_soc_dai *cp
w = snd_soc_dai_get_widget(cpu_dai, substream->stream);
swidget = w->dobj.private;
pipe_widget = swidget->spipe->pipe_widget;
pipeline = pipe_widget->private;

if (pipe_widget->instance_id < 0)
return 0;
if (pipe_widget->instance_id < 0) {
swidget = hda_ipc4_find_owning_pipe_widget(substream);
if (!swidget)
return 0;

pipe_widget = swidget->spipe->pipe_widget;
}

pipeline = pipe_widget->private;

guard(mutex)(&ipc4_data->pipeline_state_mutex);

Expand All @@ -383,13 +447,22 @@ static int hda_ipc4_pre_trigger(struct snd_sof_dev *sdev, struct snd_soc_dai *cp
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
case SNDRV_PCM_TRIGGER_SUSPEND:
case SNDRV_PCM_TRIGGER_STOP:
/*
* For aggregate DAIs with shared pipelines, the state check
* deduplicates: the first DAI sends the IPC, subsequent DAIs
* sharing the same pipeline see it already paused and skip.
* For aggregate DAIs with different pipelines, each DAI pauses
* its own pipeline independently.
*/
if (pipeline->state == SOF_IPC4_PIPE_PAUSED)
break;

ret = sof_ipc4_set_pipeline_state(sdev, pipe_widget->instance_id,
SOF_IPC4_PIPE_PAUSED);
if (ret < 0)
return ret;

pipeline->state = SOF_IPC4_PIPE_PAUSED;

break;
default:
dev_err(sdev->dev, "unknown trigger command %d\n", cmd);
Expand Down Expand Up @@ -436,25 +509,42 @@ static int hda_trigger(struct snd_sof_dev *sdev, struct snd_soc_dai *cpu_dai,
static int hda_ipc4_post_trigger(struct snd_sof_dev *sdev, struct snd_soc_dai *cpu_dai,
struct snd_pcm_substream *substream, int cmd)
{
struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
struct sof_ipc4_fw_data *ipc4_data = sdev->private;
struct snd_sof_widget *pipe_widget;
struct sof_ipc4_pipeline *pipeline;
struct snd_sof_widget *swidget;
struct snd_soc_dapm_widget *w;
int num_cpus = rtd->dai_link->num_cpus;
int ret = 0;

w = snd_soc_dai_get_widget(cpu_dai, substream->stream);
swidget = w->dobj.private;
pipe_widget = swidget->spipe->pipe_widget;
pipeline = pipe_widget->private;

if (pipe_widget->instance_id < 0)
return 0;
if (pipe_widget->instance_id < 0) {
swidget = hda_ipc4_find_owning_pipe_widget(substream);
if (!swidget)
return 0;

pipe_widget = swidget->spipe->pipe_widget;
}

pipeline = pipe_widget->private;

guard(mutex)(&ipc4_data->pipeline_state_mutex);

switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
/*
* For aggregated DAIs (num_cpus > 1), defer the RUNNING IPC
* until every CPU DAI's link DMA has been armed via
* hda_trigger(): the firmware starts all of the aggregate's
* physical links as soon as it sees this one IPC.
*/
if (num_cpus > 1 && !hda_ipc4_all_link_dmas_running(substream))
break;

if (pipeline->state != SOF_IPC4_PIPE_PAUSED) {
ret = sof_ipc4_set_pipeline_state(sdev, pipe_widget->instance_id,
SOF_IPC4_PIPE_PAUSED);
Expand All @@ -473,6 +563,9 @@ static int hda_ipc4_post_trigger(struct snd_sof_dev *sdev, struct snd_soc_dai *c
swidget->spipe->started_count++;
break;
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
if (num_cpus > 1 && !hda_ipc4_all_link_dmas_running(substream))
break;

ret = sof_ipc4_set_pipeline_state(sdev, pipe_widget->instance_id,
SOF_IPC4_PIPE_RUNNING);
if (ret < 0)
Expand Down
Loading