All of lore.kernel.org
 help / color / mirror / Atom feed
From: Will Porter <mrwillporter@gmail.com>
To: Takashi Iwai <tiwai@suse.com>, Jaroslav Kysela <perex@perex.cz>
Cc: linux-sound@vger.kernel.org, linux-kernel@vger.kernel.org,
	Neil Andrews <neil@androos.io>, Christian Ruppert <arc@gmx.li>,
	Will Porter <mrwillporter@gmail.com>
Subject: [PATCH 3/5] ALSA: usb-audio: Add Audient iD14 MkI mixer matrix
Date: Mon, 19 Oct 2026 18:01:57 -0500	[thread overview]
Message-ID: <20261019230159.834954-4-mrwillporter@gmail.com> (raw)
In-Reply-To: <20261019230159.834954-1-mrwillporter@gmail.com>

Mixer unit 60 mixes 14 inputs (Mic 1-2, Digi 1-8, DAW 1-4) into four
outputs (Main L/R, Cue L/R). GET RANGE reports -127 to 0 dB in 1 dB
steps, so the generic driver creates four "Analogue 4 Volume" controls
with values from 0 to 127. The crosspoints take -128 dB (mute) to +6 dB
in 1/256 dB steps. The reported range cannot show the gains above 0 dB
or the fine steps that Audient's iD application uses.

Add one control per crosspoint, "Mix <output> <input> Playback Volume".
The value is the device's s16 in 1/256 dB, from -128 dB (mute) to
+6 dB. The fine steps carry the pan law of Audient's iD application.
GET CUR works, so get reads the device. Hide the generic controls in
the MkI map. The driver writes nothing to the mixer at probe.

Test setup:
- iD14 MkI, firmware 1.1.1
- Bazzite 44, kernel 7.2.9-ogc3.1.fc44
- snd-usb-audio built out of tree from the v7.2.9 sound/usb sources
  and the complete driver (this commit and the later ones)

Results:
- crosspoints read back after writes
- DAW 1/2 reach Main L/R and Cue L/R through them (listening test)

Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Will Porter <mrwillporter@gmail.com>
---
 sound/usb/mixer_audient_id.c | 124 +++++++++++++++++++++++++++++++++++
 sound/usb/mixer_maps.c       |   1 +
 2 files changed, 125 insertions(+)

diff --git a/sound/usb/mixer_audient_id.c b/sound/usb/mixer_audient_id.c
index b4d1e1697..5b38590ce 100644
--- a/sound/usb/mixer_audient_id.c
+++ b/sound/usb/mixer_audient_id.c
@@ -43,6 +43,7 @@
 #define ID14_FU_OUT		0x0a	/* feature unit before the outputs */
 #define ID14_ROUTE		0x33	/* routing XU: CN = destination */
 #define ID14_MON		0x36	/* monitor controller XU */
+#define ID14_MIXER		0x3c	/* mixer unit: 14 inputs x 4 outputs */
 
 /* Control selectors */
 #define ID14_ROUTE_SOURCE	0x06	/* u8: source of the destination */
@@ -60,6 +61,7 @@
  * use 1 dB steps. The controls show the steps: 0 (-128 dB, mute) ... 128 (0 dB).
  */
 #define ID_DB(db)		((db) * 256)
+#define ID_DB_MIN		ID_DB(-128)	/* 0x8000: mute */
 #define ID_VOL_MAX		128		/* control value for 0 dB */
 
 enum id_kind {
@@ -392,10 +394,125 @@ static int id_add_ctl(struct usb_mixer_interface *mixer, const struct id_ctl *c)
 	return snd_usb_mixer_add_list(&e->list, kctl, false, true);
 }
 
+/*
+ * iD14 MkI mixer crosspoints: CS 1, CN = input * 4 + output, s16 in
+ * 1/256 dB. The control value is the raw s16, -128 dB (mute) ... +6 dB;
+ * the iD application's pan law needs the fine steps. GET CUR works; no
+ * probe writes.
+ */
+#define ID14_MIX_OUTPUTS	4
+#define ID14_MIX_MAX		ID_DB(6)
+
+static const char *const id14_mix_inputs[] = {
+	"Mic 1", "Mic 2",
+	"Digi 1", "Digi 2", "Digi 3", "Digi 4", "Digi 5", "Digi 6", "Digi 7", "Digi 8",
+	"DAW 1", "DAW 2", "DAW 3", "DAW 4",
+};
+
+static const char *const id14_mix_outputs[ID14_MIX_OUTPUTS] = {
+	"Main L", "Main R", "Cue L", "Cue R",
+};
+
+static const DECLARE_TLV_DB_MINMAX_MUTE(id14_mix_tlv, -12800, 600);
+
+static int id14_mix_info(struct snd_kcontrol *kctl, struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 1;
+	uinfo->value.integer.min = ID_DB_MIN;
+	uinfo->value.integer.max = ID14_MIX_MAX;
+	return 0;
+}
+
+struct id14_mix {
+	struct usb_mixer_elem_list list;	/* first: snd_usb_mixer_elem_free */
+	u8 cn;
+};
+
+static int id14_mix_read(struct id14_mix *m, long *val)
+{
+	u8 buf[2];
+	int err;
+
+	err = id_xfer(m->list.mixer, false, ID14_MIXER, UAC2_MU_MIXER, m->cn, buf, sizeof(buf));
+	if (err < 0)
+		return err;
+	*val = clamp_t(long, (s16)get_unaligned_le16(buf), ID_DB_MIN, ID14_MIX_MAX);
+	return 0;
+}
+
+static int id14_mix_get(struct snd_kcontrol *kctl, struct snd_ctl_elem_value *v)
+{
+	struct id14_mix *m = snd_kcontrol_chip(kctl);
+	long val;
+	int err;
+
+	err = id14_mix_read(m, &val);
+	if (err < 0)
+		return err;
+	v->value.integer.value[0] = val;
+	return 0;
+}
+
+static int id14_mix_put(struct snd_kcontrol *kctl, struct snd_ctl_elem_value *v)
+{
+	struct id14_mix *m = snd_kcontrol_chip(kctl);
+	long cur, val = v->value.integer.value[0];
+	u8 buf[2];
+	int err;
+
+	if (val < ID_DB_MIN || val > ID14_MIX_MAX)
+		return -EINVAL;
+	CLASS(snd_usb_lock, pm)(m->list.mixer->chip);
+	if (pm.err < 0)
+		return pm.err;
+	err = id14_mix_read(m, &cur);
+	if (err < 0 || cur == val)
+		return err;
+	put_unaligned_le16(val, buf);
+	err = id_xfer(m->list.mixer, true, ID14_MIXER, UAC2_MU_MIXER, m->cn, buf, sizeof(buf));
+	return err < 0 ? err : 1;
+}
+
+static int id14_mix_add_ctl(struct usb_mixer_interface *mixer, unsigned int in,
+			    unsigned int out)
+{
+	struct snd_kcontrol_new knew = {
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.access = SNDRV_CTL_ELEM_ACCESS_READWRITE |
+			  SNDRV_CTL_ELEM_ACCESS_VOLATILE |
+			  SNDRV_CTL_ELEM_ACCESS_TLV_READ,
+		.info = id14_mix_info,
+		.get = id14_mix_get,
+		.put = id14_mix_put,
+		.tlv.p = id14_mix_tlv,
+	};
+	struct snd_kcontrol *kctl;
+	struct id14_mix *m;
+
+	m = kzalloc_obj(*m);
+	if (!m)
+		return -ENOMEM;
+	m->list.mixer = mixer;
+	m->list.id = ID14_MIXER;
+	m->cn = in * ID14_MIX_OUTPUTS + out;
+
+	kctl = snd_ctl_new1(&knew, m);
+	if (!kctl) {
+		kfree(m);
+		return -ENOMEM;
+	}
+	snprintf(kctl->id.name, sizeof(kctl->id.name), "Mix %s %s Playback Volume",
+		 id14_mix_outputs[out], id14_mix_inputs[in]);
+	kctl->private_free = snd_usb_mixer_elem_free;
+	return snd_usb_mixer_add_list(&m->list, kctl, false, true);
+}
+
 /* called from mixer_quirks.c */
 int snd_audient_id14_mk1_controls_create(struct usb_mixer_interface *mixer)
 {
 	const struct id_ctl *c;
+	unsigned int in, out;
 	int err;
 
 	for (c = id14_mk1_ctls; c->name; c++) {
@@ -403,5 +520,12 @@ int snd_audient_id14_mk1_controls_create(struct usb_mixer_interface *mixer)
 		if (err < 0)
 			return err;
 	}
+	for (in = 0; in < ARRAY_SIZE(id14_mix_inputs); in++) {
+		for (out = 0; out < ID14_MIX_OUTPUTS; out++) {
+			err = id14_mix_add_ctl(mixer, in, out);
+			if (err < 0)
+				return err;
+		}
+	}
 	return 0;
 }
diff --git a/sound/usb/mixer_maps.c b/sound/usb/mixer_maps.c
index 7795a5fbe..e147c1a48 100644
--- a/sound/usb/mixer_maps.c
+++ b/sound/usb/mixer_maps.c
@@ -529,6 +529,7 @@ static const struct usbmix_name_map audient_id14_map[] = {
 /* Audient iD14 MkI: model-specific control map. */
 static const struct usbmix_name_map audient_id14_mk1_map[] = {
 	{ 12, "Monitor Mix Playback" },	/* FU, partial coverage */
+	{ 60, NULL },			/* MU, see mixer_audient_id.c */
 	{}
 };
 
-- 
2.47.3


  parent reply	other threads:[~2026-10-19 23:02 UTC|newest]

Thread overview: 7+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-19 23:01 [PATCH 0/5] ALSA: usb-audio: Audient iD14 MkI vendor controls Will Porter
2026-10-19 23:01 ` [PATCH 1/5] ALSA: usb-audio: Give the Audient iD14 MkI its own mixer map Will Porter
2026-10-19 23:01 ` [PATCH 2/5] ALSA: usb-audio: Add Audient iD14 MkI monitor and routing controls Will Porter
2026-10-19 23:01 ` Will Porter [this message]
2026-10-19 23:01 ` [PATCH 4/5] ALSA: usb-audio: Add Audient iD14 MkI write-only controls Will Porter
2026-10-19 23:01 ` [PATCH 5/5] ALSA: usb-audio: Replace the Audient iD14 MkI FU 12 control Will Porter
2026-10-20 13:10 ` [PATCH 0/5] ALSA: usb-audio: Audient iD14 MkI vendor controls Takashi Iwai

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20261019230159.834954-4-mrwillporter@gmail.com \
    --to=mrwillporter@gmail.com \
    --cc=arc@gmx.li \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-sound@vger.kernel.org \
    --cc=neil@androos.io \
    --cc=perex@perex.cz \
    --cc=tiwai@suse.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.