* [RFC PATCH 1/6] ALSA: mixer_audient: Fold in the review changes to patches 12-16
2026-11-12 2:37 ` [RFC PATCH 0/6] ALSA: mixer_audient: Cache rework and simplifications Will Porter
@ 2026-11-12 2:37 ` Will Porter
2026-11-12 2:37 ` [RFC PATCH 2/6] ALSA: mixer_audient: Use the cache of usb_mixer_elem_info Will Porter
` (24 subsequent siblings)
25 siblings, 0 replies; 31+ messages in thread
From: Will Porter @ 2026-11-12 2:37 UTC (permalink / raw)
To: Christian Ruppert
Cc: Takashi Iwai, Jaroslav Kysela, linux-sound, linux-kernel, Neil Andrews
This patch is not for merging. It carries the changes from the review
of RFC patches 12 to 16 as one diff, so that the patches after it apply
to the RFC with the enum fix. The next version of the series has these
changes in patches 12, 13, 15 and 16.
- Declare snd_audient_int_max(), snd_audient_int_min() and
snd_audient_int_from_ctl() inline.
- snd_audient_enum_item() returns the item or -EIO, and get propagates
the error.
- snd_ctl_audient_info() uses a switch.
- A comment on ID14_ENUM() says that readable enums do not use init.
- The two case labels in mixer_quirks.c share one call.
- Rename the create hook to extra_init and say that it is optional.
Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Will Porter <mrwillporter@gmail.com>
---
sound/usb/mixer_audient.c | 58 +++++++++++++++++++++++----------------
sound/usb/mixer_quirks.c | 2 --
2 files changed, 34 insertions(+), 26 deletions(-)
diff --git a/sound/usb/mixer_audient.c b/sound/usb/mixer_audient.c
index 4180d7d8b325c..df1e25a55a7a9 100644
--- a/sound/usb/mixer_audient.c
+++ b/sound/usb/mixer_audient.c
@@ -219,6 +219,11 @@ SND_AUDIENT_ENUM(snd_audient_id14_optical, snd_audient_id14_optical_texts,
.sel = (sel_), \
}
+/*
+ * A readable enum. It has no cache entry, so a get always reads the device
+ * and never uses init. Write-only enums set .init and .flags without this
+ * macro.
+ */
#define ID14_ENUM(unit_, sel_, len_, enum_) \
{ \
.type = SNDRV_CTL_ELEM_TYPE_ENUMERATED, \
@@ -494,8 +499,8 @@ static const struct audient_devinfo {
const struct snd_audient_ctrl *controls;
const char *const *const matrix_names;
const unsigned int matrix_channels;
- /* controls the tables cannot describe; called after the tables */
- int (*create)(struct usb_mixer_interface *mixer);
+ /* 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),
@@ -508,7 +513,7 @@ static const struct audient_devinfo {
.controls = snd_audient_id14_mk1_controls,
.matrix_names = snd_audient_id14_mk1_matrix,
.matrix_channels = 2,
- .create = snd_audient_id14_mk1_create,
+ .extra_init = snd_audient_id14_mk1_create,
},
{ 0 } /* sentinel */
};
@@ -779,13 +784,15 @@ static int snd_ctl_audient_boolean_set(struct snd_kcontrol *kctl,
* With a step size (res), an integer control counts steps from min:
* 0 ... (max - min) / res. Without one it shows the device value.
*/
-static long snd_audient_int_max(const struct snd_audient_ctrl_type *ctype)
+static inline long
+snd_audient_int_max(const struct snd_audient_ctrl_type *ctype)
{
return ctype->res ? (ctype->max.integer - ctype->min.integer) / ctype->res
: ctype->max.integer;
}
-static long snd_audient_int_min(const struct snd_audient_ctrl_type *ctype)
+static inline long
+snd_audient_int_min(const struct snd_audient_ctrl_type *ctype)
{
return ctype->res ? 0 : ctype->min.integer;
}
@@ -802,8 +809,8 @@ static long snd_audient_int_to_ctl(const struct snd_audient_ctrl_type *ctype,
snd_audient_int_max(ctype));
}
-static long snd_audient_int_from_ctl(const struct snd_audient_ctrl_type *ctype,
- long val)
+static inline long
+snd_audient_int_from_ctl(const struct snd_audient_ctrl_type *ctype, long val)
{
return ctype->res ? ctype->min.integer + val * ctype->res : val;
}
@@ -896,16 +903,16 @@ static int snd_ctl_audient_integer_set(struct snd_kcontrol *kctl,
return changed;
}
-static unsigned int snd_audient_enum_item(const struct snd_audient_enum *e,
- u32 value)
+/* The item index of a device value, or -EIO if the value is not in the list */
+static int snd_audient_enum_item(const struct snd_audient_enum *e, u32 value)
{
unsigned int item;
for (item = 0; item < e->items; item++)
if (e->values[item] == value)
- break;
+ return item;
- return item; /* e->items: not in the list */
+ return -EIO;
}
static int snd_ctl_audient_enum_get(struct snd_kcontrol *kctl,
@@ -929,8 +936,8 @@ static int snd_ctl_audient_enum_get(struct snd_kcontrol *kctl,
guard(mutex)(&mixer->lock);
for (i = 0; i < hwchan; i++) {
- unsigned int item;
u32 val;
+ int item;
int ret;
ret = snd_audient_raw_get_one(mixer, ctype,
@@ -942,8 +949,8 @@ static int snd_ctl_audient_enum_get(struct snd_kcontrol *kctl,
return ret;
item = snd_audient_enum_item(ctype->enumeration, val);
- if (item >= ctype->enumeration->items)
- return -EIO;
+ if (item < 0)
+ return item;
ctl_val->value.enumerated.item[i] = item;
}
@@ -1002,22 +1009,25 @@ static int snd_ctl_audient_info(struct snd_kcontrol *kctl,
const struct snd_audient_ctrl_type *const ctype
= snd_audient_kctl_type(kctl);
- if (ctype->type == SNDRV_CTL_ELEM_TYPE_ENUMERATED)
- return snd_ctl_enum_info(uinfo, 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);
-
- uinfo->type = ctype->type;
- uinfo->count = snd_audient_kctl_channels(kctl);
-
- if (ctype->type == SNDRV_CTL_ELEM_TYPE_BOOLEAN) {
+ case SNDRV_CTL_ELEM_TYPE_BOOLEAN:
uinfo->value.integer.min = 0;
uinfo->value.integer.max = 1;
- } else {
+ break;
+ default:
uinfo->value.integer.min = snd_audient_int_min(ctype);
uinfo->value.integer.max = snd_audient_int_max(ctype);
+ break;
}
+ uinfo->type = ctype->type;
+ uinfo->count = snd_audient_kctl_channels(kctl);
+
return 0;
}
@@ -1332,8 +1342,8 @@ int snd_audient_controls_create(struct usb_mixer_interface *mixer)
}
}
- if (info->create)
- return info->create(mixer);
+ if (info->extra_init)
+ return info->extra_init(mixer);
return 0;
}
diff --git a/sound/usb/mixer_quirks.c b/sound/usb/mixer_quirks.c
index 41d559d639d22..763bbeb2cd9da 100644
--- a/sound/usb/mixer_quirks.c
+++ b/sound/usb/mixer_quirks.c
@@ -4541,8 +4541,6 @@ int snd_usb_mixer_apply_create_quirk(struct usb_mixer_interface *mixer)
break;
case USB_ID(USB_AUDIENT_VID, USB_EVO4_PID): /* Audient EVO 4 */
- err = snd_audient_controls_create(mixer);
- break;
case USB_ID(USB_AUDIENT_VID, USB_ID14_MK1_PID): /* Audient iD14 MkI */
err = snd_audient_controls_create(mixer);
break;
--
2.47.3
^ permalink raw reply [flat|nested] 31+ messages in thread* [RFC PATCH 2/6] ALSA: mixer_audient: Use the cache of usb_mixer_elem_info
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 ` Will Porter
2026-11-12 2:37 ` [RFC PATCH 3/6] ALSA: mixer_audient: Replace three flags with an access mode Will Porter
` (23 subsequent siblings)
25 siblings, 0 replies; 31+ messages in thread
From: Will Porter @ 2026-11-12 2:37 UTC (permalink / raw)
To: Christian Ruppert
Cc: Takashi Iwai, Jaroslav Kysela, linux-sound, linux-kernel, Neil Andrews
A cached control keeps its values in an array that is allocated after
the element and reached through elem->private_data. Every struct
usb_mixer_elem_info already has fields for this: the cached bitmap and
the cache_val[] array.
Use bit i of elem->cached and elem->cache_val[i] for element channel i.
The bit means what the valid flag meant: a transfer confirmed the
value. A successful read or write records the value and sets its bit.
A failed transfer leaves the cached value and the bit unchanged. Remove
the cache struct, its accessor and the extra allocation.
The generic cache holds MAX_CHANNELS values, so refuse a control with
more hardware channels. No control in the tables has more than two.
The generic notify paths clear the cache of an element unless
get_cur_broken is set. A write-only control cannot read its value back
after that, so set get_cur_broken for the cached FORCE_SET controls.
The notify paths do not reach these elements today, because the
elements do not set their unit id.
No functional change for the controls in the tables.
Assisted-by: Claude:claude-fable-5-2
Assisted-by: Claude:claude-sonnet-5-5
Signed-off-by: Will Porter <mrwillporter@gmail.com>
---
sound/usb/mixer_audient.c | 131 +++++++++++++++++---------------------
1 file changed, 60 insertions(+), 71 deletions(-)
diff --git a/sound/usb/mixer_audient.c b/sound/usb/mixer_audient.c
index df1e25a55a7a9..35b562b3aac51 100644
--- a/sound/usb/mixer_audient.c
+++ b/sound/usb/mixer_audient.c
@@ -555,24 +555,13 @@ static inline u16 snd_audient_kctl_channels(struct snd_kcontrol *kctl)
}
/*
- * A cached value is valid once a transfer confirmed it: a read, or a write
- * that succeeded. A probe transfer that fails leaves it invalid. CACHE |
- * FORCE_SET without INIT_HW keeps the earlier behaviour: the init value is
- * assumed, without a transfer.
+ * A cached control keeps its values in the cache of struct
+ * usb_mixer_elem_info: bit i of elem->cached and elem->cache_val[i] for
+ * element channel i. The bit is set once a transfer confirmed the value: a
+ * read, or a write that succeeded. A probe transfer that fails leaves it
+ * clear. CACHE | FORCE_SET without INIT_HW keeps the earlier behaviour: the
+ * init value is assumed, without a transfer.
*/
-struct snd_audient_cache_entry {
- u32 value;
- bool valid;
-};
-
-/* Cache entry of element channel i, or NULL for an uncached control. */
-static inline struct snd_audient_cache_entry *
-snd_audient_cache_slot(struct usb_mixer_elem_info *elem, unsigned int i)
-{
- struct snd_audient_cache_entry *const cache = elem->private_data;
-
- return cache ? cache + i : NULL;
-}
#define AUDIENT_REQTYPE_SET (USB_DIR_OUT | USB_TYPE_CLASS \
@@ -621,26 +610,28 @@ static inline int snd_audient_set_cur(struct usb_mixer_interface *mixer,
}
-static int snd_audient_raw_get_one(struct usb_mixer_interface *const mixer,
+static int snd_audient_raw_get_one(struct usb_mixer_elem_info *const elem,
const struct snd_audient_ctrl_type *const ctype,
- struct snd_audient_cache_entry *const slot,
- const u8 cidx, u32 *const value)
+ const unsigned int ch, const u8 cidx,
+ u32 *const value)
{
+ struct usb_mixer_interface *const mixer = elem->head.mixer;
+ const bool cache = ctype->flags & SND_AUDIENT_CTYPE_FLAGS_CACHE;
int ret;
__le32 buf = 0;
s32 val;
- if (slot && slot->valid) {
- *value = slot->value;
+ if (cache && test_bit(ch, elem->cached)) {
+ *value = elem->cache_val[ch];
return ctype->len;
}
/*
- * Cached FORCE_SET controls are treated as write-only: an invalid
- * cache entry does not trigger a GET.
+ * Cached FORCE_SET controls are treated as write-only: a clear
+ * cache bit does not trigger a GET.
*/
- if (slot && (ctype->flags & SND_AUDIENT_CTYPE_FLAGS_FORCE_SET))
+ if (cache && (ctype->flags & SND_AUDIENT_CTYPE_FLAGS_FORCE_SET))
return -ENODATA;
ret = snd_audient_get_cur(mixer, ctype, cidx, &buf);
@@ -654,27 +645,27 @@ static int snd_audient_raw_get_one(struct usb_mixer_interface *const mixer,
*value = val;
- if (slot) {
- slot->value = val;
- slot->valid = true;
+ if (cache) {
+ elem->cache_val[ch] = val;
+ set_bit(ch, elem->cached);
}
return ret;
}
-static int snd_audient_raw_set_one(struct usb_mixer_interface *const mixer,
+static int snd_audient_raw_set_one(struct usb_mixer_elem_info *const elem,
const struct snd_audient_ctrl_type *const ctype,
- struct snd_audient_cache_entry *const slot,
- const u8 cidx, const u32 value,
- bool force)
+ const unsigned int ch, const u8 cidx,
+ const u32 value, bool force)
{
+ struct usb_mixer_interface *const mixer = elem->head.mixer;
int ret;
u32 val;
if (force) {
val = !value; /* just make it different */
} else {
- ret = snd_audient_raw_get_one(mixer, ctype, slot, cidx, &val);
+ ret = snd_audient_raw_get_one(elem, ctype, ch, cidx, &val);
if (ret == -ENODATA) /* FORCE_SET and not cached yet */
val = !value; /* just make it different */
else if (ret < 0)
@@ -690,9 +681,9 @@ static int snd_audient_raw_set_one(struct usb_mixer_interface *const mixer,
if (ret < 0)
return ret;
- if (slot) {
- slot->value = value;
- slot->valid = true;
+ if (ctype->flags & SND_AUDIENT_CTYPE_FLAGS_CACHE) {
+ elem->cache_val[ch] = value;
+ set_bit(ch, elem->cached);
}
}
@@ -724,9 +715,8 @@ static int snd_ctl_audient_boolean_get(struct snd_kcontrol *kctl,
u32 buf;
int ret;
- ret = snd_audient_raw_get_one(mixer, ctype,
- snd_audient_cache_slot(elem, i),
- base + i, &buf);
+ ret = snd_audient_raw_get_one(elem, ctype, i, base + i,
+ &buf);
if (ret == -ENODATA) /* unknown write-only value: off */
buf = ctype->min.integer;
else if (ret < 0)
@@ -767,9 +757,8 @@ static int snd_ctl_audient_boolean_set(struct snd_kcontrol *kctl,
: ctype->min.integer;
int ret;
- ret = snd_audient_raw_set_one(mixer, ctype,
- snd_audient_cache_slot(elem, i),
- base + i, buf, false);
+ ret = snd_audient_raw_set_one(elem, ctype, i, base + i,
+ buf, false);
if (ret < 0)
return ret;
@@ -839,9 +828,8 @@ static int snd_ctl_audient_integer_get(struct snd_kcontrol *kctl,
s32 val;
int ret;
- ret = snd_audient_raw_get_one(mixer, ctype,
- snd_audient_cache_slot(elem, i),
- base + i, &val);
+ ret = snd_audient_raw_get_one(elem, ctype, i, base + i,
+ &val);
if (ret == -ENODATA) /* unknown write-only value: minimum */
val = ctype->min.integer;
else if (ret < 0)
@@ -890,9 +878,8 @@ static int snd_ctl_audient_integer_set(struct snd_kcontrol *kctl,
ctl_val->value.integer.value[i]);
int ret;
- ret = snd_audient_raw_set_one(mixer, ctype,
- snd_audient_cache_slot(elem, i),
- base + i, val, false);
+ ret = snd_audient_raw_set_one(elem, ctype, i, base + i,
+ val, false);
if (ret < 0)
return ret;
@@ -940,9 +927,8 @@ static int snd_ctl_audient_enum_get(struct snd_kcontrol *kctl,
int item;
int ret;
- ret = snd_audient_raw_get_one(mixer, ctype,
- snd_audient_cache_slot(elem, i),
- base + i, &val);
+ ret = snd_audient_raw_get_one(elem, ctype, i, base + i,
+ &val);
if (ret == -ENODATA) /* unknown write-only value: init */
val = ctype->init;
else if (ret < 0)
@@ -990,9 +976,8 @@ static int snd_ctl_audient_enum_set(struct snd_kcontrol *kctl,
const unsigned int item = ctl_val->value.enumerated.item[i];
int ret;
- ret = snd_audient_raw_set_one(mixer, ctype,
- snd_audient_cache_slot(elem, i),
- base + i, e->values[item], false);
+ ret = snd_audient_raw_set_one(elem, ctype, i, base + i,
+ e->values[item], false);
if (ret < 0)
return ret;
@@ -1072,24 +1057,28 @@ static int snd_audient_add_ctrl(struct usb_mixer_interface *mixer,
return -EINVAL;
}
- elem = kzalloc(sizeof(*elem)
- + ((ctype->flags & SND_AUDIENT_CTYPE_FLAGS_CACHE)
- ? (sizeof(struct snd_audient_cache_entry)
- * ctrl->hwchannels)
- : 0),
- GFP_KERNEL);
+ if (ctrl->hwchannels > MAX_CHANNELS)
+ return -EINVAL;
+
+ elem = kzalloc_obj(*elem);
if (!elem)
return -ENOMEM;
elem->head.mixer = mixer;
elem->channels = ctrl->channels;
- if (ctype->flags & SND_AUDIENT_CTYPE_FLAGS_CACHE)
- elem->private_data = elem + 1;
+ /*
+ * The generic notify paths clear the cache of an element unless
+ * get_cur_broken is set. A write-only control cannot read its value
+ * back after that.
+ */
+ if ((ctype->flags & SND_AUDIENT_CTYPE_FLAGS_CACHE) &&
+ (ctype->flags & SND_AUDIENT_CTYPE_FLAGS_FORCE_SET))
+ elem->get_cur_broken = 1;
/*
* A failed probe transfer must not fail the card: warn and keep the
- * control. Its cache entry stays invalid, so a readable control reads
+ * control. Its cache bit stays clear, so a readable control reads
* the device again on its next get, and a write-only control shows an
* unknown value until a put succeeds.
*/
@@ -1097,8 +1086,7 @@ static int snd_audient_add_ctrl(struct usb_mixer_interface *mixer,
int i;
for (i = 0; i < ctrl->hwchannels; i++) {
- ret = snd_audient_raw_set_one(mixer, ctype,
- snd_audient_cache_slot(elem, i),
+ ret = snd_audient_raw_set_one(elem, ctype, i,
ctrl->val_offs + i,
ctype->init, true);
if (ret < 0)
@@ -1106,20 +1094,21 @@ static int snd_audient_add_ctrl(struct usb_mixer_interface *mixer,
"%s: cannot set channel %d (%d)\n",
ctrl->name, i, ret);
}
- } else if (elem->private_data) {
- struct snd_audient_cache_entry *const cache = elem->private_data;
+ } else if (ctype->flags & SND_AUDIENT_CTYPE_FLAGS_CACHE) {
int i;
for (i = 0; i < ctrl->hwchannels; i++) {
+ u32 val;
+
if (ctype->flags & SND_AUDIENT_CTYPE_FLAGS_FORCE_SET) {
- cache[i].value = ctype->init;
- cache[i].valid = true;
+ elem->cache_val[i] = ctype->init;
+ set_bit(i, elem->cached);
continue;
}
- ret = snd_audient_raw_get_one(mixer, ctype, cache + i,
+ ret = snd_audient_raw_get_one(elem, ctype, i,
ctrl->val_offs + i,
- &cache[i].value);
+ &val);
if (ret < 0)
usb_audio_warn(mixer->chip,
"%s: cannot read channel %d (%d)\n",
--
2.47.3
^ permalink raw reply [flat|nested] 31+ messages in thread* [RFC PATCH 3/6] ALSA: mixer_audient: Replace three flags with an access mode
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 ` Will Porter
2026-11-12 2:37 ` [RFC PATCH 4/6] ALSA: mixer_audient: Fill the cache on first use Will Porter
` (22 subsequent siblings)
25 siblings, 0 replies; 31+ messages in thread
From: Will Porter @ 2026-11-12 2:37 UTC (permalink / raw)
To: Christian Ruppert
Cc: Takashi Iwai, Jaroslav Kysela, linux-sound, linux-kernel, Neil Andrews
The INIT_HW, FORCE_SET and CACHE flags allow eight combinations. The
control tables use three: no flag, CACHE alone, and all three together
(ID14_WO). Name these three with an enum in a new field, access:
READBACK: every get reads the device
CACHED: read once, then use the cache
WRITE_ONLY: never read; show the last write
The SIGNED flag stays.
With a mode, the set helper decides "is it a change" and "send it"
separately. It no longer needs the force argument, or a compare value
that is made different to force a write. The probe write of a
write-only control calls the send helper directly.
The mode also decides the VOLATILE access bit and get_cur_broken.
Remove the ID14_WO macro and the code for the combinations that no
table uses.
No functional change for the controls in the tables.
Assisted-by: Claude:claude-fable-5-2
Assisted-by: Claude:claude-sonnet-5-5
Signed-off-by: Will Porter <mrwillporter@gmail.com>
---
sound/usb/mixer_audient.c | 172 ++++++++++++++++++++------------------
1 file changed, 90 insertions(+), 82 deletions(-)
diff --git a/sound/usb/mixer_audient.c b/sound/usb/mixer_audient.c
index 35b562b3aac51..1bc6b4bbd2910 100644
--- a/sound/usb/mixer_audient.c
+++ b/sound/usb/mixer_audient.c
@@ -62,9 +62,23 @@ enum snd_audient_type {
static const DECLARE_TLV_DB_MINMAX_MUTE(snd_audient_tlv_mixer, -12800, 600);
#define SND_AUDIENT_CTYPE_FLAGS_SIGNED BIT(0)
-#define SND_AUDIENT_CTYPE_FLAGS_INIT_HW BIT(1)
-#define SND_AUDIENT_CTYPE_FLAGS_FORCE_SET BIT(2)
-#define SND_AUDIENT_CTYPE_FLAGS_CACHE BIT(3)
+
+/*
+ * How a control reaches the device.
+ *
+ * A cached value is valid once a transfer confirmed it. A read or a write
+ * that succeeds records the value and sets its cache bit. A transfer that
+ * fails leaves the cached value and the bit unchanged.
+ *
+ * WRITE_ONLY: GET stalls or does not report writes. The driver writes the
+ * init value at probe and sends every put. A get shows the last value
+ * written, or an unknown value while none was.
+ */
+enum snd_audient_access {
+ SND_AUDIENT_ACCESS_READBACK = 0, /* every get reads the device */
+ SND_AUDIENT_ACCESS_CACHED, /* read once, then use the cache */
+ SND_AUDIENT_ACCESS_WRITE_ONLY, /* never read; show the last write */
+};
/*
* An enumerated control: item i sends values[i]. A get of a device value
@@ -177,14 +191,6 @@ static const char *const snd_audient_id14_optical_texts[] = {
SND_AUDIENT_ENUM(snd_audient_id14_optical, snd_audient_id14_optical_texts,
0, 1);
-/*
- * Write-only: GET stalls or does not report writes. Show the last value
- * written, write the init value at probe, and send every put.
- */
-#define ID14_WO (SND_AUDIENT_CTYPE_FLAGS_CACHE \
- | SND_AUDIENT_CTYPE_FLAGS_FORCE_SET \
- | SND_AUDIENT_CTYPE_FLAGS_INIT_HW)
-
#define ID14_BOOL(unit_, sel_) \
{ \
.type = SNDRV_CTL_ELEM_TYPE_BOOLEAN, \
@@ -220,9 +226,9 @@ SND_AUDIENT_ENUM(snd_audient_id14_optical, snd_audient_id14_optical_texts,
}
/*
- * A readable enum. It has no cache entry, so a get always reads the device
- * and never uses init. Write-only enums set .init and .flags without this
- * macro.
+ * A readable enum. Its access mode is READBACK, so a get always reads the
+ * device and never uses init. Write-only enums set .init and .access
+ * without this macro.
*/
#define ID14_ENUM(unit_, sel_, len_, enum_) \
{ \
@@ -235,6 +241,7 @@ SND_AUDIENT_ENUM(snd_audient_id14_optical, snd_audient_id14_optical_texts,
static const struct snd_audient_ctrl_type {
snd_ctl_elem_type_t type;
+ enum snd_audient_access access;
unsigned long flags;
size_t len; /* datatype size, can be 1, 2 or 4 bytes */
union {
@@ -285,8 +292,8 @@ static const struct snd_audient_ctrl_type {
},
[SND_AUDIENT_TYPE_MIXER] = {
.type = SNDRV_CTL_ELEM_TYPE_INTEGER,
- .flags = SND_AUDIENT_CTYPE_FLAGS_SIGNED
- | SND_AUDIENT_CTYPE_FLAGS_CACHE,
+ .access = SND_AUDIENT_ACCESS_CACHED,
+ .flags = SND_AUDIENT_CTYPE_FLAGS_SIGNED,
.len = 2,
.min.integer = -128 * 256,
.max.integer = 6 * 256,
@@ -330,7 +337,7 @@ static const struct snd_audient_ctrl_type {
/* the documented boost is a fixed +10 dB */
[SND_AUDIENT_TYPE_ID14_IN_BOOST] = {
.type = SNDRV_CTL_ELEM_TYPE_BOOLEAN,
- .flags = ID14_WO,
+ .access = SND_AUDIENT_ACCESS_WRITE_ONLY,
.len = 2,
.min.integer = 0,
.max.integer = 10 * 256,
@@ -340,7 +347,7 @@ static const struct snd_audient_ctrl_type {
},
[SND_AUDIENT_TYPE_ID14_IN_POLARITY] = {
.type = SNDRV_CTL_ELEM_TYPE_BOOLEAN,
- .flags = ID14_WO,
+ .access = SND_AUDIENT_ACCESS_WRITE_ONLY,
.len = 1,
.min.integer = 0,
.max.integer = 1,
@@ -350,7 +357,7 @@ static const struct snd_audient_ctrl_type {
},
[SND_AUDIENT_TYPE_ID14_MON_ID_KEY] = {
.type = SNDRV_CTL_ELEM_TYPE_ENUMERATED,
- .flags = ID14_WO,
+ .access = SND_AUDIENT_ACCESS_WRITE_ONLY,
.len = 2,
.init = 0xffff, /* ScrollControl */
.enumeration = &snd_audient_id14_id_key,
@@ -359,7 +366,7 @@ static const struct snd_audient_ctrl_type {
},
[SND_AUDIENT_TYPE_ID14_MON_TALKBACK_SRC] = {
.type = SNDRV_CTL_ELEM_TYPE_ENUMERATED,
- .flags = ID14_WO,
+ .access = SND_AUDIENT_ACCESS_WRITE_ONLY,
.len = 1,
.init = 0xff, /* None */
.enumeration = &snd_audient_id14_talkback,
@@ -369,7 +376,7 @@ static const struct snd_audient_ctrl_type {
/* the power-on mode is S/PDIF */
[SND_AUDIENT_TYPE_ID14_OPTICAL_MODE] = {
.type = SNDRV_CTL_ELEM_TYPE_ENUMERATED,
- .flags = ID14_WO,
+ .access = SND_AUDIENT_ACCESS_WRITE_ONLY,
.len = 4,
.init = 1, /* S/PDIF */
.enumeration = &snd_audient_id14_optical,
@@ -557,10 +564,7 @@ static inline u16 snd_audient_kctl_channels(struct snd_kcontrol *kctl)
/*
* A cached control keeps its values in the cache of struct
* usb_mixer_elem_info: bit i of elem->cached and elem->cache_val[i] for
- * element channel i. The bit is set once a transfer confirmed the value: a
- * read, or a write that succeeded. A probe transfer that fails leaves it
- * clear. CACHE | FORCE_SET without INIT_HW keeps the earlier behaviour: the
- * init value is assumed, without a transfer.
+ * element channel i. See enum snd_audient_access for when the bit is set.
*/
@@ -611,27 +615,24 @@ static inline int snd_audient_set_cur(struct usb_mixer_interface *mixer,
static int snd_audient_raw_get_one(struct usb_mixer_elem_info *const elem,
- const struct snd_audient_ctrl_type *const ctype,
- const unsigned int ch, const u8 cidx,
- u32 *const value)
+ const struct snd_audient_ctrl_type *const ctype,
+ const unsigned int ch, const u8 cidx,
+ u32 *const value)
{
struct usb_mixer_interface *const mixer = elem->head.mixer;
- const bool cache = ctype->flags & SND_AUDIENT_CTYPE_FLAGS_CACHE;
int ret;
__le32 buf = 0;
s32 val;
- if (cache && test_bit(ch, elem->cached)) {
+ if (ctype->access != SND_AUDIENT_ACCESS_READBACK &&
+ test_bit(ch, elem->cached)) {
*value = elem->cache_val[ch];
return ctype->len;
}
- /*
- * Cached FORCE_SET controls are treated as write-only: a clear
- * cache bit does not trigger a GET.
- */
- if (cache && (ctype->flags & SND_AUDIENT_CTYPE_FLAGS_FORCE_SET))
+ /* A write-only control never sends a GET: its value is unknown. */
+ if (ctype->access == SND_AUDIENT_ACCESS_WRITE_ONLY)
return -ENODATA;
ret = snd_audient_get_cur(mixer, ctype, cidx, &buf);
@@ -645,7 +646,7 @@ static int snd_audient_raw_get_one(struct usb_mixer_elem_info *const elem,
*value = val;
- if (cache) {
+ if (ctype->access == SND_AUDIENT_ACCESS_CACHED) {
elem->cache_val[ch] = val;
set_bit(ch, elem->cached);
}
@@ -653,41 +654,55 @@ static int snd_audient_raw_get_one(struct usb_mixer_elem_info *const elem,
return ret;
}
-static int snd_audient_raw_set_one(struct usb_mixer_elem_info *const elem,
- const struct snd_audient_ctrl_type *const ctype,
- const unsigned int ch, const u8 cidx,
- const u32 value, bool force)
+/* Send a value and, for a cached control, record that the device has it. */
+static int snd_audient_raw_send_one(struct usb_mixer_elem_info *const elem,
+ const struct snd_audient_ctrl_type *const ctype,
+ const unsigned int ch, const u8 cidx,
+ const u32 value)
{
- struct usb_mixer_interface *const mixer = elem->head.mixer;
+ __le32 buf = cpu_to_le32(value);
int ret;
- u32 val;
-
- if (force) {
- val = !value; /* just make it different */
- } else {
- ret = snd_audient_raw_get_one(elem, ctype, ch, cidx, &val);
- if (ret == -ENODATA) /* FORCE_SET and not cached yet */
- val = !value; /* just make it different */
- else if (ret < 0)
- return ret;
+
+ ret = snd_audient_set_cur(elem->head.mixer, ctype, cidx, &buf);
+ if (ret < 0)
+ return ret;
+
+ if (ctype->access != SND_AUDIENT_ACCESS_READBACK) {
+ elem->cache_val[ch] = value;
+ set_bit(ch, elem->cached);
}
- force = force || (ctype->flags & SND_AUDIENT_CTYPE_FLAGS_FORCE_SET);
+ return 0;
+}
+
+/*
+ * Set one channel. Returns 1 if the value changed, 0 if not, or a negative
+ * error. A write-only control sends every value; the others send only a
+ * changed one.
+ */
+static int snd_audient_raw_set_one(struct usb_mixer_elem_info *const elem,
+ const struct snd_audient_ctrl_type *const ctype,
+ const unsigned int ch, const u8 cidx,
+ const u32 value)
+{
+ bool changed;
+ u32 cur = 0;
+ int ret;
+
+ ret = snd_audient_raw_get_one(elem, ctype, ch, cidx, &cur);
+ if (ret < 0 && ret != -ENODATA)
+ return ret;
- if (force || (val != value)) {
- __le32 buf = cpu_to_le32(value);
+ /* -ENODATA: write-only and not cached yet */
+ changed = ret == -ENODATA || cur != value;
- ret = snd_audient_set_cur(mixer, ctype, cidx, &buf);
+ if (changed || ctype->access == SND_AUDIENT_ACCESS_WRITE_ONLY) {
+ ret = snd_audient_raw_send_one(elem, ctype, ch, cidx, value);
if (ret < 0)
return ret;
-
- if (ctype->flags & SND_AUDIENT_CTYPE_FLAGS_CACHE) {
- elem->cache_val[ch] = value;
- set_bit(ch, elem->cached);
- }
}
- return val != value;
+ return changed;
}
@@ -758,7 +773,7 @@ static int snd_ctl_audient_boolean_set(struct snd_kcontrol *kctl,
int ret;
ret = snd_audient_raw_set_one(elem, ctype, i, base + i,
- buf, false);
+ buf);
if (ret < 0)
return ret;
@@ -879,7 +894,7 @@ static int snd_ctl_audient_integer_set(struct snd_kcontrol *kctl,
int ret;
ret = snd_audient_raw_set_one(elem, ctype, i, base + i,
- val, false);
+ val);
if (ret < 0)
return ret;
@@ -977,7 +992,7 @@ static int snd_ctl_audient_enum_set(struct snd_kcontrol *kctl,
int ret;
ret = snd_audient_raw_set_one(elem, ctype, i, base + i,
- e->values[item], false);
+ e->values[item]);
if (ret < 0)
return ret;
@@ -1035,9 +1050,9 @@ static int snd_audient_add_ctrl(struct usb_mixer_interface *mixer,
ctrl->hwchannels);
knew.tlv.p = ctype->tlv;
knew.access = SNDRV_CTL_ELEM_ACCESS_READWRITE
- | ((ctype->flags & SND_AUDIENT_CTYPE_FLAGS_CACHE)
- ? 0
- : SNDRV_CTL_ELEM_ACCESS_VOLATILE)
+ | ((ctype->access == SND_AUDIENT_ACCESS_READBACK)
+ ? SNDRV_CTL_ELEM_ACCESS_VOLATILE
+ : 0)
| (ctype->tlv ? SNDRV_CTL_ELEM_ACCESS_TLV_READ : 0);
switch (ctype->type) {
@@ -1072,40 +1087,33 @@ static int snd_audient_add_ctrl(struct usb_mixer_interface *mixer,
* get_cur_broken is set. A write-only control cannot read its value
* back after that.
*/
- if ((ctype->flags & SND_AUDIENT_CTYPE_FLAGS_CACHE) &&
- (ctype->flags & SND_AUDIENT_CTYPE_FLAGS_FORCE_SET))
+ if (ctype->access == SND_AUDIENT_ACCESS_WRITE_ONLY)
elem->get_cur_broken = 1;
/*
* A failed probe transfer must not fail the card: warn and keep the
- * control. Its cache bit stays clear, so a readable control reads
- * the device again on its next get, and a write-only control shows an
+ * control. Its cache bit stays clear, so a cached control reads the
+ * device again on its next get, and a write-only control shows an
* unknown value until a put succeeds.
*/
- if (ctype->flags & SND_AUDIENT_CTYPE_FLAGS_INIT_HW) {
+ if (ctype->access == SND_AUDIENT_ACCESS_WRITE_ONLY) {
int i;
for (i = 0; i < ctrl->hwchannels; i++) {
- ret = snd_audient_raw_set_one(elem, ctype, i,
- ctrl->val_offs + i,
- ctype->init, true);
+ ret = snd_audient_raw_send_one(elem, ctype, i,
+ ctrl->val_offs + i,
+ ctype->init);
if (ret < 0)
usb_audio_warn(mixer->chip,
"%s: cannot set channel %d (%d)\n",
ctrl->name, i, ret);
}
- } else if (ctype->flags & SND_AUDIENT_CTYPE_FLAGS_CACHE) {
+ } else if (ctype->access == SND_AUDIENT_ACCESS_CACHED) {
int i;
for (i = 0; i < ctrl->hwchannels; i++) {
u32 val;
- if (ctype->flags & SND_AUDIENT_CTYPE_FLAGS_FORCE_SET) {
- elem->cache_val[i] = ctype->init;
- set_bit(i, elem->cached);
- continue;
- }
-
ret = snd_audient_raw_get_one(elem, ctype, i,
ctrl->val_offs + i,
&val);
--
2.47.3
^ permalink raw reply [flat|nested] 31+ messages in thread* [RFC PATCH 4/6] ALSA: mixer_audient: Fill the cache on first use
2026-11-12 2:37 ` [RFC PATCH 0/6] ALSA: mixer_audient: Cache rework and simplifications Will Porter
` (2 preceding siblings ...)
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 ` 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
` (21 subsequent siblings)
25 siblings, 0 replies; 31+ messages in thread
From: Will Porter @ 2026-11-12 2:37 UTC (permalink / raw)
To: Christian Ruppert
Cc: Takashi Iwai, Jaroslav Kysela, linux-sound, linux-kernel, Neil Andrews
The probe reads every channel of a cached control. That is not
necessary: a get or put of a channel whose cache bit is clear already
reads the device and fills the cache.
Remove the probe read of cached controls. A failed first read returns
its error to the caller and leaves the cache bit clear, so the next
call reads again.
This changes behaviour. The probe sends no GET for a cached control:
that is 12 fewer requests on the EVO4 and 56 fewer on the iD14 MkI. A
failed read no longer logs "cannot read channel" at probe. A put to a
channel whose cache bit is clear first sends a GET, then sends a SET
only if the value differs.
The probe write of the init value of write-only controls stays.
Assisted-by: Claude:claude-fable-5-2
Assisted-by: Claude:claude-sonnet-5-5
Signed-off-by: Will Porter <mrwillporter@gmail.com>
---
sound/usb/mixer_audient.c | 22 ++++------------------
1 file changed, 4 insertions(+), 18 deletions(-)
diff --git a/sound/usb/mixer_audient.c b/sound/usb/mixer_audient.c
index 1bc6b4bbd2910..ff7bf518743ee 100644
--- a/sound/usb/mixer_audient.c
+++ b/sound/usb/mixer_audient.c
@@ -1091,10 +1091,10 @@ static int snd_audient_add_ctrl(struct usb_mixer_interface *mixer,
elem->get_cur_broken = 1;
/*
- * A failed probe transfer must not fail the card: warn and keep the
- * control. Its cache bit stays clear, so a cached control reads the
- * device again on its next get, and a write-only control shows an
- * unknown value until a put succeeds.
+ * A cached control reads the device on its first get or put. A
+ * write-only control is written once at probe. A failed probe write
+ * must not fail the card: warn and keep the control. Its cache bit
+ * stays clear, so it shows an unknown value until a put succeeds.
*/
if (ctype->access == SND_AUDIENT_ACCESS_WRITE_ONLY) {
int i;
@@ -1108,20 +1108,6 @@ static int snd_audient_add_ctrl(struct usb_mixer_interface *mixer,
"%s: cannot set channel %d (%d)\n",
ctrl->name, i, ret);
}
- } else if (ctype->access == SND_AUDIENT_ACCESS_CACHED) {
- int i;
-
- for (i = 0; i < ctrl->hwchannels; i++) {
- u32 val;
-
- ret = snd_audient_raw_get_one(elem, ctype, i,
- ctrl->val_offs + i,
- &val);
- if (ret < 0)
- usb_audio_warn(mixer->chip,
- "%s: cannot read channel %d (%d)\n",
- ctrl->name, i, ret);
- }
}
kctl = snd_ctl_new1(&knew, elem);
--
2.47.3
^ permalink raw reply [flat|nested] 31+ messages in thread* [RFC PATCH 5/6] ALSA: mixer_audient: Use one get and one put for all control types
2026-11-12 2:37 ` [RFC PATCH 0/6] ALSA: mixer_audient: Cache rework and simplifications Will Porter
` (3 preceding siblings ...)
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 ` 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
` (20 subsequent siblings)
25 siblings, 0 replies; 31+ messages in thread
From: Will Porter @ 2026-11-12 2:37 UTC (permalink / raw)
To: Christian Ruppert
Cc: Takashi Iwai, Jaroslav Kysela, linux-sound, linux-kernel, Neil Andrews
The boolean, integer and enum get and put functions differ only in how
they convert a value between the device and the control. Replace the
six functions with snd_ctl_audient_get() and snd_ctl_audient_put().
Three helpers switch on the control type: one converts a device value
to a control value, one converts a control value back and checks its
range, and one fills a channel that has no hardware.
The locking and the order stay. Put checks every hardware channel
before it takes the lock or sends a request. Get returns -EIO for an
enum value that is not in the list.
The get functions showed an unknown write-only value as off, as the
minimum or as init, by type. Every write-only control in the tables is
a boolean whose init equals min, or an enum. So init gives the same
value for each of them: use init for all types.
No functional change for the controls in the tables.
Assisted-by: Claude:claude-fable-5-2
Assisted-by: Claude:claude-sonnet-5-5
Signed-off-by: Will Porter <mrwillporter@gmail.com>
---
sound/usb/mixer_audient.c | 284 ++++++++++++++------------------------
1 file changed, 101 insertions(+), 183 deletions(-)
diff --git a/sound/usb/mixer_audient.c b/sound/usb/mixer_audient.c
index ff7bf518743ee..edc568830366c 100644
--- a/sound/usb/mixer_audient.c
+++ b/sound/usb/mixer_audient.c
@@ -706,84 +706,6 @@ static int snd_audient_raw_set_one(struct usb_mixer_elem_info *const elem,
}
-static int snd_ctl_audient_boolean_get(struct snd_kcontrol *kctl,
- struct snd_ctl_elem_value *ctl_val)
-{
- struct usb_mixer_elem_info *const elem = snd_kcontrol_chip(kctl);
- struct usb_mixer_interface *const mixer = elem->head.mixer;
-
- const unsigned int nchan = snd_audient_kctl_channels(kctl);
- const unsigned int hwchan = snd_audient_kctl_hwchannels(kctl);
- const struct snd_audient_ctrl_type *const ctype
- = snd_audient_kctl_type(kctl);
- const u8 base = snd_audient_kctl_cidxoffs(kctl);
-
- int i;
-
- CLASS(snd_usb_lock, pm)(mixer->chip);
- if (pm.err < 0)
- return pm.err;
- /* serializes the transfers and the cache, also between gets */
- guard(mutex)(&mixer->lock);
-
- for (i = 0; i < hwchan; i++) {
- u32 buf;
- int ret;
-
- ret = snd_audient_raw_get_one(elem, ctype, i, base + i,
- &buf);
- if (ret == -ENODATA) /* unknown write-only value: off */
- buf = ctype->min.integer;
- else if (ret < 0)
- return ret;
-
- ctl_val->value.integer.value[i] = (buf == ctype->min.integer)
- ? false : true;
- }
-
- for (; i < nchan; i++)
- ctl_val->value.integer.value[i] = 0;
-
- return 0;
-}
-
-static int snd_ctl_audient_boolean_set(struct snd_kcontrol *kctl,
- struct snd_ctl_elem_value *ctl_val)
-{
- struct usb_mixer_elem_info *const elem = snd_kcontrol_chip(kctl);
- struct usb_mixer_interface *const mixer = elem->head.mixer;
-
- const unsigned int hwchan = snd_audient_kctl_hwchannels(kctl);
- const struct snd_audient_ctrl_type *const ctype
- = snd_audient_kctl_type(kctl);
- const u8 base = snd_audient_kctl_cidxoffs(kctl);
-
- int changed = 0;
- int i;
-
- CLASS(snd_usb_lock, pm)(mixer->chip);
- if (pm.err < 0)
- return pm.err;
- /* serializes the transfers and the cache, also between gets */
- guard(mutex)(&mixer->lock);
-
- for (i = 0; i < hwchan; i++) {
- u32 buf = ctl_val->value.integer.value[i] ? ctype->max.integer
- : ctype->min.integer;
- int ret;
-
- ret = snd_audient_raw_set_one(elem, ctype, i, base + i,
- buf);
- if (ret < 0)
- return ret;
-
- if (ret)
- changed = 1;
- }
-
- return changed;
-}
-
/*
* With a step size (res), an integer control counts steps from min:
* 0 ... (max - min) / res. Without one it shows the device value.
@@ -819,106 +741,104 @@ snd_audient_int_from_ctl(const struct snd_audient_ctrl_type *ctype, long val)
return ctype->res ? ctype->min.integer + val * ctype->res : val;
}
-static int snd_ctl_audient_integer_get(struct snd_kcontrol *kctl,
- struct snd_ctl_elem_value *ctl_val)
+/* The item index of a device value, or -EIO if the value is not in the list */
+static int snd_audient_enum_item(const struct snd_audient_enum *e, u32 value)
{
- struct usb_mixer_elem_info *const elem = snd_kcontrol_chip(kctl);
- struct usb_mixer_interface *const mixer = elem->head.mixer;
-
- const unsigned int nchan = snd_audient_kctl_channels(kctl);
- const unsigned int hwchan = snd_audient_kctl_hwchannels(kctl);
- const struct snd_audient_ctrl_type *const ctype
- = snd_audient_kctl_type(kctl);
- const u8 base = snd_audient_kctl_cidxoffs(kctl);
-
- int i;
+ unsigned int item;
- CLASS(snd_usb_lock, pm)(mixer->chip);
- if (pm.err < 0)
- return pm.err;
- /* serializes the transfers and the cache, also between gets */
- guard(mutex)(&mixer->lock);
+ for (item = 0; item < e->items; item++)
+ if (e->values[item] == value)
+ return item;
- for (i = 0; i < hwchan; i++) {
- s32 val;
- int ret;
+ return -EIO;
+}
- ret = snd_audient_raw_get_one(elem, ctype, i, base + i,
- &val);
- if (ret == -ENODATA) /* unknown write-only value: minimum */
- val = ctype->min.integer;
- else if (ret < 0)
- return ret;
+/*
+ * Convert a device value to the control value of hardware channel i. An
+ * enumerated value that is not in the list returns -EIO.
+ */
+static int snd_audient_value_to_ctl(const struct snd_audient_ctrl_type *ctype,
+ struct snd_ctl_elem_value *ctl_val,
+ unsigned int i, u32 raw)
+{
+ int item;
+ switch (ctype->type) {
+ case SNDRV_CTL_ELEM_TYPE_BOOLEAN:
+ ctl_val->value.integer.value[i] = raw != ctype->min.integer;
+ break;
+ case SNDRV_CTL_ELEM_TYPE_INTEGER:
ctl_val->value.integer.value[i] =
- snd_audient_int_to_ctl(ctype, val);
- }
+ snd_audient_int_to_ctl(ctype, (s32)raw);
+ break;
+ default: /* SNDRV_CTL_ELEM_TYPE_ENUMERATED */
+ item = snd_audient_enum_item(ctype->enumeration, raw);
+ if (item < 0)
+ return item;
- for (; i < nchan; i++)
- ctl_val->value.integer.value[i] = snd_audient_int_min(ctype);
+ ctl_val->value.enumerated.item[i] = item;
+ break;
+ }
return 0;
}
-static int snd_ctl_audient_integer_set(struct snd_kcontrol *kctl,
- struct snd_ctl_elem_value *ctl_val)
+/* The control value of a channel without hardware: off, minimum, item 0 */
+static void snd_audient_empty_ctl(const struct snd_audient_ctrl_type *ctype,
+ struct snd_ctl_elem_value *ctl_val,
+ unsigned int i)
{
- struct usb_mixer_elem_info *const elem = snd_kcontrol_chip(kctl);
- struct usb_mixer_interface *mixer = elem->head.mixer;
-
- const unsigned int hwchan = snd_audient_kctl_hwchannels(kctl);
- const struct snd_audient_ctrl_type *const ctype
- = snd_audient_kctl_type(kctl);
- const u8 base = snd_audient_kctl_cidxoffs(kctl);
-
- int changed = 0;
- int i;
+ switch (ctype->type) {
+ case SNDRV_CTL_ELEM_TYPE_BOOLEAN:
+ ctl_val->value.integer.value[i] = 0;
+ break;
+ case SNDRV_CTL_ELEM_TYPE_INTEGER:
+ ctl_val->value.integer.value[i] = snd_audient_int_min(ctype);
+ break;
+ default: /* SNDRV_CTL_ELEM_TYPE_ENUMERATED */
+ ctl_val->value.enumerated.item[i] = 0;
+ break;
+ }
+}
- for (i = 0; i < hwchan; i++) {
- long val = ctl_val->value.integer.value[i];
+/*
+ * Convert the control value of hardware channel i to a device value. A
+ * value out of range returns -EINVAL; a boolean has no range error.
+ */
+static int snd_audient_value_from_ctl(const struct snd_audient_ctrl_type *ctype,
+ const struct snd_ctl_elem_value *ctl_val,
+ unsigned int i, u32 *raw)
+{
+ unsigned int item;
+ long val;
+ switch (ctype->type) {
+ case SNDRV_CTL_ELEM_TYPE_BOOLEAN:
+ *raw = ctl_val->value.integer.value[i] ? ctype->max.integer
+ : ctype->min.integer;
+ break;
+ case SNDRV_CTL_ELEM_TYPE_INTEGER:
+ val = ctl_val->value.integer.value[i];
if (val < snd_audient_int_min(ctype) ||
val > snd_audient_int_max(ctype))
return -EINVAL;
- }
- CLASS(snd_usb_lock, pm)(mixer->chip);
- if (pm.err < 0)
- return pm.err;
- /* serializes the transfers and the cache, also between gets */
- guard(mutex)(&mixer->lock);
-
- for (i = 0; i < hwchan; i++) {
- s32 val = snd_audient_int_from_ctl(ctype,
- ctl_val->value.integer.value[i]);
- int ret;
-
- ret = snd_audient_raw_set_one(elem, ctype, i, base + i,
- val);
- if (ret < 0)
- return ret;
+ *raw = (s32)snd_audient_int_from_ctl(ctype, val);
+ break;
+ default: /* SNDRV_CTL_ELEM_TYPE_ENUMERATED */
+ item = ctl_val->value.enumerated.item[i];
+ if (item >= ctype->enumeration->items)
+ return -EINVAL;
- if (ret)
- changed = 1;
+ *raw = ctype->enumeration->values[item];
+ break;
}
- return changed;
-}
-
-/* The item index of a device value, or -EIO if the value is not in the list */
-static int snd_audient_enum_item(const struct snd_audient_enum *e, u32 value)
-{
- unsigned int item;
-
- for (item = 0; item < e->items; item++)
- if (e->values[item] == value)
- return item;
-
- return -EIO;
+ return 0;
}
-static int snd_ctl_audient_enum_get(struct snd_kcontrol *kctl,
- struct snd_ctl_elem_value *ctl_val)
+static int snd_ctl_audient_get(struct snd_kcontrol *kctl,
+ struct snd_ctl_elem_value *ctl_val)
{
struct usb_mixer_elem_info *const elem = snd_kcontrol_chip(kctl);
struct usb_mixer_interface *const mixer = elem->head.mixer;
@@ -938,32 +858,28 @@ static int snd_ctl_audient_enum_get(struct snd_kcontrol *kctl,
guard(mutex)(&mixer->lock);
for (i = 0; i < hwchan; i++) {
- u32 val;
- int item;
+ u32 raw;
int ret;
- ret = snd_audient_raw_get_one(elem, ctype, i, base + i,
- &val);
+ ret = snd_audient_raw_get_one(elem, ctype, i, base + i, &raw);
if (ret == -ENODATA) /* unknown write-only value: init */
- val = ctype->init;
+ raw = ctype->init;
else if (ret < 0)
return ret;
- item = snd_audient_enum_item(ctype->enumeration, val);
- if (item < 0)
- return item;
-
- ctl_val->value.enumerated.item[i] = item;
+ ret = snd_audient_value_to_ctl(ctype, ctl_val, i, raw);
+ if (ret < 0)
+ return ret;
}
for (; i < nchan; i++)
- ctl_val->value.enumerated.item[i] = 0;
+ snd_audient_empty_ctl(ctype, ctl_val, i);
return 0;
}
-static int snd_ctl_audient_enum_set(struct snd_kcontrol *kctl,
- struct snd_ctl_elem_value *ctl_val)
+static int snd_ctl_audient_put(struct snd_kcontrol *kctl,
+ struct snd_ctl_elem_value *ctl_val)
{
struct usb_mixer_elem_info *const elem = snd_kcontrol_chip(kctl);
struct usb_mixer_interface *const mixer = elem->head.mixer;
@@ -971,15 +887,19 @@ static int snd_ctl_audient_enum_set(struct snd_kcontrol *kctl,
const unsigned int hwchan = snd_audient_kctl_hwchannels(kctl);
const struct snd_audient_ctrl_type *const ctype
= snd_audient_kctl_type(kctl);
- const struct snd_audient_enum *const e = ctype->enumeration;
const u8 base = snd_audient_kctl_cidxoffs(kctl);
unsigned int i;
int changed = 0;
- for (i = 0; i < hwchan; i++)
- if (ctl_val->value.enumerated.item[i] >= e->items)
- return -EINVAL;
+ for (i = 0; i < hwchan; i++) {
+ u32 raw;
+ int ret;
+
+ ret = snd_audient_value_from_ctl(ctype, ctl_val, i, &raw);
+ if (ret < 0)
+ return ret;
+ }
CLASS(snd_usb_lock, pm)(mixer->chip);
if (pm.err < 0)
@@ -988,11 +908,14 @@ static int snd_ctl_audient_enum_set(struct snd_kcontrol *kctl,
guard(mutex)(&mixer->lock);
for (i = 0; i < hwchan; i++) {
- const unsigned int item = ctl_val->value.enumerated.item[i];
+ u32 raw;
int ret;
- ret = snd_audient_raw_set_one(elem, ctype, i, base + i,
- e->values[item]);
+ ret = snd_audient_value_from_ctl(ctype, ctl_val, i, &raw);
+ if (ret < 0)
+ return ret;
+
+ ret = snd_audient_raw_set_one(elem, ctype, i, base + i, raw);
if (ret < 0)
return ret;
@@ -1057,21 +980,16 @@ static int snd_audient_add_ctrl(struct usb_mixer_interface *mixer,
switch (ctype->type) {
case SNDRV_CTL_ELEM_TYPE_INTEGER:
- knew.get = snd_ctl_audient_integer_get;
- knew.put = snd_ctl_audient_integer_set;
- break;
case SNDRV_CTL_ELEM_TYPE_BOOLEAN:
- knew.get = snd_ctl_audient_boolean_get;
- knew.put = snd_ctl_audient_boolean_set;
- break;
case SNDRV_CTL_ELEM_TYPE_ENUMERATED:
- knew.get = snd_ctl_audient_enum_get;
- knew.put = snd_ctl_audient_enum_set;
break;
default:
return -EINVAL;
}
+ knew.get = snd_ctl_audient_get;
+ knew.put = snd_ctl_audient_put;
+
if (ctrl->hwchannels > MAX_CHANNELS)
return -EINVAL;
--
2.47.3
^ permalink raw reply [flat|nested] 31+ messages in thread* [RFC PATCH 6/6] ALSA: mixer_audient: Use the same cache for the iD14 MkI FU 12 control
2026-11-12 2:37 ` [RFC PATCH 0/6] ALSA: mixer_audient: Cache rework and simplifications Will Porter
` (4 preceding siblings ...)
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 ` Will Porter
2026-11-15 21:43 ` [RFC V4 PATCH 00/19] Re: ALSA: mixer_audient: Cache rework and simplifications Christian Ruppert
` (19 subsequent siblings)
25 siblings, 0 replies; 31+ messages in thread
From: Will Porter @ 2026-11-12 2:37 UTC (permalink / raw)
To: Christian Ruppert
Cc: Takashi Iwai, Jaroslav Kysela, linux-sound, linux-kernel, Neil Andrews
The FU 12 control keeps its own value array and valid mask on a
struct usb_mixer_elem_list. Make it a struct usb_mixer_elem_info, and
use cache_val[ch] and bit ch of cached as the other controls do. Set
channels and get_cur_broken. Remove the private struct.
The list head is the first member of struct usb_mixer_elem_info, so
snd_usb_mixer_elem_free() still frees the right pointer. The control
is still registered with is_std_info false, so the generic notify
paths do not touch it.
No functional change.
Assisted-by: Claude:claude-fable-5-2
Assisted-by: Claude:claude-sonnet-5-5
Signed-off-by: Will Porter <mrwillporter@gmail.com>
---
sound/usb/mixer_audient.c | 54 ++++++++++++++++++++-------------------
1 file changed, 28 insertions(+), 26 deletions(-)
diff --git a/sound/usb/mixer_audient.c b/sound/usb/mixer_audient.c
index edc568830366c..8c81a4c05995f 100644
--- a/sound/usb/mixer_audient.c
+++ b/sound/usb/mixer_audient.c
@@ -1074,26 +1074,26 @@ static const struct snd_audient_ctrl_type snd_audient_id14_fu12_type = {
.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,
+/*
+ * The control is a struct usb_mixer_elem_info whose list head is registered
+ * with is_std_info false. The head is the first member, as
+ * snd_usb_mixer_elem_free needs. cache_val[ch] and bit ch of cached hold
+ * the last value the device accepted.
+ */
+static int snd_audient_id14_fu12_write(struct usb_mixer_elem_info *elem,
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,
+ ret = snd_audient_set_cur(elem->head.mixer, &snd_audient_id14_fu12_type,
ch + 1, &buf);
if (ret < 0)
return ret;
- f->val[ch] = val;
- f->valid |= BIT(ch);
+ elem->cache_val[ch] = val;
+ set_bit(ch, elem->cached);
return 0;
}
@@ -1112,13 +1112,13 @@ static int snd_ctl_audient_id14_fu12_info(struct snd_kcontrol *kctl,
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);
+ struct usb_mixer_elem_info *elem = 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]
+ ctl_val->value.integer.value[ch] = test_bit(ch, elem->cached)
+ ? elem->cache_val[ch]
: ID14_FU12_MUTE;
return 0;
@@ -1127,7 +1127,7 @@ static int snd_ctl_audient_id14_fu12_get(struct snd_kcontrol *kctl,
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);
+ struct usb_mixer_elem_info *elem = snd_kcontrol_chip(kctl);
long *const v = ctl_val->value.integer.value;
unsigned int ch;
int changed = 0;
@@ -1136,7 +1136,7 @@ static int snd_ctl_audient_id14_fu12_set(struct snd_kcontrol *kctl,
if (v[ch] < 0 || v[ch] > ID14_FU12_MAX)
return -EINVAL;
- CLASS(snd_usb_lock, pm)(f->list.mixer->chip);
+ CLASS(snd_usb_lock, pm)(elem->head.mixer->chip);
if (pm.err < 0)
return pm.err;
@@ -1147,10 +1147,10 @@ static int snd_ctl_audient_id14_fu12_set(struct snd_kcontrol *kctl,
for (ch = 0; ch < ID14_FU12_CHANNELS; ch++) {
int ret;
- if (!(f->valid & BIT(ch)) || f->val[ch] != v[ch])
+ if (!test_bit(ch, elem->cached) || elem->cache_val[ch] != v[ch])
changed = 1;
- ret = snd_audient_id14_fu12_write(f, ch, v[ch]);
+ ret = snd_audient_id14_fu12_write(elem, ch, v[ch]);
if (ret < 0)
return ret;
}
@@ -1171,19 +1171,21 @@ static int snd_audient_id14_mk1_create(struct usb_mixer_interface *mixer)
.put = snd_ctl_audient_id14_fu12_set,
.tlv.p = snd_audient_tlv_id14_fu12,
};
- struct snd_audient_id14_fu12 *f;
+ struct usb_mixer_elem_info *elem;
struct snd_kcontrol *kctl;
unsigned int ch;
- f = kzalloc_obj(*f);
- if (!f)
+ elem = kzalloc_obj(*elem);
+ if (!elem)
return -ENOMEM;
- f->list.mixer = mixer;
- f->list.id = ID14_FU_MIX;
+ elem->head.mixer = mixer;
+ elem->head.id = ID14_FU_MIX;
+ elem->channels = ID14_FU12_CHANNELS;
+ elem->get_cur_broken = 1;
for (ch = 0; ch < ID14_FU12_CHANNELS; ch++) {
- const int ret = snd_audient_id14_fu12_write(f, ch,
+ const int ret = snd_audient_id14_fu12_write(elem, ch,
ch < ID14_FU12_MAIN_CHANNELS ? ID14_FU12_MAX
: ID14_FU12_MUTE);
@@ -1193,15 +1195,15 @@ static int snd_audient_id14_mk1_create(struct usb_mixer_interface *mixer)
ch + 1, ret);
}
- kctl = snd_ctl_new1(&knew, f);
+ kctl = snd_ctl_new1(&knew, elem);
if (!kctl) {
- kfree(f);
+ kfree(elem);
return -ENOMEM;
}
kctl->private_free = snd_usb_mixer_elem_free;
- return snd_usb_mixer_add_list(&f->list, kctl, false, true);
+ return snd_usb_mixer_add_list(&elem->head, kctl, false, true);
}
/* called from mixer_quirks.c */
--
2.47.3
^ permalink raw reply [flat|nested] 31+ messages in thread* [RFC V4 PATCH 00/19] Re: ALSA: mixer_audient: Cache rework and simplifications
2026-11-12 2:37 ` [RFC PATCH 0/6] ALSA: mixer_audient: Cache rework and simplifications Will Porter
` (5 preceding siblings ...)
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 ` Christian Ruppert
2026-11-15 21:43 ` [RFC V4 PATCH 01/19] ALSA: rename mixer_evo to mixer_audient Christian Ruppert
` (18 subsequent siblings)
25 siblings, 0 replies; 31+ messages in thread
From: Christian Ruppert @ 2026-11-15 21:43 UTC (permalink / raw)
To: Will Porter
Cc: Takashi Iwai, Jaroslav Kysela, linux-sound, linux-kernel,
Neil Andrews, Christian Ruppert
Hi Will,
On Wed, Nov 11, 2026 at 08:37:14PM -0600, Will Porter wrote:
> On Tue, Nov 10, 2026 at 09:57:40PM +0100, Christian Ruppert wrote:
> > I realized that usb_mixer_elem_info already contains an integer array for
> > caching we could use instead of our manually allocated cache. I suspect we
> > should rework patch 10 accordingly.
>
> I like the idea. When I read the code for it, I saw a few more places
> where the driver could be simpler. It seemed easier to show them as
> patches than to describe them, so here they are for you to try on the
> EVO4. Please take what you like and drop the rest.
OK, so here's RFC V4 with the cache changes and a few other
improvements inspired by your suggestions. Also contains your follow-up
patch weaved into 13 and 15. See below for details.
Compiles on Alsa master and compiled and tested on 7.3.4.
> > I'll prepare a new patch 10 but won't be able to test before the week-end.
>
> Patches 3 to 6 need the cache change as a base, so patch 2 is my
> version of it, only so that the rest can be tested. Please replace it
> with yours. Two questions came up when I wrote it:
>
> - The notify paths clear info->cached unless get_cur_broken is set. A
> write-only control could not read its value back after that. Patch 2
> sets get_cur_broken for the write-only controls. Is that a correct
> use of the field?
I was wondering about the same but originally didn't plan to set it as
it doesn't make much sense without the control and head.id fields. To
settle the question once and for all, I decided to add all three to
patch 10 after all.
> - The interrupt path clears the bit of the channel number in the
> message, and channel 0 clears all bits. Patch 2 uses the channel
> index in the element as the bit. Would the channel number of the
> request fit your EVO4 events better?
I tried to stick to the hardware channel numbers as they are the ones
used in mixer.c as well. Please see patch 10.
> > Do you think it makes sense to refactor FU12 caching as well?
>
> Yes, I think so: the driver then has one cache. Patch 6 does it.
>
> The patches:
>
> 1. Not for merging. It has the changes from your review as one diff,
> so that patches 2 to 6 apply to the RFC with the enum fix.
OK, didn't touch those in the RFC, please squash them into the
respective patches.
> 2. Use cached and cache_val[] of usb_mixer_elem_info.
This is almost identical to what I had prepared, except for a few
parameter names and indices. Please double-check the new patch 10 to
make sure I didn't miss anything, I can't fully test it on the EVO.
There is also a subtle behavioural change when initializing write-only
cache values. Shouldn't have an impact as it only affects integer and
boolean types but let me know if something comes up in your tests.
> 3. Replace INIT_HW, FORCE_SET and CACHE with an access mode:
> read-back, cached or write-only. The tables use only these three
> combinations of the flags. Do you plan to use another combination
> on the EVO4, for example INIT_HW on a control that can be read? If
> so, I will drop this patch.
I don't like this, it violates the separation of mechanism from politics
which I hold dear. Also, see the newly added patch 19 for an example why
the flags are useful as they are (turns out ID12 is just such an INIT_HW
control that can be read).
> 4. Fill the cache on the first get or put, not at probe. The probe
> then sends 12 fewer GET requests on the EVO4 and 56 fewer on the
> iD14. Is there a reason for the EVO4 to read at probe?
This is a good idea, now (no longer) part of patch 10.
> 5. Use one get and one put for booleans, integers and enums. This
> changes the code path of the EVO4 controls, which I cannot test.
This is also a good idea, I added patch 18 for this.
> 6. Use the same cache for the FU 12 control.
I added patch 19 with another proposal I came up with when working on
patch 18. Removes all iD14/FU12 specific functions and shortens the code
by another 60ish lines. Please double-check if this makes any sense as I
might have badly misunderstood something. We could even get rid of the
FU12 specific value type if the iD14 accepts -127 * 256 as a volume
setting (the one which is currently skipped and replaced with MUTE). In
this case FU12 would become a normal integer control with a step size of
256.
We might also make some more code more generic if we replace the current
hwchannels with a mask of don't-touch channels. This is not yet included
in the patches because I first wanted to discuss it with you and we
don't have to submit this as part of the current patch set, can be
submitted as a follow-up simplification for a future kernel version.
With above two items, all FU12 specific branches should be gone.
> [...]
I suggest we now proceed as follows:
- Re-check patches 1 through 12, I don't believe they need to be changed
anymore unless if we discover a bug.
- Weave patch 17 into patches 13 through 16.
- Review patch 18 (I suggest to keep it separate as it is).
- Decide what you would like to do with patch 19 and the two related
comments after your point 6, either drop or modify patch 16
accordingly, possibly after rebasing it on top of patch 18.
- Submit the series for final review and testing and subsequent merging.
Do you think this would work?
Christian
Christian Ruppert (13):
ALSA: rename mixer_evo to mixer_audient
ALSA: mixer_audient: explicitly define full control names
ALSA: mixer_audient: clarify USB control message definition
ALSA: mixer_audient: Add length check to USB transfer
ALSA: mixer_audient: make USB control transfer size configurable
ALSA: mixer_audient: Allow arbitrary boolean control values
ALSA: mixer_audient: Add integer range checks
ALSA: mixer_audient: Add control initialization at probe
ALSA: mixer_audient: Add readback-free parameter set
ALSA: mixer_audient: add control value caching
ALSA: mixer_audient: Create matrix programmatically
ALSA: mixer_audient: Unify snd_audient_*_set/get functions for
different types
ALSA: mixer_audient: Refactor FU12 special cases into generic set/get
functions
Will Porter (6):
ALSA: mixer_audient: Add a step size to integer controls
ALSA: mixer_audient: Add enumerated controls
ALSA: usb-audio: Give the Audient iD14 MkI its own mixer map
ALSA: mixer_audient: Add Audient iD14 MkI controls
ALSA: mixer_audient: Replace the Audient iD14 MkI FU 12 control
ALSA: mixer_audient: Fold in the review changes to patches 12-16
MAINTAINERS | 7 +-
sound/usb/Makefile | 2 +-
sound/usb/mixer_audient.c | 1118 +++++++++++++++++++++++++++++++++++++
sound/usb/mixer_audient.h | 13 +
sound/usb/mixer_evo.c | 526 -----------------
sound/usb/mixer_evo.h | 12 -
sound/usb/mixer_maps.c | 10 +-
sound/usb/mixer_quirks.c | 5 +-
8 files changed, 1148 insertions(+), 545 deletions(-)
create mode 100644 sound/usb/mixer_audient.c
create mode 100644 sound/usb/mixer_audient.h
delete mode 100644 sound/usb/mixer_evo.c
delete mode 100644 sound/usb/mixer_evo.h
--
2.56.1
^ permalink raw reply [flat|nested] 31+ messages in thread* [RFC V4 PATCH 01/19] ALSA: rename mixer_evo to mixer_audient
2026-11-12 2:37 ` [RFC PATCH 0/6] ALSA: mixer_audient: Cache rework and simplifications Will Porter
` (6 preceding siblings ...)
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 ` Christian Ruppert
2026-11-15 21:43 ` [RFC V4 PATCH 02/19] ALSA: mixer_audient: explicitly define full control names Christian Ruppert
` (17 subsequent siblings)
25 siblings, 0 replies; 31+ messages in thread
From: Christian Ruppert @ 2026-11-15 21:43 UTC (permalink / raw)
To: Will Porter
Cc: Takashi Iwai, Jaroslav Kysela, linux-sound, linux-kernel,
Neil Andrews, Christian Ruppert
Rename mixer_evo files and functions to *_audient in preparation of a more
generic driver for audient devices.
Signed-off-by: Christian Ruppert <arc@gmx.li>
Signed-off-by: Will Porter <mrwillporter@gmail.com>
---
MAINTAINERS | 7 +-
sound/usb/Makefile | 2 +-
sound/usb/{mixer_evo.c => mixer_audient.c} | 249 +++++++++++----------
sound/usb/mixer_audient.h | 12 +
sound/usb/mixer_evo.h | 12 -
sound/usb/mixer_quirks.c | 4 +-
6 files changed, 148 insertions(+), 138 deletions(-)
rename sound/usb/{mixer_evo.c => mixer_audient.c} (54%)
create mode 100644 sound/usb/mixer_audient.h
delete mode 100644 sound/usb/mixer_evo.h
diff --git a/MAINTAINERS b/MAINTAINERS
index 222b7fc2c3aa..94a594e17498 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -4448,12 +4448,13 @@ F: drivers/net/ieee802154/at86rf230.h
F: drivers/net/ieee802154/atusb.c
F: drivers/net/ieee802154/atusb.h
-AUDIENT EVO MIXER DRIVER
+AUDIENT MIXER DRIVER
M: Christian Ruppert <arc@gmx.li>
+M: Will Porter <mrwillporter@gmail.com>
L: linux-sound@vger.kernel.org
S: Maintained
-F: sound/usb/mixer_evo.c
-F: sound/usb/mixer_evo.h
+F: sound/usb/mixer_audient.c
+F: sound/usb/mixer_audient.h
AUDIT SUBSYSTEM
M: Paul Moore <paul@paul-moore.com>
diff --git a/sound/usb/Makefile b/sound/usb/Makefile
index 2e842dcc73de..44f4e4c820fb 100644
--- a/sound/usb/Makefile
+++ b/sound/usb/Makefile
@@ -11,7 +11,7 @@ snd-usb-audio-y := card.o \
helper.o \
implicit.o \
mixer.o \
- mixer_evo.o \
+ mixer_audient.o \
mixer_quirks.o \
mixer_scarlett.o \
mixer_scarlett2.o \
diff --git a/sound/usb/mixer_evo.c b/sound/usb/mixer_audient.c
similarity index 54%
rename from sound/usb/mixer_evo.c
rename to sound/usb/mixer_audient.c
index c1e0332232f2..e2700b08cf80 100644
--- a/sound/usb/mixer_evo.c
+++ b/sound/usb/mixer_audient.c
@@ -1,9 +1,9 @@
// SPDX-License-Identifier: GPL-2.0
/*
- * Audient EVO driver for ALSA
+ * Audient driver for ALSA
* Copyright (C) 2026 Christian Ruppert <arc@gmx.li>
*
- * Based on the work of Ivan Hazucha for audient-evo-py.
+ * EVO4 support is based on the work of Ivan Hazucha for audient-evo-py.
* https://github.com/vanzaho/audient-evo-py/
*/
@@ -20,22 +20,22 @@
#include "mixer.h"
#include "mixer_quirks.h"
#include "helper.h"
-#include "mixer_evo.h"
+#include "mixer_audient.h"
-enum snd_evo_type {
- SND_EVO_TYPE_MONITOR = 0,
- SND_EVO_TYPE_PHANTOM,
- SND_EVO_TYPE_MUTE,
- SND_EVO_TYPE_MUTE_OUT,
- SND_EVO_TYPE_MIXER,
- SND_EVO_TYPE_NCTYPES
+enum snd_audient_type {
+ SND_AUDIENT_TYPE_MONITOR = 0,
+ SND_AUDIENT_TYPE_PHANTOM,
+ SND_AUDIENT_TYPE_MUTE,
+ SND_AUDIENT_TYPE_MUTE_OUT,
+ SND_AUDIENT_TYPE_MIXER,
+ SND_AUDIENT_TYPE_NCTYPES
};
/* Mixer matrix: -128dB (mute) to +6dB */
-static const DECLARE_TLV_DB_MINMAX_MUTE(snd_evo_tlv_mixer, -12800, 600);
+static const DECLARE_TLV_DB_MINMAX_MUTE(snd_audient_tlv_mixer, -12800, 600);
-static const struct snd_evo_ctrl_type {
+static const struct snd_audient_ctrl_type {
snd_ctl_elem_type_t type;
bool invert;
union {
@@ -47,8 +47,8 @@ static const struct snd_evo_ctrl_type {
const unsigned int *tlv;
u16 wValue;
u16 wIndex;
-} snd_evo_ctypes[SND_EVO_TYPE_NCTYPES] = {
- [SND_EVO_TYPE_MONITOR] = {
+} snd_audient_ctypes[SND_AUDIENT_TYPE_NCTYPES] = {
+ [SND_AUDIENT_TYPE_MONITOR] = {
.type = SNDRV_CTL_ELEM_TYPE_INTEGER,
.min.integer = 0,
.max.integer = 127,
@@ -56,7 +56,7 @@ static const struct snd_evo_ctrl_type {
.wIndex = 0x3800,
/* no TLV here because we can't describe the cross-fader */
},
- [SND_EVO_TYPE_PHANTOM] = {
+ [SND_AUDIENT_TYPE_PHANTOM] = {
.type = SNDRV_CTL_ELEM_TYPE_BOOLEAN,
.invert = false,
.min.integer = 0,
@@ -64,7 +64,7 @@ static const struct snd_evo_ctrl_type {
.wValue = 0x0000,
.wIndex = 0x3A00,
},
- [SND_EVO_TYPE_MUTE] = {
+ [SND_AUDIENT_TYPE_MUTE] = {
.type = SNDRV_CTL_ELEM_TYPE_BOOLEAN,
.invert = true,
.min.integer = 0,
@@ -72,7 +72,7 @@ static const struct snd_evo_ctrl_type {
.wValue = 0x0200,
.wIndex = 0x3A00,
},
- [SND_EVO_TYPE_MUTE_OUT] = {
+ [SND_AUDIENT_TYPE_MUTE_OUT] = {
.type = SNDRV_CTL_ELEM_TYPE_BOOLEAN,
.invert = true,
.min.integer = 0,
@@ -80,135 +80,135 @@ static const struct snd_evo_ctrl_type {
.wValue = 0x0100,
.wIndex = 0x3B00,
},
- [SND_EVO_TYPE_MIXER] = {
+ [SND_AUDIENT_TYPE_MIXER] = {
.type = SNDRV_CTL_ELEM_TYPE_INTEGER,
.min.integer = -128 * 256,
.max.integer = 6 * 256,
.wValue = 0x0100,
.wIndex = 0x3C00,
- .tlv = snd_evo_tlv_mixer,
+ .tlv = snd_audient_tlv_mixer,
},
};
-#define SND_EVO_FLAGS_DIRECTION (1L<<0)
-#define SND_EVO_FLAGS_CAPTURE (1L<<0)
-#define SND_EVO_FLAGS_PLAYBACK (0L<<0)
+#define SND_AUDIENT_FLAGS_DIRECTION (1L<<0)
+#define SND_AUDIENT_FLAGS_CAPTURE (1L<<0)
+#define SND_AUDIENT_FLAGS_PLAYBACK (0L<<0)
-struct snd_evo_ctrl {
+struct snd_audient_ctrl {
char *basename;
- enum snd_evo_type type;
+ enum snd_audient_type type;
unsigned int channels;
unsigned int hwchannels;
unsigned int val_offs;
unsigned long flags;
};
-static const struct snd_evo_ctrl evo4_controls[] = {
+static const struct snd_audient_ctrl snd_audient_evo4_controls[] = {
{
.basename = "Master",
- .type = SND_EVO_TYPE_MUTE_OUT,
+ .type = SND_AUDIENT_TYPE_MUTE_OUT,
.channels = 1,
.hwchannels = 1,
.val_offs = 0,
- .flags = SND_EVO_FLAGS_PLAYBACK,
+ .flags = SND_AUDIENT_FLAGS_PLAYBACK,
},
{
.basename = "Mic",
- .type = SND_EVO_TYPE_MUTE,
+ .type = SND_AUDIENT_TYPE_MUTE,
.channels = 4,
.hwchannels = 2,
.val_offs = 0,
- .flags = SND_EVO_FLAGS_PLAYBACK,
+ .flags = SND_AUDIENT_FLAGS_PLAYBACK,
},
{
.basename = "Monitor",
- .type = SND_EVO_TYPE_MONITOR,
+ .type = SND_AUDIENT_TYPE_MONITOR,
.channels = 1,
.hwchannels = 1,
.val_offs = 0,
- .flags = SND_EVO_FLAGS_PLAYBACK,
+ .flags = SND_AUDIENT_FLAGS_PLAYBACK,
},
{
.basename = "Mic 1",
- .type = SND_EVO_TYPE_MIXER,
+ .type = SND_AUDIENT_TYPE_MIXER,
.channels = 2,
.hwchannels = 2,
.val_offs = 0,
- .flags = SND_EVO_FLAGS_CAPTURE,
+ .flags = SND_AUDIENT_FLAGS_CAPTURE,
},
{
.basename = "Mic 2",
- .type = SND_EVO_TYPE_MIXER,
+ .type = SND_AUDIENT_TYPE_MIXER,
.channels = 2,
.hwchannels = 2,
.val_offs = 2,
- .flags = SND_EVO_FLAGS_CAPTURE,
+ .flags = SND_AUDIENT_FLAGS_CAPTURE,
},
{
.basename = "Master Left",
- .type = SND_EVO_TYPE_MIXER,
+ .type = SND_AUDIENT_TYPE_MIXER,
.channels = 2,
.hwchannels = 2,
.val_offs = 4,
- .flags = SND_EVO_FLAGS_CAPTURE,
+ .flags = SND_AUDIENT_FLAGS_CAPTURE,
},
{
.basename = "Master Right",
- .type = SND_EVO_TYPE_MIXER,
+ .type = SND_AUDIENT_TYPE_MIXER,
.channels = 2,
.hwchannels = 2,
.val_offs = 6,
- .flags = SND_EVO_FLAGS_CAPTURE,
+ .flags = SND_AUDIENT_FLAGS_CAPTURE,
},
{
.basename = "Loopback Left",
- .type = SND_EVO_TYPE_MIXER,
+ .type = SND_AUDIENT_TYPE_MIXER,
.channels = 2,
.hwchannels = 2,
.val_offs = 8,
- .flags = SND_EVO_FLAGS_CAPTURE,
+ .flags = SND_AUDIENT_FLAGS_CAPTURE,
},
{
.basename = "Loopback Right",
- .type = SND_EVO_TYPE_MIXER,
+ .type = SND_AUDIENT_TYPE_MIXER,
.channels = 2,
.hwchannels = 2,
.val_offs = 10,
- .flags = SND_EVO_FLAGS_CAPTURE,
+ .flags = SND_AUDIENT_FLAGS_CAPTURE,
},
{
.basename = "Mic 1 Phantom",
- .type = SND_EVO_TYPE_PHANTOM,
+ .type = SND_AUDIENT_TYPE_PHANTOM,
.channels = 1,
.hwchannels = 1,
.val_offs = 0,
- .flags = SND_EVO_FLAGS_CAPTURE,
+ .flags = SND_AUDIENT_FLAGS_CAPTURE,
},
{
.basename = "Mic 2 Phantom",
- .type = SND_EVO_TYPE_PHANTOM,
+ .type = SND_AUDIENT_TYPE_PHANTOM,
.channels = 1,
.hwchannels = 1,
.val_offs = 1,
- .flags = SND_EVO_FLAGS_CAPTURE,
+ .flags = SND_AUDIENT_FLAGS_CAPTURE,
},
{ 0 }, /* sentinel */
};
-static const struct evo_devinfo {
+static const struct audient_devinfo {
u32 usb_id;
- const struct snd_evo_ctrl *controls;
-} evo_devinfo[] = {
+ const struct snd_audient_ctrl *controls;
+} audient_devinfo[] = {
{
.usb_id = USB_ID(USB_AUDIENT_VID, USB_EVO4_PID),
- .controls = evo4_controls,
+ .controls = snd_audient_evo4_controls,
},
{ 0 } /* sentinel */
};
-static inline unsigned long snd_evo_kctl_priv(enum snd_evo_type type,
- u8 valoffs, u8 hwchannels)
+static inline unsigned long snd_audient_kctl_priv(enum snd_audient_type type,
+ u8 valoffs, u8 hwchannels)
{
unsigned long type_l = type & 0xFF;
unsigned long valoffs_l = valoffs & 0xFF;
@@ -218,22 +218,24 @@ static inline unsigned long snd_evo_kctl_priv(enum snd_evo_type type,
}
static __always_inline
-const struct snd_evo_ctrl_type *snd_evo_kctl_type(struct snd_kcontrol *kctl)
+const struct snd_audient_ctrl_type *snd_audient_kctl_type(struct snd_kcontrol
+ *kctl)
{
- return &snd_evo_ctypes[kctl->private_value & 0xFF];
+ return &snd_audient_ctypes[kctl->private_value & 0xFF];
}
-static __always_inline u16 snd_evo_kctl_valoffs(struct snd_kcontrol *kctl)
+static __always_inline u16 snd_audient_kctl_valoffs(struct snd_kcontrol *kctl)
{
return (kctl->private_value >> 8) & 0xFF;
}
-static __always_inline u16 snd_evo_kctl_hwchannels(struct snd_kcontrol *kctl)
+static __always_inline
+u16 snd_audient_kctl_hwchannels(struct snd_kcontrol *kctl)
{
return (kctl->private_value >> 16) & 0xFF;
}
-static inline u16 snd_evo_kctl_channels(struct snd_kcontrol *kctl)
+static inline u16 snd_audient_kctl_channels(struct snd_kcontrol *kctl)
{
struct usb_mixer_elem_info *info = kctl->private_data;
@@ -241,43 +243,45 @@ static inline u16 snd_evo_kctl_channels(struct snd_kcontrol *kctl)
}
-#define EVO_REQ_CUR (0x01)
-#define EVO_REQTYPE_SET (USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE)
-#define EVO_REQTYPE_GET (USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE)
+#define AUDIENT_REQTYPE_SET (USB_DIR_OUT | USB_TYPE_CLASS \
+ | USB_RECIP_INTERFACE)
+#define AUDIENT_REQTYPE_GET (USB_DIR_IN | USB_TYPE_CLASS \
+ | USB_RECIP_INTERFACE)
-static inline int snd_evo_get_cur(struct usb_mixer_interface *mixer,
- u16 value, u16 index,
- void *buf, size_t len)
+static inline int snd_audient_get_cur(struct usb_mixer_interface *mixer,
+ u16 value, u16 index,
+ void *buf, size_t len)
{
struct usb_device *const dev = mixer->chip->dev;
return snd_usb_ctl_msg(dev, usb_rcvctrlpipe(dev, 0),
- EVO_REQ_CUR, EVO_REQTYPE_GET,
+ UAC2_CS_CUR, AUDIENT_REQTYPE_GET,
value, index, buf, len);
}
-static inline int snd_evo_set_cur(struct usb_mixer_interface *mixer,
- u16 value, u16 index,
- void *buf, size_t len)
+static inline int snd_audient_set_cur(struct usb_mixer_interface *mixer,
+ u16 value, u16 index,
+ void *buf, size_t len)
{
struct usb_device *const dev = mixer->chip->dev;
return snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0),
- EVO_REQ_CUR, EVO_REQTYPE_SET,
+ UAC2_CS_CUR, AUDIENT_REQTYPE_SET,
value, index, buf, len);
}
-static int snd_ctl_evo_boolean_get(struct snd_kcontrol *kctl,
- struct snd_ctl_elem_value *ctl_val)
+static int snd_ctl_audient_boolean_get(struct snd_kcontrol *kctl,
+ struct snd_ctl_elem_value *ctl_val)
{
struct usb_mixer_elem_list *const list = snd_kcontrol_chip(kctl);
struct usb_mixer_interface *const mixer = list->mixer;
- const unsigned int nchan = snd_evo_kctl_channels(kctl);
- const unsigned int hwchan = snd_evo_kctl_hwchannels(kctl);
- const struct snd_evo_ctrl_type *const ctype = snd_evo_kctl_type(kctl);
- const u16 value = ctype->wValue + snd_evo_kctl_valoffs(kctl);
+ const unsigned int nchan = snd_audient_kctl_channels(kctl);
+ const unsigned int hwchan = snd_audient_kctl_hwchannels(kctl);
+ const struct snd_audient_ctrl_type *const ctype
+ = snd_audient_kctl_type(kctl);
+ const u16 value = ctype->wValue + snd_audient_kctl_valoffs(kctl);
const u16 index = ctype->wIndex;
int i;
@@ -290,8 +294,8 @@ static int snd_ctl_evo_boolean_get(struct snd_kcontrol *kctl,
u32 buf;
int ret;
- ret = snd_evo_get_cur(mixer, value + i, index,
- &buf, sizeof(buf));
+ ret = snd_audient_get_cur(mixer, value + i, index,
+ &buf, sizeof(buf));
if (ret < 0)
return ret;
@@ -309,15 +313,16 @@ static int snd_ctl_evo_boolean_get(struct snd_kcontrol *kctl,
return 0;
}
-static int snd_ctl_evo_boolean_set(struct snd_kcontrol *kctl,
- struct snd_ctl_elem_value *ctl_val)
+static int snd_ctl_audient_boolean_set(struct snd_kcontrol *kctl,
+ struct snd_ctl_elem_value *ctl_val)
{
struct usb_mixer_elem_list *list = snd_kcontrol_chip(kctl);
struct usb_mixer_interface *mixer = list->mixer;
- const unsigned int hwchan = snd_evo_kctl_hwchannels(kctl);
- const struct snd_evo_ctrl_type *const ctype = snd_evo_kctl_type(kctl);
- const u16 value = ctype->wValue + snd_evo_kctl_valoffs(kctl);
+ const unsigned int hwchan = snd_audient_kctl_hwchannels(kctl);
+ const struct snd_audient_ctrl_type *const ctype
+ = snd_audient_kctl_type(kctl);
+ const u16 value = ctype->wValue + snd_audient_kctl_valoffs(kctl);
const u16 index = ctype->wIndex;
int changed = 0;
@@ -335,7 +340,7 @@ static int snd_ctl_evo_boolean_set(struct snd_kcontrol *kctl,
if (ctype->invert)
buf ^= cpu_to_le32(0x1);
- ret = snd_evo_get_cur(mixer, value + i, index,
+ ret = snd_audient_get_cur(mixer, value + i, index,
&rbuf, sizeof(rbuf));
if (ret < 0)
return ret;
@@ -343,8 +348,8 @@ static int snd_ctl_evo_boolean_set(struct snd_kcontrol *kctl,
if (rbuf != buf) {
changed = 1;
- ret = snd_evo_set_cur(mixer, value + i, index,
- &buf, sizeof(buf));
+ ret = snd_audient_set_cur(mixer, value + i, index,
+ &buf, sizeof(buf));
if (ret < 0)
return ret;
}
@@ -353,16 +358,17 @@ static int snd_ctl_evo_boolean_set(struct snd_kcontrol *kctl,
return changed;
}
-static int snd_ctl_evo_integer_get(struct snd_kcontrol *kctl,
+static int snd_ctl_audient_integer_get(struct snd_kcontrol *kctl,
struct snd_ctl_elem_value *ctl_val)
{
struct usb_mixer_elem_list *const list = snd_kcontrol_chip(kctl);
struct usb_mixer_interface *const mixer = list->mixer;
- const unsigned int nchan = snd_evo_kctl_channels(kctl);
- const unsigned int hwchan = snd_evo_kctl_hwchannels(kctl);
- const struct snd_evo_ctrl_type *const ctype = snd_evo_kctl_type(kctl);
- const u16 value = ctype->wValue + snd_evo_kctl_valoffs(kctl);
+ const unsigned int nchan = snd_audient_kctl_channels(kctl);
+ const unsigned int hwchan = snd_audient_kctl_hwchannels(kctl);
+ const struct snd_audient_ctrl_type *const ctype
+ = snd_audient_kctl_type(kctl);
+ const u16 value = ctype->wValue + snd_audient_kctl_valoffs(kctl);
const u16 index = ctype->wIndex;
int i;
@@ -376,8 +382,8 @@ static int snd_ctl_evo_integer_get(struct snd_kcontrol *kctl,
s16 val;
int ret;
- ret = snd_evo_get_cur(mixer, value + i, index,
- &buf, sizeof(buf));
+ ret = snd_audient_get_cur(mixer, value + i, index,
+ &buf, sizeof(buf));
if (ret < 0)
return ret;
@@ -391,15 +397,16 @@ static int snd_ctl_evo_integer_get(struct snd_kcontrol *kctl,
return 0;
}
-static int snd_ctl_evo_integer_set(struct snd_kcontrol *kctl,
+static int snd_ctl_audient_integer_set(struct snd_kcontrol *kctl,
struct snd_ctl_elem_value *ctl_val)
{
struct usb_mixer_elem_list *list = snd_kcontrol_chip(kctl);
struct usb_mixer_interface *mixer = list->mixer;
- const unsigned int hwchan = snd_evo_kctl_hwchannels(kctl);
- const struct snd_evo_ctrl_type *const ctype = snd_evo_kctl_type(kctl);
- const u16 value = ctype->wValue + snd_evo_kctl_valoffs(kctl);
+ const unsigned int hwchan = snd_audient_kctl_hwchannels(kctl);
+ const struct snd_audient_ctrl_type *const ctype
+ = snd_audient_kctl_type(kctl);
+ const u16 value = ctype->wValue + snd_audient_kctl_valoffs(kctl);
const u16 index = ctype->wIndex;
int changed = 0;
@@ -414,16 +421,16 @@ static int snd_ctl_evo_integer_set(struct snd_kcontrol *kctl,
s16 buf = cpu_to_le16(ctl_val->value.integer.value[i]);
int ret;
- ret = snd_evo_get_cur(mixer, value + i, index,
- &rbuf, sizeof(rbuf));
+ ret = snd_audient_get_cur(mixer, value + i, index,
+ &rbuf, sizeof(rbuf));
if (ret < 0)
return ret;
if (rbuf != buf) {
changed = 1;
- ret = snd_evo_set_cur(mixer, value + i, index,
- &buf, sizeof(buf));
+ ret = snd_audient_set_cur(mixer, value + i, index,
+ &buf, sizeof(buf));
if (ret < 0)
return ret;
}
@@ -432,23 +439,25 @@ static int snd_ctl_evo_integer_set(struct snd_kcontrol *kctl,
return changed;
}
-static int snd_ctl_evo_info(struct snd_kcontrol *kctl,
+static int snd_ctl_audient_info(struct snd_kcontrol *kctl,
struct snd_ctl_elem_info *uinfo)
{
- const struct snd_evo_ctrl_type *const ctype = snd_evo_kctl_type(kctl);
+ const struct snd_audient_ctrl_type *const ctype
+ = snd_audient_kctl_type(kctl);
uinfo->type = ctype->type;
- uinfo->count = snd_evo_kctl_channels(kctl);
+ uinfo->count = snd_audient_kctl_channels(kctl);
uinfo->value.integer.min = ctype->min.integer;
uinfo->value.integer.max = ctype->max.integer;
return 0;
}
-static int snd_evo_add_ctrl(struct usb_mixer_interface *mixer,
- const struct snd_evo_ctrl *ctrl)
+static int snd_audient_add_ctrl(struct usb_mixer_interface *mixer,
+ const struct snd_audient_ctrl *ctrl)
{
- const struct snd_evo_ctrl_type *ctype = &snd_evo_ctypes[ctrl->type];
+ const struct snd_audient_ctrl_type *ctype
+ = &snd_audient_ctypes[ctrl->type];
char name[SNDRV_CTL_ELEM_ID_NAME_MAXLEN];
struct snd_kcontrol_new knew = { 0 };
struct snd_kcontrol *kctl;
@@ -456,9 +465,9 @@ static int snd_evo_add_ctrl(struct usb_mixer_interface *mixer,
knew.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
knew.name = name;
- knew.info = snd_ctl_evo_info;
- knew.private_value = snd_evo_kctl_priv(ctrl->type, ctrl->val_offs,
- ctrl->hwchannels);
+ knew.info = snd_ctl_audient_info;
+ knew.private_value = snd_audient_kctl_priv(ctrl->type, ctrl->val_offs,
+ ctrl->hwchannels);
knew.tlv.p = ctype->tlv;
knew.access = SNDRV_CTL_ELEM_ACCESS_READWRITE
| SNDRV_CTL_ELEM_ACCESS_VOLATILE
@@ -466,20 +475,20 @@ static int snd_evo_add_ctrl(struct usb_mixer_interface *mixer,
switch (ctype->type) {
case SNDRV_CTL_ELEM_TYPE_INTEGER:
- knew.get = snd_ctl_evo_integer_get;
- knew.put = snd_ctl_evo_integer_set;
+ knew.get = snd_ctl_audient_integer_get;
+ knew.put = snd_ctl_audient_integer_set;
break;
case SNDRV_CTL_ELEM_TYPE_BOOLEAN:
- knew.get = snd_ctl_evo_boolean_get;
- knew.put = snd_ctl_evo_boolean_set;
+ knew.get = snd_ctl_audient_boolean_get;
+ knew.put = snd_ctl_audient_boolean_set;
break;
default:
return -EINVAL;
}
snprintf(name, sizeof(name), "%s %s %s", ctrl->basename,
- ((ctrl->flags & SND_EVO_FLAGS_DIRECTION)
- == SND_EVO_FLAGS_CAPTURE) ? "Capture" : "Playback",
+ ((ctrl->flags & SND_AUDIENT_FLAGS_DIRECTION)
+ == SND_AUDIENT_FLAGS_CAPTURE) ? "Capture" : "Playback",
(ctype->type == SNDRV_CTL_ELEM_TYPE_BOOLEAN)
? "Switch" : "Volume");
@@ -503,12 +512,12 @@ static int snd_evo_add_ctrl(struct usb_mixer_interface *mixer,
/* called from mixer_quirks.c */
-int snd_evo_controls_create(struct usb_mixer_interface *mixer)
+int snd_audient_controls_create(struct usb_mixer_interface *mixer)
{
- const struct evo_devinfo *info;
- const struct snd_evo_ctrl *ctrl;
+ const struct audient_devinfo *info;
+ const struct snd_audient_ctrl *ctrl;
- for (info = evo_devinfo; info->controls; info++)
+ for (info = audient_devinfo; info->controls; info++)
if (info->usb_id == mixer->chip->usb_id)
break;
@@ -516,7 +525,7 @@ int snd_evo_controls_create(struct usb_mixer_interface *mixer)
return -ENODEV;
for (ctrl = info->controls; ctrl->basename; ctrl++) {
- const int ret = snd_evo_add_ctrl(mixer, ctrl);
+ const int ret = snd_audient_add_ctrl(mixer, ctrl);
if (ret < 0)
return ret;
diff --git a/sound/usb/mixer_audient.h b/sound/usb/mixer_audient.h
new file mode 100644
index 000000000000..db4f31e02b5e
--- /dev/null
+++ b/sound/usb/mixer_audient.h
@@ -0,0 +1,12 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __USB_MIXER_AUDIENT_H
+#define __USB_MIXER_AUDIENT_H
+
+#include "mixer.h"
+
+#define USB_AUDIENT_VID (0x2708)
+#define USB_EVO4_PID (0x0006)
+
+int snd_audient_controls_create(struct usb_mixer_interface *mixer);
+
+#endif /* __USB_MIXER_AUDIENT_H */
diff --git a/sound/usb/mixer_evo.h b/sound/usb/mixer_evo.h
deleted file mode 100644
index 7fe7e197185c..000000000000
--- a/sound/usb/mixer_evo.h
+++ /dev/null
@@ -1,12 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0 */
-#ifndef __USB_MIXER_EVO_H
-#define __USB_MIXER_EVO_H
-
-#include "mixer.h"
-
-#define USB_AUDIENT_VID (0x2708)
-#define USB_EVO4_PID (0x0006)
-
-int snd_evo_controls_create(struct usb_mixer_interface *mixer);
-
-#endif /* __USB_MIXER_EVO_H */
diff --git a/sound/usb/mixer_quirks.c b/sound/usb/mixer_quirks.c
index 4c7d68311f20..c250ebea5af5 100644
--- a/sound/usb/mixer_quirks.c
+++ b/sound/usb/mixer_quirks.c
@@ -39,7 +39,7 @@
#include "mixer_scarlett2.h"
#include "mixer_us16x08.h"
#include "mixer_s1810c.h"
-#include "mixer_evo.h"
+#include "mixer_audient.h"
#include "helper.h"
#include "fcp.h"
@@ -4781,7 +4781,7 @@ int snd_usb_mixer_apply_create_quirk(struct usb_mixer_interface *mixer)
break;
case USB_ID(USB_AUDIENT_VID, USB_EVO4_PID): /* Audient EVO 4 */
- err = snd_evo_controls_create(mixer);
+ err = snd_audient_controls_create(mixer);
break;
case USB_ID(0x041e, 0x323b): /* Creative Sound Blaster E1 */
--
2.56.1
^ permalink raw reply [flat|nested] 31+ messages in thread* [RFC V4 PATCH 02/19] ALSA: mixer_audient: explicitly define full control names
2026-11-12 2:37 ` [RFC PATCH 0/6] ALSA: mixer_audient: Cache rework and simplifications Will Porter
` (7 preceding siblings ...)
2026-11-15 21:43 ` [RFC V4 PATCH 01/19] ALSA: rename mixer_evo to mixer_audient Christian Ruppert
@ 2026-11-15 21:43 ` Christian Ruppert
2026-11-15 21:43 ` [RFC V4 PATCH 03/19] ALSA: mixer_audient: clarify USB control message definition Christian Ruppert
` (16 subsequent siblings)
25 siblings, 0 replies; 31+ messages in thread
From: Christian Ruppert @ 2026-11-15 21:43 UTC (permalink / raw)
To: Will Porter
Cc: Takashi Iwai, Jaroslav Kysela, linux-sound, linux-kernel,
Neil Andrews, Christian Ruppert
Previously, control names were dynamically created depending if the
control was an input or an output, a volume or a switch. Get rid of this
logic by simply defining the full name in the control structure.
Signed-off-by: Christian Ruppert <arc@gmx.li>
Signed-off-by: Will Porter <mrwillporter@gmail.com>
---
sound/usb/mixer_audient.c | 50 +++++++++++----------------------------
1 file changed, 14 insertions(+), 36 deletions(-)
diff --git a/sound/usb/mixer_audient.c b/sound/usb/mixer_audient.c
index e2700b08cf80..2afef52a12d9 100644
--- a/sound/usb/mixer_audient.c
+++ b/sound/usb/mixer_audient.c
@@ -90,107 +90,92 @@ static const struct snd_audient_ctrl_type {
},
};
-#define SND_AUDIENT_FLAGS_DIRECTION (1L<<0)
-#define SND_AUDIENT_FLAGS_CAPTURE (1L<<0)
-#define SND_AUDIENT_FLAGS_PLAYBACK (0L<<0)
struct snd_audient_ctrl {
- char *basename;
+ char *name;
enum snd_audient_type type;
unsigned int channels;
unsigned int hwchannels;
unsigned int val_offs;
- unsigned long flags;
};
static const struct snd_audient_ctrl snd_audient_evo4_controls[] = {
{
- .basename = "Master",
+ .name = "Master Playback Switch",
.type = SND_AUDIENT_TYPE_MUTE_OUT,
.channels = 1,
.hwchannels = 1,
.val_offs = 0,
- .flags = SND_AUDIENT_FLAGS_PLAYBACK,
},
{
- .basename = "Mic",
+ .name = "Mic Playback Switch",
.type = SND_AUDIENT_TYPE_MUTE,
.channels = 4,
.hwchannels = 2,
.val_offs = 0,
- .flags = SND_AUDIENT_FLAGS_PLAYBACK,
},
{
- .basename = "Monitor",
+ .name = "Monitor Playback Volume",
.type = SND_AUDIENT_TYPE_MONITOR,
.channels = 1,
.hwchannels = 1,
.val_offs = 0,
- .flags = SND_AUDIENT_FLAGS_PLAYBACK,
},
{
- .basename = "Mic 1",
+ .name = "Mic 1 Capture Volume",
.type = SND_AUDIENT_TYPE_MIXER,
.channels = 2,
.hwchannels = 2,
.val_offs = 0,
- .flags = SND_AUDIENT_FLAGS_CAPTURE,
},
{
- .basename = "Mic 2",
+ .name = "Mic 2 Capture Volume",
.type = SND_AUDIENT_TYPE_MIXER,
.channels = 2,
.hwchannels = 2,
.val_offs = 2,
- .flags = SND_AUDIENT_FLAGS_CAPTURE,
},
{
- .basename = "Master Left",
+ .name = "Master Left Capture Volume",
.type = SND_AUDIENT_TYPE_MIXER,
.channels = 2,
.hwchannels = 2,
.val_offs = 4,
- .flags = SND_AUDIENT_FLAGS_CAPTURE,
},
{
- .basename = "Master Right",
+ .name = "Master Right Capture Volume",
.type = SND_AUDIENT_TYPE_MIXER,
.channels = 2,
.hwchannels = 2,
.val_offs = 6,
- .flags = SND_AUDIENT_FLAGS_CAPTURE,
},
{
- .basename = "Loopback Left",
+ .name = "Loopback Left Capture Volume",
.type = SND_AUDIENT_TYPE_MIXER,
.channels = 2,
.hwchannels = 2,
.val_offs = 8,
- .flags = SND_AUDIENT_FLAGS_CAPTURE,
},
{
- .basename = "Loopback Right",
+ .name = "Loopback Right Capture Volume",
.type = SND_AUDIENT_TYPE_MIXER,
.channels = 2,
.hwchannels = 2,
.val_offs = 10,
- .flags = SND_AUDIENT_FLAGS_CAPTURE,
},
{
- .basename = "Mic 1 Phantom",
+ .name = "Mic 1 Phantom Capture Switch",
.type = SND_AUDIENT_TYPE_PHANTOM,
.channels = 1,
.hwchannels = 1,
.val_offs = 0,
- .flags = SND_AUDIENT_FLAGS_CAPTURE,
},
{
- .basename = "Mic 2 Phantom",
+ .name = "Mic 2 Phantom Capture Switch",
.type = SND_AUDIENT_TYPE_PHANTOM,
.channels = 1,
.hwchannels = 1,
.val_offs = 1,
- .flags = SND_AUDIENT_FLAGS_CAPTURE,
},
{ 0 }, /* sentinel */
};
@@ -458,13 +443,12 @@ static int snd_audient_add_ctrl(struct usb_mixer_interface *mixer,
{
const struct snd_audient_ctrl_type *ctype
= &snd_audient_ctypes[ctrl->type];
- char name[SNDRV_CTL_ELEM_ID_NAME_MAXLEN];
struct snd_kcontrol_new knew = { 0 };
struct snd_kcontrol *kctl;
struct usb_mixer_elem_info *elem;
knew.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
- knew.name = name;
+ knew.name = ctrl->name;
knew.info = snd_ctl_audient_info;
knew.private_value = snd_audient_kctl_priv(ctrl->type, ctrl->val_offs,
ctrl->hwchannels);
@@ -486,12 +470,6 @@ static int snd_audient_add_ctrl(struct usb_mixer_interface *mixer,
return -EINVAL;
}
- snprintf(name, sizeof(name), "%s %s %s", ctrl->basename,
- ((ctrl->flags & SND_AUDIENT_FLAGS_DIRECTION)
- == SND_AUDIENT_FLAGS_CAPTURE) ? "Capture" : "Playback",
- (ctype->type == SNDRV_CTL_ELEM_TYPE_BOOLEAN)
- ? "Switch" : "Volume");
-
elem = kzalloc_obj(*elem);
if (!elem)
return -ENOMEM;
@@ -524,7 +502,7 @@ int snd_audient_controls_create(struct usb_mixer_interface *mixer)
if (!info->controls)
return -ENODEV;
- for (ctrl = info->controls; ctrl->basename; ctrl++) {
+ for (ctrl = info->controls; ctrl->name; ctrl++) {
const int ret = snd_audient_add_ctrl(mixer, ctrl);
if (ret < 0)
--
2.56.1
^ permalink raw reply [flat|nested] 31+ messages in thread* [RFC V4 PATCH 03/19] ALSA: mixer_audient: clarify USB control message definition
2026-11-12 2:37 ` [RFC PATCH 0/6] ALSA: mixer_audient: Cache rework and simplifications Will Porter
` (8 preceding siblings ...)
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 ` Christian Ruppert
2026-11-15 21:43 ` [RFC V4 PATCH 04/19] ALSA: mixer_audient: Add length check to USB transfer Christian Ruppert
` (15 subsequent siblings)
25 siblings, 0 replies; 31+ messages in thread
From: Christian Ruppert @ 2026-11-15 21:43 UTC (permalink / raw)
To: Will Porter
Cc: Takashi Iwai, Jaroslav Kysela, linux-sound, linux-kernel,
Neil Andrews, Christian Ruppert
Previously, base values for wIndex and wValue were directly given in the
control type. Construct them from a unit ID and selector for clarity.
Signed-off-by: Christian Ruppert <arc@gmx.li>
Signed-off-by: Will Porter <mrwillporter@gmail.com>
---
sound/usb/mixer_audient.c | 76 +++++++++++++++++++++------------------
1 file changed, 41 insertions(+), 35 deletions(-)
diff --git a/sound/usb/mixer_audient.c b/sound/usb/mixer_audient.c
index 2afef52a12d9..63714d00592c 100644
--- a/sound/usb/mixer_audient.c
+++ b/sound/usb/mixer_audient.c
@@ -45,15 +45,15 @@ static const struct snd_audient_ctrl_type {
int integer;
} max;
const unsigned int *tlv;
- u16 wValue;
- u16 wIndex;
+ u8 unit;
+ u8 sel;
} snd_audient_ctypes[SND_AUDIENT_TYPE_NCTYPES] = {
[SND_AUDIENT_TYPE_MONITOR] = {
.type = SNDRV_CTL_ELEM_TYPE_INTEGER,
.min.integer = 0,
.max.integer = 127,
- .wValue = 0x0000,
- .wIndex = 0x3800,
+ .unit = 0x38,
+ .sel = 0x00,
/* no TLV here because we can't describe the cross-fader */
},
[SND_AUDIENT_TYPE_PHANTOM] = {
@@ -61,31 +61,31 @@ static const struct snd_audient_ctrl_type {
.invert = false,
.min.integer = 0,
.max.integer = 1,
- .wValue = 0x0000,
- .wIndex = 0x3A00,
+ .unit = 0x3A,
+ .sel = 0x00,
},
[SND_AUDIENT_TYPE_MUTE] = {
.type = SNDRV_CTL_ELEM_TYPE_BOOLEAN,
.invert = true,
.min.integer = 0,
.max.integer = 1,
- .wValue = 0x0200,
- .wIndex = 0x3A00,
+ .unit = 0x3A,
+ .sel = 0x02,
},
[SND_AUDIENT_TYPE_MUTE_OUT] = {
.type = SNDRV_CTL_ELEM_TYPE_BOOLEAN,
.invert = true,
.min.integer = 0,
.max.integer = 1,
- .wValue = 0x0100,
- .wIndex = 0x3B00,
+ .unit = 0x3B,
+ .sel = 0x01,
},
[SND_AUDIENT_TYPE_MIXER] = {
.type = SNDRV_CTL_ELEM_TYPE_INTEGER,
.min.integer = -128 * 256,
.max.integer = 6 * 256,
- .wValue = 0x0100,
- .wIndex = 0x3C00,
+ .unit = 0x3C,
+ .sel = 0x01,
.tlv = snd_audient_tlv_mixer,
},
};
@@ -209,7 +209,7 @@ const struct snd_audient_ctrl_type *snd_audient_kctl_type(struct snd_kcontrol
return &snd_audient_ctypes[kctl->private_value & 0xFF];
}
-static __always_inline u16 snd_audient_kctl_valoffs(struct snd_kcontrol *kctl)
+static __always_inline u16 snd_audient_kctl_cidxoffs(struct snd_kcontrol *kctl)
{
return (kctl->private_value >> 8) & 0xFF;
}
@@ -233,11 +233,16 @@ static inline u16 snd_audient_kctl_channels(struct snd_kcontrol *kctl)
#define AUDIENT_REQTYPE_GET (USB_DIR_IN | USB_TYPE_CLASS \
| USB_RECIP_INTERFACE)
+#define U8_U8(HI, LO) ((((u16)(HI)) << 8) | ((LO) & 0xFF))
+
static inline int snd_audient_get_cur(struct usb_mixer_interface *mixer,
- u16 value, u16 index,
- void *buf, size_t len)
+ const u8 unit, const u8 sel,
+ const u8 cidx,
+ void *const buf, const size_t len)
{
struct usb_device *const dev = mixer->chip->dev;
+ const u16 value = U8_U8(sel, cidx);
+ const u16 index = U8_U8(unit, mixer->hostif->desc.bInterfaceNumber);
return snd_usb_ctl_msg(dev, usb_rcvctrlpipe(dev, 0),
UAC2_CS_CUR, AUDIENT_REQTYPE_GET,
@@ -245,10 +250,13 @@ static inline int snd_audient_get_cur(struct usb_mixer_interface *mixer,
}
static inline int snd_audient_set_cur(struct usb_mixer_interface *mixer,
- u16 value, u16 index,
- void *buf, size_t len)
+ const u8 unit, const u8 sel,
+ const u8 cidx,
+ void *const buf, const size_t len)
{
struct usb_device *const dev = mixer->chip->dev;
+ const u16 value = U8_U8(sel, cidx);
+ const u16 index = U8_U8(unit, mixer->hostif->desc.bInterfaceNumber);
return snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0),
UAC2_CS_CUR, AUDIENT_REQTYPE_SET,
@@ -266,8 +274,7 @@ static int snd_ctl_audient_boolean_get(struct snd_kcontrol *kctl,
const unsigned int hwchan = snd_audient_kctl_hwchannels(kctl);
const struct snd_audient_ctrl_type *const ctype
= snd_audient_kctl_type(kctl);
- const u16 value = ctype->wValue + snd_audient_kctl_valoffs(kctl);
- const u16 index = ctype->wIndex;
+ const u8 base = snd_audient_kctl_cidxoffs(kctl);
int i;
@@ -279,8 +286,8 @@ static int snd_ctl_audient_boolean_get(struct snd_kcontrol *kctl,
u32 buf;
int ret;
- ret = snd_audient_get_cur(mixer, value + i, index,
- &buf, sizeof(buf));
+ ret = snd_audient_get_cur(mixer, ctype->unit, ctype->sel,
+ base + i, &buf, sizeof(buf));
if (ret < 0)
return ret;
@@ -307,8 +314,7 @@ static int snd_ctl_audient_boolean_set(struct snd_kcontrol *kctl,
const unsigned int hwchan = snd_audient_kctl_hwchannels(kctl);
const struct snd_audient_ctrl_type *const ctype
= snd_audient_kctl_type(kctl);
- const u16 value = ctype->wValue + snd_audient_kctl_valoffs(kctl);
- const u16 index = ctype->wIndex;
+ const u8 base = snd_audient_kctl_cidxoffs(kctl);
int changed = 0;
int i;
@@ -325,15 +331,16 @@ static int snd_ctl_audient_boolean_set(struct snd_kcontrol *kctl,
if (ctype->invert)
buf ^= cpu_to_le32(0x1);
- ret = snd_audient_get_cur(mixer, value + i, index,
- &rbuf, sizeof(rbuf));
+ ret = snd_audient_get_cur(mixer, ctype->unit, ctype->sel,
+ base + i, &rbuf, sizeof(rbuf));
if (ret < 0)
return ret;
if (rbuf != buf) {
changed = 1;
- ret = snd_audient_set_cur(mixer, value + i, index,
+ ret = snd_audient_set_cur(mixer, ctype->unit,
+ ctype->sel, base + i,
&buf, sizeof(buf));
if (ret < 0)
return ret;
@@ -353,8 +360,7 @@ static int snd_ctl_audient_integer_get(struct snd_kcontrol *kctl,
const unsigned int hwchan = snd_audient_kctl_hwchannels(kctl);
const struct snd_audient_ctrl_type *const ctype
= snd_audient_kctl_type(kctl);
- const u16 value = ctype->wValue + snd_audient_kctl_valoffs(kctl);
- const u16 index = ctype->wIndex;
+ const u8 base = snd_audient_kctl_cidxoffs(kctl);
int i;
@@ -367,8 +373,8 @@ static int snd_ctl_audient_integer_get(struct snd_kcontrol *kctl,
s16 val;
int ret;
- ret = snd_audient_get_cur(mixer, value + i, index,
- &buf, sizeof(buf));
+ ret = snd_audient_get_cur(mixer, ctype->unit, ctype->sel,
+ base + i, &buf, sizeof(buf));
if (ret < 0)
return ret;
@@ -391,8 +397,7 @@ static int snd_ctl_audient_integer_set(struct snd_kcontrol *kctl,
const unsigned int hwchan = snd_audient_kctl_hwchannels(kctl);
const struct snd_audient_ctrl_type *const ctype
= snd_audient_kctl_type(kctl);
- const u16 value = ctype->wValue + snd_audient_kctl_valoffs(kctl);
- const u16 index = ctype->wIndex;
+ const u8 base = snd_audient_kctl_cidxoffs(kctl);
int changed = 0;
int i;
@@ -406,15 +411,16 @@ static int snd_ctl_audient_integer_set(struct snd_kcontrol *kctl,
s16 buf = cpu_to_le16(ctl_val->value.integer.value[i]);
int ret;
- ret = snd_audient_get_cur(mixer, value + i, index,
- &rbuf, sizeof(rbuf));
+ ret = snd_audient_get_cur(mixer, ctype->unit, ctype->sel,
+ base + i, &rbuf, sizeof(rbuf));
if (ret < 0)
return ret;
if (rbuf != buf) {
changed = 1;
- ret = snd_audient_set_cur(mixer, value + i, index,
+ ret = snd_audient_set_cur(mixer, ctype->unit,
+ ctype->sel, base + i,
&buf, sizeof(buf));
if (ret < 0)
return ret;
--
2.56.1
^ permalink raw reply [flat|nested] 31+ messages in thread* [RFC V4 PATCH 04/19] ALSA: mixer_audient: Add length check to USB transfer
2026-11-12 2:37 ` [RFC PATCH 0/6] ALSA: mixer_audient: Cache rework and simplifications Will Porter
` (9 preceding siblings ...)
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 ` Christian Ruppert
2026-11-15 21:43 ` [RFC V4 PATCH 05/19] ALSA: mixer_audient: make USB control transfer size configurable Christian Ruppert
` (14 subsequent siblings)
25 siblings, 0 replies; 31+ messages in thread
From: Christian Ruppert @ 2026-11-15 21:43 UTC (permalink / raw)
To: Will Porter
Cc: Takashi Iwai, Jaroslav Kysela, linux-sound, linux-kernel,
Neil Andrews, Christian Ruppert
Check USB control transfers not only for errors but also for expected
length and return -EIO in case of error.
Signed-off-by: Christian Ruppert <arc@gmx.li>
Signed-off-by: Will Porter <mrwillporter@gmail.com>
---
sound/usb/mixer_audient.c | 22 ++++++++++++++++------
1 file changed, 16 insertions(+), 6 deletions(-)
diff --git a/sound/usb/mixer_audient.c b/sound/usb/mixer_audient.c
index 63714d00592c..1f3744a7c10a 100644
--- a/sound/usb/mixer_audient.c
+++ b/sound/usb/mixer_audient.c
@@ -244,9 +244,14 @@ static inline int snd_audient_get_cur(struct usb_mixer_interface *mixer,
const u16 value = U8_U8(sel, cidx);
const u16 index = U8_U8(unit, mixer->hostif->desc.bInterfaceNumber);
- return snd_usb_ctl_msg(dev, usb_rcvctrlpipe(dev, 0),
- UAC2_CS_CUR, AUDIENT_REQTYPE_GET,
- value, index, buf, len);
+ const int ret = snd_usb_ctl_msg(dev, usb_rcvctrlpipe(dev, 0),
+ UAC2_CS_CUR, AUDIENT_REQTYPE_GET,
+ value, index, buf, len);
+
+ if ((ret >= 0) && (ret != len))
+ return -EIO;
+
+ return ret;
}
static inline int snd_audient_set_cur(struct usb_mixer_interface *mixer,
@@ -258,9 +263,14 @@ static inline int snd_audient_set_cur(struct usb_mixer_interface *mixer,
const u16 value = U8_U8(sel, cidx);
const u16 index = U8_U8(unit, mixer->hostif->desc.bInterfaceNumber);
- return snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0),
- UAC2_CS_CUR, AUDIENT_REQTYPE_SET,
- value, index, buf, len);
+ const int ret = snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0),
+ UAC2_CS_CUR, AUDIENT_REQTYPE_SET,
+ value, index, buf, len);
+
+ if ((ret >= 0) && (ret != len))
+ return -EIO;
+
+ return ret;
}
--
2.56.1
^ permalink raw reply [flat|nested] 31+ messages in thread* [RFC V4 PATCH 05/19] ALSA: mixer_audient: make USB control transfer size configurable
2026-11-12 2:37 ` [RFC PATCH 0/6] ALSA: mixer_audient: Cache rework and simplifications Will Porter
` (10 preceding siblings ...)
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 ` Christian Ruppert
2026-11-15 21:43 ` [RFC V4 PATCH 06/19] ALSA: mixer_audient: Allow arbitrary boolean control values Christian Ruppert
` (13 subsequent siblings)
25 siblings, 0 replies; 31+ messages in thread
From: Christian Ruppert @ 2026-11-15 21:43 UTC (permalink / raw)
To: Will Porter
Cc: Takashi Iwai, Jaroslav Kysela, linux-sound, linux-kernel,
Neil Andrews, Christian Ruppert
Previously, the USB transfer size was hardcoded in the datatype get/set
functions. Make it configurable in the ctype structure in preparation of
new control types for the id14.
Signed-off-by: Christian Ruppert <arc@gmx.li>
Signed-off-by: Will Porter <mrwillporter@gmail.com>
---
sound/usb/mixer_audient.c | 131 ++++++++++++++++++++++++--------------
1 file changed, 84 insertions(+), 47 deletions(-)
diff --git a/sound/usb/mixer_audient.c b/sound/usb/mixer_audient.c
index 1f3744a7c10a..57d856d25271 100644
--- a/sound/usb/mixer_audient.c
+++ b/sound/usb/mixer_audient.c
@@ -12,6 +12,8 @@
#include <linux/usb.h>
#include <linux/usb/audio-v2.h>
#include <linux/slab.h>
+#include <linux/types.h>
+#include <linux/bitops.h>
#include <sound/core.h>
#include <sound/control.h>
#include <sound/tlv.h>
@@ -35,8 +37,12 @@ enum snd_audient_type {
/* Mixer matrix: -128dB (mute) to +6dB */
static const DECLARE_TLV_DB_MINMAX_MUTE(snd_audient_tlv_mixer, -12800, 600);
+#define SND_AUDIENT_CTYPE_FLAGS_SIGNED BIT(0)
+
static const struct snd_audient_ctrl_type {
snd_ctl_elem_type_t type;
+ unsigned long flags;
+ size_t len; /* datatype size, can be 1, 2 or 4 bytes */
bool invert;
union {
int integer;
@@ -50,6 +56,7 @@ static const struct snd_audient_ctrl_type {
} snd_audient_ctypes[SND_AUDIENT_TYPE_NCTYPES] = {
[SND_AUDIENT_TYPE_MONITOR] = {
.type = SNDRV_CTL_ELEM_TYPE_INTEGER,
+ .len = 2,
.min.integer = 0,
.max.integer = 127,
.unit = 0x38,
@@ -59,6 +66,7 @@ static const struct snd_audient_ctrl_type {
[SND_AUDIENT_TYPE_PHANTOM] = {
.type = SNDRV_CTL_ELEM_TYPE_BOOLEAN,
.invert = false,
+ .len = 4,
.min.integer = 0,
.max.integer = 1,
.unit = 0x3A,
@@ -67,6 +75,7 @@ static const struct snd_audient_ctrl_type {
[SND_AUDIENT_TYPE_MUTE] = {
.type = SNDRV_CTL_ELEM_TYPE_BOOLEAN,
.invert = true,
+ .len = 4,
.min.integer = 0,
.max.integer = 1,
.unit = 0x3A,
@@ -75,6 +84,7 @@ static const struct snd_audient_ctrl_type {
[SND_AUDIENT_TYPE_MUTE_OUT] = {
.type = SNDRV_CTL_ELEM_TYPE_BOOLEAN,
.invert = true,
+ .len = 4,
.min.integer = 0,
.max.integer = 1,
.unit = 0x3B,
@@ -82,6 +92,8 @@ static const struct snd_audient_ctrl_type {
},
[SND_AUDIENT_TYPE_MIXER] = {
.type = SNDRV_CTL_ELEM_TYPE_INTEGER,
+ .flags = SND_AUDIENT_CTYPE_FLAGS_SIGNED,
+ .len = 2,
.min.integer = -128 * 256,
.max.integer = 6 * 256,
.unit = 0x3C,
@@ -236,44 +248,91 @@ static inline u16 snd_audient_kctl_channels(struct snd_kcontrol *kctl)
#define U8_U8(HI, LO) ((((u16)(HI)) << 8) | ((LO) & 0xFF))
static inline int snd_audient_get_cur(struct usb_mixer_interface *mixer,
- const u8 unit, const u8 sel,
- const u8 cidx,
- void *const buf, const size_t len)
+ const struct snd_audient_ctrl_type *const ctype,
+ const u8 cidx, void *const buf)
{
struct usb_device *const dev = mixer->chip->dev;
- const u16 value = U8_U8(sel, cidx);
- const u16 index = U8_U8(unit, mixer->hostif->desc.bInterfaceNumber);
+ const u16 value = U8_U8(ctype->sel, cidx);
+ const u16 index = U8_U8(ctype->unit,
+ mixer->hostif->desc.bInterfaceNumber);
const int ret = snd_usb_ctl_msg(dev, usb_rcvctrlpipe(dev, 0),
UAC2_CS_CUR, AUDIENT_REQTYPE_GET,
- value, index, buf, len);
+ value, index, buf, ctype->len);
- if ((ret >= 0) && (ret != len))
+ if ((ret >= 0) && (ret != ctype->len))
return -EIO;
return ret;
}
static inline int snd_audient_set_cur(struct usb_mixer_interface *mixer,
- const u8 unit, const u8 sel,
- const u8 cidx,
- void *const buf, const size_t len)
+ const struct snd_audient_ctrl_type *const ctype,
+ const u8 cidx, void *const buf)
{
struct usb_device *const dev = mixer->chip->dev;
- const u16 value = U8_U8(sel, cidx);
- const u16 index = U8_U8(unit, mixer->hostif->desc.bInterfaceNumber);
+ const u16 value = U8_U8(ctype->sel, cidx);
+ const u16 index = U8_U8(ctype->unit,
+ mixer->hostif->desc.bInterfaceNumber);
const int ret = snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0),
UAC2_CS_CUR, AUDIENT_REQTYPE_SET,
- value, index, buf, len);
+ value, index, buf, ctype->len);
- if ((ret >= 0) && (ret != len))
+ if ((ret >= 0) && (ret != ctype->len))
return -EIO;
return ret;
}
+static int snd_audient_raw_get_one(struct usb_mixer_interface *const mixer,
+ const struct snd_audient_ctrl_type *const ctype,
+ const u8 cidx, u32 *const value)
+{
+ int ret;
+ __le32 buf = 0;
+ s32 val;
+
+ ret = snd_audient_get_cur(mixer, ctype, cidx, &buf);
+ if (ret < 0)
+ return ret;
+
+ val = le32_to_cpu(buf);
+
+ if (ctype->flags & SND_AUDIENT_CTYPE_FLAGS_SIGNED)
+ val = sign_extend32(val, 8 * ctype->len - 1);
+
+ *value = val;
+
+ return ret;
+}
+
+static int snd_audient_raw_set_one(struct usb_mixer_interface *const mixer,
+ const struct snd_audient_ctrl_type *const ctype,
+ const u8 cidx, const u32 value)
+{
+ int ret;
+ u32 val;
+
+ ret = snd_audient_raw_get_one(mixer, ctype, cidx, &val);
+ if (ret < 0)
+ return ret;
+
+ if (val != value) {
+ __le32 buf = cpu_to_le32(value);
+
+ ret = snd_audient_set_cur(mixer, ctype, cidx, &buf);
+ if (ret < 0)
+ return ret;
+
+ return 1;
+ }
+
+ return 0;
+}
+
+
static int snd_ctl_audient_boolean_get(struct snd_kcontrol *kctl,
struct snd_ctl_elem_value *ctl_val)
{
@@ -296,12 +355,11 @@ static int snd_ctl_audient_boolean_get(struct snd_kcontrol *kctl,
u32 buf;
int ret;
- ret = snd_audient_get_cur(mixer, ctype->unit, ctype->sel,
- base + i, &buf, sizeof(buf));
+ ret = snd_audient_raw_get_one(mixer, ctype, base + i, &buf);
if (ret < 0)
return ret;
- ctl_val->value.integer.value[i] = le32_to_cpu(buf) ? 1 : 0;
+ ctl_val->value.integer.value[i] = buf ? 1 : 0;
if (ctype->invert)
ctl_val->value.integer.value[i] ^= 0x1;
@@ -334,27 +392,18 @@ static int snd_ctl_audient_boolean_set(struct snd_kcontrol *kctl,
return pm.err;
for (i = 0; i < hwchan; i++) {
- u32 rbuf;
- u32 buf = cpu_to_le32(ctl_val->value.integer.value[i] ? 1 : 0);
+ u32 buf = ctl_val->value.integer.value[i] ? 1 : 0;
int ret;
if (ctype->invert)
- buf ^= cpu_to_le32(0x1);
+ buf ^= 0x1;
- ret = snd_audient_get_cur(mixer, ctype->unit, ctype->sel,
- base + i, &rbuf, sizeof(rbuf));
+ ret = snd_audient_raw_set_one(mixer, ctype, base + i, buf);
if (ret < 0)
return ret;
- if (rbuf != buf) {
+ if (ret)
changed = 1;
-
- ret = snd_audient_set_cur(mixer, ctype->unit,
- ctype->sel, base + i,
- &buf, sizeof(buf));
- if (ret < 0)
- return ret;
- }
}
return changed;
@@ -379,16 +428,13 @@ static int snd_ctl_audient_integer_get(struct snd_kcontrol *kctl,
return pm.err;
for (i = 0; i < hwchan; i++) {
- u16 buf;
- s16 val;
+ s32 val;
int ret;
- ret = snd_audient_get_cur(mixer, ctype->unit, ctype->sel,
- base + i, &buf, sizeof(buf));
+ ret = snd_audient_raw_get_one(mixer, ctype, base + i, &val);
if (ret < 0)
return ret;
- val = le16_to_cpu(buf);
ctl_val->value.integer.value[i] = val;
}
@@ -417,24 +463,15 @@ static int snd_ctl_audient_integer_set(struct snd_kcontrol *kctl,
return pm.err;
for (i = 0; i < hwchan; i++) {
- s16 rbuf;
- s16 buf = cpu_to_le16(ctl_val->value.integer.value[i]);
+ s32 val = ctl_val->value.integer.value[i];
int ret;
- ret = snd_audient_get_cur(mixer, ctype->unit, ctype->sel,
- base + i, &rbuf, sizeof(rbuf));
+ ret = snd_audient_raw_set_one(mixer, ctype, base + i, val);
if (ret < 0)
return ret;
- if (rbuf != buf) {
+ if (ret)
changed = 1;
-
- ret = snd_audient_set_cur(mixer, ctype->unit,
- ctype->sel, base + i,
- &buf, sizeof(buf));
- if (ret < 0)
- return ret;
- }
}
return changed;
--
2.56.1
^ permalink raw reply [flat|nested] 31+ messages in thread* [RFC V4 PATCH 06/19] ALSA: mixer_audient: Allow arbitrary boolean control values
2026-11-12 2:37 ` [RFC PATCH 0/6] ALSA: mixer_audient: Cache rework and simplifications Will Porter
` (11 preceding siblings ...)
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 ` Christian Ruppert
2026-11-15 21:43 ` [RFC V4 PATCH 07/19] ALSA: mixer_audient: Add integer range checks Christian Ruppert
` (12 subsequent siblings)
25 siblings, 0 replies; 31+ messages in thread
From: Christian Ruppert @ 2026-11-15 21:43 UTC (permalink / raw)
To: Will Porter
Cc: Takashi Iwai, Jaroslav Kysela, linux-sound, linux-kernel,
Neil Andrews, Christian Ruppert
In preparation of id14 support, allow arbitrary values for true and false
of boolean switches. This also rids us of the tedious invert flag.
Signed-off-by: Christian Ruppert <arc@gmx.li>
Signed-off-by: Will Porter <mrwillporter@gmail.com>
---
sound/usb/mixer_audient.c | 43 ++++++++++++++++++---------------------
1 file changed, 20 insertions(+), 23 deletions(-)
diff --git a/sound/usb/mixer_audient.c b/sound/usb/mixer_audient.c
index 57d856d25271..4b20db9a82e2 100644
--- a/sound/usb/mixer_audient.c
+++ b/sound/usb/mixer_audient.c
@@ -43,13 +43,12 @@ static const struct snd_audient_ctrl_type {
snd_ctl_elem_type_t type;
unsigned long flags;
size_t len; /* datatype size, can be 1, 2 or 4 bytes */
- bool invert;
union {
int integer;
- } min;
+ } min; /* for booleans holds physical int value for false */
union {
int integer;
- } max;
+ } max; /* for booleans holds physical int value for true */
const unsigned int *tlv;
u8 unit;
u8 sel;
@@ -65,7 +64,6 @@ static const struct snd_audient_ctrl_type {
},
[SND_AUDIENT_TYPE_PHANTOM] = {
.type = SNDRV_CTL_ELEM_TYPE_BOOLEAN,
- .invert = false,
.len = 4,
.min.integer = 0,
.max.integer = 1,
@@ -74,19 +72,17 @@ static const struct snd_audient_ctrl_type {
},
[SND_AUDIENT_TYPE_MUTE] = {
.type = SNDRV_CTL_ELEM_TYPE_BOOLEAN,
- .invert = true,
.len = 4,
- .min.integer = 0,
- .max.integer = 1,
+ .min.integer = 1,
+ .max.integer = 0,
.unit = 0x3A,
.sel = 0x02,
},
[SND_AUDIENT_TYPE_MUTE_OUT] = {
.type = SNDRV_CTL_ELEM_TYPE_BOOLEAN,
- .invert = true,
.len = 4,
- .min.integer = 0,
- .max.integer = 1,
+ .min.integer = 1,
+ .max.integer = 0,
.unit = 0x3B,
.sel = 0x01,
},
@@ -359,16 +355,12 @@ static int snd_ctl_audient_boolean_get(struct snd_kcontrol *kctl,
if (ret < 0)
return ret;
- ctl_val->value.integer.value[i] = buf ? 1 : 0;
-
- if (ctype->invert)
- ctl_val->value.integer.value[i] ^= 0x1;
+ ctl_val->value.integer.value[i] = (buf == ctype->min.integer)
+ ? false : true;
}
for (; i < nchan; i++)
- ctl_val->value.integer.value[i] = ctype->invert
- ? ctype->max.integer
- : ctype->min.integer;
+ ctl_val->value.integer.value[i] = 0;
return 0;
}
@@ -392,12 +384,10 @@ static int snd_ctl_audient_boolean_set(struct snd_kcontrol *kctl,
return pm.err;
for (i = 0; i < hwchan; i++) {
- u32 buf = ctl_val->value.integer.value[i] ? 1 : 0;
+ u32 buf = ctl_val->value.integer.value[i] ? ctype->max.integer
+ : ctype->min.integer;
int ret;
- if (ctype->invert)
- buf ^= 0x1;
-
ret = snd_audient_raw_set_one(mixer, ctype, base + i, buf);
if (ret < 0)
return ret;
@@ -485,8 +475,15 @@ static int snd_ctl_audient_info(struct snd_kcontrol *kctl,
uinfo->type = ctype->type;
uinfo->count = snd_audient_kctl_channels(kctl);
- uinfo->value.integer.min = ctype->min.integer;
- uinfo->value.integer.max = ctype->max.integer;
+
+ if (ctype->type == SNDRV_CTL_ELEM_TYPE_BOOLEAN) {
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = 1;
+ } else {
+ uinfo->value.integer.min = ctype->min.integer;
+ uinfo->value.integer.max = ctype->max.integer;
+ }
+
return 0;
}
--
2.56.1
^ permalink raw reply [flat|nested] 31+ messages in thread* [RFC V4 PATCH 07/19] ALSA: mixer_audient: Add integer range checks
2026-11-12 2:37 ` [RFC PATCH 0/6] ALSA: mixer_audient: Cache rework and simplifications Will Porter
` (12 preceding siblings ...)
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 ` Christian Ruppert
2026-11-15 21:43 ` [RFC V4 PATCH 08/19] ALSA: mixer_audient: Add control initialization at probe Christian Ruppert
` (11 subsequent siblings)
25 siblings, 0 replies; 31+ messages in thread
From: Christian Ruppert @ 2026-11-15 21:43 UTC (permalink / raw)
To: Will Porter
Cc: Takashi Iwai, Jaroslav Kysela, linux-sound, linux-kernel,
Neil Andrews, Christian Ruppert
Add range checks to integer parameters to improve error management:
- Reject out-of-range write attempts with -EINVAL
- Prune out-of-range values returned by the device to the range limits
Signed-off-by: Christian Ruppert <arc@gmx.li>
Signed-off-by: Will Porter <mrwillporter@gmail.com>
---
sound/usb/mixer_audient.c | 17 +++++++++++++++--
1 file changed, 15 insertions(+), 2 deletions(-)
diff --git a/sound/usb/mixer_audient.c b/sound/usb/mixer_audient.c
index 4b20db9a82e2..433bb5479d8e 100644
--- a/sound/usb/mixer_audient.c
+++ b/sound/usb/mixer_audient.c
@@ -44,10 +44,10 @@ static const struct snd_audient_ctrl_type {
unsigned long flags;
size_t len; /* datatype size, can be 1, 2 or 4 bytes */
union {
- int integer;
+ long integer;
} min; /* for booleans holds physical int value for false */
union {
- int integer;
+ long integer;
} max; /* for booleans holds physical int value for true */
const unsigned int *tlv;
u8 unit;
@@ -425,6 +425,12 @@ static int snd_ctl_audient_integer_get(struct snd_kcontrol *kctl,
if (ret < 0)
return ret;
+ if (val < ctype->min.integer)
+ val = ctype->min.integer;
+
+ if (val > ctype->max.integer)
+ val = ctype->max.integer;
+
ctl_val->value.integer.value[i] = val;
}
@@ -448,6 +454,13 @@ static int snd_ctl_audient_integer_set(struct snd_kcontrol *kctl,
int changed = 0;
int i;
+ for (i = 0; i < hwchan; i++) {
+ long val = ctl_val->value.integer.value[i];
+
+ if ((val < ctype->min.integer) || (val > ctype->max.integer))
+ return -EINVAL;
+ }
+
CLASS(snd_usb_lock, pm)(mixer->chip);
if (pm.err < 0)
return pm.err;
--
2.56.1
^ permalink raw reply [flat|nested] 31+ messages in thread* [RFC V4 PATCH 08/19] ALSA: mixer_audient: Add control initialization at probe
2026-11-12 2:37 ` [RFC PATCH 0/6] ALSA: mixer_audient: Cache rework and simplifications Will Porter
` (13 preceding siblings ...)
2026-11-15 21:43 ` [RFC V4 PATCH 07/19] ALSA: mixer_audient: Add integer range checks Christian Ruppert
@ 2026-11-15 21:43 ` Christian Ruppert
2026-11-15 21:43 ` [RFC V4 PATCH 09/19] ALSA: mixer_audient: Add readback-free parameter set Christian Ruppert
` (10 subsequent siblings)
25 siblings, 0 replies; 31+ messages in thread
From: Christian Ruppert @ 2026-11-15 21:43 UTC (permalink / raw)
To: Will Porter
Cc: Takashi Iwai, Jaroslav Kysela, linux-sound, linux-kernel,
Neil Andrews, Christian Ruppert
Add the possibility to initialize certain control types to known values at
probe time. This is particularly useful for controls which initially read
wrong values and for controls which cannot be read at all.
Signed-off-by: Christian Ruppert <arc@gmx.li>
Co-developed-by: Will Porter <mrwillporter@gmail.com>
Signed-off-by: Will Porter <mrwillporter@gmail.com>
---
sound/usb/mixer_audient.c | 16 ++++++++++++++++
1 file changed, 16 insertions(+)
diff --git a/sound/usb/mixer_audient.c b/sound/usb/mixer_audient.c
index 433bb5479d8e..70ec3274a5e4 100644
--- a/sound/usb/mixer_audient.c
+++ b/sound/usb/mixer_audient.c
@@ -38,6 +38,7 @@ enum snd_audient_type {
static const DECLARE_TLV_DB_MINMAX_MUTE(snd_audient_tlv_mixer, -12800, 600);
#define SND_AUDIENT_CTYPE_FLAGS_SIGNED BIT(0)
+#define SND_AUDIENT_CTYPE_FLAGS_INIT_HW BIT(1)
static const struct snd_audient_ctrl_type {
snd_ctl_elem_type_t type;
@@ -49,6 +50,7 @@ static const struct snd_audient_ctrl_type {
union {
long integer;
} max; /* for booleans holds physical int value for true */
+ u32 init;
const unsigned int *tlv;
u8 unit;
u8 sel;
@@ -533,6 +535,20 @@ static int snd_audient_add_ctrl(struct usb_mixer_interface *mixer,
return -EINVAL;
}
+ if (ctype->flags & SND_AUDIENT_CTYPE_FLAGS_INIT_HW) {
+ int i;
+
+ for (i = 0; i < ctrl->hwchannels; i++) {
+ int ret = snd_audient_raw_set_one(mixer, ctype,
+ ctrl->val_offs + i,
+ ctype->init);
+ if (ret < 0)
+ usb_audio_warn(mixer->chip,
+ "%s: cannot set channel %d (%d)\n",
+ ctrl->name, i, ret);
+ }
+ }
+
elem = kzalloc_obj(*elem);
if (!elem)
return -ENOMEM;
--
2.56.1
^ permalink raw reply [flat|nested] 31+ messages in thread* [RFC V4 PATCH 09/19] ALSA: mixer_audient: Add readback-free parameter set
2026-11-12 2:37 ` [RFC PATCH 0/6] ALSA: mixer_audient: Cache rework and simplifications Will Porter
` (14 preceding siblings ...)
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 ` Christian Ruppert
2026-11-15 21:43 ` [RFC V4 PATCH 10/19] ALSA: mixer_audient: add control value caching Christian Ruppert
` (9 subsequent siblings)
25 siblings, 0 replies; 31+ messages in thread
From: Christian Ruppert @ 2026-11-15 21:43 UTC (permalink / raw)
To: Will Porter
Cc: Takashi Iwai, Jaroslav Kysela, linux-sound, linux-kernel,
Neil Andrews, Christian Ruppert
Add a flag to systematically force parameter writing without prior readback
to certain control types. In preparation of the id14 driver.
Signed-off-by: Christian Ruppert <arc@gmx.li>
Signed-off-by: Will Porter <mrwillporter@gmail.com>
---
sound/usb/mixer_audient.c | 11 ++++++++---
1 file changed, 8 insertions(+), 3 deletions(-)
diff --git a/sound/usb/mixer_audient.c b/sound/usb/mixer_audient.c
index 70ec3274a5e4..bdfcb7fcf531 100644
--- a/sound/usb/mixer_audient.c
+++ b/sound/usb/mixer_audient.c
@@ -39,6 +39,7 @@ static const DECLARE_TLV_DB_MINMAX_MUTE(snd_audient_tlv_mixer, -12800, 600);
#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)
static const struct snd_audient_ctrl_type {
snd_ctl_elem_type_t type;
@@ -313,9 +314,13 @@ static int snd_audient_raw_set_one(struct usb_mixer_interface *const mixer,
int ret;
u32 val;
- ret = snd_audient_raw_get_one(mixer, ctype, cidx, &val);
- if (ret < 0)
- return ret;
+ if (ctype->flags & SND_AUDIENT_CTYPE_FLAGS_GET_BROKEN) {
+ val = !value; /* just make it different */
+ } else {
+ ret = snd_audient_raw_get_one(mixer, ctype, cidx, &val);
+ if (ret < 0)
+ return ret;
+ }
if (val != value) {
__le32 buf = cpu_to_le32(value);
--
2.56.1
^ permalink raw reply [flat|nested] 31+ messages in thread* [RFC V4 PATCH 10/19] ALSA: mixer_audient: add control value caching
2026-11-12 2:37 ` [RFC PATCH 0/6] ALSA: mixer_audient: Cache rework and simplifications Will Porter
` (15 preceding siblings ...)
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 ` Christian Ruppert
2026-11-15 21:43 ` [RFC V4 PATCH 11/19] ALSA: mixer_audient: Create matrix programmatically Christian Ruppert
` (8 subsequent siblings)
25 siblings, 0 replies; 31+ messages in thread
From: Christian Ruppert @ 2026-11-15 21:43 UTC (permalink / raw)
To: Will Porter
Cc: Takashi Iwai, Jaroslav Kysela, linux-sound, linux-kernel,
Neil Andrews, Christian Ruppert
In preparation of id14 support, add control value caching.
Signed-off-by: Christian Ruppert <arc@gmx.li>
Co-developed-by: Will Porter <mrwillporter@gmail.com>
Signed-off-by: Will Porter <mrwillporter@gmail.com>
---
sound/usb/mixer_audient.c | 144 +++++++++++++++++++++++++++-----------
1 file changed, 105 insertions(+), 39 deletions(-)
diff --git a/sound/usb/mixer_audient.c b/sound/usb/mixer_audient.c
index bdfcb7fcf531..33c5e0a8f0e0 100644
--- a/sound/usb/mixer_audient.c
+++ b/sound/usb/mixer_audient.c
@@ -40,6 +40,7 @@ static const DECLARE_TLV_DB_MINMAX_MUTE(snd_audient_tlv_mixer, -12800, 600);
#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)
static const struct snd_audient_ctrl_type {
snd_ctl_elem_type_t type;
@@ -51,7 +52,7 @@ static const struct snd_audient_ctrl_type {
union {
long integer;
} max; /* for booleans holds physical int value for true */
- u32 init;
+ u32 init; /* hardware and cache are initialized to this value */
const unsigned int *tlv;
u8 unit;
u8 sel;
@@ -91,7 +92,8 @@ static const struct snd_audient_ctrl_type {
},
[SND_AUDIENT_TYPE_MIXER] = {
.type = SNDRV_CTL_ELEM_TYPE_INTEGER,
- .flags = SND_AUDIENT_CTYPE_FLAGS_SIGNED,
+ .flags = SND_AUDIENT_CTYPE_FLAGS_SIGNED
+ | SND_AUDIENT_CTYPE_FLAGS_CACHE,
.len = 2,
.min.integer = -128 * 256,
.max.integer = 6 * 256,
@@ -285,14 +287,33 @@ static inline int snd_audient_set_cur(struct usb_mixer_interface *mixer,
}
-static int snd_audient_raw_get_one(struct usb_mixer_interface *const mixer,
+static int snd_audient_raw_get_one(struct usb_mixer_elem_info *const elem,
const struct snd_audient_ctrl_type *const ctype,
- const u8 cidx, u32 *const value)
+ const unsigned int cidx, u32 *const value)
{
+ struct usb_mixer_interface *const mixer = elem->head.mixer;
int ret;
__le32 buf = 0;
s32 val;
+ if (cidx > MAX_CHANNELS)
+ return -EINVAL;
+
+ if (ctype->flags & SND_AUDIENT_CTYPE_FLAGS_CACHE) {
+ if (test_bit(cidx, elem->cached)) {
+ *value = elem->cache_val[cidx];
+
+ return ctype->len;
+ } else if (ctype->flags & SND_AUDIENT_CTYPE_FLAGS_GET_BROKEN) {
+ /*
+ * Cached FORCE_SET controls are treated as write-only:
+ * an invalid cache entry does not trigger a GET.
+ */
+ return -ENODATA;
+ }
+ }
+
+
ret = snd_audient_get_cur(mixer, ctype, cidx, &buf);
if (ret < 0)
return ret;
@@ -304,43 +325,55 @@ static int snd_audient_raw_get_one(struct usb_mixer_interface *const mixer,
*value = val;
+ set_bit(cidx, elem->cached);
+ elem->cache_val[cidx] = val;
+
return ret;
}
-static int snd_audient_raw_set_one(struct usb_mixer_interface *const mixer,
+static int snd_audient_raw_set_one(struct usb_mixer_elem_info *const elem,
const struct snd_audient_ctrl_type *const ctype,
- const u8 cidx, const u32 value)
+ const unsigned int cidx, const u32 value, bool force)
{
+ struct usb_mixer_interface *const mixer = elem->head.mixer;
int ret;
u32 val;
- if (ctype->flags & SND_AUDIENT_CTYPE_FLAGS_GET_BROKEN) {
+ if (cidx > MAX_CHANNELS)
+ return -EINVAL;
+
+ if (force) {
val = !value; /* just make it different */
} else {
- ret = snd_audient_raw_get_one(mixer, ctype, cidx, &val);
- if (ret < 0)
+ ret = snd_audient_raw_get_one(elem, ctype, cidx, &val);
+ if (ret == -ENODATA) /* FORCE_SET and not cached yet */
+ val = !value; /* just make it different */
+ else if (ret < 0)
return ret;
}
- if (val != value) {
+ force = force || (ctype->flags & SND_AUDIENT_CTYPE_FLAGS_GET_BROKEN);
+
+ if (force || (val != value)) {
__le32 buf = cpu_to_le32(value);
ret = snd_audient_set_cur(mixer, ctype, cidx, &buf);
if (ret < 0)
return ret;
- return 1;
+ set_bit(cidx, elem->cached);
+ elem->cache_val[cidx] = value;
}
- return 0;
+ return val != value;
}
static int snd_ctl_audient_boolean_get(struct snd_kcontrol *kctl,
struct snd_ctl_elem_value *ctl_val)
{
- struct usb_mixer_elem_list *const list = snd_kcontrol_chip(kctl);
- struct usb_mixer_interface *const mixer = list->mixer;
+ struct usb_mixer_elem_info *const elem = snd_kcontrol_chip(kctl);
+ struct usb_mixer_interface *const mixer = elem->head.mixer;
const unsigned int nchan = snd_audient_kctl_channels(kctl);
const unsigned int hwchan = snd_audient_kctl_hwchannels(kctl);
@@ -353,13 +386,17 @@ static int snd_ctl_audient_boolean_get(struct snd_kcontrol *kctl,
CLASS(snd_usb_lock, pm)(mixer->chip);
if (pm.err < 0)
return pm.err;
+ /* serializes the transfers and the cache, also between gets */
+ guard(mutex)(&mixer->lock);
for (i = 0; i < hwchan; i++) {
u32 buf;
int ret;
- ret = snd_audient_raw_get_one(mixer, ctype, base + i, &buf);
- if (ret < 0)
+ ret = snd_audient_raw_get_one(elem, ctype, base + i, &buf);
+ if (ret == -ENODATA) /* unknown write-only value: off */
+ buf = ctype->min.integer;
+ else if (ret < 0)
return ret;
ctl_val->value.integer.value[i] = (buf == ctype->min.integer)
@@ -375,8 +412,8 @@ static int snd_ctl_audient_boolean_get(struct snd_kcontrol *kctl,
static int snd_ctl_audient_boolean_set(struct snd_kcontrol *kctl,
struct snd_ctl_elem_value *ctl_val)
{
- struct usb_mixer_elem_list *list = snd_kcontrol_chip(kctl);
- struct usb_mixer_interface *mixer = list->mixer;
+ struct usb_mixer_elem_info *const elem = snd_kcontrol_chip(kctl);
+ struct usb_mixer_interface *const mixer = elem->head.mixer;
const unsigned int hwchan = snd_audient_kctl_hwchannels(kctl);
const struct snd_audient_ctrl_type *const ctype
@@ -389,13 +426,16 @@ static int snd_ctl_audient_boolean_set(struct snd_kcontrol *kctl,
CLASS(snd_usb_lock, pm)(mixer->chip);
if (pm.err < 0)
return pm.err;
+ /* serializes the transfers and the cache, also between gets */
+ guard(mutex)(&mixer->lock);
for (i = 0; i < hwchan; i++) {
u32 buf = ctl_val->value.integer.value[i] ? ctype->max.integer
: ctype->min.integer;
int ret;
- ret = snd_audient_raw_set_one(mixer, ctype, base + i, buf);
+ ret = snd_audient_raw_set_one(elem, ctype, base + i, buf,
+ false);
if (ret < 0)
return ret;
@@ -409,8 +449,8 @@ static int snd_ctl_audient_boolean_set(struct snd_kcontrol *kctl,
static int snd_ctl_audient_integer_get(struct snd_kcontrol *kctl,
struct snd_ctl_elem_value *ctl_val)
{
- struct usb_mixer_elem_list *const list = snd_kcontrol_chip(kctl);
- struct usb_mixer_interface *const mixer = list->mixer;
+ struct usb_mixer_elem_info *const elem = snd_kcontrol_chip(kctl);
+ struct usb_mixer_interface *const mixer = elem->head.mixer;
const unsigned int nchan = snd_audient_kctl_channels(kctl);
const unsigned int hwchan = snd_audient_kctl_hwchannels(kctl);
@@ -423,13 +463,17 @@ static int snd_ctl_audient_integer_get(struct snd_kcontrol *kctl,
CLASS(snd_usb_lock, pm)(mixer->chip);
if (pm.err < 0)
return pm.err;
+ /* serializes the transfers and the cache, also between gets */
+ guard(mutex)(&mixer->lock);
for (i = 0; i < hwchan; i++) {
s32 val;
int ret;
- ret = snd_audient_raw_get_one(mixer, ctype, base + i, &val);
- if (ret < 0)
+ ret = snd_audient_raw_get_one(elem, ctype, base + i, &val);
+ if (ret == -ENODATA) /* unknown write-only value: minimum */
+ val = ctype->min.integer;
+ else if (ret < 0)
return ret;
if (val < ctype->min.integer)
@@ -450,8 +494,8 @@ static int snd_ctl_audient_integer_get(struct snd_kcontrol *kctl,
static int snd_ctl_audient_integer_set(struct snd_kcontrol *kctl,
struct snd_ctl_elem_value *ctl_val)
{
- struct usb_mixer_elem_list *list = snd_kcontrol_chip(kctl);
- struct usb_mixer_interface *mixer = list->mixer;
+ struct usb_mixer_elem_info *const elem = snd_kcontrol_chip(kctl);
+ struct usb_mixer_interface *mixer = elem->head.mixer;
const unsigned int hwchan = snd_audient_kctl_hwchannels(kctl);
const struct snd_audient_ctrl_type *const ctype
@@ -471,12 +515,15 @@ static int snd_ctl_audient_integer_set(struct snd_kcontrol *kctl,
CLASS(snd_usb_lock, pm)(mixer->chip);
if (pm.err < 0)
return pm.err;
+ /* serializes the transfers and the cache, also between gets */
+ guard(mutex)(&mixer->lock);
for (i = 0; i < hwchan; i++) {
s32 val = ctl_val->value.integer.value[i];
int ret;
- ret = snd_audient_raw_set_one(mixer, ctype, base + i, val);
+ ret = snd_audient_raw_set_one(elem, ctype, base + i, val,
+ false);
if (ret < 0)
return ret;
@@ -517,6 +564,8 @@ static int snd_audient_add_ctrl(struct usb_mixer_interface *mixer,
struct snd_kcontrol *kctl;
struct usb_mixer_elem_info *elem;
+ int ret;
+
knew.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
knew.name = ctrl->name;
knew.info = snd_ctl_audient_info;
@@ -524,7 +573,9 @@ static int snd_audient_add_ctrl(struct usb_mixer_interface *mixer,
ctrl->hwchannels);
knew.tlv.p = ctype->tlv;
knew.access = SNDRV_CTL_ELEM_ACCESS_READWRITE
- | SNDRV_CTL_ELEM_ACCESS_VOLATILE
+ | ((ctype->flags & SND_AUDIENT_CTYPE_FLAGS_CACHE)
+ ? 0
+ : SNDRV_CTL_ELEM_ACCESS_VOLATILE)
| (ctype->tlv ? SNDRV_CTL_ELEM_ACCESS_TLV_READ : 0);
switch (ctype->type) {
@@ -540,13 +591,30 @@ static int snd_audient_add_ctrl(struct usb_mixer_interface *mixer,
return -EINVAL;
}
+ elem = kzalloc_obj(*elem);
+ if (!elem)
+ return -ENOMEM;
+
+ elem->head.mixer = mixer;
+ elem->channels = ctrl->channels;
+ elem->head.id = ctype->unit;
+ elem->control = ctype->sel;
+ elem->get_cur_broken = (ctype->flags & SND_AUDIENT_CTYPE_FLAGS_CACHE)
+ && (ctype->flags & SND_AUDIENT_CTYPE_FLAGS_GET_BROKEN);
+
+ /*
+ * A failed probe transfer must not fail the card: warn and keep the
+ * control.
+ * We don't initialize the cache: This will be done implicitly when
+ * the control is first successfully accessed.
+ */
if (ctype->flags & SND_AUDIENT_CTYPE_FLAGS_INIT_HW) {
int i;
for (i = 0; i < ctrl->hwchannels; i++) {
- int ret = snd_audient_raw_set_one(mixer, ctype,
- ctrl->val_offs + i,
- ctype->init);
+ ret = snd_audient_raw_set_one(elem, ctype,
+ ctrl->val_offs + i,
+ ctype->init, true);
if (ret < 0)
usb_audio_warn(mixer->chip,
"%s: cannot set channel %d (%d)\n",
@@ -554,22 +622,20 @@ static int snd_audient_add_ctrl(struct usb_mixer_interface *mixer,
}
}
- elem = kzalloc_obj(*elem);
- if (!elem)
- return -ENOMEM;
-
- elem->head.mixer = mixer;
- elem->channels = ctrl->channels;
-
kctl = snd_ctl_new1(&knew, elem);
if (!kctl) {
- kfree(elem);
- return -ENOMEM;
+ ret = -ENOMEM;
+ goto free_elem;
}
kctl->private_free = snd_usb_mixer_elem_free;
return snd_usb_mixer_add_control(&elem->head, kctl);
+
+free_elem:
+ kfree(elem);
+
+ return ret;
}
--
2.56.1
^ permalink raw reply [flat|nested] 31+ messages in thread* [RFC V4 PATCH 11/19] ALSA: mixer_audient: Create matrix programmatically
2026-11-12 2:37 ` [RFC PATCH 0/6] ALSA: mixer_audient: Cache rework and simplifications Will Porter
` (16 preceding siblings ...)
2026-11-15 21:43 ` [RFC V4 PATCH 10/19] ALSA: mixer_audient: add control value caching Christian Ruppert
@ 2026-11-15 21:43 ` Christian Ruppert
2026-11-15 21:43 ` [RFC V4 PATCH 12/19] ALSA: mixer_audient: Add a step size to integer controls Christian Ruppert
` (7 subsequent siblings)
25 siblings, 0 replies; 31+ messages in thread
From: Christian Ruppert @ 2026-11-15 21:43 UTC (permalink / raw)
To: Will Porter
Cc: Takashi Iwai, Jaroslav Kysela, linux-sound, linux-kernel,
Neil Andrews, Christian Ruppert
Most Audient devices seem to have a similar mixer matrix with many
identical channels. The only difference between devices seems to be
the matrix' dimension. Create it programmatically in a loop rather
than from the controls table.
Signed-off-by: Christian Ruppert <arc@gmx.li>
Co-developed-by: Will Porter <mrwillporter@gmail.com>
Signed-off-by: Will Porter <mrwillporter@gmail.com>
---
sound/usb/mixer_audient.c | 77 +++++++++++++++++----------------------
1 file changed, 34 insertions(+), 43 deletions(-)
diff --git a/sound/usb/mixer_audient.c b/sound/usb/mixer_audient.c
index 33c5e0a8f0e0..8a858860033d 100644
--- a/sound/usb/mixer_audient.c
+++ b/sound/usb/mixer_audient.c
@@ -105,7 +105,7 @@ static const struct snd_audient_ctrl_type {
struct snd_audient_ctrl {
- char *name;
+ const char *const name;
enum snd_audient_type type;
unsigned int channels;
unsigned int hwchannels;
@@ -134,48 +134,6 @@ static const struct snd_audient_ctrl snd_audient_evo4_controls[] = {
.hwchannels = 1,
.val_offs = 0,
},
- {
- .name = "Mic 1 Capture Volume",
- .type = SND_AUDIENT_TYPE_MIXER,
- .channels = 2,
- .hwchannels = 2,
- .val_offs = 0,
- },
- {
- .name = "Mic 2 Capture Volume",
- .type = SND_AUDIENT_TYPE_MIXER,
- .channels = 2,
- .hwchannels = 2,
- .val_offs = 2,
- },
- {
- .name = "Master Left Capture Volume",
- .type = SND_AUDIENT_TYPE_MIXER,
- .channels = 2,
- .hwchannels = 2,
- .val_offs = 4,
- },
- {
- .name = "Master Right Capture Volume",
- .type = SND_AUDIENT_TYPE_MIXER,
- .channels = 2,
- .hwchannels = 2,
- .val_offs = 6,
- },
- {
- .name = "Loopback Left Capture Volume",
- .type = SND_AUDIENT_TYPE_MIXER,
- .channels = 2,
- .hwchannels = 2,
- .val_offs = 8,
- },
- {
- .name = "Loopback Right Capture Volume",
- .type = SND_AUDIENT_TYPE_MIXER,
- .channels = 2,
- .hwchannels = 2,
- .val_offs = 10,
- },
{
.name = "Mic 1 Phantom Capture Switch",
.type = SND_AUDIENT_TYPE_PHANTOM,
@@ -193,13 +151,27 @@ static const struct snd_audient_ctrl snd_audient_evo4_controls[] = {
{ 0 }, /* sentinel */
};
+static const char *const snd_audient_evo4_matrix[] = {
+ "Mic 1 Capture Volume",
+ "Mic 2 Capture Volume",
+ "Master Left Capture Volume",
+ "Master Right Capture Volume",
+ "Loopback Left Capture Volume",
+ "Loopback Right Capture Volume",
+ NULL
+};
+
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;
} audient_devinfo[] = {
{
.usb_id = USB_ID(USB_AUDIENT_VID, USB_EVO4_PID),
.controls = snd_audient_evo4_controls,
+ .matrix_names = snd_audient_evo4_matrix,
+ .matrix_channels = 2,
},
{ 0 } /* sentinel */
};
@@ -645,6 +617,8 @@ int snd_audient_controls_create(struct usb_mixer_interface *mixer)
const struct audient_devinfo *info;
const struct snd_audient_ctrl *ctrl;
+ int i;
+
for (info = audient_devinfo; info->controls; info++)
if (info->usb_id == mixer->chip->usb_id)
break;
@@ -659,5 +633,22 @@ int snd_audient_controls_create(struct usb_mixer_interface *mixer)
return ret;
}
+ if (info->matrix_names) {
+ for (i = 0; info->matrix_names[i]; i++) {
+ const struct snd_audient_ctrl c = {
+ .name = info->matrix_names[i],
+ .type = SND_AUDIENT_TYPE_MIXER,
+ .channels = info->matrix_channels,
+ .hwchannels = info->matrix_channels,
+ .val_offs = i * info->matrix_channels,
+ };
+
+ const int ret = snd_audient_add_ctrl(mixer, &c);
+
+ if (ret < 0)
+ return ret;
+ }
+ }
+
return 0;
}
--
2.56.1
^ permalink raw reply [flat|nested] 31+ messages in thread* [RFC V4 PATCH 12/19] ALSA: mixer_audient: Add a step size to integer controls
2026-11-12 2:37 ` [RFC PATCH 0/6] ALSA: mixer_audient: Cache rework and simplifications Will Porter
` (17 preceding siblings ...)
2026-11-15 21:43 ` [RFC V4 PATCH 11/19] ALSA: mixer_audient: Create matrix programmatically Christian Ruppert
@ 2026-11-15 21:43 ` Christian Ruppert
2026-11-15 21:43 ` [RFC V4 PATCH 13/19] ALSA: mixer_audient: Add enumerated controls Christian Ruppert
` (6 subsequent siblings)
25 siblings, 0 replies; 31+ messages in thread
From: Christian Ruppert @ 2026-11-15 21:43 UTC (permalink / raw)
To: Will Porter
Cc: Takashi Iwai, Jaroslav Kysela, linux-sound, linux-kernel,
Neil Andrews, Christian Ruppert
From: Will Porter <mrwillporter@gmail.com>
The iD14 level controls use s16 values in 1/256 dB. Expose them in
1 dB steps, matching the volume knob's adjustment. Let a control type
give a step size: the control then counts steps from the minimum, as
the generic USB mixer does with the RANGE resolution. A device value
between two steps reads as the nearest step, clamped to the maximum.
Types without a step size keep the device value.
Signed-off-by: Will Porter <mrwillporter@gmail.com>
---
sound/usb/mixer_audient.c | 55 ++++++++++++++++++++++++++++++---------
1 file changed, 43 insertions(+), 12 deletions(-)
diff --git a/sound/usb/mixer_audient.c b/sound/usb/mixer_audient.c
index 8a858860033d..771a45cf6b45 100644
--- a/sound/usb/mixer_audient.c
+++ b/sound/usb/mixer_audient.c
@@ -53,6 +53,7 @@ static const struct snd_audient_ctrl_type {
long integer;
} max; /* for booleans holds physical int value for true */
u32 init; /* hardware and cache are initialized to this value */
+ unsigned int res; /* integers: device units per control step, 0 = 1 */
const unsigned int *tlv;
u8 unit;
u8 sel;
@@ -418,6 +419,39 @@ static int snd_ctl_audient_boolean_set(struct snd_kcontrol *kctl,
return changed;
}
+/*
+ * With a step size (res), an integer control counts steps from min:
+ * 0 ... (max - min) / res. Without one it shows the device value.
+ */
+static long snd_audient_int_max(const struct snd_audient_ctrl_type *ctype)
+{
+ return ctype->res ? (ctype->max.integer - ctype->min.integer) / ctype->res
+ : ctype->max.integer;
+}
+
+static long snd_audient_int_min(const struct snd_audient_ctrl_type *ctype)
+{
+ return ctype->res ? 0 : ctype->min.integer;
+}
+
+static long snd_audient_int_to_ctl(const struct snd_audient_ctrl_type *ctype,
+ long val)
+{
+ val = clamp(val, ctype->min.integer, ctype->max.integer);
+ if (!ctype->res)
+ return val;
+
+ /* round a device value between two steps to the nearest one */
+ return min(DIV_ROUND_CLOSEST(val - ctype->min.integer, (long)ctype->res),
+ snd_audient_int_max(ctype));
+}
+
+static long snd_audient_int_from_ctl(const struct snd_audient_ctrl_type *ctype,
+ long val)
+{
+ return ctype->res ? ctype->min.integer + val * ctype->res : val;
+}
+
static int snd_ctl_audient_integer_get(struct snd_kcontrol *kctl,
struct snd_ctl_elem_value *ctl_val)
{
@@ -448,17 +482,12 @@ static int snd_ctl_audient_integer_get(struct snd_kcontrol *kctl,
else if (ret < 0)
return ret;
- if (val < ctype->min.integer)
- val = ctype->min.integer;
-
- if (val > ctype->max.integer)
- val = ctype->max.integer;
-
- ctl_val->value.integer.value[i] = val;
+ ctl_val->value.integer.value[i] =
+ snd_audient_int_to_ctl(ctype, val);
}
for (; i < nchan; i++)
- ctl_val->value.integer.value[i] = ctype->min.integer;
+ ctl_val->value.integer.value[i] = snd_audient_int_min(ctype);
return 0;
}
@@ -480,7 +509,8 @@ static int snd_ctl_audient_integer_set(struct snd_kcontrol *kctl,
for (i = 0; i < hwchan; i++) {
long val = ctl_val->value.integer.value[i];
- if ((val < ctype->min.integer) || (val > ctype->max.integer))
+ if (val < snd_audient_int_min(ctype) ||
+ val > snd_audient_int_max(ctype))
return -EINVAL;
}
@@ -491,7 +521,8 @@ static int snd_ctl_audient_integer_set(struct snd_kcontrol *kctl,
guard(mutex)(&mixer->lock);
for (i = 0; i < hwchan; i++) {
- s32 val = ctl_val->value.integer.value[i];
+ s32 val = snd_audient_int_from_ctl(ctype,
+ ctl_val->value.integer.value[i]);
int ret;
ret = snd_audient_raw_set_one(elem, ctype, base + i, val,
@@ -519,8 +550,8 @@ static int snd_ctl_audient_info(struct snd_kcontrol *kctl,
uinfo->value.integer.min = 0;
uinfo->value.integer.max = 1;
} else {
- uinfo->value.integer.min = ctype->min.integer;
- uinfo->value.integer.max = ctype->max.integer;
+ uinfo->value.integer.min = snd_audient_int_min(ctype);
+ uinfo->value.integer.max = snd_audient_int_max(ctype);
}
return 0;
--
2.56.1
^ permalink raw reply [flat|nested] 31+ messages in thread* [RFC V4 PATCH 13/19] ALSA: mixer_audient: Add enumerated controls
2026-11-12 2:37 ` [RFC PATCH 0/6] ALSA: mixer_audient: Cache rework and simplifications Will Porter
` (18 preceding siblings ...)
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 ` 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
` (5 subsequent siblings)
25 siblings, 0 replies; 31+ messages in thread
From: Christian Ruppert @ 2026-11-15 21:43 UTC (permalink / raw)
To: Will Porter
Cc: Takashi Iwai, Jaroslav Kysela, linux-sound, linux-kernel,
Neil Andrews, Christian Ruppert
From: Will Porter <mrwillporter@gmail.com>
The iD14 has selectors with a fixed set of device values: output
routing, mono mode, the iD key function, talkback source and the
digital input mode. Add an enumerated control type with a value list.
The item list has one item more than the values. A put cannot select
it. A get shows it for a device value outside the list, so a value set
by other means stays visible as such, not as a wrong item, and for a
write-only value that no write has confirmed.
Signed-off-by: Will Porter <mrwillporter@gmail.com>
---
sound/usb/mixer_audient.c | 126 ++++++++++++++++++++++++++++++++++++++
1 file changed, 126 insertions(+)
diff --git a/sound/usb/mixer_audient.c b/sound/usb/mixer_audient.c
index 771a45cf6b45..2e1d4809645f 100644
--- a/sound/usb/mixer_audient.c
+++ b/sound/usb/mixer_audient.c
@@ -42,6 +42,25 @@ static const DECLARE_TLV_DB_MINMAX_MUTE(snd_audient_tlv_mixer, -12800, 600);
#define SND_AUDIENT_CTYPE_FLAGS_GET_BROKEN BIT(2)
#define SND_AUDIENT_CTYPE_FLAGS_CACHE BIT(3)
+/*
+ * 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.
+ */
+struct snd_audient_enum {
+ const char *const *texts;
+ const u32 *values;
+ unsigned int items;
+};
+
+#define SND_AUDIENT_ENUM(name, texts_, ...) \
+ static const u32 name##_values[] = { __VA_ARGS__ }; \
+ static const struct snd_audient_enum name = { \
+ .texts = texts_, \
+ .values = name##_values, \
+ .items = ARRAY_SIZE(name##_values), \
+ }; \
+ static_assert(ARRAY_SIZE(texts_) == ARRAY_SIZE(name##_values))
+
static const struct snd_audient_ctrl_type {
snd_ctl_elem_type_t type;
unsigned long flags;
@@ -54,6 +73,7 @@ static const struct snd_audient_ctrl_type {
} max; /* for booleans holds physical int value for true */
u32 init; /* hardware and cache are initialized to this value */
unsigned int res; /* integers: device units per control step, 0 = 1 */
+ const struct snd_audient_enum *enumeration;
const unsigned int *tlv;
u8 unit;
u8 sel;
@@ -537,12 +557,114 @@ static int snd_ctl_audient_integer_set(struct snd_kcontrol *kctl,
return changed;
}
+static unsigned int snd_audient_enum_item(const struct snd_audient_enum *e,
+ u32 value)
+{
+ unsigned int item;
+
+ for (item = 0; item < e->items; item++)
+ if (e->values[item] == value)
+ break;
+
+ return item; /* e->items: not in the list */
+}
+
+static int snd_ctl_audient_enum_get(struct snd_kcontrol *kctl,
+ struct snd_ctl_elem_value *ctl_val)
+{
+ struct usb_mixer_elem_info *const elem = snd_kcontrol_chip(kctl);
+ struct usb_mixer_interface *const mixer = elem->head.mixer;
+
+ const unsigned int nchan = snd_audient_kctl_channels(kctl);
+ const unsigned int hwchan = snd_audient_kctl_hwchannels(kctl);
+ const struct snd_audient_ctrl_type *const ctype
+ = snd_audient_kctl_type(kctl);
+ const u8 base = snd_audient_kctl_cidxoffs(kctl);
+
+ unsigned int i;
+
+ CLASS(snd_usb_lock, pm)(mixer->chip);
+ if (pm.err < 0)
+ return pm.err;
+ /* serializes the transfers and the cache, also between gets */
+ guard(mutex)(&mixer->lock);
+
+ for (i = 0; i < hwchan; i++) {
+ unsigned int item;
+ u32 val;
+ int ret;
+
+ ret = snd_audient_raw_get_one(elem, ctype, base + i, &val);
+ if (ret == -ENODATA) /* unknown write-only value: init */
+ val = ctype->init;
+ else if (ret < 0)
+ return ret;
+
+ item = snd_audient_enum_item(ctype->enumeration, val);
+ if (item >= ctype->enumeration->items)
+ return -EIO;
+
+ ctl_val->value.enumerated.item[i] = item;
+ }
+
+ for (; i < nchan; i++)
+ ctl_val->value.enumerated.item[i] = 0;
+
+ return 0;
+}
+
+static int snd_ctl_audient_enum_set(struct snd_kcontrol *kctl,
+ struct snd_ctl_elem_value *ctl_val)
+{
+ struct usb_mixer_elem_info *const elem = snd_kcontrol_chip(kctl);
+ struct usb_mixer_interface *const mixer = elem->head.mixer;
+
+ const unsigned int hwchan = snd_audient_kctl_hwchannels(kctl);
+ const struct snd_audient_ctrl_type *const ctype
+ = snd_audient_kctl_type(kctl);
+ const struct snd_audient_enum *const e = ctype->enumeration;
+ const u8 base = snd_audient_kctl_cidxoffs(kctl);
+
+ unsigned int i;
+ int changed = 0;
+
+ for (i = 0; i < hwchan; i++)
+ if (ctl_val->value.enumerated.item[i] >= e->items)
+ return -EINVAL;
+
+ CLASS(snd_usb_lock, pm)(mixer->chip);
+ if (pm.err < 0)
+ return pm.err;
+ /* serializes the transfers and the cache, also between gets */
+ guard(mutex)(&mixer->lock);
+
+ for (i = 0; i < hwchan; i++) {
+ const unsigned int item = ctl_val->value.enumerated.item[i];
+ int ret;
+
+ ret = snd_audient_raw_set_one(elem, ctype, base + i,
+ e->values[item], false);
+ if (ret < 0)
+ return ret;
+
+ if (ret)
+ changed = 1;
+ }
+
+ return changed;
+}
+
static int snd_ctl_audient_info(struct snd_kcontrol *kctl,
struct snd_ctl_elem_info *uinfo)
{
const struct snd_audient_ctrl_type *const ctype
= snd_audient_kctl_type(kctl);
+ if (ctype->type == SNDRV_CTL_ELEM_TYPE_ENUMERATED)
+ return snd_ctl_enum_info(uinfo, snd_audient_kctl_channels(kctl),
+ ctype->enumeration->items,
+ ctype->enumeration->texts);
+
uinfo->type = ctype->type;
uinfo->count = snd_audient_kctl_channels(kctl);
@@ -590,6 +712,10 @@ static int snd_audient_add_ctrl(struct usb_mixer_interface *mixer,
knew.get = snd_ctl_audient_boolean_get;
knew.put = snd_ctl_audient_boolean_set;
break;
+ case SNDRV_CTL_ELEM_TYPE_ENUMERATED:
+ knew.get = snd_ctl_audient_enum_get;
+ knew.put = snd_ctl_audient_enum_set;
+ break;
default:
return -EINVAL;
}
--
2.56.1
^ permalink raw reply [flat|nested] 31+ messages in thread* [RFC V4 PATCH 14/19] ALSA: usb-audio: Give the Audient iD14 MkI its own mixer map
2026-11-12 2:37 ` [RFC PATCH 0/6] ALSA: mixer_audient: Cache rework and simplifications Will Porter
` (19 preceding siblings ...)
2026-11-15 21:43 ` [RFC V4 PATCH 13/19] ALSA: mixer_audient: Add enumerated controls Christian Ruppert
@ 2026-11-15 21:43 ` Christian Ruppert
2026-11-15 21:43 ` [RFC V4 PATCH 15/19] ALSA: mixer_audient: Add Audient iD14 MkI controls Christian Ruppert
` (4 subsequent siblings)
25 siblings, 0 replies; 31+ messages in thread
From: Christian Ruppert @ 2026-11-15 21:43 UTC (permalink / raw)
To: Will Porter
Cc: Takashi Iwai, Jaroslav Kysela, linux-sound, linux-kernel,
Neil Andrews, Christian Ruppert
From: Will Porter <mrwillporter@gmail.com>
The iD14 MkI (2708:0002) and MkII (2708:0008) share audient_id14_map.
Later commits add MkI vendor controls that replace some of the generic
MkI controls. The MkII keeps the generic controls. Give the MkI its own
copy of the map.
No functional change.
Signed-off-by: Will Porter <mrwillporter@gmail.com>
---
sound/usb/mixer_maps.c | 8 +++++++-
1 file changed, 7 insertions(+), 1 deletion(-)
diff --git a/sound/usb/mixer_maps.c b/sound/usb/mixer_maps.c
index 9218ca6314ad..31f8d0253437 100644
--- a/sound/usb/mixer_maps.c
+++ b/sound/usb/mixer_maps.c
@@ -540,6 +540,12 @@ 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 */
+ {}
+};
+
/*
* Audient iD24: feature unit 12 ("Speaker Playback Volume") sits in the
* monitor-mixer branch and does not apply volume to all of its channels;
@@ -652,7 +658,7 @@ static const struct usbmix_ctl_map usbmix_ctl_maps[] = {
{
/* Audient iD14 MkI */
.id = USB_ID(0x2708, 0x0002),
- .map = audient_id14_map,
+ .map = audient_id14_mk1_map,
},
{
/* Audient EVO4 MkI */
--
2.56.1
^ permalink raw reply [flat|nested] 31+ messages in thread* [RFC V4 PATCH 15/19] ALSA: mixer_audient: Add Audient iD14 MkI controls
2026-11-12 2:37 ` [RFC PATCH 0/6] ALSA: mixer_audient: Cache rework and simplifications Will Porter
` (20 preceding siblings ...)
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 ` 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
` (3 subsequent siblings)
25 siblings, 0 replies; 31+ messages in thread
From: Christian Ruppert @ 2026-11-15 21:43 UTC (permalink / raw)
To: Will Porter
Cc: Takashi Iwai, Jaroslav Kysela, linux-sound, linux-kernel,
Neil Andrews, Christian Ruppert
From: Will Porter <mrwillporter@gmail.com>
The iD14 MkI (2708:0002) has its monitor controller on extension unit
54 and its output routing on extension unit 51. The headphone level is
on feature unit 10 (CN 3-4, which the descriptor does not declare).
The mixer is mixer unit 60. The generic driver cannot find the vendor
controls and creates wrong controls for mixer unit 60 and feature unit
11. Add the MkI as a device with these tables. Apart from the tables
and their macros, the patch adds the device ID, its case in
mixer_quirks.c and the map entries that hide the generic controls.
Monitor and routing controls: speaker volume, cut, dim, dim level,
mono, mono mode, polarity, talkback, headphone volume and mute, and
one routing selector per output channel. All of them read the device
on every get: the front panel knob changes the volumes and the cut and
mute states.
The volumes are s16 values in 1/256 dB from -128 to 0 dB, in the 1 dB
steps of the knob. -128 dB does not mute the headphones, so the TLV
does not declare a mute.
DAW Thru plays the USB stream past the monitor controller, so the main
output selectors name it "DAW Thru (full scale)". The headphone volume
and mute still act on every headphone source, so the headphone
selectors name it "DAW Thru".
Mixer matrix: mixer unit 60 mixes 14 inputs (Mic 1-2, Digi 1-8, DAW
1-4) into four outputs (Main L/R, Cue L/R), CN = input * 4 + output.
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, as on
the EVO4, so they use the same control type. Add one stereo control
per input and output pair: "Mix Main <input> Playback Volume" and "Mix
Cue <input> Playback Volume". Hide the generic controls in the MkI
map. The driver reads each crosspoint at probe and writes nothing to
the mixer.
Write-only controls: some MkI controls cannot be read back:
- GET stalls for the input gain boost and polarity (feature unit 11),
the iD key function and the digital input mode.
- GET of the talkback source answers 0, whatever value a SET wrote.
For FU 11 the generic driver creates "Input Gain Control" and "Phase
Inverter Control" and logs "cannot get min/max values". Add:
- Mic 1/2 Boost Capture Switch (+10 dB)
- Mic 1/2 and Digi 1/2 Polarity Invert Capture Switch
- iD Key Function Playback Enum (ScrollControl, Mono, Mono + Polarity,
Dim, Talkback)
- Talkback Source Capture Enum (None, Mic 1-2, Digi 1-8)
- Digital In Mode Capture Enum (ADAT, S/PDIF)
and hide the generic FU 11 controls in the MkI map. The device refuses
polarity on Digi 3-8 with EPIPE, in ADAT and in S/PDIF mode, so Digi
3-8 have no polarity control.
These controls use the cache, init and force-set flags: they show the
last value written, every put sends its value, and the probe writes
each one once so that the value shown is true:
- boost and polarity off, iD key ScrollControl, talkback source None
- S/PDIF: the MkI's measured power-on mode
If a probe write fails, the card still registers: the control shows
"Unknown" (or off) until a put succeeds.
The probe write replaces any value the device kept. On mains power the
device keeps its values across a replug, so the controls return to the
probe values until alsactl restore runs.
Signed-off-by: Will Porter <mrwillporter@gmail.com>
---
sound/usb/mixer_audient.c | 311 ++++++++++++++++++++++++++++++++++++++
sound/usb/mixer_audient.h | 1 +
sound/usb/mixer_maps.c | 2 +
sound/usb/mixer_quirks.c | 3 +
4 files changed, 317 insertions(+)
diff --git a/sound/usb/mixer_audient.c b/sound/usb/mixer_audient.c
index 2e1d4809645f..30e1309cec3a 100644
--- a/sound/usb/mixer_audient.c
+++ b/sound/usb/mixer_audient.c
@@ -3,6 +3,10 @@
* Audient driver for ALSA
* Copyright (C) 2026 Christian Ruppert <arc@gmx.li>
*
+ * Supported models: EVO4 (2708:0006), iD14 MkI (2708:0002).
+ * The iD14 MkII and other iD models keep the generic driver: their units
+ * may differ.
+ *
* EVO4 support is based on the work of Ivan Hazucha for audient-evo-py.
* https://github.com/vanzaho/audient-evo-py/
*/
@@ -10,6 +14,7 @@
#include <asm/byteorder.h>
#include <linux/usb.h>
+#include <linux/usb/audio.h>
#include <linux/usb/audio-v2.h>
#include <linux/slab.h>
#include <linux/types.h>
@@ -31,6 +36,25 @@ enum snd_audient_type {
SND_AUDIENT_TYPE_MUTE,
SND_AUDIENT_TYPE_MUTE_OUT,
SND_AUDIENT_TYPE_MIXER,
+ SND_AUDIENT_TYPE_ID14_MON_VOLUME,
+ SND_AUDIENT_TYPE_ID14_MON_CUT,
+ SND_AUDIENT_TYPE_ID14_MON_DIM,
+ SND_AUDIENT_TYPE_ID14_MON_DIM_LEVEL,
+ SND_AUDIENT_TYPE_ID14_MON_MONO,
+ SND_AUDIENT_TYPE_ID14_MON_MONO_MODE,
+ SND_AUDIENT_TYPE_ID14_MON_POLARITY,
+ SND_AUDIENT_TYPE_ID14_MON_TALKBACK,
+ SND_AUDIENT_TYPE_ID14_HP_VOLUME,
+ SND_AUDIENT_TYPE_ID14_HP_MUTE,
+ SND_AUDIENT_TYPE_ID14_ROUTE_MAIN_L,
+ SND_AUDIENT_TYPE_ID14_ROUTE_MAIN_R,
+ SND_AUDIENT_TYPE_ID14_ROUTE_HP_L,
+ SND_AUDIENT_TYPE_ID14_ROUTE_HP_R,
+ SND_AUDIENT_TYPE_ID14_IN_BOOST,
+ SND_AUDIENT_TYPE_ID14_IN_POLARITY,
+ SND_AUDIENT_TYPE_ID14_MON_ID_KEY,
+ SND_AUDIENT_TYPE_ID14_MON_TALKBACK_SRC,
+ SND_AUDIENT_TYPE_ID14_OPTICAL_MODE,
SND_AUDIENT_TYPE_NCTYPES
};
@@ -61,6 +85,149 @@ struct snd_audient_enum {
}; \
static_assert(ARRAY_SIZE(texts_) == ARRAY_SIZE(name##_values))
+/*
+ * iD14 MkI units. The monitor controller, output routing, headphone level,
+ * input options and the mixer sit on extension units and on feature units
+ * whose descriptors declare no controls; the generic driver cannot find them.
+ */
+#define ID14_IT_DIGITAL 0x01 /* input terminal: optical and coaxial */
+#define ID14_FU_OUT 0x0a /* before the outputs: CN 3-4 headphones */
+#define ID14_FU_IN 0x0b /* inputs: CN 1-2 Mic, 3-10 Digi */
+#define ID14_FU_MIX 0x0c /* after the mixer: CN 1-2 Main, 3-4 Cue */
+#define ID14_ROUTE 0x33 /* routing XU: CN = destination */
+#define ID14_MON 0x36 /* monitor controller XU */
+
+/* Monitor controller selectors */
+#define ID14_MON_MONO 0x00
+#define ID14_MON_MONO_MODE 0x01
+#define ID14_MON_POLARITY 0x03
+#define ID14_MON_CUT 0x04
+#define ID14_MON_DIM 0x05
+#define ID14_MON_DIM_LEVEL 0x06 /* s16, 1/256 dB */
+#define ID14_MON_TALKBACK 0x07
+#define ID14_MON_TALKBACK_SRC 0x08 /* GET always returns 0 */
+#define ID14_MON_ID_KEY 0x10 /* u16: the selector the iD key toggles */
+#define ID14_MON_VOLUME 0x12 /* s16, 1/256 dB; the knob also sets it */
+
+#define ID14_IN_BOOST 0x0b /* s16: 0x0a00 = +10 dB, 0 = off */
+#define ID14_IN_POLARITY 0x0d
+#define ID14_IT_OPTICAL_MODE 0x00 /* u32: 0 ADAT, 1 S/PDIF */
+
+#define ID14_ROUTE_SOURCE 0x06 /* u8: source of the destination */
+
+/*
+ * iD14 volumes: s16 in 1/256 dB, -128 ... 0 dB, in the 1 dB steps of the
+ * front panel knob. Control value 0 is -128 dB, 128 is 0 dB. -128 dB is not
+ * a mute: on the headphones the level stops falling at a small residual.
+ */
+static const DECLARE_TLV_DB_SCALE(snd_audient_tlv_id14_vol, -12800, 100, 0);
+
+/* Mono mode: one-speaker sum (Left, Right) or phantom centre */
+static const char *const snd_audient_id14_mono_mode_texts[] = {
+ "Left", "Centre", "Right",
+};
+SND_AUDIENT_ENUM(snd_audient_id14_mono_mode,
+ snd_audient_id14_mono_mode_texts, 1, 0, 2);
+
+/*
+ * Output routing. Main Mix is 0x14/0x15 on the main outputs and 0x18/0x19
+ * on the headphones; DAW Thru on destination n plays DAW n + 1. DAW Thru
+ * bypasses the monitor controller (speaker volume, cut and dim), so the
+ * main outputs play the USB stream at full scale. The headphone volume and
+ * mute still act on every headphone source.
+ */
+static const char *const snd_audient_id14_route_main_texts[] = {
+ "Main Mix", "Cue Mix", "DAW Thru (full scale)",
+};
+static const char *const snd_audient_id14_route_hp_texts[] = {
+ "Main Mix", "Cue Mix", "DAW Thru",
+};
+SND_AUDIENT_ENUM(snd_audient_id14_route_main_l,
+ snd_audient_id14_route_main_texts, 0x14, 0x10, 0x00);
+SND_AUDIENT_ENUM(snd_audient_id14_route_main_r,
+ snd_audient_id14_route_main_texts, 0x15, 0x11, 0x01);
+SND_AUDIENT_ENUM(snd_audient_id14_route_hp_l,
+ snd_audient_id14_route_hp_texts, 0x18, 0x10, 0x02);
+SND_AUDIENT_ENUM(snd_audient_id14_route_hp_r,
+ snd_audient_id14_route_hp_texts, 0x19, 0x11, 0x03);
+
+/*
+ * iD key function: the value is the monitor controller selector the key
+ * toggles. ScrollControl (0xffff) toggles nothing: the knob then scrolls
+ * through the HID interface.
+ */
+static const char *const snd_audient_id14_id_key_texts[] = {
+ "ScrollControl", "Mono", "Mono + Polarity", "Dim", "Talkback",
+};
+SND_AUDIENT_ENUM(snd_audient_id14_id_key, snd_audient_id14_id_key_texts,
+ 0xffff, ID14_MON_MONO, ID14_MON_POLARITY, ID14_MON_DIM,
+ ID14_MON_TALKBACK);
+
+/* Talkback source: the routing source number of the input; none = 0xff */
+static const char *const snd_audient_id14_talkback_texts[] = {
+ "None", "Mic 1", "Mic 2", "Digi 1", "Digi 2", "Digi 3", "Digi 4",
+ "Digi 5", "Digi 6", "Digi 7", "Digi 8",
+};
+SND_AUDIENT_ENUM(snd_audient_id14_talkback, snd_audient_id14_talkback_texts,
+ 0xff, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13);
+
+static const char *const snd_audient_id14_optical_texts[] = {
+ "ADAT", "S/PDIF",
+};
+SND_AUDIENT_ENUM(snd_audient_id14_optical, snd_audient_id14_optical_texts,
+ 0, 1);
+
+/*
+ * Write-only: GET stalls or does not report writes. Show the last value
+ * written, write the init value at probe, and send every put.
+ */
+#define ID14_WO (SND_AUDIENT_CTYPE_FLAGS_CACHE \
+ | SND_AUDIENT_CTYPE_FLAGS_GET_BROKEN \
+ | SND_AUDIENT_CTYPE_FLAGS_INIT_HW)
+
+#define ID14_BOOL(unit_, sel_) \
+ { \
+ .type = SNDRV_CTL_ELEM_TYPE_BOOLEAN, \
+ .len = 1, \
+ .min.integer = 0, \
+ .max.integer = 1, \
+ .unit = (unit_), \
+ .sel = (sel_), \
+ }
+
+/* 1 = cut or mute: shown as a switch that is off */
+#define ID14_BOOL_INV(unit_, sel_) \
+ { \
+ .type = SNDRV_CTL_ELEM_TYPE_BOOLEAN, \
+ .len = 1, \
+ .min.integer = 1, \
+ .max.integer = 0, \
+ .unit = (unit_), \
+ .sel = (sel_), \
+ }
+
+#define ID14_VOLUME(unit_, sel_) \
+ { \
+ .type = SNDRV_CTL_ELEM_TYPE_INTEGER, \
+ .flags = SND_AUDIENT_CTYPE_FLAGS_SIGNED, \
+ .len = 2, \
+ .min.integer = -128 * 256, \
+ .max.integer = 0, \
+ .res = 256, \
+ .tlv = snd_audient_tlv_id14_vol, \
+ .unit = (unit_), \
+ .sel = (sel_), \
+ }
+
+#define ID14_ENUM(unit_, sel_, len_, enum_) \
+ { \
+ .type = SNDRV_CTL_ELEM_TYPE_ENUMERATED, \
+ .len = (len_), \
+ .enumeration = &(enum_), \
+ .unit = (unit_), \
+ .sel = (sel_), \
+ }
+
static const struct snd_audient_ctrl_type {
snd_ctl_elem_type_t type;
unsigned long flags;
@@ -122,6 +289,88 @@ static const struct snd_audient_ctrl_type {
.sel = 0x01,
.tlv = snd_audient_tlv_mixer,
},
+ [SND_AUDIENT_TYPE_ID14_MON_VOLUME] =
+ ID14_VOLUME(ID14_MON, ID14_MON_VOLUME),
+ [SND_AUDIENT_TYPE_ID14_MON_CUT] =
+ ID14_BOOL_INV(ID14_MON, ID14_MON_CUT),
+ [SND_AUDIENT_TYPE_ID14_MON_DIM] =
+ ID14_BOOL(ID14_MON, ID14_MON_DIM),
+ [SND_AUDIENT_TYPE_ID14_MON_DIM_LEVEL] =
+ ID14_VOLUME(ID14_MON, ID14_MON_DIM_LEVEL),
+ [SND_AUDIENT_TYPE_ID14_MON_MONO] =
+ ID14_BOOL(ID14_MON, ID14_MON_MONO),
+ [SND_AUDIENT_TYPE_ID14_MON_MONO_MODE] =
+ ID14_ENUM(ID14_MON, ID14_MON_MONO_MODE, 1,
+ snd_audient_id14_mono_mode),
+ [SND_AUDIENT_TYPE_ID14_MON_POLARITY] =
+ ID14_BOOL(ID14_MON, ID14_MON_POLARITY),
+ [SND_AUDIENT_TYPE_ID14_MON_TALKBACK] =
+ ID14_BOOL(ID14_MON, ID14_MON_TALKBACK),
+ [SND_AUDIENT_TYPE_ID14_HP_VOLUME] =
+ ID14_VOLUME(ID14_FU_OUT, UAC_FU_VOLUME),
+ [SND_AUDIENT_TYPE_ID14_HP_MUTE] =
+ ID14_BOOL_INV(ID14_FU_OUT, UAC_FU_MUTE),
+ [SND_AUDIENT_TYPE_ID14_ROUTE_MAIN_L] =
+ ID14_ENUM(ID14_ROUTE, ID14_ROUTE_SOURCE, 1,
+ snd_audient_id14_route_main_l),
+ [SND_AUDIENT_TYPE_ID14_ROUTE_MAIN_R] =
+ ID14_ENUM(ID14_ROUTE, ID14_ROUTE_SOURCE, 1,
+ snd_audient_id14_route_main_r),
+ [SND_AUDIENT_TYPE_ID14_ROUTE_HP_L] =
+ ID14_ENUM(ID14_ROUTE, ID14_ROUTE_SOURCE, 1,
+ snd_audient_id14_route_hp_l),
+ [SND_AUDIENT_TYPE_ID14_ROUTE_HP_R] =
+ ID14_ENUM(ID14_ROUTE, ID14_ROUTE_SOURCE, 1,
+ snd_audient_id14_route_hp_r),
+ /* the documented boost is a fixed +10 dB */
+ [SND_AUDIENT_TYPE_ID14_IN_BOOST] = {
+ .type = SNDRV_CTL_ELEM_TYPE_BOOLEAN,
+ .flags = ID14_WO,
+ .len = 2,
+ .min.integer = 0,
+ .max.integer = 10 * 256,
+ .init = 0,
+ .unit = ID14_FU_IN,
+ .sel = ID14_IN_BOOST,
+ },
+ [SND_AUDIENT_TYPE_ID14_IN_POLARITY] = {
+ .type = SNDRV_CTL_ELEM_TYPE_BOOLEAN,
+ .flags = ID14_WO,
+ .len = 1,
+ .min.integer = 0,
+ .max.integer = 1,
+ .init = 0,
+ .unit = ID14_FU_IN,
+ .sel = ID14_IN_POLARITY,
+ },
+ [SND_AUDIENT_TYPE_ID14_MON_ID_KEY] = {
+ .type = SNDRV_CTL_ELEM_TYPE_ENUMERATED,
+ .flags = ID14_WO,
+ .len = 2,
+ .init = 0xffff, /* ScrollControl */
+ .enumeration = &snd_audient_id14_id_key,
+ .unit = ID14_MON,
+ .sel = ID14_MON_ID_KEY,
+ },
+ [SND_AUDIENT_TYPE_ID14_MON_TALKBACK_SRC] = {
+ .type = SNDRV_CTL_ELEM_TYPE_ENUMERATED,
+ .flags = ID14_WO,
+ .len = 1,
+ .init = 0xff, /* None */
+ .enumeration = &snd_audient_id14_talkback,
+ .unit = ID14_MON,
+ .sel = ID14_MON_TALKBACK_SRC,
+ },
+ /* the power-on mode is S/PDIF */
+ [SND_AUDIENT_TYPE_ID14_OPTICAL_MODE] = {
+ .type = SNDRV_CTL_ELEM_TYPE_ENUMERATED,
+ .flags = ID14_WO,
+ .len = 4,
+ .init = 1, /* S/PDIF */
+ .enumeration = &snd_audient_id14_optical,
+ .unit = ID14_IT_DIGITAL,
+ .sel = ID14_IT_OPTICAL_MODE,
+ },
};
@@ -182,6 +431,62 @@ static const char *const snd_audient_evo4_matrix[] = {
NULL
};
+#define ID14_CTRL(name_, type_, offs_, chans_) \
+ { \
+ .name = (name_), \
+ .type = SND_AUDIENT_TYPE_ID14_##type_, \
+ .channels = (chans_), \
+ .hwchannels = (chans_), \
+ .val_offs = (offs_), \
+ }
+
+static const struct snd_audient_ctrl snd_audient_id14_mk1_controls[] = {
+ ID14_CTRL("Monitor Speaker Playback Volume", MON_VOLUME, 0, 1),
+ ID14_CTRL("Monitor Speaker Playback Switch", MON_CUT, 0, 1),
+ ID14_CTRL("Monitor Dim Playback Switch", MON_DIM, 0, 1),
+ ID14_CTRL("Monitor Dim Level Playback Volume", MON_DIM_LEVEL, 0, 1),
+ ID14_CTRL("Monitor Mono Playback Switch", MON_MONO, 0, 1),
+ ID14_CTRL("Monitor Mono Mode Playback Enum", MON_MONO_MODE, 0, 1),
+ ID14_CTRL("Monitor Polarity Invert Playback Switch", MON_POLARITY, 0, 1),
+ ID14_CTRL("Monitor Headphone Playback Volume", HP_VOLUME, 3, 2),
+ ID14_CTRL("Monitor Headphone Playback Switch", HP_MUTE, 3, 2),
+ ID14_CTRL("Talkback Playback Switch", MON_TALKBACK, 0, 1),
+ ID14_CTRL("Main Out L Source Playback Enum", ROUTE_MAIN_L, 0, 1),
+ ID14_CTRL("Main Out R Source Playback Enum", ROUTE_MAIN_R, 1, 1),
+ ID14_CTRL("Headphone L Source Playback Enum", ROUTE_HP_L, 2, 1),
+ ID14_CTRL("Headphone R Source Playback Enum", ROUTE_HP_R, 3, 1),
+ ID14_CTRL("Mic 1 Boost Capture Switch", IN_BOOST, 1, 1),
+ ID14_CTRL("Mic 2 Boost Capture Switch", IN_BOOST, 2, 1),
+ ID14_CTRL("Mic 1 Polarity Invert Capture Switch", IN_POLARITY, 1, 1),
+ ID14_CTRL("Mic 2 Polarity Invert Capture Switch", IN_POLARITY, 2, 1),
+ /* Digi 3-8 refuse polarity (CN 5-10 stall) */
+ ID14_CTRL("Digi 1 Polarity Invert Capture Switch", IN_POLARITY, 3, 1),
+ ID14_CTRL("Digi 2 Polarity Invert Capture Switch", IN_POLARITY, 4, 1),
+ 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),
+ { 0 }, /* sentinel */
+};
+
+/*
+ * iD14 MkI mixer: unit 60, 14 inputs x 4 outputs, CN = input * 4 + output
+ * (outputs Main L, Main R, Cue L, Cue R). Each input has a Main and a Cue
+ * pair, so with 2 channels per element, element n starts at CN 2 * n.
+ */
+#define ID14_MIX(in) "Mix Main " in " Playback Volume", \
+ "Mix Cue " in " Playback Volume"
+
+static const char *const snd_audient_id14_mk1_matrix[] = {
+ ID14_MIX("Mic 1"), ID14_MIX("Mic 2"),
+ ID14_MIX("Digi 1"), ID14_MIX("Digi 2"),
+ ID14_MIX("Digi 3"), ID14_MIX("Digi 4"),
+ ID14_MIX("Digi 5"), ID14_MIX("Digi 6"),
+ ID14_MIX("Digi 7"), ID14_MIX("Digi 8"),
+ ID14_MIX("DAW 1"), ID14_MIX("DAW 2"),
+ ID14_MIX("DAW 3"), ID14_MIX("DAW 4"),
+ NULL
+};
+
static const struct audient_devinfo {
u32 usb_id;
const struct snd_audient_ctrl *controls;
@@ -194,6 +499,12 @@ static const struct audient_devinfo {
.matrix_names = snd_audient_evo4_matrix,
.matrix_channels = 2,
},
+ {
+ .usb_id = USB_ID(USB_AUDIENT_VID, USB_ID14_MK1_PID),
+ .controls = snd_audient_id14_mk1_controls,
+ .matrix_names = snd_audient_id14_mk1_matrix,
+ .matrix_channels = 2,
+ },
{ 0 } /* sentinel */
};
diff --git a/sound/usb/mixer_audient.h b/sound/usb/mixer_audient.h
index db4f31e02b5e..5d705ffe2cfa 100644
--- a/sound/usb/mixer_audient.h
+++ b/sound/usb/mixer_audient.h
@@ -6,6 +6,7 @@
#define USB_AUDIENT_VID (0x2708)
#define USB_EVO4_PID (0x0006)
+#define USB_ID14_MK1_PID (0x0002)
int snd_audient_controls_create(struct usb_mixer_interface *mixer);
diff --git a/sound/usb/mixer_maps.c b/sound/usb/mixer_maps.c
index 31f8d0253437..b88244b12a98 100644
--- a/sound/usb/mixer_maps.c
+++ b/sound/usb/mixer_maps.c
@@ -542,7 +542,9 @@ 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[] = {
+ { 11, NULL }, /* FU, see mixer_audient.c */
{ 12, "Monitor Mix Playback" }, /* FU, partial coverage */
+ { 60, NULL }, /* MU, see mixer_audient.c */
{}
};
diff --git a/sound/usb/mixer_quirks.c b/sound/usb/mixer_quirks.c
index c250ebea5af5..f06839b89fe1 100644
--- a/sound/usb/mixer_quirks.c
+++ b/sound/usb/mixer_quirks.c
@@ -4783,6 +4783,9 @@ int snd_usb_mixer_apply_create_quirk(struct usb_mixer_interface *mixer)
case USB_ID(USB_AUDIENT_VID, USB_EVO4_PID): /* Audient EVO 4 */
err = snd_audient_controls_create(mixer);
break;
+ case USB_ID(USB_AUDIENT_VID, USB_ID14_MK1_PID): /* Audient iD14 MkI */
+ err = snd_audient_controls_create(mixer);
+ break;
case USB_ID(0x041e, 0x323b): /* Creative Sound Blaster E1 */
err = snd_soundblaster_e1_switch_create(mixer);
--
2.56.1
^ permalink raw reply [flat|nested] 31+ messages in thread* [RFC V4 PATCH 16/19] ALSA: mixer_audient: Replace the Audient iD14 MkI FU 12 control
2026-11-12 2:37 ` [RFC PATCH 0/6] ALSA: mixer_audient: Cache rework and simplifications Will Porter
` (21 preceding siblings ...)
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 ` 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
` (2 subsequent siblings)
25 siblings, 0 replies; 31+ messages in thread
From: Christian Ruppert @ 2026-11-15 21:43 UTC (permalink / raw)
To: Will Porter
Cc: Takashi Iwai, Jaroslav Kysela, linux-sound, linux-kernel,
Neil Andrews, Christian Ruppert
From: Will Porter <mrwillporter@gmail.com>
Feature unit 12 sets the level of the mixer outputs: channels 1-2 Main,
3-4 Cue. Two firmware faults make the generic control wrong on the
MkI:
- GET CUR does not return the FU 12 value. It returns a reply buffer
that the device shares with FU 10, MU 60 and XU 51. The buffer holds
the data of the last GET or SET to any of them. So the generic
sticky-GET check passes, because GET echoes the SET just sent. The
check then writes the stale value that it read first to channel 1.
- Main L ignores its level, but Main R applies it. So any Main value
other than 0 dB unbalances the main outputs. audient_id14_map already
records this asymmetry. A loopback cable measured it: Main R at
-20 dB and -40 dB changes its output by -20.0 and -40.9 dB. Main L
at the same values changes nothing. Cue L and Cue R both work: Cue L
follows the set value within about 1 dB down to at least -70 dB; the
headphone residual hides lower levels.
Replace the generic control with one of the same name, count and range.
It is "Monitor Mix Playback Volume", with four values from 0 to 127 in
1 dB steps. 127 is 0 dB. Value 0 writes 0x8000, the floor that
Audient's iD application writes, and the TLV declares it as mute. On
Cue L, value 0 gives no output above the fixed headphone residual,
about 76 dB below 0 dB. The four values keep the Main L/R, Cue L/R
layout of existing saved ALSA states.
The control holds Main at 0 dB, as the iD application does. (The
application has no Main fader and writes 0 dB at launch.) A put always
sends 0 dB to channels 1-2 and returns that value to the caller. Get
reports it too. For Cue the control shows the last value the device
accepted. At probe the driver writes the iD application's launch values
once per channel: Main 0 dB, Cue muted. A channel whose probe write
fails reads as mute until a put succeeds.
The iD14 MkI profile in alsa-ucm-conf writes 127,127,127,127 to this
control at boot and when its HiFi verb starts. The new control accepts
that write: Main stays at 0 dB and Cue goes to 0 dB, the same result as
with the generic control.
Differences from the generic control:
- Value 0 is now mute. It was -127 dB.
- The control holds Main at 0 dB. A Main R attenuation stored with the
generic control (for example 107, -20 dB) now restores as 0 dB, which
raises Main R by 20 dB. Main L never applied that attenuation, so
Main R comes up to the level that Main L already had. FU 12 comes
before the monitor controller (XU 54) and the headphone level
(FU 10), so the volume knob still sets the listening level.
- Cue starts muted at every probe. An output routed to the Cue Mix
keeps that routing across a module reload or a rebind. On mains
power it also keeps it across a replug. If the probe writes succeed,
that output is then muted until user space sets the Cue level
(alsactl restore or the UCM boot write). On bus power the MkI returns
to DAW Thru when it enumerates, so no output is on the Cue Mix at
probe.
The control does not fit the generic types, so the device table gains
a create hook for controls the tables cannot describe, called after
the tables.
Signed-off-by: Will Porter <mrwillporter@gmail.com>
---
sound/usb/mixer_audient.c | 167 ++++++++++++++++++++++++++++++++++++++
sound/usb/mixer_maps.c | 2 +-
2 files changed, 168 insertions(+), 1 deletion(-)
diff --git a/sound/usb/mixer_audient.c b/sound/usb/mixer_audient.c
index 30e1309cec3a..e86f0e1ec4f0 100644
--- a/sound/usb/mixer_audient.c
+++ b/sound/usb/mixer_audient.c
@@ -487,11 +487,15 @@ 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;
+ /* controls the tables cannot describe; called after the tables */
+ int (*create)(struct usb_mixer_interface *mixer);
} audient_devinfo[] = {
{
.usb_id = USB_ID(USB_AUDIENT_VID, USB_EVO4_PID),
@@ -504,6 +508,7 @@ static const struct audient_devinfo {
.controls = snd_audient_id14_mk1_controls,
.matrix_names = snd_audient_id14_mk1_matrix,
.matrix_channels = 2,
+ .create = snd_audient_id14_mk1_create,
},
{ 0 } /* sentinel */
};
@@ -1079,6 +1084,165 @@ 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)
{
@@ -1118,5 +1282,8 @@ int snd_audient_controls_create(struct usb_mixer_interface *mixer)
}
}
+ if (info->create)
+ return info->create(mixer);
+
return 0;
}
diff --git a/sound/usb/mixer_maps.c b/sound/usb/mixer_maps.c
index b88244b12a98..0ff180e450b5 100644
--- a/sound/usb/mixer_maps.c
+++ b/sound/usb/mixer_maps.c
@@ -543,7 +543,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[] = {
{ 11, NULL }, /* FU, see mixer_audient.c */
- { 12, "Monitor Mix Playback" }, /* FU, partial coverage */
+ { 12, NULL }, /* FU, see mixer_audient.c */
{ 60, NULL }, /* MU, see mixer_audient.c */
{}
};
--
2.56.1
^ permalink raw reply [flat|nested] 31+ messages in thread* [RFC V4 PATCH 17/19] ALSA: mixer_audient: Fold in the review changes to patches 12-16
2026-11-12 2:37 ` [RFC PATCH 0/6] ALSA: mixer_audient: Cache rework and simplifications Will Porter
` (22 preceding siblings ...)
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 ` 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 ` [RFC V4 PATCH 19/19] ALSA: mixer_audient: Refactor FU12 special cases into generic set/get functions Christian Ruppert
25 siblings, 0 replies; 31+ messages in thread
From: Christian Ruppert @ 2026-11-15 21:43 UTC (permalink / raw)
To: Will Porter
Cc: Takashi Iwai, Jaroslav Kysela, linux-sound, linux-kernel,
Neil Andrews, Christian Ruppert
From: Will Porter <mrwillporter@gmail.com>
This patch is not for merging. It carries the changes from the review
of RFC patches 12 to 16 as one diff, so that the patches after it apply
to the RFC with the enum fix. The next version of the series has these
changes in patches 12, 13, 15 and 16.
- Declare snd_audient_int_max(), snd_audient_int_min() and
snd_audient_int_from_ctl() inline.
- snd_audient_enum_item() returns the item or -EIO, and get propagates
the error.
- snd_ctl_audient_info() uses a switch.
- A comment on ID14_ENUM() says that readable enums do not use init.
- The two case labels in mixer_quirks.c share one call.
- Rename the create hook to extra_init and say that it is optional.
Signed-off-by: Will Porter <mrwillporter@gmail.com>
---
sound/usb/mixer_audient.c | 58 +++++++++++++++++++++++----------------
sound/usb/mixer_quirks.c | 2 --
2 files changed, 34 insertions(+), 26 deletions(-)
diff --git a/sound/usb/mixer_audient.c b/sound/usb/mixer_audient.c
index e86f0e1ec4f0..f144fc549c88 100644
--- a/sound/usb/mixer_audient.c
+++ b/sound/usb/mixer_audient.c
@@ -219,6 +219,11 @@ SND_AUDIENT_ENUM(snd_audient_id14_optical, snd_audient_id14_optical_texts,
.sel = (sel_), \
}
+/*
+ * A readable enum. It has no cache entry, so a get always reads the device
+ * and never uses init. Write-only enums set .init and .flags without this
+ * macro.
+ */
#define ID14_ENUM(unit_, sel_, len_, enum_) \
{ \
.type = SNDRV_CTL_ELEM_TYPE_ENUMERATED, \
@@ -494,8 +499,8 @@ static const struct audient_devinfo {
const struct snd_audient_ctrl *controls;
const char *const *const matrix_names;
const unsigned int matrix_channels;
- /* controls the tables cannot describe; called after the tables */
- int (*create)(struct usb_mixer_interface *mixer);
+ /* 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),
@@ -508,7 +513,7 @@ static const struct audient_devinfo {
.controls = snd_audient_id14_mk1_controls,
.matrix_names = snd_audient_id14_mk1_matrix,
.matrix_channels = 2,
- .create = snd_audient_id14_mk1_create,
+ .extra_init = snd_audient_id14_mk1_create,
},
{ 0 } /* sentinel */
};
@@ -759,13 +764,15 @@ static int snd_ctl_audient_boolean_set(struct snd_kcontrol *kctl,
* With a step size (res), an integer control counts steps from min:
* 0 ... (max - min) / res. Without one it shows the device value.
*/
-static long snd_audient_int_max(const struct snd_audient_ctrl_type *ctype)
+static inline long
+snd_audient_int_max(const struct snd_audient_ctrl_type *ctype)
{
return ctype->res ? (ctype->max.integer - ctype->min.integer) / ctype->res
: ctype->max.integer;
}
-static long snd_audient_int_min(const struct snd_audient_ctrl_type *ctype)
+static inline long
+snd_audient_int_min(const struct snd_audient_ctrl_type *ctype)
{
return ctype->res ? 0 : ctype->min.integer;
}
@@ -782,8 +789,8 @@ static long snd_audient_int_to_ctl(const struct snd_audient_ctrl_type *ctype,
snd_audient_int_max(ctype));
}
-static long snd_audient_int_from_ctl(const struct snd_audient_ctrl_type *ctype,
- long val)
+static inline long
+snd_audient_int_from_ctl(const struct snd_audient_ctrl_type *ctype, long val)
{
return ctype->res ? ctype->min.integer + val * ctype->res : val;
}
@@ -873,16 +880,16 @@ static int snd_ctl_audient_integer_set(struct snd_kcontrol *kctl,
return changed;
}
-static unsigned int snd_audient_enum_item(const struct snd_audient_enum *e,
- u32 value)
+/* The item index of a device value, or -EIO if the value is not in the list */
+static int snd_audient_enum_item(const struct snd_audient_enum *e, u32 value)
{
unsigned int item;
for (item = 0; item < e->items; item++)
if (e->values[item] == value)
- break;
+ return item;
- return item; /* e->items: not in the list */
+ return -EIO;
}
static int snd_ctl_audient_enum_get(struct snd_kcontrol *kctl,
@@ -906,8 +913,8 @@ static int snd_ctl_audient_enum_get(struct snd_kcontrol *kctl,
guard(mutex)(&mixer->lock);
for (i = 0; i < hwchan; i++) {
- unsigned int item;
u32 val;
+ int item;
int ret;
ret = snd_audient_raw_get_one(elem, ctype, base + i, &val);
@@ -917,8 +924,8 @@ static int snd_ctl_audient_enum_get(struct snd_kcontrol *kctl,
return ret;
item = snd_audient_enum_item(ctype->enumeration, val);
- if (item >= ctype->enumeration->items)
- return -EIO;
+ if (item < 0)
+ return item;
ctl_val->value.enumerated.item[i] = item;
}
@@ -976,22 +983,25 @@ static int snd_ctl_audient_info(struct snd_kcontrol *kctl,
const struct snd_audient_ctrl_type *const ctype
= snd_audient_kctl_type(kctl);
- if (ctype->type == SNDRV_CTL_ELEM_TYPE_ENUMERATED)
- return snd_ctl_enum_info(uinfo, 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);
-
- uinfo->type = ctype->type;
- uinfo->count = snd_audient_kctl_channels(kctl);
-
- if (ctype->type == SNDRV_CTL_ELEM_TYPE_BOOLEAN) {
+ case SNDRV_CTL_ELEM_TYPE_BOOLEAN:
uinfo->value.integer.min = 0;
uinfo->value.integer.max = 1;
- } else {
+ break;
+ default:
uinfo->value.integer.min = snd_audient_int_min(ctype);
uinfo->value.integer.max = snd_audient_int_max(ctype);
+ break;
}
+ uinfo->type = ctype->type;
+ uinfo->count = snd_audient_kctl_channels(kctl);
+
return 0;
}
@@ -1282,8 +1292,8 @@ int snd_audient_controls_create(struct usb_mixer_interface *mixer)
}
}
- if (info->create)
- return info->create(mixer);
+ if (info->extra_init)
+ return info->extra_init(mixer);
return 0;
}
diff --git a/sound/usb/mixer_quirks.c b/sound/usb/mixer_quirks.c
index f06839b89fe1..c68aa6f17e45 100644
--- a/sound/usb/mixer_quirks.c
+++ b/sound/usb/mixer_quirks.c
@@ -4781,8 +4781,6 @@ int snd_usb_mixer_apply_create_quirk(struct usb_mixer_interface *mixer)
break;
case USB_ID(USB_AUDIENT_VID, USB_EVO4_PID): /* Audient EVO 4 */
- err = snd_audient_controls_create(mixer);
- break;
case USB_ID(USB_AUDIENT_VID, USB_ID14_MK1_PID): /* Audient iD14 MkI */
err = snd_audient_controls_create(mixer);
break;
--
2.56.1
^ permalink raw reply [flat|nested] 31+ messages in thread* [RFC V4 PATCH 18/19] ALSA: mixer_audient: Unify snd_audient_*_set/get functions for different types
2026-11-12 2:37 ` [RFC PATCH 0/6] ALSA: mixer_audient: Cache rework and simplifications Will Porter
` (23 preceding siblings ...)
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 ` Christian Ruppert
2026-11-15 21:43 ` [RFC V4 PATCH 19/19] ALSA: mixer_audient: Refactor FU12 special cases into generic set/get functions Christian Ruppert
25 siblings, 0 replies; 31+ messages in thread
From: Christian Ruppert @ 2026-11-15 21:43 UTC (permalink / raw)
To: Will Porter
Cc: Takashi Iwai, Jaroslav Kysela, linux-sound, linux-kernel,
Neil Andrews, Christian Ruppert
The set/get functions were originally type specific. With more and more
types needed we reached the threshold where it makes sense to merge them
into one function for get and one for set and handle the type differences
in a switch statement. Perform this merge to reduce code duplication.
Signed-off-by: Christian Ruppert <arc@gmx.li>
---
sound/usb/mixer_audient.c | 294 ++++++++++++--------------------------
1 file changed, 88 insertions(+), 206 deletions(-)
diff --git a/sound/usb/mixer_audient.c b/sound/usb/mixer_audient.c
index f144fc549c88..13940572de3f 100644
--- a/sound/usb/mixer_audient.c
+++ b/sound/usb/mixer_audient.c
@@ -683,83 +683,6 @@ static int snd_audient_raw_set_one(struct usb_mixer_elem_info *const elem,
}
-static int snd_ctl_audient_boolean_get(struct snd_kcontrol *kctl,
- struct snd_ctl_elem_value *ctl_val)
-{
- struct usb_mixer_elem_info *const elem = snd_kcontrol_chip(kctl);
- struct usb_mixer_interface *const mixer = elem->head.mixer;
-
- const unsigned int nchan = snd_audient_kctl_channels(kctl);
- const unsigned int hwchan = snd_audient_kctl_hwchannels(kctl);
- const struct snd_audient_ctrl_type *const ctype
- = snd_audient_kctl_type(kctl);
- const u8 base = snd_audient_kctl_cidxoffs(kctl);
-
- int i;
-
- CLASS(snd_usb_lock, pm)(mixer->chip);
- if (pm.err < 0)
- return pm.err;
- /* serializes the transfers and the cache, also between gets */
- guard(mutex)(&mixer->lock);
-
- for (i = 0; i < hwchan; i++) {
- u32 buf;
- int ret;
-
- ret = snd_audient_raw_get_one(elem, ctype, base + i, &buf);
- if (ret == -ENODATA) /* unknown write-only value: off */
- buf = ctype->min.integer;
- else if (ret < 0)
- return ret;
-
- ctl_val->value.integer.value[i] = (buf == ctype->min.integer)
- ? false : true;
- }
-
- for (; i < nchan; i++)
- ctl_val->value.integer.value[i] = 0;
-
- return 0;
-}
-
-static int snd_ctl_audient_boolean_set(struct snd_kcontrol *kctl,
- struct snd_ctl_elem_value *ctl_val)
-{
- struct usb_mixer_elem_info *const elem = snd_kcontrol_chip(kctl);
- struct usb_mixer_interface *const mixer = elem->head.mixer;
-
- const unsigned int hwchan = snd_audient_kctl_hwchannels(kctl);
- const struct snd_audient_ctrl_type *const ctype
- = snd_audient_kctl_type(kctl);
- const u8 base = snd_audient_kctl_cidxoffs(kctl);
-
- int changed = 0;
- int i;
-
- CLASS(snd_usb_lock, pm)(mixer->chip);
- if (pm.err < 0)
- return pm.err;
- /* serializes the transfers and the cache, also between gets */
- guard(mutex)(&mixer->lock);
-
- for (i = 0; i < hwchan; i++) {
- u32 buf = ctl_val->value.integer.value[i] ? ctype->max.integer
- : ctype->min.integer;
- int ret;
-
- ret = snd_audient_raw_set_one(elem, ctype, base + i, buf,
- false);
- if (ret < 0)
- return ret;
-
- if (ret)
- changed = 1;
- }
-
- return changed;
-}
-
/*
* With a step size (res), an integer control counts steps from min:
* 0 ... (max - min) / res. Without one it shows the device value.
@@ -778,8 +701,9 @@ snd_audient_int_min(const struct snd_audient_ctrl_type *ctype)
}
static long snd_audient_int_to_ctl(const struct snd_audient_ctrl_type *ctype,
- long val)
+ u32 buf)
{
+ s32 val = (s32)buf;
val = clamp(val, ctype->min.integer, ctype->max.integer);
if (!ctype->res)
return val;
@@ -789,96 +713,19 @@ static long snd_audient_int_to_ctl(const struct snd_audient_ctrl_type *ctype,
snd_audient_int_max(ctype));
}
-static inline long
+static inline u32
snd_audient_int_from_ctl(const struct snd_audient_ctrl_type *ctype, long val)
{
- return ctype->res ? ctype->min.integer + val * ctype->res : val;
-}
-
-static int snd_ctl_audient_integer_get(struct snd_kcontrol *kctl,
- struct snd_ctl_elem_value *ctl_val)
-{
- struct usb_mixer_elem_info *const elem = snd_kcontrol_chip(kctl);
- struct usb_mixer_interface *const mixer = elem->head.mixer;
-
- const unsigned int nchan = snd_audient_kctl_channels(kctl);
- const unsigned int hwchan = snd_audient_kctl_hwchannels(kctl);
- const struct snd_audient_ctrl_type *const ctype
- = snd_audient_kctl_type(kctl);
- const u8 base = snd_audient_kctl_cidxoffs(kctl);
-
- int i;
-
- CLASS(snd_usb_lock, pm)(mixer->chip);
- if (pm.err < 0)
- return pm.err;
- /* serializes the transfers and the cache, also between gets */
- guard(mutex)(&mixer->lock);
-
- for (i = 0; i < hwchan; i++) {
- s32 val;
- int ret;
-
- ret = snd_audient_raw_get_one(elem, ctype, base + i, &val);
- if (ret == -ENODATA) /* unknown write-only value: minimum */
- val = ctype->min.integer;
- else if (ret < 0)
- return ret;
+ s32 ret;
- ctl_val->value.integer.value[i] =
- snd_audient_int_to_ctl(ctype, val);
- }
+ if (ctype->res)
+ ret = ctype->min.integer + val * ctype->res;
+ else
+ ret = val;
- for (; i < nchan; i++)
- ctl_val->value.integer.value[i] = snd_audient_int_min(ctype);
-
- return 0;
+ return (u32)ret;
}
-static int snd_ctl_audient_integer_set(struct snd_kcontrol *kctl,
- struct snd_ctl_elem_value *ctl_val)
-{
- struct usb_mixer_elem_info *const elem = snd_kcontrol_chip(kctl);
- struct usb_mixer_interface *mixer = elem->head.mixer;
-
- const unsigned int hwchan = snd_audient_kctl_hwchannels(kctl);
- const struct snd_audient_ctrl_type *const ctype
- = snd_audient_kctl_type(kctl);
- const u8 base = snd_audient_kctl_cidxoffs(kctl);
-
- int changed = 0;
- int i;
-
- for (i = 0; i < hwchan; i++) {
- long val = ctl_val->value.integer.value[i];
-
- if (val < snd_audient_int_min(ctype) ||
- val > snd_audient_int_max(ctype))
- return -EINVAL;
- }
-
- CLASS(snd_usb_lock, pm)(mixer->chip);
- if (pm.err < 0)
- return pm.err;
- /* serializes the transfers and the cache, also between gets */
- guard(mutex)(&mixer->lock);
-
- for (i = 0; i < hwchan; i++) {
- s32 val = snd_audient_int_from_ctl(ctype,
- ctl_val->value.integer.value[i]);
- int ret;
-
- ret = snd_audient_raw_set_one(elem, ctype, base + i, val,
- false);
- if (ret < 0)
- return ret;
-
- if (ret)
- changed = 1;
- }
-
- return changed;
-}
/* The item index of a device value, or -EIO if the value is not in the list */
static int snd_audient_enum_item(const struct snd_audient_enum *e, u32 value)
@@ -892,8 +739,9 @@ static int snd_audient_enum_item(const struct snd_audient_enum *e, u32 value)
return -EIO;
}
-static int snd_ctl_audient_enum_get(struct snd_kcontrol *kctl,
- struct snd_ctl_elem_value *ctl_val)
+
+static int snd_ctl_audient_get(struct snd_kcontrol *kctl,
+ struct snd_ctl_elem_value *ctl_val)
{
struct usb_mixer_elem_info *const elem = snd_kcontrol_chip(kctl);
struct usb_mixer_interface *const mixer = elem->head.mixer;
@@ -904,7 +752,7 @@ static int snd_ctl_audient_enum_get(struct snd_kcontrol *kctl,
= snd_audient_kctl_type(kctl);
const u8 base = snd_audient_kctl_cidxoffs(kctl);
- unsigned int i;
+ int i;
CLASS(snd_usb_lock, pm)(mixer->chip);
if (pm.err < 0)
@@ -912,32 +760,53 @@ static int snd_ctl_audient_enum_get(struct snd_kcontrol *kctl,
/* serializes the transfers and the cache, also between gets */
guard(mutex)(&mixer->lock);
- for (i = 0; i < hwchan; i++) {
- u32 val;
- int item;
- int ret;
+ for (i = 0; i < nchan; i++) {
+ u32 buf;
+ long ret;
- ret = snd_audient_raw_get_one(elem, ctype, base + i, &val);
- if (ret == -ENODATA) /* unknown write-only value: init */
- val = ctype->init;
+ ret = snd_audient_raw_get_one(elem, ctype, base + i, &buf);
+ if (ret == -ENODATA) /* unknown write-only value: off */
+ buf = ctype->init;
else if (ret < 0)
return ret;
- item = snd_audient_enum_item(ctype->enumeration, val);
- if (item < 0)
- return item;
-
- ctl_val->value.enumerated.item[i] = item;
+ switch (ctype->type) {
+ case SNDRV_CTL_ELEM_TYPE_BOOLEAN:
+ if (i >= hwchan)
+ ctl_val->value.integer.value[i] = 0;
+ else if (buf == ctype->min.integer)
+ ctl_val->value.integer.value[i] = false;
+ else
+ ctl_val->value.integer.value[i] = true;
+ break;
+ case SNDRV_CTL_ELEM_TYPE_INTEGER:
+ if (i >= hwchan)
+ ctl_val->value.integer.value[i] =
+ snd_audient_int_min(ctype);
+ else
+ ctl_val->value.integer.value[i] =
+ snd_audient_int_to_ctl(ctype, buf);
+ break;
+ case SNDRV_CTL_ELEM_TYPE_ENUMERATED:
+ if (i >= hwchan) {
+ ctl_val->value.enumerated.item[i] = 0;
+ } else {
+ ret = snd_audient_enum_item(ctype->enumeration,
+ buf);
+ if (ret < 0)
+ return ret;
+
+ ctl_val->value.enumerated.item[i] = ret;
+ }
+ break;
+ }
}
- for (; i < nchan; i++)
- ctl_val->value.enumerated.item[i] = 0;
-
return 0;
}
-static int snd_ctl_audient_enum_set(struct snd_kcontrol *kctl,
- struct snd_ctl_elem_value *ctl_val)
+static int snd_ctl_audient_set(struct snd_kcontrol *kctl,
+ struct snd_ctl_elem_value *ctl_val)
{
struct usb_mixer_elem_info *const elem = snd_kcontrol_chip(kctl);
struct usb_mixer_interface *const mixer = elem->head.mixer;
@@ -945,15 +814,28 @@ static int snd_ctl_audient_enum_set(struct snd_kcontrol *kctl,
const unsigned int hwchan = snd_audient_kctl_hwchannels(kctl);
const struct snd_audient_ctrl_type *const ctype
= snd_audient_kctl_type(kctl);
- const struct snd_audient_enum *const e = ctype->enumeration;
const u8 base = snd_audient_kctl_cidxoffs(kctl);
- unsigned int i;
+ typeof(ctl_val->value) *const v = &ctl_val->value;
+ const struct snd_audient_enum *const e = ctype->enumeration;
+
int changed = 0;
+ int i;
- for (i = 0; i < hwchan; i++)
- if (ctl_val->value.enumerated.item[i] >= e->items)
- return -EINVAL;
+
+ for (i = 0; i < hwchan; i++) {
+ switch (ctype->type) {
+ case SNDRV_CTL_ELEM_TYPE_INTEGER:
+ if (v->integer.value[i] < snd_audient_int_min(ctype) ||
+ v->integer.value[i] > snd_audient_int_max(ctype))
+ return -EINVAL;
+ break;
+ case SNDRV_CTL_ELEM_TYPE_ENUMERATED:
+ if (v->enumerated.item[i] >= e->items)
+ return -EINVAL;
+ break;
+ }
+ }
CLASS(snd_usb_lock, pm)(mixer->chip);
if (pm.err < 0)
@@ -962,11 +844,25 @@ static int snd_ctl_audient_enum_set(struct snd_kcontrol *kctl,
guard(mutex)(&mixer->lock);
for (i = 0; i < hwchan; i++) {
- const unsigned int item = ctl_val->value.enumerated.item[i];
+ u32 val;
int ret;
- ret = snd_audient_raw_set_one(elem, ctype, base + i,
- e->values[item], false);
+ switch (ctype->type) {
+ case SNDRV_CTL_ELEM_TYPE_BOOLEAN:
+ val = v->integer.value[i] ? ctype->max.integer
+ : ctype->min.integer;
+ break;
+ case SNDRV_CTL_ELEM_TYPE_INTEGER:
+ val = snd_audient_int_from_ctl(ctype,
+ v->integer.value[i]);
+ break;
+ case SNDRV_CTL_ELEM_TYPE_ENUMERATED:
+ val = e->values[v->enumerated.item[i]];
+ break;
+ }
+
+ ret = snd_audient_raw_set_one(elem, ctype, base + i, val,
+ false);
if (ret < 0)
return ret;
@@ -1029,22 +925,8 @@ static int snd_audient_add_ctrl(struct usb_mixer_interface *mixer,
: SNDRV_CTL_ELEM_ACCESS_VOLATILE)
| (ctype->tlv ? SNDRV_CTL_ELEM_ACCESS_TLV_READ : 0);
- switch (ctype->type) {
- case SNDRV_CTL_ELEM_TYPE_INTEGER:
- knew.get = snd_ctl_audient_integer_get;
- knew.put = snd_ctl_audient_integer_set;
- break;
- case SNDRV_CTL_ELEM_TYPE_BOOLEAN:
- knew.get = snd_ctl_audient_boolean_get;
- knew.put = snd_ctl_audient_boolean_set;
- break;
- case SNDRV_CTL_ELEM_TYPE_ENUMERATED:
- knew.get = snd_ctl_audient_enum_get;
- knew.put = snd_ctl_audient_enum_set;
- break;
- default:
- return -EINVAL;
- }
+ knew.get = snd_ctl_audient_get;
+ knew.put = snd_ctl_audient_set;
elem = kzalloc_obj(*elem);
if (!elem)
--
2.56.1
^ permalink raw reply [flat|nested] 31+ messages in thread* [RFC V4 PATCH 19/19] ALSA: mixer_audient: Refactor FU12 special cases into generic set/get functions
2026-11-12 2:37 ` [RFC PATCH 0/6] ALSA: mixer_audient: Cache rework and simplifications Will Porter
` (24 preceding siblings ...)
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
25 siblings, 0 replies; 31+ messages in thread
From: Christian Ruppert @ 2026-11-15 21:43 UTC (permalink / raw)
To: Will Porter
Cc: Takashi Iwai, Jaroslav Kysela, linux-sound, linux-kernel,
Neil Andrews, Christian Ruppert
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
^ permalink raw reply [flat|nested] 31+ messages in thread