From 216feda71ccf524ce6c68a239389a9ca9dc5ac15 Mon Sep 17 00:00:00 2001 From: Ranjani Sridharan Date: Mon, 24 Nov 2025 14:16:10 -0800 Subject: [PATCH 1/6] uapi: sound: sof: tokens: Add missing token for KCPS Align with the firmware and add the missing token for pipeline kcps. Signed-off-by: Ranjani Sridharan --- include/uapi/sound/sof/tokens.h | 1 + 1 file changed, 1 insertion(+) diff --git a/include/uapi/sound/sof/tokens.h b/include/uapi/sound/sof/tokens.h index 5fa8ab5088e031..a68381a263ebe3 100644 --- a/include/uapi/sound/sof/tokens.h +++ b/include/uapi/sound/sof/tokens.h @@ -56,6 +56,7 @@ #define SOF_TKN_SCHED_LP_MODE 207 #define SOF_TKN_SCHED_MEM_USAGE 208 #define SOF_TKN_SCHED_USE_CHAIN_DMA 209 +#define SOF_TKN_SCHED_KCPS 210 /* volume */ #define SOF_TKN_VOLUME_RAMP_STEP_TYPE 250 From 4e33d00c18868ae60c1add75fa264df081a9ca9b Mon Sep 17 00:00:00 2001 From: Ranjani Sridharan Date: Mon, 24 Nov 2025 14:43:48 -0800 Subject: [PATCH 2/6] ASoC: Intel: sof_sdw: Add a DAI link for loopback capture Add a DAI link for loopback capture as the last link to make sure the other DAI link ID's remain unaffected. It serves as a dummy DAI link to enable echo reference capture in the SDW topologies which do not have an actual backend capture DAI. Signed-off-by: Ranjani Sridharan --- sound/soc/intel/boards/sof_sdw.c | 43 ++++++++++++++++++++++++++++++-- 1 file changed, 41 insertions(+), 2 deletions(-) diff --git a/sound/soc/intel/boards/sof_sdw.c b/sound/soc/intel/boards/sof_sdw.c index c726d6c30f640f..09f889ac273889 100644 --- a/sound/soc/intel/boards/sof_sdw.c +++ b/sound/soc/intel/boards/sof_sdw.c @@ -1196,6 +1196,34 @@ static int create_bt_dailinks(struct snd_soc_card *card, return 0; } +static int create_echoref_dailink(struct snd_soc_card *card, + struct snd_soc_dai_link **dai_links, int *be_id) +{ + struct device *dev = card->dev; + int ret; + char *name = devm_kasprintf(dev, GFP_KERNEL, "Loopback_Virtual"); + + if (!name) + return -ENOMEM; + + /* + * use dummy DAI names as this won't be connected to an actual DAI but just to establish a + * fe <-> be connection for loopback capture for echo reference + */ + ret = asoc_sdw_init_simple_dai_link(dev, *dai_links, be_id, name, + 0, 1, "snd-soc-dummy-dai", "dummy", + snd_soc_dummy_dlc.name, snd_soc_dummy_dlc.dai_name, + 1, NULL, NULL); + if (ret) + return ret; + + (*dai_links)++; + + dev_dbg(dev, "Added echo reference DAI link\n"); + + return 0; +} + static int sof_card_dai_links_create(struct snd_soc_card *card) { struct device *dev = card->dev; @@ -1304,8 +1332,12 @@ static int sof_card_dai_links_create(struct snd_soc_card *card) goto err_end; } - /* allocate BE dailinks */ - num_links = sdw_be_num + ssp_num + dmic_num + hdmi_num + bt_num; + /* + * allocate BE dailinks, add an extra DAI link for echo reference capture. + * This should be the last DAI link and it is expected both for monolithic + * and functional SOF topologies to support echo reference. + */ + num_links = sdw_be_num + ssp_num + dmic_num + hdmi_num + bt_num + 1; dai_links = devm_kcalloc(dev, num_links, sizeof(*dai_links), GFP_KERNEL); if (!dai_links) { ret = -ENOMEM; @@ -1354,6 +1386,13 @@ static int sof_card_dai_links_create(struct snd_soc_card *card) goto err_end; } + /* dummy echo ref link. keep this as the last DAI link. The DAI link ID does not matter */ + ret = create_echoref_dailink(card, &dai_links, &be_id); + if (ret) { + dev_err(dev, "failed to create echo ref dai link: %d\n", ret); + goto err_end; + } + WARN_ON(codec_conf != card->codec_conf + card->num_configs); WARN_ON(dai_links != card->dai_link + card->num_links); From d6ec5838116d9d0ce6e322837588f4ac682e9d2c Mon Sep 17 00:00:00 2001 From: Ranjani Sridharan Date: Mon, 1 Dec 2025 11:31:30 -0800 Subject: [PATCH 3/6] ASoC: SOF: ipc4-topology: Add new tokens for pipeline direction Parse the pipeline direction from topology. The direction_valid token is required for backward-compatibility with older topologies that may not have the direction set for pipelines. This will be used when setting up pipelines to check if a pipeline is in the same direction as the requested params and skip those in the opposite direction like in the case of echo reference capture pipelines during playback. Signed-off-by: Ranjani Sridharan --- include/uapi/sound/sof/tokens.h | 2 ++ sound/soc/sof/ipc4-topology.c | 12 ++++++++++-- sound/soc/sof/ipc4-topology.h | 4 ++++ sound/soc/sof/sof-audio.h | 5 +++++ 4 files changed, 21 insertions(+), 2 deletions(-) diff --git a/include/uapi/sound/sof/tokens.h b/include/uapi/sound/sof/tokens.h index a68381a263ebe3..f4a7baadb44da0 100644 --- a/include/uapi/sound/sof/tokens.h +++ b/include/uapi/sound/sof/tokens.h @@ -57,6 +57,8 @@ #define SOF_TKN_SCHED_MEM_USAGE 208 #define SOF_TKN_SCHED_USE_CHAIN_DMA 209 #define SOF_TKN_SCHED_KCPS 210 +#define SOF_TKN_SCHED_DIRECTION 211 +#define SOF_TKN_SCHED_DIRECTION_VALID 212 /* volume */ #define SOF_TKN_VOLUME_RAMP_STEP_TYPE 250 diff --git a/sound/soc/sof/ipc4-topology.c b/sound/soc/sof/ipc4-topology.c index d28aad71c7edd4..09aca52e020ab1 100644 --- a/sound/soc/sof/ipc4-topology.c +++ b/sound/soc/sof/ipc4-topology.c @@ -76,6 +76,10 @@ static const struct sof_topology_token ipc4_sched_tokens[] = { offsetof(struct sof_ipc4_pipeline, core_id)}, {SOF_TKN_SCHED_PRIORITY, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, offsetof(struct sof_ipc4_pipeline, priority)}, + {SOF_TKN_SCHED_DIRECTION, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + offsetof(struct sof_ipc4_pipeline, direction)}, + {SOF_TKN_SCHED_DIRECTION, SND_SOC_TPLG_TUPLE_TYPE_BOOL, get_token_u16, + offsetof(struct sof_ipc4_pipeline, direction_valid)}, }; static const struct sof_topology_token pipeline_tokens[] = { @@ -939,6 +943,10 @@ static int sof_ipc4_widget_setup_comp_pipeline(struct snd_sof_widget *swidget) swidget->core = pipeline->core_id; spipe->core_mask |= BIT(pipeline->core_id); + if (pipeline->direction_valid) { + spipe->direction = pipeline->direction; + spipe->direction_valid = true; + } if (pipeline->use_chain_dma) { dev_dbg(scomp->dev, "Set up chain DMA for %s\n", swidget->widget->name); @@ -954,9 +962,9 @@ static int sof_ipc4_widget_setup_comp_pipeline(struct snd_sof_widget *swidget) goto err; } - dev_dbg(scomp->dev, "pipeline '%s': id %d, pri %d, core_id %u, lp mode %d\n", + dev_dbg(scomp->dev, "pipeline '%s': id %d, pri %d, core_id %u, lp mode %d direction %d\n", swidget->widget->name, swidget->pipeline_id, - pipeline->priority, pipeline->core_id, pipeline->lp_mode); + pipeline->priority, pipeline->core_id, pipeline->lp_mode, pipeline->direction); swidget->private = pipeline; diff --git a/sound/soc/sof/ipc4-topology.h b/sound/soc/sof/ipc4-topology.h index 9a028a59c5536a..a289c1d8f3ff0e 100644 --- a/sound/soc/sof/ipc4-topology.h +++ b/sound/soc/sof/ipc4-topology.h @@ -150,6 +150,8 @@ struct sof_ipc4_copier_config_set_sink_format { * @use_chain_dma: flag to indicate if the firmware shall use chained DMA * @msg: message structure for pipeline * @skip_during_fe_trigger: skip triggering this pipeline during the FE DAI trigger + * @direction_valid: flag indicating if valid direction is set in topology + * @direction: pipeline direction set in topology if direction_valid is true */ struct sof_ipc4_pipeline { uint32_t priority; @@ -160,6 +162,8 @@ struct sof_ipc4_pipeline { bool use_chain_dma; struct sof_ipc4_msg msg; bool skip_during_fe_trigger; + bool direction_valid; + u32 direction; }; /** diff --git a/sound/soc/sof/sof-audio.h b/sound/soc/sof/sof-audio.h index 5f62a34582dab9..36082e764bf99e 100644 --- a/sound/soc/sof/sof-audio.h +++ b/sound/soc/sof/sof-audio.h @@ -512,6 +512,9 @@ 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 + * @direction_valid: flag indicating if the direction is set in topology + * @direction: pipeline direction set in topology, valid is direction_valid is true + * */ struct snd_sof_pipeline { struct snd_sof_widget *pipe_widget; @@ -520,6 +523,8 @@ struct snd_sof_pipeline { int complete; unsigned long core_mask; struct list_head list; + bool direction_valid; + u32 direction; }; /* ASoC SOF DAPM route */ From 01ffdb74fc0395cd5532014a6f7a65b00300e8a5 Mon Sep 17 00:00:00 2001 From: Ranjani Sridharan Date: Mon, 1 Dec 2025 11:36:19 -0800 Subject: [PATCH 4/6] ASoC: SOF: ipc4-topology: Add support for process modules with no input pins A tone generator module can be a type of processing module with no input pins. Adjust the logic to set the reference params for selecting output format and the basecfg format based on the output format. Signed-off-by: Ranjani Sridharan --- sound/soc/sof/ipc4-topology.c | 50 +++++++++++++++++++++++++++-------- 1 file changed, 39 insertions(+), 11 deletions(-) diff --git a/sound/soc/sof/ipc4-topology.c b/sound/soc/sof/ipc4-topology.c index 09aca52e020ab1..622bffb50a1c79 100644 --- a/sound/soc/sof/ipc4-topology.c +++ b/sound/soc/sof/ipc4-topology.c @@ -2753,12 +2753,14 @@ static int sof_ipc4_prepare_process_module(struct snd_sof_widget *swidget, int input_fmt_index = 0; int ret; - input_fmt_index = sof_ipc4_init_input_audio_fmt(sdev, swidget, - &process->base_config, - pipeline_params, - available_fmt); - if (input_fmt_index < 0) - return input_fmt_index; + if (available_fmt->num_input_formats) { + input_fmt_index = sof_ipc4_init_input_audio_fmt(sdev, swidget, + &process->base_config, + pipeline_params, + available_fmt); + if (input_fmt_index < 0) + return input_fmt_index; + } /* Configure output audio format only if the module supports output */ if (available_fmt->num_output_formats) { @@ -2767,12 +2769,28 @@ static int sof_ipc4_prepare_process_module(struct snd_sof_widget *swidget, u32 out_ref_rate, out_ref_channels; int out_ref_valid_bits, out_ref_type; - in_fmt = &available_fmt->input_pin_fmts[input_fmt_index].audio_fmt; + if (available_fmt->num_input_formats) { + in_fmt = &available_fmt->input_pin_fmts[input_fmt_index].audio_fmt; - out_ref_rate = in_fmt->sampling_frequency; - out_ref_channels = SOF_IPC4_AUDIO_FORMAT_CFG_CHANNELS_COUNT(in_fmt->fmt_cfg); - out_ref_valid_bits = SOF_IPC4_AUDIO_FORMAT_CFG_V_BIT_DEPTH(in_fmt->fmt_cfg); - out_ref_type = sof_ipc4_fmt_cfg_to_type(in_fmt->fmt_cfg); + out_ref_rate = in_fmt->sampling_frequency; + out_ref_channels = + SOF_IPC4_AUDIO_FORMAT_CFG_CHANNELS_COUNT(in_fmt->fmt_cfg); + out_ref_valid_bits = + SOF_IPC4_AUDIO_FORMAT_CFG_V_BIT_DEPTH(in_fmt->fmt_cfg); + out_ref_type = sof_ipc4_fmt_cfg_to_type(in_fmt->fmt_cfg); + } else { + /* for modules without input formats, use FE params as reference */ + out_ref_rate = params_rate(fe_params); + out_ref_channels = params_channels(fe_params); + ret = sof_ipc4_get_sample_type(sdev, fe_params); + if (ret < 0) + return ret; + out_ref_type = (u32)ret; + + out_ref_valid_bits = sof_ipc4_get_valid_bits(sdev, fe_params); + if (out_ref_valid_bits < 0) + return out_ref_valid_bits; + } output_fmt_index = sof_ipc4_init_output_audio_fmt(sdev, swidget, &process->base_config, @@ -2800,6 +2818,16 @@ static int sof_ipc4_prepare_process_module(struct snd_sof_widget *swidget, if (ret) return ret; } + + /* set base cfg to match the first output format if there are no input formats */ + if (!available_fmt->num_input_formats) { + struct sof_ipc4_audio_format *out_fmt; + + out_fmt = &available_fmt->output_pin_fmts[0].audio_fmt; + + /* copy output format */ + memcpy(&process->base_config.audio_fmt, out_fmt, sizeof(*out_fmt)); + } } sof_ipc4_dbg_module_audio_format(sdev->dev, swidget, available_fmt, From 7700d9f3ba28670f35d81bd7a3cdf48430e32343 Mon Sep 17 00:00:00 2001 From: Ranjani Sridharan Date: Mon, 1 Dec 2025 12:54:04 -0800 Subject: [PATCH 5/6] ASoC: SOF: sof-audio: Traverse paths with aggregated DAI widgets Aggregated DAI widgets exist in topology for representation and are not actually initialized in the firmware. But in preparation for using this as a virtual DAI for loopback capture, make sure that we can traverse the path from an aggregated DAI widget to the host widget. Signed-off-by: Ranjani Sridharan --- sound/soc/sof/sof-audio.c | 37 ++++++++++++++++++++++++++++++++----- 1 file changed, 32 insertions(+), 5 deletions(-) diff --git a/sound/soc/sof/sof-audio.c b/sound/soc/sof/sof-audio.c index ac2d6660d2fa61..efb5ed7d2b1391 100644 --- a/sound/soc/sof/sof-audio.c +++ b/sound/soc/sof/sof-audio.c @@ -13,6 +13,21 @@ #include "sof-audio.h" #include "ops.h" +/* + * Check if a DAI widget is an aggregated DAI. Aggregated DAI's have names ending in numbers + * starting with 0. For example: in the case of a SDW speaker with 2 amps, the topology contains + * 2 DAI's names alh-copier.SDW1.Playback.0 and alh-copier-SDW1.Playback.1. In this case, only the + * DAI alh-copier.SDW1.Playback.0 is set up in the firmware. The other DAI, + * alh-copier.SDW1.Playback.1 in topology is for the sake of completeness to show aggregation for + * the speaker amp and does 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) { @@ -402,8 +417,9 @@ sof_unprepare_widgets_in_path(struct snd_sof_dev *sdev, struct snd_soc_dapm_widg if (is_virtual_widget(sdev, widget, __func__)) return; - /* skip if the widget is in use or if it is already unprepared */ - if (!swidget || !swidget->prepared || swidget->use_count > 0) + /* skip if the widget in use or already unprepared or is an aggregated DAI */ + if (!swidget || !swidget->prepared || swidget->use_count > 0 || + is_aggregated_dai(swidget)) goto sink_unprepare; widget_ops = tplg_ops ? tplg_ops->widget : NULL; @@ -449,6 +465,10 @@ sof_prepare_widgets_in_path(struct snd_sof_dev *sdev, struct snd_soc_dapm_widget if (!swidget || !widget_ops[widget->id].ipc_prepare || swidget->prepared) goto sink_prepare; + /* skip aggregated DAIs */ + if (is_aggregated_dai(swidget)) + goto sink_prepare; + /* prepare the source widget */ ret = widget_ops[widget->id].ipc_prepare(swidget, fe_params, platform_params, pipeline_params, dir); @@ -493,6 +513,7 @@ static int sof_free_widgets_in_path(struct snd_sof_dev *sdev, struct snd_soc_dap int dir, struct snd_sof_pcm *spcm) { struct snd_soc_dapm_widget_list *list = spcm->stream[dir].list; + struct snd_sof_widget *swidget; struct snd_soc_dapm_path *p; int err; int ret = 0; @@ -500,8 +521,11 @@ static int sof_free_widgets_in_path(struct snd_sof_dev *sdev, struct snd_soc_dap if (is_virtual_widget(sdev, widget, __func__)) return 0; - if (widget->dobj.private) { - err = sof_widget_free(sdev, widget->dobj.private); + swidget = widget->dobj.private; + + /* no need to free aggregated DAI widgets */ + if (swidget && !is_aggregated_dai(swidget)) { + err = sof_widget_free(sdev, swidget); if (err < 0) ret = err; } @@ -545,7 +569,10 @@ static int sof_set_up_widgets_in_path(struct snd_sof_dev *sdev, struct snd_soc_d if (swidget) { int i; - ret = sof_widget_setup(sdev, widget->dobj.private); + if (is_aggregated_dai(swidget)) + goto sink_setup; + + ret = sof_widget_setup(sdev, swidget); if (ret < 0) return ret; From ab6ff2231b99960f70cd0d137b39221420f00fd0 Mon Sep 17 00:00:00 2001 From: Ranjani Sridharan Date: Mon, 1 Dec 2025 17:31:27 -0800 Subject: [PATCH 6/6] ASoC: SOF: sof-audio: Add support for loopback capture An example of a DAI-less loopback pipeline would be the echo reference capture in the speaker playback path. This pipeline is set up as follows: Host(Playback) -> mixin -> mixout -> gain -> module-copier -> DAI | V Host(Capture) <- Process module <- virtual DAI In the above example, the virtual DAI exploits the concept of an aggregated DAI (one with a non-zero DAI ID) in topology to enable this pipeline to work with DPCM. A virtual DAI is a DAI widget with a non-zero DAI ID and hence is skipped when traversing the list of DAPM widgets during widget prepare/set/up/free/unprepare. The process module in the above pipeline generates 0's that are captured by the echo reference PCM. When the playback path is active, the process module acts as a passthrough module to allow the playback samples to be passthrough to the capture host. In order for these pipelines to work properly, the logic for setting/preparing/freeing/unpreparing the widgets needs to be amended to make sure that only the widgets that are in the pipeline in the same direction as the PCM being started are set up. For example, when the playback PCM is started, the capture pipeline widgets also show up in the list of connected DAPM widgets but they shouldn't be set up yet because the echo reference capture PCM hasn't been started yet. Alternatively, when the echo reference capture PCM is started, the playback pipeline widgets should not be setup. Finally, the last step needed to put this all together is the set the routes for widgets connecting the playback and the capture pipelines when both are active. Signed-off-by: Ranjani Sridharan --- sound/soc/sof/sof-audio.c | 148 ++++++++++++++++++++++++-------------- 1 file changed, 96 insertions(+), 52 deletions(-) diff --git a/sound/soc/sof/sof-audio.c b/sound/soc/sof/sof-audio.c index efb5ed7d2b1391..77ff3d0bac8748 100644 --- a/sound/soc/sof/sof-audio.c +++ b/sound/soc/sof/sof-audio.c @@ -269,6 +269,10 @@ int sof_route_setup(struct snd_sof_dev *sdev, struct snd_soc_dapm_widget *wsourc is_virtual_widget(sdev, sink_widget->widget, __func__)) return 0; + /* skip route if source/sink widget is not set up */ + if (!src_widget->use_count || !sink_widget->use_count) + return 0; + /* find route matching source and sink widgets */ list_for_each_entry(sroute, &sdev->route_list, list) if (sroute->src_widget == src_widget && sroute->sink_widget == sink_widget) { @@ -297,10 +301,34 @@ int sof_route_setup(struct snd_sof_dev *sdev, struct snd_soc_dapm_widget *wsourc return 0; } +static bool sof_widget_in_same_direction(struct snd_sof_widget *swidget, int dir) +{ + return swidget->spipe->direction == dir; +} + +static int sof_set_up_same_dir_widget_routes(struct snd_sof_dev *sdev, + struct snd_soc_dapm_widget *wsource, + struct snd_soc_dapm_widget *wsink) +{ + struct snd_sof_widget *src_widget = wsource->dobj.private; + struct snd_sof_widget *sink_widget = wsink->dobj.private; + + /* + * skip setting up route if source and sink are in different directions (ex. playback and + * echo ref) if the direction is set in topology. These will be set up later. It is enough + * to check if the direction_valid is set for one of the widgets as all widgets will have + * the direction set in topology if one is set. + */ + if (sink_widget->spipe->direction_valid && + !sof_widget_in_same_direction(sink_widget, src_widget->spipe->direction)) + return 0; + + return sof_route_setup(sdev, wsource, wsink); +} + static int sof_setup_pipeline_connections(struct snd_sof_dev *sdev, struct snd_soc_dapm_widget_list *list, int dir) { - const struct sof_ipc_tplg_ops *tplg_ops = sof_ipc_get_ops(sdev, tplg); struct snd_soc_dapm_widget *widget; struct snd_sof_route *sroute; struct snd_soc_dapm_path *p; @@ -323,7 +351,8 @@ static int sof_setup_pipeline_connections(struct snd_sof_dev *sdev, continue; if (p->sink->dobj.private) { - ret = sof_route_setup(sdev, widget, p->sink); + ret = sof_set_up_same_dir_widget_routes(sdev, widget, + p->sink); if (ret < 0) return ret; } @@ -339,7 +368,8 @@ static int sof_setup_pipeline_connections(struct snd_sof_dev *sdev, continue; if (p->source->dobj.private) { - ret = sof_route_setup(sdev, p->source, widget); + ret = sof_set_up_same_dir_widget_routes(sdev, p->source, + widget); if (ret < 0) return ret; } @@ -355,7 +385,6 @@ static int sof_setup_pipeline_connections(struct snd_sof_dev *sdev, */ list_for_each_entry(sroute, &sdev->route_list, list) { bool src_widget_in_dapm_list, sink_widget_in_dapm_list; - struct snd_sof_widget *swidget; if (sroute->setup) continue; @@ -364,40 +393,37 @@ static int sof_setup_pipeline_connections(struct snd_sof_dev *sdev, sink_widget_in_dapm_list = widget_in_list(list, sroute->sink_widget->widget); /* - * if both source and sink are in the DAPM list, the route must already have been - * set up above. And if neither are in the DAPM list, the route shouldn't be - * handled now. + * no need to set up the route if both the source and sink widgets are not in the + * DAPM list */ - if (src_widget_in_dapm_list == sink_widget_in_dapm_list) + if (!src_widget_in_dapm_list && !sink_widget_in_dapm_list) continue; /* - * At this point either the source widget or the sink widget is in the DAPM list - * with a route that might need to be set up. Check the use_count of the widget - * that is not in the DAPM list to confirm if it is in use currently before setting - * up the route. + * set up the route only if both the source and sink widgets are in the DAPM list + * but are in different directions. The ones in the same direction would already + * have been set up in the previous loop. */ - if (src_widget_in_dapm_list) - swidget = sroute->sink_widget; - else - swidget = sroute->src_widget; + if (src_widget_in_dapm_list && sink_widget_in_dapm_list) { + struct snd_sof_widget *src_widget, *sink_widget; - scoped_guard(mutex, &swidget->setup_mutex) { - if (!swidget->use_count) - continue; + src_widget = sroute->src_widget->widget->dobj.private; + sink_widget = sroute->sink_widget->widget->dobj.private; - if (tplg_ops && tplg_ops->route_setup) { - /* - * this route will get freed when either the - * source widget or the sink widget is freed - * during hw_free - */ - ret = tplg_ops->route_setup(sdev, sroute); - if (!ret) - sroute->setup = true; - } + /* + * it is enough to check if the direction_valid is set for one of the + * widgets as all widgets will have the direction set in topology if one + * is set. + */ + if (src_widget && sink_widget && + src_widget->spipe->direction_valid && + sof_widget_in_same_direction(sink_widget, src_widget->spipe->direction)) + continue; } + ret = sof_route_setup(sdev, sroute->src_widget->widget, + sroute->sink_widget->widget); + if (ret < 0) return ret; } @@ -407,7 +433,7 @@ static int sof_setup_pipeline_connections(struct snd_sof_dev *sdev, static void sof_unprepare_widgets_in_path(struct snd_sof_dev *sdev, struct snd_soc_dapm_widget *widget, - struct snd_soc_dapm_widget_list *list) + struct snd_soc_dapm_widget_list *list, int dir) { const struct sof_ipc_tplg_ops *tplg_ops = sof_ipc_get_ops(sdev, tplg); struct snd_sof_widget *swidget = widget->dobj.private; @@ -417,9 +443,14 @@ sof_unprepare_widgets_in_path(struct snd_sof_dev *sdev, struct snd_soc_dapm_widg if (is_virtual_widget(sdev, widget, __func__)) return; - /* skip if the widget in use or already unprepared or is an aggregated DAI */ - if (!swidget || !swidget->prepared || swidget->use_count > 0 || - is_aggregated_dai(swidget)) + if (!swidget) + goto sink_unprepare; + + if (swidget->spipe->direction_valid && !sof_widget_in_same_direction(swidget, dir)) + return; + + /* skip widgets in use, those already unprepared or aggregated DAIs */ + if (!swidget->prepared || swidget->use_count > 0 || is_aggregated_dai(swidget)) goto sink_unprepare; widget_ops = tplg_ops ? tplg_ops->widget : NULL; @@ -434,9 +465,10 @@ sof_unprepare_widgets_in_path(struct snd_sof_dev *sdev, struct snd_soc_dapm_widg snd_soc_dapm_widget_for_each_sink_path(widget, p) { if (!widget_in_list(list, p->sink)) continue; + if (!p->walking && p->sink->dobj.private) { p->walking = true; - sof_unprepare_widgets_in_path(sdev, p->sink, list); + sof_unprepare_widgets_in_path(sdev, p->sink, list, dir); p->walking = false; } } @@ -458,15 +490,19 @@ sof_prepare_widgets_in_path(struct snd_sof_dev *sdev, struct snd_soc_dapm_widget if (is_virtual_widget(sdev, widget, __func__)) return 0; + if (!swidget) + goto sink_prepare; + widget_ops = tplg_ops ? tplg_ops->widget : NULL; if (!widget_ops) return 0; - if (!swidget || !widget_ops[widget->id].ipc_prepare || swidget->prepared) - goto sink_prepare; + if (swidget->spipe->direction_valid && !sof_widget_in_same_direction(swidget, dir)) + return 0; - /* skip aggregated DAIs */ - if (is_aggregated_dai(swidget)) + /* skip widgets already prepared or aggregated DAI widgets*/ + if (!widget_ops[widget->id].ipc_prepare || swidget->prepared || + is_aggregated_dai(swidget)) goto sink_prepare; /* prepare the source widget */ @@ -484,6 +520,7 @@ sof_prepare_widgets_in_path(struct snd_sof_dev *sdev, struct snd_soc_dapm_widget snd_soc_dapm_widget_for_each_sink_path(widget, p) { if (!widget_in_list(list, p->sink)) continue; + if (!p->walking && p->sink->dobj.private) { p->walking = true; ret = sof_prepare_widgets_in_path(sdev, p->sink, fe_params, @@ -513,7 +550,7 @@ static int sof_free_widgets_in_path(struct snd_sof_dev *sdev, struct snd_soc_dap int dir, struct snd_sof_pcm *spcm) { struct snd_soc_dapm_widget_list *list = spcm->stream[dir].list; - struct snd_sof_widget *swidget; + struct snd_sof_widget *swidget = widget->dobj.private; struct snd_soc_dapm_path *p; int err; int ret = 0; @@ -521,15 +558,20 @@ static int sof_free_widgets_in_path(struct snd_sof_dev *sdev, struct snd_soc_dap if (is_virtual_widget(sdev, widget, __func__)) return 0; - swidget = widget->dobj.private; + if (!swidget) + goto sink_free; - /* no need to free aggregated DAI widgets */ - if (swidget && !is_aggregated_dai(swidget)) { - err = sof_widget_free(sdev, swidget); - if (err < 0) - ret = err; - } + if (swidget->spipe->direction_valid && !sof_widget_in_same_direction(swidget, dir)) + return 0; + /* skip aggregated DAIs */ + if (is_aggregated_dai(swidget)) + goto sink_free; + + 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) { @@ -569,6 +611,10 @@ static int sof_set_up_widgets_in_path(struct snd_sof_dev *sdev, struct snd_soc_d if (swidget) { int i; + if (swidget->spipe->direction_valid && !sof_widget_in_same_direction(swidget, dir)) + return 0; + + /* skip aggregated DAIs */ if (is_aggregated_dai(swidget)) goto sink_setup; @@ -634,15 +680,13 @@ 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 */ + /* starting widget for playback is of AIF type */ if (dir == SNDRV_PCM_STREAM_PLAYBACK && widget->id != snd_soc_dapm_aif_in) continue; /* starting widget for capture is DAI type */ - if (dir == SNDRV_PCM_STREAM_CAPTURE && widget->id != snd_soc_dapm_dai_out) + if (dir == SNDRV_PCM_STREAM_CAPTURE && widget->id != snd_soc_dapm_dai_out && + widget->id != snd_soc_dapm_output) continue; switch (op) { @@ -672,7 +716,7 @@ sof_walk_widgets_in_order(struct snd_sof_dev *sdev, struct snd_sof_pcm *spcm, break; } case SOF_WIDGET_UNPREPARE: - sof_unprepare_widgets_in_path(sdev, widget, list); + sof_unprepare_widgets_in_path(sdev, widget, list, dir); break; default: dev_err(sdev->dev, "Invalid widget op %d\n", op);