From 1934f8e58f7a8831622342b82b0dcd6406994494 Mon Sep 17 00:00:00 2001 From: Ranjani Sridharan Date: Fri, 24 Oct 2025 14:02:28 -0700 Subject: [PATCH 1/4] ASoC: SOF: ipc4-topology: Add direction in the pipeline message Add a couple of bitfields in the IPC message extension for pipelines to include the direction. This will be useful to set the direction for modules belonging to hostless pipelines. Signed-off-by: Ranjani Sridharan --- include/sound/sof/ipc4/header.h | 6 ++++++ sound/soc/sof/ipc4-topology.c | 16 ++++++++++------ 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/include/sound/sof/ipc4/header.h b/include/sound/sof/ipc4/header.h index 5fd2486582cd49..a495ae21c680f6 100644 --- a/include/sound/sof/ipc4/header.h +++ b/include/sound/sof/ipc4/header.h @@ -188,6 +188,12 @@ enum sof_ipc4_pipeline_state { #define SOF_IPC4_GLB_PIPE_EXT_CORE_ID_MASK GENMASK(23, 20) #define SOF_IPC4_GLB_PIPE_EXT_CORE_ID(x) ((x) << SOF_IPC4_GLB_PIPE_EXT_CORE_ID_SHIFT) +#define SOF_IPC4_GLB_PIPE_EXT_DIRECTION_SET_SHIFT 24 +#define SOF_IPC4_GLB_PIPE_EXT_DIRECTION_SET(x) ((x) << SOF_IPC4_GLB_PIPE_EXT_DIRECTION_SET_SHIFT) + +#define SOF_IPC4_GLB_PIPE_EXT_DIRECTION_SHIFT 25 +#define SOF_IPC4_GLB_PIPE_EXT_DIRECTION(x) ((x) << SOF_IPC4_GLB_PIPE_EXT_DIRECTION_SHIFT) + /* pipeline set state ipc msg */ #define SOF_IPC4_GLB_PIPE_STATE_ID_SHIFT 16 #define SOF_IPC4_GLB_PIPE_STATE_ID_MASK GENMASK(23, 16) diff --git a/sound/soc/sof/ipc4-topology.c b/sound/soc/sof/ipc4-topology.c index 020e7a71f26ceb..6f59d8739f47e0 100644 --- a/sound/soc/sof/ipc4-topology.c +++ b/sound/soc/sof/ipc4-topology.c @@ -1281,7 +1281,7 @@ static void sof_ipc4_widget_free_comp_process(struct snd_sof_widget *swidget) static void sof_ipc4_update_resource_usage(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget, - struct sof_ipc4_base_module_cfg *base_config) + struct sof_ipc4_base_module_cfg *base_config, int dir) { struct sof_ipc4_fw_module *fw_module = swidget->module_info; struct snd_sof_widget *pipe_widget; @@ -1313,6 +1313,10 @@ sof_ipc4_update_resource_usage(struct snd_sof_dev *sdev, struct snd_sof_widget * pipeline = pipe_widget->private; pipeline->mem_usage += total; + /* set pipeline direction */ + pipeline->msg.extension |= SOF_IPC4_GLB_PIPE_EXT_DIRECTION_SET(0x1); + pipeline->msg.extension |= SOF_IPC4_GLB_PIPE_EXT_DIRECTION(dir); + /* Update base_config->cpc from the module manifest */ sof_ipc4_update_cpc_from_manifest(sdev, fw_module, base_config); @@ -2452,7 +2456,7 @@ sof_ipc4_prepare_copier_module(struct snd_sof_widget *swidget, input_fmt_index, output_fmt_index); /* update pipeline memory usage */ - sof_ipc4_update_resource_usage(sdev, swidget, &copier_data->base_config); + sof_ipc4_update_resource_usage(sdev, swidget, &copier_data->base_config, dir); /* copy IPC data */ memcpy(*ipc_config_data, (void *)copier_data, sizeof(*copier_data)); @@ -2515,7 +2519,7 @@ static int sof_ipc4_prepare_gain_module(struct snd_sof_widget *swidget, input_fmt_index, output_fmt_index); /* update pipeline memory usage */ - sof_ipc4_update_resource_usage(sdev, swidget, &gain->data.base_config); + sof_ipc4_update_resource_usage(sdev, swidget, &gain->data.base_config, dir); return 0; } @@ -2560,7 +2564,7 @@ static int sof_ipc4_prepare_mixer_module(struct snd_sof_widget *swidget, input_fmt_index, output_fmt_index); /* update pipeline memory usage */ - sof_ipc4_update_resource_usage(sdev, swidget, &mixer->base_config); + sof_ipc4_update_resource_usage(sdev, swidget, &mixer->base_config, dir); return 0; } @@ -2626,7 +2630,7 @@ static int sof_ipc4_prepare_src_module(struct snd_sof_widget *swidget, input_fmt_index, output_fmt_index); /* update pipeline memory usage */ - sof_ipc4_update_resource_usage(sdev, swidget, &src->data.base_config); + sof_ipc4_update_resource_usage(sdev, swidget, &src->data.base_config, dir); out_audio_fmt = &available_fmt->output_pin_fmts[output_fmt_index].audio_fmt; src->data.sink_rate = out_audio_fmt->sampling_frequency; @@ -2783,7 +2787,7 @@ static int sof_ipc4_prepare_process_module(struct snd_sof_widget *swidget, input_fmt_index, output_fmt_index); /* update pipeline memory usage */ - sof_ipc4_update_resource_usage(sdev, swidget, &process->base_config); + sof_ipc4_update_resource_usage(sdev, swidget, &process->base_config, dir); /* ipc_config_data is composed of the base_config followed by an optional extension */ memcpy(cfg, &process->base_config, sizeof(struct sof_ipc4_base_module_cfg)); From fed3d971ebfd268e96b8115a69e105cadbeb02e7 Mon Sep 17 00:00:00 2001 From: Ranjani Sridharan Date: Wed, 22 Oct 2025 07:19:24 -0700 Subject: [PATCH 2/4] ASoC: SOF: ipc4-topology: Add support for siggen widget Add support for the siggen type widgets in order to support hostless pipelines with the tone generator module in the firmware. Signed-off-by: Ranjani Sridharan --- sound/soc/sof/ipc4-topology.c | 95 +++++++++++++++++++++++++++++++++++ sound/soc/sof/ipc4-topology.h | 21 ++++++++ 2 files changed, 116 insertions(+) diff --git a/sound/soc/sof/ipc4-topology.c b/sound/soc/sof/ipc4-topology.c index 6f59d8739f47e0..a9f6eade4880cd 100644 --- a/sound/soc/sof/ipc4-topology.c +++ b/sound/soc/sof/ipc4-topology.c @@ -1063,6 +1063,52 @@ static int sof_ipc4_widget_setup_comp_mixer(struct snd_sof_widget *swidget) return ret; } +static int sof_ipc4_widget_setup_comp_siggen(struct snd_sof_widget *swidget) +{ + struct snd_soc_component *scomp = swidget->scomp; + struct snd_sof_pipeline *spipe = swidget->spipe; + struct sof_ipc4_siggen *siggen; + int ret; + + dev_dbg(scomp->dev, "Updating IPC structure for %s\n", swidget->widget->name); + + siggen = kzalloc(sizeof(*siggen), GFP_KERNEL); + if (!siggen) + return -ENOMEM; + + swidget->private = siggen; + + ret = sof_ipc4_get_audio_fmt(scomp, swidget, &siggen->available_fmt, + &siggen->data.base_config); + if (ret) + goto err; + + spipe->core_mask |= BIT(swidget->core); + + ret = sof_ipc4_widget_setup_msg(swidget, &siggen->msg); + if (ret) + goto err; + + return 0; +err: + sof_ipc4_free_audio_fmt(&siggen->available_fmt); + kfree(siggen); + swidget->private = NULL; + return ret; +} + +static void sof_ipc4_widget_free_comp_siggen(struct snd_sof_widget *swidget) +{ + struct sof_ipc4_siggen *siggen = swidget->private; + + if (!siggen) + return; + + sof_ipc4_free_audio_fmt(&siggen->available_fmt); + kfree(swidget->private); + swidget->private = NULL; +} + static int sof_ipc4_widget_setup_comp_src(struct snd_sof_widget *swidget) { struct snd_soc_component *scomp = swidget->scomp; @@ -2479,6 +2525,34 @@ sof_ipc4_prepare_copier_module(struct snd_sof_widget *swidget, return 0; } +static int sof_ipc4_prepare_siggen_module(struct snd_sof_widget *swidget, + struct snd_pcm_hw_params *fe_params, + struct snd_sof_platform_stream_params *platform_params, + struct snd_pcm_hw_params *pipeline_params, int dir) +{ + struct snd_soc_component *scomp = swidget->scomp; + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); + struct sof_ipc4_siggen *siggen = swidget->private; + struct sof_ipc4_available_audio_format *available_fmt = &siggen->available_fmt; + struct sof_ipc4_audio_format *out_fmt; + + /* use the first available output format to set the base config audio format */ + if (available_fmt->num_output_formats != 1) { + dev_err(sdev->dev, "siggen should only have one available output format"); + return -EINVAL; + } + out_fmt = &available_fmt->output_pin_fmts[0].audio_fmt; + + /* copy output format */ + memcpy(&siggen->data.base_config.audio_fmt, out_fmt, sizeof(struct sof_ipc4_audio_format)); + siggen->data.base_config.obs = available_fmt->output_pin_fmts[0].buffer_size; + + /* update pipeline memory usage */ + sof_ipc4_update_resource_usage(sdev, swidget, &siggen->data.base_config, dir); + + return 0; +} + static int sof_ipc4_prepare_gain_module(struct snd_sof_widget *swidget, struct snd_pcm_hw_params *fe_params, struct snd_sof_platform_stream_params *platform_params, @@ -3162,6 +3236,16 @@ static int sof_ipc4_widget_setup(struct snd_sof_dev *sdev, struct snd_sof_widget msg = &asrc->msg; break; } + case snd_soc_dapm_siggen: + { + struct sof_ipc4_siggen *siggen = swidget->private; + + ipc_size = sizeof(siggen->data); + ipc_data = &siggen->data; + + msg = &siggen->msg; + break; + } case snd_soc_dapm_effect: { struct sof_ipc4_process *process = swidget->private; @@ -3852,6 +3936,13 @@ static enum sof_tokens mixer_token_list[] = { SOF_COMP_EXT_TOKENS, }; +static enum sof_tokens siggen_token_list[] = { + SOF_COMP_TOKENS, + SOF_AUDIO_FMT_NUM_TOKENS, + SOF_OUT_AUDIO_FORMAT_TOKENS, + SOF_COMP_EXT_TOKENS, +}; + static enum sof_tokens src_token_list[] = { SOF_COMP_TOKENS, SOF_SRC_TOKENS, @@ -3924,6 +4015,10 @@ static const struct sof_ipc_tplg_widget_ops tplg_ipc4_widget_ops[SND_SOC_DAPM_TY process_token_list, ARRAY_SIZE(process_token_list), NULL, sof_ipc4_prepare_process_module, NULL}, + [snd_soc_dapm_siggen] = {sof_ipc4_widget_setup_comp_siggen, + sof_ipc4_widget_free_comp_siggen, + siggen_token_list, ARRAY_SIZE(siggen_token_list), NULL, + sof_ipc4_prepare_siggen_module, NULL}, }; const struct sof_ipc_tplg_ops ipc4_tplg_ops = { diff --git a/sound/soc/sof/ipc4-topology.h b/sound/soc/sof/ipc4-topology.h index 9a028a59c5536a..eea32730a039bf 100644 --- a/sound/soc/sof/ipc4-topology.h +++ b/sound/soc/sof/ipc4-topology.h @@ -462,6 +462,27 @@ struct sof_ipc4_src { struct sof_ipc4_msg msg; }; +/* + * struct sof_ipc4_siggen_data - IPC data for siggen + * @base_config: IPC base config data + * @sink_rate: Output rate for sink module + */ +struct sof_ipc4_siggen_data { + struct sof_ipc4_base_module_cfg base_config; +} __packed __aligned(4); + +/** + * struct sof_ipc4_siggen - siggen config data + * @data: IPC base config data + * @available_fmt: Available audio format + * @msg: IPC4 message struct containing header and data info + */ +struct sof_ipc4_siggen { + struct sof_ipc4_siggen_data data; + struct sof_ipc4_available_audio_format available_fmt; + struct sof_ipc4_msg msg; +}; + /* * struct sof_ipc4_asrc_data - IPC data for ASRC * @base_config: IPC base config data From a2ba70c0fcb73de974cded999ae62561832e1a9a Mon Sep 17 00:00:00 2001 From: Ranjani Sridharan Date: Fri, 24 Oct 2025 14:04:49 -0700 Subject: [PATCH 3/4] ASoC: SOF: pcm: Invoke host_config op for AIF widgets only In the case of hostless pipelines, there are no AIF-type widgets and the tone generator is the source of the pipeline. For such pipelines, skip setting the host config when setting the hw params. Signed-off-by: Ranjani Sridharan --- sound/soc/sof/pcm.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/sof/pcm.c b/sound/soc/sof/pcm.c index 78fbe020e203a5..58a52fd5c8ccb6 100644 --- a/sound/soc/sof/pcm.c +++ b/sound/soc/sof/pcm.c @@ -185,7 +185,7 @@ static int sof_pcm_hw_params(struct snd_soc_component *component, } /* set the host DMA ID */ - if (tplg_ops && tplg_ops->host_config) + if (WIDGET_IS_AIF(host_widget->id) && tplg_ops && tplg_ops->host_config) tplg_ops->host_config(sdev, host_widget, platform_params); } From fb6647492ae6cfd785c9694b372b743c7a9b8c72 Mon Sep 17 00:00:00 2001 From: Ranjani Sridharan Date: Fri, 24 Oct 2025 17:49:44 -0700 Subject: [PATCH 4/4] ASoC: SOF: sof-audio: Add support for hostless pipelines This patch introduces the implementation of hostless pipelines in the SOF driver by leveraging virtual widgets for DAPM. Hostless pipelines are used in scenarios where audio data does not originate from or terminate at the host, such as internal DSP processing pipelines or feedback loops. The implementation includes: - Support for virtual widgets in the topology to represent hostless pipelines. These virtual widgets are solely for the purpose of propagating the hw_params confirguation to the codec in the absence of an AIF component. - Virtual widgets are treated as checkpoints to stop propagating widget setup/free in the SOF driver. This patch updates the logic to the widget setup and free routines to handle virtual widgets and the rest of pipeline downstream from a virtual widget. An example of a hostless pipeline in topology is: Virtual input widget -> ToneGen -> mixin -> mixout -> gain -> DAI widget This change enables the SOF driver to handle pipelines that are entirely internal to the DSP, improving support for use cases like sidetone, amplifier feedback, and other DSP-only processing scenarios. Signed-off-by: Ranjani Sridharan --- sound/soc/sof/sof-audio.c | 89 ++++++++++++++++++++++++++++----------- 1 file changed, 65 insertions(+), 24 deletions(-) diff --git a/sound/soc/sof/sof-audio.c b/sound/soc/sof/sof-audio.c index afb0acb4e3dc4e..6bd3e99112600f 100644 --- a/sound/soc/sof/sof-audio.c +++ b/sound/soc/sof/sof-audio.c @@ -13,6 +13,19 @@ #include "sof-audio.h" #include "ops.h" +/* + * Check is a DAI widget is an aggregated DAI. Aggregated DAI's have names ending in numbers + * starting with 0 and only the first DAI needs to be set up in the firmware. The rest of + * the aggregated DAI's are represented in the topology graph for completeness but do not + * need any firmware configuration. + */ +static bool is_aggregated_dai(struct snd_sof_widget *swidget) +{ + return (WIDGET_IS_DAI(swidget->id) && + isdigit(swidget->widget->name[strlen(swidget->widget->name) - 1]) && + swidget->widget->name[strlen(swidget->widget->name) - 1] != '0'); +} + static bool is_virtual_widget(struct snd_sof_dev *sdev, struct snd_soc_dapm_widget *widget, const char *func) { @@ -412,6 +425,10 @@ sof_unprepare_widgets_in_path(struct snd_sof_dev *sdev, struct snd_soc_dapm_widg struct snd_soc_dapm_path *p; if (is_virtual_widget(sdev, widget, __func__)) + goto sink_unprepare; + + /* skip aggregated DAIs */ + if (!swidget || is_aggregated_dai(swidget)) return; /* skip if the widget is in use or if it is already unprepared */ @@ -452,12 +469,16 @@ sof_prepare_widgets_in_path(struct snd_sof_dev *sdev, struct snd_soc_dapm_widget int ret; if (is_virtual_widget(sdev, widget, __func__)) - return 0; + goto sink_prepare; widget_ops = tplg_ops ? tplg_ops->widget : NULL; if (!widget_ops) return 0; + /* skip aggregated DAIs */ + if (!swidget || is_aggregated_dai(swidget)) + return 0; + if (!swidget || !widget_ops[widget->id].ipc_prepare || swidget->prepared) goto sink_prepare; @@ -506,18 +527,23 @@ static int sof_free_widgets_in_path(struct snd_sof_dev *sdev, struct snd_soc_dap { struct snd_soc_dapm_widget_list *list = spcm->stream[dir].list; struct snd_soc_dapm_path *p; + struct snd_sof_widget *swidget; int err; int ret = 0; if (is_virtual_widget(sdev, widget, __func__)) - return 0; + goto sink_free; - if (widget->dobj.private) { - err = sof_widget_free(sdev, widget->dobj.private); - if (err < 0) - ret = err; - } + swidget = widget->dobj.private; + /* skip aggregated DAIs */ + if (!swidget || is_aggregated_dai(swidget)) + return 0; + + err = sof_widget_free(sdev, widget->dobj.private); + if (err < 0) + ret = err; +sink_free: /* free all widgets in the sink paths even in case of error to keep use counts balanced */ snd_soc_dapm_widget_for_each_sink_path(widget, p) { if (!p->walking) { @@ -552,11 +578,15 @@ static int sof_set_up_widgets_in_path(struct snd_sof_dev *sdev, struct snd_soc_d int ret; if (is_virtual_widget(sdev, widget, __func__)) - return 0; + goto sink_setup; if (swidget) { int i; + /* skip aggregated DAIs */ + if (!swidget || is_aggregated_dai(swidget)) + return 0; + ret = sof_widget_setup(sdev, widget->dobj.private); if (ret < 0) return ret; @@ -619,11 +649,10 @@ sof_walk_widgets_in_order(struct snd_sof_dev *sdev, struct snd_sof_pcm *spcm, return 0; for_each_dapm_widgets(list, i, widget) { - if (is_virtual_widget(sdev, widget, __func__)) - continue; - /* starting widget for playback is AIF type */ - if (dir == SNDRV_PCM_STREAM_PLAYBACK && widget->id != snd_soc_dapm_aif_in) + /* starting widget for playback is of AIF or snd_soc_dapm_input type */ + if (dir == SNDRV_PCM_STREAM_PLAYBACK && (widget->id != snd_soc_dapm_aif_in && + widget->id != snd_soc_dapm_input)) continue; /* starting widget for capture is DAI type */ @@ -904,29 +933,41 @@ struct snd_sof_widget *snd_sof_find_swidget(struct snd_soc_component *scomp, return NULL; } -/* find widget by stream name and direction */ -struct snd_sof_widget * -snd_sof_find_swidget_sname(struct snd_soc_component *scomp, - const char *pcm_name, int dir) +static struct snd_sof_widget *snd_sof_find_swidget_sname_type(struct snd_soc_component *scomp, + const char *sname, + enum snd_soc_dapm_type type) { struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); struct snd_sof_widget *swidget; - enum snd_soc_dapm_type type; - - if (dir == SNDRV_PCM_STREAM_PLAYBACK) - type = snd_soc_dapm_aif_in; - else - type = snd_soc_dapm_aif_out; list_for_each_entry(swidget, &sdev->widget_list, list) { - if (!strcmp(pcm_name, swidget->widget->sname) && - swidget->id == type) + if (!strcmp(sname, swidget->widget->sname) && swidget->id == type) return swidget; } return NULL; } +/* find widget by stream name and direction */ +struct snd_sof_widget * +snd_sof_find_swidget_sname(struct snd_soc_component *scomp, + const char *pcm_name, int dir) +{ + if (dir == SNDRV_PCM_STREAM_PLAYBACK) { + struct snd_sof_widget *swidget; + + /* first look for an aif_in type widget */ + swidget = snd_sof_find_swidget_sname_type(scomp, pcm_name, snd_soc_dapm_aif_in); + if (swidget) + return swidget; + + /* if not found, look for an input type widget */ + return snd_sof_find_swidget_sname_type(scomp, pcm_name, snd_soc_dapm_input); + } + + return snd_sof_find_swidget_sname_type(scomp, pcm_name, snd_soc_dapm_aif_out); +} + struct snd_sof_dai *snd_sof_find_dai(struct snd_soc_component *scomp, const char *name) {