diff --git a/include/uapi/sound/sof/tokens.h b/include/uapi/sound/sof/tokens.h index 5fa8ab5088e031..f4a7baadb44da0 100644 --- a/include/uapi/sound/sof/tokens.h +++ b/include/uapi/sound/sof/tokens.h @@ -56,6 +56,9 @@ #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 +#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/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); diff --git a/sound/soc/sof/ipc4-topology.c b/sound/soc/sof/ipc4-topology.c index d28aad71c7edd4..622bffb50a1c79 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; @@ -2745,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) { @@ -2759,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, @@ -2792,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, 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.c b/sound/soc/sof/sof-audio.c index ac2d6660d2fa61..77ff3d0bac8748 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) { @@ -254,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) { @@ -282,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; @@ -308,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; } @@ -324,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; } @@ -340,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; @@ -349,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; } @@ -392,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; @@ -402,8 +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 is in use or if it is already unprepared */ - if (!swidget || !swidget->prepared || swidget->use_count > 0) + 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; @@ -418,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; } } @@ -442,11 +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) + if (swidget->spipe->direction_valid && !sof_widget_in_same_direction(swidget, dir)) + return 0; + + /* 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 */ @@ -464,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, @@ -493,6 +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 = widget->dobj.private; struct snd_soc_dapm_path *p; int err; int ret = 0; @@ -500,12 +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; - if (widget->dobj.private) { - err = sof_widget_free(sdev, widget->dobj.private); - if (err < 0) - ret = err; - } + if (!swidget) + goto sink_free; + + 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) { @@ -545,7 +611,14 @@ 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 (swidget->spipe->direction_valid && !sof_widget_in_same_direction(swidget, dir)) + return 0; + + /* skip aggregated DAIs */ + if (is_aggregated_dai(swidget)) + goto sink_setup; + + ret = sof_widget_setup(sdev, swidget); if (ret < 0) return ret; @@ -607,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) { @@ -645,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); 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 */