From b8320857979b1f3b6d3555a0b12721db214f8dc6 Mon Sep 17 00:00:00 2001 From: Bard Liao Date: Thu, 29 Oct 2026 08:48:28 +0800 Subject: [PATCH 1/8] ASoC: sdw_utils: add companion amp support Add asoc_sdw_companion_amp_endpoint struct to help machine driver to create codec to codec dai links for SoundWire companion amps. Signed-off-by: Bard Liao --- include/sound/soc_sdw_utils.h | 18 +++- sound/soc/amd/acp/acp-sdw-legacy-mach.c | 17 ++- sound/soc/amd/acp/acp-sdw-sof-mach.c | 17 ++- sound/soc/intel/boards/sof_sdw.c | 134 +++++++++++++++++++++++- sound/soc/sdw_utils/soc_sdw_utils.c | 105 +++++++++++++++++-- 5 files changed, 269 insertions(+), 22 deletions(-) diff --git a/include/sound/soc_sdw_utils.h b/include/sound/soc_sdw_utils.h index 418466438ea1cb..a592ec2101c424 100644 --- a/include/sound/soc_sdw_utils.h +++ b/include/sound/soc_sdw_utils.h @@ -30,6 +30,13 @@ */ #define SOC_SDW_SIDECAR_AMPS BIT(16) #define SOC_SDW_CODEC_MIC BIT(17) +/* + * Unlike the sidecar amps, which are not on the SoundWire bus the companion amps are SoundWire + * peripherals. They get their audio data from another SoundWire peripheral (the main amp) over + * the SoundWire bus instead of from the host, so a codec-to-codec dai link is created between + * the main amp and the companion amps. + */ +#define SOC_SDW_COMPANION_AMPS BIT(18) #define SOC_SDW_UNUSED_DAI_ID -1 #define SOC_SDW_JACK_OUT_DAI_ID 0 @@ -128,6 +135,11 @@ struct asoc_sdw_dailink { struct list_head endpoints; }; +struct asoc_sdw_companion_amp_endpoint { + struct asoc_sdw_endpoint *main_amp; + struct list_head comp_ends; +}; + extern struct asoc_sdw_codec_info codec_info_list[]; int asoc_sdw_get_codec_info_list_count(void); @@ -176,10 +188,13 @@ int asoc_sdw_init_simple_dai_link(struct device *dev, struct snd_soc_dai_link *d const struct snd_soc_ops *ops); int asoc_sdw_count_sdw_endpoints(struct snd_soc_card *card, - int *num_devs, int *num_ends, int *num_aux); + int *num_devs, int *num_ends, int *num_aux, int *num_comp_amps); struct asoc_sdw_dailink *asoc_sdw_find_dailink(struct asoc_sdw_dailink *dailinks, const struct snd_soc_acpi_endpoint *new); +int asoc_sdw_find_amp_endpoints(struct asoc_sdw_dailink *dailink, + struct asoc_sdw_endpoint **amp_ends, + int max_ends); int asoc_sdw_get_dai_type(u32 type); int asoc_sdw_parse_sdw_endpoints(struct device *dev, @@ -187,6 +202,7 @@ int asoc_sdw_parse_sdw_endpoints(struct device *dev, struct snd_soc_aux_dev *soc_aux, struct asoc_sdw_dailink *soc_dais, struct asoc_sdw_endpoint *soc_ends, + struct asoc_sdw_companion_amp_endpoint *comp_ends, int *num_devs); int asoc_sdw_rtd_init(struct snd_soc_pcm_runtime *rtd); diff --git a/sound/soc/amd/acp/acp-sdw-legacy-mach.c b/sound/soc/amd/acp/acp-sdw-legacy-mach.c index a8b62817b2344e..0a6d1d91a5c4fa 100644 --- a/sound/soc/amd/acp/acp-sdw-legacy-mach.c +++ b/sound/soc/amd/acp/acp-sdw-legacy-mach.c @@ -415,9 +415,11 @@ static int soc_card_dai_links_create(struct snd_soc_card *card) struct device *dev = card->dev; int sdw_be_num = 0, dmic_num = 0; struct asoc_sdw_mc_private *ctx = snd_soc_card_get_drvdata(card); + struct asoc_sdw_companion_amp_endpoint *sof_comp_ends; struct snd_soc_aux_dev *soc_aux; struct snd_soc_codec_conf *codec_conf; struct snd_soc_dai_link *dai_links; + int num_comp_amps = 0; int num_devs = 0; int num_ends = 0; int num_aux = 0; @@ -425,8 +427,9 @@ static int soc_card_dai_links_create(struct snd_soc_card *card) int num_links; int be_id = 0; int ret; + int i; - ret = asoc_sdw_count_sdw_endpoints(card, &num_devs, &num_ends, &num_aux); + ret = asoc_sdw_count_sdw_endpoints(card, &num_devs, &num_ends, &num_aux, &num_comp_amps); if (ret < 0) { dev_err(dev, "failed to count devices/endpoints: %d\n", ret); return ret; @@ -442,7 +445,7 @@ static int soc_card_dai_links_create(struct snd_soc_card *card) /* One per endpoint, ie. each DAI on each codec/amp */ struct asoc_sdw_endpoint *soc_ends __free(kfree) = - kzalloc_objs(*soc_ends, num_ends); + kzalloc_objs(*soc_ends, num_ends + num_comp_amps); if (!soc_ends) return -ENOMEM; @@ -450,7 +453,15 @@ static int soc_card_dai_links_create(struct snd_soc_card *card) if (!soc_aux) return -ENOMEM; - ret = asoc_sdw_parse_sdw_endpoints(dev, ctx, soc_aux, soc_dais, soc_ends, &num_confs); + sof_comp_ends = devm_kcalloc(dev, num_comp_amps, sizeof(*sof_comp_ends), GFP_KERNEL); + if (!sof_comp_ends) + return -ENOMEM; + + for (i = 0; i < num_comp_amps; i++) + INIT_LIST_HEAD(&sof_comp_ends[i].comp_ends); + + ret = asoc_sdw_parse_sdw_endpoints(dev, ctx, soc_aux, soc_dais, soc_ends, sof_comp_ends, + &num_confs); if (ret < 0) return ret; diff --git a/sound/soc/amd/acp/acp-sdw-sof-mach.c b/sound/soc/amd/acp/acp-sdw-sof-mach.c index ec3e1f5f1052c5..73a6b71ac95397 100644 --- a/sound/soc/amd/acp/acp-sdw-sof-mach.c +++ b/sound/soc/amd/acp/acp-sdw-sof-mach.c @@ -280,9 +280,11 @@ static int sof_card_dai_links_create(struct snd_soc_card *card) int sdw_be_num = 0, dmic_num = 0; struct asoc_sdw_mc_private *ctx = snd_soc_card_get_drvdata(card); struct snd_soc_acpi_mach_params *mach_params = &mach->mach_params; + struct asoc_sdw_companion_amp_endpoint *sof_comp_ends; struct snd_soc_aux_dev *sof_aux; struct snd_soc_codec_conf *codec_conf; struct snd_soc_dai_link *dai_links; + int num_comp_amps = 0; int num_devs = 0; int num_ends = 0; int num_aux = 0; @@ -290,8 +292,9 @@ static int sof_card_dai_links_create(struct snd_soc_card *card) int num_links; int be_id = 0; int ret; + int i; - ret = asoc_sdw_count_sdw_endpoints(card, &num_devs, &num_ends, &num_aux); + ret = asoc_sdw_count_sdw_endpoints(card, &num_devs, &num_ends, &num_aux, &num_comp_amps); if (ret < 0) { dev_err(dev, "failed to count devices/endpoints: %d\n", ret); return ret; @@ -306,7 +309,7 @@ static int sof_card_dai_links_create(struct snd_soc_card *card) /* One per endpoint, ie. each DAI on each codec/amp */ struct asoc_sdw_endpoint *sof_ends __free(kfree) = - kzalloc_objs(*sof_ends, num_ends); + kzalloc_objs(*sof_ends, num_ends + num_comp_amps); if (!sof_ends) return -ENOMEM; @@ -314,7 +317,15 @@ static int sof_card_dai_links_create(struct snd_soc_card *card) if (!sof_aux) return -ENOMEM; - ret = asoc_sdw_parse_sdw_endpoints(dev, ctx, sof_aux, sof_dais, sof_ends, &num_confs); + sof_comp_ends = devm_kcalloc(dev, num_comp_amps, sizeof(*sof_comp_ends), GFP_KERNEL); + if (!sof_comp_ends) + return -ENOMEM; + + for (i = 0; i < num_comp_amps; i++) + INIT_LIST_HEAD(&sof_comp_ends[i].comp_ends); + + ret = asoc_sdw_parse_sdw_endpoints(dev, ctx, sof_aux, sof_dais, sof_ends, sof_comp_ends, + &num_confs); if (ret < 0) return ret; diff --git a/sound/soc/intel/boards/sof_sdw.c b/sound/soc/intel/boards/sof_sdw.c index b263f554dcc9a9..d9c2ec23fac1e8 100644 --- a/sound/soc/intel/boards/sof_sdw.c +++ b/sound/soc/intel/boards/sof_sdw.c @@ -1225,6 +1225,99 @@ static int create_bt_dailinks(struct snd_soc_card *card, return 0; } +static const struct snd_soc_pcm_stream asoc_sdw_bridge_params = { + .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | + SNDRV_PCM_FMTBIT_S24_LE, + .rate_min = 48000, + .rate_max = 48000, + .channels_min = 2, + .channels_max = 2, +}; + +static const struct snd_soc_dai_link bridge_dai_template = { + .name = "SDW Companion Amp", + .stream_name = "Companion Amp", + .c2c_params = &asoc_sdw_bridge_params, + .num_c2c_params = 1, +}; + +static int create_sdw_comp_dailinks(struct snd_soc_card *card, + struct snd_soc_dai_link **dai_links, int *be_id, + struct snd_soc_codec_conf **codec_conf, + struct asoc_sdw_companion_amp_endpoint *comp_ends, + int comp_ends_num) +{ + struct device *dev = card->dev; + struct asoc_sdw_endpoint *end; + struct snd_soc_dai_link_component *platform; + struct snd_soc_dai_link_component *codecs; + struct snd_soc_dai_link_component *cpus; + char *name; + int num_codecs; + int i; + + for (i = 0; i < comp_ends_num; i++) { + if (!comp_ends[i].main_amp || list_empty(&comp_ends[i].comp_ends)) { + dev_err(dev, "companion amp group %d: missing main amp or companion amps\n", i); + return -EINVAL; + } + + num_codecs = 0; + list_for_each_entry(end, &comp_ends[i].comp_ends, list) + num_codecs++; + + cpus = devm_kzalloc(dev, sizeof(*cpus), GFP_KERNEL); + if (!cpus) + return -ENOMEM; + + codecs = devm_kcalloc(dev, num_codecs, sizeof(*codecs), GFP_KERNEL); + if (!codecs) + return -ENOMEM; + + platform = devm_kzalloc(dev, sizeof(*platform), GFP_KERNEL); + if (!platform) + return -ENOMEM; + + name = devm_kasprintf(dev, GFP_KERNEL, "SDW Companion Amp %d", i); + if (!name) + return -ENOMEM; + + cpus[0].name = comp_ends[i].main_amp->codec_name; + cpus[0].dai_name = comp_ends[i].main_amp->dai_info->dai_name; + platform->name = cpus[0].name; + + (*codec_conf)->dlc.name = comp_ends[i].main_amp->codec_name; + (*codec_conf)->name_prefix = comp_ends[i].main_amp->name_prefix; + (*codec_conf)++; + + num_codecs = 0; + list_for_each_entry(end, &comp_ends[i].comp_ends, list) { + codecs[num_codecs].name = end->codec_name; + codecs[num_codecs].dai_name = end->dai_info->dai_name; + num_codecs++; + + (*codec_conf)->dlc.name = end->codec_name; + (*codec_conf)->name_prefix = end->name_prefix; + (*codec_conf)++; + } + + **dai_links = bridge_dai_template; + (*dai_links)->name = name; + (*dai_links)->stream_name = name; + (*dai_links)->cpus = cpus; + (*dai_links)->num_cpus = 1; + (*dai_links)->codecs = codecs; + (*dai_links)->num_codecs = num_codecs; + (*dai_links)->platforms = platform; + (*dai_links)->num_platforms = 1; + (*dai_links)++; + } + + dev_dbg(dev, "Added SDW Companion AMP Codec to Codec link\n"); + + return 0; +} + static int create_echoref_dailink(struct snd_soc_card *card, struct snd_soc_dai_link **dai_links, int *be_id) { @@ -1261,11 +1354,13 @@ static int sof_card_dai_links_create(struct snd_soc_card *card) struct asoc_sdw_mc_private *ctx = snd_soc_card_get_drvdata(card); struct intel_mc_ctx *intel_ctx = (struct intel_mc_ctx *)ctx->private; struct snd_soc_acpi_mach_params *mach_params = &mach->mach_params; + struct asoc_sdw_companion_amp_endpoint *sof_comp_ends; struct snd_soc_codec_conf *codec_conf; struct asoc_sdw_codec_info *ssp_info; struct asoc_sdw_endpoint *sof_ends; struct asoc_sdw_dailink *sof_dais; struct snd_soc_aux_dev *sof_aux; + int num_comp_amps = 0; int num_devs = 0; int num_ends = 0; int num_aux = 0; @@ -1276,8 +1371,9 @@ static int sof_card_dai_links_create(struct snd_soc_card *card) int hdmi_num; unsigned long ssp_mask; int ret; + int i; - ret = asoc_sdw_count_sdw_endpoints(card, &num_devs, &num_ends, &num_aux); + ret = asoc_sdw_count_sdw_endpoints(card, &num_devs, &num_ends, &num_aux, &num_comp_amps); if (ret < 0) { dev_err(dev, "failed to count devices/endpoints: %d\n", ret); return ret; @@ -1307,12 +1403,31 @@ static int sof_card_dai_links_create(struct snd_soc_card *card) goto err_dai; } - ret = asoc_sdw_parse_sdw_endpoints(dev, ctx, sof_aux, sof_dais, sof_ends, &num_confs); + sof_comp_ends = devm_kcalloc(dev, num_comp_amps, sizeof(*sof_comp_ends), GFP_KERNEL); + if (!sof_comp_ends) { + ret = -ENOMEM; + goto err_dai; + } + + for (i = 0; i < num_comp_amps; i++) + INIT_LIST_HEAD(&sof_comp_ends[i].comp_ends); + + ret = asoc_sdw_parse_sdw_endpoints(dev, ctx, sof_aux, sof_dais, sof_ends, sof_comp_ends, + &num_confs); if (ret < 0) goto err_end; sdw_be_num = ret; + /* Companion amps are not in num_ends but still need a name_prefix codec_conf */ + for (i = 0; i < num_comp_amps; i++) { + struct asoc_sdw_endpoint *comp_end; + + if (sof_comp_ends[i].main_amp) + num_confs++; + list_for_each_entry(comp_end, &sof_comp_ends[i].comp_ends, list) + num_confs++; + } /* * on generic tgl platform, I2S or sdw mode is supported * based on board rework. A ACPI device is registered in @@ -1366,8 +1481,8 @@ static int sof_card_dai_links_create(struct snd_soc_card *card) * 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); + num_links = sdw_be_num + ssp_num + dmic_num + hdmi_num + bt_num + num_comp_amps + 1; + dai_links = devm_kcalloc(dev, num_links + 1, sizeof(*dai_links), GFP_KERNEL); if (!dai_links) { ret = -ENOMEM; goto err_end; @@ -1415,6 +1530,17 @@ static int sof_card_dai_links_create(struct snd_soc_card *card) goto err_end; } + /* + * Companion amps are SoundWire peripherals. They get their audio data from another + * SoundWire peripheral (the main amp) over the SoundWire bus instead of from the host, + * so a codec-to-codec dai link is created between the main amp and the companion amps. + */ + if (num_comp_amps) { + ret = create_sdw_comp_dailinks(card, &dai_links, &be_id, &codec_conf, + sof_comp_ends, num_comp_amps); + if (ret) + 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) { diff --git a/sound/soc/sdw_utils/soc_sdw_utils.c b/sound/soc/sdw_utils/soc_sdw_utils.c index 7a19de75f32b53..7c20ee811d6564 100644 --- a/sound/soc/sdw_utils/soc_sdw_utils.c +++ b/sound/soc/sdw_utils/soc_sdw_utils.c @@ -2269,12 +2269,14 @@ static int is_sdca_aux_dev_present(struct device *dev, } int asoc_sdw_count_sdw_endpoints(struct snd_soc_card *card, - int *num_devs, int *num_ends, int *num_aux) + int *num_devs, int *num_ends, int *num_aux, int *num_comp_amps) { struct device *dev = card->dev; + struct asoc_sdw_mc_private *ctx = snd_soc_card_get_drvdata(card); struct snd_soc_acpi_mach *mach = dev_get_platdata(dev); struct snd_soc_acpi_mach_params *mach_params = &mach->mach_params; const struct snd_soc_acpi_link_adr *adr_link; + u32 companion_amp_mask = 0; int i, j, ret; for (adr_link = mach_params->links; adr_link->num_adr; adr_link++) { @@ -2284,12 +2286,30 @@ int asoc_sdw_count_sdw_endpoints(struct snd_soc_card *card, const struct snd_soc_acpi_adr_device *adr_dev = &adr_link->adr_d[i]; struct asoc_sdw_codec_info *codec_info; - *num_ends += adr_dev->num_endpoints; - codec_info = asoc_sdw_find_codec_info_part(adr_dev->adr); if (!codec_info) return -EINVAL; + for (j = 0; j < adr_dev->num_endpoints; j++) { + /* Skip companion amps */ + if (adr_dev->endpoints[j].num >= codec_info->dai_num) + continue; + if (codec_info->dais[adr_dev->endpoints[j].num].quirk & SOC_SDW_COMPANION_AMPS) { + if (adr_dev->endpoints[j].group_id >= + BITS_PER_TYPE(companion_amp_mask)) { + dev_err(dev, "invalid companion group_id %d\n", + adr_dev->endpoints[j].group_id); + return -EINVAL; + } + /* The companion amp is only present when the quirk is set */ + if (!(ctx->mc_quirk & SOC_SDW_COMPANION_AMPS)) + continue; + companion_amp_mask |= BIT(adr_dev->endpoints[j].group_id); + continue; + } + (*num_ends)++; + } + for (j = 0; j < codec_info->aux_num; j++) { ret = is_sdca_aux_dev_present(dev, codec_info->auxs[j].codec_name, adr_link, i); @@ -2301,6 +2321,7 @@ int asoc_sdw_count_sdw_endpoints(struct snd_soc_card *card, } } + *num_comp_amps = hweight32(companion_amp_mask); dev_dbg(dev, "Found %d devices with %d endpoints\n", *num_devs, *num_ends); return 0; @@ -2412,16 +2433,41 @@ int asoc_sdw_parse_sdw_endpoints(struct device *dev, struct snd_soc_aux_dev *soc_aux, struct asoc_sdw_dailink *soc_dais, struct asoc_sdw_endpoint *soc_ends, + struct asoc_sdw_companion_amp_endpoint *comp_ends, int *num_devs) { struct snd_soc_acpi_mach *mach = dev_get_platdata(dev); struct snd_soc_acpi_mach_params *mach_params = &mach->mach_params; const struct snd_soc_acpi_link_adr *adr_link; struct asoc_sdw_endpoint *soc_end = soc_ends; + u32 companion_amp_mask = 0; int num_dais = 0; int i, j; int ret; + /* Get companion_amp_mask to calculate the companion amp group array index */ + for (adr_link = mach_params->links; adr_link->num_adr; adr_link++) { + for (i = 0; i < adr_link->num_adr; i++) { + const struct snd_soc_acpi_adr_device *adr_dev = &adr_link->adr_d[i]; + struct asoc_sdw_codec_info *codec_info; + + codec_info = asoc_sdw_find_codec_info_part(adr_dev->adr); + if (!codec_info) + return -EINVAL; + + for (j = 0; j < adr_dev->num_endpoints; j++) { + if (adr_dev->endpoints[j].num >= codec_info->dai_num) + continue; + if (!(codec_info->dais[adr_dev->endpoints[j].num].quirk & + SOC_SDW_COMPANION_AMPS)) + continue; + if (!(ctx->mc_quirk & SOC_SDW_COMPANION_AMPS)) + continue; + companion_amp_mask |= BIT(adr_dev->endpoints[j].group_id); + } + } + } + for (adr_link = mach_params->links; adr_link->num_adr; adr_link++) { int num_link_dailinks = 0; @@ -2492,9 +2538,53 @@ int asoc_sdw_parse_sdw_endpoints(struct device *dev, int stream; adr_end = &adr_dev->endpoints[j]; + if (adr_end->num >= codec_info->dai_num) { + dev_err(dev, + "%d is too many endpoints for codec: 0x%x\n", + adr_end->num, codec_info->part_id); + return -EINVAL; + } dai_info = &codec_info->dais[adr_end->num]; - soc_dai = asoc_sdw_find_dailink(soc_dais, adr_end); + if (dai_info->quirk & SOC_SDW_COMPANION_AMPS) { + struct asoc_sdw_endpoint *comp_end; + int comp_index; + + if (!(ctx->mc_quirk & SOC_SDW_COMPANION_AMPS)) + continue; + + comp_index = hweight32(companion_amp_mask & + (BIT(adr_end->group_id) - 1)); + + codec_name = asoc_sdw_get_codec_name(dev, dai_info, + adr_link, i); + if (IS_ERR(codec_name)) + return PTR_ERR(codec_name); + if (!codec_name) + return -ENOMEM; + + comp_end = devm_kcalloc(dev, 1, sizeof(*comp_end), + GFP_KERNEL); + if (!comp_end) + return -ENOMEM; + comp_end->name_prefix = adr_dev->name_prefix; + + comp_end->link_mask = adr_link->mask; + comp_end->codec_name = codec_name; + comp_end->codec_info = codec_info; + comp_end->dai_info = dai_info; + + /* group_position = 0 is the main amp */ + if (!adr_end->group_position) + comp_ends[comp_index].main_amp = comp_end; + else + list_add_tail(&comp_end->list, + &comp_ends[comp_index].comp_ends); + + continue; + } + + soc_dai = asoc_sdw_find_dailink(soc_dais, adr_end); /* * quirk should have higher priority than the sdca properties * in the BIOS. We can't always check the DAI quirk because we @@ -2538,13 +2628,6 @@ int asoc_sdw_parse_sdw_endpoints(struct device *dev, adr_end->aggregated ? "group" : "solo", adr_end->group_id); - if (adr_end->num >= codec_info->dai_num) { - dev_err(dev, - "%d is too many endpoints for codec: 0x%x\n", - adr_end->num, codec_info->part_id); - return -EINVAL; - } - for_each_pcm_streams(stream) { if (dai_info->direction[stream] && dai_info->dailink[stream] < 0) { From e50faaac46d427a2cd0a98239d78376acd16de2e Mon Sep 17 00:00:00 2001 From: Bard Liao Date: Tue, 10 Nov 2026 11:33:48 +0800 Subject: [PATCH 2/8] soundwire: zero-initialize struct sdw_stream_config in manager drivers The sdw_stream_config structures in the manager drivers are not initialized and only some of the fields are set. Initialize them to zero so that any field added to the structure later, and not explicitly set by these drivers, gets its default value instead of stack garbage. Signed-off-by: Bard Liao --- drivers/soundwire/amd_manager.c | 2 +- drivers/soundwire/intel.c | 2 +- drivers/soundwire/intel_ace2x.c | 4 ++-- drivers/soundwire/qcom.c | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/drivers/soundwire/amd_manager.c b/drivers/soundwire/amd_manager.c index a3316efdf8ac27..106a38f936a5ee 100644 --- a/drivers/soundwire/amd_manager.c +++ b/drivers/soundwire/amd_manager.c @@ -769,7 +769,7 @@ static int amd_sdw_hw_params(struct snd_pcm_substream *substream, { struct amd_sdw_manager *amd_manager = snd_soc_dai_get_drvdata(dai); struct sdw_amd_dai_runtime *dai_runtime; - struct sdw_stream_config sconfig; + struct sdw_stream_config sconfig = {0}; int ch, dir; int ret; diff --git a/drivers/soundwire/intel.c b/drivers/soundwire/intel.c index af65214836b4d0..8d769e5864ed09 100644 --- a/drivers/soundwire/intel.c +++ b/drivers/soundwire/intel.c @@ -725,7 +725,7 @@ static int intel_hw_params(struct snd_pcm_substream *substream, struct sdw_intel *sdw = cdns_to_intel(cdns); struct sdw_cdns_dai_runtime *dai_runtime; struct sdw_cdns_pdi *pdi; - struct sdw_stream_config sconfig; + struct sdw_stream_config sconfig = {0}; int ch, dir; int ret; diff --git a/drivers/soundwire/intel_ace2x.c b/drivers/soundwire/intel_ace2x.c index 642b33ab552606..f7a256adc85331 100644 --- a/drivers/soundwire/intel_ace2x.c +++ b/drivers/soundwire/intel_ace2x.c @@ -53,7 +53,7 @@ static int intel_ace2x_bpt_open_stream(struct sdw_intel *sdw, struct sdw_slave * struct sdw_bus *bus = &cdns->bus; struct sdw_master_prop *prop = &bus->prop; struct sdw_stream_runtime *stream; - struct sdw_stream_config sconfig; + struct sdw_stream_config sconfig = {0}; struct sdw_port_config *pconfig; unsigned int pdi0_buf_size_pre_frame; unsigned int pdi1_buf_size_pre_frame; @@ -724,7 +724,7 @@ static int intel_hw_params(struct snd_pcm_substream *substream, struct sdw_intel *sdw = cdns_to_intel(cdns); struct sdw_cdns_dai_runtime *dai_runtime; struct sdw_cdns_pdi *pdi; - struct sdw_stream_config sconfig; + struct sdw_stream_config sconfig = {0}; int ch, dir; int ret; diff --git a/drivers/soundwire/qcom.c b/drivers/soundwire/qcom.c index 6e0c5fb10efad4..a3bffe8a2869c4 100644 --- a/drivers/soundwire/qcom.c +++ b/drivers/soundwire/qcom.c @@ -1301,7 +1301,7 @@ static int qcom_swrm_stream_alloc_ports(struct qcom_swrm_ctrl *ctrl, struct snd_pcm_hw_params *params, int direction) { - struct sdw_stream_config sconfig; + struct sdw_stream_config sconfig = {0}; struct sdw_master_runtime *m_rt; struct sdw_slave_runtime *s_rt; struct sdw_port_runtime *p_rt; From 2fefb0d28f1424f815404f51600f5037c16165dc Mon Sep 17 00:00:00 2001 From: Bard Liao Date: Tue, 8 Sep 2026 10:58:14 +0800 Subject: [PATCH 3/8] soundwire: add companion stream support The companion stream is from peripheral to peripheral and all peripherals need to use the same SoundWire frame area to transfer the stream data. Signed-off-by: Bard Liao --- drivers/soundwire/generic_bandwidth_allocation.c | 4 ++++ drivers/soundwire/stream.c | 1 + include/linux/soundwire/sdw.h | 15 +++++++++++++++ 3 files changed, 20 insertions(+) diff --git a/drivers/soundwire/generic_bandwidth_allocation.c b/drivers/soundwire/generic_bandwidth_allocation.c index 29d9fcef8c709b..5c2d6d4a23d766 100644 --- a/drivers/soundwire/generic_bandwidth_allocation.c +++ b/drivers/soundwire/generic_bandwidth_allocation.c @@ -70,7 +70,11 @@ void sdw_compute_slave_ports(struct sdw_master_runtime *m_rt, port_bo += bps * ch; slave_total_ch += ch; + } + /* Make sure all Peripherals use the same bit slots */ + if (m_rt->stream->transport_type == SDW_TRANSPORT_PERIPHERAL_PERIPHERAL) + port_bo = t_data->block_offset; if (m_rt->direction == SDW_DATA_DIR_TX && m_rt->ch_count == slave_total_ch) { diff --git a/drivers/soundwire/stream.c b/drivers/soundwire/stream.c index c158bf91e020f9..9f6c06df9859a8 100644 --- a/drivers/soundwire/stream.c +++ b/drivers/soundwire/stream.c @@ -1391,6 +1391,7 @@ static int sdw_config_stream(struct device *dev, } stream->type = stream_config->type; + stream->transport_type = stream_config->transport_type; stream->params.rate = stream_config->frame_rate; stream->params.bps = stream_config->bps; diff --git a/include/linux/soundwire/sdw.h b/include/linux/soundwire/sdw.h index 326db122bbe6ea..1dea2e4da7fdbd 100644 --- a/include/linux/soundwire/sdw.h +++ b/include/linux/soundwire/sdw.h @@ -760,6 +760,17 @@ struct sdw_port_params { unsigned int data_mode; }; +/** + * enum sdw_transport_type: Data Port transport type + * + * @SDW_TRANSPORT_MANAGER_PERIPHERAL: Data is exchanged between the Manager and a Peripheral + * @SDW_TRANSPORT_PERIPHERAL_PERIPHERAL: Data is exchanged between Peripherals + */ +enum sdw_transport_type { + SDW_TRANSPORT_MANAGER_PERIPHERAL = 0, + SDW_TRANSPORT_PERIPHERAL_PERIPHERAL = 1, +}; + /** * struct sdw_transport_params: Data Port Transport Parameters * @@ -924,6 +935,7 @@ struct sdw_port_config { * @bps: Number of bits per audio sample * @direction: Data direction * @type: Stream type PCM, PDM or BPT + * @transport_type: Manager-Peripheral (default) or Peripheral-Peripheral transport */ struct sdw_stream_config { unsigned int frame_rate; @@ -931,6 +943,7 @@ struct sdw_stream_config { unsigned int bps; enum sdw_data_direction direction; enum sdw_stream_type type; + enum sdw_transport_type transport_type; }; /** @@ -974,6 +987,7 @@ struct sdw_stream_params { * @params: Stream parameters * @state: Current state of the stream * @type: Stream type PCM, PDM or BPT + * @transport_type: Manager-Peripheral or Peripheral-Peripheral transport * @m_rt_count: Count of Master runtime(s) in this stream * @master_list: List of Master runtime(s) in this stream. * master_list can contain only one m_rt per Master instance @@ -984,6 +998,7 @@ struct sdw_stream_runtime { struct sdw_stream_params params; enum sdw_stream_state state; enum sdw_stream_type type; + enum sdw_transport_type transport_type; int m_rt_count; struct list_head master_list; }; From 7b876adc67d0943900b8721f21b5ff319ef73503 Mon Sep 17 00:00:00 2001 From: Bard Liao Date: Tue, 8 Sep 2026 11:34:15 +0800 Subject: [PATCH 4/8] ASoC: SOF: Intel: skip companion amp dai The companion amp dai links should not be created by the default machine driver. We will create a mach table for the config that companion amp is used. Signed-off-by: Bard Liao --- sound/soc/sof/intel/hda.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/sound/soc/sof/intel/hda.c b/sound/soc/sof/intel/hda.c index e1815bfe358b5d..409e54edcb5196 100644 --- a/sound/soc/sof/intel/hda.c +++ b/sound/soc/sof/intel/hda.c @@ -1209,8 +1209,13 @@ static struct snd_soc_acpi_adr_device *find_acpi_adr_device(struct device *dev, return NULL; } for (j = 0; j < codec_info_list[i].dai_num; j++) { - /* Check if the endpoint is present by the SDCA DisCo table */ - if (!is_endpoint_present(sdw_device, codec_info_list[i].dais[j].dai_type)) + /* + * Check if the endpoint is present by the SDCA DisCo table. Also skip the + * companion amp endpoints. The companion amp endpoints should be added in + * the mach table if it is present. + */ + if (!is_endpoint_present(sdw_device, codec_info_list[i].dais[j].dai_type) || + (codec_info_list[i].dais[j].dai_type == SOC_SDW_DAI_TYPE_COMP_AMP)) continue; endpoints[ep_index].num = j; From 1424590fa92d39795f1e98ad4e4220ac3e13cfba Mon Sep 17 00:00:00 2001 From: Shuming Fan Date: Thu, 5 Nov 2026 14:45:18 +0800 Subject: [PATCH 5/8] sdw-utils: add companion dai ops Signed-off-by: Shuming Fan --- include/sound/soc_sdw_utils.h | 11 ++ sound/soc/sdw_utils/soc_sdw_utils.c | 198 ++++++++++++++++++++++++++++ 2 files changed, 209 insertions(+) diff --git a/include/sound/soc_sdw_utils.h b/include/sound/soc_sdw_utils.h index a592ec2101c424..aa2a9225492c93 100644 --- a/include/sound/soc_sdw_utils.h +++ b/include/sound/soc_sdw_utils.h @@ -152,6 +152,17 @@ int asoc_sdw_hw_params(struct snd_pcm_substream *substream, int asoc_sdw_hw_free(struct snd_pcm_substream *substream); void asoc_sdw_shutdown(struct snd_pcm_substream *substream); +int asoc_sdw_c_amp_probe(struct snd_soc_dai *dai); +void *asoc_sdw_c_amp_get_sdw_stream(struct snd_soc_dai *dai, int direction); +int asoc_sdw_c_amp_startup(struct snd_pcm_substream *substream, struct snd_soc_dai *dai); +int asoc_sdw_c_amp_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, struct snd_soc_dai *dai); +int asoc_sdw_c_amp_hw_free(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai); +int asoc_sdw_c_amp_prepare(struct snd_pcm_substream *substream, struct snd_soc_dai *dai); +void asoc_sdw_c_amp_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai); + const char *asoc_sdw_get_codec_name(struct device *dev, const struct asoc_sdw_dai_info *dai_info, const struct snd_soc_acpi_link_adr *adr_link, diff --git a/sound/soc/sdw_utils/soc_sdw_utils.c b/sound/soc/sdw_utils/soc_sdw_utils.c index 7c20ee811d6564..8aca9d2f9d88e1 100644 --- a/sound/soc/sdw_utils/soc_sdw_utils.c +++ b/sound/soc/sdw_utils/soc_sdw_utils.c @@ -10,6 +10,8 @@ #include #include #include +#include +#include #include #include @@ -2015,6 +2017,202 @@ void asoc_sdw_shutdown(struct snd_pcm_substream *substream) } EXPORT_SYMBOL_NS(asoc_sdw_shutdown, "SND_SOC_SDW_UTILS"); +int asoc_sdw_c_amp_probe(struct snd_soc_dai *dai) +{ + void *data = dev_get_drvdata(dai->dev); + + dev_dbg(dai->dev, "%s %s id %d", __func__, dai->name, dai->id); + + snd_soc_dai_set_drvdata(dai, data); + + return 0; +} +EXPORT_SYMBOL_NS(asoc_sdw_c_amp_probe, "SND_SOC_SDW_UTILS"); + +void *asoc_sdw_c_amp_get_sdw_stream(struct snd_soc_dai *dai, int direction) +{ + void *data = snd_soc_dai_get_drvdata(dai); + + dev_dbg(dai->dev, "%s %s id %d direction %d", __func__, dai->name, dai->id, direction); + + if (data && dai->stream[direction].dma_data) + return dai->stream[direction].dma_data; + else + return ERR_PTR(-EINVAL); +} +EXPORT_SYMBOL_NS(asoc_sdw_c_amp_get_sdw_stream, "SND_SOC_SDW_UTILS"); + +int asoc_sdw_c_amp_startup(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) +{ + struct sdw_stream_runtime *sdw_stream = NULL; + int ret; + dev_dbg(dai->dev, "%s %s id %d", __func__, dai->name, dai->id); + dev_dbg(dai->dev, "%s substream stream %d", __func__, substream->stream); + + //dump_stack(); + ret = sdw_startup_stream(substream); + if (ret) { + dev_err(dai->dev, "%s startup failed: %d\n", __func__, ret); + return ret; + } + + sdw_stream = snd_soc_dai_get_stream(dai, substream->stream); + if (IS_ERR(sdw_stream)) { + dev_err(dai->dev, "no stream found for DAI %s\n", dai->name); + return PTR_ERR(sdw_stream); + } + + sdw_stream->type = SDW_STREAM_COMPANION; + + return 0; +} +EXPORT_SYMBOL_NS(asoc_sdw_c_amp_startup, "SND_SOC_SDW_UTILS"); + +int asoc_sdw_c_amp_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) +{ + struct sdw_slave *slave = dev_to_sdw_dev(dai->dev); + struct sdw_stream_runtime *sdw_stream = NULL; + struct sdw_stream_config sconfig; + struct sdw_port_config pconfig; + int retval; + + dev_err(dai->dev, "bard: %s %s id %d", __func__, dai->name, dai->id); + dev_dbg(dai->dev, "%s substream stream %d", __func__, substream->stream); + + sdw_stream = snd_soc_dai_get_dma_data(dai, substream); + if (!sdw_stream) + return -EINVAL; + printk("%s, sdw_stream 0x%p\n", __func__, sdw_stream); + + snd_sdw_params_to_config(substream, params, &sconfig, &pconfig); + + //sconfig.direction = SDW_DATA_DIR_TX; // capture + //sconfig.direction = SDW_DATA_DIR_RX; // playback + //sconfig.ch_count = 2; + //sconfig.frame_rate = params_rate(params); + //sconfig.bps = snd_pcm_format_width(params_format(params)); + //pconfig.ch_mask = BIT(0) | BIT(1); + sconfig.type = sdw_stream->type; + pconfig.num = 0; // no meaning for companion stream + + retval = sdw_stream_add_master(slave->bus, &sconfig, &pconfig, 1, sdw_stream); + if (retval) { + dev_err(dai->dev, "%s: Unable to add master, ret=%d\n", __func__, retval); + return retval; + } + printk("%s, master sdw_stream 0x%p, type %d\n", __func__, sdw_stream, sdw_stream->type); + + snd_soc_dai_set_stream(dai, sdw_stream, SNDRV_PCM_STREAM_PLAYBACK); + snd_soc_dai_set_stream(dai, sdw_stream, SNDRV_PCM_STREAM_CAPTURE); + + return 0; +} +EXPORT_SYMBOL_NS(asoc_sdw_c_amp_hw_params, "SND_SOC_SDW_UTILS"); + +int asoc_sdw_c_amp_prepare(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) +{ + //struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); + struct sdw_stream_runtime *sdw_stream; + //struct snd_soc_dai *dai; + int ret; + + // whether SNDRV_PCM_STREAM_PLAYBACK/SNDRV_PCM_STREAM_CAPTURE direction is used + // all can enables the companion stream + + dev_dbg(dai->dev, "%s %s id %d", __func__, dai->name, dai->id); + dev_dbg(dai->dev, "%s substream stream %d", __func__, substream->stream); + + //dump_stack(); + + /* Find stream from first CPU DAI */ + //dai = snd_soc_rtd_to_cpu(rtd, 0); + + sdw_stream = snd_soc_dai_get_stream(dai, substream->stream); + if (IS_ERR(sdw_stream)) { + dev_err(dai->dev, "%s no stream found for DAI %s id %d\n", __func__, dai->name, dai->id); + return PTR_ERR(sdw_stream); + } + + ret = sdw_prepare_stream(sdw_stream); + if (ret) { + dev_err(dai->dev, "%s prepare failed: %d\n", __func__, ret); + return ret; + } + + ret = sdw_enable_stream(sdw_stream); + if (ret) + dev_err(dai->dev, "%s trigger failed: %d\n", __func__, ret); + + return ret; +} +EXPORT_SYMBOL_NS(asoc_sdw_c_amp_prepare, "SND_SOC_SDW_UTILS"); + +int asoc_sdw_c_amp_hw_free(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + //struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); + struct sdw_slave *slave = dev_to_sdw_dev(dai->dev); + struct sdw_stream_runtime *sdw_stream = + snd_soc_dai_get_dma_data(dai, substream); + int ret; + //struct sdw_stream_runtime *sdw_stream; + //struct snd_soc_dai *dai; + + dev_dbg(dai->dev, "%s %s id %d", __func__, dai->name, dai->id); + dev_dbg(dai->dev, "%s substream stream %d", __func__, substream->stream); + + /* Find stream from first CPU DAI */ + //dai = snd_soc_rtd_to_cpu(rtd, 0); + + sdw_stream = snd_soc_dai_get_stream(dai, substream->stream); + if (IS_ERR(sdw_stream)) { + dev_err(dai->dev, "%s no stream found for DAI %s\n", __func__, dai->name); + return PTR_ERR(sdw_stream); + } + + ret = sdw_disable_stream(sdw_stream); + if (ret) { + dev_err(dai->dev, "%s trigger (disable stream) failed: %d\n", __func__, ret); + return ret; + } + + ret = sdw_deprepare_stream(sdw_stream); + if (ret) { + dev_err(dai->dev, "%s deprepare failed: %d\n", __func__, ret); + return ret; + } + + ret = sdw_stream_remove_slave(slave, sdw_stream); + if (ret < 0) { + dev_err(dai->dev, "%s: remove slave failed: %d\n", __func__, ret); + return ret; + } + + ret = sdw_stream_remove_master(slave->bus, sdw_stream); + if (ret < 0) { + dev_err(dai->dev, "remove master from stream %s failed: %d\n", + sdw_stream->name, ret); + return ret; + } + + printk("%s, id %d done\n", __func__, dai->id); + + return ret; +} +EXPORT_SYMBOL_NS(asoc_sdw_c_amp_hw_free, "SND_SOC_SDW_UTILS"); + +void asoc_sdw_c_amp_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + dev_dbg(dai->dev, "%s %s id %d", __func__, dai->name, dai->id); + dev_dbg(dai->dev, "%s substream stream %d", __func__, substream->stream); + + snd_soc_dai_stream_dma_data_set_playback(dai, NULL); + snd_soc_dai_stream_dma_data_set_capture(dai, NULL); +} +EXPORT_SYMBOL_NS(asoc_sdw_c_amp_shutdown, "SND_SOC_SDW_UTILS"); + static bool asoc_sdw_is_unique_device(const struct snd_soc_acpi_link_adr *adr_link, unsigned int sdw_version, unsigned int mfg_id, From aca6fbe6258ae5da7a694edadc40c89ea41e6e1e Mon Sep 17 00:00:00 2001 From: Shuming Fan Date: Thu, 5 Nov 2026 14:47:39 +0800 Subject: [PATCH 6/8] ASoC: rt712: add companion input dai Signed-off-by: Shuming Fan --- sound/soc/codecs/rt712-sdca.c | 38 +++++++++++++++++++++++++++++++++-- sound/soc/codecs/rt712-sdca.h | 1 + 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/sound/soc/codecs/rt712-sdca.c b/sound/soc/codecs/rt712-sdca.c index 20961e5760c652..f47e6eaba89eca 100644 --- a/sound/soc/codecs/rt712-sdca.c +++ b/sound/soc/codecs/rt712-sdca.c @@ -1183,6 +1183,7 @@ static const struct snd_soc_dapm_route rt712_sdca_audio_map[] = { static const struct snd_soc_dapm_widget rt712_sdca_spk_dapm_widgets[] = { SND_SOC_DAPM_AIF_IN("DP3RX", "DP3 Playback", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("DP3RX-Comp", "DP3 Comp-In", 0, SND_SOC_NOPM, 0, 0), /* Digital Interface */ SND_SOC_DAPM_PGA("FU06", SND_SOC_NOPM, 0, 0, NULL, 0), @@ -1200,6 +1201,7 @@ static const struct snd_soc_dapm_widget rt712_sdca_spk_dapm_widgets[] = { static const struct snd_soc_dapm_route rt712_sdca_spk_dapm_routes[] = { { "FU06", NULL, "DP3RX" }, + { "FU06", NULL, "DP3RX-Comp" }, { "FU06", NULL, "PDE 23" }, { "OT23 L", "Switch", "FU06" }, { "OT23 R", "Switch", "FU06" }, @@ -1657,9 +1659,25 @@ static int rt712_sdca_pcm_hw_params(struct snd_pcm_substream *substream, struct sdw_stream_runtime *sdw_stream; int retval, port; unsigned int sampling_rate; + struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); + struct snd_soc_dai *cpu_dai; dev_dbg(dai->dev, "%s %s id %d", __func__, dai->name, dai->id); - sdw_stream = snd_soc_dai_get_dma_data(dai, substream); + dev_dbg(dai->dev, "%s substream stream %d", __func__, substream->stream); + + if (dai->id == RT712_AIF4) { + cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); + dev_dbg(dai->dev, "%s cpu_dai %s id %d", __func__, cpu_dai->name, cpu_dai->id); + + sdw_stream = snd_soc_dai_get_stream(cpu_dai, 1); + if (IS_ERR(sdw_stream)) { + dev_err(rtd->dev, "%s no stream found for DAI %s id %d\n", + __func__, cpu_dai->name, cpu_dai->id); + return PTR_ERR(sdw_stream); + } + } else { + sdw_stream = snd_soc_dai_get_dma_data(dai, substream); + } if (!sdw_stream) return -EINVAL; @@ -1675,7 +1693,7 @@ static int rt712_sdca_pcm_hw_params(struct snd_pcm_substream *substream, if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { if (dai->id == RT712_AIF1) port = 1; - else if (dai->id == RT712_AIF2) + else if (dai->id == RT712_AIF2 || dai->id == RT712_AIF4) port = 3; else return -EINVAL; @@ -1690,6 +1708,9 @@ static int rt712_sdca_pcm_hw_params(struct snd_pcm_substream *substream, /* SoundWire specific configuration */ snd_sdw_params_to_config(substream, params, &stream_config, &port_config); + + if (dai->id == RT712_AIF4) + stream_config.type = SDW_STREAM_COMPANION; port_config.num = port; retval = sdw_stream_add_slave(rt712->slave, &stream_config, @@ -1736,6 +1757,7 @@ static int rt712_sdca_pcm_hw_params(struct snd_pcm_substream *substream, sampling_rate); break; case RT712_AIF2: + case RT712_AIF4: regmap_write(rt712->regmap, SDW_SDCA_CTL(FUNC_NUM_AMP, RT712_SDCA_ENT_CS31, RT712_SDCA_CTL_SAMPLE_FREQ_INDEX, 0), sampling_rate); @@ -1814,6 +1836,18 @@ static struct snd_soc_dai_driver rt712_sdca_dai[] = { .formats = RT712_FORMATS, }, .ops = &rt712_sdca_ops, + }, + { + .name = "rt712-sdca-aif4", + .id = RT712_AIF4, + .playback = { + .stream_name = "DP3 Comp-In", + .channels_min = 2, + .channels_max = 2, + .rates = RT712_STEREO_RATES, + .formats = RT712_FORMATS, + }, + .ops = &rt712_sdca_ops, } }; diff --git a/sound/soc/codecs/rt712-sdca.h b/sound/soc/codecs/rt712-sdca.h index 7e6ee21c8a172d..0ea6568c6edb66 100644 --- a/sound/soc/codecs/rt712-sdca.h +++ b/sound/soc/codecs/rt712-sdca.h @@ -249,6 +249,7 @@ enum { RT712_AIF1, RT712_AIF2, RT712_AIF3, + RT712_AIF4, }; enum rt712_sdca_jd_src { From fdd00c69a8b1cd6d8a765c3183d69b3cf3e004e8 Mon Sep 17 00:00:00 2001 From: Shuming Fan Date: Thu, 5 Nov 2026 14:47:55 +0800 Subject: [PATCH 7/8] ASoC: rt1320: add companion dai Signed-off-by: Shuming Fan --- sound/soc/codecs/rt1320-sdw.c | 78 ++++++++++++++++++++++++++++++++++- sound/soc/codecs/rt1320-sdw.h | 1 + 2 files changed, 78 insertions(+), 1 deletion(-) diff --git a/sound/soc/codecs/rt1320-sdw.c b/sound/soc/codecs/rt1320-sdw.c index fa89db09665149..e286e0f94b9022 100644 --- a/sound/soc/codecs/rt1320-sdw.c +++ b/sound/soc/codecs/rt1320-sdw.c @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include @@ -251,6 +252,7 @@ static const struct reg_sequence rt1320_vc_blind_write[] = { { 0xc06b, 0x0a }, { 0xd172, 0x2a }, { 0xc5d6, 0x01 }, + { 0xc5d0, 0x8f }, // DP6 Data Select: SRCIN OUT // for companion output setting { SDW_SDCA_CTL(FUNC_NUM_AMP, RT1320_SDCA_ENT_PDE23, RT1320_SDCA_CTL_REQ_POWER_STATE, 0), 0x03 }, }; @@ -1090,7 +1092,7 @@ static int rt1320_read_prop(struct sdw_slave *slave) prop->lane_control_support = true; /* first we need to allocate memory for set bits in port lists */ - prop->source_ports = BIT(4) | BIT(8) | BIT(10); + prop->source_ports = BIT(4) | BIT(6) | BIT(8) | BIT(10); prop->sink_ports = BIT(1); nval = hweight32(prop->source_ports); @@ -3183,6 +3185,9 @@ static const struct snd_soc_dapm_widget rt1320_dapm_widgets[] = { /* Audio Interface */ SND_SOC_DAPM_AIF_IN("DP1RX", "DP1 Playback", 0, SND_SOC_NOPM, 0, 0), SND_SOC_DAPM_AIF_OUT("DP4TX", "DP4 Capture", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("DP6TX", "DP6 Comp-Out", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("DP6RX", "DP6 Comp-Playback", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("DP8-10TX", "DP8-10 Capture", 0, SND_SOC_NOPM, 0, 0), /* Digital Interface */ @@ -3221,6 +3226,9 @@ static const struct snd_soc_dapm_route rt1320_dapm_routes[] = { { "AEC Data", NULL, "AEC Gen" }, { "DP4TX", NULL, "AEC Data" }, + { "DP6TX", NULL, "DP1RX" }, + { "DP6RX", NULL, "DP1RX" }, + {"DP8-10TX", NULL, "FU"}, {"FU", NULL, "PDE 11"}, {"FU", NULL, "FU 113"}, @@ -3232,6 +3240,8 @@ static const struct snd_soc_dapm_route rt1320_dapm_routes[] = { static int rt1320_set_sdw_stream(struct snd_soc_dai *dai, void *sdw_stream, int direction) { + dev_dbg(dai->dev, "%s %s id %d", __func__, dai->name, dai->id); + dev_dbg(dai->dev, "%s direction %d", __func__, direction); snd_soc_dai_dma_data_set(dai, direction, sdw_stream); return 0; } @@ -3239,6 +3249,9 @@ static int rt1320_set_sdw_stream(struct snd_soc_dai *dai, void *sdw_stream, static void rt1320_sdw_shutdown(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { + dev_dbg(dai->dev, "%s %s id %d", __func__, dai->name, dai->id); + dev_dbg(dai->dev, "%s substream stream %d", __func__, substream->stream); + snd_soc_dai_set_dma_data(dai, substream, NULL); } @@ -3251,21 +3264,34 @@ static int rt1320_sdw_hw_params(struct snd_pcm_substream *substream, struct sdw_stream_config stream_config = {0}; struct sdw_port_config port_config; struct sdw_port_config dmic_port_config[2]; + struct sdw_port_config companion_config = { + .num = 6, + .ch_mask = BIT(0) | BIT(1), + }; struct sdw_stream_runtime *sdw_stream; int retval, num_channels; unsigned int sampling_rate; + dev_err(dai->dev, "bard: %s %s id %d", __func__, dai->name, dai->id); + dev_dbg(dai->dev, "%s substream stream %d", __func__, substream->stream); + dev_dbg(dai->dev, "%s %s", __func__, dai->name); sdw_stream = snd_soc_dai_get_dma_data(dai, substream); if (!sdw_stream) return -EINVAL; + printk("%s, sdw_stream 0x%p\n", __func__, sdw_stream); + if (!rt1320->sdw_slave) return -EINVAL; /* SoundWire specific configuration */ snd_sdw_params_to_config(substream, params, &stream_config, &port_config); + dev_dbg(dai->dev, "%s stream %d", __func__, substream->stream); + + if (dai->id == RT1320_AIF3) + goto _add_slave_; if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { if (dai->id == RT1320_AIF1) @@ -3295,6 +3321,7 @@ static int rt1320_sdw_hw_params(struct snd_pcm_substream *substream, return -EINVAL; } +_add_slave_: if (dai->id == RT1320_AIF1) retval = sdw_stream_add_slave(rt1320->sdw_slave, &stream_config, &port_config, 1, sdw_stream); @@ -3312,6 +3339,21 @@ static int rt1320_sdw_hw_params(struct snd_pcm_substream *substream, dev_err(dai->dev, "%s: Unknown device ID %d\n", __func__, rt1320->dev_id); return -EINVAL; } + } else if (dai->id == RT1320_AIF3) { + retval = asoc_sdw_c_amp_hw_params(substream, params, dai); + if (retval) { + dev_err(dai->dev, "%s: master companion stream configuration failed, ret=%d\n", + __func__, retval); + return retval; + } + stream_config.type = SDW_STREAM_COMPANION; + retval = sdw_stream_add_slave(rt1320->sdw_slave, &stream_config, + &companion_config, 1, sdw_stream); + if (retval) { + sdw_stream_remove_master(rt1320->sdw_slave->bus, sdw_stream); + dev_err(dai->dev, "%s: Unable to add slave, ret=%d\n", __func__, retval); + return retval; + } } else return -EINVAL; if (retval) { @@ -3469,6 +3511,17 @@ static const struct snd_soc_dai_ops rt1320_aif_dai_ops = { .shutdown = rt1320_sdw_shutdown, }; +static const struct snd_soc_dai_ops rt1320_comp_dai_asoc_ops = { + .probe = asoc_sdw_c_amp_probe, + .startup = asoc_sdw_c_amp_startup, + .prepare = asoc_sdw_c_amp_prepare, + .hw_params = rt1320_sdw_hw_params, + .hw_free = asoc_sdw_c_amp_hw_free, + .set_stream = rt1320_set_sdw_stream, + .shutdown = asoc_sdw_c_amp_shutdown, + .get_stream = asoc_sdw_c_amp_get_sdw_stream, +}; + #define RT1320_STEREO_RATES (SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | \ SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_96000 | SNDRV_PCM_RATE_192000) #define RT1320_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE | \ @@ -3507,6 +3560,28 @@ static struct snd_soc_dai_driver rt1320_sdw_dai[] = { }, .ops = &rt1320_aif_dai_ops, }, + /* Companion stream output */ + { + .name = "rt1320-aif3", + .id = RT1320_AIF3, + .playback = { + .stream_name = "DP6 Comp-Playback", + .channels_min = 2, + .channels_max = 2, + .rates = RT1320_STEREO_RATES, + .formats = RT1320_FORMATS, + }, + .capture = { + .stream_name = "DP6 Comp-Out", + .channels_min = 2, + .channels_max = 2, + .rates = RT1320_STEREO_RATES, + .formats = RT1320_FORMATS, + }, + .ops = &rt1320_comp_dai_asoc_ops, + .symmetric_rate = 1, + .symmetric_channels = 1, + }, }; static int rt1320_parse_dp(struct rt1320_sdw_priv *rt1320, struct device *dev) @@ -3694,3 +3769,4 @@ module_sdw_driver(rt1320_sdw_driver); MODULE_DESCRIPTION("ASoC RT1320 driver SDCA SDW"); MODULE_AUTHOR("Shuming Fan "); MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("SND_SOC_SDW_UTILS"); diff --git a/sound/soc/codecs/rt1320-sdw.h b/sound/soc/codecs/rt1320-sdw.h index df9ccdf92d361c..9af4670f2fbb01 100644 --- a/sound/soc/codecs/rt1320-sdw.h +++ b/sound/soc/codecs/rt1320-sdw.h @@ -87,6 +87,7 @@ enum { RT1320_AIF1, RT1320_AIF2, + RT1320_AIF3, }; /* From ced34fe517a6ec19c6a073ee4ac986e758200662 Mon Sep 17 00:00:00 2001 From: Bard Liao Date: Tue, 8 Sep 2026 11:29:38 +0800 Subject: [PATCH 8/8] TEST: ASoC: soc_sdw_utils: add componion amp dai for rt712 and rt1320 Add the componion amp dai info for rt712 and rt1320. This commit is only for testing which is not a correct information. Signed-off-by: Bard Liao --- sound/soc/sdw_utils/soc_sdw_utils.c | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/sound/soc/sdw_utils/soc_sdw_utils.c b/sound/soc/sdw_utils/soc_sdw_utils.c index 8aca9d2f9d88e1..dbe23ea040760c 100644 --- a/sound/soc/sdw_utils/soc_sdw_utils.c +++ b/sound/soc/sdw_utils/soc_sdw_utils.c @@ -372,8 +372,16 @@ struct asoc_sdw_codec_info codec_info_list[] = { .quirk = SOC_SDW_CODEC_MIC, .quirk_exclude = true, }, + /* Companion amp */ + { + .direction = {true, true}, + .dai_name = "rt712-sdca-aif4", + .dai_type = SOC_SDW_DAI_TYPE_COMP_AMP, + .dailink = {SOC_SDW_AMP_OUT_DAI_ID, SOC_SDW_AMP_IN_DAI_ID}, + .quirk = SOC_SDW_COMPANION_AMPS, + }, }, - .dai_num = 3, + .dai_num = 4, }, { .vendor_id = 0x025d, @@ -537,8 +545,16 @@ struct asoc_sdw_codec_info codec_info_list[] = { .widgets = generic_dmic_widgets, .num_widgets = ARRAY_SIZE(generic_dmic_widgets), }, + /* Companion amp */ + { + .direction = {true, true}, + .dai_name = "rt1320-aif3", + .dai_type = SOC_SDW_DAI_TYPE_COMP_AMP, + .dailink = {SOC_SDW_AMP_OUT_DAI_ID, SOC_SDW_AMP_IN_DAI_ID}, + .quirk = SOC_SDW_COMPANION_AMPS, + }, }, - .dai_num = 2, + .dai_num = 3, }, { .vendor_id = 0x025d,