From 4f328ac73f35186fa9f094f5fdf5a6b2bab33569 Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Wed, 30 Sep 2026 11:29:40 +0300 Subject: [PATCH] ASoC: SOF: ipc4-topology: Pick the output pin format per path A process module with several output pins can be shared by paths of different PCMs, each using a different pin, e.g. an ECNS module with a mono pin 0 feeding a keyword buffer and a stereo pin 1 feeding a host copier. The output format is chosen per path from the FE params when the widget is first prepared, but it is only stored in process->output_format, and only propagated to the downstream widgets, when it belongs to pin 0. When a second PCM walks the already prepared widget, the prepared shortcut propagates process->output_format. If the first PCM used pin 1, that format was never set and hw_params fails: invalid PCM valid_bits 0 failed to prepare widget ecns.115.1 Even when it was set, it is the pin 0 format regardless of the pin the current path uses. Move the output format lookup to sof_ipc4_process_output_fmt() and use it for already prepared widgets too, based on the input format already selected for the module. The lookup writes to a scratch copy of the base config, as the module has been instantiated already. Update the pipeline params from the selected pin format for every pin index, not only for pin 0. Signed-off-by: Peter Ujfalusi --- sound/soc/sof/ipc4-topology.c | 168 +++++++++++++++++++--------------- 1 file changed, 95 insertions(+), 73 deletions(-) diff --git a/sound/soc/sof/ipc4-topology.c b/sound/soc/sof/ipc4-topology.c index 8b13c97b348044..273282135057f7 100644 --- a/sound/soc/sof/ipc4-topology.c +++ b/sound/soc/sof/ipc4-topology.c @@ -2949,6 +2949,74 @@ static int sof_ipc4_process_add_base_cfg_extn(struct snd_sof_widget *swidget) return 0; } +/* + * Look up the output format of a process module for the path being prepared. + * @in_fmt is the selected input format, NULL for modules without input formats. + */ +static int sof_ipc4_process_output_fmt(struct snd_sof_dev *sdev, + struct snd_sof_widget *swidget, + struct sof_ipc4_base_module_cfg *base_config, + struct sof_ipc4_available_audio_format *available_fmt, + struct sof_ipc4_audio_format *in_fmt, + struct snd_pcm_hw_params *fe_params, int dir) +{ + u32 ref_rate, ref_channels; + int ref_valid_bits, ref_type; + + if (in_fmt) { + /* + * The process module can change parameters and their operation + * depends on the direction: + * Playback: typically they have single output format. This is + * to 'force' the conversion from input to output. + * Use the input format as reference since the single + * format is going to be picked. + * Capture: typically they have multiple output formats to + * convert from dai (input) to FE (output) parameters. + * Use the input format as base and replace the param + * which is changed by the module with the FE parameter + * Reason: we can have module which changes the + * parameters in path, we cannot use the full + * FE param set for the module output lookup. + */ + ref_rate = in_fmt->sampling_frequency; + ref_channels = SOF_IPC4_AUDIO_FORMAT_CFG_CHANNELS_COUNT(in_fmt->fmt_cfg); + ref_valid_bits = SOF_IPC4_AUDIO_FORMAT_CFG_V_BIT_DEPTH(in_fmt->fmt_cfg); + ref_type = sof_ipc4_fmt_cfg_to_type(in_fmt->fmt_cfg); + } else { + /* for modules without input formats, use FE params as reference */ + ref_rate = params_rate(fe_params); + ref_channels = params_channels(fe_params); + ref_type = sof_ipc4_get_sample_type(sdev, fe_params); + if (ref_type < 0) + return ref_type; + + ref_valid_bits = sof_ipc4_get_valid_bits(sdev, fe_params); + if (ref_valid_bits < 0) + return ref_valid_bits; + } + + if (dir == SNDRV_PCM_STREAM_CAPTURE) { + if (available_fmt->changed_params & BIT(SNDRV_PCM_HW_PARAM_RATE)) + ref_rate = params_rate(fe_params); + if (available_fmt->changed_params & BIT(SNDRV_PCM_HW_PARAM_CHANNELS)) + ref_channels = params_channels(fe_params); + if (available_fmt->changed_params & BIT(SNDRV_PCM_HW_PARAM_FORMAT)) { + ref_valid_bits = sof_ipc4_get_valid_bits(sdev, fe_params); + if (ref_valid_bits < 0) + return ref_valid_bits; + + ref_type = sof_ipc4_get_sample_type(sdev, fe_params); + if (ref_type < 0) + return ref_type; + } + } + + return sof_ipc4_init_output_audio_fmt(sdev, swidget, base_config, available_fmt, + ref_rate, ref_channels, ref_valid_bits, + ref_type); +} + static int sof_ipc4_prepare_process_module(struct snd_sof_widget *swidget, struct snd_pcm_hw_params *fe_params, struct snd_sof_platform_stream_params *platform_params, @@ -2965,15 +3033,26 @@ static int sof_ipc4_prepare_process_module(struct snd_sof_widget *swidget, if (available_fmt->num_input_formats) { if (swidget->prepared) { + /* the module is instantiated, do not modify its base config */ + struct sof_ipc4_base_module_cfg scratch = process->base_config; + struct sof_ipc4_audio_format *in_fmt = &process->base_config.audio_fmt; + if (!available_fmt->num_output_formats) return 0; + /* a multi-output module can be shared by paths using different pins */ + output_fmt_index = sof_ipc4_process_output_fmt(sdev, swidget, &scratch, + available_fmt, in_fmt, + fe_params, dir); + if (output_fmt_index < 0) + return output_fmt_index; + /* modify the pipeline params with the output format */ return sof_ipc4_update_hw_params(sdev, pipeline_params, - &process->output_format, - BIT(SNDRV_PCM_HW_PARAM_FORMAT) | - BIT(SNDRV_PCM_HW_PARAM_CHANNELS) | - BIT(SNDRV_PCM_HW_PARAM_RATE)); + &available_fmt->output_pin_fmts[output_fmt_index].audio_fmt, + BIT(SNDRV_PCM_HW_PARAM_FORMAT) | + BIT(SNDRV_PCM_HW_PARAM_CHANNELS) | + BIT(SNDRV_PCM_HW_PARAM_RATE)); } input_fmt_index = sof_ipc4_init_input_audio_fmt(sdev, swidget, @@ -2986,88 +3065,31 @@ static int sof_ipc4_prepare_process_module(struct snd_sof_widget *swidget, /* Configure output audio format only if the module supports output */ if (available_fmt->num_output_formats) { - struct sof_ipc4_audio_format *in_fmt; + struct sof_ipc4_audio_format *in_fmt = NULL; struct sof_ipc4_pin_format *pin_fmt; - u32 ref_rate, ref_channels; - int ref_valid_bits, ref_type; - if (available_fmt->num_input_formats) { - /* - * The process module can change parameters and their operation - * depends on the direction: - * Playback: typically they have single output format. This is - * to 'force' the conversion from input to output. - * Use the input format as reference since the single - * format is going to be picked. - * Capture: typically they have multiple output formats to - * convert from dai (input) to FE (output) parameters. - * Use the input format as base and replace the param - * which is changed by the module with the FE parameter - * Reason: we can have module which changes the - * parameters in path, we cannot use the full - * FE param set for the module output lookup. - */ + if (available_fmt->num_input_formats) in_fmt = &available_fmt->input_pin_fmts[input_fmt_index].audio_fmt; - ref_rate = in_fmt->sampling_frequency; - ref_channels = - SOF_IPC4_AUDIO_FORMAT_CFG_CHANNELS_COUNT(in_fmt->fmt_cfg); - ref_valid_bits = - SOF_IPC4_AUDIO_FORMAT_CFG_V_BIT_DEPTH(in_fmt->fmt_cfg); - ref_type = sof_ipc4_fmt_cfg_to_type(in_fmt->fmt_cfg); - } else { - /* for modules without input formats, use FE params as reference */ - ref_rate = params_rate(fe_params); - ref_channels = params_channels(fe_params); - ret = sof_ipc4_get_sample_type(sdev, fe_params); - if (ret < 0) - return ret; - ref_type = (u32)ret; - - ref_valid_bits = sof_ipc4_get_valid_bits(sdev, fe_params); - if (ref_valid_bits < 0) - return ref_valid_bits; - } - - if (dir == SNDRV_PCM_STREAM_CAPTURE) { - if (available_fmt->changed_params & BIT(SNDRV_PCM_HW_PARAM_RATE)) - ref_rate = params_rate(fe_params); - if (available_fmt->changed_params & BIT(SNDRV_PCM_HW_PARAM_CHANNELS)) - ref_channels = params_channels(fe_params); - if (available_fmt->changed_params & BIT(SNDRV_PCM_HW_PARAM_FORMAT)) { - ref_valid_bits = sof_ipc4_get_valid_bits(sdev, fe_params); - if (ref_valid_bits < 0) - return ref_valid_bits; - - ref_type = sof_ipc4_get_sample_type(sdev, fe_params); - if (ref_type < 0) - return ref_type; - } - } - output_fmt_index = sof_ipc4_init_output_audio_fmt(sdev, swidget, - &process->base_config, - available_fmt, - ref_rate, - ref_channels, - ref_valid_bits, - ref_type); + output_fmt_index = sof_ipc4_process_output_fmt(sdev, swidget, + &process->base_config, + available_fmt, in_fmt, + fe_params, dir); if (output_fmt_index < 0) return output_fmt_index; pin_fmt = &available_fmt->output_pin_fmts[output_fmt_index]; /* copy Pin output format for Pin 0 only */ - if (pin_fmt->pin_index == 0) { + if (pin_fmt->pin_index == 0) memcpy(&process->output_format, &pin_fmt->audio_fmt, sizeof(struct sof_ipc4_audio_format)); - /* modify the pipeline params with the output format */ - ret = sof_ipc4_update_hw_params(sdev, pipeline_params, - &process->output_format, - available_fmt->changed_params); - if (ret) - return ret; - } + /* modify the pipeline params with the output format of the selected pin */ + ret = sof_ipc4_update_hw_params(sdev, pipeline_params, &pin_fmt->audio_fmt, + available_fmt->changed_params); + 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) {