[PATCH v4 3/3] ASoC: SDCA: Add port constraints into sdca_asoc_set_constraints()

From: Charles Keepax

Date: Mon Nov 09 2026 - 12:30:39 EST


Currently the SDCA code constrains the params based on the DisCo for
the SDCA Terminal used. However, it ignores the properties of the
SoundWire port, which in some cases may further constrain the allowed
params. Add additional constraints to apply these restrictions.

Some minor additional refactoring is required since port constraints
should be applied for both Input and Output Terminals, whereas
currently Cluster constraints are only handled for Input Terminals.

Signed-off-by: Charles Keepax <ckeepax@xxxxxxxxxxxxxxxxxxxxx>
---

Changes since v3:
- Added defaults for the word length min/max
- Removed unused static_assert

Also regarding ch_min/ch_max one can't assume defaults locally for those
as 0/0 is a valid combo. I have a patch that adds defaults at the
soundwire end, I will upstream that likely tomorrow.

include/sound/sdca_asoc.h | 4 +-
sound/soc/sdca/sdca_asoc.c | 133 +++++++++++++++++++++++++--
sound/soc/sdca/sdca_class_function.c | 3 +-
3 files changed, 128 insertions(+), 12 deletions(-)

diff --git a/include/sound/sdca_asoc.h b/include/sound/sdca_asoc.h
index 236a441ec9d89..26740f69f2bee 100644
--- a/include/sound/sdca_asoc.h
+++ b/include/sound/sdca_asoc.h
@@ -12,6 +12,7 @@

struct device;
struct regmap;
+struct sdw_slave;
struct sdca_function_data;
struct sdca_pde_delay;
struct snd_ctl_elem_value;
@@ -91,7 +92,8 @@ int sdca_asoc_populate_rate_format(struct device *dev,

int sdca_asoc_alloc_constraints(struct snd_soc_dai *dai);
int sdca_asoc_free_constraints(struct snd_soc_dai *dai);
-int sdca_asoc_set_constraints(struct device *dev, struct regmap *regmap,
+int sdca_asoc_set_constraints(struct device *dev, struct sdw_slave *sdw,
+ struct regmap *regmap,
struct sdca_function_data *function,
struct snd_pcm_substream *substream,
struct snd_soc_dai *dai);
diff --git a/sound/soc/sdca/sdca_asoc.c b/sound/soc/sdca/sdca_asoc.c
index be1b4c0075796..37cd1f9f58bd1 100644
--- a/sound/soc/sdca/sdca_asoc.c
+++ b/sound/soc/sdca/sdca_asoc.c
@@ -13,11 +13,13 @@
#include <linux/delay.h>
#include <linux/dev_printk.h>
#include <linux/device.h>
+#include <linux/limits.h>
#include <linux/minmax.h>
#include <linux/module.h>
#include <linux/overflow.h>
#include <linux/pm_runtime.h>
#include <linux/regmap.h>
+#include <linux/soundwire/sdw.h>
#include <linux/soundwire/sdw_registers.h>
#include <linux/string_helpers.h>
#include <linux/types.h>
@@ -1436,6 +1438,7 @@ EXPORT_SYMBOL_NS(sdca_asoc_populate_component, "SND_SOC_SDCA");

struct sdca_asoc_constraints {
struct snd_pcm_hw_constraint_list channels;
+ u64 width;
};

/**
@@ -1507,9 +1510,91 @@ static int get_cluster_mask(struct device *dev, struct sdca_function_data *funct
return 0;
}

+static u64 get_word_mask(unsigned int width)
+{
+ switch (width) {
+ case 8:
+ return SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_U8;
+ case 16:
+ return SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S16_BE |
+ SNDRV_PCM_FMTBIT_U16_LE | SNDRV_PCM_FMTBIT_U16_BE;
+ case 18:
+ return SNDRV_PCM_FMTBIT_S18_3LE | SNDRV_PCM_FMTBIT_U18_3LE |
+ SNDRV_PCM_FMTBIT_S18_3BE | SNDRV_PCM_FMTBIT_U18_3BE;
+ case 20:
+ return SNDRV_PCM_FMTBIT_S20_LE | SNDRV_PCM_FMTBIT_U20_LE |
+ SNDRV_PCM_FMTBIT_S20_BE | SNDRV_PCM_FMTBIT_U20_BE |
+ SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_U20_3LE |
+ SNDRV_PCM_FMTBIT_S20_3BE | SNDRV_PCM_FMTBIT_U20_3BE;
+ case 24:
+ return SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S24_BE |
+ SNDRV_PCM_FMTBIT_U24_LE | SNDRV_PCM_FMTBIT_U24_BE |
+ SNDRV_PCM_FMTBIT_S24_3LE | SNDRV_PCM_FMTBIT_U24_3LE |
+ SNDRV_PCM_FMTBIT_S24_3BE | SNDRV_PCM_FMTBIT_U24_3BE;
+ case 32:
+ return SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_S32_BE |
+ SNDRV_PCM_FMTBIT_U32_LE | SNDRV_PCM_FMTBIT_U32_BE;
+ default:
+ return 0;
+ }
+}
+
+static int get_port_masks(struct sdw_slave *sdw, unsigned int port,
+ struct snd_pcm_substream *substream,
+ u64 *word_mask, unsigned int *ch_mask)
+{
+ struct sdw_dpn_prop *prop;
+ unsigned int nprop;
+ int i, j;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ prop = sdw->prop.sink_dpn_prop;
+ nprop = hweight32(sdw->prop.sink_ports);
+ } else {
+ prop = sdw->prop.src_dpn_prop;
+ nprop = hweight32(sdw->prop.source_ports);
+ }
+
+ *word_mask = 0;
+ *ch_mask = 0;
+
+ for (i = 0; i < nprop; i++) {
+ if (prop[i].num != port)
+ continue;
+
+ if (prop[i].num_words) {
+ for (j = 0; j < prop[i].num_words; j++)
+ *word_mask |= get_word_mask(prop[i].words[j]);
+ } else {
+ unsigned int max = prop[i].max_word;
+
+ if (!max || max > 32)
+ max = 32;
+
+ for (j = MAX(1, prop[i].min_word); j <= max; j++)
+ *word_mask |= get_word_mask(j);
+ }
+
+ /* Only handle the number of channels for constraints. */
+ if (prop[i].num_ch_combinations) {
+ for (j = 0; j < prop[i].num_ch_combinations; j++)
+ *ch_mask |= BIT(hweight32(prop[i].ch_combinations[j]) - 1);
+ } else if (prop[i].num_channels) {
+ *ch_mask = GENMASK(prop[i].num_channels - 1, 0);
+ } else {
+ *ch_mask = GENMASK(prop[i].max_ch - prop[i].min_ch, 0);
+ }
+
+ return 0;
+ }
+
+ return -ENODEV;
+}
+
/**
* sdca_asoc_set_constraints - constrain channels available on a DAI
* @dev: Pointer to the device, used for error messages.
+ * @sdw: Pointer to the SoundWire peripheral device.
* @regmap: Pointer to the Function register map.
* @function: Pointer to the Function information.
* @substream: Pointer to the PCM substream.
@@ -1519,7 +1604,8 @@ static int get_cluster_mask(struct device *dev, struct sdca_function_data *funct
*
* Return: Returns zero on success, and a negative error code on failure.
*/
-int sdca_asoc_set_constraints(struct device *dev, struct regmap *regmap,
+int sdca_asoc_set_constraints(struct device *dev, struct sdw_slave *sdw,
+ struct regmap *regmap,
struct sdca_function_data *function,
struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
@@ -1530,31 +1616,58 @@ int sdca_asoc_set_constraints(struct device *dev, struct regmap *regmap,
};
struct sdca_asoc_constraints *constraints = snd_soc_dai_to_priv(dai);
struct sdca_entity *entity = &function->entities[dai->id];
- unsigned int cluster_ch_mask;
+ unsigned int port_ch_mask, cluster_ch_mask;
+ int port;
int ret;

static_assert(ARRAY_SIZE(channel_list) == SDCA_MAX_CHANNEL_COUNT);
static_assert(sizeof(cluster_ch_mask) * BITS_PER_BYTE >= SDCA_MAX_CHANNEL_COUNT);

- if (entity->type != SDCA_ENTITY_TYPE_IT)
- return 0;
+ /* FIXME: Currently only a single unshared port is supported */
+ port = sdca_asoc_get_port(dev, regmap, function, dai);
+ if (port < 0)
+ return port;

- ret = get_cluster_mask(dev, function, entity, &cluster_ch_mask);
- if (ret)
+ ret = get_port_masks(sdw, port, substream, &constraints->width, &port_ch_mask);
+ if (ret < 0) {
+ dev_err(dev, "%s: no properties for port %d\n", entity->label, port);
return ret;
+ }

- dev_dbg(dev, "%s: set channel constraint mask: %#x\n",
- entity->label, cluster_ch_mask);
+ /* FIXME: OTs should inherit cluster information */
+ if (entity->type == SDCA_ENTITY_TYPE_IT) {
+ ret = get_cluster_mask(dev, function, entity, &cluster_ch_mask);
+ if (ret)
+ return ret;
+ } else {
+ cluster_ch_mask = UINT_MAX;
+ }

constraints->channels.count = ARRAY_SIZE(channel_list);
constraints->channels.list = channel_list;
- constraints->channels.mask = cluster_ch_mask;
+ constraints->channels.mask = cluster_ch_mask & port_ch_mask;
+
+ if (!constraints->channels.mask || !constraints->width) {
+ dev_err(dev, "%s: no valid constraint masks channel: %#x,%#x width: %#llx\n",
+ entity->label, cluster_ch_mask, port_ch_mask, constraints->width);
+ return -EINVAL;
+ }

ret = snd_pcm_hw_constraint_list(substream->runtime, 0,
SNDRV_PCM_HW_PARAM_CHANNELS,
&constraints->channels);
if (ret) {
- dev_err(dev, "%s: failed to add constraint: %d\n", entity->label, ret);
+ dev_err(dev, "%s: failed to add channel constraint: %d\n",
+ entity->label, ret);
+ return ret;
+ }
+
+ ret = snd_pcm_hw_constraint_mask64(substream->runtime,
+ SNDRV_PCM_HW_PARAM_FORMAT,
+ constraints->width);
+ if (ret) {
+ dev_err(dev, "%s: failed to add bit width constraint: %d\n",
+ entity->label, ret);
return ret;
}

diff --git a/sound/soc/sdca/sdca_class_function.c b/sound/soc/sdca/sdca_class_function.c
index 6406c9d0f6119..abccc63640c68 100644
--- a/sound/soc/sdca/sdca_class_function.c
+++ b/sound/soc/sdca/sdca_class_function.c
@@ -121,8 +121,9 @@ static int class_function_startup(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
struct class_function_drv *drv = snd_soc_component_get_drvdata(dai->component);
+ struct sdw_slave *sdw = dev_to_sdw_dev(drv->dev->parent);

- return sdca_asoc_set_constraints(drv->dev, drv->regmap, drv->function,
+ return sdca_asoc_set_constraints(drv->dev, sdw, drv->regmap, drv->function,
substream, dai);
}

--
2.47.3