// SPDX-License-Identifier: GPL-2.0 /* * Audient EVO driver for ALSA * Copyright (C) 2026 Christian Ruppert * * Based on the work of Ivan Hazucha for audient-evo-py. * https://github.com/vanzaho/audient-evo-py/ */ #include #include #include #include #include #include #include "usbaudio.h" #include "mixer.h" #include "mixer_quirks.h" #include "helper.h" #include "mixer_evo.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 }; static const struct snd_evo_ctrl_type { snd_ctl_elem_type_t type; bool invert; union { int integer; } min; union { int integer; } max; u16 wValue; u16 wIndex; } snd_evo_ctypes[SND_EVO_TYPE_NCTYPES] = { [SND_EVO_TYPE_MONITOR] = { .type = SNDRV_CTL_ELEM_TYPE_INTEGER, .min.integer = 0, .max.integer = 127, .wValue = 0x0000, .wIndex = 0x3800, }, [SND_EVO_TYPE_PHANTOM] = { .type = SNDRV_CTL_ELEM_TYPE_BOOLEAN, .invert = false, .min.integer = 0, .max.integer = 1, .wValue = 0x0000, .wIndex = 0x3A00, }, [SND_EVO_TYPE_MUTE] = { .type = SNDRV_CTL_ELEM_TYPE_BOOLEAN, .invert = true, .min.integer = 0, .max.integer = 1, .wValue = 0x0200, .wIndex = 0x3A00, }, [SND_EVO_TYPE_MUTE_OUT] = { .type = SNDRV_CTL_ELEM_TYPE_BOOLEAN, .invert = true, .min.integer = 0, .max.integer = 1, .wValue = 0x0100, .wIndex = 0x3B00, }, [SND_EVO_TYPE_MIXER] = { .type = SNDRV_CTL_ELEM_TYPE_INTEGER, .min.integer = -128 * 256, .max.integer = 6 * 256, .wValue = 0x0100, .wIndex = 0x3C00, }, }; #define SND_EVO_FLAGS_DIRECTION (1L<<0) #define SND_EVO_FLAGS_CAPTURE (1L<<0) #define SND_EVO_FLAGS_PLAYBACK (0L<<0) struct snd_evo_ctrl { char *basename; enum snd_evo_type type; unsigned int channels; unsigned int hwchannels; unsigned int val_offs; unsigned long flags; }; static const struct snd_evo_ctrl evo4_controls[] = { { .basename = "Master", .type = SND_EVO_TYPE_MUTE_OUT, .channels = 1, .hwchannels = 1, .val_offs = 0, .flags = SND_EVO_FLAGS_PLAYBACK, }, { .basename = "Mic", .type = SND_EVO_TYPE_MUTE, .channels = 4, .hwchannels = 2, .val_offs = 0, .flags = SND_EVO_FLAGS_PLAYBACK, }, { .basename = "Monitor", .type = SND_EVO_TYPE_MONITOR, .channels = 1, .hwchannels = 1, .val_offs = 0, .flags = SND_EVO_FLAGS_PLAYBACK, }, { .basename = "Mic 1", .type = SND_EVO_TYPE_MIXER, .channels = 2, .hwchannels = 2, .val_offs = 0, .flags = SND_EVO_FLAGS_CAPTURE, }, { .basename = "Mic 2", .type = SND_EVO_TYPE_MIXER, .channels = 2, .hwchannels = 2, .val_offs = 2, .flags = SND_EVO_FLAGS_CAPTURE, }, { .basename = "Master Left", .type = SND_EVO_TYPE_MIXER, .channels = 2, .hwchannels = 2, .val_offs = 4, .flags = SND_EVO_FLAGS_CAPTURE, }, { .basename = "Master Right", .type = SND_EVO_TYPE_MIXER, .channels = 2, .hwchannels = 2, .val_offs = 6, .flags = SND_EVO_FLAGS_CAPTURE, }, { .basename = "Loopback Left", .type = SND_EVO_TYPE_MIXER, .channels = 2, .hwchannels = 2, .val_offs = 8, .flags = SND_EVO_FLAGS_CAPTURE, }, { .basename = "Loopback Right", .type = SND_EVO_TYPE_MIXER, .channels = 2, .hwchannels = 2, .val_offs = 10, .flags = SND_EVO_FLAGS_CAPTURE, }, { .basename = "Mic 1 Phantom", .type = SND_EVO_TYPE_PHANTOM, .channels = 1, .hwchannels = 1, .val_offs = 0, .flags = SND_EVO_FLAGS_CAPTURE, }, { .basename = "Mic 2 Phantom", .type = SND_EVO_TYPE_PHANTOM, .channels = 1, .hwchannels = 1, .val_offs = 1, .flags = SND_EVO_FLAGS_CAPTURE, }, { 0 }, /* sentinel */ }; static const struct evo_devinfo { u32 usb_id; const struct snd_evo_ctrl *controls; } evo_devinfo[] = { { .usb_id = USB_ID(USB_AUDIENT_VID, USB_EVO4_PID), .controls = evo4_controls, }, { .usb_id = USB_ID(USB_AUDIENT_VID, USB_EVO4_PID), .controls = NULL, }, { 0 } /* sentinel */ }; static inline unsigned long snd_evo_kctl_priv(enum snd_evo_type type, u8 valoffs, u8 hwchannels) { unsigned long type_l = type & 0xFF; unsigned long valoffs_l = valoffs & 0xFF; unsigned long hwchannels_l = hwchannels & 0xFF; return (hwchannels_l << 16) | (valoffs_l << 8) | type_l; } static __always_inline const struct snd_evo_ctrl_type *snd_evo_kctl_type(struct snd_kcontrol *kctl) { return &snd_evo_ctypes[kctl->private_value & 0xFF]; } static __always_inline u16 snd_evo_kctl_valoffs(struct snd_kcontrol *kctl) { return (kctl->private_value >> 8) & 0xFF; } static __always_inline u16 snd_evo_kctl_hwchannels(struct snd_kcontrol *kctl) { return (kctl->private_value >> 16) & 0xFF; } static inline u16 snd_evo_kctl_channels(struct snd_kcontrol *kctl) { struct usb_mixer_elem_info *info = kctl->private_data; return info->channels; } #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) static inline int snd_evo_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, 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) { struct usb_device *const dev = mixer->chip->dev; return snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), EVO_REQ_CUR, EVO_REQTYPE_SET, value, index, buf, len); } static int snd_ctl_evo_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 u16 index = ctype->wIndex; int i; for (i = 0; i < hwchan; i++) { u32 buf; int ret; ret = snd_evo_get_cur(mixer, value + i, index, &buf, sizeof(buf)); if (ret < 0) return ret; ctl_val->value.integer.value[i] = le32_to_cpu(buf) ? 1 : 0; if (ctype->invert) ctl_val->value.integer.value[i] ^= 0x1; } for (; i < nchan; i++) ctl_val->value.integer.value[i] = ctype->invert ? ctype->max.integer : ctype->min.integer; return 0; } static int snd_ctl_evo_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 u16 index = ctype->wIndex; int changed = 0; int i; for (i = 0; i < hwchan; i++) { u32 rbuf; u32 buf = cpu_to_le32(ctl_val->value.integer.value[i] ? 1 : 0); int ret; if (ctype->invert) buf ^= 0x1; ret = snd_evo_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)); if (ret < 0) return ret; } } return changed; } static int snd_ctl_evo_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 u16 index = ctype->wIndex; int i; for (i = 0; i < hwchan; i++) { u16 buf; s16 val; int ret; ret = snd_evo_get_cur(mixer, value + i, index, &buf, sizeof(buf)); if (ret < 0) return ret; val = le16_to_cpu(buf); ctl_val->value.integer.value[i] = val; } for (; i < nchan; i++) ctl_val->value.integer.value[i] = ctype->min.integer; return 0; } static int snd_ctl_evo_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 u16 index = ctype->wIndex; int changed = 0; int i; for (i = 0; i < hwchan; i++) { s16 rbuf; 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)); if (ret < 0) return ret; if (rbuf != buf) { changed = 1; ret = snd_evo_set_cur(mixer, value + i, index, &buf, sizeof(buf)); if (ret < 0) return ret; } } return changed; } static int snd_ctl_evo_info(struct snd_kcontrol *kctl, struct snd_ctl_elem_info *uinfo) { const struct snd_evo_ctrl_type *const ctype = snd_evo_kctl_type(kctl); uinfo->type = ctype->type; uinfo->access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_VOLATILE; uinfo->count = snd_evo_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) { const struct snd_evo_ctrl_type *ctype = &snd_evo_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.info = snd_ctl_evo_info; knew.private_value = snd_evo_kctl_priv(ctrl->type, ctrl->val_offs, ctrl->hwchannels); switch (ctype->type) { case SNDRV_CTL_ELEM_TYPE_INTEGER: knew.get = snd_ctl_evo_integer_get; knew.put = snd_ctl_evo_integer_set; break; case SNDRV_CTL_ELEM_TYPE_BOOLEAN: knew.get = snd_ctl_evo_boolean_get; knew.put = snd_ctl_evo_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", (ctype->type == SNDRV_CTL_ELEM_TYPE_BOOLEAN) ? "Switch" : "Volume"); 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; } kctl->private_free = snd_usb_mixer_elem_free; return snd_usb_mixer_add_control(&elem->head, kctl); } /* called from mixer_quirks.c */ int snd_evo_controls_create(struct usb_mixer_interface *mixer) { const struct evo_devinfo *info; const struct snd_evo_ctrl *ctrl; for (info = evo_devinfo; info->controls; info++) if (info->usb_id == mixer->chip->usb_id) break; if (!info->controls) return -ENODEV; for (ctrl = info->controls; ctrl->basename; ctrl++) { const int ret = snd_evo_add_ctrl(mixer, ctrl); if (ret < 0) return ret; } return 0; }