diff --git a/drivers/soundwire/intel.c b/drivers/soundwire/intel.c index e15666962fe49d..f7b856fea15194 100644 --- a/drivers/soundwire/intel.c +++ b/drivers/soundwire/intel.c @@ -700,6 +700,25 @@ static int intel_params_stream(struct sdw_intel *sdw, * DAI routines */ +static int intel_free_stream(struct sdw_intel *sdw, + struct snd_pcm_substream *substream, + struct snd_soc_dai *dai, + int link_id) +{ + struct sdw_intel_link_res *res = sdw->link_res; + struct sdw_intel_stream_free_data free_data; + + free_data.substream = substream; + free_data.dai = dai; + free_data.link_id = link_id; + + if (res->ops && res->ops->free_stream && res->dev) + return res->ops->free_stream(res->dev, + &free_data); + + return 0; +} + static int intel_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) @@ -831,6 +850,7 @@ static int intel_hw_free(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { struct sdw_cdns *cdns = snd_soc_dai_get_drvdata(dai); + struct sdw_intel *sdw = cdns_to_intel(cdns); struct sdw_cdns_dai_runtime *dai_runtime; int ret; @@ -851,6 +871,12 @@ intel_hw_free(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) return ret; } + ret = intel_free_stream(sdw, substream, dai, sdw->instance); + if (ret < 0) { + dev_err(dai->dev, "intel_free_stream: failed %d\n", ret); + return ret; + } + dai_runtime->pdi = NULL; return 0; diff --git a/sound/soc/sof/intel/hda-dai-ops.c b/sound/soc/sof/intel/hda-dai-ops.c index 01e5f0617ae5b9..8713659edbe791 100644 --- a/sound/soc/sof/intel/hda-dai-ops.c +++ b/sound/soc/sof/intel/hda-dai-ops.c @@ -410,6 +410,12 @@ static int hda_ipc4_post_trigger(struct snd_sof_dev *sdev, struct snd_soc_dai *c * been stopped. So, clear the started_count so that the pipeline can be reset */ swidget->spipe->started_count = 0; + + ret = sof_ipc4_set_pipeline_state(sdev, pipe_widget->instance_id, + SOF_IPC4_PIPE_RESET); + if (ret < 0) + goto out; + pipeline->state = SOF_IPC4_PIPE_RESET; break; case SNDRV_PCM_TRIGGER_PAUSE_PUSH: break; diff --git a/sound/soc/sof/intel/hda.c b/sound/soc/sof/intel/hda.c index 66126fb31d52db..887feacc993008 100644 --- a/sound/soc/sof/intel/hda.c +++ b/sound/soc/sof/intel/hda.c @@ -31,6 +31,7 @@ #include "../sof-audio.h" #include "../sof-pci-dev.h" #include "../ops.h" +#include "../ipc4-topology.h" #include "hda.h" #include @@ -68,8 +69,35 @@ static int sdw_params_stream(struct device *dev, return hda_dai_config(w, SOF_DAI_CONFIG_FLAGS_HW_PARAMS, &data); } +static int sdw_params_free(struct device *dev, struct sdw_intel_stream_free_data *free_data) +{ + struct snd_soc_dai *d = free_data->dai; + struct snd_soc_dapm_widget *w = snd_soc_dai_get_widget(d, free_data->substream->stream); + struct snd_sof_widget *swidget = w->dobj.private; + struct snd_soc_component *component = swidget->scomp; + struct snd_sof_dai *dai = swidget->private; + struct sof_ipc4_copier_data *copier_data; + struct sof_ipc4_copier *ipc4_copier; + struct snd_sof_dev *sdev; + + sdev = snd_soc_component_get_drvdata(component); + + if (sdev->pdata->ipc_type != SOF_IPC_TYPE_4) + return 0; + + ipc4_copier = dai->private; + ipc4_copier->dai_index = 0; + copier_data = &ipc4_copier->data; + + /* clear the node ID */ + copier_data->gtw_cfg.node_id &= ~SOF_IPC4_NODE_INDEX_MASK; + + return 0; +} + struct sdw_intel_ops sdw_callback = { .params_stream = sdw_params_stream, + .free_stream = sdw_params_free, }; static int sdw_ace2x_params_stream(struct device *dev, diff --git a/sound/soc/sof/ipc4-pcm.c b/sound/soc/sof/ipc4-pcm.c index 0f332c8cdbe6af..d5548f0267894a 100644 --- a/sound/soc/sof/ipc4-pcm.c +++ b/sound/soc/sof/ipc4-pcm.c @@ -97,7 +97,7 @@ sof_ipc4_add_pipeline_to_trigger_list(struct snd_sof_dev *sdev, int state, struct snd_sof_widget *pipe_widget = spipe->pipe_widget; struct sof_ipc4_pipeline *pipeline = pipe_widget->private; - if (pipeline->skip_during_fe_trigger && state != SOF_IPC4_PIPE_RESET) + if (pipeline->skip_during_fe_trigger) return; switch (state) { @@ -136,7 +136,7 @@ sof_ipc4_update_pipeline_state(struct snd_sof_dev *sdev, int state, int cmd, struct sof_ipc4_pipeline *pipeline = pipe_widget->private; int i; - if (pipeline->skip_during_fe_trigger && state != SOF_IPC4_PIPE_RESET) + if (pipeline->skip_during_fe_trigger) return; /* set state for pipeline if it was just triggered */ @@ -485,8 +485,8 @@ static int sof_ipc4_pcm_trigger(struct snd_soc_component *component, return sof_ipc4_trigger_pipelines(component, substream, state, cmd); } -static int sof_ipc4_pcm_hw_free(struct snd_soc_component *component, - struct snd_pcm_substream *substream) +static int sof_ipc4_pcm_post_trigger(struct snd_soc_component *component, + struct snd_pcm_substream *substream) { /* command is not relevant with RESET, so just pass 0 */ return sof_ipc4_trigger_pipelines(component, substream, SOF_IPC4_PIPE_RESET, 0); @@ -937,11 +937,10 @@ static snd_pcm_sframes_t sof_ipc4_pcm_delay(struct snd_soc_component *component, const struct sof_ipc_pcm_ops ipc4_pcm_ops = { .hw_params = sof_ipc4_pcm_hw_params, .trigger = sof_ipc4_pcm_trigger, - .hw_free = sof_ipc4_pcm_hw_free, .dai_link_fixup = sof_ipc4_pcm_dai_link_fixup, .pcm_setup = sof_ipc4_pcm_setup, .pcm_free = sof_ipc4_pcm_free, .delay = sof_ipc4_pcm_delay, .ipc_first_on_start = true, - .platform_stop_during_hw_free = true, + .post_trigger = sof_ipc4_pcm_post_trigger, }; diff --git a/sound/soc/sof/ipc4-topology.c b/sound/soc/sof/ipc4-topology.c index f05b000f1d9dc8..1749e74c66ad59 100644 --- a/sound/soc/sof/ipc4-topology.c +++ b/sound/soc/sof/ipc4-topology.c @@ -1277,7 +1277,6 @@ static void sof_ipc4_unprepare_copier_module(struct snd_sof_widget *swidget) } if (ipc4_copier->dai_type == SOF_DAI_INTEL_ALH) { - struct sof_ipc4_copier_data *copier_data = &ipc4_copier->data; struct sof_ipc4_alh_configuration_blob *blob; unsigned int group_id; @@ -1287,9 +1286,6 @@ static void sof_ipc4_unprepare_copier_module(struct snd_sof_widget *swidget) ALH_MULTI_GTW_BASE; ida_free(&alh_group_ida, group_id); } - - /* clear the node ID */ - copier_data->gtw_cfg.node_id &= ~SOF_IPC4_NODE_INDEX_MASK; } } @@ -1717,6 +1713,8 @@ sof_ipc4_prepare_copier_module(struct snd_sof_widget *swidget, */ i = 0; list_for_each_entry(w, &sdev->widget_list, list) { + u32 node_type; + if (w->widget->sname && strcmp(w->widget->sname, swidget->widget->sname)) continue; @@ -1724,7 +1722,11 @@ sof_ipc4_prepare_copier_module(struct snd_sof_widget *swidget, dai = w->private; alh_copier = (struct sof_ipc4_copier *)dai->private; alh_data = &alh_copier->data; - blob->alh_cfg.mapping[i].device = alh_data->gtw_cfg.node_id; + node_type = SOF_IPC4_GET_NODE_TYPE(alh_data->gtw_cfg.node_id); + blob->alh_cfg.mapping[i].device = + SOF_IPC4_GET_NODE_TYPE(node_type); + blob->alh_cfg.mapping[i].device |= + SOF_IPC4_NODE_INDEX(alh_copier->dai_index); /* * The mapping[i] device in ALH blob should be the same as the @@ -2843,12 +2845,15 @@ static int sof_ipc4_dai_config(struct snd_sof_dev *sdev, struct snd_sof_widget * /* * Do not clear the node ID when this op is invoked with * SOF_DAI_CONFIG_FLAGS_HW_FREE. It is needed to free the group_ida during - * unprepare. + * unprepare. The node_id for multi-gateway DAI's will be overwritten with the + * group_id during copier's ipc_prepare op. */ if (flags & SOF_DAI_CONFIG_FLAGS_HW_PARAMS) { + ipc4_copier->dai_index = data->dai_node_id; copier_data->gtw_cfg.node_id &= ~SOF_IPC4_NODE_INDEX_MASK; copier_data->gtw_cfg.node_id |= SOF_IPC4_NODE_INDEX(data->dai_node_id); } + break; case SOF_DAI_INTEL_DMIC: case SOF_DAI_INTEL_SSP: diff --git a/sound/soc/sof/ipc4-topology.h b/sound/soc/sof/ipc4-topology.h index aa5122c3721d3a..6e33208a357f5d 100644 --- a/sound/soc/sof/ipc4-topology.h +++ b/sound/soc/sof/ipc4-topology.h @@ -45,6 +45,7 @@ #define SOF_IPC4_NODE_INDEX_MASK 0xFF #define SOF_IPC4_NODE_INDEX(x) ((x) & SOF_IPC4_NODE_INDEX_MASK) #define SOF_IPC4_NODE_TYPE(x) ((x) << 8) +#define SOF_IPC4_GET_NODE_TYPE(node_id) ((node_id) >> 8) /* Node ID for SSP type DAI copiers */ #define SOF_IPC4_NODE_INDEX_INTEL_SSP(x) (((x) & 0xf) << 4) diff --git a/sound/soc/sof/pcm.c b/sound/soc/sof/pcm.c index 2f86aef4bc9c08..2adcf852953494 100644 --- a/sound/soc/sof/pcm.c +++ b/sound/soc/sof/pcm.c @@ -184,6 +184,7 @@ static int sof_pcm_hw_params(struct snd_soc_component *component, } spcm->prepared[substream->stream] = true; + spcm->stream_stopped[substream->stream] = false; /* save pcm hw_params */ memcpy(&spcm->params[substream->stream], params, sizeof(*params)); @@ -196,7 +197,6 @@ static int sof_pcm_hw_free(struct snd_soc_component *component, { struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component); - const struct sof_ipc_pcm_ops *pcm_ops = sof_ipc_get_ops(sdev, pcm); struct snd_sof_pcm *spcm; int ret, err = 0; @@ -211,21 +211,6 @@ static int sof_pcm_hw_free(struct snd_soc_component *component, dev_dbg(component->dev, "pcm: free stream %d dir %d\n", spcm->pcm.pcm_id, substream->stream); - if (spcm->prepared[substream->stream]) { - /* stop DMA first if needed */ - if (pcm_ops && pcm_ops->platform_stop_during_hw_free) - snd_sof_pcm_platform_trigger(sdev, substream, SNDRV_PCM_TRIGGER_STOP); - - /* free PCM in the DSP */ - if (pcm_ops && pcm_ops->hw_free) { - ret = pcm_ops->hw_free(component, substream); - if (ret < 0) - err = ret; - } - - spcm->prepared[substream->stream] = false; - } - /* reset DMA */ ret = snd_sof_pcm_platform_hw_free(sdev, substream); if (ret < 0) { @@ -238,6 +223,9 @@ static int sof_pcm_hw_free(struct snd_soc_component *component, if (ret < 0) err = ret; + spcm->prepared[substream->stream] = false; + spcm->stream_stopped[substream->stream] = false; + cancel_work_sync(&spcm->stream[substream->stream].period_elapsed_work); return err; @@ -247,6 +235,7 @@ static int sof_pcm_prepare(struct snd_soc_component *component, struct snd_pcm_substream *substream) { struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component); struct snd_sof_pcm *spcm; int ret; @@ -258,8 +247,16 @@ static int sof_pcm_prepare(struct snd_soc_component *component, if (!spcm) return -EINVAL; - if (spcm->prepared[substream->stream]) - return 0; + if (spcm->prepared[substream->stream]) { + /* reset DMA and free widget lists to start over */ + if (spcm->stream_stopped[substream->stream]) { + ret = sof_pcm_stream_free(sdev, substream, spcm, substream->stream, true); + if (ret < 0) + return ret; + } else { + return 0; + } + } dev_dbg(component->dev, "pcm: prepare stream %d dir %d\n", spcm->pcm.pcm_id, substream->stream); @@ -367,9 +364,20 @@ static int sof_pcm_trigger(struct snd_soc_component *component, case SNDRV_PCM_TRIGGER_SUSPEND: case SNDRV_PCM_TRIGGER_PAUSE_PUSH: case SNDRV_PCM_TRIGGER_STOP: - /* invoke platform trigger to stop DMA even if pcm_ops isn't set or if it failed */ - if (!pcm_ops || !pcm_ops->platform_stop_during_hw_free) - snd_sof_pcm_platform_trigger(sdev, substream, cmd); + /* always invoke platform trigger to stop DMA */ + snd_sof_pcm_platform_trigger(sdev, substream, cmd); + + /* nothing more to do for DSP-less mode */ + if (!pcm_ops || cmd == SNDRV_PCM_TRIGGER_PAUSE_PUSH) + break; + + /* For IPC4, reset pipelines after stopping DMA */ + if (pcm_ops->post_trigger) { + ret = pcm_ops->post_trigger(sdev->component, substream); + if (ret < 0) + return ret; + spcm->stream_stopped[substream->stream] = true; + } break; default: break; diff --git a/sound/soc/sof/sof-audio.c b/sound/soc/sof/sof-audio.c index e693dcb475e4d6..91fd858b1bb30e 100644 --- a/sound/soc/sof/sof-audio.c +++ b/sound/soc/sof/sof-audio.c @@ -835,19 +835,10 @@ int sof_pcm_stream_free(struct snd_sof_dev *sdev, struct snd_pcm_substream *subs const struct sof_ipc_pcm_ops *pcm_ops = sof_ipc_get_ops(sdev, pcm); int ret; - if (spcm->prepared[substream->stream]) { - /* stop DMA first if needed */ - if (pcm_ops && pcm_ops->platform_stop_during_hw_free) - snd_sof_pcm_platform_trigger(sdev, substream, SNDRV_PCM_TRIGGER_STOP); - - /* Send PCM_FREE IPC to reset pipeline */ - if (pcm_ops && pcm_ops->hw_free) { - ret = pcm_ops->hw_free(sdev->component, substream); - if (ret < 0) - return ret; - } - - spcm->prepared[substream->stream] = false; + if (pcm_ops && pcm_ops->hw_free) { + ret = pcm_ops->hw_free(sdev->component, substream); + if (ret < 0) + return ret; } /* reset the DMA */ @@ -862,6 +853,9 @@ int sof_pcm_stream_free(struct snd_sof_dev *sdev, struct snd_pcm_substream *subs dev_err(sdev->dev, "failed to free widgets during suspend\n"); } + spcm->prepared[substream->stream] = false; + spcm->stream_stopped[substream->stream] = false; + return ret; } diff --git a/sound/soc/sof/sof-audio.h b/sound/soc/sof/sof-audio.h index 64158ee6c3a839..f05a4af4120594 100644 --- a/sound/soc/sof/sof-audio.h +++ b/sound/soc/sof/sof-audio.h @@ -104,16 +104,12 @@ struct snd_sof_dai_config_data { * additional memory in the SOF PCM stream structure * @pcm_free: Function pointer for PCM free that can be used for freeing any * additional memory in the SOF PCM stream structure + * @post_trigger: Op to perform operations after platform trigger has been completed * @delay: Function pointer for pcm delay calculation * @reset_hw_params_during_stop: Flag indicating whether the hw_params should be reset during the * STOP pcm trigger * @ipc_first_on_start: Send IPC before invoking platform trigger during * START/PAUSE_RELEASE triggers - * @platform_stop_during_hw_free: Invoke the platform trigger during hw_free. This is needed for - * IPC4 where a pipeline is only paused during stop/pause/suspend - * triggers. The FW keeps the host DMA running in this case and - * therefore the host must do the same and should stop the DMA during - * hw_free. * @d0i3_supported_in_s0ix: Allow DSP D0I3 during S0iX */ struct sof_ipc_pcm_ops { @@ -126,11 +122,12 @@ struct sof_ipc_pcm_ops { int (*dai_link_fixup)(struct snd_soc_pcm_runtime *rtd, struct snd_pcm_hw_params *params); int (*pcm_setup)(struct snd_sof_dev *sdev, struct snd_sof_pcm *spcm); void (*pcm_free)(struct snd_sof_dev *sdev, struct snd_sof_pcm *spcm); + int (*post_trigger)(struct snd_soc_component *component, + struct snd_pcm_substream *substream); snd_pcm_sframes_t (*delay)(struct snd_soc_component *component, struct snd_pcm_substream *substream); bool reset_hw_params_during_stop; bool ipc_first_on_start; - bool platform_stop_during_hw_free; bool d0i3_supported_in_s0ix; }; @@ -344,6 +341,7 @@ struct snd_sof_pcm { struct list_head list; /* list in sdev pcm list */ struct snd_pcm_hw_params params[2]; bool prepared[2]; /* PCM_PARAMS set successfully */ + bool stream_stopped[2]; /* only used with pcm_ops->post_trigger */ }; struct snd_sof_led_control {