mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH] ASoC: qcom: x1e80100: Allow userspace WSA RX channel map override
@ 2026-06-10 11:16 Abel Vesa
  2026-06-10 16:00 ` Mark Brown
  2026-06-11  8:58 ` Srinivas Kandagatla
  0 siblings, 2 replies; 4+ messages in thread
From: Abel Vesa @ 2026-06-10 11:16 UTC (permalink / raw)
  To: Srinivas Kandagatla, Liam Girdwood, Mark Brown, Jaroslav Kysela,
	Takashi Iwai
  Cc: Neil Armstrong, linux-sound, linux-arm-msm, linux-kernel, Abel Vesa

The WSA RX slot mapping is currently computed entirely from the channel
count in .prepare() and pushed unconditionally via
snd_soc_dai_set_channel_map(). Userspace has no way to express a different
channel map, so any map defined in UCM is ignored.

Add a writable "Playback Channel Map" control on the WSA RX PCM via
snd_pcm_add_chmap_ctls() and make it writable. The map selected by
userspace (SNDRV_CHMAP_*) is stored per AFE port and, at .prepare()
time, translated into the q6 PCM_CHANNEL_* slots and programmed into the
DSP.

When userspace has not provided a full map, the previous default
mapping is used, so existing behaviour is preserved.

Assisted-by: Codex:GPT-5.5
Signed-off-by: Abel Vesa <abel.vesa@oss.qualcomm.com>
---
 sound/soc/qcom/x1e80100.c | 152 +++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 150 insertions(+), 2 deletions(-)

diff --git a/sound/soc/qcom/x1e80100.c b/sound/soc/qcom/x1e80100.c
index c81df41ace88..63cd00620ade 100644
--- a/sound/soc/qcom/x1e80100.c
+++ b/sound/soc/qcom/x1e80100.c
@@ -15,14 +15,105 @@
 #include "qdsp6/q6dsp-common.h"
 #include "sdw.h"
 
+#define X1E80100_WSA_MAX_CHANNELS	4
+
 struct x1e80100_snd_data {
 	bool stream_prepared[AFE_PORT_MAX];
 	struct snd_soc_card *card;
 	struct snd_soc_jack jack;
 	struct snd_soc_jack dp_jack[8];
 	bool jack_setup;
+	unsigned int user_chmap[AFE_PORT_MAX][4];
+	bool chmap_added[AFE_PORT_MAX];
 };
 
+static const struct snd_pcm_chmap_elem x1e80100_wsa_chmaps[] = {
+	{ .channels = 1, .map = { SNDRV_CHMAP_FC } },
+	{ .channels = 2, .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR } },
+	{ .channels = 3, .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR,
+				  SNDRV_CHMAP_FC } },
+	{ .channels = 4, .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR,
+				  SNDRV_CHMAP_RL, SNDRV_CHMAP_RR } },
+	{ }
+};
+
+static int x1e80100_chmap_to_q6(unsigned int pos)
+{
+	switch (pos) {
+	case SNDRV_CHMAP_FL:	return PCM_CHANNEL_FL;
+	case SNDRV_CHMAP_FR:	return PCM_CHANNEL_FR;
+	case SNDRV_CHMAP_MONO:
+	case SNDRV_CHMAP_FC:	return PCM_CHANNEL_FC;
+	case SNDRV_CHMAP_RL:	return PCM_CHANNEL_LB;
+	case SNDRV_CHMAP_RR:	return PCM_CHANNEL_RB;
+	case SNDRV_CHMAP_SL:	return PCM_CHANNEL_LS;
+	case SNDRV_CHMAP_SR:	return PCM_CHANNEL_RS;
+	case SNDRV_CHMAP_LFE:	return PCM_CHANNEL_LFE;
+	case SNDRV_CHMAP_RC:	return PCM_CHANNEL_CS;
+	default:		return -EINVAL;
+	}
+}
+
+static int x1e80100_chmap_ctl_get(struct snd_kcontrol *kcontrol,
+				  struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_pcm_chmap *info = snd_kcontrol_chip(kcontrol);
+	struct snd_soc_pcm_runtime *rtd = info->pcm->private_data;
+	struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
+	struct x1e80100_snd_data *data = snd_soc_card_get_drvdata(rtd->card);
+	unsigned int *map = data->user_chmap[cpu_dai->id];
+	int i;
+
+	for (i = 0; i < info->max_channels; i++)
+		ucontrol->value.integer.value[i] = map[i];
+
+	return 0;
+}
+
+static int x1e80100_chmap_ctl_put(struct snd_kcontrol *kcontrol,
+				  struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_pcm_chmap *info = snd_kcontrol_chip(kcontrol);
+	struct snd_soc_pcm_runtime *rtd = info->pcm->private_data;
+	struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
+	struct x1e80100_snd_data *data = snd_soc_card_get_drvdata(rtd->card);
+	unsigned int *map = data->user_chmap[cpu_dai->id];
+	int i;
+
+	for (i = 0; i < info->max_channels; i++) {
+		unsigned int pos = ucontrol->value.integer.value[i];
+
+		/* Validate every requested non-unset position up front. */
+		if (pos && x1e80100_chmap_to_q6(pos) < 0)
+			return -EINVAL;
+
+		map[i] = pos;
+	}
+
+	return 0;
+}
+
+static int x1e80100_add_wsa_chmap_ctls(struct snd_soc_pcm_runtime *rtd)
+{
+	struct snd_pcm_chmap *info;
+	unsigned int i;
+	int ret;
+
+	ret = snd_pcm_add_chmap_ctls(rtd->pcm, SNDRV_PCM_STREAM_PLAYBACK,
+				     x1e80100_wsa_chmaps,
+				     X1E80100_WSA_MAX_CHANNELS, 0, &info);
+	if (ret < 0)
+		return ret;
+
+	/* Make the query-only chmap control writable from userspace. */
+	for (i = 0; i < info->kctl->count; i++)
+		info->kctl->vd[i].access |= SNDRV_CTL_ELEM_ACCESS_WRITE;
+	info->kctl->get = x1e80100_chmap_ctl_get;
+	info->kctl->put = x1e80100_chmap_ctl_put;
+
+	return 0;
+}
+
 static int x1e80100_snd_init(struct snd_soc_pcm_runtime *rtd)
 {
 	struct x1e80100_snd_data *data = snd_soc_card_get_drvdata(rtd->card);
@@ -34,6 +125,9 @@ static int x1e80100_snd_init(struct snd_soc_pcm_runtime *rtd)
 	switch (cpu_dai->id) {
 	case WSA_CODEC_DMA_RX_0:
 	case WSA_CODEC_DMA_RX_1:
+		if (!rtd->pcm)
+			return 0;
+
 		/*
 		 * Set limit of -3 dB on Digital Volume and 0 dB on PA Volume
 		 * to reduce the risk of speaker damage until we have active
@@ -92,6 +186,7 @@ static int x1e80100_be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd,
 	return 0;
 }
 
+/* Default WSA RX slot mapping when userspace has not provided a channel map. */
 static int x1e80100_snd_hw_map_channels(unsigned int *ch_map, int num)
 {
 	switch (num) {
@@ -120,6 +215,58 @@ static int x1e80100_snd_hw_map_channels(unsigned int *ch_map, int num)
 	return 0;
 }
 
+static int x1e80100_snd_build_rx_slot(struct x1e80100_snd_data *data,
+				      unsigned int dai_id, unsigned int channels,
+				      unsigned int *rx_slot)
+{
+	unsigned int *user = data->user_chmap[dai_id];
+	unsigned int i, set = 0;
+
+	for (i = 0; i < channels && i < ARRAY_SIZE(data->user_chmap[0]); i++)
+		if (user[i])
+			set++;
+
+	if (set != channels)
+		return x1e80100_snd_hw_map_channels(rx_slot, channels);
+
+	for (i = 0; i < channels; i++) {
+		int q6 = x1e80100_chmap_to_q6(user[i]);
+
+		if (q6 < 0)
+			return q6;
+		rx_slot[i] = q6;
+	}
+
+	return 0;
+}
+
+static int x1e80100_snd_startup(struct snd_pcm_substream *substream)
+{
+	struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
+	struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
+	struct x1e80100_snd_data *data = snd_soc_card_get_drvdata(rtd->card);
+	int ret;
+
+	ret = qcom_snd_sdw_startup(substream);
+	if (ret)
+		return ret;
+
+	switch (cpu_dai->id) {
+	case WSA_CODEC_DMA_RX_0:
+	case WSA_CODEC_DMA_RX_1:
+		if (!data->chmap_added[cpu_dai->id]) {
+			ret = x1e80100_add_wsa_chmap_ctls(rtd);
+			if (ret)
+				return ret;
+
+			data->chmap_added[cpu_dai->id] = true;
+		}
+		break;
+	}
+
+	return 0;
+}
+
 static int x1e80100_snd_prepare(struct snd_pcm_substream *substream)
 {
 	struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
@@ -132,7 +279,8 @@ static int x1e80100_snd_prepare(struct snd_pcm_substream *substream)
 	switch (cpu_dai->id) {
 	case WSA_CODEC_DMA_RX_0:
 	case WSA_CODEC_DMA_RX_1:
-		ret = x1e80100_snd_hw_map_channels(rx_slot, channels);
+		ret = x1e80100_snd_build_rx_slot(data, cpu_dai->id, channels,
+						 rx_slot);
 		if (ret)
 			return ret;
 
@@ -158,7 +306,7 @@ static int x1e80100_snd_hw_free(struct snd_pcm_substream *substream)
 }
 
 static const struct snd_soc_ops x1e80100_be_ops = {
-	.startup = qcom_snd_sdw_startup,
+	.startup = x1e80100_snd_startup,
 	.shutdown = qcom_snd_sdw_shutdown,
 	.hw_free = x1e80100_snd_hw_free,
 	.prepare = x1e80100_snd_prepare,

---
base-commit: a87737435cfa134f9cdcc696ba3080759d04cf72
change-id: 20260609-sound-qcom-x1e80100-allow-ucm-channel-map-2302f2d552d5

Best regards,
--  
Abel Vesa <abel.vesa@oss.qualcomm.com>


^ permalink raw reply	[flat|nested] 4+ messages in thread

end of thread, other threads:[~2026-06-11  9:01 UTC | newest]

Thread overview: 4+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-06-10 11:16 [PATCH] ASoC: qcom: x1e80100: Allow userspace WSA RX channel map override Abel Vesa
2026-06-10 16:00 ` Mark Brown
2026-06-11  8:58 ` Srinivas Kandagatla
2026-06-11  9:00   ` Srinivas Kandagatla

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®