/* * USB Audio Driver for ALSA * * Quirks and vendor-specific extensions for mixer interfaces * * Copyright (c) 2002 by Takashi Iwai <tiwai@suse.de> * * Many codes borrowed from audio.c by * Alan Cox (alan@lxorguk.ukuu.org.uk) * Thomas Sailer (sailer@ife.ee.ethz.ch) * * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include <linux/init.h> #include <linux/slab.h> #include <linux/usb.h> #include <linux/usb/audio.h> #include <sound/core.h> #include <sound/control.h> #include <sound/hwdep.h> #include <sound/info.h> #include "usbaudio.h" #include "mixer.h" #include "mixer_quirks.h" #include "helper.h" /* * Sound Blaster remote control configuration * * format of remote control data: * Extigy: xx 00 * Audigy 2 NX: 06 80 xx 00 00 00 * Live! 24-bit: 06 80 xx yy 22 83 */ static const struct rc_config { u32 usb_id; u8 offset; u8 length; u8 packet_length; u8 min_packet_length; /* minimum accepted length of the URB result */ u8 mute_mixer_id; u32 mute_code; } rc_configs[] = { { USB_ID(0x041e, 0x3000), 0, 1, 2, 1, 18, 0x0013 }, /* Extigy */ { USB_ID(0x041e, 0x3020), 2, 1, 6, 6, 18, 0x0013 }, /* Audigy 2 NX */ { USB_ID(0x041e, 0x3040), 2, 2, 6, 6, 2, 0x6e91 }, /* Live! 24-bit */ { USB_ID(0x041e, 0x3042), 0, 1, 1, 1, 1, 0x000d }, /* Usb X-Fi S51 */ { USB_ID(0x041e, 0x3048), 2, 2, 6, 6, 2, 0x6e91 }, /* Toshiba SB0500 */ }; static void snd_usb_soundblaster_remote_complete(struct urb *urb) { struct usb_mixer_interface *mixer = urb->context; const struct rc_config *rc = mixer->rc_cfg; u32 code; if (urb->status < 0 || urb->actual_length < rc->min_packet_length) return; code = mixer->rc_buffer[rc->offset]; if (rc->length == 2) code |= mixer->rc_buffer[rc->offset + 1] << 8; /* the Mute button actually changes the mixer control */ 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) { struct usb_mixer_interface *mixer = hw->private_data; int err; u32 rc_code; if (count != 1 && count != 4) return -EINVAL; err = wait_event_interruptible(mixer->rc_waitq, (rc_code = xchg(&mixer->rc_code, 0)) != 0); if (err == 0) { if (count == 1) err = put_user(rc_code, buf); else err = put_user(rc_code, (u32 __user *)buf); } return err < 0 ? err : count; } static unsigned int snd_usb_sbrc_hwdep_poll(struct snd_hwdep *hw, struct file *file, poll_table *wait) { struct usb_mixer_interface *mixer = hw->private_data; poll_wait(file, &mixer->rc_waitq, wait); return mixer->rc_code ? POLLIN | POLLRDNORM : 0; } static int snd_usb_soundblaster_remote_init(struct usb_mixer_interface *mixer) { struct snd_hwdep *hwdep; int err, len, i; for (i = 0; i < ARRAY_SIZE(rc_configs); ++i) if (rc_configs[i].usb_id == mixer->chip->usb_id) break; if (i >= ARRAY_SIZE(rc_configs)) return 0; mixer->rc_cfg = &rc_configs[i]; len = mixer->rc_cfg->packet_length; init_waitqueue_head(&mixer->rc_waitq); err = snd_hwdep_new(mixer->chip->card, "SB remote control", 0, &hwdep); if (err < 0) return err; snprintf(hwdep->name, sizeof(hwdep->name), "%s remote control", mixer->chip->card->shortname); hwdep->iface = SNDRV_HWDEP_IFACE_SB_RC; hwdep->private_data = mixer; hwdep->ops.read = snd_usb_sbrc_hwdep_read; hwdep->ops.poll = snd_usb_sbrc_hwdep_poll; hwdep->exclusive = 1; mixer->rc_urb = usb_alloc_urb(0, GFP_KERNEL); if (!mixer->rc_urb) return -ENOMEM; mixer->rc_setup_packet = kmalloc(sizeof(*mixer->rc_setup_packet), GFP_KERNEL); if (!mixer->rc_setup_packet) { usb_free_urb(mixer->rc_urb); mixer->rc_urb = NULL; return -ENOMEM; } mixer->rc_setup_packet->bRequestType = USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE; mixer->rc_setup_packet->bRequest = UAC_GET_MEM; mixer->rc_setup_packet->wValue = cpu_to_le16(0); mixer->rc_setup_packet->wIndex = cpu_to_le16(0); 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, snd_usb_soundblaster_remote_complete, mixer); return 0; } #define snd_audigy2nx_led_info snd_ctl_boolean_mono_info static int snd_audigy2nx_led_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct usb_mixer_interface *mixer = snd_kcontrol_chip(kcontrol); int index = kcontrol->private_value; ucontrol->value.integer.value[0] = mixer->audigy2nx_leds[index]; return 0; } static int snd_audigy2nx_led_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct usb_mixer_interface *mixer = snd_kcontrol_chip(kcontrol); int index = kcontrol->private_value; int value = ucontrol->value.integer.value[0]; int err, changed; if (value > 1) return -EINVAL; changed = value != mixer->audigy2nx_leds[index]; if (mixer->chip->usb_id == USB_ID(0x041e, 0x3042)) err = snd_usb_ctl_msg(mixer->chip->dev, usb_sndctrlpipe(mixer->chip->dev, 0), 0x24, USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_OTHER, !value, 0, NULL, 0, 100); else err = snd_usb_ctl_msg(mixer->chip->dev, usb_sndctrlpipe(mixer->chip->dev, 0), 0x24, USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_OTHER, value, index + 2, NULL, 0, 100); if (err < 0) return err; mixer->audigy2nx_leds[index] = value; return changed; } static struct snd_kcontrol_new snd_audigy2nx_controls[] = { { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "CMSS LED Switch", .info = snd_audigy2nx_led_info, .get = snd_audigy2nx_led_get, .put = snd_audigy2nx_led_put, .private_value = 0, }, { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Power LED Switch", .info = snd_audigy2nx_led_info, .get = snd_audigy2nx_led_get, .put = snd_audigy2nx_led_put, .private_value = 1, }, { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Dolby Digital LED Switch", .info = snd_audigy2nx_led_info, .get = snd_audigy2nx_led_get, .put = snd_audigy2nx_led_put, .private_value = 2, }, }; static int snd_audigy2nx_controls_create(struct usb_mixer_interface *mixer) { int i, err; for (i = 0; i < ARRAY_SIZE(snd_audigy2nx_controls); ++i) { /* USB X-Fi S51 doesn't have a CMSS LED */ if ((mixer->chip->usb_id == USB_ID(0x041e, 0x3042)) && 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, 0x3048))) break; err = snd_ctl_add(mixer->chip->card, snd_ctl_new1(&snd_audigy2nx_controls[i], mixer)); if (err < 0) return err; } mixer->audigy2nx_leds[1] = 1; /* Power LED is on by default */ return 0; } static void snd_audigy2nx_proc_read(struct snd_info_entry *entry, struct snd_info_buffer *buffer) { static const struct sb_jack { int unitid; const char *name; } jacks_audigy2nx[] = { {4, "dig in "}, {7, "line in"}, {19, "spk out"}, {20, "hph out"}, {-1, NULL} }, jacks_live24ext[] = { {4, "line in"}, /* &1=Line, &2=Mic*/ {3, "hph out"}, /* headphones */ {0, "RC "}, /* last command, 6 bytes see rc_config above */ {-1, NULL} }; const struct sb_jack *jacks; struct usb_mixer_interface *mixer = entry->private_data; int i, err; u8 buf[3]; snd_iprintf(buffer, "%s jacks\n\n", mixer->chip->card->shortname); if (mixer->chip->usb_id == USB_ID(0x041e, 0x3020)) jacks = jacks_audigy2nx; else if (mixer->chip->usb_id == USB_ID(0x041e, 0x3040) || mixer->chip->usb_id == USB_ID(0x041e, 0x3048)) jacks = jacks_live24ext; else return; for (i = 0; jacks[i].name; ++i) { snd_iprintf(buffer, "%s: ", jacks[i].name); 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, 100); if (err == 3 && (buf[0] == 3 || buf[0] == 6)) snd_iprintf(buffer, "%02x %02x\n", buf[1], buf[2]); else snd_iprintf(buffer, "?\n"); } } static int snd_xonar_u1_switch_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct usb_mixer_interface *mixer = snd_kcontrol_chip(kcontrol); ucontrol->value.integer.value[0] = !!(mixer->xonar_u1_status & 0x02); return 0; } static int snd_xonar_u1_switch_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct usb_mixer_interface *mixer = snd_kcontrol_chip(kcontrol); u8 old_status, new_status; int err, changed; old_status = mixer->xonar_u1_status; if (ucontrol->value.integer.value[0]) new_status = old_status | 0x02; else new_status = old_status & ~0x02; changed = new_status != old_status; err = snd_usb_ctl_msg(mixer->chip->dev, usb_sndctrlpipe(mixer->chip->dev, 0), 0x08, USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_OTHER, 50, 0, &new_status, 1, 100); if (err < 0) return err; mixer->xonar_u1_status = new_status; return changed; } static struct snd_kcontrol_new snd_xonar_u1_output_switch = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Digital Playback Switch", .info = snd_ctl_boolean_mono_info, .get = snd_xonar_u1_switch_get, .put = snd_xonar_u1_switch_put, }; static int snd_xonar_u1_controls_create(struct usb_mixer_interface *mixer) { int err; err = snd_ctl_add(mixer->chip->card, snd_ctl_new1(&snd_xonar_u1_output_switch, mixer)); if (err < 0) return err; mixer->xonar_u1_status = 0x05; return 0; } void snd_emuusb_set_samplerate(struct snd_usb_audio *chip, unsigned char samplerate_id) { struct usb_mixer_interface *mixer; struct usb_mixer_elem_info *cval; int unitid = 12; /* SamleRate ExtensionUnit ID */ list_for_each_entry(mixer, &chip->mixer_list, list) { cval = mixer->id_elems[unitid]; if (cval) { snd_usb_mixer_set_ctl_value(cval, UAC_SET_CUR, cval->control << 8, samplerate_id); snd_usb_mixer_notify_id(mixer, unitid); } break; } } int snd_usb_mixer_apply_create_quirk(struct usb_mixer_interface *mixer) { int err; struct snd_info_entry *entry; if ((err = snd_usb_soundblaster_remote_init(mixer)) < 0) return err; if (mixer->chip->usb_id == USB_ID(0x041e, 0x3020) || mixer->chip->usb_id == USB_ID(0x041e, 0x3040) || mixer->chip->usb_id == USB_ID(0x041e, 0x3042) || mixer->chip->usb_id == USB_ID(0x041e, 0x3048)) { if ((err = snd_audigy2nx_controls_create(mixer)) < 0) return err; if (!snd_card_proc_new(mixer->chip->card, "audigy2nx", &entry)) snd_info_set_text_ops(entry, mixer, snd_audigy2nx_proc_read); } if (mixer->chip->usb_id == USB_ID(0x0b05, 0x1739) || mixer->chip->usb_id == USB_ID(0x0b05, 0x1743)) { err = snd_xonar_u1_controls_create(mixer); if (err < 0) return err; } return 0; } void snd_usb_mixer_rc_memory_change(struct usb_mixer_interface *mixer, int unitid) { if (!mixer->rc_cfg) return; /* unit ids specific to Extigy/Audigy 2 NX: */ switch (unitid) { case 0: /* remote control */ mixer->rc_urb->dev = mixer->chip->dev; usb_submit_urb(mixer->rc_urb, GFP_ATOMIC); break; case 4: /* digital in jack */ case 7: /* line in jacks */ case 19: /* speaker out jacks */ case 20: /* headphones out jack */ break; /* live24ext: 4 = line-in jack */ case 3: /* hp-out jack (may actuate Mute) */ if (mixer->chip->usb_id == USB_ID(0x041e, 0x3040) || mixer->chip->usb_id == USB_ID(0x041e, 0x3048)) snd_usb_mixer_notify_id(mixer, mixer->rc_cfg->mute_mixer_id); break; default: snd_printd(KERN_DEBUG "memory change in unknown unit %d\n", unitid); break; } }