diff options
Diffstat (limited to 'sound/usb/mixer_quirks.c')
| -rw-r--r-- | sound/usb/mixer_quirks.c | 2576 |
1 files changed, 2221 insertions, 355 deletions
diff --git a/sound/usb/mixer_quirks.c b/sound/usb/mixer_quirks.c index b6bcf2f92383..f7189990dd6a 100644 --- a/sound/usb/mixer_quirks.c +++ b/sound/usb/mixer_quirks.c @@ -14,8 +14,10 @@ * Przemek Rudy (prudy1@o2.pl) */ +#include <linux/bitfield.h> #include <linux/hid.h> #include <linux/init.h> +#include <linux/input.h> #include <linux/math64.h> #include <linux/slab.h> #include <linux/usb.h> @@ -24,6 +26,7 @@ #include <sound/asoundef.h> #include <sound/core.h> #include <sound/control.h> +#include <sound/hda_verbs.h> #include <sound/hwdep.h> #include <sound/info.h> #include <sound/tlv.h> @@ -32,10 +35,11 @@ #include "mixer.h" #include "mixer_quirks.h" #include "mixer_scarlett.h" -#include "mixer_scarlett_gen2.h" +#include "mixer_scarlett2.h" #include "mixer_us16x08.h" #include "mixer_s1810c.h" #include "helper.h" +#include "fcp.h" struct std_mono_table { unsigned int unitid, control, cmask; @@ -52,13 +56,13 @@ struct std_mono_table { * version, we keep it mono for simplicity. */ static int snd_create_std_mono_ctl_offset(struct usb_mixer_interface *mixer, - unsigned int unitid, - unsigned int control, - unsigned int cmask, - int val_type, - unsigned int idx_off, - const char *name, - snd_kcontrol_tlv_rw_t *tlv_callback) + unsigned int unitid, + unsigned int control, + unsigned int cmask, + int val_type, + unsigned int idx_off, + const char *name, + snd_kcontrol_tlv_rw_t *tlv_callback) { struct usb_mixer_elem_info *cval; struct snd_kcontrol *kctl; @@ -75,7 +79,8 @@ static int snd_create_std_mono_ctl_offset(struct usb_mixer_interface *mixer, cval->idx_off = idx_off; /* get_min_max() is called only for integer volumes later, - * so provide a short-cut for booleans */ + * so provide a short-cut for booleans + */ cval->min = 0; cval->max = 1; cval->res = 0; @@ -105,15 +110,16 @@ static int snd_create_std_mono_ctl_offset(struct usb_mixer_interface *mixer, } static int snd_create_std_mono_ctl(struct usb_mixer_interface *mixer, - unsigned int unitid, - unsigned int control, - unsigned int cmask, - int val_type, - const char *name, - snd_kcontrol_tlv_rw_t *tlv_callback) + unsigned int unitid, + unsigned int control, + unsigned int cmask, + int val_type, + const char *name, + snd_kcontrol_tlv_rw_t *tlv_callback) { return snd_create_std_mono_ctl_offset(mixer, unitid, control, cmask, - val_type, 0 /* Offset */, name, tlv_callback); + val_type, 0 /* Offset */, + name, tlv_callback); } /* @@ -124,9 +130,10 @@ static int snd_create_std_mono_table(struct usb_mixer_interface *mixer, { int err; - while (t->name != NULL) { + while (t->name) { err = snd_create_std_mono_ctl(mixer, t->unitid, t->control, - t->cmask, t->val_type, t->name, t->tlv_callback); + t->cmask, t->val_type, t->name, + t->tlv_callback); if (err < 0) return err; t++; @@ -158,7 +165,8 @@ static int add_single_ctl_with_resume(struct usb_mixer_interface *mixer, return -ENOMEM; } kctl->private_free = snd_usb_mixer_elem_free; - return snd_usb_mixer_add_control(list, kctl); + /* don't use snd_usb_mixer_add_control() here, this is a special list element */ + return snd_usb_mixer_add_list(list, kctl, false); } /* @@ -184,6 +192,7 @@ static const struct rc_config { { USB_ID(0x041e, 0x3042), 0, 1, 1, 1, 1, 0x000d }, /* Usb X-Fi S51 */ { USB_ID(0x041e, 0x30df), 0, 1, 1, 1, 1, 0x000d }, /* Usb X-Fi S51 Pro */ { USB_ID(0x041e, 0x3237), 0, 1, 1, 1, 1, 0x000d }, /* Usb X-Fi S51 Pro */ + { USB_ID(0x041e, 0x3263), 0, 1, 1, 1, 1, 0x000d }, /* Usb X-Fi S51 Pro */ { USB_ID(0x041e, 0x3048), 2, 2, 6, 6, 2, 0x6e91 }, /* Toshiba SB0500 */ }; @@ -204,12 +213,11 @@ static void snd_usb_soundblaster_remote_complete(struct urb *urb) if (code == rc->mute_code) snd_usb_mixer_notify_id(mixer, rc->mute_mixer_id); mixer->rc_code = code; - wmb(); wake_up(&mixer->rc_waitq); } static long snd_usb_sbrc_hwdep_read(struct snd_hwdep *hw, char __user *buf, - long count, loff_t *offset) + long count, loff_t *offset) { struct usb_mixer_interface *mixer = hw->private_data; int err; @@ -229,7 +237,7 @@ static long snd_usb_sbrc_hwdep_read(struct snd_hwdep *hw, char __user *buf, } static __poll_t snd_usb_sbrc_hwdep_poll(struct snd_hwdep *hw, struct file *file, - poll_table *wait) + poll_table *wait) { struct usb_mixer_interface *mixer = hw->private_data; @@ -280,7 +288,7 @@ static int snd_usb_soundblaster_remote_init(struct usb_mixer_interface *mixer) mixer->rc_setup_packet->wLength = cpu_to_le16(len); usb_fill_control_urb(mixer->rc_urb, mixer->chip->dev, usb_rcvctrlpipe(mixer->chip->dev, 0), - (u8*)mixer->rc_setup_packet, mixer->rc_buffer, len, + (u8 *)mixer->rc_setup_packet, mixer->rc_buffer, len, snd_usb_soundblaster_remote_complete, mixer); return 0; } @@ -299,27 +307,26 @@ static int snd_audigy2nx_led_update(struct usb_mixer_interface *mixer, struct snd_usb_audio *chip = mixer->chip; int err; - err = snd_usb_lock_shutdown(chip); - if (err < 0) - return err; + CLASS(snd_usb_lock, pm)(chip); + if (pm.err < 0) + return pm.err; if (chip->usb_id == USB_ID(0x041e, 0x3042)) err = snd_usb_ctl_msg(chip->dev, - usb_sndctrlpipe(chip->dev, 0), 0x24, - USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_OTHER, - !value, 0, NULL, 0); + usb_sndctrlpipe(chip->dev, 0), 0x24, + USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_OTHER, + !value, 0, NULL, 0); /* USB X-Fi S51 Pro */ if (chip->usb_id == USB_ID(0x041e, 0x30df)) err = snd_usb_ctl_msg(chip->dev, - usb_sndctrlpipe(chip->dev, 0), 0x24, - USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_OTHER, - !value, 0, NULL, 0); + usb_sndctrlpipe(chip->dev, 0), 0x24, + USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_OTHER, + !value, 0, NULL, 0); else err = snd_usb_ctl_msg(chip->dev, - usb_sndctrlpipe(chip->dev, 0), 0x24, - USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_OTHER, - value, index + 2, NULL, 0); - snd_usb_unlock_shutdown(chip); + usb_sndctrlpipe(chip->dev, 0), 0x24, + USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_OTHER, + value, index + 2, NULL, 0); return err; } @@ -372,17 +379,17 @@ static int snd_audigy2nx_controls_create(struct usb_mixer_interface *mixer) struct snd_kcontrol_new knew; /* USB X-Fi S51 doesn't have a CMSS LED */ - if ((mixer->chip->usb_id == USB_ID(0x041e, 0x3042)) && i == 0) + if (mixer->chip->usb_id == USB_ID(0x041e, 0x3042) && i == 0) continue; /* USB X-Fi S51 Pro doesn't have one either */ - if ((mixer->chip->usb_id == USB_ID(0x041e, 0x30df)) && i == 0) + if (mixer->chip->usb_id == USB_ID(0x041e, 0x30df) && i == 0) continue; if (i > 1 && /* Live24ext has 2 LEDs only */ (mixer->chip->usb_id == USB_ID(0x041e, 0x3040) || mixer->chip->usb_id == USB_ID(0x041e, 0x3042) || mixer->chip->usb_id == USB_ID(0x041e, 0x30df) || mixer->chip->usb_id == USB_ID(0x041e, 0x3048))) - break; + break; knew = snd_audigy2nx_control; knew.name = snd_audigy2nx_led_names[i]; @@ -430,15 +437,14 @@ static void snd_audigy2nx_proc_read(struct snd_info_entry *entry, for (i = 0; jacks[i].name; ++i) { snd_iprintf(buffer, "%s: ", jacks[i].name); - err = snd_usb_lock_shutdown(mixer->chip); - if (err < 0) + CLASS(snd_usb_lock, pm)(mixer->chip); + if (pm.err < 0) return; err = snd_usb_ctl_msg(mixer->chip->dev, usb_rcvctrlpipe(mixer->chip->dev, 0), UAC_GET_MEM, USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE, 0, jacks[i].unitid << 8, buf, 3); - snd_usb_unlock_shutdown(mixer->chip); if (err == 3 && (buf[0] == 3 || buf[0] == 6)) snd_iprintf(buffer, "%02x %02x\n", buf[1], buf[2]); else @@ -466,21 +472,18 @@ static int snd_emu0204_ch_switch_update(struct usb_mixer_interface *mixer, int value) { struct snd_usb_audio *chip = mixer->chip; - int err; unsigned char buf[2]; - err = snd_usb_lock_shutdown(chip); - if (err < 0) - return err; + CLASS(snd_usb_lock, pm)(chip); + if (pm.err < 0) + return pm.err; buf[0] = 0x01; buf[1] = value ? 0x02 : 0x01; - err = snd_usb_ctl_msg(chip->dev, - usb_sndctrlpipe(chip->dev, 0), UAC_SET_CUR, - USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_OUT, - 0x0400, 0x0e00, buf, 2); - snd_usb_unlock_shutdown(chip); - return err; + return snd_usb_ctl_msg(chip->dev, + usb_sndctrlpipe(chip->dev, 0), UAC_SET_CUR, + USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_OUT, + 0x0400, 0x0e00, buf, 2); } static int snd_emu0204_ch_switch_put(struct snd_kcontrol *kcontrol, @@ -524,6 +527,265 @@ static int snd_emu0204_controls_create(struct usb_mixer_interface *mixer) &snd_emu0204_control, NULL); } +#if IS_REACHABLE(CONFIG_INPUT) +/* + * Sony DualSense controller (PS5) jack detection + * + * Since this is an UAC 1 device, it doesn't support jack detection. + * However, the controller hid-playstation driver reports HP & MIC + * insert events through a dedicated input device. + */ + +#define SND_DUALSENSE_JACK_OUT_TERM_ID 3 +#define SND_DUALSENSE_JACK_IN_TERM_ID 4 + +struct dualsense_mixer_elem_info { + struct usb_mixer_elem_info info; + struct input_handler ih; + struct input_device_id id_table[2]; + bool connected; +}; + +static void snd_dualsense_ih_event(struct input_handle *handle, + unsigned int type, unsigned int code, + int value) +{ + struct dualsense_mixer_elem_info *mei; + struct usb_mixer_elem_list *me; + + if (type != EV_SW) + return; + + mei = container_of(handle->handler, struct dualsense_mixer_elem_info, ih); + me = &mei->info.head; + + if ((me->id == SND_DUALSENSE_JACK_OUT_TERM_ID && code == SW_HEADPHONE_INSERT) || + (me->id == SND_DUALSENSE_JACK_IN_TERM_ID && code == SW_MICROPHONE_INSERT)) { + mei->connected = !!value; + snd_ctl_notify(me->mixer->chip->card, SNDRV_CTL_EVENT_MASK_VALUE, + &me->kctl->id); + } +} + +static bool snd_dualsense_ih_match(struct input_handler *handler, + struct input_dev *dev) +{ + struct dualsense_mixer_elem_info *mei; + struct usb_device *snd_dev; + char *input_dev_path, *usb_dev_path; + size_t usb_dev_path_len; + bool match = false; + + mei = container_of(handler, struct dualsense_mixer_elem_info, ih); + snd_dev = mei->info.head.mixer->chip->dev; + + input_dev_path = kobject_get_path(&dev->dev.kobj, GFP_KERNEL); + if (!input_dev_path) { + dev_warn(&snd_dev->dev, "Failed to get input dev path\n"); + return false; + } + + usb_dev_path = kobject_get_path(&snd_dev->dev.kobj, GFP_KERNEL); + if (!usb_dev_path) { + dev_warn(&snd_dev->dev, "Failed to get USB dev path\n"); + goto free_paths; + } + + /* + * Ensure the VID:PID matched input device supposedly owned by the + * hid-playstation driver belongs to the actual hardware handled by + * the current USB audio device, which implies input_dev_path being + * a subpath of usb_dev_path. + * + * This verification is necessary when there is more than one identical + * controller attached to the host system. + */ + usb_dev_path_len = strlen(usb_dev_path); + if (usb_dev_path_len >= strlen(input_dev_path)) + goto free_paths; + + usb_dev_path[usb_dev_path_len] = '/'; + match = !memcmp(input_dev_path, usb_dev_path, usb_dev_path_len + 1); + +free_paths: + kfree(input_dev_path); + kfree(usb_dev_path); + + return match; +} + +static int snd_dualsense_ih_connect(struct input_handler *handler, + struct input_dev *dev, + const struct input_device_id *id) +{ + struct input_handle *handle; + int err; + + handle = kzalloc(sizeof(*handle), GFP_KERNEL); + if (!handle) + return -ENOMEM; + + handle->dev = dev; + handle->handler = handler; + handle->name = handler->name; + + err = input_register_handle(handle); + if (err) + goto err_free; + + err = input_open_device(handle); + if (err) + goto err_unregister; + + return 0; + +err_unregister: + input_unregister_handle(handle); +err_free: + kfree(handle); + return err; +} + +static void snd_dualsense_ih_disconnect(struct input_handle *handle) +{ + input_close_device(handle); + input_unregister_handle(handle); + kfree(handle); +} + +static void snd_dualsense_ih_start(struct input_handle *handle) +{ + struct dualsense_mixer_elem_info *mei; + struct usb_mixer_elem_list *me; + int status = -1; + + mei = container_of(handle->handler, struct dualsense_mixer_elem_info, ih); + me = &mei->info.head; + + if (me->id == SND_DUALSENSE_JACK_OUT_TERM_ID && + test_bit(SW_HEADPHONE_INSERT, handle->dev->swbit)) + status = test_bit(SW_HEADPHONE_INSERT, handle->dev->sw); + else if (me->id == SND_DUALSENSE_JACK_IN_TERM_ID && + test_bit(SW_MICROPHONE_INSERT, handle->dev->swbit)) + status = test_bit(SW_MICROPHONE_INSERT, handle->dev->sw); + + if (status >= 0) { + mei->connected = !!status; + snd_ctl_notify(me->mixer->chip->card, SNDRV_CTL_EVENT_MASK_VALUE, + &me->kctl->id); + } +} + +static int snd_dualsense_jack_get(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct dualsense_mixer_elem_info *mei = snd_kcontrol_chip(kctl); + + ucontrol->value.integer.value[0] = mei->connected; + + return 0; +} + +static const struct snd_kcontrol_new snd_dualsense_jack_control = { + .iface = SNDRV_CTL_ELEM_IFACE_CARD, + .access = SNDRV_CTL_ELEM_ACCESS_READ, + .info = snd_ctl_boolean_mono_info, + .get = snd_dualsense_jack_get, +}; + +static int snd_dualsense_resume_jack(struct usb_mixer_elem_list *list) +{ + snd_ctl_notify(list->mixer->chip->card, SNDRV_CTL_EVENT_MASK_VALUE, + &list->kctl->id); + return 0; +} + +static void snd_dualsense_mixer_elem_free(struct snd_kcontrol *kctl) +{ + struct dualsense_mixer_elem_info *mei = snd_kcontrol_chip(kctl); + + if (mei->ih.event) + input_unregister_handler(&mei->ih); + + snd_usb_mixer_elem_free(kctl); +} + +static int snd_dualsense_jack_create(struct usb_mixer_interface *mixer, + const char *name, bool is_output) +{ + struct dualsense_mixer_elem_info *mei; + struct input_device_id *idev_id; + struct snd_kcontrol *kctl; + int err; + + mei = kzalloc(sizeof(*mei), GFP_KERNEL); + if (!mei) + return -ENOMEM; + + snd_usb_mixer_elem_init_std(&mei->info.head, mixer, + is_output ? SND_DUALSENSE_JACK_OUT_TERM_ID : + SND_DUALSENSE_JACK_IN_TERM_ID); + + mei->info.head.resume = snd_dualsense_resume_jack; + mei->info.val_type = USB_MIXER_BOOLEAN; + mei->info.channels = 1; + mei->info.min = 0; + mei->info.max = 1; + + kctl = snd_ctl_new1(&snd_dualsense_jack_control, mei); + if (!kctl) { + kfree(mei); + return -ENOMEM; + } + + strscpy(kctl->id.name, name, sizeof(kctl->id.name)); + kctl->private_free = snd_dualsense_mixer_elem_free; + + err = snd_usb_mixer_add_control(&mei->info.head, kctl); + if (err) + return err; + + idev_id = &mei->id_table[0]; + idev_id->flags = INPUT_DEVICE_ID_MATCH_VENDOR | INPUT_DEVICE_ID_MATCH_PRODUCT | + INPUT_DEVICE_ID_MATCH_EVBIT | INPUT_DEVICE_ID_MATCH_SWBIT; + idev_id->vendor = USB_ID_VENDOR(mixer->chip->usb_id); + idev_id->product = USB_ID_PRODUCT(mixer->chip->usb_id); + idev_id->evbit[BIT_WORD(EV_SW)] = BIT_MASK(EV_SW); + if (is_output) + idev_id->swbit[BIT_WORD(SW_HEADPHONE_INSERT)] = BIT_MASK(SW_HEADPHONE_INSERT); + else + idev_id->swbit[BIT_WORD(SW_MICROPHONE_INSERT)] = BIT_MASK(SW_MICROPHONE_INSERT); + + mei->ih.event = snd_dualsense_ih_event; + mei->ih.match = snd_dualsense_ih_match; + mei->ih.connect = snd_dualsense_ih_connect; + mei->ih.disconnect = snd_dualsense_ih_disconnect; + mei->ih.start = snd_dualsense_ih_start; + mei->ih.name = name; + mei->ih.id_table = mei->id_table; + + err = input_register_handler(&mei->ih); + if (err) { + dev_warn(&mixer->chip->dev->dev, + "Could not register input handler: %d\n", err); + mei->ih.event = NULL; + } + + return 0; +} + +static int snd_dualsense_controls_create(struct usb_mixer_interface *mixer) +{ + int err; + + err = snd_dualsense_jack_create(mixer, "Headphone Jack", true); + if (err < 0) + return err; + + return snd_dualsense_jack_create(mixer, "Headset Mic Jack", false); +} +#endif /* IS_REACHABLE(CONFIG_INPUT) */ + /* ASUS Xonar U1 / U3 controls */ static int snd_xonar_u1_switch_get(struct snd_kcontrol *kcontrol, @@ -537,17 +799,14 @@ static int snd_xonar_u1_switch_update(struct usb_mixer_interface *mixer, unsigned char status) { struct snd_usb_audio *chip = mixer->chip; - int err; - err = snd_usb_lock_shutdown(chip); - if (err < 0) - return err; - err = snd_usb_ctl_msg(chip->dev, - usb_sndctrlpipe(chip->dev, 0), 0x08, - USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_OTHER, - 50, 0, &status, 1); - snd_usb_unlock_shutdown(chip); - return err; + CLASS(snd_usb_lock, pm)(chip); + if (pm.err < 0) + return pm.err; + return snd_usb_ctl_msg(chip->dev, + usb_sndctrlpipe(chip->dev, 0), 0x08, + USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_OTHER, + 50, 0, &status, 1); } static int snd_xonar_u1_switch_put(struct snd_kcontrol *kcontrol, @@ -592,85 +851,199 @@ static int snd_xonar_u1_controls_create(struct usb_mixer_interface *mixer) &snd_xonar_u1_output_switch, NULL); } +/* Digidesign Mbox 1 helper functions */ + +static int snd_mbox1_is_spdif_synced(struct snd_usb_audio *chip) +{ + unsigned char buff[3]; + int err; + int is_spdif_synced; + + /* Read clock source */ + err = snd_usb_ctl_msg(chip->dev, + usb_rcvctrlpipe(chip->dev, 0), 0x81, + USB_DIR_IN | + USB_TYPE_CLASS | + USB_RECIP_ENDPOINT, 0x100, 0x81, buff, 3); + if (err < 0) + return err; + + /* spdif sync: buff is all zeroes */ + is_spdif_synced = !(buff[0] | buff[1] | buff[2]); + return is_spdif_synced; +} + +static int snd_mbox1_set_clk_source(struct snd_usb_audio *chip, int rate_or_zero) +{ + /* 2 possibilities: Internal -> expects sample rate + * S/PDIF sync -> expects rate = 0 + */ + unsigned char buff[3]; + + buff[0] = (rate_or_zero >> 0) & 0xff; + buff[1] = (rate_or_zero >> 8) & 0xff; + buff[2] = (rate_or_zero >> 16) & 0xff; + + /* Set clock source */ + return snd_usb_ctl_msg(chip->dev, + usb_sndctrlpipe(chip->dev, 0), 0x1, + USB_TYPE_CLASS | + USB_RECIP_ENDPOINT, 0x100, 0x81, buff, 3); +} + +static int snd_mbox1_is_spdif_input(struct snd_usb_audio *chip) +{ + /* Hardware gives 2 possibilities: ANALOG Source -> 0x01 + * S/PDIF Source -> 0x02 + */ + int err; + unsigned char source[1]; + + /* Read input source */ + err = snd_usb_ctl_msg(chip->dev, + usb_rcvctrlpipe(chip->dev, 0), 0x81, + USB_DIR_IN | + USB_TYPE_CLASS | + USB_RECIP_INTERFACE, 0x00, 0x500, source, 1); + if (err < 0) + return err; + + return (source[0] == 2); +} + +static int snd_mbox1_set_input_source(struct snd_usb_audio *chip, int is_spdif) +{ + /* NB: Setting the input source to S/PDIF resets the clock source to S/PDIF + * Hardware expects 2 possibilities: ANALOG Source -> 0x01 + * S/PDIF Source -> 0x02 + */ + unsigned char buff[1]; + + buff[0] = (is_spdif & 1) + 1; + + /* Set input source */ + return snd_usb_ctl_msg(chip->dev, + usb_sndctrlpipe(chip->dev, 0), 0x1, + USB_TYPE_CLASS | + USB_RECIP_INTERFACE, 0x00, 0x500, buff, 1); +} + /* Digidesign Mbox 1 clock source switch (internal/spdif) */ -static int snd_mbox1_switch_get(struct snd_kcontrol *kctl, - struct snd_ctl_elem_value *ucontrol) +static int snd_mbox1_clk_switch_get(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) { + struct usb_mixer_elem_list *list = snd_kcontrol_chip(kctl); + struct snd_usb_audio *chip = list->mixer->chip; + int err; + + CLASS(snd_usb_lock, pm)(chip); + if (pm.err < 0) + return pm.err; + + err = snd_mbox1_is_spdif_synced(chip); + if (err < 0) + return err; + + kctl->private_value = err; ucontrol->value.enumerated.item[0] = kctl->private_value; return 0; } -static int snd_mbox1_switch_update(struct usb_mixer_interface *mixer, int val) +static int snd_mbox1_clk_switch_update(struct usb_mixer_interface *mixer, int is_spdif_sync) { struct snd_usb_audio *chip = mixer->chip; int err; - unsigned char buff[3]; - err = snd_usb_lock_shutdown(chip); + CLASS(snd_usb_lock, pm)(chip); + if (pm.err < 0) + return pm.err; + + err = snd_mbox1_is_spdif_input(chip); if (err < 0) return err; - /* Prepare for magic command to toggle clock source */ - err = snd_usb_ctl_msg(chip->dev, - usb_rcvctrlpipe(chip->dev, 0), 0x81, - USB_DIR_IN | - USB_TYPE_CLASS | - USB_RECIP_INTERFACE, 0x00, 0x500, buff, 1); + err = snd_mbox1_is_spdif_synced(chip); if (err < 0) - goto err; - err = snd_usb_ctl_msg(chip->dev, - usb_rcvctrlpipe(chip->dev, 0), 0x81, - USB_DIR_IN | - USB_TYPE_CLASS | - USB_RECIP_ENDPOINT, 0x100, 0x81, buff, 3); + return err; + + /* FIXME: hardcoded sample rate */ + err = snd_mbox1_set_clk_source(chip, is_spdif_sync ? 0 : 48000); if (err < 0) - goto err; - - /* 2 possibilities: Internal -> send sample rate - * S/PDIF sync -> send zeroes - * NB: Sample rate locked to 48kHz on purpose to - * prevent user from resetting the sample rate - * while S/PDIF sync is enabled and confusing - * this configuration. - */ - if (val == 0) { - buff[0] = 0x80; - buff[1] = 0xbb; - buff[2] = 0x00; - } else { - buff[0] = buff[1] = buff[2] = 0x00; - } + return err; - /* Send the magic command to toggle the clock source */ - err = snd_usb_ctl_msg(chip->dev, - usb_sndctrlpipe(chip->dev, 0), 0x1, - USB_TYPE_CLASS | - USB_RECIP_ENDPOINT, 0x100, 0x81, buff, 3); + return snd_mbox1_is_spdif_synced(chip); +} + +static int snd_mbox1_clk_switch_put(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_list *list = snd_kcontrol_chip(kctl); + struct usb_mixer_interface *mixer = list->mixer; + int err; + bool cur_val, new_val; + + cur_val = kctl->private_value; + new_val = ucontrol->value.enumerated.item[0]; + if (cur_val == new_val) + return 0; + + kctl->private_value = new_val; + err = snd_mbox1_clk_switch_update(mixer, new_val); + return err < 0 ? err : 1; +} + +static int snd_mbox1_clk_switch_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + static const char *const texts[2] = { + "Internal", + "S/PDIF" + }; + + return snd_ctl_enum_info(uinfo, 1, ARRAY_SIZE(texts), texts); +} + +static int snd_mbox1_clk_switch_resume(struct usb_mixer_elem_list *list) +{ + return snd_mbox1_clk_switch_update(list->mixer, list->kctl->private_value); +} + +/* Digidesign Mbox 1 input source switch (analog/spdif) */ + +static int snd_mbox1_src_switch_get(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.enumerated.item[0] = kctl->private_value; + return 0; +} + +static int snd_mbox1_src_switch_update(struct usb_mixer_interface *mixer, int is_spdif_input) +{ + struct snd_usb_audio *chip = mixer->chip; + int err; + + CLASS(snd_usb_lock, pm)(chip); + if (pm.err < 0) + return pm.err; + + err = snd_mbox1_is_spdif_input(chip); if (err < 0) - goto err; - err = snd_usb_ctl_msg(chip->dev, - usb_rcvctrlpipe(chip->dev, 0), 0x81, - USB_DIR_IN | - USB_TYPE_CLASS | - USB_RECIP_ENDPOINT, 0x100, 0x81, buff, 3); + return err; + + err = snd_mbox1_set_input_source(chip, is_spdif_input); if (err < 0) - goto err; - err = snd_usb_ctl_msg(chip->dev, - usb_rcvctrlpipe(chip->dev, 0), 0x81, - USB_DIR_IN | - USB_TYPE_CLASS | - USB_RECIP_ENDPOINT, 0x100, 0x2, buff, 3); + return err; + + err = snd_mbox1_is_spdif_input(chip); if (err < 0) - goto err; + return err; -err: - snd_usb_unlock_shutdown(chip); - return err; + return snd_mbox1_is_spdif_synced(chip); } -static int snd_mbox1_switch_put(struct snd_kcontrol *kctl, - struct snd_ctl_elem_value *ucontrol) +static int snd_mbox1_src_switch_put(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) { struct usb_mixer_elem_list *list = snd_kcontrol_chip(kctl); struct usb_mixer_interface *mixer = list->mixer; @@ -683,47 +1056,66 @@ static int snd_mbox1_switch_put(struct snd_kcontrol *kctl, return 0; kctl->private_value = new_val; - err = snd_mbox1_switch_update(mixer, new_val); + err = snd_mbox1_src_switch_update(mixer, new_val); return err < 0 ? err : 1; } -static int snd_mbox1_switch_info(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_info *uinfo) +static int snd_mbox1_src_switch_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) { static const char *const texts[2] = { - "Internal", + "Analog", "S/PDIF" }; return snd_ctl_enum_info(uinfo, 1, ARRAY_SIZE(texts), texts); } -static int snd_mbox1_switch_resume(struct usb_mixer_elem_list *list) +static int snd_mbox1_src_switch_resume(struct usb_mixer_elem_list *list) { - return snd_mbox1_switch_update(list->mixer, list->kctl->private_value); + return snd_mbox1_src_switch_update(list->mixer, list->kctl->private_value); } -static const struct snd_kcontrol_new snd_mbox1_switch = { +static const struct snd_kcontrol_new snd_mbox1_clk_switch = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Clock Source", .index = 0, .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, - .info = snd_mbox1_switch_info, - .get = snd_mbox1_switch_get, - .put = snd_mbox1_switch_put, + .info = snd_mbox1_clk_switch_info, + .get = snd_mbox1_clk_switch_get, + .put = snd_mbox1_clk_switch_put, + .private_value = 0 +}; + +static const struct snd_kcontrol_new snd_mbox1_src_switch = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Input Source", + .index = 1, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .info = snd_mbox1_src_switch_info, + .get = snd_mbox1_src_switch_get, + .put = snd_mbox1_src_switch_put, .private_value = 0 }; -static int snd_mbox1_create_sync_switch(struct usb_mixer_interface *mixer) +static int snd_mbox1_controls_create(struct usb_mixer_interface *mixer) { - return add_single_ctl_with_resume(mixer, 0, - snd_mbox1_switch_resume, - &snd_mbox1_switch, NULL); + int err; + + err = add_single_ctl_with_resume(mixer, 0, + snd_mbox1_clk_switch_resume, + &snd_mbox1_clk_switch, NULL); + if (err < 0) + return err; + + return add_single_ctl_with_resume(mixer, 1, + snd_mbox1_src_switch_resume, + &snd_mbox1_src_switch, NULL); } /* Native Instruments device quirks */ -#define _MAKE_NI_CONTROL(bRequest,wIndex) ((bRequest) << 16 | (wIndex)) +#define _MAKE_NI_CONTROL(bRequest, wIndex) ((bRequest) << 16 | (wIndex)) static int snd_ni_control_init_val(struct usb_mixer_interface *mixer, struct snd_kcontrol *kctl) @@ -758,17 +1150,14 @@ static int snd_ni_update_cur_val(struct usb_mixer_elem_list *list) { struct snd_usb_audio *chip = list->mixer->chip; unsigned int pval = list->kctl->private_value; - int err; - err = snd_usb_lock_shutdown(chip); - if (err < 0) - return err; - err = usb_control_msg(chip->dev, usb_sndctrlpipe(chip->dev, 0), - (pval >> 16) & 0xff, - USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_OUT, - pval >> 24, pval & 0xffff, NULL, 0, 1000); - snd_usb_unlock_shutdown(chip); - return err; + CLASS(snd_usb_lock, pm)(chip); + if (pm.err < 0) + return pm.err; + return usb_control_msg(chip->dev, usb_sndctrlpipe(chip->dev, 0), + (pval >> 16) & 0xff, + USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_OUT, + pval >> 24, pval & 0xffff, NULL, 0, 1000); } static int snd_nativeinstruments_control_put(struct snd_kcontrol *kcontrol, @@ -875,7 +1264,7 @@ static int snd_nativeinstruments_create_mixer(struct usb_mixer_interface *mixer, /* M-Audio FastTrack Ultra quirks */ /* FTU Effect switch (also used by C400/C600) */ static int snd_ftu_eff_switch_info(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_info *uinfo) + struct snd_ctl_elem_info *uinfo) { static const char *const texts[8] = { "Room 1", "Room 2", "Room 3", "Hall 1", @@ -899,7 +1288,7 @@ static int snd_ftu_eff_switch_init(struct usb_mixer_interface *mixer, err = snd_usb_ctl_msg(dev, usb_rcvctrlpipe(dev, 0), UAC_GET_CUR, USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_IN, pval & 0xff00, - snd_usb_ctrl_intf(mixer->chip) | ((pval & 0xff) << 8), + snd_usb_ctrl_intf(mixer->hostif) | ((pval & 0xff) << 8), value, 2); if (err < 0) return err; @@ -909,7 +1298,7 @@ static int snd_ftu_eff_switch_init(struct usb_mixer_interface *mixer, } static int snd_ftu_eff_switch_get(struct snd_kcontrol *kctl, - struct snd_ctl_elem_value *ucontrol) + struct snd_ctl_elem_value *ucontrol) { ucontrol->value.enumerated.item[0] = kctl->private_value >> 24; return 0; @@ -920,27 +1309,24 @@ static int snd_ftu_eff_switch_update(struct usb_mixer_elem_list *list) struct snd_usb_audio *chip = list->mixer->chip; unsigned int pval = list->kctl->private_value; unsigned char value[2]; - int err; value[0] = pval >> 24; value[1] = 0; - err = snd_usb_lock_shutdown(chip); - if (err < 0) - return err; - err = snd_usb_ctl_msg(chip->dev, - usb_sndctrlpipe(chip->dev, 0), - UAC_SET_CUR, - USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_OUT, - pval & 0xff00, - snd_usb_ctrl_intf(chip) | ((pval & 0xff) << 8), - value, 2); - snd_usb_unlock_shutdown(chip); - return err; + CLASS(snd_usb_lock, pm)(chip); + if (pm.err < 0) + return pm.err; + return snd_usb_ctl_msg(chip->dev, + usb_sndctrlpipe(chip->dev, 0), + UAC_SET_CUR, + USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_OUT, + pval & 0xff00, + snd_usb_ctrl_intf(list->mixer->hostif) | ((pval & 0xff) << 8), + value, 2); } static int snd_ftu_eff_switch_put(struct snd_kcontrol *kctl, - struct snd_ctl_elem_value *ucontrol) + struct snd_ctl_elem_value *ucontrol) { struct usb_mixer_elem_list *list = snd_kcontrol_chip(kctl); unsigned int pval = list->kctl->private_value; @@ -958,7 +1344,7 @@ static int snd_ftu_eff_switch_put(struct snd_kcontrol *kctl, } static int snd_ftu_create_effect_switch(struct usb_mixer_interface *mixer, - int validx, int bUnitID) + int validx, int bUnitID) { static struct snd_kcontrol_new template = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, @@ -995,24 +1381,24 @@ static int snd_ftu_create_volume_ctls(struct usb_mixer_interface *mixer) for (out = 0; out < 8; out++) { control = out + 1; for (in = 0; in < 8; in++) { - cmask = 1 << in; + cmask = BIT(in); snprintf(name, sizeof(name), - "AIn%d - Out%d Capture Volume", - in + 1, out + 1); + "AIn%d - Out%d Capture Volume", + in + 1, out + 1); err = snd_create_std_mono_ctl(mixer, id, control, - cmask, val_type, name, - &snd_usb_mixer_vol_tlv); + cmask, val_type, name, + &snd_usb_mixer_vol_tlv); if (err < 0) return err; } for (in = 8; in < 16; in++) { - cmask = 1 << in; + cmask = BIT(in); snprintf(name, sizeof(name), - "DIn%d - Out%d Playback Volume", - in - 7, out + 1); + "DIn%d - Out%d Playback Volume", + in - 7, out + 1); err = snd_create_std_mono_ctl(mixer, id, control, - cmask, val_type, name, - &snd_usb_mixer_vol_tlv); + cmask, val_type, name, + &snd_usb_mixer_vol_tlv); if (err < 0) return err; } @@ -1071,12 +1457,12 @@ static int snd_ftu_create_effect_return_ctls(struct usb_mixer_interface *mixer) const unsigned int control = 7; for (ch = 0; ch < 4; ++ch) { - cmask = 1 << ch; + cmask = BIT(ch); snprintf(name, sizeof(name), - "Effect Return %d Volume", ch + 1); + "Effect Return %d Volume", ch + 1); err = snd_create_std_mono_ctl(mixer, id, control, - cmask, val_type, name, - snd_usb_mixer_vol_tlv); + cmask, val_type, name, + snd_usb_mixer_vol_tlv); if (err < 0) return err; } @@ -1095,22 +1481,22 @@ static int snd_ftu_create_effect_send_ctls(struct usb_mixer_interface *mixer) const unsigned int control = 9; for (ch = 0; ch < 8; ++ch) { - cmask = 1 << ch; + cmask = BIT(ch); snprintf(name, sizeof(name), - "Effect Send AIn%d Volume", ch + 1); + "Effect Send AIn%d Volume", ch + 1); err = snd_create_std_mono_ctl(mixer, id, control, cmask, - val_type, name, - snd_usb_mixer_vol_tlv); + val_type, name, + snd_usb_mixer_vol_tlv); if (err < 0) return err; } for (ch = 8; ch < 16; ++ch) { - cmask = 1 << ch; + cmask = BIT(ch); snprintf(name, sizeof(name), - "Effect Send DIn%d Volume", ch - 7); + "Effect Send DIn%d Volume", ch - 7); err = snd_create_std_mono_ctl(mixer, id, control, cmask, - val_type, name, - snd_usb_mixer_vol_tlv); + val_type, name, + snd_usb_mixer_vol_tlv); if (err < 0) return err; } @@ -1200,19 +1586,19 @@ static int snd_c400_create_vol_ctls(struct usb_mixer_interface *mixer) for (out = 0; out < num_outs; out++) { if (chan < num_outs) { snprintf(name, sizeof(name), - "PCM%d-Out%d Playback Volume", - chan + 1, out + 1); + "PCM%d-Out%d Playback Volume", + chan + 1, out + 1); } else { snprintf(name, sizeof(name), - "In%d-Out%d Playback Volume", - chan - num_outs + 1, out + 1); + "In%d-Out%d Playback Volume", + chan - num_outs + 1, out + 1); } - cmask = (out == 0) ? 0 : 1 << (out - 1); + cmask = (out == 0) ? 0 : BIT(out - 1); offset = chan * num_outs; err = snd_create_std_mono_ctl_offset(mixer, id, control, - cmask, val_type, offset, name, - &snd_usb_mixer_vol_tlv); + cmask, val_type, offset, name, + &snd_usb_mixer_vol_tlv); if (err < 0) return err; } @@ -1231,7 +1617,7 @@ static int snd_c400_create_effect_volume_ctl(struct usb_mixer_interface *mixer) const unsigned int cmask = 0; return snd_create_std_mono_ctl(mixer, id, control, cmask, val_type, - name, snd_usb_mixer_vol_tlv); + name, snd_usb_mixer_vol_tlv); } /* This control needs a volume quirk, see mixer.c */ @@ -1244,7 +1630,7 @@ static int snd_c400_create_effect_duration_ctl(struct usb_mixer_interface *mixer const unsigned int cmask = 0; return snd_create_std_mono_ctl(mixer, id, control, cmask, val_type, - name, snd_usb_mixer_vol_tlv); + name, snd_usb_mixer_vol_tlv); } /* This control needs a volume quirk, see mixer.c */ @@ -1257,7 +1643,7 @@ static int snd_c400_create_effect_feedback_ctl(struct usb_mixer_interface *mixer const unsigned int cmask = 0; return snd_create_std_mono_ctl(mixer, id, control, cmask, val_type, - name, NULL); + name, NULL); } static int snd_c400_create_effect_vol_ctls(struct usb_mixer_interface *mixer) @@ -1286,18 +1672,18 @@ static int snd_c400_create_effect_vol_ctls(struct usb_mixer_interface *mixer) for (chan = 0; chan < num_outs + num_ins; chan++) { if (chan < num_outs) { snprintf(name, sizeof(name), - "Effect Send DOut%d", - chan + 1); + "Effect Send DOut%d", + chan + 1); } else { snprintf(name, sizeof(name), - "Effect Send AIn%d", - chan - num_outs + 1); + "Effect Send AIn%d", + chan - num_outs + 1); } - cmask = (chan == 0) ? 0 : 1 << (chan - 1); + cmask = (chan == 0) ? 0 : BIT(chan - 1); err = snd_create_std_mono_ctl(mixer, id, control, - cmask, val_type, name, - &snd_usb_mixer_vol_tlv); + cmask, val_type, name, + &snd_usb_mixer_vol_tlv); if (err < 0) return err; } @@ -1332,14 +1718,14 @@ static int snd_c400_create_effect_ret_vol_ctls(struct usb_mixer_interface *mixer for (chan = 0; chan < num_outs; chan++) { snprintf(name, sizeof(name), - "Effect Return %d", - chan + 1); + "Effect Return %d", + chan + 1); cmask = (chan == 0) ? 0 : - 1 << (chan + (chan % 2) * num_outs - 1); + BIT(chan + (chan % 2) * num_outs - 1); err = snd_create_std_mono_ctl_offset(mixer, id, control, - cmask, val_type, offset, name, - &snd_usb_mixer_vol_tlv); + cmask, val_type, offset, name, + &snd_usb_mixer_vol_tlv); if (err < 0) return err; } @@ -1480,7 +1866,7 @@ static const struct std_mono_table ebox44_table[] = { * */ static int snd_microii_spdif_info(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_info *uinfo) + struct snd_ctl_elem_info *uinfo) { uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958; uinfo->count = 1; @@ -1488,7 +1874,7 @@ static int snd_microii_spdif_info(struct snd_kcontrol *kcontrol, } static int snd_microii_spdif_default_get(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_value *ucontrol) + struct snd_ctl_elem_value *ucontrol) { struct usb_mixer_elem_list *list = snd_kcontrol_chip(kcontrol); struct snd_usb_audio *chip = list->mixer->chip; @@ -1499,9 +1885,9 @@ static int snd_microii_spdif_default_get(struct snd_kcontrol *kcontrol, unsigned char data[3]; int rate; - err = snd_usb_lock_shutdown(chip); - if (err < 0) - return err; + CLASS(snd_usb_lock, pm)(chip); + if (pm.err < 0) + return pm.err; ucontrol->value.iec958.status[0] = kcontrol->private_value & 0xff; ucontrol->value.iec958.status[1] = (kcontrol->private_value >> 8) & 0xff; @@ -1509,36 +1895,29 @@ static int snd_microii_spdif_default_get(struct snd_kcontrol *kcontrol, /* use known values for that card: interface#1 altsetting#1 */ iface = usb_ifnum_to_if(chip->dev, 1); - if (!iface || iface->num_altsetting < 2) { - err = -EINVAL; - goto end; - } + if (!iface || iface->num_altsetting < 2) + return -EINVAL; alts = &iface->altsetting[1]; - if (get_iface_desc(alts)->bNumEndpoints < 1) { - err = -EINVAL; - goto end; - } + if (get_iface_desc(alts)->bNumEndpoints < 1) + return -EINVAL; ep = get_endpoint(alts, 0)->bEndpointAddress; err = snd_usb_ctl_msg(chip->dev, - usb_rcvctrlpipe(chip->dev, 0), - UAC_GET_CUR, - USB_TYPE_CLASS | USB_RECIP_ENDPOINT | USB_DIR_IN, - UAC_EP_CS_ATTR_SAMPLE_RATE << 8, - ep, - data, - sizeof(data)); + usb_rcvctrlpipe(chip->dev, 0), + UAC_GET_CUR, + USB_TYPE_CLASS | USB_RECIP_ENDPOINT | USB_DIR_IN, + UAC_EP_CS_ATTR_SAMPLE_RATE << 8, + ep, + data, + sizeof(data)); if (err < 0) - goto end; + return err; rate = data[0] | (data[1] << 8) | (data[2] << 16); ucontrol->value.iec958.status[3] = (rate == 48000) ? IEC958_AES3_CON_FS_48000 : IEC958_AES3_CON_FS_44100; - err = 0; - end: - snd_usb_unlock_shutdown(chip); - return err; + return 0; } static int snd_microii_spdif_default_update(struct usb_mixer_elem_list *list) @@ -1548,48 +1927,44 @@ static int snd_microii_spdif_default_update(struct usb_mixer_elem_list *list) u8 reg; int err; - err = snd_usb_lock_shutdown(chip); - if (err < 0) - return err; + CLASS(snd_usb_lock, pm)(chip); + if (pm.err < 0) + return pm.err; reg = ((pval >> 4) & 0xf0) | (pval & 0x0f); err = snd_usb_ctl_msg(chip->dev, - usb_sndctrlpipe(chip->dev, 0), - UAC_SET_CUR, - USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_OTHER, - reg, - 2, - NULL, - 0); + usb_sndctrlpipe(chip->dev, 0), + UAC_SET_CUR, + USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_OTHER, + reg, + 2, + NULL, + 0); if (err < 0) - goto end; + return err; reg = (pval & IEC958_AES0_NONAUDIO) ? 0xa0 : 0x20; reg |= (pval >> 12) & 0x0f; err = snd_usb_ctl_msg(chip->dev, - usb_sndctrlpipe(chip->dev, 0), - UAC_SET_CUR, - USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_OTHER, - reg, - 3, - NULL, - 0); - if (err < 0) - goto end; - - end: - snd_usb_unlock_shutdown(chip); + usb_sndctrlpipe(chip->dev, 0), + UAC_SET_CUR, + USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_OTHER, + reg, + 3, + NULL, + 0); return err; } static int snd_microii_spdif_default_put(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_value *ucontrol) + struct snd_ctl_elem_value *ucontrol) { struct usb_mixer_elem_list *list = snd_kcontrol_chip(kcontrol); unsigned int pval, pval_old; int err; - pval = pval_old = kcontrol->private_value; + pval = kcontrol->private_value; + pval_old = pval; pval &= 0xfffff0f0; pval |= (ucontrol->value.iec958.status[1] & 0x0f) << 8; pval |= (ucontrol->value.iec958.status[0] & 0x0f); @@ -1610,7 +1985,7 @@ static int snd_microii_spdif_default_put(struct snd_kcontrol *kcontrol, } static int snd_microii_spdif_mask_get(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_value *ucontrol) + struct snd_ctl_elem_value *ucontrol) { ucontrol->value.iec958.status[0] = 0x0f; ucontrol->value.iec958.status[1] = 0xff; @@ -1621,7 +1996,7 @@ static int snd_microii_spdif_mask_get(struct snd_kcontrol *kcontrol, } static int snd_microii_spdif_switch_get(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_value *ucontrol) + struct snd_ctl_elem_value *ucontrol) { ucontrol->value.integer.value[0] = !(kcontrol->private_value & 0x02); @@ -1632,27 +2007,23 @@ static int snd_microii_spdif_switch_update(struct usb_mixer_elem_list *list) { struct snd_usb_audio *chip = list->mixer->chip; u8 reg = list->kctl->private_value; - int err; - err = snd_usb_lock_shutdown(chip); - if (err < 0) - return err; - - err = snd_usb_ctl_msg(chip->dev, - usb_sndctrlpipe(chip->dev, 0), - UAC_SET_CUR, - USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_OTHER, - reg, - 9, - NULL, - 0); - - snd_usb_unlock_shutdown(chip); - return err; + CLASS(snd_usb_lock, pm)(chip); + if (pm.err < 0) + return pm.err; + + return snd_usb_ctl_msg(chip->dev, + usb_sndctrlpipe(chip->dev, 0), + UAC_SET_CUR, + USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_OTHER, + reg, + 9, + NULL, + 0); } static int snd_microii_spdif_switch_put(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_value *ucontrol) + struct snd_ctl_elem_value *ucontrol) { struct usb_mixer_elem_list *list = snd_kcontrol_chip(kcontrol); u8 reg; @@ -1727,21 +2098,18 @@ static int snd_soundblaster_e1_switch_update(struct usb_mixer_interface *mixer, unsigned char state) { struct snd_usb_audio *chip = mixer->chip; - int err; unsigned char buff[2]; buff[0] = 0x02; buff[1] = state ? 0x02 : 0x00; - err = snd_usb_lock_shutdown(chip); - if (err < 0) - return err; - err = snd_usb_ctl_msg(chip->dev, - usb_sndctrlpipe(chip->dev, 0), HID_REQ_SET_REPORT, - USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_OUT, - 0x0202, 3, buff, 2); - snd_usb_unlock_shutdown(chip); - return err; + CLASS(snd_usb_lock, pm)(chip); + if (pm.err < 0) + return pm.err; + return snd_usb_ctl_msg(chip->dev, + usb_sndctrlpipe(chip->dev, 0), HID_REQ_SET_REPORT, + USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_OUT, + 0x0202, 3, buff, 2); } static int snd_soundblaster_e1_switch_put(struct snd_kcontrol *kcontrol, @@ -1791,26 +2159,309 @@ static int snd_soundblaster_e1_switch_create(struct usb_mixer_interface *mixer) NULL); } -static void dell_dock_init_vol(struct snd_usb_audio *chip, int ch, int id) +/* + * Dell WD15 dock jack detection + * + * The WD15 contains an ALC4020 USB audio controller and ALC3263 audio codec + * from Realtek. It is a UAC 1 device, and UAC 1 does not support jack + * detection. Instead, jack detection works by sending HD Audio commands over + * vendor-type USB messages. + */ + +#define HDA_VERB_CMD(V, N, D) (((N) << 20) | ((V) << 8) | (D)) + +#define REALTEK_HDA_VALUE 0x0038 + +#define REALTEK_HDA_SET 62 +#define REALTEK_MANUAL_MODE 72 +#define REALTEK_HDA_GET_OUT 88 +#define REALTEK_HDA_GET_IN 89 + +#define REALTEK_AUDIO_FUNCTION_GROUP 0x01 +#define REALTEK_LINE1 0x1a +#define REALTEK_VENDOR_REGISTERS 0x20 +#define REALTEK_HP_OUT 0x21 + +#define REALTEK_CBJ_CTRL2 0x50 + +#define REALTEK_JACK_INTERRUPT_NODE 5 + +#define REALTEK_MIC_FLAG 0x100 + +static int realtek_hda_set(struct snd_usb_audio *chip, u32 cmd) +{ + struct usb_device *dev = chip->dev; + __be32 buf = cpu_to_be32(cmd); + + return snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), REALTEK_HDA_SET, + USB_RECIP_DEVICE | USB_TYPE_VENDOR | USB_DIR_OUT, + REALTEK_HDA_VALUE, 0, &buf, sizeof(buf)); +} + +static int realtek_hda_get(struct snd_usb_audio *chip, u32 cmd, u32 *value) +{ + struct usb_device *dev = chip->dev; + int err; + __be32 buf = cpu_to_be32(cmd); + + err = snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), REALTEK_HDA_GET_OUT, + USB_RECIP_DEVICE | USB_TYPE_VENDOR | USB_DIR_OUT, + REALTEK_HDA_VALUE, 0, &buf, sizeof(buf)); + if (err < 0) + return err; + err = snd_usb_ctl_msg(dev, usb_rcvctrlpipe(dev, 0), REALTEK_HDA_GET_IN, + USB_RECIP_DEVICE | USB_TYPE_VENDOR | USB_DIR_IN, + REALTEK_HDA_VALUE, 0, &buf, sizeof(buf)); + if (err < 0) + return err; + + *value = be32_to_cpu(buf); + return 0; +} + +static int realtek_ctl_connector_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *cval = snd_kcontrol_chip(kcontrol); + struct snd_usb_audio *chip = cval->head.mixer->chip; + u32 pv = kcontrol->private_value; + u32 node_id = pv & 0xff; + u32 sense; + u32 cbj_ctrl2; + bool presence; + int err; + + CLASS(snd_usb_lock, pm)(chip); + if (pm.err < 0) + return pm.err; + err = realtek_hda_get(chip, + HDA_VERB_CMD(AC_VERB_GET_PIN_SENSE, node_id, 0), + &sense); + if (err < 0) + return err; + if (pv & REALTEK_MIC_FLAG) { + err = realtek_hda_set(chip, + HDA_VERB_CMD(AC_VERB_SET_COEF_INDEX, + REALTEK_VENDOR_REGISTERS, + REALTEK_CBJ_CTRL2)); + if (err < 0) + return err; + err = realtek_hda_get(chip, + HDA_VERB_CMD(AC_VERB_GET_PROC_COEF, + REALTEK_VENDOR_REGISTERS, 0), + &cbj_ctrl2); + if (err < 0) + return err; + } + + presence = sense & AC_PINSENSE_PRESENCE; + if (pv & REALTEK_MIC_FLAG) + presence = presence && (cbj_ctrl2 & 0x0070) == 0x0070; + ucontrol->value.integer.value[0] = presence; + return 0; +} + +static const struct snd_kcontrol_new realtek_connector_ctl_ro = { + .iface = SNDRV_CTL_ELEM_IFACE_CARD, + .name = "", /* will be filled later manually */ + .access = SNDRV_CTL_ELEM_ACCESS_READ, + .info = snd_ctl_boolean_mono_info, + .get = realtek_ctl_connector_get, +}; + +static int realtek_resume_jack(struct usb_mixer_elem_list *list) +{ + snd_ctl_notify(list->mixer->chip->card, SNDRV_CTL_EVENT_MASK_VALUE, + &list->kctl->id); + return 0; +} + +static int realtek_add_jack(struct usb_mixer_interface *mixer, + char *name, u32 val, int unitid, + const struct snd_kcontrol_new *kctl_new) { + struct usb_mixer_elem_info *cval; + struct snd_kcontrol *kctl; + + cval = kzalloc(sizeof(*cval), GFP_KERNEL); + if (!cval) + return -ENOMEM; + snd_usb_mixer_elem_init_std(&cval->head, mixer, unitid); + cval->head.resume = realtek_resume_jack; + cval->val_type = USB_MIXER_BOOLEAN; + cval->channels = 1; + cval->min = 0; + cval->max = 1; + kctl = snd_ctl_new1(kctl_new, cval); + if (!kctl) { + kfree(cval); + return -ENOMEM; + } + kctl->private_value = val; + strscpy(kctl->id.name, name, sizeof(kctl->id.name)); + kctl->private_free = snd_usb_mixer_elem_free; + return snd_usb_mixer_add_control(&cval->head, kctl); +} + +static int dell_dock_mixer_create(struct usb_mixer_interface *mixer) +{ + int err; + struct usb_device *dev = mixer->chip->dev; + + /* Power down the audio codec to avoid loud pops in the next step. */ + realtek_hda_set(mixer->chip, + HDA_VERB_CMD(AC_VERB_SET_POWER_STATE, + REALTEK_AUDIO_FUNCTION_GROUP, + AC_PWRST_D3)); + + /* + * Turn off 'manual mode' in case it was enabled. This removes the need + * to power cycle the dock after it was attached to a Windows machine. + */ + snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), REALTEK_MANUAL_MODE, + USB_RECIP_DEVICE | USB_TYPE_VENDOR | USB_DIR_OUT, + 0, 0, NULL, 0); + + err = realtek_add_jack(mixer, "Line Out Jack", REALTEK_LINE1, + REALTEK_JACK_INTERRUPT_NODE, + &realtek_connector_ctl_ro); + if (err < 0) + return err; + err = realtek_add_jack(mixer, "Headphone Jack", REALTEK_HP_OUT, + REALTEK_JACK_INTERRUPT_NODE, + &realtek_connector_ctl_ro); + if (err < 0) + return err; + err = realtek_add_jack(mixer, "Headset Mic Jack", + REALTEK_HP_OUT | REALTEK_MIC_FLAG, + REALTEK_JACK_INTERRUPT_NODE, + &realtek_connector_ctl_ro); + if (err < 0) + return err; + return 0; +} + +static void dell_dock_init_vol(struct usb_mixer_interface *mixer, int ch, int id) +{ + struct snd_usb_audio *chip = mixer->chip; u16 buf = 0; snd_usb_ctl_msg(chip->dev, usb_sndctrlpipe(chip->dev, 0), UAC_SET_CUR, USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_OUT, - ch, snd_usb_ctrl_intf(chip) | (id << 8), + (UAC_FU_VOLUME << 8) | ch, + snd_usb_ctrl_intf(mixer->hostif) | (id << 8), &buf, 2); } static int dell_dock_mixer_init(struct usb_mixer_interface *mixer) { /* fix to 0dB playback volumes */ - dell_dock_init_vol(mixer->chip, 1, 16); - dell_dock_init_vol(mixer->chip, 2, 16); - dell_dock_init_vol(mixer->chip, 1, 19); - dell_dock_init_vol(mixer->chip, 2, 19); + dell_dock_init_vol(mixer, 1, 16); + dell_dock_init_vol(mixer, 2, 16); + dell_dock_init_vol(mixer, 1, 19); + dell_dock_init_vol(mixer, 2, 19); + return 0; +} + +/* + * HP Thunderbolt Dock G2 jack detection + * + * Similar to the Dell WD15/WD19, but with different commands. + */ + +#define HP_DOCK_JACK_INTERRUPT_NODE 7 + +#define HP_DOCK_GET 37 + +#define HP_DOCK_JACK_PRESENCE 0xffb8 +#define HP_DOCK_JACK_PRESENCE_BIT BIT(2) + +#define HP_DOCK_MIC_SENSE 0xf753 +#define HP_DOCK_MIC_SENSE_COMPLETE_BIT BIT(4) + +#define HP_DOCK_MIC_SENSE_MASK (BIT(2) | BIT(1) | BIT(0)) +/* #define HP_DOCK_MIC_SENSE_PRESENT 0x2 */ +#define HP_DOCK_MIC_SENSE_NOT_PRESENT 0x4 + +static int hp_dock_ctl_connector_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *cval = snd_kcontrol_chip(kcontrol); + struct snd_usb_audio *chip = cval->head.mixer->chip; + u32 pv = kcontrol->private_value; + bool presence; + int err; + u8 buf; + + CLASS(snd_usb_lock, pm)(chip); + if (pm.err < 0) + return pm.err; + + err = snd_usb_ctl_msg(chip->dev, usb_rcvctrlpipe(chip->dev, 0), + HP_DOCK_GET, + USB_RECIP_DEVICE | USB_TYPE_VENDOR | USB_DIR_IN, + 0, HP_DOCK_JACK_PRESENCE, &buf, sizeof(buf)); + if (err < 0) + return err; + + presence = !(buf & HP_DOCK_JACK_PRESENCE_BIT); + + if (pv && presence) { + for (int i = 0; i < 20; i++) { + err = snd_usb_ctl_msg(chip->dev, usb_rcvctrlpipe(chip->dev, 0), + HP_DOCK_GET, + USB_RECIP_DEVICE | USB_TYPE_VENDOR | USB_DIR_IN, + 0, HP_DOCK_MIC_SENSE, &buf, sizeof(buf)); + if (err < 0) + return err; + + /* Mic sense is complete, we have a result. */ + if (buf & HP_DOCK_MIC_SENSE_COMPLETE_BIT) + break; + + msleep(100); + } + + /* + * If we reach the retry limit without mic sense having + * completed, buf will contain HP_DOCK_MIC_SENSE_PRESENT, + * thus presence remains true even when detection fails. + */ + if ((buf & HP_DOCK_MIC_SENSE_MASK) == HP_DOCK_MIC_SENSE_NOT_PRESENT) + presence = false; + } + ucontrol->value.integer.value[0] = presence; return 0; } +static const struct snd_kcontrol_new hp_dock_connector_ctl_ro = { + .iface = SNDRV_CTL_ELEM_IFACE_CARD, + .name = "", /* will be filled later manually */ + .access = SNDRV_CTL_ELEM_ACCESS_READ, + .info = snd_ctl_boolean_mono_info, + .get = hp_dock_ctl_connector_get, +}; + +static int hp_dock_mixer_create(struct usb_mixer_interface *mixer) +{ + int err; + + err = realtek_add_jack(mixer, "Headsets Playback Jack", 0, + HP_DOCK_JACK_INTERRUPT_NODE, + &hp_dock_connector_ctl_ro); + if (err < 0) + return err; + + err = realtek_add_jack(mixer, "Headset Capture Jack", 1, + HP_DOCK_JACK_INTERRUPT_NODE, + &hp_dock_connector_ctl_ro); + if (err < 0) + return err; + + return 0; +} + + /* RME Class Compliant device quirks */ #define SND_RME_GET_STATUS1 23 @@ -1825,15 +2476,15 @@ static int dell_dock_mixer_init(struct usb_mixer_interface *mixer) #define SND_RME_CLK_FREQMUL_SHIFT 18 #define SND_RME_CLK_FREQMUL_MASK 0x7 #define SND_RME_CLK_SYSTEM(x) \ - ((x >> SND_RME_CLK_SYSTEM_SHIFT) & SND_RME_CLK_SYSTEM_MASK) + (((x) >> SND_RME_CLK_SYSTEM_SHIFT) & SND_RME_CLK_SYSTEM_MASK) #define SND_RME_CLK_AES(x) \ - ((x >> SND_RME_CLK_AES_SHIFT) & SND_RME_CLK_AES_SPDIF_MASK) + (((x) >> SND_RME_CLK_AES_SHIFT) & SND_RME_CLK_AES_SPDIF_MASK) #define SND_RME_CLK_SPDIF(x) \ - ((x >> SND_RME_CLK_SPDIF_SHIFT) & SND_RME_CLK_AES_SPDIF_MASK) + (((x) >> SND_RME_CLK_SPDIF_SHIFT) & SND_RME_CLK_AES_SPDIF_MASK) #define SND_RME_CLK_SYNC(x) \ - ((x >> SND_RME_CLK_SYNC_SHIFT) & SND_RME_CLK_SYNC_MASK) + (((x) >> SND_RME_CLK_SYNC_SHIFT) & SND_RME_CLK_SYNC_MASK) #define SND_RME_CLK_FREQMUL(x) \ - ((x >> SND_RME_CLK_FREQMUL_SHIFT) & SND_RME_CLK_FREQMUL_MASK) + (((x) >> SND_RME_CLK_FREQMUL_SHIFT) & SND_RME_CLK_FREQMUL_MASK) #define SND_RME_CLK_AES_LOCK 0x1 #define SND_RME_CLK_AES_SYNC 0x4 #define SND_RME_CLK_SPDIF_LOCK 0x2 @@ -1842,9 +2493,9 @@ static int dell_dock_mixer_init(struct usb_mixer_interface *mixer) #define SND_RME_SPDIF_FORMAT_SHIFT 5 #define SND_RME_BINARY_MASK 0x1 #define SND_RME_SPDIF_IF(x) \ - ((x >> SND_RME_SPDIF_IF_SHIFT) & SND_RME_BINARY_MASK) + (((x) >> SND_RME_SPDIF_IF_SHIFT) & SND_RME_BINARY_MASK) #define SND_RME_SPDIF_FORMAT(x) \ - ((x >> SND_RME_SPDIF_FORMAT_SHIFT) & SND_RME_BINARY_MASK) + (((x) >> SND_RME_SPDIF_FORMAT_SHIFT) & SND_RME_BINARY_MASK) static const u32 snd_rme_rate_table[] = { 32000, 44100, 48000, 50000, @@ -1853,6 +2504,7 @@ static const u32 snd_rme_rate_table[] = { 256000, 352800, 384000, 400000, 512000, 705600, 768000, 800000 }; + /* maximum number of items for AES and S/PDIF rates for above table */ #define SND_RME_RATE_IDX_AES_SPDIF_NUM 12 @@ -1892,14 +2544,11 @@ static int snd_rme_get_status1(struct snd_kcontrol *kcontrol, { struct usb_mixer_elem_list *list = snd_kcontrol_chip(kcontrol); struct snd_usb_audio *chip = list->mixer->chip; - int err; - err = snd_usb_lock_shutdown(chip); - if (err < 0) - return err; - err = snd_rme_read_value(chip, SND_RME_GET_STATUS1, status1); - snd_usb_unlock_shutdown(chip); - return err; + CLASS(snd_usb_lock, pm)(chip); + if (pm.err < 0) + return pm.err; + return snd_rme_read_value(chip, SND_RME_GET_STATUS1, status1); } static int snd_rme_rate_get(struct snd_kcontrol *kcontrol, @@ -2016,22 +2665,19 @@ static int snd_rme_current_freq_get(struct snd_kcontrol *kcontrol, unsigned int freq; int err; - err = snd_usb_lock_shutdown(chip); - if (err < 0) - return err; + CLASS(snd_usb_lock, pm)(chip); + if (pm.err < 0) + return pm.err; err = snd_rme_read_value(chip, SND_RME_GET_STATUS1, &status1); if (err < 0) - goto end; + return err; err = snd_rme_read_value(chip, SND_RME_GET_CURRENT_FREQ, &den); if (err < 0) - goto end; + return err; freq = (den == 0) ? 0 : div64_u64(num, den); freq <<= SND_RME_CLK_FREQMUL(status1); ucontrol->value.integer.value[0] = freq; - -end: - snd_usb_unlock_shutdown(chip); - return err; + return 0; } static int snd_rme_rate_info(struct snd_kcontrol *kcontrol, @@ -2216,26 +2862,34 @@ enum { #define SND_BBFPRO_CTL_REG2_PAD_AN1 4 #define SND_BBFPRO_CTL_REG2_PAD_AN2 5 -#define SND_BBFPRO_MIXER_IDX_MASK 0x1ff +#define SND_BBFPRO_MIXER_MAIN_OUT_CH_OFFSET 992 +#define SND_BBFPRO_MIXER_IDX_MASK 0x3ff #define SND_BBFPRO_MIXER_VAL_MASK 0x3ffff #define SND_BBFPRO_MIXER_VAL_SHIFT 9 #define SND_BBFPRO_MIXER_VAL_MIN 0 // -inf #define SND_BBFPRO_MIXER_VAL_MAX 65536 // +6dB +#define SND_BBFPRO_GAIN_CHANNEL_MASK 0x03 +#define SND_BBFPRO_GAIN_CHANNEL_SHIFT 7 +#define SND_BBFPRO_GAIN_VAL_MASK 0x7f +#define SND_BBFPRO_GAIN_VAL_MIN 0 +#define SND_BBFPRO_GAIN_VAL_MIC_MAX 65 +#define SND_BBFPRO_GAIN_VAL_LINE_MAX 18 // 9db in 0.5db incraments + #define SND_BBFPRO_USBREQ_CTL_REG1 0x10 #define SND_BBFPRO_USBREQ_CTL_REG2 0x17 +#define SND_BBFPRO_USBREQ_GAIN 0x1a #define SND_BBFPRO_USBREQ_MIXER 0x12 static int snd_bbfpro_ctl_update(struct usb_mixer_interface *mixer, u8 reg, u8 index, u8 value) { - int err; u16 usb_req, usb_idx, usb_val; struct snd_usb_audio *chip = mixer->chip; - err = snd_usb_lock_shutdown(chip); - if (err < 0) - return err; + CLASS(snd_usb_lock, pm)(chip); + if (pm.err < 0) + return pm.err; if (reg == SND_BBFPRO_CTL_REG1) { usb_req = SND_BBFPRO_USBREQ_CTL_REG1; @@ -2243,22 +2897,19 @@ static int snd_bbfpro_ctl_update(struct usb_mixer_interface *mixer, u8 reg, usb_idx = 3; usb_val = value ? 3 : 0; } else { - usb_idx = 1 << index; + usb_idx = BIT(index); usb_val = value ? usb_idx : 0; } } else { usb_req = SND_BBFPRO_USBREQ_CTL_REG2; - usb_idx = 1 << index; + usb_idx = BIT(index); usb_val = value ? usb_idx : 0; } - err = snd_usb_ctl_msg(chip->dev, - usb_sndctrlpipe(chip->dev, 0), usb_req, - USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE, - usb_val, usb_idx, NULL, 0); - - snd_usb_unlock_shutdown(chip); - return err; + return snd_usb_ctl_msg(chip->dev, + usb_sndctrlpipe(chip->dev, 0), usb_req, + USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + usb_val, usb_idx, NULL, 0); } static int snd_bbfpro_ctl_get(struct snd_kcontrol *kcontrol, @@ -2370,18 +3021,121 @@ static int snd_bbfpro_ctl_resume(struct usb_mixer_elem_list *list) return snd_bbfpro_ctl_update(list->mixer, reg, idx, value); } +static int snd_bbfpro_gain_update(struct usb_mixer_interface *mixer, + u8 channel, u8 gain) +{ + struct snd_usb_audio *chip = mixer->chip; + + if (channel < 2) { + // XLR preamp: 3-bit fine, 5-bit coarse; special case >60 + if (gain < 60) + gain = ((gain % 3) << 5) | (gain / 3); + else + gain = ((gain % 6) << 5) | (60 / 3); + } + + CLASS(snd_usb_lock, pm)(chip); + if (pm.err < 0) + return pm.err; + + return snd_usb_ctl_msg(chip->dev, + usb_sndctrlpipe(chip->dev, 0), + SND_BBFPRO_USBREQ_GAIN, + USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + gain, channel, NULL, 0); +} + +static int snd_bbfpro_gain_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int value = kcontrol->private_value & SND_BBFPRO_GAIN_VAL_MASK; + + ucontrol->value.integer.value[0] = value; + return 0; +} + +static int snd_bbfpro_gain_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + int pv, channel; + + pv = kcontrol->private_value; + channel = (pv >> SND_BBFPRO_GAIN_CHANNEL_SHIFT) & + SND_BBFPRO_GAIN_CHANNEL_MASK; + + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = SND_BBFPRO_GAIN_VAL_MIN; + + if (channel < 2) + uinfo->value.integer.max = SND_BBFPRO_GAIN_VAL_MIC_MAX; + else + uinfo->value.integer.max = SND_BBFPRO_GAIN_VAL_LINE_MAX; + + return 0; +} + +static int snd_bbfpro_gain_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int pv, channel, old_value, value, err; + + struct usb_mixer_elem_list *list = snd_kcontrol_chip(kcontrol); + struct usb_mixer_interface *mixer = list->mixer; + + pv = kcontrol->private_value; + channel = (pv >> SND_BBFPRO_GAIN_CHANNEL_SHIFT) & + SND_BBFPRO_GAIN_CHANNEL_MASK; + old_value = pv & SND_BBFPRO_GAIN_VAL_MASK; + value = ucontrol->value.integer.value[0]; + + if (value < SND_BBFPRO_GAIN_VAL_MIN) + return -EINVAL; + + if (channel < 2) { + if (value > SND_BBFPRO_GAIN_VAL_MIC_MAX) + return -EINVAL; + } else { + if (value > SND_BBFPRO_GAIN_VAL_LINE_MAX) + return -EINVAL; + } + + if (value == old_value) + return 0; + + err = snd_bbfpro_gain_update(mixer, channel, value); + if (err < 0) + return err; + + kcontrol->private_value = + (channel << SND_BBFPRO_GAIN_CHANNEL_SHIFT) | value; + return 1; +} + +static int snd_bbfpro_gain_resume(struct usb_mixer_elem_list *list) +{ + int pv, channel, value; + struct snd_kcontrol *kctl = list->kctl; + + pv = kctl->private_value; + channel = (pv >> SND_BBFPRO_GAIN_CHANNEL_SHIFT) & + SND_BBFPRO_GAIN_CHANNEL_MASK; + value = pv & SND_BBFPRO_GAIN_VAL_MASK; + + return snd_bbfpro_gain_update(list->mixer, channel, value); +} + static int snd_bbfpro_vol_update(struct usb_mixer_interface *mixer, u16 index, u32 value) { struct snd_usb_audio *chip = mixer->chip; - int err; u16 idx; u16 usb_idx, usb_val; u32 v; - err = snd_usb_lock_shutdown(chip); - if (err < 0) - return err; + CLASS(snd_usb_lock, pm)(chip); + if (pm.err < 0) + return pm.err; idx = index & SND_BBFPRO_MIXER_IDX_MASK; // 18 bit linear volume, split so 2 bits end up in index. @@ -2389,15 +3143,12 @@ static int snd_bbfpro_vol_update(struct usb_mixer_interface *mixer, u16 index, usb_idx = idx | (v & 0x3) << 14; usb_val = (v >> 2) & 0xffff; - err = snd_usb_ctl_msg(chip->dev, - usb_sndctrlpipe(chip->dev, 0), - SND_BBFPRO_USBREQ_MIXER, - USB_DIR_OUT | USB_TYPE_VENDOR | - USB_RECIP_DEVICE, - usb_val, usb_idx, NULL, 0); - - snd_usb_unlock_shutdown(chip); - return err; + return snd_usb_ctl_msg(chip->dev, + usb_sndctrlpipe(chip->dev, 0), + SND_BBFPRO_USBREQ_MIXER, + USB_DIR_OUT | USB_TYPE_VENDOR | + USB_RECIP_DEVICE, + usb_val, usb_idx, NULL, 0); } static int snd_bbfpro_vol_get(struct snd_kcontrol *kcontrol, @@ -2465,6 +3216,15 @@ static const struct snd_kcontrol_new snd_bbfpro_ctl_control = { .put = snd_bbfpro_ctl_put }; +static const struct snd_kcontrol_new snd_bbfpro_gain_control = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .index = 0, + .info = snd_bbfpro_gain_info, + .get = snd_bbfpro_gain_get, + .put = snd_bbfpro_gain_put +}; + static const struct snd_kcontrol_new snd_bbfpro_vol_control = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, @@ -2488,6 +3248,18 @@ static int snd_bbfpro_ctl_add(struct usb_mixer_interface *mixer, u8 reg, &knew, NULL); } +static int snd_bbfpro_gain_add(struct usb_mixer_interface *mixer, u8 channel, + char *name) +{ + struct snd_kcontrol_new knew = snd_bbfpro_gain_control; + + knew.name = name; + knew.private_value = channel << SND_BBFPRO_GAIN_CHANNEL_SHIFT; + + return add_single_ctl_with_resume(mixer, 0, snd_bbfpro_gain_resume, + &knew, NULL); +} + static int snd_bbfpro_vol_add(struct usb_mixer_interface *mixer, u16 index, char *name) { @@ -2535,6 +3307,29 @@ static int snd_bbfpro_controls_create(struct usb_mixer_interface *mixer) } } + // Main out volume + for (i = 0 ; i < 12 ; ++i) { + snprintf(name, sizeof(name), "Main-Out %s", output[i]); + // Main outs are offset to 992 + err = snd_bbfpro_vol_add(mixer, + i + SND_BBFPRO_MIXER_MAIN_OUT_CH_OFFSET, + name); + if (err < 0) + return err; + } + + // Input gain + for (i = 0 ; i < 4 ; ++i) { + if (i < 2) + snprintf(name, sizeof(name), "Mic-%s Gain", input[i]); + else + snprintf(name, sizeof(name), "Line-%s Gain", input[i]); + + err = snd_bbfpro_gain_add(mixer, i, name); + if (err < 0) + return err; + } + // Control Reg 1 err = snd_bbfpro_ctl_add(mixer, SND_BBFPRO_CTL_REG1, SND_BBFPRO_CTL_REG1_CLK_OPTICAL, @@ -2600,6 +3395,947 @@ static int snd_bbfpro_controls_create(struct usb_mixer_interface *mixer) return 0; } +/* + * RME Digiface USB + */ + +#define RME_DIGIFACE_READ_STATUS 17 +#define RME_DIGIFACE_STATUS_REG0L 0 +#define RME_DIGIFACE_STATUS_REG0H 1 +#define RME_DIGIFACE_STATUS_REG1L 2 +#define RME_DIGIFACE_STATUS_REG1H 3 +#define RME_DIGIFACE_STATUS_REG2L 4 +#define RME_DIGIFACE_STATUS_REG2H 5 +#define RME_DIGIFACE_STATUS_REG3L 6 +#define RME_DIGIFACE_STATUS_REG3H 7 + +#define RME_DIGIFACE_CTL_REG1 16 +#define RME_DIGIFACE_CTL_REG2 18 + +/* Reg is overloaded, 0-7 for status halfwords or 16 or 18 for control registers */ +#define RME_DIGIFACE_REGISTER(reg, mask) (((reg) << 16) | (mask)) +#define RME_DIGIFACE_INVERT BIT(31) + +static int snd_rme_digiface_write_reg(struct snd_kcontrol *kcontrol, int item, u16 mask, u16 val) +{ + struct usb_mixer_elem_list *list = snd_kcontrol_chip(kcontrol); + struct snd_usb_audio *chip = list->mixer->chip; + struct usb_device *dev = chip->dev; + int err; + + err = snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), + item, + USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + val, mask, NULL, 0); + if (err < 0) + dev_err(&dev->dev, + "unable to issue control set request %d (ret = %d)", + item, err); + return err; +} + +static int snd_rme_digiface_read_status(struct snd_kcontrol *kcontrol, u32 status[4]) +{ + struct usb_mixer_elem_list *list = snd_kcontrol_chip(kcontrol); + struct snd_usb_audio *chip = list->mixer->chip; + struct usb_device *dev = chip->dev; + __le32 buf[4]; + int err; + + err = snd_usb_ctl_msg(dev, usb_rcvctrlpipe(dev, 0), + RME_DIGIFACE_READ_STATUS, + USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + 0, 0, + buf, sizeof(buf)); + if (err < 0) { + dev_err(&dev->dev, + "unable to issue status read request (ret = %d)", + err); + } else { + for (int i = 0; i < ARRAY_SIZE(buf); i++) + status[i] = le32_to_cpu(buf[i]); + } + return err; +} + +static int snd_rme_digiface_get_status_val(struct snd_kcontrol *kcontrol) +{ + int err; + u32 status[4]; + bool invert = kcontrol->private_value & RME_DIGIFACE_INVERT; + u8 reg = (kcontrol->private_value >> 16) & 0xff; + u16 mask = kcontrol->private_value & 0xffff; + u16 val; + + err = snd_rme_digiface_read_status(kcontrol, status); + if (err < 0) + return err; + + switch (reg) { + /* Status register halfwords */ + case RME_DIGIFACE_STATUS_REG0L ... RME_DIGIFACE_STATUS_REG3H: + break; + case RME_DIGIFACE_CTL_REG1: /* Control register 1, present in halfword 3L */ + reg = RME_DIGIFACE_STATUS_REG3L; + break; + case RME_DIGIFACE_CTL_REG2: /* Control register 2, present in halfword 3H */ + reg = RME_DIGIFACE_STATUS_REG3H; + break; + default: + return -EINVAL; + } + + if (reg & 1) + val = status[reg >> 1] >> 16; + else + val = status[reg >> 1] & 0xffff; + + if (invert) + val ^= mask; + + return field_get(mask, val); +} + +static int snd_rme_digiface_rate_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int freq = snd_rme_digiface_get_status_val(kcontrol); + + if (freq < 0) + return freq; + if (freq >= ARRAY_SIZE(snd_rme_rate_table)) + return -EIO; + + ucontrol->value.integer.value[0] = snd_rme_rate_table[freq]; + return 0; +} + +static int snd_rme_digiface_enum_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int val = snd_rme_digiface_get_status_val(kcontrol); + + if (val < 0) + return val; + + ucontrol->value.enumerated.item[0] = val; + return 0; +} + +static int snd_rme_digiface_enum_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + bool invert = kcontrol->private_value & RME_DIGIFACE_INVERT; + u8 reg = (kcontrol->private_value >> 16) & 0xff; + u16 mask = kcontrol->private_value & 0xffff; + u16 val = field_prep(mask, ucontrol->value.enumerated.item[0]); + + if (invert) + val ^= mask; + + return snd_rme_digiface_write_reg(kcontrol, reg, mask, val); +} + +static int snd_rme_digiface_current_sync_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int ret = snd_rme_digiface_enum_get(kcontrol, ucontrol); + + /* 7 means internal for current sync */ + if (ucontrol->value.enumerated.item[0] == 7) + ucontrol->value.enumerated.item[0] = 0; + + return ret; +} + +static int snd_rme_digiface_sync_state_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + u32 status[4]; + int err; + bool valid, sync; + + err = snd_rme_digiface_read_status(kcontrol, status); + if (err < 0) + return err; + + valid = status[0] & BIT(kcontrol->private_value); + sync = status[0] & BIT(5 + kcontrol->private_value); + + if (!valid) + ucontrol->value.enumerated.item[0] = SND_RME_CLOCK_NOLOCK; + else if (!sync) + ucontrol->value.enumerated.item[0] = SND_RME_CLOCK_LOCK; + else + ucontrol->value.enumerated.item[0] = SND_RME_CLOCK_SYNC; + return 0; +} + +static int snd_rme_digiface_format_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + static const char *const format[] = { + "ADAT", "S/PDIF" + }; + + return snd_ctl_enum_info(uinfo, 1, + ARRAY_SIZE(format), format); +} + +static int snd_rme_digiface_sync_source_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + static const char *const sync_sources[] = { + "Internal", "Input 1", "Input 2", "Input 3", "Input 4" + }; + + return snd_ctl_enum_info(uinfo, 1, + ARRAY_SIZE(sync_sources), sync_sources); +} + +static int snd_rme_digiface_rate_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 200000; + uinfo->value.integer.step = 0; + return 0; +} + +static const struct snd_kcontrol_new snd_rme_digiface_controls[] = { + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Input 1 Sync", + .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = snd_rme_sync_state_info, + .get = snd_rme_digiface_sync_state_get, + .private_value = 0, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Input 1 Format", + .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = snd_rme_digiface_format_info, + .get = snd_rme_digiface_enum_get, + .private_value = RME_DIGIFACE_REGISTER(RME_DIGIFACE_STATUS_REG0H, BIT(0)) | + RME_DIGIFACE_INVERT, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Input 1 Rate", + .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = snd_rme_digiface_rate_info, + .get = snd_rme_digiface_rate_get, + .private_value = RME_DIGIFACE_REGISTER(RME_DIGIFACE_STATUS_REG1L, GENMASK(3, 0)), + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Input 2 Sync", + .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = snd_rme_sync_state_info, + .get = snd_rme_digiface_sync_state_get, + .private_value = 1, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Input 2 Format", + .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = snd_rme_digiface_format_info, + .get = snd_rme_digiface_enum_get, + .private_value = RME_DIGIFACE_REGISTER(RME_DIGIFACE_STATUS_REG0L, BIT(13)) | + RME_DIGIFACE_INVERT, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Input 2 Rate", + .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = snd_rme_digiface_rate_info, + .get = snd_rme_digiface_rate_get, + .private_value = RME_DIGIFACE_REGISTER(RME_DIGIFACE_STATUS_REG1L, GENMASK(7, 4)), + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Input 3 Sync", + .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = snd_rme_sync_state_info, + .get = snd_rme_digiface_sync_state_get, + .private_value = 2, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Input 3 Format", + .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = snd_rme_digiface_format_info, + .get = snd_rme_digiface_enum_get, + .private_value = RME_DIGIFACE_REGISTER(RME_DIGIFACE_STATUS_REG0L, BIT(14)) | + RME_DIGIFACE_INVERT, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Input 3 Rate", + .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = snd_rme_digiface_rate_info, + .get = snd_rme_digiface_rate_get, + .private_value = RME_DIGIFACE_REGISTER(RME_DIGIFACE_STATUS_REG1L, GENMASK(11, 8)), + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Input 4 Sync", + .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = snd_rme_sync_state_info, + .get = snd_rme_digiface_sync_state_get, + .private_value = 3, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Input 4 Format", + .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = snd_rme_digiface_format_info, + .get = snd_rme_digiface_enum_get, + .private_value = RME_DIGIFACE_REGISTER(RME_DIGIFACE_STATUS_REG0L, GENMASK(15, 12)) | + RME_DIGIFACE_INVERT, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Input 4 Rate", + .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = snd_rme_digiface_rate_info, + .get = snd_rme_digiface_rate_get, + .private_value = RME_DIGIFACE_REGISTER(RME_DIGIFACE_STATUS_REG1L, GENMASK(3, 0)), + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Output 1 Format", + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .info = snd_rme_digiface_format_info, + .get = snd_rme_digiface_enum_get, + .put = snd_rme_digiface_enum_put, + .private_value = RME_DIGIFACE_REGISTER(RME_DIGIFACE_CTL_REG2, BIT(0)), + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Output 2 Format", + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .info = snd_rme_digiface_format_info, + .get = snd_rme_digiface_enum_get, + .put = snd_rme_digiface_enum_put, + .private_value = RME_DIGIFACE_REGISTER(RME_DIGIFACE_CTL_REG2, BIT(1)), + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Output 3 Format", + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .info = snd_rme_digiface_format_info, + .get = snd_rme_digiface_enum_get, + .put = snd_rme_digiface_enum_put, + .private_value = RME_DIGIFACE_REGISTER(RME_DIGIFACE_CTL_REG2, BIT(3)), + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Output 4 Format", + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .info = snd_rme_digiface_format_info, + .get = snd_rme_digiface_enum_get, + .put = snd_rme_digiface_enum_put, + .private_value = RME_DIGIFACE_REGISTER(RME_DIGIFACE_CTL_REG2, BIT(4)), + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Sync Source", + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .info = snd_rme_digiface_sync_source_info, + .get = snd_rme_digiface_enum_get, + .put = snd_rme_digiface_enum_put, + .private_value = RME_DIGIFACE_REGISTER(RME_DIGIFACE_CTL_REG1, GENMASK(2, 0)), + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Current Sync Source", + .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = snd_rme_digiface_sync_source_info, + .get = snd_rme_digiface_current_sync_get, + .private_value = RME_DIGIFACE_REGISTER(RME_DIGIFACE_STATUS_REG0L, GENMASK(12, 10)), + }, + { + /* + * This is writeable, but it is only set by the PCM rate. + * Mixer apps currently need to drive the mixer using raw USB requests, + * so they can also change this that way to configure the rate for + * stand-alone operation when the PCM is closed. + */ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "System Rate", + .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = snd_rme_rate_info, + .get = snd_rme_digiface_rate_get, + .private_value = RME_DIGIFACE_REGISTER(RME_DIGIFACE_CTL_REG1, GENMASK(6, 3)), + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Current Rate", + .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = snd_rme_rate_info, + .get = snd_rme_digiface_rate_get, + .private_value = RME_DIGIFACE_REGISTER(RME_DIGIFACE_STATUS_REG1H, GENMASK(7, 4)), + } +}; + +static int snd_rme_digiface_controls_create(struct usb_mixer_interface *mixer) +{ + int err, i; + + for (i = 0; i < ARRAY_SIZE(snd_rme_digiface_controls); ++i) { + err = add_single_ctl_with_resume(mixer, 0, + NULL, + &snd_rme_digiface_controls[i], + NULL); + if (err < 0) + return err; + } + + return 0; +} + +/* + * Pioneer DJ / AlphaTheta DJM Mixers + * + * These devices generally have options for soft-switching the playback and + * capture sources in addition to the recording level. Although different + * devices have different configurations, there seems to be canonical values + * for specific capture/playback types: See the definitions of these below. + * + * The wValue is masked with the stereo channel number. e.g. Setting Ch2 to + * capture phono would be 0x0203. Capture, playback and capture level have + * different wIndexes. + */ + +// Capture types +#define SND_DJM_CAP_LINE 0x00 +#define SND_DJM_CAP_CDLINE 0x01 +#define SND_DJM_CAP_DIGITAL 0x02 +#define SND_DJM_CAP_PHONO 0x03 +#define SND_DJM_CAP_PREFADER 0x05 +#define SND_DJM_CAP_PFADER 0x06 +#define SND_DJM_CAP_XFADERA 0x07 +#define SND_DJM_CAP_XFADERB 0x08 +#define SND_DJM_CAP_MIC 0x09 +#define SND_DJM_CAP_AUX 0x0d +#define SND_DJM_CAP_RECOUT 0x0a +#define SND_DJM_CAP_RECOUT_NOMIC 0x0e +#define SND_DJM_CAP_NONE 0x0f +#define SND_DJM_CAP_FXSEND 0x10 +#define SND_DJM_CAP_CH1PFADER 0x11 +#define SND_DJM_CAP_CH2PFADER 0x12 +#define SND_DJM_CAP_CH3PFADER 0x13 +#define SND_DJM_CAP_CH4PFADER 0x14 +#define SND_DJM_CAP_EXT1SEND 0x21 +#define SND_DJM_CAP_EXT2SEND 0x22 +#define SND_DJM_CAP_CH1PREFADER 0x31 +#define SND_DJM_CAP_CH2PREFADER 0x32 +#define SND_DJM_CAP_CH3PREFADER 0x33 +#define SND_DJM_CAP_CH4PREFADER 0x34 + +// Playback types +#define SND_DJM_PB_CH1 0x00 +#define SND_DJM_PB_CH2 0x01 +#define SND_DJM_PB_AUX 0x04 + +#define SND_DJM_WINDEX_CAP 0x8002 +#define SND_DJM_WINDEX_CAPLVL 0x8003 +#define SND_DJM_WINDEX_PB 0x8016 + +// kcontrol->private_value layout +#define SND_DJM_VALUE_MASK 0x0000ffff +#define SND_DJM_GROUP_MASK 0x00ff0000 +#define SND_DJM_DEVICE_MASK 0xff000000 +#define SND_DJM_GROUP_SHIFT 16 +#define SND_DJM_DEVICE_SHIFT 24 + +// device table index +// used for the snd_djm_devices table, so please update accordingly +#define SND_DJM_250MK2_IDX 0x0 +#define SND_DJM_750_IDX 0x1 +#define SND_DJM_850_IDX 0x2 +#define SND_DJM_900NXS2_IDX 0x3 +#define SND_DJM_750MK2_IDX 0x4 +#define SND_DJM_450_IDX 0x5 +#define SND_DJM_A9_IDX 0x6 +#define SND_DJM_V10_IDX 0x7 + +#define SND_DJM_CTL(_name, suffix, _default_value, _windex) { \ + .name = _name, \ + .options = snd_djm_opts_##suffix, \ + .noptions = ARRAY_SIZE(snd_djm_opts_##suffix), \ + .default_value = _default_value, \ + .wIndex = _windex } + +#define SND_DJM_DEVICE(suffix) { \ + .controls = snd_djm_ctls_##suffix, \ + .ncontrols = ARRAY_SIZE(snd_djm_ctls_##suffix) } + +struct snd_djm_device { + const char *name; + const struct snd_djm_ctl *controls; + size_t ncontrols; +}; + +struct snd_djm_ctl { + const char *name; + const u16 *options; + size_t noptions; + u16 default_value; + u16 wIndex; +}; + +static const char *snd_djm_get_label_caplevel_common(u16 wvalue) +{ + switch (wvalue) { + case 0x0000: return "-19dB"; + case 0x0100: return "-15dB"; + case 0x0200: return "-10dB"; + case 0x0300: return "-5dB"; + default: return NULL; + } +}; + +// Models like DJM-A9 or DJM-V10 have different capture levels than others +static const char *snd_djm_get_label_caplevel_high(u16 wvalue) +{ + switch (wvalue) { + case 0x0000: return "+15dB"; + case 0x0100: return "+12dB"; + case 0x0200: return "+9dB"; + case 0x0300: return "+6dB"; + case 0x0400: return "+3dB"; + case 0x0500: return "0dB"; + default: return NULL; + } +}; + +static const char *snd_djm_get_label_cap_common(u16 wvalue) +{ + switch (wvalue & 0x00ff) { + case SND_DJM_CAP_LINE: return "Control Tone LINE"; + case SND_DJM_CAP_CDLINE: return "Control Tone CD/LINE"; + case SND_DJM_CAP_DIGITAL: return "Control Tone DIGITAL"; + case SND_DJM_CAP_PHONO: return "Control Tone PHONO"; + case SND_DJM_CAP_PFADER: return "Post Fader"; + case SND_DJM_CAP_XFADERA: return "Cross Fader A"; + case SND_DJM_CAP_XFADERB: return "Cross Fader B"; + case SND_DJM_CAP_MIC: return "Mic"; + case SND_DJM_CAP_RECOUT: return "Rec Out"; + case SND_DJM_CAP_RECOUT_NOMIC: return "Rec Out without Mic"; + case SND_DJM_CAP_AUX: return "Aux"; + case SND_DJM_CAP_NONE: return "None"; + case SND_DJM_CAP_FXSEND: return "FX SEND"; + case SND_DJM_CAP_CH1PREFADER: return "Pre Fader Ch1"; + case SND_DJM_CAP_CH2PREFADER: return "Pre Fader Ch2"; + case SND_DJM_CAP_CH3PREFADER: return "Pre Fader Ch3"; + case SND_DJM_CAP_CH4PREFADER: return "Pre Fader Ch4"; + case SND_DJM_CAP_CH1PFADER: return "Post Fader Ch1"; + case SND_DJM_CAP_CH2PFADER: return "Post Fader Ch2"; + case SND_DJM_CAP_CH3PFADER: return "Post Fader Ch3"; + case SND_DJM_CAP_CH4PFADER: return "Post Fader Ch4"; + case SND_DJM_CAP_EXT1SEND: return "EXT1 SEND"; + case SND_DJM_CAP_EXT2SEND: return "EXT2 SEND"; + default: return NULL; + } +}; + +// The DJM-850 has different values for CD/LINE and LINE capture +// control options than the other DJM declared in this file. +static const char *snd_djm_get_label_cap_850(u16 wvalue) +{ + switch (wvalue & 0x00ff) { + case 0x00: return "Control Tone CD/LINE"; + case 0x01: return "Control Tone LINE"; + default: return snd_djm_get_label_cap_common(wvalue); + } +}; + +static const char *snd_djm_get_label_caplevel(u8 device_idx, u16 wvalue) +{ + switch (device_idx) { + case SND_DJM_A9_IDX: return snd_djm_get_label_caplevel_high(wvalue); + case SND_DJM_V10_IDX: return snd_djm_get_label_caplevel_high(wvalue); + default: return snd_djm_get_label_caplevel_common(wvalue); + } +}; + +static const char *snd_djm_get_label_cap(u8 device_idx, u16 wvalue) +{ + switch (device_idx) { + case SND_DJM_850_IDX: return snd_djm_get_label_cap_850(wvalue); + default: return snd_djm_get_label_cap_common(wvalue); + } +}; + +static const char *snd_djm_get_label_pb(u16 wvalue) +{ + switch (wvalue & 0x00ff) { + case SND_DJM_PB_CH1: return "Ch1"; + case SND_DJM_PB_CH2: return "Ch2"; + case SND_DJM_PB_AUX: return "Aux"; + default: return NULL; + } +}; + +static const char *snd_djm_get_label(u8 device_idx, u16 wvalue, u16 windex) +{ + switch (windex) { + case SND_DJM_WINDEX_CAPLVL: return snd_djm_get_label_caplevel(device_idx, wvalue); + case SND_DJM_WINDEX_CAP: return snd_djm_get_label_cap(device_idx, wvalue); + case SND_DJM_WINDEX_PB: return snd_djm_get_label_pb(wvalue); + default: return NULL; + } +}; + +// common DJM capture level option values +static const u16 snd_djm_opts_cap_level[] = { + 0x0000, 0x0100, 0x0200, 0x0300 }; + +// DJM-250MK2 +static const u16 snd_djm_opts_250mk2_cap1[] = { + 0x0103, 0x0100, 0x0106, 0x0107, 0x0108, 0x0109, 0x010d, 0x010a }; + +static const u16 snd_djm_opts_250mk2_cap2[] = { + 0x0203, 0x0200, 0x0206, 0x0207, 0x0208, 0x0209, 0x020d, 0x020a }; + +static const u16 snd_djm_opts_250mk2_cap3[] = { + 0x030a, 0x0311, 0x0312, 0x0307, 0x0308, 0x0309, 0x030d }; + +static const u16 snd_djm_opts_250mk2_pb1[] = { 0x0100, 0x0101, 0x0104 }; +static const u16 snd_djm_opts_250mk2_pb2[] = { 0x0200, 0x0201, 0x0204 }; +static const u16 snd_djm_opts_250mk2_pb3[] = { 0x0300, 0x0301, 0x0304 }; + +static const struct snd_djm_ctl snd_djm_ctls_250mk2[] = { + SND_DJM_CTL("Master Input Level Capture Switch", cap_level, 0, SND_DJM_WINDEX_CAPLVL), + SND_DJM_CTL("Input 1 Capture Switch", 250mk2_cap1, 2, SND_DJM_WINDEX_CAP), + SND_DJM_CTL("Input 2 Capture Switch", 250mk2_cap2, 2, SND_DJM_WINDEX_CAP), + SND_DJM_CTL("Input 3 Capture Switch", 250mk2_cap3, 0, SND_DJM_WINDEX_CAP), + SND_DJM_CTL("Output 1 Playback Switch", 250mk2_pb1, 0, SND_DJM_WINDEX_PB), + SND_DJM_CTL("Output 2 Playback Switch", 250mk2_pb2, 1, SND_DJM_WINDEX_PB), + SND_DJM_CTL("Output 3 Playback Switch", 250mk2_pb3, 2, SND_DJM_WINDEX_PB) +}; + +// DJM-450 +static const u16 snd_djm_opts_450_cap1[] = { + 0x0103, 0x0100, 0x0106, 0x0107, 0x0108, 0x0109, 0x010d, 0x010a }; + +static const u16 snd_djm_opts_450_cap2[] = { + 0x0203, 0x0200, 0x0206, 0x0207, 0x0208, 0x0209, 0x020d, 0x020a }; + +static const u16 snd_djm_opts_450_cap3[] = { + 0x030a, 0x0311, 0x0312, 0x0307, 0x0308, 0x0309, 0x030d }; + +static const u16 snd_djm_opts_450_pb1[] = { 0x0100, 0x0101, 0x0104 }; +static const u16 snd_djm_opts_450_pb2[] = { 0x0200, 0x0201, 0x0204 }; +static const u16 snd_djm_opts_450_pb3[] = { 0x0300, 0x0301, 0x0304 }; + +static const struct snd_djm_ctl snd_djm_ctls_450[] = { + SND_DJM_CTL("Master Input Level Capture Switch", cap_level, 0, SND_DJM_WINDEX_CAPLVL), + SND_DJM_CTL("Input 1 Capture Switch", 450_cap1, 2, SND_DJM_WINDEX_CAP), + SND_DJM_CTL("Input 2 Capture Switch", 450_cap2, 2, SND_DJM_WINDEX_CAP), + SND_DJM_CTL("Input 3 Capture Switch", 450_cap3, 0, SND_DJM_WINDEX_CAP), + SND_DJM_CTL("Output 1 Playback Switch", 450_pb1, 0, SND_DJM_WINDEX_PB), + SND_DJM_CTL("Output 2 Playback Switch", 450_pb2, 1, SND_DJM_WINDEX_PB), + SND_DJM_CTL("Output 3 Playback Switch", 450_pb3, 2, SND_DJM_WINDEX_PB) +}; + +// DJM-750 +static const u16 snd_djm_opts_750_cap1[] = { + 0x0101, 0x0103, 0x0106, 0x0107, 0x0108, 0x0109, 0x010a, 0x010f }; +static const u16 snd_djm_opts_750_cap2[] = { + 0x0200, 0x0201, 0x0206, 0x0207, 0x0208, 0x0209, 0x020a, 0x020f }; +static const u16 snd_djm_opts_750_cap3[] = { + 0x0300, 0x0301, 0x0306, 0x0307, 0x0308, 0x0309, 0x030a, 0x030f }; +static const u16 snd_djm_opts_750_cap4[] = { + 0x0401, 0x0403, 0x0406, 0x0407, 0x0408, 0x0409, 0x040a, 0x040f }; + +static const struct snd_djm_ctl snd_djm_ctls_750[] = { + SND_DJM_CTL("Master Input Level Capture Switch", cap_level, 0, SND_DJM_WINDEX_CAPLVL), + SND_DJM_CTL("Input 1 Capture Switch", 750_cap1, 2, SND_DJM_WINDEX_CAP), + SND_DJM_CTL("Input 2 Capture Switch", 750_cap2, 2, SND_DJM_WINDEX_CAP), + SND_DJM_CTL("Input 3 Capture Switch", 750_cap3, 0, SND_DJM_WINDEX_CAP), + SND_DJM_CTL("Input 4 Capture Switch", 750_cap4, 0, SND_DJM_WINDEX_CAP) +}; + +// DJM-850 +static const u16 snd_djm_opts_850_cap1[] = { + 0x0100, 0x0103, 0x0106, 0x0107, 0x0108, 0x0109, 0x010a, 0x010f }; +static const u16 snd_djm_opts_850_cap2[] = { + 0x0200, 0x0201, 0x0206, 0x0207, 0x0208, 0x0209, 0x020a, 0x020f }; +static const u16 snd_djm_opts_850_cap3[] = { + 0x0300, 0x0301, 0x0306, 0x0307, 0x0308, 0x0309, 0x030a, 0x030f }; +static const u16 snd_djm_opts_850_cap4[] = { + 0x0400, 0x0403, 0x0406, 0x0407, 0x0408, 0x0409, 0x040a, 0x040f }; + +static const struct snd_djm_ctl snd_djm_ctls_850[] = { + SND_DJM_CTL("Master Input Level Capture Switch", cap_level, 0, SND_DJM_WINDEX_CAPLVL), + SND_DJM_CTL("Input 1 Capture Switch", 850_cap1, 1, SND_DJM_WINDEX_CAP), + SND_DJM_CTL("Input 2 Capture Switch", 850_cap2, 0, SND_DJM_WINDEX_CAP), + SND_DJM_CTL("Input 3 Capture Switch", 850_cap3, 0, SND_DJM_WINDEX_CAP), + SND_DJM_CTL("Input 4 Capture Switch", 850_cap4, 1, SND_DJM_WINDEX_CAP) +}; + +// DJM-900NXS2 +static const u16 snd_djm_opts_900nxs2_cap1[] = { + 0x0100, 0x0102, 0x0103, 0x0106, 0x0107, 0x0108, 0x0109, 0x010a }; +static const u16 snd_djm_opts_900nxs2_cap2[] = { + 0x0200, 0x0202, 0x0203, 0x0206, 0x0207, 0x0208, 0x0209, 0x020a }; +static const u16 snd_djm_opts_900nxs2_cap3[] = { + 0x0300, 0x0302, 0x0303, 0x0306, 0x0307, 0x0308, 0x0309, 0x030a }; +static const u16 snd_djm_opts_900nxs2_cap4[] = { + 0x0400, 0x0402, 0x0403, 0x0406, 0x0407, 0x0408, 0x0409, 0x040a }; +static const u16 snd_djm_opts_900nxs2_cap5[] = { + 0x0507, 0x0508, 0x0509, 0x050a, 0x0511, 0x0512, 0x0513, 0x0514 }; + +static const struct snd_djm_ctl snd_djm_ctls_900nxs2[] = { + SND_DJM_CTL("Master Input Level Capture Switch", cap_level, 0, SND_DJM_WINDEX_CAPLVL), + SND_DJM_CTL("Input 1 Capture Switch", 900nxs2_cap1, 2, SND_DJM_WINDEX_CAP), + SND_DJM_CTL("Input 2 Capture Switch", 900nxs2_cap2, 2, SND_DJM_WINDEX_CAP), + SND_DJM_CTL("Input 3 Capture Switch", 900nxs2_cap3, 2, SND_DJM_WINDEX_CAP), + SND_DJM_CTL("Input 4 Capture Switch", 900nxs2_cap4, 2, SND_DJM_WINDEX_CAP), + SND_DJM_CTL("Input 5 Capture Switch", 900nxs2_cap5, 3, SND_DJM_WINDEX_CAP) +}; + +// DJM-750MK2 +static const u16 snd_djm_opts_750mk2_cap1[] = { + 0x0100, 0x0102, 0x0103, 0x0106, 0x0107, 0x0108, 0x0109, 0x010a }; +static const u16 snd_djm_opts_750mk2_cap2[] = { + 0x0200, 0x0202, 0x0203, 0x0206, 0x0207, 0x0208, 0x0209, 0x020a }; +static const u16 snd_djm_opts_750mk2_cap3[] = { + 0x0300, 0x0302, 0x0303, 0x0306, 0x0307, 0x0308, 0x0309, 0x030a }; +static const u16 snd_djm_opts_750mk2_cap4[] = { + 0x0400, 0x0402, 0x0403, 0x0406, 0x0407, 0x0408, 0x0409, 0x040a }; +static const u16 snd_djm_opts_750mk2_cap5[] = { + 0x0507, 0x0508, 0x0509, 0x050a, 0x0511, 0x0512, 0x0513, 0x0514 }; + +static const u16 snd_djm_opts_750mk2_pb1[] = { 0x0100, 0x0101, 0x0104 }; +static const u16 snd_djm_opts_750mk2_pb2[] = { 0x0200, 0x0201, 0x0204 }; +static const u16 snd_djm_opts_750mk2_pb3[] = { 0x0300, 0x0301, 0x0304 }; + +static const struct snd_djm_ctl snd_djm_ctls_750mk2[] = { + SND_DJM_CTL("Master Input Level Capture Switch", cap_level, 0, SND_DJM_WINDEX_CAPLVL), + SND_DJM_CTL("Input 1 Capture Switch", 750mk2_cap1, 2, SND_DJM_WINDEX_CAP), + SND_DJM_CTL("Input 2 Capture Switch", 750mk2_cap2, 2, SND_DJM_WINDEX_CAP), + SND_DJM_CTL("Input 3 Capture Switch", 750mk2_cap3, 2, SND_DJM_WINDEX_CAP), + SND_DJM_CTL("Input 4 Capture Switch", 750mk2_cap4, 2, SND_DJM_WINDEX_CAP), + SND_DJM_CTL("Input 5 Capture Switch", 750mk2_cap5, 3, SND_DJM_WINDEX_CAP), + SND_DJM_CTL("Output 1 Playback Switch", 750mk2_pb1, 0, SND_DJM_WINDEX_PB), + SND_DJM_CTL("Output 2 Playback Switch", 750mk2_pb2, 1, SND_DJM_WINDEX_PB), + SND_DJM_CTL("Output 3 Playback Switch", 750mk2_pb3, 2, SND_DJM_WINDEX_PB) +}; + +// DJM-A9 +static const u16 snd_djm_opts_a9_cap_level[] = { + 0x0000, 0x0100, 0x0200, 0x0300, 0x0400, 0x0500 }; +static const u16 snd_djm_opts_a9_cap1[] = { + 0x0107, 0x0108, 0x0109, 0x010a, 0x010e, + 0x111, 0x112, 0x113, 0x114, 0x0131, 0x132, 0x133, 0x134 }; +static const u16 snd_djm_opts_a9_cap2[] = { + 0x0201, 0x0202, 0x0203, 0x0205, 0x0206, 0x0207, 0x0208, 0x0209, 0x020a, 0x020e }; +static const u16 snd_djm_opts_a9_cap3[] = { + 0x0301, 0x0302, 0x0303, 0x0305, 0x0306, 0x0307, 0x0308, 0x0309, 0x030a, 0x030e }; +static const u16 snd_djm_opts_a9_cap4[] = { + 0x0401, 0x0402, 0x0403, 0x0405, 0x0406, 0x0407, 0x0408, 0x0409, 0x040a, 0x040e }; +static const u16 snd_djm_opts_a9_cap5[] = { + 0x0501, 0x0502, 0x0503, 0x0505, 0x0506, 0x0507, 0x0508, 0x0509, 0x050a, 0x050e }; + +static const struct snd_djm_ctl snd_djm_ctls_a9[] = { + SND_DJM_CTL("Master Input Level Capture Switch", a9_cap_level, 0, SND_DJM_WINDEX_CAPLVL), + SND_DJM_CTL("Master Input Capture Switch", a9_cap1, 3, SND_DJM_WINDEX_CAP), + SND_DJM_CTL("Input 1 Capture Switch", a9_cap2, 2, SND_DJM_WINDEX_CAP), + SND_DJM_CTL("Input 2 Capture Switch", a9_cap3, 2, SND_DJM_WINDEX_CAP), + SND_DJM_CTL("Input 3 Capture Switch", a9_cap4, 2, SND_DJM_WINDEX_CAP), + SND_DJM_CTL("Input 4 Capture Switch", a9_cap5, 2, SND_DJM_WINDEX_CAP) +}; + +// DJM-V10 +static const u16 snd_djm_opts_v10_cap_level[] = { + 0x0000, 0x0100, 0x0200, 0x0300, 0x0400, 0x0500 +}; + +static const u16 snd_djm_opts_v10_cap1[] = { + 0x0103, + 0x0100, 0x0102, 0x0106, 0x0110, 0x0107, + 0x0108, 0x0109, 0x010a, 0x0121, 0x0122 +}; + +static const u16 snd_djm_opts_v10_cap2[] = { + 0x0200, 0x0202, 0x0206, 0x0210, 0x0207, + 0x0208, 0x0209, 0x020a, 0x0221, 0x0222 +}; + +static const u16 snd_djm_opts_v10_cap3[] = { + 0x0303, + 0x0300, 0x0302, 0x0306, 0x0310, 0x0307, + 0x0308, 0x0309, 0x030a, 0x0321, 0x0322 +}; + +static const u16 snd_djm_opts_v10_cap4[] = { + 0x0403, + 0x0400, 0x0402, 0x0406, 0x0410, 0x0407, + 0x0408, 0x0409, 0x040a, 0x0421, 0x0422 +}; + +static const u16 snd_djm_opts_v10_cap5[] = { + 0x0500, 0x0502, 0x0506, 0x0510, 0x0507, + 0x0508, 0x0509, 0x050a, 0x0521, 0x0522 +}; + +static const u16 snd_djm_opts_v10_cap6[] = { + 0x0603, + 0x0600, 0x0602, 0x0606, 0x0610, 0x0607, + 0x0608, 0x0609, 0x060a, 0x0621, 0x0622 +}; + +static const struct snd_djm_ctl snd_djm_ctls_v10[] = { + SND_DJM_CTL("Master Input Level Capture Switch", v10_cap_level, 0, SND_DJM_WINDEX_CAPLVL), + SND_DJM_CTL("Input 1 Capture Switch", v10_cap1, 2, SND_DJM_WINDEX_CAP), + SND_DJM_CTL("Input 2 Capture Switch", v10_cap2, 2, SND_DJM_WINDEX_CAP), + SND_DJM_CTL("Input 3 Capture Switch", v10_cap3, 0, SND_DJM_WINDEX_CAP), + SND_DJM_CTL("Input 4 Capture Switch", v10_cap4, 0, SND_DJM_WINDEX_CAP), + SND_DJM_CTL("Input 5 Capture Switch", v10_cap5, 0, SND_DJM_WINDEX_CAP), + SND_DJM_CTL("Input 6 Capture Switch", v10_cap6, 0, SND_DJM_WINDEX_CAP) + // playback channels are fixed and controlled by hardware knobs on the mixer +}; + +static const struct snd_djm_device snd_djm_devices[] = { + [SND_DJM_250MK2_IDX] = SND_DJM_DEVICE(250mk2), + [SND_DJM_750_IDX] = SND_DJM_DEVICE(750), + [SND_DJM_850_IDX] = SND_DJM_DEVICE(850), + [SND_DJM_900NXS2_IDX] = SND_DJM_DEVICE(900nxs2), + [SND_DJM_750MK2_IDX] = SND_DJM_DEVICE(750mk2), + [SND_DJM_450_IDX] = SND_DJM_DEVICE(450), + [SND_DJM_A9_IDX] = SND_DJM_DEVICE(a9), + [SND_DJM_V10_IDX] = SND_DJM_DEVICE(v10), +}; + +static int snd_djm_controls_info(struct snd_kcontrol *kctl, + struct snd_ctl_elem_info *info) +{ + unsigned long private_value = kctl->private_value; + u8 device_idx = (private_value & SND_DJM_DEVICE_MASK) >> SND_DJM_DEVICE_SHIFT; + u8 ctl_idx = (private_value & SND_DJM_GROUP_MASK) >> SND_DJM_GROUP_SHIFT; + const struct snd_djm_device *device = &snd_djm_devices[device_idx]; + const char *name; + const struct snd_djm_ctl *ctl; + size_t noptions; + + if (ctl_idx >= device->ncontrols) + return -EINVAL; + + ctl = &device->controls[ctl_idx]; + noptions = ctl->noptions; + if (info->value.enumerated.item >= noptions) + info->value.enumerated.item = noptions - 1; + + name = snd_djm_get_label(device_idx, + ctl->options[info->value.enumerated.item], + ctl->wIndex); + if (!name) + return -EINVAL; + + strscpy(info->value.enumerated.name, name, sizeof(info->value.enumerated.name)); + info->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; + info->count = 1; + info->value.enumerated.items = noptions; + return 0; +} + +static int snd_djm_controls_update(struct usb_mixer_interface *mixer, + u8 device_idx, u8 group, u16 value) +{ + const struct snd_djm_device *device = &snd_djm_devices[device_idx]; + + if (group >= device->ncontrols || value >= device->controls[group].noptions) + return -EINVAL; + + CLASS(snd_usb_lock, pm)(mixer->chip); + if (pm.err) + return pm.err; + + return snd_usb_ctl_msg(mixer->chip->dev, + usb_sndctrlpipe(mixer->chip->dev, 0), + USB_REQ_SET_FEATURE, + USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + device->controls[group].options[value], + device->controls[group].wIndex, + NULL, 0); +} + +static int snd_djm_controls_get(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *elem) +{ + elem->value.enumerated.item[0] = kctl->private_value & SND_DJM_VALUE_MASK; + return 0; +} + +static int snd_djm_controls_put(struct snd_kcontrol *kctl, struct snd_ctl_elem_value *elem) +{ + struct usb_mixer_elem_list *list = snd_kcontrol_chip(kctl); + struct usb_mixer_interface *mixer = list->mixer; + unsigned long private_value = kctl->private_value; + + u8 device = (private_value & SND_DJM_DEVICE_MASK) >> SND_DJM_DEVICE_SHIFT; + u8 group = (private_value & SND_DJM_GROUP_MASK) >> SND_DJM_GROUP_SHIFT; + u16 value = elem->value.enumerated.item[0]; + + kctl->private_value = (((unsigned long)device << SND_DJM_DEVICE_SHIFT) | + (group << SND_DJM_GROUP_SHIFT) | + value); + + return snd_djm_controls_update(mixer, device, group, value); +} + +static int snd_djm_controls_resume(struct usb_mixer_elem_list *list) +{ + unsigned long private_value = list->kctl->private_value; + u8 device = (private_value & SND_DJM_DEVICE_MASK) >> SND_DJM_DEVICE_SHIFT; + u8 group = (private_value & SND_DJM_GROUP_MASK) >> SND_DJM_GROUP_SHIFT; + u16 value = (private_value & SND_DJM_VALUE_MASK); + + return snd_djm_controls_update(list->mixer, device, group, value); +} + +static int snd_djm_controls_create(struct usb_mixer_interface *mixer, + const u8 device_idx) +{ + int err, i; + u16 value; + + const struct snd_djm_device *device = &snd_djm_devices[device_idx]; + + struct snd_kcontrol_new knew = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .index = 0, + .info = snd_djm_controls_info, + .get = snd_djm_controls_get, + .put = snd_djm_controls_put + }; + + for (i = 0; i < device->ncontrols; i++) { + value = device->controls[i].default_value; + knew.name = device->controls[i].name; + knew.private_value = + ((unsigned long)device_idx << SND_DJM_DEVICE_SHIFT) | + (i << SND_DJM_GROUP_SHIFT) | + value; + err = snd_djm_controls_update(mixer, device_idx, i, value); + if (err) + return err; + err = add_single_ctl_with_resume(mixer, 0, snd_djm_controls_resume, + &knew, NULL); + if (err) + return err; + } + return 0; +} + int snd_usb_mixer_apply_create_quirk(struct usb_mixer_interface *mixer) { int err = 0; @@ -2630,6 +4366,13 @@ int snd_usb_mixer_apply_create_quirk(struct usb_mixer_interface *mixer) err = snd_emu0204_controls_create(mixer); break; +#if IS_REACHABLE(CONFIG_INPUT) + case USB_ID(0x054c, 0x0ce6): /* Sony DualSense controller (PS5) */ + case USB_ID(0x054c, 0x0df2): /* Sony DualSense Edge controller (PS5) */ + err = snd_dualsense_controls_create(mixer); + break; +#endif /* IS_REACHABLE(CONFIG_INPUT) */ + case USB_ID(0x0763, 0x2030): /* M-Audio Fast Track C400 */ case USB_ID(0x0763, 0x2031): /* M-Audio Fast Track C400 */ err = snd_c400_create_mixer(mixer); @@ -2651,17 +4394,19 @@ int snd_usb_mixer_apply_create_quirk(struct usb_mixer_interface *mixer) break; case USB_ID(0x0dba, 0x1000): /* Digidesign Mbox 1 */ - err = snd_mbox1_create_sync_switch(mixer); + err = snd_mbox1_controls_create(mixer); break; case USB_ID(0x17cc, 0x1011): /* Traktor Audio 6 */ - err = snd_nativeinstruments_create_mixer(mixer, + err = snd_nativeinstruments_create_mixer(/* checkpatch hack */ + mixer, snd_nativeinstruments_ta6_mixers, ARRAY_SIZE(snd_nativeinstruments_ta6_mixers)); break; case USB_ID(0x17cc, 0x1021): /* Traktor Audio 10 */ - err = snd_nativeinstruments_create_mixer(mixer, + err = snd_nativeinstruments_create_mixer(/* checkpatch hack */ + mixer, snd_nativeinstruments_ta10_mixers, ARRAY_SIZE(snd_nativeinstruments_ta10_mixers)); break; @@ -2682,15 +4427,44 @@ int snd_usb_mixer_apply_create_quirk(struct usb_mixer_interface *mixer) case USB_ID(0x1235, 0x8203): /* Focusrite Scarlett 6i6 2nd Gen */ case USB_ID(0x1235, 0x8204): /* Focusrite Scarlett 18i8 2nd Gen */ case USB_ID(0x1235, 0x8201): /* Focusrite Scarlett 18i20 2nd Gen */ - err = snd_scarlett_gen2_controls_create(mixer); + case USB_ID(0x1235, 0x8211): /* Focusrite Scarlett Solo 3rd Gen */ + case USB_ID(0x1235, 0x8210): /* Focusrite Scarlett 2i2 3rd Gen */ + case USB_ID(0x1235, 0x8212): /* Focusrite Scarlett 4i4 3rd Gen */ + case USB_ID(0x1235, 0x8213): /* Focusrite Scarlett 8i6 3rd Gen */ + case USB_ID(0x1235, 0x8214): /* Focusrite Scarlett 18i8 3rd Gen */ + case USB_ID(0x1235, 0x8215): /* Focusrite Scarlett 18i20 3rd Gen */ + case USB_ID(0x1235, 0x8216): /* Focusrite Vocaster One */ + case USB_ID(0x1235, 0x8217): /* Focusrite Vocaster Two */ + case USB_ID(0x1235, 0x8218): /* Focusrite Scarlett Solo 4th Gen */ + case USB_ID(0x1235, 0x8219): /* Focusrite Scarlett 2i2 4th Gen */ + case USB_ID(0x1235, 0x821a): /* Focusrite Scarlett 4i4 4th Gen */ + case USB_ID(0x1235, 0x8206): /* Focusrite Clarett 2Pre USB */ + case USB_ID(0x1235, 0x8207): /* Focusrite Clarett 4Pre USB */ + case USB_ID(0x1235, 0x8208): /* Focusrite Clarett 8Pre USB */ + case USB_ID(0x1235, 0x820a): /* Focusrite Clarett+ 2Pre */ + case USB_ID(0x1235, 0x820b): /* Focusrite Clarett+ 4Pre */ + case USB_ID(0x1235, 0x820c): /* Focusrite Clarett+ 8Pre */ + err = snd_scarlett2_init(mixer); + break; + + case USB_ID(0x1235, 0x821b): /* Focusrite Scarlett 16i16 4th Gen */ + case USB_ID(0x1235, 0x821c): /* Focusrite Scarlett 18i16 4th Gen */ + case USB_ID(0x1235, 0x821d): /* Focusrite Scarlett 18i20 4th Gen */ + err = snd_fcp_init(mixer); break; case USB_ID(0x041e, 0x323b): /* Creative Sound Blaster E1 */ err = snd_soundblaster_e1_switch_create(mixer); break; case USB_ID(0x0bda, 0x4014): /* Dell WD15 dock */ + err = dell_dock_mixer_create(mixer); + if (err < 0) + break; err = dell_dock_mixer_init(mixer); break; + case USB_ID(0x0bda, 0x402e): /* Dell WD19 dock */ + err = dell_dock_mixer_create(mixer); + break; case USB_ID(0x2a39, 0x3fd2): /* RME ADI-2 Pro */ case USB_ID(0x2a39, 0x3fd3): /* RME ADI-2 DAC */ @@ -2698,18 +4472,51 @@ int snd_usb_mixer_apply_create_quirk(struct usb_mixer_interface *mixer) err = snd_rme_controls_create(mixer); break; - case USB_ID(0x0194f, 0x010c): /* Presonus Studio 1810c */ + case USB_ID(0x194f, 0x010c): /* Presonus Studio 1810c */ + err = snd_sc1810_init_mixer(mixer); + break; + case USB_ID(0x194f, 0x010d): /* Presonus Studio 1824c */ err = snd_sc1810_init_mixer(mixer); break; case USB_ID(0x2a39, 0x3fb0): /* RME Babyface Pro FS */ err = snd_bbfpro_controls_create(mixer); break; + case USB_ID(0x2a39, 0x3f8c): /* RME Digiface USB */ + case USB_ID(0x2a39, 0x3fa0): /* RME Digiface USB (alternate) */ + err = snd_rme_digiface_controls_create(mixer); + break; + case USB_ID(0x2b73, 0x0017): /* Pioneer DJ DJM-250MK2 */ + err = snd_djm_controls_create(mixer, SND_DJM_250MK2_IDX); + break; + case USB_ID(0x2b73, 0x0013): /* Pioneer DJ DJM-450 */ + err = snd_djm_controls_create(mixer, SND_DJM_450_IDX); + break; + case USB_ID(0x08e4, 0x017f): /* Pioneer DJ DJM-750 */ + err = snd_djm_controls_create(mixer, SND_DJM_750_IDX); + break; + case USB_ID(0x2b73, 0x001b): /* Pioneer DJ DJM-750MK2 */ + err = snd_djm_controls_create(mixer, SND_DJM_750MK2_IDX); + break; + case USB_ID(0x08e4, 0x0163): /* Pioneer DJ DJM-850 */ + err = snd_djm_controls_create(mixer, SND_DJM_850_IDX); + break; + case USB_ID(0x2b73, 0x000a): /* Pioneer DJ DJM-900NXS2 */ + err = snd_djm_controls_create(mixer, SND_DJM_900NXS2_IDX); + break; + case USB_ID(0x2b73, 0x003c): /* Pioneer DJ / AlphaTheta DJM-A9 */ + err = snd_djm_controls_create(mixer, SND_DJM_A9_IDX); + break; + case USB_ID(0x2b73, 0x0034): /* Pioneer DJ DJM-V10 */ + err = snd_djm_controls_create(mixer, SND_DJM_V10_IDX); + break; + case USB_ID(0x03f0, 0x0269): /* HP TB Dock G2 */ + err = hp_dock_mixer_create(mixer); + break; } return err; } -#ifdef CONFIG_PM void snd_usb_mixer_resume_quirk(struct usb_mixer_interface *mixer) { switch (mixer->chip->usb_id) { @@ -2718,7 +4525,6 @@ void snd_usb_mixer_resume_quirk(struct usb_mixer_interface *mixer) break; } } -#endif void snd_usb_mixer_rc_memory_change(struct usb_mixer_interface *mixer, int unitid) @@ -2753,7 +4559,8 @@ static void snd_dragonfly_quirk_db_scale(struct usb_mixer_interface *mixer, struct snd_kcontrol *kctl) { /* Approximation using 10 ranges based on output measurement on hw v1.2. - * This seems close to the cubic mapping e.g. alsamixer uses. */ + * This seems close to the cubic mapping e.g. alsamixer uses. + */ static const DECLARE_TLV_DB_RANGE(scale, 0, 1, TLV_DB_MINMAX_ITEM(-5300, -4970), 2, 5, TLV_DB_MINMAX_ITEM(-4710, -4160), @@ -2782,6 +4589,52 @@ static void snd_dragonfly_quirk_db_scale(struct usb_mixer_interface *mixer, } } +/* + * Some Plantronics headsets have control names that don't meet ALSA naming + * standards. This function fixes nonstandard source names. By the time + * this function is called the control name should look like one of these: + * "source names Playback Volume" + * "source names Playback Switch" + * "source names Capture Volume" + * "source names Capture Switch" + * If any of the trigger words are found in the name then the name will + * be changed to: + * "Headset Playback Volume" + * "Headset Playback Switch" + * "Headset Capture Volume" + * "Headset Capture Switch" + * depending on the current suffix. + */ +static void snd_fix_plt_name(struct snd_usb_audio *chip, + struct snd_ctl_elem_id *id) +{ + /* no variant of "Sidetone" should be added to this list */ + static const char * const trigger[] = { + "Earphone", "Microphone", "Receive", "Transmit" + }; + static const char * const suffix[] = { + " Playback Volume", " Playback Switch", + " Capture Volume", " Capture Switch" + }; + int i; + + for (i = 0; i < ARRAY_SIZE(trigger); i++) + if (strstr(id->name, trigger[i])) + goto triggered; + usb_audio_dbg(chip, "no change in %s\n", id->name); + return; + +triggered: + for (i = 0; i < ARRAY_SIZE(suffix); i++) + if (strstr(id->name, suffix[i])) { + usb_audio_dbg(chip, "fixing kctl name %s\n", id->name); + snprintf(id->name, sizeof(id->name), "Headset%s", + suffix[i]); + return; + } + usb_audio_dbg(chip, "something wrong in kctl name %s\n", id->name); +} + void snd_usb_mixer_fu_apply_quirk(struct usb_mixer_interface *mixer, struct usb_mixer_elem_info *cval, int unitid, struct snd_kcontrol *kctl) @@ -2791,12 +4644,25 @@ void snd_usb_mixer_fu_apply_quirk(struct usb_mixer_interface *mixer, if (unitid == 7 && cval->control == UAC_FU_VOLUME) snd_dragonfly_quirk_db_scale(mixer, cval, kctl); break; - /* lowest playback value is muted on C-Media devices */ - case USB_ID(0x0d8c, 0x000c): - case USB_ID(0x0d8c, 0x0014): - if (strstr(kctl->id.name, "Playback")) - cval->min_mute = 1; - break; } -} + /* lowest playback value is muted on some devices */ + if (mixer->chip->quirk_flags & QUIRK_FLAG_MIXER_PLAYBACK_MIN_MUTE) + if (strstr(kctl->id.name, "Playback")) { + usb_audio_info(mixer->chip, + "applying playback min mute quirk\n"); + cval->min_mute = 1; + } + + /* lowest capture value is muted on some devices */ + if (mixer->chip->quirk_flags & QUIRK_FLAG_MIXER_CAPTURE_MIN_MUTE) + if (strstr(kctl->id.name, "Capture")) { + usb_audio_info(mixer->chip, + "applying capture min mute quirk\n"); + cval->min_mute = 1; + } + /* ALSA-ify some Plantronics headset control names */ + if (USB_ID_VENDOR(mixer->chip->usb_id) == 0x047f && + (cval->control == UAC_FU_MUTE || cval->control == UAC_FU_VOLUME)) + snd_fix_plt_name(mixer->chip, &kctl->id); +} |
