ALSA: usb-mixer: factor out quirks

Move all non-standard mixer controls and vendor-specific extensions to a
separate file. Some structs need to be exported now.

Signed-off-by: Daniel Mack <daniel@caiaq.de>
Cc: Clemens Ladisch <clemens@ladisch.de>
Signed-off-by: Takashi Iwai <tiwai@suse.de>
diff --git a/sound/usb/Makefile b/sound/usb/Makefile
index 0758d8d..744024a 100644
--- a/sound/usb/Makefile
+++ b/sound/usb/Makefile
@@ -4,6 +4,7 @@
 
 snd-usb-audio-objs := 	card.o \
 			usbmixer.o \
+			mixer_quirks.o \
 			proc.o \
 			quirks.o \
 			format.o \
diff --git a/sound/usb/mixer_quirks.c b/sound/usb/mixer_quirks.c
new file mode 100644
index 0000000..d2f4dcd
--- /dev/null
+++ b/sound/usb/mixer_quirks.c
@@ -0,0 +1,411 @@
+/*
+ *   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/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 "usbmixer.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, 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];
+	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) {
+		if (i > 1 && /* Live24ext has 2 LEDs only */
+			(mixer->chip->usb_id == USB_ID(0x041e, 0x3040) ||
+			 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, 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;
+	}
+}
+
diff --git a/sound/usb/mixer_quirks.h b/sound/usb/mixer_quirks.h
new file mode 100644
index 0000000..bdbfab0
--- /dev/null
+++ b/sound/usb/mixer_quirks.h
@@ -0,0 +1,13 @@
+#ifndef SND_USB_MIXER_QUIRKS_H
+#define SND_USB_MIXER_QUIRKS_H
+
+int snd_usb_mixer_apply_create_quirk(struct usb_mixer_interface *mixer);
+
+void snd_emuusb_set_samplerate(struct snd_usb_audio *chip,
+			       unsigned char samplerate_id);
+
+void snd_usb_mixer_rc_memory_change(struct usb_mixer_interface *mixer,
+				    int unitid);
+
+#endif /* SND_USB_MIXER_QUIRKS_H */
+
diff --git a/sound/usb/quirks.c b/sound/usb/quirks.c
index 0c0b23b..a82cfed 100644
--- a/sound/usb/quirks.c
+++ b/sound/usb/quirks.c
@@ -25,6 +25,7 @@
 #include "usbaudio.h"
 #include "card.h"
 #include "usbmixer.h"
+#include "mixer_quirks.h"
 #include "midi.h"
 #include "quirks.h"
 #include "helper.h"
diff --git a/sound/usb/usbmixer.c b/sound/usb/usbmixer.c
index ab8f0f0..ec2436e 100644
--- a/sound/usb/usbmixer.c
+++ b/sound/usb/usbmixer.c
@@ -43,60 +43,10 @@
 #include "usbaudio.h"
 #include "usbmixer.h"
 #include "helper.h"
-
-/*
- */
-
-/* ignore error from controls - for debugging */
-/* #define IGNORE_CTL_ERROR */
-
-/*
- * 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, 0x3048), 2, 2, 6, 6,  2,  0x6e91 }, /* Toshiba SB0500 */
-};
+#include "mixer_quirks.h"
 
 #define MAX_ID_ELEMS	256
 
-struct usb_mixer_interface {
-	struct snd_usb_audio *chip;
-	unsigned int ctrlif;
-	struct list_head list;
-	unsigned int ignore_ctl_error;
-	struct urb *urb;
-	/* array[MAX_ID_ELEMS], indexed by unit id */
-	struct usb_mixer_elem_info **id_elems;
-
-	/* Sound Blaster remote control stuff */
-	const struct rc_config *rc_cfg;
-	u32 rc_code;
-	wait_queue_head_t rc_waitq;
-	struct urb *rc_urb;
-	struct usb_ctrlrequest *rc_setup_packet;
-	u8 rc_buffer[6];
-
-	u8 audigy2nx_leds[3];
-	u8 xonar_u1_status;
-};
-
-
 struct usb_audio_term {
 	int id;
 	int type;
@@ -118,24 +68,6 @@
 	const struct usbmix_selector_map *selector_map;
 };
 
-#define MAX_CHANNELS	10	/* max logical channels */
-
-struct usb_mixer_elem_info {
-	struct usb_mixer_interface *mixer;
-	struct usb_mixer_elem_info *next_id_elem; /* list of controls with same id */
-	struct snd_ctl_elem_id *elem_id;
-	unsigned int id;
-	unsigned int control;	/* CS or ICN (high byte) */
-	unsigned int cmask; /* channel mask bitmap: 0 = master */
-	int channels;
-	int val_type;
-	int min, max, res;
-	int dBmin, dBmax;
-	int cached;
-	int cache_val[MAX_CHANNELS];
-	u8 initialized;
-};
-
 enum {
 	USB_MIXER_BOOLEAN,
 	USB_MIXER_INV_BOOLEAN,
@@ -431,7 +363,8 @@
  * set a mixer value
  */
 
-static int set_ctl_value(struct usb_mixer_elem_info *cval, int request, int validx, int value_set)
+int snd_usb_mixer_set_ctl_value(struct usb_mixer_elem_info *cval,
+				int request, int validx, int value_set)
 {
 	unsigned char buf[2];
 	int val_len = cval->val_type >= USB_MIXER_S16 ? 2 : 1;
@@ -455,14 +388,14 @@
 
 static int set_cur_ctl_value(struct usb_mixer_elem_info *cval, int validx, int value)
 {
-	return set_ctl_value(cval, UAC_SET_CUR, validx, value);
+	return snd_usb_mixer_set_ctl_value(cval, UAC_SET_CUR, validx, value);
 }
 
 static int set_cur_mix_value(struct usb_mixer_elem_info *cval, int channel,
 			     int index, int value)
 {
 	int err;
-	err = set_ctl_value(cval, UAC_SET_CUR, (cval->control << 8) | channel,
+	err = snd_usb_mixer_set_ctl_value(cval, UAC_SET_CUR, (cval->control << 8) | channel,
 			    value);
 	if (err < 0)
 		return err;
@@ -751,7 +684,8 @@
 			int last_valid_res = cval->res;
 
 			while (cval->res > 1) {
-				if (set_ctl_value(cval, UAC_SET_RES, (cval->control << 8) | minchn, cval->res / 2) < 0)
+				if (snd_usb_mixer_set_ctl_value(cval, UAC_SET_RES,
+								(cval->control << 8) | minchn, cval->res / 2) < 0)
 					break;
 				cval->res /= 2;
 			}
@@ -1808,8 +1742,7 @@
 	return 0;
 }
 
-static void snd_usb_mixer_notify_id(struct usb_mixer_interface *mixer,
-				    int unitid)
+void snd_usb_mixer_notify_id(struct usb_mixer_interface *mixer, int unitid)
 {
 	struct usb_mixer_elem_info *info;
 
@@ -1858,34 +1791,6 @@
 	}
 }
 
-static void snd_usb_mixer_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;
-	}
-}
-
 static void snd_usb_mixer_status_complete(struct urb *urb)
 {
 	struct usb_mixer_interface *mixer = urb->context;
@@ -1903,7 +1808,7 @@
 			if (!(buf[0] & 0x40))
 				snd_usb_mixer_notify_id(mixer, buf[1]);
 			else
-				snd_usb_mixer_memory_change(mixer, buf[1]);
+				snd_usb_mixer_rc_memory_change(mixer, buf[1]);
 		}
 	}
 	if (urb->status != -ENOENT && urb->status != -ECONNRESET) {
@@ -1947,296 +1852,6 @@
 	return 0;
 }
 
-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];
-	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) {
-		if (i > 1 && /* Live24ext has 2 LEDs only */
-			(mixer->chip->usb_id == USB_ID(0x041e, 0x3040) ||
-			 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) {
-			set_cur_ctl_value(cval, cval->control << 8,
-					  samplerate_id);
-			snd_usb_mixer_notify_id(mixer, unitid);
-		}
-		break;
-	}
-}
-
 int snd_usb_create_mixer(struct snd_usb_audio *chip, int ctrlif,
 			 int ignore_error)
 {
@@ -2277,25 +1892,7 @@
 	    (err = snd_usb_mixer_status_create(mixer)) < 0)
 		goto _error;
 
-	if ((err = snd_usb_soundblaster_remote_init(mixer)) < 0)
-		goto _error;
-
-	if (mixer->chip->usb_id == USB_ID(0x041e, 0x3020) ||
-	    mixer->chip->usb_id == USB_ID(0x041e, 0x3040) ||
-	    mixer->chip->usb_id == USB_ID(0x041e, 0x3048)) {
-		if ((err = snd_audigy2nx_controls_create(mixer)) < 0)
-			goto _error;
-		if (!snd_card_proc_new(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)
-			goto _error;
-	}
+	snd_usb_mixer_apply_create_quirk(mixer);
 
 	err = snd_device_new(chip->card, SNDRV_DEV_LOWLEVEL, mixer, &dev_ops);
 	if (err < 0)
@@ -2316,7 +1913,7 @@
 void snd_usb_mixer_disconnect(struct list_head *p)
 {
 	struct usb_mixer_interface *mixer;
-	
+
 	mixer = list_entry(p, struct usb_mixer_interface, list);
 	usb_kill_urb(mixer->urb);
 	usb_kill_urb(mixer->rc_urb);
diff --git a/sound/usb/usbmixer.h b/sound/usb/usbmixer.h
index e199e4b..63101ae 100644
--- a/sound/usb/usbmixer.h
+++ b/sound/usb/usbmixer.h
@@ -1,11 +1,52 @@
 #ifndef __USBMIXER_H
 #define __USBMIXER_H
 
+struct usb_mixer_interface {
+	struct snd_usb_audio *chip;
+	unsigned int ctrlif;
+	struct list_head list;
+	unsigned int ignore_ctl_error;
+	struct urb *urb;
+	/* array[MAX_ID_ELEMS], indexed by unit id */
+	struct usb_mixer_elem_info **id_elems;
+
+	/* Sound Blaster remote control stuff */
+	const struct rc_config *rc_cfg;
+	u32 rc_code;
+	wait_queue_head_t rc_waitq;
+	struct urb *rc_urb;
+	struct usb_ctrlrequest *rc_setup_packet;
+	u8 rc_buffer[6];
+
+	u8 audigy2nx_leds[3];
+	u8 xonar_u1_status;
+};
+
+#define MAX_CHANNELS	10	/* max logical channels */
+
+struct usb_mixer_elem_info {
+	struct usb_mixer_interface *mixer;
+	struct usb_mixer_elem_info *next_id_elem; /* list of controls with same id */
+	struct snd_ctl_elem_id *elem_id;
+	unsigned int id;
+	unsigned int control;	/* CS or ICN (high byte) */
+	unsigned int cmask; /* channel mask bitmap: 0 = master */
+	int channels;
+	int val_type;
+	int min, max, res;
+	int dBmin, dBmax;
+	int cached;
+	int cache_val[MAX_CHANNELS];
+	u8 initialized;
+};
+
 int snd_usb_create_mixer(struct snd_usb_audio *chip, int ctrlif,
 			 int ignore_error);
 void snd_usb_mixer_disconnect(struct list_head *p);
 
-void snd_emuusb_set_samplerate(struct snd_usb_audio *chip,
-			unsigned char samplerate_id);
+void snd_usb_mixer_notify_id(struct usb_mixer_interface *mixer, int unitid);
+
+int snd_usb_mixer_set_ctl_value(struct usb_mixer_elem_info *cval,
+				int request, int validx, int value_set);
 
 #endif /* __USBMIXER_H */