mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: Christian Ruppert <arc@gmx.li>
To: Will Porter <mrwillporter@gmail.com>
Cc: Takashi Iwai <tiwai@suse.com>, Jaroslav Kysela <perex@perex.cz>,
	linux-sound@vger.kernel.org, linux-kernel@vger.kernel.org,
	Neil Andrews <neil@androos.io>, Christian Ruppert <arc@gmx.li>
Subject: [RFC V4 PATCH 19/19] ALSA: mixer_audient: Refactor FU12 special cases into generic set/get functions
Date: Sun, 15 Nov 2026 22:43:36 +0100	[thread overview]
Message-ID: <20261115214336.17917-20-arc@gmx.li> (raw)
In-Reply-To: <20261112023720.1503814-1-mrwillporter@gmail.com>

With the unified get and set functions it is trivial to merge the iD14 FU12
quirks into the generic table controls and remove even more redundant code.
Add an ID14_FU12 control type, implement the respective case branches in
set/get and remove specific iD14 FU12 code.

Signed-off-by: Christian Ruppert <arc@gmx.li>
---
 sound/usb/mixer_audient.c | 289 +++++++++++++++-----------------------
 1 file changed, 113 insertions(+), 176 deletions(-)

diff --git a/sound/usb/mixer_audient.c b/sound/usb/mixer_audient.c
index 13940572de3f..e24a6e9facc1 100644
--- a/sound/usb/mixer_audient.c
+++ b/sound/usb/mixer_audient.c
@@ -55,17 +55,45 @@ enum snd_audient_type {
 	SND_AUDIENT_TYPE_ID14_MON_ID_KEY,
 	SND_AUDIENT_TYPE_ID14_MON_TALKBACK_SRC,
 	SND_AUDIENT_TYPE_ID14_OPTICAL_MODE,
+	SND_AUDIENT_TYPE_ID14_FU12,
 	SND_AUDIENT_TYPE_NCTYPES
 };
 
 /* Mixer matrix: -128dB (mute) to +6dB */
 static const DECLARE_TLV_DB_MINMAX_MUTE(snd_audient_tlv_mixer, -12800, 600);
 
+/*
+ * iD14 MkI FU 12, the mixer output level: CN 1-2 Main, 3-4 Cue. GET RANGE
+ * works (-127 ... 0 dB, 1 dB steps), but GET CUR returns a reply buffer the
+ * device shares with other units, not the FU 12 value. So the generic
+ * control shows stale data, and its sticky-GET check passes and then writes
+ * that stale value to Main L.
+ *
+ * The firmware ignores Main L (CN 1) but applies Main R (CN 2), so any Main
+ * level other than 0 dB unbalances the main outputs (Cue L and Cue R both
+ * work). This control holds Main at 0 dB: a put sends 0 dB to CN 1-2
+ * whatever it asks for, and get reports it.
+ *
+ * It keeps the generic control's name and format: four values, 0 ... 127
+ * in 1 dB steps, 127 = 0 dB, 0 = mute (0x8000). It shows the last value the
+ * device accepted, and every put sends all four channels.
+ */
+#define ID14_FU12_MAIN_CHANNELS	2	/* CN 1-2, held at 0 dB */
+#define ID14_FU12_MAX		127
+#define ID14_FU12_MUTE		0
+#define ID14_FU12_HW_MUTE	(-128 * 256)
+
+static const DECLARE_TLV_DB_MINMAX_MUTE(snd_audient_tlv_id14_fu12, -12700, 0);
+
+/* control type flags */
 #define SND_AUDIENT_CTYPE_FLAGS_SIGNED		BIT(0)
 #define SND_AUDIENT_CTYPE_FLAGS_INIT_HW		BIT(1)
 #define SND_AUDIENT_CTYPE_FLAGS_GET_BROKEN	BIT(2)
 #define SND_AUDIENT_CTYPE_FLAGS_CACHE		BIT(3)
 
+/* Private control element types for quirky controls */
+#define SNDRV_AUDIENT_CTL_ELEM_TYPE_ID14_FU12	(SNDRV_CTL_ELEM_TYPE_LAST + 1)
+
 /*
  * An enumerated control: item i sends values[i]. A get of a device value
  * outside the list returns -EIO; it does not show a wrong item.
@@ -376,6 +404,20 @@ static const struct snd_audient_ctrl_type {
 		.unit = ID14_IT_DIGITAL,
 		.sel = ID14_IT_OPTICAL_MODE,
 	},
+	[SND_AUDIENT_TYPE_ID14_FU12] = {
+		.type = SNDRV_AUDIENT_CTL_ELEM_TYPE_ID14_FU12,
+		.flags = SND_AUDIENT_CTYPE_FLAGS_SIGNED
+			 | SND_AUDIENT_CTYPE_FLAGS_INIT_HW
+			 | SND_AUDIENT_CTYPE_FLAGS_CACHE,
+		.len = 2,
+		.min.integer = -127 * 256,
+		.max.integer = 0,
+		.init = ID14_FU12_HW_MUTE,
+		.res = 256,
+		.unit = ID14_FU_MIX,
+		.sel = UAC_FU_VOLUME,
+		.tlv = snd_audient_tlv_id14_fu12,
+	},
 };
 
 
@@ -470,6 +512,7 @@ static const struct snd_audient_ctrl snd_audient_id14_mk1_controls[] = {
 	ID14_CTRL("iD Key Function Playback Enum", MON_ID_KEY, 0, 1),
 	ID14_CTRL("Talkback Source Capture Enum", MON_TALKBACK_SRC, 0, 1),
 	ID14_CTRL("Digital In Mode Capture Enum", OPTICAL_MODE, 0, 1),
+	ID14_CTRL("Monitor Mix Playback Volume", FU12, 1, 4),
 	{ 0 }, /* sentinel */
 };
 
@@ -492,15 +535,12 @@ static const char *const snd_audient_id14_mk1_matrix[] = {
 	NULL
 };
 
-static int snd_audient_id14_mk1_create(struct usb_mixer_interface *mixer);
-
 static const struct audient_devinfo {
 	u32 usb_id;
 	const struct snd_audient_ctrl *controls;
 	const char *const *const matrix_names;
 	const unsigned int matrix_channels;
 	/* optional: controls the tables cannot describe, created after them */
-	int (*extra_init)(struct usb_mixer_interface *mixer);
 } audient_devinfo[] = {
 	{
 		.usb_id = USB_ID(USB_AUDIENT_VID, USB_EVO4_PID),
@@ -513,7 +553,6 @@ static const struct audient_devinfo {
 		.controls = snd_audient_id14_mk1_controls,
 		.matrix_names = snd_audient_id14_mk1_matrix,
 		.matrix_channels = 2,
-		.extra_init = snd_audient_id14_mk1_create,
 	},
 	{ 0 } /* sentinel */
 };
@@ -739,6 +778,31 @@ static int snd_audient_enum_item(const struct snd_audient_enum *e, u32 value)
 	return -EIO;
 }
 
+static inline u32
+snd_audient_fu12_from_ctl(const struct snd_audient_ctrl_type *ctype, long val)
+{
+	s32 ret;
+
+	if (val == ID14_FU12_MUTE)
+		ret = ID14_FU12_HW_MUTE;
+	else
+		ret = ctype->min.integer + val * ctype->res;
+
+	return (u32)ret;
+}
+
+static inline long
+snd_audient_fu12_to_ctl(const struct snd_audient_ctrl_type *ctype, u32 buf)
+{
+	s32 val = (s32)buf;
+
+	if (val == ID14_FU12_HW_MUTE)
+		return ID14_FU12_MUTE;
+	else
+		return min(DIV_ROUND_CLOSEST(val - ctype->min.integer,
+					     (long)ctype->res),
+			   snd_audient_int_max(ctype));
+}
 
 static int snd_ctl_audient_get(struct snd_kcontrol *kctl,
 				       struct snd_ctl_elem_value *ctl_val)
@@ -799,6 +863,10 @@ static int snd_ctl_audient_get(struct snd_kcontrol *kctl,
 				ctl_val->value.enumerated.item[i] = ret;
 			}
 			break;
+		case SNDRV_AUDIENT_CTL_ELEM_TYPE_ID14_FU12:
+			ctl_val->value.integer.value[i] =
+					snd_audient_fu12_to_ctl(ctype, buf);
+			break;
 		}
 	}
 
@@ -834,6 +902,11 @@ static int snd_ctl_audient_set(struct snd_kcontrol *kctl,
 			if (v->enumerated.item[i] >= e->items)
 				return -EINVAL;
 			break;
+		case SNDRV_AUDIENT_CTL_ELEM_TYPE_ID14_FU12:
+			if (v->integer.value[i] < 0
+			    || v->integer.value[i] > ID14_FU12_MAX)
+				return -EINVAL;
+			break;
 		}
 	}
 
@@ -859,6 +932,14 @@ static int snd_ctl_audient_set(struct snd_kcontrol *kctl,
 		case SNDRV_CTL_ELEM_TYPE_ENUMERATED:
 			val = e->values[v->enumerated.item[i]];
 			break;
+		case SNDRV_AUDIENT_CTL_ELEM_TYPE_ID14_FU12:
+			if (i < ID14_FU12_MAIN_CHANNELS) {
+				val = ctype->max.integer;
+			} else {
+				val = snd_audient_fu12_from_ctl(ctype,
+							v->integer.value[i]);
+			}
+			break;
 		}
 
 		ret = snd_audient_raw_set_one(elem, ctype, base + i, val,
@@ -879,25 +960,30 @@ static int snd_ctl_audient_info(struct snd_kcontrol *kctl,
 	const struct snd_audient_ctrl_type *const ctype
 						= snd_audient_kctl_type(kctl);
 
+	uinfo->type = ctype->type;
+	uinfo->count = snd_audient_kctl_channels(kctl);
+
 	switch (ctype->type) {
-	case SNDRV_CTL_ELEM_TYPE_ENUMERATED:
-		return snd_ctl_enum_info(uinfo,
-					 snd_audient_kctl_channels(kctl),
-					 ctype->enumeration->items,
-					 ctype->enumeration->texts);
 	case SNDRV_CTL_ELEM_TYPE_BOOLEAN:
 		uinfo->value.integer.min = 0;
 		uinfo->value.integer.max = 1;
 		break;
-	default:
+	case SNDRV_CTL_ELEM_TYPE_INTEGER:
 		uinfo->value.integer.min = snd_audient_int_min(ctype);
 		uinfo->value.integer.max = snd_audient_int_max(ctype);
 		break;
+	case SNDRV_CTL_ELEM_TYPE_ENUMERATED:
+		return snd_ctl_enum_info(uinfo,
+					 snd_audient_kctl_channels(kctl),
+					 ctype->enumeration->items,
+					 ctype->enumeration->texts);
+	case SNDRV_AUDIENT_CTL_ELEM_TYPE_ID14_FU12:
+		uinfo->value.integer.min = 0;
+		uinfo->value.integer.max = ID14_FU12_MAX;
+		uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+		break;
 	}
 
-	uinfo->type = ctype->type;
-	uinfo->count = snd_audient_kctl_channels(kctl);
-
 	return 0;
 }
 
@@ -949,9 +1035,22 @@ static int snd_audient_add_ctrl(struct usb_mixer_interface *mixer,
 		int i;
 
 		for (i = 0; i < ctrl->hwchannels; i++) {
+			u32 init;
+
+			switch (ctype->type) {
+			case SNDRV_AUDIENT_CTL_ELEM_TYPE_ID14_FU12:
+				if (i < ID14_FU12_MAIN_CHANNELS)
+					init = ctype->max.integer;
+				else
+					init = ctype->init;
+				break;
+			default:
+				init = ctype->init;
+				break;
+			}
 			ret = snd_audient_raw_set_one(elem, ctype,
 						      ctrl->val_offs + i,
-						      ctype->init, true);
+						      init, true);
 			if (ret < 0)
 				usb_audio_warn(mixer->chip,
 					       "%s: cannot set channel %d (%d)\n",
@@ -976,165 +1075,6 @@ static int snd_audient_add_ctrl(struct usb_mixer_interface *mixer,
 }
 
 
-/*
- * iD14 MkI FU 12, the mixer output level: CN 1-2 Main, 3-4 Cue. GET RANGE
- * works (-127 ... 0 dB, 1 dB steps), but GET CUR returns a reply buffer the
- * device shares with other units, not the FU 12 value. So the generic
- * control shows stale data, and its sticky-GET check passes and then writes
- * that stale value to Main L.
- *
- * The firmware ignores Main L (CN 1) but applies Main R (CN 2), so any Main
- * level other than 0 dB unbalances the main outputs (Cue L and Cue R both
- * work). This control holds Main at 0 dB: a put sends 0 dB to CN 1-2
- * whatever it asks for, and get reports it.
- *
- * It keeps the generic control's name and format: four values, 0 ... 127
- * in 1 dB steps, 127 = 0 dB, 0 = mute (0x8000). It shows the last value the
- * device accepted, and every put sends all four channels.
- */
-#define ID14_FU12_CHANNELS	4
-#define ID14_FU12_MAIN_CHANNELS	2	/* CN 1-2, held at 0 dB */
-#define ID14_FU12_MAX		127
-#define ID14_FU12_MUTE		0
-
-static const DECLARE_TLV_DB_MINMAX_MUTE(snd_audient_tlv_id14_fu12, -12700, 0);
-
-static const struct snd_audient_ctrl_type snd_audient_id14_fu12_type = {
-	.len = 2,
-	.unit = ID14_FU_MIX,
-	.sel = UAC_FU_VOLUME,
-};
-
-struct snd_audient_id14_fu12 {
-	struct usb_mixer_elem_list list;	/* first: snd_usb_mixer_elem_free */
-	long val[ID14_FU12_CHANNELS];
-	u8 valid;				/* bit per channel */
-};
-
-static int snd_audient_id14_fu12_write(struct snd_audient_id14_fu12 *f,
-				       unsigned int ch, long val)
-{
-	__le16 buf = cpu_to_le16(val == ID14_FU12_MUTE ? -128 * 256
-				 : (val - ID14_FU12_MAX) * 256);
-	int ret;
-
-	ret = snd_audient_set_cur(f->list.mixer, &snd_audient_id14_fu12_type,
-				  ch + 1, &buf);
-	if (ret < 0)
-		return ret;
-
-	f->val[ch] = val;
-	f->valid |= BIT(ch);
-
-	return 0;
-}
-
-static int snd_ctl_audient_id14_fu12_info(struct snd_kcontrol *kctl,
-					  struct snd_ctl_elem_info *uinfo)
-{
-	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
-	uinfo->count = ID14_FU12_CHANNELS;
-	uinfo->value.integer.min = 0;
-	uinfo->value.integer.max = ID14_FU12_MAX;
-
-	return 0;
-}
-
-static int snd_ctl_audient_id14_fu12_get(struct snd_kcontrol *kctl,
-					 struct snd_ctl_elem_value *ctl_val)
-{
-	struct snd_audient_id14_fu12 *f = snd_kcontrol_chip(kctl);
-	unsigned int ch;
-
-	/* a channel that was never written reads as mute */
-	for (ch = 0; ch < ID14_FU12_CHANNELS; ch++)
-		ctl_val->value.integer.value[ch] = (f->valid & BIT(ch))
-						   ? f->val[ch]
-						   : ID14_FU12_MUTE;
-
-	return 0;
-}
-
-static int snd_ctl_audient_id14_fu12_set(struct snd_kcontrol *kctl,
-					 struct snd_ctl_elem_value *ctl_val)
-{
-	struct snd_audient_id14_fu12 *f = snd_kcontrol_chip(kctl);
-	long *const v = ctl_val->value.integer.value;
-	unsigned int ch;
-	int changed = 0;
-
-	for (ch = 0; ch < ID14_FU12_CHANNELS; ch++)
-		if (v[ch] < 0 || v[ch] > ID14_FU12_MAX)
-			return -EINVAL;
-
-	CLASS(snd_usb_lock, pm)(f->list.mixer->chip);
-	if (pm.err < 0)
-		return pm.err;
-
-	/* Main is held at 0 dB: report that back to the caller */
-	for (ch = 0; ch < ID14_FU12_MAIN_CHANNELS; ch++)
-		v[ch] = ID14_FU12_MAX;
-
-	for (ch = 0; ch < ID14_FU12_CHANNELS; ch++) {
-		int ret;
-
-		if (!(f->valid & BIT(ch)) || f->val[ch] != v[ch])
-			changed = 1;
-
-		ret = snd_audient_id14_fu12_write(f, ch, v[ch]);
-		if (ret < 0)
-			return ret;
-	}
-
-	return changed;
-}
-
-/* At probe, set Main to 0 dB and mute Cue. */
-static int snd_audient_id14_mk1_create(struct usb_mixer_interface *mixer)
-{
-	struct snd_kcontrol_new knew = {
-		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
-		.name = "Monitor Mix Playback Volume",
-		.access = SNDRV_CTL_ELEM_ACCESS_READWRITE
-			  | SNDRV_CTL_ELEM_ACCESS_TLV_READ,
-		.info = snd_ctl_audient_id14_fu12_info,
-		.get = snd_ctl_audient_id14_fu12_get,
-		.put = snd_ctl_audient_id14_fu12_set,
-		.tlv.p = snd_audient_tlv_id14_fu12,
-	};
-	struct snd_audient_id14_fu12 *f;
-	struct snd_kcontrol *kctl;
-	unsigned int ch;
-
-	f = kzalloc_obj(*f);
-	if (!f)
-		return -ENOMEM;
-
-	f->list.mixer = mixer;
-	f->list.id = ID14_FU_MIX;
-
-	for (ch = 0; ch < ID14_FU12_CHANNELS; ch++) {
-		const int ret = snd_audient_id14_fu12_write(f, ch,
-				ch < ID14_FU12_MAIN_CHANNELS ? ID14_FU12_MAX
-							     : ID14_FU12_MUTE);
-
-		if (ret < 0)	/* the channel stays unknown and reads as mute */
-			usb_audio_warn(mixer->chip,
-				       "iD14: cannot set FU 12 channel %u (%d)\n",
-				       ch + 1, ret);
-	}
-
-	kctl = snd_ctl_new1(&knew, f);
-	if (!kctl) {
-		kfree(f);
-		return -ENOMEM;
-	}
-
-	kctl->private_free = snd_usb_mixer_elem_free;
-
-	return snd_usb_mixer_add_list(&f->list, kctl, false, true);
-}
-
 /* called from mixer_quirks.c */
 int snd_audient_controls_create(struct usb_mixer_interface *mixer)
 {
@@ -1174,8 +1114,5 @@ int snd_audient_controls_create(struct usb_mixer_interface *mixer)
 		}
 	}
 
-	if (info->extra_init)
-		return info->extra_init(mixer);
-
 	return 0;
 }
-- 
2.56.1


      parent reply	other threads:[~2026-11-15 21:43 UTC|newest]

Thread overview: 31+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
     [not found] <sashiko-outbox-201869@kernel.org>
2026-11-06 20:29 ` [RFC PATCH 13/16] ALSA: mixer_audient: Add enumerated controls Will Porter
2026-11-09 22:30   ` Christian Ruppert
2026-11-10  2:13     ` Will Porter
2026-11-10 20:57       ` Christian Ruppert
2026-11-12  2:37         ` [RFC PATCH 0/6] ALSA: mixer_audient: Cache rework and simplifications Will Porter
2026-11-12  2:37           ` [RFC PATCH 1/6] ALSA: mixer_audient: Fold in the review changes to patches 12-16 Will Porter
2026-11-12  2:37           ` [RFC PATCH 2/6] ALSA: mixer_audient: Use the cache of usb_mixer_elem_info Will Porter
2026-11-12  2:37           ` [RFC PATCH 3/6] ALSA: mixer_audient: Replace three flags with an access mode Will Porter
2026-11-12  2:37           ` [RFC PATCH 4/6] ALSA: mixer_audient: Fill the cache on first use Will Porter
2026-11-12  2:37           ` [RFC PATCH 5/6] ALSA: mixer_audient: Use one get and one put for all control types Will Porter
2026-11-12  2:37           ` [RFC PATCH 6/6] ALSA: mixer_audient: Use the same cache for the iD14 MkI FU 12 control Will Porter
2026-11-15 21:43           ` [RFC V4 PATCH 00/19] Re: ALSA: mixer_audient: Cache rework and simplifications Christian Ruppert
2026-11-15 21:43           ` [RFC V4 PATCH 01/19] ALSA: rename mixer_evo to mixer_audient Christian Ruppert
2026-11-15 21:43           ` [RFC V4 PATCH 02/19] ALSA: mixer_audient: explicitly define full control names Christian Ruppert
2026-11-15 21:43           ` [RFC V4 PATCH 03/19] ALSA: mixer_audient: clarify USB control message definition Christian Ruppert
2026-11-15 21:43           ` [RFC V4 PATCH 04/19] ALSA: mixer_audient: Add length check to USB transfer Christian Ruppert
2026-11-15 21:43           ` [RFC V4 PATCH 05/19] ALSA: mixer_audient: make USB control transfer size configurable Christian Ruppert
2026-11-15 21:43           ` [RFC V4 PATCH 06/19] ALSA: mixer_audient: Allow arbitrary boolean control values Christian Ruppert
2026-11-15 21:43           ` [RFC V4 PATCH 07/19] ALSA: mixer_audient: Add integer range checks Christian Ruppert
2026-11-15 21:43           ` [RFC V4 PATCH 08/19] ALSA: mixer_audient: Add control initialization at probe Christian Ruppert
2026-11-15 21:43           ` [RFC V4 PATCH 09/19] ALSA: mixer_audient: Add readback-free parameter set Christian Ruppert
2026-11-15 21:43           ` [RFC V4 PATCH 10/19] ALSA: mixer_audient: add control value caching Christian Ruppert
2026-11-15 21:43           ` [RFC V4 PATCH 11/19] ALSA: mixer_audient: Create matrix programmatically Christian Ruppert
2026-11-15 21:43           ` [RFC V4 PATCH 12/19] ALSA: mixer_audient: Add a step size to integer controls Christian Ruppert
2026-11-15 21:43           ` [RFC V4 PATCH 13/19] ALSA: mixer_audient: Add enumerated controls Christian Ruppert
2026-11-15 21:43           ` [RFC V4 PATCH 14/19] ALSA: usb-audio: Give the Audient iD14 MkI its own mixer map Christian Ruppert
2026-11-15 21:43           ` [RFC V4 PATCH 15/19] ALSA: mixer_audient: Add Audient iD14 MkI controls Christian Ruppert
2026-11-15 21:43           ` [RFC V4 PATCH 16/19] ALSA: mixer_audient: Replace the Audient iD14 MkI FU 12 control Christian Ruppert
2026-11-15 21:43           ` [RFC V4 PATCH 17/19] ALSA: mixer_audient: Fold in the review changes to patches 12-16 Christian Ruppert
2026-11-15 21:43           ` [RFC V4 PATCH 18/19] ALSA: mixer_audient: Unify snd_audient_*_set/get functions for different types Christian Ruppert
2026-11-15 21:43           ` Christian Ruppert [this message]

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=20261115214336.17917-20-arc@gmx.li \
    --to=arc@gmx.li \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-sound@vger.kernel.org \
    --cc=mrwillporter@gmail.com \
    --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 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®