diff --git a/sound/soc/sof/intel/hda-dai-ops.c b/sound/soc/sof/intel/hda-dai-ops.c index b2c55955996294..652a91d30fb0bd 100644 --- a/sound/soc/sof/intel/hda-dai-ops.c +++ b/sound/soc/sof/intel/hda-dai-ops.c @@ -292,6 +292,35 @@ static struct hdac_ext_link *sdw_get_hlink(struct snd_sof_dev *sdev, return hdac_bus_eml_sdw_get_hlink(bus); } +/* + * Count how many CPU DAIs in this BE link share the same pipeline (spipe) + * as the target. Used to determine the per-pipeline trigger threshold for + * aggregate DAIs where some may share a pipeline and others may not. + */ +static int hda_ipc4_count_spipe_dais(struct snd_pcm_substream *substream, + struct snd_sof_pipeline *target_spipe) +{ + struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); + struct snd_soc_dai *dai; + int count = 0; + int i; + + for_each_rtd_cpu_dais(rtd, i, dai) { + struct snd_soc_dapm_widget *w; + struct snd_sof_widget *sw; + + w = snd_soc_dai_get_widget(dai, substream->stream); + if (!w) + continue; + + sw = w->dobj.private; + if (sw->spipe == target_spipe) + count++; + } + + return count; +} + static int hda_ipc4_pre_trigger(struct snd_sof_dev *sdev, struct snd_soc_dai *cpu_dai, struct snd_pcm_substream *substream, int cmd) { @@ -319,13 +348,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); @@ -372,11 +410,13 @@ 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); @@ -391,6 +431,26 @@ static int hda_ipc4_post_trigger(struct snd_sof_dev *sdev, struct snd_soc_dai *c switch (cmd) { case SNDRV_PCM_TRIGGER_START: + /* + * For aggregated DAIs (num_cpus > 1), defer pipeline RUNNING + * IPC until all CPU DAIs sharing this pipeline have started + * their link DMAs. The per-spipe threshold handles all cases: + * - shared pipeline: waits for all DAIs on the same spipe + * - different pipelines: each fires independently (threshold=1) + * - mixed: each group fires when its members complete + */ + if (num_cpus > 1) { + int spipe_dais = hda_ipc4_count_spipe_dais(substream, + swidget->spipe); + + swidget->spipe->trigger_count++; + swidget->spipe->started_count++; + if (swidget->spipe->trigger_count < spipe_dais) + break; + + swidget->spipe->trigger_count = 0; + } + if (pipeline->state != SOF_IPC4_PIPE_PAUSED) { ret = sof_ipc4_set_pipeline_state(sdev, pipe_widget->instance_id, SOF_IPC4_PIPE_PAUSED); @@ -406,9 +466,21 @@ static int hda_ipc4_post_trigger(struct snd_sof_dev *sdev, struct snd_soc_dai *c return ret; pipeline->state = SOF_IPC4_PIPE_RUNNING; - swidget->spipe->started_count++; + if (num_cpus <= 1) + swidget->spipe->started_count++; break; case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + if (num_cpus > 1) { + int spipe_dais = hda_ipc4_count_spipe_dais(substream, + swidget->spipe); + + swidget->spipe->trigger_count++; + if (swidget->spipe->trigger_count < spipe_dais) + break; + + swidget->spipe->trigger_count = 0; + } + ret = sof_ipc4_set_pipeline_state(sdev, pipe_widget->instance_id, SOF_IPC4_PIPE_RUNNING); if (ret < 0) @@ -420,9 +492,11 @@ static int hda_ipc4_post_trigger(struct snd_sof_dev *sdev, struct snd_soc_dai *c case SNDRV_PCM_TRIGGER_STOP: /* * STOP/SUSPEND trigger is invoked only once when all users of this pipeline have - * been stopped. So, clear the started_count so that the pipeline can be reset + * been stopped. So, clear the started_count so that the pipeline can be reset. + * Also reset trigger_count for aggregate DAIs in case of partial trigger rollback. */ swidget->spipe->started_count = 0; + swidget->spipe->trigger_count = 0; break; case SNDRV_PCM_TRIGGER_PAUSE_PUSH: break; diff --git a/sound/soc/sof/sof-audio.h b/sound/soc/sof/sof-audio.h index 138e5fcc2dd094..56083903e792e0 100644 --- a/sound/soc/sof/sof-audio.h +++ b/sound/soc/sof/sof-audio.h @@ -512,6 +512,7 @@ struct snd_sof_widget { * @complete: flag used to indicate that pipeline set up is complete. * @core_mask: Mask containing target cores for all modules in the pipeline * @list: List item in sdev pipeline_list + * @trigger_count: tracks the number of DAIs that have completed link DMA trigger for aggregate * @direction_valid: flag indicating if the direction is set in topology * @direction: pipeline direction set in topology, valid is direction_valid is true * @@ -523,6 +524,7 @@ struct snd_sof_pipeline { int complete; unsigned long core_mask; struct list_head list; + int trigger_count; bool direction_valid; u32 direction; };