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
prev 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®