From 15207a8136c78dea2ade502efa09c279ebaee552 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 | 19 +++- 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 | 135 +++++++++++++++++++++++- sound/soc/sdw_utils/soc_sdw_utils.c | 105 ++++++++++++++++-- 5 files changed, 271 insertions(+), 22 deletions(-) diff --git a/include/sound/soc_sdw_utils.h b/include/sound/soc_sdw_utils.h index 418466438ea1cb..3502548c9a6b90 100644 --- a/include/sound/soc_sdw_utils.h +++ b/include/sound/soc_sdw_utils.h @@ -30,6 +30,14 @@ */ #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. + */ +/* BIT 18:21 is used by SOF_SSP_BT_OFFLOAD which are defined in sof_sdw_common.h */ +#define SOC_SDW_COMPANION_AMPS BIT(22) #define SOC_SDW_UNUSED_DAI_ID -1 #define SOC_SDW_JACK_OUT_DAI_ID 0 @@ -128,6 +136,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 +189,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 +203,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..854abf767e627d 100644 --- a/sound/soc/intel/boards/sof_sdw.c +++ b/sound/soc/intel/boards/sof_sdw.c @@ -1225,6 +1225,100 @@ 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)->init = asoc_sdw_rtd_init; + (*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 +1355,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 +1372,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 +1404,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 +1482,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 +1531,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 e9979817ceddd6e73f3174902e80e0941c1815d6 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 0133840c9e288b5cce063076a14741be5c5ba874 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 2985ca9ff0e0ccac34b1b414d7816ed1949c1fcb 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..efe3d3ac58a95e 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].quirk & SOC_SDW_COMPANION_AMPS)) continue; endpoints[ep_index].num = j; From 54e4f16013b67f25da7a1844dba0c9ce5a5590ae Mon Sep 17 00:00:00 2001 From: Shuming Fan Date: Thu, 5 Nov 2026 14:45:18 +0800 Subject: [PATCH 5/8] ASoC: sdw_utils: add companion amp dai ops A companion amp gets its audio data from another SoundWire peripheral (the main amp) instead of from the host, so the machine driver does not open a host DMA stream for it. Add common dai ops that the companion amp codec drivers can plug into their dai to handle the peripheral to peripheral stream. Signed-off-by: Shuming Fan Co-developed-by: Bard Liao Signed-off-by: Bard Liao --- include/sound/soc_sdw_utils.h | 11 ++ sound/soc/sdw_utils/soc_sdw_utils.c | 188 ++++++++++++++++++++++++++++ 2 files changed, 199 insertions(+) diff --git a/include/sound/soc_sdw_utils.h b/include/sound/soc_sdw_utils.h index 3502548c9a6b90..7f0af8d6745d04 100644 --- a/include/sound/soc_sdw_utils.h +++ b/include/sound/soc_sdw_utils.h @@ -153,6 +153,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..c6b0584ac35c07 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,192 @@ 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 substream stream %d dai %s id %d", + __func__, substream->stream, dai->name, dai->id); + + 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->transport_type = SDW_TRANSPORT_PERIPHERAL_PERIPHERAL; + + 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 = {0}; + struct sdw_port_config pconfig; + int retval; + + 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; + + snd_sdw_params_to_config(substream, params, &sconfig, &pconfig); + + sconfig.type = SDW_STREAM_PCM; + sconfig.transport_type = SDW_TRANSPORT_PERIPHERAL_PERIPHERAL; + pconfig.num = 2; /* Use port 2 as port 0 and 1 can be used for BPT */ + + 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; + } + + 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 sdw_stream_runtime *sdw_stream; + 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); + + /* Find stream from first CPU DAI */ + + 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 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; + + dev_dbg(dai->dev, "%s substream stream %d dai %s id %d", + __func__, substream->stream, dai->name, dai->id); + + /* Find stream from first CPU DAI */ + 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); + } + + /* A failed prepare/enable leaves the stream CONFIGURED/PREPARED; still remove it from the bus */ + if (sdw_stream->state == SDW_STREAM_ENABLED) { + ret = sdw_disable_stream(sdw_stream); + if (ret) { + dev_err(dai->dev, "%s trigger (disable stream) failed: %d\n", __func__, ret); + return ret; + } + } + + if (sdw_stream->state == SDW_STREAM_PREPARED || + sdw_stream->state == SDW_STREAM_DISABLED) { + 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; + } + + 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); + + sdw_shutdown_stream(substream); + + 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 e03b3d0bf428e4a59d62b891891f7c231b30bb8f 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..80d0b22e4b2cfc 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.transport_type = SDW_TRANSPORT_PERIPHERAL_PERIPHERAL; 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 ef324911b70d3d8c09a8e017984290a3443dbbb7 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/Kconfig | 1 + sound/soc/codecs/rt1320-sdw.c | 78 ++++++++++++++++++++++++++++++++++- sound/soc/codecs/rt1320-sdw.h | 1 + 3 files changed, 79 insertions(+), 1 deletion(-) diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index ba59dabeeccde5..182d85450f6b42 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -1844,6 +1844,7 @@ config SND_SOC_RT1320_SDW depends on SOUNDWIRE select REGMAP_SOUNDWIRE select REGMAP_SOUNDWIRE_MBQ + select SND_SOC_SDW_UTILS config SND_SOC_RT5514 tristate diff --git a/sound/soc/codecs/rt1320-sdw.c b/sound/soc/codecs/rt1320-sdw.c index fa89db09665149..c0cbcf21274a6b 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.transport_type = SDW_TRANSPORT_PERIPHERAL_PERIPHERAL; + 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 f033865cedde3e05c52bde8a753208e73f3eb104 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 | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/sound/soc/sdw_utils/soc_sdw_utils.c b/sound/soc/sdw_utils/soc_sdw_utils.c index c6b0584ac35c07..aa7e67378ee39d 100644 --- a/sound/soc/sdw_utils/soc_sdw_utils.c +++ b/sound/soc/sdw_utils/soc_sdw_utils.c @@ -372,8 +372,17 @@ 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_AMP, + .dailink = {SOC_SDW_AMP_OUT_DAI_ID, SOC_SDW_AMP_IN_DAI_ID}, + .rtd_init = asoc_sdw_rt_mf_sdca_spk_rtd_init, + .quirk = SOC_SDW_COMPANION_AMPS, + }, }, - .dai_num = 3, + .dai_num = 4, }, { .vendor_id = 0x025d, @@ -537,8 +546,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_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,