v4.19.13 snapshot.
diff --git a/sound/usb/6fire/Makefile b/sound/usb/6fire/Makefile
new file mode 100644
index 0000000..dfce6ec
--- /dev/null
+++ b/sound/usb/6fire/Makefile
@@ -0,0 +1,3 @@
+snd-usb-6fire-objs += chip.o comm.o midi.o control.o firmware.o pcm.o
+obj-$(CONFIG_SND_USB_6FIRE) += snd-usb-6fire.o
+
diff --git a/sound/usb/6fire/chip.c b/sound/usb/6fire/chip.c
new file mode 100644
index 0000000..17d5e3e
--- /dev/null
+++ b/sound/usb/6fire/chip.c
@@ -0,0 +1,212 @@
+/*
+ * Linux driver for TerraTec DMX 6Fire USB
+ *
+ * Main routines and module definitions.
+ *
+ * Author:	Torsten Schenk <torsten.schenk@zoho.com>
+ * Created:	Jan 01, 2011
+ * Copyright:	(C) Torsten Schenk
+ *
+ * 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.
+ */
+
+#include "chip.h"
+#include "firmware.h"
+#include "pcm.h"
+#include "control.h"
+#include "comm.h"
+#include "midi.h"
+
+#include <linux/moduleparam.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/gfp.h>
+#include <sound/initval.h>
+
+MODULE_AUTHOR("Torsten Schenk <torsten.schenk@zoho.com>");
+MODULE_DESCRIPTION("TerraTec DMX 6Fire USB audio driver");
+MODULE_LICENSE("GPL v2");
+MODULE_SUPPORTED_DEVICE("{{TerraTec,DMX 6Fire USB}}");
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 0-max */
+static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* Id for card */
+static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP; /* Enable card */
+static struct sfire_chip *chips[SNDRV_CARDS] = SNDRV_DEFAULT_PTR;
+static struct usb_device *devices[SNDRV_CARDS] = SNDRV_DEFAULT_PTR;
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for the 6fire sound device");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for the 6fire sound device.");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "Enable the 6fire sound device.");
+
+static DEFINE_MUTEX(register_mutex);
+
+static void usb6fire_chip_abort(struct sfire_chip *chip)
+{
+	if (chip) {
+		if (chip->pcm)
+			usb6fire_pcm_abort(chip);
+		if (chip->midi)
+			usb6fire_midi_abort(chip);
+		if (chip->comm)
+			usb6fire_comm_abort(chip);
+		if (chip->control)
+			usb6fire_control_abort(chip);
+		if (chip->card) {
+			snd_card_disconnect(chip->card);
+			snd_card_free_when_closed(chip->card);
+			chip->card = NULL;
+		}
+	}
+}
+
+static void usb6fire_chip_destroy(struct sfire_chip *chip)
+{
+	if (chip) {
+		if (chip->pcm)
+			usb6fire_pcm_destroy(chip);
+		if (chip->midi)
+			usb6fire_midi_destroy(chip);
+		if (chip->comm)
+			usb6fire_comm_destroy(chip);
+		if (chip->control)
+			usb6fire_control_destroy(chip);
+		if (chip->card)
+			snd_card_free(chip->card);
+	}
+}
+
+static int usb6fire_chip_probe(struct usb_interface *intf,
+			       const struct usb_device_id *usb_id)
+{
+	int ret;
+	int i;
+	struct sfire_chip *chip = NULL;
+	struct usb_device *device = interface_to_usbdev(intf);
+	int regidx = -1; /* index in module parameter array */
+	struct snd_card *card = NULL;
+
+	/* look if we already serve this card and return if so */
+	mutex_lock(&register_mutex);
+	for (i = 0; i < SNDRV_CARDS; i++) {
+		if (devices[i] == device) {
+			if (chips[i])
+				chips[i]->intf_count++;
+			usb_set_intfdata(intf, chips[i]);
+			mutex_unlock(&register_mutex);
+			return 0;
+		} else if (!devices[i] && regidx < 0)
+			regidx = i;
+	}
+	if (regidx < 0) {
+		mutex_unlock(&register_mutex);
+		dev_err(&intf->dev, "too many cards registered.\n");
+		return -ENODEV;
+	}
+	devices[regidx] = device;
+	mutex_unlock(&register_mutex);
+
+	/* check, if firmware is present on device, upload it if not */
+	ret = usb6fire_fw_init(intf);
+	if (ret < 0)
+		return ret;
+	else if (ret == FW_NOT_READY) /* firmware update performed */
+		return 0;
+
+	/* if we are here, card can be registered in alsa. */
+	if (usb_set_interface(device, 0, 0) != 0) {
+		dev_err(&intf->dev, "can't set first interface.\n");
+		return -EIO;
+	}
+	ret = snd_card_new(&intf->dev, index[regidx], id[regidx],
+			   THIS_MODULE, sizeof(struct sfire_chip), &card);
+	if (ret < 0) {
+		dev_err(&intf->dev, "cannot create alsa card.\n");
+		return ret;
+	}
+	strcpy(card->driver, "6FireUSB");
+	strcpy(card->shortname, "TerraTec DMX6FireUSB");
+	sprintf(card->longname, "%s at %d:%d", card->shortname,
+			device->bus->busnum, device->devnum);
+
+	chip = card->private_data;
+	chips[regidx] = chip;
+	chip->dev = device;
+	chip->regidx = regidx;
+	chip->intf_count = 1;
+	chip->card = card;
+
+	ret = usb6fire_comm_init(chip);
+	if (ret < 0)
+		goto destroy_chip;
+
+	ret = usb6fire_midi_init(chip);
+	if (ret < 0)
+		goto destroy_chip;
+
+	ret = usb6fire_pcm_init(chip);
+	if (ret < 0)
+		goto destroy_chip;
+
+	ret = usb6fire_control_init(chip);
+	if (ret < 0)
+		goto destroy_chip;
+
+	ret = snd_card_register(card);
+	if (ret < 0) {
+		dev_err(&intf->dev, "cannot register card.");
+		goto destroy_chip;
+	}
+	usb_set_intfdata(intf, chip);
+	return 0;
+
+destroy_chip:
+	usb6fire_chip_destroy(chip);
+	return ret;
+}
+
+static void usb6fire_chip_disconnect(struct usb_interface *intf)
+{
+	struct sfire_chip *chip;
+
+	chip = usb_get_intfdata(intf);
+	if (chip) { /* if !chip, fw upload has been performed */
+		chip->intf_count--;
+		if (!chip->intf_count) {
+			mutex_lock(&register_mutex);
+			devices[chip->regidx] = NULL;
+			chips[chip->regidx] = NULL;
+			mutex_unlock(&register_mutex);
+
+			chip->shutdown = true;
+			usb6fire_chip_abort(chip);
+			usb6fire_chip_destroy(chip);
+		}
+	}
+}
+
+static const struct usb_device_id device_table[] = {
+	{
+		.match_flags = USB_DEVICE_ID_MATCH_DEVICE,
+		.idVendor = 0x0ccd,
+		.idProduct = 0x0080
+	},
+	{}
+};
+
+MODULE_DEVICE_TABLE(usb, device_table);
+
+static struct usb_driver usb_driver = {
+	.name = "snd-usb-6fire",
+	.probe = usb6fire_chip_probe,
+	.disconnect = usb6fire_chip_disconnect,
+	.id_table = device_table,
+};
+
+module_usb_driver(usb_driver);
diff --git a/sound/usb/6fire/chip.h b/sound/usb/6fire/chip.h
new file mode 100644
index 0000000..bde02d1
--- /dev/null
+++ b/sound/usb/6fire/chip.h
@@ -0,0 +1,31 @@
+/*
+ * Linux driver for TerraTec DMX 6Fire USB
+ *
+ * Author:	Torsten Schenk <torsten.schenk@zoho.com>
+ * Created:	Jan 01, 2011
+ * Copyright:	(C) Torsten Schenk
+ *
+ * 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.
+ */
+#ifndef USB6FIRE_CHIP_H
+#define USB6FIRE_CHIP_H
+
+#include "common.h"
+
+struct sfire_chip {
+	struct usb_device *dev;
+	struct snd_card *card;
+	int intf_count; /* number of registered interfaces */
+	int regidx; /* index in module parameter arrays */
+	bool shutdown;
+
+	struct midi_runtime *midi;
+	struct pcm_runtime *pcm;
+	struct control_runtime *control;
+	struct comm_runtime *comm;
+};
+#endif /* USB6FIRE_CHIP_H */
+
diff --git a/sound/usb/6fire/comm.c b/sound/usb/6fire/comm.c
new file mode 100644
index 0000000..161215d
--- /dev/null
+++ b/sound/usb/6fire/comm.c
@@ -0,0 +1,204 @@
+/*
+ * Linux driver for TerraTec DMX 6Fire USB
+ *
+ * Device communications
+ *
+ * Author:	Torsten Schenk <torsten.schenk@zoho.com>
+ * Created:	Jan 01, 2011
+ * Copyright:	(C) Torsten Schenk
+ *
+ * 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.
+ */
+
+#include "comm.h"
+#include "chip.h"
+#include "midi.h"
+
+enum {
+	COMM_EP = 1,
+	COMM_FPGA_EP = 2
+};
+
+static void usb6fire_comm_init_urb(struct comm_runtime *rt, struct urb *urb,
+		u8 *buffer, void *context, void(*handler)(struct urb *urb))
+{
+	usb_init_urb(urb);
+	urb->transfer_buffer = buffer;
+	urb->pipe = usb_sndintpipe(rt->chip->dev, COMM_EP);
+	urb->complete = handler;
+	urb->context = context;
+	urb->interval = 1;
+	urb->dev = rt->chip->dev;
+}
+
+static void usb6fire_comm_receiver_handler(struct urb *urb)
+{
+	struct comm_runtime *rt = urb->context;
+	struct midi_runtime *midi_rt = rt->chip->midi;
+
+	if (!urb->status) {
+		if (rt->receiver_buffer[0] == 0x10) /* midi in event */
+			if (midi_rt)
+				midi_rt->in_received(midi_rt,
+						rt->receiver_buffer + 2,
+						rt->receiver_buffer[1]);
+	}
+
+	if (!rt->chip->shutdown) {
+		urb->status = 0;
+		urb->actual_length = 0;
+		if (usb_submit_urb(urb, GFP_ATOMIC) < 0)
+			dev_warn(&urb->dev->dev,
+					"comm data receiver aborted.\n");
+	}
+}
+
+static void usb6fire_comm_init_buffer(u8 *buffer, u8 id, u8 request,
+		u8 reg, u8 vl, u8 vh)
+{
+	buffer[0] = 0x01;
+	buffer[2] = request;
+	buffer[3] = id;
+	switch (request) {
+	case 0x02:
+		buffer[1] = 0x05; /* length (starting at buffer[2]) */
+		buffer[4] = reg;
+		buffer[5] = vl;
+		buffer[6] = vh;
+		break;
+
+	case 0x12:
+		buffer[1] = 0x0b; /* length (starting at buffer[2]) */
+		buffer[4] = 0x00;
+		buffer[5] = 0x18;
+		buffer[6] = 0x05;
+		buffer[7] = 0x00;
+		buffer[8] = 0x01;
+		buffer[9] = 0x00;
+		buffer[10] = 0x9e;
+		buffer[11] = reg;
+		buffer[12] = vl;
+		break;
+
+	case 0x20:
+	case 0x21:
+	case 0x22:
+		buffer[1] = 0x04;
+		buffer[4] = reg;
+		buffer[5] = vl;
+		break;
+	}
+}
+
+static int usb6fire_comm_send_buffer(u8 *buffer, struct usb_device *dev)
+{
+	int ret;
+	int actual_len;
+
+	ret = usb_interrupt_msg(dev, usb_sndintpipe(dev, COMM_EP),
+			buffer, buffer[1] + 2, &actual_len, HZ);
+	if (ret < 0)
+		return ret;
+	else if (actual_len != buffer[1] + 2)
+		return -EIO;
+	return 0;
+}
+
+static int usb6fire_comm_write8(struct comm_runtime *rt, u8 request,
+		u8 reg, u8 value)
+{
+	u8 *buffer;
+	int ret;
+
+	/* 13: maximum length of message */
+	buffer = kmalloc(13, GFP_KERNEL);
+	if (!buffer)
+		return -ENOMEM;
+
+	usb6fire_comm_init_buffer(buffer, 0x00, request, reg, value, 0x00);
+	ret = usb6fire_comm_send_buffer(buffer, rt->chip->dev);
+
+	kfree(buffer);
+	return ret;
+}
+
+static int usb6fire_comm_write16(struct comm_runtime *rt, u8 request,
+		u8 reg, u8 vl, u8 vh)
+{
+	u8 *buffer;
+	int ret;
+
+	/* 13: maximum length of message */
+	buffer = kmalloc(13, GFP_KERNEL);
+	if (!buffer)
+		return -ENOMEM;
+
+	usb6fire_comm_init_buffer(buffer, 0x00, request, reg, vl, vh);
+	ret = usb6fire_comm_send_buffer(buffer, rt->chip->dev);
+
+	kfree(buffer);
+	return ret;
+}
+
+int usb6fire_comm_init(struct sfire_chip *chip)
+{
+	struct comm_runtime *rt = kzalloc(sizeof(struct comm_runtime),
+			GFP_KERNEL);
+	struct urb *urb;
+	int ret;
+
+	if (!rt)
+		return -ENOMEM;
+
+	rt->receiver_buffer = kzalloc(COMM_RECEIVER_BUFSIZE, GFP_KERNEL);
+	if (!rt->receiver_buffer) {
+		kfree(rt);
+		return -ENOMEM;
+	}
+
+	urb = &rt->receiver;
+	rt->serial = 1;
+	rt->chip = chip;
+	usb_init_urb(urb);
+	rt->init_urb = usb6fire_comm_init_urb;
+	rt->write8 = usb6fire_comm_write8;
+	rt->write16 = usb6fire_comm_write16;
+
+	/* submit an urb that receives communication data from device */
+	urb->transfer_buffer = rt->receiver_buffer;
+	urb->transfer_buffer_length = COMM_RECEIVER_BUFSIZE;
+	urb->pipe = usb_rcvintpipe(chip->dev, COMM_EP);
+	urb->dev = chip->dev;
+	urb->complete = usb6fire_comm_receiver_handler;
+	urb->context = rt;
+	urb->interval = 1;
+	ret = usb_submit_urb(urb, GFP_KERNEL);
+	if (ret < 0) {
+		kfree(rt->receiver_buffer);
+		kfree(rt);
+		dev_err(&chip->dev->dev, "cannot create comm data receiver.");
+		return ret;
+	}
+	chip->comm = rt;
+	return 0;
+}
+
+void usb6fire_comm_abort(struct sfire_chip *chip)
+{
+	struct comm_runtime *rt = chip->comm;
+
+	if (rt)
+		usb_poison_urb(&rt->receiver);
+}
+
+void usb6fire_comm_destroy(struct sfire_chip *chip)
+{
+	struct comm_runtime *rt = chip->comm;
+
+	kfree(rt->receiver_buffer);
+	kfree(rt);
+	chip->comm = NULL;
+}
diff --git a/sound/usb/6fire/comm.h b/sound/usb/6fire/comm.h
new file mode 100644
index 0000000..780d5ed
--- /dev/null
+++ b/sound/usb/6fire/comm.h
@@ -0,0 +1,43 @@
+/*
+ * Linux driver for TerraTec DMX 6Fire USB
+ *
+ * Author:	Torsten Schenk <torsten.schenk@zoho.com>
+ * Created:	Jan 01, 2011
+ * Copyright:	(C) Torsten Schenk
+ *
+ * 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.
+ */
+#ifndef USB6FIRE_COMM_H
+#define USB6FIRE_COMM_H
+
+#include "common.h"
+
+enum /* settings for comm */
+{
+	COMM_RECEIVER_BUFSIZE = 64,
+};
+
+struct comm_runtime {
+	struct sfire_chip *chip;
+
+	struct urb receiver;
+	u8 *receiver_buffer;
+
+	u8 serial; /* urb serial */
+
+	void (*init_urb)(struct comm_runtime *rt, struct urb *urb, u8 *buffer,
+			void *context, void(*handler)(struct urb *urb));
+	/* writes control data to the device */
+	int (*write8)(struct comm_runtime *rt, u8 request, u8 reg, u8 value);
+	int (*write16)(struct comm_runtime *rt, u8 request, u8 reg,
+			u8 vh, u8 vl);
+};
+
+int usb6fire_comm_init(struct sfire_chip *chip);
+void usb6fire_comm_abort(struct sfire_chip *chip);
+void usb6fire_comm_destroy(struct sfire_chip *chip);
+#endif /* USB6FIRE_COMM_H */
+
diff --git a/sound/usb/6fire/common.h b/sound/usb/6fire/common.h
new file mode 100644
index 0000000..b6eb03e
--- /dev/null
+++ b/sound/usb/6fire/common.h
@@ -0,0 +1,29 @@
+/*
+ * Linux driver for TerraTec DMX 6Fire USB
+ *
+ * Author:	Torsten Schenk <torsten.schenk@zoho.com>
+ * Created:	Jan 01, 2011
+ * Copyright:	(C) Torsten Schenk
+ *
+ * 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.
+ */
+
+#ifndef USB6FIRE_COMMON_H
+#define USB6FIRE_COMMON_H
+
+#include <linux/slab.h>
+#include <linux/usb.h>
+#include <sound/core.h>
+
+#define PREFIX "6fire: "
+
+struct sfire_chip;
+struct midi_runtime;
+struct pcm_runtime;
+struct control_runtime;
+struct comm_runtime;
+#endif /* USB6FIRE_COMMON_H */
+
diff --git a/sound/usb/6fire/control.c b/sound/usb/6fire/control.c
new file mode 100644
index 0000000..54656ee
--- /dev/null
+++ b/sound/usb/6fire/control.c
@@ -0,0 +1,621 @@
+/*
+ * Linux driver for TerraTec DMX 6Fire USB
+ *
+ * Mixer control
+ *
+ * Author:	Torsten Schenk <torsten.schenk@zoho.com>
+ * Created:	Jan 01, 2011
+ * Copyright:	(C) Torsten Schenk
+ *
+ * Thanks to:
+ * - Holger Ruckdeschel: he found out how to control individual channel
+ *   volumes and introduced mute switch
+ *
+ * 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.
+ */
+
+#include <linux/interrupt.h>
+#include <sound/control.h>
+#include <sound/tlv.h>
+
+#include "control.h"
+#include "comm.h"
+#include "chip.h"
+
+static const char * const opt_coax_texts[2] = { "Optical", "Coax" };
+static const char * const line_phono_texts[2] = { "Line", "Phono" };
+
+/*
+ * data that needs to be sent to device. sets up card internal stuff.
+ * values dumped from windows driver and filtered by trial'n'error.
+ */
+static const struct {
+	u8 type;
+	u8 reg;
+	u8 value;
+}
+init_data[] = {
+	{ 0x22, 0x00, 0x00 }, { 0x20, 0x00, 0x08 }, { 0x22, 0x01, 0x01 },
+	{ 0x20, 0x01, 0x08 }, { 0x22, 0x02, 0x00 }, { 0x20, 0x02, 0x08 },
+	{ 0x22, 0x03, 0x00 }, { 0x20, 0x03, 0x08 }, { 0x22, 0x04, 0x00 },
+	{ 0x20, 0x04, 0x08 }, { 0x22, 0x05, 0x01 }, { 0x20, 0x05, 0x08 },
+	{ 0x22, 0x04, 0x01 }, { 0x12, 0x04, 0x00 }, { 0x12, 0x05, 0x00 },
+	{ 0x12, 0x0d, 0x38 }, { 0x12, 0x21, 0x82 }, { 0x12, 0x22, 0x80 },
+	{ 0x12, 0x23, 0x00 }, { 0x12, 0x06, 0x02 }, { 0x12, 0x03, 0x00 },
+	{ 0x12, 0x02, 0x00 }, { 0x22, 0x03, 0x01 },
+	{ 0 } /* TERMINATING ENTRY */
+};
+
+static const int rates_altsetting[] = { 1, 1, 2, 2, 3, 3 };
+/* values to write to soundcard register for all samplerates */
+static const u16 rates_6fire_vl[] = {0x00, 0x01, 0x00, 0x01, 0x00, 0x01};
+static const u16 rates_6fire_vh[] = {0x11, 0x11, 0x10, 0x10, 0x00, 0x00};
+
+static DECLARE_TLV_DB_MINMAX(tlv_output, -9000, 0);
+static DECLARE_TLV_DB_MINMAX(tlv_input, -1500, 1500);
+
+enum {
+	DIGITAL_THRU_ONLY_SAMPLERATE = 3
+};
+
+static void usb6fire_control_output_vol_update(struct control_runtime *rt)
+{
+	struct comm_runtime *comm_rt = rt->chip->comm;
+	int i;
+
+	if (comm_rt)
+		for (i = 0; i < 6; i++)
+			if (!(rt->ovol_updated & (1 << i))) {
+				comm_rt->write8(comm_rt, 0x12, 0x0f + i,
+					180 - rt->output_vol[i]);
+				rt->ovol_updated |= 1 << i;
+			}
+}
+
+static void usb6fire_control_output_mute_update(struct control_runtime *rt)
+{
+	struct comm_runtime *comm_rt = rt->chip->comm;
+
+	if (comm_rt)
+		comm_rt->write8(comm_rt, 0x12, 0x0e, ~rt->output_mute);
+}
+
+static void usb6fire_control_input_vol_update(struct control_runtime *rt)
+{
+	struct comm_runtime *comm_rt = rt->chip->comm;
+	int i;
+
+	if (comm_rt)
+		for (i = 0; i < 2; i++)
+			if (!(rt->ivol_updated & (1 << i))) {
+				comm_rt->write8(comm_rt, 0x12, 0x1c + i,
+					rt->input_vol[i] & 0x3f);
+				rt->ivol_updated |= 1 << i;
+			}
+}
+
+static void usb6fire_control_line_phono_update(struct control_runtime *rt)
+{
+	struct comm_runtime *comm_rt = rt->chip->comm;
+	if (comm_rt) {
+		comm_rt->write8(comm_rt, 0x22, 0x02, rt->line_phono_switch);
+		comm_rt->write8(comm_rt, 0x21, 0x02, rt->line_phono_switch);
+	}
+}
+
+static void usb6fire_control_opt_coax_update(struct control_runtime *rt)
+{
+	struct comm_runtime *comm_rt = rt->chip->comm;
+	if (comm_rt) {
+		comm_rt->write8(comm_rt, 0x22, 0x00, rt->opt_coax_switch);
+		comm_rt->write8(comm_rt, 0x21, 0x00, rt->opt_coax_switch);
+	}
+}
+
+static int usb6fire_control_set_rate(struct control_runtime *rt, int rate)
+{
+	int ret;
+	struct usb_device *device = rt->chip->dev;
+	struct comm_runtime *comm_rt = rt->chip->comm;
+
+	if (rate < 0 || rate >= CONTROL_N_RATES)
+		return -EINVAL;
+
+	ret = usb_set_interface(device, 1, rates_altsetting[rate]);
+	if (ret < 0)
+		return ret;
+
+	/* set soundcard clock */
+	ret = comm_rt->write16(comm_rt, 0x02, 0x01, rates_6fire_vl[rate],
+			rates_6fire_vh[rate]);
+	if (ret < 0)
+		return ret;
+
+	return 0;
+}
+
+static int usb6fire_control_set_channels(
+	struct control_runtime *rt, int n_analog_out,
+	int n_analog_in, bool spdif_out, bool spdif_in)
+{
+	int ret;
+	struct comm_runtime *comm_rt = rt->chip->comm;
+
+	/* enable analog inputs and outputs
+	 * (one bit per stereo-channel) */
+	ret = comm_rt->write16(comm_rt, 0x02, 0x02,
+			(1 << (n_analog_out / 2)) - 1,
+			(1 << (n_analog_in / 2)) - 1);
+	if (ret < 0)
+		return ret;
+
+	/* disable digital inputs and outputs */
+	/* TODO: use spdif_x to enable/disable digital channels */
+	ret = comm_rt->write16(comm_rt, 0x02, 0x03, 0x00, 0x00);
+	if (ret < 0)
+		return ret;
+
+	return 0;
+}
+
+static int usb6fire_control_streaming_update(struct control_runtime *rt)
+{
+	struct comm_runtime *comm_rt = rt->chip->comm;
+
+	if (comm_rt) {
+		if (!rt->usb_streaming && rt->digital_thru_switch)
+			usb6fire_control_set_rate(rt,
+				DIGITAL_THRU_ONLY_SAMPLERATE);
+		return comm_rt->write16(comm_rt, 0x02, 0x00, 0x00,
+			(rt->usb_streaming ? 0x01 : 0x00) |
+			(rt->digital_thru_switch ? 0x08 : 0x00));
+	}
+	return -EINVAL;
+}
+
+static int usb6fire_control_output_vol_info(struct snd_kcontrol *kcontrol,
+		struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 2;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = 180;
+	return 0;
+}
+
+static int usb6fire_control_output_vol_put(struct snd_kcontrol *kcontrol,
+		struct snd_ctl_elem_value *ucontrol)
+{
+	struct control_runtime *rt = snd_kcontrol_chip(kcontrol);
+	unsigned int ch = kcontrol->private_value;
+	int changed = 0;
+
+	if (ch > 4) {
+		dev_err(&rt->chip->dev->dev,
+			"Invalid channel in volume control.");
+		return -EINVAL;
+	}
+
+	if (rt->output_vol[ch] != ucontrol->value.integer.value[0]) {
+		rt->output_vol[ch] = ucontrol->value.integer.value[0];
+		rt->ovol_updated &= ~(1 << ch);
+		changed = 1;
+	}
+	if (rt->output_vol[ch + 1] != ucontrol->value.integer.value[1]) {
+		rt->output_vol[ch + 1] = ucontrol->value.integer.value[1];
+		rt->ovol_updated &= ~(2 << ch);
+		changed = 1;
+	}
+
+	if (changed)
+		usb6fire_control_output_vol_update(rt);
+
+	return changed;
+}
+
+static int usb6fire_control_output_vol_get(struct snd_kcontrol *kcontrol,
+		struct snd_ctl_elem_value *ucontrol)
+{
+	struct control_runtime *rt = snd_kcontrol_chip(kcontrol);
+	unsigned int ch = kcontrol->private_value;
+
+	if (ch > 4) {
+		dev_err(&rt->chip->dev->dev,
+			"Invalid channel in volume control.");
+		return -EINVAL;
+	}
+
+	ucontrol->value.integer.value[0] = rt->output_vol[ch];
+	ucontrol->value.integer.value[1] = rt->output_vol[ch + 1];
+	return 0;
+}
+
+static int usb6fire_control_output_mute_put(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	struct control_runtime *rt = snd_kcontrol_chip(kcontrol);
+	unsigned int ch = kcontrol->private_value;
+	u8 old = rt->output_mute;
+	u8 value = 0;
+
+	if (ch > 4) {
+		dev_err(&rt->chip->dev->dev,
+			"Invalid channel in volume control.");
+		return -EINVAL;
+	}
+
+	rt->output_mute &= ~(3 << ch);
+	if (ucontrol->value.integer.value[0])
+		value |= 1;
+	if (ucontrol->value.integer.value[1])
+		value |= 2;
+	rt->output_mute |= value << ch;
+
+	if (rt->output_mute != old)
+		usb6fire_control_output_mute_update(rt);
+
+	return rt->output_mute != old;
+}
+
+static int usb6fire_control_output_mute_get(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	struct control_runtime *rt = snd_kcontrol_chip(kcontrol);
+	unsigned int ch = kcontrol->private_value;
+	u8 value = rt->output_mute >> ch;
+
+	if (ch > 4) {
+		dev_err(&rt->chip->dev->dev,
+			"Invalid channel in volume control.");
+		return -EINVAL;
+	}
+
+	ucontrol->value.integer.value[0] = 1 & value;
+	value >>= 1;
+	ucontrol->value.integer.value[1] = 1 & value;
+
+	return 0;
+}
+
+static int usb6fire_control_input_vol_info(struct snd_kcontrol *kcontrol,
+		struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 2;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = 30;
+	return 0;
+}
+
+static int usb6fire_control_input_vol_put(struct snd_kcontrol *kcontrol,
+		struct snd_ctl_elem_value *ucontrol)
+{
+	struct control_runtime *rt = snd_kcontrol_chip(kcontrol);
+	int changed = 0;
+
+	if (rt->input_vol[0] != ucontrol->value.integer.value[0]) {
+		rt->input_vol[0] = ucontrol->value.integer.value[0] - 15;
+		rt->ivol_updated &= ~(1 << 0);
+		changed = 1;
+	}
+	if (rt->input_vol[1] != ucontrol->value.integer.value[1]) {
+		rt->input_vol[1] = ucontrol->value.integer.value[1] - 15;
+		rt->ivol_updated &= ~(1 << 1);
+		changed = 1;
+	}
+
+	if (changed)
+		usb6fire_control_input_vol_update(rt);
+
+	return changed;
+}
+
+static int usb6fire_control_input_vol_get(struct snd_kcontrol *kcontrol,
+		struct snd_ctl_elem_value *ucontrol)
+{
+	struct control_runtime *rt = snd_kcontrol_chip(kcontrol);
+
+	ucontrol->value.integer.value[0] = rt->input_vol[0] + 15;
+	ucontrol->value.integer.value[1] = rt->input_vol[1] + 15;
+
+	return 0;
+}
+
+static int usb6fire_control_line_phono_info(struct snd_kcontrol *kcontrol,
+		struct snd_ctl_elem_info *uinfo)
+{
+	return snd_ctl_enum_info(uinfo, 1, 2, line_phono_texts);
+}
+
+static int usb6fire_control_line_phono_put(struct snd_kcontrol *kcontrol,
+		struct snd_ctl_elem_value *ucontrol)
+{
+	struct control_runtime *rt = snd_kcontrol_chip(kcontrol);
+	int changed = 0;
+	if (rt->line_phono_switch != ucontrol->value.integer.value[0]) {
+		rt->line_phono_switch = ucontrol->value.integer.value[0];
+		usb6fire_control_line_phono_update(rt);
+		changed = 1;
+	}
+	return changed;
+}
+
+static int usb6fire_control_line_phono_get(struct snd_kcontrol *kcontrol,
+		struct snd_ctl_elem_value *ucontrol)
+{
+	struct control_runtime *rt = snd_kcontrol_chip(kcontrol);
+	ucontrol->value.integer.value[0] = rt->line_phono_switch;
+	return 0;
+}
+
+static int usb6fire_control_opt_coax_info(struct snd_kcontrol *kcontrol,
+		struct snd_ctl_elem_info *uinfo)
+{
+	return snd_ctl_enum_info(uinfo, 1, 2, opt_coax_texts);
+}
+
+static int usb6fire_control_opt_coax_put(struct snd_kcontrol *kcontrol,
+		struct snd_ctl_elem_value *ucontrol)
+{
+	struct control_runtime *rt = snd_kcontrol_chip(kcontrol);
+	int changed = 0;
+
+	if (rt->opt_coax_switch != ucontrol->value.enumerated.item[0]) {
+		rt->opt_coax_switch = ucontrol->value.enumerated.item[0];
+		usb6fire_control_opt_coax_update(rt);
+		changed = 1;
+	}
+	return changed;
+}
+
+static int usb6fire_control_opt_coax_get(struct snd_kcontrol *kcontrol,
+		struct snd_ctl_elem_value *ucontrol)
+{
+	struct control_runtime *rt = snd_kcontrol_chip(kcontrol);
+	ucontrol->value.enumerated.item[0] = rt->opt_coax_switch;
+	return 0;
+}
+
+static int usb6fire_control_digital_thru_put(struct snd_kcontrol *kcontrol,
+		struct snd_ctl_elem_value *ucontrol)
+{
+	struct control_runtime *rt = snd_kcontrol_chip(kcontrol);
+	int changed = 0;
+
+	if (rt->digital_thru_switch != ucontrol->value.integer.value[0]) {
+		rt->digital_thru_switch = ucontrol->value.integer.value[0];
+		usb6fire_control_streaming_update(rt);
+		changed = 1;
+	}
+	return changed;
+}
+
+static int usb6fire_control_digital_thru_get(struct snd_kcontrol *kcontrol,
+		struct snd_ctl_elem_value *ucontrol)
+{
+	struct control_runtime *rt = snd_kcontrol_chip(kcontrol);
+	ucontrol->value.integer.value[0] = rt->digital_thru_switch;
+	return 0;
+}
+
+static struct snd_kcontrol_new vol_elements[] = {
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Analog Playback Volume",
+		.index = 0,
+		.private_value = 0,
+		.access = SNDRV_CTL_ELEM_ACCESS_READWRITE |
+			SNDRV_CTL_ELEM_ACCESS_TLV_READ,
+		.info = usb6fire_control_output_vol_info,
+		.get = usb6fire_control_output_vol_get,
+		.put = usb6fire_control_output_vol_put,
+		.tlv = { .p = tlv_output }
+	},
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Analog Playback Volume",
+		.index = 1,
+		.private_value = 2,
+		.access = SNDRV_CTL_ELEM_ACCESS_READWRITE |
+			SNDRV_CTL_ELEM_ACCESS_TLV_READ,
+		.info = usb6fire_control_output_vol_info,
+		.get = usb6fire_control_output_vol_get,
+		.put = usb6fire_control_output_vol_put,
+		.tlv = { .p = tlv_output }
+	},
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Analog Playback Volume",
+		.index = 2,
+		.private_value = 4,
+		.access = SNDRV_CTL_ELEM_ACCESS_READWRITE |
+			SNDRV_CTL_ELEM_ACCESS_TLV_READ,
+		.info = usb6fire_control_output_vol_info,
+		.get = usb6fire_control_output_vol_get,
+		.put = usb6fire_control_output_vol_put,
+		.tlv = { .p = tlv_output }
+	},
+	{}
+};
+
+static struct snd_kcontrol_new mute_elements[] = {
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Analog Playback Switch",
+		.index = 0,
+		.private_value = 0,
+		.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+		.info = snd_ctl_boolean_stereo_info,
+		.get = usb6fire_control_output_mute_get,
+		.put = usb6fire_control_output_mute_put,
+	},
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Analog Playback Switch",
+		.index = 1,
+		.private_value = 2,
+		.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+		.info = snd_ctl_boolean_stereo_info,
+		.get = usb6fire_control_output_mute_get,
+		.put = usb6fire_control_output_mute_put,
+	},
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Analog Playback Switch",
+		.index = 2,
+		.private_value = 4,
+		.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+		.info = snd_ctl_boolean_stereo_info,
+		.get = usb6fire_control_output_mute_get,
+		.put = usb6fire_control_output_mute_put,
+	},
+	{}
+};
+
+static struct snd_kcontrol_new elements[] = {
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Line/Phono Capture Route",
+		.index = 0,
+		.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+		.info = usb6fire_control_line_phono_info,
+		.get = usb6fire_control_line_phono_get,
+		.put = usb6fire_control_line_phono_put
+	},
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Opt/Coax Capture Route",
+		.index = 0,
+		.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+		.info = usb6fire_control_opt_coax_info,
+		.get = usb6fire_control_opt_coax_get,
+		.put = usb6fire_control_opt_coax_put
+	},
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Digital Thru Playback Route",
+		.index = 0,
+		.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+		.info = snd_ctl_boolean_mono_info,
+		.get = usb6fire_control_digital_thru_get,
+		.put = usb6fire_control_digital_thru_put
+	},
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Analog Capture Volume",
+		.index = 0,
+		.access = SNDRV_CTL_ELEM_ACCESS_READWRITE |
+			SNDRV_CTL_ELEM_ACCESS_TLV_READ,
+		.info = usb6fire_control_input_vol_info,
+		.get = usb6fire_control_input_vol_get,
+		.put = usb6fire_control_input_vol_put,
+		.tlv = { .p = tlv_input }
+	},
+	{}
+};
+
+static int usb6fire_control_add_virtual(
+	struct control_runtime *rt,
+	struct snd_card *card,
+	char *name,
+	struct snd_kcontrol_new *elems)
+{
+	int ret;
+	int i;
+	struct snd_kcontrol *vmaster =
+		snd_ctl_make_virtual_master(name, tlv_output);
+	struct snd_kcontrol *control;
+
+	if (!vmaster)
+		return -ENOMEM;
+	ret = snd_ctl_add(card, vmaster);
+	if (ret < 0)
+		return ret;
+
+	i = 0;
+	while (elems[i].name) {
+		control = snd_ctl_new1(&elems[i], rt);
+		if (!control)
+			return -ENOMEM;
+		ret = snd_ctl_add(card, control);
+		if (ret < 0)
+			return ret;
+		ret = snd_ctl_add_slave(vmaster, control);
+		if (ret < 0)
+			return ret;
+		i++;
+	}
+	return 0;
+}
+
+int usb6fire_control_init(struct sfire_chip *chip)
+{
+	int i;
+	int ret;
+	struct control_runtime *rt = kzalloc(sizeof(struct control_runtime),
+			GFP_KERNEL);
+	struct comm_runtime *comm_rt = chip->comm;
+
+	if (!rt)
+		return -ENOMEM;
+
+	rt->chip = chip;
+	rt->update_streaming = usb6fire_control_streaming_update;
+	rt->set_rate = usb6fire_control_set_rate;
+	rt->set_channels = usb6fire_control_set_channels;
+
+	i = 0;
+	while (init_data[i].type) {
+		comm_rt->write8(comm_rt, init_data[i].type, init_data[i].reg,
+				init_data[i].value);
+		i++;
+	}
+
+	usb6fire_control_opt_coax_update(rt);
+	usb6fire_control_line_phono_update(rt);
+	usb6fire_control_output_vol_update(rt);
+	usb6fire_control_output_mute_update(rt);
+	usb6fire_control_input_vol_update(rt);
+	usb6fire_control_streaming_update(rt);
+
+	ret = usb6fire_control_add_virtual(rt, chip->card,
+		"Master Playback Volume", vol_elements);
+	if (ret) {
+		dev_err(&chip->dev->dev, "cannot add control.\n");
+		kfree(rt);
+		return ret;
+	}
+	ret = usb6fire_control_add_virtual(rt, chip->card,
+		"Master Playback Switch", mute_elements);
+	if (ret) {
+		dev_err(&chip->dev->dev, "cannot add control.\n");
+		kfree(rt);
+		return ret;
+	}
+
+	i = 0;
+	while (elements[i].name) {
+		ret = snd_ctl_add(chip->card, snd_ctl_new1(&elements[i], rt));
+		if (ret < 0) {
+			kfree(rt);
+			dev_err(&chip->dev->dev, "cannot add control.\n");
+			return ret;
+		}
+		i++;
+	}
+
+	chip->control = rt;
+	return 0;
+}
+
+void usb6fire_control_abort(struct sfire_chip *chip)
+{}
+
+void usb6fire_control_destroy(struct sfire_chip *chip)
+{
+	kfree(chip->control);
+	chip->control = NULL;
+}
diff --git a/sound/usb/6fire/control.h b/sound/usb/6fire/control.h
new file mode 100644
index 0000000..5a40ba1
--- /dev/null
+++ b/sound/usb/6fire/control.h
@@ -0,0 +1,57 @@
+/*
+ * Linux driver for TerraTec DMX 6Fire USB
+ *
+ * Author:	Torsten Schenk <torsten.schenk@zoho.com>
+ * Created:	Jan 01, 2011
+ * Copyright:	(C) Torsten Schenk
+ *
+ * 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.
+ */
+
+#ifndef USB6FIRE_CONTROL_H
+#define USB6FIRE_CONTROL_H
+
+#include "common.h"
+
+enum {
+	CONTROL_MAX_ELEMENTS = 32
+};
+
+enum {
+	CONTROL_RATE_44KHZ,
+	CONTROL_RATE_48KHZ,
+	CONTROL_RATE_88KHZ,
+	CONTROL_RATE_96KHZ,
+	CONTROL_RATE_176KHZ,
+	CONTROL_RATE_192KHZ,
+	CONTROL_N_RATES
+};
+
+struct control_runtime {
+	int (*update_streaming)(struct control_runtime *rt);
+	int (*set_rate)(struct control_runtime *rt, int rate);
+	int (*set_channels)(struct control_runtime *rt, int n_analog_out,
+		int n_analog_in, bool spdif_out, bool spdif_in);
+
+	struct sfire_chip *chip;
+
+	struct snd_kcontrol *element[CONTROL_MAX_ELEMENTS];
+	bool opt_coax_switch;
+	bool line_phono_switch;
+	bool digital_thru_switch;
+	bool usb_streaming;
+	u8 output_vol[6];
+	u8 ovol_updated;
+	u8 output_mute;
+	s8 input_vol[2];
+	u8 ivol_updated;
+};
+
+int usb6fire_control_init(struct sfire_chip *chip);
+void usb6fire_control_abort(struct sfire_chip *chip);
+void usb6fire_control_destroy(struct sfire_chip *chip);
+#endif /* USB6FIRE_CONTROL_H */
+
diff --git a/sound/usb/6fire/firmware.c b/sound/usb/6fire/firmware.c
new file mode 100644
index 0000000..9520b4c
--- /dev/null
+++ b/sound/usb/6fire/firmware.c
@@ -0,0 +1,421 @@
+/*
+ * Linux driver for TerraTec DMX 6Fire USB
+ *
+ * Firmware loader
+ *
+ * Author:	Torsten Schenk <torsten.schenk@zoho.com>
+ * Created:	Jan 01, 2011
+ * Copyright:	(C) Torsten Schenk
+ *
+ * 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.
+ */
+
+#include <linux/firmware.h>
+#include <linux/module.h>
+#include <linux/bitrev.h>
+#include <linux/kernel.h>
+
+#include "firmware.h"
+#include "chip.h"
+
+MODULE_FIRMWARE("6fire/dmx6firel2.ihx");
+MODULE_FIRMWARE("6fire/dmx6fireap.ihx");
+MODULE_FIRMWARE("6fire/dmx6firecf.bin");
+
+enum {
+	FPGA_BUFSIZE = 512, FPGA_EP = 2
+};
+
+/*
+ * wMaxPacketSize of pcm endpoints.
+ * keep synced with rates_in_packet_size and rates_out_packet_size in pcm.c
+ * fpp: frames per isopacket
+ *
+ * CAUTION: keep sizeof <= buffer[] in usb6fire_fw_init
+ */
+static const u8 ep_w_max_packet_size[] = {
+	0xe4, 0x00, 0xe4, 0x00, /* alt 1: 228 EP2 and EP6 (7 fpp) */
+	0xa4, 0x01, 0xa4, 0x01, /* alt 2: 420 EP2 and EP6 (13 fpp)*/
+	0x94, 0x01, 0x5c, 0x02  /* alt 3: 404 EP2 and 604 EP6 (25 fpp) */
+};
+
+static const u8 known_fw_versions[][2] = {
+	{ 0x03, 0x01 }
+};
+
+struct ihex_record {
+	u16 address;
+	u8 len;
+	u8 data[256];
+	char error; /* true if an error occurred parsing this record */
+
+	u8 max_len; /* maximum record length in whole ihex */
+
+	/* private */
+	const char *txt_data;
+	unsigned int txt_length;
+	unsigned int txt_offset; /* current position in txt_data */
+};
+
+static u8 usb6fire_fw_ihex_hex(const u8 *data, u8 *crc)
+{
+	u8 val = 0;
+	int hval;
+
+	hval = hex_to_bin(data[0]);
+	if (hval >= 0)
+		val |= (hval << 4);
+
+	hval = hex_to_bin(data[1]);
+	if (hval >= 0)
+		val |= hval;
+
+	*crc += val;
+	return val;
+}
+
+/*
+ * returns true if record is available, false otherwise.
+ * iff an error occurred, false will be returned and record->error will be true.
+ */
+static bool usb6fire_fw_ihex_next_record(struct ihex_record *record)
+{
+	u8 crc = 0;
+	u8 type;
+	int i;
+
+	record->error = false;
+
+	/* find begin of record (marked by a colon) */
+	while (record->txt_offset < record->txt_length
+			&& record->txt_data[record->txt_offset] != ':')
+		record->txt_offset++;
+	if (record->txt_offset == record->txt_length)
+		return false;
+
+	/* number of characters needed for len, addr and type entries */
+	record->txt_offset++;
+	if (record->txt_offset + 8 > record->txt_length) {
+		record->error = true;
+		return false;
+	}
+
+	record->len = usb6fire_fw_ihex_hex(record->txt_data +
+			record->txt_offset, &crc);
+	record->txt_offset += 2;
+	record->address = usb6fire_fw_ihex_hex(record->txt_data +
+			record->txt_offset, &crc) << 8;
+	record->txt_offset += 2;
+	record->address |= usb6fire_fw_ihex_hex(record->txt_data +
+			record->txt_offset, &crc);
+	record->txt_offset += 2;
+	type = usb6fire_fw_ihex_hex(record->txt_data +
+			record->txt_offset, &crc);
+	record->txt_offset += 2;
+
+	/* number of characters needed for data and crc entries */
+	if (record->txt_offset + 2 * (record->len + 1) > record->txt_length) {
+		record->error = true;
+		return false;
+	}
+	for (i = 0; i < record->len; i++) {
+		record->data[i] = usb6fire_fw_ihex_hex(record->txt_data
+				+ record->txt_offset, &crc);
+		record->txt_offset += 2;
+	}
+	usb6fire_fw_ihex_hex(record->txt_data + record->txt_offset, &crc);
+	if (crc) {
+		record->error = true;
+		return false;
+	}
+
+	if (type == 1 || !record->len) /* eof */
+		return false;
+	else if (type == 0)
+		return true;
+	else {
+		record->error = true;
+		return false;
+	}
+}
+
+static int usb6fire_fw_ihex_init(const struct firmware *fw,
+		struct ihex_record *record)
+{
+	record->txt_data = fw->data;
+	record->txt_length = fw->size;
+	record->txt_offset = 0;
+	record->max_len = 0;
+	/* read all records, if loop ends, record->error indicates,
+	 * whether ihex is valid. */
+	while (usb6fire_fw_ihex_next_record(record))
+		record->max_len = max(record->len, record->max_len);
+	if (record->error)
+		return -EINVAL;
+	record->txt_offset = 0;
+	return 0;
+}
+
+static int usb6fire_fw_ezusb_write(struct usb_device *device,
+		int type, int value, char *data, int len)
+{
+	int ret;
+
+	ret = usb_control_msg(device, usb_sndctrlpipe(device, 0), type,
+			USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+			value, 0, data, len, HZ);
+	if (ret < 0)
+		return ret;
+	else if (ret != len)
+		return -EIO;
+	return 0;
+}
+
+static int usb6fire_fw_ezusb_read(struct usb_device *device,
+		int type, int value, char *data, int len)
+{
+	int ret = usb_control_msg(device, usb_rcvctrlpipe(device, 0), type,
+			USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE, value,
+			0, data, len, HZ);
+	if (ret < 0)
+		return ret;
+	else if (ret != len)
+		return -EIO;
+	return 0;
+}
+
+static int usb6fire_fw_fpga_write(struct usb_device *device,
+		char *data, int len)
+{
+	int actual_len;
+	int ret;
+
+	ret = usb_bulk_msg(device, usb_sndbulkpipe(device, FPGA_EP), data, len,
+			&actual_len, HZ);
+	if (ret < 0)
+		return ret;
+	else if (actual_len != len)
+		return -EIO;
+	return 0;
+}
+
+static int usb6fire_fw_ezusb_upload(
+		struct usb_interface *intf, const char *fwname,
+		unsigned int postaddr, u8 *postdata, unsigned int postlen)
+{
+	int ret;
+	u8 data;
+	struct usb_device *device = interface_to_usbdev(intf);
+	const struct firmware *fw = NULL;
+	struct ihex_record *rec = kmalloc(sizeof(struct ihex_record),
+			GFP_KERNEL);
+
+	if (!rec)
+		return -ENOMEM;
+
+	ret = request_firmware(&fw, fwname, &device->dev);
+	if (ret < 0) {
+		kfree(rec);
+		dev_err(&intf->dev,
+			"error requesting ezusb firmware %s.\n", fwname);
+		return ret;
+	}
+	ret = usb6fire_fw_ihex_init(fw, rec);
+	if (ret < 0) {
+		kfree(rec);
+		release_firmware(fw);
+		dev_err(&intf->dev,
+			"error validating ezusb firmware %s.\n", fwname);
+		return ret;
+	}
+	/* upload firmware image */
+	data = 0x01; /* stop ezusb cpu */
+	ret = usb6fire_fw_ezusb_write(device, 0xa0, 0xe600, &data, 1);
+	if (ret < 0) {
+		kfree(rec);
+		release_firmware(fw);
+		dev_err(&intf->dev,
+			"unable to upload ezusb firmware %s: begin message.\n",
+			fwname);
+		return ret;
+	}
+
+	while (usb6fire_fw_ihex_next_record(rec)) { /* write firmware */
+		ret = usb6fire_fw_ezusb_write(device, 0xa0, rec->address,
+				rec->data, rec->len);
+		if (ret < 0) {
+			kfree(rec);
+			release_firmware(fw);
+			dev_err(&intf->dev,
+				"unable to upload ezusb firmware %s: data urb.\n",
+				fwname);
+			return ret;
+		}
+	}
+
+	release_firmware(fw);
+	kfree(rec);
+	if (postdata) { /* write data after firmware has been uploaded */
+		ret = usb6fire_fw_ezusb_write(device, 0xa0, postaddr,
+				postdata, postlen);
+		if (ret < 0) {
+			dev_err(&intf->dev,
+				"unable to upload ezusb firmware %s: post urb.\n",
+				fwname);
+			return ret;
+		}
+	}
+
+	data = 0x00; /* resume ezusb cpu */
+	ret = usb6fire_fw_ezusb_write(device, 0xa0, 0xe600, &data, 1);
+	if (ret < 0) {
+		dev_err(&intf->dev,
+			"unable to upload ezusb firmware %s: end message.\n",
+			fwname);
+		return ret;
+	}
+	return 0;
+}
+
+static int usb6fire_fw_fpga_upload(
+		struct usb_interface *intf, const char *fwname)
+{
+	int ret;
+	int i;
+	struct usb_device *device = interface_to_usbdev(intf);
+	u8 *buffer = kmalloc(FPGA_BUFSIZE, GFP_KERNEL);
+	const char *c;
+	const char *end;
+	const struct firmware *fw;
+
+	if (!buffer)
+		return -ENOMEM;
+
+	ret = request_firmware(&fw, fwname, &device->dev);
+	if (ret < 0) {
+		dev_err(&intf->dev, "unable to get fpga firmware %s.\n",
+				fwname);
+		kfree(buffer);
+		return -EIO;
+	}
+
+	c = fw->data;
+	end = fw->data + fw->size;
+
+	ret = usb6fire_fw_ezusb_write(device, 8, 0, NULL, 0);
+	if (ret < 0) {
+		kfree(buffer);
+		release_firmware(fw);
+		dev_err(&intf->dev,
+			"unable to upload fpga firmware: begin urb.\n");
+		return ret;
+	}
+
+	while (c != end) {
+		for (i = 0; c != end && i < FPGA_BUFSIZE; i++, c++)
+			buffer[i] = bitrev8((u8)*c);
+
+		ret = usb6fire_fw_fpga_write(device, buffer, i);
+		if (ret < 0) {
+			release_firmware(fw);
+			kfree(buffer);
+			dev_err(&intf->dev,
+				"unable to upload fpga firmware: fw urb.\n");
+			return ret;
+		}
+	}
+	release_firmware(fw);
+	kfree(buffer);
+
+	ret = usb6fire_fw_ezusb_write(device, 9, 0, NULL, 0);
+	if (ret < 0) {
+		dev_err(&intf->dev,
+			"unable to upload fpga firmware: end urb.\n");
+		return ret;
+	}
+	return 0;
+}
+
+/* check, if the firmware version the devices has currently loaded
+ * is known by this driver. 'version' needs to have 4 bytes version
+ * info data. */
+static int usb6fire_fw_check(struct usb_interface *intf, const u8 *version)
+{
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(known_fw_versions); i++)
+		if (!memcmp(version, known_fw_versions + i, 2))
+			return 0;
+
+	dev_err(&intf->dev, "invalid firmware version in device: %4ph. "
+			"please reconnect to power. if this failure "
+			"still happens, check your firmware installation.",
+			version);
+	return -EINVAL;
+}
+
+int usb6fire_fw_init(struct usb_interface *intf)
+{
+	int i;
+	int ret;
+	struct usb_device *device = interface_to_usbdev(intf);
+	/* buffer: 8 receiving bytes from device and
+	 * sizeof(EP_W_MAX_PACKET_SIZE) bytes for non-const copy */
+	u8 buffer[12];
+
+	ret = usb6fire_fw_ezusb_read(device, 1, 0, buffer, 8);
+	if (ret < 0) {
+		dev_err(&intf->dev,
+			"unable to receive device firmware state.\n");
+		return ret;
+	}
+	if (buffer[0] != 0xeb || buffer[1] != 0xaa || buffer[2] != 0x55) {
+		dev_err(&intf->dev,
+			"unknown device firmware state received from device:");
+		for (i = 0; i < 8; i++)
+			printk(KERN_CONT "%02x ", buffer[i]);
+		printk(KERN_CONT "\n");
+		return -EIO;
+	}
+	/* do we need fpga loader ezusb firmware? */
+	if (buffer[3] == 0x01) {
+		ret = usb6fire_fw_ezusb_upload(intf,
+				"6fire/dmx6firel2.ihx", 0, NULL, 0);
+		if (ret < 0)
+			return ret;
+		return FW_NOT_READY;
+	}
+	/* do we need fpga firmware and application ezusb firmware? */
+	else if (buffer[3] == 0x02) {
+		ret = usb6fire_fw_check(intf, buffer + 4);
+		if (ret < 0)
+			return ret;
+		ret = usb6fire_fw_fpga_upload(intf, "6fire/dmx6firecf.bin");
+		if (ret < 0)
+			return ret;
+		memcpy(buffer, ep_w_max_packet_size,
+				sizeof(ep_w_max_packet_size));
+		ret = usb6fire_fw_ezusb_upload(intf, "6fire/dmx6fireap.ihx",
+				0x0003,	buffer, sizeof(ep_w_max_packet_size));
+		if (ret < 0)
+			return ret;
+		return FW_NOT_READY;
+	}
+	/* all fw loaded? */
+	else if (buffer[3] == 0x03)
+		return usb6fire_fw_check(intf, buffer + 4);
+	/* unknown data? */
+	else {
+		dev_err(&intf->dev,
+			"unknown device firmware state received from device: ");
+		for (i = 0; i < 8; i++)
+			printk(KERN_CONT "%02x ", buffer[i]);
+		printk(KERN_CONT "\n");
+		return -EIO;
+	}
+	return 0;
+}
+
diff --git a/sound/usb/6fire/firmware.h b/sound/usb/6fire/firmware.h
new file mode 100644
index 0000000..c109c4f
--- /dev/null
+++ b/sound/usb/6fire/firmware.h
@@ -0,0 +1,27 @@
+/*
+ * Linux driver for TerraTec DMX 6Fire USB
+ *
+ * Author: Torsten Schenk
+ * Created: Jan 01, 2011
+ * Copyright:	(C) Torsten Schenk
+ *
+ * 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.
+ */
+
+#ifndef USB6FIRE_FIRMWARE_H
+#define USB6FIRE_FIRMWARE_H
+
+#include "common.h"
+
+enum /* firmware state of device */
+{
+	FW_READY = 0,
+	FW_NOT_READY = 1
+};
+
+int usb6fire_fw_init(struct usb_interface *intf);
+#endif /* USB6FIRE_FIRMWARE_H */
+
diff --git a/sound/usb/6fire/midi.c b/sound/usb/6fire/midi.c
new file mode 100644
index 0000000..aa5adbb
--- /dev/null
+++ b/sound/usb/6fire/midi.c
@@ -0,0 +1,218 @@
+/*
+ * Linux driver for TerraTec DMX 6Fire USB
+ *
+ * Rawmidi driver
+ *
+ * Author:	Torsten Schenk <torsten.schenk@zoho.com>
+ * Created:	Jan 01, 2011
+ * Copyright:	(C) Torsten Schenk
+ *
+ * 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.
+ */
+
+#include <sound/rawmidi.h>
+
+#include "midi.h"
+#include "chip.h"
+#include "comm.h"
+
+enum {
+	MIDI_BUFSIZE = 64
+};
+
+static void usb6fire_midi_out_handler(struct urb *urb)
+{
+	struct midi_runtime *rt = urb->context;
+	int ret;
+	unsigned long flags;
+
+	spin_lock_irqsave(&rt->out_lock, flags);
+
+	if (rt->out) {
+		ret = snd_rawmidi_transmit(rt->out, rt->out_buffer + 4,
+				MIDI_BUFSIZE - 4);
+		if (ret > 0) { /* more data available, send next packet */
+			rt->out_buffer[1] = ret + 2;
+			rt->out_buffer[3] = rt->out_serial++;
+			urb->transfer_buffer_length = ret + 4;
+
+			ret = usb_submit_urb(urb, GFP_ATOMIC);
+			if (ret < 0)
+				dev_err(&urb->dev->dev,
+					"midi out urb submit failed: %d\n",
+					ret);
+		} else /* no more data to transmit */
+			rt->out = NULL;
+	}
+	spin_unlock_irqrestore(&rt->out_lock, flags);
+}
+
+static void usb6fire_midi_in_received(
+		struct midi_runtime *rt, u8 *data, int length)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&rt->in_lock, flags);
+	if (rt->in)
+		snd_rawmidi_receive(rt->in, data, length);
+	spin_unlock_irqrestore(&rt->in_lock, flags);
+}
+
+static int usb6fire_midi_out_open(struct snd_rawmidi_substream *alsa_sub)
+{
+	return 0;
+}
+
+static int usb6fire_midi_out_close(struct snd_rawmidi_substream *alsa_sub)
+{
+	return 0;
+}
+
+static void usb6fire_midi_out_trigger(
+		struct snd_rawmidi_substream *alsa_sub, int up)
+{
+	struct midi_runtime *rt = alsa_sub->rmidi->private_data;
+	struct urb *urb = &rt->out_urb;
+	__s8 ret;
+	unsigned long flags;
+
+	spin_lock_irqsave(&rt->out_lock, flags);
+	if (up) { /* start transfer */
+		if (rt->out) { /* we are already transmitting so just return */
+			spin_unlock_irqrestore(&rt->out_lock, flags);
+			return;
+		}
+
+		ret = snd_rawmidi_transmit(alsa_sub, rt->out_buffer + 4,
+				MIDI_BUFSIZE - 4);
+		if (ret > 0) {
+			rt->out_buffer[1] = ret + 2;
+			rt->out_buffer[3] = rt->out_serial++;
+			urb->transfer_buffer_length = ret + 4;
+
+			ret = usb_submit_urb(urb, GFP_ATOMIC);
+			if (ret < 0)
+				dev_err(&urb->dev->dev,
+					"midi out urb submit failed: %d\n",
+					ret);
+			else
+				rt->out = alsa_sub;
+		}
+	} else if (rt->out == alsa_sub)
+		rt->out = NULL;
+	spin_unlock_irqrestore(&rt->out_lock, flags);
+}
+
+static void usb6fire_midi_out_drain(struct snd_rawmidi_substream *alsa_sub)
+{
+	struct midi_runtime *rt = alsa_sub->rmidi->private_data;
+	int retry = 0;
+
+	while (rt->out && retry++ < 100)
+		msleep(10);
+}
+
+static int usb6fire_midi_in_open(struct snd_rawmidi_substream *alsa_sub)
+{
+	return 0;
+}
+
+static int usb6fire_midi_in_close(struct snd_rawmidi_substream *alsa_sub)
+{
+	return 0;
+}
+
+static void usb6fire_midi_in_trigger(
+		struct snd_rawmidi_substream *alsa_sub, int up)
+{
+	struct midi_runtime *rt = alsa_sub->rmidi->private_data;
+	unsigned long flags;
+
+	spin_lock_irqsave(&rt->in_lock, flags);
+	if (up)
+		rt->in = alsa_sub;
+	else
+		rt->in = NULL;
+	spin_unlock_irqrestore(&rt->in_lock, flags);
+}
+
+static const struct snd_rawmidi_ops out_ops = {
+	.open = usb6fire_midi_out_open,
+	.close = usb6fire_midi_out_close,
+	.trigger = usb6fire_midi_out_trigger,
+	.drain = usb6fire_midi_out_drain
+};
+
+static const struct snd_rawmidi_ops in_ops = {
+	.open = usb6fire_midi_in_open,
+	.close = usb6fire_midi_in_close,
+	.trigger = usb6fire_midi_in_trigger
+};
+
+int usb6fire_midi_init(struct sfire_chip *chip)
+{
+	int ret;
+	struct midi_runtime *rt = kzalloc(sizeof(struct midi_runtime),
+			GFP_KERNEL);
+	struct comm_runtime *comm_rt = chip->comm;
+
+	if (!rt)
+		return -ENOMEM;
+
+	rt->out_buffer = kzalloc(MIDI_BUFSIZE, GFP_KERNEL);
+	if (!rt->out_buffer) {
+		kfree(rt);
+		return -ENOMEM;
+	}
+
+	rt->chip = chip;
+	rt->in_received = usb6fire_midi_in_received;
+	rt->out_buffer[0] = 0x80; /* 'send midi' command */
+	rt->out_buffer[1] = 0x00; /* size of data */
+	rt->out_buffer[2] = 0x00; /* always 0 */
+	spin_lock_init(&rt->in_lock);
+	spin_lock_init(&rt->out_lock);
+
+	comm_rt->init_urb(comm_rt, &rt->out_urb, rt->out_buffer, rt,
+			usb6fire_midi_out_handler);
+
+	ret = snd_rawmidi_new(chip->card, "6FireUSB", 0, 1, 1, &rt->instance);
+	if (ret < 0) {
+		kfree(rt->out_buffer);
+		kfree(rt);
+		dev_err(&chip->dev->dev, "unable to create midi.\n");
+		return ret;
+	}
+	rt->instance->private_data = rt;
+	strcpy(rt->instance->name, "DMX6FireUSB MIDI");
+	rt->instance->info_flags = SNDRV_RAWMIDI_INFO_OUTPUT |
+			SNDRV_RAWMIDI_INFO_INPUT |
+			SNDRV_RAWMIDI_INFO_DUPLEX;
+	snd_rawmidi_set_ops(rt->instance, SNDRV_RAWMIDI_STREAM_OUTPUT,
+			&out_ops);
+	snd_rawmidi_set_ops(rt->instance, SNDRV_RAWMIDI_STREAM_INPUT,
+			&in_ops);
+
+	chip->midi = rt;
+	return 0;
+}
+
+void usb6fire_midi_abort(struct sfire_chip *chip)
+{
+	struct midi_runtime *rt = chip->midi;
+
+	if (rt)
+		usb_poison_urb(&rt->out_urb);
+}
+
+void usb6fire_midi_destroy(struct sfire_chip *chip)
+{
+	struct midi_runtime *rt = chip->midi;
+
+	kfree(rt->out_buffer);
+	kfree(rt);
+	chip->midi = NULL;
+}
diff --git a/sound/usb/6fire/midi.h b/sound/usb/6fire/midi.h
new file mode 100644
index 0000000..84851b9
--- /dev/null
+++ b/sound/usb/6fire/midi.h
@@ -0,0 +1,41 @@
+/*
+ * Linux driver for TerraTec DMX 6Fire USB
+ *
+ * Author:	Torsten Schenk <torsten.schenk@zoho.com>
+ * Created:	Jan 01, 2011
+ * Copyright:	(C) Torsten Schenk
+ *
+ * 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.
+ */
+
+#ifndef USB6FIRE_MIDI_H
+#define USB6FIRE_MIDI_H
+
+#include "common.h"
+
+struct midi_runtime {
+	struct sfire_chip *chip;
+	struct snd_rawmidi *instance;
+
+	struct snd_rawmidi_substream *in;
+	char in_active;
+
+	spinlock_t in_lock;
+	spinlock_t out_lock;
+	struct snd_rawmidi_substream *out;
+	struct urb out_urb;
+	u8 out_serial; /* serial number of out packet */
+	u8 *out_buffer;
+	int buffer_offset;
+
+	void (*in_received)(struct midi_runtime *rt, u8 *data, int length);
+};
+
+int usb6fire_midi_init(struct sfire_chip *chip);
+void usb6fire_midi_abort(struct sfire_chip *chip);
+void usb6fire_midi_destroy(struct sfire_chip *chip);
+#endif /* USB6FIRE_MIDI_H */
+
diff --git a/sound/usb/6fire/pcm.c b/sound/usb/6fire/pcm.c
new file mode 100644
index 0000000..f8ef3e2
--- /dev/null
+++ b/sound/usb/6fire/pcm.c
@@ -0,0 +1,709 @@
+/*
+ * Linux driver for TerraTec DMX 6Fire USB
+ *
+ * PCM driver
+ *
+ * Author:	Torsten Schenk <torsten.schenk@zoho.com>
+ * Created:	Jan 01, 2011
+ * Copyright:	(C) Torsten Schenk
+ *
+ * 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.
+ */
+
+#include "pcm.h"
+#include "chip.h"
+#include "comm.h"
+#include "control.h"
+
+enum {
+	OUT_N_CHANNELS = 6, IN_N_CHANNELS = 4
+};
+
+/* keep next two synced with
+ * FW_EP_W_MAX_PACKET_SIZE[] and RATES_MAX_PACKET_SIZE
+ * and CONTROL_RATE_XXX in control.h */
+static const int rates_in_packet_size[] = { 228, 228, 420, 420, 404, 404 };
+static const int rates_out_packet_size[] = { 228, 228, 420, 420, 604, 604 };
+static const int rates[] = { 44100, 48000, 88200, 96000, 176400, 192000 };
+static const int rates_alsaid[] = {
+	SNDRV_PCM_RATE_44100, SNDRV_PCM_RATE_48000,
+	SNDRV_PCM_RATE_88200, SNDRV_PCM_RATE_96000,
+	SNDRV_PCM_RATE_176400, SNDRV_PCM_RATE_192000 };
+
+enum { /* settings for pcm */
+	OUT_EP = 6, IN_EP = 2, MAX_BUFSIZE = 128 * 1024
+};
+
+enum { /* pcm streaming states */
+	STREAM_DISABLED, /* no pcm streaming */
+	STREAM_STARTING, /* pcm streaming requested, waiting to become ready */
+	STREAM_RUNNING, /* pcm streaming running */
+	STREAM_STOPPING
+};
+
+static const struct snd_pcm_hardware pcm_hw = {
+	.info = SNDRV_PCM_INFO_MMAP |
+		SNDRV_PCM_INFO_INTERLEAVED |
+		SNDRV_PCM_INFO_BLOCK_TRANSFER |
+		SNDRV_PCM_INFO_MMAP_VALID |
+		SNDRV_PCM_INFO_BATCH,
+
+	.formats = SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE,
+
+	.rates = SNDRV_PCM_RATE_44100 |
+		SNDRV_PCM_RATE_48000 |
+		SNDRV_PCM_RATE_88200 |
+		SNDRV_PCM_RATE_96000 |
+		SNDRV_PCM_RATE_176400 |
+		SNDRV_PCM_RATE_192000,
+
+	.rate_min = 44100,
+	.rate_max = 192000,
+	.channels_min = 1,
+	.channels_max = 0, /* set in pcm_open, depending on capture/playback */
+	.buffer_bytes_max = MAX_BUFSIZE,
+	.period_bytes_min = PCM_N_PACKETS_PER_URB * (PCM_MAX_PACKET_SIZE - 4),
+	.period_bytes_max = MAX_BUFSIZE,
+	.periods_min = 2,
+	.periods_max = 1024
+};
+
+static int usb6fire_pcm_set_rate(struct pcm_runtime *rt)
+{
+	int ret;
+	struct control_runtime *ctrl_rt = rt->chip->control;
+
+	ctrl_rt->usb_streaming = false;
+	ret = ctrl_rt->update_streaming(ctrl_rt);
+	if (ret < 0) {
+		dev_err(&rt->chip->dev->dev,
+			"error stopping streaming while setting samplerate %d.\n",
+			rates[rt->rate]);
+		return ret;
+	}
+
+	ret = ctrl_rt->set_rate(ctrl_rt, rt->rate);
+	if (ret < 0) {
+		dev_err(&rt->chip->dev->dev,
+			"error setting samplerate %d.\n",
+			rates[rt->rate]);
+		return ret;
+	}
+
+	ret = ctrl_rt->set_channels(ctrl_rt, OUT_N_CHANNELS, IN_N_CHANNELS,
+			false, false);
+	if (ret < 0) {
+		dev_err(&rt->chip->dev->dev,
+			"error initializing channels while setting samplerate %d.\n",
+			rates[rt->rate]);
+		return ret;
+	}
+
+	ctrl_rt->usb_streaming = true;
+	ret = ctrl_rt->update_streaming(ctrl_rt);
+	if (ret < 0) {
+		dev_err(&rt->chip->dev->dev,
+			"error starting streaming while setting samplerate %d.\n",
+			rates[rt->rate]);
+		return ret;
+	}
+
+	rt->in_n_analog = IN_N_CHANNELS;
+	rt->out_n_analog = OUT_N_CHANNELS;
+	rt->in_packet_size = rates_in_packet_size[rt->rate];
+	rt->out_packet_size = rates_out_packet_size[rt->rate];
+	return 0;
+}
+
+static struct pcm_substream *usb6fire_pcm_get_substream(
+		struct snd_pcm_substream *alsa_sub)
+{
+	struct pcm_runtime *rt = snd_pcm_substream_chip(alsa_sub);
+
+	if (alsa_sub->stream == SNDRV_PCM_STREAM_PLAYBACK)
+		return &rt->playback;
+	else if (alsa_sub->stream == SNDRV_PCM_STREAM_CAPTURE)
+		return &rt->capture;
+	dev_err(&rt->chip->dev->dev, "error getting pcm substream slot.\n");
+	return NULL;
+}
+
+/* call with stream_mutex locked */
+static void usb6fire_pcm_stream_stop(struct pcm_runtime *rt)
+{
+	int i;
+	struct control_runtime *ctrl_rt = rt->chip->control;
+
+	if (rt->stream_state != STREAM_DISABLED) {
+
+		rt->stream_state = STREAM_STOPPING;
+
+		for (i = 0; i < PCM_N_URBS; i++) {
+			usb_kill_urb(&rt->in_urbs[i].instance);
+			usb_kill_urb(&rt->out_urbs[i].instance);
+		}
+		ctrl_rt->usb_streaming = false;
+		ctrl_rt->update_streaming(ctrl_rt);
+		rt->stream_state = STREAM_DISABLED;
+	}
+}
+
+/* call with stream_mutex locked */
+static int usb6fire_pcm_stream_start(struct pcm_runtime *rt)
+{
+	int ret;
+	int i;
+	int k;
+	struct usb_iso_packet_descriptor *packet;
+
+	if (rt->stream_state == STREAM_DISABLED) {
+		/* submit our in urbs */
+		rt->stream_wait_cond = false;
+		rt->stream_state = STREAM_STARTING;
+		for (i = 0; i < PCM_N_URBS; i++) {
+			for (k = 0; k < PCM_N_PACKETS_PER_URB; k++) {
+				packet = &rt->in_urbs[i].packets[k];
+				packet->offset = k * rt->in_packet_size;
+				packet->length = rt->in_packet_size;
+				packet->actual_length = 0;
+				packet->status = 0;
+			}
+			ret = usb_submit_urb(&rt->in_urbs[i].instance,
+					GFP_ATOMIC);
+			if (ret) {
+				usb6fire_pcm_stream_stop(rt);
+				return ret;
+			}
+		}
+
+		/* wait for first out urb to return (sent in in urb handler) */
+		wait_event_timeout(rt->stream_wait_queue, rt->stream_wait_cond,
+				HZ);
+		if (rt->stream_wait_cond)
+			rt->stream_state = STREAM_RUNNING;
+		else {
+			usb6fire_pcm_stream_stop(rt);
+			return -EIO;
+		}
+	}
+	return 0;
+}
+
+/* call with substream locked */
+static void usb6fire_pcm_capture(struct pcm_substream *sub, struct pcm_urb *urb)
+{
+	int i;
+	int frame;
+	int frame_count;
+	unsigned int total_length = 0;
+	struct pcm_runtime *rt = snd_pcm_substream_chip(sub->instance);
+	struct snd_pcm_runtime *alsa_rt = sub->instance->runtime;
+	u32 *src = NULL;
+	u32 *dest = (u32 *) (alsa_rt->dma_area + sub->dma_off
+			* (alsa_rt->frame_bits >> 3));
+	u32 *dest_end = (u32 *) (alsa_rt->dma_area + alsa_rt->buffer_size
+			* (alsa_rt->frame_bits >> 3));
+	int bytes_per_frame = alsa_rt->channels << 2;
+
+	for (i = 0; i < PCM_N_PACKETS_PER_URB; i++) {
+		/* at least 4 header bytes for valid packet.
+		 * after that: 32 bits per sample for analog channels */
+		if (urb->packets[i].actual_length > 4)
+			frame_count = (urb->packets[i].actual_length - 4)
+					/ (rt->in_n_analog << 2);
+		else
+			frame_count = 0;
+
+		if (alsa_rt->format == SNDRV_PCM_FORMAT_S24_LE)
+			src = (u32 *) (urb->buffer + total_length);
+		else if (alsa_rt->format == SNDRV_PCM_FORMAT_S32_LE)
+			src = (u32 *) (urb->buffer - 1 + total_length);
+		else
+			return;
+		src++; /* skip leading 4 bytes of every packet */
+		total_length += urb->packets[i].length;
+		for (frame = 0; frame < frame_count; frame++) {
+			memcpy(dest, src, bytes_per_frame);
+			dest += alsa_rt->channels;
+			src += rt->in_n_analog;
+			sub->dma_off++;
+			sub->period_off++;
+			if (dest == dest_end) {
+				sub->dma_off = 0;
+				dest = (u32 *) alsa_rt->dma_area;
+			}
+		}
+	}
+}
+
+/* call with substream locked */
+static void usb6fire_pcm_playback(struct pcm_substream *sub,
+		struct pcm_urb *urb)
+{
+	int i;
+	int frame;
+	int frame_count;
+	struct pcm_runtime *rt = snd_pcm_substream_chip(sub->instance);
+	struct snd_pcm_runtime *alsa_rt = sub->instance->runtime;
+	u32 *src = (u32 *) (alsa_rt->dma_area + sub->dma_off
+			* (alsa_rt->frame_bits >> 3));
+	u32 *src_end = (u32 *) (alsa_rt->dma_area + alsa_rt->buffer_size
+			* (alsa_rt->frame_bits >> 3));
+	u32 *dest;
+	int bytes_per_frame = alsa_rt->channels << 2;
+
+	if (alsa_rt->format == SNDRV_PCM_FORMAT_S32_LE)
+		dest = (u32 *) (urb->buffer - 1);
+	else if (alsa_rt->format == SNDRV_PCM_FORMAT_S24_LE)
+		dest = (u32 *) (urb->buffer);
+	else {
+		dev_err(&rt->chip->dev->dev, "Unknown sample format.");
+		return;
+	}
+
+	for (i = 0; i < PCM_N_PACKETS_PER_URB; i++) {
+		/* at least 4 header bytes for valid packet.
+		 * after that: 32 bits per sample for analog channels */
+		if (urb->packets[i].length > 4)
+			frame_count = (urb->packets[i].length - 4)
+					/ (rt->out_n_analog << 2);
+		else
+			frame_count = 0;
+		dest++; /* skip leading 4 bytes of every frame */
+		for (frame = 0; frame < frame_count; frame++) {
+			memcpy(dest, src, bytes_per_frame);
+			src += alsa_rt->channels;
+			dest += rt->out_n_analog;
+			sub->dma_off++;
+			sub->period_off++;
+			if (src == src_end) {
+				src = (u32 *) alsa_rt->dma_area;
+				sub->dma_off = 0;
+			}
+		}
+	}
+}
+
+static void usb6fire_pcm_in_urb_handler(struct urb *usb_urb)
+{
+	struct pcm_urb *in_urb = usb_urb->context;
+	struct pcm_urb *out_urb = in_urb->peer;
+	struct pcm_runtime *rt = in_urb->chip->pcm;
+	struct pcm_substream *sub;
+	unsigned long flags;
+	int total_length = 0;
+	int frame_count;
+	int frame;
+	int channel;
+	int i;
+	u8 *dest;
+
+	if (usb_urb->status || rt->panic || rt->stream_state == STREAM_STOPPING)
+		return;
+	for (i = 0; i < PCM_N_PACKETS_PER_URB; i++)
+		if (in_urb->packets[i].status) {
+			rt->panic = true;
+			return;
+		}
+
+	if (rt->stream_state == STREAM_DISABLED) {
+		dev_err(&rt->chip->dev->dev,
+			"internal error: stream disabled in in-urb handler.\n");
+		return;
+	}
+
+	/* receive our capture data */
+	sub = &rt->capture;
+	spin_lock_irqsave(&sub->lock, flags);
+	if (sub->active) {
+		usb6fire_pcm_capture(sub, in_urb);
+		if (sub->period_off >= sub->instance->runtime->period_size) {
+			sub->period_off %= sub->instance->runtime->period_size;
+			spin_unlock_irqrestore(&sub->lock, flags);
+			snd_pcm_period_elapsed(sub->instance);
+		} else
+			spin_unlock_irqrestore(&sub->lock, flags);
+	} else
+		spin_unlock_irqrestore(&sub->lock, flags);
+
+	/* setup out urb structure */
+	for (i = 0; i < PCM_N_PACKETS_PER_URB; i++) {
+		out_urb->packets[i].offset = total_length;
+		out_urb->packets[i].length = (in_urb->packets[i].actual_length
+				- 4) / (rt->in_n_analog << 2)
+				* (rt->out_n_analog << 2) + 4;
+		out_urb->packets[i].status = 0;
+		total_length += out_urb->packets[i].length;
+	}
+	memset(out_urb->buffer, 0, total_length);
+
+	/* now send our playback data (if a free out urb was found) */
+	sub = &rt->playback;
+	spin_lock_irqsave(&sub->lock, flags);
+	if (sub->active) {
+		usb6fire_pcm_playback(sub, out_urb);
+		if (sub->period_off >= sub->instance->runtime->period_size) {
+			sub->period_off %= sub->instance->runtime->period_size;
+			spin_unlock_irqrestore(&sub->lock, flags);
+			snd_pcm_period_elapsed(sub->instance);
+		} else
+			spin_unlock_irqrestore(&sub->lock, flags);
+	} else
+		spin_unlock_irqrestore(&sub->lock, flags);
+
+	/* setup the 4th byte of each sample (0x40 for analog channels) */
+	dest = out_urb->buffer;
+	for (i = 0; i < PCM_N_PACKETS_PER_URB; i++)
+		if (out_urb->packets[i].length >= 4) {
+			frame_count = (out_urb->packets[i].length - 4)
+					/ (rt->out_n_analog << 2);
+			*(dest++) = 0xaa;
+			*(dest++) = 0xaa;
+			*(dest++) = frame_count;
+			*(dest++) = 0x00;
+			for (frame = 0; frame < frame_count; frame++)
+				for (channel = 0;
+						channel < rt->out_n_analog;
+						channel++) {
+					dest += 3; /* skip sample data */
+					*(dest++) = 0x40;
+				}
+		}
+	usb_submit_urb(&out_urb->instance, GFP_ATOMIC);
+	usb_submit_urb(&in_urb->instance, GFP_ATOMIC);
+}
+
+static void usb6fire_pcm_out_urb_handler(struct urb *usb_urb)
+{
+	struct pcm_urb *urb = usb_urb->context;
+	struct pcm_runtime *rt = urb->chip->pcm;
+
+	if (rt->stream_state == STREAM_STARTING) {
+		rt->stream_wait_cond = true;
+		wake_up(&rt->stream_wait_queue);
+	}
+}
+
+static int usb6fire_pcm_open(struct snd_pcm_substream *alsa_sub)
+{
+	struct pcm_runtime *rt = snd_pcm_substream_chip(alsa_sub);
+	struct pcm_substream *sub = NULL;
+	struct snd_pcm_runtime *alsa_rt = alsa_sub->runtime;
+
+	if (rt->panic)
+		return -EPIPE;
+
+	mutex_lock(&rt->stream_mutex);
+	alsa_rt->hw = pcm_hw;
+
+	if (alsa_sub->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+		if (rt->rate < ARRAY_SIZE(rates))
+			alsa_rt->hw.rates = rates_alsaid[rt->rate];
+		alsa_rt->hw.channels_max = OUT_N_CHANNELS;
+		sub = &rt->playback;
+	} else if (alsa_sub->stream == SNDRV_PCM_STREAM_CAPTURE) {
+		if (rt->rate < ARRAY_SIZE(rates))
+			alsa_rt->hw.rates = rates_alsaid[rt->rate];
+		alsa_rt->hw.channels_max = IN_N_CHANNELS;
+		sub = &rt->capture;
+	}
+
+	if (!sub) {
+		mutex_unlock(&rt->stream_mutex);
+		dev_err(&rt->chip->dev->dev, "invalid stream type.\n");
+		return -EINVAL;
+	}
+
+	sub->instance = alsa_sub;
+	sub->active = false;
+	mutex_unlock(&rt->stream_mutex);
+	return 0;
+}
+
+static int usb6fire_pcm_close(struct snd_pcm_substream *alsa_sub)
+{
+	struct pcm_runtime *rt = snd_pcm_substream_chip(alsa_sub);
+	struct pcm_substream *sub = usb6fire_pcm_get_substream(alsa_sub);
+	unsigned long flags;
+
+	if (rt->panic)
+		return 0;
+
+	mutex_lock(&rt->stream_mutex);
+	if (sub) {
+		/* deactivate substream */
+		spin_lock_irqsave(&sub->lock, flags);
+		sub->instance = NULL;
+		sub->active = false;
+		spin_unlock_irqrestore(&sub->lock, flags);
+
+		/* all substreams closed? if so, stop streaming */
+		if (!rt->playback.instance && !rt->capture.instance) {
+			usb6fire_pcm_stream_stop(rt);
+			rt->rate = ARRAY_SIZE(rates);
+		}
+	}
+	mutex_unlock(&rt->stream_mutex);
+	return 0;
+}
+
+static int usb6fire_pcm_hw_params(struct snd_pcm_substream *alsa_sub,
+		struct snd_pcm_hw_params *hw_params)
+{
+	return snd_pcm_lib_alloc_vmalloc_buffer(alsa_sub,
+						params_buffer_bytes(hw_params));
+}
+
+static int usb6fire_pcm_hw_free(struct snd_pcm_substream *alsa_sub)
+{
+	return snd_pcm_lib_free_vmalloc_buffer(alsa_sub);
+}
+
+static int usb6fire_pcm_prepare(struct snd_pcm_substream *alsa_sub)
+{
+	struct pcm_runtime *rt = snd_pcm_substream_chip(alsa_sub);
+	struct pcm_substream *sub = usb6fire_pcm_get_substream(alsa_sub);
+	struct snd_pcm_runtime *alsa_rt = alsa_sub->runtime;
+	int ret;
+
+	if (rt->panic)
+		return -EPIPE;
+	if (!sub)
+		return -ENODEV;
+
+	mutex_lock(&rt->stream_mutex);
+	sub->dma_off = 0;
+	sub->period_off = 0;
+
+	if (rt->stream_state == STREAM_DISABLED) {
+		for (rt->rate = 0; rt->rate < ARRAY_SIZE(rates); rt->rate++)
+			if (alsa_rt->rate == rates[rt->rate])
+				break;
+		if (rt->rate == ARRAY_SIZE(rates)) {
+			mutex_unlock(&rt->stream_mutex);
+			dev_err(&rt->chip->dev->dev,
+				"invalid rate %d in prepare.\n",
+				alsa_rt->rate);
+			return -EINVAL;
+		}
+
+		ret = usb6fire_pcm_set_rate(rt);
+		if (ret) {
+			mutex_unlock(&rt->stream_mutex);
+			return ret;
+		}
+		ret = usb6fire_pcm_stream_start(rt);
+		if (ret) {
+			mutex_unlock(&rt->stream_mutex);
+			dev_err(&rt->chip->dev->dev,
+				"could not start pcm stream.\n");
+			return ret;
+		}
+	}
+	mutex_unlock(&rt->stream_mutex);
+	return 0;
+}
+
+static int usb6fire_pcm_trigger(struct snd_pcm_substream *alsa_sub, int cmd)
+{
+	struct pcm_substream *sub = usb6fire_pcm_get_substream(alsa_sub);
+	struct pcm_runtime *rt = snd_pcm_substream_chip(alsa_sub);
+	unsigned long flags;
+
+	if (rt->panic)
+		return -EPIPE;
+	if (!sub)
+		return -ENODEV;
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+		spin_lock_irqsave(&sub->lock, flags);
+		sub->active = true;
+		spin_unlock_irqrestore(&sub->lock, flags);
+		return 0;
+
+	case SNDRV_PCM_TRIGGER_STOP:
+	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+		spin_lock_irqsave(&sub->lock, flags);
+		sub->active = false;
+		spin_unlock_irqrestore(&sub->lock, flags);
+		return 0;
+
+	default:
+		return -EINVAL;
+	}
+}
+
+static snd_pcm_uframes_t usb6fire_pcm_pointer(
+		struct snd_pcm_substream *alsa_sub)
+{
+	struct pcm_substream *sub = usb6fire_pcm_get_substream(alsa_sub);
+	struct pcm_runtime *rt = snd_pcm_substream_chip(alsa_sub);
+	unsigned long flags;
+	snd_pcm_uframes_t ret;
+
+	if (rt->panic || !sub)
+		return SNDRV_PCM_POS_XRUN;
+
+	spin_lock_irqsave(&sub->lock, flags);
+	ret = sub->dma_off;
+	spin_unlock_irqrestore(&sub->lock, flags);
+	return ret;
+}
+
+static const struct snd_pcm_ops pcm_ops = {
+	.open = usb6fire_pcm_open,
+	.close = usb6fire_pcm_close,
+	.ioctl = snd_pcm_lib_ioctl,
+	.hw_params = usb6fire_pcm_hw_params,
+	.hw_free = usb6fire_pcm_hw_free,
+	.prepare = usb6fire_pcm_prepare,
+	.trigger = usb6fire_pcm_trigger,
+	.pointer = usb6fire_pcm_pointer,
+	.page = snd_pcm_lib_get_vmalloc_page,
+};
+
+static void usb6fire_pcm_init_urb(struct pcm_urb *urb,
+				  struct sfire_chip *chip, bool in, int ep,
+				  void (*handler)(struct urb *))
+{
+	urb->chip = chip;
+	usb_init_urb(&urb->instance);
+	urb->instance.transfer_buffer = urb->buffer;
+	urb->instance.transfer_buffer_length =
+			PCM_N_PACKETS_PER_URB * PCM_MAX_PACKET_SIZE;
+	urb->instance.dev = chip->dev;
+	urb->instance.pipe = in ? usb_rcvisocpipe(chip->dev, ep)
+			: usb_sndisocpipe(chip->dev, ep);
+	urb->instance.interval = 1;
+	urb->instance.complete = handler;
+	urb->instance.context = urb;
+	urb->instance.number_of_packets = PCM_N_PACKETS_PER_URB;
+}
+
+static int usb6fire_pcm_buffers_init(struct pcm_runtime *rt)
+{
+	int i;
+
+	for (i = 0; i < PCM_N_URBS; i++) {
+		rt->out_urbs[i].buffer = kcalloc(PCM_MAX_PACKET_SIZE,
+						 PCM_N_PACKETS_PER_URB,
+						 GFP_KERNEL);
+		if (!rt->out_urbs[i].buffer)
+			return -ENOMEM;
+		rt->in_urbs[i].buffer = kcalloc(PCM_MAX_PACKET_SIZE,
+						PCM_N_PACKETS_PER_URB,
+						GFP_KERNEL);
+		if (!rt->in_urbs[i].buffer)
+			return -ENOMEM;
+	}
+	return 0;
+}
+
+static void usb6fire_pcm_buffers_destroy(struct pcm_runtime *rt)
+{
+	int i;
+
+	for (i = 0; i < PCM_N_URBS; i++) {
+		kfree(rt->out_urbs[i].buffer);
+		kfree(rt->in_urbs[i].buffer);
+	}
+}
+
+int usb6fire_pcm_init(struct sfire_chip *chip)
+{
+	int i;
+	int ret;
+	struct snd_pcm *pcm;
+	struct pcm_runtime *rt =
+			kzalloc(sizeof(struct pcm_runtime), GFP_KERNEL);
+
+	if (!rt)
+		return -ENOMEM;
+
+	ret = usb6fire_pcm_buffers_init(rt);
+	if (ret) {
+		usb6fire_pcm_buffers_destroy(rt);
+		kfree(rt);
+		return ret;
+	}
+
+	rt->chip = chip;
+	rt->stream_state = STREAM_DISABLED;
+	rt->rate = ARRAY_SIZE(rates);
+	init_waitqueue_head(&rt->stream_wait_queue);
+	mutex_init(&rt->stream_mutex);
+
+	spin_lock_init(&rt->playback.lock);
+	spin_lock_init(&rt->capture.lock);
+
+	for (i = 0; i < PCM_N_URBS; i++) {
+		usb6fire_pcm_init_urb(&rt->in_urbs[i], chip, true, IN_EP,
+				usb6fire_pcm_in_urb_handler);
+		usb6fire_pcm_init_urb(&rt->out_urbs[i], chip, false, OUT_EP,
+				usb6fire_pcm_out_urb_handler);
+
+		rt->in_urbs[i].peer = &rt->out_urbs[i];
+		rt->out_urbs[i].peer = &rt->in_urbs[i];
+	}
+
+	ret = snd_pcm_new(chip->card, "DMX6FireUSB", 0, 1, 1, &pcm);
+	if (ret < 0) {
+		usb6fire_pcm_buffers_destroy(rt);
+		kfree(rt);
+		dev_err(&chip->dev->dev, "cannot create pcm instance.\n");
+		return ret;
+	}
+
+	pcm->private_data = rt;
+	strcpy(pcm->name, "DMX 6Fire USB");
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &pcm_ops);
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &pcm_ops);
+
+	if (ret) {
+		usb6fire_pcm_buffers_destroy(rt);
+		kfree(rt);
+		dev_err(&chip->dev->dev,
+			"error preallocating pcm buffers.\n");
+		return ret;
+	}
+	rt->instance = pcm;
+
+	chip->pcm = rt;
+	return 0;
+}
+
+void usb6fire_pcm_abort(struct sfire_chip *chip)
+{
+	struct pcm_runtime *rt = chip->pcm;
+	int i;
+
+	if (rt) {
+		rt->panic = true;
+
+		if (rt->playback.instance)
+			snd_pcm_stop_xrun(rt->playback.instance);
+
+		if (rt->capture.instance)
+			snd_pcm_stop_xrun(rt->capture.instance);
+
+		for (i = 0; i < PCM_N_URBS; i++) {
+			usb_poison_urb(&rt->in_urbs[i].instance);
+			usb_poison_urb(&rt->out_urbs[i].instance);
+		}
+
+	}
+}
+
+void usb6fire_pcm_destroy(struct sfire_chip *chip)
+{
+	struct pcm_runtime *rt = chip->pcm;
+
+	usb6fire_pcm_buffers_destroy(rt);
+	kfree(rt);
+	chip->pcm = NULL;
+}
diff --git a/sound/usb/6fire/pcm.h b/sound/usb/6fire/pcm.h
new file mode 100644
index 0000000..f5779d6
--- /dev/null
+++ b/sound/usb/6fire/pcm.h
@@ -0,0 +1,75 @@
+/*
+ * Linux driver for TerraTec DMX 6Fire USB
+ *
+ * Author:	Torsten Schenk <torsten.schenk@zoho.com>
+ * Created:	Jan 01, 2011
+ * Copyright:	(C) Torsten Schenk
+ *
+ * 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.
+ */
+
+#ifndef USB6FIRE_PCM_H
+#define USB6FIRE_PCM_H
+
+#include <sound/pcm.h>
+#include <linux/mutex.h>
+
+#include "common.h"
+
+enum /* settings for pcm */
+{
+	/* maximum of EP_W_MAX_PACKET_SIZE[] (see firmware.c) */
+	PCM_N_URBS = 16, PCM_N_PACKETS_PER_URB = 8, PCM_MAX_PACKET_SIZE = 604
+};
+
+struct pcm_urb {
+	struct sfire_chip *chip;
+
+	/* BEGIN DO NOT SEPARATE */
+	struct urb instance;
+	struct usb_iso_packet_descriptor packets[PCM_N_PACKETS_PER_URB];
+	/* END DO NOT SEPARATE */
+	u8 *buffer;
+
+	struct pcm_urb *peer;
+};
+
+struct pcm_substream {
+	spinlock_t lock;
+	struct snd_pcm_substream *instance;
+
+	bool active;
+
+	snd_pcm_uframes_t dma_off; /* current position in alsa dma_area */
+	snd_pcm_uframes_t period_off; /* current position in current period */
+};
+
+struct pcm_runtime {
+	struct sfire_chip *chip;
+	struct snd_pcm *instance;
+
+	struct pcm_substream playback;
+	struct pcm_substream capture;
+	bool panic; /* if set driver won't do anymore pcm on device */
+
+	struct pcm_urb in_urbs[PCM_N_URBS];
+	struct pcm_urb out_urbs[PCM_N_URBS];
+	int in_packet_size;
+	int out_packet_size;
+	int in_n_analog; /* number of analog channels soundcard sends */
+	int out_n_analog; /* number of analog channels soundcard receives */
+
+	struct mutex stream_mutex;
+	u8 stream_state; /* one of STREAM_XXX (pcm.c) */
+	u8 rate; /* one of PCM_RATE_XXX */
+	wait_queue_head_t stream_wait_queue;
+	bool stream_wait_cond;
+};
+
+int usb6fire_pcm_init(struct sfire_chip *chip);
+void usb6fire_pcm_abort(struct sfire_chip *chip);
+void usb6fire_pcm_destroy(struct sfire_chip *chip);
+#endif /* USB6FIRE_PCM_H */
diff --git a/sound/usb/Kconfig b/sound/usb/Kconfig
new file mode 100644
index 0000000..f61b566
--- /dev/null
+++ b/sound/usb/Kconfig
@@ -0,0 +1,166 @@
+# ALSA USB drivers
+
+menuconfig SND_USB
+	bool "USB sound devices"
+	depends on USB
+	default y
+	help
+	  Support for sound devices connected via the USB bus.
+
+if SND_USB && USB
+
+config SND_USB_AUDIO
+	tristate "USB Audio/MIDI driver"
+	select SND_HWDEP
+	select SND_RAWMIDI
+	select SND_PCM
+	select BITREVERSE
+	help
+	  Say Y here to include support for USB audio and USB MIDI
+	  devices.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-usb-audio.
+
+config SND_USB_UA101
+	tristate "Edirol UA-101/UA-1000 driver"
+	select SND_PCM
+	select SND_RAWMIDI
+	help
+	  Say Y here to include support for the Edirol UA-101 and UA-1000
+	  audio/MIDI interfaces.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-ua101.
+
+config SND_USB_USX2Y
+	tristate "Tascam US-122, US-224 and US-428 USB driver"
+	depends on X86 || PPC || ALPHA
+	select SND_HWDEP
+	select SND_RAWMIDI
+	select SND_PCM
+	help
+	  Say Y here to include support for Tascam USB Audio/MIDI
+	  interfaces or controllers US-122, US-224 and US-428.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-usb-usx2y.
+
+config SND_USB_CAIAQ
+	tristate "Native Instruments USB audio devices"
+	select SND_HWDEP
+	select SND_RAWMIDI
+	select SND_PCM
+	help
+	   Say Y here to include support for caiaq USB audio interfaces,
+	   namely:
+
+	    * Native Instruments RigKontrol2
+	    * Native Instruments RigKontrol3
+	    * Native Instruments Kore Controller
+	    * Native Instruments Kore Controller 2
+	    * Native Instruments Audio Kontrol 1
+	    * Native Instruments Audio 2 DJ
+	    * Native Instruments Audio 4 DJ
+	    * Native Instruments Audio 8 DJ
+	    * Native Instruments Traktor Audio 2
+	    * Native Instruments Guitar Rig Session I/O
+	    * Native Instruments Guitar Rig mobile
+	    * Native Instruments Traktor Kontrol X1
+	    * Native Instruments Traktor Kontrol S4
+	    * Native Instruments Maschine Controller
+
+	   To compile this driver as a module, choose M here: the module
+	   will be called snd-usb-caiaq.
+
+config SND_USB_CAIAQ_INPUT
+	bool "enable input device for controllers"
+	depends on SND_USB_CAIAQ
+	depends on INPUT=y || INPUT=SND_USB_CAIAQ
+	help
+	  Say Y here to support input controllers like buttons, knobs,
+	  alpha dials and analog pedals on the following products:
+
+	   * Native Instruments RigKontrol2
+	   * Native Instruments RigKontrol3
+	   * Native Instruments Kore Controller
+	   * Native Instruments Kore Controller 2
+	   * Native Instruments Audio Kontrol 1
+	   * Native Instruments Traktor Kontrol S4
+	   * Native Instruments Maschine Controller
+
+config SND_USB_US122L
+	tristate "Tascam US-122L USB driver"
+	depends on X86 || COMPILE_TEST
+	select SND_HWDEP
+	select SND_RAWMIDI
+	help
+	  Say Y here to include support for Tascam US-122L USB Audio/MIDI
+	  interfaces.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-usb-us122l.
+
+config SND_USB_6FIRE
+        tristate "TerraTec DMX 6Fire USB"
+        select FW_LOADER
+        select BITREVERSE
+        select SND_RAWMIDI
+        select SND_PCM
+        select SND_VMASTER
+        help
+          Say Y here to include support for TerraTec 6fire DMX USB interface.
+
+          You will need firmware files in order to be able to use the device
+          after it has been coldstarted. An install script for the firmware
+          and further help can be found at
+          http://sixfireusb.sourceforge.net
+
+config SND_USB_HIFACE
+        tristate "M2Tech hiFace USB-SPDIF driver"
+        select SND_PCM
+        help
+	  Select this option to include support for M2Tech hiFace USB-SPDIF
+	  interface.
+
+	  This driver supports the original M2Tech hiFace and some other
+	  compatible devices. The supported products are:
+
+	    * M2Tech Young
+	    * M2Tech hiFace
+	    * M2Tech North Star
+	    * M2Tech W4S Young
+	    * M2Tech Corrson
+	    * M2Tech AUDIA
+	    * M2Tech SL Audio
+	    * M2Tech Empirical
+	    * M2Tech Rockna
+	    * M2Tech Pathos
+	    * M2Tech Metronome
+	    * M2Tech CAD
+	    * M2Tech Audio Esclusive
+	    * M2Tech Rotel
+	    * M2Tech Eeaudio
+	    * The Chord Company CHORD
+	    * AVA Group A/S Vitus
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-usb-hiface.
+
+config SND_BCD2000
+	tristate "Behringer BCD2000 MIDI driver"
+	select SND_RAWMIDI
+	help
+	  Say Y here to include MIDI support for the Behringer BCD2000 DJ
+	  controller.
+
+	  Audio support is still work-in-progress at
+	  https://github.com/anyc/snd-usb-bcd2000
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-bcd2000.
+
+source "sound/usb/line6/Kconfig"
+
+endif	# SND_USB
+
diff --git a/sound/usb/Makefile b/sound/usb/Makefile
new file mode 100644
index 0000000..d330f74
--- /dev/null
+++ b/sound/usb/Makefile
@@ -0,0 +1,31 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# Makefile for ALSA
+#
+
+snd-usb-audio-objs := 	card.o \
+			clock.o \
+			endpoint.o \
+			format.o \
+			helper.o \
+			mixer.o \
+			mixer_quirks.o \
+			mixer_scarlett.o \
+			mixer_us16x08.o \
+			pcm.o \
+			power.o \
+			proc.o \
+			quirks.o \
+			stream.o
+
+snd-usbmidi-lib-objs := midi.o
+
+# Toplevel Module Dependency
+obj-$(CONFIG_SND_USB_AUDIO) += snd-usb-audio.o snd-usbmidi-lib.o
+
+obj-$(CONFIG_SND_USB_UA101) += snd-usbmidi-lib.o
+obj-$(CONFIG_SND_USB_USX2Y) += snd-usbmidi-lib.o
+obj-$(CONFIG_SND_USB_US122L) += snd-usbmidi-lib.o
+
+obj-$(CONFIG_SND) += misc/ usx2y/ caiaq/ 6fire/ hiface/ bcd2000/
+obj-$(CONFIG_SND_USB_LINE6)	+= line6/
diff --git a/sound/usb/bcd2000/Makefile b/sound/usb/bcd2000/Makefile
new file mode 100644
index 0000000..f09ccc0
--- /dev/null
+++ b/sound/usb/bcd2000/Makefile
@@ -0,0 +1,3 @@
+snd-bcd2000-y := bcd2000.o
+
+obj-$(CONFIG_SND_BCD2000) += snd-bcd2000.o
\ No newline at end of file
diff --git a/sound/usb/bcd2000/bcd2000.c b/sound/usb/bcd2000/bcd2000.c
new file mode 100644
index 0000000..d6c8b29
--- /dev/null
+++ b/sound/usb/bcd2000/bcd2000.c
@@ -0,0 +1,468 @@
+/*
+ * Behringer BCD2000 driver
+ *
+ *   Copyright (C) 2014 Mario Kicherer (dev@kicherer.org)
+ *
+ *   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.
+ */
+
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/bitmap.h>
+#include <linux/usb.h>
+#include <linux/usb/audio.h>
+#include <sound/core.h>
+#include <sound/initval.h>
+#include <sound/rawmidi.h>
+
+#define PREFIX "snd-bcd2000: "
+#define BUFSIZE 64
+
+static const struct usb_device_id id_table[] = {
+	{ USB_DEVICE(0x1397, 0x00bd) },
+	{ },
+};
+
+static unsigned char device_cmd_prefix[] = {0x03, 0x00};
+
+static unsigned char bcd2000_init_sequence[] = {
+	0x07, 0x00, 0x00, 0x00, 0x78, 0x48, 0x1c, 0x81,
+	0xc4, 0x00, 0x00, 0x00, 0x5e, 0x53, 0x4a, 0xf7,
+	0x18, 0xfa, 0x11, 0xff, 0x6c, 0xf3, 0x90, 0xff,
+	0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+	0x18, 0xfa, 0x11, 0xff, 0x14, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0xf2, 0x34, 0x4a, 0xf7,
+	0x18, 0xfa, 0x11, 0xff
+};
+
+struct bcd2000 {
+	struct usb_device *dev;
+	struct snd_card *card;
+	struct usb_interface *intf;
+	int card_index;
+
+	int midi_out_active;
+	struct snd_rawmidi *rmidi;
+	struct snd_rawmidi_substream *midi_receive_substream;
+	struct snd_rawmidi_substream *midi_out_substream;
+
+	unsigned char midi_in_buf[BUFSIZE];
+	unsigned char midi_out_buf[BUFSIZE];
+
+	struct urb *midi_out_urb;
+	struct urb *midi_in_urb;
+
+	struct usb_anchor anchor;
+};
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;
+static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;
+
+static DEFINE_MUTEX(devices_mutex);
+static DECLARE_BITMAP(devices_used, SNDRV_CARDS);
+static struct usb_driver bcd2000_driver;
+
+#ifdef CONFIG_SND_DEBUG
+static void bcd2000_dump_buffer(const char *prefix, const char *buf, int len)
+{
+	print_hex_dump(KERN_DEBUG, prefix,
+			DUMP_PREFIX_NONE, 16, 1,
+			buf, len, false);
+}
+#else
+static void bcd2000_dump_buffer(const char *prefix, const char *buf, int len) {}
+#endif
+
+static int bcd2000_midi_input_open(struct snd_rawmidi_substream *substream)
+{
+	return 0;
+}
+
+static int bcd2000_midi_input_close(struct snd_rawmidi_substream *substream)
+{
+	return 0;
+}
+
+/* (de)register midi substream from client */
+static void bcd2000_midi_input_trigger(struct snd_rawmidi_substream *substream,
+						int up)
+{
+	struct bcd2000 *bcd2k = substream->rmidi->private_data;
+	bcd2k->midi_receive_substream = up ? substream : NULL;
+}
+
+static void bcd2000_midi_handle_input(struct bcd2000 *bcd2k,
+				const unsigned char *buf, unsigned int buf_len)
+{
+	unsigned int payload_length, tocopy;
+	struct snd_rawmidi_substream *midi_receive_substream;
+
+	midi_receive_substream = READ_ONCE(bcd2k->midi_receive_substream);
+	if (!midi_receive_substream)
+		return;
+
+	bcd2000_dump_buffer(PREFIX "received from device: ", buf, buf_len);
+
+	if (buf_len < 2)
+		return;
+
+	payload_length = buf[0];
+
+	/* ignore packets without payload */
+	if (payload_length == 0)
+		return;
+
+	tocopy = min(payload_length, buf_len-1);
+
+	bcd2000_dump_buffer(PREFIX "sending to userspace: ",
+					&buf[1], tocopy);
+
+	snd_rawmidi_receive(midi_receive_substream,
+					&buf[1], tocopy);
+}
+
+static void bcd2000_midi_send(struct bcd2000 *bcd2k)
+{
+	int len, ret;
+	struct snd_rawmidi_substream *midi_out_substream;
+
+	BUILD_BUG_ON(sizeof(device_cmd_prefix) >= BUFSIZE);
+
+	midi_out_substream = READ_ONCE(bcd2k->midi_out_substream);
+	if (!midi_out_substream)
+		return;
+
+	/* copy command prefix bytes */
+	memcpy(bcd2k->midi_out_buf, device_cmd_prefix,
+		sizeof(device_cmd_prefix));
+
+	/*
+	 * get MIDI packet and leave space for command prefix
+	 * and payload length
+	 */
+	len = snd_rawmidi_transmit(midi_out_substream,
+				bcd2k->midi_out_buf + 3, BUFSIZE - 3);
+
+	if (len < 0)
+		dev_err(&bcd2k->dev->dev, "%s: snd_rawmidi_transmit error %d\n",
+				__func__, len);
+
+	if (len <= 0)
+		return;
+
+	/* set payload length */
+	bcd2k->midi_out_buf[2] = len;
+	bcd2k->midi_out_urb->transfer_buffer_length = BUFSIZE;
+
+	bcd2000_dump_buffer(PREFIX "sending to device: ",
+			bcd2k->midi_out_buf, len+3);
+
+	/* send packet to the BCD2000 */
+	ret = usb_submit_urb(bcd2k->midi_out_urb, GFP_ATOMIC);
+	if (ret < 0)
+		dev_err(&bcd2k->dev->dev, PREFIX
+			"%s (%p): usb_submit_urb() failed, ret=%d, len=%d\n",
+			__func__, midi_out_substream, ret, len);
+	else
+		bcd2k->midi_out_active = 1;
+}
+
+static int bcd2000_midi_output_open(struct snd_rawmidi_substream *substream)
+{
+	return 0;
+}
+
+static int bcd2000_midi_output_close(struct snd_rawmidi_substream *substream)
+{
+	struct bcd2000 *bcd2k = substream->rmidi->private_data;
+
+	if (bcd2k->midi_out_active) {
+		usb_kill_urb(bcd2k->midi_out_urb);
+		bcd2k->midi_out_active = 0;
+	}
+
+	return 0;
+}
+
+/* (de)register midi substream from client */
+static void bcd2000_midi_output_trigger(struct snd_rawmidi_substream *substream,
+						int up)
+{
+	struct bcd2000 *bcd2k = substream->rmidi->private_data;
+
+	if (up) {
+		bcd2k->midi_out_substream = substream;
+		/* check if there is data userspace wants to send */
+		if (!bcd2k->midi_out_active)
+			bcd2000_midi_send(bcd2k);
+	} else {
+		bcd2k->midi_out_substream = NULL;
+	}
+}
+
+static void bcd2000_output_complete(struct urb *urb)
+{
+	struct bcd2000 *bcd2k = urb->context;
+
+	bcd2k->midi_out_active = 0;
+
+	if (urb->status)
+		dev_warn(&urb->dev->dev,
+			PREFIX "output urb->status: %d\n", urb->status);
+
+	if (urb->status == -ESHUTDOWN)
+		return;
+
+	/* check if there is more data userspace wants to send */
+	bcd2000_midi_send(bcd2k);
+}
+
+static void bcd2000_input_complete(struct urb *urb)
+{
+	int ret;
+	struct bcd2000 *bcd2k = urb->context;
+
+	if (urb->status)
+		dev_warn(&urb->dev->dev,
+			PREFIX "input urb->status: %i\n", urb->status);
+
+	if (!bcd2k || urb->status == -ESHUTDOWN)
+		return;
+
+	if (urb->actual_length > 0)
+		bcd2000_midi_handle_input(bcd2k, urb->transfer_buffer,
+					urb->actual_length);
+
+	/* return URB to device */
+	ret = usb_submit_urb(bcd2k->midi_in_urb, GFP_ATOMIC);
+	if (ret < 0)
+		dev_err(&bcd2k->dev->dev, PREFIX
+			"%s: usb_submit_urb() failed, ret=%d\n",
+			__func__, ret);
+}
+
+static const struct snd_rawmidi_ops bcd2000_midi_output = {
+	.open =    bcd2000_midi_output_open,
+	.close =   bcd2000_midi_output_close,
+	.trigger = bcd2000_midi_output_trigger,
+};
+
+static const struct snd_rawmidi_ops bcd2000_midi_input = {
+	.open =    bcd2000_midi_input_open,
+	.close =   bcd2000_midi_input_close,
+	.trigger = bcd2000_midi_input_trigger,
+};
+
+static void bcd2000_init_device(struct bcd2000 *bcd2k)
+{
+	int ret;
+
+	init_usb_anchor(&bcd2k->anchor);
+	usb_anchor_urb(bcd2k->midi_out_urb, &bcd2k->anchor);
+	usb_anchor_urb(bcd2k->midi_in_urb, &bcd2k->anchor);
+
+	/* copy init sequence into buffer */
+	memcpy(bcd2k->midi_out_buf, bcd2000_init_sequence, 52);
+	bcd2k->midi_out_urb->transfer_buffer_length = 52;
+
+	/* submit sequence */
+	ret = usb_submit_urb(bcd2k->midi_out_urb, GFP_KERNEL);
+	if (ret < 0)
+		dev_err(&bcd2k->dev->dev, PREFIX
+			"%s: usb_submit_urb() out failed, ret=%d: ",
+			__func__, ret);
+	else
+		bcd2k->midi_out_active = 1;
+
+	/* pass URB to device to enable button and controller events */
+	ret = usb_submit_urb(bcd2k->midi_in_urb, GFP_KERNEL);
+	if (ret < 0)
+		dev_err(&bcd2k->dev->dev, PREFIX
+			"%s: usb_submit_urb() in failed, ret=%d: ",
+			__func__, ret);
+
+	/* ensure initialization is finished */
+	usb_wait_anchor_empty_timeout(&bcd2k->anchor, 1000);
+}
+
+static int bcd2000_init_midi(struct bcd2000 *bcd2k)
+{
+	int ret;
+	struct snd_rawmidi *rmidi;
+
+	ret = snd_rawmidi_new(bcd2k->card, bcd2k->card->shortname, 0,
+					1, /* output */
+					1, /* input */
+					&rmidi);
+
+	if (ret < 0)
+		return ret;
+
+	strlcpy(rmidi->name, bcd2k->card->shortname, sizeof(rmidi->name));
+
+	rmidi->info_flags = SNDRV_RAWMIDI_INFO_DUPLEX;
+	rmidi->private_data = bcd2k;
+
+	rmidi->info_flags |= SNDRV_RAWMIDI_INFO_OUTPUT;
+	snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT,
+					&bcd2000_midi_output);
+
+	rmidi->info_flags |= SNDRV_RAWMIDI_INFO_INPUT;
+	snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT,
+					&bcd2000_midi_input);
+
+	bcd2k->rmidi = rmidi;
+
+	bcd2k->midi_in_urb = usb_alloc_urb(0, GFP_KERNEL);
+	bcd2k->midi_out_urb = usb_alloc_urb(0, GFP_KERNEL);
+
+	if (!bcd2k->midi_in_urb || !bcd2k->midi_out_urb) {
+		dev_err(&bcd2k->dev->dev, PREFIX "usb_alloc_urb failed\n");
+		return -ENOMEM;
+	}
+
+	usb_fill_int_urb(bcd2k->midi_in_urb, bcd2k->dev,
+				usb_rcvintpipe(bcd2k->dev, 0x81),
+				bcd2k->midi_in_buf, BUFSIZE,
+				bcd2000_input_complete, bcd2k, 1);
+
+	usb_fill_int_urb(bcd2k->midi_out_urb, bcd2k->dev,
+				usb_sndintpipe(bcd2k->dev, 0x1),
+				bcd2k->midi_out_buf, BUFSIZE,
+				bcd2000_output_complete, bcd2k, 1);
+
+	/* sanity checks of EPs before actually submitting */
+	if (usb_urb_ep_type_check(bcd2k->midi_in_urb) ||
+	    usb_urb_ep_type_check(bcd2k->midi_out_urb)) {
+		dev_err(&bcd2k->dev->dev, "invalid MIDI EP\n");
+		return -EINVAL;
+	}
+
+	bcd2000_init_device(bcd2k);
+
+	return 0;
+}
+
+static void bcd2000_free_usb_related_resources(struct bcd2000 *bcd2k,
+						struct usb_interface *interface)
+{
+	/* usb_kill_urb not necessary, urb is aborted automatically */
+
+	usb_free_urb(bcd2k->midi_out_urb);
+	usb_free_urb(bcd2k->midi_in_urb);
+
+	if (bcd2k->intf) {
+		usb_set_intfdata(bcd2k->intf, NULL);
+		bcd2k->intf = NULL;
+	}
+}
+
+static int bcd2000_probe(struct usb_interface *interface,
+				const struct usb_device_id *usb_id)
+{
+	struct snd_card *card;
+	struct bcd2000 *bcd2k;
+	unsigned int card_index;
+	char usb_path[32];
+	int err;
+
+	mutex_lock(&devices_mutex);
+
+	for (card_index = 0; card_index < SNDRV_CARDS; ++card_index)
+		if (!test_bit(card_index, devices_used))
+			break;
+
+	if (card_index >= SNDRV_CARDS) {
+		mutex_unlock(&devices_mutex);
+		return -ENOENT;
+	}
+
+	err = snd_card_new(&interface->dev, index[card_index], id[card_index],
+			THIS_MODULE, sizeof(*bcd2k), &card);
+	if (err < 0) {
+		mutex_unlock(&devices_mutex);
+		return err;
+	}
+
+	bcd2k = card->private_data;
+	bcd2k->dev = interface_to_usbdev(interface);
+	bcd2k->card = card;
+	bcd2k->card_index = card_index;
+	bcd2k->intf = interface;
+
+	snd_card_set_dev(card, &interface->dev);
+
+	strncpy(card->driver, "snd-bcd2000", sizeof(card->driver));
+	strncpy(card->shortname, "BCD2000", sizeof(card->shortname));
+	usb_make_path(bcd2k->dev, usb_path, sizeof(usb_path));
+	snprintf(bcd2k->card->longname, sizeof(bcd2k->card->longname),
+		    "Behringer BCD2000 at %s",
+			usb_path);
+
+	err = bcd2000_init_midi(bcd2k);
+	if (err < 0)
+		goto probe_error;
+
+	err = snd_card_register(card);
+	if (err < 0)
+		goto probe_error;
+
+	usb_set_intfdata(interface, bcd2k);
+	set_bit(card_index, devices_used);
+
+	mutex_unlock(&devices_mutex);
+	return 0;
+
+probe_error:
+	dev_info(&bcd2k->dev->dev, PREFIX "error during probing");
+	bcd2000_free_usb_related_resources(bcd2k, interface);
+	snd_card_free(card);
+	mutex_unlock(&devices_mutex);
+	return err;
+}
+
+static void bcd2000_disconnect(struct usb_interface *interface)
+{
+	struct bcd2000 *bcd2k = usb_get_intfdata(interface);
+
+	if (!bcd2k)
+		return;
+
+	mutex_lock(&devices_mutex);
+
+	/* make sure that userspace cannot create new requests */
+	snd_card_disconnect(bcd2k->card);
+
+	bcd2000_free_usb_related_resources(bcd2k, interface);
+
+	clear_bit(bcd2k->card_index, devices_used);
+
+	snd_card_free_when_closed(bcd2k->card);
+
+	mutex_unlock(&devices_mutex);
+}
+
+static struct usb_driver bcd2000_driver = {
+	.name =		"snd-bcd2000",
+	.probe =	bcd2000_probe,
+	.disconnect =	bcd2000_disconnect,
+	.id_table =	id_table,
+};
+
+module_usb_driver(bcd2000_driver);
+
+MODULE_DEVICE_TABLE(usb, id_table);
+MODULE_AUTHOR("Mario Kicherer, dev@kicherer.org");
+MODULE_DESCRIPTION("Behringer BCD2000 driver");
+MODULE_LICENSE("GPL");
diff --git a/sound/usb/caiaq/Makefile b/sound/usb/caiaq/Makefile
new file mode 100644
index 0000000..3889996
--- /dev/null
+++ b/sound/usb/caiaq/Makefile
@@ -0,0 +1,4 @@
+snd-usb-caiaq-y := device.o audio.o midi.o control.o
+snd-usb-caiaq-$(CONFIG_SND_USB_CAIAQ_INPUT) += input.o
+
+obj-$(CONFIG_SND_USB_CAIAQ) += snd-usb-caiaq.o
diff --git a/sound/usb/caiaq/audio.c b/sound/usb/caiaq/audio.c
new file mode 100644
index 0000000..c6108a3
--- /dev/null
+++ b/sound/usb/caiaq/audio.c
@@ -0,0 +1,903 @@
+/*
+ *   Copyright (c) 2006-2008 Daniel Mack, Karsten Wiese
+ *
+ *   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/device.h>
+#include <linux/spinlock.h>
+#include <linux/slab.h>
+#include <linux/init.h>
+#include <linux/usb.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+
+#include "device.h"
+#include "audio.h"
+
+#define N_URBS			32
+#define CLOCK_DRIFT_TOLERANCE	5
+#define FRAMES_PER_URB		8
+#define BYTES_PER_FRAME		512
+#define CHANNELS_PER_STREAM	2
+#define BYTES_PER_SAMPLE	3
+#define BYTES_PER_SAMPLE_USB	4
+#define MAX_BUFFER_SIZE		(128*1024)
+#define MAX_ENDPOINT_SIZE	512
+
+#define ENDPOINT_CAPTURE	2
+#define ENDPOINT_PLAYBACK	6
+
+#define MAKE_CHECKBYTE(cdev,stream,i) \
+	(stream << 1) | (~(i / (cdev->n_streams * BYTES_PER_SAMPLE_USB)) & 1)
+
+static struct snd_pcm_hardware snd_usb_caiaq_pcm_hardware = {
+	.info 		= (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
+			   SNDRV_PCM_INFO_BLOCK_TRANSFER),
+	.formats 	= SNDRV_PCM_FMTBIT_S24_3BE,
+	.rates 		= (SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 |
+			   SNDRV_PCM_RATE_96000),
+	.rate_min	= 44100,
+	.rate_max	= 0, /* will overwrite later */
+	.channels_min	= CHANNELS_PER_STREAM,
+	.channels_max	= CHANNELS_PER_STREAM,
+	.buffer_bytes_max = MAX_BUFFER_SIZE,
+	.period_bytes_min = 128,
+	.period_bytes_max = MAX_BUFFER_SIZE,
+	.periods_min	= 1,
+	.periods_max	= 1024,
+};
+
+static void
+activate_substream(struct snd_usb_caiaqdev *cdev,
+	           struct snd_pcm_substream *sub)
+{
+	spin_lock(&cdev->spinlock);
+
+	if (sub->stream == SNDRV_PCM_STREAM_PLAYBACK)
+		cdev->sub_playback[sub->number] = sub;
+	else
+		cdev->sub_capture[sub->number] = sub;
+
+	spin_unlock(&cdev->spinlock);
+}
+
+static void
+deactivate_substream(struct snd_usb_caiaqdev *cdev,
+		     struct snd_pcm_substream *sub)
+{
+	unsigned long flags;
+	spin_lock_irqsave(&cdev->spinlock, flags);
+
+	if (sub->stream == SNDRV_PCM_STREAM_PLAYBACK)
+		cdev->sub_playback[sub->number] = NULL;
+	else
+		cdev->sub_capture[sub->number] = NULL;
+
+	spin_unlock_irqrestore(&cdev->spinlock, flags);
+}
+
+static int
+all_substreams_zero(struct snd_pcm_substream **subs)
+{
+	int i;
+	for (i = 0; i < MAX_STREAMS; i++)
+		if (subs[i] != NULL)
+			return 0;
+	return 1;
+}
+
+static int stream_start(struct snd_usb_caiaqdev *cdev)
+{
+	int i, ret;
+	struct device *dev = caiaqdev_to_dev(cdev);
+
+	dev_dbg(dev, "%s(%p)\n", __func__, cdev);
+
+	if (cdev->streaming)
+		return -EINVAL;
+
+	memset(cdev->sub_playback, 0, sizeof(cdev->sub_playback));
+	memset(cdev->sub_capture, 0, sizeof(cdev->sub_capture));
+	cdev->input_panic = 0;
+	cdev->output_panic = 0;
+	cdev->first_packet = 4;
+	cdev->streaming = 1;
+	cdev->warned = 0;
+
+	for (i = 0; i < N_URBS; i++) {
+		ret = usb_submit_urb(cdev->data_urbs_in[i], GFP_ATOMIC);
+		if (ret) {
+			dev_err(dev, "unable to trigger read #%d! (ret %d)\n",
+				i, ret);
+			cdev->streaming = 0;
+			return -EPIPE;
+		}
+	}
+
+	return 0;
+}
+
+static void stream_stop(struct snd_usb_caiaqdev *cdev)
+{
+	int i;
+	struct device *dev = caiaqdev_to_dev(cdev);
+
+	dev_dbg(dev, "%s(%p)\n", __func__, cdev);
+	if (!cdev->streaming)
+		return;
+
+	cdev->streaming = 0;
+
+	for (i = 0; i < N_URBS; i++) {
+		usb_kill_urb(cdev->data_urbs_in[i]);
+
+		if (test_bit(i, &cdev->outurb_active_mask))
+			usb_kill_urb(cdev->data_urbs_out[i]);
+	}
+
+	cdev->outurb_active_mask = 0;
+}
+
+static int snd_usb_caiaq_substream_open(struct snd_pcm_substream *substream)
+{
+	struct snd_usb_caiaqdev *cdev = snd_pcm_substream_chip(substream);
+	struct device *dev = caiaqdev_to_dev(cdev);
+
+	dev_dbg(dev, "%s(%p)\n", __func__, substream);
+	substream->runtime->hw = cdev->pcm_info;
+	snd_pcm_limit_hw_rates(substream->runtime);
+
+	return 0;
+}
+
+static int snd_usb_caiaq_substream_close(struct snd_pcm_substream *substream)
+{
+	struct snd_usb_caiaqdev *cdev = snd_pcm_substream_chip(substream);
+	struct device *dev = caiaqdev_to_dev(cdev);
+
+	dev_dbg(dev, "%s(%p)\n", __func__, substream);
+	if (all_substreams_zero(cdev->sub_playback) &&
+	    all_substreams_zero(cdev->sub_capture)) {
+		/* when the last client has stopped streaming,
+		 * all sample rates are allowed again */
+		stream_stop(cdev);
+		cdev->pcm_info.rates = cdev->samplerates;
+	}
+
+	return 0;
+}
+
+static int snd_usb_caiaq_pcm_hw_params(struct snd_pcm_substream *sub,
+				       struct snd_pcm_hw_params *hw_params)
+{
+	return snd_pcm_lib_alloc_vmalloc_buffer(sub,
+						params_buffer_bytes(hw_params));
+}
+
+static int snd_usb_caiaq_pcm_hw_free(struct snd_pcm_substream *sub)
+{
+	struct snd_usb_caiaqdev *cdev = snd_pcm_substream_chip(sub);
+	deactivate_substream(cdev, sub);
+	return snd_pcm_lib_free_vmalloc_buffer(sub);
+}
+
+/* this should probably go upstream */
+#if SNDRV_PCM_RATE_5512 != 1 << 0 || SNDRV_PCM_RATE_192000 != 1 << 12
+#error "Change this table"
+#endif
+
+static unsigned int rates[] = { 5512, 8000, 11025, 16000, 22050, 32000, 44100,
+				48000, 64000, 88200, 96000, 176400, 192000 };
+
+static int snd_usb_caiaq_pcm_prepare(struct snd_pcm_substream *substream)
+{
+	int bytes_per_sample, bpp, ret, i;
+	int index = substream->number;
+	struct snd_usb_caiaqdev *cdev = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct device *dev = caiaqdev_to_dev(cdev);
+
+	dev_dbg(dev, "%s(%p)\n", __func__, substream);
+
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+		int out_pos;
+
+		switch (cdev->spec.data_alignment) {
+		case 0:
+		case 2:
+			out_pos = BYTES_PER_SAMPLE + 1;
+			break;
+		case 3:
+		default:
+			out_pos = 0;
+			break;
+		}
+
+		cdev->period_out_count[index] = out_pos;
+		cdev->audio_out_buf_pos[index] = out_pos;
+	} else {
+		int in_pos;
+
+		switch (cdev->spec.data_alignment) {
+		case 0:
+			in_pos = BYTES_PER_SAMPLE + 2;
+			break;
+		case 2:
+			in_pos = BYTES_PER_SAMPLE;
+			break;
+		case 3:
+		default:
+			in_pos = 0;
+			break;
+		}
+
+		cdev->period_in_count[index] = in_pos;
+		cdev->audio_in_buf_pos[index] = in_pos;
+	}
+
+	if (cdev->streaming)
+		return 0;
+
+	/* the first client that opens a stream defines the sample rate
+	 * setting for all subsequent calls, until the last client closed. */
+	for (i=0; i < ARRAY_SIZE(rates); i++)
+		if (runtime->rate == rates[i])
+			cdev->pcm_info.rates = 1 << i;
+
+	snd_pcm_limit_hw_rates(runtime);
+
+	bytes_per_sample = BYTES_PER_SAMPLE;
+	if (cdev->spec.data_alignment >= 2)
+		bytes_per_sample++;
+
+	bpp = ((runtime->rate / 8000) + CLOCK_DRIFT_TOLERANCE)
+		* bytes_per_sample * CHANNELS_PER_STREAM * cdev->n_streams;
+
+	if (bpp > MAX_ENDPOINT_SIZE)
+		bpp = MAX_ENDPOINT_SIZE;
+
+	ret = snd_usb_caiaq_set_audio_params(cdev, runtime->rate,
+					     runtime->sample_bits, bpp);
+	if (ret)
+		return ret;
+
+	ret = stream_start(cdev);
+	if (ret)
+		return ret;
+
+	cdev->output_running = 0;
+	wait_event_timeout(cdev->prepare_wait_queue, cdev->output_running, HZ);
+	if (!cdev->output_running) {
+		stream_stop(cdev);
+		return -EPIPE;
+	}
+
+	return 0;
+}
+
+static int snd_usb_caiaq_pcm_trigger(struct snd_pcm_substream *sub, int cmd)
+{
+	struct snd_usb_caiaqdev *cdev = snd_pcm_substream_chip(sub);
+	struct device *dev = caiaqdev_to_dev(cdev);
+
+	dev_dbg(dev, "%s(%p) cmd %d\n", __func__, sub, cmd);
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+		activate_substream(cdev, sub);
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+		deactivate_substream(cdev, sub);
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static snd_pcm_uframes_t
+snd_usb_caiaq_pcm_pointer(struct snd_pcm_substream *sub)
+{
+	int index = sub->number;
+	struct snd_usb_caiaqdev *cdev = snd_pcm_substream_chip(sub);
+	snd_pcm_uframes_t ptr;
+
+	spin_lock(&cdev->spinlock);
+
+	if (cdev->input_panic || cdev->output_panic) {
+		ptr = SNDRV_PCM_POS_XRUN;
+		goto unlock;
+	}
+
+	if (sub->stream == SNDRV_PCM_STREAM_PLAYBACK)
+		ptr = bytes_to_frames(sub->runtime,
+					cdev->audio_out_buf_pos[index]);
+	else
+		ptr = bytes_to_frames(sub->runtime,
+					cdev->audio_in_buf_pos[index]);
+
+unlock:
+	spin_unlock(&cdev->spinlock);
+	return ptr;
+}
+
+/* operators for both playback and capture */
+static const struct snd_pcm_ops snd_usb_caiaq_ops = {
+	.open =		snd_usb_caiaq_substream_open,
+	.close =	snd_usb_caiaq_substream_close,
+	.ioctl =	snd_pcm_lib_ioctl,
+	.hw_params =	snd_usb_caiaq_pcm_hw_params,
+	.hw_free =	snd_usb_caiaq_pcm_hw_free,
+	.prepare =	snd_usb_caiaq_pcm_prepare,
+	.trigger =	snd_usb_caiaq_pcm_trigger,
+	.pointer =	snd_usb_caiaq_pcm_pointer,
+	.page =		snd_pcm_lib_get_vmalloc_page,
+};
+
+static void check_for_elapsed_periods(struct snd_usb_caiaqdev *cdev,
+				      struct snd_pcm_substream **subs)
+{
+	int stream, pb, *cnt;
+	struct snd_pcm_substream *sub;
+
+	for (stream = 0; stream < cdev->n_streams; stream++) {
+		sub = subs[stream];
+		if (!sub)
+			continue;
+
+		pb = snd_pcm_lib_period_bytes(sub);
+		cnt = (sub->stream == SNDRV_PCM_STREAM_PLAYBACK) ?
+					&cdev->period_out_count[stream] :
+					&cdev->period_in_count[stream];
+
+		if (*cnt >= pb) {
+			snd_pcm_period_elapsed(sub);
+			*cnt %= pb;
+		}
+	}
+}
+
+static void read_in_urb_mode0(struct snd_usb_caiaqdev *cdev,
+			      const struct urb *urb,
+			      const struct usb_iso_packet_descriptor *iso)
+{
+	unsigned char *usb_buf = urb->transfer_buffer + iso->offset;
+	struct snd_pcm_substream *sub;
+	int stream, i;
+
+	if (all_substreams_zero(cdev->sub_capture))
+		return;
+
+	for (i = 0; i < iso->actual_length;) {
+		for (stream = 0; stream < cdev->n_streams; stream++, i++) {
+			sub = cdev->sub_capture[stream];
+			if (sub) {
+				struct snd_pcm_runtime *rt = sub->runtime;
+				char *audio_buf = rt->dma_area;
+				int sz = frames_to_bytes(rt, rt->buffer_size);
+				audio_buf[cdev->audio_in_buf_pos[stream]++]
+					= usb_buf[i];
+				cdev->period_in_count[stream]++;
+				if (cdev->audio_in_buf_pos[stream] == sz)
+					cdev->audio_in_buf_pos[stream] = 0;
+			}
+		}
+	}
+}
+
+static void read_in_urb_mode2(struct snd_usb_caiaqdev *cdev,
+			      const struct urb *urb,
+			      const struct usb_iso_packet_descriptor *iso)
+{
+	unsigned char *usb_buf = urb->transfer_buffer + iso->offset;
+	unsigned char check_byte;
+	struct snd_pcm_substream *sub;
+	int stream, i;
+
+	for (i = 0; i < iso->actual_length;) {
+		if (i % (cdev->n_streams * BYTES_PER_SAMPLE_USB) == 0) {
+			for (stream = 0;
+			     stream < cdev->n_streams;
+			     stream++, i++) {
+				if (cdev->first_packet)
+					continue;
+
+				check_byte = MAKE_CHECKBYTE(cdev, stream, i);
+
+				if ((usb_buf[i] & 0x3f) != check_byte)
+					cdev->input_panic = 1;
+
+				if (usb_buf[i] & 0x80)
+					cdev->output_panic = 1;
+			}
+		}
+		cdev->first_packet = 0;
+
+		for (stream = 0; stream < cdev->n_streams; stream++, i++) {
+			sub = cdev->sub_capture[stream];
+			if (cdev->input_panic)
+				usb_buf[i] = 0;
+
+			if (sub) {
+				struct snd_pcm_runtime *rt = sub->runtime;
+				char *audio_buf = rt->dma_area;
+				int sz = frames_to_bytes(rt, rt->buffer_size);
+				audio_buf[cdev->audio_in_buf_pos[stream]++] =
+					usb_buf[i];
+				cdev->period_in_count[stream]++;
+				if (cdev->audio_in_buf_pos[stream] == sz)
+					cdev->audio_in_buf_pos[stream] = 0;
+			}
+		}
+	}
+}
+
+static void read_in_urb_mode3(struct snd_usb_caiaqdev *cdev,
+			      const struct urb *urb,
+			      const struct usb_iso_packet_descriptor *iso)
+{
+	unsigned char *usb_buf = urb->transfer_buffer + iso->offset;
+	struct device *dev = caiaqdev_to_dev(cdev);
+	int stream, i;
+
+	/* paranoia check */
+	if (iso->actual_length % (BYTES_PER_SAMPLE_USB * CHANNELS_PER_STREAM))
+		return;
+
+	for (i = 0; i < iso->actual_length;) {
+		for (stream = 0; stream < cdev->n_streams; stream++) {
+			struct snd_pcm_substream *sub = cdev->sub_capture[stream];
+			char *audio_buf = NULL;
+			int c, n, sz = 0;
+
+			if (sub && !cdev->input_panic) {
+				struct snd_pcm_runtime *rt = sub->runtime;
+				audio_buf = rt->dma_area;
+				sz = frames_to_bytes(rt, rt->buffer_size);
+			}
+
+			for (c = 0; c < CHANNELS_PER_STREAM; c++) {
+				/* 3 audio data bytes, followed by 1 check byte */
+				if (audio_buf) {
+					for (n = 0; n < BYTES_PER_SAMPLE; n++) {
+						audio_buf[cdev->audio_in_buf_pos[stream]++] = usb_buf[i+n];
+
+						if (cdev->audio_in_buf_pos[stream] == sz)
+							cdev->audio_in_buf_pos[stream] = 0;
+					}
+
+					cdev->period_in_count[stream] += BYTES_PER_SAMPLE;
+				}
+
+				i += BYTES_PER_SAMPLE;
+
+				if (usb_buf[i] != ((stream << 1) | c) &&
+				    !cdev->first_packet) {
+					if (!cdev->input_panic)
+						dev_warn(dev, " EXPECTED: %02x got %02x, c %d, stream %d, i %d\n",
+							 ((stream << 1) | c), usb_buf[i], c, stream, i);
+					cdev->input_panic = 1;
+				}
+
+				i++;
+			}
+		}
+	}
+
+	if (cdev->first_packet > 0)
+		cdev->first_packet--;
+}
+
+static void read_in_urb(struct snd_usb_caiaqdev *cdev,
+			const struct urb *urb,
+			const struct usb_iso_packet_descriptor *iso)
+{
+	struct device *dev = caiaqdev_to_dev(cdev);
+
+	if (!cdev->streaming)
+		return;
+
+	if (iso->actual_length < cdev->bpp)
+		return;
+
+	switch (cdev->spec.data_alignment) {
+	case 0:
+		read_in_urb_mode0(cdev, urb, iso);
+		break;
+	case 2:
+		read_in_urb_mode2(cdev, urb, iso);
+		break;
+	case 3:
+		read_in_urb_mode3(cdev, urb, iso);
+		break;
+	}
+
+	if ((cdev->input_panic || cdev->output_panic) && !cdev->warned) {
+		dev_warn(dev, "streaming error detected %s %s\n",
+				cdev->input_panic ? "(input)" : "",
+				cdev->output_panic ? "(output)" : "");
+		cdev->warned = 1;
+	}
+}
+
+static void fill_out_urb_mode_0(struct snd_usb_caiaqdev *cdev,
+				struct urb *urb,
+				const struct usb_iso_packet_descriptor *iso)
+{
+	unsigned char *usb_buf = urb->transfer_buffer + iso->offset;
+	struct snd_pcm_substream *sub;
+	int stream, i;
+
+	for (i = 0; i < iso->length;) {
+		for (stream = 0; stream < cdev->n_streams; stream++, i++) {
+			sub = cdev->sub_playback[stream];
+			if (sub) {
+				struct snd_pcm_runtime *rt = sub->runtime;
+				char *audio_buf = rt->dma_area;
+				int sz = frames_to_bytes(rt, rt->buffer_size);
+				usb_buf[i] =
+					audio_buf[cdev->audio_out_buf_pos[stream]];
+				cdev->period_out_count[stream]++;
+				cdev->audio_out_buf_pos[stream]++;
+				if (cdev->audio_out_buf_pos[stream] == sz)
+					cdev->audio_out_buf_pos[stream] = 0;
+			} else
+				usb_buf[i] = 0;
+		}
+
+		/* fill in the check bytes */
+		if (cdev->spec.data_alignment == 2 &&
+		    i % (cdev->n_streams * BYTES_PER_SAMPLE_USB) ==
+		        (cdev->n_streams * CHANNELS_PER_STREAM))
+			for (stream = 0; stream < cdev->n_streams; stream++, i++)
+				usb_buf[i] = MAKE_CHECKBYTE(cdev, stream, i);
+	}
+}
+
+static void fill_out_urb_mode_3(struct snd_usb_caiaqdev *cdev,
+				struct urb *urb,
+				const struct usb_iso_packet_descriptor *iso)
+{
+	unsigned char *usb_buf = urb->transfer_buffer + iso->offset;
+	int stream, i;
+
+	for (i = 0; i < iso->length;) {
+		for (stream = 0; stream < cdev->n_streams; stream++) {
+			struct snd_pcm_substream *sub = cdev->sub_playback[stream];
+			char *audio_buf = NULL;
+			int c, n, sz = 0;
+
+			if (sub) {
+				struct snd_pcm_runtime *rt = sub->runtime;
+				audio_buf = rt->dma_area;
+				sz = frames_to_bytes(rt, rt->buffer_size);
+			}
+
+			for (c = 0; c < CHANNELS_PER_STREAM; c++) {
+				for (n = 0; n < BYTES_PER_SAMPLE; n++) {
+					if (audio_buf) {
+						usb_buf[i+n] = audio_buf[cdev->audio_out_buf_pos[stream]++];
+
+						if (cdev->audio_out_buf_pos[stream] == sz)
+							cdev->audio_out_buf_pos[stream] = 0;
+					} else {
+						usb_buf[i+n] = 0;
+					}
+				}
+
+				if (audio_buf)
+					cdev->period_out_count[stream] += BYTES_PER_SAMPLE;
+
+				i += BYTES_PER_SAMPLE;
+
+				/* fill in the check byte pattern */
+				usb_buf[i++] = (stream << 1) | c;
+			}
+		}
+	}
+}
+
+static inline void fill_out_urb(struct snd_usb_caiaqdev *cdev,
+				struct urb *urb,
+				const struct usb_iso_packet_descriptor *iso)
+{
+	switch (cdev->spec.data_alignment) {
+	case 0:
+	case 2:
+		fill_out_urb_mode_0(cdev, urb, iso);
+		break;
+	case 3:
+		fill_out_urb_mode_3(cdev, urb, iso);
+		break;
+	}
+}
+
+static void read_completed(struct urb *urb)
+{
+	struct snd_usb_caiaq_cb_info *info = urb->context;
+	struct snd_usb_caiaqdev *cdev;
+	struct device *dev;
+	struct urb *out = NULL;
+	int i, frame, len, send_it = 0, outframe = 0;
+	unsigned long flags;
+	size_t offset = 0;
+
+	if (urb->status || !info)
+		return;
+
+	cdev = info->cdev;
+	dev = caiaqdev_to_dev(cdev);
+
+	if (!cdev->streaming)
+		return;
+
+	/* find an unused output urb that is unused */
+	for (i = 0; i < N_URBS; i++)
+		if (test_and_set_bit(i, &cdev->outurb_active_mask) == 0) {
+			out = cdev->data_urbs_out[i];
+			break;
+		}
+
+	if (!out) {
+		dev_err(dev, "Unable to find an output urb to use\n");
+		goto requeue;
+	}
+
+	/* read the recently received packet and send back one which has
+	 * the same layout */
+	for (frame = 0; frame < FRAMES_PER_URB; frame++) {
+		if (urb->iso_frame_desc[frame].status)
+			continue;
+
+		len = urb->iso_frame_desc[outframe].actual_length;
+		out->iso_frame_desc[outframe].length = len;
+		out->iso_frame_desc[outframe].actual_length = 0;
+		out->iso_frame_desc[outframe].offset = offset;
+		offset += len;
+
+		if (len > 0) {
+			spin_lock_irqsave(&cdev->spinlock, flags);
+			fill_out_urb(cdev, out, &out->iso_frame_desc[outframe]);
+			read_in_urb(cdev, urb, &urb->iso_frame_desc[frame]);
+			spin_unlock_irqrestore(&cdev->spinlock, flags);
+			check_for_elapsed_periods(cdev, cdev->sub_playback);
+			check_for_elapsed_periods(cdev, cdev->sub_capture);
+			send_it = 1;
+		}
+
+		outframe++;
+	}
+
+	if (send_it) {
+		out->number_of_packets = outframe;
+		usb_submit_urb(out, GFP_ATOMIC);
+	} else {
+		struct snd_usb_caiaq_cb_info *oinfo = out->context;
+		clear_bit(oinfo->index, &cdev->outurb_active_mask);
+	}
+
+requeue:
+	/* re-submit inbound urb */
+	for (frame = 0; frame < FRAMES_PER_URB; frame++) {
+		urb->iso_frame_desc[frame].offset = BYTES_PER_FRAME * frame;
+		urb->iso_frame_desc[frame].length = BYTES_PER_FRAME;
+		urb->iso_frame_desc[frame].actual_length = 0;
+	}
+
+	urb->number_of_packets = FRAMES_PER_URB;
+	usb_submit_urb(urb, GFP_ATOMIC);
+}
+
+static void write_completed(struct urb *urb)
+{
+	struct snd_usb_caiaq_cb_info *info = urb->context;
+	struct snd_usb_caiaqdev *cdev = info->cdev;
+
+	if (!cdev->output_running) {
+		cdev->output_running = 1;
+		wake_up(&cdev->prepare_wait_queue);
+	}
+
+	clear_bit(info->index, &cdev->outurb_active_mask);
+}
+
+static struct urb **alloc_urbs(struct snd_usb_caiaqdev *cdev, int dir, int *ret)
+{
+	int i, frame;
+	struct urb **urbs;
+	struct usb_device *usb_dev = cdev->chip.dev;
+	unsigned int pipe;
+
+	pipe = (dir == SNDRV_PCM_STREAM_PLAYBACK) ?
+		usb_sndisocpipe(usb_dev, ENDPOINT_PLAYBACK) :
+		usb_rcvisocpipe(usb_dev, ENDPOINT_CAPTURE);
+
+	urbs = kmalloc_array(N_URBS, sizeof(*urbs), GFP_KERNEL);
+	if (!urbs) {
+		*ret = -ENOMEM;
+		return NULL;
+	}
+
+	for (i = 0; i < N_URBS; i++) {
+		urbs[i] = usb_alloc_urb(FRAMES_PER_URB, GFP_KERNEL);
+		if (!urbs[i]) {
+			*ret = -ENOMEM;
+			return urbs;
+		}
+
+		urbs[i]->transfer_buffer =
+			kmalloc_array(BYTES_PER_FRAME, FRAMES_PER_URB,
+				      GFP_KERNEL);
+		if (!urbs[i]->transfer_buffer) {
+			*ret = -ENOMEM;
+			return urbs;
+		}
+
+		for (frame = 0; frame < FRAMES_PER_URB; frame++) {
+			struct usb_iso_packet_descriptor *iso =
+				&urbs[i]->iso_frame_desc[frame];
+
+			iso->offset = BYTES_PER_FRAME * frame;
+			iso->length = BYTES_PER_FRAME;
+		}
+
+		urbs[i]->dev = usb_dev;
+		urbs[i]->pipe = pipe;
+		urbs[i]->transfer_buffer_length = FRAMES_PER_URB
+						* BYTES_PER_FRAME;
+		urbs[i]->context = &cdev->data_cb_info[i];
+		urbs[i]->interval = 1;
+		urbs[i]->number_of_packets = FRAMES_PER_URB;
+		urbs[i]->complete = (dir == SNDRV_PCM_STREAM_CAPTURE) ?
+					read_completed : write_completed;
+	}
+
+	*ret = 0;
+	return urbs;
+}
+
+static void free_urbs(struct urb **urbs)
+{
+	int i;
+
+	if (!urbs)
+		return;
+
+	for (i = 0; i < N_URBS; i++) {
+		if (!urbs[i])
+			continue;
+
+		usb_kill_urb(urbs[i]);
+		kfree(urbs[i]->transfer_buffer);
+		usb_free_urb(urbs[i]);
+	}
+
+	kfree(urbs);
+}
+
+int snd_usb_caiaq_audio_init(struct snd_usb_caiaqdev *cdev)
+{
+	int i, ret;
+	struct device *dev = caiaqdev_to_dev(cdev);
+
+	cdev->n_audio_in  = max(cdev->spec.num_analog_audio_in,
+			       cdev->spec.num_digital_audio_in) /
+				CHANNELS_PER_STREAM;
+	cdev->n_audio_out = max(cdev->spec.num_analog_audio_out,
+			       cdev->spec.num_digital_audio_out) /
+				CHANNELS_PER_STREAM;
+	cdev->n_streams = max(cdev->n_audio_in, cdev->n_audio_out);
+
+	dev_dbg(dev, "cdev->n_audio_in = %d\n", cdev->n_audio_in);
+	dev_dbg(dev, "cdev->n_audio_out = %d\n", cdev->n_audio_out);
+	dev_dbg(dev, "cdev->n_streams = %d\n", cdev->n_streams);
+
+	if (cdev->n_streams > MAX_STREAMS) {
+		dev_err(dev, "unable to initialize device, too many streams.\n");
+		return -EINVAL;
+	}
+
+	if (cdev->n_streams < 1) {
+		dev_err(dev, "bogus number of streams: %d\n", cdev->n_streams);
+		return -EINVAL;
+	}
+
+	ret = snd_pcm_new(cdev->chip.card, cdev->product_name, 0,
+			cdev->n_audio_out, cdev->n_audio_in, &cdev->pcm);
+
+	if (ret < 0) {
+		dev_err(dev, "snd_pcm_new() returned %d\n", ret);
+		return ret;
+	}
+
+	cdev->pcm->private_data = cdev;
+	strlcpy(cdev->pcm->name, cdev->product_name, sizeof(cdev->pcm->name));
+
+	memset(cdev->sub_playback, 0, sizeof(cdev->sub_playback));
+	memset(cdev->sub_capture, 0, sizeof(cdev->sub_capture));
+
+	memcpy(&cdev->pcm_info, &snd_usb_caiaq_pcm_hardware,
+			sizeof(snd_usb_caiaq_pcm_hardware));
+
+	/* setup samplerates */
+	cdev->samplerates = cdev->pcm_info.rates;
+	switch (cdev->chip.usb_id) {
+	case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_AK1):
+	case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_RIGKONTROL3):
+	case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_SESSIONIO):
+	case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_GUITARRIGMOBILE):
+		cdev->samplerates |= SNDRV_PCM_RATE_192000;
+		/* fall thru */
+	case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_AUDIO2DJ):
+	case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_AUDIO4DJ):
+	case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_AUDIO8DJ):
+	case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_TRAKTORAUDIO2):
+		cdev->samplerates |= SNDRV_PCM_RATE_88200;
+		break;
+	}
+
+	snd_pcm_set_ops(cdev->pcm, SNDRV_PCM_STREAM_PLAYBACK,
+				&snd_usb_caiaq_ops);
+	snd_pcm_set_ops(cdev->pcm, SNDRV_PCM_STREAM_CAPTURE,
+				&snd_usb_caiaq_ops);
+
+	cdev->data_cb_info =
+		kmalloc_array(N_URBS, sizeof(struct snd_usb_caiaq_cb_info),
+					GFP_KERNEL);
+
+	if (!cdev->data_cb_info)
+		return -ENOMEM;
+
+	cdev->outurb_active_mask = 0;
+	BUILD_BUG_ON(N_URBS > (sizeof(cdev->outurb_active_mask) * 8));
+
+	for (i = 0; i < N_URBS; i++) {
+		cdev->data_cb_info[i].cdev = cdev;
+		cdev->data_cb_info[i].index = i;
+	}
+
+	cdev->data_urbs_in = alloc_urbs(cdev, SNDRV_PCM_STREAM_CAPTURE, &ret);
+	if (ret < 0) {
+		kfree(cdev->data_cb_info);
+		free_urbs(cdev->data_urbs_in);
+		return ret;
+	}
+
+	cdev->data_urbs_out = alloc_urbs(cdev, SNDRV_PCM_STREAM_PLAYBACK, &ret);
+	if (ret < 0) {
+		kfree(cdev->data_cb_info);
+		free_urbs(cdev->data_urbs_in);
+		free_urbs(cdev->data_urbs_out);
+		return ret;
+	}
+
+	return 0;
+}
+
+void snd_usb_caiaq_audio_free(struct snd_usb_caiaqdev *cdev)
+{
+	struct device *dev = caiaqdev_to_dev(cdev);
+
+	dev_dbg(dev, "%s(%p)\n", __func__, cdev);
+	stream_stop(cdev);
+	free_urbs(cdev->data_urbs_in);
+	free_urbs(cdev->data_urbs_out);
+	kfree(cdev->data_cb_info);
+}
+
diff --git a/sound/usb/caiaq/audio.h b/sound/usb/caiaq/audio.h
new file mode 100644
index 0000000..869bf62
--- /dev/null
+++ b/sound/usb/caiaq/audio.h
@@ -0,0 +1,8 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef CAIAQ_AUDIO_H
+#define CAIAQ_AUDIO_H
+
+int snd_usb_caiaq_audio_init(struct snd_usb_caiaqdev *cdev);
+void snd_usb_caiaq_audio_free(struct snd_usb_caiaqdev *cdev);
+
+#endif /* CAIAQ_AUDIO_H */
diff --git a/sound/usb/caiaq/control.c b/sound/usb/caiaq/control.c
new file mode 100644
index 0000000..b7a7c80
--- /dev/null
+++ b/sound/usb/caiaq/control.c
@@ -0,0 +1,656 @@
+/*
+ *   Copyright (c) 2007 Daniel Mack
+ *   friendly supported by NI.
+ *
+ *   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/device.h>
+#include <linux/init.h>
+#include <linux/usb.h>
+#include <sound/control.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+
+#include "device.h"
+#include "control.h"
+
+#define CNT_INTVAL 0x10000
+#define MASCHINE_BANK_SIZE 32
+
+static int control_info(struct snd_kcontrol *kcontrol,
+			struct snd_ctl_elem_info *uinfo)
+{
+	struct snd_usb_audio *chip = snd_kcontrol_chip(kcontrol);
+	struct snd_usb_caiaqdev *cdev = caiaqdev(chip->card);
+	int pos = kcontrol->private_value;
+	int is_intval = pos & CNT_INTVAL;
+	int maxval = 63;
+
+	uinfo->count = 1;
+	pos &= ~CNT_INTVAL;
+
+	switch (cdev->chip.usb_id) {
+	case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_AUDIO8DJ):
+	case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_AUDIO4DJ):
+		if (pos == 0) {
+			/* current input mode of A8DJ and A4DJ */
+			uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+			uinfo->value.integer.min = 0;
+			uinfo->value.integer.max = 2;
+			return 0;
+		}
+		break;
+
+	case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_TRAKTORKONTROLX1):
+		maxval = 127;
+		break;
+
+	case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_TRAKTORKONTROLS4):
+		maxval = 31;
+		break;
+	}
+
+	if (is_intval) {
+		uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+		uinfo->value.integer.min = 0;
+		uinfo->value.integer.max = maxval;
+	} else {
+		uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
+		uinfo->value.integer.min = 0;
+		uinfo->value.integer.max = 1;
+	}
+
+	return 0;
+}
+
+static int control_get(struct snd_kcontrol *kcontrol,
+		       struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_usb_audio *chip = snd_kcontrol_chip(kcontrol);
+	struct snd_usb_caiaqdev *cdev = caiaqdev(chip->card);
+	int pos = kcontrol->private_value;
+
+	if (pos & CNT_INTVAL)
+		ucontrol->value.integer.value[0]
+			= cdev->control_state[pos & ~CNT_INTVAL];
+	else
+		ucontrol->value.integer.value[0]
+			= !!(cdev->control_state[pos / 8] & (1 << pos % 8));
+
+	return 0;
+}
+
+static int control_put(struct snd_kcontrol *kcontrol,
+		       struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_usb_audio *chip = snd_kcontrol_chip(kcontrol);
+	struct snd_usb_caiaqdev *cdev = caiaqdev(chip->card);
+	int pos = kcontrol->private_value;
+	int v = ucontrol->value.integer.value[0];
+	unsigned char cmd;
+
+	switch (cdev->chip.usb_id) {
+	case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_MASCHINECONTROLLER):
+	case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_TRAKTORKONTROLX1):
+	case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_KORECONTROLLER2):
+	case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_KORECONTROLLER):
+		cmd = EP1_CMD_DIMM_LEDS;
+		break;
+	default:
+		cmd = EP1_CMD_WRITE_IO;
+		break;
+	}
+
+	if (pos & CNT_INTVAL) {
+		int i = pos & ~CNT_INTVAL;
+
+		cdev->control_state[i] = v;
+
+		if (cdev->chip.usb_id ==
+			USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_TRAKTORKONTROLS4)) {
+			int actual_len;
+
+			cdev->ep8_out_buf[0] = i;
+			cdev->ep8_out_buf[1] = v;
+
+			usb_bulk_msg(cdev->chip.dev,
+				     usb_sndbulkpipe(cdev->chip.dev, 8),
+				     cdev->ep8_out_buf, sizeof(cdev->ep8_out_buf),
+				     &actual_len, 200);
+		} else if (cdev->chip.usb_id ==
+			USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_MASCHINECONTROLLER)) {
+
+			int bank = 0;
+			int offset = 0;
+
+			if (i >= MASCHINE_BANK_SIZE) {
+				bank = 0x1e;
+				offset = MASCHINE_BANK_SIZE;
+			}
+
+			snd_usb_caiaq_send_command_bank(cdev, cmd, bank,
+					cdev->control_state + offset,
+					MASCHINE_BANK_SIZE);
+		} else {
+			snd_usb_caiaq_send_command(cdev, cmd,
+					cdev->control_state, sizeof(cdev->control_state));
+		}
+	} else {
+		if (v)
+			cdev->control_state[pos / 8] |= 1 << (pos % 8);
+		else
+			cdev->control_state[pos / 8] &= ~(1 << (pos % 8));
+
+		snd_usb_caiaq_send_command(cdev, cmd,
+				cdev->control_state, sizeof(cdev->control_state));
+	}
+
+	return 1;
+}
+
+static struct snd_kcontrol_new kcontrol_template = {
+	.iface = SNDRV_CTL_ELEM_IFACE_HWDEP,
+	.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+	.index = 0,
+	.info = control_info,
+	.get  = control_get,
+	.put  = control_put,
+	/* name and private_value filled later */
+};
+
+struct caiaq_controller {
+	char *name;
+	int index;
+};
+
+static struct caiaq_controller ak1_controller[] = {
+	{ "LED left", 	2 },
+	{ "LED middle", 1 },
+	{ "LED right", 	0 },
+	{ "LED ring", 	3 }
+};
+
+static struct caiaq_controller rk2_controller[] = {
+	{ "LED 1",		5  },
+	{ "LED 2",		4  },
+	{ "LED 3",		3  },
+	{ "LED 4",		2  },
+	{ "LED 5",		1  },
+	{ "LED 6",		0  },
+	{ "LED pedal",		6  },
+	{ "LED 7seg_1b",	8  },
+	{ "LED 7seg_1c",	9  },
+	{ "LED 7seg_2a",	10 },
+	{ "LED 7seg_2b",	11 },
+	{ "LED 7seg_2c",	12 },
+	{ "LED 7seg_2d",	13 },
+	{ "LED 7seg_2e",	14 },
+	{ "LED 7seg_2f",	15 },
+	{ "LED 7seg_2g",	16 },
+	{ "LED 7seg_3a",	17 },
+	{ "LED 7seg_3b",	18 },
+	{ "LED 7seg_3c",	19 },
+	{ "LED 7seg_3d",	20 },
+	{ "LED 7seg_3e",	21 },
+	{ "LED 7seg_3f",	22 },
+	{ "LED 7seg_3g",	23 }
+};
+
+static struct caiaq_controller rk3_controller[] = {
+	{ "LED 7seg_1a",        0 + 0 },
+	{ "LED 7seg_1b",        0 + 1 },
+	{ "LED 7seg_1c",        0 + 2 },
+	{ "LED 7seg_1d",        0 + 3 },
+	{ "LED 7seg_1e",        0 + 4 },
+	{ "LED 7seg_1f",        0 + 5 },
+	{ "LED 7seg_1g",        0 + 6 },
+	{ "LED 7seg_1p",        0 + 7 },
+
+	{ "LED 7seg_2a",        8 + 0 },
+	{ "LED 7seg_2b",        8 + 1 },
+	{ "LED 7seg_2c",        8 + 2 },
+	{ "LED 7seg_2d",        8 + 3 },
+	{ "LED 7seg_2e",        8 + 4 },
+	{ "LED 7seg_2f",        8 + 5 },
+	{ "LED 7seg_2g",        8 + 6 },
+	{ "LED 7seg_2p",        8 + 7 },
+
+	{ "LED 7seg_3a",        16 + 0 },
+	{ "LED 7seg_3b",        16 + 1 },
+	{ "LED 7seg_3c",        16 + 2 },
+	{ "LED 7seg_3d",        16 + 3 },
+	{ "LED 7seg_3e",        16 + 4 },
+	{ "LED 7seg_3f",        16 + 5 },
+	{ "LED 7seg_3g",        16 + 6 },
+	{ "LED 7seg_3p",        16 + 7 },
+
+	{ "LED 7seg_4a",        24 + 0 },
+	{ "LED 7seg_4b",        24 + 1 },
+	{ "LED 7seg_4c",        24 + 2 },
+	{ "LED 7seg_4d",        24 + 3 },
+	{ "LED 7seg_4e",        24 + 4 },
+	{ "LED 7seg_4f",        24 + 5 },
+	{ "LED 7seg_4g",        24 + 6 },
+	{ "LED 7seg_4p",        24 + 7 },
+
+	{ "LED 1",		32 + 0 },
+	{ "LED 2",		32 + 1 },
+	{ "LED 3",		32 + 2 },
+	{ "LED 4",		32 + 3 },
+	{ "LED 5",		32 + 4 },
+	{ "LED 6",		32 + 5 },
+	{ "LED 7",		32 + 6 },
+	{ "LED 8",		32 + 7 },
+	{ "LED pedal",		32 + 8 }
+};
+
+static struct caiaq_controller kore_controller[] = {
+	{ "LED F1",		8   | CNT_INTVAL },
+	{ "LED F2",		12  | CNT_INTVAL },
+	{ "LED F3",		0   | CNT_INTVAL },
+	{ "LED F4",		4   | CNT_INTVAL },
+	{ "LED F5",		11  | CNT_INTVAL },
+	{ "LED F6",		15  | CNT_INTVAL },
+	{ "LED F7",		3   | CNT_INTVAL },
+	{ "LED F8",		7   | CNT_INTVAL },
+	{ "LED touch1",	     	10  | CNT_INTVAL },
+	{ "LED touch2",	     	14  | CNT_INTVAL },
+	{ "LED touch3",	     	2   | CNT_INTVAL },
+	{ "LED touch4",	     	6   | CNT_INTVAL },
+	{ "LED touch5",	     	9   | CNT_INTVAL },
+	{ "LED touch6",	     	13  | CNT_INTVAL },
+	{ "LED touch7",	     	1   | CNT_INTVAL },
+	{ "LED touch8",	     	5   | CNT_INTVAL },
+	{ "LED left",	       	18  | CNT_INTVAL },
+	{ "LED right",	     	22  | CNT_INTVAL },
+	{ "LED up",		16  | CNT_INTVAL },
+	{ "LED down",	       	20  | CNT_INTVAL },
+	{ "LED stop",	       	23  | CNT_INTVAL },
+	{ "LED play",	       	21  | CNT_INTVAL },
+	{ "LED record",	     	19  | CNT_INTVAL },
+	{ "LED listen",		17  | CNT_INTVAL },
+	{ "LED lcd",		30  | CNT_INTVAL },
+	{ "LED menu",		28  | CNT_INTVAL },
+	{ "LED sound",	 	31  | CNT_INTVAL },
+	{ "LED esc",		29  | CNT_INTVAL },
+	{ "LED view",		27  | CNT_INTVAL },
+	{ "LED enter",		24  | CNT_INTVAL },
+	{ "LED control",	26  | CNT_INTVAL }
+};
+
+static struct caiaq_controller a8dj_controller[] = {
+	{ "Current input mode",			0 | CNT_INTVAL 	},
+	{ "GND lift for TC Vinyl mode", 	24 + 0 		},
+	{ "GND lift for TC CD/Line mode", 	24 + 1 		},
+	{ "GND lift for phono mode", 		24 + 2 		},
+	{ "Software lock", 			40 		}
+};
+
+static struct caiaq_controller a4dj_controller[] = {
+	{ "Current input mode",	0 | CNT_INTVAL 	}
+};
+
+static struct caiaq_controller kontrolx1_controller[] = {
+	{ "LED FX A: ON",		7 | CNT_INTVAL	},
+	{ "LED FX A: 1",		6 | CNT_INTVAL	},
+	{ "LED FX A: 2",		5 | CNT_INTVAL	},
+	{ "LED FX A: 3",		4 | CNT_INTVAL	},
+	{ "LED FX B: ON",		3 | CNT_INTVAL	},
+	{ "LED FX B: 1",		2 | CNT_INTVAL	},
+	{ "LED FX B: 2",		1 | CNT_INTVAL	},
+	{ "LED FX B: 3",		0 | CNT_INTVAL	},
+
+	{ "LED Hotcue",			28 | CNT_INTVAL	},
+	{ "LED Shift (white)",		29 | CNT_INTVAL	},
+	{ "LED Shift (green)",		30 | CNT_INTVAL	},
+
+	{ "LED Deck A: FX1",		24 | CNT_INTVAL	},
+	{ "LED Deck A: FX2",		25 | CNT_INTVAL	},
+	{ "LED Deck A: IN",		17 | CNT_INTVAL	},
+	{ "LED Deck A: OUT",		16 | CNT_INTVAL	},
+	{ "LED Deck A: < BEAT",		19 | CNT_INTVAL	},
+	{ "LED Deck A: BEAT >",		18 | CNT_INTVAL	},
+	{ "LED Deck A: CUE/ABS",	21 | CNT_INTVAL	},
+	{ "LED Deck A: CUP/REL",	20 | CNT_INTVAL	},
+	{ "LED Deck A: PLAY",		23 | CNT_INTVAL	},
+	{ "LED Deck A: SYNC",		22 | CNT_INTVAL	},
+
+	{ "LED Deck B: FX1",		26 | CNT_INTVAL	},
+	{ "LED Deck B: FX2",		27 | CNT_INTVAL	},
+	{ "LED Deck B: IN",		15 | CNT_INTVAL	},
+	{ "LED Deck B: OUT",		14 | CNT_INTVAL	},
+	{ "LED Deck B: < BEAT",		13 | CNT_INTVAL	},
+	{ "LED Deck B: BEAT >",		12 | CNT_INTVAL	},
+	{ "LED Deck B: CUE/ABS",	11 | CNT_INTVAL	},
+	{ "LED Deck B: CUP/REL",	10 | CNT_INTVAL	},
+	{ "LED Deck B: PLAY",		9  | CNT_INTVAL	},
+	{ "LED Deck B: SYNC",		8  | CNT_INTVAL	},
+};
+
+static struct caiaq_controller kontrols4_controller[] = {
+	{ "LED: Master: Quant",			10  | CNT_INTVAL },
+	{ "LED: Master: Headphone",		11  | CNT_INTVAL },
+	{ "LED: Master: Master",		12  | CNT_INTVAL },
+	{ "LED: Master: Snap",			14  | CNT_INTVAL },
+	{ "LED: Master: Warning",		15  | CNT_INTVAL },
+	{ "LED: Master: Master button",		112 | CNT_INTVAL },
+	{ "LED: Master: Snap button",		113 | CNT_INTVAL },
+	{ "LED: Master: Rec",			118 | CNT_INTVAL },
+	{ "LED: Master: Size",			119 | CNT_INTVAL },
+	{ "LED: Master: Quant button",		120 | CNT_INTVAL },
+	{ "LED: Master: Browser button",	121 | CNT_INTVAL },
+	{ "LED: Master: Play button",		126 | CNT_INTVAL },
+	{ "LED: Master: Undo button",		127 | CNT_INTVAL },
+
+	{ "LED: Channel A: >",			4   | CNT_INTVAL },
+	{ "LED: Channel A: <",			5   | CNT_INTVAL },
+	{ "LED: Channel A: Meter 1",		97  | CNT_INTVAL },
+	{ "LED: Channel A: Meter 2",		98  | CNT_INTVAL },
+	{ "LED: Channel A: Meter 3",		99  | CNT_INTVAL },
+	{ "LED: Channel A: Meter 4",		100 | CNT_INTVAL },
+	{ "LED: Channel A: Meter 5",		101 | CNT_INTVAL },
+	{ "LED: Channel A: Meter 6",		102 | CNT_INTVAL },
+	{ "LED: Channel A: Meter clip",		103 | CNT_INTVAL },
+	{ "LED: Channel A: Active",		114 | CNT_INTVAL },
+	{ "LED: Channel A: Cue",		116 | CNT_INTVAL },
+	{ "LED: Channel A: FX1",		149 | CNT_INTVAL },
+	{ "LED: Channel A: FX2",		148 | CNT_INTVAL },
+
+	{ "LED: Channel B: >",			2   | CNT_INTVAL },
+	{ "LED: Channel B: <",			3   | CNT_INTVAL },
+	{ "LED: Channel B: Meter 1",		89  | CNT_INTVAL },
+	{ "LED: Channel B: Meter 2",		90  | CNT_INTVAL },
+	{ "LED: Channel B: Meter 3",		91  | CNT_INTVAL },
+	{ "LED: Channel B: Meter 4",		92  | CNT_INTVAL },
+	{ "LED: Channel B: Meter 5",		93  | CNT_INTVAL },
+	{ "LED: Channel B: Meter 6",		94  | CNT_INTVAL },
+	{ "LED: Channel B: Meter clip",		95  | CNT_INTVAL },
+	{ "LED: Channel B: Active",		122 | CNT_INTVAL },
+	{ "LED: Channel B: Cue",		125 | CNT_INTVAL },
+	{ "LED: Channel B: FX1",		147 | CNT_INTVAL },
+	{ "LED: Channel B: FX2",		146 | CNT_INTVAL },
+
+	{ "LED: Channel C: >",			6   | CNT_INTVAL },
+	{ "LED: Channel C: <",			7   | CNT_INTVAL },
+	{ "LED: Channel C: Meter 1",		105 | CNT_INTVAL },
+	{ "LED: Channel C: Meter 2",		106 | CNT_INTVAL },
+	{ "LED: Channel C: Meter 3",		107 | CNT_INTVAL },
+	{ "LED: Channel C: Meter 4",		108 | CNT_INTVAL },
+	{ "LED: Channel C: Meter 5",		109 | CNT_INTVAL },
+	{ "LED: Channel C: Meter 6",		110 | CNT_INTVAL },
+	{ "LED: Channel C: Meter clip",		111 | CNT_INTVAL },
+	{ "LED: Channel C: Active",		115 | CNT_INTVAL },
+	{ "LED: Channel C: Cue",		117 | CNT_INTVAL },
+	{ "LED: Channel C: FX1",		151 | CNT_INTVAL },
+	{ "LED: Channel C: FX2",		150 | CNT_INTVAL },
+
+	{ "LED: Channel D: >",			0   | CNT_INTVAL },
+	{ "LED: Channel D: <",			1   | CNT_INTVAL },
+	{ "LED: Channel D: Meter 1",		81  | CNT_INTVAL },
+	{ "LED: Channel D: Meter 2",		82  | CNT_INTVAL },
+	{ "LED: Channel D: Meter 3",		83  | CNT_INTVAL },
+	{ "LED: Channel D: Meter 4",		84  | CNT_INTVAL },
+	{ "LED: Channel D: Meter 5",		85  | CNT_INTVAL },
+	{ "LED: Channel D: Meter 6",		86  | CNT_INTVAL },
+	{ "LED: Channel D: Meter clip",		87  | CNT_INTVAL },
+	{ "LED: Channel D: Active",		123 | CNT_INTVAL },
+	{ "LED: Channel D: Cue",		124 | CNT_INTVAL },
+	{ "LED: Channel D: FX1",		145 | CNT_INTVAL },
+	{ "LED: Channel D: FX2",		144 | CNT_INTVAL },
+
+	{ "LED: Deck A: 1 (blue)",		22  | CNT_INTVAL },
+	{ "LED: Deck A: 1 (green)",		23  | CNT_INTVAL },
+	{ "LED: Deck A: 2 (blue)",		20  | CNT_INTVAL },
+	{ "LED: Deck A: 2 (green)",		21  | CNT_INTVAL },
+	{ "LED: Deck A: 3 (blue)",		18  | CNT_INTVAL },
+	{ "LED: Deck A: 3 (green)",		19  | CNT_INTVAL },
+	{ "LED: Deck A: 4 (blue)",		16  | CNT_INTVAL },
+	{ "LED: Deck A: 4 (green)",		17  | CNT_INTVAL },
+	{ "LED: Deck A: Load",			44  | CNT_INTVAL },
+	{ "LED: Deck A: Deck C button",		45  | CNT_INTVAL },
+	{ "LED: Deck A: In",			47  | CNT_INTVAL },
+	{ "LED: Deck A: Out",			46  | CNT_INTVAL },
+	{ "LED: Deck A: Shift",			24  | CNT_INTVAL },
+	{ "LED: Deck A: Sync",			27  | CNT_INTVAL },
+	{ "LED: Deck A: Cue",			26  | CNT_INTVAL },
+	{ "LED: Deck A: Play",			25  | CNT_INTVAL },
+	{ "LED: Deck A: Tempo up",		33  | CNT_INTVAL },
+	{ "LED: Deck A: Tempo down",		32  | CNT_INTVAL },
+	{ "LED: Deck A: Master",		34  | CNT_INTVAL },
+	{ "LED: Deck A: Keylock",		35  | CNT_INTVAL },
+	{ "LED: Deck A: Deck A",		37  | CNT_INTVAL },
+	{ "LED: Deck A: Deck C",		36  | CNT_INTVAL },
+	{ "LED: Deck A: Samples",		38  | CNT_INTVAL },
+	{ "LED: Deck A: On Air",		39  | CNT_INTVAL },
+	{ "LED: Deck A: Sample 1",		31  | CNT_INTVAL },
+	{ "LED: Deck A: Sample 2",		30  | CNT_INTVAL },
+	{ "LED: Deck A: Sample 3",		29  | CNT_INTVAL },
+	{ "LED: Deck A: Sample 4",		28  | CNT_INTVAL },
+	{ "LED: Deck A: Digit 1 - A",		55  | CNT_INTVAL },
+	{ "LED: Deck A: Digit 1 - B",		54  | CNT_INTVAL },
+	{ "LED: Deck A: Digit 1 - C",		53  | CNT_INTVAL },
+	{ "LED: Deck A: Digit 1 - D",		52  | CNT_INTVAL },
+	{ "LED: Deck A: Digit 1 - E",		51  | CNT_INTVAL },
+	{ "LED: Deck A: Digit 1 - F",		50  | CNT_INTVAL },
+	{ "LED: Deck A: Digit 1 - G",		49  | CNT_INTVAL },
+	{ "LED: Deck A: Digit 1 - dot",		48  | CNT_INTVAL },
+	{ "LED: Deck A: Digit 2 - A",		63  | CNT_INTVAL },
+	{ "LED: Deck A: Digit 2 - B",		62  | CNT_INTVAL },
+	{ "LED: Deck A: Digit 2 - C",		61  | CNT_INTVAL },
+	{ "LED: Deck A: Digit 2 - D",		60  | CNT_INTVAL },
+	{ "LED: Deck A: Digit 2 - E",		59  | CNT_INTVAL },
+	{ "LED: Deck A: Digit 2 - F",		58  | CNT_INTVAL },
+	{ "LED: Deck A: Digit 2 - G",		57  | CNT_INTVAL },
+	{ "LED: Deck A: Digit 2 - dot",		56  | CNT_INTVAL },
+
+	{ "LED: Deck B: 1 (blue)",		78  | CNT_INTVAL },
+	{ "LED: Deck B: 1 (green)",		79  | CNT_INTVAL },
+	{ "LED: Deck B: 2 (blue)",		76  | CNT_INTVAL },
+	{ "LED: Deck B: 2 (green)",		77  | CNT_INTVAL },
+	{ "LED: Deck B: 3 (blue)",		74  | CNT_INTVAL },
+	{ "LED: Deck B: 3 (green)",		75  | CNT_INTVAL },
+	{ "LED: Deck B: 4 (blue)",		72  | CNT_INTVAL },
+	{ "LED: Deck B: 4 (green)",		73  | CNT_INTVAL },
+	{ "LED: Deck B: Load",			180 | CNT_INTVAL },
+	{ "LED: Deck B: Deck D button",		181 | CNT_INTVAL },
+	{ "LED: Deck B: In",			183 | CNT_INTVAL },
+	{ "LED: Deck B: Out",			182 | CNT_INTVAL },
+	{ "LED: Deck B: Shift",			64  | CNT_INTVAL },
+	{ "LED: Deck B: Sync",			67  | CNT_INTVAL },
+	{ "LED: Deck B: Cue",			66  | CNT_INTVAL },
+	{ "LED: Deck B: Play",			65  | CNT_INTVAL },
+	{ "LED: Deck B: Tempo up",		185 | CNT_INTVAL },
+	{ "LED: Deck B: Tempo down",		184 | CNT_INTVAL },
+	{ "LED: Deck B: Master",		186 | CNT_INTVAL },
+	{ "LED: Deck B: Keylock",		187 | CNT_INTVAL },
+	{ "LED: Deck B: Deck B",		189 | CNT_INTVAL },
+	{ "LED: Deck B: Deck D",		188 | CNT_INTVAL },
+	{ "LED: Deck B: Samples",		190 | CNT_INTVAL },
+	{ "LED: Deck B: On Air",		191 | CNT_INTVAL },
+	{ "LED: Deck B: Sample 1",		71  | CNT_INTVAL },
+	{ "LED: Deck B: Sample 2",		70  | CNT_INTVAL },
+	{ "LED: Deck B: Sample 3",		69  | CNT_INTVAL },
+	{ "LED: Deck B: Sample 4",		68  | CNT_INTVAL },
+	{ "LED: Deck B: Digit 1 - A",		175 | CNT_INTVAL },
+	{ "LED: Deck B: Digit 1 - B",		174 | CNT_INTVAL },
+	{ "LED: Deck B: Digit 1 - C",		173 | CNT_INTVAL },
+	{ "LED: Deck B: Digit 1 - D",		172 | CNT_INTVAL },
+	{ "LED: Deck B: Digit 1 - E",		171 | CNT_INTVAL },
+	{ "LED: Deck B: Digit 1 - F",		170 | CNT_INTVAL },
+	{ "LED: Deck B: Digit 1 - G",		169 | CNT_INTVAL },
+	{ "LED: Deck B: Digit 1 - dot",		168 | CNT_INTVAL },
+	{ "LED: Deck B: Digit 2 - A",		167 | CNT_INTVAL },
+	{ "LED: Deck B: Digit 2 - B",		166 | CNT_INTVAL },
+	{ "LED: Deck B: Digit 2 - C",		165 | CNT_INTVAL },
+	{ "LED: Deck B: Digit 2 - D",		164 | CNT_INTVAL },
+	{ "LED: Deck B: Digit 2 - E",		163 | CNT_INTVAL },
+	{ "LED: Deck B: Digit 2 - F",		162 | CNT_INTVAL },
+	{ "LED: Deck B: Digit 2 - G",		161 | CNT_INTVAL },
+	{ "LED: Deck B: Digit 2 - dot",		160 | CNT_INTVAL },
+
+	{ "LED: FX1: dry/wet",			153 | CNT_INTVAL },
+	{ "LED: FX1: 1",			154 | CNT_INTVAL },
+	{ "LED: FX1: 2",			155 | CNT_INTVAL },
+	{ "LED: FX1: 3",			156 | CNT_INTVAL },
+	{ "LED: FX1: Mode",			157 | CNT_INTVAL },
+	{ "LED: FX2: dry/wet",			129 | CNT_INTVAL },
+	{ "LED: FX2: 1",			130 | CNT_INTVAL },
+	{ "LED: FX2: 2",			131 | CNT_INTVAL },
+	{ "LED: FX2: 3",			132 | CNT_INTVAL },
+	{ "LED: FX2: Mode",			133 | CNT_INTVAL },
+};
+
+static struct caiaq_controller maschine_controller[] = {
+	{ "LED: Pad 1",				3  | CNT_INTVAL },
+	{ "LED: Pad 2",				2  | CNT_INTVAL },
+	{ "LED: Pad 3",				1  | CNT_INTVAL },
+	{ "LED: Pad 4",				0  | CNT_INTVAL },
+	{ "LED: Pad 5",				7  | CNT_INTVAL },
+	{ "LED: Pad 6",				6  | CNT_INTVAL },
+	{ "LED: Pad 7",				5  | CNT_INTVAL },
+	{ "LED: Pad 8",				4  | CNT_INTVAL },
+	{ "LED: Pad 9",				11 | CNT_INTVAL },
+	{ "LED: Pad 10",			10 | CNT_INTVAL },
+	{ "LED: Pad 11",			9  | CNT_INTVAL },
+	{ "LED: Pad 12",			8  | CNT_INTVAL },
+	{ "LED: Pad 13",			15 | CNT_INTVAL },
+	{ "LED: Pad 14",			14 | CNT_INTVAL },
+	{ "LED: Pad 15",			13 | CNT_INTVAL },
+	{ "LED: Pad 16",			12 | CNT_INTVAL },
+
+	{ "LED: Mute",				16 | CNT_INTVAL },
+	{ "LED: Solo",				17 | CNT_INTVAL },
+	{ "LED: Select",			18 | CNT_INTVAL },
+	{ "LED: Duplicate",			19 | CNT_INTVAL },
+	{ "LED: Navigate",			20 | CNT_INTVAL },
+	{ "LED: Pad Mode",			21 | CNT_INTVAL },
+	{ "LED: Pattern",			22 | CNT_INTVAL },
+	{ "LED: Scene",				23 | CNT_INTVAL },
+
+	{ "LED: Shift",				24 | CNT_INTVAL },
+	{ "LED: Erase",				25 | CNT_INTVAL },
+	{ "LED: Grid",				26 | CNT_INTVAL },
+	{ "LED: Right Bottom",			27 | CNT_INTVAL },
+	{ "LED: Rec",				28 | CNT_INTVAL },
+	{ "LED: Play",				29 | CNT_INTVAL },
+	{ "LED: Left Bottom",			32 | CNT_INTVAL },
+	{ "LED: Restart",			33 | CNT_INTVAL },
+
+	{ "LED: Group A",			41 | CNT_INTVAL },
+	{ "LED: Group B",			40 | CNT_INTVAL },
+	{ "LED: Group C",			37 | CNT_INTVAL },
+	{ "LED: Group D",			36 | CNT_INTVAL },
+	{ "LED: Group E",			39 | CNT_INTVAL },
+	{ "LED: Group F",			38 | CNT_INTVAL },
+	{ "LED: Group G",			35 | CNT_INTVAL },
+	{ "LED: Group H",			34 | CNT_INTVAL },
+
+	{ "LED: Auto Write",			42 | CNT_INTVAL },
+	{ "LED: Snap",				43 | CNT_INTVAL },
+	{ "LED: Right Top",			44 | CNT_INTVAL },
+	{ "LED: Left Top",			45 | CNT_INTVAL },
+	{ "LED: Sampling",			46 | CNT_INTVAL },
+	{ "LED: Browse",			47 | CNT_INTVAL },
+	{ "LED: Step",				48 | CNT_INTVAL },
+	{ "LED: Control",			49 | CNT_INTVAL },
+
+	{ "LED: Top Button 1",			57 | CNT_INTVAL },
+	{ "LED: Top Button 2",			56 | CNT_INTVAL },
+	{ "LED: Top Button 3",			55 | CNT_INTVAL },
+	{ "LED: Top Button 4",			54 | CNT_INTVAL },
+	{ "LED: Top Button 5",			53 | CNT_INTVAL },
+	{ "LED: Top Button 6",			52 | CNT_INTVAL },
+	{ "LED: Top Button 7",			51 | CNT_INTVAL },
+	{ "LED: Top Button 8",			50 | CNT_INTVAL },
+
+	{ "LED: Note Repeat",			58 | CNT_INTVAL },
+
+	{ "Backlight Display",			59 | CNT_INTVAL }
+};
+
+static int add_controls(struct caiaq_controller *c, int num,
+			struct snd_usb_caiaqdev *cdev)
+{
+	int i, ret;
+	struct snd_kcontrol *kc;
+
+	for (i = 0; i < num; i++, c++) {
+		kcontrol_template.name = c->name;
+		kcontrol_template.private_value = c->index;
+		kc = snd_ctl_new1(&kcontrol_template, cdev);
+		ret = snd_ctl_add(cdev->chip.card, kc);
+		if (ret < 0)
+			return ret;
+	}
+
+	return 0;
+}
+
+int snd_usb_caiaq_control_init(struct snd_usb_caiaqdev *cdev)
+{
+	int ret = 0;
+
+	switch (cdev->chip.usb_id) {
+	case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_AK1):
+		ret = add_controls(ak1_controller,
+			ARRAY_SIZE(ak1_controller), cdev);
+		break;
+
+	case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_RIGKONTROL2):
+		ret = add_controls(rk2_controller,
+			ARRAY_SIZE(rk2_controller), cdev);
+		break;
+
+	case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_RIGKONTROL3):
+		ret = add_controls(rk3_controller,
+			ARRAY_SIZE(rk3_controller), cdev);
+		break;
+
+	case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_KORECONTROLLER):
+	case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_KORECONTROLLER2):
+		ret = add_controls(kore_controller,
+			ARRAY_SIZE(kore_controller), cdev);
+		break;
+
+	case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_AUDIO8DJ):
+		ret = add_controls(a8dj_controller,
+			ARRAY_SIZE(a8dj_controller), cdev);
+		break;
+
+	case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_AUDIO4DJ):
+		ret = add_controls(a4dj_controller,
+			ARRAY_SIZE(a4dj_controller), cdev);
+		break;
+
+	case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_TRAKTORKONTROLX1):
+		ret = add_controls(kontrolx1_controller,
+			ARRAY_SIZE(kontrolx1_controller), cdev);
+		break;
+
+	case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_TRAKTORKONTROLS4):
+		ret = add_controls(kontrols4_controller,
+			ARRAY_SIZE(kontrols4_controller), cdev);
+		break;
+
+	case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_MASCHINECONTROLLER):
+		ret = add_controls(maschine_controller,
+			ARRAY_SIZE(maschine_controller), cdev);
+		break;
+	}
+
+	return ret;
+}
+
diff --git a/sound/usb/caiaq/control.h b/sound/usb/caiaq/control.h
new file mode 100644
index 0000000..cb204fd
--- /dev/null
+++ b/sound/usb/caiaq/control.h
@@ -0,0 +1,7 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef CAIAQ_CONTROL_H
+#define CAIAQ_CONTROL_H
+
+int snd_usb_caiaq_control_init(struct snd_usb_caiaqdev *cdev);
+
+#endif /* CAIAQ_CONTROL_H */
diff --git a/sound/usb/caiaq/device.c b/sound/usb/caiaq/device.c
new file mode 100644
index 0000000..d55ca48
--- /dev/null
+++ b/sound/usb/caiaq/device.c
@@ -0,0 +1,583 @@
+/*
+ * caiaq.c: ALSA driver for caiaq/NativeInstruments devices
+ *
+ *   Copyright (c) 2007 Daniel Mack <daniel@caiaq.de>
+ *                      Karsten Wiese <fzu@wemgehoertderstaat.de>
+ *
+ *   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/moduleparam.h>
+#include <linux/device.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/gfp.h>
+#include <linux/usb.h>
+#include <sound/initval.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+
+#include "device.h"
+#include "audio.h"
+#include "midi.h"
+#include "control.h"
+#include "input.h"
+
+MODULE_AUTHOR("Daniel Mack <daniel@caiaq.de>");
+MODULE_DESCRIPTION("caiaq USB audio");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("{{Native Instruments,RigKontrol2},"
+			 "{Native Instruments,RigKontrol3},"
+			 "{Native Instruments,Kore Controller},"
+			 "{Native Instruments,Kore Controller 2},"
+			 "{Native Instruments,Audio Kontrol 1},"
+			 "{Native Instruments,Audio 2 DJ},"
+			 "{Native Instruments,Audio 4 DJ},"
+			 "{Native Instruments,Audio 8 DJ},"
+			 "{Native Instruments,Traktor Audio 2},"
+			 "{Native Instruments,Session I/O},"
+			 "{Native Instruments,GuitarRig mobile},"
+			 "{Native Instruments,Traktor Kontrol X1},"
+			 "{Native Instruments,Traktor Kontrol S4},"
+			 "{Native Instruments,Maschine Controller}}");
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 0-max */
+static char* id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* Id for this card */
+static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP; /* Enable this card */
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for the caiaq sound device");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for the caiaq soundcard.");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "Enable the caiaq soundcard.");
+
+enum {
+	SAMPLERATE_44100	= 0,
+	SAMPLERATE_48000	= 1,
+	SAMPLERATE_96000	= 2,
+	SAMPLERATE_192000	= 3,
+	SAMPLERATE_88200	= 4,
+	SAMPLERATE_INVALID	= 0xff
+};
+
+enum {
+	DEPTH_NONE	= 0,
+	DEPTH_16	= 1,
+	DEPTH_24	= 2,
+	DEPTH_32	= 3
+};
+
+static const struct usb_device_id snd_usb_id_table[] = {
+	{
+		.match_flags =	USB_DEVICE_ID_MATCH_DEVICE,
+		.idVendor =	USB_VID_NATIVEINSTRUMENTS,
+		.idProduct =	USB_PID_RIGKONTROL2
+	},
+	{
+		.match_flags =	USB_DEVICE_ID_MATCH_DEVICE,
+		.idVendor =	USB_VID_NATIVEINSTRUMENTS,
+		.idProduct =	USB_PID_RIGKONTROL3
+	},
+	{
+		.match_flags =	USB_DEVICE_ID_MATCH_DEVICE,
+		.idVendor =	USB_VID_NATIVEINSTRUMENTS,
+		.idProduct =	USB_PID_KORECONTROLLER
+	},
+	{
+		.match_flags =	USB_DEVICE_ID_MATCH_DEVICE,
+		.idVendor =	USB_VID_NATIVEINSTRUMENTS,
+		.idProduct =	USB_PID_KORECONTROLLER2
+	},
+	{
+		.match_flags =  USB_DEVICE_ID_MATCH_DEVICE,
+		.idVendor =     USB_VID_NATIVEINSTRUMENTS,
+		.idProduct =    USB_PID_AK1
+	},
+	{
+		.match_flags =  USB_DEVICE_ID_MATCH_DEVICE,
+		.idVendor =     USB_VID_NATIVEINSTRUMENTS,
+		.idProduct =    USB_PID_AUDIO8DJ
+	},
+	{
+		.match_flags =  USB_DEVICE_ID_MATCH_DEVICE,
+		.idVendor =     USB_VID_NATIVEINSTRUMENTS,
+		.idProduct =    USB_PID_SESSIONIO
+	},
+	{
+		.match_flags =  USB_DEVICE_ID_MATCH_DEVICE,
+		.idVendor =     USB_VID_NATIVEINSTRUMENTS,
+		.idProduct =    USB_PID_GUITARRIGMOBILE
+	},
+	{
+		.match_flags =  USB_DEVICE_ID_MATCH_DEVICE,
+		.idVendor =     USB_VID_NATIVEINSTRUMENTS,
+		.idProduct =    USB_PID_AUDIO4DJ
+	},
+	{
+		.match_flags =  USB_DEVICE_ID_MATCH_DEVICE,
+		.idVendor =     USB_VID_NATIVEINSTRUMENTS,
+		.idProduct =    USB_PID_AUDIO2DJ
+	},
+	{
+		.match_flags =  USB_DEVICE_ID_MATCH_DEVICE,
+		.idVendor =     USB_VID_NATIVEINSTRUMENTS,
+		.idProduct =    USB_PID_TRAKTORKONTROLX1
+	},
+	{
+		.match_flags =  USB_DEVICE_ID_MATCH_DEVICE,
+		.idVendor =     USB_VID_NATIVEINSTRUMENTS,
+		.idProduct =    USB_PID_TRAKTORKONTROLS4
+	},
+	{
+		.match_flags =  USB_DEVICE_ID_MATCH_DEVICE,
+		.idVendor =     USB_VID_NATIVEINSTRUMENTS,
+		.idProduct =    USB_PID_TRAKTORAUDIO2
+	},
+	{
+		.match_flags =  USB_DEVICE_ID_MATCH_DEVICE,
+		.idVendor =     USB_VID_NATIVEINSTRUMENTS,
+		.idProduct =    USB_PID_MASCHINECONTROLLER
+	},
+	{ /* terminator */ }
+};
+
+static void usb_ep1_command_reply_dispatch (struct urb* urb)
+{
+	int ret;
+	struct device *dev = &urb->dev->dev;
+	struct snd_usb_caiaqdev *cdev = urb->context;
+	unsigned char *buf = urb->transfer_buffer;
+
+	if (urb->status || !cdev) {
+		dev_warn(dev, "received EP1 urb->status = %i\n", urb->status);
+		return;
+	}
+
+	switch(buf[0]) {
+	case EP1_CMD_GET_DEVICE_INFO:
+	 	memcpy(&cdev->spec, buf+1, sizeof(struct caiaq_device_spec));
+		cdev->spec.fw_version = le16_to_cpu(cdev->spec.fw_version);
+		dev_dbg(dev, "device spec (firmware %d): audio: %d in, %d out, "
+			"MIDI: %d in, %d out, data alignment %d\n",
+			cdev->spec.fw_version,
+			cdev->spec.num_analog_audio_in,
+			cdev->spec.num_analog_audio_out,
+			cdev->spec.num_midi_in,
+			cdev->spec.num_midi_out,
+			cdev->spec.data_alignment);
+
+		cdev->spec_received++;
+		wake_up(&cdev->ep1_wait_queue);
+		break;
+	case EP1_CMD_AUDIO_PARAMS:
+		cdev->audio_parm_answer = buf[1];
+		wake_up(&cdev->ep1_wait_queue);
+		break;
+	case EP1_CMD_MIDI_READ:
+		snd_usb_caiaq_midi_handle_input(cdev, buf[1], buf + 3, buf[2]);
+		break;
+	case EP1_CMD_READ_IO:
+		if (cdev->chip.usb_id ==
+			USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_AUDIO8DJ)) {
+			if (urb->actual_length > sizeof(cdev->control_state))
+				urb->actual_length = sizeof(cdev->control_state);
+			memcpy(cdev->control_state, buf + 1, urb->actual_length);
+			wake_up(&cdev->ep1_wait_queue);
+			break;
+		}
+#ifdef CONFIG_SND_USB_CAIAQ_INPUT
+	case EP1_CMD_READ_ERP:
+	case EP1_CMD_READ_ANALOG:
+		snd_usb_caiaq_input_dispatch(cdev, buf, urb->actual_length);
+#endif
+		break;
+	}
+
+	cdev->ep1_in_urb.actual_length = 0;
+	ret = usb_submit_urb(&cdev->ep1_in_urb, GFP_ATOMIC);
+	if (ret < 0)
+		dev_err(dev, "unable to submit urb. OOM!?\n");
+}
+
+int snd_usb_caiaq_send_command(struct snd_usb_caiaqdev *cdev,
+			       unsigned char command,
+			       const unsigned char *buffer,
+			       int len)
+{
+	int actual_len;
+	struct usb_device *usb_dev = cdev->chip.dev;
+
+	if (!usb_dev)
+		return -EIO;
+
+	if (len > EP1_BUFSIZE - 1)
+		len = EP1_BUFSIZE - 1;
+
+	if (buffer && len > 0)
+		memcpy(cdev->ep1_out_buf+1, buffer, len);
+
+	cdev->ep1_out_buf[0] = command;
+	return usb_bulk_msg(usb_dev, usb_sndbulkpipe(usb_dev, 1),
+			   cdev->ep1_out_buf, len+1, &actual_len, 200);
+}
+
+int snd_usb_caiaq_send_command_bank(struct snd_usb_caiaqdev *cdev,
+			       unsigned char command,
+			       unsigned char bank,
+			       const unsigned char *buffer,
+			       int len)
+{
+	int actual_len;
+	struct usb_device *usb_dev = cdev->chip.dev;
+
+	if (!usb_dev)
+		return -EIO;
+
+	if (len > EP1_BUFSIZE - 2)
+		len = EP1_BUFSIZE - 2;
+
+	if (buffer && len > 0)
+		memcpy(cdev->ep1_out_buf+2, buffer, len);
+
+	cdev->ep1_out_buf[0] = command;
+	cdev->ep1_out_buf[1] = bank;
+
+	return usb_bulk_msg(usb_dev, usb_sndbulkpipe(usb_dev, 1),
+			   cdev->ep1_out_buf, len+2, &actual_len, 200);
+}
+
+int snd_usb_caiaq_set_audio_params (struct snd_usb_caiaqdev *cdev,
+		   		    int rate, int depth, int bpp)
+{
+	int ret;
+	char tmp[5];
+	struct device *dev = caiaqdev_to_dev(cdev);
+
+	switch (rate) {
+	case 44100:	tmp[0] = SAMPLERATE_44100;   break;
+	case 48000:	tmp[0] = SAMPLERATE_48000;   break;
+	case 88200:	tmp[0] = SAMPLERATE_88200;   break;
+	case 96000:	tmp[0] = SAMPLERATE_96000;   break;
+	case 192000:	tmp[0] = SAMPLERATE_192000;  break;
+	default:	return -EINVAL;
+	}
+
+	switch (depth) {
+	case 16:	tmp[1] = DEPTH_16;   break;
+	case 24:	tmp[1] = DEPTH_24;   break;
+	default:	return -EINVAL;
+	}
+
+	tmp[2] = bpp & 0xff;
+	tmp[3] = bpp >> 8;
+	tmp[4] = 1; /* packets per microframe */
+
+	dev_dbg(dev, "setting audio params: %d Hz, %d bits, %d bpp\n",
+		rate, depth, bpp);
+
+	cdev->audio_parm_answer = -1;
+	ret = snd_usb_caiaq_send_command(cdev, EP1_CMD_AUDIO_PARAMS,
+					 tmp, sizeof(tmp));
+
+	if (ret)
+		return ret;
+
+	if (!wait_event_timeout(cdev->ep1_wait_queue,
+	    cdev->audio_parm_answer >= 0, HZ))
+		return -EPIPE;
+
+	if (cdev->audio_parm_answer != 1)
+		dev_dbg(dev, "unable to set the device's audio params\n");
+	else
+		cdev->bpp = bpp;
+
+	return cdev->audio_parm_answer == 1 ? 0 : -EINVAL;
+}
+
+int snd_usb_caiaq_set_auto_msg(struct snd_usb_caiaqdev *cdev,
+			       int digital, int analog, int erp)
+{
+	char tmp[3] = { digital, analog, erp };
+	return snd_usb_caiaq_send_command(cdev, EP1_CMD_AUTO_MSG,
+					  tmp, sizeof(tmp));
+}
+
+static void setup_card(struct snd_usb_caiaqdev *cdev)
+{
+	int ret;
+	char val[4];
+	struct device *dev = caiaqdev_to_dev(cdev);
+
+	/* device-specific startup specials */
+	switch (cdev->chip.usb_id) {
+	case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_RIGKONTROL2):
+		/* RigKontrol2 - display centered dash ('-') */
+		val[0] = 0x00;
+		val[1] = 0x00;
+		val[2] = 0x01;
+		snd_usb_caiaq_send_command(cdev, EP1_CMD_WRITE_IO, val, 3);
+		break;
+	case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_RIGKONTROL3):
+		/* RigKontrol2 - display two centered dashes ('--') */
+		val[0] = 0x00;
+		val[1] = 0x40;
+		val[2] = 0x40;
+		val[3] = 0x00;
+		snd_usb_caiaq_send_command(cdev, EP1_CMD_WRITE_IO, val, 4);
+		break;
+	case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_AK1):
+		/* Audio Kontrol 1 - make USB-LED stop blinking */
+		val[0] = 0x00;
+		snd_usb_caiaq_send_command(cdev, EP1_CMD_WRITE_IO, val, 1);
+		break;
+	case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_AUDIO8DJ):
+		/* Audio 8 DJ - trigger read of current settings */
+		cdev->control_state[0] = 0xff;
+		snd_usb_caiaq_set_auto_msg(cdev, 1, 0, 0);
+		snd_usb_caiaq_send_command(cdev, EP1_CMD_READ_IO, NULL, 0);
+
+		if (!wait_event_timeout(cdev->ep1_wait_queue,
+					cdev->control_state[0] != 0xff, HZ))
+			return;
+
+		/* fix up some defaults */
+		if ((cdev->control_state[1] != 2) ||
+		    (cdev->control_state[2] != 3) ||
+		    (cdev->control_state[4] != 2)) {
+			cdev->control_state[1] = 2;
+			cdev->control_state[2] = 3;
+			cdev->control_state[4] = 2;
+			snd_usb_caiaq_send_command(cdev,
+				EP1_CMD_WRITE_IO, cdev->control_state, 6);
+		}
+
+		break;
+	}
+
+	if (cdev->spec.num_analog_audio_out +
+	    cdev->spec.num_analog_audio_in +
+	    cdev->spec.num_digital_audio_out +
+	    cdev->spec.num_digital_audio_in > 0) {
+		ret = snd_usb_caiaq_audio_init(cdev);
+		if (ret < 0)
+			dev_err(dev, "Unable to set up audio system (ret=%d)\n", ret);
+	}
+
+	if (cdev->spec.num_midi_in +
+	    cdev->spec.num_midi_out > 0) {
+		ret = snd_usb_caiaq_midi_init(cdev);
+		if (ret < 0)
+			dev_err(dev, "Unable to set up MIDI system (ret=%d)\n", ret);
+	}
+
+#ifdef CONFIG_SND_USB_CAIAQ_INPUT
+	ret = snd_usb_caiaq_input_init(cdev);
+	if (ret < 0)
+		dev_err(dev, "Unable to set up input system (ret=%d)\n", ret);
+#endif
+
+	/* finally, register the card and all its sub-instances */
+	ret = snd_card_register(cdev->chip.card);
+	if (ret < 0) {
+		dev_err(dev, "snd_card_register() returned %d\n", ret);
+		snd_card_free(cdev->chip.card);
+	}
+
+	ret = snd_usb_caiaq_control_init(cdev);
+	if (ret < 0)
+		dev_err(dev, "Unable to set up control system (ret=%d)\n", ret);
+}
+
+static int create_card(struct usb_device *usb_dev,
+		       struct usb_interface *intf,
+		       struct snd_card **cardp)
+{
+	int devnum;
+	int err;
+	struct snd_card *card;
+	struct snd_usb_caiaqdev *cdev;
+
+	for (devnum = 0; devnum < SNDRV_CARDS; devnum++)
+		if (enable[devnum])
+			break;
+
+	if (devnum >= SNDRV_CARDS)
+		return -ENODEV;
+
+	err = snd_card_new(&intf->dev,
+			   index[devnum], id[devnum], THIS_MODULE,
+			   sizeof(struct snd_usb_caiaqdev), &card);
+	if (err < 0)
+		return err;
+
+	cdev = caiaqdev(card);
+	cdev->chip.dev = usb_dev;
+	cdev->chip.card = card;
+	cdev->chip.usb_id = USB_ID(le16_to_cpu(usb_dev->descriptor.idVendor),
+				  le16_to_cpu(usb_dev->descriptor.idProduct));
+	spin_lock_init(&cdev->spinlock);
+
+	*cardp = card;
+	return 0;
+}
+
+static int init_card(struct snd_usb_caiaqdev *cdev)
+{
+	char *c, usbpath[32];
+	struct usb_device *usb_dev = cdev->chip.dev;
+	struct snd_card *card = cdev->chip.card;
+	struct device *dev = caiaqdev_to_dev(cdev);
+	int err, len;
+
+	if (usb_set_interface(usb_dev, 0, 1) != 0) {
+		dev_err(dev, "can't set alt interface.\n");
+		return -EIO;
+	}
+
+	usb_init_urb(&cdev->ep1_in_urb);
+	usb_init_urb(&cdev->midi_out_urb);
+
+	usb_fill_bulk_urb(&cdev->ep1_in_urb, usb_dev,
+			  usb_rcvbulkpipe(usb_dev, 0x1),
+			  cdev->ep1_in_buf, EP1_BUFSIZE,
+			  usb_ep1_command_reply_dispatch, cdev);
+
+	usb_fill_bulk_urb(&cdev->midi_out_urb, usb_dev,
+			  usb_sndbulkpipe(usb_dev, 0x1),
+			  cdev->midi_out_buf, EP1_BUFSIZE,
+			  snd_usb_caiaq_midi_output_done, cdev);
+
+	/* sanity checks of EPs before actually submitting */
+	if (usb_urb_ep_type_check(&cdev->ep1_in_urb) ||
+	    usb_urb_ep_type_check(&cdev->midi_out_urb)) {
+		dev_err(dev, "invalid EPs\n");
+		return -EINVAL;
+	}
+
+	init_waitqueue_head(&cdev->ep1_wait_queue);
+	init_waitqueue_head(&cdev->prepare_wait_queue);
+
+	if (usb_submit_urb(&cdev->ep1_in_urb, GFP_KERNEL) != 0)
+		return -EIO;
+
+	err = snd_usb_caiaq_send_command(cdev, EP1_CMD_GET_DEVICE_INFO, NULL, 0);
+	if (err)
+		goto err_kill_urb;
+
+	if (!wait_event_timeout(cdev->ep1_wait_queue, cdev->spec_received, HZ)) {
+		err = -ENODEV;
+		goto err_kill_urb;
+	}
+
+	usb_string(usb_dev, usb_dev->descriptor.iManufacturer,
+		   cdev->vendor_name, CAIAQ_USB_STR_LEN);
+
+	usb_string(usb_dev, usb_dev->descriptor.iProduct,
+		   cdev->product_name, CAIAQ_USB_STR_LEN);
+
+	strlcpy(card->driver, MODNAME, sizeof(card->driver));
+	strlcpy(card->shortname, cdev->product_name, sizeof(card->shortname));
+	strlcpy(card->mixername, cdev->product_name, sizeof(card->mixername));
+
+	/* if the id was not passed as module option, fill it with a shortened
+	 * version of the product string which does not contain any
+	 * whitespaces */
+
+	if (*card->id == '\0') {
+		char id[sizeof(card->id)];
+
+		memset(id, 0, sizeof(id));
+
+		for (c = card->shortname, len = 0;
+			*c && len < sizeof(card->id); c++)
+			if (*c != ' ')
+				id[len++] = *c;
+
+		snd_card_set_id(card, id);
+	}
+
+	usb_make_path(usb_dev, usbpath, sizeof(usbpath));
+	snprintf(card->longname, sizeof(card->longname), "%s %s (%s)",
+		       cdev->vendor_name, cdev->product_name, usbpath);
+
+	setup_card(cdev);
+	return 0;
+
+ err_kill_urb:
+	usb_kill_urb(&cdev->ep1_in_urb);
+	return err;
+}
+
+static int snd_probe(struct usb_interface *intf,
+		     const struct usb_device_id *id)
+{
+	int ret;
+	struct snd_card *card = NULL;
+	struct usb_device *usb_dev = interface_to_usbdev(intf);
+
+	ret = create_card(usb_dev, intf, &card);
+
+	if (ret < 0)
+		return ret;
+
+	usb_set_intfdata(intf, card);
+	ret = init_card(caiaqdev(card));
+	if (ret < 0) {
+		dev_err(&usb_dev->dev, "unable to init card! (ret=%d)\n", ret);
+		snd_card_free(card);
+		return ret;
+	}
+
+	return 0;
+}
+
+static void snd_disconnect(struct usb_interface *intf)
+{
+	struct snd_card *card = usb_get_intfdata(intf);
+	struct device *dev = intf->usb_dev;
+	struct snd_usb_caiaqdev *cdev;
+
+	if (!card)
+		return;
+
+	cdev = caiaqdev(card);
+	dev_dbg(dev, "%s(%p)\n", __func__, intf);
+
+	snd_card_disconnect(card);
+
+#ifdef CONFIG_SND_USB_CAIAQ_INPUT
+	snd_usb_caiaq_input_free(cdev);
+#endif
+	snd_usb_caiaq_audio_free(cdev);
+
+	usb_kill_urb(&cdev->ep1_in_urb);
+	usb_kill_urb(&cdev->midi_out_urb);
+
+	snd_card_free(card);
+	usb_reset_device(interface_to_usbdev(intf));
+}
+
+
+MODULE_DEVICE_TABLE(usb, snd_usb_id_table);
+static struct usb_driver snd_usb_driver = {
+	.name 		= MODNAME,
+	.probe 		= snd_probe,
+	.disconnect	= snd_disconnect,
+	.id_table 	= snd_usb_id_table,
+};
+
+module_usb_driver(snd_usb_driver);
diff --git a/sound/usb/caiaq/device.h b/sound/usb/caiaq/device.h
new file mode 100644
index 0000000..50fea08
--- /dev/null
+++ b/sound/usb/caiaq/device.h
@@ -0,0 +1,138 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef CAIAQ_DEVICE_H
+#define CAIAQ_DEVICE_H
+
+#include "../usbaudio.h"
+
+#define USB_VID_NATIVEINSTRUMENTS 0x17cc
+
+#define USB_PID_RIGKONTROL2		0x1969
+#define USB_PID_RIGKONTROL3		0x1940
+#define USB_PID_KORECONTROLLER		0x4711
+#define USB_PID_KORECONTROLLER2		0x4712
+#define USB_PID_AK1			0x0815
+#define USB_PID_AUDIO2DJ		0x041c
+#define USB_PID_AUDIO4DJ		0x0839
+#define USB_PID_AUDIO8DJ		0x1978
+#define USB_PID_SESSIONIO		0x1915
+#define USB_PID_GUITARRIGMOBILE		0x0d8d
+#define USB_PID_TRAKTORKONTROLX1	0x2305
+#define USB_PID_TRAKTORKONTROLS4	0xbaff
+#define USB_PID_TRAKTORAUDIO2		0x041d
+#define USB_PID_MASCHINECONTROLLER  0x0808
+
+#define EP1_BUFSIZE 64
+#define EP4_BUFSIZE 512
+#define CAIAQ_USB_STR_LEN 0xff
+#define MAX_STREAMS 32
+
+#define MODNAME "snd-usb-caiaq"
+
+#define EP1_CMD_GET_DEVICE_INFO	0x1
+#define EP1_CMD_READ_ERP	0x2
+#define EP1_CMD_READ_ANALOG	0x3
+#define EP1_CMD_READ_IO		0x4
+#define EP1_CMD_WRITE_IO	0x5
+#define EP1_CMD_MIDI_READ	0x6
+#define EP1_CMD_MIDI_WRITE	0x7
+#define EP1_CMD_AUDIO_PARAMS	0x9
+#define EP1_CMD_AUTO_MSG	0xb
+#define EP1_CMD_DIMM_LEDS       0xc
+
+struct caiaq_device_spec {
+	unsigned short fw_version;
+	unsigned char hw_subtype;
+	unsigned char num_erp;
+	unsigned char num_analog_in;
+	unsigned char num_digital_in;
+	unsigned char num_digital_out;
+	unsigned char num_analog_audio_out;
+	unsigned char num_analog_audio_in;
+	unsigned char num_digital_audio_out;
+	unsigned char num_digital_audio_in;
+	unsigned char num_midi_out;
+	unsigned char num_midi_in;
+	unsigned char data_alignment;
+} __attribute__ ((packed));
+
+struct snd_usb_caiaq_cb_info;
+
+struct snd_usb_caiaqdev {
+	struct snd_usb_audio chip;
+
+	struct urb ep1_in_urb;
+	struct urb midi_out_urb;
+	struct urb **data_urbs_in;
+	struct urb **data_urbs_out;
+	struct snd_usb_caiaq_cb_info *data_cb_info;
+
+	unsigned char ep1_in_buf[EP1_BUFSIZE];
+	unsigned char ep1_out_buf[EP1_BUFSIZE];
+	unsigned char midi_out_buf[EP1_BUFSIZE];
+
+	struct caiaq_device_spec spec;
+	spinlock_t spinlock;
+	wait_queue_head_t ep1_wait_queue;
+	wait_queue_head_t prepare_wait_queue;
+	int spec_received, audio_parm_answer;
+	int midi_out_active;
+
+	char vendor_name[CAIAQ_USB_STR_LEN];
+	char product_name[CAIAQ_USB_STR_LEN];
+
+	int n_streams, n_audio_in, n_audio_out;
+	int streaming, first_packet, output_running;
+	int audio_in_buf_pos[MAX_STREAMS];
+	int audio_out_buf_pos[MAX_STREAMS];
+	int period_in_count[MAX_STREAMS];
+	int period_out_count[MAX_STREAMS];
+	int input_panic, output_panic, warned;
+	char *audio_in_buf, *audio_out_buf;
+	unsigned int samplerates, bpp;
+	unsigned long outurb_active_mask;
+
+	struct snd_pcm_substream *sub_playback[MAX_STREAMS];
+	struct snd_pcm_substream *sub_capture[MAX_STREAMS];
+
+	/* Controls */
+	unsigned char control_state[256];
+	unsigned char ep8_out_buf[2];
+
+	/* Linux input */
+#ifdef CONFIG_SND_USB_CAIAQ_INPUT
+	struct input_dev *input_dev;
+	char phys[64];			/* physical device path */
+	unsigned short keycode[128];
+	struct urb *ep4_in_urb;
+	unsigned char ep4_in_buf[EP4_BUFSIZE];
+#endif
+
+	/* ALSA */
+	struct snd_pcm *pcm;
+	struct snd_pcm_hardware pcm_info;
+	struct snd_rawmidi *rmidi;
+	struct snd_rawmidi_substream *midi_receive_substream;
+	struct snd_rawmidi_substream *midi_out_substream;
+};
+
+struct snd_usb_caiaq_cb_info {
+	struct snd_usb_caiaqdev *cdev;
+	int index;
+};
+
+#define caiaqdev(c) ((struct snd_usb_caiaqdev*)(c)->private_data)
+#define caiaqdev_to_dev(d)	(d->chip.card->dev)
+
+int snd_usb_caiaq_set_audio_params (struct snd_usb_caiaqdev *cdev, int rate, int depth, int bbp);
+int snd_usb_caiaq_set_auto_msg (struct snd_usb_caiaqdev *cdev, int digital, int analog, int erp);
+int snd_usb_caiaq_send_command(struct snd_usb_caiaqdev *cdev,
+			       unsigned char command,
+			       const unsigned char *buffer,
+			       int len);
+int snd_usb_caiaq_send_command_bank(struct snd_usb_caiaqdev *cdev,
+			       unsigned char command,
+			       unsigned char bank,
+			       const unsigned char *buffer,
+			       int len);
+
+#endif /* CAIAQ_DEVICE_H */
diff --git a/sound/usb/caiaq/input.c b/sound/usb/caiaq/input.c
new file mode 100644
index 0000000..e883659
--- /dev/null
+++ b/sound/usb/caiaq/input.c
@@ -0,0 +1,855 @@
+/*
+ *   Copyright (c) 2006,2007 Daniel Mack, Tim Ruetz
+ *
+ *   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/device.h>
+#include <linux/gfp.h>
+#include <linux/init.h>
+#include <linux/usb.h>
+#include <linux/usb/input.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+
+#include "device.h"
+#include "input.h"
+
+static unsigned short keycode_ak1[] =  { KEY_C, KEY_B, KEY_A };
+static unsigned short keycode_rk2[] =  { KEY_1, KEY_2, KEY_3, KEY_4,
+					 KEY_5, KEY_6, KEY_7 };
+static unsigned short keycode_rk3[] =  { KEY_1, KEY_2, KEY_3, KEY_4,
+					 KEY_5, KEY_6, KEY_7, KEY_8, KEY_9 };
+
+static unsigned short keycode_kore[] = {
+	KEY_FN_F1,      /* "menu"               */
+	KEY_FN_F7,      /* "lcd backlight       */
+	KEY_FN_F2,      /* "control"            */
+	KEY_FN_F3,      /* "enter"              */
+	KEY_FN_F4,      /* "view"               */
+	KEY_FN_F5,      /* "esc"                */
+	KEY_FN_F6,      /* "sound"              */
+	KEY_FN_F8,      /* array spacer, never triggered. */
+	KEY_RIGHT,
+	KEY_DOWN,
+	KEY_UP,
+	KEY_LEFT,
+	KEY_SOUND,      /* "listen"             */
+	KEY_RECORD,
+	KEY_PLAYPAUSE,
+	KEY_STOP,
+	BTN_4,          /* 8 softkeys */
+	BTN_3,
+	BTN_2,
+	BTN_1,
+	BTN_8,
+	BTN_7,
+	BTN_6,
+	BTN_5,
+	KEY_BRL_DOT4,   /* touch sensitive knobs */
+	KEY_BRL_DOT3,
+	KEY_BRL_DOT2,
+	KEY_BRL_DOT1,
+	KEY_BRL_DOT8,
+	KEY_BRL_DOT7,
+	KEY_BRL_DOT6,
+	KEY_BRL_DOT5
+};
+
+#define MASCHINE_BUTTONS   (42)
+#define MASCHINE_BUTTON(X) ((X) + BTN_MISC)
+#define MASCHINE_PADS      (16)
+#define MASCHINE_PAD(X)    ((X) + ABS_PRESSURE)
+
+static unsigned short keycode_maschine[] = {
+	MASCHINE_BUTTON(40), /* mute       */
+	MASCHINE_BUTTON(39), /* solo       */
+	MASCHINE_BUTTON(38), /* select     */
+	MASCHINE_BUTTON(37), /* duplicate  */
+	MASCHINE_BUTTON(36), /* navigate   */
+	MASCHINE_BUTTON(35), /* pad mode   */
+	MASCHINE_BUTTON(34), /* pattern    */
+	MASCHINE_BUTTON(33), /* scene      */
+	KEY_RESERVED, /* spacer */
+
+	MASCHINE_BUTTON(30), /* rec        */
+	MASCHINE_BUTTON(31), /* erase      */
+	MASCHINE_BUTTON(32), /* shift      */
+	MASCHINE_BUTTON(28), /* grid       */
+	MASCHINE_BUTTON(27), /* >          */
+	MASCHINE_BUTTON(26), /* <          */
+	MASCHINE_BUTTON(25), /* restart    */
+
+	MASCHINE_BUTTON(21), /* E          */
+	MASCHINE_BUTTON(22), /* F          */
+	MASCHINE_BUTTON(23), /* G          */
+	MASCHINE_BUTTON(24), /* H          */
+	MASCHINE_BUTTON(20), /* D          */
+	MASCHINE_BUTTON(19), /* C          */
+	MASCHINE_BUTTON(18), /* B          */
+	MASCHINE_BUTTON(17), /* A          */
+
+	MASCHINE_BUTTON(0),  /* control    */
+	MASCHINE_BUTTON(2),  /* browse     */
+	MASCHINE_BUTTON(4),  /* <          */
+	MASCHINE_BUTTON(6),  /* snap       */
+	MASCHINE_BUTTON(7),  /* autowrite  */
+	MASCHINE_BUTTON(5),  /* >          */
+	MASCHINE_BUTTON(3),  /* sampling   */
+	MASCHINE_BUTTON(1),  /* step       */
+
+	MASCHINE_BUTTON(15), /* 8 softkeys */
+	MASCHINE_BUTTON(14),
+	MASCHINE_BUTTON(13),
+	MASCHINE_BUTTON(12),
+	MASCHINE_BUTTON(11),
+	MASCHINE_BUTTON(10),
+	MASCHINE_BUTTON(9),
+	MASCHINE_BUTTON(8),
+
+	MASCHINE_BUTTON(16), /* note repeat */
+	MASCHINE_BUTTON(29)  /* play        */
+};
+
+#define KONTROLX1_INPUTS	(40)
+#define KONTROLS4_BUTTONS	(12 * 8)
+#define KONTROLS4_AXIS		(46)
+
+#define KONTROLS4_BUTTON(X)	((X) + BTN_MISC)
+#define KONTROLS4_ABS(X)	((X) + ABS_HAT0X)
+
+#define DEG90		(range / 2)
+#define DEG180		(range)
+#define DEG270		(DEG90 + DEG180)
+#define DEG360		(DEG180 * 2)
+#define HIGH_PEAK	(268)
+#define LOW_PEAK	(-7)
+
+/* some of these devices have endless rotation potentiometers
+ * built in which use two tapers, 90 degrees phase shifted.
+ * this algorithm decodes them to one single value, ranging
+ * from 0 to 999 */
+static unsigned int decode_erp(unsigned char a, unsigned char b)
+{
+	int weight_a, weight_b;
+	int pos_a, pos_b;
+	int ret;
+	int range = HIGH_PEAK - LOW_PEAK;
+	int mid_value = (HIGH_PEAK + LOW_PEAK) / 2;
+
+	weight_b = abs(mid_value - a) - (range / 2 - 100) / 2;
+
+	if (weight_b < 0)
+		weight_b = 0;
+
+	if (weight_b > 100)
+		weight_b = 100;
+
+	weight_a = 100 - weight_b;
+
+	if (a < mid_value) {
+		/* 0..90 and 270..360 degrees */
+		pos_b = b - LOW_PEAK + DEG270;
+		if (pos_b >= DEG360)
+			pos_b -= DEG360;
+	} else
+		/* 90..270 degrees */
+		pos_b = HIGH_PEAK - b + DEG90;
+
+
+	if (b > mid_value)
+		/* 0..180 degrees */
+		pos_a = a - LOW_PEAK;
+	else
+		/* 180..360 degrees */
+		pos_a = HIGH_PEAK - a + DEG180;
+
+	/* interpolate both slider values, depending on weight factors */
+	/* 0..99 x DEG360 */
+	ret = pos_a * weight_a + pos_b * weight_b;
+
+	/* normalize to 0..999 */
+	ret *= 10;
+	ret /= DEG360;
+
+	if (ret < 0)
+		ret += 1000;
+
+	if (ret >= 1000)
+		ret -= 1000;
+
+	return ret;
+}
+
+#undef DEG90
+#undef DEG180
+#undef DEG270
+#undef DEG360
+#undef HIGH_PEAK
+#undef LOW_PEAK
+
+static inline void snd_caiaq_input_report_abs(struct snd_usb_caiaqdev *cdev,
+					      int axis, const unsigned char *buf,
+					      int offset)
+{
+	input_report_abs(cdev->input_dev, axis,
+			 (buf[offset * 2] << 8) | buf[offset * 2 + 1]);
+}
+
+static void snd_caiaq_input_read_analog(struct snd_usb_caiaqdev *cdev,
+					const unsigned char *buf,
+					unsigned int len)
+{
+	struct input_dev *input_dev = cdev->input_dev;
+
+	switch (cdev->chip.usb_id) {
+	case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_RIGKONTROL2):
+		snd_caiaq_input_report_abs(cdev, ABS_X, buf, 2);
+		snd_caiaq_input_report_abs(cdev, ABS_Y, buf, 0);
+		snd_caiaq_input_report_abs(cdev, ABS_Z, buf, 1);
+		break;
+	case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_RIGKONTROL3):
+	case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_KORECONTROLLER):
+	case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_KORECONTROLLER2):
+		snd_caiaq_input_report_abs(cdev, ABS_X, buf, 0);
+		snd_caiaq_input_report_abs(cdev, ABS_Y, buf, 1);
+		snd_caiaq_input_report_abs(cdev, ABS_Z, buf, 2);
+		break;
+	case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_TRAKTORKONTROLX1):
+		snd_caiaq_input_report_abs(cdev, ABS_HAT0X, buf, 4);
+		snd_caiaq_input_report_abs(cdev, ABS_HAT0Y, buf, 2);
+		snd_caiaq_input_report_abs(cdev, ABS_HAT1X, buf, 6);
+		snd_caiaq_input_report_abs(cdev, ABS_HAT1Y, buf, 1);
+		snd_caiaq_input_report_abs(cdev, ABS_HAT2X, buf, 7);
+		snd_caiaq_input_report_abs(cdev, ABS_HAT2Y, buf, 0);
+		snd_caiaq_input_report_abs(cdev, ABS_HAT3X, buf, 5);
+		snd_caiaq_input_report_abs(cdev, ABS_HAT3Y, buf, 3);
+		break;
+	}
+
+	input_sync(input_dev);
+}
+
+static void snd_caiaq_input_read_erp(struct snd_usb_caiaqdev *cdev,
+				     const char *buf, unsigned int len)
+{
+	struct input_dev *input_dev = cdev->input_dev;
+	int i;
+
+	switch (cdev->chip.usb_id) {
+	case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_AK1):
+		i = decode_erp(buf[0], buf[1]);
+		input_report_abs(input_dev, ABS_X, i);
+		input_sync(input_dev);
+		break;
+	case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_KORECONTROLLER):
+	case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_KORECONTROLLER2):
+		i = decode_erp(buf[7], buf[5]);
+		input_report_abs(input_dev, ABS_HAT0X, i);
+		i = decode_erp(buf[12], buf[14]);
+		input_report_abs(input_dev, ABS_HAT0Y, i);
+		i = decode_erp(buf[15], buf[13]);
+		input_report_abs(input_dev, ABS_HAT1X, i);
+		i = decode_erp(buf[0], buf[2]);
+		input_report_abs(input_dev, ABS_HAT1Y, i);
+		i = decode_erp(buf[3], buf[1]);
+		input_report_abs(input_dev, ABS_HAT2X, i);
+		i = decode_erp(buf[8], buf[10]);
+		input_report_abs(input_dev, ABS_HAT2Y, i);
+		i = decode_erp(buf[11], buf[9]);
+		input_report_abs(input_dev, ABS_HAT3X, i);
+		i = decode_erp(buf[4], buf[6]);
+		input_report_abs(input_dev, ABS_HAT3Y, i);
+		input_sync(input_dev);
+		break;
+
+	case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_MASCHINECONTROLLER):
+		/* 4 under the left screen */
+		input_report_abs(input_dev, ABS_HAT0X, decode_erp(buf[21], buf[20]));
+		input_report_abs(input_dev, ABS_HAT0Y, decode_erp(buf[15], buf[14]));
+		input_report_abs(input_dev, ABS_HAT1X, decode_erp(buf[9],  buf[8]));
+		input_report_abs(input_dev, ABS_HAT1Y, decode_erp(buf[3],  buf[2]));
+
+		/* 4 under the right screen */
+		input_report_abs(input_dev, ABS_HAT2X, decode_erp(buf[19], buf[18]));
+		input_report_abs(input_dev, ABS_HAT2Y, decode_erp(buf[13], buf[12]));
+		input_report_abs(input_dev, ABS_HAT3X, decode_erp(buf[7],  buf[6]));
+		input_report_abs(input_dev, ABS_HAT3Y, decode_erp(buf[1],  buf[0]));
+
+		/* volume */
+		input_report_abs(input_dev, ABS_RX, decode_erp(buf[17], buf[16]));
+		/* tempo */
+		input_report_abs(input_dev, ABS_RY, decode_erp(buf[11], buf[10]));
+		/* swing */
+		input_report_abs(input_dev, ABS_RZ, decode_erp(buf[5],  buf[4]));
+
+		input_sync(input_dev);
+		break;
+	}
+}
+
+static void snd_caiaq_input_read_io(struct snd_usb_caiaqdev *cdev,
+				    unsigned char *buf, unsigned int len)
+{
+	struct input_dev *input_dev = cdev->input_dev;
+	unsigned short *keycode = input_dev->keycode;
+	int i;
+
+	if (!keycode)
+		return;
+
+	if (input_dev->id.product == USB_PID_RIGKONTROL2)
+		for (i = 0; i < len; i++)
+			buf[i] = ~buf[i];
+
+	for (i = 0; i < input_dev->keycodemax && i < len * 8; i++)
+		input_report_key(input_dev, keycode[i],
+				 buf[i / 8] & (1 << (i % 8)));
+
+	switch (cdev->chip.usb_id) {
+	case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_KORECONTROLLER):
+	case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_KORECONTROLLER2):
+		input_report_abs(cdev->input_dev, ABS_MISC, 255 - buf[4]);
+		break;
+	case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_TRAKTORKONTROLX1):
+		/* rotary encoders */
+		input_report_abs(cdev->input_dev, ABS_X, buf[5] & 0xf);
+		input_report_abs(cdev->input_dev, ABS_Y, buf[5] >> 4);
+		input_report_abs(cdev->input_dev, ABS_Z, buf[6] & 0xf);
+		input_report_abs(cdev->input_dev, ABS_MISC, buf[6] >> 4);
+		break;
+	}
+
+	input_sync(input_dev);
+}
+
+#define TKS4_MSGBLOCK_SIZE	16
+
+static void snd_usb_caiaq_tks4_dispatch(struct snd_usb_caiaqdev *cdev,
+					const unsigned char *buf,
+					unsigned int len)
+{
+	struct device *dev = caiaqdev_to_dev(cdev);
+
+	while (len) {
+		unsigned int i, block_id = (buf[0] << 8) | buf[1];
+
+		switch (block_id) {
+		case 0:
+			/* buttons */
+			for (i = 0; i < KONTROLS4_BUTTONS; i++)
+				input_report_key(cdev->input_dev, KONTROLS4_BUTTON(i),
+						 (buf[4 + (i / 8)] >> (i % 8)) & 1);
+			break;
+
+		case 1:
+			/* left wheel */
+			input_report_abs(cdev->input_dev, KONTROLS4_ABS(36), buf[9] | ((buf[8] & 0x3) << 8));
+			/* right wheel */
+			input_report_abs(cdev->input_dev, KONTROLS4_ABS(37), buf[13] | ((buf[12] & 0x3) << 8));
+
+			/* rotary encoders */
+			input_report_abs(cdev->input_dev, KONTROLS4_ABS(38), buf[3] & 0xf);
+			input_report_abs(cdev->input_dev, KONTROLS4_ABS(39), buf[4] >> 4);
+			input_report_abs(cdev->input_dev, KONTROLS4_ABS(40), buf[4] & 0xf);
+			input_report_abs(cdev->input_dev, KONTROLS4_ABS(41), buf[5] >> 4);
+			input_report_abs(cdev->input_dev, KONTROLS4_ABS(42), buf[5] & 0xf);
+			input_report_abs(cdev->input_dev, KONTROLS4_ABS(43), buf[6] >> 4);
+			input_report_abs(cdev->input_dev, KONTROLS4_ABS(44), buf[6] & 0xf);
+			input_report_abs(cdev->input_dev, KONTROLS4_ABS(45), buf[7] >> 4);
+			input_report_abs(cdev->input_dev, KONTROLS4_ABS(46), buf[7] & 0xf);
+
+			break;
+		case 2:
+			/* Volume Fader Channel D */
+			snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(0), buf, 1);
+			/* Volume Fader Channel B */
+			snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(1), buf, 2);
+			/* Volume Fader Channel A */
+			snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(2), buf, 3);
+			/* Volume Fader Channel C */
+			snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(3), buf, 4);
+			/* Loop Volume */
+			snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(4), buf, 6);
+			/* Crossfader */
+			snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(7), buf, 7);
+
+			break;
+
+		case 3:
+			/* Tempo Fader R */
+			snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(6), buf, 3);
+			/* Tempo Fader L */
+			snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(5), buf, 4);
+			/* Mic Volume */
+			snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(8), buf, 6);
+			/* Cue Mix */
+			snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(9), buf, 7);
+
+			break;
+
+		case 4:
+			/* Wheel distance sensor L */
+			snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(10), buf, 1);
+			/* Wheel distance sensor R */
+			snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(11), buf, 2);
+			/* Channel D EQ - Filter */
+			snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(12), buf, 3);
+			/* Channel D EQ - Low */
+			snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(13), buf, 4);
+			/* Channel D EQ - Mid */
+			snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(14), buf, 5);
+			/* Channel D EQ - Hi */
+			snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(15), buf, 6);
+			/* FX2 - dry/wet */
+			snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(16), buf, 7);
+
+			break;
+
+		case 5:
+			/* FX2 - 1 */
+			snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(17), buf, 1);
+			/* FX2 - 2 */
+			snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(18), buf, 2);
+			/* FX2 - 3 */
+			snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(19), buf, 3);
+			/* Channel B EQ - Filter */
+			snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(20), buf, 4);
+			/* Channel B EQ - Low */
+			snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(21), buf, 5);
+			/* Channel B EQ - Mid */
+			snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(22), buf, 6);
+			/* Channel B EQ - Hi */
+			snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(23), buf, 7);
+
+			break;
+
+		case 6:
+			/* Channel A EQ - Filter */
+			snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(24), buf, 1);
+			/* Channel A EQ - Low */
+			snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(25), buf, 2);
+			/* Channel A EQ - Mid */
+			snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(26), buf, 3);
+			/* Channel A EQ - Hi */
+			snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(27), buf, 4);
+			/* Channel C EQ - Filter */
+			snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(28), buf, 5);
+			/* Channel C EQ - Low */
+			snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(29), buf, 6);
+			/* Channel C EQ - Mid */
+			snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(30), buf, 7);
+
+			break;
+
+		case 7:
+			/* Channel C EQ - Hi */
+			snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(31), buf, 1);
+			/* FX1 - wet/dry */
+			snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(32), buf, 2);
+			/* FX1 - 1 */
+			snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(33), buf, 3);
+			/* FX1 - 2 */
+			snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(34), buf, 4);
+			/* FX1 - 3 */
+			snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(35), buf, 5);
+
+			break;
+
+		default:
+			dev_dbg(dev, "%s(): bogus block (id %d)\n",
+				__func__, block_id);
+			return;
+		}
+
+		len -= TKS4_MSGBLOCK_SIZE;
+		buf += TKS4_MSGBLOCK_SIZE;
+	}
+
+	input_sync(cdev->input_dev);
+}
+
+#define MASCHINE_MSGBLOCK_SIZE 2
+
+static void snd_usb_caiaq_maschine_dispatch(struct snd_usb_caiaqdev *cdev,
+					const unsigned char *buf,
+					unsigned int len)
+{
+	unsigned int i, pad_id;
+	__le16 *pressure = (__le16 *) buf;
+
+	for (i = 0; i < MASCHINE_PADS; i++) {
+		pad_id = le16_to_cpu(*pressure) >> 12;
+		input_report_abs(cdev->input_dev, MASCHINE_PAD(pad_id),
+				 le16_to_cpu(*pressure) & 0xfff);
+		pressure++;
+	}
+
+	input_sync(cdev->input_dev);
+}
+
+static void snd_usb_caiaq_ep4_reply_dispatch(struct urb *urb)
+{
+	struct snd_usb_caiaqdev *cdev = urb->context;
+	unsigned char *buf = urb->transfer_buffer;
+	struct device *dev = &urb->dev->dev;
+	int ret;
+
+	if (urb->status || !cdev || urb != cdev->ep4_in_urb)
+		return;
+
+	switch (cdev->chip.usb_id) {
+	case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_TRAKTORKONTROLX1):
+		if (urb->actual_length < 24)
+			goto requeue;
+
+		if (buf[0] & 0x3)
+			snd_caiaq_input_read_io(cdev, buf + 1, 7);
+
+		if (buf[0] & 0x4)
+			snd_caiaq_input_read_analog(cdev, buf + 8, 16);
+
+		break;
+
+	case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_TRAKTORKONTROLS4):
+		snd_usb_caiaq_tks4_dispatch(cdev, buf, urb->actual_length);
+		break;
+
+	case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_MASCHINECONTROLLER):
+		if (urb->actual_length < (MASCHINE_PADS * MASCHINE_MSGBLOCK_SIZE))
+			goto requeue;
+
+		snd_usb_caiaq_maschine_dispatch(cdev, buf, urb->actual_length);
+		break;
+	}
+
+requeue:
+	cdev->ep4_in_urb->actual_length = 0;
+	ret = usb_submit_urb(cdev->ep4_in_urb, GFP_ATOMIC);
+	if (ret < 0)
+		dev_err(dev, "unable to submit urb. OOM!?\n");
+}
+
+static int snd_usb_caiaq_input_open(struct input_dev *idev)
+{
+	struct snd_usb_caiaqdev *cdev = input_get_drvdata(idev);
+
+	if (!cdev)
+		return -EINVAL;
+
+	switch (cdev->chip.usb_id) {
+	case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_TRAKTORKONTROLX1):
+	case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_TRAKTORKONTROLS4):
+	case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_MASCHINECONTROLLER):
+		if (usb_submit_urb(cdev->ep4_in_urb, GFP_KERNEL) != 0)
+			return -EIO;
+		break;
+	}
+
+	return 0;
+}
+
+static void snd_usb_caiaq_input_close(struct input_dev *idev)
+{
+	struct snd_usb_caiaqdev *cdev = input_get_drvdata(idev);
+
+	if (!cdev)
+		return;
+
+	switch (cdev->chip.usb_id) {
+	case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_TRAKTORKONTROLX1):
+	case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_TRAKTORKONTROLS4):
+	case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_MASCHINECONTROLLER):
+		usb_kill_urb(cdev->ep4_in_urb);
+		break;
+	}
+}
+
+void snd_usb_caiaq_input_dispatch(struct snd_usb_caiaqdev *cdev,
+				  char *buf,
+				  unsigned int len)
+{
+	if (!cdev->input_dev || len < 1)
+		return;
+
+	switch (buf[0]) {
+	case EP1_CMD_READ_ANALOG:
+		snd_caiaq_input_read_analog(cdev, buf + 1, len - 1);
+		break;
+	case EP1_CMD_READ_ERP:
+		snd_caiaq_input_read_erp(cdev, buf + 1, len - 1);
+		break;
+	case EP1_CMD_READ_IO:
+		snd_caiaq_input_read_io(cdev, buf + 1, len - 1);
+		break;
+	}
+}
+
+int snd_usb_caiaq_input_init(struct snd_usb_caiaqdev *cdev)
+{
+	struct usb_device *usb_dev = cdev->chip.dev;
+	struct input_dev *input;
+	int i, ret = 0;
+
+	input = input_allocate_device();
+	if (!input)
+		return -ENOMEM;
+
+	usb_make_path(usb_dev, cdev->phys, sizeof(cdev->phys));
+	strlcat(cdev->phys, "/input0", sizeof(cdev->phys));
+
+	input->name = cdev->product_name;
+	input->phys = cdev->phys;
+	usb_to_input_id(usb_dev, &input->id);
+	input->dev.parent = &usb_dev->dev;
+
+	input_set_drvdata(input, cdev);
+
+	switch (cdev->chip.usb_id) {
+	case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_RIGKONTROL2):
+		input->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS);
+		input->absbit[0] = BIT_MASK(ABS_X) | BIT_MASK(ABS_Y) |
+			BIT_MASK(ABS_Z);
+		BUILD_BUG_ON(sizeof(cdev->keycode) < sizeof(keycode_rk2));
+		memcpy(cdev->keycode, keycode_rk2, sizeof(keycode_rk2));
+		input->keycodemax = ARRAY_SIZE(keycode_rk2);
+		input_set_abs_params(input, ABS_X, 0, 4096, 0, 10);
+		input_set_abs_params(input, ABS_Y, 0, 4096, 0, 10);
+		input_set_abs_params(input, ABS_Z, 0, 4096, 0, 10);
+		snd_usb_caiaq_set_auto_msg(cdev, 1, 10, 0);
+		break;
+	case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_RIGKONTROL3):
+		input->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS);
+		input->absbit[0] = BIT_MASK(ABS_X) | BIT_MASK(ABS_Y) |
+			BIT_MASK(ABS_Z);
+		BUILD_BUG_ON(sizeof(cdev->keycode) < sizeof(keycode_rk3));
+		memcpy(cdev->keycode, keycode_rk3, sizeof(keycode_rk3));
+		input->keycodemax = ARRAY_SIZE(keycode_rk3);
+		input_set_abs_params(input, ABS_X, 0, 1024, 0, 10);
+		input_set_abs_params(input, ABS_Y, 0, 1024, 0, 10);
+		input_set_abs_params(input, ABS_Z, 0, 1024, 0, 10);
+		snd_usb_caiaq_set_auto_msg(cdev, 1, 10, 0);
+		break;
+	case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_AK1):
+		input->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS);
+		input->absbit[0] = BIT_MASK(ABS_X);
+		BUILD_BUG_ON(sizeof(cdev->keycode) < sizeof(keycode_ak1));
+		memcpy(cdev->keycode, keycode_ak1, sizeof(keycode_ak1));
+		input->keycodemax = ARRAY_SIZE(keycode_ak1);
+		input_set_abs_params(input, ABS_X, 0, 999, 0, 10);
+		snd_usb_caiaq_set_auto_msg(cdev, 1, 0, 5);
+		break;
+	case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_KORECONTROLLER):
+	case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_KORECONTROLLER2):
+		input->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS);
+		input->absbit[0] = BIT_MASK(ABS_HAT0X) | BIT_MASK(ABS_HAT0Y) |
+				   BIT_MASK(ABS_HAT1X) | BIT_MASK(ABS_HAT1Y) |
+				   BIT_MASK(ABS_HAT2X) | BIT_MASK(ABS_HAT2Y) |
+				   BIT_MASK(ABS_HAT3X) | BIT_MASK(ABS_HAT3Y) |
+				   BIT_MASK(ABS_X) | BIT_MASK(ABS_Y) |
+				   BIT_MASK(ABS_Z);
+		input->absbit[BIT_WORD(ABS_MISC)] |= BIT_MASK(ABS_MISC);
+		BUILD_BUG_ON(sizeof(cdev->keycode) < sizeof(keycode_kore));
+		memcpy(cdev->keycode, keycode_kore, sizeof(keycode_kore));
+		input->keycodemax = ARRAY_SIZE(keycode_kore);
+		input_set_abs_params(input, ABS_HAT0X, 0, 999, 0, 10);
+		input_set_abs_params(input, ABS_HAT0Y, 0, 999, 0, 10);
+		input_set_abs_params(input, ABS_HAT1X, 0, 999, 0, 10);
+		input_set_abs_params(input, ABS_HAT1Y, 0, 999, 0, 10);
+		input_set_abs_params(input, ABS_HAT2X, 0, 999, 0, 10);
+		input_set_abs_params(input, ABS_HAT2Y, 0, 999, 0, 10);
+		input_set_abs_params(input, ABS_HAT3X, 0, 999, 0, 10);
+		input_set_abs_params(input, ABS_HAT3Y, 0, 999, 0, 10);
+		input_set_abs_params(input, ABS_X, 0, 4096, 0, 10);
+		input_set_abs_params(input, ABS_Y, 0, 4096, 0, 10);
+		input_set_abs_params(input, ABS_Z, 0, 4096, 0, 10);
+		input_set_abs_params(input, ABS_MISC, 0, 255, 0, 1);
+		snd_usb_caiaq_set_auto_msg(cdev, 1, 10, 5);
+		break;
+	case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_TRAKTORKONTROLX1):
+		input->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS);
+		input->absbit[0] = BIT_MASK(ABS_HAT0X) | BIT_MASK(ABS_HAT0Y) |
+				   BIT_MASK(ABS_HAT1X) | BIT_MASK(ABS_HAT1Y) |
+				   BIT_MASK(ABS_HAT2X) | BIT_MASK(ABS_HAT2Y) |
+				   BIT_MASK(ABS_HAT3X) | BIT_MASK(ABS_HAT3Y) |
+				   BIT_MASK(ABS_X) | BIT_MASK(ABS_Y) |
+				   BIT_MASK(ABS_Z);
+		input->absbit[BIT_WORD(ABS_MISC)] |= BIT_MASK(ABS_MISC);
+		BUILD_BUG_ON(sizeof(cdev->keycode) < KONTROLX1_INPUTS);
+		for (i = 0; i < KONTROLX1_INPUTS; i++)
+			cdev->keycode[i] = BTN_MISC + i;
+		input->keycodemax = KONTROLX1_INPUTS;
+
+		/* analog potentiometers */
+		input_set_abs_params(input, ABS_HAT0X, 0, 4096, 0, 10);
+		input_set_abs_params(input, ABS_HAT0Y, 0, 4096, 0, 10);
+		input_set_abs_params(input, ABS_HAT1X, 0, 4096, 0, 10);
+		input_set_abs_params(input, ABS_HAT1Y, 0, 4096, 0, 10);
+		input_set_abs_params(input, ABS_HAT2X, 0, 4096, 0, 10);
+		input_set_abs_params(input, ABS_HAT2Y, 0, 4096, 0, 10);
+		input_set_abs_params(input, ABS_HAT3X, 0, 4096, 0, 10);
+		input_set_abs_params(input, ABS_HAT3Y, 0, 4096, 0, 10);
+
+		/* rotary encoders */
+		input_set_abs_params(input, ABS_X, 0, 0xf, 0, 1);
+		input_set_abs_params(input, ABS_Y, 0, 0xf, 0, 1);
+		input_set_abs_params(input, ABS_Z, 0, 0xf, 0, 1);
+		input_set_abs_params(input, ABS_MISC, 0, 0xf, 0, 1);
+
+		cdev->ep4_in_urb = usb_alloc_urb(0, GFP_KERNEL);
+		if (!cdev->ep4_in_urb) {
+			ret = -ENOMEM;
+			goto exit_free_idev;
+		}
+
+		usb_fill_bulk_urb(cdev->ep4_in_urb, usb_dev,
+				  usb_rcvbulkpipe(usb_dev, 0x4),
+				  cdev->ep4_in_buf, EP4_BUFSIZE,
+				  snd_usb_caiaq_ep4_reply_dispatch, cdev);
+		ret = usb_urb_ep_type_check(cdev->ep4_in_urb);
+		if (ret < 0)
+			goto exit_free_idev;
+
+		snd_usb_caiaq_set_auto_msg(cdev, 1, 10, 5);
+
+		break;
+
+	case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_TRAKTORKONTROLS4):
+		input->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS);
+		BUILD_BUG_ON(sizeof(cdev->keycode) < KONTROLS4_BUTTONS);
+		for (i = 0; i < KONTROLS4_BUTTONS; i++)
+			cdev->keycode[i] = KONTROLS4_BUTTON(i);
+		input->keycodemax = KONTROLS4_BUTTONS;
+
+		for (i = 0; i < KONTROLS4_AXIS; i++) {
+			int axis = KONTROLS4_ABS(i);
+			input->absbit[BIT_WORD(axis)] |= BIT_MASK(axis);
+		}
+
+		/* 36 analog potentiometers and faders */
+		for (i = 0; i < 36; i++)
+			input_set_abs_params(input, KONTROLS4_ABS(i), 0, 0xfff, 0, 10);
+
+		/* 2 encoder wheels */
+		input_set_abs_params(input, KONTROLS4_ABS(36), 0, 0x3ff, 0, 1);
+		input_set_abs_params(input, KONTROLS4_ABS(37), 0, 0x3ff, 0, 1);
+
+		/* 9 rotary encoders */
+		for (i = 0; i < 9; i++)
+			input_set_abs_params(input, KONTROLS4_ABS(38+i), 0, 0xf, 0, 1);
+
+		cdev->ep4_in_urb = usb_alloc_urb(0, GFP_KERNEL);
+		if (!cdev->ep4_in_urb) {
+			ret = -ENOMEM;
+			goto exit_free_idev;
+		}
+
+		usb_fill_bulk_urb(cdev->ep4_in_urb, usb_dev,
+				  usb_rcvbulkpipe(usb_dev, 0x4),
+				  cdev->ep4_in_buf, EP4_BUFSIZE,
+				  snd_usb_caiaq_ep4_reply_dispatch, cdev);
+		ret = usb_urb_ep_type_check(cdev->ep4_in_urb);
+		if (ret < 0)
+			goto exit_free_idev;
+
+		snd_usb_caiaq_set_auto_msg(cdev, 1, 10, 5);
+
+		break;
+
+	case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_MASCHINECONTROLLER):
+		input->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS);
+		input->absbit[0] = BIT_MASK(ABS_HAT0X) | BIT_MASK(ABS_HAT0Y) |
+			BIT_MASK(ABS_HAT1X) | BIT_MASK(ABS_HAT1Y) |
+			BIT_MASK(ABS_HAT2X) | BIT_MASK(ABS_HAT2Y) |
+			BIT_MASK(ABS_HAT3X) | BIT_MASK(ABS_HAT3Y) |
+			BIT_MASK(ABS_RX) | BIT_MASK(ABS_RY) |
+			BIT_MASK(ABS_RZ);
+
+		BUILD_BUG_ON(sizeof(cdev->keycode) < sizeof(keycode_maschine));
+		memcpy(cdev->keycode, keycode_maschine, sizeof(keycode_maschine));
+		input->keycodemax = ARRAY_SIZE(keycode_maschine);
+
+		for (i = 0; i < MASCHINE_PADS; i++) {
+			input->absbit[0] |= MASCHINE_PAD(i);
+			input_set_abs_params(input, MASCHINE_PAD(i), 0, 0xfff, 5, 10);
+		}
+
+		input_set_abs_params(input, ABS_HAT0X, 0, 999, 0, 10);
+		input_set_abs_params(input, ABS_HAT0Y, 0, 999, 0, 10);
+		input_set_abs_params(input, ABS_HAT1X, 0, 999, 0, 10);
+		input_set_abs_params(input, ABS_HAT1Y, 0, 999, 0, 10);
+		input_set_abs_params(input, ABS_HAT2X, 0, 999, 0, 10);
+		input_set_abs_params(input, ABS_HAT2Y, 0, 999, 0, 10);
+		input_set_abs_params(input, ABS_HAT3X, 0, 999, 0, 10);
+		input_set_abs_params(input, ABS_HAT3Y, 0, 999, 0, 10);
+		input_set_abs_params(input, ABS_RX, 0, 999, 0, 10);
+		input_set_abs_params(input, ABS_RY, 0, 999, 0, 10);
+		input_set_abs_params(input, ABS_RZ, 0, 999, 0, 10);
+
+		cdev->ep4_in_urb = usb_alloc_urb(0, GFP_KERNEL);
+		if (!cdev->ep4_in_urb) {
+			ret = -ENOMEM;
+			goto exit_free_idev;
+		}
+
+		usb_fill_bulk_urb(cdev->ep4_in_urb, usb_dev,
+				  usb_rcvbulkpipe(usb_dev, 0x4),
+				  cdev->ep4_in_buf, EP4_BUFSIZE,
+				  snd_usb_caiaq_ep4_reply_dispatch, cdev);
+		ret = usb_urb_ep_type_check(cdev->ep4_in_urb);
+		if (ret < 0)
+			goto exit_free_idev;
+
+		snd_usb_caiaq_set_auto_msg(cdev, 1, 10, 5);
+		break;
+
+	default:
+		/* no input methods supported on this device */
+		goto exit_free_idev;
+	}
+
+	input->open = snd_usb_caiaq_input_open;
+	input->close = snd_usb_caiaq_input_close;
+	input->keycode = cdev->keycode;
+	input->keycodesize = sizeof(unsigned short);
+	for (i = 0; i < input->keycodemax; i++)
+		__set_bit(cdev->keycode[i], input->keybit);
+
+	cdev->input_dev = input;
+
+	ret = input_register_device(input);
+	if (ret < 0)
+		goto exit_free_idev;
+
+	return 0;
+
+exit_free_idev:
+	input_free_device(input);
+	cdev->input_dev = NULL;
+	return ret;
+}
+
+void snd_usb_caiaq_input_free(struct snd_usb_caiaqdev *cdev)
+{
+	if (!cdev || !cdev->input_dev)
+		return;
+
+	usb_kill_urb(cdev->ep4_in_urb);
+	usb_free_urb(cdev->ep4_in_urb);
+	cdev->ep4_in_urb = NULL;
+
+	input_unregister_device(cdev->input_dev);
+	cdev->input_dev = NULL;
+}
diff --git a/sound/usb/caiaq/input.h b/sound/usb/caiaq/input.h
new file mode 100644
index 0000000..c42891e
--- /dev/null
+++ b/sound/usb/caiaq/input.h
@@ -0,0 +1,9 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef CAIAQ_INPUT_H
+#define CAIAQ_INPUT_H
+
+void snd_usb_caiaq_input_dispatch(struct snd_usb_caiaqdev *cdev, char *buf, unsigned int len);
+int snd_usb_caiaq_input_init(struct snd_usb_caiaqdev *cdev);
+void snd_usb_caiaq_input_free(struct snd_usb_caiaqdev *cdev);
+
+#endif
diff --git a/sound/usb/caiaq/midi.c b/sound/usb/caiaq/midi.c
new file mode 100644
index 0000000..f8e5b1b
--- /dev/null
+++ b/sound/usb/caiaq/midi.c
@@ -0,0 +1,175 @@
+/*
+ *   Copyright (c) 2006,2007 Daniel Mack
+ *
+ *   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/device.h>
+#include <linux/usb.h>
+#include <linux/gfp.h>
+#include <sound/rawmidi.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+
+#include "device.h"
+#include "midi.h"
+
+static int snd_usb_caiaq_midi_input_open(struct snd_rawmidi_substream *substream)
+{
+	return 0;
+}
+
+static int snd_usb_caiaq_midi_input_close(struct snd_rawmidi_substream *substream)
+{
+	return 0;
+}
+
+static void snd_usb_caiaq_midi_input_trigger(struct snd_rawmidi_substream *substream, int up)
+{
+	struct snd_usb_caiaqdev *cdev = substream->rmidi->private_data;
+
+	if (!cdev)
+		return;
+
+	cdev->midi_receive_substream = up ? substream : NULL;
+}
+
+
+static int snd_usb_caiaq_midi_output_open(struct snd_rawmidi_substream *substream)
+{
+	return 0;
+}
+
+static int snd_usb_caiaq_midi_output_close(struct snd_rawmidi_substream *substream)
+{
+	struct snd_usb_caiaqdev *cdev = substream->rmidi->private_data;
+	if (cdev->midi_out_active) {
+		usb_kill_urb(&cdev->midi_out_urb);
+		cdev->midi_out_active = 0;
+	}
+	return 0;
+}
+
+static void snd_usb_caiaq_midi_send(struct snd_usb_caiaqdev *cdev,
+				    struct snd_rawmidi_substream *substream)
+{
+	int len, ret;
+	struct device *dev = caiaqdev_to_dev(cdev);
+
+	cdev->midi_out_buf[0] = EP1_CMD_MIDI_WRITE;
+	cdev->midi_out_buf[1] = 0; /* port */
+	len = snd_rawmidi_transmit(substream, cdev->midi_out_buf + 3,
+				   EP1_BUFSIZE - 3);
+
+	if (len <= 0)
+		return;
+
+	cdev->midi_out_buf[2] = len;
+	cdev->midi_out_urb.transfer_buffer_length = len+3;
+
+	ret = usb_submit_urb(&cdev->midi_out_urb, GFP_ATOMIC);
+	if (ret < 0)
+		dev_err(dev,
+			"snd_usb_caiaq_midi_send(%p): usb_submit_urb() failed,"
+			"ret=%d, len=%d\n", substream, ret, len);
+	else
+		cdev->midi_out_active = 1;
+}
+
+static void snd_usb_caiaq_midi_output_trigger(struct snd_rawmidi_substream *substream, int up)
+{
+	struct snd_usb_caiaqdev *cdev = substream->rmidi->private_data;
+
+	if (up) {
+		cdev->midi_out_substream = substream;
+		if (!cdev->midi_out_active)
+			snd_usb_caiaq_midi_send(cdev, substream);
+	} else {
+		cdev->midi_out_substream = NULL;
+	}
+}
+
+
+static const struct snd_rawmidi_ops snd_usb_caiaq_midi_output =
+{
+	.open =		snd_usb_caiaq_midi_output_open,
+	.close =	snd_usb_caiaq_midi_output_close,
+	.trigger =      snd_usb_caiaq_midi_output_trigger,
+};
+
+static const struct snd_rawmidi_ops snd_usb_caiaq_midi_input =
+{
+	.open =		snd_usb_caiaq_midi_input_open,
+	.close =	snd_usb_caiaq_midi_input_close,
+	.trigger =      snd_usb_caiaq_midi_input_trigger,
+};
+
+void snd_usb_caiaq_midi_handle_input(struct snd_usb_caiaqdev *cdev,
+				     int port, const char *buf, int len)
+{
+	if (!cdev->midi_receive_substream)
+		return;
+
+	snd_rawmidi_receive(cdev->midi_receive_substream, buf, len);
+}
+
+int snd_usb_caiaq_midi_init(struct snd_usb_caiaqdev *device)
+{
+	int ret;
+	struct snd_rawmidi *rmidi;
+
+	ret = snd_rawmidi_new(device->chip.card, device->product_name, 0,
+					device->spec.num_midi_out,
+					device->spec.num_midi_in,
+					&rmidi);
+
+	if (ret < 0)
+		return ret;
+
+	strlcpy(rmidi->name, device->product_name, sizeof(rmidi->name));
+
+	rmidi->info_flags = SNDRV_RAWMIDI_INFO_DUPLEX;
+	rmidi->private_data = device;
+
+	if (device->spec.num_midi_out > 0) {
+		rmidi->info_flags |= SNDRV_RAWMIDI_INFO_OUTPUT;
+		snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT,
+				    &snd_usb_caiaq_midi_output);
+	}
+
+	if (device->spec.num_midi_in > 0) {
+		rmidi->info_flags |= SNDRV_RAWMIDI_INFO_INPUT;
+		snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT,
+				    &snd_usb_caiaq_midi_input);
+	}
+
+	device->rmidi = rmidi;
+
+	return 0;
+}
+
+void snd_usb_caiaq_midi_output_done(struct urb* urb)
+{
+	struct snd_usb_caiaqdev *cdev = urb->context;
+
+	cdev->midi_out_active = 0;
+	if (urb->status != 0)
+		return;
+
+	if (!cdev->midi_out_substream)
+		return;
+
+	snd_usb_caiaq_midi_send(cdev, cdev->midi_out_substream);
+}
diff --git a/sound/usb/caiaq/midi.h b/sound/usb/caiaq/midi.h
new file mode 100644
index 0000000..a6ae0c2
--- /dev/null
+++ b/sound/usb/caiaq/midi.h
@@ -0,0 +1,10 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef CAIAQ_MIDI_H
+#define CAIAQ_MIDI_H
+
+int snd_usb_caiaq_midi_init(struct snd_usb_caiaqdev *cdev);
+void snd_usb_caiaq_midi_handle_input(struct snd_usb_caiaqdev *cdev,
+				     int port, const char *buf, int len);
+void snd_usb_caiaq_midi_output_done(struct urb *urb);
+
+#endif /* CAIAQ_MIDI_H */
diff --git a/sound/usb/card.c b/sound/usb/card.c
new file mode 100644
index 0000000..a105947
--- /dev/null
+++ b/sound/usb/card.c
@@ -0,0 +1,911 @@
+/*
+ *   (Tentative) USB Audio Driver for ALSA
+ *
+ *   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)
+ *
+ *   Audio Class 3.0 support by Ruslan Bilovol <ruslan.bilovol@gmail.com>
+ *
+ *   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
+ *
+ *
+ *  NOTES:
+ *
+ *   - the linked URBs would be preferred but not used so far because of
+ *     the instability of unlinking.
+ *   - type II is not supported properly.  there is no device which supports
+ *     this type *correctly*.  SB extigy looks as if it supports, but it's
+ *     indeed an AC3 stream packed in SPDIF frames (i.e. no real AC3 stream).
+ */
+
+
+#include <linux/bitops.h>
+#include <linux/init.h>
+#include <linux/list.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <linux/ctype.h>
+#include <linux/usb.h>
+#include <linux/moduleparam.h>
+#include <linux/mutex.h>
+#include <linux/usb/audio.h>
+#include <linux/usb/audio-v2.h>
+#include <linux/usb/audio-v3.h>
+#include <linux/module.h>
+
+#include <sound/control.h>
+#include <sound/core.h>
+#include <sound/info.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/initval.h>
+
+#include "usbaudio.h"
+#include "card.h"
+#include "midi.h"
+#include "mixer.h"
+#include "proc.h"
+#include "quirks.h"
+#include "endpoint.h"
+#include "helper.h"
+#include "debug.h"
+#include "pcm.h"
+#include "format.h"
+#include "power.h"
+#include "stream.h"
+
+MODULE_AUTHOR("Takashi Iwai <tiwai@suse.de>");
+MODULE_DESCRIPTION("USB Audio");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("{{Generic,USB Audio}}");
+
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;	/* Index 0-MAX */
+static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;	/* ID for this card */
+static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP;/* Enable this card */
+/* Vendor/product IDs for this card */
+static int vid[SNDRV_CARDS] = { [0 ... (SNDRV_CARDS-1)] = -1 };
+static int pid[SNDRV_CARDS] = { [0 ... (SNDRV_CARDS-1)] = -1 };
+static int device_setup[SNDRV_CARDS]; /* device parameter for this card */
+static bool ignore_ctl_error;
+static bool autoclock = true;
+static char *quirk_alias[SNDRV_CARDS];
+
+bool snd_usb_use_vmalloc = true;
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for the USB audio adapter.");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for the USB audio adapter.");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "Enable USB audio adapter.");
+module_param_array(vid, int, NULL, 0444);
+MODULE_PARM_DESC(vid, "Vendor ID for the USB audio device.");
+module_param_array(pid, int, NULL, 0444);
+MODULE_PARM_DESC(pid, "Product ID for the USB audio device.");
+module_param_array(device_setup, int, NULL, 0444);
+MODULE_PARM_DESC(device_setup, "Specific device setup (if needed).");
+module_param(ignore_ctl_error, bool, 0444);
+MODULE_PARM_DESC(ignore_ctl_error,
+		 "Ignore errors from USB controller for mixer interfaces.");
+module_param(autoclock, bool, 0444);
+MODULE_PARM_DESC(autoclock, "Enable auto-clock selection for UAC2 devices (default: yes).");
+module_param_array(quirk_alias, charp, NULL, 0444);
+MODULE_PARM_DESC(quirk_alias, "Quirk aliases, e.g. 0123abcd:5678beef.");
+module_param_named(use_vmalloc, snd_usb_use_vmalloc, bool, 0444);
+MODULE_PARM_DESC(use_vmalloc, "Use vmalloc for PCM intermediate buffers (default: yes).");
+
+/*
+ * we keep the snd_usb_audio_t instances by ourselves for merging
+ * the all interfaces on the same card as one sound device.
+ */
+
+static DEFINE_MUTEX(register_mutex);
+static struct snd_usb_audio *usb_chip[SNDRV_CARDS];
+static struct usb_driver usb_audio_driver;
+
+/*
+ * disconnect streams
+ * called from usb_audio_disconnect()
+ */
+static void snd_usb_stream_disconnect(struct snd_usb_stream *as)
+{
+	int idx;
+	struct snd_usb_substream *subs;
+
+	for (idx = 0; idx < 2; idx++) {
+		subs = &as->substream[idx];
+		if (!subs->num_formats)
+			continue;
+		subs->interface = -1;
+		subs->data_endpoint = NULL;
+		subs->sync_endpoint = NULL;
+	}
+}
+
+static int snd_usb_create_stream(struct snd_usb_audio *chip, int ctrlif, int interface)
+{
+	struct usb_device *dev = chip->dev;
+	struct usb_host_interface *alts;
+	struct usb_interface_descriptor *altsd;
+	struct usb_interface *iface = usb_ifnum_to_if(dev, interface);
+
+	if (!iface) {
+		dev_err(&dev->dev, "%u:%d : does not exist\n",
+			ctrlif, interface);
+		return -EINVAL;
+	}
+
+	alts = &iface->altsetting[0];
+	altsd = get_iface_desc(alts);
+
+	/*
+	 * Android with both accessory and audio interfaces enabled gets the
+	 * interface numbers wrong.
+	 */
+	if ((chip->usb_id == USB_ID(0x18d1, 0x2d04) ||
+	     chip->usb_id == USB_ID(0x18d1, 0x2d05)) &&
+	    interface == 0 &&
+	    altsd->bInterfaceClass == USB_CLASS_VENDOR_SPEC &&
+	    altsd->bInterfaceSubClass == USB_SUBCLASS_VENDOR_SPEC) {
+		interface = 2;
+		iface = usb_ifnum_to_if(dev, interface);
+		if (!iface)
+			return -EINVAL;
+		alts = &iface->altsetting[0];
+		altsd = get_iface_desc(alts);
+	}
+
+	if (usb_interface_claimed(iface)) {
+		dev_dbg(&dev->dev, "%d:%d: skipping, already claimed\n",
+			ctrlif, interface);
+		return -EINVAL;
+	}
+
+	if ((altsd->bInterfaceClass == USB_CLASS_AUDIO ||
+	     altsd->bInterfaceClass == USB_CLASS_VENDOR_SPEC) &&
+	    altsd->bInterfaceSubClass == USB_SUBCLASS_MIDISTREAMING) {
+		int err = __snd_usbmidi_create(chip->card, iface,
+					     &chip->midi_list, NULL,
+					     chip->usb_id);
+		if (err < 0) {
+			dev_err(&dev->dev,
+				"%u:%d: cannot create sequencer device\n",
+				ctrlif, interface);
+			return -EINVAL;
+		}
+		usb_driver_claim_interface(&usb_audio_driver, iface, (void *)-1L);
+
+		return 0;
+	}
+
+	if ((altsd->bInterfaceClass != USB_CLASS_AUDIO &&
+	     altsd->bInterfaceClass != USB_CLASS_VENDOR_SPEC) ||
+	    altsd->bInterfaceSubClass != USB_SUBCLASS_AUDIOSTREAMING) {
+		dev_dbg(&dev->dev,
+			"%u:%d: skipping non-supported interface %d\n",
+			ctrlif, interface, altsd->bInterfaceClass);
+		/* skip non-supported classes */
+		return -EINVAL;
+	}
+
+	if (snd_usb_get_speed(dev) == USB_SPEED_LOW) {
+		dev_err(&dev->dev, "low speed audio streaming not supported\n");
+		return -EINVAL;
+	}
+
+	if (! snd_usb_parse_audio_interface(chip, interface)) {
+		usb_set_interface(dev, interface, 0); /* reset the current interface */
+		usb_driver_claim_interface(&usb_audio_driver, iface, (void *)-1L);
+	}
+
+	return 0;
+}
+
+/*
+ * parse audio control descriptor and create pcm/midi streams
+ */
+static int snd_usb_create_streams(struct snd_usb_audio *chip, int ctrlif)
+{
+	struct usb_device *dev = chip->dev;
+	struct usb_host_interface *host_iface;
+	struct usb_interface_descriptor *altsd;
+	int i, protocol;
+
+	/* find audiocontrol interface */
+	host_iface = &usb_ifnum_to_if(dev, ctrlif)->altsetting[0];
+	altsd = get_iface_desc(host_iface);
+	protocol = altsd->bInterfaceProtocol;
+
+	switch (protocol) {
+	default:
+		dev_warn(&dev->dev,
+			 "unknown interface protocol %#02x, assuming v1\n",
+			 protocol);
+		/* fall through */
+
+	case UAC_VERSION_1: {
+		struct uac1_ac_header_descriptor *h1;
+		int rest_bytes;
+
+		h1 = snd_usb_find_csint_desc(host_iface->extra,
+							 host_iface->extralen,
+							 NULL, UAC_HEADER);
+		if (!h1) {
+			dev_err(&dev->dev, "cannot find UAC_HEADER\n");
+			return -EINVAL;
+		}
+
+		rest_bytes = (void *)(host_iface->extra +
+				host_iface->extralen) - (void *)h1;
+
+		/* just to be sure -- this shouldn't hit at all */
+		if (rest_bytes <= 0) {
+			dev_err(&dev->dev, "invalid control header\n");
+			return -EINVAL;
+		}
+
+		if (rest_bytes < sizeof(*h1)) {
+			dev_err(&dev->dev, "too short v1 buffer descriptor\n");
+			return -EINVAL;
+		}
+
+		if (!h1->bInCollection) {
+			dev_info(&dev->dev, "skipping empty audio interface (v1)\n");
+			return -EINVAL;
+		}
+
+		if (rest_bytes < h1->bLength) {
+			dev_err(&dev->dev, "invalid buffer length (v1)\n");
+			return -EINVAL;
+		}
+
+		if (h1->bLength < sizeof(*h1) + h1->bInCollection) {
+			dev_err(&dev->dev, "invalid UAC_HEADER (v1)\n");
+			return -EINVAL;
+		}
+
+		for (i = 0; i < h1->bInCollection; i++)
+			snd_usb_create_stream(chip, ctrlif, h1->baInterfaceNr[i]);
+
+		break;
+	}
+
+	case UAC_VERSION_2:
+	case UAC_VERSION_3: {
+		struct usb_interface_assoc_descriptor *assoc =
+			usb_ifnum_to_if(dev, ctrlif)->intf_assoc;
+
+		if (!assoc) {
+			/*
+			 * Firmware writers cannot count to three.  So to find
+			 * the IAD on the NuForce UDH-100, also check the next
+			 * interface.
+			 */
+			struct usb_interface *iface =
+				usb_ifnum_to_if(dev, ctrlif + 1);
+			if (iface &&
+			    iface->intf_assoc &&
+			    iface->intf_assoc->bFunctionClass == USB_CLASS_AUDIO &&
+			    iface->intf_assoc->bFunctionProtocol == UAC_VERSION_2)
+				assoc = iface->intf_assoc;
+		}
+
+		if (!assoc) {
+			dev_err(&dev->dev, "Audio class v2/v3 interfaces need an interface association\n");
+			return -EINVAL;
+		}
+
+		if (protocol == UAC_VERSION_3) {
+			int badd = assoc->bFunctionSubClass;
+
+			if (badd != UAC3_FUNCTION_SUBCLASS_FULL_ADC_3_0 &&
+			    (badd < UAC3_FUNCTION_SUBCLASS_GENERIC_IO ||
+			     badd > UAC3_FUNCTION_SUBCLASS_SPEAKERPHONE)) {
+				dev_err(&dev->dev,
+					"Unsupported UAC3 BADD profile\n");
+				return -EINVAL;
+			}
+
+			chip->badd_profile = badd;
+		}
+
+		for (i = 0; i < assoc->bInterfaceCount; i++) {
+			int intf = assoc->bFirstInterface + i;
+
+			if (intf != ctrlif)
+				snd_usb_create_stream(chip, ctrlif, intf);
+		}
+
+		break;
+	}
+	}
+
+	return 0;
+}
+
+/*
+ * free the chip instance
+ *
+ * here we have to do not much, since pcm and controls are already freed
+ *
+ */
+
+static void snd_usb_audio_free(struct snd_card *card)
+{
+	struct snd_usb_audio *chip = card->private_data;
+	struct snd_usb_endpoint *ep, *n;
+
+	list_for_each_entry_safe(ep, n, &chip->ep_list, list)
+		snd_usb_endpoint_free(ep);
+
+	mutex_destroy(&chip->mutex);
+	if (!atomic_read(&chip->shutdown))
+		dev_set_drvdata(&chip->dev->dev, NULL);
+}
+
+static void usb_audio_make_shortname(struct usb_device *dev,
+				     struct snd_usb_audio *chip,
+				     const struct snd_usb_audio_quirk *quirk)
+{
+	struct snd_card *card = chip->card;
+
+	if (quirk && quirk->product_name && *quirk->product_name) {
+		strlcpy(card->shortname, quirk->product_name,
+			sizeof(card->shortname));
+		return;
+	}
+
+	/* retrieve the device string as shortname */
+	if (!dev->descriptor.iProduct ||
+	    usb_string(dev, dev->descriptor.iProduct,
+		       card->shortname, sizeof(card->shortname)) <= 0) {
+		/* no name available from anywhere, so use ID */
+		sprintf(card->shortname, "USB Device %#04x:%#04x",
+			USB_ID_VENDOR(chip->usb_id),
+			USB_ID_PRODUCT(chip->usb_id));
+	}
+
+	strim(card->shortname);
+}
+
+static void usb_audio_make_longname(struct usb_device *dev,
+				    struct snd_usb_audio *chip,
+				    const struct snd_usb_audio_quirk *quirk)
+{
+	struct snd_card *card = chip->card;
+	int len;
+
+	/* shortcut - if any pre-defined string is given, use it */
+	if (quirk && quirk->profile_name && *quirk->profile_name) {
+		strlcpy(card->longname, quirk->profile_name,
+			sizeof(card->longname));
+		return;
+	}
+
+	if (quirk && quirk->vendor_name && *quirk->vendor_name) {
+		len = strlcpy(card->longname, quirk->vendor_name, sizeof(card->longname));
+	} else {
+		/* retrieve the vendor and device strings as longname */
+		if (dev->descriptor.iManufacturer)
+			len = usb_string(dev, dev->descriptor.iManufacturer,
+					 card->longname, sizeof(card->longname));
+		else
+			len = 0;
+		/* we don't really care if there isn't any vendor string */
+	}
+	if (len > 0) {
+		strim(card->longname);
+		if (*card->longname)
+			strlcat(card->longname, " ", sizeof(card->longname));
+	}
+
+	strlcat(card->longname, card->shortname, sizeof(card->longname));
+
+	len = strlcat(card->longname, " at ", sizeof(card->longname));
+
+	if (len < sizeof(card->longname))
+		usb_make_path(dev, card->longname + len, sizeof(card->longname) - len);
+
+	switch (snd_usb_get_speed(dev)) {
+	case USB_SPEED_LOW:
+		strlcat(card->longname, ", low speed", sizeof(card->longname));
+		break;
+	case USB_SPEED_FULL:
+		strlcat(card->longname, ", full speed", sizeof(card->longname));
+		break;
+	case USB_SPEED_HIGH:
+		strlcat(card->longname, ", high speed", sizeof(card->longname));
+		break;
+	case USB_SPEED_SUPER:
+		strlcat(card->longname, ", super speed", sizeof(card->longname));
+		break;
+	case USB_SPEED_SUPER_PLUS:
+		strlcat(card->longname, ", super speed plus", sizeof(card->longname));
+		break;
+	default:
+		break;
+	}
+}
+
+/*
+ * create a chip instance and set its names.
+ */
+static int snd_usb_audio_create(struct usb_interface *intf,
+				struct usb_device *dev, int idx,
+				const struct snd_usb_audio_quirk *quirk,
+				unsigned int usb_id,
+				struct snd_usb_audio **rchip)
+{
+	struct snd_card *card;
+	struct snd_usb_audio *chip;
+	int err;
+	char component[14];
+
+	*rchip = NULL;
+
+	switch (snd_usb_get_speed(dev)) {
+	case USB_SPEED_LOW:
+	case USB_SPEED_FULL:
+	case USB_SPEED_HIGH:
+	case USB_SPEED_WIRELESS:
+	case USB_SPEED_SUPER:
+	case USB_SPEED_SUPER_PLUS:
+		break;
+	default:
+		dev_err(&dev->dev, "unknown device speed %d\n", snd_usb_get_speed(dev));
+		return -ENXIO;
+	}
+
+	err = snd_card_new(&intf->dev, index[idx], id[idx], THIS_MODULE,
+			   sizeof(*chip), &card);
+	if (err < 0) {
+		dev_err(&dev->dev, "cannot create card instance %d\n", idx);
+		return err;
+	}
+
+	chip = card->private_data;
+	mutex_init(&chip->mutex);
+	init_waitqueue_head(&chip->shutdown_wait);
+	chip->index = idx;
+	chip->dev = dev;
+	chip->card = card;
+	chip->setup = device_setup[idx];
+	chip->autoclock = autoclock;
+	atomic_set(&chip->active, 1); /* avoid autopm during probing */
+	atomic_set(&chip->usage_count, 0);
+	atomic_set(&chip->shutdown, 0);
+
+	chip->usb_id = usb_id;
+	INIT_LIST_HEAD(&chip->pcm_list);
+	INIT_LIST_HEAD(&chip->ep_list);
+	INIT_LIST_HEAD(&chip->midi_list);
+	INIT_LIST_HEAD(&chip->mixer_list);
+
+	card->private_free = snd_usb_audio_free;
+
+	strcpy(card->driver, "USB-Audio");
+	sprintf(component, "USB%04x:%04x",
+		USB_ID_VENDOR(chip->usb_id), USB_ID_PRODUCT(chip->usb_id));
+	snd_component_add(card, component);
+
+	usb_audio_make_shortname(dev, chip, quirk);
+	usb_audio_make_longname(dev, chip, quirk);
+
+	snd_usb_audio_create_proc(chip);
+
+	*rchip = chip;
+	return 0;
+}
+
+/* look for a matching quirk alias id */
+static bool get_alias_id(struct usb_device *dev, unsigned int *id)
+{
+	int i;
+	unsigned int src, dst;
+
+	for (i = 0; i < ARRAY_SIZE(quirk_alias); i++) {
+		if (!quirk_alias[i] ||
+		    sscanf(quirk_alias[i], "%x:%x", &src, &dst) != 2 ||
+		    src != *id)
+			continue;
+		dev_info(&dev->dev,
+			 "device (%04x:%04x): applying quirk alias %04x:%04x\n",
+			 USB_ID_VENDOR(*id), USB_ID_PRODUCT(*id),
+			 USB_ID_VENDOR(dst), USB_ID_PRODUCT(dst));
+		*id = dst;
+		return true;
+	}
+
+	return false;
+}
+
+static const struct usb_device_id usb_audio_ids[]; /* defined below */
+
+/* look for the corresponding quirk */
+static const struct snd_usb_audio_quirk *
+get_alias_quirk(struct usb_device *dev, unsigned int id)
+{
+	const struct usb_device_id *p;
+
+	for (p = usb_audio_ids; p->match_flags; p++) {
+		/* FIXME: this checks only vendor:product pair in the list */
+		if ((p->match_flags & USB_DEVICE_ID_MATCH_DEVICE) ==
+		    USB_DEVICE_ID_MATCH_DEVICE &&
+		    p->idVendor == USB_ID_VENDOR(id) &&
+		    p->idProduct == USB_ID_PRODUCT(id))
+			return (const struct snd_usb_audio_quirk *)p->driver_info;
+	}
+
+	return NULL;
+}
+
+/*
+ * probe the active usb device
+ *
+ * note that this can be called multiple times per a device, when it
+ * includes multiple audio control interfaces.
+ *
+ * thus we check the usb device pointer and creates the card instance
+ * only at the first time.  the successive calls of this function will
+ * append the pcm interface to the corresponding card.
+ */
+static int usb_audio_probe(struct usb_interface *intf,
+			   const struct usb_device_id *usb_id)
+{
+	struct usb_device *dev = interface_to_usbdev(intf);
+	const struct snd_usb_audio_quirk *quirk =
+		(const struct snd_usb_audio_quirk *)usb_id->driver_info;
+	struct snd_usb_audio *chip;
+	int i, err;
+	struct usb_host_interface *alts;
+	int ifnum;
+	u32 id;
+
+	alts = &intf->altsetting[0];
+	ifnum = get_iface_desc(alts)->bInterfaceNumber;
+	id = USB_ID(le16_to_cpu(dev->descriptor.idVendor),
+		    le16_to_cpu(dev->descriptor.idProduct));
+	if (get_alias_id(dev, &id))
+		quirk = get_alias_quirk(dev, id);
+	if (quirk && quirk->ifnum >= 0 && ifnum != quirk->ifnum)
+		return -ENXIO;
+
+	err = snd_usb_apply_boot_quirk(dev, intf, quirk, id);
+	if (err < 0)
+		return err;
+
+	/*
+	 * found a config.  now register to ALSA
+	 */
+
+	/* check whether it's already registered */
+	chip = NULL;
+	mutex_lock(&register_mutex);
+	for (i = 0; i < SNDRV_CARDS; i++) {
+		if (usb_chip[i] && usb_chip[i]->dev == dev) {
+			if (atomic_read(&usb_chip[i]->shutdown)) {
+				dev_err(&dev->dev, "USB device is in the shutdown state, cannot create a card instance\n");
+				err = -EIO;
+				goto __error;
+			}
+			chip = usb_chip[i];
+			atomic_inc(&chip->active); /* avoid autopm */
+			break;
+		}
+	}
+	if (! chip) {
+		/* it's a fresh one.
+		 * now look for an empty slot and create a new card instance
+		 */
+		for (i = 0; i < SNDRV_CARDS; i++)
+			if (!usb_chip[i] &&
+			    (vid[i] == -1 || vid[i] == USB_ID_VENDOR(id)) &&
+			    (pid[i] == -1 || pid[i] == USB_ID_PRODUCT(id))) {
+				if (enable[i]) {
+					err = snd_usb_audio_create(intf, dev, i, quirk,
+								   id, &chip);
+					if (err < 0)
+						goto __error;
+					chip->pm_intf = intf;
+					break;
+				} else if (vid[i] != -1 || pid[i] != -1) {
+					dev_info(&dev->dev,
+						 "device (%04x:%04x) is disabled\n",
+						 USB_ID_VENDOR(id),
+						 USB_ID_PRODUCT(id));
+					err = -ENOENT;
+					goto __error;
+				}
+			}
+		if (!chip) {
+			dev_err(&dev->dev, "no available usb audio device\n");
+			err = -ENODEV;
+			goto __error;
+		}
+	}
+	dev_set_drvdata(&dev->dev, chip);
+
+	/*
+	 * For devices with more than one control interface, we assume the
+	 * first contains the audio controls. We might need a more specific
+	 * check here in the future.
+	 */
+	if (!chip->ctrl_intf)
+		chip->ctrl_intf = alts;
+
+	chip->txfr_quirk = 0;
+	err = 1; /* continue */
+	if (quirk && quirk->ifnum != QUIRK_NO_INTERFACE) {
+		/* need some special handlings */
+		err = snd_usb_create_quirk(chip, intf, &usb_audio_driver, quirk);
+		if (err < 0)
+			goto __error;
+	}
+
+	if (err > 0) {
+		/* create normal USB audio interfaces */
+		err = snd_usb_create_streams(chip, ifnum);
+		if (err < 0)
+			goto __error;
+		err = snd_usb_create_mixer(chip, ifnum, ignore_ctl_error);
+		if (err < 0)
+			goto __error;
+	}
+
+	/* we are allowed to call snd_card_register() many times */
+	err = snd_card_register(chip->card);
+	if (err < 0)
+		goto __error;
+
+	usb_chip[chip->index] = chip;
+	chip->num_interfaces++;
+	usb_set_intfdata(intf, chip);
+	atomic_dec(&chip->active);
+	mutex_unlock(&register_mutex);
+	return 0;
+
+ __error:
+	if (chip) {
+		/* chip->active is inside the chip->card object,
+		 * decrement before memory is possibly returned.
+		 */
+		atomic_dec(&chip->active);
+		if (!chip->num_interfaces)
+			snd_card_free(chip->card);
+	}
+	mutex_unlock(&register_mutex);
+	return err;
+}
+
+/*
+ * we need to take care of counter, since disconnection can be called also
+ * many times as well as usb_audio_probe().
+ */
+static void usb_audio_disconnect(struct usb_interface *intf)
+{
+	struct snd_usb_audio *chip = usb_get_intfdata(intf);
+	struct snd_card *card;
+	struct list_head *p;
+
+	if (chip == (void *)-1L)
+		return;
+
+	card = chip->card;
+
+	mutex_lock(&register_mutex);
+	if (atomic_inc_return(&chip->shutdown) == 1) {
+		struct snd_usb_stream *as;
+		struct snd_usb_endpoint *ep;
+		struct usb_mixer_interface *mixer;
+
+		/* wait until all pending tasks done;
+		 * they are protected by snd_usb_lock_shutdown()
+		 */
+		wait_event(chip->shutdown_wait,
+			   !atomic_read(&chip->usage_count));
+		snd_card_disconnect(card);
+		/* release the pcm resources */
+		list_for_each_entry(as, &chip->pcm_list, list) {
+			snd_usb_stream_disconnect(as);
+		}
+		/* release the endpoint resources */
+		list_for_each_entry(ep, &chip->ep_list, list) {
+			snd_usb_endpoint_release(ep);
+		}
+		/* release the midi resources */
+		list_for_each(p, &chip->midi_list) {
+			snd_usbmidi_disconnect(p);
+		}
+		/* release mixer resources */
+		list_for_each_entry(mixer, &chip->mixer_list, list) {
+			snd_usb_mixer_disconnect(mixer);
+		}
+	}
+
+	chip->num_interfaces--;
+	if (chip->num_interfaces <= 0) {
+		usb_chip[chip->index] = NULL;
+		mutex_unlock(&register_mutex);
+		snd_card_free_when_closed(card);
+	} else {
+		mutex_unlock(&register_mutex);
+	}
+}
+
+/* lock the shutdown (disconnect) task and autoresume */
+int snd_usb_lock_shutdown(struct snd_usb_audio *chip)
+{
+	int err;
+
+	atomic_inc(&chip->usage_count);
+	if (atomic_read(&chip->shutdown)) {
+		err = -EIO;
+		goto error;
+	}
+	err = snd_usb_autoresume(chip);
+	if (err < 0)
+		goto error;
+	return 0;
+
+ error:
+	if (atomic_dec_and_test(&chip->usage_count))
+		wake_up(&chip->shutdown_wait);
+	return err;
+}
+
+/* autosuspend and unlock the shutdown */
+void snd_usb_unlock_shutdown(struct snd_usb_audio *chip)
+{
+	snd_usb_autosuspend(chip);
+	if (atomic_dec_and_test(&chip->usage_count))
+		wake_up(&chip->shutdown_wait);
+}
+
+#ifdef CONFIG_PM
+
+int snd_usb_autoresume(struct snd_usb_audio *chip)
+{
+	if (atomic_read(&chip->shutdown))
+		return -EIO;
+	if (atomic_inc_return(&chip->active) == 1)
+		return usb_autopm_get_interface(chip->pm_intf);
+	return 0;
+}
+
+void snd_usb_autosuspend(struct snd_usb_audio *chip)
+{
+	if (atomic_read(&chip->shutdown))
+		return;
+	if (atomic_dec_and_test(&chip->active))
+		usb_autopm_put_interface(chip->pm_intf);
+}
+
+static int usb_audio_suspend(struct usb_interface *intf, pm_message_t message)
+{
+	struct snd_usb_audio *chip = usb_get_intfdata(intf);
+	struct snd_usb_stream *as;
+	struct usb_mixer_interface *mixer;
+	struct list_head *p;
+
+	if (chip == (void *)-1L)
+		return 0;
+
+	chip->autosuspended = !!PMSG_IS_AUTO(message);
+	if (!chip->autosuspended)
+		snd_power_change_state(chip->card, SNDRV_CTL_POWER_D3hot);
+	if (!chip->num_suspended_intf++) {
+		list_for_each_entry(as, &chip->pcm_list, list) {
+			snd_pcm_suspend_all(as->pcm);
+			snd_usb_pcm_suspend(as);
+			as->substream[0].need_setup_ep =
+				as->substream[1].need_setup_ep = true;
+		}
+		list_for_each(p, &chip->midi_list)
+			snd_usbmidi_suspend(p);
+		list_for_each_entry(mixer, &chip->mixer_list, list)
+			snd_usb_mixer_suspend(mixer);
+	}
+
+	return 0;
+}
+
+static int __usb_audio_resume(struct usb_interface *intf, bool reset_resume)
+{
+	struct snd_usb_audio *chip = usb_get_intfdata(intf);
+	struct snd_usb_stream *as;
+	struct usb_mixer_interface *mixer;
+	struct list_head *p;
+	int err = 0;
+
+	if (chip == (void *)-1L)
+		return 0;
+	if (--chip->num_suspended_intf)
+		return 0;
+
+	atomic_inc(&chip->active); /* avoid autopm */
+
+	list_for_each_entry(as, &chip->pcm_list, list) {
+		err = snd_usb_pcm_resume(as);
+		if (err < 0)
+			goto err_out;
+	}
+
+	/*
+	 * ALSA leaves material resumption to user space
+	 * we just notify and restart the mixers
+	 */
+	list_for_each_entry(mixer, &chip->mixer_list, list) {
+		err = snd_usb_mixer_resume(mixer, reset_resume);
+		if (err < 0)
+			goto err_out;
+	}
+
+	list_for_each(p, &chip->midi_list) {
+		snd_usbmidi_resume(p);
+	}
+
+	if (!chip->autosuspended)
+		snd_power_change_state(chip->card, SNDRV_CTL_POWER_D0);
+	chip->autosuspended = 0;
+
+err_out:
+	atomic_dec(&chip->active); /* allow autopm after this point */
+	return err;
+}
+
+static int usb_audio_resume(struct usb_interface *intf)
+{
+	return __usb_audio_resume(intf, false);
+}
+
+static int usb_audio_reset_resume(struct usb_interface *intf)
+{
+	return __usb_audio_resume(intf, true);
+}
+#else
+#define usb_audio_suspend	NULL
+#define usb_audio_resume	NULL
+#define usb_audio_reset_resume	NULL
+#endif		/* CONFIG_PM */
+
+static const struct usb_device_id usb_audio_ids [] = {
+#include "quirks-table.h"
+    { .match_flags = (USB_DEVICE_ID_MATCH_INT_CLASS | USB_DEVICE_ID_MATCH_INT_SUBCLASS),
+      .bInterfaceClass = USB_CLASS_AUDIO,
+      .bInterfaceSubClass = USB_SUBCLASS_AUDIOCONTROL },
+    { }						/* Terminating entry */
+};
+MODULE_DEVICE_TABLE(usb, usb_audio_ids);
+
+/*
+ * entry point for linux usb interface
+ */
+
+static struct usb_driver usb_audio_driver = {
+	.name =		"snd-usb-audio",
+	.probe =	usb_audio_probe,
+	.disconnect =	usb_audio_disconnect,
+	.suspend =	usb_audio_suspend,
+	.resume =	usb_audio_resume,
+	.reset_resume =	usb_audio_reset_resume,
+	.id_table =	usb_audio_ids,
+	.supports_autosuspend = 1,
+};
+
+module_usb_driver(usb_audio_driver);
diff --git a/sound/usb/card.h b/sound/usb/card.h
new file mode 100644
index 0000000..ac785d1
--- /dev/null
+++ b/sound/usb/card.h
@@ -0,0 +1,174 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __USBAUDIO_CARD_H
+#define __USBAUDIO_CARD_H
+
+#define MAX_NR_RATES	1024
+#define MAX_PACKS	6		/* per URB */
+#define MAX_PACKS_HS	(MAX_PACKS * 8)	/* in high speed mode */
+#define MAX_URBS	12
+#define SYNC_URBS	4	/* always four urbs for sync */
+#define MAX_QUEUE	18	/* try not to exceed this queue length, in ms */
+
+struct audioformat {
+	struct list_head list;
+	u64 formats;			/* ALSA format bits */
+	unsigned int channels;		/* # channels */
+	unsigned int fmt_type;		/* USB audio format type (1-3) */
+	unsigned int frame_size;	/* samples per frame for non-audio */
+	int iface;			/* interface number */
+	unsigned char altsetting;	/* corresponding alternate setting */
+	unsigned char altset_idx;	/* array index of altenate setting */
+	unsigned char attributes;	/* corresponding attributes of cs endpoint */
+	unsigned char endpoint;		/* endpoint */
+	unsigned char ep_attr;		/* endpoint attributes */
+	unsigned char datainterval;	/* log_2 of data packet interval */
+	unsigned char protocol;		/* UAC_VERSION_1/2/3 */
+	unsigned int maxpacksize;	/* max. packet size */
+	unsigned int rates;		/* rate bitmasks */
+	unsigned int rate_min, rate_max;	/* min/max rates */
+	unsigned int nr_rates;		/* number of rate table entries */
+	unsigned int *rate_table;	/* rate table */
+	unsigned char clock;		/* associated clock */
+	struct snd_pcm_chmap_elem *chmap; /* (optional) channel map */
+	bool dsd_dop;			/* add DOP headers in case of DSD samples */
+	bool dsd_bitrev;		/* reverse the bits of each DSD sample */
+	bool dsd_raw;			/* altsetting is raw DSD */
+};
+
+struct snd_usb_substream;
+struct snd_usb_endpoint;
+struct snd_usb_power_domain;
+
+struct snd_urb_ctx {
+	struct urb *urb;
+	unsigned int buffer_size;	/* size of data buffer, if data URB */
+	struct snd_usb_substream *subs;
+	struct snd_usb_endpoint *ep;
+	int index;	/* index for urb array */
+	int packets;	/* number of packets per urb */
+	int packet_size[MAX_PACKS_HS]; /* size of packets for next submission */
+	struct list_head ready_list;
+};
+
+struct snd_usb_endpoint {
+	struct snd_usb_audio *chip;
+
+	int use_count;
+	int ep_num;		/* the referenced endpoint number */
+	int type;		/* SND_USB_ENDPOINT_TYPE_* */
+	unsigned long flags;
+
+	void (*prepare_data_urb) (struct snd_usb_substream *subs,
+				  struct urb *urb);
+	void (*retire_data_urb) (struct snd_usb_substream *subs,
+				 struct urb *urb);
+
+	struct snd_usb_substream *data_subs;
+	struct snd_usb_endpoint *sync_master;
+	struct snd_usb_endpoint *sync_slave;
+
+	struct snd_urb_ctx urb[MAX_URBS];
+
+	struct snd_usb_packet_info {
+		uint32_t packet_size[MAX_PACKS_HS];
+		int packets;
+	} next_packet[MAX_URBS];
+	int next_packet_read_pos, next_packet_write_pos;
+	struct list_head ready_playback_urbs;
+
+	unsigned int nurbs;		/* # urbs */
+	unsigned long active_mask;	/* bitmask of active urbs */
+	unsigned long unlink_mask;	/* bitmask of unlinked urbs */
+	char *syncbuf;			/* sync buffer for all sync URBs */
+	dma_addr_t sync_dma;		/* DMA address of syncbuf */
+
+	unsigned int pipe;		/* the data i/o pipe */
+	unsigned int freqn;		/* nominal sampling rate in fs/fps in Q16.16 format */
+	unsigned int freqm;		/* momentary sampling rate in fs/fps in Q16.16 format */
+	int	   freqshift;		/* how much to shift the feedback value to get Q16.16 */
+	unsigned int freqmax;		/* maximum sampling rate, used for buffer management */
+	unsigned int phase;		/* phase accumulator */
+	unsigned int maxpacksize;	/* max packet size in bytes */
+	unsigned int maxframesize;      /* max packet size in frames */
+	unsigned int max_urb_frames;	/* max URB size in frames */
+	unsigned int curpacksize;	/* current packet size in bytes (for capture) */
+	unsigned int curframesize;      /* current packet size in frames (for capture) */
+	unsigned int syncmaxsize;	/* sync endpoint packet size */
+	unsigned int fill_max:1;	/* fill max packet size always */
+	unsigned int tenor_fb_quirk:1;	/* corrupted feedback data */
+	unsigned int datainterval;      /* log_2 of data packet interval */
+	unsigned int syncinterval;	/* P for adaptive mode, 0 otherwise */
+	unsigned char silence_value;
+	unsigned int stride;
+	int iface, altsetting;
+	int skip_packets;		/* quirks for devices to ignore the first n packets
+					   in a stream */
+
+	spinlock_t lock;
+	struct list_head list;
+};
+
+struct snd_usb_substream {
+	struct snd_usb_stream *stream;
+	struct usb_device *dev;
+	struct snd_pcm_substream *pcm_substream;
+	int direction;	/* playback or capture */
+	int interface;	/* current interface */
+	int endpoint;	/* assigned endpoint */
+	struct audioformat *cur_audiofmt;	/* current audioformat pointer (for hw_params callback) */
+	struct snd_usb_power_domain *str_pd;	/* UAC3 Power Domain for streaming path */
+	snd_pcm_format_t pcm_format;	/* current audio format (for hw_params callback) */
+	unsigned int channels;		/* current number of channels (for hw_params callback) */
+	unsigned int channels_max;	/* max channels in the all audiofmts */
+	unsigned int cur_rate;		/* current rate (for hw_params callback) */
+	unsigned int period_bytes;	/* current period bytes (for hw_params callback) */
+	unsigned int period_frames;	/* current frames per period */
+	unsigned int buffer_periods;	/* current periods per buffer */
+	unsigned int altset_idx;     /* USB data format: index of alternate setting */
+	unsigned int txfr_quirk:1;	/* allow sub-frame alignment */
+	unsigned int tx_length_quirk:1;	/* add length specifier to transfers */
+	unsigned int fmt_type;		/* USB audio format type (1-3) */
+	unsigned int pkt_offset_adj;	/* Bytes to drop from beginning of packets (for non-compliant devices) */
+
+	unsigned int running: 1;	/* running status */
+
+	unsigned int hwptr_done;	/* processed byte position in the buffer */
+	unsigned int transfer_done;		/* processed frames since last period update */
+	unsigned int frame_limit;	/* limits number of packets in URB */
+
+	/* data and sync endpoints for this stream */
+	unsigned int ep_num;		/* the endpoint number */
+	struct snd_usb_endpoint *data_endpoint;
+	struct snd_usb_endpoint *sync_endpoint;
+	unsigned long flags;
+	bool need_setup_ep;		/* (re)configure EP at prepare? */
+	unsigned int speed;		/* USB_SPEED_XXX */
+
+	u64 formats;			/* format bitmasks (all or'ed) */
+	unsigned int num_formats;		/* number of supported audio formats (list) */
+	struct list_head fmt_list;	/* format list */
+	struct snd_pcm_hw_constraint_list rate_list;	/* limited rates */
+	spinlock_t lock;
+
+	int last_frame_number;          /* stored frame number */
+	int last_delay;                 /* stored delay */
+
+	struct {
+		int marker;
+		int channel;
+		int byte_idx;
+	} dsd_dop;
+
+	bool trigger_tstamp_pending_update; /* trigger timestamp being updated from initial estimate */
+};
+
+struct snd_usb_stream {
+	struct snd_usb_audio *chip;
+	struct snd_pcm *pcm;
+	int pcm_index;
+	unsigned int fmt_type;		/* USB audio format type (1-3) */
+	struct snd_usb_substream substream[2];
+	struct list_head list;
+};
+
+#endif /* __USBAUDIO_CARD_H */
diff --git a/sound/usb/clock.c b/sound/usb/clock.c
new file mode 100644
index 0000000..db5e39d
--- /dev/null
+++ b/sound/usb/clock.c
@@ -0,0 +1,622 @@
+/*
+ *   Clock domain and sample rate management functions
+ *
+ *   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/bitops.h>
+#include <linux/init.h>
+#include <linux/string.h>
+#include <linux/usb.h>
+#include <linux/usb/audio.h>
+#include <linux/usb/audio-v2.h>
+#include <linux/usb/audio-v3.h>
+
+#include <sound/core.h>
+#include <sound/info.h>
+#include <sound/pcm.h>
+
+#include "usbaudio.h"
+#include "card.h"
+#include "helper.h"
+#include "clock.h"
+#include "quirks.h"
+
+static void *find_uac_clock_desc(struct usb_host_interface *iface, int id,
+				 bool (*validator)(void *, int), u8 type)
+{
+	void *cs = NULL;
+
+	while ((cs = snd_usb_find_csint_desc(iface->extra, iface->extralen,
+					     cs, type))) {
+		if (validator(cs, id))
+			return cs;
+	}
+
+	return NULL;
+}
+
+static bool validate_clock_source_v2(void *p, int id)
+{
+	struct uac_clock_source_descriptor *cs = p;
+	return cs->bLength == sizeof(*cs) && cs->bClockID == id;
+}
+
+static bool validate_clock_source_v3(void *p, int id)
+{
+	struct uac3_clock_source_descriptor *cs = p;
+	return cs->bLength == sizeof(*cs) && cs->bClockID == id;
+}
+
+static bool validate_clock_selector_v2(void *p, int id)
+{
+	struct uac_clock_selector_descriptor *cs = p;
+	return cs->bLength >= sizeof(*cs) && cs->bClockID == id &&
+		cs->bLength == 7 + cs->bNrInPins;
+}
+
+static bool validate_clock_selector_v3(void *p, int id)
+{
+	struct uac3_clock_selector_descriptor *cs = p;
+	return cs->bLength >= sizeof(*cs) && cs->bClockID == id &&
+		cs->bLength == 11 + cs->bNrInPins;
+}
+
+static bool validate_clock_multiplier_v2(void *p, int id)
+{
+	struct uac_clock_multiplier_descriptor *cs = p;
+	return cs->bLength == sizeof(*cs) && cs->bClockID == id;
+}
+
+static bool validate_clock_multiplier_v3(void *p, int id)
+{
+	struct uac3_clock_multiplier_descriptor *cs = p;
+	return cs->bLength == sizeof(*cs) && cs->bClockID == id;
+}
+
+#define DEFINE_FIND_HELPER(name, obj, validator, type)		\
+static obj *name(struct usb_host_interface *iface, int id)	\
+{								\
+	return find_uac_clock_desc(iface, id, validator, type);	\
+}
+
+DEFINE_FIND_HELPER(snd_usb_find_clock_source,
+		   struct uac_clock_source_descriptor,
+		   validate_clock_source_v2, UAC2_CLOCK_SOURCE);
+DEFINE_FIND_HELPER(snd_usb_find_clock_source_v3,
+		   struct uac3_clock_source_descriptor,
+		   validate_clock_source_v3, UAC3_CLOCK_SOURCE);
+
+DEFINE_FIND_HELPER(snd_usb_find_clock_selector,
+		   struct uac_clock_selector_descriptor,
+		   validate_clock_selector_v2, UAC2_CLOCK_SELECTOR);
+DEFINE_FIND_HELPER(snd_usb_find_clock_selector_v3,
+		   struct uac3_clock_selector_descriptor,
+		   validate_clock_selector_v3, UAC3_CLOCK_SELECTOR);
+
+DEFINE_FIND_HELPER(snd_usb_find_clock_multiplier,
+		   struct uac_clock_multiplier_descriptor,
+		   validate_clock_multiplier_v2, UAC2_CLOCK_MULTIPLIER);
+DEFINE_FIND_HELPER(snd_usb_find_clock_multiplier_v3,
+		   struct uac3_clock_multiplier_descriptor,
+		   validate_clock_multiplier_v3, UAC3_CLOCK_MULTIPLIER);
+
+static int uac_clock_selector_get_val(struct snd_usb_audio *chip, int selector_id)
+{
+	unsigned char buf;
+	int ret;
+
+	ret = snd_usb_ctl_msg(chip->dev, usb_rcvctrlpipe(chip->dev, 0),
+			      UAC2_CS_CUR,
+			      USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_IN,
+			      UAC2_CX_CLOCK_SELECTOR << 8,
+			      snd_usb_ctrl_intf(chip) | (selector_id << 8),
+			      &buf, sizeof(buf));
+
+	if (ret < 0)
+		return ret;
+
+	return buf;
+}
+
+static int uac_clock_selector_set_val(struct snd_usb_audio *chip, int selector_id,
+					unsigned char pin)
+{
+	int ret;
+
+	ret = snd_usb_ctl_msg(chip->dev, usb_sndctrlpipe(chip->dev, 0),
+			      UAC2_CS_CUR,
+			      USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_OUT,
+			      UAC2_CX_CLOCK_SELECTOR << 8,
+			      snd_usb_ctrl_intf(chip) | (selector_id << 8),
+			      &pin, sizeof(pin));
+	if (ret < 0)
+		return ret;
+
+	if (ret != sizeof(pin)) {
+		usb_audio_err(chip,
+			"setting selector (id %d) unexpected length %d\n",
+			selector_id, ret);
+		return -EINVAL;
+	}
+
+	ret = uac_clock_selector_get_val(chip, selector_id);
+	if (ret < 0)
+		return ret;
+
+	if (ret != pin) {
+		usb_audio_err(chip,
+			"setting selector (id %d) to %x failed (current: %d)\n",
+			selector_id, pin, ret);
+		return -EINVAL;
+	}
+
+	return ret;
+}
+
+static bool uac_clock_source_is_valid(struct snd_usb_audio *chip,
+				      int protocol,
+				      int source_id)
+{
+	int err;
+	unsigned char data;
+	struct usb_device *dev = chip->dev;
+	u32 bmControls;
+
+	if (protocol == UAC_VERSION_3) {
+		struct uac3_clock_source_descriptor *cs_desc =
+			snd_usb_find_clock_source_v3(chip->ctrl_intf, source_id);
+
+		if (!cs_desc)
+			return 0;
+		bmControls = le32_to_cpu(cs_desc->bmControls);
+	} else { /* UAC_VERSION_1/2 */
+		struct uac_clock_source_descriptor *cs_desc =
+			snd_usb_find_clock_source(chip->ctrl_intf, source_id);
+
+		if (!cs_desc)
+			return 0;
+		bmControls = cs_desc->bmControls;
+	}
+
+	/* If a clock source can't tell us whether it's valid, we assume it is */
+	if (!uac_v2v3_control_is_readable(bmControls,
+				      UAC2_CS_CONTROL_CLOCK_VALID))
+		return 1;
+
+	err = snd_usb_ctl_msg(dev, usb_rcvctrlpipe(dev, 0), UAC2_CS_CUR,
+			      USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_IN,
+			      UAC2_CS_CONTROL_CLOCK_VALID << 8,
+			      snd_usb_ctrl_intf(chip) | (source_id << 8),
+			      &data, sizeof(data));
+
+	if (err < 0) {
+		dev_warn(&dev->dev,
+			 "%s(): cannot get clock validity for id %d\n",
+			   __func__, source_id);
+		return 0;
+	}
+
+	return !!data;
+}
+
+static int __uac_clock_find_source(struct snd_usb_audio *chip, int entity_id,
+				   unsigned long *visited, bool validate)
+{
+	struct uac_clock_source_descriptor *source;
+	struct uac_clock_selector_descriptor *selector;
+	struct uac_clock_multiplier_descriptor *multiplier;
+
+	entity_id &= 0xff;
+
+	if (test_and_set_bit(entity_id, visited)) {
+		usb_audio_warn(chip,
+			 "%s(): recursive clock topology detected, id %d.\n",
+			 __func__, entity_id);
+		return -EINVAL;
+	}
+
+	/* first, see if the ID we're looking for is a clock source already */
+	source = snd_usb_find_clock_source(chip->ctrl_intf, entity_id);
+	if (source) {
+		entity_id = source->bClockID;
+		if (validate && !uac_clock_source_is_valid(chip, UAC_VERSION_2,
+								entity_id)) {
+			usb_audio_err(chip,
+				"clock source %d is not valid, cannot use\n",
+				entity_id);
+			return -ENXIO;
+		}
+		return entity_id;
+	}
+
+	selector = snd_usb_find_clock_selector(chip->ctrl_intf, entity_id);
+	if (selector) {
+		int ret, i, cur;
+
+		/* the entity ID we are looking for is a selector.
+		 * find out what it currently selects */
+		ret = uac_clock_selector_get_val(chip, selector->bClockID);
+		if (ret < 0)
+			return ret;
+
+		/* Selector values are one-based */
+
+		if (ret > selector->bNrInPins || ret < 1) {
+			usb_audio_err(chip,
+				"%s(): selector reported illegal value, id %d, ret %d\n",
+				__func__, selector->bClockID, ret);
+
+			return -EINVAL;
+		}
+
+		cur = ret;
+		ret = __uac_clock_find_source(chip, selector->baCSourceID[ret - 1],
+					       visited, validate);
+		if (!validate || ret > 0 || !chip->autoclock)
+			return ret;
+
+		/* The current clock source is invalid, try others. */
+		for (i = 1; i <= selector->bNrInPins; i++) {
+			int err;
+
+			if (i == cur)
+				continue;
+
+			ret = __uac_clock_find_source(chip, selector->baCSourceID[i - 1],
+				visited, true);
+			if (ret < 0)
+				continue;
+
+			err = uac_clock_selector_set_val(chip, entity_id, i);
+			if (err < 0)
+				continue;
+
+			usb_audio_info(chip,
+				 "found and selected valid clock source %d\n",
+				 ret);
+			return ret;
+		}
+
+		return -ENXIO;
+	}
+
+	/* FIXME: multipliers only act as pass-thru element for now */
+	multiplier = snd_usb_find_clock_multiplier(chip->ctrl_intf, entity_id);
+	if (multiplier)
+		return __uac_clock_find_source(chip, multiplier->bCSourceID,
+						visited, validate);
+
+	return -EINVAL;
+}
+
+static int __uac3_clock_find_source(struct snd_usb_audio *chip, int entity_id,
+				   unsigned long *visited, bool validate)
+{
+	struct uac3_clock_source_descriptor *source;
+	struct uac3_clock_selector_descriptor *selector;
+	struct uac3_clock_multiplier_descriptor *multiplier;
+
+	entity_id &= 0xff;
+
+	if (test_and_set_bit(entity_id, visited)) {
+		usb_audio_warn(chip,
+			 "%s(): recursive clock topology detected, id %d.\n",
+			 __func__, entity_id);
+		return -EINVAL;
+	}
+
+	/* first, see if the ID we're looking for is a clock source already */
+	source = snd_usb_find_clock_source_v3(chip->ctrl_intf, entity_id);
+	if (source) {
+		entity_id = source->bClockID;
+		if (validate && !uac_clock_source_is_valid(chip, UAC_VERSION_3,
+								entity_id)) {
+			usb_audio_err(chip,
+				"clock source %d is not valid, cannot use\n",
+				entity_id);
+			return -ENXIO;
+		}
+		return entity_id;
+	}
+
+	selector = snd_usb_find_clock_selector_v3(chip->ctrl_intf, entity_id);
+	if (selector) {
+		int ret, i, cur;
+
+		/* the entity ID we are looking for is a selector.
+		 * find out what it currently selects */
+		ret = uac_clock_selector_get_val(chip, selector->bClockID);
+		if (ret < 0)
+			return ret;
+
+		/* Selector values are one-based */
+
+		if (ret > selector->bNrInPins || ret < 1) {
+			usb_audio_err(chip,
+				"%s(): selector reported illegal value, id %d, ret %d\n",
+				__func__, selector->bClockID, ret);
+
+			return -EINVAL;
+		}
+
+		cur = ret;
+		ret = __uac3_clock_find_source(chip, selector->baCSourceID[ret - 1],
+					       visited, validate);
+		if (!validate || ret > 0 || !chip->autoclock)
+			return ret;
+
+		/* The current clock source is invalid, try others. */
+		for (i = 1; i <= selector->bNrInPins; i++) {
+			int err;
+
+			if (i == cur)
+				continue;
+
+			ret = __uac3_clock_find_source(chip, selector->baCSourceID[i - 1],
+				visited, true);
+			if (ret < 0)
+				continue;
+
+			err = uac_clock_selector_set_val(chip, entity_id, i);
+			if (err < 0)
+				continue;
+
+			usb_audio_info(chip,
+				 "found and selected valid clock source %d\n",
+				 ret);
+			return ret;
+		}
+
+		return -ENXIO;
+	}
+
+	/* FIXME: multipliers only act as pass-thru element for now */
+	multiplier = snd_usb_find_clock_multiplier_v3(chip->ctrl_intf,
+						      entity_id);
+	if (multiplier)
+		return __uac3_clock_find_source(chip, multiplier->bCSourceID,
+						visited, validate);
+
+	return -EINVAL;
+}
+
+/*
+ * For all kinds of sample rate settings and other device queries,
+ * the clock source (end-leaf) must be used. However, clock selectors,
+ * clock multipliers and sample rate converters may be specified as
+ * clock source input to terminal. This functions walks the clock path
+ * to its end and tries to find the source.
+ *
+ * The 'visited' bitfield is used internally to detect recursive loops.
+ *
+ * Returns the clock source UnitID (>=0) on success, or an error.
+ */
+int snd_usb_clock_find_source(struct snd_usb_audio *chip, int protocol,
+			      int entity_id, bool validate)
+{
+	DECLARE_BITMAP(visited, 256);
+	memset(visited, 0, sizeof(visited));
+
+	switch (protocol) {
+	case UAC_VERSION_2:
+		return __uac_clock_find_source(chip, entity_id, visited,
+					       validate);
+	case UAC_VERSION_3:
+		return __uac3_clock_find_source(chip, entity_id, visited,
+					       validate);
+	default:
+		return -EINVAL;
+	}
+}
+
+static int set_sample_rate_v1(struct snd_usb_audio *chip, int iface,
+			      struct usb_host_interface *alts,
+			      struct audioformat *fmt, int rate)
+{
+	struct usb_device *dev = chip->dev;
+	unsigned int ep;
+	unsigned char data[3];
+	int err, crate;
+
+	if (get_iface_desc(alts)->bNumEndpoints < 1)
+		return -EINVAL;
+	ep = get_endpoint(alts, 0)->bEndpointAddress;
+
+	/* if endpoint doesn't have sampling rate control, bail out */
+	if (!(fmt->attributes & UAC_EP_CS_ATTR_SAMPLE_RATE))
+		return 0;
+
+	data[0] = rate;
+	data[1] = rate >> 8;
+	data[2] = rate >> 16;
+	err = snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), UAC_SET_CUR,
+			      USB_TYPE_CLASS | USB_RECIP_ENDPOINT | USB_DIR_OUT,
+			      UAC_EP_CS_ATTR_SAMPLE_RATE << 8, ep,
+			      data, sizeof(data));
+	if (err < 0) {
+		dev_err(&dev->dev, "%d:%d: cannot set freq %d to ep %#x\n",
+			iface, fmt->altsetting, rate, ep);
+		return err;
+	}
+
+	/* Don't check the sample rate for devices which we know don't
+	 * support reading */
+	if (snd_usb_get_sample_rate_quirk(chip))
+		return 0;
+	/* the firmware is likely buggy, don't repeat to fail too many times */
+	if (chip->sample_rate_read_error > 2)
+		return 0;
+
+	err = snd_usb_ctl_msg(dev, usb_rcvctrlpipe(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) {
+		dev_err(&dev->dev, "%d:%d: cannot get freq at ep %#x\n",
+			iface, fmt->altsetting, ep);
+		chip->sample_rate_read_error++;
+		return 0; /* some devices don't support reading */
+	}
+
+	crate = data[0] | (data[1] << 8) | (data[2] << 16);
+	if (crate != rate) {
+		dev_warn(&dev->dev, "current rate %d is different from the runtime rate %d\n", crate, rate);
+		// runtime->rate = crate;
+	}
+
+	return 0;
+}
+
+static int get_sample_rate_v2v3(struct snd_usb_audio *chip, int iface,
+			      int altsetting, int clock)
+{
+	struct usb_device *dev = chip->dev;
+	__le32 data;
+	int err;
+
+	err = snd_usb_ctl_msg(dev, usb_rcvctrlpipe(dev, 0), UAC2_CS_CUR,
+			      USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_IN,
+			      UAC2_CS_CONTROL_SAM_FREQ << 8,
+			      snd_usb_ctrl_intf(chip) | (clock << 8),
+			      &data, sizeof(data));
+	if (err < 0) {
+		dev_warn(&dev->dev, "%d:%d: cannot get freq (v2/v3): err %d\n",
+			 iface, altsetting, err);
+		return 0;
+	}
+
+	return le32_to_cpu(data);
+}
+
+static int set_sample_rate_v2v3(struct snd_usb_audio *chip, int iface,
+			      struct usb_host_interface *alts,
+			      struct audioformat *fmt, int rate)
+{
+	struct usb_device *dev = chip->dev;
+	__le32 data;
+	int err, cur_rate, prev_rate;
+	int clock;
+	bool writeable;
+	u32 bmControls;
+
+	/* First, try to find a valid clock. This may trigger
+	 * automatic clock selection if the current clock is not
+	 * valid.
+	 */
+	clock = snd_usb_clock_find_source(chip, fmt->protocol,
+					  fmt->clock, true);
+	if (clock < 0) {
+		/* We did not find a valid clock, but that might be
+		 * because the current sample rate does not match an
+		 * external clock source. Try again without validation
+		 * and we will do another validation after setting the
+		 * rate.
+		 */
+		clock = snd_usb_clock_find_source(chip, fmt->protocol,
+						  fmt->clock, false);
+		if (clock < 0)
+			return clock;
+	}
+
+	prev_rate = get_sample_rate_v2v3(chip, iface, fmt->altsetting, clock);
+	if (prev_rate == rate)
+		goto validation;
+
+	if (fmt->protocol == UAC_VERSION_3) {
+		struct uac3_clock_source_descriptor *cs_desc;
+
+		cs_desc = snd_usb_find_clock_source_v3(chip->ctrl_intf, clock);
+		bmControls = le32_to_cpu(cs_desc->bmControls);
+	} else {
+		struct uac_clock_source_descriptor *cs_desc;
+
+		cs_desc = snd_usb_find_clock_source(chip->ctrl_intf, clock);
+		bmControls = cs_desc->bmControls;
+	}
+
+	writeable = uac_v2v3_control_is_writeable(bmControls,
+						  UAC2_CS_CONTROL_SAM_FREQ);
+	if (writeable) {
+		data = cpu_to_le32(rate);
+		err = snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), UAC2_CS_CUR,
+				      USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_OUT,
+				      UAC2_CS_CONTROL_SAM_FREQ << 8,
+				      snd_usb_ctrl_intf(chip) | (clock << 8),
+				      &data, sizeof(data));
+		if (err < 0) {
+			usb_audio_err(chip,
+				"%d:%d: cannot set freq %d (v2/v3): err %d\n",
+				iface, fmt->altsetting, rate, err);
+			return err;
+		}
+
+		cur_rate = get_sample_rate_v2v3(chip, iface,
+						fmt->altsetting, clock);
+	} else {
+		cur_rate = prev_rate;
+	}
+
+	if (cur_rate != rate) {
+		if (!writeable) {
+			usb_audio_warn(chip,
+				 "%d:%d: freq mismatch (RO clock): req %d, clock runs @%d\n",
+				 iface, fmt->altsetting, rate, cur_rate);
+			return -ENXIO;
+		}
+		usb_audio_dbg(chip,
+			"current rate %d is different from the runtime rate %d\n",
+			cur_rate, rate);
+	}
+
+	/* Some devices doesn't respond to sample rate changes while the
+	 * interface is active. */
+	if (rate != prev_rate) {
+		usb_set_interface(dev, iface, 0);
+		snd_usb_set_interface_quirk(dev);
+		usb_set_interface(dev, iface, fmt->altsetting);
+		snd_usb_set_interface_quirk(dev);
+	}
+
+validation:
+	/* validate clock after rate change */
+	if (!uac_clock_source_is_valid(chip, fmt->protocol, clock))
+		return -ENXIO;
+	return 0;
+}
+
+int snd_usb_init_sample_rate(struct snd_usb_audio *chip, int iface,
+			     struct usb_host_interface *alts,
+			     struct audioformat *fmt, int rate)
+{
+	switch (fmt->protocol) {
+	case UAC_VERSION_1:
+	default:
+		return set_sample_rate_v1(chip, iface, alts, fmt, rate);
+
+	case UAC_VERSION_3:
+		if (chip->badd_profile >= UAC3_FUNCTION_SUBCLASS_GENERIC_IO) {
+			if (rate != UAC3_BADD_SAMPLING_RATE)
+				return -ENXIO;
+			else
+				return 0;
+		}
+	/* fall through */
+	case UAC_VERSION_2:
+		return set_sample_rate_v2v3(chip, iface, alts, fmt, rate);
+	}
+}
+
diff --git a/sound/usb/clock.h b/sound/usb/clock.h
new file mode 100644
index 0000000..076e31b
--- /dev/null
+++ b/sound/usb/clock.h
@@ -0,0 +1,12 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __USBAUDIO_CLOCK_H
+#define __USBAUDIO_CLOCK_H
+
+int snd_usb_init_sample_rate(struct snd_usb_audio *chip, int iface,
+			     struct usb_host_interface *alts,
+			     struct audioformat *fmt, int rate);
+
+int snd_usb_clock_find_source(struct snd_usb_audio *chip, int protocol,
+			     int entity_id, bool validate);
+
+#endif /* __USBAUDIO_CLOCK_H */
diff --git a/sound/usb/debug.h b/sound/usb/debug.h
new file mode 100644
index 0000000..7dd983c
--- /dev/null
+++ b/sound/usb/debug.h
@@ -0,0 +1,16 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __USBAUDIO_DEBUG_H
+#define __USBAUDIO_DEBUG_H
+
+/*
+ * h/w constraints
+ */
+
+#ifdef HW_CONST_DEBUG
+#define hwc_debug(fmt, args...) printk(KERN_DEBUG fmt, ##args)
+#else
+#define hwc_debug(fmt, args...) do { } while(0)
+#endif
+
+#endif /* __USBAUDIO_DEBUG_H */
+
diff --git a/sound/usb/endpoint.c b/sound/usb/endpoint.c
new file mode 100644
index 0000000..d86be8b
--- /dev/null
+++ b/sound/usb/endpoint.c
@@ -0,0 +1,1230 @@
+/*
+ *   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/gfp.h>
+#include <linux/init.h>
+#include <linux/ratelimit.h>
+#include <linux/usb.h>
+#include <linux/usb/audio.h>
+#include <linux/slab.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+
+#include "usbaudio.h"
+#include "helper.h"
+#include "card.h"
+#include "endpoint.h"
+#include "pcm.h"
+#include "quirks.h"
+
+#define EP_FLAG_RUNNING		1
+#define EP_FLAG_STOPPING	2
+
+/*
+ * snd_usb_endpoint is a model that abstracts everything related to an
+ * USB endpoint and its streaming.
+ *
+ * There are functions to activate and deactivate the streaming URBs and
+ * optional callbacks to let the pcm logic handle the actual content of the
+ * packets for playback and record. Thus, the bus streaming and the audio
+ * handlers are fully decoupled.
+ *
+ * There are two different types of endpoints in audio applications.
+ *
+ * SND_USB_ENDPOINT_TYPE_DATA handles full audio data payload for both
+ * inbound and outbound traffic.
+ *
+ * SND_USB_ENDPOINT_TYPE_SYNC endpoints are for inbound traffic only and
+ * expect the payload to carry Q10.14 / Q16.16 formatted sync information
+ * (3 or 4 bytes).
+ *
+ * Each endpoint has to be configured prior to being used by calling
+ * snd_usb_endpoint_set_params().
+ *
+ * The model incorporates a reference counting, so that multiple users
+ * can call snd_usb_endpoint_start() and snd_usb_endpoint_stop(), and
+ * only the first user will effectively start the URBs, and only the last
+ * one to stop it will tear the URBs down again.
+ */
+
+/*
+ * convert a sampling rate into our full speed format (fs/1000 in Q16.16)
+ * this will overflow at approx 524 kHz
+ */
+static inline unsigned get_usb_full_speed_rate(unsigned int rate)
+{
+	return ((rate << 13) + 62) / 125;
+}
+
+/*
+ * convert a sampling rate into USB high speed format (fs/8000 in Q16.16)
+ * this will overflow at approx 4 MHz
+ */
+static inline unsigned get_usb_high_speed_rate(unsigned int rate)
+{
+	return ((rate << 10) + 62) / 125;
+}
+
+/*
+ * release a urb data
+ */
+static void release_urb_ctx(struct snd_urb_ctx *u)
+{
+	if (u->buffer_size)
+		usb_free_coherent(u->ep->chip->dev, u->buffer_size,
+				  u->urb->transfer_buffer,
+				  u->urb->transfer_dma);
+	usb_free_urb(u->urb);
+	u->urb = NULL;
+}
+
+static const char *usb_error_string(int err)
+{
+	switch (err) {
+	case -ENODEV:
+		return "no device";
+	case -ENOENT:
+		return "endpoint not enabled";
+	case -EPIPE:
+		return "endpoint stalled";
+	case -ENOSPC:
+		return "not enough bandwidth";
+	case -ESHUTDOWN:
+		return "device disabled";
+	case -EHOSTUNREACH:
+		return "device suspended";
+	case -EINVAL:
+	case -EAGAIN:
+	case -EFBIG:
+	case -EMSGSIZE:
+		return "internal error";
+	default:
+		return "unknown error";
+	}
+}
+
+/**
+ * snd_usb_endpoint_implicit_feedback_sink: Report endpoint usage type
+ *
+ * @ep: The snd_usb_endpoint
+ *
+ * Determine whether an endpoint is driven by an implicit feedback
+ * data endpoint source.
+ */
+int snd_usb_endpoint_implicit_feedback_sink(struct snd_usb_endpoint *ep)
+{
+	return  ep->sync_master &&
+		ep->sync_master->type == SND_USB_ENDPOINT_TYPE_DATA &&
+		ep->type == SND_USB_ENDPOINT_TYPE_DATA &&
+		usb_pipeout(ep->pipe);
+}
+
+/*
+ * For streaming based on information derived from sync endpoints,
+ * prepare_outbound_urb_sizes() will call next_packet_size() to
+ * determine the number of samples to be sent in the next packet.
+ *
+ * For implicit feedback, next_packet_size() is unused.
+ */
+int snd_usb_endpoint_next_packet_size(struct snd_usb_endpoint *ep)
+{
+	unsigned long flags;
+	int ret;
+
+	if (ep->fill_max)
+		return ep->maxframesize;
+
+	spin_lock_irqsave(&ep->lock, flags);
+	ep->phase = (ep->phase & 0xffff)
+		+ (ep->freqm << ep->datainterval);
+	ret = min(ep->phase >> 16, ep->maxframesize);
+	spin_unlock_irqrestore(&ep->lock, flags);
+
+	return ret;
+}
+
+static void retire_outbound_urb(struct snd_usb_endpoint *ep,
+				struct snd_urb_ctx *urb_ctx)
+{
+	if (ep->retire_data_urb)
+		ep->retire_data_urb(ep->data_subs, urb_ctx->urb);
+}
+
+static void retire_inbound_urb(struct snd_usb_endpoint *ep,
+			       struct snd_urb_ctx *urb_ctx)
+{
+	struct urb *urb = urb_ctx->urb;
+
+	if (unlikely(ep->skip_packets > 0)) {
+		ep->skip_packets--;
+		return;
+	}
+
+	if (ep->sync_slave)
+		snd_usb_handle_sync_urb(ep->sync_slave, ep, urb);
+
+	if (ep->retire_data_urb)
+		ep->retire_data_urb(ep->data_subs, urb);
+}
+
+static void prepare_silent_urb(struct snd_usb_endpoint *ep,
+			       struct snd_urb_ctx *ctx)
+{
+	struct urb *urb = ctx->urb;
+	unsigned int offs = 0;
+	unsigned int extra = 0;
+	__le32 packet_length;
+	int i;
+
+	/* For tx_length_quirk, put packet length at start of packet */
+	if (ep->chip->tx_length_quirk)
+		extra = sizeof(packet_length);
+
+	for (i = 0; i < ctx->packets; ++i) {
+		unsigned int offset;
+		unsigned int length;
+		int counts;
+
+		if (ctx->packet_size[i])
+			counts = ctx->packet_size[i];
+		else
+			counts = snd_usb_endpoint_next_packet_size(ep);
+
+		length = counts * ep->stride; /* number of silent bytes */
+		offset = offs * ep->stride + extra * i;
+		urb->iso_frame_desc[i].offset = offset;
+		urb->iso_frame_desc[i].length = length + extra;
+		if (extra) {
+			packet_length = cpu_to_le32(length);
+			memcpy(urb->transfer_buffer + offset,
+			       &packet_length, sizeof(packet_length));
+		}
+		memset(urb->transfer_buffer + offset + extra,
+		       ep->silence_value, length);
+		offs += counts;
+	}
+
+	urb->number_of_packets = ctx->packets;
+	urb->transfer_buffer_length = offs * ep->stride + ctx->packets * extra;
+}
+
+/*
+ * Prepare a PLAYBACK urb for submission to the bus.
+ */
+static void prepare_outbound_urb(struct snd_usb_endpoint *ep,
+				 struct snd_urb_ctx *ctx)
+{
+	struct urb *urb = ctx->urb;
+	unsigned char *cp = urb->transfer_buffer;
+
+	urb->dev = ep->chip->dev; /* we need to set this at each time */
+
+	switch (ep->type) {
+	case SND_USB_ENDPOINT_TYPE_DATA:
+		if (ep->prepare_data_urb) {
+			ep->prepare_data_urb(ep->data_subs, urb);
+		} else {
+			/* no data provider, so send silence */
+			prepare_silent_urb(ep, ctx);
+		}
+		break;
+
+	case SND_USB_ENDPOINT_TYPE_SYNC:
+		if (snd_usb_get_speed(ep->chip->dev) >= USB_SPEED_HIGH) {
+			/*
+			 * fill the length and offset of each urb descriptor.
+			 * the fixed 12.13 frequency is passed as 16.16 through the pipe.
+			 */
+			urb->iso_frame_desc[0].length = 4;
+			urb->iso_frame_desc[0].offset = 0;
+			cp[0] = ep->freqn;
+			cp[1] = ep->freqn >> 8;
+			cp[2] = ep->freqn >> 16;
+			cp[3] = ep->freqn >> 24;
+		} else {
+			/*
+			 * fill the length and offset of each urb descriptor.
+			 * the fixed 10.14 frequency is passed through the pipe.
+			 */
+			urb->iso_frame_desc[0].length = 3;
+			urb->iso_frame_desc[0].offset = 0;
+			cp[0] = ep->freqn >> 2;
+			cp[1] = ep->freqn >> 10;
+			cp[2] = ep->freqn >> 18;
+		}
+
+		break;
+	}
+}
+
+/*
+ * Prepare a CAPTURE or SYNC urb for submission to the bus.
+ */
+static inline void prepare_inbound_urb(struct snd_usb_endpoint *ep,
+				       struct snd_urb_ctx *urb_ctx)
+{
+	int i, offs;
+	struct urb *urb = urb_ctx->urb;
+
+	urb->dev = ep->chip->dev; /* we need to set this at each time */
+
+	switch (ep->type) {
+	case SND_USB_ENDPOINT_TYPE_DATA:
+		offs = 0;
+		for (i = 0; i < urb_ctx->packets; i++) {
+			urb->iso_frame_desc[i].offset = offs;
+			urb->iso_frame_desc[i].length = ep->curpacksize;
+			offs += ep->curpacksize;
+		}
+
+		urb->transfer_buffer_length = offs;
+		urb->number_of_packets = urb_ctx->packets;
+		break;
+
+	case SND_USB_ENDPOINT_TYPE_SYNC:
+		urb->iso_frame_desc[0].length = min(4u, ep->syncmaxsize);
+		urb->iso_frame_desc[0].offset = 0;
+		break;
+	}
+}
+
+/*
+ * Send output urbs that have been prepared previously. URBs are dequeued
+ * from ep->ready_playback_urbs and in case there there aren't any available
+ * or there are no packets that have been prepared, this function does
+ * nothing.
+ *
+ * The reason why the functionality of sending and preparing URBs is separated
+ * is that host controllers don't guarantee the order in which they return
+ * inbound and outbound packets to their submitters.
+ *
+ * This function is only used for implicit feedback endpoints. For endpoints
+ * driven by dedicated sync endpoints, URBs are immediately re-submitted
+ * from their completion handler.
+ */
+static void queue_pending_output_urbs(struct snd_usb_endpoint *ep)
+{
+	while (test_bit(EP_FLAG_RUNNING, &ep->flags)) {
+
+		unsigned long flags;
+		struct snd_usb_packet_info *uninitialized_var(packet);
+		struct snd_urb_ctx *ctx = NULL;
+		int err, i;
+
+		spin_lock_irqsave(&ep->lock, flags);
+		if (ep->next_packet_read_pos != ep->next_packet_write_pos) {
+			packet = ep->next_packet + ep->next_packet_read_pos;
+			ep->next_packet_read_pos++;
+			ep->next_packet_read_pos %= MAX_URBS;
+
+			/* take URB out of FIFO */
+			if (!list_empty(&ep->ready_playback_urbs))
+				ctx = list_first_entry(&ep->ready_playback_urbs,
+					       struct snd_urb_ctx, ready_list);
+		}
+		spin_unlock_irqrestore(&ep->lock, flags);
+
+		if (ctx == NULL)
+			return;
+
+		list_del_init(&ctx->ready_list);
+
+		/* copy over the length information */
+		for (i = 0; i < packet->packets; i++)
+			ctx->packet_size[i] = packet->packet_size[i];
+
+		/* call the data handler to fill in playback data */
+		prepare_outbound_urb(ep, ctx);
+
+		err = usb_submit_urb(ctx->urb, GFP_ATOMIC);
+		if (err < 0)
+			usb_audio_err(ep->chip,
+				"Unable to submit urb #%d: %d (urb %p)\n",
+				ctx->index, err, ctx->urb);
+		else
+			set_bit(ctx->index, &ep->active_mask);
+	}
+}
+
+/*
+ * complete callback for urbs
+ */
+static void snd_complete_urb(struct urb *urb)
+{
+	struct snd_urb_ctx *ctx = urb->context;
+	struct snd_usb_endpoint *ep = ctx->ep;
+	struct snd_pcm_substream *substream;
+	unsigned long flags;
+	int err;
+
+	if (unlikely(urb->status == -ENOENT ||		/* unlinked */
+		     urb->status == -ENODEV ||		/* device removed */
+		     urb->status == -ECONNRESET ||	/* unlinked */
+		     urb->status == -ESHUTDOWN))	/* device disabled */
+		goto exit_clear;
+	/* device disconnected */
+	if (unlikely(atomic_read(&ep->chip->shutdown)))
+		goto exit_clear;
+
+	if (unlikely(!test_bit(EP_FLAG_RUNNING, &ep->flags)))
+		goto exit_clear;
+
+	if (usb_pipeout(ep->pipe)) {
+		retire_outbound_urb(ep, ctx);
+		/* can be stopped during retire callback */
+		if (unlikely(!test_bit(EP_FLAG_RUNNING, &ep->flags)))
+			goto exit_clear;
+
+		if (snd_usb_endpoint_implicit_feedback_sink(ep)) {
+			spin_lock_irqsave(&ep->lock, flags);
+			list_add_tail(&ctx->ready_list, &ep->ready_playback_urbs);
+			spin_unlock_irqrestore(&ep->lock, flags);
+			queue_pending_output_urbs(ep);
+
+			goto exit_clear;
+		}
+
+		prepare_outbound_urb(ep, ctx);
+	} else {
+		retire_inbound_urb(ep, ctx);
+		/* can be stopped during retire callback */
+		if (unlikely(!test_bit(EP_FLAG_RUNNING, &ep->flags)))
+			goto exit_clear;
+
+		prepare_inbound_urb(ep, ctx);
+	}
+
+	err = usb_submit_urb(urb, GFP_ATOMIC);
+	if (err == 0)
+		return;
+
+	usb_audio_err(ep->chip, "cannot submit urb (err = %d)\n", err);
+	if (ep->data_subs && ep->data_subs->pcm_substream) {
+		substream = ep->data_subs->pcm_substream;
+		snd_pcm_stop_xrun(substream);
+	}
+
+exit_clear:
+	clear_bit(ctx->index, &ep->active_mask);
+}
+
+/**
+ * snd_usb_add_endpoint: Add an endpoint to an USB audio chip
+ *
+ * @chip: The chip
+ * @alts: The USB host interface
+ * @ep_num: The number of the endpoint to use
+ * @direction: SNDRV_PCM_STREAM_PLAYBACK or SNDRV_PCM_STREAM_CAPTURE
+ * @type: SND_USB_ENDPOINT_TYPE_DATA or SND_USB_ENDPOINT_TYPE_SYNC
+ *
+ * If the requested endpoint has not been added to the given chip before,
+ * a new instance is created. Otherwise, a pointer to the previoulsy
+ * created instance is returned. In case of any error, NULL is returned.
+ *
+ * New endpoints will be added to chip->ep_list and must be freed by
+ * calling snd_usb_endpoint_free().
+ *
+ * For SND_USB_ENDPOINT_TYPE_SYNC, the caller needs to guarantee that
+ * bNumEndpoints > 1 beforehand.
+ */
+struct snd_usb_endpoint *snd_usb_add_endpoint(struct snd_usb_audio *chip,
+					      struct usb_host_interface *alts,
+					      int ep_num, int direction, int type)
+{
+	struct snd_usb_endpoint *ep;
+	int is_playback = direction == SNDRV_PCM_STREAM_PLAYBACK;
+
+	if (WARN_ON(!alts))
+		return NULL;
+
+	mutex_lock(&chip->mutex);
+
+	list_for_each_entry(ep, &chip->ep_list, list) {
+		if (ep->ep_num == ep_num &&
+		    ep->iface == alts->desc.bInterfaceNumber &&
+		    ep->altsetting == alts->desc.bAlternateSetting) {
+			usb_audio_dbg(ep->chip,
+				      "Re-using EP %x in iface %d,%d @%p\n",
+					ep_num, ep->iface, ep->altsetting, ep);
+			goto __exit_unlock;
+		}
+	}
+
+	usb_audio_dbg(chip, "Creating new %s %s endpoint #%x\n",
+		    is_playback ? "playback" : "capture",
+		    type == SND_USB_ENDPOINT_TYPE_DATA ? "data" : "sync",
+		    ep_num);
+
+	ep = kzalloc(sizeof(*ep), GFP_KERNEL);
+	if (!ep)
+		goto __exit_unlock;
+
+	ep->chip = chip;
+	spin_lock_init(&ep->lock);
+	ep->type = type;
+	ep->ep_num = ep_num;
+	ep->iface = alts->desc.bInterfaceNumber;
+	ep->altsetting = alts->desc.bAlternateSetting;
+	INIT_LIST_HEAD(&ep->ready_playback_urbs);
+	ep_num &= USB_ENDPOINT_NUMBER_MASK;
+
+	if (is_playback)
+		ep->pipe = usb_sndisocpipe(chip->dev, ep_num);
+	else
+		ep->pipe = usb_rcvisocpipe(chip->dev, ep_num);
+
+	if (type == SND_USB_ENDPOINT_TYPE_SYNC) {
+		if (get_endpoint(alts, 1)->bLength >= USB_DT_ENDPOINT_AUDIO_SIZE &&
+		    get_endpoint(alts, 1)->bRefresh >= 1 &&
+		    get_endpoint(alts, 1)->bRefresh <= 9)
+			ep->syncinterval = get_endpoint(alts, 1)->bRefresh;
+		else if (snd_usb_get_speed(chip->dev) == USB_SPEED_FULL)
+			ep->syncinterval = 1;
+		else if (get_endpoint(alts, 1)->bInterval >= 1 &&
+			 get_endpoint(alts, 1)->bInterval <= 16)
+			ep->syncinterval = get_endpoint(alts, 1)->bInterval - 1;
+		else
+			ep->syncinterval = 3;
+
+		ep->syncmaxsize = le16_to_cpu(get_endpoint(alts, 1)->wMaxPacketSize);
+	}
+
+	list_add_tail(&ep->list, &chip->ep_list);
+
+__exit_unlock:
+	mutex_unlock(&chip->mutex);
+
+	return ep;
+}
+
+/*
+ *  wait until all urbs are processed.
+ */
+static int wait_clear_urbs(struct snd_usb_endpoint *ep)
+{
+	unsigned long end_time = jiffies + msecs_to_jiffies(1000);
+	int alive;
+
+	do {
+		alive = bitmap_weight(&ep->active_mask, ep->nurbs);
+		if (!alive)
+			break;
+
+		schedule_timeout_uninterruptible(1);
+	} while (time_before(jiffies, end_time));
+
+	if (alive)
+		usb_audio_err(ep->chip,
+			"timeout: still %d active urbs on EP #%x\n",
+			alive, ep->ep_num);
+	clear_bit(EP_FLAG_STOPPING, &ep->flags);
+
+	ep->data_subs = NULL;
+	ep->sync_slave = NULL;
+	ep->retire_data_urb = NULL;
+	ep->prepare_data_urb = NULL;
+
+	return 0;
+}
+
+/* sync the pending stop operation;
+ * this function itself doesn't trigger the stop operation
+ */
+void snd_usb_endpoint_sync_pending_stop(struct snd_usb_endpoint *ep)
+{
+	if (ep && test_bit(EP_FLAG_STOPPING, &ep->flags))
+		wait_clear_urbs(ep);
+}
+
+/*
+ * unlink active urbs.
+ */
+static int deactivate_urbs(struct snd_usb_endpoint *ep, bool force)
+{
+	unsigned int i;
+
+	if (!force && atomic_read(&ep->chip->shutdown)) /* to be sure... */
+		return -EBADFD;
+
+	clear_bit(EP_FLAG_RUNNING, &ep->flags);
+
+	INIT_LIST_HEAD(&ep->ready_playback_urbs);
+	ep->next_packet_read_pos = 0;
+	ep->next_packet_write_pos = 0;
+
+	for (i = 0; i < ep->nurbs; i++) {
+		if (test_bit(i, &ep->active_mask)) {
+			if (!test_and_set_bit(i, &ep->unlink_mask)) {
+				struct urb *u = ep->urb[i].urb;
+				usb_unlink_urb(u);
+			}
+		}
+	}
+
+	return 0;
+}
+
+/*
+ * release an endpoint's urbs
+ */
+static void release_urbs(struct snd_usb_endpoint *ep, int force)
+{
+	int i;
+
+	/* route incoming urbs to nirvana */
+	ep->retire_data_urb = NULL;
+	ep->prepare_data_urb = NULL;
+
+	/* stop urbs */
+	deactivate_urbs(ep, force);
+	wait_clear_urbs(ep);
+
+	for (i = 0; i < ep->nurbs; i++)
+		release_urb_ctx(&ep->urb[i]);
+
+	if (ep->syncbuf)
+		usb_free_coherent(ep->chip->dev, SYNC_URBS * 4,
+				  ep->syncbuf, ep->sync_dma);
+
+	ep->syncbuf = NULL;
+	ep->nurbs = 0;
+}
+
+/*
+ * configure a data endpoint
+ */
+static int data_ep_set_params(struct snd_usb_endpoint *ep,
+			      snd_pcm_format_t pcm_format,
+			      unsigned int channels,
+			      unsigned int period_bytes,
+			      unsigned int frames_per_period,
+			      unsigned int periods_per_buffer,
+			      struct audioformat *fmt,
+			      struct snd_usb_endpoint *sync_ep)
+{
+	unsigned int maxsize, minsize, packs_per_ms, max_packs_per_urb;
+	unsigned int max_packs_per_period, urbs_per_period, urb_packs;
+	unsigned int max_urbs, i;
+	int frame_bits = snd_pcm_format_physical_width(pcm_format) * channels;
+	int tx_length_quirk = (ep->chip->tx_length_quirk &&
+			       usb_pipeout(ep->pipe));
+
+	if (pcm_format == SNDRV_PCM_FORMAT_DSD_U16_LE && fmt->dsd_dop) {
+		/*
+		 * When operating in DSD DOP mode, the size of a sample frame
+		 * in hardware differs from the actual physical format width
+		 * because we need to make room for the DOP markers.
+		 */
+		frame_bits += channels << 3;
+	}
+
+	ep->datainterval = fmt->datainterval;
+	ep->stride = frame_bits >> 3;
+
+	switch (pcm_format) {
+	case SNDRV_PCM_FORMAT_U8:
+		ep->silence_value = 0x80;
+		break;
+	case SNDRV_PCM_FORMAT_DSD_U8:
+	case SNDRV_PCM_FORMAT_DSD_U16_LE:
+	case SNDRV_PCM_FORMAT_DSD_U32_LE:
+	case SNDRV_PCM_FORMAT_DSD_U16_BE:
+	case SNDRV_PCM_FORMAT_DSD_U32_BE:
+		ep->silence_value = 0x69;
+		break;
+	default:
+		ep->silence_value = 0;
+	}
+
+	/* assume max. frequency is 50% higher than nominal */
+	ep->freqmax = ep->freqn + (ep->freqn >> 1);
+	/* Round up freqmax to nearest integer in order to calculate maximum
+	 * packet size, which must represent a whole number of frames.
+	 * This is accomplished by adding 0x0.ffff before converting the
+	 * Q16.16 format into integer.
+	 * In order to accurately calculate the maximum packet size when
+	 * the data interval is more than 1 (i.e. ep->datainterval > 0),
+	 * multiply by the data interval prior to rounding. For instance,
+	 * a freqmax of 41 kHz will result in a max packet size of 6 (5.125)
+	 * frames with a data interval of 1, but 11 (10.25) frames with a
+	 * data interval of 2.
+	 * (ep->freqmax << ep->datainterval overflows at 8.192 MHz for the
+	 * maximum datainterval value of 3, at USB full speed, higher for
+	 * USB high speed, noting that ep->freqmax is in units of
+	 * frames per packet in Q16.16 format.)
+	 */
+	maxsize = (((ep->freqmax << ep->datainterval) + 0xffff) >> 16) *
+			 (frame_bits >> 3);
+	if (tx_length_quirk)
+		maxsize += sizeof(__le32); /* Space for length descriptor */
+	/* but wMaxPacketSize might reduce this */
+	if (ep->maxpacksize && ep->maxpacksize < maxsize) {
+		/* whatever fits into a max. size packet */
+		unsigned int data_maxsize = maxsize = ep->maxpacksize;
+
+		if (tx_length_quirk)
+			/* Need to remove the length descriptor to calc freq */
+			data_maxsize -= sizeof(__le32);
+		ep->freqmax = (data_maxsize / (frame_bits >> 3))
+				<< (16 - ep->datainterval);
+	}
+
+	if (ep->fill_max)
+		ep->curpacksize = ep->maxpacksize;
+	else
+		ep->curpacksize = maxsize;
+
+	if (snd_usb_get_speed(ep->chip->dev) != USB_SPEED_FULL) {
+		packs_per_ms = 8 >> ep->datainterval;
+		max_packs_per_urb = MAX_PACKS_HS;
+	} else {
+		packs_per_ms = 1;
+		max_packs_per_urb = MAX_PACKS;
+	}
+	if (sync_ep && !snd_usb_endpoint_implicit_feedback_sink(ep))
+		max_packs_per_urb = min(max_packs_per_urb,
+					1U << sync_ep->syncinterval);
+	max_packs_per_urb = max(1u, max_packs_per_urb >> ep->datainterval);
+
+	/*
+	 * Capture endpoints need to use small URBs because there's no way
+	 * to tell in advance where the next period will end, and we don't
+	 * want the next URB to complete much after the period ends.
+	 *
+	 * Playback endpoints with implicit sync much use the same parameters
+	 * as their corresponding capture endpoint.
+	 */
+	if (usb_pipein(ep->pipe) ||
+			snd_usb_endpoint_implicit_feedback_sink(ep)) {
+
+		urb_packs = packs_per_ms;
+		/*
+		 * Wireless devices can poll at a max rate of once per 4ms.
+		 * For dataintervals less than 5, increase the packet count to
+		 * allow the host controller to use bursting to fill in the
+		 * gaps.
+		 */
+		if (snd_usb_get_speed(ep->chip->dev) == USB_SPEED_WIRELESS) {
+			int interval = ep->datainterval;
+			while (interval < 5) {
+				urb_packs <<= 1;
+				++interval;
+			}
+		}
+		/* make capture URBs <= 1 ms and smaller than a period */
+		urb_packs = min(max_packs_per_urb, urb_packs);
+		while (urb_packs > 1 && urb_packs * maxsize >= period_bytes)
+			urb_packs >>= 1;
+		ep->nurbs = MAX_URBS;
+
+	/*
+	 * Playback endpoints without implicit sync are adjusted so that
+	 * a period fits as evenly as possible in the smallest number of
+	 * URBs.  The total number of URBs is adjusted to the size of the
+	 * ALSA buffer, subject to the MAX_URBS and MAX_QUEUE limits.
+	 */
+	} else {
+		/* determine how small a packet can be */
+		minsize = (ep->freqn >> (16 - ep->datainterval)) *
+				(frame_bits >> 3);
+		/* with sync from device, assume it can be 12% lower */
+		if (sync_ep)
+			minsize -= minsize >> 3;
+		minsize = max(minsize, 1u);
+
+		/* how many packets will contain an entire ALSA period? */
+		max_packs_per_period = DIV_ROUND_UP(period_bytes, minsize);
+
+		/* how many URBs will contain a period? */
+		urbs_per_period = DIV_ROUND_UP(max_packs_per_period,
+				max_packs_per_urb);
+		/* how many packets are needed in each URB? */
+		urb_packs = DIV_ROUND_UP(max_packs_per_period, urbs_per_period);
+
+		/* limit the number of frames in a single URB */
+		ep->max_urb_frames = DIV_ROUND_UP(frames_per_period,
+					urbs_per_period);
+
+		/* try to use enough URBs to contain an entire ALSA buffer */
+		max_urbs = min((unsigned) MAX_URBS,
+				MAX_QUEUE * packs_per_ms / urb_packs);
+		ep->nurbs = min(max_urbs, urbs_per_period * periods_per_buffer);
+	}
+
+	/* allocate and initialize data urbs */
+	for (i = 0; i < ep->nurbs; i++) {
+		struct snd_urb_ctx *u = &ep->urb[i];
+		u->index = i;
+		u->ep = ep;
+		u->packets = urb_packs;
+		u->buffer_size = maxsize * u->packets;
+
+		if (fmt->fmt_type == UAC_FORMAT_TYPE_II)
+			u->packets++; /* for transfer delimiter */
+		u->urb = usb_alloc_urb(u->packets, GFP_KERNEL);
+		if (!u->urb)
+			goto out_of_memory;
+
+		u->urb->transfer_buffer =
+			usb_alloc_coherent(ep->chip->dev, u->buffer_size,
+					   GFP_KERNEL, &u->urb->transfer_dma);
+		if (!u->urb->transfer_buffer)
+			goto out_of_memory;
+		u->urb->pipe = ep->pipe;
+		u->urb->transfer_flags = URB_NO_TRANSFER_DMA_MAP;
+		u->urb->interval = 1 << ep->datainterval;
+		u->urb->context = u;
+		u->urb->complete = snd_complete_urb;
+		INIT_LIST_HEAD(&u->ready_list);
+	}
+
+	return 0;
+
+out_of_memory:
+	release_urbs(ep, 0);
+	return -ENOMEM;
+}
+
+/*
+ * configure a sync endpoint
+ */
+static int sync_ep_set_params(struct snd_usb_endpoint *ep)
+{
+	int i;
+
+	ep->syncbuf = usb_alloc_coherent(ep->chip->dev, SYNC_URBS * 4,
+					 GFP_KERNEL, &ep->sync_dma);
+	if (!ep->syncbuf)
+		return -ENOMEM;
+
+	for (i = 0; i < SYNC_URBS; i++) {
+		struct snd_urb_ctx *u = &ep->urb[i];
+		u->index = i;
+		u->ep = ep;
+		u->packets = 1;
+		u->urb = usb_alloc_urb(1, GFP_KERNEL);
+		if (!u->urb)
+			goto out_of_memory;
+		u->urb->transfer_buffer = ep->syncbuf + i * 4;
+		u->urb->transfer_dma = ep->sync_dma + i * 4;
+		u->urb->transfer_buffer_length = 4;
+		u->urb->pipe = ep->pipe;
+		u->urb->transfer_flags = URB_NO_TRANSFER_DMA_MAP;
+		u->urb->number_of_packets = 1;
+		u->urb->interval = 1 << ep->syncinterval;
+		u->urb->context = u;
+		u->urb->complete = snd_complete_urb;
+	}
+
+	ep->nurbs = SYNC_URBS;
+
+	return 0;
+
+out_of_memory:
+	release_urbs(ep, 0);
+	return -ENOMEM;
+}
+
+/**
+ * snd_usb_endpoint_set_params: configure an snd_usb_endpoint
+ *
+ * @ep: the snd_usb_endpoint to configure
+ * @pcm_format: the audio fomat.
+ * @channels: the number of audio channels.
+ * @period_bytes: the number of bytes in one alsa period.
+ * @period_frames: the number of frames in one alsa period.
+ * @buffer_periods: the number of periods in one alsa buffer.
+ * @rate: the frame rate.
+ * @fmt: the USB audio format information
+ * @sync_ep: the sync endpoint to use, if any
+ *
+ * Determine the number of URBs to be used on this endpoint.
+ * An endpoint must be configured before it can be started.
+ * An endpoint that is already running can not be reconfigured.
+ */
+int snd_usb_endpoint_set_params(struct snd_usb_endpoint *ep,
+				snd_pcm_format_t pcm_format,
+				unsigned int channels,
+				unsigned int period_bytes,
+				unsigned int period_frames,
+				unsigned int buffer_periods,
+				unsigned int rate,
+				struct audioformat *fmt,
+				struct snd_usb_endpoint *sync_ep)
+{
+	int err;
+
+	if (ep->use_count != 0) {
+		usb_audio_warn(ep->chip,
+			 "Unable to change format on ep #%x: already in use\n",
+			 ep->ep_num);
+		return -EBUSY;
+	}
+
+	/* release old buffers, if any */
+	release_urbs(ep, 0);
+
+	ep->datainterval = fmt->datainterval;
+	ep->maxpacksize = fmt->maxpacksize;
+	ep->fill_max = !!(fmt->attributes & UAC_EP_CS_ATTR_FILL_MAX);
+
+	if (snd_usb_get_speed(ep->chip->dev) == USB_SPEED_FULL)
+		ep->freqn = get_usb_full_speed_rate(rate);
+	else
+		ep->freqn = get_usb_high_speed_rate(rate);
+
+	/* calculate the frequency in 16.16 format */
+	ep->freqm = ep->freqn;
+	ep->freqshift = INT_MIN;
+
+	ep->phase = 0;
+
+	switch (ep->type) {
+	case  SND_USB_ENDPOINT_TYPE_DATA:
+		err = data_ep_set_params(ep, pcm_format, channels,
+					 period_bytes, period_frames,
+					 buffer_periods, fmt, sync_ep);
+		break;
+	case  SND_USB_ENDPOINT_TYPE_SYNC:
+		err = sync_ep_set_params(ep);
+		break;
+	default:
+		err = -EINVAL;
+	}
+
+	usb_audio_dbg(ep->chip,
+		"Setting params for ep #%x (type %d, %d urbs), ret=%d\n",
+		ep->ep_num, ep->type, ep->nurbs, err);
+
+	return err;
+}
+
+/**
+ * snd_usb_endpoint_start: start an snd_usb_endpoint
+ *
+ * @ep: the endpoint to start
+ *
+ * A call to this function will increment the use count of the endpoint.
+ * In case it is not already running, the URBs for this endpoint will be
+ * submitted. Otherwise, this function does nothing.
+ *
+ * Must be balanced to calls of snd_usb_endpoint_stop().
+ *
+ * Returns an error if the URB submission failed, 0 in all other cases.
+ */
+int snd_usb_endpoint_start(struct snd_usb_endpoint *ep)
+{
+	int err;
+	unsigned int i;
+
+	if (atomic_read(&ep->chip->shutdown))
+		return -EBADFD;
+
+	/* already running? */
+	if (++ep->use_count != 1)
+		return 0;
+
+	/* just to be sure */
+	deactivate_urbs(ep, false);
+
+	ep->active_mask = 0;
+	ep->unlink_mask = 0;
+	ep->phase = 0;
+
+	snd_usb_endpoint_start_quirk(ep);
+
+	/*
+	 * If this endpoint has a data endpoint as implicit feedback source,
+	 * don't start the urbs here. Instead, mark them all as available,
+	 * wait for the record urbs to return and queue the playback urbs
+	 * from that context.
+	 */
+
+	set_bit(EP_FLAG_RUNNING, &ep->flags);
+
+	if (snd_usb_endpoint_implicit_feedback_sink(ep)) {
+		for (i = 0; i < ep->nurbs; i++) {
+			struct snd_urb_ctx *ctx = ep->urb + i;
+			list_add_tail(&ctx->ready_list, &ep->ready_playback_urbs);
+		}
+
+		return 0;
+	}
+
+	for (i = 0; i < ep->nurbs; i++) {
+		struct urb *urb = ep->urb[i].urb;
+
+		if (snd_BUG_ON(!urb))
+			goto __error;
+
+		if (usb_pipeout(ep->pipe)) {
+			prepare_outbound_urb(ep, urb->context);
+		} else {
+			prepare_inbound_urb(ep, urb->context);
+		}
+
+		err = usb_submit_urb(urb, GFP_ATOMIC);
+		if (err < 0) {
+			usb_audio_err(ep->chip,
+				"cannot submit urb %d, error %d: %s\n",
+				i, err, usb_error_string(err));
+			goto __error;
+		}
+		set_bit(i, &ep->active_mask);
+	}
+
+	return 0;
+
+__error:
+	clear_bit(EP_FLAG_RUNNING, &ep->flags);
+	ep->use_count--;
+	deactivate_urbs(ep, false);
+	return -EPIPE;
+}
+
+/**
+ * snd_usb_endpoint_stop: stop an snd_usb_endpoint
+ *
+ * @ep: the endpoint to stop (may be NULL)
+ *
+ * A call to this function will decrement the use count of the endpoint.
+ * In case the last user has requested the endpoint stop, the URBs will
+ * actually be deactivated.
+ *
+ * Must be balanced to calls of snd_usb_endpoint_start().
+ *
+ * The caller needs to synchronize the pending stop operation via
+ * snd_usb_endpoint_sync_pending_stop().
+ */
+void snd_usb_endpoint_stop(struct snd_usb_endpoint *ep)
+{
+	if (!ep)
+		return;
+
+	if (snd_BUG_ON(ep->use_count == 0))
+		return;
+
+	if (--ep->use_count == 0) {
+		deactivate_urbs(ep, false);
+		set_bit(EP_FLAG_STOPPING, &ep->flags);
+	}
+}
+
+/**
+ * snd_usb_endpoint_deactivate: deactivate an snd_usb_endpoint
+ *
+ * @ep: the endpoint to deactivate
+ *
+ * If the endpoint is not currently in use, this functions will
+ * deactivate its associated URBs.
+ *
+ * In case of any active users, this functions does nothing.
+ */
+void snd_usb_endpoint_deactivate(struct snd_usb_endpoint *ep)
+{
+	if (!ep)
+		return;
+
+	if (ep->use_count != 0)
+		return;
+
+	deactivate_urbs(ep, true);
+	wait_clear_urbs(ep);
+}
+
+/**
+ * snd_usb_endpoint_release: Tear down an snd_usb_endpoint
+ *
+ * @ep: the endpoint to release
+ *
+ * This function does not care for the endpoint's use count but will tear
+ * down all the streaming URBs immediately.
+ */
+void snd_usb_endpoint_release(struct snd_usb_endpoint *ep)
+{
+	release_urbs(ep, 1);
+}
+
+/**
+ * snd_usb_endpoint_free: Free the resources of an snd_usb_endpoint
+ *
+ * @ep: the endpoint to free
+ *
+ * This free all resources of the given ep.
+ */
+void snd_usb_endpoint_free(struct snd_usb_endpoint *ep)
+{
+	kfree(ep);
+}
+
+/**
+ * snd_usb_handle_sync_urb: parse an USB sync packet
+ *
+ * @ep: the endpoint to handle the packet
+ * @sender: the sending endpoint
+ * @urb: the received packet
+ *
+ * This function is called from the context of an endpoint that received
+ * the packet and is used to let another endpoint object handle the payload.
+ */
+void snd_usb_handle_sync_urb(struct snd_usb_endpoint *ep,
+			     struct snd_usb_endpoint *sender,
+			     const struct urb *urb)
+{
+	int shift;
+	unsigned int f;
+	unsigned long flags;
+
+	snd_BUG_ON(ep == sender);
+
+	/*
+	 * In case the endpoint is operating in implicit feedback mode, prepare
+	 * a new outbound URB that has the same layout as the received packet
+	 * and add it to the list of pending urbs. queue_pending_output_urbs()
+	 * will take care of them later.
+	 */
+	if (snd_usb_endpoint_implicit_feedback_sink(ep) &&
+	    ep->use_count != 0) {
+
+		/* implicit feedback case */
+		int i, bytes = 0;
+		struct snd_urb_ctx *in_ctx;
+		struct snd_usb_packet_info *out_packet;
+
+		in_ctx = urb->context;
+
+		/* Count overall packet size */
+		for (i = 0; i < in_ctx->packets; i++)
+			if (urb->iso_frame_desc[i].status == 0)
+				bytes += urb->iso_frame_desc[i].actual_length;
+
+		/*
+		 * skip empty packets. At least M-Audio's Fast Track Ultra stops
+		 * streaming once it received a 0-byte OUT URB
+		 */
+		if (bytes == 0)
+			return;
+
+		spin_lock_irqsave(&ep->lock, flags);
+		out_packet = ep->next_packet + ep->next_packet_write_pos;
+
+		/*
+		 * Iterate through the inbound packet and prepare the lengths
+		 * for the output packet. The OUT packet we are about to send
+		 * will have the same amount of payload bytes per stride as the
+		 * IN packet we just received. Since the actual size is scaled
+		 * by the stride, use the sender stride to calculate the length
+		 * in case the number of channels differ between the implicitly
+		 * fed-back endpoint and the synchronizing endpoint.
+		 */
+
+		out_packet->packets = in_ctx->packets;
+		for (i = 0; i < in_ctx->packets; i++) {
+			if (urb->iso_frame_desc[i].status == 0)
+				out_packet->packet_size[i] =
+					urb->iso_frame_desc[i].actual_length / sender->stride;
+			else
+				out_packet->packet_size[i] = 0;
+		}
+
+		ep->next_packet_write_pos++;
+		ep->next_packet_write_pos %= MAX_URBS;
+		spin_unlock_irqrestore(&ep->lock, flags);
+		queue_pending_output_urbs(ep);
+
+		return;
+	}
+
+	/*
+	 * process after playback sync complete
+	 *
+	 * Full speed devices report feedback values in 10.14 format as samples
+	 * per frame, high speed devices in 16.16 format as samples per
+	 * microframe.
+	 *
+	 * Because the Audio Class 1 spec was written before USB 2.0, many high
+	 * speed devices use a wrong interpretation, some others use an
+	 * entirely different format.
+	 *
+	 * Therefore, we cannot predict what format any particular device uses
+	 * and must detect it automatically.
+	 */
+
+	if (urb->iso_frame_desc[0].status != 0 ||
+	    urb->iso_frame_desc[0].actual_length < 3)
+		return;
+
+	f = le32_to_cpup(urb->transfer_buffer);
+	if (urb->iso_frame_desc[0].actual_length == 3)
+		f &= 0x00ffffff;
+	else
+		f &= 0x0fffffff;
+
+	if (f == 0)
+		return;
+
+	if (unlikely(sender->tenor_fb_quirk)) {
+		/*
+		 * Devices based on Tenor 8802 chipsets (TEAC UD-H01
+		 * and others) sometimes change the feedback value
+		 * by +/- 0x1.0000.
+		 */
+		if (f < ep->freqn - 0x8000)
+			f += 0xf000;
+		else if (f > ep->freqn + 0x8000)
+			f -= 0xf000;
+	} else if (unlikely(ep->freqshift == INT_MIN)) {
+		/*
+		 * The first time we see a feedback value, determine its format
+		 * by shifting it left or right until it matches the nominal
+		 * frequency value.  This assumes that the feedback does not
+		 * differ from the nominal value more than +50% or -25%.
+		 */
+		shift = 0;
+		while (f < ep->freqn - ep->freqn / 4) {
+			f <<= 1;
+			shift++;
+		}
+		while (f > ep->freqn + ep->freqn / 2) {
+			f >>= 1;
+			shift--;
+		}
+		ep->freqshift = shift;
+	} else if (ep->freqshift >= 0)
+		f <<= ep->freqshift;
+	else
+		f >>= -ep->freqshift;
+
+	if (likely(f >= ep->freqn - ep->freqn / 8 && f <= ep->freqmax)) {
+		/*
+		 * If the frequency looks valid, set it.
+		 * This value is referred to in prepare_playback_urb().
+		 */
+		spin_lock_irqsave(&ep->lock, flags);
+		ep->freqm = f;
+		spin_unlock_irqrestore(&ep->lock, flags);
+	} else {
+		/*
+		 * Out of range; maybe the shift value is wrong.
+		 * Reset it so that we autodetect again the next time.
+		 */
+		ep->freqshift = INT_MIN;
+	}
+}
+
diff --git a/sound/usb/endpoint.h b/sound/usb/endpoint.h
new file mode 100644
index 0000000..63a39d4
--- /dev/null
+++ b/sound/usb/endpoint.h
@@ -0,0 +1,37 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __USBAUDIO_ENDPOINT_H
+#define __USBAUDIO_ENDPOINT_H
+
+#define SND_USB_ENDPOINT_TYPE_DATA     0
+#define SND_USB_ENDPOINT_TYPE_SYNC     1
+
+struct snd_usb_endpoint *snd_usb_add_endpoint(struct snd_usb_audio *chip,
+					      struct usb_host_interface *alts,
+					      int ep_num, int direction, int type);
+
+int snd_usb_endpoint_set_params(struct snd_usb_endpoint *ep,
+				snd_pcm_format_t pcm_format,
+				unsigned int channels,
+				unsigned int period_bytes,
+				unsigned int period_frames,
+				unsigned int buffer_periods,
+				unsigned int rate,
+				struct audioformat *fmt,
+				struct snd_usb_endpoint *sync_ep);
+
+int  snd_usb_endpoint_start(struct snd_usb_endpoint *ep);
+void snd_usb_endpoint_stop(struct snd_usb_endpoint *ep);
+void snd_usb_endpoint_sync_pending_stop(struct snd_usb_endpoint *ep);
+int  snd_usb_endpoint_activate(struct snd_usb_endpoint *ep);
+void snd_usb_endpoint_deactivate(struct snd_usb_endpoint *ep);
+void snd_usb_endpoint_release(struct snd_usb_endpoint *ep);
+void snd_usb_endpoint_free(struct snd_usb_endpoint *ep);
+
+int snd_usb_endpoint_implicit_feedback_sink(struct snd_usb_endpoint *ep);
+int snd_usb_endpoint_next_packet_size(struct snd_usb_endpoint *ep);
+
+void snd_usb_handle_sync_urb(struct snd_usb_endpoint *ep,
+			     struct snd_usb_endpoint *sender,
+			     const struct urb *urb);
+
+#endif /* __USBAUDIO_ENDPOINT_H */
diff --git a/sound/usb/format.c b/sound/usb/format.c
new file mode 100644
index 0000000..fd13ac1
--- /dev/null
+++ b/sound/usb/format.c
@@ -0,0 +1,585 @@
+/*
+ *   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 <linux/usb/audio-v2.h>
+#include <linux/usb/audio-v3.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+
+#include "usbaudio.h"
+#include "card.h"
+#include "quirks.h"
+#include "helper.h"
+#include "debug.h"
+#include "clock.h"
+#include "format.h"
+
+/*
+ * parse the audio format type I descriptor
+ * and returns the corresponding pcm format
+ *
+ * @dev: usb device
+ * @fp: audioformat record
+ * @format: the format tag (wFormatTag)
+ * @fmt: the format type descriptor (v1/v2) or AudioStreaming descriptor (v3)
+ */
+static u64 parse_audio_format_i_type(struct snd_usb_audio *chip,
+				     struct audioformat *fp,
+				     u64 format, void *_fmt)
+{
+	int sample_width, sample_bytes;
+	u64 pcm_formats = 0;
+
+	switch (fp->protocol) {
+	case UAC_VERSION_1:
+	default: {
+		struct uac_format_type_i_discrete_descriptor *fmt = _fmt;
+		sample_width = fmt->bBitResolution;
+		sample_bytes = fmt->bSubframeSize;
+		format = 1ULL << format;
+		break;
+	}
+
+	case UAC_VERSION_2: {
+		struct uac_format_type_i_ext_descriptor *fmt = _fmt;
+		sample_width = fmt->bBitResolution;
+		sample_bytes = fmt->bSubslotSize;
+
+		if (format & UAC2_FORMAT_TYPE_I_RAW_DATA) {
+			pcm_formats |= SNDRV_PCM_FMTBIT_SPECIAL;
+			/* flag potentially raw DSD capable altsettings */
+			fp->dsd_raw = true;
+		}
+
+		format <<= 1;
+		break;
+	}
+	case UAC_VERSION_3: {
+		struct uac3_as_header_descriptor *as = _fmt;
+
+		sample_width = as->bBitResolution;
+		sample_bytes = as->bSubslotSize;
+
+		if (format & UAC3_FORMAT_TYPE_I_RAW_DATA)
+			pcm_formats |= SNDRV_PCM_FMTBIT_SPECIAL;
+
+		format <<= 1;
+		break;
+	}
+	}
+
+	if ((pcm_formats == 0) &&
+	    (format == 0 || format == (1 << UAC_FORMAT_TYPE_I_UNDEFINED))) {
+		/* some devices don't define this correctly... */
+		usb_audio_info(chip, "%u:%d : format type 0 is detected, processed as PCM\n",
+			fp->iface, fp->altsetting);
+		format = 1 << UAC_FORMAT_TYPE_I_PCM;
+	}
+	if (format & (1 << UAC_FORMAT_TYPE_I_PCM)) {
+		if (((chip->usb_id == USB_ID(0x0582, 0x0016)) ||
+		     /* Edirol SD-90 */
+		     (chip->usb_id == USB_ID(0x0582, 0x000c))) &&
+		     /* Roland SC-D70 */
+		    sample_width == 24 && sample_bytes == 2)
+			sample_bytes = 3;
+		else if (sample_width > sample_bytes * 8) {
+			usb_audio_info(chip, "%u:%d : sample bitwidth %d in over sample bytes %d\n",
+				 fp->iface, fp->altsetting,
+				 sample_width, sample_bytes);
+		}
+		/* check the format byte size */
+		switch (sample_bytes) {
+		case 1:
+			pcm_formats |= SNDRV_PCM_FMTBIT_S8;
+			break;
+		case 2:
+			if (snd_usb_is_big_endian_format(chip, fp))
+				pcm_formats |= SNDRV_PCM_FMTBIT_S16_BE; /* grrr, big endian!! */
+			else
+				pcm_formats |= SNDRV_PCM_FMTBIT_S16_LE;
+			break;
+		case 3:
+			if (snd_usb_is_big_endian_format(chip, fp))
+				pcm_formats |= SNDRV_PCM_FMTBIT_S24_3BE; /* grrr, big endian!! */
+			else
+				pcm_formats |= SNDRV_PCM_FMTBIT_S24_3LE;
+			break;
+		case 4:
+			pcm_formats |= SNDRV_PCM_FMTBIT_S32_LE;
+			break;
+		default:
+			usb_audio_info(chip,
+				 "%u:%d : unsupported sample bitwidth %d in %d bytes\n",
+				 fp->iface, fp->altsetting,
+				 sample_width, sample_bytes);
+			break;
+		}
+	}
+	if (format & (1 << UAC_FORMAT_TYPE_I_PCM8)) {
+		/* Dallas DS4201 workaround: it advertises U8 format, but really
+		   supports S8. */
+		if (chip->usb_id == USB_ID(0x04fa, 0x4201))
+			pcm_formats |= SNDRV_PCM_FMTBIT_S8;
+		else
+			pcm_formats |= SNDRV_PCM_FMTBIT_U8;
+	}
+	if (format & (1 << UAC_FORMAT_TYPE_I_IEEE_FLOAT)) {
+		pcm_formats |= SNDRV_PCM_FMTBIT_FLOAT_LE;
+	}
+	if (format & (1 << UAC_FORMAT_TYPE_I_ALAW)) {
+		pcm_formats |= SNDRV_PCM_FMTBIT_A_LAW;
+	}
+	if (format & (1 << UAC_FORMAT_TYPE_I_MULAW)) {
+		pcm_formats |= SNDRV_PCM_FMTBIT_MU_LAW;
+	}
+	if (format & ~0x3f) {
+		usb_audio_info(chip,
+			 "%u:%d : unsupported format bits %#llx\n",
+			 fp->iface, fp->altsetting, format);
+	}
+
+	pcm_formats |= snd_usb_interface_dsd_format_quirks(chip, fp, sample_bytes);
+
+	return pcm_formats;
+}
+
+
+/*
+ * parse the format descriptor and stores the possible sample rates
+ * on the audioformat table (audio class v1).
+ *
+ * @dev: usb device
+ * @fp: audioformat record
+ * @fmt: the format descriptor
+ * @offset: the start offset of descriptor pointing the rate type
+ *          (7 for type I and II, 8 for type II)
+ */
+static int parse_audio_format_rates_v1(struct snd_usb_audio *chip, struct audioformat *fp,
+				       unsigned char *fmt, int offset)
+{
+	int nr_rates = fmt[offset];
+
+	if (fmt[0] < offset + 1 + 3 * (nr_rates ? nr_rates : 2)) {
+		usb_audio_err(chip,
+			"%u:%d : invalid UAC_FORMAT_TYPE desc\n",
+			fp->iface, fp->altsetting);
+		return -EINVAL;
+	}
+
+	if (nr_rates) {
+		/*
+		 * build the rate table and bitmap flags
+		 */
+		int r, idx;
+
+		fp->rate_table = kmalloc_array(nr_rates, sizeof(int),
+					       GFP_KERNEL);
+		if (fp->rate_table == NULL)
+			return -ENOMEM;
+
+		fp->nr_rates = 0;
+		fp->rate_min = fp->rate_max = 0;
+		for (r = 0, idx = offset + 1; r < nr_rates; r++, idx += 3) {
+			unsigned int rate = combine_triple(&fmt[idx]);
+			if (!rate)
+				continue;
+			/* C-Media CM6501 mislabels its 96 kHz altsetting */
+			/* Terratec Aureon 7.1 USB C-Media 6206, too */
+			if (rate == 48000 && nr_rates == 1 &&
+			    (chip->usb_id == USB_ID(0x0d8c, 0x0201) ||
+			     chip->usb_id == USB_ID(0x0d8c, 0x0102) ||
+			     chip->usb_id == USB_ID(0x0ccd, 0x00b1)) &&
+			    fp->altsetting == 5 && fp->maxpacksize == 392)
+				rate = 96000;
+			/* Creative VF0420/VF0470 Live Cams report 16 kHz instead of 8kHz */
+			if (rate == 16000 &&
+			    (chip->usb_id == USB_ID(0x041e, 0x4064) ||
+			     chip->usb_id == USB_ID(0x041e, 0x4068)))
+				rate = 8000;
+
+			fp->rate_table[fp->nr_rates] = rate;
+			if (!fp->rate_min || rate < fp->rate_min)
+				fp->rate_min = rate;
+			if (!fp->rate_max || rate > fp->rate_max)
+				fp->rate_max = rate;
+			fp->rates |= snd_pcm_rate_to_rate_bit(rate);
+			fp->nr_rates++;
+		}
+		if (!fp->nr_rates) {
+			hwc_debug("All rates were zero. Skipping format!\n");
+			return -EINVAL;
+		}
+	} else {
+		/* continuous rates */
+		fp->rates = SNDRV_PCM_RATE_CONTINUOUS;
+		fp->rate_min = combine_triple(&fmt[offset + 1]);
+		fp->rate_max = combine_triple(&fmt[offset + 4]);
+	}
+	return 0;
+}
+
+/*
+ * Helper function to walk the array of sample rate triplets reported by
+ * the device. The problem is that we need to parse whole array first to
+ * get to know how many sample rates we have to expect.
+ * Then fp->rate_table can be allocated and filled.
+ */
+static int parse_uac2_sample_rate_range(struct snd_usb_audio *chip,
+					struct audioformat *fp, int nr_triplets,
+					const unsigned char *data)
+{
+	int i, nr_rates = 0;
+
+	fp->rates = fp->rate_min = fp->rate_max = 0;
+
+	for (i = 0; i < nr_triplets; i++) {
+		int min = combine_quad(&data[2 + 12 * i]);
+		int max = combine_quad(&data[6 + 12 * i]);
+		int res = combine_quad(&data[10 + 12 * i]);
+		unsigned int rate;
+
+		if ((max < 0) || (min < 0) || (res < 0) || (max < min))
+			continue;
+
+		/*
+		 * for ranges with res == 1, we announce a continuous sample
+		 * rate range, and this function should return 0 for no further
+		 * parsing.
+		 */
+		if (res == 1) {
+			fp->rate_min = min;
+			fp->rate_max = max;
+			fp->rates = SNDRV_PCM_RATE_CONTINUOUS;
+			return 0;
+		}
+
+		for (rate = min; rate <= max; rate += res) {
+			if (fp->rate_table)
+				fp->rate_table[nr_rates] = rate;
+			if (!fp->rate_min || rate < fp->rate_min)
+				fp->rate_min = rate;
+			if (!fp->rate_max || rate > fp->rate_max)
+				fp->rate_max = rate;
+			fp->rates |= snd_pcm_rate_to_rate_bit(rate);
+
+			nr_rates++;
+			if (nr_rates >= MAX_NR_RATES) {
+				usb_audio_err(chip, "invalid uac2 rates\n");
+				break;
+			}
+
+			/* avoid endless loop */
+			if (res == 0)
+				break;
+		}
+	}
+
+	return nr_rates;
+}
+
+/*
+ * parse the format descriptor and stores the possible sample rates
+ * on the audioformat table (audio class v2 and v3).
+ */
+static int parse_audio_format_rates_v2v3(struct snd_usb_audio *chip,
+				       struct audioformat *fp)
+{
+	struct usb_device *dev = chip->dev;
+	unsigned char tmp[2], *data;
+	int nr_triplets, data_size, ret = 0;
+	int clock = snd_usb_clock_find_source(chip, fp->protocol,
+					      fp->clock, false);
+
+	if (clock < 0) {
+		dev_err(&dev->dev,
+			"%s(): unable to find clock source (clock %d)\n",
+				__func__, clock);
+		goto err;
+	}
+
+	/* get the number of sample rates first by only fetching 2 bytes */
+	ret = snd_usb_ctl_msg(dev, usb_rcvctrlpipe(dev, 0), UAC2_CS_RANGE,
+			      USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_IN,
+			      UAC2_CS_CONTROL_SAM_FREQ << 8,
+			      snd_usb_ctrl_intf(chip) | (clock << 8),
+			      tmp, sizeof(tmp));
+
+	if (ret < 0) {
+		dev_err(&dev->dev,
+			"%s(): unable to retrieve number of sample rates (clock %d)\n",
+				__func__, clock);
+		goto err;
+	}
+
+	nr_triplets = (tmp[1] << 8) | tmp[0];
+	data_size = 2 + 12 * nr_triplets;
+	data = kzalloc(data_size, GFP_KERNEL);
+	if (!data) {
+		ret = -ENOMEM;
+		goto err;
+	}
+
+	/* now get the full information */
+	ret = snd_usb_ctl_msg(dev, usb_rcvctrlpipe(dev, 0), UAC2_CS_RANGE,
+			      USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_IN,
+			      UAC2_CS_CONTROL_SAM_FREQ << 8,
+			      snd_usb_ctrl_intf(chip) | (clock << 8),
+			      data, data_size);
+
+	if (ret < 0) {
+		dev_err(&dev->dev,
+			"%s(): unable to retrieve sample rate range (clock %d)\n",
+				__func__, clock);
+		ret = -EINVAL;
+		goto err_free;
+	}
+
+	/* Call the triplet parser, and make sure fp->rate_table is NULL.
+	 * We just use the return value to know how many sample rates we
+	 * will have to deal with. */
+	kfree(fp->rate_table);
+	fp->rate_table = NULL;
+	fp->nr_rates = parse_uac2_sample_rate_range(chip, fp, nr_triplets, data);
+
+	if (fp->nr_rates == 0) {
+		/* SNDRV_PCM_RATE_CONTINUOUS */
+		ret = 0;
+		goto err_free;
+	}
+
+	fp->rate_table = kmalloc_array(fp->nr_rates, sizeof(int), GFP_KERNEL);
+	if (!fp->rate_table) {
+		ret = -ENOMEM;
+		goto err_free;
+	}
+
+	/* Call the triplet parser again, but this time, fp->rate_table is
+	 * allocated, so the rates will be stored */
+	parse_uac2_sample_rate_range(chip, fp, nr_triplets, data);
+
+err_free:
+	kfree(data);
+err:
+	return ret;
+}
+
+/*
+ * parse the format type I and III descriptors
+ */
+static int parse_audio_format_i(struct snd_usb_audio *chip,
+				struct audioformat *fp, u64 format,
+				void *_fmt)
+{
+	snd_pcm_format_t pcm_format;
+	unsigned int fmt_type;
+	int ret;
+
+	switch (fp->protocol) {
+	default:
+	case UAC_VERSION_1:
+	case UAC_VERSION_2: {
+		struct uac_format_type_i_continuous_descriptor *fmt = _fmt;
+
+		fmt_type = fmt->bFormatType;
+		break;
+	}
+	case UAC_VERSION_3: {
+		/* fp->fmt_type is already set in this case */
+		fmt_type = fp->fmt_type;
+		break;
+	}
+	}
+
+	if (fmt_type == UAC_FORMAT_TYPE_III) {
+		/* FIXME: the format type is really IECxxx
+		 *        but we give normal PCM format to get the existing
+		 *        apps working...
+		 */
+		switch (chip->usb_id) {
+
+		case USB_ID(0x0763, 0x2003): /* M-Audio Audiophile USB */
+			if (chip->setup == 0x00 && 
+			    fp->altsetting == 6)
+				pcm_format = SNDRV_PCM_FORMAT_S16_BE;
+			else
+				pcm_format = SNDRV_PCM_FORMAT_S16_LE;
+			break;
+		default:
+			pcm_format = SNDRV_PCM_FORMAT_S16_LE;
+		}
+		fp->formats = pcm_format_to_bits(pcm_format);
+	} else {
+		fp->formats = parse_audio_format_i_type(chip, fp, format, _fmt);
+		if (!fp->formats)
+			return -EINVAL;
+	}
+
+	/* gather possible sample rates */
+	/* audio class v1 reports possible sample rates as part of the
+	 * proprietary class specific descriptor.
+	 * audio class v2 uses class specific EP0 range requests for that.
+	 */
+	switch (fp->protocol) {
+	default:
+	case UAC_VERSION_1: {
+		struct uac_format_type_i_continuous_descriptor *fmt = _fmt;
+
+		fp->channels = fmt->bNrChannels;
+		ret = parse_audio_format_rates_v1(chip, fp, (unsigned char *) fmt, 7);
+		break;
+	}
+	case UAC_VERSION_2:
+	case UAC_VERSION_3: {
+		/* fp->channels is already set in this case */
+		ret = parse_audio_format_rates_v2v3(chip, fp);
+		break;
+	}
+	}
+
+	if (fp->channels < 1) {
+		usb_audio_err(chip,
+			"%u:%d : invalid channels %d\n",
+			fp->iface, fp->altsetting, fp->channels);
+		return -EINVAL;
+	}
+
+	return ret;
+}
+
+/*
+ * parse the format type II descriptor
+ */
+static int parse_audio_format_ii(struct snd_usb_audio *chip,
+				 struct audioformat *fp,
+				 u64 format, void *_fmt)
+{
+	int brate, framesize, ret;
+
+	switch (format) {
+	case UAC_FORMAT_TYPE_II_AC3:
+		/* FIXME: there is no AC3 format defined yet */
+		// fp->formats = SNDRV_PCM_FMTBIT_AC3;
+		fp->formats = SNDRV_PCM_FMTBIT_U8; /* temporary hack to receive byte streams */
+		break;
+	case UAC_FORMAT_TYPE_II_MPEG:
+		fp->formats = SNDRV_PCM_FMTBIT_MPEG;
+		break;
+	default:
+		usb_audio_info(chip,
+			 "%u:%d : unknown format tag %#llx is detected.  processed as MPEG.\n",
+			 fp->iface, fp->altsetting, format);
+		fp->formats = SNDRV_PCM_FMTBIT_MPEG;
+		break;
+	}
+
+	fp->channels = 1;
+
+	switch (fp->protocol) {
+	default:
+	case UAC_VERSION_1: {
+		struct uac_format_type_ii_discrete_descriptor *fmt = _fmt;
+		brate = le16_to_cpu(fmt->wMaxBitRate);
+		framesize = le16_to_cpu(fmt->wSamplesPerFrame);
+		usb_audio_info(chip, "found format II with max.bitrate = %d, frame size=%d\n", brate, framesize);
+		fp->frame_size = framesize;
+		ret = parse_audio_format_rates_v1(chip, fp, _fmt, 8); /* fmt[8..] sample rates */
+		break;
+	}
+	case UAC_VERSION_2: {
+		struct uac_format_type_ii_ext_descriptor *fmt = _fmt;
+		brate = le16_to_cpu(fmt->wMaxBitRate);
+		framesize = le16_to_cpu(fmt->wSamplesPerFrame);
+		usb_audio_info(chip, "found format II with max.bitrate = %d, frame size=%d\n", brate, framesize);
+		fp->frame_size = framesize;
+		ret = parse_audio_format_rates_v2v3(chip, fp);
+		break;
+	}
+	}
+
+	return ret;
+}
+
+int snd_usb_parse_audio_format(struct snd_usb_audio *chip,
+			       struct audioformat *fp, u64 format,
+			       struct uac_format_type_i_continuous_descriptor *fmt,
+			       int stream)
+{
+	int err;
+
+	switch (fmt->bFormatType) {
+	case UAC_FORMAT_TYPE_I:
+	case UAC_FORMAT_TYPE_III:
+		err = parse_audio_format_i(chip, fp, format, fmt);
+		break;
+	case UAC_FORMAT_TYPE_II:
+		err = parse_audio_format_ii(chip, fp, format, fmt);
+		break;
+	default:
+		usb_audio_info(chip,
+			 "%u:%d : format type %d is not supported yet\n",
+			 fp->iface, fp->altsetting,
+			 fmt->bFormatType);
+		return -ENOTSUPP;
+	}
+	fp->fmt_type = fmt->bFormatType;
+	if (err < 0)
+		return err;
+#if 1
+	/* FIXME: temporary hack for extigy/audigy 2 nx/zs */
+	/* extigy apparently supports sample rates other than 48k
+	 * but not in ordinary way.  so we enable only 48k atm.
+	 */
+	if (chip->usb_id == USB_ID(0x041e, 0x3000) ||
+	    chip->usb_id == USB_ID(0x041e, 0x3020) ||
+	    chip->usb_id == USB_ID(0x041e, 0x3061)) {
+		if (fmt->bFormatType == UAC_FORMAT_TYPE_I &&
+		    fp->rates != SNDRV_PCM_RATE_48000 &&
+		    fp->rates != SNDRV_PCM_RATE_96000)
+			return -ENOTSUPP;
+	}
+#endif
+	return 0;
+}
+
+int snd_usb_parse_audio_format_v3(struct snd_usb_audio *chip,
+			       struct audioformat *fp,
+			       struct uac3_as_header_descriptor *as,
+			       int stream)
+{
+	u64 format = le64_to_cpu(as->bmFormats);
+	int err;
+
+	/*
+	 * Type I format bits are D0..D6
+	 * This test works because type IV is not supported
+	 */
+	if (format & 0x7f)
+		fp->fmt_type = UAC_FORMAT_TYPE_I;
+	else
+		fp->fmt_type = UAC_FORMAT_TYPE_III;
+
+	err = parse_audio_format_i(chip, fp, format, as);
+	if (err < 0)
+		return err;
+
+	return 0;
+}
diff --git a/sound/usb/format.h b/sound/usb/format.h
new file mode 100644
index 0000000..e701718
--- /dev/null
+++ b/sound/usb/format.h
@@ -0,0 +1,14 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __USBAUDIO_FORMAT_H
+#define __USBAUDIO_FORMAT_H
+
+int snd_usb_parse_audio_format(struct snd_usb_audio *chip,
+			       struct audioformat *fp, u64 format,
+			       struct uac_format_type_i_continuous_descriptor *fmt,
+			       int stream);
+
+int snd_usb_parse_audio_format_v3(struct snd_usb_audio *chip,
+			       struct audioformat *fp,
+			       struct uac3_as_header_descriptor *as,
+			       int stream);
+#endif /*  __USBAUDIO_FORMAT_H */
diff --git a/sound/usb/helper.c b/sound/usb/helper.c
new file mode 100644
index 0000000..7712e2b
--- /dev/null
+++ b/sound/usb/helper.c
@@ -0,0 +1,133 @@
+/*
+ *   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 "usbaudio.h"
+#include "helper.h"
+#include "quirks.h"
+
+/*
+ * combine bytes and get an integer value
+ */
+unsigned int snd_usb_combine_bytes(unsigned char *bytes, int size)
+{
+	switch (size) {
+	case 1:  return *bytes;
+	case 2:  return combine_word(bytes);
+	case 3:  return combine_triple(bytes);
+	case 4:  return combine_quad(bytes);
+	default: return 0;
+	}
+}
+
+/*
+ * parse descriptor buffer and return the pointer starting the given
+ * descriptor type.
+ */
+void *snd_usb_find_desc(void *descstart, int desclen, void *after, u8 dtype)
+{
+	u8 *p, *end, *next;
+
+	p = descstart;
+	end = p + desclen;
+	for (; p < end;) {
+		if (p[0] < 2)
+			return NULL;
+		next = p + p[0];
+		if (next > end)
+			return NULL;
+		if (p[1] == dtype && (!after || (void *)p > after)) {
+			return p;
+		}
+		p = next;
+	}
+	return NULL;
+}
+
+/*
+ * find a class-specified interface descriptor with the given subtype.
+ */
+void *snd_usb_find_csint_desc(void *buffer, int buflen, void *after, u8 dsubtype)
+{
+	unsigned char *p = after;
+
+	while ((p = snd_usb_find_desc(buffer, buflen, p,
+				      USB_DT_CS_INTERFACE)) != NULL) {
+		if (p[0] >= 3 && p[2] == dsubtype)
+			return p;
+	}
+	return NULL;
+}
+
+/*
+ * Wrapper for usb_control_msg().
+ * Allocates a temp buffer to prevent dmaing from/to the stack.
+ */
+int snd_usb_ctl_msg(struct usb_device *dev, unsigned int pipe, __u8 request,
+		    __u8 requesttype, __u16 value, __u16 index, void *data,
+		    __u16 size)
+{
+	int err;
+	void *buf = NULL;
+	int timeout;
+
+	if (size > 0) {
+		buf = kmemdup(data, size, GFP_KERNEL);
+		if (!buf)
+			return -ENOMEM;
+	}
+
+	if (requesttype & USB_DIR_IN)
+		timeout = USB_CTRL_GET_TIMEOUT;
+	else
+		timeout = USB_CTRL_SET_TIMEOUT;
+
+	err = usb_control_msg(dev, pipe, request, requesttype,
+			      value, index, buf, size, timeout);
+
+	if (size > 0) {
+		memcpy(data, buf, size);
+		kfree(buf);
+	}
+
+	snd_usb_ctl_msg_quirk(dev, pipe, request, requesttype,
+			      value, index, data, size);
+
+	return err;
+}
+
+unsigned char snd_usb_parse_datainterval(struct snd_usb_audio *chip,
+					 struct usb_host_interface *alts)
+{
+	switch (snd_usb_get_speed(chip->dev)) {
+	case USB_SPEED_HIGH:
+	case USB_SPEED_WIRELESS:
+	case USB_SPEED_SUPER:
+	case USB_SPEED_SUPER_PLUS:
+		if (get_endpoint(alts, 0)->bInterval >= 1 &&
+		    get_endpoint(alts, 0)->bInterval <= 4)
+			return get_endpoint(alts, 0)->bInterval - 1;
+		break;
+	default:
+		break;
+	}
+	return 0;
+}
+
diff --git a/sound/usb/helper.h b/sound/usb/helper.h
new file mode 100644
index 0000000..d338bd0
--- /dev/null
+++ b/sound/usb/helper.h
@@ -0,0 +1,33 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __USBAUDIO_HELPER_H
+#define __USBAUDIO_HELPER_H
+
+unsigned int snd_usb_combine_bytes(unsigned char *bytes, int size);
+
+void *snd_usb_find_desc(void *descstart, int desclen, void *after, u8 dtype);
+void *snd_usb_find_csint_desc(void *descstart, int desclen, void *after, u8 dsubtype);
+
+int snd_usb_ctl_msg(struct usb_device *dev, unsigned int pipe,
+		    __u8 request, __u8 requesttype, __u16 value, __u16 index,
+		    void *data, __u16 size);
+
+unsigned char snd_usb_parse_datainterval(struct snd_usb_audio *chip,
+					 struct usb_host_interface *alts);
+
+/*
+ * retrieve usb_interface descriptor from the host interface
+ * (conditional for compatibility with the older API)
+ */
+#define get_iface_desc(iface)	(&(iface)->desc)
+#define get_endpoint(alt,ep)	(&(alt)->endpoint[ep].desc)
+#define get_ep_desc(ep)		(&(ep)->desc)
+#define get_cfg_desc(cfg)	(&(cfg)->desc)
+
+#define snd_usb_get_speed(dev) ((dev)->speed)
+
+static inline int snd_usb_ctrl_intf(struct snd_usb_audio *chip)
+{
+	return get_iface_desc(chip->ctrl_intf)->bInterfaceNumber;
+}
+
+#endif /* __USBAUDIO_HELPER_H */
diff --git a/sound/usb/hiface/Makefile b/sound/usb/hiface/Makefile
new file mode 100644
index 0000000..463b136
--- /dev/null
+++ b/sound/usb/hiface/Makefile
@@ -0,0 +1,2 @@
+snd-usb-hiface-objs := chip.o pcm.o
+obj-$(CONFIG_SND_USB_HIFACE) += snd-usb-hiface.o
diff --git a/sound/usb/hiface/chip.c b/sound/usb/hiface/chip.c
new file mode 100644
index 0000000..2670d64
--- /dev/null
+++ b/sound/usb/hiface/chip.c
@@ -0,0 +1,297 @@
+/*
+ * Linux driver for M2Tech hiFace compatible devices
+ *
+ * Copyright 2012-2013 (C) M2TECH S.r.l and Amarula Solutions B.V.
+ *
+ * Authors:  Michael Trimarchi <michael@amarulasolutions.com>
+ *           Antonio Ospite <ao2@amarulasolutions.com>
+ *
+ * The driver is based on the work done in TerraTec DMX 6Fire USB
+ *
+ * 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.
+ */
+
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <sound/initval.h>
+
+#include "chip.h"
+#include "pcm.h"
+
+MODULE_AUTHOR("Michael Trimarchi <michael@amarulasolutions.com>");
+MODULE_AUTHOR("Antonio Ospite <ao2@amarulasolutions.com>");
+MODULE_DESCRIPTION("M2Tech hiFace USB-SPDIF audio driver");
+MODULE_LICENSE("GPL v2");
+MODULE_SUPPORTED_DEVICE("{{M2Tech,Young},"
+			 "{M2Tech,hiFace},"
+			 "{M2Tech,North Star},"
+			 "{M2Tech,W4S Young},"
+			 "{M2Tech,Corrson},"
+			 "{M2Tech,AUDIA},"
+			 "{M2Tech,SL Audio},"
+			 "{M2Tech,Empirical},"
+			 "{M2Tech,Rockna},"
+			 "{M2Tech,Pathos},"
+			 "{M2Tech,Metronome},"
+			 "{M2Tech,CAD},"
+			 "{M2Tech,Audio Esclusive},"
+			 "{M2Tech,Rotel},"
+			 "{M2Tech,Eeaudio},"
+			 "{The Chord Company,CHORD},"
+			 "{AVA Group A/S,Vitus}}");
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 0-max */
+static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* Id for card */
+static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP; /* Enable this card */
+
+#define DRIVER_NAME "snd-usb-hiface"
+#define CARD_NAME "hiFace"
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for " CARD_NAME " soundcard.");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for " CARD_NAME " soundcard.");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "Enable " CARD_NAME " soundcard.");
+
+static DEFINE_MUTEX(register_mutex);
+
+struct hiface_vendor_quirk {
+	const char *device_name;
+	u8 extra_freq;
+};
+
+static int hiface_chip_create(struct usb_interface *intf,
+			      struct usb_device *device, int idx,
+			      const struct hiface_vendor_quirk *quirk,
+			      struct hiface_chip **rchip)
+{
+	struct snd_card *card = NULL;
+	struct hiface_chip *chip;
+	int ret;
+	int len;
+
+	*rchip = NULL;
+
+	/* if we are here, card can be registered in alsa. */
+	ret = snd_card_new(&intf->dev, index[idx], id[idx], THIS_MODULE,
+			   sizeof(*chip), &card);
+	if (ret < 0) {
+		dev_err(&device->dev, "cannot create alsa card.\n");
+		return ret;
+	}
+
+	strlcpy(card->driver, DRIVER_NAME, sizeof(card->driver));
+
+	if (quirk && quirk->device_name)
+		strlcpy(card->shortname, quirk->device_name, sizeof(card->shortname));
+	else
+		strlcpy(card->shortname, "M2Tech generic audio", sizeof(card->shortname));
+
+	strlcat(card->longname, card->shortname, sizeof(card->longname));
+	len = strlcat(card->longname, " at ", sizeof(card->longname));
+	if (len < sizeof(card->longname))
+		usb_make_path(device, card->longname + len,
+			      sizeof(card->longname) - len);
+
+	chip = card->private_data;
+	chip->dev = device;
+	chip->card = card;
+
+	*rchip = chip;
+	return 0;
+}
+
+static int hiface_chip_probe(struct usb_interface *intf,
+			     const struct usb_device_id *usb_id)
+{
+	const struct hiface_vendor_quirk *quirk = (struct hiface_vendor_quirk *)usb_id->driver_info;
+	int ret;
+	int i;
+	struct hiface_chip *chip;
+	struct usb_device *device = interface_to_usbdev(intf);
+
+	ret = usb_set_interface(device, 0, 0);
+	if (ret != 0) {
+		dev_err(&device->dev, "can't set first interface for " CARD_NAME " device.\n");
+		return -EIO;
+	}
+
+	/* check whether the card is already registered */
+	chip = NULL;
+	mutex_lock(&register_mutex);
+
+	for (i = 0; i < SNDRV_CARDS; i++)
+		if (enable[i])
+			break;
+
+	if (i >= SNDRV_CARDS) {
+		dev_err(&device->dev, "no available " CARD_NAME " audio device\n");
+		ret = -ENODEV;
+		goto err;
+	}
+
+	ret = hiface_chip_create(intf, device, i, quirk, &chip);
+	if (ret < 0)
+		goto err;
+
+	ret = hiface_pcm_init(chip, quirk ? quirk->extra_freq : 0);
+	if (ret < 0)
+		goto err_chip_destroy;
+
+	ret = snd_card_register(chip->card);
+	if (ret < 0) {
+		dev_err(&device->dev, "cannot register " CARD_NAME " card\n");
+		goto err_chip_destroy;
+	}
+
+	mutex_unlock(&register_mutex);
+
+	usb_set_intfdata(intf, chip);
+	return 0;
+
+err_chip_destroy:
+	snd_card_free(chip->card);
+err:
+	mutex_unlock(&register_mutex);
+	return ret;
+}
+
+static void hiface_chip_disconnect(struct usb_interface *intf)
+{
+	struct hiface_chip *chip;
+	struct snd_card *card;
+
+	chip = usb_get_intfdata(intf);
+	if (!chip)
+		return;
+
+	card = chip->card;
+
+	/* Make sure that the userspace cannot create new request */
+	snd_card_disconnect(card);
+
+	hiface_pcm_abort(chip);
+	snd_card_free_when_closed(card);
+}
+
+static const struct usb_device_id device_table[] = {
+	{
+		USB_DEVICE(0x04b4, 0x0384),
+		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
+			.device_name = "Young",
+			.extra_freq = 1,
+		}
+	},
+	{
+		USB_DEVICE(0x04b4, 0x930b),
+		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
+			.device_name = "hiFace",
+		}
+	},
+	{
+		USB_DEVICE(0x04b4, 0x931b),
+		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
+			.device_name = "North Star",
+		}
+	},
+	{
+		USB_DEVICE(0x04b4, 0x931c),
+		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
+			.device_name = "W4S Young",
+		}
+	},
+	{
+		USB_DEVICE(0x04b4, 0x931d),
+		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
+			.device_name = "Corrson",
+		}
+	},
+	{
+		USB_DEVICE(0x04b4, 0x931e),
+		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
+			.device_name = "AUDIA",
+		}
+	},
+	{
+		USB_DEVICE(0x04b4, 0x931f),
+		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
+			.device_name = "SL Audio",
+		}
+	},
+	{
+		USB_DEVICE(0x04b4, 0x9320),
+		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
+			.device_name = "Empirical",
+		}
+	},
+	{
+		USB_DEVICE(0x04b4, 0x9321),
+		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
+			.device_name = "Rockna",
+		}
+	},
+	{
+		USB_DEVICE(0x249c, 0x9001),
+		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
+			.device_name = "Pathos",
+		}
+	},
+	{
+		USB_DEVICE(0x249c, 0x9002),
+		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
+			.device_name = "Metronome",
+		}
+	},
+	{
+		USB_DEVICE(0x249c, 0x9006),
+		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
+			.device_name = "CAD",
+		}
+	},
+	{
+		USB_DEVICE(0x249c, 0x9008),
+		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
+			.device_name = "Audio Esclusive",
+		}
+	},
+	{
+		USB_DEVICE(0x249c, 0x931c),
+		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
+			.device_name = "Rotel",
+		}
+	},
+	{
+		USB_DEVICE(0x249c, 0x932c),
+		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
+			.device_name = "Eeaudio",
+		}
+	},
+	{
+		USB_DEVICE(0x245f, 0x931c),
+		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
+			.device_name = "CHORD",
+		}
+	},
+	{
+		USB_DEVICE(0x25c6, 0x9002),
+		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
+			.device_name = "Vitus",
+		}
+	},
+	{}
+};
+
+MODULE_DEVICE_TABLE(usb, device_table);
+
+static struct usb_driver hiface_usb_driver = {
+	.name = DRIVER_NAME,
+	.probe = hiface_chip_probe,
+	.disconnect = hiface_chip_disconnect,
+	.id_table = device_table,
+};
+
+module_usb_driver(hiface_usb_driver);
diff --git a/sound/usb/hiface/chip.h b/sound/usb/hiface/chip.h
new file mode 100644
index 0000000..189a137
--- /dev/null
+++ b/sound/usb/hiface/chip.h
@@ -0,0 +1,30 @@
+/*
+ * Linux driver for M2Tech hiFace compatible devices
+ *
+ * Copyright 2012-2013 (C) M2TECH S.r.l and Amarula Solutions B.V.
+ *
+ * Authors:  Michael Trimarchi <michael@amarulasolutions.com>
+ *           Antonio Ospite <ao2@amarulasolutions.com>
+ *
+ * The driver is based on the work done in TerraTec DMX 6Fire USB
+ *
+ * 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.
+ */
+
+#ifndef HIFACE_CHIP_H
+#define HIFACE_CHIP_H
+
+#include <linux/usb.h>
+#include <sound/core.h>
+
+struct pcm_runtime;
+
+struct hiface_chip {
+	struct usb_device *dev;
+	struct snd_card *card;
+	struct pcm_runtime *pcm;
+};
+#endif /* HIFACE_CHIP_H */
diff --git a/sound/usb/hiface/pcm.c b/sound/usb/hiface/pcm.c
new file mode 100644
index 0000000..e1fbb9c
--- /dev/null
+++ b/sound/usb/hiface/pcm.c
@@ -0,0 +1,627 @@
+/*
+ * Linux driver for M2Tech hiFace compatible devices
+ *
+ * Copyright 2012-2013 (C) M2TECH S.r.l and Amarula Solutions B.V.
+ *
+ * Authors:  Michael Trimarchi <michael@amarulasolutions.com>
+ *           Antonio Ospite <ao2@amarulasolutions.com>
+ *
+ * The driver is based on the work done in TerraTec DMX 6Fire USB
+ *
+ * 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.
+ */
+
+#include <linux/slab.h>
+#include <sound/pcm.h>
+
+#include "pcm.h"
+#include "chip.h"
+
+#define OUT_EP          0x2
+#define PCM_N_URBS      8
+#define PCM_PACKET_SIZE 4096
+#define PCM_BUFFER_SIZE (2 * PCM_N_URBS * PCM_PACKET_SIZE)
+
+struct pcm_urb {
+	struct hiface_chip *chip;
+
+	struct urb instance;
+	struct usb_anchor submitted;
+	u8 *buffer;
+};
+
+struct pcm_substream {
+	spinlock_t lock;
+	struct snd_pcm_substream *instance;
+
+	bool active;
+	snd_pcm_uframes_t dma_off;    /* current position in alsa dma_area */
+	snd_pcm_uframes_t period_off; /* current position in current period */
+};
+
+enum { /* pcm streaming states */
+	STREAM_DISABLED, /* no pcm streaming */
+	STREAM_STARTING, /* pcm streaming requested, waiting to become ready */
+	STREAM_RUNNING,  /* pcm streaming running */
+	STREAM_STOPPING
+};
+
+struct pcm_runtime {
+	struct hiface_chip *chip;
+	struct snd_pcm *instance;
+
+	struct pcm_substream playback;
+	bool panic; /* if set driver won't do anymore pcm on device */
+
+	struct pcm_urb out_urbs[PCM_N_URBS];
+
+	struct mutex stream_mutex;
+	u8 stream_state; /* one of STREAM_XXX */
+	u8 extra_freq;
+	wait_queue_head_t stream_wait_queue;
+	bool stream_wait_cond;
+};
+
+static const unsigned int rates[] = { 44100, 48000, 88200, 96000, 176400, 192000,
+				      352800, 384000 };
+static const struct snd_pcm_hw_constraint_list constraints_extra_rates = {
+	.count = ARRAY_SIZE(rates),
+	.list = rates,
+	.mask = 0,
+};
+
+static const struct snd_pcm_hardware pcm_hw = {
+	.info = SNDRV_PCM_INFO_MMAP |
+		SNDRV_PCM_INFO_INTERLEAVED |
+		SNDRV_PCM_INFO_BLOCK_TRANSFER |
+		SNDRV_PCM_INFO_PAUSE |
+		SNDRV_PCM_INFO_MMAP_VALID |
+		SNDRV_PCM_INFO_BATCH,
+
+	.formats = SNDRV_PCM_FMTBIT_S32_LE,
+
+	.rates = SNDRV_PCM_RATE_44100 |
+		SNDRV_PCM_RATE_48000 |
+		SNDRV_PCM_RATE_88200 |
+		SNDRV_PCM_RATE_96000 |
+		SNDRV_PCM_RATE_176400 |
+		SNDRV_PCM_RATE_192000,
+
+	.rate_min = 44100,
+	.rate_max = 192000, /* changes in hiface_pcm_open to support extra rates */
+	.channels_min = 2,
+	.channels_max = 2,
+	.buffer_bytes_max = PCM_BUFFER_SIZE,
+	.period_bytes_min = PCM_PACKET_SIZE,
+	.period_bytes_max = PCM_BUFFER_SIZE,
+	.periods_min = 2,
+	.periods_max = 1024
+};
+
+/* message values used to change the sample rate */
+#define HIFACE_SET_RATE_REQUEST 0xb0
+
+#define HIFACE_RATE_44100  0x43
+#define HIFACE_RATE_48000  0x4b
+#define HIFACE_RATE_88200  0x42
+#define HIFACE_RATE_96000  0x4a
+#define HIFACE_RATE_176400 0x40
+#define HIFACE_RATE_192000 0x48
+#define HIFACE_RATE_352800 0x58
+#define HIFACE_RATE_384000 0x68
+
+static int hiface_pcm_set_rate(struct pcm_runtime *rt, unsigned int rate)
+{
+	struct usb_device *device = rt->chip->dev;
+	u16 rate_value;
+	int ret;
+
+	/* We are already sure that the rate is supported here thanks to
+	 * ALSA constraints
+	 */
+	switch (rate) {
+	case 44100:
+		rate_value = HIFACE_RATE_44100;
+		break;
+	case 48000:
+		rate_value = HIFACE_RATE_48000;
+		break;
+	case 88200:
+		rate_value = HIFACE_RATE_88200;
+		break;
+	case 96000:
+		rate_value = HIFACE_RATE_96000;
+		break;
+	case 176400:
+		rate_value = HIFACE_RATE_176400;
+		break;
+	case 192000:
+		rate_value = HIFACE_RATE_192000;
+		break;
+	case 352800:
+		rate_value = HIFACE_RATE_352800;
+		break;
+	case 384000:
+		rate_value = HIFACE_RATE_384000;
+		break;
+	default:
+		dev_err(&device->dev, "Unsupported rate %d\n", rate);
+		return -EINVAL;
+	}
+
+	/*
+	 * USBIO: Vendor 0xb0(wValue=0x0043, wIndex=0x0000)
+	 * 43 b0 43 00 00 00 00 00
+	 * USBIO: Vendor 0xb0(wValue=0x004b, wIndex=0x0000)
+	 * 43 b0 4b 00 00 00 00 00
+	 * This control message doesn't have any ack from the
+	 * other side
+	 */
+	ret = usb_control_msg(device, usb_sndctrlpipe(device, 0),
+			      HIFACE_SET_RATE_REQUEST,
+			      USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_OTHER,
+			      rate_value, 0, NULL, 0, 100);
+	if (ret < 0) {
+		dev_err(&device->dev, "Error setting samplerate %d.\n", rate);
+		return ret;
+	}
+
+	return 0;
+}
+
+static struct pcm_substream *hiface_pcm_get_substream(struct snd_pcm_substream
+						      *alsa_sub)
+{
+	struct pcm_runtime *rt = snd_pcm_substream_chip(alsa_sub);
+	struct device *device = &rt->chip->dev->dev;
+
+	if (alsa_sub->stream == SNDRV_PCM_STREAM_PLAYBACK)
+		return &rt->playback;
+
+	dev_err(device, "Error getting pcm substream slot.\n");
+	return NULL;
+}
+
+/* call with stream_mutex locked */
+static void hiface_pcm_stream_stop(struct pcm_runtime *rt)
+{
+	int i, time;
+
+	if (rt->stream_state != STREAM_DISABLED) {
+		rt->stream_state = STREAM_STOPPING;
+
+		for (i = 0; i < PCM_N_URBS; i++) {
+			time = usb_wait_anchor_empty_timeout(
+					&rt->out_urbs[i].submitted, 100);
+			if (!time)
+				usb_kill_anchored_urbs(
+					&rt->out_urbs[i].submitted);
+			usb_kill_urb(&rt->out_urbs[i].instance);
+		}
+
+		rt->stream_state = STREAM_DISABLED;
+	}
+}
+
+/* call with stream_mutex locked */
+static int hiface_pcm_stream_start(struct pcm_runtime *rt)
+{
+	int ret = 0;
+	int i;
+
+	if (rt->stream_state == STREAM_DISABLED) {
+
+		/* reset panic state when starting a new stream */
+		rt->panic = false;
+
+		/* submit our out urbs zero init */
+		rt->stream_state = STREAM_STARTING;
+		for (i = 0; i < PCM_N_URBS; i++) {
+			memset(rt->out_urbs[i].buffer, 0, PCM_PACKET_SIZE);
+			usb_anchor_urb(&rt->out_urbs[i].instance,
+				       &rt->out_urbs[i].submitted);
+			ret = usb_submit_urb(&rt->out_urbs[i].instance,
+					     GFP_ATOMIC);
+			if (ret) {
+				hiface_pcm_stream_stop(rt);
+				return ret;
+			}
+		}
+
+		/* wait for first out urb to return (sent in in urb handler) */
+		wait_event_timeout(rt->stream_wait_queue, rt->stream_wait_cond,
+				   HZ);
+		if (rt->stream_wait_cond) {
+			struct device *device = &rt->chip->dev->dev;
+			dev_dbg(device, "%s: Stream is running wakeup event\n",
+				 __func__);
+			rt->stream_state = STREAM_RUNNING;
+		} else {
+			hiface_pcm_stream_stop(rt);
+			return -EIO;
+		}
+	}
+	return ret;
+}
+
+/* The hardware wants word-swapped 32-bit values */
+static void memcpy_swahw32(u8 *dest, u8 *src, unsigned int n)
+{
+	unsigned int i;
+
+	for (i = 0; i < n / 4; i++)
+		((u32 *)dest)[i] = swahw32(((u32 *)src)[i]);
+}
+
+/* call with substream locked */
+/* returns true if a period elapsed */
+static bool hiface_pcm_playback(struct pcm_substream *sub, struct pcm_urb *urb)
+{
+	struct snd_pcm_runtime *alsa_rt = sub->instance->runtime;
+	struct device *device = &urb->chip->dev->dev;
+	u8 *source;
+	unsigned int pcm_buffer_size;
+
+	WARN_ON(alsa_rt->format != SNDRV_PCM_FORMAT_S32_LE);
+
+	pcm_buffer_size = snd_pcm_lib_buffer_bytes(sub->instance);
+
+	if (sub->dma_off + PCM_PACKET_SIZE <= pcm_buffer_size) {
+		dev_dbg(device, "%s: (1) buffer_size %#x dma_offset %#x\n", __func__,
+			 (unsigned int) pcm_buffer_size,
+			 (unsigned int) sub->dma_off);
+
+		source = alsa_rt->dma_area + sub->dma_off;
+		memcpy_swahw32(urb->buffer, source, PCM_PACKET_SIZE);
+	} else {
+		/* wrap around at end of ring buffer */
+		unsigned int len;
+
+		dev_dbg(device, "%s: (2) buffer_size %#x dma_offset %#x\n", __func__,
+			 (unsigned int) pcm_buffer_size,
+			 (unsigned int) sub->dma_off);
+
+		len = pcm_buffer_size - sub->dma_off;
+
+		source = alsa_rt->dma_area + sub->dma_off;
+		memcpy_swahw32(urb->buffer, source, len);
+
+		source = alsa_rt->dma_area;
+		memcpy_swahw32(urb->buffer + len, source,
+			       PCM_PACKET_SIZE - len);
+	}
+	sub->dma_off += PCM_PACKET_SIZE;
+	if (sub->dma_off >= pcm_buffer_size)
+		sub->dma_off -= pcm_buffer_size;
+
+	sub->period_off += PCM_PACKET_SIZE;
+	if (sub->period_off >= alsa_rt->period_size) {
+		sub->period_off %= alsa_rt->period_size;
+		return true;
+	}
+	return false;
+}
+
+static void hiface_pcm_out_urb_handler(struct urb *usb_urb)
+{
+	struct pcm_urb *out_urb = usb_urb->context;
+	struct pcm_runtime *rt = out_urb->chip->pcm;
+	struct pcm_substream *sub;
+	bool do_period_elapsed = false;
+	unsigned long flags;
+	int ret;
+
+	if (rt->panic || rt->stream_state == STREAM_STOPPING)
+		return;
+
+	if (unlikely(usb_urb->status == -ENOENT ||	/* unlinked */
+		     usb_urb->status == -ENODEV ||	/* device removed */
+		     usb_urb->status == -ECONNRESET ||	/* unlinked */
+		     usb_urb->status == -ESHUTDOWN)) {	/* device disabled */
+		goto out_fail;
+	}
+
+	if (rt->stream_state == STREAM_STARTING) {
+		rt->stream_wait_cond = true;
+		wake_up(&rt->stream_wait_queue);
+	}
+
+	/* now send our playback data (if a free out urb was found) */
+	sub = &rt->playback;
+	spin_lock_irqsave(&sub->lock, flags);
+	if (sub->active)
+		do_period_elapsed = hiface_pcm_playback(sub, out_urb);
+	else
+		memset(out_urb->buffer, 0, PCM_PACKET_SIZE);
+
+	spin_unlock_irqrestore(&sub->lock, flags);
+
+	if (do_period_elapsed)
+		snd_pcm_period_elapsed(sub->instance);
+
+	ret = usb_submit_urb(&out_urb->instance, GFP_ATOMIC);
+	if (ret < 0)
+		goto out_fail;
+
+	return;
+
+out_fail:
+	rt->panic = true;
+}
+
+static int hiface_pcm_open(struct snd_pcm_substream *alsa_sub)
+{
+	struct pcm_runtime *rt = snd_pcm_substream_chip(alsa_sub);
+	struct pcm_substream *sub = NULL;
+	struct snd_pcm_runtime *alsa_rt = alsa_sub->runtime;
+	int ret;
+
+	if (rt->panic)
+		return -EPIPE;
+
+	mutex_lock(&rt->stream_mutex);
+	alsa_rt->hw = pcm_hw;
+
+	if (alsa_sub->stream == SNDRV_PCM_STREAM_PLAYBACK)
+		sub = &rt->playback;
+
+	if (!sub) {
+		struct device *device = &rt->chip->dev->dev;
+		mutex_unlock(&rt->stream_mutex);
+		dev_err(device, "Invalid stream type\n");
+		return -EINVAL;
+	}
+
+	if (rt->extra_freq) {
+		alsa_rt->hw.rates |= SNDRV_PCM_RATE_KNOT;
+		alsa_rt->hw.rate_max = 384000;
+
+		/* explicit constraints needed as we added SNDRV_PCM_RATE_KNOT */
+		ret = snd_pcm_hw_constraint_list(alsa_sub->runtime, 0,
+						 SNDRV_PCM_HW_PARAM_RATE,
+						 &constraints_extra_rates);
+		if (ret < 0) {
+			mutex_unlock(&rt->stream_mutex);
+			return ret;
+		}
+	}
+
+	sub->instance = alsa_sub;
+	sub->active = false;
+	mutex_unlock(&rt->stream_mutex);
+	return 0;
+}
+
+static int hiface_pcm_close(struct snd_pcm_substream *alsa_sub)
+{
+	struct pcm_runtime *rt = snd_pcm_substream_chip(alsa_sub);
+	struct pcm_substream *sub = hiface_pcm_get_substream(alsa_sub);
+	unsigned long flags;
+
+	if (rt->panic)
+		return 0;
+
+	mutex_lock(&rt->stream_mutex);
+	if (sub) {
+		hiface_pcm_stream_stop(rt);
+
+		/* deactivate substream */
+		spin_lock_irqsave(&sub->lock, flags);
+		sub->instance = NULL;
+		sub->active = false;
+		spin_unlock_irqrestore(&sub->lock, flags);
+
+	}
+	mutex_unlock(&rt->stream_mutex);
+	return 0;
+}
+
+static int hiface_pcm_hw_params(struct snd_pcm_substream *alsa_sub,
+				struct snd_pcm_hw_params *hw_params)
+{
+	return snd_pcm_lib_alloc_vmalloc_buffer(alsa_sub,
+						params_buffer_bytes(hw_params));
+}
+
+static int hiface_pcm_hw_free(struct snd_pcm_substream *alsa_sub)
+{
+	return snd_pcm_lib_free_vmalloc_buffer(alsa_sub);
+}
+
+static int hiface_pcm_prepare(struct snd_pcm_substream *alsa_sub)
+{
+	struct pcm_runtime *rt = snd_pcm_substream_chip(alsa_sub);
+	struct pcm_substream *sub = hiface_pcm_get_substream(alsa_sub);
+	struct snd_pcm_runtime *alsa_rt = alsa_sub->runtime;
+	int ret;
+
+	if (rt->panic)
+		return -EPIPE;
+	if (!sub)
+		return -ENODEV;
+
+	mutex_lock(&rt->stream_mutex);
+
+	hiface_pcm_stream_stop(rt);
+
+	sub->dma_off = 0;
+	sub->period_off = 0;
+
+	if (rt->stream_state == STREAM_DISABLED) {
+
+		ret = hiface_pcm_set_rate(rt, alsa_rt->rate);
+		if (ret) {
+			mutex_unlock(&rt->stream_mutex);
+			return ret;
+		}
+		ret = hiface_pcm_stream_start(rt);
+		if (ret) {
+			mutex_unlock(&rt->stream_mutex);
+			return ret;
+		}
+	}
+	mutex_unlock(&rt->stream_mutex);
+	return 0;
+}
+
+static int hiface_pcm_trigger(struct snd_pcm_substream *alsa_sub, int cmd)
+{
+	struct pcm_substream *sub = hiface_pcm_get_substream(alsa_sub);
+	struct pcm_runtime *rt = snd_pcm_substream_chip(alsa_sub);
+
+	if (rt->panic)
+		return -EPIPE;
+	if (!sub)
+		return -ENODEV;
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+		spin_lock_irq(&sub->lock);
+		sub->active = true;
+		spin_unlock_irq(&sub->lock);
+		return 0;
+
+	case SNDRV_PCM_TRIGGER_STOP:
+	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+		spin_lock_irq(&sub->lock);
+		sub->active = false;
+		spin_unlock_irq(&sub->lock);
+		return 0;
+
+	default:
+		return -EINVAL;
+	}
+}
+
+static snd_pcm_uframes_t hiface_pcm_pointer(struct snd_pcm_substream *alsa_sub)
+{
+	struct pcm_substream *sub = hiface_pcm_get_substream(alsa_sub);
+	struct pcm_runtime *rt = snd_pcm_substream_chip(alsa_sub);
+	unsigned long flags;
+	snd_pcm_uframes_t dma_offset;
+
+	if (rt->panic || !sub)
+		return SNDRV_PCM_POS_XRUN;
+
+	spin_lock_irqsave(&sub->lock, flags);
+	dma_offset = sub->dma_off;
+	spin_unlock_irqrestore(&sub->lock, flags);
+	return bytes_to_frames(alsa_sub->runtime, dma_offset);
+}
+
+static const struct snd_pcm_ops pcm_ops = {
+	.open = hiface_pcm_open,
+	.close = hiface_pcm_close,
+	.ioctl = snd_pcm_lib_ioctl,
+	.hw_params = hiface_pcm_hw_params,
+	.hw_free = hiface_pcm_hw_free,
+	.prepare = hiface_pcm_prepare,
+	.trigger = hiface_pcm_trigger,
+	.pointer = hiface_pcm_pointer,
+	.page = snd_pcm_lib_get_vmalloc_page,
+};
+
+static int hiface_pcm_init_urb(struct pcm_urb *urb,
+			       struct hiface_chip *chip,
+			       unsigned int ep,
+			       void (*handler)(struct urb *))
+{
+	urb->chip = chip;
+	usb_init_urb(&urb->instance);
+
+	urb->buffer = kzalloc(PCM_PACKET_SIZE, GFP_KERNEL);
+	if (!urb->buffer)
+		return -ENOMEM;
+
+	usb_fill_bulk_urb(&urb->instance, chip->dev,
+			  usb_sndbulkpipe(chip->dev, ep), (void *)urb->buffer,
+			  PCM_PACKET_SIZE, handler, urb);
+	if (usb_urb_ep_type_check(&urb->instance))
+		return -EINVAL;
+	init_usb_anchor(&urb->submitted);
+
+	return 0;
+}
+
+void hiface_pcm_abort(struct hiface_chip *chip)
+{
+	struct pcm_runtime *rt = chip->pcm;
+
+	if (rt) {
+		rt->panic = true;
+
+		mutex_lock(&rt->stream_mutex);
+		hiface_pcm_stream_stop(rt);
+		mutex_unlock(&rt->stream_mutex);
+	}
+}
+
+static void hiface_pcm_destroy(struct hiface_chip *chip)
+{
+	struct pcm_runtime *rt = chip->pcm;
+	int i;
+
+	for (i = 0; i < PCM_N_URBS; i++)
+		kfree(rt->out_urbs[i].buffer);
+
+	kfree(chip->pcm);
+	chip->pcm = NULL;
+}
+
+static void hiface_pcm_free(struct snd_pcm *pcm)
+{
+	struct pcm_runtime *rt = pcm->private_data;
+
+	if (rt)
+		hiface_pcm_destroy(rt->chip);
+}
+
+int hiface_pcm_init(struct hiface_chip *chip, u8 extra_freq)
+{
+	int i;
+	int ret;
+	struct snd_pcm *pcm;
+	struct pcm_runtime *rt;
+
+	rt = kzalloc(sizeof(*rt), GFP_KERNEL);
+	if (!rt)
+		return -ENOMEM;
+
+	rt->chip = chip;
+	rt->stream_state = STREAM_DISABLED;
+	if (extra_freq)
+		rt->extra_freq = 1;
+
+	init_waitqueue_head(&rt->stream_wait_queue);
+	mutex_init(&rt->stream_mutex);
+	spin_lock_init(&rt->playback.lock);
+
+	for (i = 0; i < PCM_N_URBS; i++) {
+		ret = hiface_pcm_init_urb(&rt->out_urbs[i], chip, OUT_EP,
+				    hiface_pcm_out_urb_handler);
+		if (ret < 0)
+			return ret;
+	}
+
+	ret = snd_pcm_new(chip->card, "USB-SPDIF Audio", 0, 1, 0, &pcm);
+	if (ret < 0) {
+		kfree(rt);
+		dev_err(&chip->dev->dev, "Cannot create pcm instance\n");
+		return ret;
+	}
+
+	pcm->private_data = rt;
+	pcm->private_free = hiface_pcm_free;
+
+	strlcpy(pcm->name, "USB-SPDIF Audio", sizeof(pcm->name));
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &pcm_ops);
+
+	rt->instance = pcm;
+
+	chip->pcm = rt;
+	return 0;
+}
diff --git a/sound/usb/hiface/pcm.h b/sound/usb/hiface/pcm.h
new file mode 100644
index 0000000..77edd7c
--- /dev/null
+++ b/sound/usb/hiface/pcm.h
@@ -0,0 +1,24 @@
+/*
+ * Linux driver for M2Tech hiFace compatible devices
+ *
+ * Copyright 2012-2013 (C) M2TECH S.r.l and Amarula Solutions B.V.
+ *
+ * Authors:  Michael Trimarchi <michael@amarulasolutions.com>
+ *           Antonio Ospite <ao2@amarulasolutions.com>
+ *
+ * The driver is based on the work done in TerraTec DMX 6Fire USB
+ *
+ * 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.
+ */
+
+#ifndef HIFACE_PCM_H
+#define HIFACE_PCM_H
+
+struct hiface_chip;
+
+int hiface_pcm_init(struct hiface_chip *chip, u8 extra_freq);
+void hiface_pcm_abort(struct hiface_chip *chip);
+#endif /* HIFACE_PCM_H */
diff --git a/sound/usb/line6/Kconfig b/sound/usb/line6/Kconfig
new file mode 100644
index 0000000..39b4003
--- /dev/null
+++ b/sound/usb/line6/Kconfig
@@ -0,0 +1,43 @@
+config SND_USB_LINE6
+	tristate
+	select SND_RAWMIDI
+	select SND_PCM
+	select SND_HWDEP
+
+config SND_USB_POD
+	tristate "Line 6 POD USB support"
+	select SND_USB_LINE6
+	help
+	  This is a driver for PODxt and other similar devices,
+	  supporting the following features:
+	    * Reading/writing individual parameters
+	    * Reading/writing complete channel, effects setup, and amp
+	      setup data
+	    * Channel switching
+	    * Virtual MIDI interface
+	    * Tuner access
+	    * Playback/capture/mixer device for any ALSA-compatible PCM
+	      audio application
+	    * Signal routing (record clean/processed guitar signal,
+	      re-amping)
+
+config SND_USB_PODHD
+	tristate "Line 6 POD X3/HD300/400/500 USB support"
+	select SND_USB_LINE6
+	help
+	  This is a driver for POD X3, HD300, 400 and 500 devices.
+
+config SND_USB_TONEPORT
+	tristate "TonePort GX, UX1 and UX2 USB support"
+	select SND_USB_LINE6
+	select NEW_LEDS
+	select LEDS_CLASS
+	help
+	  This is a driver for TonePort GX, UX1 and UX2 devices.
+
+config SND_USB_VARIAX
+	tristate "Variax Workbench USB support"
+	select SND_USB_LINE6
+	help
+	  This is a driver for Variax Workbench device.
+
diff --git a/sound/usb/line6/Makefile b/sound/usb/line6/Makefile
new file mode 100644
index 0000000..4ba98eb
--- /dev/null
+++ b/sound/usb/line6/Makefile
@@ -0,0 +1,19 @@
+# SPDX-License-Identifier: GPL-2.0
+snd-usb-line6-y := 		\
+		capture.o	\
+		driver.o	\
+		midi.o		\
+		midibuf.o	\
+		pcm.o		\
+		playback.o
+
+snd-usb-pod-y := pod.o
+snd-usb-podhd-y := podhd.o
+snd-usb-toneport-y := toneport.o
+snd-usb-variax-y := variax.o
+
+obj-$(CONFIG_SND_USB_LINE6)	+= snd-usb-line6.o
+obj-$(CONFIG_SND_USB_POD)	+= snd-usb-pod.o
+obj-$(CONFIG_SND_USB_PODHD)	+= snd-usb-podhd.o
+obj-$(CONFIG_SND_USB_TONEPORT)	+= snd-usb-toneport.o
+obj-$(CONFIG_SND_USB_VARIAX)	+= snd-usb-variax.o
diff --git a/sound/usb/line6/capture.c b/sound/usb/line6/capture.c
new file mode 100644
index 0000000..d8a14d7
--- /dev/null
+++ b/sound/usb/line6/capture.c
@@ -0,0 +1,297 @@
+/*
+ * Line 6 Linux USB driver
+ *
+ * Copyright (C) 2004-2010 Markus Grabner (grabner@icg.tugraz.at)
+ *
+ *	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, version 2.
+ *
+ */
+
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+
+#include "capture.h"
+#include "driver.h"
+#include "pcm.h"
+
+/*
+	Find a free URB and submit it.
+	must be called in line6pcm->in.lock context
+*/
+static int submit_audio_in_urb(struct snd_line6_pcm *line6pcm)
+{
+	int index;
+	int i, urb_size;
+	int ret;
+	struct urb *urb_in;
+
+	index = find_first_zero_bit(&line6pcm->in.active_urbs,
+				    line6pcm->line6->iso_buffers);
+
+	if (index < 0 || index >= line6pcm->line6->iso_buffers) {
+		dev_err(line6pcm->line6->ifcdev, "no free URB found\n");
+		return -EINVAL;
+	}
+
+	urb_in = line6pcm->in.urbs[index];
+	urb_size = 0;
+
+	for (i = 0; i < LINE6_ISO_PACKETS; ++i) {
+		struct usb_iso_packet_descriptor *fin =
+		    &urb_in->iso_frame_desc[i];
+		fin->offset = urb_size;
+		fin->length = line6pcm->max_packet_size_in;
+		urb_size += line6pcm->max_packet_size_in;
+	}
+
+	urb_in->transfer_buffer =
+	    line6pcm->in.buffer +
+	    index * LINE6_ISO_PACKETS * line6pcm->max_packet_size_in;
+	urb_in->transfer_buffer_length = urb_size;
+	urb_in->context = line6pcm;
+
+	ret = usb_submit_urb(urb_in, GFP_ATOMIC);
+
+	if (ret == 0)
+		set_bit(index, &line6pcm->in.active_urbs);
+	else
+		dev_err(line6pcm->line6->ifcdev,
+			"URB in #%d submission failed (%d)\n", index, ret);
+
+	return 0;
+}
+
+/*
+	Submit all currently available capture URBs.
+	must be called in line6pcm->in.lock context
+*/
+int line6_submit_audio_in_all_urbs(struct snd_line6_pcm *line6pcm)
+{
+	int ret = 0, i;
+
+	for (i = 0; i < line6pcm->line6->iso_buffers; ++i) {
+		ret = submit_audio_in_urb(line6pcm);
+		if (ret < 0)
+			break;
+	}
+
+	return ret;
+}
+
+/*
+	Copy data into ALSA capture buffer.
+*/
+void line6_capture_copy(struct snd_line6_pcm *line6pcm, char *fbuf, int fsize)
+{
+	struct snd_pcm_substream *substream =
+	    get_substream(line6pcm, SNDRV_PCM_STREAM_CAPTURE);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	const int bytes_per_frame =
+		line6pcm->properties->bytes_per_channel *
+		line6pcm->properties->capture_hw.channels_max;
+	int frames = fsize / bytes_per_frame;
+
+	if (runtime == NULL)
+		return;
+
+	if (line6pcm->in.pos_done + frames > runtime->buffer_size) {
+		/*
+		   The transferred area goes over buffer boundary,
+		   copy two separate chunks.
+		 */
+		int len;
+
+		len = runtime->buffer_size - line6pcm->in.pos_done;
+
+		if (len > 0) {
+			memcpy(runtime->dma_area +
+			       line6pcm->in.pos_done * bytes_per_frame, fbuf,
+			       len * bytes_per_frame);
+			memcpy(runtime->dma_area, fbuf + len * bytes_per_frame,
+			       (frames - len) * bytes_per_frame);
+		} else {
+			/* this is somewhat paranoid */
+			dev_err(line6pcm->line6->ifcdev,
+				"driver bug: len = %d\n", len);
+		}
+	} else {
+		/* copy single chunk */
+		memcpy(runtime->dma_area +
+		       line6pcm->in.pos_done * bytes_per_frame, fbuf, fsize);
+	}
+
+	line6pcm->in.pos_done += frames;
+	if (line6pcm->in.pos_done >= runtime->buffer_size)
+		line6pcm->in.pos_done -= runtime->buffer_size;
+}
+
+void line6_capture_check_period(struct snd_line6_pcm *line6pcm, int length)
+{
+	struct snd_pcm_substream *substream =
+	    get_substream(line6pcm, SNDRV_PCM_STREAM_CAPTURE);
+
+	line6pcm->in.bytes += length;
+	if (line6pcm->in.bytes >= line6pcm->in.period) {
+		line6pcm->in.bytes %= line6pcm->in.period;
+		spin_unlock(&line6pcm->in.lock);
+		snd_pcm_period_elapsed(substream);
+		spin_lock(&line6pcm->in.lock);
+	}
+}
+
+/*
+ * Callback for completed capture URB.
+ */
+static void audio_in_callback(struct urb *urb)
+{
+	int i, index, length = 0, shutdown = 0;
+	unsigned long flags;
+
+	struct snd_line6_pcm *line6pcm = (struct snd_line6_pcm *)urb->context;
+
+	line6pcm->in.last_frame = urb->start_frame;
+
+	/* find index of URB */
+	for (index = 0; index < line6pcm->line6->iso_buffers; ++index)
+		if (urb == line6pcm->in.urbs[index])
+			break;
+
+	spin_lock_irqsave(&line6pcm->in.lock, flags);
+
+	for (i = 0; i < LINE6_ISO_PACKETS; ++i) {
+		char *fbuf;
+		int fsize;
+		struct usb_iso_packet_descriptor *fin = &urb->iso_frame_desc[i];
+
+		if (fin->status == -EXDEV) {
+			shutdown = 1;
+			break;
+		}
+
+		fbuf = urb->transfer_buffer + fin->offset;
+		fsize = fin->actual_length;
+
+		if (fsize > line6pcm->max_packet_size_in) {
+			dev_err(line6pcm->line6->ifcdev,
+				"driver and/or device bug: packet too large (%d > %d)\n",
+				fsize, line6pcm->max_packet_size_in);
+		}
+
+		length += fsize;
+
+		BUILD_BUG_ON_MSG(LINE6_ISO_PACKETS != 1,
+			"The following code assumes LINE6_ISO_PACKETS == 1");
+		/* TODO:
+		 * Also, if iso_buffers != 2, the prev frame is almost random at
+		 * playback side.
+		 * This needs to be redesigned. It should be "stable", but we may
+		 * experience sync problems on such high-speed configs.
+		 */
+
+		line6pcm->prev_fbuf = fbuf;
+		line6pcm->prev_fsize = fsize /
+			(line6pcm->properties->bytes_per_channel *
+			line6pcm->properties->capture_hw.channels_max);
+
+		if (!test_bit(LINE6_STREAM_IMPULSE, &line6pcm->in.running) &&
+		    test_bit(LINE6_STREAM_PCM, &line6pcm->in.running) &&
+		    fsize > 0)
+			line6_capture_copy(line6pcm, fbuf, fsize);
+	}
+
+	clear_bit(index, &line6pcm->in.active_urbs);
+
+	if (test_and_clear_bit(index, &line6pcm->in.unlink_urbs))
+		shutdown = 1;
+
+	if (!shutdown) {
+		submit_audio_in_urb(line6pcm);
+
+		if (!test_bit(LINE6_STREAM_IMPULSE, &line6pcm->in.running) &&
+		    test_bit(LINE6_STREAM_PCM, &line6pcm->in.running))
+			line6_capture_check_period(line6pcm, length);
+	}
+
+	spin_unlock_irqrestore(&line6pcm->in.lock, flags);
+}
+
+/* open capture callback */
+static int snd_line6_capture_open(struct snd_pcm_substream *substream)
+{
+	int err;
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct snd_line6_pcm *line6pcm = snd_pcm_substream_chip(substream);
+
+	err = snd_pcm_hw_constraint_ratdens(runtime, 0,
+					    SNDRV_PCM_HW_PARAM_RATE,
+					    &line6pcm->properties->rates);
+	if (err < 0)
+		return err;
+
+	line6_pcm_acquire(line6pcm, LINE6_STREAM_CAPTURE_HELPER, false);
+
+	runtime->hw = line6pcm->properties->capture_hw;
+	return 0;
+}
+
+/* close capture callback */
+static int snd_line6_capture_close(struct snd_pcm_substream *substream)
+{
+	struct snd_line6_pcm *line6pcm = snd_pcm_substream_chip(substream);
+
+	line6_pcm_release(line6pcm, LINE6_STREAM_CAPTURE_HELPER);
+	return 0;
+}
+
+/* capture operators */
+const struct snd_pcm_ops snd_line6_capture_ops = {
+	.open = snd_line6_capture_open,
+	.close = snd_line6_capture_close,
+	.ioctl = snd_pcm_lib_ioctl,
+	.hw_params = snd_line6_hw_params,
+	.hw_free = snd_line6_hw_free,
+	.prepare = snd_line6_prepare,
+	.trigger = snd_line6_trigger,
+	.pointer = snd_line6_pointer,
+};
+
+int line6_create_audio_in_urbs(struct snd_line6_pcm *line6pcm)
+{
+	struct usb_line6 *line6 = line6pcm->line6;
+	int i;
+
+	line6pcm->in.urbs = kcalloc(line6->iso_buffers, sizeof(struct urb *),
+				    GFP_KERNEL);
+	if (line6pcm->in.urbs == NULL)
+		return -ENOMEM;
+
+	/* create audio URBs and fill in constant values: */
+	for (i = 0; i < line6->iso_buffers; ++i) {
+		struct urb *urb;
+
+		/* URB for audio in: */
+		urb = line6pcm->in.urbs[i] =
+		    usb_alloc_urb(LINE6_ISO_PACKETS, GFP_KERNEL);
+
+		if (urb == NULL)
+			return -ENOMEM;
+
+		urb->dev = line6->usbdev;
+		urb->pipe =
+		    usb_rcvisocpipe(line6->usbdev,
+				    line6->properties->ep_audio_r &
+				    USB_ENDPOINT_NUMBER_MASK);
+		urb->transfer_flags = URB_ISO_ASAP;
+		urb->start_frame = -1;
+		urb->number_of_packets = LINE6_ISO_PACKETS;
+		urb->interval = LINE6_ISO_INTERVAL;
+		urb->error_count = 0;
+		urb->complete = audio_in_callback;
+	}
+
+	return 0;
+}
diff --git a/sound/usb/line6/capture.h b/sound/usb/line6/capture.h
new file mode 100644
index 0000000..b67ccc3
--- /dev/null
+++ b/sound/usb/line6/capture.h
@@ -0,0 +1,29 @@
+/*
+ * Line 6 Linux USB driver
+ *
+ * Copyright (C) 2004-2010 Markus Grabner (grabner@icg.tugraz.at)
+ *
+ *	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, version 2.
+ *
+ */
+
+#ifndef CAPTURE_H
+#define CAPTURE_H
+
+#include <sound/pcm.h>
+
+#include "driver.h"
+#include "pcm.h"
+
+extern const struct snd_pcm_ops snd_line6_capture_ops;
+
+extern void line6_capture_copy(struct snd_line6_pcm *line6pcm, char *fbuf,
+			       int fsize);
+extern void line6_capture_check_period(struct snd_line6_pcm *line6pcm,
+				       int length);
+extern int line6_create_audio_in_urbs(struct snd_line6_pcm *line6pcm);
+extern int line6_submit_audio_in_all_urbs(struct snd_line6_pcm *line6pcm);
+
+#endif
diff --git a/sound/usb/line6/driver.c b/sound/usb/line6/driver.c
new file mode 100644
index 0000000..c1376bf
--- /dev/null
+++ b/sound/usb/line6/driver.c
@@ -0,0 +1,881 @@
+/*
+ * Line 6 Linux USB driver
+ *
+ * Copyright (C) 2004-2010 Markus Grabner (grabner@icg.tugraz.at)
+ *
+ *	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, version 2.
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/export.h>
+#include <linux/slab.h>
+#include <linux/usb.h>
+
+#include <sound/core.h>
+#include <sound/initval.h>
+#include <sound/hwdep.h>
+
+#include "capture.h"
+#include "driver.h"
+#include "midi.h"
+#include "playback.h"
+
+#define DRIVER_AUTHOR  "Markus Grabner <grabner@icg.tugraz.at>"
+#define DRIVER_DESC    "Line 6 USB Driver"
+
+/*
+	This is Line 6's MIDI manufacturer ID.
+*/
+const unsigned char line6_midi_id[3] = {
+	0x00, 0x01, 0x0c
+};
+EXPORT_SYMBOL_GPL(line6_midi_id);
+
+/*
+	Code to request version of POD, Variax interface
+	(and maybe other devices).
+*/
+static const char line6_request_version[] = {
+	0xf0, 0x7e, 0x7f, 0x06, 0x01, 0xf7
+};
+
+/*
+	 Class for asynchronous messages.
+*/
+struct message {
+	struct usb_line6 *line6;
+	const char *buffer;
+	int size;
+	int done;
+};
+
+/*
+	Forward declarations.
+*/
+static void line6_data_received(struct urb *urb);
+static int line6_send_raw_message_async_part(struct message *msg,
+					     struct urb *urb);
+
+/*
+	Start to listen on endpoint.
+*/
+static int line6_start_listen(struct usb_line6 *line6)
+{
+	int err;
+
+	if (line6->properties->capabilities & LINE6_CAP_CONTROL_MIDI) {
+		usb_fill_int_urb(line6->urb_listen, line6->usbdev,
+			usb_rcvintpipe(line6->usbdev, line6->properties->ep_ctrl_r),
+			line6->buffer_listen, LINE6_BUFSIZE_LISTEN,
+			line6_data_received, line6, line6->interval);
+	} else {
+		usb_fill_bulk_urb(line6->urb_listen, line6->usbdev,
+			usb_rcvbulkpipe(line6->usbdev, line6->properties->ep_ctrl_r),
+			line6->buffer_listen, LINE6_BUFSIZE_LISTEN,
+			line6_data_received, line6);
+	}
+
+	/* sanity checks of EP before actually submitting */
+	if (usb_urb_ep_type_check(line6->urb_listen)) {
+		dev_err(line6->ifcdev, "invalid control EP\n");
+		return -EINVAL;
+	}
+
+	line6->urb_listen->actual_length = 0;
+	err = usb_submit_urb(line6->urb_listen, GFP_ATOMIC);
+	return err;
+}
+
+/*
+	Stop listening on endpoint.
+*/
+static void line6_stop_listen(struct usb_line6 *line6)
+{
+	usb_kill_urb(line6->urb_listen);
+}
+
+/*
+	Send raw message in pieces of wMaxPacketSize bytes.
+*/
+static int line6_send_raw_message(struct usb_line6 *line6, const char *buffer,
+				  int size)
+{
+	int i, done = 0;
+	const struct line6_properties *properties = line6->properties;
+
+	for (i = 0; i < size; i += line6->max_packet_size) {
+		int partial;
+		const char *frag_buf = buffer + i;
+		int frag_size = min(line6->max_packet_size, size - i);
+		int retval;
+
+		if (properties->capabilities & LINE6_CAP_CONTROL_MIDI) {
+			retval = usb_interrupt_msg(line6->usbdev,
+						usb_sndintpipe(line6->usbdev, properties->ep_ctrl_w),
+						(char *)frag_buf, frag_size,
+						&partial, LINE6_TIMEOUT * HZ);
+		} else {
+			retval = usb_bulk_msg(line6->usbdev,
+						usb_sndbulkpipe(line6->usbdev, properties->ep_ctrl_w),
+						(char *)frag_buf, frag_size,
+						&partial, LINE6_TIMEOUT * HZ);
+		}
+
+		if (retval) {
+			dev_err(line6->ifcdev,
+				"usb_bulk_msg failed (%d)\n", retval);
+			break;
+		}
+
+		done += frag_size;
+	}
+
+	return done;
+}
+
+/*
+	Notification of completion of asynchronous request transmission.
+*/
+static void line6_async_request_sent(struct urb *urb)
+{
+	struct message *msg = (struct message *)urb->context;
+
+	if (msg->done >= msg->size) {
+		usb_free_urb(urb);
+		kfree(msg);
+	} else
+		line6_send_raw_message_async_part(msg, urb);
+}
+
+/*
+	Asynchronously send part of a raw message.
+*/
+static int line6_send_raw_message_async_part(struct message *msg,
+					     struct urb *urb)
+{
+	int retval;
+	struct usb_line6 *line6 = msg->line6;
+	int done = msg->done;
+	int bytes = min(msg->size - done, line6->max_packet_size);
+
+	if (line6->properties->capabilities & LINE6_CAP_CONTROL_MIDI) {
+		usb_fill_int_urb(urb, line6->usbdev,
+			usb_sndintpipe(line6->usbdev, line6->properties->ep_ctrl_w),
+			(char *)msg->buffer + done, bytes,
+			line6_async_request_sent, msg, line6->interval);
+	} else {
+		usb_fill_bulk_urb(urb, line6->usbdev,
+			usb_sndbulkpipe(line6->usbdev, line6->properties->ep_ctrl_w),
+			(char *)msg->buffer + done, bytes,
+			line6_async_request_sent, msg);
+	}
+
+	msg->done += bytes;
+
+	/* sanity checks of EP before actually submitting */
+	retval = usb_urb_ep_type_check(urb);
+	if (retval < 0)
+		goto error;
+
+	retval = usb_submit_urb(urb, GFP_ATOMIC);
+	if (retval < 0)
+		goto error;
+
+	return 0;
+
+ error:
+	dev_err(line6->ifcdev, "%s: usb_submit_urb failed (%d)\n",
+		__func__, retval);
+	usb_free_urb(urb);
+	kfree(msg);
+	return retval;
+}
+
+/*
+	Setup and start timer.
+*/
+void line6_start_timer(struct timer_list *timer, unsigned long msecs,
+		       void (*function)(struct timer_list *t))
+{
+	timer->function = function;
+	mod_timer(timer, jiffies + msecs_to_jiffies(msecs));
+}
+EXPORT_SYMBOL_GPL(line6_start_timer);
+
+/*
+	Asynchronously send raw message.
+*/
+int line6_send_raw_message_async(struct usb_line6 *line6, const char *buffer,
+				 int size)
+{
+	struct message *msg;
+	struct urb *urb;
+
+	/* create message: */
+	msg = kmalloc(sizeof(struct message), GFP_ATOMIC);
+	if (msg == NULL)
+		return -ENOMEM;
+
+	/* create URB: */
+	urb = usb_alloc_urb(0, GFP_ATOMIC);
+
+	if (urb == NULL) {
+		kfree(msg);
+		return -ENOMEM;
+	}
+
+	/* set message data: */
+	msg->line6 = line6;
+	msg->buffer = buffer;
+	msg->size = size;
+	msg->done = 0;
+
+	/* start sending: */
+	return line6_send_raw_message_async_part(msg, urb);
+}
+EXPORT_SYMBOL_GPL(line6_send_raw_message_async);
+
+/*
+	Send asynchronous device version request.
+*/
+int line6_version_request_async(struct usb_line6 *line6)
+{
+	char *buffer;
+	int retval;
+
+	buffer = kmemdup(line6_request_version,
+			sizeof(line6_request_version), GFP_ATOMIC);
+	if (buffer == NULL)
+		return -ENOMEM;
+
+	retval = line6_send_raw_message_async(line6, buffer,
+					      sizeof(line6_request_version));
+	kfree(buffer);
+	return retval;
+}
+EXPORT_SYMBOL_GPL(line6_version_request_async);
+
+/*
+	Send sysex message in pieces of wMaxPacketSize bytes.
+*/
+int line6_send_sysex_message(struct usb_line6 *line6, const char *buffer,
+			     int size)
+{
+	return line6_send_raw_message(line6, buffer,
+				      size + SYSEX_EXTRA_SIZE) -
+	    SYSEX_EXTRA_SIZE;
+}
+EXPORT_SYMBOL_GPL(line6_send_sysex_message);
+
+/*
+	Allocate buffer for sysex message and prepare header.
+	@param code sysex message code
+	@param size number of bytes between code and sysex end
+*/
+char *line6_alloc_sysex_buffer(struct usb_line6 *line6, int code1, int code2,
+			       int size)
+{
+	char *buffer = kmalloc(size + SYSEX_EXTRA_SIZE, GFP_ATOMIC);
+
+	if (!buffer)
+		return NULL;
+
+	buffer[0] = LINE6_SYSEX_BEGIN;
+	memcpy(buffer + 1, line6_midi_id, sizeof(line6_midi_id));
+	buffer[sizeof(line6_midi_id) + 1] = code1;
+	buffer[sizeof(line6_midi_id) + 2] = code2;
+	buffer[sizeof(line6_midi_id) + 3 + size] = LINE6_SYSEX_END;
+	return buffer;
+}
+EXPORT_SYMBOL_GPL(line6_alloc_sysex_buffer);
+
+/*
+	Notification of data received from the Line 6 device.
+*/
+static void line6_data_received(struct urb *urb)
+{
+	struct usb_line6 *line6 = (struct usb_line6 *)urb->context;
+	struct midi_buffer *mb = &line6->line6midi->midibuf_in;
+	int done;
+
+	if (urb->status == -ESHUTDOWN)
+		return;
+
+	if (line6->properties->capabilities & LINE6_CAP_CONTROL_MIDI) {
+		done =
+			line6_midibuf_write(mb, urb->transfer_buffer, urb->actual_length);
+
+		if (done < urb->actual_length) {
+			line6_midibuf_ignore(mb, done);
+			dev_dbg(line6->ifcdev, "%d %d buffer overflow - message skipped\n",
+				done, urb->actual_length);
+		}
+
+		for (;;) {
+			done =
+				line6_midibuf_read(mb, line6->buffer_message,
+						LINE6_MIDI_MESSAGE_MAXLEN);
+
+			if (done == 0)
+				break;
+
+			line6->message_length = done;
+			line6_midi_receive(line6, line6->buffer_message, done);
+
+			if (line6->process_message)
+				line6->process_message(line6);
+		}
+	} else {
+		line6->buffer_message = urb->transfer_buffer;
+		line6->message_length = urb->actual_length;
+		if (line6->process_message)
+			line6->process_message(line6);
+		line6->buffer_message = NULL;
+	}
+
+	line6_start_listen(line6);
+}
+
+#define LINE6_READ_WRITE_STATUS_DELAY 2  /* milliseconds */
+#define LINE6_READ_WRITE_MAX_RETRIES 50
+
+/*
+	Read data from device.
+*/
+int line6_read_data(struct usb_line6 *line6, unsigned address, void *data,
+		    unsigned datalen)
+{
+	struct usb_device *usbdev = line6->usbdev;
+	int ret;
+	unsigned char len;
+	unsigned count;
+
+	if (address > 0xffff || datalen > 0xff)
+		return -EINVAL;
+
+	/* query the serial number: */
+	ret = usb_control_msg(usbdev, usb_sndctrlpipe(usbdev, 0), 0x67,
+			      USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_OUT,
+			      (datalen << 8) | 0x21, address,
+			      NULL, 0, LINE6_TIMEOUT * HZ);
+
+	if (ret < 0) {
+		dev_err(line6->ifcdev, "read request failed (error %d)\n", ret);
+		return ret;
+	}
+
+	/* Wait for data length. We'll get 0xff until length arrives. */
+	for (count = 0; count < LINE6_READ_WRITE_MAX_RETRIES; count++) {
+		mdelay(LINE6_READ_WRITE_STATUS_DELAY);
+
+		ret = usb_control_msg(usbdev, usb_rcvctrlpipe(usbdev, 0), 0x67,
+				      USB_TYPE_VENDOR | USB_RECIP_DEVICE |
+				      USB_DIR_IN,
+				      0x0012, 0x0000, &len, 1,
+				      LINE6_TIMEOUT * HZ);
+		if (ret < 0) {
+			dev_err(line6->ifcdev,
+				"receive length failed (error %d)\n", ret);
+			return ret;
+		}
+
+		if (len != 0xff)
+			break;
+	}
+
+	if (len == 0xff) {
+		dev_err(line6->ifcdev, "read failed after %d retries\n",
+			count);
+		return -EIO;
+	} else if (len != datalen) {
+		/* should be equal or something went wrong */
+		dev_err(line6->ifcdev,
+			"length mismatch (expected %d, got %d)\n",
+			(int)datalen, (int)len);
+		return -EIO;
+	}
+
+	/* receive the result: */
+	ret = usb_control_msg(usbdev, usb_rcvctrlpipe(usbdev, 0), 0x67,
+			      USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_IN,
+			      0x0013, 0x0000, data, datalen,
+			      LINE6_TIMEOUT * HZ);
+
+	if (ret < 0) {
+		dev_err(line6->ifcdev, "read failed (error %d)\n", ret);
+		return ret;
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(line6_read_data);
+
+/*
+	Write data to device.
+*/
+int line6_write_data(struct usb_line6 *line6, unsigned address, void *data,
+		     unsigned datalen)
+{
+	struct usb_device *usbdev = line6->usbdev;
+	int ret;
+	unsigned char status;
+	int count;
+
+	if (address > 0xffff || datalen > 0xffff)
+		return -EINVAL;
+
+	ret = usb_control_msg(usbdev, usb_sndctrlpipe(usbdev, 0), 0x67,
+			      USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_OUT,
+			      0x0022, address, data, datalen,
+			      LINE6_TIMEOUT * HZ);
+
+	if (ret < 0) {
+		dev_err(line6->ifcdev,
+			"write request failed (error %d)\n", ret);
+		return ret;
+	}
+
+	for (count = 0; count < LINE6_READ_WRITE_MAX_RETRIES; count++) {
+		mdelay(LINE6_READ_WRITE_STATUS_DELAY);
+
+		ret = usb_control_msg(usbdev, usb_rcvctrlpipe(usbdev, 0),
+				      0x67,
+				      USB_TYPE_VENDOR | USB_RECIP_DEVICE |
+				      USB_DIR_IN,
+				      0x0012, 0x0000,
+				      &status, 1, LINE6_TIMEOUT * HZ);
+
+		if (ret < 0) {
+			dev_err(line6->ifcdev,
+				"receiving status failed (error %d)\n", ret);
+			return ret;
+		}
+
+		if (status != 0xff)
+			break;
+	}
+
+	if (status == 0xff) {
+		dev_err(line6->ifcdev, "write failed after %d retries\n",
+			count);
+		return -EIO;
+	} else if (status != 0) {
+		dev_err(line6->ifcdev, "write failed (error %d)\n", ret);
+		return -EIO;
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(line6_write_data);
+
+/*
+	Read Line 6 device serial number.
+	(POD, TonePort, GuitarPort)
+*/
+int line6_read_serial_number(struct usb_line6 *line6, u32 *serial_number)
+{
+	return line6_read_data(line6, 0x80d0, serial_number,
+			       sizeof(*serial_number));
+}
+EXPORT_SYMBOL_GPL(line6_read_serial_number);
+
+/*
+	Card destructor.
+*/
+static void line6_destruct(struct snd_card *card)
+{
+	struct usb_line6 *line6 = card->private_data;
+	struct usb_device *usbdev = line6->usbdev;
+
+	/* Free buffer memory first. We cannot depend on the existence of private
+	 * data from the (podhd) module, it may be gone already during this call
+	 */
+	kfree(line6->buffer_message);
+
+	kfree(line6->buffer_listen);
+
+	/* then free URBs: */
+	usb_free_urb(line6->urb_listen);
+	line6->urb_listen = NULL;
+
+	/* decrement reference counters: */
+	usb_put_dev(usbdev);
+}
+
+static void line6_get_usb_properties(struct usb_line6 *line6)
+{
+	struct usb_device *usbdev = line6->usbdev;
+	const struct line6_properties *properties = line6->properties;
+	int pipe;
+	struct usb_host_endpoint *ep = NULL;
+
+	if (properties->capabilities & LINE6_CAP_CONTROL) {
+		if (properties->capabilities & LINE6_CAP_CONTROL_MIDI) {
+			pipe = usb_rcvintpipe(line6->usbdev,
+				line6->properties->ep_ctrl_r);
+		} else {
+			pipe = usb_rcvbulkpipe(line6->usbdev,
+				line6->properties->ep_ctrl_r);
+		}
+		ep = usbdev->ep_in[usb_pipeendpoint(pipe)];
+	}
+
+	/* Control data transfer properties */
+	if (ep) {
+		line6->interval = ep->desc.bInterval;
+		line6->max_packet_size = le16_to_cpu(ep->desc.wMaxPacketSize);
+	} else {
+		if (properties->capabilities & LINE6_CAP_CONTROL) {
+			dev_err(line6->ifcdev,
+				"endpoint not available, using fallback values");
+		}
+		line6->interval = LINE6_FALLBACK_INTERVAL;
+		line6->max_packet_size = LINE6_FALLBACK_MAXPACKETSIZE;
+	}
+
+	/* Isochronous transfer properties */
+	if (usbdev->speed == USB_SPEED_LOW) {
+		line6->intervals_per_second = USB_LOW_INTERVALS_PER_SECOND;
+		line6->iso_buffers = USB_LOW_ISO_BUFFERS;
+	} else {
+		line6->intervals_per_second = USB_HIGH_INTERVALS_PER_SECOND;
+		line6->iso_buffers = USB_HIGH_ISO_BUFFERS;
+	}
+}
+
+/* Enable buffering of incoming messages, flush the buffer */
+static int line6_hwdep_open(struct snd_hwdep *hw, struct file *file)
+{
+	struct usb_line6 *line6 = hw->private_data;
+
+	/* NOTE: hwdep layer provides atomicity here */
+
+	line6->messages.active = 1;
+
+	return 0;
+}
+
+/* Stop buffering */
+static int line6_hwdep_release(struct snd_hwdep *hw, struct file *file)
+{
+	struct usb_line6 *line6 = hw->private_data;
+
+	line6->messages.active = 0;
+
+	return 0;
+}
+
+/* Read from circular buffer, return to user */
+static long
+line6_hwdep_read(struct snd_hwdep *hwdep, char __user *buf, long count,
+					loff_t *offset)
+{
+	struct usb_line6 *line6 = hwdep->private_data;
+	long rv = 0;
+	unsigned int out_count;
+
+	if (mutex_lock_interruptible(&line6->messages.read_lock))
+		return -ERESTARTSYS;
+
+	while (kfifo_len(&line6->messages.fifo) == 0) {
+		mutex_unlock(&line6->messages.read_lock);
+
+		rv = wait_event_interruptible(
+			line6->messages.wait_queue,
+			kfifo_len(&line6->messages.fifo) != 0);
+		if (rv < 0)
+			return rv;
+
+		if (mutex_lock_interruptible(&line6->messages.read_lock))
+			return -ERESTARTSYS;
+	}
+
+	if (kfifo_peek_len(&line6->messages.fifo) > count) {
+		/* Buffer too small; allow re-read of the current item... */
+		rv = -EINVAL;
+	} else {
+		rv = kfifo_to_user(&line6->messages.fifo, buf, count, &out_count);
+		if (rv == 0)
+			rv = out_count;
+	}
+
+	mutex_unlock(&line6->messages.read_lock);
+	return rv;
+}
+
+/* Write directly (no buffering) to device by user*/
+static long
+line6_hwdep_write(struct snd_hwdep *hwdep, const char __user *data, long count,
+					loff_t *offset)
+{
+	struct usb_line6 *line6 = hwdep->private_data;
+	int rv;
+	char *data_copy;
+
+	if (count > line6->max_packet_size * LINE6_RAW_MESSAGES_MAXCOUNT) {
+		/* This is an arbitrary limit - still better than nothing... */
+		return -EINVAL;
+	}
+
+	data_copy = memdup_user(data, count);
+	if (IS_ERR(data_copy))
+		return PTR_ERR(data_copy);
+
+	rv = line6_send_raw_message(line6, data_copy, count);
+
+	kfree(data_copy);
+	return rv;
+}
+
+static const struct snd_hwdep_ops hwdep_ops = {
+	.open    = line6_hwdep_open,
+	.release = line6_hwdep_release,
+	.read    = line6_hwdep_read,
+	.write   = line6_hwdep_write,
+};
+
+/* Insert into circular buffer */
+static void line6_hwdep_push_message(struct usb_line6 *line6)
+{
+	if (!line6->messages.active)
+		return;
+
+	if (kfifo_avail(&line6->messages.fifo) >= line6->message_length) {
+		/* No race condition here, there's only one writer */
+		kfifo_in(&line6->messages.fifo,
+			line6->buffer_message, line6->message_length);
+	} /* else TODO: signal overflow */
+
+	wake_up_interruptible(&line6->messages.wait_queue);
+}
+
+static int line6_hwdep_init(struct usb_line6 *line6)
+{
+	int err;
+	struct snd_hwdep *hwdep;
+
+	/* TODO: usb_driver_claim_interface(); */
+	line6->process_message = line6_hwdep_push_message;
+	line6->messages.active = 0;
+	init_waitqueue_head(&line6->messages.wait_queue);
+	mutex_init(&line6->messages.read_lock);
+	INIT_KFIFO(line6->messages.fifo);
+
+	err = snd_hwdep_new(line6->card, "config", 0, &hwdep);
+	if (err < 0)
+		goto end;
+	strcpy(hwdep->name, "config");
+	hwdep->iface = SNDRV_HWDEP_IFACE_LINE6;
+	hwdep->ops = hwdep_ops;
+	hwdep->private_data = line6;
+	hwdep->exclusive = true;
+
+end:
+	return err;
+}
+
+static int line6_init_cap_control(struct usb_line6 *line6)
+{
+	int ret;
+
+	/* initialize USB buffers: */
+	line6->buffer_listen = kmalloc(LINE6_BUFSIZE_LISTEN, GFP_KERNEL);
+	if (!line6->buffer_listen)
+		return -ENOMEM;
+
+	line6->urb_listen = usb_alloc_urb(0, GFP_KERNEL);
+	if (!line6->urb_listen)
+		return -ENOMEM;
+
+	if (line6->properties->capabilities & LINE6_CAP_CONTROL_MIDI) {
+		line6->buffer_message = kmalloc(LINE6_MIDI_MESSAGE_MAXLEN, GFP_KERNEL);
+		if (!line6->buffer_message)
+			return -ENOMEM;
+	} else {
+		ret = line6_hwdep_init(line6);
+		if (ret < 0)
+			return ret;
+	}
+
+	ret = line6_start_listen(line6);
+	if (ret < 0) {
+		dev_err(line6->ifcdev, "cannot start listening: %d\n", ret);
+		return ret;
+	}
+
+	return 0;
+}
+
+/*
+	Probe USB device.
+*/
+int line6_probe(struct usb_interface *interface,
+		const struct usb_device_id *id,
+		const char *driver_name,
+		const struct line6_properties *properties,
+		int (*private_init)(struct usb_line6 *, const struct usb_device_id *id),
+		size_t data_size)
+{
+	struct usb_device *usbdev = interface_to_usbdev(interface);
+	struct snd_card *card;
+	struct usb_line6 *line6;
+	int interface_number;
+	int ret;
+
+	if (WARN_ON(data_size < sizeof(*line6)))
+		return -EINVAL;
+
+	/* we don't handle multiple configurations */
+	if (usbdev->descriptor.bNumConfigurations != 1)
+		return -ENODEV;
+
+	ret = snd_card_new(&interface->dev,
+			   SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1,
+			   THIS_MODULE, data_size, &card);
+	if (ret < 0)
+		return ret;
+
+	/* store basic data: */
+	line6 = card->private_data;
+	line6->card = card;
+	line6->properties = properties;
+	line6->usbdev = usbdev;
+	line6->ifcdev = &interface->dev;
+
+	strcpy(card->id, properties->id);
+	strcpy(card->driver, driver_name);
+	strcpy(card->shortname, properties->name);
+	sprintf(card->longname, "Line 6 %s at USB %s", properties->name,
+		dev_name(line6->ifcdev));
+	card->private_free = line6_destruct;
+
+	usb_set_intfdata(interface, line6);
+
+	/* increment reference counters: */
+	usb_get_dev(usbdev);
+
+	/* initialize device info: */
+	dev_info(&interface->dev, "Line 6 %s found\n", properties->name);
+
+	/* query interface number */
+	interface_number = interface->cur_altsetting->desc.bInterfaceNumber;
+
+	/* TODO reserves the bus bandwidth even without actual transfer */
+	ret = usb_set_interface(usbdev, interface_number,
+				properties->altsetting);
+	if (ret < 0) {
+		dev_err(&interface->dev, "set_interface failed\n");
+		goto error;
+	}
+
+	line6_get_usb_properties(line6);
+
+	if (properties->capabilities & LINE6_CAP_CONTROL) {
+		ret = line6_init_cap_control(line6);
+		if (ret < 0)
+			goto error;
+	}
+
+	/* initialize device data based on device: */
+	ret = private_init(line6, id);
+	if (ret < 0)
+		goto error;
+
+	/* creation of additional special files should go here */
+
+	dev_info(&interface->dev, "Line 6 %s now attached\n",
+		 properties->name);
+
+	return 0;
+
+ error:
+	/* we can call disconnect callback here because no close-sync is
+	 * needed yet at this point
+	 */
+	line6_disconnect(interface);
+	return ret;
+}
+EXPORT_SYMBOL_GPL(line6_probe);
+
+/*
+	Line 6 device disconnected.
+*/
+void line6_disconnect(struct usb_interface *interface)
+{
+	struct usb_line6 *line6 = usb_get_intfdata(interface);
+	struct usb_device *usbdev = interface_to_usbdev(interface);
+
+	if (!line6)
+		return;
+
+	if (WARN_ON(usbdev != line6->usbdev))
+		return;
+
+	if (line6->urb_listen != NULL)
+		line6_stop_listen(line6);
+
+	snd_card_disconnect(line6->card);
+	if (line6->line6pcm)
+		line6_pcm_disconnect(line6->line6pcm);
+	if (line6->disconnect)
+		line6->disconnect(line6);
+
+	dev_info(&interface->dev, "Line 6 %s now disconnected\n",
+		 line6->properties->name);
+
+	/* make sure the device isn't destructed twice: */
+	usb_set_intfdata(interface, NULL);
+
+	snd_card_free_when_closed(line6->card);
+}
+EXPORT_SYMBOL_GPL(line6_disconnect);
+
+#ifdef CONFIG_PM
+
+/*
+	Suspend Line 6 device.
+*/
+int line6_suspend(struct usb_interface *interface, pm_message_t message)
+{
+	struct usb_line6 *line6 = usb_get_intfdata(interface);
+	struct snd_line6_pcm *line6pcm = line6->line6pcm;
+
+	snd_power_change_state(line6->card, SNDRV_CTL_POWER_D3hot);
+
+	if (line6->properties->capabilities & LINE6_CAP_CONTROL)
+		line6_stop_listen(line6);
+
+	if (line6pcm != NULL) {
+		snd_pcm_suspend_all(line6pcm->pcm);
+		line6pcm->flags = 0;
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(line6_suspend);
+
+/*
+	Resume Line 6 device.
+*/
+int line6_resume(struct usb_interface *interface)
+{
+	struct usb_line6 *line6 = usb_get_intfdata(interface);
+
+	if (line6->properties->capabilities & LINE6_CAP_CONTROL)
+		line6_start_listen(line6);
+
+	snd_power_change_state(line6->card, SNDRV_CTL_POWER_D0);
+	return 0;
+}
+EXPORT_SYMBOL_GPL(line6_resume);
+
+#endif /* CONFIG_PM */
+
+MODULE_AUTHOR(DRIVER_AUTHOR);
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_LICENSE("GPL");
+
diff --git a/sound/usb/line6/driver.h b/sound/usb/line6/driver.h
new file mode 100644
index 0000000..6142559
--- /dev/null
+++ b/sound/usb/line6/driver.h
@@ -0,0 +1,220 @@
+/*
+ * Line 6 Linux USB driver
+ *
+ * Copyright (C) 2004-2010 Markus Grabner (grabner@icg.tugraz.at)
+ *
+ *	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, version 2.
+ *
+ */
+
+#ifndef DRIVER_H
+#define DRIVER_H
+
+#include <linux/usb.h>
+#include <linux/mutex.h>
+#include <linux/kfifo.h>
+#include <sound/core.h>
+
+#include "midi.h"
+
+/* USB 1.1 speed configuration */
+#define USB_LOW_INTERVALS_PER_SECOND 1000
+#define USB_LOW_ISO_BUFFERS 2
+
+/* USB 2.0+ speed configuration */
+#define USB_HIGH_INTERVALS_PER_SECOND 8000
+#define USB_HIGH_ISO_BUFFERS 16
+
+/* Fallback USB interval and max packet size values */
+#define LINE6_FALLBACK_INTERVAL 10
+#define LINE6_FALLBACK_MAXPACKETSIZE 16
+
+#define LINE6_TIMEOUT 1
+#define LINE6_BUFSIZE_LISTEN 64
+#define LINE6_MIDI_MESSAGE_MAXLEN 256
+
+#define LINE6_RAW_MESSAGES_MAXCOUNT_ORDER 7
+/* 4k packets are common, BUFSIZE * MAXCOUNT should be bigger... */
+#define LINE6_RAW_MESSAGES_MAXCOUNT (1 << LINE6_RAW_MESSAGES_MAXCOUNT_ORDER)
+
+
+#if LINE6_BUFSIZE_LISTEN > 65535
+#error "Use dynamic fifo instead"
+#endif
+
+/*
+	Line 6 MIDI control commands
+*/
+#define LINE6_PARAM_CHANGE   0xb0
+#define LINE6_PROGRAM_CHANGE 0xc0
+#define LINE6_SYSEX_BEGIN    0xf0
+#define LINE6_SYSEX_END      0xf7
+#define LINE6_RESET          0xff
+
+/*
+	MIDI channel for messages initiated by the host
+	(and eventually echoed back by the device)
+*/
+#define LINE6_CHANNEL_HOST   0x00
+
+/*
+	MIDI channel for messages initiated by the device
+*/
+#define LINE6_CHANNEL_DEVICE 0x02
+
+#define LINE6_CHANNEL_UNKNOWN 5	/* don't know yet what this is good for */
+
+#define LINE6_CHANNEL_MASK 0x0f
+
+#define CHECK_STARTUP_PROGRESS(x, n)	\
+do {					\
+	if ((x) >= (n))			\
+		return;			\
+	x = (n);			\
+} while (0)
+
+extern const unsigned char line6_midi_id[3];
+
+static const int SYSEX_DATA_OFS = sizeof(line6_midi_id) + 3;
+static const int SYSEX_EXTRA_SIZE = sizeof(line6_midi_id) + 4;
+
+/*
+	 Common properties of Line 6 devices.
+*/
+struct line6_properties {
+	/* Card id string (maximum 16 characters).
+	 * This can be used to address the device in ALSA programs as
+	 * "default:CARD=<id>"
+	 */
+	const char *id;
+
+	/* Card short name (maximum 32 characters) */
+	const char *name;
+
+	/* Bit vector defining this device's capabilities in line6usb driver */
+	int capabilities;
+
+	int altsetting;
+
+	unsigned int ctrl_if;
+	unsigned int ep_ctrl_r;
+	unsigned int ep_ctrl_w;
+	unsigned int ep_audio_r;
+	unsigned int ep_audio_w;
+};
+
+/* Capability bits */
+enum {
+	/* device supports settings parameter via USB */
+	LINE6_CAP_CONTROL =	1 << 0,
+	/* device supports PCM input/output via USB */
+	LINE6_CAP_PCM =		1 << 1,
+	/* device supports hardware monitoring */
+	LINE6_CAP_HWMON =	1 << 2,
+	/* device requires output data when input is read */
+	LINE6_CAP_IN_NEEDS_OUT = 1 << 3,
+	/* device uses raw MIDI via USB (data endpoints) */
+	LINE6_CAP_CONTROL_MIDI = 1 << 4,
+	/* device provides low-level information */
+	LINE6_CAP_CONTROL_INFO = 1 << 5,
+};
+
+/*
+	 Common data shared by all Line 6 devices.
+	 Corresponds to a pair of USB endpoints.
+*/
+struct usb_line6 {
+	/* USB device */
+	struct usb_device *usbdev;
+
+	/* Properties */
+	const struct line6_properties *properties;
+
+	/* Interval for data USB packets */
+	int interval;
+	/* ...for isochronous transfers framing */
+	int intervals_per_second;
+
+	/* Number of isochronous URBs used for frame transfers */
+	int iso_buffers;
+
+	/* Maximum size of data USB packet */
+	int max_packet_size;
+
+	/* Device representing the USB interface */
+	struct device *ifcdev;
+
+	/* Line 6 sound card data structure.
+	 * Each device has at least MIDI or PCM.
+	 */
+	struct snd_card *card;
+
+	/* Line 6 PCM device data structure */
+	struct snd_line6_pcm *line6pcm;
+
+	/* Line 6 MIDI device data structure */
+	struct snd_line6_midi *line6midi;
+
+	/* URB for listening to POD data endpoint */
+	struct urb *urb_listen;
+
+	/* Buffer for incoming data from POD data endpoint */
+	unsigned char *buffer_listen;
+
+	/* Buffer for message to be processed, generated from MIDI layer */
+	unsigned char *buffer_message;
+
+	/* Length of message to be processed, generated from MIDI layer  */
+	int message_length;
+
+	/* Circular buffer for non-MIDI control messages */
+	struct {
+		struct mutex read_lock;
+		wait_queue_head_t wait_queue;
+		unsigned int active:1;
+		STRUCT_KFIFO_REC_2(LINE6_BUFSIZE_LISTEN * LINE6_RAW_MESSAGES_MAXCOUNT)
+			fifo;
+	} messages;
+
+	/* If MIDI is supported, buffer_message contains the pre-processed data;
+	 * otherwise the data is only in urb_listen (buffer_incoming).
+	 */
+	void (*process_message)(struct usb_line6 *);
+	void (*disconnect)(struct usb_line6 *line6);
+};
+
+extern char *line6_alloc_sysex_buffer(struct usb_line6 *line6, int code1,
+				      int code2, int size);
+extern int line6_read_data(struct usb_line6 *line6, unsigned address,
+			   void *data, unsigned datalen);
+extern int line6_read_serial_number(struct usb_line6 *line6,
+				    u32 *serial_number);
+extern int line6_send_raw_message_async(struct usb_line6 *line6,
+					const char *buffer, int size);
+extern int line6_send_sysex_message(struct usb_line6 *line6,
+				    const char *buffer, int size);
+extern ssize_t line6_set_raw(struct device *dev, struct device_attribute *attr,
+			     const char *buf, size_t count);
+extern void line6_start_timer(struct timer_list *timer, unsigned long msecs,
+			      void (*function)(struct timer_list *t));
+extern int line6_version_request_async(struct usb_line6 *line6);
+extern int line6_write_data(struct usb_line6 *line6, unsigned address,
+			    void *data, unsigned datalen);
+
+int line6_probe(struct usb_interface *interface,
+		const struct usb_device_id *id,
+		const char *driver_name,
+		const struct line6_properties *properties,
+		int (*private_init)(struct usb_line6 *, const struct usb_device_id *id),
+		size_t data_size);
+
+void line6_disconnect(struct usb_interface *interface);
+
+#ifdef CONFIG_PM
+int line6_suspend(struct usb_interface *interface, pm_message_t message);
+int line6_resume(struct usb_interface *interface);
+#endif
+
+#endif
diff --git a/sound/usb/line6/midi.c b/sound/usb/line6/midi.c
new file mode 100644
index 0000000..e2cf55c
--- /dev/null
+++ b/sound/usb/line6/midi.c
@@ -0,0 +1,297 @@
+/*
+ * Line 6 Linux USB driver
+ *
+ * Copyright (C) 2004-2010 Markus Grabner (grabner@icg.tugraz.at)
+ *
+ *	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, version 2.
+ *
+ */
+
+#include <linux/slab.h>
+#include <linux/usb.h>
+#include <linux/export.h>
+#include <sound/core.h>
+#include <sound/rawmidi.h>
+
+#include "driver.h"
+#include "midi.h"
+
+#define line6_rawmidi_substream_midi(substream) \
+	((struct snd_line6_midi *)((substream)->rmidi->private_data))
+
+static int send_midi_async(struct usb_line6 *line6, unsigned char *data,
+			   int length);
+
+/*
+	Pass data received via USB to MIDI.
+*/
+void line6_midi_receive(struct usb_line6 *line6, unsigned char *data,
+			int length)
+{
+	if (line6->line6midi->substream_receive)
+		snd_rawmidi_receive(line6->line6midi->substream_receive,
+				    data, length);
+}
+
+/*
+	Read data from MIDI buffer and transmit them via USB.
+*/
+static void line6_midi_transmit(struct snd_rawmidi_substream *substream)
+{
+	struct usb_line6 *line6 =
+	    line6_rawmidi_substream_midi(substream)->line6;
+	struct snd_line6_midi *line6midi = line6->line6midi;
+	struct midi_buffer *mb = &line6midi->midibuf_out;
+	unsigned char chunk[LINE6_FALLBACK_MAXPACKETSIZE];
+	int req, done;
+
+	for (;;) {
+		req = min(line6_midibuf_bytes_free(mb), line6->max_packet_size);
+		done = snd_rawmidi_transmit_peek(substream, chunk, req);
+
+		if (done == 0)
+			break;
+
+		line6_midibuf_write(mb, chunk, done);
+		snd_rawmidi_transmit_ack(substream, done);
+	}
+
+	for (;;) {
+		done = line6_midibuf_read(mb, chunk,
+					  LINE6_FALLBACK_MAXPACKETSIZE);
+
+		if (done == 0)
+			break;
+
+		send_midi_async(line6, chunk, done);
+	}
+}
+
+/*
+	Notification of completion of MIDI transmission.
+*/
+static void midi_sent(struct urb *urb)
+{
+	unsigned long flags;
+	int status;
+	int num;
+	struct usb_line6 *line6 = (struct usb_line6 *)urb->context;
+
+	status = urb->status;
+	kfree(urb->transfer_buffer);
+	usb_free_urb(urb);
+
+	if (status == -ESHUTDOWN)
+		return;
+
+	spin_lock_irqsave(&line6->line6midi->lock, flags);
+	num = --line6->line6midi->num_active_send_urbs;
+
+	if (num == 0) {
+		line6_midi_transmit(line6->line6midi->substream_transmit);
+		num = line6->line6midi->num_active_send_urbs;
+	}
+
+	if (num == 0)
+		wake_up(&line6->line6midi->send_wait);
+
+	spin_unlock_irqrestore(&line6->line6midi->lock, flags);
+}
+
+/*
+	Send an asynchronous MIDI message.
+	Assumes that line6->line6midi->lock is held
+	(i.e., this function is serialized).
+*/
+static int send_midi_async(struct usb_line6 *line6, unsigned char *data,
+			   int length)
+{
+	struct urb *urb;
+	int retval;
+	unsigned char *transfer_buffer;
+
+	urb = usb_alloc_urb(0, GFP_ATOMIC);
+
+	if (urb == NULL)
+		return -ENOMEM;
+
+	transfer_buffer = kmemdup(data, length, GFP_ATOMIC);
+
+	if (transfer_buffer == NULL) {
+		usb_free_urb(urb);
+		return -ENOMEM;
+	}
+
+	usb_fill_int_urb(urb, line6->usbdev,
+			 usb_sndintpipe(line6->usbdev,
+					 line6->properties->ep_ctrl_w),
+			 transfer_buffer, length, midi_sent, line6,
+			 line6->interval);
+	urb->actual_length = 0;
+	retval = usb_urb_ep_type_check(urb);
+	if (retval < 0)
+		goto error;
+
+	retval = usb_submit_urb(urb, GFP_ATOMIC);
+	if (retval < 0)
+		goto error;
+
+	++line6->line6midi->num_active_send_urbs;
+	return 0;
+
+ error:
+	dev_err(line6->ifcdev, "usb_submit_urb failed\n");
+	usb_free_urb(urb);
+	return retval;
+}
+
+static int line6_midi_output_open(struct snd_rawmidi_substream *substream)
+{
+	return 0;
+}
+
+static int line6_midi_output_close(struct snd_rawmidi_substream *substream)
+{
+	return 0;
+}
+
+static void line6_midi_output_trigger(struct snd_rawmidi_substream *substream,
+				      int up)
+{
+	unsigned long flags;
+	struct usb_line6 *line6 =
+	    line6_rawmidi_substream_midi(substream)->line6;
+
+	line6->line6midi->substream_transmit = substream;
+	spin_lock_irqsave(&line6->line6midi->lock, flags);
+
+	if (line6->line6midi->num_active_send_urbs == 0)
+		line6_midi_transmit(substream);
+
+	spin_unlock_irqrestore(&line6->line6midi->lock, flags);
+}
+
+static void line6_midi_output_drain(struct snd_rawmidi_substream *substream)
+{
+	struct usb_line6 *line6 =
+	    line6_rawmidi_substream_midi(substream)->line6;
+	struct snd_line6_midi *midi = line6->line6midi;
+
+	wait_event_interruptible(midi->send_wait,
+				 midi->num_active_send_urbs == 0);
+}
+
+static int line6_midi_input_open(struct snd_rawmidi_substream *substream)
+{
+	return 0;
+}
+
+static int line6_midi_input_close(struct snd_rawmidi_substream *substream)
+{
+	return 0;
+}
+
+static void line6_midi_input_trigger(struct snd_rawmidi_substream *substream,
+				     int up)
+{
+	struct usb_line6 *line6 =
+	    line6_rawmidi_substream_midi(substream)->line6;
+
+	if (up)
+		line6->line6midi->substream_receive = substream;
+	else
+		line6->line6midi->substream_receive = NULL;
+}
+
+static const struct snd_rawmidi_ops line6_midi_output_ops = {
+	.open = line6_midi_output_open,
+	.close = line6_midi_output_close,
+	.trigger = line6_midi_output_trigger,
+	.drain = line6_midi_output_drain,
+};
+
+static const struct snd_rawmidi_ops line6_midi_input_ops = {
+	.open = line6_midi_input_open,
+	.close = line6_midi_input_close,
+	.trigger = line6_midi_input_trigger,
+};
+
+/* Create a MIDI device */
+static int snd_line6_new_midi(struct usb_line6 *line6,
+			      struct snd_rawmidi **rmidi_ret)
+{
+	struct snd_rawmidi *rmidi;
+	int err;
+
+	err = snd_rawmidi_new(line6->card, "Line 6 MIDI", 0, 1, 1, rmidi_ret);
+	if (err < 0)
+		return err;
+
+	rmidi = *rmidi_ret;
+	strcpy(rmidi->id, line6->properties->id);
+	strcpy(rmidi->name, line6->properties->name);
+
+	rmidi->info_flags =
+	    SNDRV_RAWMIDI_INFO_OUTPUT |
+	    SNDRV_RAWMIDI_INFO_INPUT | SNDRV_RAWMIDI_INFO_DUPLEX;
+
+	snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT,
+			    &line6_midi_output_ops);
+	snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT,
+			    &line6_midi_input_ops);
+	return 0;
+}
+
+/* MIDI device destructor */
+static void snd_line6_midi_free(struct snd_rawmidi *rmidi)
+{
+	struct snd_line6_midi *line6midi = rmidi->private_data;
+
+	line6_midibuf_destroy(&line6midi->midibuf_in);
+	line6_midibuf_destroy(&line6midi->midibuf_out);
+	kfree(line6midi);
+}
+
+/*
+	Initialize the Line 6 MIDI subsystem.
+*/
+int line6_init_midi(struct usb_line6 *line6)
+{
+	int err;
+	struct snd_rawmidi *rmidi;
+	struct snd_line6_midi *line6midi;
+
+	if (!(line6->properties->capabilities & LINE6_CAP_CONTROL_MIDI)) {
+		/* skip MIDI initialization and report success */
+		return 0;
+	}
+
+	err = snd_line6_new_midi(line6, &rmidi);
+	if (err < 0)
+		return err;
+
+	line6midi = kzalloc(sizeof(struct snd_line6_midi), GFP_KERNEL);
+	if (!line6midi)
+		return -ENOMEM;
+
+	rmidi->private_data = line6midi;
+	rmidi->private_free = snd_line6_midi_free;
+
+	init_waitqueue_head(&line6midi->send_wait);
+	spin_lock_init(&line6midi->lock);
+	line6midi->line6 = line6;
+
+	err = line6_midibuf_init(&line6midi->midibuf_in, MIDI_BUFFER_SIZE, 0);
+	if (err < 0)
+		return err;
+
+	err = line6_midibuf_init(&line6midi->midibuf_out, MIDI_BUFFER_SIZE, 1);
+	if (err < 0)
+		return err;
+
+	line6->line6midi = line6midi;
+	return 0;
+}
+EXPORT_SYMBOL_GPL(line6_init_midi);
diff --git a/sound/usb/line6/midi.h b/sound/usb/line6/midi.h
new file mode 100644
index 0000000..cf82d69
--- /dev/null
+++ b/sound/usb/line6/midi.h
@@ -0,0 +1,51 @@
+/*
+ * Line 6 Linux USB driver
+ *
+ * Copyright (C) 2004-2010 Markus Grabner (grabner@icg.tugraz.at)
+ *
+ *	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, version 2.
+ *
+ */
+
+#ifndef MIDI_H
+#define MIDI_H
+
+#include <sound/rawmidi.h>
+
+#include "midibuf.h"
+
+#define MIDI_BUFFER_SIZE 1024
+
+struct snd_line6_midi {
+	/* Pointer back to the Line 6 driver data structure */
+	struct usb_line6 *line6;
+
+	/* MIDI substream for receiving (or NULL if not active) */
+	struct snd_rawmidi_substream *substream_receive;
+
+	/* MIDI substream for transmitting (or NULL if not active) */
+	struct snd_rawmidi_substream *substream_transmit;
+
+	/* Number of currently active MIDI send URBs */
+	int num_active_send_urbs;
+
+	/* Spin lock to protect MIDI buffer handling */
+	spinlock_t lock;
+
+	/* Wait queue for MIDI transmission */
+	wait_queue_head_t send_wait;
+
+	/* Buffer for incoming MIDI stream */
+	struct midi_buffer midibuf_in;
+
+	/* Buffer for outgoing MIDI stream */
+	struct midi_buffer midibuf_out;
+};
+
+extern int line6_init_midi(struct usb_line6 *line6);
+extern void line6_midi_receive(struct usb_line6 *line6, unsigned char *data,
+			       int length);
+
+#endif
diff --git a/sound/usb/line6/midibuf.c b/sound/usb/line6/midibuf.c
new file mode 100644
index 0000000..36a610b
--- /dev/null
+++ b/sound/usb/line6/midibuf.c
@@ -0,0 +1,252 @@
+/*
+ * Line 6 Linux USB driver
+ *
+ * Copyright (C) 2004-2010 Markus Grabner (grabner@icg.tugraz.at)
+ *
+ *	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, version 2.
+ *
+ */
+
+#include <linux/slab.h>
+
+#include "midibuf.h"
+
+static int midibuf_message_length(unsigned char code)
+{
+	int message_length;
+
+	if (code < 0x80)
+		message_length = -1;
+	else if (code < 0xf0) {
+		static const int length[] = { 3, 3, 3, 3, 2, 2, 3 };
+
+		message_length = length[(code >> 4) - 8];
+	} else {
+		/*
+		   Note that according to the MIDI specification 0xf2 is
+		   the "Song Position Pointer", but this is used by Line 6
+		   to send sysex messages to the host.
+		 */
+		static const int length[] = { -1, 2, -1, 2, -1, -1, 1, 1, 1, 1,
+			1, 1, 1, -1, 1, 1
+		};
+		message_length = length[code & 0x0f];
+	}
+
+	return message_length;
+}
+
+static int midibuf_is_empty(struct midi_buffer *this)
+{
+	return (this->pos_read == this->pos_write) && !this->full;
+}
+
+static int midibuf_is_full(struct midi_buffer *this)
+{
+	return this->full;
+}
+
+void line6_midibuf_reset(struct midi_buffer *this)
+{
+	this->pos_read = this->pos_write = this->full = 0;
+	this->command_prev = -1;
+}
+
+int line6_midibuf_init(struct midi_buffer *this, int size, int split)
+{
+	this->buf = kmalloc(size, GFP_KERNEL);
+
+	if (this->buf == NULL)
+		return -ENOMEM;
+
+	this->size = size;
+	this->split = split;
+	line6_midibuf_reset(this);
+	return 0;
+}
+
+int line6_midibuf_bytes_free(struct midi_buffer *this)
+{
+	return
+	    midibuf_is_full(this) ?
+	    0 :
+	    (this->pos_read - this->pos_write + this->size - 1) % this->size +
+	    1;
+}
+
+int line6_midibuf_bytes_used(struct midi_buffer *this)
+{
+	return
+	    midibuf_is_empty(this) ?
+	    0 :
+	    (this->pos_write - this->pos_read + this->size - 1) % this->size +
+	    1;
+}
+
+int line6_midibuf_write(struct midi_buffer *this, unsigned char *data,
+			int length)
+{
+	int bytes_free;
+	int length1, length2;
+	int skip_active_sense = 0;
+
+	if (midibuf_is_full(this) || (length <= 0))
+		return 0;
+
+	/* skip trailing active sense */
+	if (data[length - 1] == 0xfe) {
+		--length;
+		skip_active_sense = 1;
+	}
+
+	bytes_free = line6_midibuf_bytes_free(this);
+
+	if (length > bytes_free)
+		length = bytes_free;
+
+	if (length > 0) {
+		length1 = this->size - this->pos_write;
+
+		if (length < length1) {
+			/* no buffer wraparound */
+			memcpy(this->buf + this->pos_write, data, length);
+			this->pos_write += length;
+		} else {
+			/* buffer wraparound */
+			length2 = length - length1;
+			memcpy(this->buf + this->pos_write, data, length1);
+			memcpy(this->buf, data + length1, length2);
+			this->pos_write = length2;
+		}
+
+		if (this->pos_write == this->pos_read)
+			this->full = 1;
+	}
+
+	return length + skip_active_sense;
+}
+
+int line6_midibuf_read(struct midi_buffer *this, unsigned char *data,
+		       int length)
+{
+	int bytes_used;
+	int length1, length2;
+	int command;
+	int midi_length;
+	int repeat = 0;
+	int i;
+
+	/* we need to be able to store at least a 3 byte MIDI message */
+	if (length < 3)
+		return -EINVAL;
+
+	if (midibuf_is_empty(this))
+		return 0;
+
+	bytes_used = line6_midibuf_bytes_used(this);
+
+	if (length > bytes_used)
+		length = bytes_used;
+
+	length1 = this->size - this->pos_read;
+
+	/* check MIDI command length */
+	command = this->buf[this->pos_read];
+
+	if (command & 0x80) {
+		midi_length = midibuf_message_length(command);
+		this->command_prev = command;
+	} else {
+		if (this->command_prev > 0) {
+			int midi_length_prev =
+			    midibuf_message_length(this->command_prev);
+
+			if (midi_length_prev > 0) {
+				midi_length = midi_length_prev - 1;
+				repeat = 1;
+			} else
+				midi_length = -1;
+		} else
+			midi_length = -1;
+	}
+
+	if (midi_length < 0) {
+		/* search for end of message */
+		if (length < length1) {
+			/* no buffer wraparound */
+			for (i = 1; i < length; ++i)
+				if (this->buf[this->pos_read + i] & 0x80)
+					break;
+
+			midi_length = i;
+		} else {
+			/* buffer wraparound */
+			length2 = length - length1;
+
+			for (i = 1; i < length1; ++i)
+				if (this->buf[this->pos_read + i] & 0x80)
+					break;
+
+			if (i < length1)
+				midi_length = i;
+			else {
+				for (i = 0; i < length2; ++i)
+					if (this->buf[i] & 0x80)
+						break;
+
+				midi_length = length1 + i;
+			}
+		}
+
+		if (midi_length == length)
+			midi_length = -1;	/* end of message not found */
+	}
+
+	if (midi_length < 0) {
+		if (!this->split)
+			return 0;	/* command is not yet complete */
+	} else {
+		if (length < midi_length)
+			return 0;	/* command is not yet complete */
+
+		length = midi_length;
+	}
+
+	if (length < length1) {
+		/* no buffer wraparound */
+		memcpy(data + repeat, this->buf + this->pos_read, length);
+		this->pos_read += length;
+	} else {
+		/* buffer wraparound */
+		length2 = length - length1;
+		memcpy(data + repeat, this->buf + this->pos_read, length1);
+		memcpy(data + repeat + length1, this->buf, length2);
+		this->pos_read = length2;
+	}
+
+	if (repeat)
+		data[0] = this->command_prev;
+
+	this->full = 0;
+	return length + repeat;
+}
+
+int line6_midibuf_ignore(struct midi_buffer *this, int length)
+{
+	int bytes_used = line6_midibuf_bytes_used(this);
+
+	if (length > bytes_used)
+		length = bytes_used;
+
+	this->pos_read = (this->pos_read + length) % this->size;
+	this->full = 0;
+	return length;
+}
+
+void line6_midibuf_destroy(struct midi_buffer *this)
+{
+	kfree(this->buf);
+	this->buf = NULL;
+}
diff --git a/sound/usb/line6/midibuf.h b/sound/usb/line6/midibuf.h
new file mode 100644
index 0000000..6ea21ff
--- /dev/null
+++ b/sound/usb/line6/midibuf.h
@@ -0,0 +1,35 @@
+/*
+ * Line 6 Linux USB driver
+ *
+ * Copyright (C) 2004-2010 Markus Grabner (grabner@icg.tugraz.at)
+ *
+ *	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, version 2.
+ *
+ */
+
+#ifndef MIDIBUF_H
+#define MIDIBUF_H
+
+struct midi_buffer {
+	unsigned char *buf;
+	int size;
+	int split;
+	int pos_read, pos_write;
+	int full;
+	int command_prev;
+};
+
+extern int line6_midibuf_bytes_used(struct midi_buffer *mb);
+extern int line6_midibuf_bytes_free(struct midi_buffer *mb);
+extern void line6_midibuf_destroy(struct midi_buffer *mb);
+extern int line6_midibuf_ignore(struct midi_buffer *mb, int length);
+extern int line6_midibuf_init(struct midi_buffer *mb, int size, int split);
+extern int line6_midibuf_read(struct midi_buffer *mb, unsigned char *data,
+			      int length);
+extern void line6_midibuf_reset(struct midi_buffer *mb);
+extern int line6_midibuf_write(struct midi_buffer *mb, unsigned char *data,
+			       int length);
+
+#endif
diff --git a/sound/usb/line6/pcm.c b/sound/usb/line6/pcm.c
new file mode 100644
index 0000000..72c6f8e
--- /dev/null
+++ b/sound/usb/line6/pcm.c
@@ -0,0 +1,615 @@
+/*
+ * Line 6 Linux USB driver
+ *
+ * Copyright (C) 2004-2010 Markus Grabner (grabner@icg.tugraz.at)
+ *
+ *	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, version 2.
+ *
+ */
+
+#include <linux/slab.h>
+#include <linux/export.h>
+#include <sound/core.h>
+#include <sound/control.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+
+#include "capture.h"
+#include "driver.h"
+#include "playback.h"
+
+/* impulse response volume controls */
+static int snd_line6_impulse_volume_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 = 255;
+	return 0;
+}
+
+static int snd_line6_impulse_volume_get(struct snd_kcontrol *kcontrol,
+					struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_line6_pcm *line6pcm = snd_kcontrol_chip(kcontrol);
+
+	ucontrol->value.integer.value[0] = line6pcm->impulse_volume;
+	return 0;
+}
+
+static int snd_line6_impulse_volume_put(struct snd_kcontrol *kcontrol,
+					struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_line6_pcm *line6pcm = snd_kcontrol_chip(kcontrol);
+	int value = ucontrol->value.integer.value[0];
+	int err;
+
+	if (line6pcm->impulse_volume == value)
+		return 0;
+
+	line6pcm->impulse_volume = value;
+	if (value > 0) {
+		err = line6_pcm_acquire(line6pcm, LINE6_STREAM_IMPULSE, true);
+		if (err < 0) {
+			line6pcm->impulse_volume = 0;
+			return err;
+		}
+	} else {
+		line6_pcm_release(line6pcm, LINE6_STREAM_IMPULSE);
+	}
+	return 1;
+}
+
+/* impulse response period controls */
+static int snd_line6_impulse_period_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 = 2000;
+	return 0;
+}
+
+static int snd_line6_impulse_period_get(struct snd_kcontrol *kcontrol,
+					struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_line6_pcm *line6pcm = snd_kcontrol_chip(kcontrol);
+
+	ucontrol->value.integer.value[0] = line6pcm->impulse_period;
+	return 0;
+}
+
+static int snd_line6_impulse_period_put(struct snd_kcontrol *kcontrol,
+					struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_line6_pcm *line6pcm = snd_kcontrol_chip(kcontrol);
+	int value = ucontrol->value.integer.value[0];
+
+	if (line6pcm->impulse_period == value)
+		return 0;
+
+	line6pcm->impulse_period = value;
+	return 1;
+}
+
+/*
+	Unlink all currently active URBs.
+*/
+static void line6_unlink_audio_urbs(struct snd_line6_pcm *line6pcm,
+				    struct line6_pcm_stream *pcms)
+{
+	int i;
+
+	for (i = 0; i < line6pcm->line6->iso_buffers; i++) {
+		if (test_bit(i, &pcms->active_urbs)) {
+			if (!test_and_set_bit(i, &pcms->unlink_urbs))
+				usb_unlink_urb(pcms->urbs[i]);
+		}
+	}
+}
+
+/*
+	Wait until unlinking of all currently active URBs has been finished.
+*/
+static void line6_wait_clear_audio_urbs(struct snd_line6_pcm *line6pcm,
+					struct line6_pcm_stream *pcms)
+{
+	int timeout = HZ;
+	int i;
+	int alive;
+
+	do {
+		alive = 0;
+		for (i = 0; i < line6pcm->line6->iso_buffers; i++) {
+			if (test_bit(i, &pcms->active_urbs))
+				alive++;
+		}
+		if (!alive)
+			break;
+		set_current_state(TASK_UNINTERRUPTIBLE);
+		schedule_timeout(1);
+	} while (--timeout > 0);
+	if (alive)
+		dev_err(line6pcm->line6->ifcdev,
+			"timeout: still %d active urbs..\n", alive);
+}
+
+static inline struct line6_pcm_stream *
+get_stream(struct snd_line6_pcm *line6pcm, int direction)
+{
+	return (direction == SNDRV_PCM_STREAM_PLAYBACK) ?
+		&line6pcm->out : &line6pcm->in;
+}
+
+/* allocate a buffer if not opened yet;
+ * call this in line6pcm.state_mutex
+ */
+static int line6_buffer_acquire(struct snd_line6_pcm *line6pcm,
+				struct line6_pcm_stream *pstr, int direction, int type)
+{
+	const int pkt_size =
+		(direction == SNDRV_PCM_STREAM_PLAYBACK) ?
+			line6pcm->max_packet_size_out :
+			line6pcm->max_packet_size_in;
+
+	/* Invoked multiple times in a row so allocate once only */
+	if (!test_and_set_bit(type, &pstr->opened) && !pstr->buffer) {
+		pstr->buffer =
+			kmalloc(array3_size(line6pcm->line6->iso_buffers,
+					    LINE6_ISO_PACKETS, pkt_size),
+				GFP_KERNEL);
+		if (!pstr->buffer)
+			return -ENOMEM;
+	}
+	return 0;
+}
+
+/* free a buffer if all streams are closed;
+ * call this in line6pcm.state_mutex
+ */
+static void line6_buffer_release(struct snd_line6_pcm *line6pcm,
+				 struct line6_pcm_stream *pstr, int type)
+{
+	clear_bit(type, &pstr->opened);
+	if (!pstr->opened) {
+		line6_wait_clear_audio_urbs(line6pcm, pstr);
+		kfree(pstr->buffer);
+		pstr->buffer = NULL;
+	}
+}
+
+/* start a PCM stream */
+static int line6_stream_start(struct snd_line6_pcm *line6pcm, int direction,
+			      int type)
+{
+	unsigned long flags;
+	struct line6_pcm_stream *pstr = get_stream(line6pcm, direction);
+	int ret = 0;
+
+	spin_lock_irqsave(&pstr->lock, flags);
+	if (!test_and_set_bit(type, &pstr->running) &&
+	    !(pstr->active_urbs || pstr->unlink_urbs)) {
+		pstr->count = 0;
+		/* Submit all currently available URBs */
+		if (direction == SNDRV_PCM_STREAM_PLAYBACK)
+			ret = line6_submit_audio_out_all_urbs(line6pcm);
+		else
+			ret = line6_submit_audio_in_all_urbs(line6pcm);
+	}
+
+	if (ret < 0)
+		clear_bit(type, &pstr->running);
+	spin_unlock_irqrestore(&pstr->lock, flags);
+	return ret;
+}
+
+/* stop a PCM stream; this doesn't sync with the unlinked URBs */
+static void line6_stream_stop(struct snd_line6_pcm *line6pcm, int direction,
+			  int type)
+{
+	unsigned long flags;
+	struct line6_pcm_stream *pstr = get_stream(line6pcm, direction);
+
+	spin_lock_irqsave(&pstr->lock, flags);
+	clear_bit(type, &pstr->running);
+	if (!pstr->running) {
+		spin_unlock_irqrestore(&pstr->lock, flags);
+		line6_unlink_audio_urbs(line6pcm, pstr);
+		spin_lock_irqsave(&pstr->lock, flags);
+		if (direction == SNDRV_PCM_STREAM_CAPTURE) {
+			line6pcm->prev_fbuf = NULL;
+			line6pcm->prev_fsize = 0;
+		}
+	}
+	spin_unlock_irqrestore(&pstr->lock, flags);
+}
+
+/* common PCM trigger callback */
+int snd_line6_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+	struct snd_line6_pcm *line6pcm = snd_pcm_substream_chip(substream);
+	struct snd_pcm_substream *s;
+	int err;
+
+	clear_bit(LINE6_FLAG_PREPARED, &line6pcm->flags);
+
+	snd_pcm_group_for_each_entry(s, substream) {
+		if (s->pcm->card != substream->pcm->card)
+			continue;
+
+		switch (cmd) {
+		case SNDRV_PCM_TRIGGER_START:
+		case SNDRV_PCM_TRIGGER_RESUME:
+			if (s->stream == SNDRV_PCM_STREAM_CAPTURE &&
+				(line6pcm->line6->properties->capabilities &
+					LINE6_CAP_IN_NEEDS_OUT)) {
+				err = line6_stream_start(line6pcm, SNDRV_PCM_STREAM_PLAYBACK,
+						 LINE6_STREAM_CAPTURE_HELPER);
+				if (err < 0)
+					return err;
+			}
+			err = line6_stream_start(line6pcm, s->stream,
+						 LINE6_STREAM_PCM);
+			if (err < 0)
+				return err;
+			break;
+
+		case SNDRV_PCM_TRIGGER_STOP:
+		case SNDRV_PCM_TRIGGER_SUSPEND:
+			if (s->stream == SNDRV_PCM_STREAM_CAPTURE &&
+				(line6pcm->line6->properties->capabilities &
+					LINE6_CAP_IN_NEEDS_OUT)) {
+				line6_stream_stop(line6pcm, SNDRV_PCM_STREAM_PLAYBACK,
+					  LINE6_STREAM_CAPTURE_HELPER);
+			}
+			line6_stream_stop(line6pcm, s->stream,
+					  LINE6_STREAM_PCM);
+			break;
+
+		case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+			if (s->stream != SNDRV_PCM_STREAM_PLAYBACK)
+				return -EINVAL;
+			set_bit(LINE6_FLAG_PAUSE_PLAYBACK, &line6pcm->flags);
+			break;
+
+		case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+			if (s->stream != SNDRV_PCM_STREAM_PLAYBACK)
+				return -EINVAL;
+			clear_bit(LINE6_FLAG_PAUSE_PLAYBACK, &line6pcm->flags);
+			break;
+
+		default:
+			return -EINVAL;
+		}
+	}
+
+	return 0;
+}
+
+/* common PCM pointer callback */
+snd_pcm_uframes_t snd_line6_pointer(struct snd_pcm_substream *substream)
+{
+	struct snd_line6_pcm *line6pcm = snd_pcm_substream_chip(substream);
+	struct line6_pcm_stream *pstr = get_stream(line6pcm, substream->stream);
+
+	return pstr->pos_done;
+}
+
+/* Acquire and optionally start duplex streams:
+ * type is either LINE6_STREAM_IMPULSE or LINE6_STREAM_MONITOR
+ */
+int line6_pcm_acquire(struct snd_line6_pcm *line6pcm, int type, bool start)
+{
+	struct line6_pcm_stream *pstr;
+	int ret = 0, dir;
+
+	/* TODO: We should assert SNDRV_PCM_STREAM_PLAYBACK/CAPTURE == 0/1 */
+	mutex_lock(&line6pcm->state_mutex);
+	for (dir = 0; dir < 2; dir++) {
+		pstr = get_stream(line6pcm, dir);
+		ret = line6_buffer_acquire(line6pcm, pstr, dir, type);
+		if (ret < 0)
+			goto error;
+		if (!pstr->running)
+			line6_wait_clear_audio_urbs(line6pcm, pstr);
+	}
+	if (start) {
+		for (dir = 0; dir < 2; dir++) {
+			ret = line6_stream_start(line6pcm, dir, type);
+			if (ret < 0)
+				goto error;
+		}
+	}
+ error:
+	mutex_unlock(&line6pcm->state_mutex);
+	if (ret < 0)
+		line6_pcm_release(line6pcm, type);
+	return ret;
+}
+EXPORT_SYMBOL_GPL(line6_pcm_acquire);
+
+/* Stop and release duplex streams */
+void line6_pcm_release(struct snd_line6_pcm *line6pcm, int type)
+{
+	struct line6_pcm_stream *pstr;
+	int dir;
+
+	mutex_lock(&line6pcm->state_mutex);
+	for (dir = 0; dir < 2; dir++)
+		line6_stream_stop(line6pcm, dir, type);
+	for (dir = 0; dir < 2; dir++) {
+		pstr = get_stream(line6pcm, dir);
+		line6_buffer_release(line6pcm, pstr, type);
+	}
+	mutex_unlock(&line6pcm->state_mutex);
+}
+EXPORT_SYMBOL_GPL(line6_pcm_release);
+
+/* common PCM hw_params callback */
+int snd_line6_hw_params(struct snd_pcm_substream *substream,
+			struct snd_pcm_hw_params *hw_params)
+{
+	int ret;
+	struct snd_line6_pcm *line6pcm = snd_pcm_substream_chip(substream);
+	struct line6_pcm_stream *pstr = get_stream(line6pcm, substream->stream);
+
+	mutex_lock(&line6pcm->state_mutex);
+	ret = line6_buffer_acquire(line6pcm, pstr, substream->stream,
+	                           LINE6_STREAM_PCM);
+	if (ret < 0)
+		goto error;
+
+	ret = snd_pcm_lib_malloc_pages(substream,
+				       params_buffer_bytes(hw_params));
+	if (ret < 0) {
+		line6_buffer_release(line6pcm, pstr, LINE6_STREAM_PCM);
+		goto error;
+	}
+
+	pstr->period = params_period_bytes(hw_params);
+ error:
+	mutex_unlock(&line6pcm->state_mutex);
+	return ret;
+}
+
+/* common PCM hw_free callback */
+int snd_line6_hw_free(struct snd_pcm_substream *substream)
+{
+	struct snd_line6_pcm *line6pcm = snd_pcm_substream_chip(substream);
+	struct line6_pcm_stream *pstr = get_stream(line6pcm, substream->stream);
+
+	mutex_lock(&line6pcm->state_mutex);
+	line6_buffer_release(line6pcm, pstr, LINE6_STREAM_PCM);
+	mutex_unlock(&line6pcm->state_mutex);
+	return snd_pcm_lib_free_pages(substream);
+}
+
+
+/* control info callback */
+static int snd_line6_control_playback_info(struct snd_kcontrol *kcontrol,
+					   struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 2;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = 256;
+	return 0;
+}
+
+/* control get callback */
+static int snd_line6_control_playback_get(struct snd_kcontrol *kcontrol,
+					  struct snd_ctl_elem_value *ucontrol)
+{
+	int i;
+	struct snd_line6_pcm *line6pcm = snd_kcontrol_chip(kcontrol);
+
+	for (i = 0; i < 2; i++)
+		ucontrol->value.integer.value[i] = line6pcm->volume_playback[i];
+
+	return 0;
+}
+
+/* control put callback */
+static int snd_line6_control_playback_put(struct snd_kcontrol *kcontrol,
+					  struct snd_ctl_elem_value *ucontrol)
+{
+	int i, changed = 0;
+	struct snd_line6_pcm *line6pcm = snd_kcontrol_chip(kcontrol);
+
+	for (i = 0; i < 2; i++)
+		if (line6pcm->volume_playback[i] !=
+		    ucontrol->value.integer.value[i]) {
+			line6pcm->volume_playback[i] =
+			    ucontrol->value.integer.value[i];
+			changed = 1;
+		}
+
+	return changed;
+}
+
+/* control definition */
+static const struct snd_kcontrol_new line6_controls[] = {
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "PCM Playback Volume",
+		.info = snd_line6_control_playback_info,
+		.get = snd_line6_control_playback_get,
+		.put = snd_line6_control_playback_put
+	},
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Impulse Response Volume",
+		.info = snd_line6_impulse_volume_info,
+		.get = snd_line6_impulse_volume_get,
+		.put = snd_line6_impulse_volume_put
+	},
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Impulse Response Period",
+		.info = snd_line6_impulse_period_info,
+		.get = snd_line6_impulse_period_get,
+		.put = snd_line6_impulse_period_put
+	},
+};
+
+/*
+	Cleanup the PCM device.
+*/
+static void cleanup_urbs(struct line6_pcm_stream *pcms, int iso_buffers)
+{
+	int i;
+
+	/* Most likely impossible in current code... */
+	if (pcms->urbs == NULL)
+		return;
+
+	for (i = 0; i < iso_buffers; i++) {
+		if (pcms->urbs[i]) {
+			usb_kill_urb(pcms->urbs[i]);
+			usb_free_urb(pcms->urbs[i]);
+		}
+	}
+	kfree(pcms->urbs);
+	pcms->urbs = NULL;
+}
+
+static void line6_cleanup_pcm(struct snd_pcm *pcm)
+{
+	struct snd_line6_pcm *line6pcm = snd_pcm_chip(pcm);
+
+	cleanup_urbs(&line6pcm->out, line6pcm->line6->iso_buffers);
+	cleanup_urbs(&line6pcm->in, line6pcm->line6->iso_buffers);
+	kfree(line6pcm);
+}
+
+/* create a PCM device */
+static int snd_line6_new_pcm(struct usb_line6 *line6, struct snd_pcm **pcm_ret)
+{
+	struct snd_pcm *pcm;
+	int err;
+
+	err = snd_pcm_new(line6->card, (char *)line6->properties->name,
+			  0, 1, 1, pcm_ret);
+	if (err < 0)
+		return err;
+	pcm = *pcm_ret;
+	strcpy(pcm->name, line6->properties->name);
+
+	/* set operators */
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK,
+			&snd_line6_playback_ops);
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_line6_capture_ops);
+
+	/* pre-allocation of buffers */
+	snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_CONTINUOUS,
+					      snd_dma_continuous_data
+					      (GFP_KERNEL), 64 * 1024,
+					      128 * 1024);
+	return 0;
+}
+
+/*
+	Sync with PCM stream stops.
+*/
+void line6_pcm_disconnect(struct snd_line6_pcm *line6pcm)
+{
+	line6_unlink_audio_urbs(line6pcm, &line6pcm->out);
+	line6_unlink_audio_urbs(line6pcm, &line6pcm->in);
+	line6_wait_clear_audio_urbs(line6pcm, &line6pcm->out);
+	line6_wait_clear_audio_urbs(line6pcm, &line6pcm->in);
+}
+
+/*
+	Create and register the PCM device and mixer entries.
+	Create URBs for playback and capture.
+*/
+int line6_init_pcm(struct usb_line6 *line6,
+		   struct line6_pcm_properties *properties)
+{
+	int i, err;
+	unsigned ep_read = line6->properties->ep_audio_r;
+	unsigned ep_write = line6->properties->ep_audio_w;
+	struct snd_pcm *pcm;
+	struct snd_line6_pcm *line6pcm;
+
+	if (!(line6->properties->capabilities & LINE6_CAP_PCM))
+		return 0;	/* skip PCM initialization and report success */
+
+	err = snd_line6_new_pcm(line6, &pcm);
+	if (err < 0)
+		return err;
+
+	line6pcm = kzalloc(sizeof(*line6pcm), GFP_KERNEL);
+	if (!line6pcm)
+		return -ENOMEM;
+
+	mutex_init(&line6pcm->state_mutex);
+	line6pcm->pcm = pcm;
+	line6pcm->properties = properties;
+	line6pcm->volume_playback[0] = line6pcm->volume_playback[1] = 255;
+	line6pcm->volume_monitor = 255;
+	line6pcm->line6 = line6;
+
+	line6pcm->max_packet_size_in =
+		usb_maxpacket(line6->usbdev,
+			usb_rcvisocpipe(line6->usbdev, ep_read), 0);
+	line6pcm->max_packet_size_out =
+		usb_maxpacket(line6->usbdev,
+			usb_sndisocpipe(line6->usbdev, ep_write), 1);
+
+	spin_lock_init(&line6pcm->out.lock);
+	spin_lock_init(&line6pcm->in.lock);
+	line6pcm->impulse_period = LINE6_IMPULSE_DEFAULT_PERIOD;
+
+	line6->line6pcm = line6pcm;
+
+	pcm->private_data = line6pcm;
+	pcm->private_free = line6_cleanup_pcm;
+
+	err = line6_create_audio_out_urbs(line6pcm);
+	if (err < 0)
+		return err;
+
+	err = line6_create_audio_in_urbs(line6pcm);
+	if (err < 0)
+		return err;
+
+	/* mixer: */
+	for (i = 0; i < ARRAY_SIZE(line6_controls); i++) {
+		err = snd_ctl_add(line6->card,
+				  snd_ctl_new1(&line6_controls[i], line6pcm));
+		if (err < 0)
+			return err;
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(line6_init_pcm);
+
+/* prepare pcm callback */
+int snd_line6_prepare(struct snd_pcm_substream *substream)
+{
+	struct snd_line6_pcm *line6pcm = snd_pcm_substream_chip(substream);
+	struct line6_pcm_stream *pstr = get_stream(line6pcm, substream->stream);
+
+	mutex_lock(&line6pcm->state_mutex);
+	if (!pstr->running)
+		line6_wait_clear_audio_urbs(line6pcm, pstr);
+
+	if (!test_and_set_bit(LINE6_FLAG_PREPARED, &line6pcm->flags)) {
+		line6pcm->out.count = 0;
+		line6pcm->out.pos = 0;
+		line6pcm->out.pos_done = 0;
+		line6pcm->out.bytes = 0;
+		line6pcm->in.count = 0;
+		line6pcm->in.pos_done = 0;
+		line6pcm->in.bytes = 0;
+	}
+
+	mutex_unlock(&line6pcm->state_mutex);
+	return 0;
+}
diff --git a/sound/usb/line6/pcm.h b/sound/usb/line6/pcm.h
new file mode 100644
index 0000000..bb0c9cb
--- /dev/null
+++ b/sound/usb/line6/pcm.h
@@ -0,0 +1,199 @@
+/*
+ * Line 6 Linux USB driver
+ *
+ * Copyright (C) 2004-2010 Markus Grabner (grabner@icg.tugraz.at)
+ *
+ *	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, version 2.
+ *
+ */
+
+/*
+	PCM interface to POD series devices.
+*/
+
+#ifndef PCM_H
+#define PCM_H
+
+#include <sound/pcm.h>
+
+#include "driver.h"
+
+/*
+	number of USB frames per URB
+	The Line 6 Windows driver always transmits two frames per packet, but
+	the Linux driver performs significantly better (i.e., lower latency)
+	with only one frame per packet.
+*/
+#define LINE6_ISO_PACKETS	1
+
+/* in a "full speed" device (such as the PODxt Pro) this means 1ms,
+ *  for "high speed" it's 1/8ms
+ */
+#define LINE6_ISO_INTERVAL	1
+
+#define LINE6_IMPULSE_DEFAULT_PERIOD 100
+
+/*
+	Get substream from Line 6 PCM data structure
+*/
+#define get_substream(line6pcm, stream)	\
+		(line6pcm->pcm->streams[stream].substream)
+
+/*
+	PCM mode bits.
+
+	There are several features of the Line 6 USB driver which require PCM
+	data to be exchanged with the device:
+	*) PCM playback and capture via ALSA
+	*) software monitoring (for devices without hardware monitoring)
+	*) optional impulse response measurement
+	However, from the device's point of view, there is just a single
+	capture and playback stream, which must be shared between these
+	subsystems. It is therefore necessary to maintain the state of the
+	subsystems with respect to PCM usage.
+
+	We define two bit flags, "opened" and "running", for each playback
+	or capture stream.  Both can contain the bit flag corresponding to
+	LINE6_STREAM_* type,
+	  LINE6_STREAM_PCM = ALSA PCM playback or capture
+	  LINE6_STREAM_MONITOR = software monitoring
+	  IMPULSE = optional impulse response measurement
+	The opened flag indicates whether the buffer is allocated while
+	the running flag indicates whether the stream is running.
+
+	For monitor or impulse operations, the driver needs to call
+	line6_pcm_acquire() or line6_pcm_release() with the appropriate
+	LINE6_STREAM_* flag.
+*/
+
+/* stream types */
+enum {
+	LINE6_STREAM_PCM,
+	LINE6_STREAM_MONITOR,
+	LINE6_STREAM_IMPULSE,
+	LINE6_STREAM_CAPTURE_HELPER,
+};
+
+/* misc bit flags for PCM operation */
+enum {
+	LINE6_FLAG_PAUSE_PLAYBACK,
+	LINE6_FLAG_PREPARED,
+};
+
+struct line6_pcm_properties {
+	struct snd_pcm_hardware playback_hw, capture_hw;
+	struct snd_pcm_hw_constraint_ratdens rates;
+	int bytes_per_channel;
+};
+
+struct line6_pcm_stream {
+	/* allocated URBs */
+	struct urb **urbs;
+
+	/* Temporary buffer;
+	 * Since the packet size is not known in advance, this buffer is
+	 * large enough to store maximum size packets.
+	 */
+	unsigned char *buffer;
+
+	/* Free frame position in the buffer. */
+	snd_pcm_uframes_t pos;
+
+	/* Count processed bytes;
+	 * This is modulo period size (to determine when a period is finished).
+	 */
+	unsigned bytes;
+
+	/* Counter to create desired sample rate */
+	unsigned count;
+
+	/* period size in bytes */
+	unsigned period;
+
+	/* Processed frame position in the buffer;
+	 * The contents of the ring buffer have been consumed by the USB
+	 * subsystem (i.e., sent to the USB device) up to this position.
+	 */
+	snd_pcm_uframes_t pos_done;
+
+	/* Bit mask of active URBs */
+	unsigned long active_urbs;
+
+	/* Bit mask of URBs currently being unlinked */
+	unsigned long unlink_urbs;
+
+	/* Spin lock to protect updates of the buffer positions (not contents)
+	 */
+	spinlock_t lock;
+
+	/* Bit flags for operational stream types */
+	unsigned long opened;
+
+	/* Bit flags for running stream types */
+	unsigned long running;
+
+	int last_frame;
+};
+
+struct snd_line6_pcm {
+	/* Pointer back to the Line 6 driver data structure */
+	struct usb_line6 *line6;
+
+	/* Properties. */
+	struct line6_pcm_properties *properties;
+
+	/* ALSA pcm stream */
+	struct snd_pcm *pcm;
+
+	/* protection to state changes of in/out streams */
+	struct mutex state_mutex;
+
+	/* Capture and playback streams */
+	struct line6_pcm_stream in;
+	struct line6_pcm_stream out;
+
+	/* Previously captured frame (for software monitoring) */
+	unsigned char *prev_fbuf;
+
+	/* Size of previously captured frame (for software monitoring/sync) */
+	int prev_fsize;
+
+	/* Maximum size of USB packet */
+	int max_packet_size_in;
+	int max_packet_size_out;
+
+	/* PCM playback volume (left and right) */
+	int volume_playback[2];
+
+	/* PCM monitor volume */
+	int volume_monitor;
+
+	/* Volume of impulse response test signal (if zero, test is disabled) */
+	int impulse_volume;
+
+	/* Period of impulse response test signal */
+	int impulse_period;
+
+	/* Counter for impulse response test signal */
+	int impulse_count;
+
+	/* Several status bits (see LINE6_FLAG_*) */
+	unsigned long flags;
+};
+
+extern int line6_init_pcm(struct usb_line6 *line6,
+			  struct line6_pcm_properties *properties);
+extern int snd_line6_trigger(struct snd_pcm_substream *substream, int cmd);
+extern int snd_line6_prepare(struct snd_pcm_substream *substream);
+extern int snd_line6_hw_params(struct snd_pcm_substream *substream,
+			       struct snd_pcm_hw_params *hw_params);
+extern int snd_line6_hw_free(struct snd_pcm_substream *substream);
+extern snd_pcm_uframes_t snd_line6_pointer(struct snd_pcm_substream *substream);
+extern void line6_pcm_disconnect(struct snd_line6_pcm *line6pcm);
+extern int line6_pcm_acquire(struct snd_line6_pcm *line6pcm, int type,
+			       bool start);
+extern void line6_pcm_release(struct snd_line6_pcm *line6pcm, int type);
+
+#endif
diff --git a/sound/usb/line6/playback.c b/sound/usb/line6/playback.c
new file mode 100644
index 0000000..dec89d2
--- /dev/null
+++ b/sound/usb/line6/playback.c
@@ -0,0 +1,442 @@
+/*
+ * Line 6 Linux USB driver
+ *
+ * Copyright (C) 2004-2010 Markus Grabner (grabner@icg.tugraz.at)
+ *
+ *	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, version 2.
+ *
+ */
+
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+
+#include "capture.h"
+#include "driver.h"
+#include "pcm.h"
+#include "playback.h"
+
+/*
+	Software stereo volume control.
+*/
+static void change_volume(struct urb *urb_out, int volume[],
+			  int bytes_per_frame)
+{
+	int chn = 0;
+
+	if (volume[0] == 256 && volume[1] == 256)
+		return;		/* maximum volume - no change */
+
+	if (bytes_per_frame == 4) {
+		__le16 *p, *buf_end;
+
+		p = (__le16 *)urb_out->transfer_buffer;
+		buf_end = p + urb_out->transfer_buffer_length / sizeof(*p);
+
+		for (; p < buf_end; ++p) {
+			short pv = le16_to_cpu(*p);
+			int val = (pv * volume[chn & 1]) >> 8;
+			pv = clamp(val, -0x8000, 0x7fff);
+			*p = cpu_to_le16(pv);
+			++chn;
+		}
+	} else if (bytes_per_frame == 6) {
+		unsigned char *p, *buf_end;
+
+		p = (unsigned char *)urb_out->transfer_buffer;
+		buf_end = p + urb_out->transfer_buffer_length;
+
+		for (; p < buf_end; p += 3) {
+			int val;
+
+			val = p[0] + (p[1] << 8) + ((signed char)p[2] << 16);
+			val = (val * volume[chn & 1]) >> 8;
+			val = clamp(val, -0x800000, 0x7fffff);
+			p[0] = val;
+			p[1] = val >> 8;
+			p[2] = val >> 16;
+			++chn;
+		}
+	}
+}
+
+/*
+	Create signal for impulse response test.
+*/
+static void create_impulse_test_signal(struct snd_line6_pcm *line6pcm,
+				       struct urb *urb_out, int bytes_per_frame)
+{
+	int frames = urb_out->transfer_buffer_length / bytes_per_frame;
+
+	if (bytes_per_frame == 4) {
+		int i;
+		short *pi = (short *)line6pcm->prev_fbuf;
+		short *po = (short *)urb_out->transfer_buffer;
+
+		for (i = 0; i < frames; ++i) {
+			po[0] = pi[0];
+			po[1] = 0;
+			pi += 2;
+			po += 2;
+		}
+	} else if (bytes_per_frame == 6) {
+		int i, j;
+		unsigned char *pi = line6pcm->prev_fbuf;
+		unsigned char *po = urb_out->transfer_buffer;
+
+		for (i = 0; i < frames; ++i) {
+			for (j = 0; j < bytes_per_frame / 2; ++j)
+				po[j] = pi[j];
+
+			for (; j < bytes_per_frame; ++j)
+				po[j] = 0;
+
+			pi += bytes_per_frame;
+			po += bytes_per_frame;
+		}
+	}
+	if (--line6pcm->impulse_count <= 0) {
+		((unsigned char *)(urb_out->transfer_buffer))[bytes_per_frame -
+							      1] =
+		    line6pcm->impulse_volume;
+		line6pcm->impulse_count = line6pcm->impulse_period;
+	}
+}
+
+/*
+	Add signal to buffer for software monitoring.
+*/
+static void add_monitor_signal(struct urb *urb_out, unsigned char *signal,
+			       int volume, int bytes_per_frame)
+{
+	if (volume == 0)
+		return;		/* zero volume - no change */
+
+	if (bytes_per_frame == 4) {
+		__le16 *pi, *po, *buf_end;
+
+		pi = (__le16 *)signal;
+		po = (__le16 *)urb_out->transfer_buffer;
+		buf_end = po + urb_out->transfer_buffer_length / sizeof(*po);
+
+		for (; po < buf_end; ++pi, ++po) {
+			short pov = le16_to_cpu(*po);
+			short piv = le16_to_cpu(*pi);
+			int val = pov + ((piv * volume) >> 8);
+			pov = clamp(val, -0x8000, 0x7fff);
+			*po = cpu_to_le16(pov);
+		}
+	}
+
+	/*
+	   We don't need to handle devices with 6 bytes per frame here
+	   since they all support hardware monitoring.
+	 */
+}
+
+/*
+	Find a free URB, prepare audio data, and submit URB.
+	must be called in line6pcm->out.lock context
+*/
+static int submit_audio_out_urb(struct snd_line6_pcm *line6pcm)
+{
+	int index;
+	int i, urb_size, urb_frames;
+	int ret;
+	const int bytes_per_frame =
+		line6pcm->properties->bytes_per_channel *
+		line6pcm->properties->playback_hw.channels_max;
+	const int frame_increment =
+		line6pcm->properties->rates.rats[0].num_min;
+	const int frame_factor =
+		line6pcm->properties->rates.rats[0].den *
+		(line6pcm->line6->intervals_per_second / LINE6_ISO_INTERVAL);
+	struct urb *urb_out;
+
+	index = find_first_zero_bit(&line6pcm->out.active_urbs,
+				    line6pcm->line6->iso_buffers);
+
+	if (index < 0 || index >= line6pcm->line6->iso_buffers) {
+		dev_err(line6pcm->line6->ifcdev, "no free URB found\n");
+		return -EINVAL;
+	}
+
+	urb_out = line6pcm->out.urbs[index];
+	urb_size = 0;
+
+	/* TODO: this may not work for LINE6_ISO_PACKETS != 1 */
+	for (i = 0; i < LINE6_ISO_PACKETS; ++i) {
+		/* compute frame size for given sampling rate */
+		int fsize = 0;
+		struct usb_iso_packet_descriptor *fout =
+		    &urb_out->iso_frame_desc[i];
+
+		fsize = line6pcm->prev_fsize;
+		if (fsize == 0) {
+			int n;
+
+			line6pcm->out.count += frame_increment;
+			n = line6pcm->out.count / frame_factor;
+			line6pcm->out.count -= n * frame_factor;
+			fsize = n;
+		}
+
+		fsize *= bytes_per_frame;
+
+		fout->offset = urb_size;
+		fout->length = fsize;
+		urb_size += fsize;
+	}
+
+	if (urb_size == 0) {
+		/* can't determine URB size */
+		dev_err(line6pcm->line6->ifcdev, "driver bug: urb_size = 0\n");
+		return -EINVAL;
+	}
+
+	urb_frames = urb_size / bytes_per_frame;
+	urb_out->transfer_buffer =
+	    line6pcm->out.buffer +
+	    index * LINE6_ISO_PACKETS * line6pcm->max_packet_size_out;
+	urb_out->transfer_buffer_length = urb_size;
+	urb_out->context = line6pcm;
+
+	if (test_bit(LINE6_STREAM_PCM, &line6pcm->out.running) &&
+	    !test_bit(LINE6_FLAG_PAUSE_PLAYBACK, &line6pcm->flags)) {
+		struct snd_pcm_runtime *runtime =
+		    get_substream(line6pcm, SNDRV_PCM_STREAM_PLAYBACK)->runtime;
+
+		if (line6pcm->out.pos + urb_frames > runtime->buffer_size) {
+			/*
+			   The transferred area goes over buffer boundary,
+			   copy the data to the temp buffer.
+			 */
+			int len;
+
+			len = runtime->buffer_size - line6pcm->out.pos;
+
+			if (len > 0) {
+				memcpy(urb_out->transfer_buffer,
+				       runtime->dma_area +
+				       line6pcm->out.pos * bytes_per_frame,
+				       len * bytes_per_frame);
+				memcpy(urb_out->transfer_buffer +
+				       len * bytes_per_frame, runtime->dma_area,
+				       (urb_frames - len) * bytes_per_frame);
+			} else
+				dev_err(line6pcm->line6->ifcdev, "driver bug: len = %d\n",
+					len);
+		} else {
+			memcpy(urb_out->transfer_buffer,
+			       runtime->dma_area +
+			       line6pcm->out.pos * bytes_per_frame,
+			       urb_out->transfer_buffer_length);
+		}
+
+		line6pcm->out.pos += urb_frames;
+		if (line6pcm->out.pos >= runtime->buffer_size)
+			line6pcm->out.pos -= runtime->buffer_size;
+
+		change_volume(urb_out, line6pcm->volume_playback,
+			      bytes_per_frame);
+	} else {
+		memset(urb_out->transfer_buffer, 0,
+		       urb_out->transfer_buffer_length);
+	}
+
+	spin_lock_nested(&line6pcm->in.lock, SINGLE_DEPTH_NESTING);
+	if (line6pcm->prev_fbuf) {
+		if (test_bit(LINE6_STREAM_IMPULSE, &line6pcm->out.running)) {
+			create_impulse_test_signal(line6pcm, urb_out,
+						   bytes_per_frame);
+			if (test_bit(LINE6_STREAM_PCM, &line6pcm->in.running)) {
+				line6_capture_copy(line6pcm,
+						   urb_out->transfer_buffer,
+						   urb_out->
+						   transfer_buffer_length);
+				line6_capture_check_period(line6pcm,
+					urb_out->transfer_buffer_length);
+			}
+		} else {
+			if (!(line6pcm->line6->properties->capabilities & LINE6_CAP_HWMON)
+			    && line6pcm->out.running && line6pcm->in.running)
+				add_monitor_signal(urb_out, line6pcm->prev_fbuf,
+						   line6pcm->volume_monitor,
+						   bytes_per_frame);
+		}
+		line6pcm->prev_fbuf = NULL;
+		line6pcm->prev_fsize = 0;
+	}
+	spin_unlock(&line6pcm->in.lock);
+
+	ret = usb_submit_urb(urb_out, GFP_ATOMIC);
+
+	if (ret == 0)
+		set_bit(index, &line6pcm->out.active_urbs);
+	else
+		dev_err(line6pcm->line6->ifcdev,
+			"URB out #%d submission failed (%d)\n", index, ret);
+
+	return 0;
+}
+
+/*
+	Submit all currently available playback URBs.
+	must be called in line6pcm->out.lock context
+ */
+int line6_submit_audio_out_all_urbs(struct snd_line6_pcm *line6pcm)
+{
+	int ret = 0, i;
+
+	for (i = 0; i < line6pcm->line6->iso_buffers; ++i) {
+		ret = submit_audio_out_urb(line6pcm);
+		if (ret < 0)
+			break;
+	}
+
+	return ret;
+}
+
+/*
+	Callback for completed playback URB.
+*/
+static void audio_out_callback(struct urb *urb)
+{
+	int i, index, length = 0, shutdown = 0;
+	unsigned long flags;
+	struct snd_line6_pcm *line6pcm = (struct snd_line6_pcm *)urb->context;
+	struct snd_pcm_substream *substream =
+	    get_substream(line6pcm, SNDRV_PCM_STREAM_PLAYBACK);
+	const int bytes_per_frame =
+		line6pcm->properties->bytes_per_channel *
+		line6pcm->properties->playback_hw.channels_max;
+
+#if USE_CLEAR_BUFFER_WORKAROUND
+	memset(urb->transfer_buffer, 0, urb->transfer_buffer_length);
+#endif
+
+	line6pcm->out.last_frame = urb->start_frame;
+
+	/* find index of URB */
+	for (index = 0; index < line6pcm->line6->iso_buffers; index++)
+		if (urb == line6pcm->out.urbs[index])
+			break;
+
+	if (index >= line6pcm->line6->iso_buffers)
+		return;		/* URB has been unlinked asynchronously */
+
+	for (i = 0; i < LINE6_ISO_PACKETS; i++)
+		length += urb->iso_frame_desc[i].length;
+
+	spin_lock_irqsave(&line6pcm->out.lock, flags);
+
+	if (test_bit(LINE6_STREAM_PCM, &line6pcm->out.running)) {
+		struct snd_pcm_runtime *runtime = substream->runtime;
+
+		line6pcm->out.pos_done +=
+		    length / bytes_per_frame;
+
+		if (line6pcm->out.pos_done >= runtime->buffer_size)
+			line6pcm->out.pos_done -= runtime->buffer_size;
+	}
+
+	clear_bit(index, &line6pcm->out.active_urbs);
+
+	for (i = 0; i < LINE6_ISO_PACKETS; i++)
+		if (urb->iso_frame_desc[i].status == -EXDEV) {
+			shutdown = 1;
+			break;
+		}
+
+	if (test_and_clear_bit(index, &line6pcm->out.unlink_urbs))
+		shutdown = 1;
+
+	if (!shutdown) {
+		submit_audio_out_urb(line6pcm);
+
+		if (test_bit(LINE6_STREAM_PCM, &line6pcm->out.running)) {
+			line6pcm->out.bytes += length;
+			if (line6pcm->out.bytes >= line6pcm->out.period) {
+				line6pcm->out.bytes %= line6pcm->out.period;
+				spin_unlock(&line6pcm->out.lock);
+				snd_pcm_period_elapsed(substream);
+				spin_lock(&line6pcm->out.lock);
+			}
+		}
+	}
+	spin_unlock_irqrestore(&line6pcm->out.lock, flags);
+}
+
+/* open playback callback */
+static int snd_line6_playback_open(struct snd_pcm_substream *substream)
+{
+	int err;
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct snd_line6_pcm *line6pcm = snd_pcm_substream_chip(substream);
+
+	err = snd_pcm_hw_constraint_ratdens(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
+					    &line6pcm->properties->rates);
+	if (err < 0)
+		return err;
+
+	runtime->hw = line6pcm->properties->playback_hw;
+	return 0;
+}
+
+/* close playback callback */
+static int snd_line6_playback_close(struct snd_pcm_substream *substream)
+{
+	return 0;
+}
+
+/* playback operators */
+const struct snd_pcm_ops snd_line6_playback_ops = {
+	.open = snd_line6_playback_open,
+	.close = snd_line6_playback_close,
+	.ioctl = snd_pcm_lib_ioctl,
+	.hw_params = snd_line6_hw_params,
+	.hw_free = snd_line6_hw_free,
+	.prepare = snd_line6_prepare,
+	.trigger = snd_line6_trigger,
+	.pointer = snd_line6_pointer,
+};
+
+int line6_create_audio_out_urbs(struct snd_line6_pcm *line6pcm)
+{
+	struct usb_line6 *line6 = line6pcm->line6;
+	int i;
+
+	line6pcm->out.urbs = kcalloc(line6->iso_buffers, sizeof(struct urb *),
+				     GFP_KERNEL);
+	if (line6pcm->out.urbs == NULL)
+		return -ENOMEM;
+
+	/* create audio URBs and fill in constant values: */
+	for (i = 0; i < line6->iso_buffers; ++i) {
+		struct urb *urb;
+
+		/* URB for audio out: */
+		urb = line6pcm->out.urbs[i] =
+		    usb_alloc_urb(LINE6_ISO_PACKETS, GFP_KERNEL);
+
+		if (urb == NULL)
+			return -ENOMEM;
+
+		urb->dev = line6->usbdev;
+		urb->pipe =
+		    usb_sndisocpipe(line6->usbdev,
+				    line6->properties->ep_audio_w &
+				    USB_ENDPOINT_NUMBER_MASK);
+		urb->transfer_flags = URB_ISO_ASAP;
+		urb->start_frame = -1;
+		urb->number_of_packets = LINE6_ISO_PACKETS;
+		urb->interval = LINE6_ISO_INTERVAL;
+		urb->error_count = 0;
+		urb->complete = audio_out_callback;
+	}
+
+	return 0;
+}
diff --git a/sound/usb/line6/playback.h b/sound/usb/line6/playback.h
new file mode 100644
index 0000000..d8d3b8a
--- /dev/null
+++ b/sound/usb/line6/playback.h
@@ -0,0 +1,35 @@
+/*
+ * Line 6 Linux USB driver
+ *
+ * Copyright (C) 2004-2010 Markus Grabner (grabner@icg.tugraz.at)
+ *
+ *	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, version 2.
+ *
+ */
+
+#ifndef PLAYBACK_H
+#define PLAYBACK_H
+
+#include <sound/pcm.h>
+
+#include "driver.h"
+
+/*
+ * When the TonePort is used with jack in full duplex mode and the outputs are
+ * not connected, the software monitor produces an ugly noise since everything
+ * written to the output buffer (i.e., the input signal) will be repeated in
+ * the next period (sounds like a delay effect). As a workaround, the output
+ * buffer is cleared after the data have been read, but there must be a better
+ * solution. Until one is found, this workaround can be used to fix the
+ * problem.
+ */
+#define USE_CLEAR_BUFFER_WORKAROUND 1
+
+extern const struct snd_pcm_ops snd_line6_playback_ops;
+
+extern int line6_create_audio_out_urbs(struct snd_line6_pcm *line6pcm);
+extern int line6_submit_audio_out_all_urbs(struct snd_line6_pcm *line6pcm);
+
+#endif
diff --git a/sound/usb/line6/pod.c b/sound/usb/line6/pod.c
new file mode 100644
index 0000000..020c818
--- /dev/null
+++ b/sound/usb/line6/pod.c
@@ -0,0 +1,589 @@
+/*
+ * Line 6 Linux USB driver
+ *
+ * Copyright (C) 2004-2010 Markus Grabner (grabner@icg.tugraz.at)
+ *
+ *	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, version 2.
+ *
+ */
+
+#include <linux/slab.h>
+#include <linux/wait.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/usb.h>
+
+#include <sound/core.h>
+#include <sound/control.h>
+
+#include "capture.h"
+#include "driver.h"
+#include "playback.h"
+
+/*
+	Locate name in binary program dump
+*/
+#define	POD_NAME_OFFSET 0
+#define	POD_NAME_LENGTH 16
+
+/*
+	Other constants
+*/
+#define POD_CONTROL_SIZE 0x80
+#define POD_BUFSIZE_DUMPREQ 7
+#define POD_STARTUP_DELAY 1000
+
+/*
+	Stages of POD startup procedure
+*/
+enum {
+	POD_STARTUP_INIT = 1,
+	POD_STARTUP_VERSIONREQ,
+	POD_STARTUP_WORKQUEUE,
+	POD_STARTUP_SETUP,
+	POD_STARTUP_LAST = POD_STARTUP_SETUP - 1
+};
+
+enum {
+	LINE6_BASSPODXT,
+	LINE6_BASSPODXTLIVE,
+	LINE6_BASSPODXTPRO,
+	LINE6_POCKETPOD,
+	LINE6_PODXT,
+	LINE6_PODXTLIVE_POD,
+	LINE6_PODXTPRO,
+};
+
+struct usb_line6_pod {
+	/* Generic Line 6 USB data */
+	struct usb_line6 line6;
+
+	/* Instrument monitor level */
+	int monitor_level;
+
+	/* Timer for device initialization */
+	struct timer_list startup_timer;
+
+	/* Work handler for device initialization */
+	struct work_struct startup_work;
+
+	/* Current progress in startup procedure */
+	int startup_progress;
+
+	/* Serial number of device */
+	u32 serial_number;
+
+	/* Firmware version (x 100) */
+	int firmware_version;
+
+	/* Device ID */
+	int device_id;
+};
+
+#define POD_SYSEX_CODE 3
+
+/* *INDENT-OFF* */
+
+enum {
+	POD_SYSEX_SAVE      = 0x24,
+	POD_SYSEX_SYSTEM    = 0x56,
+	POD_SYSEX_SYSTEMREQ = 0x57,
+	/* POD_SYSEX_UPDATE    = 0x6c, */  /* software update! */
+	POD_SYSEX_STORE     = 0x71,
+	POD_SYSEX_FINISH    = 0x72,
+	POD_SYSEX_DUMPMEM   = 0x73,
+	POD_SYSEX_DUMP      = 0x74,
+	POD_SYSEX_DUMPREQ   = 0x75
+
+	/* dumps entire internal memory of PODxt Pro */
+	/* POD_SYSEX_DUMPMEM2  = 0x76 */
+};
+
+enum {
+	POD_MONITOR_LEVEL  = 0x04,
+	POD_SYSTEM_INVALID = 0x10000
+};
+
+/* *INDENT-ON* */
+
+enum {
+	POD_DUMP_MEMORY = 2
+};
+
+enum {
+	POD_BUSY_READ,
+	POD_BUSY_WRITE,
+	POD_CHANNEL_DIRTY,
+	POD_SAVE_PRESSED,
+	POD_BUSY_MIDISEND
+};
+
+static struct snd_ratden pod_ratden = {
+	.num_min = 78125,
+	.num_max = 78125,
+	.num_step = 1,
+	.den = 2
+};
+
+static struct line6_pcm_properties pod_pcm_properties = {
+	.playback_hw = {
+				  .info = (SNDRV_PCM_INFO_MMAP |
+					   SNDRV_PCM_INFO_INTERLEAVED |
+					   SNDRV_PCM_INFO_BLOCK_TRANSFER |
+					   SNDRV_PCM_INFO_MMAP_VALID |
+					   SNDRV_PCM_INFO_PAUSE |
+					   SNDRV_PCM_INFO_SYNC_START),
+				  .formats = SNDRV_PCM_FMTBIT_S24_3LE,
+				  .rates = SNDRV_PCM_RATE_KNOT,
+				  .rate_min = 39062,
+				  .rate_max = 39063,
+				  .channels_min = 2,
+				  .channels_max = 2,
+				  .buffer_bytes_max = 60000,
+				  .period_bytes_min = 64,
+				  .period_bytes_max = 8192,
+				  .periods_min = 1,
+				  .periods_max = 1024},
+	.capture_hw = {
+				 .info = (SNDRV_PCM_INFO_MMAP |
+					  SNDRV_PCM_INFO_INTERLEAVED |
+					  SNDRV_PCM_INFO_BLOCK_TRANSFER |
+					  SNDRV_PCM_INFO_MMAP_VALID |
+					  SNDRV_PCM_INFO_SYNC_START),
+				 .formats = SNDRV_PCM_FMTBIT_S24_3LE,
+				 .rates = SNDRV_PCM_RATE_KNOT,
+				 .rate_min = 39062,
+				 .rate_max = 39063,
+				 .channels_min = 2,
+				 .channels_max = 2,
+				 .buffer_bytes_max = 60000,
+				 .period_bytes_min = 64,
+				 .period_bytes_max = 8192,
+				 .periods_min = 1,
+				 .periods_max = 1024},
+	.rates = {
+			    .nrats = 1,
+			    .rats = &pod_ratden},
+	.bytes_per_channel = 3 /* SNDRV_PCM_FMTBIT_S24_3LE */
+};
+
+static const char pod_version_header[] = {
+	0xf2, 0x7e, 0x7f, 0x06, 0x02
+};
+
+/* forward declarations: */
+static void pod_startup2(struct timer_list *t);
+static void pod_startup3(struct usb_line6_pod *pod);
+
+static char *pod_alloc_sysex_buffer(struct usb_line6_pod *pod, int code,
+				    int size)
+{
+	return line6_alloc_sysex_buffer(&pod->line6, POD_SYSEX_CODE, code,
+					size);
+}
+
+/*
+	Process a completely received message.
+*/
+static void line6_pod_process_message(struct usb_line6 *line6)
+{
+	struct usb_line6_pod *pod = (struct usb_line6_pod *) line6;
+	const unsigned char *buf = pod->line6.buffer_message;
+
+	if (memcmp(buf, pod_version_header, sizeof(pod_version_header)) == 0) {
+		pod->firmware_version = buf[13] * 100 + buf[14] * 10 + buf[15];
+		pod->device_id = ((int)buf[8] << 16) | ((int)buf[9] << 8) |
+				 (int) buf[10];
+		pod_startup3(pod);
+		return;
+	}
+
+	/* Only look for sysex messages from this device */
+	if (buf[0] != (LINE6_SYSEX_BEGIN | LINE6_CHANNEL_DEVICE) &&
+	    buf[0] != (LINE6_SYSEX_BEGIN | LINE6_CHANNEL_UNKNOWN)) {
+		return;
+	}
+	if (memcmp(buf + 1, line6_midi_id, sizeof(line6_midi_id)) != 0)
+		return;
+
+	if (buf[5] == POD_SYSEX_SYSTEM && buf[6] == POD_MONITOR_LEVEL) {
+		short value = ((int)buf[7] << 12) | ((int)buf[8] << 8) |
+			      ((int)buf[9] << 4) | (int)buf[10];
+		pod->monitor_level = value;
+	}
+}
+
+/*
+	Send system parameter (from integer).
+*/
+static int pod_set_system_param_int(struct usb_line6_pod *pod, int value,
+				    int code)
+{
+	char *sysex;
+	static const int size = 5;
+
+	sysex = pod_alloc_sysex_buffer(pod, POD_SYSEX_SYSTEM, size);
+	if (!sysex)
+		return -ENOMEM;
+	sysex[SYSEX_DATA_OFS] = code;
+	sysex[SYSEX_DATA_OFS + 1] = (value >> 12) & 0x0f;
+	sysex[SYSEX_DATA_OFS + 2] = (value >> 8) & 0x0f;
+	sysex[SYSEX_DATA_OFS + 3] = (value >> 4) & 0x0f;
+	sysex[SYSEX_DATA_OFS + 4] = (value) & 0x0f;
+	line6_send_sysex_message(&pod->line6, sysex, size);
+	kfree(sysex);
+	return 0;
+}
+
+/*
+	"read" request on "serial_number" special file.
+*/
+static ssize_t serial_number_show(struct device *dev,
+				  struct device_attribute *attr, char *buf)
+{
+	struct snd_card *card = dev_to_snd_card(dev);
+	struct usb_line6_pod *pod = card->private_data;
+
+	return sprintf(buf, "%u\n", pod->serial_number);
+}
+
+/*
+	"read" request on "firmware_version" special file.
+*/
+static ssize_t firmware_version_show(struct device *dev,
+				     struct device_attribute *attr, char *buf)
+{
+	struct snd_card *card = dev_to_snd_card(dev);
+	struct usb_line6_pod *pod = card->private_data;
+
+	return sprintf(buf, "%d.%02d\n", pod->firmware_version / 100,
+		       pod->firmware_version % 100);
+}
+
+/*
+	"read" request on "device_id" special file.
+*/
+static ssize_t device_id_show(struct device *dev,
+			      struct device_attribute *attr, char *buf)
+{
+	struct snd_card *card = dev_to_snd_card(dev);
+	struct usb_line6_pod *pod = card->private_data;
+
+	return sprintf(buf, "%d\n", pod->device_id);
+}
+
+/*
+	POD startup procedure.
+	This is a sequence of functions with special requirements (e.g., must
+	not run immediately after initialization, must not run in interrupt
+	context). After the last one has finished, the device is ready to use.
+*/
+
+static void pod_startup1(struct usb_line6_pod *pod)
+{
+	CHECK_STARTUP_PROGRESS(pod->startup_progress, POD_STARTUP_INIT);
+
+	/* delay startup procedure: */
+	line6_start_timer(&pod->startup_timer, POD_STARTUP_DELAY, pod_startup2);
+}
+
+static void pod_startup2(struct timer_list *t)
+{
+	struct usb_line6_pod *pod = from_timer(pod, t, startup_timer);
+	struct usb_line6 *line6 = &pod->line6;
+
+	CHECK_STARTUP_PROGRESS(pod->startup_progress, POD_STARTUP_VERSIONREQ);
+
+	/* request firmware version: */
+	line6_version_request_async(line6);
+}
+
+static void pod_startup3(struct usb_line6_pod *pod)
+{
+	CHECK_STARTUP_PROGRESS(pod->startup_progress, POD_STARTUP_WORKQUEUE);
+
+	/* schedule work for global work queue: */
+	schedule_work(&pod->startup_work);
+}
+
+static void pod_startup4(struct work_struct *work)
+{
+	struct usb_line6_pod *pod =
+	    container_of(work, struct usb_line6_pod, startup_work);
+	struct usb_line6 *line6 = &pod->line6;
+
+	CHECK_STARTUP_PROGRESS(pod->startup_progress, POD_STARTUP_SETUP);
+
+	/* serial number: */
+	line6_read_serial_number(&pod->line6, &pod->serial_number);
+
+	/* ALSA audio interface: */
+	snd_card_register(line6->card);
+}
+
+/* POD special files: */
+static DEVICE_ATTR_RO(device_id);
+static DEVICE_ATTR_RO(firmware_version);
+static DEVICE_ATTR_RO(serial_number);
+
+static struct attribute *pod_dev_attrs[] = {
+	&dev_attr_device_id.attr,
+	&dev_attr_firmware_version.attr,
+	&dev_attr_serial_number.attr,
+	NULL
+};
+
+static const struct attribute_group pod_dev_attr_group = {
+	.name = "pod",
+	.attrs = pod_dev_attrs,
+};
+
+/* control info callback */
+static int snd_pod_control_monitor_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 = 65535;
+	return 0;
+}
+
+/* control get callback */
+static int snd_pod_control_monitor_get(struct snd_kcontrol *kcontrol,
+				       struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_line6_pcm *line6pcm = snd_kcontrol_chip(kcontrol);
+	struct usb_line6_pod *pod = (struct usb_line6_pod *)line6pcm->line6;
+
+	ucontrol->value.integer.value[0] = pod->monitor_level;
+	return 0;
+}
+
+/* control put callback */
+static int snd_pod_control_monitor_put(struct snd_kcontrol *kcontrol,
+				       struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_line6_pcm *line6pcm = snd_kcontrol_chip(kcontrol);
+	struct usb_line6_pod *pod = (struct usb_line6_pod *)line6pcm->line6;
+
+	if (ucontrol->value.integer.value[0] == pod->monitor_level)
+		return 0;
+
+	pod->monitor_level = ucontrol->value.integer.value[0];
+	pod_set_system_param_int(pod, ucontrol->value.integer.value[0],
+				 POD_MONITOR_LEVEL);
+	return 1;
+}
+
+/* control definition */
+static const struct snd_kcontrol_new pod_control_monitor = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "Monitor Playback Volume",
+	.index = 0,
+	.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+	.info = snd_pod_control_monitor_info,
+	.get = snd_pod_control_monitor_get,
+	.put = snd_pod_control_monitor_put
+};
+
+/*
+	POD device disconnected.
+*/
+static void line6_pod_disconnect(struct usb_line6 *line6)
+{
+	struct usb_line6_pod *pod = (struct usb_line6_pod *)line6;
+
+	del_timer_sync(&pod->startup_timer);
+	cancel_work_sync(&pod->startup_work);
+}
+
+/*
+	 Try to init POD device.
+*/
+static int pod_init(struct usb_line6 *line6,
+		    const struct usb_device_id *id)
+{
+	int err;
+	struct usb_line6_pod *pod = (struct usb_line6_pod *) line6;
+
+	line6->process_message = line6_pod_process_message;
+	line6->disconnect = line6_pod_disconnect;
+
+	timer_setup(&pod->startup_timer, NULL, 0);
+	INIT_WORK(&pod->startup_work, pod_startup4);
+
+	/* create sysfs entries: */
+	err = snd_card_add_dev_attr(line6->card, &pod_dev_attr_group);
+	if (err < 0)
+		return err;
+
+	/* initialize MIDI subsystem: */
+	err = line6_init_midi(line6);
+	if (err < 0)
+		return err;
+
+	/* initialize PCM subsystem: */
+	err = line6_init_pcm(line6, &pod_pcm_properties);
+	if (err < 0)
+		return err;
+
+	/* register monitor control: */
+	err = snd_ctl_add(line6->card,
+			  snd_ctl_new1(&pod_control_monitor, line6->line6pcm));
+	if (err < 0)
+		return err;
+
+	/*
+	   When the sound card is registered at this point, the PODxt Live
+	   displays "Invalid Code Error 07", so we do it later in the event
+	   handler.
+	 */
+
+	if (pod->line6.properties->capabilities & LINE6_CAP_CONTROL) {
+		pod->monitor_level = POD_SYSTEM_INVALID;
+
+		/* initiate startup procedure: */
+		pod_startup1(pod);
+	}
+
+	return 0;
+}
+
+#define LINE6_DEVICE(prod) USB_DEVICE(0x0e41, prod)
+#define LINE6_IF_NUM(prod, n) USB_DEVICE_INTERFACE_NUMBER(0x0e41, prod, n)
+
+/* table of devices that work with this driver */
+static const struct usb_device_id pod_id_table[] = {
+	{ LINE6_DEVICE(0x4250),    .driver_info = LINE6_BASSPODXT },
+	{ LINE6_DEVICE(0x4642),    .driver_info = LINE6_BASSPODXTLIVE },
+	{ LINE6_DEVICE(0x4252),    .driver_info = LINE6_BASSPODXTPRO },
+	{ LINE6_IF_NUM(0x5051, 1), .driver_info = LINE6_POCKETPOD },
+	{ LINE6_DEVICE(0x5044),    .driver_info = LINE6_PODXT },
+	{ LINE6_IF_NUM(0x4650, 0), .driver_info = LINE6_PODXTLIVE_POD },
+	{ LINE6_DEVICE(0x5050),    .driver_info = LINE6_PODXTPRO },
+	{}
+};
+
+MODULE_DEVICE_TABLE(usb, pod_id_table);
+
+static const struct line6_properties pod_properties_table[] = {
+	[LINE6_BASSPODXT] = {
+		.id = "BassPODxt",
+		.name = "BassPODxt",
+		.capabilities	= LINE6_CAP_CONTROL
+				| LINE6_CAP_CONTROL_MIDI
+				| LINE6_CAP_PCM
+				| LINE6_CAP_HWMON,
+		.altsetting = 5,
+		.ep_ctrl_r = 0x84,
+		.ep_ctrl_w = 0x03,
+		.ep_audio_r = 0x82,
+		.ep_audio_w = 0x01,
+	},
+	[LINE6_BASSPODXTLIVE] = {
+		.id = "BassPODxtLive",
+		.name = "BassPODxt Live",
+		.capabilities	= LINE6_CAP_CONTROL
+				| LINE6_CAP_CONTROL_MIDI
+				| LINE6_CAP_PCM
+				| LINE6_CAP_HWMON,
+		.altsetting = 1,
+		.ep_ctrl_r = 0x84,
+		.ep_ctrl_w = 0x03,
+		.ep_audio_r = 0x82,
+		.ep_audio_w = 0x01,
+	},
+	[LINE6_BASSPODXTPRO] = {
+		.id = "BassPODxtPro",
+		.name = "BassPODxt Pro",
+		.capabilities	= LINE6_CAP_CONTROL
+				| LINE6_CAP_CONTROL_MIDI
+				| LINE6_CAP_PCM
+				| LINE6_CAP_HWMON,
+		.altsetting = 5,
+		.ep_ctrl_r = 0x84,
+		.ep_ctrl_w = 0x03,
+		.ep_audio_r = 0x82,
+		.ep_audio_w = 0x01,
+	},
+	[LINE6_POCKETPOD] = {
+		.id = "PocketPOD",
+		.name = "Pocket POD",
+		.capabilities	= LINE6_CAP_CONTROL
+				| LINE6_CAP_CONTROL_MIDI,
+		.altsetting = 0,
+		.ep_ctrl_r = 0x82,
+		.ep_ctrl_w = 0x02,
+		/* no audio channel */
+	},
+	[LINE6_PODXT] = {
+		.id = "PODxt",
+		.name = "PODxt",
+		.capabilities	= LINE6_CAP_CONTROL
+				| LINE6_CAP_CONTROL_MIDI
+				| LINE6_CAP_PCM
+				| LINE6_CAP_HWMON,
+		.altsetting = 5,
+		.ep_ctrl_r = 0x84,
+		.ep_ctrl_w = 0x03,
+		.ep_audio_r = 0x82,
+		.ep_audio_w = 0x01,
+	},
+	[LINE6_PODXTLIVE_POD] = {
+		.id = "PODxtLive",
+		.name = "PODxt Live",
+		.capabilities	= LINE6_CAP_CONTROL
+				| LINE6_CAP_CONTROL_MIDI
+				| LINE6_CAP_PCM
+				| LINE6_CAP_HWMON,
+		.altsetting = 1,
+		.ep_ctrl_r = 0x84,
+		.ep_ctrl_w = 0x03,
+		.ep_audio_r = 0x82,
+		.ep_audio_w = 0x01,
+	},
+	[LINE6_PODXTPRO] = {
+		.id = "PODxtPro",
+		.name = "PODxt Pro",
+		.capabilities	= LINE6_CAP_CONTROL
+				| LINE6_CAP_CONTROL_MIDI
+				| LINE6_CAP_PCM
+				| LINE6_CAP_HWMON,
+		.altsetting = 5,
+		.ep_ctrl_r = 0x84,
+		.ep_ctrl_w = 0x03,
+		.ep_audio_r = 0x82,
+		.ep_audio_w = 0x01,
+	},
+};
+
+/*
+	Probe USB device.
+*/
+static int pod_probe(struct usb_interface *interface,
+		     const struct usb_device_id *id)
+{
+	return line6_probe(interface, id, "Line6-POD",
+			   &pod_properties_table[id->driver_info],
+			   pod_init, sizeof(struct usb_line6_pod));
+}
+
+static struct usb_driver pod_driver = {
+	.name = KBUILD_MODNAME,
+	.probe = pod_probe,
+	.disconnect = line6_disconnect,
+#ifdef CONFIG_PM
+	.suspend = line6_suspend,
+	.resume = line6_resume,
+	.reset_resume = line6_resume,
+#endif
+	.id_table = pod_id_table,
+};
+
+module_usb_driver(pod_driver);
+
+MODULE_DESCRIPTION("Line 6 POD USB driver");
+MODULE_LICENSE("GPL");
diff --git a/sound/usb/line6/podhd.c b/sound/usb/line6/podhd.c
new file mode 100644
index 0000000..36ed9c8
--- /dev/null
+++ b/sound/usb/line6/podhd.c
@@ -0,0 +1,510 @@
+/*
+ * Line 6 Pod HD
+ *
+ * Copyright (C) 2011 Stefan Hajnoczi <stefanha@gmail.com>
+ * Copyright (C) 2015 Andrej Krutak <dev@andree.sk>
+ * Copyright (C) 2017 Hans P. Moller <hmoller@uc.cl>
+ *
+ *	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, version 2.
+ *
+ */
+
+#include <linux/usb.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+
+#include "driver.h"
+#include "pcm.h"
+
+#define PODHD_STARTUP_DELAY 500
+
+/*
+ * Stages of POD startup procedure
+ */
+enum {
+	PODHD_STARTUP_INIT = 1,
+	PODHD_STARTUP_SCHEDULE_WORKQUEUE,
+	PODHD_STARTUP_SETUP,
+	PODHD_STARTUP_LAST = PODHD_STARTUP_SETUP - 1
+};
+
+enum {
+	LINE6_PODHD300,
+	LINE6_PODHD400,
+	LINE6_PODHD500_0,
+	LINE6_PODHD500_1,
+	LINE6_PODX3,
+	LINE6_PODX3LIVE,
+	LINE6_PODHD500X,
+	LINE6_PODHDDESKTOP
+};
+
+struct usb_line6_podhd {
+	/* Generic Line 6 USB data */
+	struct usb_line6 line6;
+
+	/* Timer for device initialization */
+	struct timer_list startup_timer;
+
+	/* Work handler for device initialization */
+	struct work_struct startup_work;
+
+	/* Current progress in startup procedure */
+	int startup_progress;
+
+	/* Serial number of device */
+	u32 serial_number;
+
+	/* Firmware version */
+	int firmware_version;
+};
+
+static struct snd_ratden podhd_ratden = {
+	.num_min = 48000,
+	.num_max = 48000,
+	.num_step = 1,
+	.den = 1,
+};
+
+static struct line6_pcm_properties podhd_pcm_properties = {
+	.playback_hw = {
+				  .info = (SNDRV_PCM_INFO_MMAP |
+					   SNDRV_PCM_INFO_INTERLEAVED |
+					   SNDRV_PCM_INFO_BLOCK_TRANSFER |
+					   SNDRV_PCM_INFO_MMAP_VALID |
+					   SNDRV_PCM_INFO_PAUSE |
+					   SNDRV_PCM_INFO_SYNC_START),
+				  .formats = SNDRV_PCM_FMTBIT_S24_3LE,
+				  .rates = SNDRV_PCM_RATE_48000,
+				  .rate_min = 48000,
+				  .rate_max = 48000,
+				  .channels_min = 2,
+				  .channels_max = 2,
+				  .buffer_bytes_max = 60000,
+				  .period_bytes_min = 64,
+				  .period_bytes_max = 8192,
+				  .periods_min = 1,
+				  .periods_max = 1024},
+	.capture_hw = {
+				 .info = (SNDRV_PCM_INFO_MMAP |
+					  SNDRV_PCM_INFO_INTERLEAVED |
+					  SNDRV_PCM_INFO_BLOCK_TRANSFER |
+					  SNDRV_PCM_INFO_MMAP_VALID |
+					  SNDRV_PCM_INFO_SYNC_START),
+				 .formats = SNDRV_PCM_FMTBIT_S24_3LE,
+				 .rates = SNDRV_PCM_RATE_48000,
+				 .rate_min = 48000,
+				 .rate_max = 48000,
+				 .channels_min = 2,
+				 .channels_max = 2,
+				 .buffer_bytes_max = 60000,
+				 .period_bytes_min = 64,
+				 .period_bytes_max = 8192,
+				 .periods_min = 1,
+				 .periods_max = 1024},
+	.rates = {
+			    .nrats = 1,
+			    .rats = &podhd_ratden},
+	.bytes_per_channel = 3 /* SNDRV_PCM_FMTBIT_S24_3LE */
+};
+
+static struct line6_pcm_properties podx3_pcm_properties = {
+	.playback_hw = {
+				  .info = (SNDRV_PCM_INFO_MMAP |
+					   SNDRV_PCM_INFO_INTERLEAVED |
+					   SNDRV_PCM_INFO_BLOCK_TRANSFER |
+					   SNDRV_PCM_INFO_MMAP_VALID |
+					   SNDRV_PCM_INFO_PAUSE |
+					   SNDRV_PCM_INFO_SYNC_START),
+				  .formats = SNDRV_PCM_FMTBIT_S24_3LE,
+				  .rates = SNDRV_PCM_RATE_48000,
+				  .rate_min = 48000,
+				  .rate_max = 48000,
+				  .channels_min = 2,
+				  .channels_max = 2,
+				  .buffer_bytes_max = 60000,
+				  .period_bytes_min = 64,
+				  .period_bytes_max = 8192,
+				  .periods_min = 1,
+				  .periods_max = 1024},
+	.capture_hw = {
+				 .info = (SNDRV_PCM_INFO_MMAP |
+					  SNDRV_PCM_INFO_INTERLEAVED |
+					  SNDRV_PCM_INFO_BLOCK_TRANSFER |
+					  SNDRV_PCM_INFO_MMAP_VALID |
+					  SNDRV_PCM_INFO_SYNC_START),
+				 .formats = SNDRV_PCM_FMTBIT_S24_3LE,
+				 .rates = SNDRV_PCM_RATE_48000,
+				 .rate_min = 48000,
+				 .rate_max = 48000,
+				 /* 1+2: Main signal (out), 3+4: Tone 1,
+				  * 5+6: Tone 2, 7+8: raw
+				  */
+				 .channels_min = 8,
+				 .channels_max = 8,
+				 .buffer_bytes_max = 60000,
+				 .period_bytes_min = 64,
+				 .period_bytes_max = 8192,
+				 .periods_min = 1,
+				 .periods_max = 1024},
+	.rates = {
+			    .nrats = 1,
+			    .rats = &podhd_ratden},
+	.bytes_per_channel = 3 /* SNDRV_PCM_FMTBIT_S24_3LE */
+};
+static struct usb_driver podhd_driver;
+
+static void podhd_startup_start_workqueue(struct timer_list *t);
+static void podhd_startup_workqueue(struct work_struct *work);
+static int podhd_startup_finalize(struct usb_line6_podhd *pod);
+
+static ssize_t serial_number_show(struct device *dev,
+				  struct device_attribute *attr, char *buf)
+{
+	struct snd_card *card = dev_to_snd_card(dev);
+	struct usb_line6_podhd *pod = card->private_data;
+
+	return sprintf(buf, "%u\n", pod->serial_number);
+}
+
+static ssize_t firmware_version_show(struct device *dev,
+				     struct device_attribute *attr, char *buf)
+{
+	struct snd_card *card = dev_to_snd_card(dev);
+	struct usb_line6_podhd *pod = card->private_data;
+
+	return sprintf(buf, "%06x\n", pod->firmware_version);
+}
+
+static DEVICE_ATTR_RO(firmware_version);
+static DEVICE_ATTR_RO(serial_number);
+
+static struct attribute *podhd_dev_attrs[] = {
+	&dev_attr_firmware_version.attr,
+	&dev_attr_serial_number.attr,
+	NULL
+};
+
+static const struct attribute_group podhd_dev_attr_group = {
+	.name = "podhd",
+	.attrs = podhd_dev_attrs,
+};
+
+/*
+ * POD X3 startup procedure.
+ *
+ * May be compatible with other POD HD's, since it's also similar to the
+ * previous POD setup. In any case, it doesn't seem to be required for the
+ * audio nor bulk interfaces to work.
+ */
+
+static void podhd_startup(struct usb_line6_podhd *pod)
+{
+	CHECK_STARTUP_PROGRESS(pod->startup_progress, PODHD_STARTUP_INIT);
+
+	/* delay startup procedure: */
+	line6_start_timer(&pod->startup_timer, PODHD_STARTUP_DELAY,
+		podhd_startup_start_workqueue);
+}
+
+static void podhd_startup_start_workqueue(struct timer_list *t)
+{
+	struct usb_line6_podhd *pod = from_timer(pod, t, startup_timer);
+
+	CHECK_STARTUP_PROGRESS(pod->startup_progress,
+		PODHD_STARTUP_SCHEDULE_WORKQUEUE);
+
+	/* schedule work for global work queue: */
+	schedule_work(&pod->startup_work);
+}
+
+static int podhd_dev_start(struct usb_line6_podhd *pod)
+{
+	int ret;
+	u8 init_bytes[8];
+	int i;
+	struct usb_device *usbdev = pod->line6.usbdev;
+
+	ret = usb_control_msg(usbdev, usb_sndctrlpipe(usbdev, 0),
+					0x67, USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_OUT,
+					0x11, 0,
+					NULL, 0, LINE6_TIMEOUT * HZ);
+	if (ret < 0) {
+		dev_err(pod->line6.ifcdev, "read request failed (error %d)\n", ret);
+		return ret;
+	}
+
+	/* NOTE: looks like some kind of ping message */
+	ret = usb_control_msg(usbdev, usb_rcvctrlpipe(usbdev, 0), 0x67,
+					USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_IN,
+					0x11, 0x0,
+					&init_bytes, 3, LINE6_TIMEOUT * HZ);
+	if (ret < 0) {
+		dev_err(pod->line6.ifcdev,
+			"receive length failed (error %d)\n", ret);
+		return ret;
+	}
+
+	pod->firmware_version =
+		(init_bytes[0] << 16) | (init_bytes[1] << 8) | (init_bytes[2] << 0);
+
+	for (i = 0; i <= 16; i++) {
+		ret = line6_read_data(&pod->line6, 0xf000 + 0x08 * i, init_bytes, 8);
+		if (ret < 0)
+			return ret;
+	}
+
+	ret = usb_control_msg(usbdev, usb_sndctrlpipe(usbdev, 0),
+					USB_REQ_SET_FEATURE,
+					USB_TYPE_STANDARD | USB_RECIP_DEVICE | USB_DIR_OUT,
+					1, 0,
+					NULL, 0, LINE6_TIMEOUT * HZ);
+	if (ret < 0)
+		return ret;
+
+	return 0;
+}
+
+static void podhd_startup_workqueue(struct work_struct *work)
+{
+	struct usb_line6_podhd *pod =
+	    container_of(work, struct usb_line6_podhd, startup_work);
+
+	CHECK_STARTUP_PROGRESS(pod->startup_progress, PODHD_STARTUP_SETUP);
+
+	podhd_dev_start(pod);
+	line6_read_serial_number(&pod->line6, &pod->serial_number);
+
+	podhd_startup_finalize(pod);
+}
+
+static int podhd_startup_finalize(struct usb_line6_podhd *pod)
+{
+	struct usb_line6 *line6 = &pod->line6;
+
+	/* ALSA audio interface: */
+	return snd_card_register(line6->card);
+}
+
+static void podhd_disconnect(struct usb_line6 *line6)
+{
+	struct usb_line6_podhd *pod = (struct usb_line6_podhd *)line6;
+
+	if (pod->line6.properties->capabilities & LINE6_CAP_CONTROL_INFO) {
+		struct usb_interface *intf;
+
+		del_timer_sync(&pod->startup_timer);
+		cancel_work_sync(&pod->startup_work);
+
+		intf = usb_ifnum_to_if(line6->usbdev,
+					pod->line6.properties->ctrl_if);
+		if (intf)
+			usb_driver_release_interface(&podhd_driver, intf);
+	}
+}
+
+/*
+	Try to init POD HD device.
+*/
+static int podhd_init(struct usb_line6 *line6,
+		      const struct usb_device_id *id)
+{
+	int err;
+	struct usb_line6_podhd *pod = (struct usb_line6_podhd *) line6;
+	struct usb_interface *intf;
+
+	line6->disconnect = podhd_disconnect;
+
+	timer_setup(&pod->startup_timer, NULL, 0);
+	INIT_WORK(&pod->startup_work, podhd_startup_workqueue);
+
+	if (pod->line6.properties->capabilities & LINE6_CAP_CONTROL) {
+		/* claim the data interface */
+		intf = usb_ifnum_to_if(line6->usbdev,
+					pod->line6.properties->ctrl_if);
+		if (!intf) {
+			dev_err(pod->line6.ifcdev, "interface %d not found\n",
+				pod->line6.properties->ctrl_if);
+			return -ENODEV;
+		}
+
+		err = usb_driver_claim_interface(&podhd_driver, intf, NULL);
+		if (err != 0) {
+			dev_err(pod->line6.ifcdev, "can't claim interface %d, error %d\n",
+				pod->line6.properties->ctrl_if, err);
+			return err;
+		}
+	}
+
+	if (pod->line6.properties->capabilities & LINE6_CAP_CONTROL_INFO) {
+		/* create sysfs entries: */
+		err = snd_card_add_dev_attr(line6->card, &podhd_dev_attr_group);
+		if (err < 0)
+			return err;
+	}
+
+	if (pod->line6.properties->capabilities & LINE6_CAP_PCM) {
+		/* initialize PCM subsystem: */
+		err = line6_init_pcm(line6,
+			(id->driver_info == LINE6_PODX3 ||
+			id->driver_info == LINE6_PODX3LIVE) ? &podx3_pcm_properties :
+			&podhd_pcm_properties);
+		if (err < 0)
+			return err;
+	}
+
+	if (!(pod->line6.properties->capabilities & LINE6_CAP_CONTROL_INFO)) {
+		/* register USB audio system directly */
+		return podhd_startup_finalize(pod);
+	}
+
+	/* init device and delay registering */
+	podhd_startup(pod);
+	return 0;
+}
+
+#define LINE6_DEVICE(prod) USB_DEVICE(0x0e41, prod)
+#define LINE6_IF_NUM(prod, n) USB_DEVICE_INTERFACE_NUMBER(0x0e41, prod, n)
+
+/* table of devices that work with this driver */
+static const struct usb_device_id podhd_id_table[] = {
+	/* TODO: no need to alloc data interfaces when only audio is used */
+	{ LINE6_DEVICE(0x5057),    .driver_info = LINE6_PODHD300 },
+	{ LINE6_DEVICE(0x5058),    .driver_info = LINE6_PODHD400 },
+	{ LINE6_IF_NUM(0x414D, 0), .driver_info = LINE6_PODHD500_0 },
+	{ LINE6_IF_NUM(0x414D, 1), .driver_info = LINE6_PODHD500_1 },
+	{ LINE6_IF_NUM(0x414A, 0), .driver_info = LINE6_PODX3 },
+	{ LINE6_IF_NUM(0x414B, 0), .driver_info = LINE6_PODX3LIVE },
+	{ LINE6_IF_NUM(0x4159, 0), .driver_info = LINE6_PODHD500X },
+	{ LINE6_IF_NUM(0x4156, 0), .driver_info = LINE6_PODHDDESKTOP },
+	{}
+};
+
+MODULE_DEVICE_TABLE(usb, podhd_id_table);
+
+static const struct line6_properties podhd_properties_table[] = {
+	[LINE6_PODHD300] = {
+		.id = "PODHD300",
+		.name = "POD HD300",
+		.capabilities	= LINE6_CAP_PCM
+				| LINE6_CAP_HWMON,
+		.altsetting = 5,
+		.ep_ctrl_r = 0x84,
+		.ep_ctrl_w = 0x03,
+		.ep_audio_r = 0x82,
+		.ep_audio_w = 0x01,
+	},
+	[LINE6_PODHD400] = {
+		.id = "PODHD400",
+		.name = "POD HD400",
+		.capabilities	= LINE6_CAP_PCM
+				| LINE6_CAP_HWMON,
+		.altsetting = 5,
+		.ep_ctrl_r = 0x84,
+		.ep_ctrl_w = 0x03,
+		.ep_audio_r = 0x82,
+		.ep_audio_w = 0x01,
+	},
+	[LINE6_PODHD500_0] = {
+		.id = "PODHD500",
+		.name = "POD HD500",
+		.capabilities	= LINE6_CAP_PCM
+				| LINE6_CAP_HWMON,
+		.altsetting = 1,
+		.ep_ctrl_r = 0x81,
+		.ep_ctrl_w = 0x01,
+		.ep_audio_r = 0x86,
+		.ep_audio_w = 0x02,
+	},
+	[LINE6_PODHD500_1] = {
+		.id = "PODHD500",
+		.name = "POD HD500",
+		.capabilities	= LINE6_CAP_PCM
+				| LINE6_CAP_HWMON,
+		.altsetting = 1,
+		.ep_ctrl_r = 0x81,
+		.ep_ctrl_w = 0x01,
+		.ep_audio_r = 0x86,
+		.ep_audio_w = 0x02,
+	},
+	[LINE6_PODX3] = {
+		.id = "PODX3",
+		.name = "POD X3",
+		.capabilities	= LINE6_CAP_CONTROL | LINE6_CAP_CONTROL_INFO
+				| LINE6_CAP_PCM | LINE6_CAP_HWMON | LINE6_CAP_IN_NEEDS_OUT,
+		.altsetting = 1,
+		.ep_ctrl_r = 0x81,
+		.ep_ctrl_w = 0x01,
+		.ctrl_if = 1,
+		.ep_audio_r = 0x86,
+		.ep_audio_w = 0x02,
+	},
+	[LINE6_PODX3LIVE] = {
+		.id = "PODX3LIVE",
+		.name = "POD X3 LIVE",
+		.capabilities	= LINE6_CAP_CONTROL | LINE6_CAP_CONTROL_INFO
+				| LINE6_CAP_PCM | LINE6_CAP_HWMON | LINE6_CAP_IN_NEEDS_OUT,
+		.altsetting = 1,
+		.ep_ctrl_r = 0x81,
+		.ep_ctrl_w = 0x01,
+		.ctrl_if = 1,
+		.ep_audio_r = 0x86,
+		.ep_audio_w = 0x02,
+	},
+	[LINE6_PODHD500X] = {
+		.id = "PODHD500X",
+		.name = "POD HD500X",
+		.capabilities	= LINE6_CAP_CONTROL
+				| LINE6_CAP_PCM | LINE6_CAP_HWMON,
+		.altsetting = 1,
+		.ep_ctrl_r = 0x81,
+		.ep_ctrl_w = 0x01,
+		.ctrl_if = 1,
+		.ep_audio_r = 0x86,
+		.ep_audio_w = 0x02,
+	},
+	[LINE6_PODHDDESKTOP] = {
+		.id = "PODHDDESKTOP",
+		.name = "POD HDDESKTOP",
+		.capabilities    = LINE6_CAP_CONTROL
+			| LINE6_CAP_PCM | LINE6_CAP_HWMON,
+		.altsetting = 1,
+		.ep_ctrl_r = 0x81,
+		.ep_ctrl_w = 0x01,
+		.ctrl_if = 1,
+		.ep_audio_r = 0x86,
+		.ep_audio_w = 0x02,
+	},
+};
+
+/*
+	Probe USB device.
+*/
+static int podhd_probe(struct usb_interface *interface,
+		       const struct usb_device_id *id)
+{
+	return line6_probe(interface, id, "Line6-PODHD",
+			   &podhd_properties_table[id->driver_info],
+			   podhd_init, sizeof(struct usb_line6_podhd));
+}
+
+static struct usb_driver podhd_driver = {
+	.name = KBUILD_MODNAME,
+	.probe = podhd_probe,
+	.disconnect = line6_disconnect,
+#ifdef CONFIG_PM
+	.suspend = line6_suspend,
+	.resume = line6_resume,
+	.reset_resume = line6_resume,
+#endif
+	.id_table = podhd_id_table,
+};
+
+module_usb_driver(podhd_driver);
+
+MODULE_DESCRIPTION("Line 6 PODHD USB driver");
+MODULE_LICENSE("GPL");
diff --git a/sound/usb/line6/toneport.c b/sound/usb/line6/toneport.c
new file mode 100644
index 0000000..f47ba94
--- /dev/null
+++ b/sound/usb/line6/toneport.c
@@ -0,0 +1,580 @@
+/*
+ * Line 6 Linux USB driver
+ *
+ * Copyright (C) 2004-2010 Markus Grabner (grabner@icg.tugraz.at)
+ *                         Emil Myhrman (emil.myhrman@gmail.com)
+ *
+ *	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, version 2.
+ *
+ */
+
+#include <linux/wait.h>
+#include <linux/usb.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/leds.h>
+#include <sound/core.h>
+#include <sound/control.h>
+
+#include "capture.h"
+#include "driver.h"
+#include "playback.h"
+
+enum line6_device_type {
+	LINE6_GUITARPORT,
+	LINE6_PODSTUDIO_GX,
+	LINE6_PODSTUDIO_UX1,
+	LINE6_PODSTUDIO_UX2,
+	LINE6_TONEPORT_GX,
+	LINE6_TONEPORT_UX1,
+	LINE6_TONEPORT_UX2,
+};
+
+struct usb_line6_toneport;
+
+struct toneport_led {
+	struct led_classdev dev;
+	char name[64];
+	struct usb_line6_toneport *toneport;
+	bool registered;
+};
+
+struct usb_line6_toneport {
+	/* Generic Line 6 USB data */
+	struct usb_line6 line6;
+
+	/* Source selector */
+	int source;
+
+	/* Serial number of device */
+	u32 serial_number;
+
+	/* Firmware version (x 100) */
+	u8 firmware_version;
+
+	/* Timer for delayed PCM startup */
+	struct timer_list timer;
+
+	/* Device type */
+	enum line6_device_type type;
+
+	/* LED instances */
+	struct toneport_led leds[2];
+};
+
+static int toneport_send_cmd(struct usb_device *usbdev, int cmd1, int cmd2);
+
+#define TONEPORT_PCM_DELAY 1
+
+static struct snd_ratden toneport_ratden = {
+	.num_min = 44100,
+	.num_max = 44100,
+	.num_step = 1,
+	.den = 1
+};
+
+static struct line6_pcm_properties toneport_pcm_properties = {
+	.playback_hw = {
+				  .info = (SNDRV_PCM_INFO_MMAP |
+					   SNDRV_PCM_INFO_INTERLEAVED |
+					   SNDRV_PCM_INFO_BLOCK_TRANSFER |
+					   SNDRV_PCM_INFO_MMAP_VALID |
+					   SNDRV_PCM_INFO_PAUSE |
+					   SNDRV_PCM_INFO_SYNC_START),
+				  .formats = SNDRV_PCM_FMTBIT_S16_LE,
+				  .rates = SNDRV_PCM_RATE_KNOT,
+				  .rate_min = 44100,
+				  .rate_max = 44100,
+				  .channels_min = 2,
+				  .channels_max = 2,
+				  .buffer_bytes_max = 60000,
+				  .period_bytes_min = 64,
+				  .period_bytes_max = 8192,
+				  .periods_min = 1,
+				  .periods_max = 1024},
+	.capture_hw = {
+				 .info = (SNDRV_PCM_INFO_MMAP |
+					  SNDRV_PCM_INFO_INTERLEAVED |
+					  SNDRV_PCM_INFO_BLOCK_TRANSFER |
+					  SNDRV_PCM_INFO_MMAP_VALID |
+					  SNDRV_PCM_INFO_SYNC_START),
+				 .formats = SNDRV_PCM_FMTBIT_S16_LE,
+				 .rates = SNDRV_PCM_RATE_KNOT,
+				 .rate_min = 44100,
+				 .rate_max = 44100,
+				 .channels_min = 2,
+				 .channels_max = 2,
+				 .buffer_bytes_max = 60000,
+				 .period_bytes_min = 64,
+				 .period_bytes_max = 8192,
+				 .periods_min = 1,
+				 .periods_max = 1024},
+	.rates = {
+			    .nrats = 1,
+			    .rats = &toneport_ratden},
+	.bytes_per_channel = 2
+};
+
+static const struct {
+	const char *name;
+	int code;
+} toneport_source_info[] = {
+	{"Microphone", 0x0a01},
+	{"Line", 0x0801},
+	{"Instrument", 0x0b01},
+	{"Inst & Mic", 0x0901}
+};
+
+static int toneport_send_cmd(struct usb_device *usbdev, int cmd1, int cmd2)
+{
+	int ret;
+
+	ret = usb_control_msg(usbdev, usb_sndctrlpipe(usbdev, 0), 0x67,
+			      USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_OUT,
+			      cmd1, cmd2, NULL, 0, LINE6_TIMEOUT * HZ);
+
+	if (ret < 0) {
+		dev_err(&usbdev->dev, "send failed (error %d)\n", ret);
+		return ret;
+	}
+
+	return 0;
+}
+
+/* monitor info callback */
+static int snd_toneport_monitor_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 = 256;
+	return 0;
+}
+
+/* monitor get callback */
+static int snd_toneport_monitor_get(struct snd_kcontrol *kcontrol,
+				    struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_line6_pcm *line6pcm = snd_kcontrol_chip(kcontrol);
+
+	ucontrol->value.integer.value[0] = line6pcm->volume_monitor;
+	return 0;
+}
+
+/* monitor put callback */
+static int snd_toneport_monitor_put(struct snd_kcontrol *kcontrol,
+				    struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_line6_pcm *line6pcm = snd_kcontrol_chip(kcontrol);
+	int err;
+
+	if (ucontrol->value.integer.value[0] == line6pcm->volume_monitor)
+		return 0;
+
+	line6pcm->volume_monitor = ucontrol->value.integer.value[0];
+
+	if (line6pcm->volume_monitor > 0) {
+		err = line6_pcm_acquire(line6pcm, LINE6_STREAM_MONITOR, true);
+		if (err < 0) {
+			line6pcm->volume_monitor = 0;
+			line6_pcm_release(line6pcm, LINE6_STREAM_MONITOR);
+			return err;
+		}
+	} else {
+		line6_pcm_release(line6pcm, LINE6_STREAM_MONITOR);
+	}
+
+	return 1;
+}
+
+/* source info callback */
+static int snd_toneport_source_info(struct snd_kcontrol *kcontrol,
+				    struct snd_ctl_elem_info *uinfo)
+{
+	const int size = ARRAY_SIZE(toneport_source_info);
+
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	uinfo->count = 1;
+	uinfo->value.enumerated.items = size;
+
+	if (uinfo->value.enumerated.item >= size)
+		uinfo->value.enumerated.item = size - 1;
+
+	strcpy(uinfo->value.enumerated.name,
+	       toneport_source_info[uinfo->value.enumerated.item].name);
+
+	return 0;
+}
+
+/* source get callback */
+static int snd_toneport_source_get(struct snd_kcontrol *kcontrol,
+				   struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_line6_pcm *line6pcm = snd_kcontrol_chip(kcontrol);
+	struct usb_line6_toneport *toneport =
+	    (struct usb_line6_toneport *)line6pcm->line6;
+	ucontrol->value.enumerated.item[0] = toneport->source;
+	return 0;
+}
+
+/* source put callback */
+static int snd_toneport_source_put(struct snd_kcontrol *kcontrol,
+				   struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_line6_pcm *line6pcm = snd_kcontrol_chip(kcontrol);
+	struct usb_line6_toneport *toneport =
+	    (struct usb_line6_toneport *)line6pcm->line6;
+	unsigned int source;
+
+	source = ucontrol->value.enumerated.item[0];
+	if (source >= ARRAY_SIZE(toneport_source_info))
+		return -EINVAL;
+	if (source == toneport->source)
+		return 0;
+
+	toneport->source = source;
+	toneport_send_cmd(toneport->line6.usbdev,
+			  toneport_source_info[source].code, 0x0000);
+	return 1;
+}
+
+static void toneport_start_pcm(struct timer_list *t)
+{
+	struct usb_line6_toneport *toneport = from_timer(toneport, t, timer);
+	struct usb_line6 *line6 = &toneport->line6;
+
+	line6_pcm_acquire(line6->line6pcm, LINE6_STREAM_MONITOR, true);
+}
+
+/* control definition */
+static const struct snd_kcontrol_new toneport_control_monitor = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "Monitor Playback Volume",
+	.index = 0,
+	.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+	.info = snd_toneport_monitor_info,
+	.get = snd_toneport_monitor_get,
+	.put = snd_toneport_monitor_put
+};
+
+/* source selector definition */
+static const struct snd_kcontrol_new toneport_control_source = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "PCM Capture Source",
+	.index = 0,
+	.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+	.info = snd_toneport_source_info,
+	.get = snd_toneport_source_get,
+	.put = snd_toneport_source_put
+};
+
+/*
+	For the led on Guitarport.
+	Brightness goes from 0x00 to 0x26. Set a value above this to have led
+	blink.
+	(void cmd_0x02(byte red, byte green)
+*/
+
+static bool toneport_has_led(struct usb_line6_toneport *toneport)
+{
+	switch (toneport->type) {
+	case LINE6_GUITARPORT:
+	case LINE6_TONEPORT_GX:
+	/* add your device here if you are missing support for the LEDs */
+		return true;
+
+	default:
+		return false;
+	}
+}
+
+static const char * const led_colors[2] = { "red", "green" };
+static const int led_init_vals[2] = { 0x00, 0x26 };
+
+static void toneport_update_led(struct usb_line6_toneport *toneport)
+{
+	toneport_send_cmd(toneport->line6.usbdev,
+			  (toneport->leds[0].dev.brightness << 8) | 0x0002,
+			  toneport->leds[1].dev.brightness);
+}
+
+static void toneport_led_brightness_set(struct led_classdev *led_cdev,
+					enum led_brightness brightness)
+{
+	struct toneport_led *leds =
+		container_of(led_cdev, struct toneport_led, dev);
+	toneport_update_led(leds->toneport);
+}
+
+static int toneport_init_leds(struct usb_line6_toneport *toneport)
+{
+	struct device *dev = &toneport->line6.usbdev->dev;
+	int i, err;
+
+	for (i = 0; i < 2; i++) {
+		struct toneport_led *led = &toneport->leds[i];
+		struct led_classdev *leddev = &led->dev;
+
+		led->toneport = toneport;
+		snprintf(led->name, sizeof(led->name), "%s::%s",
+			 dev_name(dev), led_colors[i]);
+		leddev->name = led->name;
+		leddev->brightness = led_init_vals[i];
+		leddev->max_brightness = 0x26;
+		leddev->brightness_set = toneport_led_brightness_set;
+		err = led_classdev_register(dev, leddev);
+		if (err)
+			return err;
+		led->registered = true;
+	}
+
+	return 0;
+}
+
+static void toneport_remove_leds(struct usb_line6_toneport *toneport)
+{
+	struct toneport_led *led;
+	int i;
+
+	for (i = 0; i < 2; i++) {
+		led = &toneport->leds[i];
+		if (!led->registered)
+			break;
+		led_classdev_unregister(&led->dev);
+		led->registered = false;
+	}
+}
+
+static bool toneport_has_source_select(struct usb_line6_toneport *toneport)
+{
+	switch (toneport->type) {
+	case LINE6_TONEPORT_UX1:
+	case LINE6_TONEPORT_UX2:
+	case LINE6_PODSTUDIO_UX1:
+	case LINE6_PODSTUDIO_UX2:
+		return true;
+
+	default:
+		return false;
+	}
+}
+
+/*
+	Setup Toneport device.
+*/
+static void toneport_setup(struct usb_line6_toneport *toneport)
+{
+	u32 ticks;
+	struct usb_line6 *line6 = &toneport->line6;
+	struct usb_device *usbdev = line6->usbdev;
+
+	/* sync time on device with host: */
+	/* note: 32-bit timestamps overflow in year 2106 */
+	ticks = (u32)ktime_get_real_seconds();
+	line6_write_data(line6, 0x80c6, &ticks, 4);
+
+	/* enable device: */
+	toneport_send_cmd(usbdev, 0x0301, 0x0000);
+
+	/* initialize source select: */
+	if (toneport_has_source_select(toneport))
+		toneport_send_cmd(usbdev,
+				  toneport_source_info[toneport->source].code,
+				  0x0000);
+
+	if (toneport_has_led(toneport))
+		toneport_update_led(toneport);
+
+	mod_timer(&toneport->timer, jiffies + TONEPORT_PCM_DELAY * HZ);
+}
+
+/*
+	Toneport device disconnected.
+*/
+static void line6_toneport_disconnect(struct usb_line6 *line6)
+{
+	struct usb_line6_toneport *toneport =
+		(struct usb_line6_toneport *)line6;
+
+	del_timer_sync(&toneport->timer);
+
+	if (toneport_has_led(toneport))
+		toneport_remove_leds(toneport);
+}
+
+
+/*
+	 Try to init Toneport device.
+*/
+static int toneport_init(struct usb_line6 *line6,
+			 const struct usb_device_id *id)
+{
+	int err;
+	struct usb_line6_toneport *toneport =  (struct usb_line6_toneport *) line6;
+
+	toneport->type = id->driver_info;
+	timer_setup(&toneport->timer, toneport_start_pcm, 0);
+
+	line6->disconnect = line6_toneport_disconnect;
+
+	/* initialize PCM subsystem: */
+	err = line6_init_pcm(line6, &toneport_pcm_properties);
+	if (err < 0)
+		return err;
+
+	/* register monitor control: */
+	err = snd_ctl_add(line6->card,
+			  snd_ctl_new1(&toneport_control_monitor,
+				       line6->line6pcm));
+	if (err < 0)
+		return err;
+
+	/* register source select control: */
+	if (toneport_has_source_select(toneport)) {
+		err =
+		    snd_ctl_add(line6->card,
+				snd_ctl_new1(&toneport_control_source,
+					     line6->line6pcm));
+		if (err < 0)
+			return err;
+	}
+
+	line6_read_serial_number(line6, &toneport->serial_number);
+	line6_read_data(line6, 0x80c2, &toneport->firmware_version, 1);
+
+	if (toneport_has_led(toneport)) {
+		err = toneport_init_leds(toneport);
+		if (err < 0)
+			return err;
+	}
+
+	toneport_setup(toneport);
+
+	/* register audio system: */
+	return snd_card_register(line6->card);
+}
+
+#ifdef CONFIG_PM
+/*
+	Resume Toneport device after reset.
+*/
+static int toneport_reset_resume(struct usb_interface *interface)
+{
+	toneport_setup(usb_get_intfdata(interface));
+	return line6_resume(interface);
+}
+#endif
+
+#define LINE6_DEVICE(prod) USB_DEVICE(0x0e41, prod)
+#define LINE6_IF_NUM(prod, n) USB_DEVICE_INTERFACE_NUMBER(0x0e41, prod, n)
+
+/* table of devices that work with this driver */
+static const struct usb_device_id toneport_id_table[] = {
+	{ LINE6_DEVICE(0x4750),    .driver_info = LINE6_GUITARPORT },
+	{ LINE6_DEVICE(0x4153),    .driver_info = LINE6_PODSTUDIO_GX },
+	{ LINE6_DEVICE(0x4150),    .driver_info = LINE6_PODSTUDIO_UX1 },
+	{ LINE6_IF_NUM(0x4151, 0), .driver_info = LINE6_PODSTUDIO_UX2 },
+	{ LINE6_DEVICE(0x4147),    .driver_info = LINE6_TONEPORT_GX },
+	{ LINE6_DEVICE(0x4141),    .driver_info = LINE6_TONEPORT_UX1 },
+	{ LINE6_IF_NUM(0x4142, 0), .driver_info = LINE6_TONEPORT_UX2 },
+	{}
+};
+
+MODULE_DEVICE_TABLE(usb, toneport_id_table);
+
+static const struct line6_properties toneport_properties_table[] = {
+	[LINE6_GUITARPORT] = {
+		.id = "GuitarPort",
+		.name = "GuitarPort",
+		.capabilities	= LINE6_CAP_PCM,
+		.altsetting = 2,  /* 1..4 seem to be ok */
+		/* no control channel */
+		.ep_audio_r = 0x82,
+		.ep_audio_w = 0x01,
+	},
+	[LINE6_PODSTUDIO_GX] = {
+		.id = "PODStudioGX",
+		.name = "POD Studio GX",
+		.capabilities	= LINE6_CAP_PCM,
+		.altsetting = 2,  /* 1..4 seem to be ok */
+		/* no control channel */
+		.ep_audio_r = 0x82,
+		.ep_audio_w = 0x01,
+	},
+	[LINE6_PODSTUDIO_UX1] = {
+		.id = "PODStudioUX1",
+		.name = "POD Studio UX1",
+		.capabilities	= LINE6_CAP_PCM,
+		.altsetting = 2,  /* 1..4 seem to be ok */
+		/* no control channel */
+		.ep_audio_r = 0x82,
+		.ep_audio_w = 0x01,
+	},
+	[LINE6_PODSTUDIO_UX2] = {
+		.id = "PODStudioUX2",
+		.name = "POD Studio UX2",
+		.capabilities	= LINE6_CAP_PCM,
+		.altsetting = 2,  /* defaults to 44.1kHz, 16-bit */
+		/* no control channel */
+		.ep_audio_r = 0x82,
+		.ep_audio_w = 0x01,
+	},
+	[LINE6_TONEPORT_GX] = {
+		.id = "TonePortGX",
+		.name = "TonePort GX",
+		.capabilities	= LINE6_CAP_PCM,
+		.altsetting = 2,  /* 1..4 seem to be ok */
+		/* no control channel */
+		.ep_audio_r = 0x82,
+		.ep_audio_w = 0x01,
+	},
+	[LINE6_TONEPORT_UX1] = {
+		.id = "TonePortUX1",
+		.name = "TonePort UX1",
+		.capabilities	= LINE6_CAP_PCM,
+		.altsetting = 2,  /* 1..4 seem to be ok */
+		/* no control channel */
+		.ep_audio_r = 0x82,
+		.ep_audio_w = 0x01,
+	},
+	[LINE6_TONEPORT_UX2] = {
+		.id = "TonePortUX2",
+		.name = "TonePort UX2",
+		.capabilities	= LINE6_CAP_PCM,
+		.altsetting = 2,  /* defaults to 44.1kHz, 16-bit */
+		/* no control channel */
+		.ep_audio_r = 0x82,
+		.ep_audio_w = 0x01,
+	},
+};
+
+/*
+	Probe USB device.
+*/
+static int toneport_probe(struct usb_interface *interface,
+			  const struct usb_device_id *id)
+{
+	return line6_probe(interface, id, "Line6-TonePort",
+			   &toneport_properties_table[id->driver_info],
+			   toneport_init, sizeof(struct usb_line6_toneport));
+}
+
+static struct usb_driver toneport_driver = {
+	.name = KBUILD_MODNAME,
+	.probe = toneport_probe,
+	.disconnect = line6_disconnect,
+#ifdef CONFIG_PM
+	.suspend = line6_suspend,
+	.resume = line6_resume,
+	.reset_resume = toneport_reset_resume,
+#endif
+	.id_table = toneport_id_table,
+};
+
+module_usb_driver(toneport_driver);
+
+MODULE_DESCRIPTION("TonePort USB driver");
+MODULE_LICENSE("GPL");
diff --git a/sound/usb/line6/variax.c b/sound/usb/line6/variax.c
new file mode 100644
index 0000000..e8c852b
--- /dev/null
+++ b/sound/usb/line6/variax.c
@@ -0,0 +1,308 @@
+/*
+ * Line 6 Linux USB driver
+ *
+ * Copyright (C) 2004-2010 Markus Grabner (grabner@icg.tugraz.at)
+ *
+ *	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, version 2.
+ *
+ */
+
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <linux/usb.h>
+#include <linux/wait.h>
+#include <linux/module.h>
+#include <sound/core.h>
+
+#include "driver.h"
+
+#define VARIAX_STARTUP_DELAY1 1000
+#define VARIAX_STARTUP_DELAY3 100
+#define VARIAX_STARTUP_DELAY4 100
+
+/*
+	Stages of Variax startup procedure
+*/
+enum {
+	VARIAX_STARTUP_INIT = 1,
+	VARIAX_STARTUP_VERSIONREQ,
+	VARIAX_STARTUP_WAIT,
+	VARIAX_STARTUP_ACTIVATE,
+	VARIAX_STARTUP_WORKQUEUE,
+	VARIAX_STARTUP_SETUP,
+	VARIAX_STARTUP_LAST = VARIAX_STARTUP_SETUP - 1
+};
+
+enum {
+	LINE6_PODXTLIVE_VARIAX,
+	LINE6_VARIAX
+};
+
+struct usb_line6_variax {
+	/* Generic Line 6 USB data */
+	struct usb_line6 line6;
+
+	/* Buffer for activation code */
+	unsigned char *buffer_activate;
+
+	/* Handler for device initialization */
+	struct work_struct startup_work;
+
+	/* Timers for device initialization */
+	struct timer_list startup_timer1;
+	struct timer_list startup_timer2;
+
+	/* Current progress in startup procedure */
+	int startup_progress;
+};
+
+#define VARIAX_OFFSET_ACTIVATE 7
+
+/*
+	This message is sent by the device during initialization and identifies
+	the connected guitar version.
+*/
+static const char variax_init_version[] = {
+	0xf0, 0x7e, 0x7f, 0x06, 0x02, 0x00, 0x01, 0x0c,
+	0x07, 0x00, 0x00, 0x00
+};
+
+/*
+	This message is the last one sent by the device during initialization.
+*/
+static const char variax_init_done[] = {
+	0xf0, 0x00, 0x01, 0x0c, 0x07, 0x00, 0x6b
+};
+
+static const char variax_activate[] = {
+	0xf0, 0x00, 0x01, 0x0c, 0x07, 0x00, 0x2a, 0x01,
+	0xf7
+};
+
+/* forward declarations: */
+static void variax_startup2(struct timer_list *t);
+static void variax_startup4(struct timer_list *t);
+static void variax_startup5(struct timer_list *t);
+
+static void variax_activate_async(struct usb_line6_variax *variax, int a)
+{
+	variax->buffer_activate[VARIAX_OFFSET_ACTIVATE] = a;
+	line6_send_raw_message_async(&variax->line6, variax->buffer_activate,
+				     sizeof(variax_activate));
+}
+
+/*
+	Variax startup procedure.
+	This is a sequence of functions with special requirements (e.g., must
+	not run immediately after initialization, must not run in interrupt
+	context). After the last one has finished, the device is ready to use.
+*/
+
+static void variax_startup1(struct usb_line6_variax *variax)
+{
+	CHECK_STARTUP_PROGRESS(variax->startup_progress, VARIAX_STARTUP_INIT);
+
+	/* delay startup procedure: */
+	line6_start_timer(&variax->startup_timer1, VARIAX_STARTUP_DELAY1,
+			  variax_startup2);
+}
+
+static void variax_startup2(struct timer_list *t)
+{
+	struct usb_line6_variax *variax = from_timer(variax, t, startup_timer1);
+	struct usb_line6 *line6 = &variax->line6;
+
+	/* schedule another startup procedure until startup is complete: */
+	if (variax->startup_progress >= VARIAX_STARTUP_LAST)
+		return;
+
+	variax->startup_progress = VARIAX_STARTUP_VERSIONREQ;
+	line6_start_timer(&variax->startup_timer1, VARIAX_STARTUP_DELAY1,
+			  variax_startup2);
+
+	/* request firmware version: */
+	line6_version_request_async(line6);
+}
+
+static void variax_startup3(struct usb_line6_variax *variax)
+{
+	CHECK_STARTUP_PROGRESS(variax->startup_progress, VARIAX_STARTUP_WAIT);
+
+	/* delay startup procedure: */
+	line6_start_timer(&variax->startup_timer2, VARIAX_STARTUP_DELAY3,
+			  variax_startup4);
+}
+
+static void variax_startup4(struct timer_list *t)
+{
+	struct usb_line6_variax *variax = from_timer(variax, t, startup_timer2);
+
+	CHECK_STARTUP_PROGRESS(variax->startup_progress,
+			       VARIAX_STARTUP_ACTIVATE);
+
+	/* activate device: */
+	variax_activate_async(variax, 1);
+	line6_start_timer(&variax->startup_timer2, VARIAX_STARTUP_DELAY4,
+			  variax_startup5);
+}
+
+static void variax_startup5(struct timer_list *t)
+{
+	struct usb_line6_variax *variax = from_timer(variax, t, startup_timer2);
+
+	CHECK_STARTUP_PROGRESS(variax->startup_progress,
+			       VARIAX_STARTUP_WORKQUEUE);
+
+	/* schedule work for global work queue: */
+	schedule_work(&variax->startup_work);
+}
+
+static void variax_startup6(struct work_struct *work)
+{
+	struct usb_line6_variax *variax =
+	    container_of(work, struct usb_line6_variax, startup_work);
+
+	CHECK_STARTUP_PROGRESS(variax->startup_progress, VARIAX_STARTUP_SETUP);
+
+	/* ALSA audio interface: */
+	snd_card_register(variax->line6.card);
+}
+
+/*
+	Process a completely received message.
+*/
+static void line6_variax_process_message(struct usb_line6 *line6)
+{
+	struct usb_line6_variax *variax = (struct usb_line6_variax *) line6;
+	const unsigned char *buf = variax->line6.buffer_message;
+
+	switch (buf[0]) {
+	case LINE6_RESET:
+		dev_info(variax->line6.ifcdev, "VARIAX reset\n");
+		break;
+
+	case LINE6_SYSEX_BEGIN:
+		if (memcmp(buf + 1, variax_init_version + 1,
+			   sizeof(variax_init_version) - 1) == 0) {
+			variax_startup3(variax);
+		} else if (memcmp(buf + 1, variax_init_done + 1,
+				  sizeof(variax_init_done) - 1) == 0) {
+			/* notify of complete initialization: */
+			variax_startup4(&variax->startup_timer2);
+		}
+		break;
+	}
+}
+
+/*
+	Variax destructor.
+*/
+static void line6_variax_disconnect(struct usb_line6 *line6)
+{
+	struct usb_line6_variax *variax = (struct usb_line6_variax *)line6;
+
+	del_timer(&variax->startup_timer1);
+	del_timer(&variax->startup_timer2);
+	cancel_work_sync(&variax->startup_work);
+
+	kfree(variax->buffer_activate);
+}
+
+/*
+	 Try to init workbench device.
+*/
+static int variax_init(struct usb_line6 *line6,
+		       const struct usb_device_id *id)
+{
+	struct usb_line6_variax *variax = (struct usb_line6_variax *) line6;
+	int err;
+
+	line6->process_message = line6_variax_process_message;
+	line6->disconnect = line6_variax_disconnect;
+
+	timer_setup(&variax->startup_timer1, NULL, 0);
+	timer_setup(&variax->startup_timer2, NULL, 0);
+	INIT_WORK(&variax->startup_work, variax_startup6);
+
+	/* initialize USB buffers: */
+	variax->buffer_activate = kmemdup(variax_activate,
+					  sizeof(variax_activate), GFP_KERNEL);
+
+	if (variax->buffer_activate == NULL)
+		return -ENOMEM;
+
+	/* initialize MIDI subsystem: */
+	err = line6_init_midi(&variax->line6);
+	if (err < 0)
+		return err;
+
+	/* initiate startup procedure: */
+	variax_startup1(variax);
+	return 0;
+}
+
+#define LINE6_DEVICE(prod) USB_DEVICE(0x0e41, prod)
+#define LINE6_IF_NUM(prod, n) USB_DEVICE_INTERFACE_NUMBER(0x0e41, prod, n)
+
+/* table of devices that work with this driver */
+static const struct usb_device_id variax_id_table[] = {
+	{ LINE6_IF_NUM(0x4650, 1), .driver_info = LINE6_PODXTLIVE_VARIAX },
+	{ LINE6_DEVICE(0x534d),    .driver_info = LINE6_VARIAX },
+	{}
+};
+
+MODULE_DEVICE_TABLE(usb, variax_id_table);
+
+static const struct line6_properties variax_properties_table[] = {
+	[LINE6_PODXTLIVE_VARIAX] = {
+		.id = "PODxtLive",
+		.name = "PODxt Live",
+		.capabilities	= LINE6_CAP_CONTROL
+				| LINE6_CAP_CONTROL_MIDI,
+		.altsetting = 1,
+		.ep_ctrl_r = 0x86,
+		.ep_ctrl_w = 0x05,
+		.ep_audio_r = 0x82,
+		.ep_audio_w = 0x01,
+	},
+	[LINE6_VARIAX] = {
+		.id = "Variax",
+		.name = "Variax Workbench",
+		.capabilities	= LINE6_CAP_CONTROL
+				| LINE6_CAP_CONTROL_MIDI,
+		.altsetting = 1,
+		.ep_ctrl_r = 0x82,
+		.ep_ctrl_w = 0x01,
+		/* no audio channel */
+	}
+};
+
+/*
+	Probe USB device.
+*/
+static int variax_probe(struct usb_interface *interface,
+			const struct usb_device_id *id)
+{
+	return line6_probe(interface, id, "Line6-Variax",
+			   &variax_properties_table[id->driver_info],
+			   variax_init, sizeof(struct usb_line6_variax));
+}
+
+static struct usb_driver variax_driver = {
+	.name = KBUILD_MODNAME,
+	.probe = variax_probe,
+	.disconnect = line6_disconnect,
+#ifdef CONFIG_PM
+	.suspend = line6_suspend,
+	.resume = line6_resume,
+	.reset_resume = line6_resume,
+#endif
+	.id_table = variax_id_table,
+};
+
+module_usb_driver(variax_driver);
+
+MODULE_DESCRIPTION("Vairax Workbench USB driver");
+MODULE_LICENSE("GPL");
diff --git a/sound/usb/midi.c b/sound/usb/midi.c
new file mode 100644
index 0000000..dcfc546
--- /dev/null
+++ b/sound/usb/midi.c
@@ -0,0 +1,2492 @@
+/*
+ * usbmidi.c - ALSA USB MIDI driver
+ *
+ * Copyright (c) 2002-2009 Clemens Ladisch
+ * All rights reserved.
+ *
+ * Based on the OSS usb-midi driver by NAGANO Daisuke,
+ *          NetBSD's umidi driver by Takuya SHIOZAKI,
+ *          the "USB Device Class Definition for MIDI Devices" by Roland
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions, and the following disclaimer,
+ *    without modification.
+ * 2. The name of the author may not be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ *
+ * Alternatively, this software may be distributed and/or modified 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 SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <linux/kernel.h>
+#include <linux/types.h>
+#include <linux/bitops.h>
+#include <linux/interrupt.h>
+#include <linux/spinlock.h>
+#include <linux/string.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/timer.h>
+#include <linux/usb.h>
+#include <linux/wait.h>
+#include <linux/usb/audio.h>
+#include <linux/module.h>
+
+#include <sound/core.h>
+#include <sound/control.h>
+#include <sound/rawmidi.h>
+#include <sound/asequencer.h>
+#include "usbaudio.h"
+#include "midi.h"
+#include "power.h"
+#include "helper.h"
+
+/*
+ * define this to log all USB packets
+ */
+/* #define DUMP_PACKETS */
+
+/*
+ * how long to wait after some USB errors, so that hub_wq can disconnect() us
+ * without too many spurious errors
+ */
+#define ERROR_DELAY_JIFFIES (HZ / 10)
+
+#define OUTPUT_URBS 7
+#define INPUT_URBS 7
+
+
+MODULE_AUTHOR("Clemens Ladisch <clemens@ladisch.de>");
+MODULE_DESCRIPTION("USB Audio/MIDI helper module");
+MODULE_LICENSE("Dual BSD/GPL");
+
+
+struct usb_ms_header_descriptor {
+	__u8  bLength;
+	__u8  bDescriptorType;
+	__u8  bDescriptorSubtype;
+	__u8  bcdMSC[2];
+	__le16 wTotalLength;
+} __attribute__ ((packed));
+
+struct usb_ms_endpoint_descriptor {
+	__u8  bLength;
+	__u8  bDescriptorType;
+	__u8  bDescriptorSubtype;
+	__u8  bNumEmbMIDIJack;
+	__u8  baAssocJackID[0];
+} __attribute__ ((packed));
+
+struct snd_usb_midi_in_endpoint;
+struct snd_usb_midi_out_endpoint;
+struct snd_usb_midi_endpoint;
+
+struct usb_protocol_ops {
+	void (*input)(struct snd_usb_midi_in_endpoint*, uint8_t*, int);
+	void (*output)(struct snd_usb_midi_out_endpoint *ep, struct urb *urb);
+	void (*output_packet)(struct urb*, uint8_t, uint8_t, uint8_t, uint8_t);
+	void (*init_out_endpoint)(struct snd_usb_midi_out_endpoint *);
+	void (*finish_out_endpoint)(struct snd_usb_midi_out_endpoint *);
+};
+
+struct snd_usb_midi {
+	struct usb_device *dev;
+	struct snd_card *card;
+	struct usb_interface *iface;
+	const struct snd_usb_audio_quirk *quirk;
+	struct snd_rawmidi *rmidi;
+	const struct usb_protocol_ops *usb_protocol_ops;
+	struct list_head list;
+	struct timer_list error_timer;
+	spinlock_t disc_lock;
+	struct rw_semaphore disc_rwsem;
+	struct mutex mutex;
+	u32 usb_id;
+	int next_midi_device;
+
+	struct snd_usb_midi_endpoint {
+		struct snd_usb_midi_out_endpoint *out;
+		struct snd_usb_midi_in_endpoint *in;
+	} endpoints[MIDI_MAX_ENDPOINTS];
+	unsigned long input_triggered;
+	unsigned int opened[2];
+	unsigned char disconnected;
+	unsigned char input_running;
+
+	struct snd_kcontrol *roland_load_ctl;
+};
+
+struct snd_usb_midi_out_endpoint {
+	struct snd_usb_midi *umidi;
+	struct out_urb_context {
+		struct urb *urb;
+		struct snd_usb_midi_out_endpoint *ep;
+	} urbs[OUTPUT_URBS];
+	unsigned int active_urbs;
+	unsigned int drain_urbs;
+	int max_transfer;		/* size of urb buffer */
+	struct tasklet_struct tasklet;
+	unsigned int next_urb;
+	spinlock_t buffer_lock;
+
+	struct usbmidi_out_port {
+		struct snd_usb_midi_out_endpoint *ep;
+		struct snd_rawmidi_substream *substream;
+		int active;
+		uint8_t cable;		/* cable number << 4 */
+		uint8_t state;
+#define STATE_UNKNOWN	0
+#define STATE_1PARAM	1
+#define STATE_2PARAM_1	2
+#define STATE_2PARAM_2	3
+#define STATE_SYSEX_0	4
+#define STATE_SYSEX_1	5
+#define STATE_SYSEX_2	6
+		uint8_t data[2];
+	} ports[0x10];
+	int current_port;
+
+	wait_queue_head_t drain_wait;
+};
+
+struct snd_usb_midi_in_endpoint {
+	struct snd_usb_midi *umidi;
+	struct urb *urbs[INPUT_URBS];
+	struct usbmidi_in_port {
+		struct snd_rawmidi_substream *substream;
+		u8 running_status_length;
+	} ports[0x10];
+	u8 seen_f5;
+	bool in_sysex;
+	u8 last_cin;
+	u8 error_resubmit;
+	int current_port;
+};
+
+static void snd_usbmidi_do_output(struct snd_usb_midi_out_endpoint *ep);
+
+static const uint8_t snd_usbmidi_cin_length[] = {
+	0, 0, 2, 3, 3, 1, 2, 3, 3, 3, 3, 3, 2, 2, 3, 1
+};
+
+/*
+ * Submits the URB, with error handling.
+ */
+static int snd_usbmidi_submit_urb(struct urb *urb, gfp_t flags)
+{
+	int err = usb_submit_urb(urb, flags);
+	if (err < 0 && err != -ENODEV)
+		dev_err(&urb->dev->dev, "usb_submit_urb: %d\n", err);
+	return err;
+}
+
+/*
+ * Error handling for URB completion functions.
+ */
+static int snd_usbmidi_urb_error(const struct urb *urb)
+{
+	switch (urb->status) {
+	/* manually unlinked, or device gone */
+	case -ENOENT:
+	case -ECONNRESET:
+	case -ESHUTDOWN:
+	case -ENODEV:
+		return -ENODEV;
+	/* errors that might occur during unplugging */
+	case -EPROTO:
+	case -ETIME:
+	case -EILSEQ:
+		return -EIO;
+	default:
+		dev_err(&urb->dev->dev, "urb status %d\n", urb->status);
+		return 0; /* continue */
+	}
+}
+
+/*
+ * Receives a chunk of MIDI data.
+ */
+static void snd_usbmidi_input_data(struct snd_usb_midi_in_endpoint *ep,
+				   int portidx, uint8_t *data, int length)
+{
+	struct usbmidi_in_port *port = &ep->ports[portidx];
+
+	if (!port->substream) {
+		dev_dbg(&ep->umidi->dev->dev, "unexpected port %d!\n", portidx);
+		return;
+	}
+	if (!test_bit(port->substream->number, &ep->umidi->input_triggered))
+		return;
+	snd_rawmidi_receive(port->substream, data, length);
+}
+
+#ifdef DUMP_PACKETS
+static void dump_urb(const char *type, const u8 *data, int length)
+{
+	snd_printk(KERN_DEBUG "%s packet: [", type);
+	for (; length > 0; ++data, --length)
+		printk(KERN_CONT " %02x", *data);
+	printk(KERN_CONT " ]\n");
+}
+#else
+#define dump_urb(type, data, length) /* nothing */
+#endif
+
+/*
+ * Processes the data read from the device.
+ */
+static void snd_usbmidi_in_urb_complete(struct urb *urb)
+{
+	struct snd_usb_midi_in_endpoint *ep = urb->context;
+
+	if (urb->status == 0) {
+		dump_urb("received", urb->transfer_buffer, urb->actual_length);
+		ep->umidi->usb_protocol_ops->input(ep, urb->transfer_buffer,
+						   urb->actual_length);
+	} else {
+		int err = snd_usbmidi_urb_error(urb);
+		if (err < 0) {
+			if (err != -ENODEV) {
+				ep->error_resubmit = 1;
+				mod_timer(&ep->umidi->error_timer,
+					  jiffies + ERROR_DELAY_JIFFIES);
+			}
+			return;
+		}
+	}
+
+	urb->dev = ep->umidi->dev;
+	snd_usbmidi_submit_urb(urb, GFP_ATOMIC);
+}
+
+static void snd_usbmidi_out_urb_complete(struct urb *urb)
+{
+	struct out_urb_context *context = urb->context;
+	struct snd_usb_midi_out_endpoint *ep = context->ep;
+	unsigned int urb_index;
+	unsigned long flags;
+
+	spin_lock_irqsave(&ep->buffer_lock, flags);
+	urb_index = context - ep->urbs;
+	ep->active_urbs &= ~(1 << urb_index);
+	if (unlikely(ep->drain_urbs)) {
+		ep->drain_urbs &= ~(1 << urb_index);
+		wake_up(&ep->drain_wait);
+	}
+	spin_unlock_irqrestore(&ep->buffer_lock, flags);
+	if (urb->status < 0) {
+		int err = snd_usbmidi_urb_error(urb);
+		if (err < 0) {
+			if (err != -ENODEV)
+				mod_timer(&ep->umidi->error_timer,
+					  jiffies + ERROR_DELAY_JIFFIES);
+			return;
+		}
+	}
+	snd_usbmidi_do_output(ep);
+}
+
+/*
+ * This is called when some data should be transferred to the device
+ * (from one or more substreams).
+ */
+static void snd_usbmidi_do_output(struct snd_usb_midi_out_endpoint *ep)
+{
+	unsigned int urb_index;
+	struct urb *urb;
+	unsigned long flags;
+
+	spin_lock_irqsave(&ep->buffer_lock, flags);
+	if (ep->umidi->disconnected) {
+		spin_unlock_irqrestore(&ep->buffer_lock, flags);
+		return;
+	}
+
+	urb_index = ep->next_urb;
+	for (;;) {
+		if (!(ep->active_urbs & (1 << urb_index))) {
+			urb = ep->urbs[urb_index].urb;
+			urb->transfer_buffer_length = 0;
+			ep->umidi->usb_protocol_ops->output(ep, urb);
+			if (urb->transfer_buffer_length == 0)
+				break;
+
+			dump_urb("sending", urb->transfer_buffer,
+				 urb->transfer_buffer_length);
+			urb->dev = ep->umidi->dev;
+			if (snd_usbmidi_submit_urb(urb, GFP_ATOMIC) < 0)
+				break;
+			ep->active_urbs |= 1 << urb_index;
+		}
+		if (++urb_index >= OUTPUT_URBS)
+			urb_index = 0;
+		if (urb_index == ep->next_urb)
+			break;
+	}
+	ep->next_urb = urb_index;
+	spin_unlock_irqrestore(&ep->buffer_lock, flags);
+}
+
+static void snd_usbmidi_out_tasklet(unsigned long data)
+{
+	struct snd_usb_midi_out_endpoint *ep =
+		(struct snd_usb_midi_out_endpoint *) data;
+
+	snd_usbmidi_do_output(ep);
+}
+
+/* called after transfers had been interrupted due to some USB error */
+static void snd_usbmidi_error_timer(struct timer_list *t)
+{
+	struct snd_usb_midi *umidi = from_timer(umidi, t, error_timer);
+	unsigned int i, j;
+
+	spin_lock(&umidi->disc_lock);
+	if (umidi->disconnected) {
+		spin_unlock(&umidi->disc_lock);
+		return;
+	}
+	for (i = 0; i < MIDI_MAX_ENDPOINTS; ++i) {
+		struct snd_usb_midi_in_endpoint *in = umidi->endpoints[i].in;
+		if (in && in->error_resubmit) {
+			in->error_resubmit = 0;
+			for (j = 0; j < INPUT_URBS; ++j) {
+				if (atomic_read(&in->urbs[j]->use_count))
+					continue;
+				in->urbs[j]->dev = umidi->dev;
+				snd_usbmidi_submit_urb(in->urbs[j], GFP_ATOMIC);
+			}
+		}
+		if (umidi->endpoints[i].out)
+			snd_usbmidi_do_output(umidi->endpoints[i].out);
+	}
+	spin_unlock(&umidi->disc_lock);
+}
+
+/* helper function to send static data that may not DMA-able */
+static int send_bulk_static_data(struct snd_usb_midi_out_endpoint *ep,
+				 const void *data, int len)
+{
+	int err = 0;
+	void *buf = kmemdup(data, len, GFP_KERNEL);
+	if (!buf)
+		return -ENOMEM;
+	dump_urb("sending", buf, len);
+	if (ep->urbs[0].urb)
+		err = usb_bulk_msg(ep->umidi->dev, ep->urbs[0].urb->pipe,
+				   buf, len, NULL, 250);
+	kfree(buf);
+	return err;
+}
+
+/*
+ * Standard USB MIDI protocol: see the spec.
+ * Midiman protocol: like the standard protocol, but the control byte is the
+ * fourth byte in each packet, and uses length instead of CIN.
+ */
+
+static void snd_usbmidi_standard_input(struct snd_usb_midi_in_endpoint *ep,
+				       uint8_t *buffer, int buffer_length)
+{
+	int i;
+
+	for (i = 0; i + 3 < buffer_length; i += 4)
+		if (buffer[i] != 0) {
+			int cable = buffer[i] >> 4;
+			int length = snd_usbmidi_cin_length[buffer[i] & 0x0f];
+			snd_usbmidi_input_data(ep, cable, &buffer[i + 1],
+					       length);
+		}
+}
+
+static void snd_usbmidi_midiman_input(struct snd_usb_midi_in_endpoint *ep,
+				      uint8_t *buffer, int buffer_length)
+{
+	int i;
+
+	for (i = 0; i + 3 < buffer_length; i += 4)
+		if (buffer[i + 3] != 0) {
+			int port = buffer[i + 3] >> 4;
+			int length = buffer[i + 3] & 3;
+			snd_usbmidi_input_data(ep, port, &buffer[i], length);
+		}
+}
+
+/*
+ * Buggy M-Audio device: running status on input results in a packet that has
+ * the data bytes but not the status byte and that is marked with CIN 4.
+ */
+static void snd_usbmidi_maudio_broken_running_status_input(
+					struct snd_usb_midi_in_endpoint *ep,
+					uint8_t *buffer, int buffer_length)
+{
+	int i;
+
+	for (i = 0; i + 3 < buffer_length; i += 4)
+		if (buffer[i] != 0) {
+			int cable = buffer[i] >> 4;
+			u8 cin = buffer[i] & 0x0f;
+			struct usbmidi_in_port *port = &ep->ports[cable];
+			int length;
+
+			length = snd_usbmidi_cin_length[cin];
+			if (cin == 0xf && buffer[i + 1] >= 0xf8)
+				; /* realtime msg: no running status change */
+			else if (cin >= 0x8 && cin <= 0xe)
+				/* channel msg */
+				port->running_status_length = length - 1;
+			else if (cin == 0x4 &&
+				 port->running_status_length != 0 &&
+				 buffer[i + 1] < 0x80)
+				/* CIN 4 that is not a SysEx */
+				length = port->running_status_length;
+			else
+				/*
+				 * All other msgs cannot begin running status.
+				 * (A channel msg sent as two or three CIN 0xF
+				 * packets could in theory, but this device
+				 * doesn't use this format.)
+				 */
+				port->running_status_length = 0;
+			snd_usbmidi_input_data(ep, cable, &buffer[i + 1],
+					       length);
+		}
+}
+
+/*
+ * QinHeng CH345 is buggy: every second packet inside a SysEx has not CIN 4
+ * but the previously seen CIN, but still with three data bytes.
+ */
+static void ch345_broken_sysex_input(struct snd_usb_midi_in_endpoint *ep,
+				     uint8_t *buffer, int buffer_length)
+{
+	unsigned int i, cin, length;
+
+	for (i = 0; i + 3 < buffer_length; i += 4) {
+		if (buffer[i] == 0 && i > 0)
+			break;
+		cin = buffer[i] & 0x0f;
+		if (ep->in_sysex &&
+		    cin == ep->last_cin &&
+		    (buffer[i + 1 + (cin == 0x6)] & 0x80) == 0)
+			cin = 0x4;
+#if 0
+		if (buffer[i + 1] == 0x90) {
+			/*
+			 * Either a corrupted running status or a real note-on
+			 * message; impossible to detect reliably.
+			 */
+		}
+#endif
+		length = snd_usbmidi_cin_length[cin];
+		snd_usbmidi_input_data(ep, 0, &buffer[i + 1], length);
+		ep->in_sysex = cin == 0x4;
+		if (!ep->in_sysex)
+			ep->last_cin = cin;
+	}
+}
+
+/*
+ * CME protocol: like the standard protocol, but SysEx commands are sent as a
+ * single USB packet preceded by a 0x0F byte.
+ */
+static void snd_usbmidi_cme_input(struct snd_usb_midi_in_endpoint *ep,
+				  uint8_t *buffer, int buffer_length)
+{
+	if (buffer_length < 2 || (buffer[0] & 0x0f) != 0x0f)
+		snd_usbmidi_standard_input(ep, buffer, buffer_length);
+	else
+		snd_usbmidi_input_data(ep, buffer[0] >> 4,
+				       &buffer[1], buffer_length - 1);
+}
+
+/*
+ * Adds one USB MIDI packet to the output buffer.
+ */
+static void snd_usbmidi_output_standard_packet(struct urb *urb, uint8_t p0,
+					       uint8_t p1, uint8_t p2,
+					       uint8_t p3)
+{
+
+	uint8_t *buf =
+		(uint8_t *)urb->transfer_buffer + urb->transfer_buffer_length;
+	buf[0] = p0;
+	buf[1] = p1;
+	buf[2] = p2;
+	buf[3] = p3;
+	urb->transfer_buffer_length += 4;
+}
+
+/*
+ * Adds one Midiman packet to the output buffer.
+ */
+static void snd_usbmidi_output_midiman_packet(struct urb *urb, uint8_t p0,
+					      uint8_t p1, uint8_t p2,
+					      uint8_t p3)
+{
+
+	uint8_t *buf =
+		(uint8_t *)urb->transfer_buffer + urb->transfer_buffer_length;
+	buf[0] = p1;
+	buf[1] = p2;
+	buf[2] = p3;
+	buf[3] = (p0 & 0xf0) | snd_usbmidi_cin_length[p0 & 0x0f];
+	urb->transfer_buffer_length += 4;
+}
+
+/*
+ * Converts MIDI commands to USB MIDI packets.
+ */
+static void snd_usbmidi_transmit_byte(struct usbmidi_out_port *port,
+				      uint8_t b, struct urb *urb)
+{
+	uint8_t p0 = port->cable;
+	void (*output_packet)(struct urb*, uint8_t, uint8_t, uint8_t, uint8_t) =
+		port->ep->umidi->usb_protocol_ops->output_packet;
+
+	if (b >= 0xf8) {
+		output_packet(urb, p0 | 0x0f, b, 0, 0);
+	} else if (b >= 0xf0) {
+		switch (b) {
+		case 0xf0:
+			port->data[0] = b;
+			port->state = STATE_SYSEX_1;
+			break;
+		case 0xf1:
+		case 0xf3:
+			port->data[0] = b;
+			port->state = STATE_1PARAM;
+			break;
+		case 0xf2:
+			port->data[0] = b;
+			port->state = STATE_2PARAM_1;
+			break;
+		case 0xf4:
+		case 0xf5:
+			port->state = STATE_UNKNOWN;
+			break;
+		case 0xf6:
+			output_packet(urb, p0 | 0x05, 0xf6, 0, 0);
+			port->state = STATE_UNKNOWN;
+			break;
+		case 0xf7:
+			switch (port->state) {
+			case STATE_SYSEX_0:
+				output_packet(urb, p0 | 0x05, 0xf7, 0, 0);
+				break;
+			case STATE_SYSEX_1:
+				output_packet(urb, p0 | 0x06, port->data[0],
+					      0xf7, 0);
+				break;
+			case STATE_SYSEX_2:
+				output_packet(urb, p0 | 0x07, port->data[0],
+					      port->data[1], 0xf7);
+				break;
+			}
+			port->state = STATE_UNKNOWN;
+			break;
+		}
+	} else if (b >= 0x80) {
+		port->data[0] = b;
+		if (b >= 0xc0 && b <= 0xdf)
+			port->state = STATE_1PARAM;
+		else
+			port->state = STATE_2PARAM_1;
+	} else { /* b < 0x80 */
+		switch (port->state) {
+		case STATE_1PARAM:
+			if (port->data[0] < 0xf0) {
+				p0 |= port->data[0] >> 4;
+			} else {
+				p0 |= 0x02;
+				port->state = STATE_UNKNOWN;
+			}
+			output_packet(urb, p0, port->data[0], b, 0);
+			break;
+		case STATE_2PARAM_1:
+			port->data[1] = b;
+			port->state = STATE_2PARAM_2;
+			break;
+		case STATE_2PARAM_2:
+			if (port->data[0] < 0xf0) {
+				p0 |= port->data[0] >> 4;
+				port->state = STATE_2PARAM_1;
+			} else {
+				p0 |= 0x03;
+				port->state = STATE_UNKNOWN;
+			}
+			output_packet(urb, p0, port->data[0], port->data[1], b);
+			break;
+		case STATE_SYSEX_0:
+			port->data[0] = b;
+			port->state = STATE_SYSEX_1;
+			break;
+		case STATE_SYSEX_1:
+			port->data[1] = b;
+			port->state = STATE_SYSEX_2;
+			break;
+		case STATE_SYSEX_2:
+			output_packet(urb, p0 | 0x04, port->data[0],
+				      port->data[1], b);
+			port->state = STATE_SYSEX_0;
+			break;
+		}
+	}
+}
+
+static void snd_usbmidi_standard_output(struct snd_usb_midi_out_endpoint *ep,
+					struct urb *urb)
+{
+	int p;
+
+	/* FIXME: lower-numbered ports can starve higher-numbered ports */
+	for (p = 0; p < 0x10; ++p) {
+		struct usbmidi_out_port *port = &ep->ports[p];
+		if (!port->active)
+			continue;
+		while (urb->transfer_buffer_length + 3 < ep->max_transfer) {
+			uint8_t b;
+			if (snd_rawmidi_transmit(port->substream, &b, 1) != 1) {
+				port->active = 0;
+				break;
+			}
+			snd_usbmidi_transmit_byte(port, b, urb);
+		}
+	}
+}
+
+static const struct usb_protocol_ops snd_usbmidi_standard_ops = {
+	.input = snd_usbmidi_standard_input,
+	.output = snd_usbmidi_standard_output,
+	.output_packet = snd_usbmidi_output_standard_packet,
+};
+
+static const struct usb_protocol_ops snd_usbmidi_midiman_ops = {
+	.input = snd_usbmidi_midiman_input,
+	.output = snd_usbmidi_standard_output,
+	.output_packet = snd_usbmidi_output_midiman_packet,
+};
+
+static const
+struct usb_protocol_ops snd_usbmidi_maudio_broken_running_status_ops = {
+	.input = snd_usbmidi_maudio_broken_running_status_input,
+	.output = snd_usbmidi_standard_output,
+	.output_packet = snd_usbmidi_output_standard_packet,
+};
+
+static const struct usb_protocol_ops snd_usbmidi_cme_ops = {
+	.input = snd_usbmidi_cme_input,
+	.output = snd_usbmidi_standard_output,
+	.output_packet = snd_usbmidi_output_standard_packet,
+};
+
+static const struct usb_protocol_ops snd_usbmidi_ch345_broken_sysex_ops = {
+	.input = ch345_broken_sysex_input,
+	.output = snd_usbmidi_standard_output,
+	.output_packet = snd_usbmidi_output_standard_packet,
+};
+
+/*
+ * AKAI MPD16 protocol:
+ *
+ * For control port (endpoint 1):
+ * ==============================
+ * One or more chunks consisting of first byte of (0x10 | msg_len) and then a
+ * SysEx message (msg_len=9 bytes long).
+ *
+ * For data port (endpoint 2):
+ * ===========================
+ * One or more chunks consisting of first byte of (0x20 | msg_len) and then a
+ * MIDI message (msg_len bytes long)
+ *
+ * Messages sent: Active Sense, Note On, Poly Pressure, Control Change.
+ */
+static void snd_usbmidi_akai_input(struct snd_usb_midi_in_endpoint *ep,
+				   uint8_t *buffer, int buffer_length)
+{
+	unsigned int pos = 0;
+	unsigned int len = (unsigned int)buffer_length;
+	while (pos < len) {
+		unsigned int port = (buffer[pos] >> 4) - 1;
+		unsigned int msg_len = buffer[pos] & 0x0f;
+		pos++;
+		if (pos + msg_len <= len && port < 2)
+			snd_usbmidi_input_data(ep, 0, &buffer[pos], msg_len);
+		pos += msg_len;
+	}
+}
+
+#define MAX_AKAI_SYSEX_LEN 9
+
+static void snd_usbmidi_akai_output(struct snd_usb_midi_out_endpoint *ep,
+				    struct urb *urb)
+{
+	uint8_t *msg;
+	int pos, end, count, buf_end;
+	uint8_t tmp[MAX_AKAI_SYSEX_LEN];
+	struct snd_rawmidi_substream *substream = ep->ports[0].substream;
+
+	if (!ep->ports[0].active)
+		return;
+
+	msg = urb->transfer_buffer + urb->transfer_buffer_length;
+	buf_end = ep->max_transfer - MAX_AKAI_SYSEX_LEN - 1;
+
+	/* only try adding more data when there's space for at least 1 SysEx */
+	while (urb->transfer_buffer_length < buf_end) {
+		count = snd_rawmidi_transmit_peek(substream,
+						  tmp, MAX_AKAI_SYSEX_LEN);
+		if (!count) {
+			ep->ports[0].active = 0;
+			return;
+		}
+		/* try to skip non-SysEx data */
+		for (pos = 0; pos < count && tmp[pos] != 0xF0; pos++)
+			;
+
+		if (pos > 0) {
+			snd_rawmidi_transmit_ack(substream, pos);
+			continue;
+		}
+
+		/* look for the start or end marker */
+		for (end = 1; end < count && tmp[end] < 0xF0; end++)
+			;
+
+		/* next SysEx started before the end of current one */
+		if (end < count && tmp[end] == 0xF0) {
+			/* it's incomplete - drop it */
+			snd_rawmidi_transmit_ack(substream, end);
+			continue;
+		}
+		/* SysEx complete */
+		if (end < count && tmp[end] == 0xF7) {
+			/* queue it, ack it, and get the next one */
+			count = end + 1;
+			msg[0] = 0x10 | count;
+			memcpy(&msg[1], tmp, count);
+			snd_rawmidi_transmit_ack(substream, count);
+			urb->transfer_buffer_length += count + 1;
+			msg += count + 1;
+			continue;
+		}
+		/* less than 9 bytes and no end byte - wait for more */
+		if (count < MAX_AKAI_SYSEX_LEN) {
+			ep->ports[0].active = 0;
+			return;
+		}
+		/* 9 bytes and no end marker in sight - malformed, skip it */
+		snd_rawmidi_transmit_ack(substream, count);
+	}
+}
+
+static const struct usb_protocol_ops snd_usbmidi_akai_ops = {
+	.input = snd_usbmidi_akai_input,
+	.output = snd_usbmidi_akai_output,
+};
+
+/*
+ * Novation USB MIDI protocol: number of data bytes is in the first byte
+ * (when receiving) (+1!) or in the second byte (when sending); data begins
+ * at the third byte.
+ */
+
+static void snd_usbmidi_novation_input(struct snd_usb_midi_in_endpoint *ep,
+				       uint8_t *buffer, int buffer_length)
+{
+	if (buffer_length < 2 || !buffer[0] || buffer_length < buffer[0] + 1)
+		return;
+	snd_usbmidi_input_data(ep, 0, &buffer[2], buffer[0] - 1);
+}
+
+static void snd_usbmidi_novation_output(struct snd_usb_midi_out_endpoint *ep,
+					struct urb *urb)
+{
+	uint8_t *transfer_buffer;
+	int count;
+
+	if (!ep->ports[0].active)
+		return;
+	transfer_buffer = urb->transfer_buffer;
+	count = snd_rawmidi_transmit(ep->ports[0].substream,
+				     &transfer_buffer[2],
+				     ep->max_transfer - 2);
+	if (count < 1) {
+		ep->ports[0].active = 0;
+		return;
+	}
+	transfer_buffer[0] = 0;
+	transfer_buffer[1] = count;
+	urb->transfer_buffer_length = 2 + count;
+}
+
+static const struct usb_protocol_ops snd_usbmidi_novation_ops = {
+	.input = snd_usbmidi_novation_input,
+	.output = snd_usbmidi_novation_output,
+};
+
+/*
+ * "raw" protocol: just move raw MIDI bytes from/to the endpoint
+ */
+
+static void snd_usbmidi_raw_input(struct snd_usb_midi_in_endpoint *ep,
+				  uint8_t *buffer, int buffer_length)
+{
+	snd_usbmidi_input_data(ep, 0, buffer, buffer_length);
+}
+
+static void snd_usbmidi_raw_output(struct snd_usb_midi_out_endpoint *ep,
+				   struct urb *urb)
+{
+	int count;
+
+	if (!ep->ports[0].active)
+		return;
+	count = snd_rawmidi_transmit(ep->ports[0].substream,
+				     urb->transfer_buffer,
+				     ep->max_transfer);
+	if (count < 1) {
+		ep->ports[0].active = 0;
+		return;
+	}
+	urb->transfer_buffer_length = count;
+}
+
+static const struct usb_protocol_ops snd_usbmidi_raw_ops = {
+	.input = snd_usbmidi_raw_input,
+	.output = snd_usbmidi_raw_output,
+};
+
+/*
+ * FTDI protocol: raw MIDI bytes, but input packets have two modem status bytes.
+ */
+
+static void snd_usbmidi_ftdi_input(struct snd_usb_midi_in_endpoint *ep,
+				   uint8_t *buffer, int buffer_length)
+{
+	if (buffer_length > 2)
+		snd_usbmidi_input_data(ep, 0, buffer + 2, buffer_length - 2);
+}
+
+static const struct usb_protocol_ops snd_usbmidi_ftdi_ops = {
+	.input = snd_usbmidi_ftdi_input,
+	.output = snd_usbmidi_raw_output,
+};
+
+static void snd_usbmidi_us122l_input(struct snd_usb_midi_in_endpoint *ep,
+				     uint8_t *buffer, int buffer_length)
+{
+	if (buffer_length != 9)
+		return;
+	buffer_length = 8;
+	while (buffer_length && buffer[buffer_length - 1] == 0xFD)
+		buffer_length--;
+	if (buffer_length)
+		snd_usbmidi_input_data(ep, 0, buffer, buffer_length);
+}
+
+static void snd_usbmidi_us122l_output(struct snd_usb_midi_out_endpoint *ep,
+				      struct urb *urb)
+{
+	int count;
+
+	if (!ep->ports[0].active)
+		return;
+	switch (snd_usb_get_speed(ep->umidi->dev)) {
+	case USB_SPEED_HIGH:
+	case USB_SPEED_SUPER:
+	case USB_SPEED_SUPER_PLUS:
+		count = 1;
+		break;
+	default:
+		count = 2;
+	}
+	count = snd_rawmidi_transmit(ep->ports[0].substream,
+				     urb->transfer_buffer,
+				     count);
+	if (count < 1) {
+		ep->ports[0].active = 0;
+		return;
+	}
+
+	memset(urb->transfer_buffer + count, 0xFD, ep->max_transfer - count);
+	urb->transfer_buffer_length = ep->max_transfer;
+}
+
+static const struct usb_protocol_ops snd_usbmidi_122l_ops = {
+	.input = snd_usbmidi_us122l_input,
+	.output = snd_usbmidi_us122l_output,
+};
+
+/*
+ * Emagic USB MIDI protocol: raw MIDI with "F5 xx" port switching.
+ */
+
+static void snd_usbmidi_emagic_init_out(struct snd_usb_midi_out_endpoint *ep)
+{
+	static const u8 init_data[] = {
+		/* initialization magic: "get version" */
+		0xf0,
+		0x00, 0x20, 0x31,	/* Emagic */
+		0x64,			/* Unitor8 */
+		0x0b,			/* version number request */
+		0x00,			/* command version */
+		0x00,			/* EEPROM, box 0 */
+		0xf7
+	};
+	send_bulk_static_data(ep, init_data, sizeof(init_data));
+	/* while we're at it, pour on more magic */
+	send_bulk_static_data(ep, init_data, sizeof(init_data));
+}
+
+static void snd_usbmidi_emagic_finish_out(struct snd_usb_midi_out_endpoint *ep)
+{
+	static const u8 finish_data[] = {
+		/* switch to patch mode with last preset */
+		0xf0,
+		0x00, 0x20, 0x31,	/* Emagic */
+		0x64,			/* Unitor8 */
+		0x10,			/* patch switch command */
+		0x00,			/* command version */
+		0x7f,			/* to all boxes */
+		0x40,			/* last preset in EEPROM */
+		0xf7
+	};
+	send_bulk_static_data(ep, finish_data, sizeof(finish_data));
+}
+
+static void snd_usbmidi_emagic_input(struct snd_usb_midi_in_endpoint *ep,
+				     uint8_t *buffer, int buffer_length)
+{
+	int i;
+
+	/* FF indicates end of valid data */
+	for (i = 0; i < buffer_length; ++i)
+		if (buffer[i] == 0xff) {
+			buffer_length = i;
+			break;
+		}
+
+	/* handle F5 at end of last buffer */
+	if (ep->seen_f5)
+		goto switch_port;
+
+	while (buffer_length > 0) {
+		/* determine size of data until next F5 */
+		for (i = 0; i < buffer_length; ++i)
+			if (buffer[i] == 0xf5)
+				break;
+		snd_usbmidi_input_data(ep, ep->current_port, buffer, i);
+		buffer += i;
+		buffer_length -= i;
+
+		if (buffer_length <= 0)
+			break;
+		/* assert(buffer[0] == 0xf5); */
+		ep->seen_f5 = 1;
+		++buffer;
+		--buffer_length;
+
+	switch_port:
+		if (buffer_length <= 0)
+			break;
+		if (buffer[0] < 0x80) {
+			ep->current_port = (buffer[0] - 1) & 15;
+			++buffer;
+			--buffer_length;
+		}
+		ep->seen_f5 = 0;
+	}
+}
+
+static void snd_usbmidi_emagic_output(struct snd_usb_midi_out_endpoint *ep,
+				      struct urb *urb)
+{
+	int port0 = ep->current_port;
+	uint8_t *buf = urb->transfer_buffer;
+	int buf_free = ep->max_transfer;
+	int length, i;
+
+	for (i = 0; i < 0x10; ++i) {
+		/* round-robin, starting at the last current port */
+		int portnum = (port0 + i) & 15;
+		struct usbmidi_out_port *port = &ep->ports[portnum];
+
+		if (!port->active)
+			continue;
+		if (snd_rawmidi_transmit_peek(port->substream, buf, 1) != 1) {
+			port->active = 0;
+			continue;
+		}
+
+		if (portnum != ep->current_port) {
+			if (buf_free < 2)
+				break;
+			ep->current_port = portnum;
+			buf[0] = 0xf5;
+			buf[1] = (portnum + 1) & 15;
+			buf += 2;
+			buf_free -= 2;
+		}
+
+		if (buf_free < 1)
+			break;
+		length = snd_rawmidi_transmit(port->substream, buf, buf_free);
+		if (length > 0) {
+			buf += length;
+			buf_free -= length;
+			if (buf_free < 1)
+				break;
+		}
+	}
+	if (buf_free < ep->max_transfer && buf_free > 0) {
+		*buf = 0xff;
+		--buf_free;
+	}
+	urb->transfer_buffer_length = ep->max_transfer - buf_free;
+}
+
+static const struct usb_protocol_ops snd_usbmidi_emagic_ops = {
+	.input = snd_usbmidi_emagic_input,
+	.output = snd_usbmidi_emagic_output,
+	.init_out_endpoint = snd_usbmidi_emagic_init_out,
+	.finish_out_endpoint = snd_usbmidi_emagic_finish_out,
+};
+
+
+static void update_roland_altsetting(struct snd_usb_midi *umidi)
+{
+	struct usb_interface *intf;
+	struct usb_host_interface *hostif;
+	struct usb_interface_descriptor *intfd;
+	int is_light_load;
+
+	intf = umidi->iface;
+	is_light_load = intf->cur_altsetting != intf->altsetting;
+	if (umidi->roland_load_ctl->private_value == is_light_load)
+		return;
+	hostif = &intf->altsetting[umidi->roland_load_ctl->private_value];
+	intfd = get_iface_desc(hostif);
+	snd_usbmidi_input_stop(&umidi->list);
+	usb_set_interface(umidi->dev, intfd->bInterfaceNumber,
+			  intfd->bAlternateSetting);
+	snd_usbmidi_input_start(&umidi->list);
+}
+
+static int substream_open(struct snd_rawmidi_substream *substream, int dir,
+			  int open)
+{
+	struct snd_usb_midi *umidi = substream->rmidi->private_data;
+	struct snd_kcontrol *ctl;
+
+	down_read(&umidi->disc_rwsem);
+	if (umidi->disconnected) {
+		up_read(&umidi->disc_rwsem);
+		return open ? -ENODEV : 0;
+	}
+
+	mutex_lock(&umidi->mutex);
+	if (open) {
+		if (!umidi->opened[0] && !umidi->opened[1]) {
+			if (umidi->roland_load_ctl) {
+				ctl = umidi->roland_load_ctl;
+				ctl->vd[0].access |=
+					SNDRV_CTL_ELEM_ACCESS_INACTIVE;
+				snd_ctl_notify(umidi->card,
+				       SNDRV_CTL_EVENT_MASK_INFO, &ctl->id);
+				update_roland_altsetting(umidi);
+			}
+		}
+		umidi->opened[dir]++;
+		if (umidi->opened[1])
+			snd_usbmidi_input_start(&umidi->list);
+	} else {
+		umidi->opened[dir]--;
+		if (!umidi->opened[1])
+			snd_usbmidi_input_stop(&umidi->list);
+		if (!umidi->opened[0] && !umidi->opened[1]) {
+			if (umidi->roland_load_ctl) {
+				ctl = umidi->roland_load_ctl;
+				ctl->vd[0].access &=
+					~SNDRV_CTL_ELEM_ACCESS_INACTIVE;
+				snd_ctl_notify(umidi->card,
+				       SNDRV_CTL_EVENT_MASK_INFO, &ctl->id);
+			}
+		}
+	}
+	mutex_unlock(&umidi->mutex);
+	up_read(&umidi->disc_rwsem);
+	return 0;
+}
+
+static int snd_usbmidi_output_open(struct snd_rawmidi_substream *substream)
+{
+	struct snd_usb_midi *umidi = substream->rmidi->private_data;
+	struct usbmidi_out_port *port = NULL;
+	int i, j;
+
+	for (i = 0; i < MIDI_MAX_ENDPOINTS; ++i)
+		if (umidi->endpoints[i].out)
+			for (j = 0; j < 0x10; ++j)
+				if (umidi->endpoints[i].out->ports[j].substream == substream) {
+					port = &umidi->endpoints[i].out->ports[j];
+					break;
+				}
+	if (!port) {
+		snd_BUG();
+		return -ENXIO;
+	}
+
+	substream->runtime->private_data = port;
+	port->state = STATE_UNKNOWN;
+	return substream_open(substream, 0, 1);
+}
+
+static int snd_usbmidi_output_close(struct snd_rawmidi_substream *substream)
+{
+	return substream_open(substream, 0, 0);
+}
+
+static void snd_usbmidi_output_trigger(struct snd_rawmidi_substream *substream,
+				       int up)
+{
+	struct usbmidi_out_port *port =
+		(struct usbmidi_out_port *)substream->runtime->private_data;
+
+	port->active = up;
+	if (up) {
+		if (port->ep->umidi->disconnected) {
+			/* gobble up remaining bytes to prevent wait in
+			 * snd_rawmidi_drain_output */
+			while (!snd_rawmidi_transmit_empty(substream))
+				snd_rawmidi_transmit_ack(substream, 1);
+			return;
+		}
+		tasklet_schedule(&port->ep->tasklet);
+	}
+}
+
+static void snd_usbmidi_output_drain(struct snd_rawmidi_substream *substream)
+{
+	struct usbmidi_out_port *port = substream->runtime->private_data;
+	struct snd_usb_midi_out_endpoint *ep = port->ep;
+	unsigned int drain_urbs;
+	DEFINE_WAIT(wait);
+	long timeout = msecs_to_jiffies(50);
+
+	if (ep->umidi->disconnected)
+		return;
+	/*
+	 * The substream buffer is empty, but some data might still be in the
+	 * currently active URBs, so we have to wait for those to complete.
+	 */
+	spin_lock_irq(&ep->buffer_lock);
+	drain_urbs = ep->active_urbs;
+	if (drain_urbs) {
+		ep->drain_urbs |= drain_urbs;
+		do {
+			prepare_to_wait(&ep->drain_wait, &wait,
+					TASK_UNINTERRUPTIBLE);
+			spin_unlock_irq(&ep->buffer_lock);
+			timeout = schedule_timeout(timeout);
+			spin_lock_irq(&ep->buffer_lock);
+			drain_urbs &= ep->drain_urbs;
+		} while (drain_urbs && timeout);
+		finish_wait(&ep->drain_wait, &wait);
+	}
+	spin_unlock_irq(&ep->buffer_lock);
+}
+
+static int snd_usbmidi_input_open(struct snd_rawmidi_substream *substream)
+{
+	return substream_open(substream, 1, 1);
+}
+
+static int snd_usbmidi_input_close(struct snd_rawmidi_substream *substream)
+{
+	return substream_open(substream, 1, 0);
+}
+
+static void snd_usbmidi_input_trigger(struct snd_rawmidi_substream *substream,
+				      int up)
+{
+	struct snd_usb_midi *umidi = substream->rmidi->private_data;
+
+	if (up)
+		set_bit(substream->number, &umidi->input_triggered);
+	else
+		clear_bit(substream->number, &umidi->input_triggered);
+}
+
+static const struct snd_rawmidi_ops snd_usbmidi_output_ops = {
+	.open = snd_usbmidi_output_open,
+	.close = snd_usbmidi_output_close,
+	.trigger = snd_usbmidi_output_trigger,
+	.drain = snd_usbmidi_output_drain,
+};
+
+static const struct snd_rawmidi_ops snd_usbmidi_input_ops = {
+	.open = snd_usbmidi_input_open,
+	.close = snd_usbmidi_input_close,
+	.trigger = snd_usbmidi_input_trigger
+};
+
+static void free_urb_and_buffer(struct snd_usb_midi *umidi, struct urb *urb,
+				unsigned int buffer_length)
+{
+	usb_free_coherent(umidi->dev, buffer_length,
+			  urb->transfer_buffer, urb->transfer_dma);
+	usb_free_urb(urb);
+}
+
+/*
+ * Frees an input endpoint.
+ * May be called when ep hasn't been initialized completely.
+ */
+static void snd_usbmidi_in_endpoint_delete(struct snd_usb_midi_in_endpoint *ep)
+{
+	unsigned int i;
+
+	for (i = 0; i < INPUT_URBS; ++i)
+		if (ep->urbs[i])
+			free_urb_and_buffer(ep->umidi, ep->urbs[i],
+					    ep->urbs[i]->transfer_buffer_length);
+	kfree(ep);
+}
+
+/*
+ * Creates an input endpoint.
+ */
+static int snd_usbmidi_in_endpoint_create(struct snd_usb_midi *umidi,
+					  struct snd_usb_midi_endpoint_info *ep_info,
+					  struct snd_usb_midi_endpoint *rep)
+{
+	struct snd_usb_midi_in_endpoint *ep;
+	void *buffer;
+	unsigned int pipe;
+	int length;
+	unsigned int i;
+	int err;
+
+	rep->in = NULL;
+	ep = kzalloc(sizeof(*ep), GFP_KERNEL);
+	if (!ep)
+		return -ENOMEM;
+	ep->umidi = umidi;
+
+	for (i = 0; i < INPUT_URBS; ++i) {
+		ep->urbs[i] = usb_alloc_urb(0, GFP_KERNEL);
+		if (!ep->urbs[i]) {
+			err = -ENOMEM;
+			goto error;
+		}
+	}
+	if (ep_info->in_interval)
+		pipe = usb_rcvintpipe(umidi->dev, ep_info->in_ep);
+	else
+		pipe = usb_rcvbulkpipe(umidi->dev, ep_info->in_ep);
+	length = usb_maxpacket(umidi->dev, pipe, 0);
+	for (i = 0; i < INPUT_URBS; ++i) {
+		buffer = usb_alloc_coherent(umidi->dev, length, GFP_KERNEL,
+					    &ep->urbs[i]->transfer_dma);
+		if (!buffer) {
+			err = -ENOMEM;
+			goto error;
+		}
+		if (ep_info->in_interval)
+			usb_fill_int_urb(ep->urbs[i], umidi->dev,
+					 pipe, buffer, length,
+					 snd_usbmidi_in_urb_complete,
+					 ep, ep_info->in_interval);
+		else
+			usb_fill_bulk_urb(ep->urbs[i], umidi->dev,
+					  pipe, buffer, length,
+					  snd_usbmidi_in_urb_complete, ep);
+		ep->urbs[i]->transfer_flags = URB_NO_TRANSFER_DMA_MAP;
+		err = usb_urb_ep_type_check(ep->urbs[i]);
+		if (err < 0) {
+			dev_err(&umidi->dev->dev, "invalid MIDI in EP %x\n",
+				ep_info->in_ep);
+			goto error;
+		}
+	}
+
+	rep->in = ep;
+	return 0;
+
+ error:
+	snd_usbmidi_in_endpoint_delete(ep);
+	return -ENOMEM;
+}
+
+/*
+ * Frees an output endpoint.
+ * May be called when ep hasn't been initialized completely.
+ */
+static void snd_usbmidi_out_endpoint_clear(struct snd_usb_midi_out_endpoint *ep)
+{
+	unsigned int i;
+
+	for (i = 0; i < OUTPUT_URBS; ++i)
+		if (ep->urbs[i].urb) {
+			free_urb_and_buffer(ep->umidi, ep->urbs[i].urb,
+					    ep->max_transfer);
+			ep->urbs[i].urb = NULL;
+		}
+}
+
+static void snd_usbmidi_out_endpoint_delete(struct snd_usb_midi_out_endpoint *ep)
+{
+	snd_usbmidi_out_endpoint_clear(ep);
+	kfree(ep);
+}
+
+/*
+ * Creates an output endpoint, and initializes output ports.
+ */
+static int snd_usbmidi_out_endpoint_create(struct snd_usb_midi *umidi,
+					   struct snd_usb_midi_endpoint_info *ep_info,
+					   struct snd_usb_midi_endpoint *rep)
+{
+	struct snd_usb_midi_out_endpoint *ep;
+	unsigned int i;
+	unsigned int pipe;
+	void *buffer;
+	int err;
+
+	rep->out = NULL;
+	ep = kzalloc(sizeof(*ep), GFP_KERNEL);
+	if (!ep)
+		return -ENOMEM;
+	ep->umidi = umidi;
+
+	for (i = 0; i < OUTPUT_URBS; ++i) {
+		ep->urbs[i].urb = usb_alloc_urb(0, GFP_KERNEL);
+		if (!ep->urbs[i].urb) {
+			err = -ENOMEM;
+			goto error;
+		}
+		ep->urbs[i].ep = ep;
+	}
+	if (ep_info->out_interval)
+		pipe = usb_sndintpipe(umidi->dev, ep_info->out_ep);
+	else
+		pipe = usb_sndbulkpipe(umidi->dev, ep_info->out_ep);
+	switch (umidi->usb_id) {
+	default:
+		ep->max_transfer = usb_maxpacket(umidi->dev, pipe, 1);
+		break;
+		/*
+		 * Various chips declare a packet size larger than 4 bytes, but
+		 * do not actually work with larger packets:
+		 */
+	case USB_ID(0x0a67, 0x5011): /* Medeli DD305 */
+	case USB_ID(0x0a92, 0x1020): /* ESI M4U */
+	case USB_ID(0x1430, 0x474b): /* RedOctane GH MIDI INTERFACE */
+	case USB_ID(0x15ca, 0x0101): /* Textech USB Midi Cable */
+	case USB_ID(0x15ca, 0x1806): /* Textech USB Midi Cable */
+	case USB_ID(0x1a86, 0x752d): /* QinHeng CH345 "USB2.0-MIDI" */
+	case USB_ID(0xfc08, 0x0101): /* Unknown vendor Cable */
+		ep->max_transfer = 4;
+		break;
+		/*
+		 * Some devices only work with 9 bytes packet size:
+		 */
+	case USB_ID(0x0644, 0x800E): /* Tascam US-122L */
+	case USB_ID(0x0644, 0x800F): /* Tascam US-144 */
+		ep->max_transfer = 9;
+		break;
+	}
+	for (i = 0; i < OUTPUT_URBS; ++i) {
+		buffer = usb_alloc_coherent(umidi->dev,
+					    ep->max_transfer, GFP_KERNEL,
+					    &ep->urbs[i].urb->transfer_dma);
+		if (!buffer) {
+			err = -ENOMEM;
+			goto error;
+		}
+		if (ep_info->out_interval)
+			usb_fill_int_urb(ep->urbs[i].urb, umidi->dev,
+					 pipe, buffer, ep->max_transfer,
+					 snd_usbmidi_out_urb_complete,
+					 &ep->urbs[i], ep_info->out_interval);
+		else
+			usb_fill_bulk_urb(ep->urbs[i].urb, umidi->dev,
+					  pipe, buffer, ep->max_transfer,
+					  snd_usbmidi_out_urb_complete,
+					  &ep->urbs[i]);
+		err = usb_urb_ep_type_check(ep->urbs[i].urb);
+		if (err < 0) {
+			dev_err(&umidi->dev->dev, "invalid MIDI out EP %x\n",
+				ep_info->out_ep);
+			goto error;
+		}
+		ep->urbs[i].urb->transfer_flags = URB_NO_TRANSFER_DMA_MAP;
+	}
+
+	spin_lock_init(&ep->buffer_lock);
+	tasklet_init(&ep->tasklet, snd_usbmidi_out_tasklet, (unsigned long)ep);
+	init_waitqueue_head(&ep->drain_wait);
+
+	for (i = 0; i < 0x10; ++i)
+		if (ep_info->out_cables & (1 << i)) {
+			ep->ports[i].ep = ep;
+			ep->ports[i].cable = i << 4;
+		}
+
+	if (umidi->usb_protocol_ops->init_out_endpoint)
+		umidi->usb_protocol_ops->init_out_endpoint(ep);
+
+	rep->out = ep;
+	return 0;
+
+ error:
+	snd_usbmidi_out_endpoint_delete(ep);
+	return err;
+}
+
+/*
+ * Frees everything.
+ */
+static void snd_usbmidi_free(struct snd_usb_midi *umidi)
+{
+	int i;
+
+	for (i = 0; i < MIDI_MAX_ENDPOINTS; ++i) {
+		struct snd_usb_midi_endpoint *ep = &umidi->endpoints[i];
+		if (ep->out)
+			snd_usbmidi_out_endpoint_delete(ep->out);
+		if (ep->in)
+			snd_usbmidi_in_endpoint_delete(ep->in);
+	}
+	mutex_destroy(&umidi->mutex);
+	kfree(umidi);
+}
+
+/*
+ * Unlinks all URBs (must be done before the usb_device is deleted).
+ */
+void snd_usbmidi_disconnect(struct list_head *p)
+{
+	struct snd_usb_midi *umidi;
+	unsigned int i, j;
+
+	umidi = list_entry(p, struct snd_usb_midi, list);
+	/*
+	 * an URB's completion handler may start the timer and
+	 * a timer may submit an URB. To reliably break the cycle
+	 * a flag under lock must be used
+	 */
+	down_write(&umidi->disc_rwsem);
+	spin_lock_irq(&umidi->disc_lock);
+	umidi->disconnected = 1;
+	spin_unlock_irq(&umidi->disc_lock);
+	up_write(&umidi->disc_rwsem);
+
+	for (i = 0; i < MIDI_MAX_ENDPOINTS; ++i) {
+		struct snd_usb_midi_endpoint *ep = &umidi->endpoints[i];
+		if (ep->out)
+			tasklet_kill(&ep->out->tasklet);
+		if (ep->out) {
+			for (j = 0; j < OUTPUT_URBS; ++j)
+				usb_kill_urb(ep->out->urbs[j].urb);
+			if (umidi->usb_protocol_ops->finish_out_endpoint)
+				umidi->usb_protocol_ops->finish_out_endpoint(ep->out);
+			ep->out->active_urbs = 0;
+			if (ep->out->drain_urbs) {
+				ep->out->drain_urbs = 0;
+				wake_up(&ep->out->drain_wait);
+			}
+		}
+		if (ep->in)
+			for (j = 0; j < INPUT_URBS; ++j)
+				usb_kill_urb(ep->in->urbs[j]);
+		/* free endpoints here; later call can result in Oops */
+		if (ep->out)
+			snd_usbmidi_out_endpoint_clear(ep->out);
+		if (ep->in) {
+			snd_usbmidi_in_endpoint_delete(ep->in);
+			ep->in = NULL;
+		}
+	}
+	del_timer_sync(&umidi->error_timer);
+}
+EXPORT_SYMBOL(snd_usbmidi_disconnect);
+
+static void snd_usbmidi_rawmidi_free(struct snd_rawmidi *rmidi)
+{
+	struct snd_usb_midi *umidi = rmidi->private_data;
+	snd_usbmidi_free(umidi);
+}
+
+static struct snd_rawmidi_substream *snd_usbmidi_find_substream(struct snd_usb_midi *umidi,
+								int stream,
+								int number)
+{
+	struct snd_rawmidi_substream *substream;
+
+	list_for_each_entry(substream, &umidi->rmidi->streams[stream].substreams,
+			    list) {
+		if (substream->number == number)
+			return substream;
+	}
+	return NULL;
+}
+
+/*
+ * This list specifies names for ports that do not fit into the standard
+ * "(product) MIDI (n)" schema because they aren't external MIDI ports,
+ * such as internal control or synthesizer ports.
+ */
+static struct port_info {
+	u32 id;
+	short int port;
+	short int voices;
+	const char *name;
+	unsigned int seq_flags;
+} snd_usbmidi_port_info[] = {
+#define PORT_INFO(vendor, product, num, name_, voices_, flags) \
+	{ .id = USB_ID(vendor, product), \
+	  .port = num, .voices = voices_, \
+	  .name = name_, .seq_flags = flags }
+#define EXTERNAL_PORT(vendor, product, num, name) \
+	PORT_INFO(vendor, product, num, name, 0, \
+		  SNDRV_SEQ_PORT_TYPE_MIDI_GENERIC | \
+		  SNDRV_SEQ_PORT_TYPE_HARDWARE | \
+		  SNDRV_SEQ_PORT_TYPE_PORT)
+#define CONTROL_PORT(vendor, product, num, name) \
+	PORT_INFO(vendor, product, num, name, 0, \
+		  SNDRV_SEQ_PORT_TYPE_MIDI_GENERIC | \
+		  SNDRV_SEQ_PORT_TYPE_HARDWARE)
+#define GM_SYNTH_PORT(vendor, product, num, name, voices) \
+	PORT_INFO(vendor, product, num, name, voices, \
+		  SNDRV_SEQ_PORT_TYPE_MIDI_GENERIC | \
+		  SNDRV_SEQ_PORT_TYPE_MIDI_GM | \
+		  SNDRV_SEQ_PORT_TYPE_HARDWARE | \
+		  SNDRV_SEQ_PORT_TYPE_SYNTHESIZER)
+#define ROLAND_SYNTH_PORT(vendor, product, num, name, voices) \
+	PORT_INFO(vendor, product, num, name, voices, \
+		  SNDRV_SEQ_PORT_TYPE_MIDI_GENERIC | \
+		  SNDRV_SEQ_PORT_TYPE_MIDI_GM | \
+		  SNDRV_SEQ_PORT_TYPE_MIDI_GM2 | \
+		  SNDRV_SEQ_PORT_TYPE_MIDI_GS | \
+		  SNDRV_SEQ_PORT_TYPE_MIDI_XG | \
+		  SNDRV_SEQ_PORT_TYPE_HARDWARE | \
+		  SNDRV_SEQ_PORT_TYPE_SYNTHESIZER)
+#define SOUNDCANVAS_PORT(vendor, product, num, name, voices) \
+	PORT_INFO(vendor, product, num, name, voices, \
+		  SNDRV_SEQ_PORT_TYPE_MIDI_GENERIC | \
+		  SNDRV_SEQ_PORT_TYPE_MIDI_GM | \
+		  SNDRV_SEQ_PORT_TYPE_MIDI_GM2 | \
+		  SNDRV_SEQ_PORT_TYPE_MIDI_GS | \
+		  SNDRV_SEQ_PORT_TYPE_MIDI_XG | \
+		  SNDRV_SEQ_PORT_TYPE_MIDI_MT32 | \
+		  SNDRV_SEQ_PORT_TYPE_HARDWARE | \
+		  SNDRV_SEQ_PORT_TYPE_SYNTHESIZER)
+	/* Yamaha MOTIF XF */
+	GM_SYNTH_PORT(0x0499, 0x105c, 0, "%s Tone Generator", 128),
+	CONTROL_PORT(0x0499, 0x105c, 1, "%s Remote Control"),
+	EXTERNAL_PORT(0x0499, 0x105c, 2, "%s Thru"),
+	CONTROL_PORT(0x0499, 0x105c, 3, "%s Editor"),
+	/* Roland UA-100 */
+	CONTROL_PORT(0x0582, 0x0000, 2, "%s Control"),
+	/* Roland SC-8850 */
+	SOUNDCANVAS_PORT(0x0582, 0x0003, 0, "%s Part A", 128),
+	SOUNDCANVAS_PORT(0x0582, 0x0003, 1, "%s Part B", 128),
+	SOUNDCANVAS_PORT(0x0582, 0x0003, 2, "%s Part C", 128),
+	SOUNDCANVAS_PORT(0x0582, 0x0003, 3, "%s Part D", 128),
+	EXTERNAL_PORT(0x0582, 0x0003, 4, "%s MIDI 1"),
+	EXTERNAL_PORT(0x0582, 0x0003, 5, "%s MIDI 2"),
+	/* Roland U-8 */
+	EXTERNAL_PORT(0x0582, 0x0004, 0, "%s MIDI"),
+	CONTROL_PORT(0x0582, 0x0004, 1, "%s Control"),
+	/* Roland SC-8820 */
+	SOUNDCANVAS_PORT(0x0582, 0x0007, 0, "%s Part A", 64),
+	SOUNDCANVAS_PORT(0x0582, 0x0007, 1, "%s Part B", 64),
+	EXTERNAL_PORT(0x0582, 0x0007, 2, "%s MIDI"),
+	/* Roland SK-500 */
+	SOUNDCANVAS_PORT(0x0582, 0x000b, 0, "%s Part A", 64),
+	SOUNDCANVAS_PORT(0x0582, 0x000b, 1, "%s Part B", 64),
+	EXTERNAL_PORT(0x0582, 0x000b, 2, "%s MIDI"),
+	/* Roland SC-D70 */
+	SOUNDCANVAS_PORT(0x0582, 0x000c, 0, "%s Part A", 64),
+	SOUNDCANVAS_PORT(0x0582, 0x000c, 1, "%s Part B", 64),
+	EXTERNAL_PORT(0x0582, 0x000c, 2, "%s MIDI"),
+	/* Edirol UM-880 */
+	CONTROL_PORT(0x0582, 0x0014, 8, "%s Control"),
+	/* Edirol SD-90 */
+	ROLAND_SYNTH_PORT(0x0582, 0x0016, 0, "%s Part A", 128),
+	ROLAND_SYNTH_PORT(0x0582, 0x0016, 1, "%s Part B", 128),
+	EXTERNAL_PORT(0x0582, 0x0016, 2, "%s MIDI 1"),
+	EXTERNAL_PORT(0x0582, 0x0016, 3, "%s MIDI 2"),
+	/* Edirol UM-550 */
+	CONTROL_PORT(0x0582, 0x0023, 5, "%s Control"),
+	/* Edirol SD-20 */
+	ROLAND_SYNTH_PORT(0x0582, 0x0027, 0, "%s Part A", 64),
+	ROLAND_SYNTH_PORT(0x0582, 0x0027, 1, "%s Part B", 64),
+	EXTERNAL_PORT(0x0582, 0x0027, 2, "%s MIDI"),
+	/* Edirol SD-80 */
+	ROLAND_SYNTH_PORT(0x0582, 0x0029, 0, "%s Part A", 128),
+	ROLAND_SYNTH_PORT(0x0582, 0x0029, 1, "%s Part B", 128),
+	EXTERNAL_PORT(0x0582, 0x0029, 2, "%s MIDI 1"),
+	EXTERNAL_PORT(0x0582, 0x0029, 3, "%s MIDI 2"),
+	/* Edirol UA-700 */
+	EXTERNAL_PORT(0x0582, 0x002b, 0, "%s MIDI"),
+	CONTROL_PORT(0x0582, 0x002b, 1, "%s Control"),
+	/* Roland VariOS */
+	EXTERNAL_PORT(0x0582, 0x002f, 0, "%s MIDI"),
+	EXTERNAL_PORT(0x0582, 0x002f, 1, "%s External MIDI"),
+	EXTERNAL_PORT(0x0582, 0x002f, 2, "%s Sync"),
+	/* Edirol PCR */
+	EXTERNAL_PORT(0x0582, 0x0033, 0, "%s MIDI"),
+	EXTERNAL_PORT(0x0582, 0x0033, 1, "%s 1"),
+	EXTERNAL_PORT(0x0582, 0x0033, 2, "%s 2"),
+	/* BOSS GS-10 */
+	EXTERNAL_PORT(0x0582, 0x003b, 0, "%s MIDI"),
+	CONTROL_PORT(0x0582, 0x003b, 1, "%s Control"),
+	/* Edirol UA-1000 */
+	EXTERNAL_PORT(0x0582, 0x0044, 0, "%s MIDI"),
+	CONTROL_PORT(0x0582, 0x0044, 1, "%s Control"),
+	/* Edirol UR-80 */
+	EXTERNAL_PORT(0x0582, 0x0048, 0, "%s MIDI"),
+	EXTERNAL_PORT(0x0582, 0x0048, 1, "%s 1"),
+	EXTERNAL_PORT(0x0582, 0x0048, 2, "%s 2"),
+	/* Edirol PCR-A */
+	EXTERNAL_PORT(0x0582, 0x004d, 0, "%s MIDI"),
+	EXTERNAL_PORT(0x0582, 0x004d, 1, "%s 1"),
+	EXTERNAL_PORT(0x0582, 0x004d, 2, "%s 2"),
+	/* BOSS GT-PRO */
+	CONTROL_PORT(0x0582, 0x0089, 0, "%s Control"),
+	/* Edirol UM-3EX */
+	CONTROL_PORT(0x0582, 0x009a, 3, "%s Control"),
+	/* Roland VG-99 */
+	CONTROL_PORT(0x0582, 0x00b2, 0, "%s Control"),
+	EXTERNAL_PORT(0x0582, 0x00b2, 1, "%s MIDI"),
+	/* Cakewalk Sonar V-Studio 100 */
+	EXTERNAL_PORT(0x0582, 0x00eb, 0, "%s MIDI"),
+	CONTROL_PORT(0x0582, 0x00eb, 1, "%s Control"),
+	/* Roland VB-99 */
+	CONTROL_PORT(0x0582, 0x0102, 0, "%s Control"),
+	EXTERNAL_PORT(0x0582, 0x0102, 1, "%s MIDI"),
+	/* Roland A-PRO */
+	EXTERNAL_PORT(0x0582, 0x010f, 0, "%s MIDI"),
+	CONTROL_PORT(0x0582, 0x010f, 1, "%s 1"),
+	CONTROL_PORT(0x0582, 0x010f, 2, "%s 2"),
+	/* Roland SD-50 */
+	ROLAND_SYNTH_PORT(0x0582, 0x0114, 0, "%s Synth", 128),
+	EXTERNAL_PORT(0x0582, 0x0114, 1, "%s MIDI"),
+	CONTROL_PORT(0x0582, 0x0114, 2, "%s Control"),
+	/* Roland OCTA-CAPTURE */
+	EXTERNAL_PORT(0x0582, 0x0120, 0, "%s MIDI"),
+	CONTROL_PORT(0x0582, 0x0120, 1, "%s Control"),
+	EXTERNAL_PORT(0x0582, 0x0121, 0, "%s MIDI"),
+	CONTROL_PORT(0x0582, 0x0121, 1, "%s Control"),
+	/* Roland SPD-SX */
+	CONTROL_PORT(0x0582, 0x0145, 0, "%s Control"),
+	EXTERNAL_PORT(0x0582, 0x0145, 1, "%s MIDI"),
+	/* Roland A-Series */
+	CONTROL_PORT(0x0582, 0x0156, 0, "%s Keyboard"),
+	EXTERNAL_PORT(0x0582, 0x0156, 1, "%s MIDI"),
+	/* Roland INTEGRA-7 */
+	ROLAND_SYNTH_PORT(0x0582, 0x015b, 0, "%s Synth", 128),
+	CONTROL_PORT(0x0582, 0x015b, 1, "%s Control"),
+	/* M-Audio MidiSport 8x8 */
+	CONTROL_PORT(0x0763, 0x1031, 8, "%s Control"),
+	CONTROL_PORT(0x0763, 0x1033, 8, "%s Control"),
+	/* MOTU Fastlane */
+	EXTERNAL_PORT(0x07fd, 0x0001, 0, "%s MIDI A"),
+	EXTERNAL_PORT(0x07fd, 0x0001, 1, "%s MIDI B"),
+	/* Emagic Unitor8/AMT8/MT4 */
+	EXTERNAL_PORT(0x086a, 0x0001, 8, "%s Broadcast"),
+	EXTERNAL_PORT(0x086a, 0x0002, 8, "%s Broadcast"),
+	EXTERNAL_PORT(0x086a, 0x0003, 4, "%s Broadcast"),
+	/* Akai MPD16 */
+	CONTROL_PORT(0x09e8, 0x0062, 0, "%s Control"),
+	PORT_INFO(0x09e8, 0x0062, 1, "%s MIDI", 0,
+		SNDRV_SEQ_PORT_TYPE_MIDI_GENERIC |
+		SNDRV_SEQ_PORT_TYPE_HARDWARE),
+	/* Access Music Virus TI */
+	EXTERNAL_PORT(0x133e, 0x0815, 0, "%s MIDI"),
+	PORT_INFO(0x133e, 0x0815, 1, "%s Synth", 0,
+		SNDRV_SEQ_PORT_TYPE_MIDI_GENERIC |
+		SNDRV_SEQ_PORT_TYPE_HARDWARE |
+		SNDRV_SEQ_PORT_TYPE_SYNTHESIZER),
+};
+
+static struct port_info *find_port_info(struct snd_usb_midi *umidi, int number)
+{
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(snd_usbmidi_port_info); ++i) {
+		if (snd_usbmidi_port_info[i].id == umidi->usb_id &&
+		    snd_usbmidi_port_info[i].port == number)
+			return &snd_usbmidi_port_info[i];
+	}
+	return NULL;
+}
+
+static void snd_usbmidi_get_port_info(struct snd_rawmidi *rmidi, int number,
+				      struct snd_seq_port_info *seq_port_info)
+{
+	struct snd_usb_midi *umidi = rmidi->private_data;
+	struct port_info *port_info;
+
+	/* TODO: read port flags from descriptors */
+	port_info = find_port_info(umidi, number);
+	if (port_info) {
+		seq_port_info->type = port_info->seq_flags;
+		seq_port_info->midi_voices = port_info->voices;
+	}
+}
+
+static void snd_usbmidi_init_substream(struct snd_usb_midi *umidi,
+				       int stream, int number,
+				       struct snd_rawmidi_substream **rsubstream)
+{
+	struct port_info *port_info;
+	const char *name_format;
+
+	struct snd_rawmidi_substream *substream =
+		snd_usbmidi_find_substream(umidi, stream, number);
+	if (!substream) {
+		dev_err(&umidi->dev->dev, "substream %d:%d not found\n", stream,
+			number);
+		return;
+	}
+
+	/* TODO: read port name from jack descriptor */
+	port_info = find_port_info(umidi, number);
+	name_format = port_info ? port_info->name : "%s MIDI %d";
+	snprintf(substream->name, sizeof(substream->name),
+		 name_format, umidi->card->shortname, number + 1);
+
+	*rsubstream = substream;
+}
+
+/*
+ * Creates the endpoints and their ports.
+ */
+static int snd_usbmidi_create_endpoints(struct snd_usb_midi *umidi,
+					struct snd_usb_midi_endpoint_info *endpoints)
+{
+	int i, j, err;
+	int out_ports = 0, in_ports = 0;
+
+	for (i = 0; i < MIDI_MAX_ENDPOINTS; ++i) {
+		if (endpoints[i].out_cables) {
+			err = snd_usbmidi_out_endpoint_create(umidi,
+							      &endpoints[i],
+							      &umidi->endpoints[i]);
+			if (err < 0)
+				return err;
+		}
+		if (endpoints[i].in_cables) {
+			err = snd_usbmidi_in_endpoint_create(umidi,
+							     &endpoints[i],
+							     &umidi->endpoints[i]);
+			if (err < 0)
+				return err;
+		}
+
+		for (j = 0; j < 0x10; ++j) {
+			if (endpoints[i].out_cables & (1 << j)) {
+				snd_usbmidi_init_substream(umidi,
+							   SNDRV_RAWMIDI_STREAM_OUTPUT,
+							   out_ports,
+							   &umidi->endpoints[i].out->ports[j].substream);
+				++out_ports;
+			}
+			if (endpoints[i].in_cables & (1 << j)) {
+				snd_usbmidi_init_substream(umidi,
+							   SNDRV_RAWMIDI_STREAM_INPUT,
+							   in_ports,
+							   &umidi->endpoints[i].in->ports[j].substream);
+				++in_ports;
+			}
+		}
+	}
+	dev_dbg(&umidi->dev->dev, "created %d output and %d input ports\n",
+		    out_ports, in_ports);
+	return 0;
+}
+
+/*
+ * Returns MIDIStreaming device capabilities.
+ */
+static int snd_usbmidi_get_ms_info(struct snd_usb_midi *umidi,
+				   struct snd_usb_midi_endpoint_info *endpoints)
+{
+	struct usb_interface *intf;
+	struct usb_host_interface *hostif;
+	struct usb_interface_descriptor *intfd;
+	struct usb_ms_header_descriptor *ms_header;
+	struct usb_host_endpoint *hostep;
+	struct usb_endpoint_descriptor *ep;
+	struct usb_ms_endpoint_descriptor *ms_ep;
+	int i, epidx;
+
+	intf = umidi->iface;
+	if (!intf)
+		return -ENXIO;
+	hostif = &intf->altsetting[0];
+	intfd = get_iface_desc(hostif);
+	ms_header = (struct usb_ms_header_descriptor *)hostif->extra;
+	if (hostif->extralen >= 7 &&
+	    ms_header->bLength >= 7 &&
+	    ms_header->bDescriptorType == USB_DT_CS_INTERFACE &&
+	    ms_header->bDescriptorSubtype == UAC_HEADER)
+		dev_dbg(&umidi->dev->dev, "MIDIStreaming version %02x.%02x\n",
+			    ms_header->bcdMSC[1], ms_header->bcdMSC[0]);
+	else
+		dev_warn(&umidi->dev->dev,
+			 "MIDIStreaming interface descriptor not found\n");
+
+	epidx = 0;
+	for (i = 0; i < intfd->bNumEndpoints; ++i) {
+		hostep = &hostif->endpoint[i];
+		ep = get_ep_desc(hostep);
+		if (!usb_endpoint_xfer_bulk(ep) && !usb_endpoint_xfer_int(ep))
+			continue;
+		ms_ep = (struct usb_ms_endpoint_descriptor *)hostep->extra;
+		if (hostep->extralen < 4 ||
+		    ms_ep->bLength < 4 ||
+		    ms_ep->bDescriptorType != USB_DT_CS_ENDPOINT ||
+		    ms_ep->bDescriptorSubtype != UAC_MS_GENERAL)
+			continue;
+		if (usb_endpoint_dir_out(ep)) {
+			if (endpoints[epidx].out_ep) {
+				if (++epidx >= MIDI_MAX_ENDPOINTS) {
+					dev_warn(&umidi->dev->dev,
+						 "too many endpoints\n");
+					break;
+				}
+			}
+			endpoints[epidx].out_ep = usb_endpoint_num(ep);
+			if (usb_endpoint_xfer_int(ep))
+				endpoints[epidx].out_interval = ep->bInterval;
+			else if (snd_usb_get_speed(umidi->dev) == USB_SPEED_LOW)
+				/*
+				 * Low speed bulk transfers don't exist, so
+				 * force interrupt transfers for devices like
+				 * ESI MIDI Mate that try to use them anyway.
+				 */
+				endpoints[epidx].out_interval = 1;
+			endpoints[epidx].out_cables =
+				(1 << ms_ep->bNumEmbMIDIJack) - 1;
+			dev_dbg(&umidi->dev->dev, "EP %02X: %d jack(s)\n",
+				ep->bEndpointAddress, ms_ep->bNumEmbMIDIJack);
+		} else {
+			if (endpoints[epidx].in_ep) {
+				if (++epidx >= MIDI_MAX_ENDPOINTS) {
+					dev_warn(&umidi->dev->dev,
+						 "too many endpoints\n");
+					break;
+				}
+			}
+			endpoints[epidx].in_ep = usb_endpoint_num(ep);
+			if (usb_endpoint_xfer_int(ep))
+				endpoints[epidx].in_interval = ep->bInterval;
+			else if (snd_usb_get_speed(umidi->dev) == USB_SPEED_LOW)
+				endpoints[epidx].in_interval = 1;
+			endpoints[epidx].in_cables =
+				(1 << ms_ep->bNumEmbMIDIJack) - 1;
+			dev_dbg(&umidi->dev->dev, "EP %02X: %d jack(s)\n",
+				ep->bEndpointAddress, ms_ep->bNumEmbMIDIJack);
+		}
+	}
+	return 0;
+}
+
+static int roland_load_info(struct snd_kcontrol *kcontrol,
+			    struct snd_ctl_elem_info *info)
+{
+	static const char *const names[] = { "High Load", "Light Load" };
+
+	return snd_ctl_enum_info(info, 1, 2, names);
+}
+
+static int roland_load_get(struct snd_kcontrol *kcontrol,
+			   struct snd_ctl_elem_value *value)
+{
+	value->value.enumerated.item[0] = kcontrol->private_value;
+	return 0;
+}
+
+static int roland_load_put(struct snd_kcontrol *kcontrol,
+			   struct snd_ctl_elem_value *value)
+{
+	struct snd_usb_midi *umidi = kcontrol->private_data;
+	int changed;
+
+	if (value->value.enumerated.item[0] > 1)
+		return -EINVAL;
+	mutex_lock(&umidi->mutex);
+	changed = value->value.enumerated.item[0] != kcontrol->private_value;
+	if (changed)
+		kcontrol->private_value = value->value.enumerated.item[0];
+	mutex_unlock(&umidi->mutex);
+	return changed;
+}
+
+static const struct snd_kcontrol_new roland_load_ctl = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "MIDI Input Mode",
+	.info = roland_load_info,
+	.get = roland_load_get,
+	.put = roland_load_put,
+	.private_value = 1,
+};
+
+/*
+ * On Roland devices, use the second alternate setting to be able to use
+ * the interrupt input endpoint.
+ */
+static void snd_usbmidi_switch_roland_altsetting(struct snd_usb_midi *umidi)
+{
+	struct usb_interface *intf;
+	struct usb_host_interface *hostif;
+	struct usb_interface_descriptor *intfd;
+
+	intf = umidi->iface;
+	if (!intf || intf->num_altsetting != 2)
+		return;
+
+	hostif = &intf->altsetting[1];
+	intfd = get_iface_desc(hostif);
+       /* If either or both of the endpoints support interrupt transfer,
+        * then use the alternate setting
+        */
+	if (intfd->bNumEndpoints != 2 ||
+	    !((get_endpoint(hostif, 0)->bmAttributes &
+	       USB_ENDPOINT_XFERTYPE_MASK) == USB_ENDPOINT_XFER_INT ||
+	      (get_endpoint(hostif, 1)->bmAttributes &
+	       USB_ENDPOINT_XFERTYPE_MASK) == USB_ENDPOINT_XFER_INT))
+		return;
+
+	dev_dbg(&umidi->dev->dev, "switching to altsetting %d with int ep\n",
+		    intfd->bAlternateSetting);
+	usb_set_interface(umidi->dev, intfd->bInterfaceNumber,
+			  intfd->bAlternateSetting);
+
+	umidi->roland_load_ctl = snd_ctl_new1(&roland_load_ctl, umidi);
+	if (snd_ctl_add(umidi->card, umidi->roland_load_ctl) < 0)
+		umidi->roland_load_ctl = NULL;
+}
+
+/*
+ * Try to find any usable endpoints in the interface.
+ */
+static int snd_usbmidi_detect_endpoints(struct snd_usb_midi *umidi,
+					struct snd_usb_midi_endpoint_info *endpoint,
+					int max_endpoints)
+{
+	struct usb_interface *intf;
+	struct usb_host_interface *hostif;
+	struct usb_interface_descriptor *intfd;
+	struct usb_endpoint_descriptor *epd;
+	int i, out_eps = 0, in_eps = 0;
+
+	if (USB_ID_VENDOR(umidi->usb_id) == 0x0582)
+		snd_usbmidi_switch_roland_altsetting(umidi);
+
+	if (endpoint[0].out_ep || endpoint[0].in_ep)
+		return 0;
+
+	intf = umidi->iface;
+	if (!intf || intf->num_altsetting < 1)
+		return -ENOENT;
+	hostif = intf->cur_altsetting;
+	intfd = get_iface_desc(hostif);
+
+	for (i = 0; i < intfd->bNumEndpoints; ++i) {
+		epd = get_endpoint(hostif, i);
+		if (!usb_endpoint_xfer_bulk(epd) &&
+		    !usb_endpoint_xfer_int(epd))
+			continue;
+		if (out_eps < max_endpoints &&
+		    usb_endpoint_dir_out(epd)) {
+			endpoint[out_eps].out_ep = usb_endpoint_num(epd);
+			if (usb_endpoint_xfer_int(epd))
+				endpoint[out_eps].out_interval = epd->bInterval;
+			++out_eps;
+		}
+		if (in_eps < max_endpoints &&
+		    usb_endpoint_dir_in(epd)) {
+			endpoint[in_eps].in_ep = usb_endpoint_num(epd);
+			if (usb_endpoint_xfer_int(epd))
+				endpoint[in_eps].in_interval = epd->bInterval;
+			++in_eps;
+		}
+	}
+	return (out_eps || in_eps) ? 0 : -ENOENT;
+}
+
+/*
+ * Detects the endpoints for one-port-per-endpoint protocols.
+ */
+static int snd_usbmidi_detect_per_port_endpoints(struct snd_usb_midi *umidi,
+						 struct snd_usb_midi_endpoint_info *endpoints)
+{
+	int err, i;
+
+	err = snd_usbmidi_detect_endpoints(umidi, endpoints, MIDI_MAX_ENDPOINTS);
+	for (i = 0; i < MIDI_MAX_ENDPOINTS; ++i) {
+		if (endpoints[i].out_ep)
+			endpoints[i].out_cables = 0x0001;
+		if (endpoints[i].in_ep)
+			endpoints[i].in_cables = 0x0001;
+	}
+	return err;
+}
+
+/*
+ * Detects the endpoints and ports of Yamaha devices.
+ */
+static int snd_usbmidi_detect_yamaha(struct snd_usb_midi *umidi,
+				     struct snd_usb_midi_endpoint_info *endpoint)
+{
+	struct usb_interface *intf;
+	struct usb_host_interface *hostif;
+	struct usb_interface_descriptor *intfd;
+	uint8_t *cs_desc;
+
+	intf = umidi->iface;
+	if (!intf)
+		return -ENOENT;
+	hostif = intf->altsetting;
+	intfd = get_iface_desc(hostif);
+	if (intfd->bNumEndpoints < 1)
+		return -ENOENT;
+
+	/*
+	 * For each port there is one MIDI_IN/OUT_JACK descriptor, not
+	 * necessarily with any useful contents.  So simply count 'em.
+	 */
+	for (cs_desc = hostif->extra;
+	     cs_desc < hostif->extra + hostif->extralen && cs_desc[0] >= 2;
+	     cs_desc += cs_desc[0]) {
+		if (cs_desc[1] == USB_DT_CS_INTERFACE) {
+			if (cs_desc[2] == UAC_MIDI_IN_JACK)
+				endpoint->in_cables =
+					(endpoint->in_cables << 1) | 1;
+			else if (cs_desc[2] == UAC_MIDI_OUT_JACK)
+				endpoint->out_cables =
+					(endpoint->out_cables << 1) | 1;
+		}
+	}
+	if (!endpoint->in_cables && !endpoint->out_cables)
+		return -ENOENT;
+
+	return snd_usbmidi_detect_endpoints(umidi, endpoint, 1);
+}
+
+/*
+ * Detects the endpoints and ports of Roland devices.
+ */
+static int snd_usbmidi_detect_roland(struct snd_usb_midi *umidi,
+				     struct snd_usb_midi_endpoint_info *endpoint)
+{
+	struct usb_interface *intf;
+	struct usb_host_interface *hostif;
+	u8 *cs_desc;
+
+	intf = umidi->iface;
+	if (!intf)
+		return -ENOENT;
+	hostif = intf->altsetting;
+	/*
+	 * Some devices have a descriptor <06 24 F1 02 <inputs> <outputs>>,
+	 * some have standard class descriptors, or both kinds, or neither.
+	 */
+	for (cs_desc = hostif->extra;
+	     cs_desc < hostif->extra + hostif->extralen && cs_desc[0] >= 2;
+	     cs_desc += cs_desc[0]) {
+		if (cs_desc[0] >= 6 &&
+		    cs_desc[1] == USB_DT_CS_INTERFACE &&
+		    cs_desc[2] == 0xf1 &&
+		    cs_desc[3] == 0x02) {
+			endpoint->in_cables  = (1 << cs_desc[4]) - 1;
+			endpoint->out_cables = (1 << cs_desc[5]) - 1;
+			return snd_usbmidi_detect_endpoints(umidi, endpoint, 1);
+		} else if (cs_desc[0] >= 7 &&
+			   cs_desc[1] == USB_DT_CS_INTERFACE &&
+			   cs_desc[2] == UAC_HEADER) {
+			return snd_usbmidi_get_ms_info(umidi, endpoint);
+		}
+	}
+
+	return -ENODEV;
+}
+
+/*
+ * Creates the endpoints and their ports for Midiman devices.
+ */
+static int snd_usbmidi_create_endpoints_midiman(struct snd_usb_midi *umidi,
+						struct snd_usb_midi_endpoint_info *endpoint)
+{
+	struct snd_usb_midi_endpoint_info ep_info;
+	struct usb_interface *intf;
+	struct usb_host_interface *hostif;
+	struct usb_interface_descriptor *intfd;
+	struct usb_endpoint_descriptor *epd;
+	int cable, err;
+
+	intf = umidi->iface;
+	if (!intf)
+		return -ENOENT;
+	hostif = intf->altsetting;
+	intfd = get_iface_desc(hostif);
+	/*
+	 * The various MidiSport devices have more or less random endpoint
+	 * numbers, so we have to identify the endpoints by their index in
+	 * the descriptor array, like the driver for that other OS does.
+	 *
+	 * There is one interrupt input endpoint for all input ports, one
+	 * bulk output endpoint for even-numbered ports, and one for odd-
+	 * numbered ports.  Both bulk output endpoints have corresponding
+	 * input bulk endpoints (at indices 1 and 3) which aren't used.
+	 */
+	if (intfd->bNumEndpoints < (endpoint->out_cables > 0x0001 ? 5 : 3)) {
+		dev_dbg(&umidi->dev->dev, "not enough endpoints\n");
+		return -ENOENT;
+	}
+
+	epd = get_endpoint(hostif, 0);
+	if (!usb_endpoint_dir_in(epd) || !usb_endpoint_xfer_int(epd)) {
+		dev_dbg(&umidi->dev->dev, "endpoint[0] isn't interrupt\n");
+		return -ENXIO;
+	}
+	epd = get_endpoint(hostif, 2);
+	if (!usb_endpoint_dir_out(epd) || !usb_endpoint_xfer_bulk(epd)) {
+		dev_dbg(&umidi->dev->dev, "endpoint[2] isn't bulk output\n");
+		return -ENXIO;
+	}
+	if (endpoint->out_cables > 0x0001) {
+		epd = get_endpoint(hostif, 4);
+		if (!usb_endpoint_dir_out(epd) ||
+		    !usb_endpoint_xfer_bulk(epd)) {
+			dev_dbg(&umidi->dev->dev,
+				"endpoint[4] isn't bulk output\n");
+			return -ENXIO;
+		}
+	}
+
+	ep_info.out_ep = get_endpoint(hostif, 2)->bEndpointAddress &
+		USB_ENDPOINT_NUMBER_MASK;
+	ep_info.out_interval = 0;
+	ep_info.out_cables = endpoint->out_cables & 0x5555;
+	err = snd_usbmidi_out_endpoint_create(umidi, &ep_info,
+					      &umidi->endpoints[0]);
+	if (err < 0)
+		return err;
+
+	ep_info.in_ep = get_endpoint(hostif, 0)->bEndpointAddress &
+		USB_ENDPOINT_NUMBER_MASK;
+	ep_info.in_interval = get_endpoint(hostif, 0)->bInterval;
+	ep_info.in_cables = endpoint->in_cables;
+	err = snd_usbmidi_in_endpoint_create(umidi, &ep_info,
+					     &umidi->endpoints[0]);
+	if (err < 0)
+		return err;
+
+	if (endpoint->out_cables > 0x0001) {
+		ep_info.out_ep = get_endpoint(hostif, 4)->bEndpointAddress &
+			USB_ENDPOINT_NUMBER_MASK;
+		ep_info.out_cables = endpoint->out_cables & 0xaaaa;
+		err = snd_usbmidi_out_endpoint_create(umidi, &ep_info,
+						      &umidi->endpoints[1]);
+		if (err < 0)
+			return err;
+	}
+
+	for (cable = 0; cable < 0x10; ++cable) {
+		if (endpoint->out_cables & (1 << cable))
+			snd_usbmidi_init_substream(umidi,
+						   SNDRV_RAWMIDI_STREAM_OUTPUT,
+						   cable,
+						   &umidi->endpoints[cable & 1].out->ports[cable].substream);
+		if (endpoint->in_cables & (1 << cable))
+			snd_usbmidi_init_substream(umidi,
+						   SNDRV_RAWMIDI_STREAM_INPUT,
+						   cable,
+						   &umidi->endpoints[0].in->ports[cable].substream);
+	}
+	return 0;
+}
+
+static const struct snd_rawmidi_global_ops snd_usbmidi_ops = {
+	.get_port_info = snd_usbmidi_get_port_info,
+};
+
+static int snd_usbmidi_create_rawmidi(struct snd_usb_midi *umidi,
+				      int out_ports, int in_ports)
+{
+	struct snd_rawmidi *rmidi;
+	int err;
+
+	err = snd_rawmidi_new(umidi->card, "USB MIDI",
+			      umidi->next_midi_device++,
+			      out_ports, in_ports, &rmidi);
+	if (err < 0)
+		return err;
+	strcpy(rmidi->name, umidi->card->shortname);
+	rmidi->info_flags = SNDRV_RAWMIDI_INFO_OUTPUT |
+			    SNDRV_RAWMIDI_INFO_INPUT |
+			    SNDRV_RAWMIDI_INFO_DUPLEX;
+	rmidi->ops = &snd_usbmidi_ops;
+	rmidi->private_data = umidi;
+	rmidi->private_free = snd_usbmidi_rawmidi_free;
+	snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT,
+			    &snd_usbmidi_output_ops);
+	snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT,
+			    &snd_usbmidi_input_ops);
+
+	umidi->rmidi = rmidi;
+	return 0;
+}
+
+/*
+ * Temporarily stop input.
+ */
+void snd_usbmidi_input_stop(struct list_head *p)
+{
+	struct snd_usb_midi *umidi;
+	unsigned int i, j;
+
+	umidi = list_entry(p, struct snd_usb_midi, list);
+	if (!umidi->input_running)
+		return;
+	for (i = 0; i < MIDI_MAX_ENDPOINTS; ++i) {
+		struct snd_usb_midi_endpoint *ep = &umidi->endpoints[i];
+		if (ep->in)
+			for (j = 0; j < INPUT_URBS; ++j)
+				usb_kill_urb(ep->in->urbs[j]);
+	}
+	umidi->input_running = 0;
+}
+EXPORT_SYMBOL(snd_usbmidi_input_stop);
+
+static void snd_usbmidi_input_start_ep(struct snd_usb_midi_in_endpoint *ep)
+{
+	unsigned int i;
+
+	if (!ep)
+		return;
+	for (i = 0; i < INPUT_URBS; ++i) {
+		struct urb *urb = ep->urbs[i];
+		urb->dev = ep->umidi->dev;
+		snd_usbmidi_submit_urb(urb, GFP_KERNEL);
+	}
+}
+
+/*
+ * Resume input after a call to snd_usbmidi_input_stop().
+ */
+void snd_usbmidi_input_start(struct list_head *p)
+{
+	struct snd_usb_midi *umidi;
+	int i;
+
+	umidi = list_entry(p, struct snd_usb_midi, list);
+	if (umidi->input_running || !umidi->opened[1])
+		return;
+	for (i = 0; i < MIDI_MAX_ENDPOINTS; ++i)
+		snd_usbmidi_input_start_ep(umidi->endpoints[i].in);
+	umidi->input_running = 1;
+}
+EXPORT_SYMBOL(snd_usbmidi_input_start);
+
+/*
+ * Prepare for suspend. Typically called from the USB suspend callback.
+ */
+void snd_usbmidi_suspend(struct list_head *p)
+{
+	struct snd_usb_midi *umidi;
+
+	umidi = list_entry(p, struct snd_usb_midi, list);
+	mutex_lock(&umidi->mutex);
+	snd_usbmidi_input_stop(p);
+	mutex_unlock(&umidi->mutex);
+}
+EXPORT_SYMBOL(snd_usbmidi_suspend);
+
+/*
+ * Resume. Typically called from the USB resume callback.
+ */
+void snd_usbmidi_resume(struct list_head *p)
+{
+	struct snd_usb_midi *umidi;
+
+	umidi = list_entry(p, struct snd_usb_midi, list);
+	mutex_lock(&umidi->mutex);
+	snd_usbmidi_input_start(p);
+	mutex_unlock(&umidi->mutex);
+}
+EXPORT_SYMBOL(snd_usbmidi_resume);
+
+/*
+ * Creates and registers everything needed for a MIDI streaming interface.
+ */
+int __snd_usbmidi_create(struct snd_card *card,
+			 struct usb_interface *iface,
+			 struct list_head *midi_list,
+			 const struct snd_usb_audio_quirk *quirk,
+			 unsigned int usb_id)
+{
+	struct snd_usb_midi *umidi;
+	struct snd_usb_midi_endpoint_info endpoints[MIDI_MAX_ENDPOINTS];
+	int out_ports, in_ports;
+	int i, err;
+
+	umidi = kzalloc(sizeof(*umidi), GFP_KERNEL);
+	if (!umidi)
+		return -ENOMEM;
+	umidi->dev = interface_to_usbdev(iface);
+	umidi->card = card;
+	umidi->iface = iface;
+	umidi->quirk = quirk;
+	umidi->usb_protocol_ops = &snd_usbmidi_standard_ops;
+	spin_lock_init(&umidi->disc_lock);
+	init_rwsem(&umidi->disc_rwsem);
+	mutex_init(&umidi->mutex);
+	if (!usb_id)
+		usb_id = USB_ID(le16_to_cpu(umidi->dev->descriptor.idVendor),
+			       le16_to_cpu(umidi->dev->descriptor.idProduct));
+	umidi->usb_id = usb_id;
+	timer_setup(&umidi->error_timer, snd_usbmidi_error_timer, 0);
+
+	/* detect the endpoint(s) to use */
+	memset(endpoints, 0, sizeof(endpoints));
+	switch (quirk ? quirk->type : QUIRK_MIDI_STANDARD_INTERFACE) {
+	case QUIRK_MIDI_STANDARD_INTERFACE:
+		err = snd_usbmidi_get_ms_info(umidi, endpoints);
+		if (umidi->usb_id == USB_ID(0x0763, 0x0150)) /* M-Audio Uno */
+			umidi->usb_protocol_ops =
+				&snd_usbmidi_maudio_broken_running_status_ops;
+		break;
+	case QUIRK_MIDI_US122L:
+		umidi->usb_protocol_ops = &snd_usbmidi_122l_ops;
+		/* fall through */
+	case QUIRK_MIDI_FIXED_ENDPOINT:
+		memcpy(&endpoints[0], quirk->data,
+		       sizeof(struct snd_usb_midi_endpoint_info));
+		err = snd_usbmidi_detect_endpoints(umidi, &endpoints[0], 1);
+		break;
+	case QUIRK_MIDI_YAMAHA:
+		err = snd_usbmidi_detect_yamaha(umidi, &endpoints[0]);
+		break;
+	case QUIRK_MIDI_ROLAND:
+		err = snd_usbmidi_detect_roland(umidi, &endpoints[0]);
+		break;
+	case QUIRK_MIDI_MIDIMAN:
+		umidi->usb_protocol_ops = &snd_usbmidi_midiman_ops;
+		memcpy(&endpoints[0], quirk->data,
+		       sizeof(struct snd_usb_midi_endpoint_info));
+		err = 0;
+		break;
+	case QUIRK_MIDI_NOVATION:
+		umidi->usb_protocol_ops = &snd_usbmidi_novation_ops;
+		err = snd_usbmidi_detect_per_port_endpoints(umidi, endpoints);
+		break;
+	case QUIRK_MIDI_RAW_BYTES:
+		umidi->usb_protocol_ops = &snd_usbmidi_raw_ops;
+		/*
+		 * Interface 1 contains isochronous endpoints, but with the same
+		 * numbers as in interface 0.  Since it is interface 1 that the
+		 * USB core has most recently seen, these descriptors are now
+		 * associated with the endpoint numbers.  This will foul up our
+		 * attempts to submit bulk/interrupt URBs to the endpoints in
+		 * interface 0, so we have to make sure that the USB core looks
+		 * again at interface 0 by calling usb_set_interface() on it.
+		 */
+		if (umidi->usb_id == USB_ID(0x07fd, 0x0001)) /* MOTU Fastlane */
+			usb_set_interface(umidi->dev, 0, 0);
+		err = snd_usbmidi_detect_per_port_endpoints(umidi, endpoints);
+		break;
+	case QUIRK_MIDI_EMAGIC:
+		umidi->usb_protocol_ops = &snd_usbmidi_emagic_ops;
+		memcpy(&endpoints[0], quirk->data,
+		       sizeof(struct snd_usb_midi_endpoint_info));
+		err = snd_usbmidi_detect_endpoints(umidi, &endpoints[0], 1);
+		break;
+	case QUIRK_MIDI_CME:
+		umidi->usb_protocol_ops = &snd_usbmidi_cme_ops;
+		err = snd_usbmidi_detect_per_port_endpoints(umidi, endpoints);
+		break;
+	case QUIRK_MIDI_AKAI:
+		umidi->usb_protocol_ops = &snd_usbmidi_akai_ops;
+		err = snd_usbmidi_detect_per_port_endpoints(umidi, endpoints);
+		/* endpoint 1 is input-only */
+		endpoints[1].out_cables = 0;
+		break;
+	case QUIRK_MIDI_FTDI:
+		umidi->usb_protocol_ops = &snd_usbmidi_ftdi_ops;
+
+		/* set baud rate to 31250 (48 MHz / 16 / 96) */
+		err = usb_control_msg(umidi->dev, usb_sndctrlpipe(umidi->dev, 0),
+				      3, 0x40, 0x60, 0, NULL, 0, 1000);
+		if (err < 0)
+			break;
+
+		err = snd_usbmidi_detect_per_port_endpoints(umidi, endpoints);
+		break;
+	case QUIRK_MIDI_CH345:
+		umidi->usb_protocol_ops = &snd_usbmidi_ch345_broken_sysex_ops;
+		err = snd_usbmidi_detect_per_port_endpoints(umidi, endpoints);
+		break;
+	default:
+		dev_err(&umidi->dev->dev, "invalid quirk type %d\n",
+			quirk->type);
+		err = -ENXIO;
+		break;
+	}
+	if (err < 0)
+		goto free_midi;
+
+	/* create rawmidi device */
+	out_ports = 0;
+	in_ports = 0;
+	for (i = 0; i < MIDI_MAX_ENDPOINTS; ++i) {
+		out_ports += hweight16(endpoints[i].out_cables);
+		in_ports += hweight16(endpoints[i].in_cables);
+	}
+	err = snd_usbmidi_create_rawmidi(umidi, out_ports, in_ports);
+	if (err < 0)
+		goto free_midi;
+
+	/* create endpoint/port structures */
+	if (quirk && quirk->type == QUIRK_MIDI_MIDIMAN)
+		err = snd_usbmidi_create_endpoints_midiman(umidi, &endpoints[0]);
+	else
+		err = snd_usbmidi_create_endpoints(umidi, endpoints);
+	if (err < 0)
+		goto exit;
+
+	usb_autopm_get_interface_no_resume(umidi->iface);
+
+	list_add_tail(&umidi->list, midi_list);
+	return 0;
+
+free_midi:
+	kfree(umidi);
+exit:
+	return err;
+}
+EXPORT_SYMBOL(__snd_usbmidi_create);
diff --git a/sound/usb/midi.h b/sound/usb/midi.h
new file mode 100644
index 0000000..8c38aec
--- /dev/null
+++ b/sound/usb/midi.h
@@ -0,0 +1,63 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __USBMIDI_H
+#define __USBMIDI_H
+
+/* maximum number of endpoints per interface */
+#define MIDI_MAX_ENDPOINTS 2
+
+/* data for QUIRK_MIDI_FIXED_ENDPOINT */
+struct snd_usb_midi_endpoint_info {
+	int8_t   out_ep;	/* ep number, 0 autodetect */
+	uint8_t  out_interval;	/* interval for interrupt endpoints */
+	int8_t   in_ep;
+	uint8_t  in_interval;
+	uint16_t out_cables;	/* bitmask */
+	uint16_t in_cables;	/* bitmask */
+};
+
+/* for QUIRK_MIDI_YAMAHA, data is NULL */
+
+/* for QUIRK_MIDI_MIDIMAN, data points to a snd_usb_midi_endpoint_info
+ * structure (out_cables and in_cables only) */
+
+/* for QUIRK_COMPOSITE, data points to an array of snd_usb_audio_quirk
+ * structures, terminated with .ifnum = -1 */
+
+/* for QUIRK_AUDIO_FIXED_ENDPOINT, data points to an audioformat structure */
+
+/* for QUIRK_AUDIO/MIDI_STANDARD_INTERFACE, data is NULL */
+
+/* for QUIRK_AUDIO_EDIROL_UA700_UA25/UA1000, data is NULL */
+
+/* for QUIRK_IGNORE_INTERFACE, data is NULL */
+
+/* for QUIRK_MIDI_NOVATION and _RAW, data is NULL */
+
+/* for QUIRK_MIDI_EMAGIC, data points to a snd_usb_midi_endpoint_info
+ * structure (out_cables and in_cables only) */
+
+/* for QUIRK_MIDI_CME, data is NULL */
+
+/* for QUIRK_MIDI_AKAI, data is NULL */
+
+int __snd_usbmidi_create(struct snd_card *card,
+			 struct usb_interface *iface,
+			 struct list_head *midi_list,
+			 const struct snd_usb_audio_quirk *quirk,
+			 unsigned int usb_id);
+
+static inline int snd_usbmidi_create(struct snd_card *card,
+		       struct usb_interface *iface,
+		       struct list_head *midi_list,
+		       const struct snd_usb_audio_quirk *quirk)
+{
+	return __snd_usbmidi_create(card, iface, midi_list, quirk, 0);
+}
+
+void snd_usbmidi_input_stop(struct list_head *p);
+void snd_usbmidi_input_start(struct list_head *p);
+void snd_usbmidi_disconnect(struct list_head *p);
+void snd_usbmidi_suspend(struct list_head *p);
+void snd_usbmidi_resume(struct list_head *p);
+
+#endif /* __USBMIDI_H */
diff --git a/sound/usb/misc/Makefile b/sound/usb/misc/Makefile
new file mode 100644
index 0000000..ccefd81
--- /dev/null
+++ b/sound/usb/misc/Makefile
@@ -0,0 +1,2 @@
+snd-ua101-objs := ua101.o
+obj-$(CONFIG_SND_USB_UA101) += snd-ua101.o
diff --git a/sound/usb/misc/ua101.c b/sound/usb/misc/ua101.c
new file mode 100644
index 0000000..a0b6d03
--- /dev/null
+++ b/sound/usb/misc/ua101.c
@@ -0,0 +1,1386 @@
+/*
+ * Edirol UA-101/UA-1000 driver
+ * Copyright (c) Clemens Ladisch <clemens@ladisch.de>
+ *
+ * This driver is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License, version 2.
+ *
+ * This driver 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 driver.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/usb.h>
+#include <linux/usb/audio.h>
+#include <sound/core.h>
+#include <sound/initval.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include "../usbaudio.h"
+#include "../midi.h"
+
+MODULE_DESCRIPTION("Edirol UA-101/1000 driver");
+MODULE_AUTHOR("Clemens Ladisch <clemens@ladisch.de>");
+MODULE_LICENSE("GPL v2");
+MODULE_SUPPORTED_DEVICE("{{Edirol,UA-101},{Edirol,UA-1000}}");
+
+/*
+ * Should not be lower than the minimum scheduling delay of the host
+ * controller.  Some Intel controllers need more than one frame; as long as
+ * that driver doesn't tell us about this, use 1.5 frames just to be sure.
+ */
+#define MIN_QUEUE_LENGTH	12
+/* Somewhat random. */
+#define MAX_QUEUE_LENGTH	30
+/*
+ * This magic value optimizes memory usage efficiency for the UA-101's packet
+ * sizes at all sample rates, taking into account the stupid cache pool sizes
+ * that usb_alloc_coherent() uses.
+ */
+#define DEFAULT_QUEUE_LENGTH	21
+
+#define MAX_PACKET_SIZE		672 /* hardware specific */
+#define MAX_MEMORY_BUFFERS	DIV_ROUND_UP(MAX_QUEUE_LENGTH, \
+					     PAGE_SIZE / MAX_PACKET_SIZE)
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;
+static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;
+static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP;
+static unsigned int queue_length = 21;
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "card index");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "enable card");
+module_param(queue_length, uint, 0644);
+MODULE_PARM_DESC(queue_length, "USB queue length in microframes, "
+		 __stringify(MIN_QUEUE_LENGTH)"-"__stringify(MAX_QUEUE_LENGTH));
+
+enum {
+	INTF_PLAYBACK,
+	INTF_CAPTURE,
+	INTF_MIDI,
+
+	INTF_COUNT
+};
+
+/* bits in struct ua101::states */
+enum {
+	USB_CAPTURE_RUNNING,
+	USB_PLAYBACK_RUNNING,
+	ALSA_CAPTURE_OPEN,
+	ALSA_PLAYBACK_OPEN,
+	ALSA_CAPTURE_RUNNING,
+	ALSA_PLAYBACK_RUNNING,
+	CAPTURE_URB_COMPLETED,
+	PLAYBACK_URB_COMPLETED,
+	DISCONNECTED,
+};
+
+struct ua101 {
+	struct usb_device *dev;
+	struct snd_card *card;
+	struct usb_interface *intf[INTF_COUNT];
+	int card_index;
+	struct snd_pcm *pcm;
+	struct list_head midi_list;
+	u64 format_bit;
+	unsigned int rate;
+	unsigned int packets_per_second;
+	spinlock_t lock;
+	struct mutex mutex;
+	unsigned long states;
+
+	/* FIFO to synchronize playback rate to capture rate */
+	unsigned int rate_feedback_start;
+	unsigned int rate_feedback_count;
+	u8 rate_feedback[MAX_QUEUE_LENGTH];
+
+	struct list_head ready_playback_urbs;
+	struct tasklet_struct playback_tasklet;
+	wait_queue_head_t alsa_capture_wait;
+	wait_queue_head_t rate_feedback_wait;
+	wait_queue_head_t alsa_playback_wait;
+	struct ua101_stream {
+		struct snd_pcm_substream *substream;
+		unsigned int usb_pipe;
+		unsigned int channels;
+		unsigned int frame_bytes;
+		unsigned int max_packet_bytes;
+		unsigned int period_pos;
+		unsigned int buffer_pos;
+		unsigned int queue_length;
+		struct ua101_urb {
+			struct urb urb;
+			struct usb_iso_packet_descriptor iso_frame_desc[1];
+			struct list_head ready_list;
+		} *urbs[MAX_QUEUE_LENGTH];
+		struct {
+			unsigned int size;
+			void *addr;
+			dma_addr_t dma;
+		} buffers[MAX_MEMORY_BUFFERS];
+	} capture, playback;
+};
+
+static DEFINE_MUTEX(devices_mutex);
+static unsigned int devices_used;
+static struct usb_driver ua101_driver;
+
+static void abort_alsa_playback(struct ua101 *ua);
+static void abort_alsa_capture(struct ua101 *ua);
+
+static const char *usb_error_string(int err)
+{
+	switch (err) {
+	case -ENODEV:
+		return "no device";
+	case -ENOENT:
+		return "endpoint not enabled";
+	case -EPIPE:
+		return "endpoint stalled";
+	case -ENOSPC:
+		return "not enough bandwidth";
+	case -ESHUTDOWN:
+		return "device disabled";
+	case -EHOSTUNREACH:
+		return "device suspended";
+	case -EINVAL:
+	case -EAGAIN:
+	case -EFBIG:
+	case -EMSGSIZE:
+		return "internal error";
+	default:
+		return "unknown error";
+	}
+}
+
+static void abort_usb_capture(struct ua101 *ua)
+{
+	if (test_and_clear_bit(USB_CAPTURE_RUNNING, &ua->states)) {
+		wake_up(&ua->alsa_capture_wait);
+		wake_up(&ua->rate_feedback_wait);
+	}
+}
+
+static void abort_usb_playback(struct ua101 *ua)
+{
+	if (test_and_clear_bit(USB_PLAYBACK_RUNNING, &ua->states))
+		wake_up(&ua->alsa_playback_wait);
+}
+
+static void playback_urb_complete(struct urb *usb_urb)
+{
+	struct ua101_urb *urb = (struct ua101_urb *)usb_urb;
+	struct ua101 *ua = urb->urb.context;
+	unsigned long flags;
+
+	if (unlikely(urb->urb.status == -ENOENT ||	/* unlinked */
+		     urb->urb.status == -ENODEV ||	/* device removed */
+		     urb->urb.status == -ECONNRESET ||	/* unlinked */
+		     urb->urb.status == -ESHUTDOWN)) {	/* device disabled */
+		abort_usb_playback(ua);
+		abort_alsa_playback(ua);
+		return;
+	}
+
+	if (test_bit(USB_PLAYBACK_RUNNING, &ua->states)) {
+		/* append URB to FIFO */
+		spin_lock_irqsave(&ua->lock, flags);
+		list_add_tail(&urb->ready_list, &ua->ready_playback_urbs);
+		if (ua->rate_feedback_count > 0)
+			tasklet_schedule(&ua->playback_tasklet);
+		ua->playback.substream->runtime->delay -=
+				urb->urb.iso_frame_desc[0].length /
+						ua->playback.frame_bytes;
+		spin_unlock_irqrestore(&ua->lock, flags);
+	}
+}
+
+static void first_playback_urb_complete(struct urb *urb)
+{
+	struct ua101 *ua = urb->context;
+
+	urb->complete = playback_urb_complete;
+	playback_urb_complete(urb);
+
+	set_bit(PLAYBACK_URB_COMPLETED, &ua->states);
+	wake_up(&ua->alsa_playback_wait);
+}
+
+/* copy data from the ALSA ring buffer into the URB buffer */
+static bool copy_playback_data(struct ua101_stream *stream, struct urb *urb,
+			       unsigned int frames)
+{
+	struct snd_pcm_runtime *runtime;
+	unsigned int frame_bytes, frames1;
+	const u8 *source;
+
+	runtime = stream->substream->runtime;
+	frame_bytes = stream->frame_bytes;
+	source = runtime->dma_area + stream->buffer_pos * frame_bytes;
+	if (stream->buffer_pos + frames <= runtime->buffer_size) {
+		memcpy(urb->transfer_buffer, source, frames * frame_bytes);
+	} else {
+		/* wrap around at end of ring buffer */
+		frames1 = runtime->buffer_size - stream->buffer_pos;
+		memcpy(urb->transfer_buffer, source, frames1 * frame_bytes);
+		memcpy(urb->transfer_buffer + frames1 * frame_bytes,
+		       runtime->dma_area, (frames - frames1) * frame_bytes);
+	}
+
+	stream->buffer_pos += frames;
+	if (stream->buffer_pos >= runtime->buffer_size)
+		stream->buffer_pos -= runtime->buffer_size;
+	stream->period_pos += frames;
+	if (stream->period_pos >= runtime->period_size) {
+		stream->period_pos -= runtime->period_size;
+		return true;
+	}
+	return false;
+}
+
+static inline void add_with_wraparound(struct ua101 *ua,
+				       unsigned int *value, unsigned int add)
+{
+	*value += add;
+	if (*value >= ua->playback.queue_length)
+		*value -= ua->playback.queue_length;
+}
+
+static void playback_tasklet(unsigned long data)
+{
+	struct ua101 *ua = (void *)data;
+	unsigned long flags;
+	unsigned int frames;
+	struct ua101_urb *urb;
+	bool do_period_elapsed = false;
+	int err;
+
+	if (unlikely(!test_bit(USB_PLAYBACK_RUNNING, &ua->states)))
+		return;
+
+	/*
+	 * Synchronizing the playback rate to the capture rate is done by using
+	 * the same sequence of packet sizes for both streams.
+	 * Submitting a playback URB therefore requires both a ready URB and
+	 * the size of the corresponding capture packet, i.e., both playback
+	 * and capture URBs must have been completed.  Since the USB core does
+	 * not guarantee that playback and capture complete callbacks are
+	 * called alternately, we use two FIFOs for packet sizes and read URBs;
+	 * submitting playback URBs is possible as long as both FIFOs are
+	 * nonempty.
+	 */
+	spin_lock_irqsave(&ua->lock, flags);
+	while (ua->rate_feedback_count > 0 &&
+	       !list_empty(&ua->ready_playback_urbs)) {
+		/* take packet size out of FIFO */
+		frames = ua->rate_feedback[ua->rate_feedback_start];
+		add_with_wraparound(ua, &ua->rate_feedback_start, 1);
+		ua->rate_feedback_count--;
+
+		/* take URB out of FIFO */
+		urb = list_first_entry(&ua->ready_playback_urbs,
+				       struct ua101_urb, ready_list);
+		list_del(&urb->ready_list);
+
+		/* fill packet with data or silence */
+		urb->urb.iso_frame_desc[0].length =
+			frames * ua->playback.frame_bytes;
+		if (test_bit(ALSA_PLAYBACK_RUNNING, &ua->states))
+			do_period_elapsed |= copy_playback_data(&ua->playback,
+								&urb->urb,
+								frames);
+		else
+			memset(urb->urb.transfer_buffer, 0,
+			       urb->urb.iso_frame_desc[0].length);
+
+		/* and off you go ... */
+		err = usb_submit_urb(&urb->urb, GFP_ATOMIC);
+		if (unlikely(err < 0)) {
+			spin_unlock_irqrestore(&ua->lock, flags);
+			abort_usb_playback(ua);
+			abort_alsa_playback(ua);
+			dev_err(&ua->dev->dev, "USB request error %d: %s\n",
+				err, usb_error_string(err));
+			return;
+		}
+		ua->playback.substream->runtime->delay += frames;
+	}
+	spin_unlock_irqrestore(&ua->lock, flags);
+	if (do_period_elapsed)
+		snd_pcm_period_elapsed(ua->playback.substream);
+}
+
+/* copy data from the URB buffer into the ALSA ring buffer */
+static bool copy_capture_data(struct ua101_stream *stream, struct urb *urb,
+			      unsigned int frames)
+{
+	struct snd_pcm_runtime *runtime;
+	unsigned int frame_bytes, frames1;
+	u8 *dest;
+
+	runtime = stream->substream->runtime;
+	frame_bytes = stream->frame_bytes;
+	dest = runtime->dma_area + stream->buffer_pos * frame_bytes;
+	if (stream->buffer_pos + frames <= runtime->buffer_size) {
+		memcpy(dest, urb->transfer_buffer, frames * frame_bytes);
+	} else {
+		/* wrap around at end of ring buffer */
+		frames1 = runtime->buffer_size - stream->buffer_pos;
+		memcpy(dest, urb->transfer_buffer, frames1 * frame_bytes);
+		memcpy(runtime->dma_area,
+		       urb->transfer_buffer + frames1 * frame_bytes,
+		       (frames - frames1) * frame_bytes);
+	}
+
+	stream->buffer_pos += frames;
+	if (stream->buffer_pos >= runtime->buffer_size)
+		stream->buffer_pos -= runtime->buffer_size;
+	stream->period_pos += frames;
+	if (stream->period_pos >= runtime->period_size) {
+		stream->period_pos -= runtime->period_size;
+		return true;
+	}
+	return false;
+}
+
+static void capture_urb_complete(struct urb *urb)
+{
+	struct ua101 *ua = urb->context;
+	struct ua101_stream *stream = &ua->capture;
+	unsigned long flags;
+	unsigned int frames, write_ptr;
+	bool do_period_elapsed;
+	int err;
+
+	if (unlikely(urb->status == -ENOENT ||		/* unlinked */
+		     urb->status == -ENODEV ||		/* device removed */
+		     urb->status == -ECONNRESET ||	/* unlinked */
+		     urb->status == -ESHUTDOWN))	/* device disabled */
+		goto stream_stopped;
+
+	if (urb->status >= 0 && urb->iso_frame_desc[0].status >= 0)
+		frames = urb->iso_frame_desc[0].actual_length /
+			stream->frame_bytes;
+	else
+		frames = 0;
+
+	spin_lock_irqsave(&ua->lock, flags);
+
+	if (frames > 0 && test_bit(ALSA_CAPTURE_RUNNING, &ua->states))
+		do_period_elapsed = copy_capture_data(stream, urb, frames);
+	else
+		do_period_elapsed = false;
+
+	if (test_bit(USB_CAPTURE_RUNNING, &ua->states)) {
+		err = usb_submit_urb(urb, GFP_ATOMIC);
+		if (unlikely(err < 0)) {
+			spin_unlock_irqrestore(&ua->lock, flags);
+			dev_err(&ua->dev->dev, "USB request error %d: %s\n",
+				err, usb_error_string(err));
+			goto stream_stopped;
+		}
+
+		/* append packet size to FIFO */
+		write_ptr = ua->rate_feedback_start;
+		add_with_wraparound(ua, &write_ptr, ua->rate_feedback_count);
+		ua->rate_feedback[write_ptr] = frames;
+		if (ua->rate_feedback_count < ua->playback.queue_length) {
+			ua->rate_feedback_count++;
+			if (ua->rate_feedback_count ==
+						ua->playback.queue_length)
+				wake_up(&ua->rate_feedback_wait);
+		} else {
+			/*
+			 * Ring buffer overflow; this happens when the playback
+			 * stream is not running.  Throw away the oldest entry,
+			 * so that the playback stream, when it starts, sees
+			 * the most recent packet sizes.
+			 */
+			add_with_wraparound(ua, &ua->rate_feedback_start, 1);
+		}
+		if (test_bit(USB_PLAYBACK_RUNNING, &ua->states) &&
+		    !list_empty(&ua->ready_playback_urbs))
+			tasklet_schedule(&ua->playback_tasklet);
+	}
+
+	spin_unlock_irqrestore(&ua->lock, flags);
+
+	if (do_period_elapsed)
+		snd_pcm_period_elapsed(stream->substream);
+
+	return;
+
+stream_stopped:
+	abort_usb_playback(ua);
+	abort_usb_capture(ua);
+	abort_alsa_playback(ua);
+	abort_alsa_capture(ua);
+}
+
+static void first_capture_urb_complete(struct urb *urb)
+{
+	struct ua101 *ua = urb->context;
+
+	urb->complete = capture_urb_complete;
+	capture_urb_complete(urb);
+
+	set_bit(CAPTURE_URB_COMPLETED, &ua->states);
+	wake_up(&ua->alsa_capture_wait);
+}
+
+static int submit_stream_urbs(struct ua101 *ua, struct ua101_stream *stream)
+{
+	unsigned int i;
+
+	for (i = 0; i < stream->queue_length; ++i) {
+		int err = usb_submit_urb(&stream->urbs[i]->urb, GFP_KERNEL);
+		if (err < 0) {
+			dev_err(&ua->dev->dev, "USB request error %d: %s\n",
+				err, usb_error_string(err));
+			return err;
+		}
+	}
+	return 0;
+}
+
+static void kill_stream_urbs(struct ua101_stream *stream)
+{
+	unsigned int i;
+
+	for (i = 0; i < stream->queue_length; ++i)
+		if (stream->urbs[i])
+			usb_kill_urb(&stream->urbs[i]->urb);
+}
+
+static int enable_iso_interface(struct ua101 *ua, unsigned int intf_index)
+{
+	struct usb_host_interface *alts;
+
+	alts = ua->intf[intf_index]->cur_altsetting;
+	if (alts->desc.bAlternateSetting != 1) {
+		int err = usb_set_interface(ua->dev,
+					    alts->desc.bInterfaceNumber, 1);
+		if (err < 0) {
+			dev_err(&ua->dev->dev,
+				"cannot initialize interface; error %d: %s\n",
+				err, usb_error_string(err));
+			return err;
+		}
+	}
+	return 0;
+}
+
+static void disable_iso_interface(struct ua101 *ua, unsigned int intf_index)
+{
+	struct usb_host_interface *alts;
+
+	if (!ua->intf[intf_index])
+		return;
+
+	alts = ua->intf[intf_index]->cur_altsetting;
+	if (alts->desc.bAlternateSetting != 0) {
+		int err = usb_set_interface(ua->dev,
+					    alts->desc.bInterfaceNumber, 0);
+		if (err < 0 && !test_bit(DISCONNECTED, &ua->states))
+			dev_warn(&ua->dev->dev,
+				 "interface reset failed; error %d: %s\n",
+				 err, usb_error_string(err));
+	}
+}
+
+static void stop_usb_capture(struct ua101 *ua)
+{
+	clear_bit(USB_CAPTURE_RUNNING, &ua->states);
+
+	kill_stream_urbs(&ua->capture);
+
+	disable_iso_interface(ua, INTF_CAPTURE);
+}
+
+static int start_usb_capture(struct ua101 *ua)
+{
+	int err;
+
+	if (test_bit(DISCONNECTED, &ua->states))
+		return -ENODEV;
+
+	if (test_bit(USB_CAPTURE_RUNNING, &ua->states))
+		return 0;
+
+	kill_stream_urbs(&ua->capture);
+
+	err = enable_iso_interface(ua, INTF_CAPTURE);
+	if (err < 0)
+		return err;
+
+	clear_bit(CAPTURE_URB_COMPLETED, &ua->states);
+	ua->capture.urbs[0]->urb.complete = first_capture_urb_complete;
+	ua->rate_feedback_start = 0;
+	ua->rate_feedback_count = 0;
+
+	set_bit(USB_CAPTURE_RUNNING, &ua->states);
+	err = submit_stream_urbs(ua, &ua->capture);
+	if (err < 0)
+		stop_usb_capture(ua);
+	return err;
+}
+
+static void stop_usb_playback(struct ua101 *ua)
+{
+	clear_bit(USB_PLAYBACK_RUNNING, &ua->states);
+
+	kill_stream_urbs(&ua->playback);
+
+	tasklet_kill(&ua->playback_tasklet);
+
+	disable_iso_interface(ua, INTF_PLAYBACK);
+}
+
+static int start_usb_playback(struct ua101 *ua)
+{
+	unsigned int i, frames;
+	struct urb *urb;
+	int err = 0;
+
+	if (test_bit(DISCONNECTED, &ua->states))
+		return -ENODEV;
+
+	if (test_bit(USB_PLAYBACK_RUNNING, &ua->states))
+		return 0;
+
+	kill_stream_urbs(&ua->playback);
+	tasklet_kill(&ua->playback_tasklet);
+
+	err = enable_iso_interface(ua, INTF_PLAYBACK);
+	if (err < 0)
+		return err;
+
+	clear_bit(PLAYBACK_URB_COMPLETED, &ua->states);
+	ua->playback.urbs[0]->urb.complete =
+		first_playback_urb_complete;
+	spin_lock_irq(&ua->lock);
+	INIT_LIST_HEAD(&ua->ready_playback_urbs);
+	spin_unlock_irq(&ua->lock);
+
+	/*
+	 * We submit the initial URBs all at once, so we have to wait for the
+	 * packet size FIFO to be full.
+	 */
+	wait_event(ua->rate_feedback_wait,
+		   ua->rate_feedback_count >= ua->playback.queue_length ||
+		   !test_bit(USB_CAPTURE_RUNNING, &ua->states) ||
+		   test_bit(DISCONNECTED, &ua->states));
+	if (test_bit(DISCONNECTED, &ua->states)) {
+		stop_usb_playback(ua);
+		return -ENODEV;
+	}
+	if (!test_bit(USB_CAPTURE_RUNNING, &ua->states)) {
+		stop_usb_playback(ua);
+		return -EIO;
+	}
+
+	for (i = 0; i < ua->playback.queue_length; ++i) {
+		/* all initial URBs contain silence */
+		spin_lock_irq(&ua->lock);
+		frames = ua->rate_feedback[ua->rate_feedback_start];
+		add_with_wraparound(ua, &ua->rate_feedback_start, 1);
+		ua->rate_feedback_count--;
+		spin_unlock_irq(&ua->lock);
+		urb = &ua->playback.urbs[i]->urb;
+		urb->iso_frame_desc[0].length =
+			frames * ua->playback.frame_bytes;
+		memset(urb->transfer_buffer, 0,
+		       urb->iso_frame_desc[0].length);
+	}
+
+	set_bit(USB_PLAYBACK_RUNNING, &ua->states);
+	err = submit_stream_urbs(ua, &ua->playback);
+	if (err < 0)
+		stop_usb_playback(ua);
+	return err;
+}
+
+static void abort_alsa_capture(struct ua101 *ua)
+{
+	if (test_bit(ALSA_CAPTURE_RUNNING, &ua->states))
+		snd_pcm_stop_xrun(ua->capture.substream);
+}
+
+static void abort_alsa_playback(struct ua101 *ua)
+{
+	if (test_bit(ALSA_PLAYBACK_RUNNING, &ua->states))
+		snd_pcm_stop_xrun(ua->playback.substream);
+}
+
+static int set_stream_hw(struct ua101 *ua, struct snd_pcm_substream *substream,
+			 unsigned int channels)
+{
+	int err;
+
+	substream->runtime->hw.info =
+		SNDRV_PCM_INFO_MMAP |
+		SNDRV_PCM_INFO_MMAP_VALID |
+		SNDRV_PCM_INFO_BATCH |
+		SNDRV_PCM_INFO_INTERLEAVED |
+		SNDRV_PCM_INFO_BLOCK_TRANSFER |
+		SNDRV_PCM_INFO_FIFO_IN_FRAMES;
+	substream->runtime->hw.formats = ua->format_bit;
+	substream->runtime->hw.rates = snd_pcm_rate_to_rate_bit(ua->rate);
+	substream->runtime->hw.rate_min = ua->rate;
+	substream->runtime->hw.rate_max = ua->rate;
+	substream->runtime->hw.channels_min = channels;
+	substream->runtime->hw.channels_max = channels;
+	substream->runtime->hw.buffer_bytes_max = 45000 * 1024;
+	substream->runtime->hw.period_bytes_min = 1;
+	substream->runtime->hw.period_bytes_max = UINT_MAX;
+	substream->runtime->hw.periods_min = 2;
+	substream->runtime->hw.periods_max = UINT_MAX;
+	err = snd_pcm_hw_constraint_minmax(substream->runtime,
+					   SNDRV_PCM_HW_PARAM_PERIOD_TIME,
+					   1500000 / ua->packets_per_second,
+					   UINT_MAX);
+	if (err < 0)
+		return err;
+	err = snd_pcm_hw_constraint_msbits(substream->runtime, 0, 32, 24);
+	return err;
+}
+
+static int capture_pcm_open(struct snd_pcm_substream *substream)
+{
+	struct ua101 *ua = substream->private_data;
+	int err;
+
+	ua->capture.substream = substream;
+	err = set_stream_hw(ua, substream, ua->capture.channels);
+	if (err < 0)
+		return err;
+	substream->runtime->hw.fifo_size =
+		DIV_ROUND_CLOSEST(ua->rate, ua->packets_per_second);
+	substream->runtime->delay = substream->runtime->hw.fifo_size;
+
+	mutex_lock(&ua->mutex);
+	err = start_usb_capture(ua);
+	if (err >= 0)
+		set_bit(ALSA_CAPTURE_OPEN, &ua->states);
+	mutex_unlock(&ua->mutex);
+	return err;
+}
+
+static int playback_pcm_open(struct snd_pcm_substream *substream)
+{
+	struct ua101 *ua = substream->private_data;
+	int err;
+
+	ua->playback.substream = substream;
+	err = set_stream_hw(ua, substream, ua->playback.channels);
+	if (err < 0)
+		return err;
+	substream->runtime->hw.fifo_size =
+		DIV_ROUND_CLOSEST(ua->rate * ua->playback.queue_length,
+				  ua->packets_per_second);
+
+	mutex_lock(&ua->mutex);
+	err = start_usb_capture(ua);
+	if (err < 0)
+		goto error;
+	err = start_usb_playback(ua);
+	if (err < 0) {
+		if (!test_bit(ALSA_CAPTURE_OPEN, &ua->states))
+			stop_usb_capture(ua);
+		goto error;
+	}
+	set_bit(ALSA_PLAYBACK_OPEN, &ua->states);
+error:
+	mutex_unlock(&ua->mutex);
+	return err;
+}
+
+static int capture_pcm_close(struct snd_pcm_substream *substream)
+{
+	struct ua101 *ua = substream->private_data;
+
+	mutex_lock(&ua->mutex);
+	clear_bit(ALSA_CAPTURE_OPEN, &ua->states);
+	if (!test_bit(ALSA_PLAYBACK_OPEN, &ua->states))
+		stop_usb_capture(ua);
+	mutex_unlock(&ua->mutex);
+	return 0;
+}
+
+static int playback_pcm_close(struct snd_pcm_substream *substream)
+{
+	struct ua101 *ua = substream->private_data;
+
+	mutex_lock(&ua->mutex);
+	stop_usb_playback(ua);
+	clear_bit(ALSA_PLAYBACK_OPEN, &ua->states);
+	if (!test_bit(ALSA_CAPTURE_OPEN, &ua->states))
+		stop_usb_capture(ua);
+	mutex_unlock(&ua->mutex);
+	return 0;
+}
+
+static int capture_pcm_hw_params(struct snd_pcm_substream *substream,
+				 struct snd_pcm_hw_params *hw_params)
+{
+	struct ua101 *ua = substream->private_data;
+	int err;
+
+	mutex_lock(&ua->mutex);
+	err = start_usb_capture(ua);
+	mutex_unlock(&ua->mutex);
+	if (err < 0)
+		return err;
+
+	return snd_pcm_lib_alloc_vmalloc_buffer(substream,
+						params_buffer_bytes(hw_params));
+}
+
+static int playback_pcm_hw_params(struct snd_pcm_substream *substream,
+				  struct snd_pcm_hw_params *hw_params)
+{
+	struct ua101 *ua = substream->private_data;
+	int err;
+
+	mutex_lock(&ua->mutex);
+	err = start_usb_capture(ua);
+	if (err >= 0)
+		err = start_usb_playback(ua);
+	mutex_unlock(&ua->mutex);
+	if (err < 0)
+		return err;
+
+	return snd_pcm_lib_alloc_vmalloc_buffer(substream,
+						params_buffer_bytes(hw_params));
+}
+
+static int ua101_pcm_hw_free(struct snd_pcm_substream *substream)
+{
+	return snd_pcm_lib_free_vmalloc_buffer(substream);
+}
+
+static int capture_pcm_prepare(struct snd_pcm_substream *substream)
+{
+	struct ua101 *ua = substream->private_data;
+	int err;
+
+	mutex_lock(&ua->mutex);
+	err = start_usb_capture(ua);
+	mutex_unlock(&ua->mutex);
+	if (err < 0)
+		return err;
+
+	/*
+	 * The EHCI driver schedules the first packet of an iso stream at 10 ms
+	 * in the future, i.e., no data is actually captured for that long.
+	 * Take the wait here so that the stream is known to be actually
+	 * running when the start trigger has been called.
+	 */
+	wait_event(ua->alsa_capture_wait,
+		   test_bit(CAPTURE_URB_COMPLETED, &ua->states) ||
+		   !test_bit(USB_CAPTURE_RUNNING, &ua->states));
+	if (test_bit(DISCONNECTED, &ua->states))
+		return -ENODEV;
+	if (!test_bit(USB_CAPTURE_RUNNING, &ua->states))
+		return -EIO;
+
+	ua->capture.period_pos = 0;
+	ua->capture.buffer_pos = 0;
+	return 0;
+}
+
+static int playback_pcm_prepare(struct snd_pcm_substream *substream)
+{
+	struct ua101 *ua = substream->private_data;
+	int err;
+
+	mutex_lock(&ua->mutex);
+	err = start_usb_capture(ua);
+	if (err >= 0)
+		err = start_usb_playback(ua);
+	mutex_unlock(&ua->mutex);
+	if (err < 0)
+		return err;
+
+	/* see the comment in capture_pcm_prepare() */
+	wait_event(ua->alsa_playback_wait,
+		   test_bit(PLAYBACK_URB_COMPLETED, &ua->states) ||
+		   !test_bit(USB_PLAYBACK_RUNNING, &ua->states));
+	if (test_bit(DISCONNECTED, &ua->states))
+		return -ENODEV;
+	if (!test_bit(USB_PLAYBACK_RUNNING, &ua->states))
+		return -EIO;
+
+	substream->runtime->delay = 0;
+	ua->playback.period_pos = 0;
+	ua->playback.buffer_pos = 0;
+	return 0;
+}
+
+static int capture_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+	struct ua101 *ua = substream->private_data;
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+		if (!test_bit(USB_CAPTURE_RUNNING, &ua->states))
+			return -EIO;
+		set_bit(ALSA_CAPTURE_RUNNING, &ua->states);
+		return 0;
+	case SNDRV_PCM_TRIGGER_STOP:
+		clear_bit(ALSA_CAPTURE_RUNNING, &ua->states);
+		return 0;
+	default:
+		return -EINVAL;
+	}
+}
+
+static int playback_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+	struct ua101 *ua = substream->private_data;
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+		if (!test_bit(USB_PLAYBACK_RUNNING, &ua->states))
+			return -EIO;
+		set_bit(ALSA_PLAYBACK_RUNNING, &ua->states);
+		return 0;
+	case SNDRV_PCM_TRIGGER_STOP:
+		clear_bit(ALSA_PLAYBACK_RUNNING, &ua->states);
+		return 0;
+	default:
+		return -EINVAL;
+	}
+}
+
+static inline snd_pcm_uframes_t ua101_pcm_pointer(struct ua101 *ua,
+						  struct ua101_stream *stream)
+{
+	unsigned long flags;
+	unsigned int pos;
+
+	spin_lock_irqsave(&ua->lock, flags);
+	pos = stream->buffer_pos;
+	spin_unlock_irqrestore(&ua->lock, flags);
+	return pos;
+}
+
+static snd_pcm_uframes_t capture_pcm_pointer(struct snd_pcm_substream *subs)
+{
+	struct ua101 *ua = subs->private_data;
+
+	return ua101_pcm_pointer(ua, &ua->capture);
+}
+
+static snd_pcm_uframes_t playback_pcm_pointer(struct snd_pcm_substream *subs)
+{
+	struct ua101 *ua = subs->private_data;
+
+	return ua101_pcm_pointer(ua, &ua->playback);
+}
+
+static const struct snd_pcm_ops capture_pcm_ops = {
+	.open = capture_pcm_open,
+	.close = capture_pcm_close,
+	.ioctl = snd_pcm_lib_ioctl,
+	.hw_params = capture_pcm_hw_params,
+	.hw_free = ua101_pcm_hw_free,
+	.prepare = capture_pcm_prepare,
+	.trigger = capture_pcm_trigger,
+	.pointer = capture_pcm_pointer,
+	.page = snd_pcm_lib_get_vmalloc_page,
+};
+
+static const struct snd_pcm_ops playback_pcm_ops = {
+	.open = playback_pcm_open,
+	.close = playback_pcm_close,
+	.ioctl = snd_pcm_lib_ioctl,
+	.hw_params = playback_pcm_hw_params,
+	.hw_free = ua101_pcm_hw_free,
+	.prepare = playback_pcm_prepare,
+	.trigger = playback_pcm_trigger,
+	.pointer = playback_pcm_pointer,
+	.page = snd_pcm_lib_get_vmalloc_page,
+};
+
+static const struct uac_format_type_i_discrete_descriptor *
+find_format_descriptor(struct usb_interface *interface)
+{
+	struct usb_host_interface *alt;
+	u8 *extra;
+	int extralen;
+
+	if (interface->num_altsetting != 2) {
+		dev_err(&interface->dev, "invalid num_altsetting\n");
+		return NULL;
+	}
+
+	alt = &interface->altsetting[0];
+	if (alt->desc.bNumEndpoints != 0) {
+		dev_err(&interface->dev, "invalid bNumEndpoints\n");
+		return NULL;
+	}
+
+	alt = &interface->altsetting[1];
+	if (alt->desc.bNumEndpoints != 1) {
+		dev_err(&interface->dev, "invalid bNumEndpoints\n");
+		return NULL;
+	}
+
+	extra = alt->extra;
+	extralen = alt->extralen;
+	while (extralen >= sizeof(struct usb_descriptor_header)) {
+		struct uac_format_type_i_discrete_descriptor *desc;
+
+		desc = (struct uac_format_type_i_discrete_descriptor *)extra;
+		if (desc->bLength > extralen) {
+			dev_err(&interface->dev, "descriptor overflow\n");
+			return NULL;
+		}
+		if (desc->bLength == UAC_FORMAT_TYPE_I_DISCRETE_DESC_SIZE(1) &&
+		    desc->bDescriptorType == USB_DT_CS_INTERFACE &&
+		    desc->bDescriptorSubtype == UAC_FORMAT_TYPE) {
+			if (desc->bFormatType != UAC_FORMAT_TYPE_I_PCM ||
+			    desc->bSamFreqType != 1) {
+				dev_err(&interface->dev,
+					"invalid format type\n");
+				return NULL;
+			}
+			return desc;
+		}
+		extralen -= desc->bLength;
+		extra += desc->bLength;
+	}
+	dev_err(&interface->dev, "sample format descriptor not found\n");
+	return NULL;
+}
+
+static int detect_usb_format(struct ua101 *ua)
+{
+	const struct uac_format_type_i_discrete_descriptor *fmt_capture;
+	const struct uac_format_type_i_discrete_descriptor *fmt_playback;
+	const struct usb_endpoint_descriptor *epd;
+	unsigned int rate2;
+
+	fmt_capture = find_format_descriptor(ua->intf[INTF_CAPTURE]);
+	fmt_playback = find_format_descriptor(ua->intf[INTF_PLAYBACK]);
+	if (!fmt_capture || !fmt_playback)
+		return -ENXIO;
+
+	switch (fmt_capture->bSubframeSize) {
+	case 3:
+		ua->format_bit = SNDRV_PCM_FMTBIT_S24_3LE;
+		break;
+	case 4:
+		ua->format_bit = SNDRV_PCM_FMTBIT_S32_LE;
+		break;
+	default:
+		dev_err(&ua->dev->dev, "sample width is not 24 or 32 bits\n");
+		return -ENXIO;
+	}
+	if (fmt_capture->bSubframeSize != fmt_playback->bSubframeSize) {
+		dev_err(&ua->dev->dev,
+			"playback/capture sample widths do not match\n");
+		return -ENXIO;
+	}
+
+	if (fmt_capture->bBitResolution != 24 ||
+	    fmt_playback->bBitResolution != 24) {
+		dev_err(&ua->dev->dev, "sample width is not 24 bits\n");
+		return -ENXIO;
+	}
+
+	ua->rate = combine_triple(fmt_capture->tSamFreq[0]);
+	rate2 = combine_triple(fmt_playback->tSamFreq[0]);
+	if (ua->rate != rate2) {
+		dev_err(&ua->dev->dev,
+			"playback/capture rates do not match: %u/%u\n",
+			rate2, ua->rate);
+		return -ENXIO;
+	}
+
+	switch (ua->dev->speed) {
+	case USB_SPEED_FULL:
+		ua->packets_per_second = 1000;
+		break;
+	case USB_SPEED_HIGH:
+		ua->packets_per_second = 8000;
+		break;
+	default:
+		dev_err(&ua->dev->dev, "unknown device speed\n");
+		return -ENXIO;
+	}
+
+	ua->capture.channels = fmt_capture->bNrChannels;
+	ua->playback.channels = fmt_playback->bNrChannels;
+	ua->capture.frame_bytes =
+		fmt_capture->bSubframeSize * ua->capture.channels;
+	ua->playback.frame_bytes =
+		fmt_playback->bSubframeSize * ua->playback.channels;
+
+	epd = &ua->intf[INTF_CAPTURE]->altsetting[1].endpoint[0].desc;
+	if (!usb_endpoint_is_isoc_in(epd)) {
+		dev_err(&ua->dev->dev, "invalid capture endpoint\n");
+		return -ENXIO;
+	}
+	ua->capture.usb_pipe = usb_rcvisocpipe(ua->dev, usb_endpoint_num(epd));
+	ua->capture.max_packet_bytes = usb_endpoint_maxp(epd);
+
+	epd = &ua->intf[INTF_PLAYBACK]->altsetting[1].endpoint[0].desc;
+	if (!usb_endpoint_is_isoc_out(epd)) {
+		dev_err(&ua->dev->dev, "invalid playback endpoint\n");
+		return -ENXIO;
+	}
+	ua->playback.usb_pipe = usb_sndisocpipe(ua->dev, usb_endpoint_num(epd));
+	ua->playback.max_packet_bytes = usb_endpoint_maxp(epd);
+	return 0;
+}
+
+static int alloc_stream_buffers(struct ua101 *ua, struct ua101_stream *stream)
+{
+	unsigned int remaining_packets, packets, packets_per_page, i;
+	size_t size;
+
+	stream->queue_length = queue_length;
+	stream->queue_length = max(stream->queue_length,
+				   (unsigned int)MIN_QUEUE_LENGTH);
+	stream->queue_length = min(stream->queue_length,
+				   (unsigned int)MAX_QUEUE_LENGTH);
+
+	/*
+	 * The cache pool sizes used by usb_alloc_coherent() (128, 512, 2048) are
+	 * quite bad when used with the packet sizes of this device (e.g. 280,
+	 * 520, 624).  Therefore, we allocate and subdivide entire pages, using
+	 * a smaller buffer only for the last chunk.
+	 */
+	remaining_packets = stream->queue_length;
+	packets_per_page = PAGE_SIZE / stream->max_packet_bytes;
+	for (i = 0; i < ARRAY_SIZE(stream->buffers); ++i) {
+		packets = min(remaining_packets, packets_per_page);
+		size = packets * stream->max_packet_bytes;
+		stream->buffers[i].addr =
+			usb_alloc_coherent(ua->dev, size, GFP_KERNEL,
+					   &stream->buffers[i].dma);
+		if (!stream->buffers[i].addr)
+			return -ENOMEM;
+		stream->buffers[i].size = size;
+		remaining_packets -= packets;
+		if (!remaining_packets)
+			break;
+	}
+	if (remaining_packets) {
+		dev_err(&ua->dev->dev, "too many packets\n");
+		return -ENXIO;
+	}
+	return 0;
+}
+
+static void free_stream_buffers(struct ua101 *ua, struct ua101_stream *stream)
+{
+	unsigned int i;
+
+	for (i = 0; i < ARRAY_SIZE(stream->buffers); ++i)
+		usb_free_coherent(ua->dev,
+				  stream->buffers[i].size,
+				  stream->buffers[i].addr,
+				  stream->buffers[i].dma);
+}
+
+static int alloc_stream_urbs(struct ua101 *ua, struct ua101_stream *stream,
+			     void (*urb_complete)(struct urb *))
+{
+	unsigned max_packet_size = stream->max_packet_bytes;
+	struct ua101_urb *urb;
+	unsigned int b, u = 0;
+
+	for (b = 0; b < ARRAY_SIZE(stream->buffers); ++b) {
+		unsigned int size = stream->buffers[b].size;
+		u8 *addr = stream->buffers[b].addr;
+		dma_addr_t dma = stream->buffers[b].dma;
+
+		while (size >= max_packet_size) {
+			if (u >= stream->queue_length)
+				goto bufsize_error;
+			urb = kmalloc(sizeof(*urb), GFP_KERNEL);
+			if (!urb)
+				return -ENOMEM;
+			usb_init_urb(&urb->urb);
+			urb->urb.dev = ua->dev;
+			urb->urb.pipe = stream->usb_pipe;
+			urb->urb.transfer_flags = URB_NO_TRANSFER_DMA_MAP;
+			urb->urb.transfer_buffer = addr;
+			urb->urb.transfer_dma = dma;
+			urb->urb.transfer_buffer_length = max_packet_size;
+			urb->urb.number_of_packets = 1;
+			urb->urb.interval = 1;
+			urb->urb.context = ua;
+			urb->urb.complete = urb_complete;
+			urb->urb.iso_frame_desc[0].offset = 0;
+			urb->urb.iso_frame_desc[0].length = max_packet_size;
+			stream->urbs[u++] = urb;
+			size -= max_packet_size;
+			addr += max_packet_size;
+			dma += max_packet_size;
+		}
+	}
+	if (u == stream->queue_length)
+		return 0;
+bufsize_error:
+	dev_err(&ua->dev->dev, "internal buffer size error\n");
+	return -ENXIO;
+}
+
+static void free_stream_urbs(struct ua101_stream *stream)
+{
+	unsigned int i;
+
+	for (i = 0; i < stream->queue_length; ++i) {
+		kfree(stream->urbs[i]);
+		stream->urbs[i] = NULL;
+	}
+}
+
+static void free_usb_related_resources(struct ua101 *ua,
+				       struct usb_interface *interface)
+{
+	unsigned int i;
+	struct usb_interface *intf;
+
+	mutex_lock(&ua->mutex);
+	free_stream_urbs(&ua->capture);
+	free_stream_urbs(&ua->playback);
+	mutex_unlock(&ua->mutex);
+	free_stream_buffers(ua, &ua->capture);
+	free_stream_buffers(ua, &ua->playback);
+
+	for (i = 0; i < ARRAY_SIZE(ua->intf); ++i) {
+		mutex_lock(&ua->mutex);
+		intf = ua->intf[i];
+		ua->intf[i] = NULL;
+		mutex_unlock(&ua->mutex);
+		if (intf) {
+			usb_set_intfdata(intf, NULL);
+			if (intf != interface)
+				usb_driver_release_interface(&ua101_driver,
+							     intf);
+		}
+	}
+}
+
+static void ua101_card_free(struct snd_card *card)
+{
+	struct ua101 *ua = card->private_data;
+
+	mutex_destroy(&ua->mutex);
+}
+
+static int ua101_probe(struct usb_interface *interface,
+		       const struct usb_device_id *usb_id)
+{
+	static const struct snd_usb_midi_endpoint_info midi_ep = {
+		.out_cables = 0x0001,
+		.in_cables = 0x0001
+	};
+	static const struct snd_usb_audio_quirk midi_quirk = {
+		.type = QUIRK_MIDI_FIXED_ENDPOINT,
+		.data = &midi_ep
+	};
+	static const int intf_numbers[2][3] = {
+		{	/* UA-101 */
+			[INTF_PLAYBACK] = 0,
+			[INTF_CAPTURE] = 1,
+			[INTF_MIDI] = 2,
+		},
+		{	/* UA-1000 */
+			[INTF_CAPTURE] = 1,
+			[INTF_PLAYBACK] = 2,
+			[INTF_MIDI] = 3,
+		},
+	};
+	struct snd_card *card;
+	struct ua101 *ua;
+	unsigned int card_index, i;
+	int is_ua1000;
+	const char *name;
+	char usb_path[32];
+	int err;
+
+	is_ua1000 = usb_id->idProduct == 0x0044;
+
+	if (interface->altsetting->desc.bInterfaceNumber !=
+	    intf_numbers[is_ua1000][0])
+		return -ENODEV;
+
+	mutex_lock(&devices_mutex);
+
+	for (card_index = 0; card_index < SNDRV_CARDS; ++card_index)
+		if (enable[card_index] && !(devices_used & (1 << card_index)))
+			break;
+	if (card_index >= SNDRV_CARDS) {
+		mutex_unlock(&devices_mutex);
+		return -ENOENT;
+	}
+	err = snd_card_new(&interface->dev,
+			   index[card_index], id[card_index], THIS_MODULE,
+			   sizeof(*ua), &card);
+	if (err < 0) {
+		mutex_unlock(&devices_mutex);
+		return err;
+	}
+	card->private_free = ua101_card_free;
+	ua = card->private_data;
+	ua->dev = interface_to_usbdev(interface);
+	ua->card = card;
+	ua->card_index = card_index;
+	INIT_LIST_HEAD(&ua->midi_list);
+	spin_lock_init(&ua->lock);
+	mutex_init(&ua->mutex);
+	INIT_LIST_HEAD(&ua->ready_playback_urbs);
+	tasklet_init(&ua->playback_tasklet,
+		     playback_tasklet, (unsigned long)ua);
+	init_waitqueue_head(&ua->alsa_capture_wait);
+	init_waitqueue_head(&ua->rate_feedback_wait);
+	init_waitqueue_head(&ua->alsa_playback_wait);
+
+	ua->intf[0] = interface;
+	for (i = 1; i < ARRAY_SIZE(ua->intf); ++i) {
+		ua->intf[i] = usb_ifnum_to_if(ua->dev,
+					      intf_numbers[is_ua1000][i]);
+		if (!ua->intf[i]) {
+			dev_err(&ua->dev->dev, "interface %u not found\n",
+				intf_numbers[is_ua1000][i]);
+			err = -ENXIO;
+			goto probe_error;
+		}
+		err = usb_driver_claim_interface(&ua101_driver,
+						 ua->intf[i], ua);
+		if (err < 0) {
+			ua->intf[i] = NULL;
+			err = -EBUSY;
+			goto probe_error;
+		}
+	}
+
+	err = detect_usb_format(ua);
+	if (err < 0)
+		goto probe_error;
+
+	name = usb_id->idProduct == 0x0044 ? "UA-1000" : "UA-101";
+	strcpy(card->driver, "UA-101");
+	strcpy(card->shortname, name);
+	usb_make_path(ua->dev, usb_path, sizeof(usb_path));
+	snprintf(ua->card->longname, sizeof(ua->card->longname),
+		 "EDIROL %s (serial %s), %u Hz at %s, %s speed", name,
+		 ua->dev->serial ? ua->dev->serial : "?", ua->rate, usb_path,
+		 ua->dev->speed == USB_SPEED_HIGH ? "high" : "full");
+
+	err = alloc_stream_buffers(ua, &ua->capture);
+	if (err < 0)
+		goto probe_error;
+	err = alloc_stream_buffers(ua, &ua->playback);
+	if (err < 0)
+		goto probe_error;
+
+	err = alloc_stream_urbs(ua, &ua->capture, capture_urb_complete);
+	if (err < 0)
+		goto probe_error;
+	err = alloc_stream_urbs(ua, &ua->playback, playback_urb_complete);
+	if (err < 0)
+		goto probe_error;
+
+	err = snd_pcm_new(card, name, 0, 1, 1, &ua->pcm);
+	if (err < 0)
+		goto probe_error;
+	ua->pcm->private_data = ua;
+	strcpy(ua->pcm->name, name);
+	snd_pcm_set_ops(ua->pcm, SNDRV_PCM_STREAM_PLAYBACK, &playback_pcm_ops);
+	snd_pcm_set_ops(ua->pcm, SNDRV_PCM_STREAM_CAPTURE, &capture_pcm_ops);
+
+	err = snd_usbmidi_create(card, ua->intf[INTF_MIDI],
+				 &ua->midi_list, &midi_quirk);
+	if (err < 0)
+		goto probe_error;
+
+	err = snd_card_register(card);
+	if (err < 0)
+		goto probe_error;
+
+	usb_set_intfdata(interface, ua);
+	devices_used |= 1 << card_index;
+
+	mutex_unlock(&devices_mutex);
+	return 0;
+
+probe_error:
+	free_usb_related_resources(ua, interface);
+	snd_card_free(card);
+	mutex_unlock(&devices_mutex);
+	return err;
+}
+
+static void ua101_disconnect(struct usb_interface *interface)
+{
+	struct ua101 *ua = usb_get_intfdata(interface);
+	struct list_head *midi;
+
+	if (!ua)
+		return;
+
+	mutex_lock(&devices_mutex);
+
+	set_bit(DISCONNECTED, &ua->states);
+	wake_up(&ua->rate_feedback_wait);
+
+	/* make sure that userspace cannot create new requests */
+	snd_card_disconnect(ua->card);
+
+	/* make sure that there are no pending USB requests */
+	list_for_each(midi, &ua->midi_list)
+		snd_usbmidi_disconnect(midi);
+	abort_alsa_playback(ua);
+	abort_alsa_capture(ua);
+	mutex_lock(&ua->mutex);
+	stop_usb_playback(ua);
+	stop_usb_capture(ua);
+	mutex_unlock(&ua->mutex);
+
+	free_usb_related_resources(ua, interface);
+
+	devices_used &= ~(1 << ua->card_index);
+
+	snd_card_free_when_closed(ua->card);
+
+	mutex_unlock(&devices_mutex);
+}
+
+static const struct usb_device_id ua101_ids[] = {
+	{ USB_DEVICE(0x0582, 0x0044) }, /* UA-1000 high speed */
+	{ USB_DEVICE(0x0582, 0x007d) }, /* UA-101 high speed */
+	{ USB_DEVICE(0x0582, 0x008d) }, /* UA-101 full speed */
+	{ }
+};
+MODULE_DEVICE_TABLE(usb, ua101_ids);
+
+static struct usb_driver ua101_driver = {
+	.name = "snd-ua101",
+	.id_table = ua101_ids,
+	.probe = ua101_probe,
+	.disconnect = ua101_disconnect,
+#if 0
+	.suspend = ua101_suspend,
+	.resume = ua101_resume,
+#endif
+};
+
+module_usb_driver(ua101_driver);
diff --git a/sound/usb/mixer.c b/sound/usb/mixer.c
new file mode 100644
index 0000000..c63c84b
--- /dev/null
+++ b/sound/usb/mixer.c
@@ -0,0 +1,3601 @@
+/*
+ *   (Tentative) USB Audio Driver for ALSA
+ *
+ *   Mixer control part
+ *
+ *   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
+ *
+ */
+
+/*
+ * TODOs, for both the mixer and the streaming interfaces:
+ *
+ *  - support for UAC2 effect units
+ *  - support for graphical equalizers
+ *  - RANGE and MEM set commands (UAC2)
+ *  - RANGE and MEM interrupt dispatchers (UAC2)
+ *  - audio channel clustering (UAC2)
+ *  - audio sample rate converter units (UAC2)
+ *  - proper handling of clock multipliers (UAC2)
+ *  - dispatch clock change notifications (UAC2)
+ *  	- stop PCM streams which use a clock that became invalid
+ *  	- stop PCM streams which use a clock selector that has changed
+ *  	- parse available sample rates again when clock sources changed
+ */
+
+#include <linux/bitops.h>
+#include <linux/init.h>
+#include <linux/list.h>
+#include <linux/log2.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <linux/usb.h>
+#include <linux/usb/audio.h>
+#include <linux/usb/audio-v2.h>
+#include <linux/usb/audio-v3.h>
+
+#include <sound/core.h>
+#include <sound/control.h>
+#include <sound/hwdep.h>
+#include <sound/info.h>
+#include <sound/tlv.h>
+
+#include "usbaudio.h"
+#include "mixer.h"
+#include "helper.h"
+#include "mixer_quirks.h"
+#include "power.h"
+
+#define MAX_ID_ELEMS	256
+
+struct usb_audio_term {
+	int id;
+	int type;
+	int channels;
+	unsigned int chconfig;
+	int name;
+};
+
+struct usbmix_name_map;
+
+struct mixer_build {
+	struct snd_usb_audio *chip;
+	struct usb_mixer_interface *mixer;
+	unsigned char *buffer;
+	unsigned int buflen;
+	DECLARE_BITMAP(unitbitmap, MAX_ID_ELEMS);
+	struct usb_audio_term oterm;
+	const struct usbmix_name_map *map;
+	const struct usbmix_selector_map *selector_map;
+};
+
+/*E-mu 0202/0404/0204 eXtension Unit(XU) control*/
+enum {
+	USB_XU_CLOCK_RATE 		= 0xe301,
+	USB_XU_CLOCK_SOURCE		= 0xe302,
+	USB_XU_DIGITAL_IO_STATUS	= 0xe303,
+	USB_XU_DEVICE_OPTIONS		= 0xe304,
+	USB_XU_DIRECT_MONITORING	= 0xe305,
+	USB_XU_METERING			= 0xe306
+};
+enum {
+	USB_XU_CLOCK_SOURCE_SELECTOR = 0x02,	/* clock source*/
+	USB_XU_CLOCK_RATE_SELECTOR = 0x03,	/* clock rate */
+	USB_XU_DIGITAL_FORMAT_SELECTOR = 0x01,	/* the spdif format */
+	USB_XU_SOFT_LIMIT_SELECTOR = 0x03	/* soft limiter */
+};
+
+/*
+ * manual mapping of mixer names
+ * if the mixer topology is too complicated and the parsed names are
+ * ambiguous, add the entries in usbmixer_maps.c.
+ */
+#include "mixer_maps.c"
+
+static const struct usbmix_name_map *
+find_map(const struct usbmix_name_map *p, int unitid, int control)
+{
+	if (!p)
+		return NULL;
+
+	for (; p->id; p++) {
+		if (p->id == unitid &&
+		    (!control || !p->control || control == p->control))
+			return p;
+	}
+	return NULL;
+}
+
+/* get the mapped name if the unit matches */
+static int
+check_mapped_name(const struct usbmix_name_map *p, char *buf, int buflen)
+{
+	if (!p || !p->name)
+		return 0;
+
+	buflen--;
+	return strlcpy(buf, p->name, buflen);
+}
+
+/* ignore the error value if ignore_ctl_error flag is set */
+#define filter_error(cval, err) \
+	((cval)->head.mixer->ignore_ctl_error ? 0 : (err))
+
+/* check whether the control should be ignored */
+static inline int
+check_ignored_ctl(const struct usbmix_name_map *p)
+{
+	if (!p || p->name || p->dB)
+		return 0;
+	return 1;
+}
+
+/* dB mapping */
+static inline void check_mapped_dB(const struct usbmix_name_map *p,
+				   struct usb_mixer_elem_info *cval)
+{
+	if (p && p->dB) {
+		cval->dBmin = p->dB->min;
+		cval->dBmax = p->dB->max;
+		cval->initialized = 1;
+	}
+}
+
+/* get the mapped selector source name */
+static int check_mapped_selector_name(struct mixer_build *state, int unitid,
+				      int index, char *buf, int buflen)
+{
+	const struct usbmix_selector_map *p;
+
+	if (!state->selector_map)
+		return 0;
+	for (p = state->selector_map; p->id; p++) {
+		if (p->id == unitid && index < p->count)
+			return strlcpy(buf, p->names[index], buflen);
+	}
+	return 0;
+}
+
+/*
+ * find an audio control unit with the given unit id
+ */
+static void *find_audio_control_unit(struct mixer_build *state,
+				     unsigned char unit)
+{
+	/* we just parse the header */
+	struct uac_feature_unit_descriptor *hdr = NULL;
+
+	while ((hdr = snd_usb_find_desc(state->buffer, state->buflen, hdr,
+					USB_DT_CS_INTERFACE)) != NULL) {
+		if (hdr->bLength >= 4 &&
+		    hdr->bDescriptorSubtype >= UAC_INPUT_TERMINAL &&
+		    hdr->bDescriptorSubtype <= UAC3_SAMPLE_RATE_CONVERTER &&
+		    hdr->bUnitID == unit)
+			return hdr;
+	}
+
+	return NULL;
+}
+
+/*
+ * copy a string with the given id
+ */
+static int snd_usb_copy_string_desc(struct snd_usb_audio *chip,
+				    int index, char *buf, int maxlen)
+{
+	int len = usb_string(chip->dev, index, buf, maxlen - 1);
+
+	if (len < 0)
+		return 0;
+
+	buf[len] = 0;
+	return len;
+}
+
+/*
+ * convert from the byte/word on usb descriptor to the zero-based integer
+ */
+static int convert_signed_value(struct usb_mixer_elem_info *cval, int val)
+{
+	switch (cval->val_type) {
+	case USB_MIXER_BOOLEAN:
+		return !!val;
+	case USB_MIXER_INV_BOOLEAN:
+		return !val;
+	case USB_MIXER_U8:
+		val &= 0xff;
+		break;
+	case USB_MIXER_S8:
+		val &= 0xff;
+		if (val >= 0x80)
+			val -= 0x100;
+		break;
+	case USB_MIXER_U16:
+		val &= 0xffff;
+		break;
+	case USB_MIXER_S16:
+		val &= 0xffff;
+		if (val >= 0x8000)
+			val -= 0x10000;
+		break;
+	}
+	return val;
+}
+
+/*
+ * convert from the zero-based int to the byte/word for usb descriptor
+ */
+static int convert_bytes_value(struct usb_mixer_elem_info *cval, int val)
+{
+	switch (cval->val_type) {
+	case USB_MIXER_BOOLEAN:
+		return !!val;
+	case USB_MIXER_INV_BOOLEAN:
+		return !val;
+	case USB_MIXER_S8:
+	case USB_MIXER_U8:
+		return val & 0xff;
+	case USB_MIXER_S16:
+	case USB_MIXER_U16:
+		return val & 0xffff;
+	}
+	return 0; /* not reached */
+}
+
+static int get_relative_value(struct usb_mixer_elem_info *cval, int val)
+{
+	if (!cval->res)
+		cval->res = 1;
+	if (val < cval->min)
+		return 0;
+	else if (val >= cval->max)
+		return (cval->max - cval->min + cval->res - 1) / cval->res;
+	else
+		return (val - cval->min) / cval->res;
+}
+
+static int get_abs_value(struct usb_mixer_elem_info *cval, int val)
+{
+	if (val < 0)
+		return cval->min;
+	if (!cval->res)
+		cval->res = 1;
+	val *= cval->res;
+	val += cval->min;
+	if (val > cval->max)
+		return cval->max;
+	return val;
+}
+
+static int uac2_ctl_value_size(int val_type)
+{
+	switch (val_type) {
+	case USB_MIXER_S32:
+	case USB_MIXER_U32:
+		return 4;
+	case USB_MIXER_S16:
+	case USB_MIXER_U16:
+		return 2;
+	default:
+		return 1;
+	}
+	return 0; /* unreachable */
+}
+
+
+/*
+ * retrieve a mixer value
+ */
+
+static int get_ctl_value_v1(struct usb_mixer_elem_info *cval, int request,
+			    int validx, int *value_ret)
+{
+	struct snd_usb_audio *chip = cval->head.mixer->chip;
+	unsigned char buf[2];
+	int val_len = cval->val_type >= USB_MIXER_S16 ? 2 : 1;
+	int timeout = 10;
+	int idx = 0, err;
+
+	err = snd_usb_lock_shutdown(chip);
+	if (err < 0)
+		return -EIO;
+
+	while (timeout-- > 0) {
+		idx = snd_usb_ctrl_intf(chip) | (cval->head.id << 8);
+		err = snd_usb_ctl_msg(chip->dev, usb_rcvctrlpipe(chip->dev, 0), request,
+				      USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_IN,
+				      validx, idx, buf, val_len);
+		if (err >= val_len) {
+			*value_ret = convert_signed_value(cval, snd_usb_combine_bytes(buf, val_len));
+			err = 0;
+			goto out;
+		} else if (err == -ETIMEDOUT) {
+			goto out;
+		}
+	}
+	usb_audio_dbg(chip,
+		"cannot get ctl value: req = %#x, wValue = %#x, wIndex = %#x, type = %d\n",
+		request, validx, idx, cval->val_type);
+	err = -EINVAL;
+
+ out:
+	snd_usb_unlock_shutdown(chip);
+	return err;
+}
+
+static int get_ctl_value_v2(struct usb_mixer_elem_info *cval, int request,
+			    int validx, int *value_ret)
+{
+	struct snd_usb_audio *chip = cval->head.mixer->chip;
+	/* enough space for one range */
+	unsigned char buf[sizeof(__u16) + 3 * sizeof(__u32)];
+	unsigned char *val;
+	int idx = 0, ret, val_size, size;
+	__u8 bRequest;
+
+	val_size = uac2_ctl_value_size(cval->val_type);
+
+	if (request == UAC_GET_CUR) {
+		bRequest = UAC2_CS_CUR;
+		size = val_size;
+	} else {
+		bRequest = UAC2_CS_RANGE;
+		size = sizeof(__u16) + 3 * val_size;
+	}
+
+	memset(buf, 0, sizeof(buf));
+
+	ret = snd_usb_lock_shutdown(chip) ? -EIO : 0;
+	if (ret)
+		goto error;
+
+	idx = snd_usb_ctrl_intf(chip) | (cval->head.id << 8);
+	ret = snd_usb_ctl_msg(chip->dev, usb_rcvctrlpipe(chip->dev, 0), bRequest,
+			      USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_IN,
+			      validx, idx, buf, size);
+	snd_usb_unlock_shutdown(chip);
+
+	if (ret < 0) {
+error:
+		usb_audio_err(chip,
+			"cannot get ctl value: req = %#x, wValue = %#x, wIndex = %#x, type = %d\n",
+			request, validx, idx, cval->val_type);
+		return ret;
+	}
+
+	/* FIXME: how should we handle multiple triplets here? */
+
+	switch (request) {
+	case UAC_GET_CUR:
+		val = buf;
+		break;
+	case UAC_GET_MIN:
+		val = buf + sizeof(__u16);
+		break;
+	case UAC_GET_MAX:
+		val = buf + sizeof(__u16) + val_size;
+		break;
+	case UAC_GET_RES:
+		val = buf + sizeof(__u16) + val_size * 2;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	*value_ret = convert_signed_value(cval,
+					  snd_usb_combine_bytes(val, val_size));
+
+	return 0;
+}
+
+static int get_ctl_value(struct usb_mixer_elem_info *cval, int request,
+			 int validx, int *value_ret)
+{
+	validx += cval->idx_off;
+
+	return (cval->head.mixer->protocol == UAC_VERSION_1) ?
+		get_ctl_value_v1(cval, request, validx, value_ret) :
+		get_ctl_value_v2(cval, request, validx, value_ret);
+}
+
+static int get_cur_ctl_value(struct usb_mixer_elem_info *cval,
+			     int validx, int *value)
+{
+	return get_ctl_value(cval, UAC_GET_CUR, validx, value);
+}
+
+/* channel = 0: master, 1 = first channel */
+static inline int get_cur_mix_raw(struct usb_mixer_elem_info *cval,
+				  int channel, int *value)
+{
+	return get_ctl_value(cval, UAC_GET_CUR,
+			     (cval->control << 8) | channel,
+			     value);
+}
+
+int snd_usb_get_cur_mix_value(struct usb_mixer_elem_info *cval,
+			     int channel, int index, int *value)
+{
+	int err;
+
+	if (cval->cached & (1 << channel)) {
+		*value = cval->cache_val[index];
+		return 0;
+	}
+	err = get_cur_mix_raw(cval, channel, value);
+	if (err < 0) {
+		if (!cval->head.mixer->ignore_ctl_error)
+			usb_audio_dbg(cval->head.mixer->chip,
+				"cannot get current value for control %d ch %d: err = %d\n",
+				      cval->control, channel, err);
+		return err;
+	}
+	cval->cached |= 1 << channel;
+	cval->cache_val[index] = *value;
+	return 0;
+}
+
+/*
+ * set a mixer value
+ */
+
+int snd_usb_mixer_set_ctl_value(struct usb_mixer_elem_info *cval,
+				int request, int validx, int value_set)
+{
+	struct snd_usb_audio *chip = cval->head.mixer->chip;
+	unsigned char buf[4];
+	int idx = 0, val_len, err, timeout = 10;
+
+	validx += cval->idx_off;
+
+
+	if (cval->head.mixer->protocol == UAC_VERSION_1) {
+		val_len = cval->val_type >= USB_MIXER_S16 ? 2 : 1;
+	} else { /* UAC_VERSION_2/3 */
+		val_len = uac2_ctl_value_size(cval->val_type);
+
+		/* FIXME */
+		if (request != UAC_SET_CUR) {
+			usb_audio_dbg(chip, "RANGE setting not yet supported\n");
+			return -EINVAL;
+		}
+
+		request = UAC2_CS_CUR;
+	}
+
+	value_set = convert_bytes_value(cval, value_set);
+	buf[0] = value_set & 0xff;
+	buf[1] = (value_set >> 8) & 0xff;
+	buf[2] = (value_set >> 16) & 0xff;
+	buf[3] = (value_set >> 24) & 0xff;
+
+	err = snd_usb_lock_shutdown(chip);
+	if (err < 0)
+		return -EIO;
+
+	while (timeout-- > 0) {
+		idx = snd_usb_ctrl_intf(chip) | (cval->head.id << 8);
+		err = snd_usb_ctl_msg(chip->dev,
+				      usb_sndctrlpipe(chip->dev, 0), request,
+				      USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_OUT,
+				      validx, idx, buf, val_len);
+		if (err >= 0) {
+			err = 0;
+			goto out;
+		} else if (err == -ETIMEDOUT) {
+			goto out;
+		}
+	}
+	usb_audio_dbg(chip, "cannot set ctl value: req = %#x, wValue = %#x, wIndex = %#x, type = %d, data = %#x/%#x\n",
+		      request, validx, idx, cval->val_type, buf[0], buf[1]);
+	err = -EINVAL;
+
+ out:
+	snd_usb_unlock_shutdown(chip);
+	return err;
+}
+
+static int set_cur_ctl_value(struct usb_mixer_elem_info *cval,
+			     int validx, int value)
+{
+	return snd_usb_mixer_set_ctl_value(cval, UAC_SET_CUR, validx, value);
+}
+
+int snd_usb_set_cur_mix_value(struct usb_mixer_elem_info *cval, int channel,
+			     int index, int value)
+{
+	int err;
+	unsigned int read_only = (channel == 0) ?
+		cval->master_readonly :
+		cval->ch_readonly & (1 << (channel - 1));
+
+	if (read_only) {
+		usb_audio_dbg(cval->head.mixer->chip,
+			      "%s(): channel %d of control %d is read_only\n",
+			    __func__, channel, cval->control);
+		return 0;
+	}
+
+	err = snd_usb_mixer_set_ctl_value(cval,
+					  UAC_SET_CUR, (cval->control << 8) | channel,
+					  value);
+	if (err < 0)
+		return err;
+	cval->cached |= 1 << channel;
+	cval->cache_val[index] = value;
+	return 0;
+}
+
+/*
+ * TLV callback for mixer volume controls
+ */
+int snd_usb_mixer_vol_tlv(struct snd_kcontrol *kcontrol, int op_flag,
+			 unsigned int size, unsigned int __user *_tlv)
+{
+	struct usb_mixer_elem_info *cval = kcontrol->private_data;
+	DECLARE_TLV_DB_MINMAX(scale, 0, 0);
+
+	if (size < sizeof(scale))
+		return -ENOMEM;
+	if (cval->min_mute)
+		scale[0] = SNDRV_CTL_TLVT_DB_MINMAX_MUTE;
+	scale[2] = cval->dBmin;
+	scale[3] = cval->dBmax;
+	if (copy_to_user(_tlv, scale, sizeof(scale)))
+		return -EFAULT;
+	return 0;
+}
+
+/*
+ * parser routines begin here...
+ */
+
+static int parse_audio_unit(struct mixer_build *state, int unitid);
+
+
+/*
+ * check if the input/output channel routing is enabled on the given bitmap.
+ * used for mixer unit parser
+ */
+static int check_matrix_bitmap(unsigned char *bmap,
+			       int ich, int och, int num_outs)
+{
+	int idx = ich * num_outs + och;
+	return bmap[idx >> 3] & (0x80 >> (idx & 7));
+}
+
+/*
+ * add an alsa control element
+ * search and increment the index until an empty slot is found.
+ *
+ * if failed, give up and free the control instance.
+ */
+
+int snd_usb_mixer_add_control(struct usb_mixer_elem_list *list,
+			      struct snd_kcontrol *kctl)
+{
+	struct usb_mixer_interface *mixer = list->mixer;
+	int err;
+
+	while (snd_ctl_find_id(mixer->chip->card, &kctl->id))
+		kctl->id.index++;
+	err = snd_ctl_add(mixer->chip->card, kctl);
+	if (err < 0) {
+		usb_audio_dbg(mixer->chip, "cannot add control (err = %d)\n",
+			      err);
+		return err;
+	}
+	list->kctl = kctl;
+	list->next_id_elem = mixer->id_elems[list->id];
+	mixer->id_elems[list->id] = list;
+	return 0;
+}
+
+/*
+ * get a terminal name string
+ */
+
+static struct iterm_name_combo {
+	int type;
+	char *name;
+} iterm_names[] = {
+	{ 0x0300, "Output" },
+	{ 0x0301, "Speaker" },
+	{ 0x0302, "Headphone" },
+	{ 0x0303, "HMD Audio" },
+	{ 0x0304, "Desktop Speaker" },
+	{ 0x0305, "Room Speaker" },
+	{ 0x0306, "Com Speaker" },
+	{ 0x0307, "LFE" },
+	{ 0x0600, "External In" },
+	{ 0x0601, "Analog In" },
+	{ 0x0602, "Digital In" },
+	{ 0x0603, "Line" },
+	{ 0x0604, "Legacy In" },
+	{ 0x0605, "IEC958 In" },
+	{ 0x0606, "1394 DA Stream" },
+	{ 0x0607, "1394 DV Stream" },
+	{ 0x0700, "Embedded" },
+	{ 0x0701, "Noise Source" },
+	{ 0x0702, "Equalization Noise" },
+	{ 0x0703, "CD" },
+	{ 0x0704, "DAT" },
+	{ 0x0705, "DCC" },
+	{ 0x0706, "MiniDisk" },
+	{ 0x0707, "Analog Tape" },
+	{ 0x0708, "Phonograph" },
+	{ 0x0709, "VCR Audio" },
+	{ 0x070a, "Video Disk Audio" },
+	{ 0x070b, "DVD Audio" },
+	{ 0x070c, "TV Tuner Audio" },
+	{ 0x070d, "Satellite Rec Audio" },
+	{ 0x070e, "Cable Tuner Audio" },
+	{ 0x070f, "DSS Audio" },
+	{ 0x0710, "Radio Receiver" },
+	{ 0x0711, "Radio Transmitter" },
+	{ 0x0712, "Multi-Track Recorder" },
+	{ 0x0713, "Synthesizer" },
+	{ 0 },
+};
+
+static int get_term_name(struct snd_usb_audio *chip, struct usb_audio_term *iterm,
+			 unsigned char *name, int maxlen, int term_only)
+{
+	struct iterm_name_combo *names;
+	int len;
+
+	if (iterm->name) {
+		len = snd_usb_copy_string_desc(chip, iterm->name,
+						name, maxlen);
+		if (len)
+			return len;
+	}
+
+	/* virtual type - not a real terminal */
+	if (iterm->type >> 16) {
+		if (term_only)
+			return 0;
+		switch (iterm->type >> 16) {
+		case UAC3_SELECTOR_UNIT:
+			strcpy(name, "Selector");
+			return 8;
+		case UAC3_PROCESSING_UNIT:
+			strcpy(name, "Process Unit");
+			return 12;
+		case UAC3_EXTENSION_UNIT:
+			strcpy(name, "Ext Unit");
+			return 8;
+		case UAC3_MIXER_UNIT:
+			strcpy(name, "Mixer");
+			return 5;
+		default:
+			return sprintf(name, "Unit %d", iterm->id);
+		}
+	}
+
+	switch (iterm->type & 0xff00) {
+	case 0x0100:
+		strcpy(name, "PCM");
+		return 3;
+	case 0x0200:
+		strcpy(name, "Mic");
+		return 3;
+	case 0x0400:
+		strcpy(name, "Headset");
+		return 7;
+	case 0x0500:
+		strcpy(name, "Phone");
+		return 5;
+	}
+
+	for (names = iterm_names; names->type; names++) {
+		if (names->type == iterm->type) {
+			strcpy(name, names->name);
+			return strlen(names->name);
+		}
+	}
+
+	return 0;
+}
+
+/*
+ * Get logical cluster information for UAC3 devices.
+ */
+static int get_cluster_channels_v3(struct mixer_build *state, unsigned int cluster_id)
+{
+	struct uac3_cluster_header_descriptor c_header;
+	int err;
+
+	err = snd_usb_ctl_msg(state->chip->dev,
+			usb_rcvctrlpipe(state->chip->dev, 0),
+			UAC3_CS_REQ_HIGH_CAPABILITY_DESCRIPTOR,
+			USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_IN,
+			cluster_id,
+			snd_usb_ctrl_intf(state->chip),
+			&c_header, sizeof(c_header));
+	if (err < 0)
+		goto error;
+	if (err != sizeof(c_header)) {
+		err = -EIO;
+		goto error;
+	}
+
+	return c_header.bNrChannels;
+
+error:
+	usb_audio_err(state->chip, "cannot request logical cluster ID: %d (err: %d)\n", cluster_id, err);
+	return err;
+}
+
+/*
+ * Get number of channels for a Mixer Unit.
+ */
+static int uac_mixer_unit_get_channels(struct mixer_build *state,
+				       struct uac_mixer_unit_descriptor *desc)
+{
+	int mu_channels;
+
+	if (desc->bLength < 11)
+		return -EINVAL;
+	if (!desc->bNrInPins)
+		return -EINVAL;
+
+	switch (state->mixer->protocol) {
+	case UAC_VERSION_1:
+	case UAC_VERSION_2:
+	default:
+		mu_channels = uac_mixer_unit_bNrChannels(desc);
+		break;
+	case UAC_VERSION_3:
+		mu_channels = get_cluster_channels_v3(state,
+				uac3_mixer_unit_wClusterDescrID(desc));
+		break;
+	}
+
+	if (!mu_channels)
+		return -EINVAL;
+
+	return mu_channels;
+}
+
+/*
+ * parse the source unit recursively until it reaches to a terminal
+ * or a branched unit.
+ */
+static int check_input_term(struct mixer_build *state, int id,
+			    struct usb_audio_term *term)
+{
+	int protocol = state->mixer->protocol;
+	int err;
+	void *p1;
+
+	memset(term, 0, sizeof(*term));
+	while ((p1 = find_audio_control_unit(state, id)) != NULL) {
+		unsigned char *hdr = p1;
+		term->id = id;
+
+		if (protocol == UAC_VERSION_1 || protocol == UAC_VERSION_2) {
+			switch (hdr[2]) {
+			case UAC_INPUT_TERMINAL:
+				if (protocol == UAC_VERSION_1) {
+					struct uac_input_terminal_descriptor *d = p1;
+
+					term->type = le16_to_cpu(d->wTerminalType);
+					term->channels = d->bNrChannels;
+					term->chconfig = le16_to_cpu(d->wChannelConfig);
+					term->name = d->iTerminal;
+				} else { /* UAC_VERSION_2 */
+					struct uac2_input_terminal_descriptor *d = p1;
+
+					/* call recursively to verify that the
+					 * referenced clock entity is valid */
+					err = check_input_term(state, d->bCSourceID, term);
+					if (err < 0)
+						return err;
+
+					/* save input term properties after recursion,
+					 * to ensure they are not overriden by the
+					 * recursion calls */
+					term->id = id;
+					term->type = le16_to_cpu(d->wTerminalType);
+					term->channels = d->bNrChannels;
+					term->chconfig = le32_to_cpu(d->bmChannelConfig);
+					term->name = d->iTerminal;
+				}
+				return 0;
+			case UAC_FEATURE_UNIT: {
+				/* the header is the same for v1 and v2 */
+				struct uac_feature_unit_descriptor *d = p1;
+
+				id = d->bSourceID;
+				break; /* continue to parse */
+			}
+			case UAC_MIXER_UNIT: {
+				struct uac_mixer_unit_descriptor *d = p1;
+
+				term->type = UAC3_MIXER_UNIT << 16; /* virtual type */
+				term->channels = uac_mixer_unit_bNrChannels(d);
+				term->chconfig = uac_mixer_unit_wChannelConfig(d, protocol);
+				term->name = uac_mixer_unit_iMixer(d);
+				return 0;
+			}
+			case UAC_SELECTOR_UNIT:
+			case UAC2_CLOCK_SELECTOR: {
+				struct uac_selector_unit_descriptor *d = p1;
+				/* call recursively to retrieve the channel info */
+				err = check_input_term(state, d->baSourceID[0], term);
+				if (err < 0)
+					return err;
+				term->type = UAC3_SELECTOR_UNIT << 16; /* virtual type */
+				term->id = id;
+				term->name = uac_selector_unit_iSelector(d);
+				return 0;
+			}
+			case UAC1_PROCESSING_UNIT:
+			/* UAC2_EFFECT_UNIT */
+				if (protocol == UAC_VERSION_1)
+					term->type = UAC3_PROCESSING_UNIT << 16; /* virtual type */
+				else /* UAC_VERSION_2 */
+					term->type = UAC3_EFFECT_UNIT << 16; /* virtual type */
+				/* fall through */
+			case UAC1_EXTENSION_UNIT:
+			/* UAC2_PROCESSING_UNIT_V2 */
+				if (protocol == UAC_VERSION_1 && !term->type)
+					term->type = UAC3_EXTENSION_UNIT << 16; /* virtual type */
+				else if (protocol == UAC_VERSION_2 && !term->type)
+					term->type = UAC3_PROCESSING_UNIT << 16; /* virtual type */
+				/* fall through */
+			case UAC2_EXTENSION_UNIT_V2: {
+				struct uac_processing_unit_descriptor *d = p1;
+
+				if (protocol == UAC_VERSION_2 &&
+					hdr[2] == UAC2_EFFECT_UNIT) {
+					/* UAC2/UAC1 unit IDs overlap here in an
+					 * uncompatible way. Ignore this unit for now.
+					 */
+					return 0;
+				}
+
+				if (d->bNrInPins) {
+					id = d->baSourceID[0];
+					break; /* continue to parse */
+				}
+				if (!term->type)
+					term->type = UAC3_EXTENSION_UNIT << 16; /* virtual type */
+
+				term->channels = uac_processing_unit_bNrChannels(d);
+				term->chconfig = uac_processing_unit_wChannelConfig(d, protocol);
+				term->name = uac_processing_unit_iProcessing(d, protocol);
+				return 0;
+			}
+			case UAC2_CLOCK_SOURCE: {
+				struct uac_clock_source_descriptor *d = p1;
+
+				term->type = UAC3_CLOCK_SOURCE << 16; /* virtual type */
+				term->id = id;
+				term->name = d->iClockSource;
+				return 0;
+			}
+			default:
+				return -ENODEV;
+			}
+		} else { /* UAC_VERSION_3 */
+			switch (hdr[2]) {
+			case UAC_INPUT_TERMINAL: {
+				struct uac3_input_terminal_descriptor *d = p1;
+
+				/* call recursively to verify that the
+				 * referenced clock entity is valid */
+				err = check_input_term(state, d->bCSourceID, term);
+				if (err < 0)
+					return err;
+
+				/* save input term properties after recursion,
+				 * to ensure they are not overriden by the
+				 * recursion calls */
+				term->id = id;
+				term->type = le16_to_cpu(d->wTerminalType);
+
+				err = get_cluster_channels_v3(state, le16_to_cpu(d->wClusterDescrID));
+				if (err < 0)
+					return err;
+				term->channels = err;
+
+				/* REVISIT: UAC3 IT doesn't have channels cfg */
+				term->chconfig = 0;
+
+				term->name = le16_to_cpu(d->wTerminalDescrStr);
+				return 0;
+			}
+			case UAC3_FEATURE_UNIT: {
+				struct uac3_feature_unit_descriptor *d = p1;
+
+				id = d->bSourceID;
+				break; /* continue to parse */
+			}
+			case UAC3_CLOCK_SOURCE: {
+				struct uac3_clock_source_descriptor *d = p1;
+
+				term->type = UAC3_CLOCK_SOURCE << 16; /* virtual type */
+				term->id = id;
+				term->name = le16_to_cpu(d->wClockSourceStr);
+				return 0;
+			}
+			case UAC3_MIXER_UNIT: {
+				struct uac_mixer_unit_descriptor *d = p1;
+
+				err = uac_mixer_unit_get_channels(state, d);
+				if (err < 0)
+					return err;
+
+				term->channels = err;
+				term->type = UAC3_MIXER_UNIT << 16; /* virtual type */
+
+				return 0;
+			}
+			case UAC3_SELECTOR_UNIT:
+			case UAC3_CLOCK_SELECTOR: {
+				struct uac_selector_unit_descriptor *d = p1;
+				/* call recursively to retrieve the channel info */
+				err = check_input_term(state, d->baSourceID[0], term);
+				if (err < 0)
+					return err;
+				term->type = UAC3_SELECTOR_UNIT << 16; /* virtual type */
+				term->id = id;
+				term->name = 0; /* TODO: UAC3 Class-specific strings */
+
+				return 0;
+			}
+			case UAC3_PROCESSING_UNIT: {
+				struct uac_processing_unit_descriptor *d = p1;
+
+				if (!d->bNrInPins)
+					return -EINVAL;
+
+				/* call recursively to retrieve the channel info */
+				err = check_input_term(state, d->baSourceID[0], term);
+				if (err < 0)
+					return err;
+
+				term->type = UAC3_PROCESSING_UNIT << 16; /* virtual type */
+				term->id = id;
+				term->name = 0; /* TODO: UAC3 Class-specific strings */
+
+				return 0;
+			}
+			default:
+				return -ENODEV;
+			}
+		}
+	}
+	return -ENODEV;
+}
+
+/*
+ * Feature Unit
+ */
+
+/* feature unit control information */
+struct usb_feature_control_info {
+	int control;
+	const char *name;
+	int type;	/* data type for uac1 */
+	int type_uac2;	/* data type for uac2 if different from uac1, else -1 */
+};
+
+static struct usb_feature_control_info audio_feature_info[] = {
+	{ UAC_FU_MUTE,			"Mute",			USB_MIXER_INV_BOOLEAN, -1 },
+	{ UAC_FU_VOLUME,		"Volume",		USB_MIXER_S16, -1 },
+	{ UAC_FU_BASS,			"Tone Control - Bass",	USB_MIXER_S8, -1 },
+	{ UAC_FU_MID,			"Tone Control - Mid",	USB_MIXER_S8, -1 },
+	{ UAC_FU_TREBLE,		"Tone Control - Treble", USB_MIXER_S8, -1 },
+	{ UAC_FU_GRAPHIC_EQUALIZER,	"Graphic Equalizer",	USB_MIXER_S8, -1 }, /* FIXME: not implemented yet */
+	{ UAC_FU_AUTOMATIC_GAIN,	"Auto Gain Control",	USB_MIXER_BOOLEAN, -1 },
+	{ UAC_FU_DELAY,			"Delay Control",	USB_MIXER_U16, USB_MIXER_U32 },
+	{ UAC_FU_BASS_BOOST,		"Bass Boost",		USB_MIXER_BOOLEAN, -1 },
+	{ UAC_FU_LOUDNESS,		"Loudness",		USB_MIXER_BOOLEAN, -1 },
+	/* UAC2 specific */
+	{ UAC2_FU_INPUT_GAIN,		"Input Gain Control",	USB_MIXER_S16, -1 },
+	{ UAC2_FU_INPUT_GAIN_PAD,	"Input Gain Pad Control", USB_MIXER_S16, -1 },
+	{ UAC2_FU_PHASE_INVERTER,	 "Phase Inverter Control", USB_MIXER_BOOLEAN, -1 },
+};
+
+/* private_free callback */
+void snd_usb_mixer_elem_free(struct snd_kcontrol *kctl)
+{
+	kfree(kctl->private_data);
+	kctl->private_data = NULL;
+}
+
+/*
+ * interface to ALSA control for feature/mixer units
+ */
+
+/* volume control quirks */
+static void volume_control_quirks(struct usb_mixer_elem_info *cval,
+				  struct snd_kcontrol *kctl)
+{
+	struct snd_usb_audio *chip = cval->head.mixer->chip;
+	switch (chip->usb_id) {
+	case USB_ID(0x0763, 0x2030): /* M-Audio Fast Track C400 */
+	case USB_ID(0x0763, 0x2031): /* M-Audio Fast Track C600 */
+		if (strcmp(kctl->id.name, "Effect Duration") == 0) {
+			cval->min = 0x0000;
+			cval->max = 0xffff;
+			cval->res = 0x00e6;
+			break;
+		}
+		if (strcmp(kctl->id.name, "Effect Volume") == 0 ||
+		    strcmp(kctl->id.name, "Effect Feedback Volume") == 0) {
+			cval->min = 0x00;
+			cval->max = 0xff;
+			break;
+		}
+		if (strstr(kctl->id.name, "Effect Return") != NULL) {
+			cval->min = 0xb706;
+			cval->max = 0xff7b;
+			cval->res = 0x0073;
+			break;
+		}
+		if ((strstr(kctl->id.name, "Playback Volume") != NULL) ||
+			(strstr(kctl->id.name, "Effect Send") != NULL)) {
+			cval->min = 0xb5fb; /* -73 dB = 0xb6ff */
+			cval->max = 0xfcfe;
+			cval->res = 0x0073;
+		}
+		break;
+
+	case USB_ID(0x0763, 0x2081): /* M-Audio Fast Track Ultra 8R */
+	case USB_ID(0x0763, 0x2080): /* M-Audio Fast Track Ultra */
+		if (strcmp(kctl->id.name, "Effect Duration") == 0) {
+			usb_audio_info(chip,
+				       "set quirk for FTU Effect Duration\n");
+			cval->min = 0x0000;
+			cval->max = 0x7f00;
+			cval->res = 0x0100;
+			break;
+		}
+		if (strcmp(kctl->id.name, "Effect Volume") == 0 ||
+		    strcmp(kctl->id.name, "Effect Feedback Volume") == 0) {
+			usb_audio_info(chip,
+				       "set quirks for FTU Effect Feedback/Volume\n");
+			cval->min = 0x00;
+			cval->max = 0x7f;
+			break;
+		}
+		break;
+
+	case USB_ID(0x0d8c, 0x0103):
+		if (!strcmp(kctl->id.name, "PCM Playback Volume")) {
+			usb_audio_info(chip,
+				 "set volume quirk for CM102-A+/102S+\n");
+			cval->min = -256;
+		}
+		break;
+
+	case USB_ID(0x0471, 0x0101):
+	case USB_ID(0x0471, 0x0104):
+	case USB_ID(0x0471, 0x0105):
+	case USB_ID(0x0672, 0x1041):
+	/* quirk for UDA1321/N101.
+	 * note that detection between firmware 2.1.1.7 (N101)
+	 * and later 2.1.1.21 is not very clear from datasheets.
+	 * I hope that the min value is -15360 for newer firmware --jk
+	 */
+		if (!strcmp(kctl->id.name, "PCM Playback Volume") &&
+		    cval->min == -15616) {
+			usb_audio_info(chip,
+				 "set volume quirk for UDA1321/N101 chip\n");
+			cval->max = -256;
+		}
+		break;
+
+	case USB_ID(0x046d, 0x09a4):
+		if (!strcmp(kctl->id.name, "Mic Capture Volume")) {
+			usb_audio_info(chip,
+				"set volume quirk for QuickCam E3500\n");
+			cval->min = 6080;
+			cval->max = 8768;
+			cval->res = 192;
+		}
+		break;
+
+	case USB_ID(0x046d, 0x0807): /* Logitech Webcam C500 */
+	case USB_ID(0x046d, 0x0808):
+	case USB_ID(0x046d, 0x0809):
+	case USB_ID(0x046d, 0x0819): /* Logitech Webcam C210 */
+	case USB_ID(0x046d, 0x081b): /* HD Webcam c310 */
+	case USB_ID(0x046d, 0x081d): /* HD Webcam c510 */
+	case USB_ID(0x046d, 0x0825): /* HD Webcam c270 */
+	case USB_ID(0x046d, 0x0826): /* HD Webcam c525 */
+	case USB_ID(0x046d, 0x08ca): /* Logitech Quickcam Fusion */
+	case USB_ID(0x046d, 0x0991):
+	case USB_ID(0x046d, 0x09a2): /* QuickCam Communicate Deluxe/S7500 */
+	/* Most audio usb devices lie about volume resolution.
+	 * Most Logitech webcams have res = 384.
+	 * Probably there is some logitech magic behind this number --fishor
+	 */
+		if (!strcmp(kctl->id.name, "Mic Capture Volume")) {
+			usb_audio_info(chip,
+				"set resolution quirk: cval->res = 384\n");
+			cval->res = 384;
+		}
+		break;
+	}
+}
+
+/*
+ * retrieve the minimum and maximum values for the specified control
+ */
+static int get_min_max_with_quirks(struct usb_mixer_elem_info *cval,
+				   int default_min, struct snd_kcontrol *kctl)
+{
+	/* for failsafe */
+	cval->min = default_min;
+	cval->max = cval->min + 1;
+	cval->res = 1;
+	cval->dBmin = cval->dBmax = 0;
+
+	if (cval->val_type == USB_MIXER_BOOLEAN ||
+	    cval->val_type == USB_MIXER_INV_BOOLEAN) {
+		cval->initialized = 1;
+	} else {
+		int minchn = 0;
+		if (cval->cmask) {
+			int i;
+			for (i = 0; i < MAX_CHANNELS; i++)
+				if (cval->cmask & (1 << i)) {
+					minchn = i + 1;
+					break;
+				}
+		}
+		if (get_ctl_value(cval, UAC_GET_MAX, (cval->control << 8) | minchn, &cval->max) < 0 ||
+		    get_ctl_value(cval, UAC_GET_MIN, (cval->control << 8) | minchn, &cval->min) < 0) {
+			usb_audio_err(cval->head.mixer->chip,
+				      "%d:%d: cannot get min/max values for control %d (id %d)\n",
+				   cval->head.id, snd_usb_ctrl_intf(cval->head.mixer->chip),
+							       cval->control, cval->head.id);
+			return -EINVAL;
+		}
+		if (get_ctl_value(cval, UAC_GET_RES,
+				  (cval->control << 8) | minchn,
+				  &cval->res) < 0) {
+			cval->res = 1;
+		} else {
+			int last_valid_res = cval->res;
+
+			while (cval->res > 1) {
+				if (snd_usb_mixer_set_ctl_value(cval, UAC_SET_RES,
+								(cval->control << 8) | minchn,
+								cval->res / 2) < 0)
+					break;
+				cval->res /= 2;
+			}
+			if (get_ctl_value(cval, UAC_GET_RES,
+					  (cval->control << 8) | minchn, &cval->res) < 0)
+				cval->res = last_valid_res;
+		}
+		if (cval->res == 0)
+			cval->res = 1;
+
+		/* Additional checks for the proper resolution
+		 *
+		 * Some devices report smaller resolutions than actually
+		 * reacting.  They don't return errors but simply clip
+		 * to the lower aligned value.
+		 */
+		if (cval->min + cval->res < cval->max) {
+			int last_valid_res = cval->res;
+			int saved, test, check;
+			get_cur_mix_raw(cval, minchn, &saved);
+			for (;;) {
+				test = saved;
+				if (test < cval->max)
+					test += cval->res;
+				else
+					test -= cval->res;
+				if (test < cval->min || test > cval->max ||
+				    snd_usb_set_cur_mix_value(cval, minchn, 0, test) ||
+				    get_cur_mix_raw(cval, minchn, &check)) {
+					cval->res = last_valid_res;
+					break;
+				}
+				if (test == check)
+					break;
+				cval->res *= 2;
+			}
+			snd_usb_set_cur_mix_value(cval, minchn, 0, saved);
+		}
+
+		cval->initialized = 1;
+	}
+
+	if (kctl)
+		volume_control_quirks(cval, kctl);
+
+	/* USB descriptions contain the dB scale in 1/256 dB unit
+	 * while ALSA TLV contains in 1/100 dB unit
+	 */
+	cval->dBmin = (convert_signed_value(cval, cval->min) * 100) / 256;
+	cval->dBmax = (convert_signed_value(cval, cval->max) * 100) / 256;
+	if (cval->dBmin > cval->dBmax) {
+		/* something is wrong; assume it's either from/to 0dB */
+		if (cval->dBmin < 0)
+			cval->dBmax = 0;
+		else if (cval->dBmin > 0)
+			cval->dBmin = 0;
+		if (cval->dBmin > cval->dBmax) {
+			/* totally crap, return an error */
+			return -EINVAL;
+		}
+	}
+
+	return 0;
+}
+
+#define get_min_max(cval, def)	get_min_max_with_quirks(cval, def, NULL)
+
+/* get a feature/mixer unit info */
+static int mixer_ctl_feature_info(struct snd_kcontrol *kcontrol,
+				  struct snd_ctl_elem_info *uinfo)
+{
+	struct usb_mixer_elem_info *cval = kcontrol->private_data;
+
+	if (cval->val_type == USB_MIXER_BOOLEAN ||
+	    cval->val_type == USB_MIXER_INV_BOOLEAN)
+		uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
+	else
+		uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = cval->channels;
+	if (cval->val_type == USB_MIXER_BOOLEAN ||
+	    cval->val_type == USB_MIXER_INV_BOOLEAN) {
+		uinfo->value.integer.min = 0;
+		uinfo->value.integer.max = 1;
+	} else {
+		if (!cval->initialized) {
+			get_min_max_with_quirks(cval, 0, kcontrol);
+			if (cval->initialized && cval->dBmin >= cval->dBmax) {
+				kcontrol->vd[0].access &= 
+					~(SNDRV_CTL_ELEM_ACCESS_TLV_READ |
+					  SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK);
+				snd_ctl_notify(cval->head.mixer->chip->card,
+					       SNDRV_CTL_EVENT_MASK_INFO,
+					       &kcontrol->id);
+			}
+		}
+		uinfo->value.integer.min = 0;
+		uinfo->value.integer.max =
+			(cval->max - cval->min + cval->res - 1) / cval->res;
+	}
+	return 0;
+}
+
+/* get the current value from feature/mixer unit */
+static int mixer_ctl_feature_get(struct snd_kcontrol *kcontrol,
+				 struct snd_ctl_elem_value *ucontrol)
+{
+	struct usb_mixer_elem_info *cval = kcontrol->private_data;
+	int c, cnt, val, err;
+
+	ucontrol->value.integer.value[0] = cval->min;
+	if (cval->cmask) {
+		cnt = 0;
+		for (c = 0; c < MAX_CHANNELS; c++) {
+			if (!(cval->cmask & (1 << c)))
+				continue;
+			err = snd_usb_get_cur_mix_value(cval, c + 1, cnt, &val);
+			if (err < 0)
+				return filter_error(cval, err);
+			val = get_relative_value(cval, val);
+			ucontrol->value.integer.value[cnt] = val;
+			cnt++;
+		}
+		return 0;
+	} else {
+		/* master channel */
+		err = snd_usb_get_cur_mix_value(cval, 0, 0, &val);
+		if (err < 0)
+			return filter_error(cval, err);
+		val = get_relative_value(cval, val);
+		ucontrol->value.integer.value[0] = val;
+	}
+	return 0;
+}
+
+/* put the current value to feature/mixer unit */
+static int mixer_ctl_feature_put(struct snd_kcontrol *kcontrol,
+				 struct snd_ctl_elem_value *ucontrol)
+{
+	struct usb_mixer_elem_info *cval = kcontrol->private_data;
+	int c, cnt, val, oval, err;
+	int changed = 0;
+
+	if (cval->cmask) {
+		cnt = 0;
+		for (c = 0; c < MAX_CHANNELS; c++) {
+			if (!(cval->cmask & (1 << c)))
+				continue;
+			err = snd_usb_get_cur_mix_value(cval, c + 1, cnt, &oval);
+			if (err < 0)
+				return filter_error(cval, err);
+			val = ucontrol->value.integer.value[cnt];
+			val = get_abs_value(cval, val);
+			if (oval != val) {
+				snd_usb_set_cur_mix_value(cval, c + 1, cnt, val);
+				changed = 1;
+			}
+			cnt++;
+		}
+	} else {
+		/* master channel */
+		err = snd_usb_get_cur_mix_value(cval, 0, 0, &oval);
+		if (err < 0)
+			return filter_error(cval, err);
+		val = ucontrol->value.integer.value[0];
+		val = get_abs_value(cval, val);
+		if (val != oval) {
+			snd_usb_set_cur_mix_value(cval, 0, 0, val);
+			changed = 1;
+		}
+	}
+	return changed;
+}
+
+/* get the boolean value from the master channel of a UAC control */
+static int mixer_ctl_master_bool_get(struct snd_kcontrol *kcontrol,
+				     struct snd_ctl_elem_value *ucontrol)
+{
+	struct usb_mixer_elem_info *cval = kcontrol->private_data;
+	int val, err;
+
+	err = snd_usb_get_cur_mix_value(cval, 0, 0, &val);
+	if (err < 0)
+		return filter_error(cval, err);
+	val = (val != 0);
+	ucontrol->value.integer.value[0] = val;
+	return 0;
+}
+
+/* get the connectors status and report it as boolean type */
+static int mixer_ctl_connector_get(struct snd_kcontrol *kcontrol,
+				   struct snd_ctl_elem_value *ucontrol)
+{
+	struct usb_mixer_elem_info *cval = kcontrol->private_data;
+	struct snd_usb_audio *chip = cval->head.mixer->chip;
+	int idx = 0, validx, ret, val;
+
+	validx = cval->control << 8 | 0;
+
+	ret = snd_usb_lock_shutdown(chip) ? -EIO : 0;
+	if (ret)
+		goto error;
+
+	idx = snd_usb_ctrl_intf(chip) | (cval->head.id << 8);
+	if (cval->head.mixer->protocol == UAC_VERSION_2) {
+		struct uac2_connectors_ctl_blk uac2_conn;
+
+		ret = snd_usb_ctl_msg(chip->dev, usb_rcvctrlpipe(chip->dev, 0), UAC2_CS_CUR,
+				      USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_IN,
+				      validx, idx, &uac2_conn, sizeof(uac2_conn));
+		val = !!uac2_conn.bNrChannels;
+	} else { /* UAC_VERSION_3 */
+		struct uac3_insertion_ctl_blk uac3_conn;
+
+		ret = snd_usb_ctl_msg(chip->dev, usb_rcvctrlpipe(chip->dev, 0), UAC2_CS_CUR,
+				      USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_IN,
+				      validx, idx, &uac3_conn, sizeof(uac3_conn));
+		val = !!uac3_conn.bmConInserted;
+	}
+
+	snd_usb_unlock_shutdown(chip);
+
+	if (ret < 0) {
+error:
+		usb_audio_err(chip,
+			"cannot get connectors status: req = %#x, wValue = %#x, wIndex = %#x, type = %d\n",
+			UAC_GET_CUR, validx, idx, cval->val_type);
+		return ret;
+	}
+
+	ucontrol->value.integer.value[0] = val;
+	return 0;
+}
+
+static struct snd_kcontrol_new usb_feature_unit_ctl = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "", /* will be filled later manually */
+	.info = mixer_ctl_feature_info,
+	.get = mixer_ctl_feature_get,
+	.put = mixer_ctl_feature_put,
+};
+
+/* the read-only variant */
+static const struct snd_kcontrol_new usb_feature_unit_ctl_ro = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "", /* will be filled later manually */
+	.info = mixer_ctl_feature_info,
+	.get = mixer_ctl_feature_get,
+	.put = NULL,
+};
+
+/*
+ * A control which shows the boolean value from reading a UAC control on
+ * the master channel.
+ */
+static struct snd_kcontrol_new usb_bool_master_control_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 = mixer_ctl_master_bool_get,
+	.put = NULL,
+};
+
+static const struct snd_kcontrol_new usb_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 = mixer_ctl_connector_get,
+	.put = NULL,
+};
+
+/*
+ * This symbol is exported in order to allow the mixer quirks to
+ * hook up to the standard feature unit control mechanism
+ */
+struct snd_kcontrol_new *snd_usb_feature_unit_ctl = &usb_feature_unit_ctl;
+
+/*
+ * build a feature control
+ */
+static size_t append_ctl_name(struct snd_kcontrol *kctl, const char *str)
+{
+	return strlcat(kctl->id.name, str, sizeof(kctl->id.name));
+}
+
+/*
+ * A lot of headsets/headphones have a "Speaker" mixer. Make sure we
+ * rename it to "Headphone". We determine if something is a headphone
+ * similar to how udev determines form factor.
+ */
+static void check_no_speaker_on_headset(struct snd_kcontrol *kctl,
+					struct snd_card *card)
+{
+	const char *names_to_check[] = {
+		"Headset", "headset", "Headphone", "headphone", NULL};
+	const char **s;
+	bool found = false;
+
+	if (strcmp("Speaker", kctl->id.name))
+		return;
+
+	for (s = names_to_check; *s; s++)
+		if (strstr(card->shortname, *s)) {
+			found = true;
+			break;
+		}
+
+	if (!found)
+		return;
+
+	strlcpy(kctl->id.name, "Headphone", sizeof(kctl->id.name));
+}
+
+static struct usb_feature_control_info *get_feature_control_info(int control)
+{
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(audio_feature_info); ++i) {
+		if (audio_feature_info[i].control == control)
+			return &audio_feature_info[i];
+	}
+	return NULL;
+}
+
+static void __build_feature_ctl(struct usb_mixer_interface *mixer,
+				const struct usbmix_name_map *imap,
+				unsigned int ctl_mask, int control,
+				struct usb_audio_term *iterm,
+				struct usb_audio_term *oterm,
+				int unitid, int nameid, int readonly_mask)
+{
+	struct usb_feature_control_info *ctl_info;
+	unsigned int len = 0;
+	int mapped_name = 0;
+	struct snd_kcontrol *kctl;
+	struct usb_mixer_elem_info *cval;
+	const struct usbmix_name_map *map;
+	unsigned int range;
+
+	if (control == UAC_FU_GRAPHIC_EQUALIZER) {
+		/* FIXME: not supported yet */
+		return;
+	}
+
+	map = find_map(imap, unitid, control);
+	if (check_ignored_ctl(map))
+		return;
+
+	cval = kzalloc(sizeof(*cval), GFP_KERNEL);
+	if (!cval)
+		return;
+	snd_usb_mixer_elem_init_std(&cval->head, mixer, unitid);
+	cval->control = control;
+	cval->cmask = ctl_mask;
+
+	ctl_info = get_feature_control_info(control);
+	if (!ctl_info) {
+		kfree(cval);
+		return;
+	}
+	if (mixer->protocol == UAC_VERSION_1)
+		cval->val_type = ctl_info->type;
+	else /* UAC_VERSION_2 */
+		cval->val_type = ctl_info->type_uac2 >= 0 ?
+			ctl_info->type_uac2 : ctl_info->type;
+
+	if (ctl_mask == 0) {
+		cval->channels = 1;	/* master channel */
+		cval->master_readonly = readonly_mask;
+	} else {
+		int i, c = 0;
+		for (i = 0; i < 16; i++)
+			if (ctl_mask & (1 << i))
+				c++;
+		cval->channels = c;
+		cval->ch_readonly = readonly_mask;
+	}
+
+	/*
+	 * If all channels in the mask are marked read-only, make the control
+	 * read-only. snd_usb_set_cur_mix_value() will check the mask again and won't
+	 * issue write commands to read-only channels.
+	 */
+	if (cval->channels == readonly_mask)
+		kctl = snd_ctl_new1(&usb_feature_unit_ctl_ro, cval);
+	else
+		kctl = snd_ctl_new1(&usb_feature_unit_ctl, cval);
+
+	if (!kctl) {
+		usb_audio_err(mixer->chip, "cannot malloc kcontrol\n");
+		kfree(cval);
+		return;
+	}
+	kctl->private_free = snd_usb_mixer_elem_free;
+
+	len = check_mapped_name(map, kctl->id.name, sizeof(kctl->id.name));
+	mapped_name = len != 0;
+	if (!len && nameid)
+		len = snd_usb_copy_string_desc(mixer->chip, nameid,
+				kctl->id.name, sizeof(kctl->id.name));
+
+	switch (control) {
+	case UAC_FU_MUTE:
+	case UAC_FU_VOLUME:
+		/*
+		 * determine the control name.  the rule is:
+		 * - if a name id is given in descriptor, use it.
+		 * - if the connected input can be determined, then use the name
+		 *   of terminal type.
+		 * - if the connected output can be determined, use it.
+		 * - otherwise, anonymous name.
+		 */
+		if (!len) {
+			if (iterm)
+				len = get_term_name(mixer->chip, iterm,
+						    kctl->id.name,
+						    sizeof(kctl->id.name), 1);
+			if (!len && oterm)
+				len = get_term_name(mixer->chip, oterm,
+						    kctl->id.name,
+						    sizeof(kctl->id.name), 1);
+			if (!len)
+				snprintf(kctl->id.name, sizeof(kctl->id.name),
+					 "Feature %d", unitid);
+		}
+
+		if (!mapped_name)
+			check_no_speaker_on_headset(kctl, mixer->chip->card);
+
+		/*
+		 * determine the stream direction:
+		 * if the connected output is USB stream, then it's likely a
+		 * capture stream.  otherwise it should be playback (hopefully :)
+		 */
+		if (!mapped_name && oterm && !(oterm->type >> 16)) {
+			if ((oterm->type & 0xff00) == 0x0100)
+				append_ctl_name(kctl, " Capture");
+			else
+				append_ctl_name(kctl, " Playback");
+		}
+		append_ctl_name(kctl, control == UAC_FU_MUTE ?
+				" Switch" : " Volume");
+		break;
+	default:
+		if (!len)
+			strlcpy(kctl->id.name, audio_feature_info[control-1].name,
+				sizeof(kctl->id.name));
+		break;
+	}
+
+	/* get min/max values */
+	get_min_max_with_quirks(cval, 0, kctl);
+
+	if (control == UAC_FU_VOLUME) {
+		check_mapped_dB(map, cval);
+		if (cval->dBmin < cval->dBmax || !cval->initialized) {
+			kctl->tlv.c = snd_usb_mixer_vol_tlv;
+			kctl->vd[0].access |=
+				SNDRV_CTL_ELEM_ACCESS_TLV_READ |
+				SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK;
+		}
+	}
+
+	snd_usb_mixer_fu_apply_quirk(mixer, cval, unitid, kctl);
+
+	range = (cval->max - cval->min) / cval->res;
+	/*
+	 * Are there devices with volume range more than 255? I use a bit more
+	 * to be sure. 384 is a resolution magic number found on Logitech
+	 * devices. It will definitively catch all buggy Logitech devices.
+	 */
+	if (range > 384) {
+		usb_audio_warn(mixer->chip,
+			       "Warning! Unlikely big volume range (=%u), cval->res is probably wrong.",
+			       range);
+		usb_audio_warn(mixer->chip,
+			       "[%d] FU [%s] ch = %d, val = %d/%d/%d",
+			       cval->head.id, kctl->id.name, cval->channels,
+			       cval->min, cval->max, cval->res);
+	}
+
+	usb_audio_dbg(mixer->chip, "[%d] FU [%s] ch = %d, val = %d/%d/%d\n",
+		      cval->head.id, kctl->id.name, cval->channels,
+		      cval->min, cval->max, cval->res);
+	snd_usb_mixer_add_control(&cval->head, kctl);
+}
+
+static void build_feature_ctl(struct mixer_build *state, void *raw_desc,
+			      unsigned int ctl_mask, int control,
+			      struct usb_audio_term *iterm, int unitid,
+			      int readonly_mask)
+{
+	struct uac_feature_unit_descriptor *desc = raw_desc;
+	int nameid = uac_feature_unit_iFeature(desc);
+
+	__build_feature_ctl(state->mixer, state->map, ctl_mask, control,
+			iterm, &state->oterm, unitid, nameid, readonly_mask);
+}
+
+static void build_feature_ctl_badd(struct usb_mixer_interface *mixer,
+			      unsigned int ctl_mask, int control, int unitid,
+			      const struct usbmix_name_map *badd_map)
+{
+	__build_feature_ctl(mixer, badd_map, ctl_mask, control,
+			NULL, NULL, unitid, 0, 0);
+}
+
+static void get_connector_control_name(struct usb_mixer_interface *mixer,
+				       struct usb_audio_term *term,
+				       bool is_input, char *name, int name_size)
+{
+	int name_len = get_term_name(mixer->chip, term, name, name_size, 0);
+
+	if (name_len == 0)
+		strlcpy(name, "Unknown", name_size);
+
+	/*
+	 *  sound/core/ctljack.c has a convention of naming jack controls
+	 * by ending in " Jack".  Make it slightly more useful by
+	 * indicating Input or Output after the terminal name.
+	 */
+	if (is_input)
+		strlcat(name, " - Input Jack", name_size);
+	else
+		strlcat(name, " - Output Jack", name_size);
+}
+
+/* Build a mixer control for a UAC connector control (jack-detect) */
+static void build_connector_control(struct usb_mixer_interface *mixer,
+				    struct usb_audio_term *term, bool is_input)
+{
+	struct snd_kcontrol *kctl;
+	struct usb_mixer_elem_info *cval;
+
+	cval = kzalloc(sizeof(*cval), GFP_KERNEL);
+	if (!cval)
+		return;
+	snd_usb_mixer_elem_init_std(&cval->head, mixer, term->id);
+	/*
+	 * UAC2: The first byte from reading the UAC2_TE_CONNECTOR control returns the
+	 * number of channels connected.
+	 *
+	 * UAC3: The first byte specifies size of bitmap for the inserted controls. The
+	 * following byte(s) specifies which connectors are inserted.
+	 *
+	 * This boolean ctl will simply report if any channels are connected
+	 * or not.
+	 */
+	if (mixer->protocol == UAC_VERSION_2)
+		cval->control = UAC2_TE_CONNECTOR;
+	else /* UAC_VERSION_3 */
+		cval->control = UAC3_TE_INSERTION;
+
+	cval->val_type = USB_MIXER_BOOLEAN;
+	cval->channels = 1; /* report true if any channel is connected */
+	cval->min = 0;
+	cval->max = 1;
+	kctl = snd_ctl_new1(&usb_connector_ctl_ro, cval);
+	if (!kctl) {
+		usb_audio_err(mixer->chip, "cannot malloc kcontrol\n");
+		kfree(cval);
+		return;
+	}
+	get_connector_control_name(mixer, term, is_input, kctl->id.name,
+				   sizeof(kctl->id.name));
+	kctl->private_free = snd_usb_mixer_elem_free;
+	snd_usb_mixer_add_control(&cval->head, kctl);
+}
+
+static int parse_clock_source_unit(struct mixer_build *state, int unitid,
+				   void *_ftr)
+{
+	struct uac_clock_source_descriptor *hdr = _ftr;
+	struct usb_mixer_elem_info *cval;
+	struct snd_kcontrol *kctl;
+	char name[SNDRV_CTL_ELEM_ID_NAME_MAXLEN];
+	int ret;
+
+	if (state->mixer->protocol != UAC_VERSION_2)
+		return -EINVAL;
+
+	if (hdr->bLength != sizeof(*hdr)) {
+		usb_audio_dbg(state->chip,
+			      "Bogus clock source descriptor length of %d, ignoring.\n",
+			      hdr->bLength);
+		return 0;
+	}
+
+	/*
+	 * The only property of this unit we are interested in is the
+	 * clock source validity. If that isn't readable, just bail out.
+	 */
+	if (!uac_v2v3_control_is_readable(hdr->bmControls,
+				      UAC2_CS_CONTROL_CLOCK_VALID))
+		return 0;
+
+	cval = kzalloc(sizeof(*cval), GFP_KERNEL);
+	if (!cval)
+		return -ENOMEM;
+
+	snd_usb_mixer_elem_init_std(&cval->head, state->mixer, hdr->bClockID);
+
+	cval->min = 0;
+	cval->max = 1;
+	cval->channels = 1;
+	cval->val_type = USB_MIXER_BOOLEAN;
+	cval->control = UAC2_CS_CONTROL_CLOCK_VALID;
+
+	cval->master_readonly = 1;
+	/* From UAC2 5.2.5.1.2 "Only the get request is supported." */
+	kctl = snd_ctl_new1(&usb_bool_master_control_ctl_ro, cval);
+
+	if (!kctl) {
+		kfree(cval);
+		return -ENOMEM;
+	}
+
+	kctl->private_free = snd_usb_mixer_elem_free;
+	ret = snd_usb_copy_string_desc(state->chip, hdr->iClockSource,
+				       name, sizeof(name));
+	if (ret > 0)
+		snprintf(kctl->id.name, sizeof(kctl->id.name),
+			 "%s Validity", name);
+	else
+		snprintf(kctl->id.name, sizeof(kctl->id.name),
+			 "Clock Source %d Validity", hdr->bClockID);
+
+	return snd_usb_mixer_add_control(&cval->head, kctl);
+}
+
+/*
+ * parse a feature unit
+ *
+ * most of controls are defined here.
+ */
+static int parse_audio_feature_unit(struct mixer_build *state, int unitid,
+				    void *_ftr)
+{
+	int channels, i, j;
+	struct usb_audio_term iterm;
+	unsigned int master_bits, first_ch_bits;
+	int err, csize;
+	struct uac_feature_unit_descriptor *hdr = _ftr;
+	__u8 *bmaControls;
+
+	if (state->mixer->protocol == UAC_VERSION_1) {
+		if (hdr->bLength < 7) {
+			usb_audio_err(state->chip,
+				      "unit %u: invalid UAC_FEATURE_UNIT descriptor\n",
+				      unitid);
+			return -EINVAL;
+		}
+		csize = hdr->bControlSize;
+		if (!csize) {
+			usb_audio_dbg(state->chip,
+				      "unit %u: invalid bControlSize == 0\n",
+				      unitid);
+			return -EINVAL;
+		}
+		channels = (hdr->bLength - 7) / csize - 1;
+		bmaControls = hdr->bmaControls;
+		if (hdr->bLength < 7 + csize) {
+			usb_audio_err(state->chip,
+				      "unit %u: invalid UAC_FEATURE_UNIT descriptor\n",
+				      unitid);
+			return -EINVAL;
+		}
+	} else if (state->mixer->protocol == UAC_VERSION_2) {
+		struct uac2_feature_unit_descriptor *ftr = _ftr;
+		if (hdr->bLength < 6) {
+			usb_audio_err(state->chip,
+				      "unit %u: invalid UAC_FEATURE_UNIT descriptor\n",
+				      unitid);
+			return -EINVAL;
+		}
+		csize = 4;
+		channels = (hdr->bLength - 6) / 4 - 1;
+		bmaControls = ftr->bmaControls;
+		if (hdr->bLength < 6 + csize) {
+			usb_audio_err(state->chip,
+				      "unit %u: invalid UAC_FEATURE_UNIT descriptor\n",
+				      unitid);
+			return -EINVAL;
+		}
+	} else { /* UAC_VERSION_3 */
+		struct uac3_feature_unit_descriptor *ftr = _ftr;
+
+		if (hdr->bLength < 7) {
+			usb_audio_err(state->chip,
+				      "unit %u: invalid UAC3_FEATURE_UNIT descriptor\n",
+				      unitid);
+			return -EINVAL;
+		}
+		csize = 4;
+		channels = (ftr->bLength - 7) / 4 - 1;
+		bmaControls = ftr->bmaControls;
+		if (hdr->bLength < 7 + csize) {
+			usb_audio_err(state->chip,
+				      "unit %u: invalid UAC3_FEATURE_UNIT descriptor\n",
+				      unitid);
+			return -EINVAL;
+		}
+	}
+
+	/* parse the source unit */
+	err = parse_audio_unit(state, hdr->bSourceID);
+	if (err < 0)
+		return err;
+
+	/* determine the input source type and name */
+	err = check_input_term(state, hdr->bSourceID, &iterm);
+	if (err < 0)
+		return err;
+
+	master_bits = snd_usb_combine_bytes(bmaControls, csize);
+	/* master configuration quirks */
+	switch (state->chip->usb_id) {
+	case USB_ID(0x08bb, 0x2702):
+		usb_audio_info(state->chip,
+			       "usbmixer: master volume quirk for PCM2702 chip\n");
+		/* disable non-functional volume control */
+		master_bits &= ~UAC_CONTROL_BIT(UAC_FU_VOLUME);
+		break;
+	case USB_ID(0x1130, 0xf211):
+		usb_audio_info(state->chip,
+			       "usbmixer: volume control quirk for Tenx TP6911 Audio Headset\n");
+		/* disable non-functional volume control */
+		channels = 0;
+		break;
+
+	}
+	if (channels > 0)
+		first_ch_bits = snd_usb_combine_bytes(bmaControls + csize, csize);
+	else
+		first_ch_bits = 0;
+
+	if (state->mixer->protocol == UAC_VERSION_1) {
+		/* check all control types */
+		for (i = 0; i < 10; i++) {
+			unsigned int ch_bits = 0;
+			int control = audio_feature_info[i].control;
+
+			for (j = 0; j < channels; j++) {
+				unsigned int mask;
+
+				mask = snd_usb_combine_bytes(bmaControls +
+							     csize * (j+1), csize);
+				if (mask & (1 << i))
+					ch_bits |= (1 << j);
+			}
+			/* audio class v1 controls are never read-only */
+
+			/*
+			 * The first channel must be set
+			 * (for ease of programming).
+			 */
+			if (ch_bits & 1)
+				build_feature_ctl(state, _ftr, ch_bits, control,
+						  &iterm, unitid, 0);
+			if (master_bits & (1 << i))
+				build_feature_ctl(state, _ftr, 0, control,
+						  &iterm, unitid, 0);
+		}
+	} else { /* UAC_VERSION_2/3 */
+		for (i = 0; i < ARRAY_SIZE(audio_feature_info); i++) {
+			unsigned int ch_bits = 0;
+			unsigned int ch_read_only = 0;
+			int control = audio_feature_info[i].control;
+
+			for (j = 0; j < channels; j++) {
+				unsigned int mask;
+
+				mask = snd_usb_combine_bytes(bmaControls +
+							     csize * (j+1), csize);
+				if (uac_v2v3_control_is_readable(mask, control)) {
+					ch_bits |= (1 << j);
+					if (!uac_v2v3_control_is_writeable(mask, control))
+						ch_read_only |= (1 << j);
+				}
+			}
+
+			/*
+			 * NOTE: build_feature_ctl() will mark the control
+			 * read-only if all channels are marked read-only in
+			 * the descriptors. Otherwise, the control will be
+			 * reported as writeable, but the driver will not
+			 * actually issue a write command for read-only
+			 * channels.
+			 */
+
+			/*
+			 * The first channel must be set
+			 * (for ease of programming).
+			 */
+			if (ch_bits & 1)
+				build_feature_ctl(state, _ftr, ch_bits, control,
+						  &iterm, unitid, ch_read_only);
+			if (uac_v2v3_control_is_readable(master_bits, control))
+				build_feature_ctl(state, _ftr, 0, control,
+						  &iterm, unitid,
+						  !uac_v2v3_control_is_writeable(master_bits,
+										 control));
+		}
+	}
+
+	return 0;
+}
+
+/*
+ * Mixer Unit
+ */
+
+/*
+ * build a mixer unit control
+ *
+ * the callbacks are identical with feature unit.
+ * input channel number (zero based) is given in control field instead.
+ */
+static void build_mixer_unit_ctl(struct mixer_build *state,
+				 struct uac_mixer_unit_descriptor *desc,
+				 int in_pin, int in_ch, int num_outs,
+				 int unitid, struct usb_audio_term *iterm)
+{
+	struct usb_mixer_elem_info *cval;
+	unsigned int i, len;
+	struct snd_kcontrol *kctl;
+	const struct usbmix_name_map *map;
+
+	map = find_map(state->map, unitid, 0);
+	if (check_ignored_ctl(map))
+		return;
+
+	cval = kzalloc(sizeof(*cval), GFP_KERNEL);
+	if (!cval)
+		return;
+
+	snd_usb_mixer_elem_init_std(&cval->head, state->mixer, unitid);
+	cval->control = in_ch + 1; /* based on 1 */
+	cval->val_type = USB_MIXER_S16;
+	for (i = 0; i < num_outs; i++) {
+		__u8 *c = uac_mixer_unit_bmControls(desc, state->mixer->protocol);
+
+		if (check_matrix_bitmap(c, in_ch, i, num_outs)) {
+			cval->cmask |= (1 << i);
+			cval->channels++;
+		}
+	}
+
+	/* get min/max values */
+	get_min_max(cval, 0);
+
+	kctl = snd_ctl_new1(&usb_feature_unit_ctl, cval);
+	if (!kctl) {
+		usb_audio_err(state->chip, "cannot malloc kcontrol\n");
+		kfree(cval);
+		return;
+	}
+	kctl->private_free = snd_usb_mixer_elem_free;
+
+	len = check_mapped_name(map, kctl->id.name, sizeof(kctl->id.name));
+	if (!len)
+		len = get_term_name(state->chip, iterm, kctl->id.name,
+				    sizeof(kctl->id.name), 0);
+	if (!len)
+		len = sprintf(kctl->id.name, "Mixer Source %d", in_ch + 1);
+	append_ctl_name(kctl, " Volume");
+
+	usb_audio_dbg(state->chip, "[%d] MU [%s] ch = %d, val = %d/%d\n",
+		    cval->head.id, kctl->id.name, cval->channels, cval->min, cval->max);
+	snd_usb_mixer_add_control(&cval->head, kctl);
+}
+
+static int parse_audio_input_terminal(struct mixer_build *state, int unitid,
+				      void *raw_desc)
+{
+	struct usb_audio_term iterm;
+	unsigned int control, bmctls, term_id;
+
+	if (state->mixer->protocol == UAC_VERSION_2) {
+		struct uac2_input_terminal_descriptor *d_v2 = raw_desc;
+		control = UAC2_TE_CONNECTOR;
+		term_id = d_v2->bTerminalID;
+		bmctls = le16_to_cpu(d_v2->bmControls);
+	} else if (state->mixer->protocol == UAC_VERSION_3) {
+		struct uac3_input_terminal_descriptor *d_v3 = raw_desc;
+		control = UAC3_TE_INSERTION;
+		term_id = d_v3->bTerminalID;
+		bmctls = le32_to_cpu(d_v3->bmControls);
+	} else {
+		return 0; /* UAC1. No Insertion control */
+	}
+
+	check_input_term(state, term_id, &iterm);
+
+	/* Check for jack detection. */
+	if (uac_v2v3_control_is_readable(bmctls, control))
+		build_connector_control(state->mixer, &iterm, true);
+
+	return 0;
+}
+
+/*
+ * parse a mixer unit
+ */
+static int parse_audio_mixer_unit(struct mixer_build *state, int unitid,
+				  void *raw_desc)
+{
+	struct uac_mixer_unit_descriptor *desc = raw_desc;
+	struct usb_audio_term iterm;
+	int input_pins, num_ins, num_outs;
+	int pin, ich, err;
+
+	err = uac_mixer_unit_get_channels(state, desc);
+	if (err < 0) {
+		usb_audio_err(state->chip,
+			      "invalid MIXER UNIT descriptor %d\n",
+			      unitid);
+		return err;
+	}
+
+	num_outs = err;
+	input_pins = desc->bNrInPins;
+
+	num_ins = 0;
+	ich = 0;
+	for (pin = 0; pin < input_pins; pin++) {
+		err = parse_audio_unit(state, desc->baSourceID[pin]);
+		if (err < 0)
+			continue;
+		/* no bmControls field (e.g. Maya44) -> ignore */
+		if (desc->bLength <= 10 + input_pins)
+			continue;
+		err = check_input_term(state, desc->baSourceID[pin], &iterm);
+		if (err < 0)
+			return err;
+		num_ins += iterm.channels;
+		for (; ich < num_ins; ich++) {
+			int och, ich_has_controls = 0;
+
+			for (och = 0; och < num_outs; och++) {
+				__u8 *c = uac_mixer_unit_bmControls(desc,
+						state->mixer->protocol);
+
+				if (check_matrix_bitmap(c, ich, och, num_outs)) {
+					ich_has_controls = 1;
+					break;
+				}
+			}
+			if (ich_has_controls)
+				build_mixer_unit_ctl(state, desc, pin, ich, num_outs,
+						     unitid, &iterm);
+		}
+	}
+	return 0;
+}
+
+/*
+ * Processing Unit / Extension Unit
+ */
+
+/* get callback for processing/extension unit */
+static int mixer_ctl_procunit_get(struct snd_kcontrol *kcontrol,
+				  struct snd_ctl_elem_value *ucontrol)
+{
+	struct usb_mixer_elem_info *cval = kcontrol->private_data;
+	int err, val;
+
+	err = get_cur_ctl_value(cval, cval->control << 8, &val);
+	if (err < 0) {
+		ucontrol->value.integer.value[0] = cval->min;
+		return filter_error(cval, err);
+	}
+	val = get_relative_value(cval, val);
+	ucontrol->value.integer.value[0] = val;
+	return 0;
+}
+
+/* put callback for processing/extension unit */
+static int mixer_ctl_procunit_put(struct snd_kcontrol *kcontrol,
+				  struct snd_ctl_elem_value *ucontrol)
+{
+	struct usb_mixer_elem_info *cval = kcontrol->private_data;
+	int val, oval, err;
+
+	err = get_cur_ctl_value(cval, cval->control << 8, &oval);
+	if (err < 0)
+		return filter_error(cval, err);
+	val = ucontrol->value.integer.value[0];
+	val = get_abs_value(cval, val);
+	if (val != oval) {
+		set_cur_ctl_value(cval, cval->control << 8, val);
+		return 1;
+	}
+	return 0;
+}
+
+/* alsa control interface for processing/extension unit */
+static const struct snd_kcontrol_new mixer_procunit_ctl = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "", /* will be filled later */
+	.info = mixer_ctl_feature_info,
+	.get = mixer_ctl_procunit_get,
+	.put = mixer_ctl_procunit_put,
+};
+
+/*
+ * predefined data for processing units
+ */
+struct procunit_value_info {
+	int control;
+	char *suffix;
+	int val_type;
+	int min_value;
+};
+
+struct procunit_info {
+	int type;
+	char *name;
+	struct procunit_value_info *values;
+};
+
+static struct procunit_value_info undefined_proc_info[] = {
+	{ 0x00, "Control Undefined", 0 },
+	{ 0 }
+};
+
+static struct procunit_value_info updown_proc_info[] = {
+	{ UAC_UD_ENABLE, "Switch", USB_MIXER_BOOLEAN },
+	{ UAC_UD_MODE_SELECT, "Mode Select", USB_MIXER_U8, 1 },
+	{ 0 }
+};
+static struct procunit_value_info prologic_proc_info[] = {
+	{ UAC_DP_ENABLE, "Switch", USB_MIXER_BOOLEAN },
+	{ UAC_DP_MODE_SELECT, "Mode Select", USB_MIXER_U8, 1 },
+	{ 0 }
+};
+static struct procunit_value_info threed_enh_proc_info[] = {
+	{ UAC_3D_ENABLE, "Switch", USB_MIXER_BOOLEAN },
+	{ UAC_3D_SPACE, "Spaciousness", USB_MIXER_U8 },
+	{ 0 }
+};
+static struct procunit_value_info reverb_proc_info[] = {
+	{ UAC_REVERB_ENABLE, "Switch", USB_MIXER_BOOLEAN },
+	{ UAC_REVERB_LEVEL, "Level", USB_MIXER_U8 },
+	{ UAC_REVERB_TIME, "Time", USB_MIXER_U16 },
+	{ UAC_REVERB_FEEDBACK, "Feedback", USB_MIXER_U8 },
+	{ 0 }
+};
+static struct procunit_value_info chorus_proc_info[] = {
+	{ UAC_CHORUS_ENABLE, "Switch", USB_MIXER_BOOLEAN },
+	{ UAC_CHORUS_LEVEL, "Level", USB_MIXER_U8 },
+	{ UAC_CHORUS_RATE, "Rate", USB_MIXER_U16 },
+	{ UAC_CHORUS_DEPTH, "Depth", USB_MIXER_U16 },
+	{ 0 }
+};
+static struct procunit_value_info dcr_proc_info[] = {
+	{ UAC_DCR_ENABLE, "Switch", USB_MIXER_BOOLEAN },
+	{ UAC_DCR_RATE, "Ratio", USB_MIXER_U16 },
+	{ UAC_DCR_MAXAMPL, "Max Amp", USB_MIXER_S16 },
+	{ UAC_DCR_THRESHOLD, "Threshold", USB_MIXER_S16 },
+	{ UAC_DCR_ATTACK_TIME, "Attack Time", USB_MIXER_U16 },
+	{ UAC_DCR_RELEASE_TIME, "Release Time", USB_MIXER_U16 },
+	{ 0 }
+};
+
+static struct procunit_info procunits[] = {
+	{ UAC_PROCESS_UP_DOWNMIX, "Up Down", updown_proc_info },
+	{ UAC_PROCESS_DOLBY_PROLOGIC, "Dolby Prologic", prologic_proc_info },
+	{ UAC_PROCESS_STEREO_EXTENDER, "3D Stereo Extender", threed_enh_proc_info },
+	{ UAC_PROCESS_REVERB, "Reverb", reverb_proc_info },
+	{ UAC_PROCESS_CHORUS, "Chorus", chorus_proc_info },
+	{ UAC_PROCESS_DYN_RANGE_COMP, "DCR", dcr_proc_info },
+	{ 0 },
+};
+
+static struct procunit_value_info uac3_updown_proc_info[] = {
+	{ UAC3_UD_MODE_SELECT, "Mode Select", USB_MIXER_U8, 1 },
+	{ 0 }
+};
+static struct procunit_value_info uac3_stereo_ext_proc_info[] = {
+	{ UAC3_EXT_WIDTH_CONTROL, "Width Control", USB_MIXER_U8 },
+	{ 0 }
+};
+
+static struct procunit_info uac3_procunits[] = {
+	{ UAC3_PROCESS_UP_DOWNMIX, "Up Down", uac3_updown_proc_info },
+	{ UAC3_PROCESS_STEREO_EXTENDER, "3D Stereo Extender", uac3_stereo_ext_proc_info },
+	{ UAC3_PROCESS_MULTI_FUNCTION, "Multi-Function", undefined_proc_info },
+	{ 0 },
+};
+
+/*
+ * predefined data for extension units
+ */
+static struct procunit_value_info clock_rate_xu_info[] = {
+	{ USB_XU_CLOCK_RATE_SELECTOR, "Selector", USB_MIXER_U8, 0 },
+	{ 0 }
+};
+static struct procunit_value_info clock_source_xu_info[] = {
+	{ USB_XU_CLOCK_SOURCE_SELECTOR, "External", USB_MIXER_BOOLEAN },
+	{ 0 }
+};
+static struct procunit_value_info spdif_format_xu_info[] = {
+	{ USB_XU_DIGITAL_FORMAT_SELECTOR, "SPDIF/AC3", USB_MIXER_BOOLEAN },
+	{ 0 }
+};
+static struct procunit_value_info soft_limit_xu_info[] = {
+	{ USB_XU_SOFT_LIMIT_SELECTOR, " ", USB_MIXER_BOOLEAN },
+	{ 0 }
+};
+static struct procunit_info extunits[] = {
+	{ USB_XU_CLOCK_RATE, "Clock rate", clock_rate_xu_info },
+	{ USB_XU_CLOCK_SOURCE, "DigitalIn CLK source", clock_source_xu_info },
+	{ USB_XU_DIGITAL_IO_STATUS, "DigitalOut format:", spdif_format_xu_info },
+	{ USB_XU_DEVICE_OPTIONS, "AnalogueIn Soft Limit", soft_limit_xu_info },
+	{ 0 }
+};
+
+/*
+ * build a processing/extension unit
+ */
+static int build_audio_procunit(struct mixer_build *state, int unitid,
+				void *raw_desc, struct procunit_info *list,
+				char *name)
+{
+	struct uac_processing_unit_descriptor *desc = raw_desc;
+	int num_ins = desc->bNrInPins;
+	struct usb_mixer_elem_info *cval;
+	struct snd_kcontrol *kctl;
+	int i, err, nameid, type, len;
+	struct procunit_info *info;
+	struct procunit_value_info *valinfo;
+	const struct usbmix_name_map *map;
+	static struct procunit_value_info default_value_info[] = {
+		{ 0x01, "Switch", USB_MIXER_BOOLEAN },
+		{ 0 }
+	};
+	static struct procunit_info default_info = {
+		0, NULL, default_value_info
+	};
+
+	if (desc->bLength < 13 || desc->bLength < 13 + num_ins ||
+	    desc->bLength < num_ins + uac_processing_unit_bControlSize(desc, state->mixer->protocol)) {
+		usb_audio_err(state->chip, "invalid %s descriptor (id %d)\n", name, unitid);
+		return -EINVAL;
+	}
+
+	for (i = 0; i < num_ins; i++) {
+		err = parse_audio_unit(state, desc->baSourceID[i]);
+		if (err < 0)
+			return err;
+	}
+
+	type = le16_to_cpu(desc->wProcessType);
+	for (info = list; info && info->type; info++)
+		if (info->type == type)
+			break;
+	if (!info || !info->type)
+		info = &default_info;
+
+	for (valinfo = info->values; valinfo->control; valinfo++) {
+		__u8 *controls = uac_processing_unit_bmControls(desc, state->mixer->protocol);
+
+		if (state->mixer->protocol == UAC_VERSION_1) {
+			if (!(controls[valinfo->control / 8] &
+					(1 << ((valinfo->control % 8) - 1))))
+				continue;
+		} else { /* UAC_VERSION_2/3 */
+			if (!uac_v2v3_control_is_readable(controls[valinfo->control / 8],
+							  valinfo->control))
+				continue;
+		}
+
+		map = find_map(state->map, unitid, valinfo->control);
+		if (check_ignored_ctl(map))
+			continue;
+		cval = kzalloc(sizeof(*cval), GFP_KERNEL);
+		if (!cval)
+			return -ENOMEM;
+		snd_usb_mixer_elem_init_std(&cval->head, state->mixer, unitid);
+		cval->control = valinfo->control;
+		cval->val_type = valinfo->val_type;
+		cval->channels = 1;
+
+		if (state->mixer->protocol > UAC_VERSION_1 &&
+		    !uac_v2v3_control_is_writeable(controls[valinfo->control / 8],
+						   valinfo->control))
+			cval->master_readonly = 1;
+
+		/* get min/max values */
+		switch (type) {
+		case UAC_PROCESS_UP_DOWNMIX: {
+			bool mode_sel = false;
+
+			switch (state->mixer->protocol) {
+			case UAC_VERSION_1:
+			case UAC_VERSION_2:
+			default:
+				if (cval->control == UAC_UD_MODE_SELECT)
+					mode_sel = true;
+				break;
+			case UAC_VERSION_3:
+				if (cval->control == UAC3_UD_MODE_SELECT)
+					mode_sel = true;
+				break;
+			}
+
+			if (mode_sel) {
+				__u8 *control_spec = uac_processing_unit_specific(desc,
+								state->mixer->protocol);
+				cval->min = 1;
+				cval->max = control_spec[0];
+				cval->res = 1;
+				cval->initialized = 1;
+				break;
+			}
+
+			get_min_max(cval, valinfo->min_value);
+			break;
+		}
+		case USB_XU_CLOCK_RATE:
+			/*
+			 * E-Mu USB 0404/0202/TrackerPre/0204
+			 * samplerate control quirk
+			 */
+			cval->min = 0;
+			cval->max = 5;
+			cval->res = 1;
+			cval->initialized = 1;
+			break;
+		default:
+			get_min_max(cval, valinfo->min_value);
+			break;
+		}
+
+		kctl = snd_ctl_new1(&mixer_procunit_ctl, cval);
+		if (!kctl) {
+			kfree(cval);
+			return -ENOMEM;
+		}
+		kctl->private_free = snd_usb_mixer_elem_free;
+
+		if (check_mapped_name(map, kctl->id.name, sizeof(kctl->id.name))) {
+			/* nothing */ ;
+		} else if (info->name) {
+			strlcpy(kctl->id.name, info->name, sizeof(kctl->id.name));
+		} else {
+			nameid = uac_processing_unit_iProcessing(desc, state->mixer->protocol);
+			len = 0;
+			if (nameid)
+				len = snd_usb_copy_string_desc(state->chip,
+							       nameid,
+							       kctl->id.name,
+							       sizeof(kctl->id.name));
+			if (!len)
+				strlcpy(kctl->id.name, name, sizeof(kctl->id.name));
+		}
+		append_ctl_name(kctl, " ");
+		append_ctl_name(kctl, valinfo->suffix);
+
+		usb_audio_dbg(state->chip,
+			      "[%d] PU [%s] ch = %d, val = %d/%d\n",
+			      cval->head.id, kctl->id.name, cval->channels,
+			      cval->min, cval->max);
+
+		err = snd_usb_mixer_add_control(&cval->head, kctl);
+		if (err < 0)
+			return err;
+	}
+	return 0;
+}
+
+static int parse_audio_processing_unit(struct mixer_build *state, int unitid,
+				       void *raw_desc)
+{
+	switch (state->mixer->protocol) {
+	case UAC_VERSION_1:
+	case UAC_VERSION_2:
+	default:
+		return build_audio_procunit(state, unitid, raw_desc,
+				procunits, "Processing Unit");
+	case UAC_VERSION_3:
+		return build_audio_procunit(state, unitid, raw_desc,
+				uac3_procunits, "Processing Unit");
+	}
+}
+
+static int parse_audio_extension_unit(struct mixer_build *state, int unitid,
+				      void *raw_desc)
+{
+	/*
+	 * Note that we parse extension units with processing unit descriptors.
+	 * That's ok as the layout is the same.
+	 */
+	return build_audio_procunit(state, unitid, raw_desc,
+				    extunits, "Extension Unit");
+}
+
+/*
+ * Selector Unit
+ */
+
+/*
+ * info callback for selector unit
+ * use an enumerator type for routing
+ */
+static int mixer_ctl_selector_info(struct snd_kcontrol *kcontrol,
+				   struct snd_ctl_elem_info *uinfo)
+{
+	struct usb_mixer_elem_info *cval = kcontrol->private_data;
+	const char **itemlist = (const char **)kcontrol->private_value;
+
+	if (snd_BUG_ON(!itemlist))
+		return -EINVAL;
+	return snd_ctl_enum_info(uinfo, 1, cval->max, itemlist);
+}
+
+/* get callback for selector unit */
+static int mixer_ctl_selector_get(struct snd_kcontrol *kcontrol,
+				  struct snd_ctl_elem_value *ucontrol)
+{
+	struct usb_mixer_elem_info *cval = kcontrol->private_data;
+	int val, err;
+
+	err = get_cur_ctl_value(cval, cval->control << 8, &val);
+	if (err < 0) {
+		ucontrol->value.enumerated.item[0] = 0;
+		return filter_error(cval, err);
+	}
+	val = get_relative_value(cval, val);
+	ucontrol->value.enumerated.item[0] = val;
+	return 0;
+}
+
+/* put callback for selector unit */
+static int mixer_ctl_selector_put(struct snd_kcontrol *kcontrol,
+				  struct snd_ctl_elem_value *ucontrol)
+{
+	struct usb_mixer_elem_info *cval = kcontrol->private_data;
+	int val, oval, err;
+
+	err = get_cur_ctl_value(cval, cval->control << 8, &oval);
+	if (err < 0)
+		return filter_error(cval, err);
+	val = ucontrol->value.enumerated.item[0];
+	val = get_abs_value(cval, val);
+	if (val != oval) {
+		set_cur_ctl_value(cval, cval->control << 8, val);
+		return 1;
+	}
+	return 0;
+}
+
+/* alsa control interface for selector unit */
+static const struct snd_kcontrol_new mixer_selectunit_ctl = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "", /* will be filled later */
+	.info = mixer_ctl_selector_info,
+	.get = mixer_ctl_selector_get,
+	.put = mixer_ctl_selector_put,
+};
+
+/*
+ * private free callback.
+ * free both private_data and private_value
+ */
+static void usb_mixer_selector_elem_free(struct snd_kcontrol *kctl)
+{
+	int i, num_ins = 0;
+
+	if (kctl->private_data) {
+		struct usb_mixer_elem_info *cval = kctl->private_data;
+		num_ins = cval->max;
+		kfree(cval);
+		kctl->private_data = NULL;
+	}
+	if (kctl->private_value) {
+		char **itemlist = (char **)kctl->private_value;
+		for (i = 0; i < num_ins; i++)
+			kfree(itemlist[i]);
+		kfree(itemlist);
+		kctl->private_value = 0;
+	}
+}
+
+/*
+ * parse a selector unit
+ */
+static int parse_audio_selector_unit(struct mixer_build *state, int unitid,
+				     void *raw_desc)
+{
+	struct uac_selector_unit_descriptor *desc = raw_desc;
+	unsigned int i, nameid, len;
+	int err;
+	struct usb_mixer_elem_info *cval;
+	struct snd_kcontrol *kctl;
+	const struct usbmix_name_map *map;
+	char **namelist;
+
+	if (desc->bLength < 5 || !desc->bNrInPins ||
+	    desc->bLength < 5 + desc->bNrInPins) {
+		usb_audio_err(state->chip,
+			"invalid SELECTOR UNIT descriptor %d\n", unitid);
+		return -EINVAL;
+	}
+
+	for (i = 0; i < desc->bNrInPins; i++) {
+		err = parse_audio_unit(state, desc->baSourceID[i]);
+		if (err < 0)
+			return err;
+	}
+
+	if (desc->bNrInPins == 1) /* only one ? nonsense! */
+		return 0;
+
+	map = find_map(state->map, unitid, 0);
+	if (check_ignored_ctl(map))
+		return 0;
+
+	cval = kzalloc(sizeof(*cval), GFP_KERNEL);
+	if (!cval)
+		return -ENOMEM;
+	snd_usb_mixer_elem_init_std(&cval->head, state->mixer, unitid);
+	cval->val_type = USB_MIXER_U8;
+	cval->channels = 1;
+	cval->min = 1;
+	cval->max = desc->bNrInPins;
+	cval->res = 1;
+	cval->initialized = 1;
+
+	switch (state->mixer->protocol) {
+	case UAC_VERSION_1:
+	default:
+		cval->control = 0;
+		break;
+	case UAC_VERSION_2:
+	case UAC_VERSION_3:
+		if (desc->bDescriptorSubtype == UAC2_CLOCK_SELECTOR ||
+		    desc->bDescriptorSubtype == UAC3_CLOCK_SELECTOR)
+			cval->control = UAC2_CX_CLOCK_SELECTOR;
+		else /* UAC2/3_SELECTOR_UNIT */
+			cval->control = UAC2_SU_SELECTOR;
+		break;
+	}
+
+	namelist = kmalloc_array(desc->bNrInPins, sizeof(char *), GFP_KERNEL);
+	if (!namelist) {
+		kfree(cval);
+		return -ENOMEM;
+	}
+#define MAX_ITEM_NAME_LEN	64
+	for (i = 0; i < desc->bNrInPins; i++) {
+		struct usb_audio_term iterm;
+		len = 0;
+		namelist[i] = kmalloc(MAX_ITEM_NAME_LEN, GFP_KERNEL);
+		if (!namelist[i]) {
+			while (i--)
+				kfree(namelist[i]);
+			kfree(namelist);
+			kfree(cval);
+			return -ENOMEM;
+		}
+		len = check_mapped_selector_name(state, unitid, i, namelist[i],
+						 MAX_ITEM_NAME_LEN);
+		if (! len && check_input_term(state, desc->baSourceID[i], &iterm) >= 0)
+			len = get_term_name(state->chip, &iterm, namelist[i],
+					    MAX_ITEM_NAME_LEN, 0);
+		if (! len)
+			sprintf(namelist[i], "Input %u", i);
+	}
+
+	kctl = snd_ctl_new1(&mixer_selectunit_ctl, cval);
+	if (! kctl) {
+		usb_audio_err(state->chip, "cannot malloc kcontrol\n");
+		kfree(namelist);
+		kfree(cval);
+		return -ENOMEM;
+	}
+	kctl->private_value = (unsigned long)namelist;
+	kctl->private_free = usb_mixer_selector_elem_free;
+
+	/* check the static mapping table at first */
+	len = check_mapped_name(map, kctl->id.name, sizeof(kctl->id.name));
+	if (!len) {
+		/* no mapping ? */
+		switch (state->mixer->protocol) {
+		case UAC_VERSION_1:
+		case UAC_VERSION_2:
+		default:
+		/* if iSelector is given, use it */
+			nameid = uac_selector_unit_iSelector(desc);
+			if (nameid)
+				len = snd_usb_copy_string_desc(state->chip,
+							nameid, kctl->id.name,
+							sizeof(kctl->id.name));
+			break;
+		case UAC_VERSION_3:
+			/* TODO: Class-Specific strings not yet supported */
+			break;
+		}
+
+		/* ... or pick up the terminal name at next */
+		if (!len)
+			len = get_term_name(state->chip, &state->oterm,
+				    kctl->id.name, sizeof(kctl->id.name), 0);
+		/* ... or use the fixed string "USB" as the last resort */
+		if (!len)
+			strlcpy(kctl->id.name, "USB", sizeof(kctl->id.name));
+
+		/* and add the proper suffix */
+		if (desc->bDescriptorSubtype == UAC2_CLOCK_SELECTOR ||
+		    desc->bDescriptorSubtype == UAC3_CLOCK_SELECTOR)
+			append_ctl_name(kctl, " Clock Source");
+		else if ((state->oterm.type & 0xff00) == 0x0100)
+			append_ctl_name(kctl, " Capture Source");
+		else
+			append_ctl_name(kctl, " Playback Source");
+	}
+
+	usb_audio_dbg(state->chip, "[%d] SU [%s] items = %d\n",
+		    cval->head.id, kctl->id.name, desc->bNrInPins);
+	return snd_usb_mixer_add_control(&cval->head, kctl);
+}
+
+/*
+ * parse an audio unit recursively
+ */
+
+static int parse_audio_unit(struct mixer_build *state, int unitid)
+{
+	unsigned char *p1;
+	int protocol = state->mixer->protocol;
+
+	if (test_and_set_bit(unitid, state->unitbitmap))
+		return 0; /* the unit already visited */
+
+	p1 = find_audio_control_unit(state, unitid);
+	if (!p1) {
+		usb_audio_err(state->chip, "unit %d not found!\n", unitid);
+		return -EINVAL;
+	}
+
+	if (protocol == UAC_VERSION_1 || protocol == UAC_VERSION_2) {
+		switch (p1[2]) {
+		case UAC_INPUT_TERMINAL:
+			return parse_audio_input_terminal(state, unitid, p1);
+		case UAC_MIXER_UNIT:
+			return parse_audio_mixer_unit(state, unitid, p1);
+		case UAC2_CLOCK_SOURCE:
+			return parse_clock_source_unit(state, unitid, p1);
+		case UAC_SELECTOR_UNIT:
+		case UAC2_CLOCK_SELECTOR:
+			return parse_audio_selector_unit(state, unitid, p1);
+		case UAC_FEATURE_UNIT:
+			return parse_audio_feature_unit(state, unitid, p1);
+		case UAC1_PROCESSING_UNIT:
+		/*   UAC2_EFFECT_UNIT has the same value */
+			if (protocol == UAC_VERSION_1)
+				return parse_audio_processing_unit(state, unitid, p1);
+			else
+				return 0; /* FIXME - effect units not implemented yet */
+		case UAC1_EXTENSION_UNIT:
+		/*   UAC2_PROCESSING_UNIT_V2 has the same value */
+			if (protocol == UAC_VERSION_1)
+				return parse_audio_extension_unit(state, unitid, p1);
+			else /* UAC_VERSION_2 */
+				return parse_audio_processing_unit(state, unitid, p1);
+		case UAC2_EXTENSION_UNIT_V2:
+			return parse_audio_extension_unit(state, unitid, p1);
+		default:
+			usb_audio_err(state->chip,
+				"unit %u: unexpected type 0x%02x\n", unitid, p1[2]);
+			return -EINVAL;
+		}
+	} else { /* UAC_VERSION_3 */
+		switch (p1[2]) {
+		case UAC_INPUT_TERMINAL:
+			return parse_audio_input_terminal(state, unitid, p1);
+		case UAC3_MIXER_UNIT:
+			return parse_audio_mixer_unit(state, unitid, p1);
+		case UAC3_CLOCK_SOURCE:
+			return parse_clock_source_unit(state, unitid, p1);
+		case UAC3_SELECTOR_UNIT:
+		case UAC3_CLOCK_SELECTOR:
+			return parse_audio_selector_unit(state, unitid, p1);
+		case UAC3_FEATURE_UNIT:
+			return parse_audio_feature_unit(state, unitid, p1);
+		case UAC3_EFFECT_UNIT:
+			return 0; /* FIXME - effect units not implemented yet */
+		case UAC3_PROCESSING_UNIT:
+			return parse_audio_processing_unit(state, unitid, p1);
+		case UAC3_EXTENSION_UNIT:
+			return parse_audio_extension_unit(state, unitid, p1);
+		default:
+			usb_audio_err(state->chip,
+				"unit %u: unexpected type 0x%02x\n", unitid, p1[2]);
+			return -EINVAL;
+		}
+	}
+}
+
+static void snd_usb_mixer_free(struct usb_mixer_interface *mixer)
+{
+	/* kill pending URBs */
+	snd_usb_mixer_disconnect(mixer);
+
+	kfree(mixer->id_elems);
+	if (mixer->urb) {
+		kfree(mixer->urb->transfer_buffer);
+		usb_free_urb(mixer->urb);
+	}
+	usb_free_urb(mixer->rc_urb);
+	kfree(mixer->rc_setup_packet);
+	kfree(mixer);
+}
+
+static int snd_usb_mixer_dev_free(struct snd_device *device)
+{
+	struct usb_mixer_interface *mixer = device->device_data;
+	snd_usb_mixer_free(mixer);
+	return 0;
+}
+
+/* UAC3 predefined channels configuration */
+struct uac3_badd_profile {
+	int subclass;
+	const char *name;
+	int c_chmask;	/* capture channels mask */
+	int p_chmask;	/* playback channels mask */
+	int st_chmask;	/* side tone mixing channel mask */
+};
+
+static struct uac3_badd_profile uac3_badd_profiles[] = {
+	{
+		/*
+		 * BAIF, BAOF or combination of both
+		 * IN: Mono or Stereo cfg, Mono alt possible
+		 * OUT: Mono or Stereo cfg, Mono alt possible
+		 */
+		.subclass = UAC3_FUNCTION_SUBCLASS_GENERIC_IO,
+		.name = "GENERIC IO",
+		.c_chmask = -1,		/* dynamic channels */
+		.p_chmask = -1,		/* dynamic channels */
+	},
+	{
+		/* BAOF; Stereo only cfg, Mono alt possible */
+		.subclass = UAC3_FUNCTION_SUBCLASS_HEADPHONE,
+		.name = "HEADPHONE",
+		.p_chmask = 3,
+	},
+	{
+		/* BAOF; Mono or Stereo cfg, Mono alt possible */
+		.subclass = UAC3_FUNCTION_SUBCLASS_SPEAKER,
+		.name = "SPEAKER",
+		.p_chmask = -1,		/* dynamic channels */
+	},
+	{
+		/* BAIF; Mono or Stereo cfg, Mono alt possible */
+		.subclass = UAC3_FUNCTION_SUBCLASS_MICROPHONE,
+		.name = "MICROPHONE",
+		.c_chmask = -1,		/* dynamic channels */
+	},
+	{
+		/*
+		 * BAIOF topology
+		 * IN: Mono only
+		 * OUT: Mono or Stereo cfg, Mono alt possible
+		 */
+		.subclass = UAC3_FUNCTION_SUBCLASS_HEADSET,
+		.name = "HEADSET",
+		.c_chmask = 1,
+		.p_chmask = -1,		/* dynamic channels */
+		.st_chmask = 1,
+	},
+	{
+		/* BAIOF; IN: Mono only; OUT: Stereo only, Mono alt possible */
+		.subclass = UAC3_FUNCTION_SUBCLASS_HEADSET_ADAPTER,
+		.name = "HEADSET ADAPTER",
+		.c_chmask = 1,
+		.p_chmask = 3,
+		.st_chmask = 1,
+	},
+	{
+		/* BAIF + BAOF; IN: Mono only; OUT: Mono only */
+		.subclass = UAC3_FUNCTION_SUBCLASS_SPEAKERPHONE,
+		.name = "SPEAKERPHONE",
+		.c_chmask = 1,
+		.p_chmask = 1,
+	},
+	{ 0 } /* terminator */
+};
+
+static bool uac3_badd_func_has_valid_channels(struct usb_mixer_interface *mixer,
+					      struct uac3_badd_profile *f,
+					      int c_chmask, int p_chmask)
+{
+	/*
+	 * If both playback/capture channels are dynamic, make sure
+	 * at least one channel is present
+	 */
+	if (f->c_chmask < 0 && f->p_chmask < 0) {
+		if (!c_chmask && !p_chmask) {
+			usb_audio_warn(mixer->chip, "BAAD %s: no channels?",
+				       f->name);
+			return false;
+		}
+		return true;
+	}
+
+	if ((f->c_chmask < 0 && !c_chmask) ||
+	    (f->c_chmask >= 0 && f->c_chmask != c_chmask)) {
+		usb_audio_warn(mixer->chip, "BAAD %s c_chmask mismatch",
+			       f->name);
+		return false;
+	}
+	if ((f->p_chmask < 0 && !p_chmask) ||
+	    (f->p_chmask >= 0 && f->p_chmask != p_chmask)) {
+		usb_audio_warn(mixer->chip, "BAAD %s p_chmask mismatch",
+			       f->name);
+		return false;
+	}
+	return true;
+}
+
+/*
+ * create mixer controls for UAC3 BADD profiles
+ *
+ * UAC3 BADD device doesn't contain CS descriptors thus we will guess everything
+ *
+ * BADD device may contain Mixer Unit, which doesn't have any controls, skip it
+ */
+static int snd_usb_mixer_controls_badd(struct usb_mixer_interface *mixer,
+				       int ctrlif)
+{
+	struct usb_device *dev = mixer->chip->dev;
+	struct usb_interface_assoc_descriptor *assoc;
+	int badd_profile = mixer->chip->badd_profile;
+	struct uac3_badd_profile *f;
+	const struct usbmix_ctl_map *map;
+	int p_chmask = 0, c_chmask = 0, st_chmask = 0;
+	int i;
+
+	assoc = usb_ifnum_to_if(dev, ctrlif)->intf_assoc;
+
+	/* Detect BADD capture/playback channels from AS EP descriptors */
+	for (i = 0; i < assoc->bInterfaceCount; i++) {
+		int intf = assoc->bFirstInterface + i;
+
+		struct usb_interface *iface;
+		struct usb_host_interface *alts;
+		struct usb_interface_descriptor *altsd;
+		unsigned int maxpacksize;
+		char dir_in;
+		int chmask, num;
+
+		if (intf == ctrlif)
+			continue;
+
+		iface = usb_ifnum_to_if(dev, intf);
+		num = iface->num_altsetting;
+
+		if (num < 2)
+			return -EINVAL;
+
+		/*
+		 * The number of Channels in an AudioStreaming interface
+		 * and the audio sample bit resolution (16 bits or 24
+		 * bits) can be derived from the wMaxPacketSize field in
+		 * the Standard AS Audio Data Endpoint descriptor in
+		 * Alternate Setting 1
+		 */
+		alts = &iface->altsetting[1];
+		altsd = get_iface_desc(alts);
+
+		if (altsd->bNumEndpoints < 1)
+			return -EINVAL;
+
+		/* check direction */
+		dir_in = (get_endpoint(alts, 0)->bEndpointAddress & USB_DIR_IN);
+		maxpacksize = le16_to_cpu(get_endpoint(alts, 0)->wMaxPacketSize);
+
+		switch (maxpacksize) {
+		default:
+			usb_audio_err(mixer->chip,
+				"incorrect wMaxPacketSize 0x%x for BADD profile\n",
+				maxpacksize);
+			return -EINVAL;
+		case UAC3_BADD_EP_MAXPSIZE_SYNC_MONO_16:
+		case UAC3_BADD_EP_MAXPSIZE_ASYNC_MONO_16:
+		case UAC3_BADD_EP_MAXPSIZE_SYNC_MONO_24:
+		case UAC3_BADD_EP_MAXPSIZE_ASYNC_MONO_24:
+			chmask = 1;
+			break;
+		case UAC3_BADD_EP_MAXPSIZE_SYNC_STEREO_16:
+		case UAC3_BADD_EP_MAXPSIZE_ASYNC_STEREO_16:
+		case UAC3_BADD_EP_MAXPSIZE_SYNC_STEREO_24:
+		case UAC3_BADD_EP_MAXPSIZE_ASYNC_STEREO_24:
+			chmask = 3;
+			break;
+		}
+
+		if (dir_in)
+			c_chmask = chmask;
+		else
+			p_chmask = chmask;
+	}
+
+	usb_audio_dbg(mixer->chip,
+		"UAC3 BADD profile 0x%x: detected c_chmask=%d p_chmask=%d\n",
+		badd_profile, c_chmask, p_chmask);
+
+	/* check the mapping table */
+	for (map = uac3_badd_usbmix_ctl_maps; map->id; map++) {
+		if (map->id == badd_profile)
+			break;
+	}
+
+	if (!map->id)
+		return -EINVAL;
+
+	for (f = uac3_badd_profiles; f->name; f++) {
+		if (badd_profile == f->subclass)
+			break;
+	}
+	if (!f->name)
+		return -EINVAL;
+	if (!uac3_badd_func_has_valid_channels(mixer, f, c_chmask, p_chmask))
+		return -EINVAL;
+	st_chmask = f->st_chmask;
+
+	/* Playback */
+	if (p_chmask) {
+		/* Master channel, always writable */
+		build_feature_ctl_badd(mixer, 0, UAC_FU_MUTE,
+				       UAC3_BADD_FU_ID2, map->map);
+		/* Mono/Stereo volume channels, always writable */
+		build_feature_ctl_badd(mixer, p_chmask, UAC_FU_VOLUME,
+				       UAC3_BADD_FU_ID2, map->map);
+	}
+
+	/* Capture */
+	if (c_chmask) {
+		/* Master channel, always writable */
+		build_feature_ctl_badd(mixer, 0, UAC_FU_MUTE,
+				       UAC3_BADD_FU_ID5, map->map);
+		/* Mono/Stereo volume channels, always writable */
+		build_feature_ctl_badd(mixer, c_chmask, UAC_FU_VOLUME,
+				       UAC3_BADD_FU_ID5, map->map);
+	}
+
+	/* Side tone-mixing */
+	if (st_chmask) {
+		/* Master channel, always writable */
+		build_feature_ctl_badd(mixer, 0, UAC_FU_MUTE,
+				       UAC3_BADD_FU_ID7, map->map);
+		/* Mono volume channel, always writable */
+		build_feature_ctl_badd(mixer, 1, UAC_FU_VOLUME,
+				       UAC3_BADD_FU_ID7, map->map);
+	}
+
+	/* Insertion Control */
+	if (f->subclass == UAC3_FUNCTION_SUBCLASS_HEADSET_ADAPTER) {
+		struct usb_audio_term iterm, oterm;
+
+		/* Input Term - Insertion control */
+		memset(&iterm, 0, sizeof(iterm));
+		iterm.id = UAC3_BADD_IT_ID4;
+		iterm.type = UAC_BIDIR_TERMINAL_HEADSET;
+		build_connector_control(mixer, &iterm, true);
+
+		/* Output Term - Insertion control */
+		memset(&oterm, 0, sizeof(oterm));
+		oterm.id = UAC3_BADD_OT_ID3;
+		oterm.type = UAC_BIDIR_TERMINAL_HEADSET;
+		build_connector_control(mixer, &oterm, false);
+	}
+
+	return 0;
+}
+
+/*
+ * create mixer controls
+ *
+ * walk through all UAC_OUTPUT_TERMINAL descriptors to search for mixers
+ */
+static int snd_usb_mixer_controls(struct usb_mixer_interface *mixer)
+{
+	struct mixer_build state;
+	int err;
+	const struct usbmix_ctl_map *map;
+	void *p;
+
+	memset(&state, 0, sizeof(state));
+	state.chip = mixer->chip;
+	state.mixer = mixer;
+	state.buffer = mixer->hostif->extra;
+	state.buflen = mixer->hostif->extralen;
+
+	/* check the mapping table */
+	for (map = usbmix_ctl_maps; map->id; map++) {
+		if (map->id == state.chip->usb_id) {
+			state.map = map->map;
+			state.selector_map = map->selector_map;
+			mixer->ignore_ctl_error = map->ignore_ctl_error;
+			break;
+		}
+	}
+
+	p = NULL;
+	while ((p = snd_usb_find_csint_desc(mixer->hostif->extra,
+					    mixer->hostif->extralen,
+					    p, UAC_OUTPUT_TERMINAL)) != NULL) {
+		if (mixer->protocol == UAC_VERSION_1) {
+			struct uac1_output_terminal_descriptor *desc = p;
+
+			if (desc->bLength < sizeof(*desc))
+				continue; /* invalid descriptor? */
+			/* mark terminal ID as visited */
+			set_bit(desc->bTerminalID, state.unitbitmap);
+			state.oterm.id = desc->bTerminalID;
+			state.oterm.type = le16_to_cpu(desc->wTerminalType);
+			state.oterm.name = desc->iTerminal;
+			err = parse_audio_unit(&state, desc->bSourceID);
+			if (err < 0 && err != -EINVAL)
+				return err;
+		} else if (mixer->protocol == UAC_VERSION_2) {
+			struct uac2_output_terminal_descriptor *desc = p;
+
+			if (desc->bLength < sizeof(*desc))
+				continue; /* invalid descriptor? */
+			/* mark terminal ID as visited */
+			set_bit(desc->bTerminalID, state.unitbitmap);
+			state.oterm.id = desc->bTerminalID;
+			state.oterm.type = le16_to_cpu(desc->wTerminalType);
+			state.oterm.name = desc->iTerminal;
+			err = parse_audio_unit(&state, desc->bSourceID);
+			if (err < 0 && err != -EINVAL)
+				return err;
+
+			/*
+			 * For UAC2, use the same approach to also add the
+			 * clock selectors
+			 */
+			err = parse_audio_unit(&state, desc->bCSourceID);
+			if (err < 0 && err != -EINVAL)
+				return err;
+
+			if (uac_v2v3_control_is_readable(le16_to_cpu(desc->bmControls),
+							 UAC2_TE_CONNECTOR)) {
+				build_connector_control(state.mixer, &state.oterm,
+							false);
+			}
+		} else {  /* UAC_VERSION_3 */
+			struct uac3_output_terminal_descriptor *desc = p;
+
+			if (desc->bLength < sizeof(*desc))
+				continue; /* invalid descriptor? */
+			/* mark terminal ID as visited */
+			set_bit(desc->bTerminalID, state.unitbitmap);
+			state.oterm.id = desc->bTerminalID;
+			state.oterm.type = le16_to_cpu(desc->wTerminalType);
+			state.oterm.name = le16_to_cpu(desc->wTerminalDescrStr);
+			err = parse_audio_unit(&state, desc->bSourceID);
+			if (err < 0 && err != -EINVAL)
+				return err;
+
+			/*
+			 * For UAC3, use the same approach to also add the
+			 * clock selectors
+			 */
+			err = parse_audio_unit(&state, desc->bCSourceID);
+			if (err < 0 && err != -EINVAL)
+				return err;
+
+			if (uac_v2v3_control_is_readable(le32_to_cpu(desc->bmControls),
+							 UAC3_TE_INSERTION)) {
+				build_connector_control(state.mixer, &state.oterm,
+							false);
+			}
+		}
+	}
+
+	return 0;
+}
+
+void snd_usb_mixer_notify_id(struct usb_mixer_interface *mixer, int unitid)
+{
+	struct usb_mixer_elem_list *list;
+
+	for_each_mixer_elem(list, mixer, unitid) {
+		struct usb_mixer_elem_info *info =
+			mixer_elem_list_to_info(list);
+		/* invalidate cache, so the value is read from the device */
+		info->cached = 0;
+		snd_ctl_notify(mixer->chip->card, SNDRV_CTL_EVENT_MASK_VALUE,
+			       &list->kctl->id);
+	}
+}
+
+static void snd_usb_mixer_dump_cval(struct snd_info_buffer *buffer,
+				    struct usb_mixer_elem_list *list)
+{
+	struct usb_mixer_elem_info *cval = mixer_elem_list_to_info(list);
+	static char *val_types[] = {"BOOLEAN", "INV_BOOLEAN",
+				    "S8", "U8", "S16", "U16"};
+	snd_iprintf(buffer, "    Info: id=%i, control=%i, cmask=0x%x, "
+			    "channels=%i, type=\"%s\"\n", cval->head.id,
+			    cval->control, cval->cmask, cval->channels,
+			    val_types[cval->val_type]);
+	snd_iprintf(buffer, "    Volume: min=%i, max=%i, dBmin=%i, dBmax=%i\n",
+			    cval->min, cval->max, cval->dBmin, cval->dBmax);
+}
+
+static void snd_usb_mixer_proc_read(struct snd_info_entry *entry,
+				    struct snd_info_buffer *buffer)
+{
+	struct snd_usb_audio *chip = entry->private_data;
+	struct usb_mixer_interface *mixer;
+	struct usb_mixer_elem_list *list;
+	int unitid;
+
+	list_for_each_entry(mixer, &chip->mixer_list, list) {
+		snd_iprintf(buffer,
+			"USB Mixer: usb_id=0x%08x, ctrlif=%i, ctlerr=%i\n",
+				chip->usb_id, snd_usb_ctrl_intf(chip),
+				mixer->ignore_ctl_error);
+		snd_iprintf(buffer, "Card: %s\n", chip->card->longname);
+		for (unitid = 0; unitid < MAX_ID_ELEMS; unitid++) {
+			for_each_mixer_elem(list, mixer, unitid) {
+				snd_iprintf(buffer, "  Unit: %i\n", list->id);
+				if (list->kctl)
+					snd_iprintf(buffer,
+						    "    Control: name=\"%s\", index=%i\n",
+						    list->kctl->id.name,
+						    list->kctl->id.index);
+				if (list->dump)
+					list->dump(buffer, list);
+			}
+		}
+	}
+}
+
+static void snd_usb_mixer_interrupt_v2(struct usb_mixer_interface *mixer,
+				       int attribute, int value, int index)
+{
+	struct usb_mixer_elem_list *list;
+	__u8 unitid = (index >> 8) & 0xff;
+	__u8 control = (value >> 8) & 0xff;
+	__u8 channel = value & 0xff;
+	unsigned int count = 0;
+
+	if (channel >= MAX_CHANNELS) {
+		usb_audio_dbg(mixer->chip,
+			"%s(): bogus channel number %d\n",
+			__func__, channel);
+		return;
+	}
+
+	for_each_mixer_elem(list, mixer, unitid)
+		count++;
+
+	if (count == 0)
+		return;
+
+	for_each_mixer_elem(list, mixer, unitid) {
+		struct usb_mixer_elem_info *info;
+
+		if (!list->kctl)
+			continue;
+
+		info = mixer_elem_list_to_info(list);
+		if (count > 1 && info->control != control)
+			continue;
+
+		switch (attribute) {
+		case UAC2_CS_CUR:
+			/* invalidate cache, so the value is read from the device */
+			if (channel)
+				info->cached &= ~(1 << channel);
+			else /* master channel */
+				info->cached = 0;
+
+			snd_ctl_notify(mixer->chip->card, SNDRV_CTL_EVENT_MASK_VALUE,
+				       &info->head.kctl->id);
+			break;
+
+		case UAC2_CS_RANGE:
+			/* TODO */
+			break;
+
+		case UAC2_CS_MEM:
+			/* TODO */
+			break;
+
+		default:
+			usb_audio_dbg(mixer->chip,
+				"unknown attribute %d in interrupt\n",
+				attribute);
+			break;
+		} /* switch */
+	}
+}
+
+static void snd_usb_mixer_interrupt(struct urb *urb)
+{
+	struct usb_mixer_interface *mixer = urb->context;
+	int len = urb->actual_length;
+	int ustatus = urb->status;
+
+	if (ustatus != 0)
+		goto requeue;
+
+	if (mixer->protocol == UAC_VERSION_1) {
+		struct uac1_status_word *status;
+
+		for (status = urb->transfer_buffer;
+		     len >= sizeof(*status);
+		     len -= sizeof(*status), status++) {
+			dev_dbg(&urb->dev->dev, "status interrupt: %02x %02x\n",
+						status->bStatusType,
+						status->bOriginator);
+
+			/* ignore any notifications not from the control interface */
+			if ((status->bStatusType & UAC1_STATUS_TYPE_ORIG_MASK) !=
+				UAC1_STATUS_TYPE_ORIG_AUDIO_CONTROL_IF)
+				continue;
+
+			if (status->bStatusType & UAC1_STATUS_TYPE_MEM_CHANGED)
+				snd_usb_mixer_rc_memory_change(mixer, status->bOriginator);
+			else
+				snd_usb_mixer_notify_id(mixer, status->bOriginator);
+		}
+	} else { /* UAC_VERSION_2 */
+		struct uac2_interrupt_data_msg *msg;
+
+		for (msg = urb->transfer_buffer;
+		     len >= sizeof(*msg);
+		     len -= sizeof(*msg), msg++) {
+			/* drop vendor specific and endpoint requests */
+			if ((msg->bInfo & UAC2_INTERRUPT_DATA_MSG_VENDOR) ||
+			    (msg->bInfo & UAC2_INTERRUPT_DATA_MSG_EP))
+				continue;
+
+			snd_usb_mixer_interrupt_v2(mixer, msg->bAttribute,
+						   le16_to_cpu(msg->wValue),
+						   le16_to_cpu(msg->wIndex));
+		}
+	}
+
+requeue:
+	if (ustatus != -ENOENT &&
+	    ustatus != -ECONNRESET &&
+	    ustatus != -ESHUTDOWN) {
+		urb->dev = mixer->chip->dev;
+		usb_submit_urb(urb, GFP_ATOMIC);
+	}
+}
+
+/* create the handler for the optional status interrupt endpoint */
+static int snd_usb_mixer_status_create(struct usb_mixer_interface *mixer)
+{
+	struct usb_endpoint_descriptor *ep;
+	void *transfer_buffer;
+	int buffer_length;
+	unsigned int epnum;
+
+	/* we need one interrupt input endpoint */
+	if (get_iface_desc(mixer->hostif)->bNumEndpoints < 1)
+		return 0;
+	ep = get_endpoint(mixer->hostif, 0);
+	if (!usb_endpoint_dir_in(ep) || !usb_endpoint_xfer_int(ep))
+		return 0;
+
+	epnum = usb_endpoint_num(ep);
+	buffer_length = le16_to_cpu(ep->wMaxPacketSize);
+	transfer_buffer = kmalloc(buffer_length, GFP_KERNEL);
+	if (!transfer_buffer)
+		return -ENOMEM;
+	mixer->urb = usb_alloc_urb(0, GFP_KERNEL);
+	if (!mixer->urb) {
+		kfree(transfer_buffer);
+		return -ENOMEM;
+	}
+	usb_fill_int_urb(mixer->urb, mixer->chip->dev,
+			 usb_rcvintpipe(mixer->chip->dev, epnum),
+			 transfer_buffer, buffer_length,
+			 snd_usb_mixer_interrupt, mixer, ep->bInterval);
+	usb_submit_urb(mixer->urb, GFP_KERNEL);
+	return 0;
+}
+
+static int keep_iface_ctl_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->chip->keep_iface;
+	return 0;
+}
+
+static int keep_iface_ctl_put(struct snd_kcontrol *kcontrol,
+			      struct snd_ctl_elem_value *ucontrol)
+{
+	struct usb_mixer_interface *mixer = snd_kcontrol_chip(kcontrol);
+	bool keep_iface = !!ucontrol->value.integer.value[0];
+
+	if (mixer->chip->keep_iface == keep_iface)
+		return 0;
+	mixer->chip->keep_iface = keep_iface;
+	return 1;
+}
+
+static const struct snd_kcontrol_new keep_iface_ctl = {
+	.iface = SNDRV_CTL_ELEM_IFACE_CARD,
+	.name = "Keep Interface",
+	.info = snd_ctl_boolean_mono_info,
+	.get = keep_iface_ctl_get,
+	.put = keep_iface_ctl_put,
+};
+
+static int create_keep_iface_ctl(struct usb_mixer_interface *mixer)
+{
+	struct snd_kcontrol *kctl = snd_ctl_new1(&keep_iface_ctl, mixer);
+
+	/* need only one control per card */
+	if (snd_ctl_find_id(mixer->chip->card, &kctl->id)) {
+		snd_ctl_free_one(kctl);
+		return 0;
+	}
+
+	return snd_ctl_add(mixer->chip->card, kctl);
+}
+
+int snd_usb_create_mixer(struct snd_usb_audio *chip, int ctrlif,
+			 int ignore_error)
+{
+	static struct snd_device_ops dev_ops = {
+		.dev_free = snd_usb_mixer_dev_free
+	};
+	struct usb_mixer_interface *mixer;
+	struct snd_info_entry *entry;
+	int err;
+
+	strcpy(chip->card->mixername, "USB Mixer");
+
+	mixer = kzalloc(sizeof(*mixer), GFP_KERNEL);
+	if (!mixer)
+		return -ENOMEM;
+	mixer->chip = chip;
+	mixer->ignore_ctl_error = ignore_error;
+	mixer->id_elems = kcalloc(MAX_ID_ELEMS, sizeof(*mixer->id_elems),
+				  GFP_KERNEL);
+	if (!mixer->id_elems) {
+		kfree(mixer);
+		return -ENOMEM;
+	}
+
+	mixer->hostif = &usb_ifnum_to_if(chip->dev, ctrlif)->altsetting[0];
+	switch (get_iface_desc(mixer->hostif)->bInterfaceProtocol) {
+	case UAC_VERSION_1:
+	default:
+		mixer->protocol = UAC_VERSION_1;
+		break;
+	case UAC_VERSION_2:
+		mixer->protocol = UAC_VERSION_2;
+		break;
+	case UAC_VERSION_3:
+		mixer->protocol = UAC_VERSION_3;
+		break;
+	}
+
+	if (mixer->protocol == UAC_VERSION_3 &&
+			chip->badd_profile >= UAC3_FUNCTION_SUBCLASS_GENERIC_IO) {
+		err = snd_usb_mixer_controls_badd(mixer, ctrlif);
+		if (err < 0)
+			goto _error;
+	} else {
+		err = snd_usb_mixer_controls(mixer);
+		if (err < 0)
+			goto _error;
+	}
+
+	err = snd_usb_mixer_status_create(mixer);
+	if (err < 0)
+		goto _error;
+
+	err = create_keep_iface_ctl(mixer);
+	if (err < 0)
+		goto _error;
+
+	snd_usb_mixer_apply_create_quirk(mixer);
+
+	err = snd_device_new(chip->card, SNDRV_DEV_CODEC, mixer, &dev_ops);
+	if (err < 0)
+		goto _error;
+
+	if (list_empty(&chip->mixer_list) &&
+	    !snd_card_proc_new(chip->card, "usbmixer", &entry))
+		snd_info_set_text_ops(entry, chip, snd_usb_mixer_proc_read);
+
+	list_add(&mixer->list, &chip->mixer_list);
+	return 0;
+
+_error:
+	snd_usb_mixer_free(mixer);
+	return err;
+}
+
+void snd_usb_mixer_disconnect(struct usb_mixer_interface *mixer)
+{
+	if (mixer->disconnected)
+		return;
+	if (mixer->urb)
+		usb_kill_urb(mixer->urb);
+	if (mixer->rc_urb)
+		usb_kill_urb(mixer->rc_urb);
+	mixer->disconnected = true;
+}
+
+#ifdef CONFIG_PM
+/* stop any bus activity of a mixer */
+static void snd_usb_mixer_inactivate(struct usb_mixer_interface *mixer)
+{
+	usb_kill_urb(mixer->urb);
+	usb_kill_urb(mixer->rc_urb);
+}
+
+static int snd_usb_mixer_activate(struct usb_mixer_interface *mixer)
+{
+	int err;
+
+	if (mixer->urb) {
+		err = usb_submit_urb(mixer->urb, GFP_NOIO);
+		if (err < 0)
+			return err;
+	}
+
+	return 0;
+}
+
+int snd_usb_mixer_suspend(struct usb_mixer_interface *mixer)
+{
+	snd_usb_mixer_inactivate(mixer);
+	return 0;
+}
+
+static int restore_mixer_value(struct usb_mixer_elem_list *list)
+{
+	struct usb_mixer_elem_info *cval = mixer_elem_list_to_info(list);
+	int c, err, idx;
+
+	if (cval->cmask) {
+		idx = 0;
+		for (c = 0; c < MAX_CHANNELS; c++) {
+			if (!(cval->cmask & (1 << c)))
+				continue;
+			if (cval->cached & (1 << (c + 1))) {
+				err = snd_usb_set_cur_mix_value(cval, c + 1, idx,
+							cval->cache_val[idx]);
+				if (err < 0)
+					return err;
+			}
+			idx++;
+		}
+	} else {
+		/* master */
+		if (cval->cached) {
+			err = snd_usb_set_cur_mix_value(cval, 0, 0, *cval->cache_val);
+			if (err < 0)
+				return err;
+		}
+	}
+
+	return 0;
+}
+
+int snd_usb_mixer_resume(struct usb_mixer_interface *mixer, bool reset_resume)
+{
+	struct usb_mixer_elem_list *list;
+	int id, err;
+
+	if (reset_resume) {
+		/* restore cached mixer values */
+		for (id = 0; id < MAX_ID_ELEMS; id++) {
+			for_each_mixer_elem(list, mixer, id) {
+				if (list->resume) {
+					err = list->resume(list);
+					if (err < 0)
+						return err;
+				}
+			}
+		}
+	}
+
+	snd_usb_mixer_resume_quirk(mixer);
+
+	return snd_usb_mixer_activate(mixer);
+}
+#endif
+
+void snd_usb_mixer_elem_init_std(struct usb_mixer_elem_list *list,
+				 struct usb_mixer_interface *mixer,
+				 int unitid)
+{
+	list->mixer = mixer;
+	list->id = unitid;
+	list->dump = snd_usb_mixer_dump_cval;
+#ifdef CONFIG_PM
+	list->resume = restore_mixer_value;
+#endif
+}
diff --git a/sound/usb/mixer.h b/sound/usb/mixer.h
new file mode 100644
index 0000000..3d12af8
--- /dev/null
+++ b/sound/usb/mixer.h
@@ -0,0 +1,114 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __USBMIXER_H
+#define __USBMIXER_H
+
+#include <sound/info.h>
+
+struct usb_mixer_interface {
+	struct snd_usb_audio *chip;
+	struct usb_host_interface *hostif;
+	struct list_head list;
+	unsigned int ignore_ctl_error;
+	struct urb *urb;
+	/* array[MAX_ID_ELEMS], indexed by unit id */
+	struct usb_mixer_elem_list **id_elems;
+
+	/* the usb audio specification version this interface complies to */
+	int protocol;
+
+	/* 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];
+
+	bool disconnected;
+};
+
+#define MAX_CHANNELS	16	/* max logical channels */
+
+enum {
+	USB_MIXER_BOOLEAN,
+	USB_MIXER_INV_BOOLEAN,
+	USB_MIXER_S8,
+	USB_MIXER_U8,
+	USB_MIXER_S16,
+	USB_MIXER_U16,
+	USB_MIXER_S32,
+	USB_MIXER_U32,
+};
+
+typedef void (*usb_mixer_elem_dump_func_t)(struct snd_info_buffer *buffer,
+					 struct usb_mixer_elem_list *list);
+typedef int (*usb_mixer_elem_resume_func_t)(struct usb_mixer_elem_list *elem);
+
+struct usb_mixer_elem_list {
+	struct usb_mixer_interface *mixer;
+	struct usb_mixer_elem_list *next_id_elem; /* list of controls with same id */
+	struct snd_kcontrol *kctl;
+	unsigned int id;
+	usb_mixer_elem_dump_func_t dump;
+	usb_mixer_elem_resume_func_t resume;
+};
+
+/* iterate over mixer element list of the given unit id */
+#define for_each_mixer_elem(list, mixer, id)	\
+	for ((list) = (mixer)->id_elems[id]; (list); (list) = (list)->next_id_elem)
+#define mixer_elem_list_to_info(list) \
+	container_of(list, struct usb_mixer_elem_info, head)
+
+struct usb_mixer_elem_info {
+	struct usb_mixer_elem_list head;
+	unsigned int control;	/* CS or ICN (high byte) */
+	unsigned int cmask; /* channel mask bitmap: 0 = master */
+	unsigned int idx_off; /* Control index offset */
+	unsigned int ch_readonly;
+	unsigned int master_readonly;
+	int channels;
+	int val_type;
+	int min, max, res;
+	int dBmin, dBmax;
+	int cached;
+	int cache_val[MAX_CHANNELS];
+	u8 initialized;
+	u8 min_mute;
+	void *private_data;
+};
+
+int snd_usb_create_mixer(struct snd_usb_audio *chip, int ctrlif,
+			 int ignore_error);
+void snd_usb_mixer_disconnect(struct usb_mixer_interface *mixer);
+
+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);
+
+int snd_usb_mixer_add_control(struct usb_mixer_elem_list *list,
+			      struct snd_kcontrol *kctl);
+
+void snd_usb_mixer_elem_init_std(struct usb_mixer_elem_list *list,
+				 struct usb_mixer_interface *mixer,
+				 int unitid);
+
+int snd_usb_mixer_vol_tlv(struct snd_kcontrol *kcontrol, int op_flag,
+			  unsigned int size, unsigned int __user *_tlv);
+
+#ifdef CONFIG_PM
+int snd_usb_mixer_suspend(struct usb_mixer_interface *mixer);
+int snd_usb_mixer_resume(struct usb_mixer_interface *mixer, bool reset_resume);
+#endif
+
+int snd_usb_set_cur_mix_value(struct usb_mixer_elem_info *cval, int channel,
+                             int index, int value);
+
+int snd_usb_get_cur_mix_value(struct usb_mixer_elem_info *cval,
+                             int channel, int index, int *value);
+
+extern void snd_usb_mixer_elem_free(struct snd_kcontrol *kctl);
+
+extern struct snd_kcontrol_new *snd_usb_feature_unit_ctl;
+
+#endif /* __USBMIXER_H */
diff --git a/sound/usb/mixer_maps.c b/sound/usb/mixer_maps.c
new file mode 100644
index 0000000..71069e1
--- /dev/null
+++ b/sound/usb/mixer_maps.c
@@ -0,0 +1,552 @@
+/*
+ *   Additional mixer mapping
+ *
+ *   Copyright (c) 2002 by Takashi Iwai <tiwai@suse.de>
+ *
+ *   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
+ *
+ */
+
+struct usbmix_dB_map {
+	u32 min;
+	u32 max;
+};
+
+struct usbmix_name_map {
+	int id;
+	const char *name;
+	int control;
+	struct usbmix_dB_map *dB;
+};
+
+struct usbmix_selector_map {
+	int id;
+	int count;
+	const char **names;
+};
+
+struct usbmix_ctl_map {
+	u32 id;
+	const struct usbmix_name_map *map;
+	const struct usbmix_selector_map *selector_map;
+	int ignore_ctl_error;
+};
+
+/*
+ * USB control mappers for SB Exitigy
+ */
+
+/*
+ * Topology of SB Extigy (see on the wide screen :)
+
+USB_IN[1] --->FU[2]------------------------------+->MU[16]-->PU[17]-+->FU[18]--+->EU[27]--+->EU[21]-->FU[22]--+->FU[23] > Dig_OUT[24]
+                                                 ^                  |          |          |                   |
+USB_IN[3] -+->SU[5]-->FU[6]--+->MU[14] ->PU[15]->+                  |          |          |                   +->FU[25] > Dig_OUT[26]
+           ^                 ^                   |                  |          |          |
+Dig_IN[4] -+                 |                   |                  |          |          +->FU[28]---------------------> Spk_OUT[19]
+                             |                   |                  |          |
+Lin-IN[7] -+-->FU[8]---------+                   |                  |          +----------------------------------------> Hph_OUT[20]
+           |                                     |                  |
+Mic-IN[9] --+->FU[10]----------------------------+                  |
+           ||                                                       |
+           ||  +----------------------------------------------------+
+           VV  V
+           ++--+->SU[11]-->FU[12] --------------------------------------------------------------------------------------> USB_OUT[13]
+*/
+
+static struct usbmix_name_map extigy_map[] = {
+	/* 1: IT pcm */
+	{ 2, "PCM Playback" }, /* FU */
+	/* 3: IT pcm */
+	/* 4: IT digital in */
+	{ 5, NULL }, /* DISABLED: this seems to be bogus on some firmware */
+	{ 6, "Digital In" }, /* FU */
+	/* 7: IT line */
+	{ 8, "Line Playback" }, /* FU */
+	/* 9: IT mic */
+	{ 10, "Mic Playback" }, /* FU */
+	{ 11, "Capture Source" }, /* SU */
+	{ 12, "Capture" }, /* FU */
+	/* 13: OT pcm capture */
+	/* 14: MU (w/o controls) */
+	/* 15: PU (3D enh) */
+	/* 16: MU (w/o controls) */
+	{ 17, NULL, 1 }, /* DISABLED: PU-switch (any effect?) */
+	{ 17, "Channel Routing", 2 },	/* PU: mode select */
+	{ 18, "Tone Control - Bass", UAC_FU_BASS }, /* FU */
+	{ 18, "Tone Control - Treble", UAC_FU_TREBLE }, /* FU */
+	{ 18, "Master Playback" }, /* FU; others */
+	/* 19: OT speaker */
+	/* 20: OT headphone */
+	{ 21, NULL }, /* DISABLED: EU (for what?) */
+	{ 22, "Digital Out Playback" }, /* FU */
+	{ 23, "Digital Out1 Playback" }, /* FU */  /* FIXME: corresponds to 24 */
+	/* 24: OT digital out */
+	{ 25, "IEC958 Optical Playback" }, /* FU */
+	{ 26, "IEC958 Optical Playback" }, /* OT */
+	{ 27, NULL }, /* DISABLED: EU (for what?) */
+	/* 28: FU speaker (mute) */
+	{ 29, NULL }, /* Digital Input Playback Source? */
+	{ 0 } /* terminator */
+};
+
+/* Sound Blaster MP3+ controls mapping
+ * The default mixer channels have totally misleading names,
+ * e.g. no Master and fake PCM volume
+ *			Pavel Mihaylov <bin@bash.info>
+ */
+static struct usbmix_dB_map mp3plus_dB_1 = {.min = -4781, .max = 0};
+						/* just guess */
+static struct usbmix_dB_map mp3plus_dB_2 = {.min = -1781, .max = 618};
+						/* just guess */
+
+static struct usbmix_name_map mp3plus_map[] = {
+	/* 1: IT pcm */
+	/* 2: IT mic */
+	/* 3: IT line */
+	/* 4: IT digital in */
+	/* 5: OT digital out */
+	/* 6: OT speaker */
+	/* 7: OT pcm capture */
+	{ 8, "Capture Source" }, /* FU, default PCM Capture Source */
+		/* (Mic, Input 1 = Line input, Input 2 = Optical input) */
+	{ 9, "Master Playback" }, /* FU, default Speaker 1 */
+	/* { 10, "Mic Capture", 1 }, */ /* FU, Mic Capture */
+	{ 10, /* "Mic Capture", */ NULL, 2, .dB = &mp3plus_dB_2 },
+		/* FU, Mic Capture */
+	{ 10, "Mic Boost", 7 }, /* FU, default Auto Gain Input */
+	{ 11, "Line Capture", .dB = &mp3plus_dB_2 },
+		/* FU, default PCM Capture */
+	{ 12, "Digital In Playback" }, /* FU, default PCM 1 */
+	{ 13, /* "Mic Playback", */ .dB = &mp3plus_dB_1 },
+		/* FU, default Mic Playback */
+	{ 14, "Line Playback", .dB = &mp3plus_dB_1 }, /* FU, default Speaker */
+	/* 15: MU */
+	{ 0 } /* terminator */
+};
+
+/* Topology of SB Audigy 2 NX
+
+          +----------------------------->EU[27]--+
+          |                                      v
+          | +----------------------------------->SU[29]---->FU[22]-->Dig_OUT[24]
+          | |                                    ^
+USB_IN[1]-+------------+              +->EU[17]->+->FU[11]-+
+            |          v              |          v         |
+Dig_IN[4]---+->FU[6]-->MU[16]->FU[18]-+->EU[21]->SU[31]----->FU[30]->Hph_OUT[20]
+            |          ^              |                    |
+Lin_IN[7]-+--->FU[8]---+              +->EU[23]->FU[28]------------->Spk_OUT[19]
+          | |                                              v
+          +--->FU[12]------------------------------------->SU[14]--->USB_OUT[15]
+            |                                              ^
+            +->FU[13]--------------------------------------+
+*/
+static struct usbmix_name_map audigy2nx_map[] = {
+	/* 1: IT pcm playback */
+	/* 4: IT digital in */
+	{ 6, "Digital In Playback" }, /* FU */
+	/* 7: IT line in */
+	{ 8, "Line Playback" }, /* FU */
+	{ 11, "What-U-Hear Capture" }, /* FU */
+	{ 12, "Line Capture" }, /* FU */
+	{ 13, "Digital In Capture" }, /* FU */
+	{ 14, "Capture Source" }, /* SU */
+	/* 15: OT pcm capture */
+	/* 16: MU w/o controls */
+	{ 17, NULL }, /* DISABLED: EU (for what?) */
+	{ 18, "Master Playback" }, /* FU */
+	/* 19: OT speaker */
+	/* 20: OT headphone */
+	{ 21, NULL }, /* DISABLED: EU (for what?) */
+	{ 22, "Digital Out Playback" }, /* FU */
+	{ 23, NULL }, /* DISABLED: EU (for what?) */
+	/* 24: OT digital out */
+	{ 27, NULL }, /* DISABLED: EU (for what?) */
+	{ 28, "Speaker Playback" }, /* FU */
+	{ 29, "Digital Out Source" }, /* SU */
+	{ 30, "Headphone Playback" }, /* FU */
+	{ 31, "Headphone Source" }, /* SU */
+	{ 0 } /* terminator */
+};
+
+static struct usbmix_name_map mbox1_map[] = {
+	{ 1, "Clock" },
+	{ 0 } /* terminator */
+};
+
+static struct usbmix_selector_map c400_selectors[] = {
+	{
+		.id = 0x80,
+		.count = 2,
+		.names = (const char*[]) {"Internal", "SPDIF"}
+	},
+	{ 0 } /* terminator */
+};
+
+static struct usbmix_selector_map audigy2nx_selectors[] = {
+	{
+		.id = 14, /* Capture Source */
+		.count = 3,
+		.names = (const char*[]) {"Line", "Digital In", "What-U-Hear"}
+	},
+	{
+		.id = 29, /* Digital Out Source */
+		.count = 3,
+		.names = (const char*[]) {"Front", "PCM", "Digital In"}
+	},
+	{
+		.id = 31, /* Headphone Source */
+		.count = 2,
+		.names = (const char*[]) {"Front", "Side"}
+	},
+	{ 0 } /* terminator */
+};
+
+/* Creative SoundBlaster Live! 24-bit External */
+static struct usbmix_name_map live24ext_map[] = {
+	/* 2: PCM Playback Volume */
+	{ 5, "Mic Capture" }, /* FU, default PCM Capture Volume */
+	{ 0 } /* terminator */
+};
+
+/* LineX FM Transmitter entry - needed to bypass controls bug */
+static struct usbmix_name_map linex_map[] = {
+	/* 1: IT pcm */
+	/* 2: OT Speaker */ 
+	{ 3, "Master" }, /* FU: master volume - left / right / mute */
+	{ 0 } /* terminator */
+};
+
+static struct usbmix_name_map maya44_map[] = {
+	/* 1: IT line */
+	{ 2, "Line Playback" }, /* FU */
+	/* 3: IT line */
+	{ 4, "Line Playback" }, /* FU */
+	/* 5: IT pcm playback */
+	/* 6: MU */
+	{ 7, "Master Playback" }, /* FU */
+	/* 8: OT speaker */
+	/* 9: IT line */
+	{ 10, "Line Capture" }, /* FU */
+	/* 11: MU */
+	/* 12: OT pcm capture */
+	{ }
+};
+
+/* Section "justlink_map" below added by James Courtier-Dutton <James@superbug.demon.co.uk>
+ * sourced from Maplin Electronics (http://www.maplin.co.uk), part number A56AK
+ * Part has 2 connectors that act as a single output. (TOSLINK Optical for digital out, and 3.5mm Jack for Analogue out.)
+ * The USB Mixer publishes a Microphone and extra Volume controls for it, but none exist on the device,
+ * so this map removes all unwanted sliders from alsamixer
+ */
+
+static struct usbmix_name_map justlink_map[] = {
+	/* 1: IT pcm playback */
+	/* 2: Not present */
+	{ 3, NULL}, /* IT mic (No mic input on device) */
+	/* 4: Not present */
+	/* 5: OT speacker */
+	/* 6: OT pcm capture */
+	{ 7, "Master Playback" }, /* Mute/volume for speaker */
+	{ 8, NULL }, /* Capture Switch (No capture inputs on device) */
+	{ 9, NULL }, /* Capture Mute/volume (No capture inputs on device */
+	/* 0xa: Not present */
+	/* 0xb: MU (w/o controls) */
+	{ 0xc, NULL }, /* Mic feedback Mute/volume (No capture inputs on device) */
+	{ 0 } /* terminator */
+};
+
+/* TerraTec Aureon 5.1 MkII USB */
+static struct usbmix_name_map aureon_51_2_map[] = {
+	/* 1: IT USB */
+	/* 2: IT Mic */
+	/* 3: IT Line */
+	/* 4: IT SPDIF */
+	/* 5: OT SPDIF */
+	/* 6: OT Speaker */
+	/* 7: OT USB */
+	{ 8, "Capture Source" }, /* SU */
+	{ 9, "Master Playback" }, /* FU */
+	{ 10, "Mic Capture" }, /* FU */
+	{ 11, "Line Capture" }, /* FU */
+	{ 12, "IEC958 In Capture" }, /* FU */
+	{ 13, "Mic Playback" }, /* FU */
+	{ 14, "Line Playback" }, /* FU */
+	/* 15: MU */
+	{} /* terminator */
+};
+
+static struct usbmix_name_map scratch_live_map[] = {
+	/* 1: IT Line 1 (USB streaming) */
+	/* 2: OT Line 1 (Speaker) */
+	/* 3: IT Line 1 (Line connector) */
+	{ 4, "Line 1 In" }, /* FU */
+	/* 5: OT Line 1 (USB streaming) */
+	/* 6: IT Line 2 (USB streaming) */
+	/* 7: OT Line 2 (Speaker) */
+	/* 8: IT Line 2 (Line connector) */
+	{ 9, "Line 2 In" }, /* FU */
+	/* 10: OT Line 2 (USB streaming) */
+	/* 11: IT Mic (Line connector) */
+	/* 12: OT Mic (USB streaming) */
+	{ 0 } /* terminator */
+};
+
+static struct usbmix_name_map ebox44_map[] = {
+	{ 4, NULL }, /* FU */
+	{ 6, NULL }, /* MU */
+	{ 7, NULL }, /* FU */
+	{ 10, NULL }, /* FU */
+	{ 11, NULL }, /* MU */
+	{ 0 }
+};
+
+/* "Gamesurround Muse Pocket LT" looks same like "Sound Blaster MP3+"
+ *  most importand difference is SU[8], it should be set to "Capture Source"
+ *  to make alsamixer and PA working properly.
+ *  FIXME: or mp3plus_map should use "Capture Source" too,
+ *  so this maps can be merget
+ */
+static struct usbmix_name_map hercules_usb51_map[] = {
+	{ 8, "Capture Source" },	/* SU, default "PCM Capture Source" */
+	{ 9, "Master Playback" },	/* FU, default "Speaker Playback" */
+	{ 10, "Mic Boost", 7 },		/* FU, default "Auto Gain Input" */
+	{ 11, "Line Capture" },		/* FU, default "PCM Capture" */
+	{ 13, "Mic Bypass Playback" },	/* FU, default "Mic Playback" */
+	{ 14, "Line Bypass Playback" },	/* FU, default "Line Playback" */
+	{ 0 }				/* terminator */
+};
+
+/* Plantronics Gamecom 780 has a broken volume control, better to disable it */
+static struct usbmix_name_map gamecom780_map[] = {
+	{ 9, NULL }, /* FU, speaker out */
+	{}
+};
+
+/* some (all?) SCMS USB3318 devices are affected by a firmware lock up
+ * when anything attempts to access FU 10 (control)
+ */
+static const struct usbmix_name_map scms_usb3318_map[] = {
+	{ 10, NULL },
+	{ 0 }
+};
+
+/* Bose companion 5, the dB conversion factor is 16 instead of 256 */
+static struct usbmix_dB_map bose_companion5_dB = {-5006, -6};
+static struct usbmix_name_map bose_companion5_map[] = {
+	{ 3, NULL, .dB = &bose_companion5_dB },
+	{ 0 }	/* terminator */
+};
+
+/*
+ * Dell usb dock with ALC4020 codec had a firmware problem where it got
+ * screwed up when zero volume is passed; just skip it as a workaround
+ *
+ * Also the extension unit gives an access error, so skip it as well.
+ */
+static const struct usbmix_name_map dell_alc4020_map[] = {
+	{ 4, NULL },	/* extension unit */
+	{ 16, NULL },
+	{ 19, NULL },
+	{ 0 }
+};
+
+/*
+ * Control map entries
+ */
+
+static struct usbmix_ctl_map usbmix_ctl_maps[] = {
+	{
+		.id = USB_ID(0x041e, 0x3000),
+		.map = extigy_map,
+		.ignore_ctl_error = 1,
+	},
+	{
+		.id = USB_ID(0x041e, 0x3010),
+		.map = mp3plus_map,
+	},
+	{
+		.id = USB_ID(0x041e, 0x3020),
+		.map = audigy2nx_map,
+		.selector_map = audigy2nx_selectors,
+	},
+ 	{
+		.id = USB_ID(0x041e, 0x3040),
+		.map = live24ext_map,
+	},
+	{
+		.id = USB_ID(0x041e, 0x3048),
+		.map = audigy2nx_map,
+		.selector_map = audigy2nx_selectors,
+	},
+	{	/* Logitech, Inc. QuickCam Pro for Notebooks */
+		.id = USB_ID(0x046d, 0x0991),
+		.ignore_ctl_error = 1,
+	},
+	{	/* Logitech, Inc. QuickCam E 3500 */
+		.id = USB_ID(0x046d, 0x09a4),
+		.ignore_ctl_error = 1,
+	},
+	{	/* Plantronics GameCom 780 */
+		.id = USB_ID(0x047f, 0xc010),
+		.map = gamecom780_map,
+	},
+	{
+		/* Hercules DJ Console (Windows Edition) */
+		.id = USB_ID(0x06f8, 0xb000),
+		.ignore_ctl_error = 1,
+	},
+	{
+		/* Hercules DJ Console (Macintosh Edition) */
+		.id = USB_ID(0x06f8, 0xd002),
+		.ignore_ctl_error = 1,
+	},
+	{
+		/* Hercules Gamesurround Muse Pocket LT
+		 * (USB 5.1 Channel Audio Adapter)
+		 */
+		.id = USB_ID(0x06f8, 0xc000),
+		.map = hercules_usb51_map,
+	},
+	{
+		.id = USB_ID(0x0763, 0x2030),
+		.selector_map = c400_selectors,
+	},
+	{
+		.id = USB_ID(0x0763, 0x2031),
+		.selector_map = c400_selectors,
+	},
+	{
+		.id = USB_ID(0x08bb, 0x2702),
+		.map = linex_map,
+		.ignore_ctl_error = 1,
+	},
+	{
+		.id = USB_ID(0x0a92, 0x0091),
+		.map = maya44_map,
+	},
+	{
+		.id = USB_ID(0x0c45, 0x1158),
+		.map = justlink_map,
+	},
+	{
+		.id = USB_ID(0x0ccd, 0x0028),
+		.map = aureon_51_2_map,
+	},
+	{
+		.id = USB_ID(0x0bda, 0x4014),
+		.map = dell_alc4020_map,
+	},
+	{
+		.id = USB_ID(0x0dba, 0x1000),
+		.map = mbox1_map,
+	},
+	{
+		.id = USB_ID(0x13e5, 0x0001),
+		.map = scratch_live_map,
+		.ignore_ctl_error = 1,
+	},
+	{
+		.id = USB_ID(0x200c, 0x1018),
+		.map = ebox44_map,
+	},
+	{
+		/* MAYA44 USB+ */
+		.id = USB_ID(0x2573, 0x0008),
+		.map = maya44_map,
+	},
+	{
+		/* KEF X300A */
+		.id = USB_ID(0x27ac, 0x1000),
+		.map = scms_usb3318_map,
+	},
+	{
+		/* Arcam rPAC */
+		.id = USB_ID(0x25c4, 0x0003),
+		.map = scms_usb3318_map,
+	},
+	{
+		/* Bose Companion 5 */
+		.id = USB_ID(0x05a7, 0x1020),
+		.map = bose_companion5_map,
+	},
+	{ 0 } /* terminator */
+};
+
+/*
+ * Control map entries for UAC3 BADD profiles
+ */
+
+static struct usbmix_name_map uac3_badd_generic_io_map[] = {
+	{ UAC3_BADD_FU_ID2, "Generic Out Playback" },
+	{ UAC3_BADD_FU_ID5, "Generic In Capture" },
+	{ 0 }					/* terminator */
+};
+static struct usbmix_name_map uac3_badd_headphone_map[] = {
+	{ UAC3_BADD_FU_ID2, "Headphone Playback" },
+	{ 0 }					/* terminator */
+};
+static struct usbmix_name_map uac3_badd_speaker_map[] = {
+	{ UAC3_BADD_FU_ID2, "Speaker Playback" },
+	{ 0 }					/* terminator */
+};
+static struct usbmix_name_map uac3_badd_microphone_map[] = {
+	{ UAC3_BADD_FU_ID5, "Mic Capture" },
+	{ 0 }					/* terminator */
+};
+/* Covers also 'headset adapter' profile */
+static struct usbmix_name_map uac3_badd_headset_map[] = {
+	{ UAC3_BADD_FU_ID2, "Headset Playback" },
+	{ UAC3_BADD_FU_ID5, "Headset Capture" },
+	{ UAC3_BADD_FU_ID7, "Sidetone Mixing" },
+	{ 0 }					/* terminator */
+};
+static struct usbmix_name_map uac3_badd_speakerphone_map[] = {
+	{ UAC3_BADD_FU_ID2, "Speaker Playback" },
+	{ UAC3_BADD_FU_ID5, "Mic Capture" },
+	{ 0 }					/* terminator */
+};
+
+static struct usbmix_ctl_map uac3_badd_usbmix_ctl_maps[] = {
+	{
+		.id = UAC3_FUNCTION_SUBCLASS_GENERIC_IO,
+		.map = uac3_badd_generic_io_map,
+	},
+	{
+		.id = UAC3_FUNCTION_SUBCLASS_HEADPHONE,
+		.map = uac3_badd_headphone_map,
+	},
+	{
+		.id = UAC3_FUNCTION_SUBCLASS_SPEAKER,
+		.map = uac3_badd_speaker_map,
+	},
+	{
+		.id = UAC3_FUNCTION_SUBCLASS_MICROPHONE,
+		.map = uac3_badd_microphone_map,
+	},
+	{
+		.id = UAC3_FUNCTION_SUBCLASS_HEADSET,
+		.map = uac3_badd_headset_map,
+	},
+	{
+		.id = UAC3_FUNCTION_SUBCLASS_HEADSET_ADAPTER,
+		.map = uac3_badd_headset_map,
+	},
+	{
+		.id = UAC3_FUNCTION_SUBCLASS_SPEAKERPHONE,
+		.map = uac3_badd_speakerphone_map,
+	},
+	{ 0 } /* terminator */
+};
diff --git a/sound/usb/mixer_quirks.c b/sound/usb/mixer_quirks.c
new file mode 100644
index 0000000..cbfb48b
--- /dev/null
+++ b/sound/usb/mixer_quirks.c
@@ -0,0 +1,2002 @@
+/*
+ *   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)
+ *
+ *   Audio Advantage Micro II support added by:
+ *	    Przemek Rudy (prudy1@o2.pl)
+ *
+ *   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/hid.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/usb.h>
+#include <linux/usb/audio.h>
+
+#include <sound/asoundef.h>
+#include <sound/core.h>
+#include <sound/control.h>
+#include <sound/hwdep.h>
+#include <sound/info.h>
+#include <sound/tlv.h>
+
+#include "usbaudio.h"
+#include "mixer.h"
+#include "mixer_quirks.h"
+#include "mixer_scarlett.h"
+#include "mixer_us16x08.h"
+#include "helper.h"
+
+struct std_mono_table {
+	unsigned int unitid, control, cmask;
+	int val_type;
+	const char *name;
+	snd_kcontrol_tlv_rw_t *tlv_callback;
+};
+
+/* This function allows for the creation of standard UAC controls.
+ * See the quirks for M-Audio FTUs or Ebox-44.
+ * If you don't want to set a TLV callback pass NULL.
+ *
+ * Since there doesn't seem to be a devices that needs a multichannel
+ * 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)
+{
+	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->val_type = val_type;
+	cval->channels = 1;
+	cval->control = control;
+	cval->cmask = cmask;
+	cval->idx_off = idx_off;
+
+	/* get_min_max() is called only for integer volumes later,
+	 * so provide a short-cut for booleans */
+	cval->min = 0;
+	cval->max = 1;
+	cval->res = 0;
+	cval->dBmin = 0;
+	cval->dBmax = 0;
+
+	/* Create control */
+	kctl = snd_ctl_new1(snd_usb_feature_unit_ctl, cval);
+	if (!kctl) {
+		kfree(cval);
+		return -ENOMEM;
+	}
+
+	/* Set name */
+	snprintf(kctl->id.name, sizeof(kctl->id.name), name);
+	kctl->private_free = snd_usb_mixer_elem_free;
+
+	/* set TLV */
+	if (tlv_callback) {
+		kctl->tlv.c = tlv_callback;
+		kctl->vd[0].access |=
+			SNDRV_CTL_ELEM_ACCESS_TLV_READ |
+			SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK;
+	}
+	/* Add control to mixer */
+	return snd_usb_mixer_add_control(&cval->head, kctl);
+}
+
+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)
+{
+	return snd_create_std_mono_ctl_offset(mixer, unitid, control, cmask,
+		val_type, 0 /* Offset */, name, tlv_callback);
+}
+
+/*
+ * Create a set of standard UAC controls from a table
+ */
+static int snd_create_std_mono_table(struct usb_mixer_interface *mixer,
+				struct std_mono_table *t)
+{
+	int err;
+
+	while (t->name != NULL) {
+		err = snd_create_std_mono_ctl(mixer, t->unitid, t->control,
+				t->cmask, t->val_type, t->name, t->tlv_callback);
+		if (err < 0)
+			return err;
+		t++;
+	}
+
+	return 0;
+}
+
+static int add_single_ctl_with_resume(struct usb_mixer_interface *mixer,
+				      int id,
+				      usb_mixer_elem_resume_func_t resume,
+				      const struct snd_kcontrol_new *knew,
+				      struct usb_mixer_elem_list **listp)
+{
+	struct usb_mixer_elem_list *list;
+	struct snd_kcontrol *kctl;
+
+	list = kzalloc(sizeof(*list), GFP_KERNEL);
+	if (!list)
+		return -ENOMEM;
+	if (listp)
+		*listp = list;
+	list->mixer = mixer;
+	list->id = id;
+	list->resume = resume;
+	kctl = snd_ctl_new1(knew, list);
+	if (!kctl) {
+		kfree(list);
+		return -ENOMEM;
+	}
+	kctl->private_free = snd_usb_mixer_elem_free;
+	return snd_usb_mixer_add_control(list, kctl);
+}
+
+/*
+ * 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, 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, 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 __poll_t 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 ? EPOLLIN | EPOLLRDNORM : 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)
+{
+	ucontrol->value.integer.value[0] = kcontrol->private_value >> 8;
+	return 0;
+}
+
+static int snd_audigy2nx_led_update(struct usb_mixer_interface *mixer,
+				    int value, int index)
+{
+	struct snd_usb_audio *chip = mixer->chip;
+	int err;
+
+	err = snd_usb_lock_shutdown(chip);
+	if (err < 0)
+		return 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 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);
+	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);
+	return err;
+}
+
+static int snd_audigy2nx_led_put(struct snd_kcontrol *kcontrol,
+				 struct snd_ctl_elem_value *ucontrol)
+{
+	struct usb_mixer_elem_list *list = snd_kcontrol_chip(kcontrol);
+	struct usb_mixer_interface *mixer = list->mixer;
+	int index = kcontrol->private_value & 0xff;
+	unsigned int value = ucontrol->value.integer.value[0];
+	int old_value = kcontrol->private_value >> 8;
+	int err;
+
+	if (value > 1)
+		return -EINVAL;
+	if (value == old_value)
+		return 0;
+	kcontrol->private_value = (value << 8) | index;
+	err = snd_audigy2nx_led_update(mixer, value, index);
+	return err < 0 ? err : 1;
+}
+
+static int snd_audigy2nx_led_resume(struct usb_mixer_elem_list *list)
+{
+	int priv_value = list->kctl->private_value;
+
+	return snd_audigy2nx_led_update(list->mixer, priv_value >> 8,
+					priv_value & 0xff);
+}
+
+/* name and private_value are set dynamically */
+static const struct snd_kcontrol_new snd_audigy2nx_control = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.info = snd_audigy2nx_led_info,
+	.get = snd_audigy2nx_led_get,
+	.put = snd_audigy2nx_led_put,
+};
+
+static const char * const snd_audigy2nx_led_names[] = {
+	"CMSS LED Switch",
+	"Power LED Switch",
+	"Dolby Digital LED Switch",
+};
+
+static int snd_audigy2nx_controls_create(struct usb_mixer_interface *mixer)
+{
+	int i, err;
+
+	for (i = 0; i < ARRAY_SIZE(snd_audigy2nx_led_names); ++i) {
+		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)
+			continue;
+		/* USB X-Fi S51 Pro doesn't have one either */
+		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; 
+
+		knew = snd_audigy2nx_control;
+		knew.name = snd_audigy2nx_led_names[i];
+		knew.private_value = (1 << 8) | i; /* LED on as default */
+		err = add_single_ctl_with_resume(mixer, 0,
+						 snd_audigy2nx_led_resume,
+						 &knew, NULL);
+		if (err < 0)
+			return err;
+	}
+	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_lock_shutdown(mixer->chip);
+		if (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
+			snd_iprintf(buffer, "?\n");
+	}
+}
+
+/* EMU0204 */
+static int snd_emu0204_ch_switch_info(struct snd_kcontrol *kcontrol,
+				      struct snd_ctl_elem_info *uinfo)
+{
+	static const char * const texts[2] = {"1/2", "3/4"};
+
+	return snd_ctl_enum_info(uinfo, 1, ARRAY_SIZE(texts), texts);
+}
+
+static int snd_emu0204_ch_switch_get(struct snd_kcontrol *kcontrol,
+				     struct snd_ctl_elem_value *ucontrol)
+{
+	ucontrol->value.enumerated.item[0] = kcontrol->private_value;
+	return 0;
+}
+
+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;
+
+	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;
+}
+
+static int snd_emu0204_ch_switch_put(struct snd_kcontrol *kcontrol,
+				     struct snd_ctl_elem_value *ucontrol)
+{
+	struct usb_mixer_elem_list *list = snd_kcontrol_chip(kcontrol);
+	struct usb_mixer_interface *mixer = list->mixer;
+	unsigned int value = ucontrol->value.enumerated.item[0];
+	int err;
+
+	if (value > 1)
+		return -EINVAL;
+
+	if (value == kcontrol->private_value)
+		return 0;
+
+	kcontrol->private_value = value;
+	err = snd_emu0204_ch_switch_update(mixer, value);
+	return err < 0 ? err : 1;
+}
+
+static int snd_emu0204_ch_switch_resume(struct usb_mixer_elem_list *list)
+{
+	return snd_emu0204_ch_switch_update(list->mixer,
+					    list->kctl->private_value);
+}
+
+static struct snd_kcontrol_new snd_emu0204_control = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "Front Jack Channels",
+	.info = snd_emu0204_ch_switch_info,
+	.get = snd_emu0204_ch_switch_get,
+	.put = snd_emu0204_ch_switch_put,
+	.private_value = 0,
+};
+
+static int snd_emu0204_controls_create(struct usb_mixer_interface *mixer)
+{
+	return add_single_ctl_with_resume(mixer, 0,
+					  snd_emu0204_ch_switch_resume,
+					  &snd_emu0204_control, NULL);
+}
+
+/* ASUS Xonar U1 / U3 controls */
+
+static int snd_xonar_u1_switch_get(struct snd_kcontrol *kcontrol,
+				   struct snd_ctl_elem_value *ucontrol)
+{
+	ucontrol->value.integer.value[0] = !!(kcontrol->private_value & 0x02);
+	return 0;
+}
+
+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;
+}
+
+static int snd_xonar_u1_switch_put(struct snd_kcontrol *kcontrol,
+				   struct snd_ctl_elem_value *ucontrol)
+{
+	struct usb_mixer_elem_list *list = snd_kcontrol_chip(kcontrol);
+	u8 old_status, new_status;
+	int err;
+
+	old_status = kcontrol->private_value;
+	if (ucontrol->value.integer.value[0])
+		new_status = old_status | 0x02;
+	else
+		new_status = old_status & ~0x02;
+	if (new_status == old_status)
+		return 0;
+
+	kcontrol->private_value = new_status;
+	err = snd_xonar_u1_switch_update(list->mixer, new_status);
+	return err < 0 ? err : 1;
+}
+
+static int snd_xonar_u1_switch_resume(struct usb_mixer_elem_list *list)
+{
+	return snd_xonar_u1_switch_update(list->mixer,
+					  list->kctl->private_value);
+}
+
+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,
+	.private_value = 0x05,
+};
+
+static int snd_xonar_u1_controls_create(struct usb_mixer_interface *mixer)
+{
+	return add_single_ctl_with_resume(mixer, 0,
+					  snd_xonar_u1_switch_resume,
+					  &snd_xonar_u1_output_switch, NULL);
+}
+
+/* Digidesign Mbox 1 clock source switch (internal/spdif) */
+
+static int snd_mbox1_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_switch_update(struct usb_mixer_interface *mixer, int val)
+{
+	struct snd_usb_audio *chip = mixer->chip;
+	int err;
+	unsigned char buff[3];
+
+	err = snd_usb_lock_shutdown(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);
+	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);
+	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;
+	}
+
+	/* 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);
+	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);
+	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);
+	if (err < 0)
+		goto err;
+
+err:
+	snd_usb_unlock_shutdown(chip);
+	return err;
+}
+
+static int snd_mbox1_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_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 const char *const texts[2] = {
+		"Internal",
+		"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)
+{
+	return snd_mbox1_switch_update(list->mixer, list->kctl->private_value);
+}
+
+static struct snd_kcontrol_new snd_mbox1_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,
+	.private_value = 0
+};
+
+static int snd_mbox1_create_sync_switch(struct usb_mixer_interface *mixer)
+{
+	return add_single_ctl_with_resume(mixer, 0,
+					  snd_mbox1_switch_resume,
+					  &snd_mbox1_switch, NULL);
+}
+
+/* Native Instruments device quirks */
+
+#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)
+{
+	struct usb_device *dev = mixer->chip->dev;
+	unsigned int pval = kctl->private_value;
+	u8 value;
+	int err;
+
+	err = snd_usb_ctl_msg(dev, usb_rcvctrlpipe(dev, 0),
+			      (pval >> 16) & 0xff,
+			      USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_IN,
+			      0, pval & 0xffff, &value, 1);
+	if (err < 0) {
+		dev_err(&dev->dev,
+			"unable to issue vendor read request (ret = %d)", err);
+		return err;
+	}
+
+	kctl->private_value |= (value << 24);
+	return 0;
+}
+
+static int snd_nativeinstruments_control_get(struct snd_kcontrol *kcontrol,
+					     struct snd_ctl_elem_value *ucontrol)
+{
+	ucontrol->value.integer.value[0] = kcontrol->private_value >> 24;
+	return 0;
+}
+
+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;
+}
+
+static int snd_nativeinstruments_control_put(struct snd_kcontrol *kcontrol,
+					     struct snd_ctl_elem_value *ucontrol)
+{
+	struct usb_mixer_elem_list *list = snd_kcontrol_chip(kcontrol);
+	u8 oldval = (kcontrol->private_value >> 24) & 0xff;
+	u8 newval = ucontrol->value.integer.value[0];
+	int err;
+
+	if (oldval == newval)
+		return 0;
+
+	kcontrol->private_value &= ~(0xff << 24);
+	kcontrol->private_value |= (unsigned int)newval << 24;
+	err = snd_ni_update_cur_val(list);
+	return err < 0 ? err : 1;
+}
+
+static struct snd_kcontrol_new snd_nativeinstruments_ta6_mixers[] = {
+	{
+		.name = "Direct Thru Channel A",
+		.private_value = _MAKE_NI_CONTROL(0x01, 0x03),
+	},
+	{
+		.name = "Direct Thru Channel B",
+		.private_value = _MAKE_NI_CONTROL(0x01, 0x05),
+	},
+	{
+		.name = "Phono Input Channel A",
+		.private_value = _MAKE_NI_CONTROL(0x02, 0x03),
+	},
+	{
+		.name = "Phono Input Channel B",
+		.private_value = _MAKE_NI_CONTROL(0x02, 0x05),
+	},
+};
+
+static struct snd_kcontrol_new snd_nativeinstruments_ta10_mixers[] = {
+	{
+		.name = "Direct Thru Channel A",
+		.private_value = _MAKE_NI_CONTROL(0x01, 0x03),
+	},
+	{
+		.name = "Direct Thru Channel B",
+		.private_value = _MAKE_NI_CONTROL(0x01, 0x05),
+	},
+	{
+		.name = "Direct Thru Channel C",
+		.private_value = _MAKE_NI_CONTROL(0x01, 0x07),
+	},
+	{
+		.name = "Direct Thru Channel D",
+		.private_value = _MAKE_NI_CONTROL(0x01, 0x09),
+	},
+	{
+		.name = "Phono Input Channel A",
+		.private_value = _MAKE_NI_CONTROL(0x02, 0x03),
+	},
+	{
+		.name = "Phono Input Channel B",
+		.private_value = _MAKE_NI_CONTROL(0x02, 0x05),
+	},
+	{
+		.name = "Phono Input Channel C",
+		.private_value = _MAKE_NI_CONTROL(0x02, 0x07),
+	},
+	{
+		.name = "Phono Input Channel D",
+		.private_value = _MAKE_NI_CONTROL(0x02, 0x09),
+	},
+};
+
+static int snd_nativeinstruments_create_mixer(struct usb_mixer_interface *mixer,
+					      const struct snd_kcontrol_new *kc,
+					      unsigned int count)
+{
+	int i, err = 0;
+	struct snd_kcontrol_new template = {
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+		.get = snd_nativeinstruments_control_get,
+		.put = snd_nativeinstruments_control_put,
+		.info = snd_ctl_boolean_mono_info,
+	};
+
+	for (i = 0; i < count; i++) {
+		struct usb_mixer_elem_list *list;
+
+		template.name = kc[i].name;
+		template.private_value = kc[i].private_value;
+
+		err = add_single_ctl_with_resume(mixer, 0,
+						 snd_ni_update_cur_val,
+						 &template, &list);
+		if (err < 0)
+			break;
+		snd_ni_control_init_val(mixer, list->kctl);
+	}
+
+	return err;
+}
+
+/* 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)
+{
+	static const char *const texts[8] = {
+		"Room 1", "Room 2", "Room 3", "Hall 1",
+		"Hall 2", "Plate", "Delay", "Echo"
+	};
+
+	return snd_ctl_enum_info(uinfo, 1, ARRAY_SIZE(texts), texts);
+}
+
+static int snd_ftu_eff_switch_init(struct usb_mixer_interface *mixer,
+				   struct snd_kcontrol *kctl)
+{
+	struct usb_device *dev = mixer->chip->dev;
+	unsigned int pval = kctl->private_value;
+	int err;
+	unsigned char value[2];
+
+	value[0] = 0x00;
+	value[1] = 0x00;
+
+	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),
+			      value, 2);
+	if (err < 0)
+		return err;
+
+	kctl->private_value |= value[0] << 24;
+	return 0;
+}
+
+static int snd_ftu_eff_switch_get(struct snd_kcontrol *kctl,
+					struct snd_ctl_elem_value *ucontrol)
+{
+	ucontrol->value.enumerated.item[0] = kctl->private_value >> 24;
+	return 0;
+}
+
+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;
+}
+
+static int snd_ftu_eff_switch_put(struct snd_kcontrol *kctl,
+					struct snd_ctl_elem_value *ucontrol)
+{
+	struct usb_mixer_elem_list *list = snd_kcontrol_chip(kctl);
+	unsigned int pval = list->kctl->private_value;
+	int cur_val, err, new_val;
+
+	cur_val = pval >> 24;
+	new_val = ucontrol->value.enumerated.item[0];
+	if (cur_val == new_val)
+		return 0;
+
+	kctl->private_value &= ~(0xff << 24);
+	kctl->private_value |= new_val << 24;
+	err = snd_ftu_eff_switch_update(list);
+	return err < 0 ? err : 1;
+}
+
+static int snd_ftu_create_effect_switch(struct usb_mixer_interface *mixer,
+	int validx, int bUnitID)
+{
+	static struct snd_kcontrol_new template = {
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Effect Program Switch",
+		.index = 0,
+		.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+		.info = snd_ftu_eff_switch_info,
+		.get = snd_ftu_eff_switch_get,
+		.put = snd_ftu_eff_switch_put
+	};
+	struct usb_mixer_elem_list *list;
+	int err;
+
+	err = add_single_ctl_with_resume(mixer, bUnitID,
+					 snd_ftu_eff_switch_update,
+					 &template, &list);
+	if (err < 0)
+		return err;
+	list->kctl->private_value = (validx << 8) | bUnitID;
+	snd_ftu_eff_switch_init(mixer, list->kctl);
+	return 0;
+}
+
+/* Create volume controls for FTU devices*/
+static int snd_ftu_create_volume_ctls(struct usb_mixer_interface *mixer)
+{
+	char name[64];
+	unsigned int control, cmask;
+	int in, out, err;
+
+	const unsigned int id = 5;
+	const int val_type = USB_MIXER_S16;
+
+	for (out = 0; out < 8; out++) {
+		control = out + 1;
+		for (in = 0; in < 8; in++) {
+			cmask = 1 << in;
+			snprintf(name, sizeof(name),
+				"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);
+			if (err < 0)
+				return err;
+		}
+		for (in = 8; in < 16; in++) {
+			cmask = 1 << in;
+			snprintf(name, sizeof(name),
+				"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);
+			if (err < 0)
+				return err;
+		}
+	}
+
+	return 0;
+}
+
+/* This control needs a volume quirk, see mixer.c */
+static int snd_ftu_create_effect_volume_ctl(struct usb_mixer_interface *mixer)
+{
+	static const char name[] = "Effect Volume";
+	const unsigned int id = 6;
+	const int val_type = USB_MIXER_U8;
+	const unsigned int control = 2;
+	const unsigned int cmask = 0;
+
+	return snd_create_std_mono_ctl(mixer, id, control, cmask, val_type,
+					name, snd_usb_mixer_vol_tlv);
+}
+
+/* This control needs a volume quirk, see mixer.c */
+static int snd_ftu_create_effect_duration_ctl(struct usb_mixer_interface *mixer)
+{
+	static const char name[] = "Effect Duration";
+	const unsigned int id = 6;
+	const int val_type = USB_MIXER_S16;
+	const unsigned int control = 3;
+	const unsigned int cmask = 0;
+
+	return snd_create_std_mono_ctl(mixer, id, control, cmask, val_type,
+					name, snd_usb_mixer_vol_tlv);
+}
+
+/* This control needs a volume quirk, see mixer.c */
+static int snd_ftu_create_effect_feedback_ctl(struct usb_mixer_interface *mixer)
+{
+	static const char name[] = "Effect Feedback Volume";
+	const unsigned int id = 6;
+	const int val_type = USB_MIXER_U8;
+	const unsigned int control = 4;
+	const unsigned int cmask = 0;
+
+	return snd_create_std_mono_ctl(mixer, id, control, cmask, val_type,
+					name, NULL);
+}
+
+static int snd_ftu_create_effect_return_ctls(struct usb_mixer_interface *mixer)
+{
+	unsigned int cmask;
+	int err, ch;
+	char name[48];
+
+	const unsigned int id = 7;
+	const int val_type = USB_MIXER_S16;
+	const unsigned int control = 7;
+
+	for (ch = 0; ch < 4; ++ch) {
+		cmask = 1 << ch;
+		snprintf(name, sizeof(name),
+			"Effect Return %d Volume", ch + 1);
+		err = snd_create_std_mono_ctl(mixer, id, control,
+						cmask, val_type, name,
+						snd_usb_mixer_vol_tlv);
+		if (err < 0)
+			return err;
+	}
+
+	return 0;
+}
+
+static int snd_ftu_create_effect_send_ctls(struct usb_mixer_interface *mixer)
+{
+	unsigned int  cmask;
+	int err, ch;
+	char name[48];
+
+	const unsigned int id = 5;
+	const int val_type = USB_MIXER_S16;
+	const unsigned int control = 9;
+
+	for (ch = 0; ch < 8; ++ch) {
+		cmask = 1 << ch;
+		snprintf(name, sizeof(name),
+			"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);
+		if (err < 0)
+			return err;
+	}
+	for (ch = 8; ch < 16; ++ch) {
+		cmask = 1 << ch;
+		snprintf(name, sizeof(name),
+			"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);
+		if (err < 0)
+			return err;
+	}
+	return 0;
+}
+
+static int snd_ftu_create_mixer(struct usb_mixer_interface *mixer)
+{
+	int err;
+
+	err = snd_ftu_create_volume_ctls(mixer);
+	if (err < 0)
+		return err;
+
+	err = snd_ftu_create_effect_switch(mixer, 1, 6);
+	if (err < 0)
+		return err;
+
+	err = snd_ftu_create_effect_volume_ctl(mixer);
+	if (err < 0)
+		return err;
+
+	err = snd_ftu_create_effect_duration_ctl(mixer);
+	if (err < 0)
+		return err;
+
+	err = snd_ftu_create_effect_feedback_ctl(mixer);
+	if (err < 0)
+		return err;
+
+	err = snd_ftu_create_effect_return_ctls(mixer);
+	if (err < 0)
+		return err;
+
+	err = snd_ftu_create_effect_send_ctls(mixer);
+	if (err < 0)
+		return err;
+
+	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_elem_list_to_info(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;
+	}
+}
+
+/* M-Audio Fast Track C400/C600 */
+/* C400/C600 volume controls, this control needs a volume quirk, see mixer.c */
+static int snd_c400_create_vol_ctls(struct usb_mixer_interface *mixer)
+{
+	char name[64];
+	unsigned int cmask, offset;
+	int out, chan, err;
+	int num_outs = 0;
+	int num_ins = 0;
+
+	const unsigned int id = 0x40;
+	const int val_type = USB_MIXER_S16;
+	const int control = 1;
+
+	switch (mixer->chip->usb_id) {
+	case USB_ID(0x0763, 0x2030):
+		num_outs = 6;
+		num_ins = 4;
+		break;
+	case USB_ID(0x0763, 0x2031):
+		num_outs = 8;
+		num_ins = 6;
+		break;
+	}
+
+	for (chan = 0; chan < num_outs + num_ins; chan++) {
+		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);
+			} else {
+				snprintf(name, sizeof(name),
+					"In%d-Out%d Playback Volume",
+					chan - num_outs + 1, out + 1);
+			}
+
+			cmask = (out == 0) ? 0 : 1 << (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);
+			if (err < 0)
+				return err;
+		}
+	}
+
+	return 0;
+}
+
+/* This control needs a volume quirk, see mixer.c */
+static int snd_c400_create_effect_volume_ctl(struct usb_mixer_interface *mixer)
+{
+	static const char name[] = "Effect Volume";
+	const unsigned int id = 0x43;
+	const int val_type = USB_MIXER_U8;
+	const unsigned int control = 3;
+	const unsigned int cmask = 0;
+
+	return snd_create_std_mono_ctl(mixer, id, control, cmask, val_type,
+					name, snd_usb_mixer_vol_tlv);
+}
+
+/* This control needs a volume quirk, see mixer.c */
+static int snd_c400_create_effect_duration_ctl(struct usb_mixer_interface *mixer)
+{
+	static const char name[] = "Effect Duration";
+	const unsigned int id = 0x43;
+	const int val_type = USB_MIXER_S16;
+	const unsigned int control = 4;
+	const unsigned int cmask = 0;
+
+	return snd_create_std_mono_ctl(mixer, id, control, cmask, val_type,
+					name, snd_usb_mixer_vol_tlv);
+}
+
+/* This control needs a volume quirk, see mixer.c */
+static int snd_c400_create_effect_feedback_ctl(struct usb_mixer_interface *mixer)
+{
+	static const char name[] = "Effect Feedback Volume";
+	const unsigned int id = 0x43;
+	const int val_type = USB_MIXER_U8;
+	const unsigned int control = 5;
+	const unsigned int cmask = 0;
+
+	return snd_create_std_mono_ctl(mixer, id, control, cmask, val_type,
+					name, NULL);
+}
+
+static int snd_c400_create_effect_vol_ctls(struct usb_mixer_interface *mixer)
+{
+	char name[64];
+	unsigned int cmask;
+	int chan, err;
+	int num_outs = 0;
+	int num_ins = 0;
+
+	const unsigned int id = 0x42;
+	const int val_type = USB_MIXER_S16;
+	const int control = 1;
+
+	switch (mixer->chip->usb_id) {
+	case USB_ID(0x0763, 0x2030):
+		num_outs = 6;
+		num_ins = 4;
+		break;
+	case USB_ID(0x0763, 0x2031):
+		num_outs = 8;
+		num_ins = 6;
+		break;
+	}
+
+	for (chan = 0; chan < num_outs + num_ins; chan++) {
+		if (chan < num_outs) {
+			snprintf(name, sizeof(name),
+				"Effect Send DOut%d",
+				chan + 1);
+		} else {
+			snprintf(name, sizeof(name),
+				"Effect Send AIn%d",
+				chan - num_outs + 1);
+		}
+
+		cmask = (chan == 0) ? 0 : 1 << (chan - 1);
+		err = snd_create_std_mono_ctl(mixer, id, control,
+						cmask, val_type, name,
+						&snd_usb_mixer_vol_tlv);
+		if (err < 0)
+			return err;
+	}
+
+	return 0;
+}
+
+static int snd_c400_create_effect_ret_vol_ctls(struct usb_mixer_interface *mixer)
+{
+	char name[64];
+	unsigned int cmask;
+	int chan, err;
+	int num_outs = 0;
+	int offset = 0;
+
+	const unsigned int id = 0x40;
+	const int val_type = USB_MIXER_S16;
+	const int control = 1;
+
+	switch (mixer->chip->usb_id) {
+	case USB_ID(0x0763, 0x2030):
+		num_outs = 6;
+		offset = 0x3c;
+		/* { 0x3c, 0x43, 0x3e, 0x45, 0x40, 0x47 } */
+		break;
+	case USB_ID(0x0763, 0x2031):
+		num_outs = 8;
+		offset = 0x70;
+		/* { 0x70, 0x79, 0x72, 0x7b, 0x74, 0x7d, 0x76, 0x7f } */
+		break;
+	}
+
+	for (chan = 0; chan < num_outs; chan++) {
+		snprintf(name, sizeof(name),
+			"Effect Return %d",
+			chan + 1);
+
+		cmask = (chan == 0) ? 0 :
+			1 << (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);
+		if (err < 0)
+			return err;
+	}
+
+	return 0;
+}
+
+static int snd_c400_create_mixer(struct usb_mixer_interface *mixer)
+{
+	int err;
+
+	err = snd_c400_create_vol_ctls(mixer);
+	if (err < 0)
+		return err;
+
+	err = snd_c400_create_effect_vol_ctls(mixer);
+	if (err < 0)
+		return err;
+
+	err = snd_c400_create_effect_ret_vol_ctls(mixer);
+	if (err < 0)
+		return err;
+
+	err = snd_ftu_create_effect_switch(mixer, 2, 0x43);
+	if (err < 0)
+		return err;
+
+	err = snd_c400_create_effect_volume_ctl(mixer);
+	if (err < 0)
+		return err;
+
+	err = snd_c400_create_effect_duration_ctl(mixer);
+	if (err < 0)
+		return err;
+
+	err = snd_c400_create_effect_feedback_ctl(mixer);
+	if (err < 0)
+		return err;
+
+	return 0;
+}
+
+/*
+ * The mixer units for Ebox-44 are corrupt, and even where they
+ * are valid they presents mono controls as L and R channels of
+ * stereo. So we provide a good mixer here.
+ */
+static struct std_mono_table ebox44_table[] = {
+	{
+		.unitid = 4,
+		.control = 1,
+		.cmask = 0x0,
+		.val_type = USB_MIXER_INV_BOOLEAN,
+		.name = "Headphone Playback Switch"
+	},
+	{
+		.unitid = 4,
+		.control = 2,
+		.cmask = 0x1,
+		.val_type = USB_MIXER_S16,
+		.name = "Headphone A Mix Playback Volume"
+	},
+	{
+		.unitid = 4,
+		.control = 2,
+		.cmask = 0x2,
+		.val_type = USB_MIXER_S16,
+		.name = "Headphone B Mix Playback Volume"
+	},
+
+	{
+		.unitid = 7,
+		.control = 1,
+		.cmask = 0x0,
+		.val_type = USB_MIXER_INV_BOOLEAN,
+		.name = "Output Playback Switch"
+	},
+	{
+		.unitid = 7,
+		.control = 2,
+		.cmask = 0x1,
+		.val_type = USB_MIXER_S16,
+		.name = "Output A Playback Volume"
+	},
+	{
+		.unitid = 7,
+		.control = 2,
+		.cmask = 0x2,
+		.val_type = USB_MIXER_S16,
+		.name = "Output B Playback Volume"
+	},
+
+	{
+		.unitid = 10,
+		.control = 1,
+		.cmask = 0x0,
+		.val_type = USB_MIXER_INV_BOOLEAN,
+		.name = "Input Capture Switch"
+	},
+	{
+		.unitid = 10,
+		.control = 2,
+		.cmask = 0x1,
+		.val_type = USB_MIXER_S16,
+		.name = "Input A Capture Volume"
+	},
+	{
+		.unitid = 10,
+		.control = 2,
+		.cmask = 0x2,
+		.val_type = USB_MIXER_S16,
+		.name = "Input B Capture Volume"
+	},
+
+	{}
+};
+
+/* Audio Advantage Micro II findings:
+ *
+ * Mapping spdif AES bits to vendor register.bit:
+ * AES0: [0 0 0 0 2.3 2.2 2.1 2.0] - default 0x00
+ * AES1: [3.3 3.2.3.1.3.0 2.7 2.6 2.5 2.4] - default: 0x01
+ * AES2: [0 0 0 0 0 0 0 0]
+ * AES3: [0 0 0 0 0 0 x 0] - 'x' bit is set basing on standard usb request
+ *                           (UAC_EP_CS_ATTR_SAMPLE_RATE) for Audio Devices
+ *
+ * power on values:
+ * r2: 0x10
+ * r3: 0x20 (b7 is zeroed just before playback (except IEC61937) and set
+ *           just after it to 0xa0, presumably it disables/mutes some analog
+ *           parts when there is no audio.)
+ * r9: 0x28
+ *
+ * Optical transmitter on/off:
+ * vendor register.bit: 9.1
+ * 0 - on (0x28 register value)
+ * 1 - off (0x2a register value)
+ *
+ */
+static int snd_microii_spdif_info(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958;
+	uinfo->count = 1;
+	return 0;
+}
+
+static int snd_microii_spdif_default_get(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	struct usb_mixer_elem_list *list = snd_kcontrol_chip(kcontrol);
+	struct snd_usb_audio *chip = list->mixer->chip;
+	int err;
+	struct usb_interface *iface;
+	struct usb_host_interface *alts;
+	unsigned int ep;
+	unsigned char data[3];
+	int rate;
+
+	err = snd_usb_lock_shutdown(chip);
+	if (err < 0)
+		return err;
+
+	ucontrol->value.iec958.status[0] = kcontrol->private_value & 0xff;
+	ucontrol->value.iec958.status[1] = (kcontrol->private_value >> 8) & 0xff;
+	ucontrol->value.iec958.status[2] = 0x00;
+
+	/* use known values for that card: interface#1 altsetting#1 */
+	iface = usb_ifnum_to_if(chip->dev, 1);
+	if (!iface || iface->num_altsetting < 2)
+		return -EINVAL;
+	alts = &iface->altsetting[1];
+	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));
+	if (err < 0)
+		goto end;
+
+	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;
+}
+
+static int snd_microii_spdif_default_update(struct usb_mixer_elem_list *list)
+{
+	struct snd_usb_audio *chip = list->mixer->chip;
+	unsigned int pval = list->kctl->private_value;
+	u8 reg;
+	int err;
+
+	err = snd_usb_lock_shutdown(chip);
+	if (err < 0)
+		return 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);
+	if (err < 0)
+		goto end;
+
+	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);
+	return err;
+}
+
+static int snd_microii_spdif_default_put(struct snd_kcontrol *kcontrol,
+	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 &= 0xfffff0f0;
+	pval |= (ucontrol->value.iec958.status[1] & 0x0f) << 8;
+	pval |= (ucontrol->value.iec958.status[0] & 0x0f);
+
+	pval &= 0xffff0fff;
+	pval |= (ucontrol->value.iec958.status[1] & 0xf0) << 8;
+
+	/* The frequency bits in AES3 cannot be set via register access. */
+
+	/* Silently ignore any bits from the request that cannot be set. */
+
+	if (pval == pval_old)
+		return 0;
+
+	kcontrol->private_value = pval;
+	err = snd_microii_spdif_default_update(list);
+	return err < 0 ? err : 1;
+}
+
+static int snd_microii_spdif_mask_get(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	ucontrol->value.iec958.status[0] = 0x0f;
+	ucontrol->value.iec958.status[1] = 0xff;
+	ucontrol->value.iec958.status[2] = 0x00;
+	ucontrol->value.iec958.status[3] = 0x00;
+
+	return 0;
+}
+
+static int snd_microii_spdif_switch_get(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	ucontrol->value.integer.value[0] = !(kcontrol->private_value & 0x02);
+
+	return 0;
+}
+
+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;
+}
+
+static int snd_microii_spdif_switch_put(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	struct usb_mixer_elem_list *list = snd_kcontrol_chip(kcontrol);
+	u8 reg;
+	int err;
+
+	reg = ucontrol->value.integer.value[0] ? 0x28 : 0x2a;
+	if (reg != list->kctl->private_value)
+		return 0;
+
+	kcontrol->private_value = reg;
+	err = snd_microii_spdif_switch_update(list);
+	return err < 0 ? err : 1;
+}
+
+static struct snd_kcontrol_new snd_microii_mixer_spdif[] = {
+	{
+		.iface =    SNDRV_CTL_ELEM_IFACE_PCM,
+		.name =     SNDRV_CTL_NAME_IEC958("", PLAYBACK, DEFAULT),
+		.info =     snd_microii_spdif_info,
+		.get =      snd_microii_spdif_default_get,
+		.put =      snd_microii_spdif_default_put,
+		.private_value = 0x00000100UL,/* reset value */
+	},
+	{
+		.access =   SNDRV_CTL_ELEM_ACCESS_READ,
+		.iface =    SNDRV_CTL_ELEM_IFACE_PCM,
+		.name =     SNDRV_CTL_NAME_IEC958("", PLAYBACK, MASK),
+		.info =     snd_microii_spdif_info,
+		.get =      snd_microii_spdif_mask_get,
+	},
+	{
+		.iface =    SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name =     SNDRV_CTL_NAME_IEC958("", PLAYBACK, SWITCH),
+		.info =     snd_ctl_boolean_mono_info,
+		.get =      snd_microii_spdif_switch_get,
+		.put =      snd_microii_spdif_switch_put,
+		.private_value = 0x00000028UL,/* reset value */
+	}
+};
+
+static int snd_microii_controls_create(struct usb_mixer_interface *mixer)
+{
+	int err, i;
+	static usb_mixer_elem_resume_func_t resume_funcs[] = {
+		snd_microii_spdif_default_update,
+		NULL,
+		snd_microii_spdif_switch_update
+	};
+
+	for (i = 0; i < ARRAY_SIZE(snd_microii_mixer_spdif); ++i) {
+		err = add_single_ctl_with_resume(mixer, 0,
+						 resume_funcs[i],
+						 &snd_microii_mixer_spdif[i],
+						 NULL);
+		if (err < 0)
+			return err;
+	}
+
+	return 0;
+}
+
+/* Creative Sound Blaster E1 */
+
+static int snd_soundblaster_e1_switch_get(struct snd_kcontrol *kcontrol,
+					  struct snd_ctl_elem_value *ucontrol)
+{
+	ucontrol->value.integer.value[0] = kcontrol->private_value;
+	return 0;
+}
+
+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;
+}
+
+static int snd_soundblaster_e1_switch_put(struct snd_kcontrol *kcontrol,
+					  struct snd_ctl_elem_value *ucontrol)
+{
+	struct usb_mixer_elem_list *list = snd_kcontrol_chip(kcontrol);
+	unsigned char value = !!ucontrol->value.integer.value[0];
+	int err;
+
+	if (kcontrol->private_value == value)
+		return 0;
+	kcontrol->private_value = value;
+	err = snd_soundblaster_e1_switch_update(list->mixer, value);
+	return err < 0 ? err : 1;
+}
+
+static int snd_soundblaster_e1_switch_resume(struct usb_mixer_elem_list *list)
+{
+	return snd_soundblaster_e1_switch_update(list->mixer,
+						 list->kctl->private_value);
+}
+
+static int snd_soundblaster_e1_switch_info(struct snd_kcontrol *kcontrol,
+					   struct snd_ctl_elem_info *uinfo)
+{
+	static const char *const texts[2] = {
+		"Mic", "Aux"
+	};
+
+	return snd_ctl_enum_info(uinfo, 1, ARRAY_SIZE(texts), texts);
+}
+
+static struct snd_kcontrol_new snd_soundblaster_e1_input_switch = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "Input Source",
+	.info = snd_soundblaster_e1_switch_info,
+	.get = snd_soundblaster_e1_switch_get,
+	.put = snd_soundblaster_e1_switch_put,
+	.private_value = 0,
+};
+
+static int snd_soundblaster_e1_switch_create(struct usb_mixer_interface *mixer)
+{
+	return add_single_ctl_with_resume(mixer, 0,
+					  snd_soundblaster_e1_switch_resume,
+					  &snd_soundblaster_e1_input_switch,
+					  NULL);
+}
+
+static void dell_dock_init_vol(struct snd_usb_audio *chip, int ch, int id)
+{
+	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),
+			&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);
+	return 0;
+}
+
+int snd_usb_mixer_apply_create_quirk(struct usb_mixer_interface *mixer)
+{
+	int err = 0;
+	struct snd_info_entry *entry;
+
+	err = snd_usb_soundblaster_remote_init(mixer);
+	if (err < 0)
+		return err;
+
+	switch (mixer->chip->usb_id) {
+	/* Tascam US-16x08 */
+	case USB_ID(0x0644, 0x8047):
+		err = snd_us16x08_controls_create(mixer);
+		break;
+	case USB_ID(0x041e, 0x3020):
+	case USB_ID(0x041e, 0x3040):
+	case USB_ID(0x041e, 0x3042):
+	case USB_ID(0x041e, 0x30df):
+	case USB_ID(0x041e, 0x3048):
+		err = snd_audigy2nx_controls_create(mixer);
+		if (err < 0)
+			break;
+		if (!snd_card_proc_new(mixer->chip->card, "audigy2nx", &entry))
+			snd_info_set_text_ops(entry, mixer,
+					      snd_audigy2nx_proc_read);
+		break;
+
+	/* EMU0204 */
+	case USB_ID(0x041e, 0x3f19):
+		err = snd_emu0204_controls_create(mixer);
+		break;
+
+	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);
+		break;
+
+	case USB_ID(0x0763, 0x2080): /* M-Audio Fast Track Ultra */
+	case USB_ID(0x0763, 0x2081): /* M-Audio Fast Track Ultra 8R */
+		err = snd_ftu_create_mixer(mixer);
+		break;
+
+	case USB_ID(0x0b05, 0x1739): /* ASUS Xonar U1 */
+	case USB_ID(0x0b05, 0x1743): /* ASUS Xonar U1 (2) */
+	case USB_ID(0x0b05, 0x17a0): /* ASUS Xonar U3 */
+		err = snd_xonar_u1_controls_create(mixer);
+		break;
+
+	case USB_ID(0x0d8c, 0x0103): /* Audio Advantage Micro II */
+		err = snd_microii_controls_create(mixer);
+		break;
+
+	case USB_ID(0x0dba, 0x1000): /* Digidesign Mbox 1 */
+		err = snd_mbox1_create_sync_switch(mixer);
+		break;
+
+	case USB_ID(0x17cc, 0x1011): /* Traktor Audio 6 */
+		err = snd_nativeinstruments_create_mixer(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,
+				snd_nativeinstruments_ta10_mixers,
+				ARRAY_SIZE(snd_nativeinstruments_ta10_mixers));
+		break;
+
+	case USB_ID(0x200c, 0x1018): /* Electrix Ebox-44 */
+		/* detection is disabled in mixer_maps.c */
+		err = snd_create_std_mono_table(mixer, ebox44_table);
+		break;
+
+	case USB_ID(0x1235, 0x8012): /* Focusrite Scarlett 6i6 */
+	case USB_ID(0x1235, 0x8002): /* Focusrite Scarlett 8i6 */
+	case USB_ID(0x1235, 0x8004): /* Focusrite Scarlett 18i6 */
+	case USB_ID(0x1235, 0x8014): /* Focusrite Scarlett 18i8 */
+	case USB_ID(0x1235, 0x800c): /* Focusrite Scarlett 18i20 */
+		err = snd_scarlett_controls_create(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_init(mixer);
+		break;
+	}
+
+	return err;
+}
+
+#ifdef CONFIG_PM
+void snd_usb_mixer_resume_quirk(struct usb_mixer_interface *mixer)
+{
+	switch (mixer->chip->usb_id) {
+	case USB_ID(0x0bda, 0x4014): /* Dell WD15 dock */
+		dell_dock_mixer_init(mixer);
+		break;
+	}
+}
+#endif
+
+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:
+		usb_audio_dbg(mixer->chip, "memory change in unknown unit %d\n", unitid);
+		break;
+	}
+}
+
+static void snd_dragonfly_quirk_db_scale(struct usb_mixer_interface *mixer,
+					 struct usb_mixer_elem_info *cval,
+					 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. */
+	static const DECLARE_TLV_DB_RANGE(scale,
+		 0,  1, TLV_DB_MINMAX_ITEM(-5300, -4970),
+		 2,  5, TLV_DB_MINMAX_ITEM(-4710, -4160),
+		 6,  7, TLV_DB_MINMAX_ITEM(-3884, -3710),
+		 8, 14, TLV_DB_MINMAX_ITEM(-3443, -2560),
+		15, 16, TLV_DB_MINMAX_ITEM(-2475, -2324),
+		17, 19, TLV_DB_MINMAX_ITEM(-2228, -2031),
+		20, 26, TLV_DB_MINMAX_ITEM(-1910, -1393),
+		27, 31, TLV_DB_MINMAX_ITEM(-1322, -1032),
+		32, 40, TLV_DB_MINMAX_ITEM(-968, -490),
+		41, 50, TLV_DB_MINMAX_ITEM(-441, 0),
+	);
+
+	if (cval->min == 0 && cval->max == 50) {
+		usb_audio_info(mixer->chip, "applying DragonFly dB scale quirk (0-50 variant)\n");
+		kctl->tlv.p = scale;
+		kctl->vd[0].access |= SNDRV_CTL_ELEM_ACCESS_TLV_READ;
+		kctl->vd[0].access &= ~SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK;
+
+	} else if (cval->min == 0 && cval->max <= 1000) {
+		/* Some other clearly broken DragonFly variant.
+		 * At least a 0..53 variant (hw v1.0) exists.
+		 */
+		usb_audio_info(mixer->chip, "ignoring too narrow dB range on a DragonFly device");
+		kctl->vd[0].access &= ~SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK;
+	}
+}
+
+void snd_usb_mixer_fu_apply_quirk(struct usb_mixer_interface *mixer,
+				  struct usb_mixer_elem_info *cval, int unitid,
+				  struct snd_kcontrol *kctl)
+{
+	switch (mixer->chip->usb_id) {
+	case USB_ID(0x21b4, 0x0081): /* AudioQuest DragonFly */
+		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;
+	}
+}
+
diff --git a/sound/usb/mixer_quirks.h b/sound/usb/mixer_quirks.h
new file mode 100644
index 0000000..52be26d
--- /dev/null
+++ b/sound/usb/mixer_quirks.h
@@ -0,0 +1,22 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#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);
+
+void snd_usb_mixer_fu_apply_quirk(struct usb_mixer_interface *mixer,
+				  struct usb_mixer_elem_info *cval, int unitid,
+				  struct snd_kcontrol *kctl);
+
+#ifdef CONFIG_PM
+void snd_usb_mixer_resume_quirk(struct usb_mixer_interface *mixer);
+#endif
+
+#endif /* SND_USB_MIXER_QUIRKS_H */
+
diff --git a/sound/usb/mixer_scarlett.c b/sound/usb/mixer_scarlett.c
new file mode 100644
index 0000000..4aeb948
--- /dev/null
+++ b/sound/usb/mixer_scarlett.c
@@ -0,0 +1,1002 @@
+/*
+ *   Scarlett Driver for ALSA
+ *
+ *   Copyright (c) 2013 by Tobias Hoffmann
+ *   Copyright (c) 2013 by Robin Gareus <robin at gareus.org>
+ *   Copyright (c) 2002 by Takashi Iwai <tiwai at suse.de>
+ *   Copyright (c) 2014 by Chris J Arges <chris.j.arges at canonical.com>
+ *
+ *   Many codes borrowed from audio.c by
+ *	    Alan Cox (alan at lxorguk.ukuu.org.uk)
+ *	    Thomas Sailer (sailer at ife.ee.ethz.ch)
+ *
+ *   Code cleanup:
+ *   David Henningsson <david.henningsson at canonical.com>
+ *
+ *   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.
+ *
+ */
+
+/*
+ * Rewritten and extended to support more models, e.g. Scarlett 18i8.
+ *
+ * Auto-detection via UAC2 is not feasible to properly discover the vast
+ * majority of features. It's related to both Linux/ALSA's UAC2 as well as
+ * Focusrite's implementation of it. Eventually quirks may be sufficient but
+ * right now it's a major headache to work arount these things.
+ *
+ * NB. Neither the OSX nor the win driver provided by Focusrite performs
+ * discovery, they seem to operate the same as this driver.
+ */
+
+/* Mixer Interface for the Focusrite Scarlett 18i6 audio interface.
+ *
+ * The protocol was reverse engineered by looking at communication between
+ * Scarlett MixControl (v 1.2.128.0) and the Focusrite(R) Scarlett 18i6
+ * (firmware v305) using wireshark and usbmon in January 2013.
+ * Extended in July 2013.
+ *
+ * this mixer gives complete access to all features of the device:
+ *  - change Impedance of inputs (Line-in, Mic / Instrument, Hi-Z)
+ *  - select clock source
+ *  - dynamic input to mixer-matrix assignment
+ *  - 18 x 6 mixer-matrix gain stages
+ *  - bus routing & volume control
+ *  - automatic re-initialization on connect if device was power-cycled
+ *
+ * USB URB commands overview (bRequest = 0x01 = UAC2_CS_CUR)
+ * wIndex
+ * 0x01 Analog Input line/instrument impedance switch, wValue=0x0901 +
+ *      channel, data=Line/Inst (2bytes)
+ *      pad (-10dB) switch, wValue=0x0b01 + channel, data=Off/On (2bytes)
+ *      ?? wValue=0x0803/04, ?? (2bytes)
+ * 0x0a Master Volume, wValue=0x0200+bus[0:all + only 1..4?] data(2bytes)
+ *      Bus Mute/Unmute wValue=0x0100+bus[0:all + only 1..4?], data(2bytes)
+ * 0x28 Clock source, wValue=0x0100, data={1:int,2:spdif,3:adat} (1byte)
+ * 0x29 Set Sample-rate, wValue=0x0100, data=sample-rate(4bytes)
+ * 0x32 Mixer mux, wValue=0x0600 + mixer-channel, data=input-to-connect(2bytes)
+ * 0x33 Output mux, wValue=bus, data=input-to-connect(2bytes)
+ * 0x34 Capture mux, wValue=0...18, data=input-to-connect(2bytes)
+ * 0x3c Matrix Mixer gains, wValue=mixer-node  data=gain(2bytes)
+ *      ?? [sometimes](4bytes, e.g 0x000003be 0x000003bf ...03ff)
+ *
+ * USB reads: (i.e. actually issued by original software)
+ * 0x01 wValue=0x0901+channel (1byte!!), wValue=0x0b01+channed (1byte!!)
+ * 0x29 wValue=0x0100 sample-rate(4bytes)
+ *      wValue=0x0200 ?? 1byte (only once)
+ * 0x2a wValue=0x0100 ?? 4bytes, sample-rate2 ??
+ *
+ * USB reads with bRequest = 0x03 = UAC2_CS_MEM
+ * 0x3c wValue=0x0002 1byte: sync status (locked=1)
+ *      wValue=0x0000 18*2byte: peak meter (inputs)
+ *      wValue=0x0001 8(?)*2byte: peak meter (mix)
+ *      wValue=0x0003 6*2byte: peak meter (pcm/daw)
+ *
+ * USB write with bRequest = 0x03
+ * 0x3c Save settings to hardware: wValue=0x005a, data=0xa5
+ *
+ *
+ * <ditaa>
+ *  /--------------\    18chn            6chn    /--------------\
+ *  | Hardware  in +--+-------\        /------+--+ ALSA PCM out |
+ *  \--------------/  |       |        |      |  \--------------/
+ *                    |       |        |      |
+ *                    |       v        v      |
+ *                    |   +---------------+   |
+ *                    |    \ Matrix  Mux /    |
+ *                    |     +-----+-----+     |
+ *                    |           |           |
+ *                    |           | 18chn     |
+ *                    |           v           |
+ *                    |     +-----------+     |
+ *                    |     | Mixer     |     |
+ *                    |     |    Matrix |     |
+ *                    |     |           |     |
+ *                    |     | 18x6 Gain |     |
+ *                    |     |   stages  |     |
+ *                    |     +-----+-----+     |
+ *                    |           |           |
+ *                    |           |           |
+ *                    | 18chn     | 6chn      | 6chn
+ *                    v           v           v
+ *                    =========================
+ *             +---------------+     +--—------------+
+ *              \ Output  Mux /       \ Capture Mux /
+ *               +-----+-----+         +-----+-----+
+ *                     |                     |
+ *                     | 6chn                |
+ *                     v                     |
+ *              +-------------+              |
+ *              | Master Gain |              |
+ *              +------+------+              |
+ *                     |                     |
+ *                     | 6chn                | 18chn
+ *                     | (3 stereo pairs)    |
+ *  /--------------\   |                     |   /--------------\
+ *  | Hardware out |<--/                     \-->| ALSA PCM  in |
+ *  \--------------/                             \--------------/
+ * </ditaa>
+ *
+ */
+
+#include <linux/slab.h>
+#include <linux/usb.h>
+#include <linux/usb/audio-v2.h>
+
+#include <sound/core.h>
+#include <sound/control.h>
+#include <sound/tlv.h>
+
+#include "usbaudio.h"
+#include "mixer.h"
+#include "helper.h"
+#include "power.h"
+
+#include "mixer_scarlett.h"
+
+/* some gui mixers can't handle negative ctl values */
+#define SND_SCARLETT_LEVEL_BIAS 128
+#define SND_SCARLETT_MATRIX_IN_MAX 18
+#define SND_SCARLETT_CONTROLS_MAX 10
+#define SND_SCARLETT_OFFSETS_MAX 5
+
+enum {
+	SCARLETT_OUTPUTS,
+	SCARLETT_SWITCH_IMPEDANCE,
+	SCARLETT_SWITCH_PAD,
+};
+
+enum {
+	SCARLETT_OFFSET_PCM = 0,
+	SCARLETT_OFFSET_ANALOG = 1,
+	SCARLETT_OFFSET_SPDIF = 2,
+	SCARLETT_OFFSET_ADAT = 3,
+	SCARLETT_OFFSET_MIX = 4,
+};
+
+struct scarlett_mixer_elem_enum_info {
+	int start;
+	int len;
+	int offsets[SND_SCARLETT_OFFSETS_MAX];
+	char const * const *names;
+};
+
+struct scarlett_mixer_control {
+	unsigned char num;
+	unsigned char type;
+	const char *name;
+};
+
+struct scarlett_device_info {
+	int matrix_in;
+	int matrix_out;
+	int input_len;
+	int output_len;
+
+	struct scarlett_mixer_elem_enum_info opt_master;
+	struct scarlett_mixer_elem_enum_info opt_matrix;
+
+	/* initial values for matrix mux */
+	int matrix_mux_init[SND_SCARLETT_MATRIX_IN_MAX];
+
+	int num_controls;	/* number of items in controls */
+	const struct scarlett_mixer_control controls[SND_SCARLETT_CONTROLS_MAX];
+};
+
+/********************** Enum Strings *************************/
+
+static const struct scarlett_mixer_elem_enum_info opt_pad = {
+	.start = 0,
+	.len = 2,
+	.offsets = {},
+	.names = (char const * const []){
+		"0dB", "-10dB"
+	}
+};
+
+static const struct scarlett_mixer_elem_enum_info opt_impedance = {
+	.start = 0,
+	.len = 2,
+	.offsets = {},
+	.names = (char const * const []){
+		"Line", "Hi-Z"
+	}
+};
+
+static const struct scarlett_mixer_elem_enum_info opt_clock = {
+	.start = 1,
+	.len = 3,
+	.offsets = {},
+	.names = (char const * const []){
+		"Internal", "SPDIF", "ADAT"
+	}
+};
+
+static const struct scarlett_mixer_elem_enum_info opt_sync = {
+	.start = 0,
+	.len = 2,
+	.offsets = {},
+	.names = (char const * const []){
+		"No Lock", "Locked"
+	}
+};
+
+static int scarlett_ctl_switch_info(struct snd_kcontrol *kctl,
+		struct snd_ctl_elem_info *uinfo)
+{
+	struct usb_mixer_elem_info *elem = kctl->private_data;
+
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
+	uinfo->count = elem->channels;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = 1;
+	return 0;
+}
+
+static int scarlett_ctl_switch_get(struct snd_kcontrol *kctl,
+		struct snd_ctl_elem_value *ucontrol)
+{
+	struct usb_mixer_elem_info *elem = kctl->private_data;
+	int i, err, val;
+
+	for (i = 0; i < elem->channels; i++) {
+		err = snd_usb_get_cur_mix_value(elem, i, i, &val);
+		if (err < 0)
+			return err;
+
+		val = !val; /* invert mute logic for mixer */
+		ucontrol->value.integer.value[i] = val;
+	}
+
+	return 0;
+}
+
+static int scarlett_ctl_switch_put(struct snd_kcontrol *kctl,
+		struct snd_ctl_elem_value *ucontrol)
+{
+	struct usb_mixer_elem_info *elem = kctl->private_data;
+	int i, changed = 0;
+	int err, oval, val;
+
+	for (i = 0; i < elem->channels; i++) {
+		err = snd_usb_get_cur_mix_value(elem, i, i, &oval);
+		if (err < 0)
+			return err;
+
+		val = ucontrol->value.integer.value[i];
+		val = !val;
+		if (oval != val) {
+			err = snd_usb_set_cur_mix_value(elem, i, i, val);
+			if (err < 0)
+				return err;
+
+			changed = 1;
+		}
+	}
+
+	return changed;
+}
+
+static int scarlett_ctl_resume(struct usb_mixer_elem_list *list)
+{
+	struct usb_mixer_elem_info *elem = mixer_elem_list_to_info(list);
+	int i;
+
+	for (i = 0; i < elem->channels; i++)
+		if (elem->cached & (1 << i))
+			snd_usb_set_cur_mix_value(elem, i, i,
+						  elem->cache_val[i]);
+	return 0;
+}
+
+static int scarlett_ctl_info(struct snd_kcontrol *kctl,
+			     struct snd_ctl_elem_info *uinfo)
+{
+	struct usb_mixer_elem_info *elem = kctl->private_data;
+
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = elem->channels;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = (int)kctl->private_value +
+		SND_SCARLETT_LEVEL_BIAS;
+	uinfo->value.integer.step = 1;
+	return 0;
+}
+
+static int scarlett_ctl_get(struct snd_kcontrol *kctl,
+			    struct snd_ctl_elem_value *ucontrol)
+{
+	struct usb_mixer_elem_info *elem = kctl->private_data;
+	int i, err, val;
+
+	for (i = 0; i < elem->channels; i++) {
+		err = snd_usb_get_cur_mix_value(elem, i, i, &val);
+		if (err < 0)
+			return err;
+
+		val = clamp(val / 256, -128, (int)kctl->private_value) +
+				    SND_SCARLETT_LEVEL_BIAS;
+		ucontrol->value.integer.value[i] = val;
+	}
+
+	return 0;
+}
+
+static int scarlett_ctl_put(struct snd_kcontrol *kctl,
+			    struct snd_ctl_elem_value *ucontrol)
+{
+	struct usb_mixer_elem_info *elem = kctl->private_data;
+	int i, changed = 0;
+	int err, oval, val;
+
+	for (i = 0; i < elem->channels; i++) {
+		err = snd_usb_get_cur_mix_value(elem, i, i, &oval);
+		if (err < 0)
+			return err;
+
+		val = ucontrol->value.integer.value[i] -
+			SND_SCARLETT_LEVEL_BIAS;
+		val = val * 256;
+		if (oval != val) {
+			err = snd_usb_set_cur_mix_value(elem, i, i, val);
+			if (err < 0)
+				return err;
+
+			changed = 1;
+		}
+	}
+
+	return changed;
+}
+
+static void scarlett_generate_name(int i, char *dst, int offsets[])
+{
+	if (i > offsets[SCARLETT_OFFSET_MIX])
+		sprintf(dst, "Mix %c",
+			'A'+(i - offsets[SCARLETT_OFFSET_MIX] - 1));
+	else if (i > offsets[SCARLETT_OFFSET_ADAT])
+		sprintf(dst, "ADAT %d", i - offsets[SCARLETT_OFFSET_ADAT]);
+	else if (i > offsets[SCARLETT_OFFSET_SPDIF])
+		sprintf(dst, "SPDIF %d", i - offsets[SCARLETT_OFFSET_SPDIF]);
+	else if (i > offsets[SCARLETT_OFFSET_ANALOG])
+		sprintf(dst, "Analog %d", i - offsets[SCARLETT_OFFSET_ANALOG]);
+	else if (i > offsets[SCARLETT_OFFSET_PCM])
+		sprintf(dst, "PCM %d", i - offsets[SCARLETT_OFFSET_PCM]);
+	else
+		sprintf(dst, "Off");
+}
+
+static int scarlett_ctl_enum_dynamic_info(struct snd_kcontrol *kctl,
+					  struct snd_ctl_elem_info *uinfo)
+{
+	struct usb_mixer_elem_info *elem = kctl->private_data;
+	struct scarlett_mixer_elem_enum_info *opt = elem->private_data;
+	unsigned int items = opt->len;
+
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	uinfo->count = elem->channels;
+	uinfo->value.enumerated.items = items;
+
+	if (uinfo->value.enumerated.item >= items)
+		uinfo->value.enumerated.item = items - 1;
+
+	/* generate name dynamically based on item number and offset info */
+	scarlett_generate_name(uinfo->value.enumerated.item,
+			       uinfo->value.enumerated.name,
+			       opt->offsets);
+
+	return 0;
+}
+
+static int scarlett_ctl_enum_info(struct snd_kcontrol *kctl,
+				  struct snd_ctl_elem_info *uinfo)
+{
+	struct usb_mixer_elem_info *elem = kctl->private_data;
+	struct scarlett_mixer_elem_enum_info *opt = elem->private_data;
+
+	return snd_ctl_enum_info(uinfo, elem->channels, opt->len,
+				 (const char * const *)opt->names);
+}
+
+static int scarlett_ctl_enum_get(struct snd_kcontrol *kctl,
+				 struct snd_ctl_elem_value *ucontrol)
+{
+	struct usb_mixer_elem_info *elem = kctl->private_data;
+	struct scarlett_mixer_elem_enum_info *opt = elem->private_data;
+	int err, val;
+
+	err = snd_usb_get_cur_mix_value(elem, 0, 0, &val);
+	if (err < 0)
+		return err;
+
+	val = clamp(val - opt->start, 0, opt->len-1);
+
+	ucontrol->value.enumerated.item[0] = val;
+
+	return 0;
+}
+
+static int scarlett_ctl_enum_put(struct snd_kcontrol *kctl,
+				 struct snd_ctl_elem_value *ucontrol)
+{
+	struct usb_mixer_elem_info *elem = kctl->private_data;
+	struct scarlett_mixer_elem_enum_info *opt = elem->private_data;
+	int err, oval, val;
+
+	err = snd_usb_get_cur_mix_value(elem, 0, 0, &oval);
+	if (err < 0)
+		return err;
+
+	val = ucontrol->value.integer.value[0];
+	val = val + opt->start;
+	if (val != oval) {
+		snd_usb_set_cur_mix_value(elem, 0, 0, val);
+		return 1;
+	}
+	return 0;
+}
+
+static int scarlett_ctl_enum_resume(struct usb_mixer_elem_list *list)
+{
+	struct usb_mixer_elem_info *elem = mixer_elem_list_to_info(list);
+
+	if (elem->cached)
+		snd_usb_set_cur_mix_value(elem, 0, 0, *elem->cache_val);
+	return 0;
+}
+
+static int scarlett_ctl_meter_get(struct snd_kcontrol *kctl,
+				  struct snd_ctl_elem_value *ucontrol)
+{
+	struct usb_mixer_elem_info *elem = kctl->private_data;
+	struct snd_usb_audio *chip = elem->head.mixer->chip;
+	unsigned char buf[2 * MAX_CHANNELS] = {0, };
+	int wValue = (elem->control << 8) | elem->idx_off;
+	int idx = snd_usb_ctrl_intf(chip) | (elem->head.id << 8);
+	int err;
+
+	err = snd_usb_ctl_msg(chip->dev,
+				usb_rcvctrlpipe(chip->dev, 0),
+				UAC2_CS_MEM,
+				USB_RECIP_INTERFACE | USB_TYPE_CLASS |
+				USB_DIR_IN, wValue, idx, buf, elem->channels);
+	if (err < 0)
+		return err;
+
+	ucontrol->value.enumerated.item[0] = clamp((int)buf[0], 0, 1);
+	return 0;
+}
+
+static const struct snd_kcontrol_new usb_scarlett_ctl_switch = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "",
+	.info = scarlett_ctl_switch_info,
+	.get =  scarlett_ctl_switch_get,
+	.put =  scarlett_ctl_switch_put,
+};
+
+static const DECLARE_TLV_DB_SCALE(db_scale_scarlett_gain, -12800, 100, 0);
+
+static const struct snd_kcontrol_new usb_scarlett_ctl = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.access = SNDRV_CTL_ELEM_ACCESS_READWRITE |
+		  SNDRV_CTL_ELEM_ACCESS_TLV_READ,
+	.name = "",
+	.info = scarlett_ctl_info,
+	.get =  scarlett_ctl_get,
+	.put =  scarlett_ctl_put,
+	.private_value = 6,  /* max value */
+	.tlv = { .p = db_scale_scarlett_gain }
+};
+
+static const struct snd_kcontrol_new usb_scarlett_ctl_master = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.access = SNDRV_CTL_ELEM_ACCESS_READWRITE |
+		  SNDRV_CTL_ELEM_ACCESS_TLV_READ,
+	.name = "",
+	.info = scarlett_ctl_info,
+	.get =  scarlett_ctl_get,
+	.put =  scarlett_ctl_put,
+	.private_value = 6,  /* max value */
+	.tlv = { .p = db_scale_scarlett_gain }
+};
+
+static const struct snd_kcontrol_new usb_scarlett_ctl_enum = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "",
+	.info = scarlett_ctl_enum_info,
+	.get =  scarlett_ctl_enum_get,
+	.put =  scarlett_ctl_enum_put,
+};
+
+static const struct snd_kcontrol_new usb_scarlett_ctl_dynamic_enum = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "",
+	.info = scarlett_ctl_enum_dynamic_info,
+	.get =  scarlett_ctl_enum_get,
+	.put =  scarlett_ctl_enum_put,
+};
+
+static const struct snd_kcontrol_new usb_scarlett_ctl_sync = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE,
+	.name = "",
+	.info = scarlett_ctl_enum_info,
+	.get =  scarlett_ctl_meter_get,
+};
+
+static int add_new_ctl(struct usb_mixer_interface *mixer,
+		       const struct snd_kcontrol_new *ncontrol,
+		       usb_mixer_elem_resume_func_t resume,
+		       int index, int offset, int num,
+		       int val_type, int channels, const char *name,
+		       const struct scarlett_mixer_elem_enum_info *opt,
+		       struct usb_mixer_elem_info **elem_ret
+)
+{
+	struct snd_kcontrol *kctl;
+	struct usb_mixer_elem_info *elem;
+	int err;
+
+	elem = kzalloc(sizeof(*elem), GFP_KERNEL);
+	if (!elem)
+		return -ENOMEM;
+
+	elem->head.mixer = mixer;
+	elem->head.resume = resume;
+	elem->control = offset;
+	elem->idx_off = num;
+	elem->head.id = index;
+	elem->val_type = val_type;
+
+	elem->channels = channels;
+
+	/* add scarlett_mixer_elem_enum_info struct */
+	elem->private_data = (void *)opt;
+
+	kctl = snd_ctl_new1(ncontrol, elem);
+	if (!kctl) {
+		kfree(elem);
+		return -ENOMEM;
+	}
+	kctl->private_free = snd_usb_mixer_elem_free;
+
+	strlcpy(kctl->id.name, name, sizeof(kctl->id.name));
+
+	err = snd_usb_mixer_add_control(&elem->head, kctl);
+	if (err < 0)
+		return err;
+
+	if (elem_ret)
+		*elem_ret = elem;
+
+	return 0;
+}
+
+static int add_output_ctls(struct usb_mixer_interface *mixer,
+			   int index, const char *name,
+			   const struct scarlett_device_info *info)
+{
+	int err;
+	char mx[SNDRV_CTL_ELEM_ID_NAME_MAXLEN];
+	struct usb_mixer_elem_info *elem;
+
+	/* Add mute switch */
+	snprintf(mx, sizeof(mx), "Master %d (%s) Playback Switch",
+		index + 1, name);
+	err = add_new_ctl(mixer, &usb_scarlett_ctl_switch,
+			  scarlett_ctl_resume, 0x0a, 0x01,
+			  2*index+1, USB_MIXER_S16, 2, mx, NULL, &elem);
+	if (err < 0)
+		return err;
+
+	/* Add volume control and initialize to 0 */
+	snprintf(mx, sizeof(mx), "Master %d (%s) Playback Volume",
+		index + 1, name);
+	err = add_new_ctl(mixer, &usb_scarlett_ctl_master,
+			  scarlett_ctl_resume, 0x0a, 0x02,
+			  2*index+1, USB_MIXER_S16, 2, mx, NULL, &elem);
+	if (err < 0)
+		return err;
+
+	/* Add L channel source playback enumeration */
+	snprintf(mx, sizeof(mx), "Master %dL (%s) Source Playback Enum",
+		index + 1, name);
+	err = add_new_ctl(mixer, &usb_scarlett_ctl_dynamic_enum,
+			  scarlett_ctl_enum_resume, 0x33, 0x00,
+			  2*index, USB_MIXER_S16, 1, mx, &info->opt_master,
+			  &elem);
+	if (err < 0)
+		return err;
+
+	/* Add R channel source playback enumeration */
+	snprintf(mx, sizeof(mx), "Master %dR (%s) Source Playback Enum",
+		index + 1, name);
+	err = add_new_ctl(mixer, &usb_scarlett_ctl_dynamic_enum,
+			  scarlett_ctl_enum_resume, 0x33, 0x00,
+			  2*index+1, USB_MIXER_S16, 1, mx, &info->opt_master,
+			  &elem);
+	if (err < 0)
+		return err;
+
+	return 0;
+}
+
+/********************** device-specific config *************************/
+
+/*  untested...  */
+static struct scarlett_device_info s6i6_info = {
+	.matrix_in = 18,
+	.matrix_out = 8,
+	.input_len = 6,
+	.output_len = 6,
+
+	.opt_master = {
+		.start = -1,
+		.len = 27,
+		.offsets = {0, 12, 16, 18, 18},
+		.names = NULL
+	},
+
+	.opt_matrix = {
+		.start = -1,
+		.len = 19,
+		.offsets = {0, 12, 16, 18, 18},
+		.names = NULL
+	},
+
+	.num_controls = 9,
+	.controls = {
+		{ .num = 0, .type = SCARLETT_OUTPUTS, .name = "Monitor" },
+		{ .num = 1, .type = SCARLETT_OUTPUTS, .name = "Headphone" },
+		{ .num = 2, .type = SCARLETT_OUTPUTS, .name = "SPDIF" },
+		{ .num = 1, .type = SCARLETT_SWITCH_IMPEDANCE, .name = NULL},
+		{ .num = 1, .type = SCARLETT_SWITCH_PAD, .name = NULL},
+		{ .num = 2, .type = SCARLETT_SWITCH_IMPEDANCE, .name = NULL},
+		{ .num = 2, .type = SCARLETT_SWITCH_PAD, .name = NULL},
+		{ .num = 3, .type = SCARLETT_SWITCH_PAD, .name = NULL},
+		{ .num = 4, .type = SCARLETT_SWITCH_PAD, .name = NULL},
+	},
+
+	.matrix_mux_init = {
+		12, 13, 14, 15,                 /* Analog -> 1..4 */
+		16, 17,                          /* SPDIF -> 5,6 */
+		0, 1, 2, 3, 4, 5, 6, 7,     /* PCM[1..12] -> 7..18 */
+		8, 9, 10, 11
+	}
+};
+
+/*  untested...  */
+static struct scarlett_device_info s8i6_info = {
+	.matrix_in = 18,
+	.matrix_out = 6,
+	.input_len = 8,
+	.output_len = 6,
+
+	.opt_master = {
+		.start = -1,
+		.len = 25,
+		.offsets = {0, 12, 16, 18, 18},
+		.names = NULL
+	},
+
+	.opt_matrix = {
+		.start = -1,
+		.len = 19,
+		.offsets = {0, 12, 16, 18, 18},
+		.names = NULL
+	},
+
+	.num_controls = 7,
+	.controls = {
+		{ .num = 0, .type = SCARLETT_OUTPUTS, .name = "Monitor" },
+		{ .num = 1, .type = SCARLETT_OUTPUTS, .name = "Headphone" },
+		{ .num = 2, .type = SCARLETT_OUTPUTS, .name = "SPDIF" },
+		{ .num = 1, .type = SCARLETT_SWITCH_IMPEDANCE, .name = NULL},
+		{ .num = 2, .type = SCARLETT_SWITCH_IMPEDANCE, .name = NULL},
+		{ .num = 3, .type = SCARLETT_SWITCH_PAD, .name = NULL},
+		{ .num = 4, .type = SCARLETT_SWITCH_PAD, .name = NULL},
+	},
+
+	.matrix_mux_init = {
+		12, 13, 14, 15,                 /* Analog -> 1..4 */
+		16, 17,                          /* SPDIF -> 5,6 */
+		0, 1, 2, 3, 4, 5, 6, 7,     /* PCM[1..12] -> 7..18 */
+		8, 9, 10, 11
+	}
+};
+
+static struct scarlett_device_info s18i6_info = {
+	.matrix_in = 18,
+	.matrix_out = 6,
+	.input_len = 18,
+	.output_len = 6,
+
+	.opt_master = {
+		.start = -1,
+		.len = 31,
+		.offsets = {0, 6, 14, 16, 24},
+		.names = NULL,
+	},
+
+	.opt_matrix = {
+		.start = -1,
+		.len = 25,
+		.offsets = {0, 6, 14, 16, 24},
+		.names = NULL,
+	},
+
+	.num_controls = 5,
+	.controls = {
+		{ .num = 0, .type = SCARLETT_OUTPUTS, .name = "Monitor" },
+		{ .num = 1, .type = SCARLETT_OUTPUTS, .name = "Headphone" },
+		{ .num = 2, .type = SCARLETT_OUTPUTS, .name = "SPDIF" },
+		{ .num = 1, .type = SCARLETT_SWITCH_IMPEDANCE, .name = NULL},
+		{ .num = 2, .type = SCARLETT_SWITCH_IMPEDANCE, .name = NULL},
+	},
+
+	.matrix_mux_init = {
+		 6,  7,  8,  9, 10, 11, 12, 13, /* Analog -> 1..8 */
+		16, 17, 18, 19, 20, 21,     /* ADAT[1..6] -> 9..14 */
+		14, 15,                          /* SPDIF -> 15,16 */
+		0, 1                          /* PCM[1,2] -> 17,18 */
+	}
+};
+
+static struct scarlett_device_info s18i8_info = {
+	.matrix_in = 18,
+	.matrix_out = 8,
+	.input_len = 18,
+	.output_len = 8,
+
+	.opt_master = {
+		.start = -1,
+		.len = 35,
+		.offsets = {0, 8, 16, 18, 26},
+		.names = NULL
+	},
+
+	.opt_matrix = {
+		.start = -1,
+		.len = 27,
+		.offsets = {0, 8, 16, 18, 26},
+		.names = NULL
+	},
+
+	.num_controls = 10,
+	.controls = {
+		{ .num = 0, .type = SCARLETT_OUTPUTS, .name = "Monitor" },
+		{ .num = 1, .type = SCARLETT_OUTPUTS, .name = "Headphone 1" },
+		{ .num = 2, .type = SCARLETT_OUTPUTS, .name = "Headphone 2" },
+		{ .num = 3, .type = SCARLETT_OUTPUTS, .name = "SPDIF" },
+		{ .num = 1, .type = SCARLETT_SWITCH_IMPEDANCE, .name = NULL},
+		{ .num = 1, .type = SCARLETT_SWITCH_PAD, .name = NULL},
+		{ .num = 2, .type = SCARLETT_SWITCH_IMPEDANCE, .name = NULL},
+		{ .num = 2, .type = SCARLETT_SWITCH_PAD, .name = NULL},
+		{ .num = 3, .type = SCARLETT_SWITCH_PAD, .name = NULL},
+		{ .num = 4, .type = SCARLETT_SWITCH_PAD, .name = NULL},
+	},
+
+	.matrix_mux_init = {
+		 8,  9, 10, 11, 12, 13, 14, 15, /* Analog -> 1..8 */
+		18, 19, 20, 21, 22, 23,     /* ADAT[1..6] -> 9..14 */
+		16, 17,                          /* SPDIF -> 15,16 */
+		0, 1                          /* PCM[1,2] -> 17,18 */
+	}
+};
+
+static struct scarlett_device_info s18i20_info = {
+	.matrix_in = 18,
+	.matrix_out = 8,
+	.input_len = 18,
+	.output_len = 20,
+
+	.opt_master = {
+		.start = -1,
+		.len = 47,
+		.offsets = {0, 20, 28, 30, 38},
+		.names = NULL
+	},
+
+	.opt_matrix = {
+		.start = -1,
+		.len = 39,
+		.offsets = {0, 20, 28, 30, 38},
+		.names = NULL
+	},
+
+	.num_controls = 10,
+	.controls = {
+		{ .num = 0, .type = SCARLETT_OUTPUTS, .name = "Monitor" },
+		{ .num = 1, .type = SCARLETT_OUTPUTS, .name = "Line 3/4" },
+		{ .num = 2, .type = SCARLETT_OUTPUTS, .name = "Line 5/6" },
+		{ .num = 3, .type = SCARLETT_OUTPUTS, .name = "Line 7/8" },
+		{ .num = 4, .type = SCARLETT_OUTPUTS, .name = "Line 9/10" },
+		{ .num = 5, .type = SCARLETT_OUTPUTS, .name = "SPDIF" },
+		{ .num = 6, .type = SCARLETT_OUTPUTS, .name = "ADAT 1/2" },
+		{ .num = 7, .type = SCARLETT_OUTPUTS, .name = "ADAT 3/4" },
+		{ .num = 8, .type = SCARLETT_OUTPUTS, .name = "ADAT 5/6" },
+		{ .num = 9, .type = SCARLETT_OUTPUTS, .name = "ADAT 7/8" },
+		/*{ .num = 1, .type = SCARLETT_SWITCH_IMPEDANCE, .name = NULL},
+		{ .num = 1, .type = SCARLETT_SWITCH_PAD, .name = NULL},
+		{ .num = 2, .type = SCARLETT_SWITCH_IMPEDANCE, .name = NULL},
+		{ .num = 2, .type = SCARLETT_SWITCH_PAD, .name = NULL},
+		{ .num = 3, .type = SCARLETT_SWITCH_PAD, .name = NULL},
+		{ .num = 4, .type = SCARLETT_SWITCH_PAD, .name = NULL},*/
+	},
+
+	.matrix_mux_init = {
+		20, 21, 22, 23, 24, 25, 26, 27, /* Analog -> 1..8 */
+		30, 31, 32, 33, 34, 35,     /* ADAT[1..6] -> 9..14 */
+		28, 29,                          /* SPDIF -> 15,16 */
+		0, 1                          /* PCM[1,2] -> 17,18 */
+	}
+};
+
+
+static int scarlett_controls_create_generic(struct usb_mixer_interface *mixer,
+	struct scarlett_device_info *info)
+{
+	int i, err;
+	char mx[SNDRV_CTL_ELEM_ID_NAME_MAXLEN];
+	const struct scarlett_mixer_control *ctl;
+	struct usb_mixer_elem_info *elem;
+
+	/* create master switch and playback volume */
+	err = add_new_ctl(mixer, &usb_scarlett_ctl_switch,
+			  scarlett_ctl_resume, 0x0a, 0x01, 0,
+			  USB_MIXER_S16, 1, "Master Playback Switch", NULL,
+			  &elem);
+	if (err < 0)
+		return err;
+
+	err = add_new_ctl(mixer, &usb_scarlett_ctl_master,
+			  scarlett_ctl_resume, 0x0a, 0x02, 0,
+			  USB_MIXER_S16, 1, "Master Playback Volume", NULL,
+			  &elem);
+	if (err < 0)
+		return err;
+
+	/* iterate through controls in info struct and create each one */
+	for (i = 0; i < info->num_controls; i++) {
+		ctl = &info->controls[i];
+
+		switch (ctl->type) {
+		case SCARLETT_OUTPUTS:
+			err = add_output_ctls(mixer, ctl->num, ctl->name, info);
+			if (err < 0)
+				return err;
+			break;
+		case SCARLETT_SWITCH_IMPEDANCE:
+			sprintf(mx, "Input %d Impedance Switch", ctl->num);
+			err = add_new_ctl(mixer, &usb_scarlett_ctl_enum,
+					  scarlett_ctl_enum_resume, 0x01,
+					  0x09, ctl->num, USB_MIXER_S16, 1, mx,
+					  &opt_impedance, &elem);
+			if (err < 0)
+				return err;
+			break;
+		case SCARLETT_SWITCH_PAD:
+			sprintf(mx, "Input %d Pad Switch", ctl->num);
+			err = add_new_ctl(mixer, &usb_scarlett_ctl_enum,
+					  scarlett_ctl_enum_resume, 0x01,
+					  0x0b, ctl->num, USB_MIXER_S16, 1, mx,
+					  &opt_pad, &elem);
+			if (err < 0)
+				return err;
+			break;
+		}
+	}
+
+	return 0;
+}
+
+/*
+ * Create and initialize a mixer for the Focusrite(R) Scarlett
+ */
+int snd_scarlett_controls_create(struct usb_mixer_interface *mixer)
+{
+	int err, i, o;
+	char mx[SNDRV_CTL_ELEM_ID_NAME_MAXLEN];
+	struct scarlett_device_info *info;
+	struct usb_mixer_elem_info *elem;
+	static char sample_rate_buffer[4] = { '\x80', '\xbb', '\x00', '\x00' };
+
+	/* only use UAC_VERSION_2 */
+	if (!mixer->protocol)
+		return 0;
+
+	switch (mixer->chip->usb_id) {
+	case USB_ID(0x1235, 0x8012):
+		info = &s6i6_info;
+		break;
+	case USB_ID(0x1235, 0x8002):
+		info = &s8i6_info;
+		break;
+	case USB_ID(0x1235, 0x8004):
+		info = &s18i6_info;
+		break;
+	case USB_ID(0x1235, 0x8014):
+		info = &s18i8_info;
+		break;
+	case USB_ID(0x1235, 0x800c):
+		info = &s18i20_info;
+		break;
+	default: /* device not (yet) supported */
+		return -EINVAL;
+	}
+
+	/* generic function to create controls */
+	err = scarlett_controls_create_generic(mixer, info);
+	if (err < 0)
+		return err;
+
+	/* setup matrix controls */
+	for (i = 0; i < info->matrix_in; i++) {
+		snprintf(mx, sizeof(mx), "Matrix %02d Input Playback Route",
+			 i+1);
+		err = add_new_ctl(mixer, &usb_scarlett_ctl_dynamic_enum,
+				  scarlett_ctl_enum_resume, 0x32,
+				  0x06, i, USB_MIXER_S16, 1, mx,
+				  &info->opt_matrix, &elem);
+		if (err < 0)
+			return err;
+
+		for (o = 0; o < info->matrix_out; o++) {
+			sprintf(mx, "Matrix %02d Mix %c Playback Volume", i+1,
+				o+'A');
+			err = add_new_ctl(mixer, &usb_scarlett_ctl,
+					  scarlett_ctl_resume, 0x3c, 0x00,
+					  (i << 3) + (o & 0x07), USB_MIXER_S16,
+					  1, mx, NULL, &elem);
+			if (err < 0)
+				return err;
+
+		}
+	}
+
+	for (i = 0; i < info->input_len; i++) {
+		snprintf(mx, sizeof(mx), "Input Source %02d Capture Route",
+			 i+1);
+		err = add_new_ctl(mixer, &usb_scarlett_ctl_dynamic_enum,
+				  scarlett_ctl_enum_resume, 0x34,
+				  0x00, i, USB_MIXER_S16, 1, mx,
+				  &info->opt_master, &elem);
+		if (err < 0)
+			return err;
+	}
+
+	/* val_len == 1 needed here */
+	err = add_new_ctl(mixer, &usb_scarlett_ctl_enum,
+			  scarlett_ctl_enum_resume, 0x28, 0x01, 0,
+			  USB_MIXER_U8, 1, "Sample Clock Source",
+			  &opt_clock, &elem);
+	if (err < 0)
+		return err;
+
+	/* val_len == 1 and UAC2_CS_MEM */
+	err = add_new_ctl(mixer, &usb_scarlett_ctl_sync, NULL, 0x3c, 0x00, 2,
+			  USB_MIXER_U8, 1, "Sample Clock Sync Status",
+			  &opt_sync, &elem);
+	if (err < 0)
+		return err;
+
+	/* initialize sampling rate to 48000 */
+	err = snd_usb_ctl_msg(mixer->chip->dev,
+		usb_sndctrlpipe(mixer->chip->dev, 0), UAC2_CS_CUR,
+		USB_RECIP_INTERFACE | USB_TYPE_CLASS |
+		USB_DIR_OUT, 0x0100, snd_usb_ctrl_intf(mixer->chip) |
+		(0x29 << 8), sample_rate_buffer, 4);
+	if (err < 0)
+		return err;
+
+	return err;
+}
diff --git a/sound/usb/mixer_scarlett.h b/sound/usb/mixer_scarlett.h
new file mode 100644
index 0000000..bbf063b
--- /dev/null
+++ b/sound/usb/mixer_scarlett.h
@@ -0,0 +1,7 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __USB_MIXER_SCARLETT_H
+#define __USB_MIXER_SCARLETT_H
+
+int snd_scarlett_controls_create(struct usb_mixer_interface *mixer);
+
+#endif /* __USB_MIXER_SCARLETT_H */
diff --git a/sound/usb/mixer_us16x08.c b/sound/usb/mixer_us16x08.c
new file mode 100644
index 0000000..26ed23b
--- /dev/null
+++ b/sound/usb/mixer_us16x08.c
@@ -0,0 +1,1424 @@
+/*
+ *   Tascam US-16x08 ALSA driver
+ *
+ *   Copyright (c) 2016 by Detlef Urban (onkel@paraair.de)
+ *
+ *   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.
+ *
+ */
+
+#include <linux/slab.h>
+#include <linux/usb.h>
+#include <linux/usb/audio-v2.h>
+
+#include <sound/core.h>
+#include <sound/control.h>
+
+#include "usbaudio.h"
+#include "mixer.h"
+#include "helper.h"
+
+#include "mixer_us16x08.h"
+
+/* USB control message templates */
+static const char route_msg[] = {
+	0x61,
+	0x02,
+	0x03, /* input from master (0x02) or input from computer bus (0x03) */
+	0x62,
+	0x02,
+	0x01, /* input index (0x01/0x02 eq. left/right) or bus (0x01-0x08) */
+	0x41,
+	0x01,
+	0x61,
+	0x02,
+	0x01,
+	0x62,
+	0x02,
+	0x01, /* output index (0x01-0x08) */
+	0x42,
+	0x01,
+	0x43,
+	0x01,
+	0x00,
+	0x00
+};
+
+static const char mix_init_msg1[] = {
+	0x71, 0x01, 0x00, 0x00
+};
+
+static const char mix_init_msg2[] = {
+	0x62, 0x02, 0x00, 0x61, 0x02, 0x04, 0xb1, 0x01, 0x00, 0x00
+};
+
+static const char mix_msg_in[] = {
+	/* default message head, equal to all mixers */
+	0x61, 0x02, 0x04, 0x62, 0x02, 0x01,
+	0x81, /* 0x06: Controller ID */
+	0x02, /* 0x07:  */
+	0x00, /* 0x08: Value of common mixer */
+	0x00,
+	0x00
+};
+
+static const char mix_msg_out[] = {
+	/* default message head, equal to all mixers */
+	0x61, 0x02, 0x02, 0x62, 0x02, 0x01,
+	0x81, /* 0x06: Controller ID */
+	0x02, /*                    0x07:  */
+	0x00, /*                    0x08: Value of common mixer */
+	0x00,
+	0x00
+};
+
+static const char bypass_msg_out[] = {
+	0x45,
+	0x02,
+	0x01, /* on/off flag */
+	0x00,
+	0x00
+};
+
+static const char bus_msg_out[] = {
+	0x44,
+	0x02,
+	0x01, /* on/off flag */
+	0x00,
+	0x00
+};
+
+static const char comp_msg[] = {
+	/* default message head, equal to all mixers */
+	0x61, 0x02, 0x04, 0x62, 0x02, 0x01,
+	0x91,
+	0x02,
+	0xf0, /* 0x08: Threshold db (8) (e0 ... 00) (+-0dB -- -32dB) x-32 */
+	0x92,
+	0x02,
+	0x0a, /* 0x0b: Ratio (0a,0b,0d,0f,11,14,19,1e,23,28,32,3c,50,a0,ff)  */
+	0x93,
+	0x02,
+	0x02, /* 0x0e: Attack (0x02 ... 0xc0) (2ms ... 200ms) */
+	0x94,
+	0x02,
+	0x01, /* 0x11: Release (0x01 ... 0x64) (10ms ... 1000ms) x*10  */
+	0x95,
+	0x02,
+	0x03, /* 0x14: gain (0 ... 20) (0dB .. 20dB) */
+	0x96,
+	0x02,
+	0x01,
+	0x97,
+	0x02,
+	0x01, /* 0x1a: main Comp switch (0 ... 1) (off ... on)) */
+	0x00,
+	0x00
+};
+
+static const char eqs_msq[] = {
+	/* default message head, equal to all mixers */
+	0x61, 0x02, 0x04, 0x62, 0x02, 0x01,
+	0x51, /*                0x06: Controller ID  */
+	0x02,
+	0x04, /* 0x08: EQ set num (0x01..0x04) (LOW, LOWMID, HIGHMID, HIGH)) */
+	0x52,
+	0x02,
+	0x0c, /* 0x0b: value dB (0 ... 12) (-12db .. +12db)  x-6 */
+	0x53,
+	0x02,
+	0x0f, /* 0x0e: value freq (32-47) (1.7kHz..18kHz) */
+	0x54,
+	0x02,
+	0x02, /* 0x11: band width (0-6) (Q16-Q0.25)  2^x/4 (EQ xxMID only) */
+	0x55,
+	0x02,
+	0x01, /* 0x14: main EQ switch (0 ... 1) (off ... on)) */
+	0x00,
+	0x00
+};
+
+/* compressor ratio map */
+static const char ratio_map[] = {
+	0x0a, 0x0b, 0x0d, 0x0f, 0x11, 0x14, 0x19, 0x1e,
+	0x23, 0x28, 0x32, 0x3c, 0x50, 0xa0, 0xff
+};
+
+/* route enumeration names */
+static const char *const route_names[] = {
+	"Master Left", "Master Right", "Output 1", "Output 2", "Output 3",
+	"Output 4", "Output 5", "Output 6", "Output 7", "Output 8",
+};
+
+static int snd_us16x08_recv_urb(struct snd_usb_audio *chip,
+	unsigned char *buf, int size)
+{
+
+	mutex_lock(&chip->mutex);
+	snd_usb_ctl_msg(chip->dev,
+		usb_rcvctrlpipe(chip->dev, 0),
+		SND_US16X08_URB_METER_REQUEST,
+		SND_US16X08_URB_METER_REQUESTTYPE, 0, 0, buf, size);
+	mutex_unlock(&chip->mutex);
+	return 0;
+}
+
+/* wrapper function to send prepared URB buffer to usb device. Return an error
+ * code if something went wrong
+ */
+static int snd_us16x08_send_urb(struct snd_usb_audio *chip, char *buf, int size)
+{
+	return snd_usb_ctl_msg(chip->dev, usb_sndctrlpipe(chip->dev, 0),
+			SND_US16X08_URB_REQUEST, SND_US16X08_URB_REQUESTTYPE,
+			0, 0, buf, size);
+}
+
+static int snd_us16x08_route_info(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_info *uinfo)
+{
+	return snd_ctl_enum_info(uinfo, 1, 10, route_names);
+}
+
+static int snd_us16x08_route_get(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	struct usb_mixer_elem_info *elem = kcontrol->private_data;
+	int index = ucontrol->id.index;
+
+	/* route has no bias */
+	ucontrol->value.enumerated.item[0] = elem->cache_val[index];
+
+	return 0;
+}
+
+static int snd_us16x08_route_put(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	struct usb_mixer_elem_info *elem = kcontrol->private_data;
+	struct snd_usb_audio *chip = elem->head.mixer->chip;
+	int index = ucontrol->id.index;
+	char buf[sizeof(route_msg)];
+	int val, val_org, err;
+
+	/*  get the new value (no bias for routes) */
+	val = ucontrol->value.enumerated.item[0];
+
+	/* sanity check */
+	if (val < 0 || val > 9)
+		return -EINVAL;
+
+	/* prepare the message buffer from template */
+	memcpy(buf, route_msg, sizeof(route_msg));
+
+	if (val < 2) {
+		/* input comes from a master channel */
+		val_org = val;
+		buf[2] = 0x02;
+	} else {
+		/* input comes from a computer channel */
+		buf[2] = 0x03;
+		val_org = val - 2;
+	}
+
+	/* place new route selection in URB message */
+	buf[5] = (unsigned char) (val_org & 0x0f) + 1;
+	/* place route selector in URB message */
+	buf[13] = index + 1;
+
+	err = snd_us16x08_send_urb(chip, buf, sizeof(route_msg));
+
+	if (err > 0) {
+		elem->cached |= 1 << index;
+		elem->cache_val[index] = val;
+	} else {
+		usb_audio_dbg(chip, "Failed to set routing, err:%d\n", err);
+	}
+
+	return err > 0 ? 1 : 0;
+}
+
+static int snd_us16x08_master_info(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->count = 1;
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->value.integer.max = SND_US16X08_KCMAX(kcontrol);
+	uinfo->value.integer.min = SND_US16X08_KCMIN(kcontrol);
+	uinfo->value.integer.step = SND_US16X08_KCSTEP(kcontrol);
+	return 0;
+}
+
+static int snd_us16x08_master_get(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	struct usb_mixer_elem_info *elem = kcontrol->private_data;
+	int index = ucontrol->id.index;
+
+	ucontrol->value.integer.value[0] = elem->cache_val[index];
+
+	return 0;
+}
+
+static int snd_us16x08_master_put(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	struct usb_mixer_elem_info *elem = kcontrol->private_data;
+	struct snd_usb_audio *chip = elem->head.mixer->chip;
+	char buf[sizeof(mix_msg_out)];
+	int val, err;
+	int index = ucontrol->id.index;
+
+	/* new control value incl. bias*/
+	val = ucontrol->value.integer.value[0];
+
+	/* sanity check */
+	if (val < SND_US16X08_KCMIN(kcontrol)
+		|| val > SND_US16X08_KCMAX(kcontrol))
+		return -EINVAL;
+
+	/* prepare the message buffer from template */
+	memcpy(buf, mix_msg_out, sizeof(mix_msg_out));
+
+	buf[8] = val - SND_US16X08_KCBIAS(kcontrol);
+	buf[6] = elem->head.id;
+
+	/* place channel selector in URB message */
+	buf[5] = index + 1;
+	err = snd_us16x08_send_urb(chip, buf, sizeof(mix_msg_out));
+
+	if (err > 0) {
+		elem->cached |= 1 << index;
+		elem->cache_val[index] = val;
+	} else {
+		usb_audio_dbg(chip, "Failed to set master, err:%d\n", err);
+	}
+
+	return err > 0 ? 1 : 0;
+}
+
+static int snd_us16x08_bus_put(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	struct usb_mixer_elem_info *elem = kcontrol->private_data;
+	struct snd_usb_audio *chip = elem->head.mixer->chip;
+	char buf[sizeof(mix_msg_out)];
+	int val, err = 0;
+
+	val = ucontrol->value.integer.value[0];
+
+	/* prepare the message buffer from template */
+	switch (elem->head.id) {
+	case SND_US16X08_ID_BYPASS:
+		memcpy(buf, bypass_msg_out, sizeof(bypass_msg_out));
+		buf[2] = val;
+		err = snd_us16x08_send_urb(chip, buf, sizeof(bypass_msg_out));
+		break;
+	case SND_US16X08_ID_BUSS_OUT:
+		memcpy(buf, bus_msg_out, sizeof(bus_msg_out));
+		buf[2] = val;
+		err = snd_us16x08_send_urb(chip, buf, sizeof(bus_msg_out));
+		break;
+	case SND_US16X08_ID_MUTE:
+		memcpy(buf, mix_msg_out, sizeof(mix_msg_out));
+		buf[8] = val;
+		buf[6] = elem->head.id;
+		buf[5] = 1;
+		err = snd_us16x08_send_urb(chip, buf, sizeof(mix_msg_out));
+		break;
+	}
+
+	if (err > 0) {
+		elem->cached |= 1;
+		elem->cache_val[0] = val;
+	} else {
+		usb_audio_dbg(chip, "Failed to set buss param, err:%d\n", err);
+	}
+
+	return err > 0 ? 1 : 0;
+}
+
+static int snd_us16x08_bus_get(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	struct usb_mixer_elem_info *elem = kcontrol->private_data;
+
+	switch (elem->head.id) {
+	case SND_US16X08_ID_BUSS_OUT:
+		ucontrol->value.integer.value[0] = elem->cache_val[0];
+		break;
+	case SND_US16X08_ID_BYPASS:
+		ucontrol->value.integer.value[0] = elem->cache_val[0];
+		break;
+	case SND_US16X08_ID_MUTE:
+		ucontrol->value.integer.value[0] = elem->cache_val[0];
+		break;
+	}
+
+	return 0;
+}
+
+/* gets a current mixer value from common store */
+static int snd_us16x08_channel_get(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	struct usb_mixer_elem_info *elem = kcontrol->private_data;
+	int index = ucontrol->id.index;
+
+	ucontrol->value.integer.value[0] = elem->cache_val[index];
+
+	return 0;
+}
+
+static int snd_us16x08_channel_put(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	struct usb_mixer_elem_info *elem = kcontrol->private_data;
+	struct snd_usb_audio *chip = elem->head.mixer->chip;
+	char buf[sizeof(mix_msg_in)];
+	int val, err;
+	int index = ucontrol->id.index;
+
+	val = ucontrol->value.integer.value[0];
+
+	/* sanity check */
+	if (val < SND_US16X08_KCMIN(kcontrol)
+		|| val > SND_US16X08_KCMAX(kcontrol))
+		return -EINVAL;
+
+	/* prepare URB message from template */
+	memcpy(buf, mix_msg_in, sizeof(mix_msg_in));
+
+	/* add the bias to the new value */
+	buf[8] = val - SND_US16X08_KCBIAS(kcontrol);
+	buf[6] = elem->head.id;
+	buf[5] = index + 1;
+
+	err = snd_us16x08_send_urb(chip, buf, sizeof(mix_msg_in));
+
+	if (err > 0) {
+		elem->cached |= 1 << index;
+		elem->cache_val[index] = val;
+	} else {
+		usb_audio_dbg(chip, "Failed to set channel, err:%d\n", err);
+	}
+
+	return err > 0 ? 1 : 0;
+}
+
+static int snd_us16x08_mix_info(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->count = 1;
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->value.integer.max = SND_US16X08_KCMAX(kcontrol);
+	uinfo->value.integer.min = SND_US16X08_KCMIN(kcontrol);
+	uinfo->value.integer.step = SND_US16X08_KCSTEP(kcontrol);
+	return 0;
+}
+
+static int snd_us16x08_comp_get(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	struct usb_mixer_elem_info *elem = kcontrol->private_data;
+	struct snd_us16x08_comp_store *store = elem->private_data;
+	int index = ucontrol->id.index;
+	int val_idx = COMP_STORE_IDX(elem->head.id);
+
+	ucontrol->value.integer.value[0] = store->val[val_idx][index];
+
+	return 0;
+}
+
+static int snd_us16x08_comp_put(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	struct usb_mixer_elem_info *elem = kcontrol->private_data;
+	struct snd_usb_audio *chip = elem->head.mixer->chip;
+	struct snd_us16x08_comp_store *store = elem->private_data;
+	int index = ucontrol->id.index;
+	char buf[sizeof(comp_msg)];
+	int val_idx, val;
+	int err;
+
+	val = ucontrol->value.integer.value[0];
+
+	/* sanity check */
+	if (val < SND_US16X08_KCMIN(kcontrol)
+		|| val > SND_US16X08_KCMAX(kcontrol))
+		return -EINVAL;
+
+	/* new control value incl. bias*/
+	val_idx = elem->head.id - SND_US16X08_ID_COMP_BASE;
+
+	store->val[val_idx][index] = ucontrol->value.integer.value[0];
+
+	/* prepare compressor URB message from template  */
+	memcpy(buf, comp_msg, sizeof(comp_msg));
+
+	/* place comp values in message buffer watch bias! */
+	buf[8] = store->val[
+		COMP_STORE_IDX(SND_US16X08_ID_COMP_THRESHOLD)][index]
+		- SND_US16X08_COMP_THRESHOLD_BIAS;
+	buf[11] = ratio_map[store->val[
+		COMP_STORE_IDX(SND_US16X08_ID_COMP_RATIO)][index]];
+	buf[14] = store->val[COMP_STORE_IDX(SND_US16X08_ID_COMP_ATTACK)][index]
+		+ SND_US16X08_COMP_ATTACK_BIAS;
+	buf[17] = store->val[COMP_STORE_IDX(SND_US16X08_ID_COMP_RELEASE)][index]
+		+ SND_US16X08_COMP_RELEASE_BIAS;
+	buf[20] = store->val[COMP_STORE_IDX(SND_US16X08_ID_COMP_GAIN)][index];
+	buf[26] = store->val[COMP_STORE_IDX(SND_US16X08_ID_COMP_SWITCH)][index];
+
+	/* place channel selector in message buffer */
+	buf[5] = index + 1;
+
+	err = snd_us16x08_send_urb(chip, buf, sizeof(comp_msg));
+
+	if (err > 0) {
+		elem->cached |= 1 << index;
+		elem->cache_val[index] = val;
+	} else {
+		usb_audio_dbg(chip, "Failed to set compressor, err:%d\n", err);
+	}
+
+	return 1;
+}
+
+static int snd_us16x08_eqswitch_get(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	int val;
+	struct usb_mixer_elem_info *elem = kcontrol->private_data;
+	struct snd_us16x08_eq_store *store = elem->private_data;
+	int index = ucontrol->id.index;
+
+	/* get low switch from cache is enough, cause all bands are together */
+	val = store->val[EQ_STORE_BAND_IDX(elem->head.id)]
+		[EQ_STORE_PARAM_IDX(elem->head.id)][index];
+	ucontrol->value.integer.value[0] = val;
+
+	return 0;
+}
+
+static int snd_us16x08_eqswitch_put(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	struct usb_mixer_elem_info *elem = kcontrol->private_data;
+	struct snd_usb_audio *chip = elem->head.mixer->chip;
+	struct snd_us16x08_eq_store *store = elem->private_data;
+	int index = ucontrol->id.index;
+	char buf[sizeof(eqs_msq)];
+	int val, err = 0;
+	int b_idx;
+
+	/* new control value incl. bias*/
+	val = ucontrol->value.integer.value[0] + SND_US16X08_KCBIAS(kcontrol);
+
+	/* prepare URB message from EQ template */
+	memcpy(buf, eqs_msq, sizeof(eqs_msq));
+
+	/* place channel index in URB message */
+	buf[5] = index + 1;
+	for (b_idx = 0; b_idx < SND_US16X08_ID_EQ_BAND_COUNT; b_idx++) {
+		/* all four EQ bands have to be enabled/disabled in once */
+		buf[20] = val;
+		buf[17] = store->val[b_idx][2][index];
+		buf[14] = store->val[b_idx][1][index];
+		buf[11] = store->val[b_idx][0][index];
+		buf[8] = b_idx + 1;
+		err = snd_us16x08_send_urb(chip, buf, sizeof(eqs_msq));
+		if (err < 0)
+			break;
+		store->val[b_idx][3][index] = val;
+		msleep(15);
+	}
+
+	if (err > 0) {
+		elem->cached |= 1 << index;
+		elem->cache_val[index] = val;
+	} else {
+		usb_audio_dbg(chip, "Failed to set eq switch, err:%d\n", err);
+	}
+
+	return 1;
+}
+
+static int snd_us16x08_eq_get(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	int val;
+	struct usb_mixer_elem_info *elem = kcontrol->private_data;
+	struct snd_us16x08_eq_store *store = elem->private_data;
+	int index = ucontrol->id.index;
+	int b_idx = EQ_STORE_BAND_IDX(elem->head.id) - 1;
+	int p_idx = EQ_STORE_PARAM_IDX(elem->head.id);
+
+	val = store->val[b_idx][p_idx][index];
+
+	ucontrol->value.integer.value[0] = val;
+
+	return 0;
+}
+
+static int snd_us16x08_eq_put(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	struct usb_mixer_elem_info *elem = kcontrol->private_data;
+	struct snd_usb_audio *chip = elem->head.mixer->chip;
+	struct snd_us16x08_eq_store *store = elem->private_data;
+	int index = ucontrol->id.index;
+	char buf[sizeof(eqs_msq)];
+	int val, err;
+	int b_idx = EQ_STORE_BAND_IDX(elem->head.id) - 1;
+	int p_idx = EQ_STORE_PARAM_IDX(elem->head.id);
+
+	val = ucontrol->value.integer.value[0];
+
+	/* sanity check */
+	if (val < SND_US16X08_KCMIN(kcontrol)
+		|| val > SND_US16X08_KCMAX(kcontrol))
+		return -EINVAL;
+
+	/* copy URB buffer from EQ template */
+	memcpy(buf, eqs_msq, sizeof(eqs_msq));
+
+	store->val[b_idx][p_idx][index] = val;
+	buf[20] = store->val[b_idx][3][index];
+	buf[17] = store->val[b_idx][2][index];
+	buf[14] = store->val[b_idx][1][index];
+	buf[11] = store->val[b_idx][0][index];
+
+	/* place channel index in URB buffer */
+	buf[5] = index + 1;
+
+	/* place EQ band in URB buffer */
+	buf[8] = b_idx + 1;
+
+	err = snd_us16x08_send_urb(chip, buf, sizeof(eqs_msq));
+
+	if (err > 0) {
+		/* store new value in EQ band cache */
+		elem->cached |= 1 << index;
+		elem->cache_val[index] = val;
+	} else {
+		usb_audio_dbg(chip, "Failed to set eq param, err:%d\n", err);
+	}
+
+	return 1;
+}
+
+static int snd_us16x08_meter_info(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->count = 1;
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->value.integer.max = 0x7FFF;
+	uinfo->value.integer.min = 0;
+
+	return 0;
+}
+
+/* calculate compressor index for reduction level request */
+static int snd_get_meter_comp_index(struct snd_us16x08_meter_store *store)
+{
+	int ret;
+
+	/* any channel active */
+	if (store->comp_active_index) {
+		/* check for stereo link */
+		if (store->comp_active_index & 0x20) {
+			/* reset comp_index to left channel*/
+			if (store->comp_index -
+				store->comp_active_index > 1)
+				store->comp_index =
+				store->comp_active_index;
+
+			ret = store->comp_index++ & 0x1F;
+		} else {
+			/* no stereo link */
+			ret = store->comp_active_index;
+		}
+	} else {
+		/* skip channels with no compressor active */
+		while (!store->comp_store->val[
+			COMP_STORE_IDX(SND_US16X08_ID_COMP_SWITCH)]
+			[store->comp_index - 1]
+			&& store->comp_index <= SND_US16X08_MAX_CHANNELS) {
+			store->comp_index++;
+		}
+		ret = store->comp_index++;
+		if (store->comp_index > SND_US16X08_MAX_CHANNELS)
+			store->comp_index = 1;
+	}
+	return ret;
+}
+
+/* retrieve the meter level values from URB message */
+static void get_meter_levels_from_urb(int s,
+	struct snd_us16x08_meter_store *store,
+	u8 *meter_urb)
+{
+	int val = MUC2(meter_urb, s) + (MUC3(meter_urb, s) << 8);
+
+	if (MUA0(meter_urb, s) == 0x61 && MUA1(meter_urb, s) == 0x02 &&
+		MUA2(meter_urb, s) == 0x04 && MUB0(meter_urb, s) == 0x62) {
+		if (MUC0(meter_urb, s) == 0x72)
+			store->meter_level[MUB2(meter_urb, s) - 1] = val;
+		if (MUC0(meter_urb, s) == 0xb2)
+			store->comp_level[MUB2(meter_urb, s) - 1] = val;
+	}
+	if (MUA0(meter_urb, s) == 0x61 && MUA1(meter_urb, s) == 0x02 &&
+		MUA2(meter_urb, s) == 0x02 && MUB0(meter_urb, s) == 0x62)
+		store->master_level[MUB2(meter_urb, s) - 1] = val;
+}
+
+/* Function to retrieve current meter values from the device.
+ *
+ * The device needs to be polled for meter values with an initial
+ * requests. It will return with a sequence of different meter value
+ * packages. The first request (case 0:) initiate this meter response sequence.
+ * After the third response, an additional request can be placed,
+ * to retrieve compressor reduction level value for given channel. This round
+ * trip channel selector will skip all inactive compressors.
+ * A mixer can interrupt this round-trip by selecting one ore two (stereo-link)
+ * specific channels.
+ */
+static int snd_us16x08_meter_get(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	int i, set;
+	struct usb_mixer_elem_info *elem = kcontrol->private_data;
+	struct snd_usb_audio *chip = elem->head.mixer->chip;
+	struct snd_us16x08_meter_store *store = elem->private_data;
+	u8 meter_urb[64];
+
+	switch (kcontrol->private_value) {
+	case 0: {
+		char tmp[sizeof(mix_init_msg1)];
+
+		memcpy(tmp, mix_init_msg1, sizeof(mix_init_msg1));
+		snd_us16x08_send_urb(chip, tmp, 4);
+		snd_us16x08_recv_urb(chip, meter_urb,
+			sizeof(meter_urb));
+		kcontrol->private_value++;
+		break;
+	}
+	case 1:
+		snd_us16x08_recv_urb(chip, meter_urb,
+			sizeof(meter_urb));
+		kcontrol->private_value++;
+		break;
+	case 2:
+		snd_us16x08_recv_urb(chip, meter_urb,
+			sizeof(meter_urb));
+		kcontrol->private_value++;
+		break;
+	case 3: {
+		char tmp[sizeof(mix_init_msg2)];
+
+		memcpy(tmp, mix_init_msg2, sizeof(mix_init_msg2));
+		tmp[2] = snd_get_meter_comp_index(store);
+		snd_us16x08_send_urb(chip, tmp, 10);
+		snd_us16x08_recv_urb(chip, meter_urb,
+			sizeof(meter_urb));
+		kcontrol->private_value = 0;
+		break;
+	}
+	}
+
+	for (set = 0; set < 6; set++)
+		get_meter_levels_from_urb(set, store, meter_urb);
+
+	for (i = 0; i < SND_US16X08_MAX_CHANNELS; i++) {
+		ucontrol->value.integer.value[i] =
+			store ? store->meter_level[i] : 0;
+	}
+
+	ucontrol->value.integer.value[i++] = store ? store->master_level[0] : 0;
+	ucontrol->value.integer.value[i++] = store ? store->master_level[1] : 0;
+
+	for (i = 2; i < SND_US16X08_MAX_CHANNELS + 2; i++)
+		ucontrol->value.integer.value[i + SND_US16X08_MAX_CHANNELS] =
+		store ? store->comp_level[i - 2] : 0;
+
+	return 1;
+}
+
+static int snd_us16x08_meter_put(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	struct usb_mixer_elem_info *elem = kcontrol->private_data;
+	struct snd_us16x08_meter_store *store = elem->private_data;
+	int val;
+
+	val = ucontrol->value.integer.value[0];
+
+	/* sanity check */
+	if (val < 0 || val >= SND_US16X08_MAX_CHANNELS)
+		return -EINVAL;
+
+	store->comp_active_index = val;
+	store->comp_index = val;
+
+	return 1;
+}
+
+static struct snd_kcontrol_new snd_us16x08_ch_boolean_ctl = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+	.count = 16,
+	.info = snd_us16x08_switch_info,
+	.get = snd_us16x08_channel_get,
+	.put = snd_us16x08_channel_put,
+	.private_value = SND_US16X08_KCSET(SND_US16X08_NO_BIAS, 1, 0, 1)
+};
+
+static struct snd_kcontrol_new snd_us16x08_ch_int_ctl = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+	.count = 16,
+	.info = snd_us16x08_mix_info,
+	.get = snd_us16x08_channel_get,
+	.put = snd_us16x08_channel_put,
+	.private_value = SND_US16X08_KCSET(SND_US16X08_FADER_BIAS, 1, 0, 133)
+};
+
+static struct snd_kcontrol_new snd_us16x08_pan_int_ctl = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+	.count = 16,
+	.info = snd_us16x08_mix_info,
+	.get = snd_us16x08_channel_get,
+	.put = snd_us16x08_channel_put,
+	.private_value = SND_US16X08_KCSET(SND_US16X08_FADER_BIAS, 1, 0, 255)
+};
+
+static struct snd_kcontrol_new snd_us16x08_master_ctl = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+	.count = 1,
+	.info = snd_us16x08_master_info,
+	.get = snd_us16x08_master_get,
+	.put = snd_us16x08_master_put,
+	.private_value = SND_US16X08_KCSET(SND_US16X08_FADER_BIAS, 1, 0, 133)
+};
+
+static struct snd_kcontrol_new snd_us16x08_route_ctl = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+	.count = 8,
+	.info = snd_us16x08_route_info,
+	.get = snd_us16x08_route_get,
+	.put = snd_us16x08_route_put,
+	.private_value = SND_US16X08_KCSET(SND_US16X08_NO_BIAS, 1, 0, 9)
+};
+
+static struct snd_kcontrol_new snd_us16x08_bus_ctl = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+	.count = 1,
+	.info = snd_us16x08_switch_info,
+	.get = snd_us16x08_bus_get,
+	.put = snd_us16x08_bus_put,
+	.private_value = SND_US16X08_KCSET(SND_US16X08_NO_BIAS, 1, 0, 1)
+};
+
+static struct snd_kcontrol_new snd_us16x08_compswitch_ctl = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+	.count = 16,
+	.info = snd_us16x08_switch_info,
+	.get = snd_us16x08_comp_get,
+	.put = snd_us16x08_comp_put,
+	.private_value = SND_US16X08_KCSET(SND_US16X08_NO_BIAS, 1, 0, 1)
+};
+
+static struct snd_kcontrol_new snd_us16x08_comp_threshold_ctl = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+	.count = 16,
+	.info = snd_us16x08_mix_info,
+	.get = snd_us16x08_comp_get,
+	.put = snd_us16x08_comp_put,
+	.private_value = SND_US16X08_KCSET(SND_US16X08_COMP_THRESHOLD_BIAS, 1,
+	0, 0x20)
+};
+
+static struct snd_kcontrol_new snd_us16x08_comp_ratio_ctl = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+	.count = 16,
+	.info = snd_us16x08_mix_info,
+	.get = snd_us16x08_comp_get,
+	.put = snd_us16x08_comp_put,
+	.private_value = SND_US16X08_KCSET(SND_US16X08_NO_BIAS, 1, 0,
+	sizeof(ratio_map) - 1), /*max*/
+};
+
+static struct snd_kcontrol_new snd_us16x08_comp_gain_ctl = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+	.count = 16,
+	.info = snd_us16x08_mix_info,
+	.get = snd_us16x08_comp_get,
+	.put = snd_us16x08_comp_put,
+	.private_value = SND_US16X08_KCSET(SND_US16X08_NO_BIAS, 1, 0, 0x14)
+};
+
+static struct snd_kcontrol_new snd_us16x08_comp_attack_ctl = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+	.count = 16,
+	.info = snd_us16x08_mix_info,
+	.get = snd_us16x08_comp_get,
+	.put = snd_us16x08_comp_put,
+	.private_value =
+	SND_US16X08_KCSET(SND_US16X08_COMP_ATTACK_BIAS, 1, 0, 0xc6),
+};
+
+static struct snd_kcontrol_new snd_us16x08_comp_release_ctl = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+	.count = 16,
+	.info = snd_us16x08_mix_info,
+	.get = snd_us16x08_comp_get,
+	.put = snd_us16x08_comp_put,
+	.private_value =
+	SND_US16X08_KCSET(SND_US16X08_COMP_RELEASE_BIAS, 1, 0, 0x63),
+};
+
+static struct snd_kcontrol_new snd_us16x08_eq_gain_ctl = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+	.count = 16,
+	.info = snd_us16x08_mix_info,
+	.get = snd_us16x08_eq_get,
+	.put = snd_us16x08_eq_put,
+	.private_value = SND_US16X08_KCSET(SND_US16X08_NO_BIAS, 1, 0, 24),
+};
+
+static struct snd_kcontrol_new snd_us16x08_eq_low_freq_ctl = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+	.count = 16,
+	.info = snd_us16x08_mix_info,
+	.get = snd_us16x08_eq_get,
+	.put = snd_us16x08_eq_put,
+	.private_value = SND_US16X08_KCSET(SND_US16X08_NO_BIAS, 1, 0, 0x1F),
+};
+
+static struct snd_kcontrol_new snd_us16x08_eq_mid_freq_ctl = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+	.count = 16,
+	.info = snd_us16x08_mix_info,
+	.get = snd_us16x08_eq_get,
+	.put = snd_us16x08_eq_put,
+	.private_value = SND_US16X08_KCSET(SND_US16X08_NO_BIAS, 1, 0, 0x3F)
+};
+
+static struct snd_kcontrol_new snd_us16x08_eq_mid_width_ctl = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+	.count = 16,
+	.info = snd_us16x08_mix_info,
+	.get = snd_us16x08_eq_get,
+	.put = snd_us16x08_eq_put,
+	.private_value = SND_US16X08_KCSET(SND_US16X08_NO_BIAS, 1, 0, 0x06)
+};
+
+static struct snd_kcontrol_new snd_us16x08_eq_high_freq_ctl = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+	.count = 16,
+	.info = snd_us16x08_mix_info,
+	.get = snd_us16x08_eq_get,
+	.put = snd_us16x08_eq_put,
+	.private_value =
+	SND_US16X08_KCSET(SND_US16X08_EQ_HIGHFREQ_BIAS, 1, 0, 0x1F)
+};
+
+static struct snd_kcontrol_new snd_us16x08_eq_switch_ctl = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+	.count = 16,
+	.info = snd_us16x08_switch_info,
+	.get = snd_us16x08_eqswitch_get,
+	.put = snd_us16x08_eqswitch_put,
+	.private_value = SND_US16X08_KCSET(SND_US16X08_NO_BIAS, 1, 0, 1)
+};
+
+static struct snd_kcontrol_new snd_us16x08_meter_ctl = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+	.count = 1,
+	.info = snd_us16x08_meter_info,
+	.get = snd_us16x08_meter_get,
+	.put = snd_us16x08_meter_put
+};
+
+/* control store preparation */
+
+/* setup compressor store and assign default value */
+static struct snd_us16x08_comp_store *snd_us16x08_create_comp_store(void)
+{
+	int i;
+	struct snd_us16x08_comp_store *tmp;
+
+	tmp = kmalloc(sizeof(*tmp), GFP_KERNEL);
+	if (!tmp)
+		return NULL;
+
+	for (i = 0; i < SND_US16X08_MAX_CHANNELS; i++) {
+		tmp->val[COMP_STORE_IDX(SND_US16X08_ID_COMP_THRESHOLD)][i]
+			= 0x20;
+		tmp->val[COMP_STORE_IDX(SND_US16X08_ID_COMP_RATIO)][i] = 0x00;
+		tmp->val[COMP_STORE_IDX(SND_US16X08_ID_COMP_GAIN)][i] = 0x00;
+		tmp->val[COMP_STORE_IDX(SND_US16X08_ID_COMP_SWITCH)][i] = 0x00;
+		tmp->val[COMP_STORE_IDX(SND_US16X08_ID_COMP_ATTACK)][i] = 0x00;
+		tmp->val[COMP_STORE_IDX(SND_US16X08_ID_COMP_RELEASE)][i] = 0x00;
+	}
+	return tmp;
+}
+
+/* setup EQ store and assign default values */
+static struct snd_us16x08_eq_store *snd_us16x08_create_eq_store(void)
+{
+	int i, b_idx;
+	struct snd_us16x08_eq_store *tmp;
+
+	tmp = kmalloc(sizeof(*tmp), GFP_KERNEL);
+	if (!tmp)
+		return NULL;
+
+	for (i = 0; i < SND_US16X08_MAX_CHANNELS; i++) {
+		for (b_idx = 0; b_idx < SND_US16X08_ID_EQ_BAND_COUNT; b_idx++) {
+			tmp->val[b_idx][0][i] = 0x0c;
+			tmp->val[b_idx][3][i] = 0x00;
+			switch (b_idx) {
+			case 0: /* EQ Low */
+				tmp->val[b_idx][1][i] = 0x05;
+				tmp->val[b_idx][2][i] = 0xff;
+				break;
+			case 1: /* EQ Mid low */
+				tmp->val[b_idx][1][i] = 0x0e;
+				tmp->val[b_idx][2][i] = 0x02;
+				break;
+			case 2: /* EQ Mid High */
+				tmp->val[b_idx][1][i] = 0x1b;
+				tmp->val[b_idx][2][i] = 0x02;
+				break;
+			case 3: /* EQ High */
+				tmp->val[b_idx][1][i] = 0x2f
+					- SND_US16X08_EQ_HIGHFREQ_BIAS;
+				tmp->val[b_idx][2][i] = 0xff;
+				break;
+			}
+		}
+	}
+	return tmp;
+}
+
+static struct snd_us16x08_meter_store *snd_us16x08_create_meter_store(void)
+{
+	struct snd_us16x08_meter_store *tmp;
+
+	tmp = kzalloc(sizeof(*tmp), GFP_KERNEL);
+	if (!tmp)
+		return NULL;
+	tmp->comp_index = 1;
+	tmp->comp_active_index = 0;
+	return tmp;
+}
+
+/* release elem->private_free as well; called only once for each *_store */
+static void elem_private_free(struct snd_kcontrol *kctl)
+{
+	struct usb_mixer_elem_info *elem = kctl->private_data;
+
+	if (elem)
+		kfree(elem->private_data);
+	kfree(elem);
+	kctl->private_data = NULL;
+}
+
+static int add_new_ctl(struct usb_mixer_interface *mixer,
+	const struct snd_kcontrol_new *ncontrol,
+	int index, int val_type, int channels,
+	const char *name, void *opt,
+	bool do_private_free,
+	struct usb_mixer_elem_info **elem_ret)
+{
+	struct snd_kcontrol *kctl;
+	struct usb_mixer_elem_info *elem;
+	int err;
+
+	usb_audio_dbg(mixer->chip, "us16x08 add mixer %s\n", name);
+
+	elem = kzalloc(sizeof(*elem), GFP_KERNEL);
+	if (!elem)
+		return -ENOMEM;
+
+	elem->head.mixer = mixer;
+	elem->head.resume = NULL;
+	elem->control = 0;
+	elem->idx_off = 0;
+	elem->head.id = index;
+	elem->val_type = val_type;
+	elem->channels = channels;
+	elem->private_data = opt;
+
+	kctl = snd_ctl_new1(ncontrol, elem);
+	if (!kctl) {
+		kfree(elem);
+		return -ENOMEM;
+	}
+
+	if (do_private_free)
+		kctl->private_free = elem_private_free;
+	else
+		kctl->private_free = snd_usb_mixer_elem_free;
+
+	strlcpy(kctl->id.name, name, sizeof(kctl->id.name));
+
+	err = snd_usb_mixer_add_control(&elem->head, kctl);
+	if (err < 0)
+		return err;
+
+	if (elem_ret)
+		*elem_ret = elem;
+
+	return 0;
+}
+
+/* table of EQ controls */
+static const struct snd_us16x08_control_params eq_controls[] = {
+	{ /* EQ switch */
+		.kcontrol_new = &snd_us16x08_eq_switch_ctl,
+		.control_id = SND_US16X08_ID_EQENABLE,
+		.type = USB_MIXER_BOOLEAN,
+		.num_channels = 16,
+		.name = "EQ Switch",
+	},
+	{ /* EQ low gain */
+		.kcontrol_new = &snd_us16x08_eq_gain_ctl,
+		.control_id = SND_US16X08_ID_EQLOWLEVEL,
+		.type = USB_MIXER_U8,
+		.num_channels = 16,
+		.name = "EQ Low Volume",
+	},
+	{ /* EQ low freq */
+		.kcontrol_new = &snd_us16x08_eq_low_freq_ctl,
+		.control_id = SND_US16X08_ID_EQLOWFREQ,
+		.type = USB_MIXER_U8,
+		.num_channels = 16,
+		.name = "EQ Low Frequence",
+	},
+	{ /* EQ mid low gain */
+		.kcontrol_new = &snd_us16x08_eq_gain_ctl,
+		.control_id = SND_US16X08_ID_EQLOWMIDLEVEL,
+		.type = USB_MIXER_U8,
+		.num_channels = 16,
+		.name = "EQ MidLow Volume",
+	},
+	{ /* EQ mid low freq */
+		.kcontrol_new = &snd_us16x08_eq_mid_freq_ctl,
+		.control_id = SND_US16X08_ID_EQLOWMIDFREQ,
+		.type = USB_MIXER_U8,
+		.num_channels = 16,
+		.name = "EQ MidLow Frequence",
+	},
+	{ /* EQ mid low Q */
+		.kcontrol_new = &snd_us16x08_eq_mid_width_ctl,
+		.control_id = SND_US16X08_ID_EQLOWMIDWIDTH,
+		.type = USB_MIXER_U8,
+		.num_channels = 16,
+		.name = "EQ MidLow Q",
+	},
+	{ /* EQ mid high gain */
+		.kcontrol_new = &snd_us16x08_eq_gain_ctl,
+		.control_id = SND_US16X08_ID_EQHIGHMIDLEVEL,
+		.type = USB_MIXER_U8,
+		.num_channels = 16,
+		.name = "EQ MidHigh Volume",
+	},
+	{ /* EQ mid high freq */
+		.kcontrol_new = &snd_us16x08_eq_mid_freq_ctl,
+		.control_id = SND_US16X08_ID_EQHIGHMIDFREQ,
+		.type = USB_MIXER_U8,
+		.num_channels = 16,
+		.name = "EQ MidHigh Frequence",
+	},
+	{ /* EQ mid high Q */
+		.kcontrol_new = &snd_us16x08_eq_mid_width_ctl,
+		.control_id = SND_US16X08_ID_EQHIGHMIDWIDTH,
+		.type = USB_MIXER_U8,
+		.num_channels = 16,
+		.name = "EQ MidHigh Q",
+	},
+	{ /* EQ high gain */
+		.kcontrol_new = &snd_us16x08_eq_gain_ctl,
+		.control_id = SND_US16X08_ID_EQHIGHLEVEL,
+		.type = USB_MIXER_U8,
+		.num_channels = 16,
+		.name = "EQ High Volume",
+	},
+	{ /* EQ low freq */
+		.kcontrol_new = &snd_us16x08_eq_high_freq_ctl,
+		.control_id = SND_US16X08_ID_EQHIGHFREQ,
+		.type = USB_MIXER_U8,
+		.num_channels = 16,
+		.name = "EQ High Frequence",
+	},
+};
+
+/* table of compressor controls */
+static const struct snd_us16x08_control_params comp_controls[] = {
+	{ /* Comp enable */
+		.kcontrol_new = &snd_us16x08_compswitch_ctl,
+		.control_id = SND_US16X08_ID_COMP_SWITCH,
+		.type = USB_MIXER_BOOLEAN,
+		.num_channels = 16,
+		.name = "Compressor Switch",
+	},
+	{ /* Comp threshold */
+		.kcontrol_new = &snd_us16x08_comp_threshold_ctl,
+		.control_id = SND_US16X08_ID_COMP_THRESHOLD,
+		.type = USB_MIXER_U8,
+		.num_channels = 16,
+		.name = "Compressor Threshold Volume",
+	},
+	{ /* Comp ratio */
+		.kcontrol_new = &snd_us16x08_comp_ratio_ctl,
+		.control_id = SND_US16X08_ID_COMP_RATIO,
+		.type = USB_MIXER_U8,
+		.num_channels = 16,
+		.name = "Compressor Ratio",
+	},
+	{ /* Comp attack */
+		.kcontrol_new = &snd_us16x08_comp_attack_ctl,
+		.control_id = SND_US16X08_ID_COMP_ATTACK,
+		.type = USB_MIXER_U8,
+		.num_channels = 16,
+		.name = "Compressor Attack",
+	},
+	{ /* Comp release */
+		.kcontrol_new = &snd_us16x08_comp_release_ctl,
+		.control_id = SND_US16X08_ID_COMP_RELEASE,
+		.type = USB_MIXER_U8,
+		.num_channels = 16,
+		.name = "Compressor Release",
+	},
+	{ /* Comp gain */
+		.kcontrol_new = &snd_us16x08_comp_gain_ctl,
+		.control_id = SND_US16X08_ID_COMP_GAIN,
+		.type = USB_MIXER_U8,
+		.num_channels = 16,
+		.name = "Compressor Volume",
+	},
+};
+
+/* table of channel controls */
+static const struct snd_us16x08_control_params channel_controls[] = {
+	{ /* Phase */
+		.kcontrol_new = &snd_us16x08_ch_boolean_ctl,
+		.control_id = SND_US16X08_ID_PHASE,
+		.type = USB_MIXER_BOOLEAN,
+		.num_channels = 16,
+		.name = "Phase Switch",
+		.default_val = 0
+	},
+	{ /* Fader */
+		.kcontrol_new = &snd_us16x08_ch_int_ctl,
+		.control_id = SND_US16X08_ID_FADER,
+		.type = USB_MIXER_U8,
+		.num_channels = 16,
+		.name = "Line Volume",
+		.default_val = 127
+	},
+	{ /* Mute */
+		.kcontrol_new = &snd_us16x08_ch_boolean_ctl,
+		.control_id = SND_US16X08_ID_MUTE,
+		.type = USB_MIXER_BOOLEAN,
+		.num_channels = 16,
+		.name = "Mute Switch",
+		.default_val = 0
+	},
+	{ /* Pan */
+		.kcontrol_new = &snd_us16x08_pan_int_ctl,
+		.control_id = SND_US16X08_ID_PAN,
+		.type = USB_MIXER_U16,
+		.num_channels = 16,
+		.name = "Pan Left-Right Volume",
+		.default_val = 127
+	},
+};
+
+/* table of master controls */
+static const struct snd_us16x08_control_params master_controls[] = {
+	{ /* Master */
+		.kcontrol_new = &snd_us16x08_master_ctl,
+		.control_id = SND_US16X08_ID_FADER,
+		.type = USB_MIXER_U8,
+		.num_channels = 16,
+		.name = "Master Volume",
+		.default_val = 127
+	},
+	{ /* Bypass */
+		.kcontrol_new = &snd_us16x08_bus_ctl,
+		.control_id = SND_US16X08_ID_BYPASS,
+		.type = USB_MIXER_BOOLEAN,
+		.num_channels = 16,
+		.name = "DSP Bypass Switch",
+		.default_val = 0
+	},
+	{ /* Buss out */
+		.kcontrol_new = &snd_us16x08_bus_ctl,
+		.control_id = SND_US16X08_ID_BUSS_OUT,
+		.type = USB_MIXER_BOOLEAN,
+		.num_channels = 16,
+		.name = "Buss Out Switch",
+		.default_val = 0
+	},
+	{ /* Master mute */
+		.kcontrol_new = &snd_us16x08_bus_ctl,
+		.control_id = SND_US16X08_ID_MUTE,
+		.type = USB_MIXER_BOOLEAN,
+		.num_channels = 16,
+		.name = "Master Mute Switch",
+		.default_val = 0
+	},
+
+};
+
+int snd_us16x08_controls_create(struct usb_mixer_interface *mixer)
+{
+	int i, j;
+	int err;
+	struct usb_mixer_elem_info *elem;
+	struct snd_us16x08_comp_store *comp_store;
+	struct snd_us16x08_meter_store *meter_store;
+	struct snd_us16x08_eq_store *eq_store;
+
+	/* just check for non-MIDI interface */
+	if (mixer->hostif->desc.bInterfaceNumber == 3) {
+
+		/* add routing control */
+		err = add_new_ctl(mixer, &snd_us16x08_route_ctl,
+			SND_US16X08_ID_ROUTE, USB_MIXER_U8, 8, "Line Out Route",
+			NULL, false, &elem);
+		if (err < 0) {
+			usb_audio_dbg(mixer->chip,
+				"Failed to create route control, err:%d\n",
+				err);
+			return err;
+		}
+		for (i = 0; i < 8; i++)
+			elem->cache_val[i] = i < 2 ? i : i + 2;
+		elem->cached = 0xff;
+
+		/* create compressor mixer elements */
+		comp_store = snd_us16x08_create_comp_store();
+		if (!comp_store)
+			return -ENOMEM;
+
+		/* add master controls */
+		for (i = 0; i < ARRAY_SIZE(master_controls); i++) {
+
+			err = add_new_ctl(mixer,
+				master_controls[i].kcontrol_new,
+				master_controls[i].control_id,
+				master_controls[i].type,
+				master_controls[i].num_channels,
+				master_controls[i].name,
+				comp_store,
+				i == 0, /* release comp_store only once */
+				&elem);
+			if (err < 0)
+				return err;
+			elem->cache_val[0] = master_controls[i].default_val;
+			elem->cached = 1;
+		}
+
+		/* add channel controls */
+		for (i = 0; i < ARRAY_SIZE(channel_controls); i++) {
+
+			err = add_new_ctl(mixer,
+				channel_controls[i].kcontrol_new,
+				channel_controls[i].control_id,
+				channel_controls[i].type,
+				channel_controls[i].num_channels,
+				channel_controls[i].name,
+				comp_store,
+				false, &elem);
+			if (err < 0)
+				return err;
+			for (j = 0; j < SND_US16X08_MAX_CHANNELS; j++) {
+				elem->cache_val[j] =
+					channel_controls[i].default_val;
+			}
+			elem->cached = 0xffff;
+		}
+
+		/* create eq store */
+		eq_store = snd_us16x08_create_eq_store();
+		if (!eq_store)
+			return -ENOMEM;
+
+		/* add EQ controls */
+		for (i = 0; i < ARRAY_SIZE(eq_controls); i++) {
+
+			err = add_new_ctl(mixer,
+				eq_controls[i].kcontrol_new,
+				eq_controls[i].control_id,
+				eq_controls[i].type,
+				eq_controls[i].num_channels,
+				eq_controls[i].name,
+				eq_store,
+				i == 0, /* release eq_store only once */
+				NULL);
+			if (err < 0)
+				return err;
+		}
+
+		/* add compressor controls */
+		for (i = 0; i < ARRAY_SIZE(comp_controls); i++) {
+
+			err = add_new_ctl(mixer,
+				comp_controls[i].kcontrol_new,
+				comp_controls[i].control_id,
+				comp_controls[i].type,
+				comp_controls[i].num_channels,
+				comp_controls[i].name,
+				comp_store,
+				false, NULL);
+			if (err < 0)
+				return err;
+		}
+
+		/* create meters store */
+		meter_store = snd_us16x08_create_meter_store();
+		if (!meter_store)
+			return -ENOMEM;
+
+		/* meter function 'get' must access to compressor store
+		 * so place a reference here
+		 */
+		meter_store->comp_store = comp_store;
+		err = add_new_ctl(mixer, &snd_us16x08_meter_ctl,
+			SND_US16X08_ID_METER, USB_MIXER_U16, 0, "Level Meter",
+			meter_store, true, NULL);
+		if (err < 0)
+			return err;
+	}
+
+	return 0;
+}
+
diff --git a/sound/usb/mixer_us16x08.h b/sound/usb/mixer_us16x08.h
new file mode 100644
index 0000000..56ff16c
--- /dev/null
+++ b/sound/usb/mixer_us16x08.h
@@ -0,0 +1,122 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __USB_MIXER_US16X08_H
+#define __USB_MIXER_US16X08_H
+
+#define SND_US16X08_MAX_CHANNELS 16
+
+/* define some bias, cause some alsa-mixers wont work with
+ * negative ranges or if mixer-min != 0
+ */
+#define SND_US16X08_NO_BIAS 0
+#define SND_US16X08_FADER_BIAS 127
+#define SND_US16X08_EQ_HIGHFREQ_BIAS 0x20
+#define SND_US16X08_COMP_THRESHOLD_BIAS 0x20
+#define SND_US16X08_COMP_ATTACK_BIAS 2
+#define SND_US16X08_COMP_RELEASE_BIAS 1
+
+/* get macro for components of kcontrol private_value */
+#define SND_US16X08_KCBIAS(x) (((x)->private_value >> 24) & 0xff)
+#define SND_US16X08_KCSTEP(x) (((x)->private_value >> 16) & 0xff)
+#define SND_US16X08_KCMIN(x) (((x)->private_value >> 8) & 0xff)
+#define SND_US16X08_KCMAX(x) (((x)->private_value >> 0) & 0xff)
+/* set macro for kcontrol private_value */
+#define SND_US16X08_KCSET(bias, step, min, max)  \
+	(((bias) << 24) | ((step) << 16) | ((min) << 8) | (max))
+
+/* the URB request/type to control Tascam mixers */
+#define SND_US16X08_URB_REQUEST 0x1D
+#define SND_US16X08_URB_REQUESTTYPE 0x40
+
+/* the URB params to retrieve meter ranges */
+#define SND_US16X08_URB_METER_REQUEST       0x1e
+#define SND_US16X08_URB_METER_REQUESTTYPE   0xc0
+
+#define MUA0(x, y) ((x)[(y) * 10 + 4])
+#define MUA1(x, y) ((x)[(y) * 10 + 5])
+#define MUA2(x, y) ((x)[(y) * 10 + 6])
+#define MUB0(x, y) ((x)[(y) * 10 + 7])
+#define MUB1(x, y) ((x)[(y) * 10 + 8])
+#define MUB2(x, y) ((x)[(y) * 10 + 9])
+#define MUC0(x, y) ((x)[(y) * 10 + 10])
+#define MUC1(x, y) ((x)[(y) * 10 + 11])
+#define MUC2(x, y) ((x)[(y) * 10 + 12])
+#define MUC3(x, y) ((x)[(y) * 10 + 13])
+
+/* Common Channel control IDs */
+#define SND_US16X08_ID_BYPASS 0x45
+#define SND_US16X08_ID_BUSS_OUT 0x44
+#define SND_US16X08_ID_PHASE 0x85
+#define SND_US16X08_ID_MUTE 0x83
+#define SND_US16X08_ID_FADER 0x81
+#define SND_US16X08_ID_PAN 0x82
+#define SND_US16X08_ID_METER 0xB1
+
+#define SND_US16X08_ID_EQ_BAND_COUNT 4
+#define SND_US16X08_ID_EQ_PARAM_COUNT 4
+
+/* EQ level IDs */
+#define SND_US16X08_ID_EQLOWLEVEL 0x01
+#define SND_US16X08_ID_EQLOWMIDLEVEL 0x02
+#define SND_US16X08_ID_EQHIGHMIDLEVEL 0x03
+#define SND_US16X08_ID_EQHIGHLEVEL 0x04
+
+/* EQ frequence IDs */
+#define SND_US16X08_ID_EQLOWFREQ 0x11
+#define SND_US16X08_ID_EQLOWMIDFREQ 0x12
+#define SND_US16X08_ID_EQHIGHMIDFREQ 0x13
+#define SND_US16X08_ID_EQHIGHFREQ 0x14
+
+/* EQ width IDs */
+#define SND_US16X08_ID_EQLOWMIDWIDTH 0x22
+#define SND_US16X08_ID_EQHIGHMIDWIDTH 0x23
+
+#define SND_US16X08_ID_EQENABLE 0x30
+
+#define EQ_STORE_BAND_IDX(x) ((x) & 0xf)
+#define EQ_STORE_PARAM_IDX(x) (((x) & 0xf0) >> 4)
+
+#define SND_US16X08_ID_ROUTE 0x00
+
+/* Compressor Ids */
+#define SND_US16X08_ID_COMP_BASE	0x32
+#define SND_US16X08_ID_COMP_THRESHOLD	SND_US16X08_ID_COMP_BASE
+#define SND_US16X08_ID_COMP_RATIO	(SND_US16X08_ID_COMP_BASE + 1)
+#define SND_US16X08_ID_COMP_ATTACK	(SND_US16X08_ID_COMP_BASE + 2)
+#define SND_US16X08_ID_COMP_RELEASE	(SND_US16X08_ID_COMP_BASE + 3)
+#define SND_US16X08_ID_COMP_GAIN	(SND_US16X08_ID_COMP_BASE + 4)
+#define SND_US16X08_ID_COMP_SWITCH	(SND_US16X08_ID_COMP_BASE + 5)
+#define SND_US16X08_ID_COMP_COUNT	6
+
+#define COMP_STORE_IDX(x) ((x) - SND_US16X08_ID_COMP_BASE)
+
+struct snd_us16x08_eq_store {
+	u8 val[SND_US16X08_ID_EQ_BAND_COUNT][SND_US16X08_ID_EQ_PARAM_COUNT]
+		[SND_US16X08_MAX_CHANNELS];
+};
+
+struct snd_us16x08_comp_store {
+	u8 val[SND_US16X08_ID_COMP_COUNT][SND_US16X08_MAX_CHANNELS];
+};
+
+struct snd_us16x08_meter_store {
+	int meter_level[SND_US16X08_MAX_CHANNELS];
+	int master_level[2]; /* level of meter for master output */
+	int comp_index; /* round trip channel selector */
+	int comp_active_index; /* channel select from user space mixer */
+	int comp_level[16]; /* compressor reduction level */
+	struct snd_us16x08_comp_store *comp_store;
+};
+
+struct snd_us16x08_control_params {
+	struct snd_kcontrol_new *kcontrol_new;
+	int control_id;
+	int type;
+	int num_channels;
+	const char *name;
+	int default_val;
+};
+
+#define snd_us16x08_switch_info snd_ctl_boolean_mono_info
+
+int snd_us16x08_controls_create(struct usb_mixer_interface *mixer);
+#endif /* __USB_MIXER_US16X08_H */
diff --git a/sound/usb/pcm.c b/sound/usb/pcm.c
new file mode 100644
index 0000000..3828471
--- /dev/null
+++ b/sound/usb/pcm.c
@@ -0,0 +1,1820 @@
+/*
+ *   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/bitrev.h>
+#include <linux/ratelimit.h>
+#include <linux/usb.h>
+#include <linux/usb/audio.h>
+#include <linux/usb/audio-v2.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+
+#include "usbaudio.h"
+#include "card.h"
+#include "quirks.h"
+#include "debug.h"
+#include "endpoint.h"
+#include "helper.h"
+#include "pcm.h"
+#include "clock.h"
+#include "power.h"
+
+#define SUBSTREAM_FLAG_DATA_EP_STARTED	0
+#define SUBSTREAM_FLAG_SYNC_EP_STARTED	1
+
+/* return the estimated delay based on USB frame counters */
+snd_pcm_uframes_t snd_usb_pcm_delay(struct snd_usb_substream *subs,
+				    unsigned int rate)
+{
+	int current_frame_number;
+	int frame_diff;
+	int est_delay;
+
+	if (!subs->last_delay)
+		return 0; /* short path */
+
+	current_frame_number = usb_get_current_frame_number(subs->dev);
+	/*
+	 * HCD implementations use different widths, use lower 8 bits.
+	 * The delay will be managed up to 256ms, which is more than
+	 * enough
+	 */
+	frame_diff = (current_frame_number - subs->last_frame_number) & 0xff;
+
+	/* Approximation based on number of samples per USB frame (ms),
+	   some truncation for 44.1 but the estimate is good enough */
+	est_delay =  frame_diff * rate / 1000;
+	if (subs->direction == SNDRV_PCM_STREAM_PLAYBACK)
+		est_delay = subs->last_delay - est_delay;
+	else
+		est_delay = subs->last_delay + est_delay;
+
+	if (est_delay < 0)
+		est_delay = 0;
+	return est_delay;
+}
+
+/*
+ * return the current pcm pointer.  just based on the hwptr_done value.
+ */
+static snd_pcm_uframes_t snd_usb_pcm_pointer(struct snd_pcm_substream *substream)
+{
+	struct snd_usb_substream *subs = substream->runtime->private_data;
+	unsigned int hwptr_done;
+
+	if (atomic_read(&subs->stream->chip->shutdown))
+		return SNDRV_PCM_POS_XRUN;
+	spin_lock(&subs->lock);
+	hwptr_done = subs->hwptr_done;
+	substream->runtime->delay = snd_usb_pcm_delay(subs,
+						substream->runtime->rate);
+	spin_unlock(&subs->lock);
+	return hwptr_done / (substream->runtime->frame_bits >> 3);
+}
+
+/*
+ * find a matching audio format
+ */
+static struct audioformat *find_format(struct snd_usb_substream *subs)
+{
+	struct audioformat *fp;
+	struct audioformat *found = NULL;
+	int cur_attr = 0, attr;
+
+	list_for_each_entry(fp, &subs->fmt_list, list) {
+		if (!(fp->formats & pcm_format_to_bits(subs->pcm_format)))
+			continue;
+		if (fp->channels != subs->channels)
+			continue;
+		if (subs->cur_rate < fp->rate_min ||
+		    subs->cur_rate > fp->rate_max)
+			continue;
+		if (! (fp->rates & SNDRV_PCM_RATE_CONTINUOUS)) {
+			unsigned int i;
+			for (i = 0; i < fp->nr_rates; i++)
+				if (fp->rate_table[i] == subs->cur_rate)
+					break;
+			if (i >= fp->nr_rates)
+				continue;
+		}
+		attr = fp->ep_attr & USB_ENDPOINT_SYNCTYPE;
+		if (! found) {
+			found = fp;
+			cur_attr = attr;
+			continue;
+		}
+		/* avoid async out and adaptive in if the other method
+		 * supports the same format.
+		 * this is a workaround for the case like
+		 * M-audio audiophile USB.
+		 */
+		if (attr != cur_attr) {
+			if ((attr == USB_ENDPOINT_SYNC_ASYNC &&
+			     subs->direction == SNDRV_PCM_STREAM_PLAYBACK) ||
+			    (attr == USB_ENDPOINT_SYNC_ADAPTIVE &&
+			     subs->direction == SNDRV_PCM_STREAM_CAPTURE))
+				continue;
+			if ((cur_attr == USB_ENDPOINT_SYNC_ASYNC &&
+			     subs->direction == SNDRV_PCM_STREAM_PLAYBACK) ||
+			    (cur_attr == USB_ENDPOINT_SYNC_ADAPTIVE &&
+			     subs->direction == SNDRV_PCM_STREAM_CAPTURE)) {
+				found = fp;
+				cur_attr = attr;
+				continue;
+			}
+		}
+		/* find the format with the largest max. packet size */
+		if (fp->maxpacksize > found->maxpacksize) {
+			found = fp;
+			cur_attr = attr;
+		}
+	}
+	return found;
+}
+
+static int init_pitch_v1(struct snd_usb_audio *chip, int iface,
+			 struct usb_host_interface *alts,
+			 struct audioformat *fmt)
+{
+	struct usb_device *dev = chip->dev;
+	unsigned int ep;
+	unsigned char data[1];
+	int err;
+
+	if (get_iface_desc(alts)->bNumEndpoints < 1)
+		return -EINVAL;
+	ep = get_endpoint(alts, 0)->bEndpointAddress;
+
+	data[0] = 1;
+	err = snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), UAC_SET_CUR,
+			      USB_TYPE_CLASS|USB_RECIP_ENDPOINT|USB_DIR_OUT,
+			      UAC_EP_CS_ATTR_PITCH_CONTROL << 8, ep,
+			      data, sizeof(data));
+	if (err < 0) {
+		usb_audio_err(chip, "%d:%d: cannot set enable PITCH\n",
+			      iface, ep);
+		return err;
+	}
+
+	return 0;
+}
+
+static int init_pitch_v2(struct snd_usb_audio *chip, int iface,
+			 struct usb_host_interface *alts,
+			 struct audioformat *fmt)
+{
+	struct usb_device *dev = chip->dev;
+	unsigned char data[1];
+	int err;
+
+	data[0] = 1;
+	err = snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), UAC2_CS_CUR,
+			      USB_TYPE_CLASS | USB_RECIP_ENDPOINT | USB_DIR_OUT,
+			      UAC2_EP_CS_PITCH << 8, 0,
+			      data, sizeof(data));
+	if (err < 0) {
+		usb_audio_err(chip, "%d:%d: cannot set enable PITCH (v2)\n",
+			      iface, fmt->altsetting);
+		return err;
+	}
+
+	return 0;
+}
+
+/*
+ * initialize the pitch control and sample rate
+ */
+int snd_usb_init_pitch(struct snd_usb_audio *chip, int iface,
+		       struct usb_host_interface *alts,
+		       struct audioformat *fmt)
+{
+	/* if endpoint doesn't have pitch control, bail out */
+	if (!(fmt->attributes & UAC_EP_CS_ATTR_PITCH_CONTROL))
+		return 0;
+
+	switch (fmt->protocol) {
+	case UAC_VERSION_1:
+	default:
+		return init_pitch_v1(chip, iface, alts, fmt);
+
+	case UAC_VERSION_2:
+		return init_pitch_v2(chip, iface, alts, fmt);
+	}
+}
+
+static int start_endpoints(struct snd_usb_substream *subs)
+{
+	int err;
+
+	if (!subs->data_endpoint)
+		return -EINVAL;
+
+	if (!test_and_set_bit(SUBSTREAM_FLAG_DATA_EP_STARTED, &subs->flags)) {
+		struct snd_usb_endpoint *ep = subs->data_endpoint;
+
+		dev_dbg(&subs->dev->dev, "Starting data EP @%p\n", ep);
+
+		ep->data_subs = subs;
+		err = snd_usb_endpoint_start(ep);
+		if (err < 0) {
+			clear_bit(SUBSTREAM_FLAG_DATA_EP_STARTED, &subs->flags);
+			return err;
+		}
+	}
+
+	if (subs->sync_endpoint &&
+	    !test_and_set_bit(SUBSTREAM_FLAG_SYNC_EP_STARTED, &subs->flags)) {
+		struct snd_usb_endpoint *ep = subs->sync_endpoint;
+
+		if (subs->data_endpoint->iface != subs->sync_endpoint->iface ||
+		    subs->data_endpoint->altsetting != subs->sync_endpoint->altsetting) {
+			err = usb_set_interface(subs->dev,
+						subs->sync_endpoint->iface,
+						subs->sync_endpoint->altsetting);
+			if (err < 0) {
+				clear_bit(SUBSTREAM_FLAG_SYNC_EP_STARTED, &subs->flags);
+				dev_err(&subs->dev->dev,
+					   "%d:%d: cannot set interface (%d)\n",
+					   subs->sync_endpoint->iface,
+					   subs->sync_endpoint->altsetting, err);
+				return -EIO;
+			}
+		}
+
+		dev_dbg(&subs->dev->dev, "Starting sync EP @%p\n", ep);
+
+		ep->sync_slave = subs->data_endpoint;
+		err = snd_usb_endpoint_start(ep);
+		if (err < 0) {
+			clear_bit(SUBSTREAM_FLAG_SYNC_EP_STARTED, &subs->flags);
+			return err;
+		}
+	}
+
+	return 0;
+}
+
+static void stop_endpoints(struct snd_usb_substream *subs, bool wait)
+{
+	if (test_and_clear_bit(SUBSTREAM_FLAG_SYNC_EP_STARTED, &subs->flags))
+		snd_usb_endpoint_stop(subs->sync_endpoint);
+
+	if (test_and_clear_bit(SUBSTREAM_FLAG_DATA_EP_STARTED, &subs->flags))
+		snd_usb_endpoint_stop(subs->data_endpoint);
+
+	if (wait) {
+		snd_usb_endpoint_sync_pending_stop(subs->sync_endpoint);
+		snd_usb_endpoint_sync_pending_stop(subs->data_endpoint);
+	}
+}
+
+static int search_roland_implicit_fb(struct usb_device *dev, int ifnum,
+				     unsigned int altsetting,
+				     struct usb_host_interface **alts,
+				     unsigned int *ep)
+{
+	struct usb_interface *iface;
+	struct usb_interface_descriptor *altsd;
+	struct usb_endpoint_descriptor *epd;
+
+	iface = usb_ifnum_to_if(dev, ifnum);
+	if (!iface || iface->num_altsetting < altsetting + 1)
+		return -ENOENT;
+	*alts = &iface->altsetting[altsetting];
+	altsd = get_iface_desc(*alts);
+	if (altsd->bAlternateSetting != altsetting ||
+	    altsd->bInterfaceClass != USB_CLASS_VENDOR_SPEC ||
+	    (altsd->bInterfaceSubClass != 2 &&
+	     altsd->bInterfaceProtocol != 2   ) ||
+	    altsd->bNumEndpoints < 1)
+		return -ENOENT;
+	epd = get_endpoint(*alts, 0);
+	if (!usb_endpoint_is_isoc_in(epd) ||
+	    (epd->bmAttributes & USB_ENDPOINT_USAGE_MASK) !=
+					USB_ENDPOINT_USAGE_IMPLICIT_FB)
+		return -ENOENT;
+	*ep = epd->bEndpointAddress;
+	return 0;
+}
+
+static int set_sync_ep_implicit_fb_quirk(struct snd_usb_substream *subs,
+					 struct usb_device *dev,
+					 struct usb_interface_descriptor *altsd,
+					 unsigned int attr)
+{
+	struct usb_host_interface *alts;
+	struct usb_interface *iface;
+	unsigned int ep;
+	unsigned int ifnum;
+
+	/* Implicit feedback sync EPs consumers are always playback EPs */
+	if (subs->direction != SNDRV_PCM_STREAM_PLAYBACK)
+		return 0;
+
+	switch (subs->stream->chip->usb_id) {
+	case USB_ID(0x0763, 0x2030): /* M-Audio Fast Track C400 */
+	case USB_ID(0x0763, 0x2031): /* M-Audio Fast Track C600 */
+		ep = 0x81;
+		ifnum = 3;
+		goto add_sync_ep_from_ifnum;
+	case USB_ID(0x0763, 0x2080): /* M-Audio FastTrack Ultra */
+	case USB_ID(0x0763, 0x2081):
+		ep = 0x81;
+		ifnum = 2;
+		goto add_sync_ep_from_ifnum;
+	case USB_ID(0x2466, 0x8003): /* Fractal Audio Axe-Fx II */
+		ep = 0x86;
+		ifnum = 2;
+		goto add_sync_ep_from_ifnum;
+	case USB_ID(0x2466, 0x8010): /* Fractal Audio Axe-Fx III */
+		ep = 0x81;
+		ifnum = 2;
+		goto add_sync_ep_from_ifnum;
+	case USB_ID(0x1397, 0x0002): /* Behringer UFX1204 */
+		ep = 0x81;
+		ifnum = 1;
+		goto add_sync_ep_from_ifnum;
+	}
+
+	if (attr == USB_ENDPOINT_SYNC_ASYNC &&
+	    altsd->bInterfaceClass == USB_CLASS_VENDOR_SPEC &&
+	    altsd->bInterfaceProtocol == 2 &&
+	    altsd->bNumEndpoints == 1 &&
+	    USB_ID_VENDOR(subs->stream->chip->usb_id) == 0x0582 /* Roland */ &&
+	    search_roland_implicit_fb(dev, altsd->bInterfaceNumber + 1,
+				      altsd->bAlternateSetting,
+				      &alts, &ep) >= 0) {
+		goto add_sync_ep;
+	}
+
+	/* No quirk */
+	return 0;
+
+add_sync_ep_from_ifnum:
+	iface = usb_ifnum_to_if(dev, ifnum);
+
+	if (!iface || iface->num_altsetting == 0)
+		return -EINVAL;
+
+	alts = &iface->altsetting[1];
+
+add_sync_ep:
+	subs->sync_endpoint = snd_usb_add_endpoint(subs->stream->chip,
+						   alts, ep, !subs->direction,
+						   SND_USB_ENDPOINT_TYPE_DATA);
+	if (!subs->sync_endpoint)
+		return -EINVAL;
+
+	subs->data_endpoint->sync_master = subs->sync_endpoint;
+
+	return 0;
+}
+
+static int set_sync_endpoint(struct snd_usb_substream *subs,
+			     struct audioformat *fmt,
+			     struct usb_device *dev,
+			     struct usb_host_interface *alts,
+			     struct usb_interface_descriptor *altsd)
+{
+	int is_playback = subs->direction == SNDRV_PCM_STREAM_PLAYBACK;
+	unsigned int ep, attr;
+	bool implicit_fb;
+	int err;
+
+	/* we need a sync pipe in async OUT or adaptive IN mode */
+	/* check the number of EP, since some devices have broken
+	 * descriptors which fool us.  if it has only one EP,
+	 * assume it as adaptive-out or sync-in.
+	 */
+	attr = fmt->ep_attr & USB_ENDPOINT_SYNCTYPE;
+
+	if ((is_playback && (attr != USB_ENDPOINT_SYNC_ASYNC)) ||
+		(!is_playback && (attr != USB_ENDPOINT_SYNC_ADAPTIVE))) {
+
+		/*
+		 * In these modes the notion of sync_endpoint is irrelevant.
+		 * Reset pointers to avoid using stale data from previously
+		 * used settings, e.g. when configuration and endpoints were
+		 * changed
+		 */
+
+		subs->sync_endpoint = NULL;
+		subs->data_endpoint->sync_master = NULL;
+	}
+
+	err = set_sync_ep_implicit_fb_quirk(subs, dev, altsd, attr);
+	if (err < 0)
+		return err;
+
+	if (altsd->bNumEndpoints < 2)
+		return 0;
+
+	if ((is_playback && (attr == USB_ENDPOINT_SYNC_SYNC ||
+			     attr == USB_ENDPOINT_SYNC_ADAPTIVE)) ||
+	    (!is_playback && attr != USB_ENDPOINT_SYNC_ADAPTIVE))
+		return 0;
+
+	/*
+	 * In case of illegal SYNC_NONE for OUT endpoint, we keep going to see
+	 * if we don't find a sync endpoint, as on M-Audio Transit. In case of
+	 * error fall back to SYNC mode and don't create sync endpoint
+	 */
+
+	/* check sync-pipe endpoint */
+	/* ... and check descriptor size before accessing bSynchAddress
+	   because there is a version of the SB Audigy 2 NX firmware lacking
+	   the audio fields in the endpoint descriptors */
+	if ((get_endpoint(alts, 1)->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) != USB_ENDPOINT_XFER_ISOC ||
+	    (get_endpoint(alts, 1)->bLength >= USB_DT_ENDPOINT_AUDIO_SIZE &&
+	     get_endpoint(alts, 1)->bSynchAddress != 0)) {
+		dev_err(&dev->dev,
+			"%d:%d : invalid sync pipe. bmAttributes %02x, bLength %d, bSynchAddress %02x\n",
+			   fmt->iface, fmt->altsetting,
+			   get_endpoint(alts, 1)->bmAttributes,
+			   get_endpoint(alts, 1)->bLength,
+			   get_endpoint(alts, 1)->bSynchAddress);
+		if (is_playback && attr == USB_ENDPOINT_SYNC_NONE)
+			return 0;
+		return -EINVAL;
+	}
+	ep = get_endpoint(alts, 1)->bEndpointAddress;
+	if (get_endpoint(alts, 0)->bLength >= USB_DT_ENDPOINT_AUDIO_SIZE &&
+	    ((is_playback && ep != (unsigned int)(get_endpoint(alts, 0)->bSynchAddress | USB_DIR_IN)) ||
+	     (!is_playback && ep != (unsigned int)(get_endpoint(alts, 0)->bSynchAddress & ~USB_DIR_IN)))) {
+		dev_err(&dev->dev,
+			"%d:%d : invalid sync pipe. is_playback %d, ep %02x, bSynchAddress %02x\n",
+			   fmt->iface, fmt->altsetting,
+			   is_playback, ep, get_endpoint(alts, 0)->bSynchAddress);
+		if (is_playback && attr == USB_ENDPOINT_SYNC_NONE)
+			return 0;
+		return -EINVAL;
+	}
+
+	implicit_fb = (get_endpoint(alts, 1)->bmAttributes & USB_ENDPOINT_USAGE_MASK)
+			== USB_ENDPOINT_USAGE_IMPLICIT_FB;
+
+	subs->sync_endpoint = snd_usb_add_endpoint(subs->stream->chip,
+						   alts, ep, !subs->direction,
+						   implicit_fb ?
+							SND_USB_ENDPOINT_TYPE_DATA :
+							SND_USB_ENDPOINT_TYPE_SYNC);
+	if (!subs->sync_endpoint) {
+		if (is_playback && attr == USB_ENDPOINT_SYNC_NONE)
+			return 0;
+		return -EINVAL;
+	}
+
+	subs->data_endpoint->sync_master = subs->sync_endpoint;
+
+	return 0;
+}
+
+/*
+ * find a matching format and set up the interface
+ */
+static int set_format(struct snd_usb_substream *subs, struct audioformat *fmt)
+{
+	struct usb_device *dev = subs->dev;
+	struct usb_host_interface *alts;
+	struct usb_interface_descriptor *altsd;
+	struct usb_interface *iface;
+	int err;
+
+	iface = usb_ifnum_to_if(dev, fmt->iface);
+	if (WARN_ON(!iface))
+		return -EINVAL;
+	alts = usb_altnum_to_altsetting(iface, fmt->altsetting);
+	altsd = get_iface_desc(alts);
+	if (WARN_ON(altsd->bAlternateSetting != fmt->altsetting))
+		return -EINVAL;
+
+	if (fmt == subs->cur_audiofmt)
+		return 0;
+
+	/* close the old interface */
+	if (subs->interface >= 0 && subs->interface != fmt->iface) {
+		if (!subs->stream->chip->keep_iface) {
+			err = usb_set_interface(subs->dev, subs->interface, 0);
+			if (err < 0) {
+				dev_err(&dev->dev,
+					"%d:%d: return to setting 0 failed (%d)\n",
+					fmt->iface, fmt->altsetting, err);
+				return -EIO;
+			}
+		}
+		subs->interface = -1;
+		subs->altset_idx = 0;
+	}
+
+	/* set interface */
+	if (iface->cur_altsetting != alts) {
+		err = snd_usb_select_mode_quirk(subs, fmt);
+		if (err < 0)
+			return -EIO;
+
+		err = usb_set_interface(dev, fmt->iface, fmt->altsetting);
+		if (err < 0) {
+			dev_err(&dev->dev,
+				"%d:%d: usb_set_interface failed (%d)\n",
+				fmt->iface, fmt->altsetting, err);
+			return -EIO;
+		}
+		dev_dbg(&dev->dev, "setting usb interface %d:%d\n",
+			fmt->iface, fmt->altsetting);
+		snd_usb_set_interface_quirk(dev);
+	}
+
+	subs->interface = fmt->iface;
+	subs->altset_idx = fmt->altset_idx;
+	subs->data_endpoint = snd_usb_add_endpoint(subs->stream->chip,
+						   alts, fmt->endpoint, subs->direction,
+						   SND_USB_ENDPOINT_TYPE_DATA);
+
+	if (!subs->data_endpoint)
+		return -EINVAL;
+
+	err = set_sync_endpoint(subs, fmt, dev, alts, altsd);
+	if (err < 0)
+		return err;
+
+	err = snd_usb_init_pitch(subs->stream->chip, fmt->iface, alts, fmt);
+	if (err < 0)
+		return err;
+
+	subs->cur_audiofmt = fmt;
+
+	snd_usb_set_format_quirk(subs, fmt);
+
+	return 0;
+}
+
+/*
+ * Return the score of matching two audioformats.
+ * Veto the audioformat if:
+ * - It has no channels for some reason.
+ * - Requested PCM format is not supported.
+ * - Requested sample rate is not supported.
+ */
+static int match_endpoint_audioformats(struct snd_usb_substream *subs,
+				       struct audioformat *fp,
+				       struct audioformat *match, int rate,
+				       snd_pcm_format_t pcm_format)
+{
+	int i;
+	int score = 0;
+
+	if (fp->channels < 1) {
+		dev_dbg(&subs->dev->dev,
+			"%s: (fmt @%p) no channels\n", __func__, fp);
+		return 0;
+	}
+
+	if (!(fp->formats & pcm_format_to_bits(pcm_format))) {
+		dev_dbg(&subs->dev->dev,
+			"%s: (fmt @%p) no match for format %d\n", __func__,
+			fp, pcm_format);
+		return 0;
+	}
+
+	for (i = 0; i < fp->nr_rates; i++) {
+		if (fp->rate_table[i] == rate) {
+			score++;
+			break;
+		}
+	}
+	if (!score) {
+		dev_dbg(&subs->dev->dev,
+			"%s: (fmt @%p) no match for rate %d\n", __func__,
+			fp, rate);
+		return 0;
+	}
+
+	if (fp->channels == match->channels)
+		score++;
+
+	dev_dbg(&subs->dev->dev,
+		"%s: (fmt @%p) score %d\n", __func__, fp, score);
+
+	return score;
+}
+
+/*
+ * Configure the sync ep using the rate and pcm format of the data ep.
+ */
+static int configure_sync_endpoint(struct snd_usb_substream *subs)
+{
+	int ret;
+	struct audioformat *fp;
+	struct audioformat *sync_fp = NULL;
+	int cur_score = 0;
+	int sync_period_bytes = subs->period_bytes;
+	struct snd_usb_substream *sync_subs =
+		&subs->stream->substream[subs->direction ^ 1];
+
+	if (subs->sync_endpoint->type != SND_USB_ENDPOINT_TYPE_DATA ||
+	    !subs->stream)
+		return snd_usb_endpoint_set_params(subs->sync_endpoint,
+						   subs->pcm_format,
+						   subs->channels,
+						   subs->period_bytes,
+						   0, 0,
+						   subs->cur_rate,
+						   subs->cur_audiofmt,
+						   NULL);
+
+	/* Try to find the best matching audioformat. */
+	list_for_each_entry(fp, &sync_subs->fmt_list, list) {
+		int score = match_endpoint_audioformats(subs,
+							fp, subs->cur_audiofmt,
+			subs->cur_rate, subs->pcm_format);
+
+		if (score > cur_score) {
+			sync_fp = fp;
+			cur_score = score;
+		}
+	}
+
+	if (unlikely(sync_fp == NULL)) {
+		dev_err(&subs->dev->dev,
+			"%s: no valid audioformat for sync ep %x found\n",
+			__func__, sync_subs->ep_num);
+		return -EINVAL;
+	}
+
+	/*
+	 * Recalculate the period bytes if channel number differ between
+	 * data and sync ep audioformat.
+	 */
+	if (sync_fp->channels != subs->channels) {
+		sync_period_bytes = (subs->period_bytes / subs->channels) *
+			sync_fp->channels;
+		dev_dbg(&subs->dev->dev,
+			"%s: adjusted sync ep period bytes (%d -> %d)\n",
+			__func__, subs->period_bytes, sync_period_bytes);
+	}
+
+	ret = snd_usb_endpoint_set_params(subs->sync_endpoint,
+					  subs->pcm_format,
+					  sync_fp->channels,
+					  sync_period_bytes,
+					  0, 0,
+					  subs->cur_rate,
+					  sync_fp,
+					  NULL);
+
+	return ret;
+}
+
+/*
+ * configure endpoint params
+ *
+ * called  during initial setup and upon resume
+ */
+static int configure_endpoint(struct snd_usb_substream *subs)
+{
+	int ret;
+
+	/* format changed */
+	stop_endpoints(subs, true);
+	ret = snd_usb_endpoint_set_params(subs->data_endpoint,
+					  subs->pcm_format,
+					  subs->channels,
+					  subs->period_bytes,
+					  subs->period_frames,
+					  subs->buffer_periods,
+					  subs->cur_rate,
+					  subs->cur_audiofmt,
+					  subs->sync_endpoint);
+	if (ret < 0)
+		return ret;
+
+	if (subs->sync_endpoint)
+		ret = configure_sync_endpoint(subs);
+
+	return ret;
+}
+
+static int snd_usb_pcm_change_state(struct snd_usb_substream *subs, int state)
+{
+	int ret;
+
+	if (!subs->str_pd)
+		return 0;
+
+	ret = snd_usb_power_domain_set(subs->stream->chip, subs->str_pd, state);
+	if (ret < 0) {
+		dev_err(&subs->dev->dev,
+			"Cannot change Power Domain ID: %d to state: %d. Err: %d\n",
+			subs->str_pd->pd_id, state, ret);
+		return ret;
+	}
+
+	return 0;
+}
+
+int snd_usb_pcm_suspend(struct snd_usb_stream *as)
+{
+	int ret;
+
+	ret = snd_usb_pcm_change_state(&as->substream[0], UAC3_PD_STATE_D2);
+	if (ret < 0)
+		return ret;
+
+	ret = snd_usb_pcm_change_state(&as->substream[1], UAC3_PD_STATE_D2);
+	if (ret < 0)
+		return ret;
+
+	return 0;
+}
+
+int snd_usb_pcm_resume(struct snd_usb_stream *as)
+{
+	int ret;
+
+	ret = snd_usb_pcm_change_state(&as->substream[0], UAC3_PD_STATE_D1);
+	if (ret < 0)
+		return ret;
+
+	ret = snd_usb_pcm_change_state(&as->substream[1], UAC3_PD_STATE_D1);
+	if (ret < 0)
+		return ret;
+
+	return 0;
+}
+
+/*
+ * hw_params callback
+ *
+ * allocate a buffer and set the given audio format.
+ *
+ * so far we use a physically linear buffer although packetize transfer
+ * doesn't need a continuous area.
+ * if sg buffer is supported on the later version of alsa, we'll follow
+ * that.
+ */
+static int snd_usb_hw_params(struct snd_pcm_substream *substream,
+			     struct snd_pcm_hw_params *hw_params)
+{
+	struct snd_usb_substream *subs = substream->runtime->private_data;
+	struct audioformat *fmt;
+	int ret;
+
+	if (snd_usb_use_vmalloc)
+		ret = snd_pcm_lib_alloc_vmalloc_buffer(substream,
+						       params_buffer_bytes(hw_params));
+	else
+		ret = snd_pcm_lib_malloc_pages(substream,
+					       params_buffer_bytes(hw_params));
+	if (ret < 0)
+		return ret;
+
+	subs->pcm_format = params_format(hw_params);
+	subs->period_bytes = params_period_bytes(hw_params);
+	subs->period_frames = params_period_size(hw_params);
+	subs->buffer_periods = params_periods(hw_params);
+	subs->channels = params_channels(hw_params);
+	subs->cur_rate = params_rate(hw_params);
+
+	fmt = find_format(subs);
+	if (!fmt) {
+		dev_dbg(&subs->dev->dev,
+			"cannot set format: format = %#x, rate = %d, channels = %d\n",
+			   subs->pcm_format, subs->cur_rate, subs->channels);
+		return -EINVAL;
+	}
+
+	ret = snd_usb_lock_shutdown(subs->stream->chip);
+	if (ret < 0)
+		return ret;
+
+	ret = snd_usb_pcm_change_state(subs, UAC3_PD_STATE_D0);
+	if (ret < 0)
+		goto unlock;
+
+	ret = set_format(subs, fmt);
+	if (ret < 0)
+		goto unlock;
+
+	subs->interface = fmt->iface;
+	subs->altset_idx = fmt->altset_idx;
+	subs->need_setup_ep = true;
+
+ unlock:
+	snd_usb_unlock_shutdown(subs->stream->chip);
+	return ret;
+}
+
+/*
+ * hw_free callback
+ *
+ * reset the audio format and release the buffer
+ */
+static int snd_usb_hw_free(struct snd_pcm_substream *substream)
+{
+	struct snd_usb_substream *subs = substream->runtime->private_data;
+
+	subs->cur_audiofmt = NULL;
+	subs->cur_rate = 0;
+	subs->period_bytes = 0;
+	if (!snd_usb_lock_shutdown(subs->stream->chip)) {
+		stop_endpoints(subs, true);
+		snd_usb_endpoint_deactivate(subs->sync_endpoint);
+		snd_usb_endpoint_deactivate(subs->data_endpoint);
+		snd_usb_unlock_shutdown(subs->stream->chip);
+	}
+
+	if (snd_usb_use_vmalloc)
+		return snd_pcm_lib_free_vmalloc_buffer(substream);
+	else
+		return snd_pcm_lib_free_pages(substream);
+}
+
+/*
+ * prepare callback
+ *
+ * only a few subtle things...
+ */
+static int snd_usb_pcm_prepare(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct snd_usb_substream *subs = runtime->private_data;
+	struct usb_host_interface *alts;
+	struct usb_interface *iface;
+	int ret;
+
+	if (! subs->cur_audiofmt) {
+		dev_err(&subs->dev->dev, "no format is specified!\n");
+		return -ENXIO;
+	}
+
+	ret = snd_usb_lock_shutdown(subs->stream->chip);
+	if (ret < 0)
+		return ret;
+	if (snd_BUG_ON(!subs->data_endpoint)) {
+		ret = -EIO;
+		goto unlock;
+	}
+
+	snd_usb_endpoint_sync_pending_stop(subs->sync_endpoint);
+	snd_usb_endpoint_sync_pending_stop(subs->data_endpoint);
+
+	ret = snd_usb_pcm_change_state(subs, UAC3_PD_STATE_D0);
+	if (ret < 0)
+		goto unlock;
+
+	ret = set_format(subs, subs->cur_audiofmt);
+	if (ret < 0)
+		goto unlock;
+
+	if (subs->need_setup_ep) {
+
+		iface = usb_ifnum_to_if(subs->dev, subs->cur_audiofmt->iface);
+		alts = &iface->altsetting[subs->cur_audiofmt->altset_idx];
+		ret = snd_usb_init_sample_rate(subs->stream->chip,
+					       subs->cur_audiofmt->iface,
+					       alts,
+					       subs->cur_audiofmt,
+					       subs->cur_rate);
+		if (ret < 0)
+			goto unlock;
+
+		ret = configure_endpoint(subs);
+		if (ret < 0)
+			goto unlock;
+		subs->need_setup_ep = false;
+	}
+
+	/* some unit conversions in runtime */
+	subs->data_endpoint->maxframesize =
+		bytes_to_frames(runtime, subs->data_endpoint->maxpacksize);
+	subs->data_endpoint->curframesize =
+		bytes_to_frames(runtime, subs->data_endpoint->curpacksize);
+
+	/* reset the pointer */
+	subs->hwptr_done = 0;
+	subs->transfer_done = 0;
+	subs->last_delay = 0;
+	subs->last_frame_number = 0;
+	runtime->delay = 0;
+
+	/* for playback, submit the URBs now; otherwise, the first hwptr_done
+	 * updates for all URBs would happen at the same time when starting */
+	if (subs->direction == SNDRV_PCM_STREAM_PLAYBACK)
+		ret = start_endpoints(subs);
+
+ unlock:
+	snd_usb_unlock_shutdown(subs->stream->chip);
+	return ret;
+}
+
+static const struct snd_pcm_hardware snd_usb_hardware =
+{
+	.info =			SNDRV_PCM_INFO_MMAP |
+				SNDRV_PCM_INFO_MMAP_VALID |
+				SNDRV_PCM_INFO_BATCH |
+				SNDRV_PCM_INFO_INTERLEAVED |
+				SNDRV_PCM_INFO_BLOCK_TRANSFER |
+				SNDRV_PCM_INFO_PAUSE,
+	.buffer_bytes_max =	1024 * 1024,
+	.period_bytes_min =	64,
+	.period_bytes_max =	512 * 1024,
+	.periods_min =		2,
+	.periods_max =		1024,
+};
+
+static int hw_check_valid_format(struct snd_usb_substream *subs,
+				 struct snd_pcm_hw_params *params,
+				 struct audioformat *fp)
+{
+	struct snd_interval *it = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE);
+	struct snd_interval *ct = hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS);
+	struct snd_mask *fmts = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT);
+	struct snd_interval *pt = hw_param_interval(params, SNDRV_PCM_HW_PARAM_PERIOD_TIME);
+	struct snd_mask check_fmts;
+	unsigned int ptime;
+
+	/* check the format */
+	snd_mask_none(&check_fmts);
+	check_fmts.bits[0] = (u32)fp->formats;
+	check_fmts.bits[1] = (u32)(fp->formats >> 32);
+	snd_mask_intersect(&check_fmts, fmts);
+	if (snd_mask_empty(&check_fmts)) {
+		hwc_debug("   > check: no supported format %d\n", fp->format);
+		return 0;
+	}
+	/* check the channels */
+	if (fp->channels < ct->min || fp->channels > ct->max) {
+		hwc_debug("   > check: no valid channels %d (%d/%d)\n", fp->channels, ct->min, ct->max);
+		return 0;
+	}
+	/* check the rate is within the range */
+	if (fp->rate_min > it->max || (fp->rate_min == it->max && it->openmax)) {
+		hwc_debug("   > check: rate_min %d > max %d\n", fp->rate_min, it->max);
+		return 0;
+	}
+	if (fp->rate_max < it->min || (fp->rate_max == it->min && it->openmin)) {
+		hwc_debug("   > check: rate_max %d < min %d\n", fp->rate_max, it->min);
+		return 0;
+	}
+	/* check whether the period time is >= the data packet interval */
+	if (subs->speed != USB_SPEED_FULL) {
+		ptime = 125 * (1 << fp->datainterval);
+		if (ptime > pt->max || (ptime == pt->max && pt->openmax)) {
+			hwc_debug("   > check: ptime %u > max %u\n", ptime, pt->max);
+			return 0;
+		}
+	}
+	return 1;
+}
+
+static int hw_rule_rate(struct snd_pcm_hw_params *params,
+			struct snd_pcm_hw_rule *rule)
+{
+	struct snd_usb_substream *subs = rule->private;
+	struct audioformat *fp;
+	struct snd_interval *it = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE);
+	unsigned int rmin, rmax;
+	int changed;
+
+	hwc_debug("hw_rule_rate: (%d,%d)\n", it->min, it->max);
+	changed = 0;
+	rmin = rmax = 0;
+	list_for_each_entry(fp, &subs->fmt_list, list) {
+		if (!hw_check_valid_format(subs, params, fp))
+			continue;
+		if (changed++) {
+			if (rmin > fp->rate_min)
+				rmin = fp->rate_min;
+			if (rmax < fp->rate_max)
+				rmax = fp->rate_max;
+		} else {
+			rmin = fp->rate_min;
+			rmax = fp->rate_max;
+		}
+	}
+
+	if (!changed) {
+		hwc_debug("  --> get empty\n");
+		it->empty = 1;
+		return -EINVAL;
+	}
+
+	changed = 0;
+	if (it->min < rmin) {
+		it->min = rmin;
+		it->openmin = 0;
+		changed = 1;
+	}
+	if (it->max > rmax) {
+		it->max = rmax;
+		it->openmax = 0;
+		changed = 1;
+	}
+	if (snd_interval_checkempty(it)) {
+		it->empty = 1;
+		return -EINVAL;
+	}
+	hwc_debug("  --> (%d, %d) (changed = %d)\n", it->min, it->max, changed);
+	return changed;
+}
+
+
+static int hw_rule_channels(struct snd_pcm_hw_params *params,
+			    struct snd_pcm_hw_rule *rule)
+{
+	struct snd_usb_substream *subs = rule->private;
+	struct audioformat *fp;
+	struct snd_interval *it = hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS);
+	unsigned int rmin, rmax;
+	int changed;
+
+	hwc_debug("hw_rule_channels: (%d,%d)\n", it->min, it->max);
+	changed = 0;
+	rmin = rmax = 0;
+	list_for_each_entry(fp, &subs->fmt_list, list) {
+		if (!hw_check_valid_format(subs, params, fp))
+			continue;
+		if (changed++) {
+			if (rmin > fp->channels)
+				rmin = fp->channels;
+			if (rmax < fp->channels)
+				rmax = fp->channels;
+		} else {
+			rmin = fp->channels;
+			rmax = fp->channels;
+		}
+	}
+
+	if (!changed) {
+		hwc_debug("  --> get empty\n");
+		it->empty = 1;
+		return -EINVAL;
+	}
+
+	changed = 0;
+	if (it->min < rmin) {
+		it->min = rmin;
+		it->openmin = 0;
+		changed = 1;
+	}
+	if (it->max > rmax) {
+		it->max = rmax;
+		it->openmax = 0;
+		changed = 1;
+	}
+	if (snd_interval_checkempty(it)) {
+		it->empty = 1;
+		return -EINVAL;
+	}
+	hwc_debug("  --> (%d, %d) (changed = %d)\n", it->min, it->max, changed);
+	return changed;
+}
+
+static int hw_rule_format(struct snd_pcm_hw_params *params,
+			  struct snd_pcm_hw_rule *rule)
+{
+	struct snd_usb_substream *subs = rule->private;
+	struct audioformat *fp;
+	struct snd_mask *fmt = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT);
+	u64 fbits;
+	u32 oldbits[2];
+	int changed;
+
+	hwc_debug("hw_rule_format: %x:%x\n", fmt->bits[0], fmt->bits[1]);
+	fbits = 0;
+	list_for_each_entry(fp, &subs->fmt_list, list) {
+		if (!hw_check_valid_format(subs, params, fp))
+			continue;
+		fbits |= fp->formats;
+	}
+
+	oldbits[0] = fmt->bits[0];
+	oldbits[1] = fmt->bits[1];
+	fmt->bits[0] &= (u32)fbits;
+	fmt->bits[1] &= (u32)(fbits >> 32);
+	if (!fmt->bits[0] && !fmt->bits[1]) {
+		hwc_debug("  --> get empty\n");
+		return -EINVAL;
+	}
+	changed = (oldbits[0] != fmt->bits[0] || oldbits[1] != fmt->bits[1]);
+	hwc_debug("  --> %x:%x (changed = %d)\n", fmt->bits[0], fmt->bits[1], changed);
+	return changed;
+}
+
+static int hw_rule_period_time(struct snd_pcm_hw_params *params,
+			       struct snd_pcm_hw_rule *rule)
+{
+	struct snd_usb_substream *subs = rule->private;
+	struct audioformat *fp;
+	struct snd_interval *it;
+	unsigned char min_datainterval;
+	unsigned int pmin;
+	int changed;
+
+	it = hw_param_interval(params, SNDRV_PCM_HW_PARAM_PERIOD_TIME);
+	hwc_debug("hw_rule_period_time: (%u,%u)\n", it->min, it->max);
+	min_datainterval = 0xff;
+	list_for_each_entry(fp, &subs->fmt_list, list) {
+		if (!hw_check_valid_format(subs, params, fp))
+			continue;
+		min_datainterval = min(min_datainterval, fp->datainterval);
+	}
+	if (min_datainterval == 0xff) {
+		hwc_debug("  --> get empty\n");
+		it->empty = 1;
+		return -EINVAL;
+	}
+	pmin = 125 * (1 << min_datainterval);
+	changed = 0;
+	if (it->min < pmin) {
+		it->min = pmin;
+		it->openmin = 0;
+		changed = 1;
+	}
+	if (snd_interval_checkempty(it)) {
+		it->empty = 1;
+		return -EINVAL;
+	}
+	hwc_debug("  --> (%u,%u) (changed = %d)\n", it->min, it->max, changed);
+	return changed;
+}
+
+/*
+ *  If the device supports unusual bit rates, does the request meet these?
+ */
+static int snd_usb_pcm_check_knot(struct snd_pcm_runtime *runtime,
+				  struct snd_usb_substream *subs)
+{
+	struct audioformat *fp;
+	int *rate_list;
+	int count = 0, needs_knot = 0;
+	int err;
+
+	kfree(subs->rate_list.list);
+	subs->rate_list.list = NULL;
+
+	list_for_each_entry(fp, &subs->fmt_list, list) {
+		if (fp->rates & SNDRV_PCM_RATE_CONTINUOUS)
+			return 0;
+		count += fp->nr_rates;
+		if (fp->rates & SNDRV_PCM_RATE_KNOT)
+			needs_knot = 1;
+	}
+	if (!needs_knot)
+		return 0;
+
+	subs->rate_list.list = rate_list =
+		kmalloc_array(count, sizeof(int), GFP_KERNEL);
+	if (!subs->rate_list.list)
+		return -ENOMEM;
+	subs->rate_list.count = count;
+	subs->rate_list.mask = 0;
+	count = 0;
+	list_for_each_entry(fp, &subs->fmt_list, list) {
+		int i;
+		for (i = 0; i < fp->nr_rates; i++)
+			rate_list[count++] = fp->rate_table[i];
+	}
+	err = snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
+					 &subs->rate_list);
+	if (err < 0)
+		return err;
+
+	return 0;
+}
+
+
+/*
+ * set up the runtime hardware information.
+ */
+
+static int setup_hw_info(struct snd_pcm_runtime *runtime, struct snd_usb_substream *subs)
+{
+	struct audioformat *fp;
+	unsigned int pt, ptmin;
+	int param_period_time_if_needed;
+	int err;
+
+	runtime->hw.formats = subs->formats;
+
+	runtime->hw.rate_min = 0x7fffffff;
+	runtime->hw.rate_max = 0;
+	runtime->hw.channels_min = 256;
+	runtime->hw.channels_max = 0;
+	runtime->hw.rates = 0;
+	ptmin = UINT_MAX;
+	/* check min/max rates and channels */
+	list_for_each_entry(fp, &subs->fmt_list, list) {
+		runtime->hw.rates |= fp->rates;
+		if (runtime->hw.rate_min > fp->rate_min)
+			runtime->hw.rate_min = fp->rate_min;
+		if (runtime->hw.rate_max < fp->rate_max)
+			runtime->hw.rate_max = fp->rate_max;
+		if (runtime->hw.channels_min > fp->channels)
+			runtime->hw.channels_min = fp->channels;
+		if (runtime->hw.channels_max < fp->channels)
+			runtime->hw.channels_max = fp->channels;
+		if (fp->fmt_type == UAC_FORMAT_TYPE_II && fp->frame_size > 0) {
+			/* FIXME: there might be more than one audio formats... */
+			runtime->hw.period_bytes_min = runtime->hw.period_bytes_max =
+				fp->frame_size;
+		}
+		pt = 125 * (1 << fp->datainterval);
+		ptmin = min(ptmin, pt);
+	}
+
+	param_period_time_if_needed = SNDRV_PCM_HW_PARAM_PERIOD_TIME;
+	if (subs->speed == USB_SPEED_FULL)
+		/* full speed devices have fixed data packet interval */
+		ptmin = 1000;
+	if (ptmin == 1000)
+		/* if period time doesn't go below 1 ms, no rules needed */
+		param_period_time_if_needed = -1;
+
+	err = snd_pcm_hw_constraint_minmax(runtime,
+					   SNDRV_PCM_HW_PARAM_PERIOD_TIME,
+					   ptmin, UINT_MAX);
+	if (err < 0)
+		return err;
+
+	err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
+				  hw_rule_rate, subs,
+				  SNDRV_PCM_HW_PARAM_FORMAT,
+				  SNDRV_PCM_HW_PARAM_CHANNELS,
+				  param_period_time_if_needed,
+				  -1);
+	if (err < 0)
+		return err;
+	err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS,
+				  hw_rule_channels, subs,
+				  SNDRV_PCM_HW_PARAM_FORMAT,
+				  SNDRV_PCM_HW_PARAM_RATE,
+				  param_period_time_if_needed,
+				  -1);
+	if (err < 0)
+		return err;
+	err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_FORMAT,
+				  hw_rule_format, subs,
+				  SNDRV_PCM_HW_PARAM_RATE,
+				  SNDRV_PCM_HW_PARAM_CHANNELS,
+				  param_period_time_if_needed,
+				  -1);
+	if (err < 0)
+		return err;
+	if (param_period_time_if_needed >= 0) {
+		err = snd_pcm_hw_rule_add(runtime, 0,
+					  SNDRV_PCM_HW_PARAM_PERIOD_TIME,
+					  hw_rule_period_time, subs,
+					  SNDRV_PCM_HW_PARAM_FORMAT,
+					  SNDRV_PCM_HW_PARAM_CHANNELS,
+					  SNDRV_PCM_HW_PARAM_RATE,
+					  -1);
+		if (err < 0)
+			return err;
+	}
+	err = snd_usb_pcm_check_knot(runtime, subs);
+	if (err < 0)
+		return err;
+
+	return snd_usb_autoresume(subs->stream->chip);
+}
+
+static int snd_usb_pcm_open(struct snd_pcm_substream *substream)
+{
+	int direction = substream->stream;
+	struct snd_usb_stream *as = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct snd_usb_substream *subs = &as->substream[direction];
+
+	subs->interface = -1;
+	subs->altset_idx = 0;
+	runtime->hw = snd_usb_hardware;
+	runtime->private_data = subs;
+	subs->pcm_substream = substream;
+	/* runtime PM is also done there */
+
+	/* initialize DSD/DOP context */
+	subs->dsd_dop.byte_idx = 0;
+	subs->dsd_dop.channel = 0;
+	subs->dsd_dop.marker = 1;
+
+	return setup_hw_info(runtime, subs);
+}
+
+static int snd_usb_pcm_close(struct snd_pcm_substream *substream)
+{
+	int direction = substream->stream;
+	struct snd_usb_stream *as = snd_pcm_substream_chip(substream);
+	struct snd_usb_substream *subs = &as->substream[direction];
+	int ret;
+
+	stop_endpoints(subs, true);
+
+	if (!as->chip->keep_iface &&
+	    subs->interface >= 0 &&
+	    !snd_usb_lock_shutdown(subs->stream->chip)) {
+		usb_set_interface(subs->dev, subs->interface, 0);
+		subs->interface = -1;
+		ret = snd_usb_pcm_change_state(subs, UAC3_PD_STATE_D1);
+		snd_usb_unlock_shutdown(subs->stream->chip);
+		if (ret < 0)
+			return ret;
+	}
+
+	subs->pcm_substream = NULL;
+	snd_usb_autosuspend(subs->stream->chip);
+
+	return 0;
+}
+
+/* Since a URB can handle only a single linear buffer, we must use double
+ * buffering when the data to be transferred overflows the buffer boundary.
+ * To avoid inconsistencies when updating hwptr_done, we use double buffering
+ * for all URBs.
+ */
+static void retire_capture_urb(struct snd_usb_substream *subs,
+			       struct urb *urb)
+{
+	struct snd_pcm_runtime *runtime = subs->pcm_substream->runtime;
+	unsigned int stride, frames, bytes, oldptr;
+	int i, period_elapsed = 0;
+	unsigned long flags;
+	unsigned char *cp;
+	int current_frame_number;
+
+	/* read frame number here, update pointer in critical section */
+	current_frame_number = usb_get_current_frame_number(subs->dev);
+
+	stride = runtime->frame_bits >> 3;
+
+	for (i = 0; i < urb->number_of_packets; i++) {
+		cp = (unsigned char *)urb->transfer_buffer + urb->iso_frame_desc[i].offset + subs->pkt_offset_adj;
+		if (urb->iso_frame_desc[i].status && printk_ratelimit()) {
+			dev_dbg(&subs->dev->dev, "frame %d active: %d\n",
+				i, urb->iso_frame_desc[i].status);
+			// continue;
+		}
+		bytes = urb->iso_frame_desc[i].actual_length;
+		frames = bytes / stride;
+		if (!subs->txfr_quirk)
+			bytes = frames * stride;
+		if (bytes % (runtime->sample_bits >> 3) != 0) {
+			int oldbytes = bytes;
+			bytes = frames * stride;
+			dev_warn_ratelimited(&subs->dev->dev,
+				 "Corrected urb data len. %d->%d\n",
+							oldbytes, bytes);
+		}
+		/* update the current pointer */
+		spin_lock_irqsave(&subs->lock, flags);
+		oldptr = subs->hwptr_done;
+		subs->hwptr_done += bytes;
+		if (subs->hwptr_done >= runtime->buffer_size * stride)
+			subs->hwptr_done -= runtime->buffer_size * stride;
+		frames = (bytes + (oldptr % stride)) / stride;
+		subs->transfer_done += frames;
+		if (subs->transfer_done >= runtime->period_size) {
+			subs->transfer_done -= runtime->period_size;
+			period_elapsed = 1;
+		}
+		/* capture delay is by construction limited to one URB,
+		 * reset delays here
+		 */
+		runtime->delay = subs->last_delay = 0;
+
+		/* realign last_frame_number */
+		subs->last_frame_number = current_frame_number;
+		subs->last_frame_number &= 0xFF; /* keep 8 LSBs */
+
+		spin_unlock_irqrestore(&subs->lock, flags);
+		/* copy a data chunk */
+		if (oldptr + bytes > runtime->buffer_size * stride) {
+			unsigned int bytes1 =
+					runtime->buffer_size * stride - oldptr;
+			memcpy(runtime->dma_area + oldptr, cp, bytes1);
+			memcpy(runtime->dma_area, cp + bytes1, bytes - bytes1);
+		} else {
+			memcpy(runtime->dma_area + oldptr, cp, bytes);
+		}
+	}
+
+	if (period_elapsed)
+		snd_pcm_period_elapsed(subs->pcm_substream);
+}
+
+static inline void fill_playback_urb_dsd_dop(struct snd_usb_substream *subs,
+					     struct urb *urb, unsigned int bytes)
+{
+	struct snd_pcm_runtime *runtime = subs->pcm_substream->runtime;
+	unsigned int stride = runtime->frame_bits >> 3;
+	unsigned int dst_idx = 0;
+	unsigned int src_idx = subs->hwptr_done;
+	unsigned int wrap = runtime->buffer_size * stride;
+	u8 *dst = urb->transfer_buffer;
+	u8 *src = runtime->dma_area;
+	u8 marker[] = { 0x05, 0xfa };
+
+	/*
+	 * The DSP DOP format defines a way to transport DSD samples over
+	 * normal PCM data endpoints. It requires stuffing of marker bytes
+	 * (0x05 and 0xfa, alternating per sample frame), and then expects
+	 * 2 additional bytes of actual payload. The whole frame is stored
+	 * LSB.
+	 *
+	 * Hence, for a stereo transport, the buffer layout looks like this,
+	 * where L refers to left channel samples and R to right.
+	 *
+	 *   L1 L2 0x05   R1 R2 0x05   L3 L4 0xfa  R3 R4 0xfa
+	 *   L5 L6 0x05   R5 R6 0x05   L7 L8 0xfa  R7 R8 0xfa
+	 *   .....
+	 *
+	 */
+
+	while (bytes--) {
+		if (++subs->dsd_dop.byte_idx == 3) {
+			/* frame boundary? */
+			dst[dst_idx++] = marker[subs->dsd_dop.marker];
+			src_idx += 2;
+			subs->dsd_dop.byte_idx = 0;
+
+			if (++subs->dsd_dop.channel % runtime->channels == 0) {
+				/* alternate the marker */
+				subs->dsd_dop.marker++;
+				subs->dsd_dop.marker %= ARRAY_SIZE(marker);
+				subs->dsd_dop.channel = 0;
+			}
+		} else {
+			/* stuff the DSD payload */
+			int idx = (src_idx + subs->dsd_dop.byte_idx - 1) % wrap;
+
+			if (subs->cur_audiofmt->dsd_bitrev)
+				dst[dst_idx++] = bitrev8(src[idx]);
+			else
+				dst[dst_idx++] = src[idx];
+
+			subs->hwptr_done++;
+		}
+	}
+	if (subs->hwptr_done >= runtime->buffer_size * stride)
+		subs->hwptr_done -= runtime->buffer_size * stride;
+}
+
+static void copy_to_urb(struct snd_usb_substream *subs, struct urb *urb,
+			int offset, int stride, unsigned int bytes)
+{
+	struct snd_pcm_runtime *runtime = subs->pcm_substream->runtime;
+
+	if (subs->hwptr_done + bytes > runtime->buffer_size * stride) {
+		/* err, the transferred area goes over buffer boundary. */
+		unsigned int bytes1 =
+			runtime->buffer_size * stride - subs->hwptr_done;
+		memcpy(urb->transfer_buffer + offset,
+		       runtime->dma_area + subs->hwptr_done, bytes1);
+		memcpy(urb->transfer_buffer + offset + bytes1,
+		       runtime->dma_area, bytes - bytes1);
+	} else {
+		memcpy(urb->transfer_buffer + offset,
+		       runtime->dma_area + subs->hwptr_done, bytes);
+	}
+	subs->hwptr_done += bytes;
+	if (subs->hwptr_done >= runtime->buffer_size * stride)
+		subs->hwptr_done -= runtime->buffer_size * stride;
+}
+
+static unsigned int copy_to_urb_quirk(struct snd_usb_substream *subs,
+				      struct urb *urb, int stride,
+				      unsigned int bytes)
+{
+	__le32 packet_length;
+	int i;
+
+	/* Put __le32 length descriptor at start of each packet. */
+	for (i = 0; i < urb->number_of_packets; i++) {
+		unsigned int length = urb->iso_frame_desc[i].length;
+		unsigned int offset = urb->iso_frame_desc[i].offset;
+
+		packet_length = cpu_to_le32(length);
+		offset += i * sizeof(packet_length);
+		urb->iso_frame_desc[i].offset = offset;
+		urb->iso_frame_desc[i].length += sizeof(packet_length);
+		memcpy(urb->transfer_buffer + offset,
+		       &packet_length, sizeof(packet_length));
+		copy_to_urb(subs, urb, offset + sizeof(packet_length),
+			    stride, length);
+	}
+	/* Adjust transfer size accordingly. */
+	bytes += urb->number_of_packets * sizeof(packet_length);
+	return bytes;
+}
+
+static void prepare_playback_urb(struct snd_usb_substream *subs,
+				 struct urb *urb)
+{
+	struct snd_pcm_runtime *runtime = subs->pcm_substream->runtime;
+	struct snd_usb_endpoint *ep = subs->data_endpoint;
+	struct snd_urb_ctx *ctx = urb->context;
+	unsigned int counts, frames, bytes;
+	int i, stride, period_elapsed = 0;
+	unsigned long flags;
+
+	stride = runtime->frame_bits >> 3;
+
+	frames = 0;
+	urb->number_of_packets = 0;
+	spin_lock_irqsave(&subs->lock, flags);
+	subs->frame_limit += ep->max_urb_frames;
+	for (i = 0; i < ctx->packets; i++) {
+		if (ctx->packet_size[i])
+			counts = ctx->packet_size[i];
+		else
+			counts = snd_usb_endpoint_next_packet_size(ep);
+
+		/* set up descriptor */
+		urb->iso_frame_desc[i].offset = frames * ep->stride;
+		urb->iso_frame_desc[i].length = counts * ep->stride;
+		frames += counts;
+		urb->number_of_packets++;
+		subs->transfer_done += counts;
+		if (subs->transfer_done >= runtime->period_size) {
+			subs->transfer_done -= runtime->period_size;
+			subs->frame_limit = 0;
+			period_elapsed = 1;
+			if (subs->fmt_type == UAC_FORMAT_TYPE_II) {
+				if (subs->transfer_done > 0) {
+					/* FIXME: fill-max mode is not
+					 * supported yet */
+					frames -= subs->transfer_done;
+					counts -= subs->transfer_done;
+					urb->iso_frame_desc[i].length =
+						counts * ep->stride;
+					subs->transfer_done = 0;
+				}
+				i++;
+				if (i < ctx->packets) {
+					/* add a transfer delimiter */
+					urb->iso_frame_desc[i].offset =
+						frames * ep->stride;
+					urb->iso_frame_desc[i].length = 0;
+					urb->number_of_packets++;
+				}
+				break;
+			}
+		}
+		/* finish at the period boundary or after enough frames */
+		if ((period_elapsed ||
+				subs->transfer_done >= subs->frame_limit) &&
+		    !snd_usb_endpoint_implicit_feedback_sink(ep))
+			break;
+	}
+	bytes = frames * ep->stride;
+
+	if (unlikely(subs->pcm_format == SNDRV_PCM_FORMAT_DSD_U16_LE &&
+		     subs->cur_audiofmt->dsd_dop)) {
+		fill_playback_urb_dsd_dop(subs, urb, bytes);
+	} else if (unlikely(subs->pcm_format == SNDRV_PCM_FORMAT_DSD_U8 &&
+			   subs->cur_audiofmt->dsd_bitrev)) {
+		/* bit-reverse the bytes */
+		u8 *buf = urb->transfer_buffer;
+		for (i = 0; i < bytes; i++) {
+			int idx = (subs->hwptr_done + i)
+				% (runtime->buffer_size * stride);
+			buf[i] = bitrev8(runtime->dma_area[idx]);
+		}
+
+		subs->hwptr_done += bytes;
+		if (subs->hwptr_done >= runtime->buffer_size * stride)
+			subs->hwptr_done -= runtime->buffer_size * stride;
+	} else {
+		/* usual PCM */
+		if (!subs->tx_length_quirk)
+			copy_to_urb(subs, urb, 0, stride, bytes);
+		else
+			bytes = copy_to_urb_quirk(subs, urb, stride, bytes);
+			/* bytes is now amount of outgoing data */
+	}
+
+	/* update delay with exact number of samples queued */
+	runtime->delay = subs->last_delay;
+	runtime->delay += frames;
+	subs->last_delay = runtime->delay;
+
+	/* realign last_frame_number */
+	subs->last_frame_number = usb_get_current_frame_number(subs->dev);
+	subs->last_frame_number &= 0xFF; /* keep 8 LSBs */
+
+	if (subs->trigger_tstamp_pending_update) {
+		/* this is the first actual URB submitted,
+		 * update trigger timestamp to reflect actual start time
+		 */
+		snd_pcm_gettime(runtime, &runtime->trigger_tstamp);
+		subs->trigger_tstamp_pending_update = false;
+	}
+
+	spin_unlock_irqrestore(&subs->lock, flags);
+	urb->transfer_buffer_length = bytes;
+	if (period_elapsed)
+		snd_pcm_period_elapsed(subs->pcm_substream);
+}
+
+/*
+ * process after playback data complete
+ * - decrease the delay count again
+ */
+static void retire_playback_urb(struct snd_usb_substream *subs,
+			       struct urb *urb)
+{
+	unsigned long flags;
+	struct snd_pcm_runtime *runtime = subs->pcm_substream->runtime;
+	struct snd_usb_endpoint *ep = subs->data_endpoint;
+	int processed = urb->transfer_buffer_length / ep->stride;
+	int est_delay;
+
+	/* ignore the delay accounting when procssed=0 is given, i.e.
+	 * silent payloads are procssed before handling the actual data
+	 */
+	if (!processed)
+		return;
+
+	spin_lock_irqsave(&subs->lock, flags);
+	if (!subs->last_delay)
+		goto out; /* short path */
+
+	est_delay = snd_usb_pcm_delay(subs, runtime->rate);
+	/* update delay with exact number of samples played */
+	if (processed > subs->last_delay)
+		subs->last_delay = 0;
+	else
+		subs->last_delay -= processed;
+	runtime->delay = subs->last_delay;
+
+	/*
+	 * Report when delay estimate is off by more than 2ms.
+	 * The error should be lower than 2ms since the estimate relies
+	 * on two reads of a counter updated every ms.
+	 */
+	if (abs(est_delay - subs->last_delay) * 1000 > runtime->rate * 2)
+		dev_dbg_ratelimited(&subs->dev->dev,
+			"delay: estimated %d, actual %d\n",
+			est_delay, subs->last_delay);
+
+	if (!subs->running) {
+		/* update last_frame_number for delay counting here since
+		 * prepare_playback_urb won't be called during pause
+		 */
+		subs->last_frame_number =
+			usb_get_current_frame_number(subs->dev) & 0xff;
+	}
+
+ out:
+	spin_unlock_irqrestore(&subs->lock, flags);
+}
+
+static int snd_usb_substream_playback_trigger(struct snd_pcm_substream *substream,
+					      int cmd)
+{
+	struct snd_usb_substream *subs = substream->runtime->private_data;
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+		subs->trigger_tstamp_pending_update = true;
+		/* fall through */
+	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+		subs->data_endpoint->prepare_data_urb = prepare_playback_urb;
+		subs->data_endpoint->retire_data_urb = retire_playback_urb;
+		subs->running = 1;
+		return 0;
+	case SNDRV_PCM_TRIGGER_STOP:
+		stop_endpoints(subs, false);
+		subs->running = 0;
+		return 0;
+	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+		subs->data_endpoint->prepare_data_urb = NULL;
+		/* keep retire_data_urb for delay calculation */
+		subs->data_endpoint->retire_data_urb = retire_playback_urb;
+		subs->running = 0;
+		return 0;
+	}
+
+	return -EINVAL;
+}
+
+static int snd_usb_substream_capture_trigger(struct snd_pcm_substream *substream,
+					     int cmd)
+{
+	int err;
+	struct snd_usb_substream *subs = substream->runtime->private_data;
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+		err = start_endpoints(subs);
+		if (err < 0)
+			return err;
+
+		subs->data_endpoint->retire_data_urb = retire_capture_urb;
+		subs->running = 1;
+		return 0;
+	case SNDRV_PCM_TRIGGER_STOP:
+		stop_endpoints(subs, false);
+		subs->running = 0;
+		return 0;
+	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+		subs->data_endpoint->retire_data_urb = NULL;
+		subs->running = 0;
+		return 0;
+	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+		subs->data_endpoint->retire_data_urb = retire_capture_urb;
+		subs->running = 1;
+		return 0;
+	}
+
+	return -EINVAL;
+}
+
+static const struct snd_pcm_ops snd_usb_playback_ops = {
+	.open =		snd_usb_pcm_open,
+	.close =	snd_usb_pcm_close,
+	.ioctl =	snd_pcm_lib_ioctl,
+	.hw_params =	snd_usb_hw_params,
+	.hw_free =	snd_usb_hw_free,
+	.prepare =	snd_usb_pcm_prepare,
+	.trigger =	snd_usb_substream_playback_trigger,
+	.pointer =	snd_usb_pcm_pointer,
+	.page =		snd_pcm_lib_get_vmalloc_page,
+};
+
+static const struct snd_pcm_ops snd_usb_capture_ops = {
+	.open =		snd_usb_pcm_open,
+	.close =	snd_usb_pcm_close,
+	.ioctl =	snd_pcm_lib_ioctl,
+	.hw_params =	snd_usb_hw_params,
+	.hw_free =	snd_usb_hw_free,
+	.prepare =	snd_usb_pcm_prepare,
+	.trigger =	snd_usb_substream_capture_trigger,
+	.pointer =	snd_usb_pcm_pointer,
+	.page =		snd_pcm_lib_get_vmalloc_page,
+};
+
+static const struct snd_pcm_ops snd_usb_playback_dev_ops = {
+	.open =		snd_usb_pcm_open,
+	.close =	snd_usb_pcm_close,
+	.ioctl =	snd_pcm_lib_ioctl,
+	.hw_params =	snd_usb_hw_params,
+	.hw_free =	snd_usb_hw_free,
+	.prepare =	snd_usb_pcm_prepare,
+	.trigger =	snd_usb_substream_playback_trigger,
+	.pointer =	snd_usb_pcm_pointer,
+	.page =		snd_pcm_sgbuf_ops_page,
+};
+
+static const struct snd_pcm_ops snd_usb_capture_dev_ops = {
+	.open =		snd_usb_pcm_open,
+	.close =	snd_usb_pcm_close,
+	.ioctl =	snd_pcm_lib_ioctl,
+	.hw_params =	snd_usb_hw_params,
+	.hw_free =	snd_usb_hw_free,
+	.prepare =	snd_usb_pcm_prepare,
+	.trigger =	snd_usb_substream_capture_trigger,
+	.pointer =	snd_usb_pcm_pointer,
+	.page =		snd_pcm_sgbuf_ops_page,
+};
+
+void snd_usb_set_pcm_ops(struct snd_pcm *pcm, int stream)
+{
+	const struct snd_pcm_ops *ops;
+
+	if (snd_usb_use_vmalloc)
+		ops = stream == SNDRV_PCM_STREAM_PLAYBACK ?
+			&snd_usb_playback_ops : &snd_usb_capture_ops;
+	else
+		ops = stream == SNDRV_PCM_STREAM_PLAYBACK ?
+			&snd_usb_playback_dev_ops : &snd_usb_capture_dev_ops;
+	snd_pcm_set_ops(pcm, stream, ops);
+}
+
+void snd_usb_preallocate_buffer(struct snd_usb_substream *subs)
+{
+	struct snd_pcm *pcm = subs->stream->pcm;
+	struct snd_pcm_substream *s = pcm->streams[subs->direction].substream;
+	struct device *dev = subs->dev->bus->controller;
+
+	if (!snd_usb_use_vmalloc)
+		snd_pcm_lib_preallocate_pages(s, SNDRV_DMA_TYPE_DEV_SG,
+					      dev, 64*1024, 512*1024);
+}
diff --git a/sound/usb/pcm.h b/sound/usb/pcm.h
new file mode 100644
index 0000000..9833627
--- /dev/null
+++ b/sound/usb/pcm.h
@@ -0,0 +1,18 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __USBAUDIO_PCM_H
+#define __USBAUDIO_PCM_H
+
+snd_pcm_uframes_t snd_usb_pcm_delay(struct snd_usb_substream *subs,
+				    unsigned int rate);
+
+void snd_usb_set_pcm_ops(struct snd_pcm *pcm, int stream);
+int snd_usb_pcm_suspend(struct snd_usb_stream *as);
+int snd_usb_pcm_resume(struct snd_usb_stream *as);
+
+int snd_usb_init_pitch(struct snd_usb_audio *chip, int iface,
+		       struct usb_host_interface *alts,
+		       struct audioformat *fmt);
+void snd_usb_preallocate_buffer(struct snd_usb_substream *subs);
+
+
+#endif /* __USBAUDIO_PCM_H */
diff --git a/sound/usb/power.c b/sound/usb/power.c
new file mode 100644
index 0000000..bd303a1
--- /dev/null
+++ b/sound/usb/power.c
@@ -0,0 +1,104 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ *   UAC3 Power Domain state management functions
+ */
+
+#include <linux/slab.h>
+#include <linux/usb.h>
+#include <linux/usb/audio.h>
+#include <linux/usb/audio-v2.h>
+#include <linux/usb/audio-v3.h>
+
+#include "usbaudio.h"
+#include "helper.h"
+#include "power.h"
+
+struct snd_usb_power_domain *
+snd_usb_find_power_domain(struct usb_host_interface *ctrl_iface,
+			  unsigned char id)
+{
+	struct snd_usb_power_domain *pd;
+	void *p;
+
+	pd = kzalloc(sizeof(*pd), GFP_KERNEL);
+	if (!pd)
+		return NULL;
+
+	p = NULL;
+	while ((p = snd_usb_find_csint_desc(ctrl_iface->extra,
+					    ctrl_iface->extralen,
+					    p, UAC3_POWER_DOMAIN)) != NULL) {
+		struct uac3_power_domain_descriptor *pd_desc = p;
+		int i;
+
+		for (i = 0; i < pd_desc->bNrEntities; i++) {
+			if (pd_desc->baEntityID[i] == id) {
+				pd->pd_id = pd_desc->bPowerDomainID;
+				pd->pd_d1d0_rec =
+					le16_to_cpu(pd_desc->waRecoveryTime1);
+				pd->pd_d2d0_rec =
+					le16_to_cpu(pd_desc->waRecoveryTime2);
+				return pd;
+			}
+		}
+	}
+
+	kfree(pd);
+	return NULL;
+}
+
+int snd_usb_power_domain_set(struct snd_usb_audio *chip,
+			     struct snd_usb_power_domain *pd,
+			     unsigned char state)
+{
+	struct usb_device *dev = chip->dev;
+	unsigned char current_state;
+	int err, idx;
+
+	idx = snd_usb_ctrl_intf(chip) | (pd->pd_id << 8);
+
+	err = snd_usb_ctl_msg(chip->dev, usb_rcvctrlpipe(chip->dev, 0),
+			      UAC2_CS_CUR,
+			      USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_IN,
+			      UAC3_AC_POWER_DOMAIN_CONTROL << 8, idx,
+			      &current_state, sizeof(current_state));
+	if (err < 0) {
+		dev_err(&dev->dev, "Can't get UAC3 power state for id %d\n",
+			pd->pd_id);
+		return err;
+	}
+
+	if (current_state == state) {
+		dev_dbg(&dev->dev, "UAC3 power domain id %d already in state %d\n",
+			pd->pd_id, state);
+		return 0;
+	}
+
+	err = snd_usb_ctl_msg(chip->dev, usb_sndctrlpipe(chip->dev, 0), UAC2_CS_CUR,
+			      USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_OUT,
+			      UAC3_AC_POWER_DOMAIN_CONTROL << 8, idx,
+			      &state, sizeof(state));
+	if (err < 0) {
+		dev_err(&dev->dev, "Can't set UAC3 power state to %d for id %d\n",
+			state, pd->pd_id);
+		return err;
+	}
+
+	if (state == UAC3_PD_STATE_D0) {
+		switch (current_state) {
+		case UAC3_PD_STATE_D2:
+			udelay(pd->pd_d2d0_rec * 50);
+			break;
+		case UAC3_PD_STATE_D1:
+			udelay(pd->pd_d1d0_rec * 50);
+			break;
+		default:
+			return -EINVAL;
+		}
+	}
+
+	dev_dbg(&dev->dev, "UAC3 power domain id %d change to state %d\n",
+		pd->pd_id, state);
+
+	return 0;
+}
diff --git a/sound/usb/power.h b/sound/usb/power.h
new file mode 100644
index 0000000..6004231
--- /dev/null
+++ b/sound/usb/power.h
@@ -0,0 +1,37 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __USBAUDIO_POWER_H
+#define __USBAUDIO_POWER_H
+
+struct snd_usb_power_domain {
+	int pd_id;              /* UAC3 Power Domain ID */
+	int pd_d1d0_rec;        /* D1 to D0 recovery time */
+	int pd_d2d0_rec;        /* D2 to D0 recovery time */
+};
+
+enum {
+	UAC3_PD_STATE_D0,
+	UAC3_PD_STATE_D1,
+	UAC3_PD_STATE_D2,
+};
+
+int snd_usb_power_domain_set(struct snd_usb_audio *chip,
+			     struct snd_usb_power_domain *pd,
+			     unsigned char state);
+struct snd_usb_power_domain *
+snd_usb_find_power_domain(struct usb_host_interface *ctrl_iface,
+			  unsigned char id);
+
+#ifdef CONFIG_PM
+int snd_usb_autoresume(struct snd_usb_audio *chip);
+void snd_usb_autosuspend(struct snd_usb_audio *chip);
+#else
+static inline int snd_usb_autoresume(struct snd_usb_audio *chip)
+{
+	return 0;
+}
+static inline void snd_usb_autosuspend(struct snd_usb_audio *chip)
+{
+}
+#endif
+
+#endif /* __USBAUDIO_POWER_H */
diff --git a/sound/usb/proc.c b/sound/usb/proc.c
new file mode 100644
index 0000000..0ac89e2
--- /dev/null
+++ b/sound/usb/proc.c
@@ -0,0 +1,178 @@
+/*
+ *   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 <sound/core.h>
+#include <sound/info.h>
+#include <sound/pcm.h>
+
+#include "usbaudio.h"
+#include "helper.h"
+#include "card.h"
+#include "endpoint.h"
+#include "proc.h"
+
+/* convert our full speed USB rate into sampling rate in Hz */
+static inline unsigned get_full_speed_hz(unsigned int usb_rate)
+{
+	return (usb_rate * 125 + (1 << 12)) >> 13;
+}
+
+/* convert our high speed USB rate into sampling rate in Hz */
+static inline unsigned get_high_speed_hz(unsigned int usb_rate)
+{
+	return (usb_rate * 125 + (1 << 9)) >> 10;
+}
+
+/*
+ * common proc files to show the usb device info
+ */
+static void proc_audio_usbbus_read(struct snd_info_entry *entry, struct snd_info_buffer *buffer)
+{
+	struct snd_usb_audio *chip = entry->private_data;
+	if (!atomic_read(&chip->shutdown))
+		snd_iprintf(buffer, "%03d/%03d\n", chip->dev->bus->busnum, chip->dev->devnum);
+}
+
+static void proc_audio_usbid_read(struct snd_info_entry *entry, struct snd_info_buffer *buffer)
+{
+	struct snd_usb_audio *chip = entry->private_data;
+	if (!atomic_read(&chip->shutdown))
+		snd_iprintf(buffer, "%04x:%04x\n", 
+			    USB_ID_VENDOR(chip->usb_id),
+			    USB_ID_PRODUCT(chip->usb_id));
+}
+
+void snd_usb_audio_create_proc(struct snd_usb_audio *chip)
+{
+	struct snd_info_entry *entry;
+	if (!snd_card_proc_new(chip->card, "usbbus", &entry))
+		snd_info_set_text_ops(entry, chip, proc_audio_usbbus_read);
+	if (!snd_card_proc_new(chip->card, "usbid", &entry))
+		snd_info_set_text_ops(entry, chip, proc_audio_usbid_read);
+}
+
+/*
+ * proc interface for list the supported pcm formats
+ */
+static void proc_dump_substream_formats(struct snd_usb_substream *subs, struct snd_info_buffer *buffer)
+{
+	struct audioformat *fp;
+	static char *sync_types[4] = {
+		"NONE", "ASYNC", "ADAPTIVE", "SYNC"
+	};
+
+	list_for_each_entry(fp, &subs->fmt_list, list) {
+		snd_pcm_format_t fmt;
+
+		snd_iprintf(buffer, "  Interface %d\n", fp->iface);
+		snd_iprintf(buffer, "    Altset %d\n", fp->altsetting);
+		snd_iprintf(buffer, "    Format:");
+		for (fmt = 0; fmt <= SNDRV_PCM_FORMAT_LAST; ++fmt)
+			if (fp->formats & pcm_format_to_bits(fmt))
+				snd_iprintf(buffer, " %s",
+					    snd_pcm_format_name(fmt));
+		snd_iprintf(buffer, "\n");
+		snd_iprintf(buffer, "    Channels: %d\n", fp->channels);
+		snd_iprintf(buffer, "    Endpoint: %d %s (%s)\n",
+			    fp->endpoint & USB_ENDPOINT_NUMBER_MASK,
+			    fp->endpoint & USB_DIR_IN ? "IN" : "OUT",
+			    sync_types[(fp->ep_attr & USB_ENDPOINT_SYNCTYPE) >> 2]);
+		if (fp->rates & SNDRV_PCM_RATE_CONTINUOUS) {
+			snd_iprintf(buffer, "    Rates: %d - %d (continuous)\n",
+				    fp->rate_min, fp->rate_max);
+		} else {
+			unsigned int i;
+			snd_iprintf(buffer, "    Rates: ");
+			for (i = 0; i < fp->nr_rates; i++) {
+				if (i > 0)
+					snd_iprintf(buffer, ", ");
+				snd_iprintf(buffer, "%d", fp->rate_table[i]);
+			}
+			snd_iprintf(buffer, "\n");
+		}
+		if (subs->speed != USB_SPEED_FULL)
+			snd_iprintf(buffer, "    Data packet interval: %d us\n",
+				    125 * (1 << fp->datainterval));
+		// snd_iprintf(buffer, "    Max Packet Size = %d\n", fp->maxpacksize);
+		// snd_iprintf(buffer, "    EP Attribute = %#x\n", fp->attributes);
+	}
+}
+
+static void proc_dump_ep_status(struct snd_usb_substream *subs,
+				struct snd_usb_endpoint *data_ep,
+				struct snd_usb_endpoint *sync_ep,
+				struct snd_info_buffer *buffer)
+{
+	if (!data_ep)
+		return;
+	snd_iprintf(buffer, "    Packet Size = %d\n", data_ep->curpacksize);
+	snd_iprintf(buffer, "    Momentary freq = %u Hz (%#x.%04x)\n",
+		    subs->speed == USB_SPEED_FULL
+		    ? get_full_speed_hz(data_ep->freqm)
+		    : get_high_speed_hz(data_ep->freqm),
+		    data_ep->freqm >> 16, data_ep->freqm & 0xffff);
+	if (sync_ep && data_ep->freqshift != INT_MIN) {
+		int res = 16 - data_ep->freqshift;
+		snd_iprintf(buffer, "    Feedback Format = %d.%d\n",
+			    (sync_ep->syncmaxsize > 3 ? 32 : 24) - res, res);
+	}
+}
+
+static void proc_dump_substream_status(struct snd_usb_substream *subs, struct snd_info_buffer *buffer)
+{
+	if (subs->running) {
+		snd_iprintf(buffer, "  Status: Running\n");
+		snd_iprintf(buffer, "    Interface = %d\n", subs->interface);
+		snd_iprintf(buffer, "    Altset = %d\n", subs->altset_idx);
+		proc_dump_ep_status(subs, subs->data_endpoint, subs->sync_endpoint, buffer);
+	} else {
+		snd_iprintf(buffer, "  Status: Stop\n");
+	}
+}
+
+static void proc_pcm_format_read(struct snd_info_entry *entry, struct snd_info_buffer *buffer)
+{
+	struct snd_usb_stream *stream = entry->private_data;
+
+	snd_iprintf(buffer, "%s : %s\n", stream->chip->card->longname, stream->pcm->name);
+
+	if (stream->substream[SNDRV_PCM_STREAM_PLAYBACK].num_formats) {
+		snd_iprintf(buffer, "\nPlayback:\n");
+		proc_dump_substream_status(&stream->substream[SNDRV_PCM_STREAM_PLAYBACK], buffer);
+		proc_dump_substream_formats(&stream->substream[SNDRV_PCM_STREAM_PLAYBACK], buffer);
+	}
+	if (stream->substream[SNDRV_PCM_STREAM_CAPTURE].num_formats) {
+		snd_iprintf(buffer, "\nCapture:\n");
+		proc_dump_substream_status(&stream->substream[SNDRV_PCM_STREAM_CAPTURE], buffer);
+		proc_dump_substream_formats(&stream->substream[SNDRV_PCM_STREAM_CAPTURE], buffer);
+	}
+}
+
+void snd_usb_proc_pcm_format_add(struct snd_usb_stream *stream)
+{
+	struct snd_info_entry *entry;
+	char name[32];
+	struct snd_card *card = stream->chip->card;
+
+	sprintf(name, "stream%d", stream->pcm_index);
+	if (!snd_card_proc_new(card, name, &entry))
+		snd_info_set_text_ops(entry, stream, proc_pcm_format_read);
+}
+
diff --git a/sound/usb/proc.h b/sound/usb/proc.h
new file mode 100644
index 0000000..72b1b2d
--- /dev/null
+++ b/sound/usb/proc.h
@@ -0,0 +1,9 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __USBAUDIO_PROC_H
+#define __USBAUDIO_PROC_H
+
+void snd_usb_audio_create_proc(struct snd_usb_audio *chip);
+void snd_usb_proc_pcm_format_add(struct snd_usb_stream *stream);
+
+#endif /* __USBAUDIO_PROC_H */
+
diff --git a/sound/usb/quirks-table.h b/sound/usb/quirks-table.h
new file mode 100644
index 0000000..1c73b9e
--- /dev/null
+++ b/sound/usb/quirks-table.h
@@ -0,0 +1,3401 @@
+/*
+ * ALSA USB Audio Driver
+ *
+ * Copyright (c) 2002 by Takashi Iwai <tiwai@suse.de>,
+ *                       Clemens Ladisch <clemens@ladisch.de>
+ *
+ *
+ *  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
+ */
+
+/*
+ * The contents of this file are part of the driver's id_table.
+ *
+ * In a perfect world, this file would be empty.
+ */
+
+/*
+ * Use this for devices where other interfaces are standard compliant,
+ * to prevent the quirk being applied to those interfaces. (To work with
+ * hotplugging, bDeviceClass must be set to USB_CLASS_PER_INTERFACE.)
+ */
+#define USB_DEVICE_VENDOR_SPEC(vend, prod) \
+	.match_flags = USB_DEVICE_ID_MATCH_VENDOR | \
+		       USB_DEVICE_ID_MATCH_PRODUCT | \
+		       USB_DEVICE_ID_MATCH_INT_CLASS, \
+	.idVendor = vend, \
+	.idProduct = prod, \
+	.bInterfaceClass = USB_CLASS_VENDOR_SPEC
+
+/* FTDI devices */
+{
+	USB_DEVICE(0x0403, 0xb8d8),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		/* .vendor_name = "STARR LABS", */
+		/* .product_name = "Starr Labs MIDI USB device", */
+		.ifnum = 0,
+		.type = QUIRK_MIDI_FTDI
+	}
+},
+
+{
+	/* Creative BT-D1 */
+	USB_DEVICE(0x041e, 0x0005),
+	.driver_info = (unsigned long) &(const struct snd_usb_audio_quirk) {
+		.ifnum = 1,
+		.type = QUIRK_AUDIO_FIXED_ENDPOINT,
+		.data = &(const struct audioformat) {
+			.formats = SNDRV_PCM_FMTBIT_S16_LE,
+			.channels = 2,
+			.iface = 1,
+			.altsetting = 1,
+			.altset_idx = 1,
+			.endpoint = 0x03,
+			.ep_attr = USB_ENDPOINT_XFER_ISOC,
+			.attributes = 0,
+			.rates = SNDRV_PCM_RATE_CONTINUOUS,
+			.rate_min = 48000,
+			.rate_max = 48000,
+		}
+	}
+},
+
+/* Creative/E-Mu devices */
+{
+	USB_DEVICE(0x041e, 0x3010),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.vendor_name = "Creative Labs",
+		.product_name = "Sound Blaster MP3+",
+		.ifnum = QUIRK_NO_INTERFACE
+	}
+},
+/* Creative/Toshiba Multimedia Center SB-0500 */
+{
+	USB_DEVICE(0x041e, 0x3048),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.vendor_name = "Toshiba",
+		.product_name = "SB-0500",
+		.ifnum = QUIRK_NO_INTERFACE
+	}
+},
+{
+	/* E-Mu 0202 USB */
+	.match_flags = USB_DEVICE_ID_MATCH_DEVICE,
+	.idVendor = 0x041e,
+	.idProduct = 0x3f02,
+	.bInterfaceClass = USB_CLASS_AUDIO,
+},
+{
+	/* E-Mu 0404 USB */
+	.match_flags = USB_DEVICE_ID_MATCH_DEVICE,
+	.idVendor = 0x041e,
+	.idProduct = 0x3f04,
+	.bInterfaceClass = USB_CLASS_AUDIO,
+},
+{
+	/* E-Mu Tracker Pre */
+	.match_flags = USB_DEVICE_ID_MATCH_DEVICE,
+	.idVendor = 0x041e,
+	.idProduct = 0x3f0a,
+	.bInterfaceClass = USB_CLASS_AUDIO,
+},
+{
+	/* E-Mu 0204 USB */
+	.match_flags = USB_DEVICE_ID_MATCH_DEVICE,
+	.idVendor = 0x041e,
+	.idProduct = 0x3f19,
+	.bInterfaceClass = USB_CLASS_AUDIO,
+},
+
+/*
+ * HP Wireless Audio
+ * When not ignored, causes instability issues for some users, forcing them to
+ * blacklist the entire module.
+ */
+{
+	USB_DEVICE(0x0424, 0xb832),
+	.driver_info = (unsigned long) &(const struct snd_usb_audio_quirk) {
+		.vendor_name = "Standard Microsystems Corp.",
+		.product_name = "HP Wireless Audio",
+		.ifnum = QUIRK_ANY_INTERFACE,
+		.type = QUIRK_COMPOSITE,
+		.data = (const struct snd_usb_audio_quirk[]) {
+			/* Mixer */
+			{
+				.ifnum = 0,
+				.type = QUIRK_IGNORE_INTERFACE,
+			},
+			/* Playback */
+			{
+				.ifnum = 1,
+				.type = QUIRK_IGNORE_INTERFACE,
+			},
+			/* Capture */
+			{
+				.ifnum = 2,
+				.type = QUIRK_IGNORE_INTERFACE,
+			},
+			/* HID Device, .ifnum = 3 */
+			{
+				.ifnum = -1,
+			}
+		}
+	}
+},
+
+/*
+ * Logitech QuickCam: bDeviceClass is vendor-specific, so generic interface
+ * class matches do not take effect without an explicit ID match.
+ */
+{
+	.match_flags = USB_DEVICE_ID_MATCH_DEVICE |
+		       USB_DEVICE_ID_MATCH_INT_CLASS |
+		       USB_DEVICE_ID_MATCH_INT_SUBCLASS,
+	.idVendor = 0x046d,
+	.idProduct = 0x0850,
+	.bInterfaceClass = USB_CLASS_AUDIO,
+	.bInterfaceSubClass = USB_SUBCLASS_AUDIOCONTROL
+},
+{
+	.match_flags = USB_DEVICE_ID_MATCH_DEVICE |
+		       USB_DEVICE_ID_MATCH_INT_CLASS |
+		       USB_DEVICE_ID_MATCH_INT_SUBCLASS,
+	.idVendor = 0x046d,
+	.idProduct = 0x08ae,
+	.bInterfaceClass = USB_CLASS_AUDIO,
+	.bInterfaceSubClass = USB_SUBCLASS_AUDIOCONTROL
+},
+{
+	.match_flags = USB_DEVICE_ID_MATCH_DEVICE |
+		       USB_DEVICE_ID_MATCH_INT_CLASS |
+		       USB_DEVICE_ID_MATCH_INT_SUBCLASS,
+	.idVendor = 0x046d,
+	.idProduct = 0x08c6,
+	.bInterfaceClass = USB_CLASS_AUDIO,
+	.bInterfaceSubClass = USB_SUBCLASS_AUDIOCONTROL
+},
+{
+	.match_flags = USB_DEVICE_ID_MATCH_DEVICE |
+		       USB_DEVICE_ID_MATCH_INT_CLASS |
+		       USB_DEVICE_ID_MATCH_INT_SUBCLASS,
+	.idVendor = 0x046d,
+	.idProduct = 0x08f0,
+	.bInterfaceClass = USB_CLASS_AUDIO,
+	.bInterfaceSubClass = USB_SUBCLASS_AUDIOCONTROL
+},
+{
+	.match_flags = USB_DEVICE_ID_MATCH_DEVICE |
+		       USB_DEVICE_ID_MATCH_INT_CLASS |
+		       USB_DEVICE_ID_MATCH_INT_SUBCLASS,
+	.idVendor = 0x046d,
+	.idProduct = 0x08f5,
+	.bInterfaceClass = USB_CLASS_AUDIO,
+	.bInterfaceSubClass = USB_SUBCLASS_AUDIOCONTROL
+},
+{
+	.match_flags = USB_DEVICE_ID_MATCH_DEVICE |
+		       USB_DEVICE_ID_MATCH_INT_CLASS |
+		       USB_DEVICE_ID_MATCH_INT_SUBCLASS,
+	.idVendor = 0x046d,
+	.idProduct = 0x08f6,
+	.bInterfaceClass = USB_CLASS_AUDIO,
+	.bInterfaceSubClass = USB_SUBCLASS_AUDIOCONTROL
+},
+{
+	.match_flags = USB_DEVICE_ID_MATCH_DEVICE |
+		       USB_DEVICE_ID_MATCH_INT_CLASS |
+		       USB_DEVICE_ID_MATCH_INT_SUBCLASS,
+	.idVendor = 0x046d,
+	.idProduct = 0x0990,
+	.bInterfaceClass = USB_CLASS_AUDIO,
+	.bInterfaceSubClass = USB_SUBCLASS_AUDIOCONTROL,
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.vendor_name = "Logitech, Inc.",
+		.product_name = "QuickCam Pro 9000",
+		.ifnum = QUIRK_NO_INTERFACE
+	}
+},
+
+/*
+ * Yamaha devices
+ */
+
+#define YAMAHA_DEVICE(id, name) { \
+	USB_DEVICE(0x0499, id), \
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { \
+		.vendor_name = "Yamaha", \
+		.product_name = name, \
+		.ifnum = QUIRK_ANY_INTERFACE, \
+		.type = QUIRK_MIDI_YAMAHA \
+	} \
+}
+#define YAMAHA_INTERFACE(id, intf, name) { \
+	USB_DEVICE_VENDOR_SPEC(0x0499, id), \
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { \
+		.vendor_name = "Yamaha", \
+		.product_name = name, \
+		.ifnum = intf, \
+		.type = QUIRK_MIDI_YAMAHA \
+	} \
+}
+YAMAHA_DEVICE(0x1000, "UX256"),
+YAMAHA_DEVICE(0x1001, "MU1000"),
+YAMAHA_DEVICE(0x1002, "MU2000"),
+YAMAHA_DEVICE(0x1003, "MU500"),
+YAMAHA_INTERFACE(0x1004, 3, "UW500"),
+YAMAHA_DEVICE(0x1005, "MOTIF6"),
+YAMAHA_DEVICE(0x1006, "MOTIF7"),
+YAMAHA_DEVICE(0x1007, "MOTIF8"),
+YAMAHA_DEVICE(0x1008, "UX96"),
+YAMAHA_DEVICE(0x1009, "UX16"),
+YAMAHA_INTERFACE(0x100a, 3, "EOS BX"),
+YAMAHA_DEVICE(0x100c, "UC-MX"),
+YAMAHA_DEVICE(0x100d, "UC-KX"),
+YAMAHA_DEVICE(0x100e, "S08"),
+YAMAHA_DEVICE(0x100f, "CLP-150"),
+YAMAHA_DEVICE(0x1010, "CLP-170"),
+YAMAHA_DEVICE(0x1011, "P-250"),
+YAMAHA_DEVICE(0x1012, "TYROS"),
+YAMAHA_DEVICE(0x1013, "PF-500"),
+YAMAHA_DEVICE(0x1014, "S90"),
+YAMAHA_DEVICE(0x1015, "MOTIF-R"),
+YAMAHA_DEVICE(0x1016, "MDP-5"),
+YAMAHA_DEVICE(0x1017, "CVP-204"),
+YAMAHA_DEVICE(0x1018, "CVP-206"),
+YAMAHA_DEVICE(0x1019, "CVP-208"),
+YAMAHA_DEVICE(0x101a, "CVP-210"),
+YAMAHA_DEVICE(0x101b, "PSR-1100"),
+YAMAHA_DEVICE(0x101c, "PSR-2100"),
+YAMAHA_DEVICE(0x101d, "CLP-175"),
+YAMAHA_DEVICE(0x101e, "PSR-K1"),
+YAMAHA_DEVICE(0x101f, "EZ-J24"),
+YAMAHA_DEVICE(0x1020, "EZ-250i"),
+YAMAHA_DEVICE(0x1021, "MOTIF ES 6"),
+YAMAHA_DEVICE(0x1022, "MOTIF ES 7"),
+YAMAHA_DEVICE(0x1023, "MOTIF ES 8"),
+YAMAHA_DEVICE(0x1024, "CVP-301"),
+YAMAHA_DEVICE(0x1025, "CVP-303"),
+YAMAHA_DEVICE(0x1026, "CVP-305"),
+YAMAHA_DEVICE(0x1027, "CVP-307"),
+YAMAHA_DEVICE(0x1028, "CVP-309"),
+YAMAHA_DEVICE(0x1029, "CVP-309GP"),
+YAMAHA_DEVICE(0x102a, "PSR-1500"),
+YAMAHA_DEVICE(0x102b, "PSR-3000"),
+YAMAHA_DEVICE(0x102e, "ELS-01/01C"),
+YAMAHA_DEVICE(0x1030, "PSR-295/293"),
+YAMAHA_DEVICE(0x1031, "DGX-205/203"),
+YAMAHA_DEVICE(0x1032, "DGX-305"),
+YAMAHA_DEVICE(0x1033, "DGX-505"),
+YAMAHA_DEVICE(0x1034, NULL),
+YAMAHA_DEVICE(0x1035, NULL),
+YAMAHA_DEVICE(0x1036, NULL),
+YAMAHA_DEVICE(0x1037, NULL),
+YAMAHA_DEVICE(0x1038, NULL),
+YAMAHA_DEVICE(0x1039, NULL),
+YAMAHA_DEVICE(0x103a, NULL),
+YAMAHA_DEVICE(0x103b, NULL),
+YAMAHA_DEVICE(0x103c, NULL),
+YAMAHA_DEVICE(0x103d, NULL),
+YAMAHA_DEVICE(0x103e, NULL),
+YAMAHA_DEVICE(0x103f, NULL),
+YAMAHA_DEVICE(0x1040, NULL),
+YAMAHA_DEVICE(0x1041, NULL),
+YAMAHA_DEVICE(0x1042, NULL),
+YAMAHA_DEVICE(0x1043, NULL),
+YAMAHA_DEVICE(0x1044, NULL),
+YAMAHA_DEVICE(0x1045, NULL),
+YAMAHA_INTERFACE(0x104e, 0, NULL),
+YAMAHA_DEVICE(0x104f, NULL),
+YAMAHA_DEVICE(0x1050, NULL),
+YAMAHA_DEVICE(0x1051, NULL),
+YAMAHA_DEVICE(0x1052, NULL),
+YAMAHA_INTERFACE(0x1053, 0, NULL),
+YAMAHA_INTERFACE(0x1054, 0, NULL),
+YAMAHA_DEVICE(0x1055, NULL),
+YAMAHA_DEVICE(0x1056, NULL),
+YAMAHA_DEVICE(0x1057, NULL),
+YAMAHA_DEVICE(0x1058, NULL),
+YAMAHA_DEVICE(0x1059, NULL),
+YAMAHA_DEVICE(0x105a, NULL),
+YAMAHA_DEVICE(0x105b, NULL),
+YAMAHA_DEVICE(0x105c, NULL),
+YAMAHA_DEVICE(0x105d, NULL),
+{
+	USB_DEVICE(0x0499, 0x1503),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		/* .vendor_name = "Yamaha", */
+		/* .product_name = "MOX6/MOX8", */
+		.ifnum = QUIRK_ANY_INTERFACE,
+		.type = QUIRK_COMPOSITE,
+		.data = (const struct snd_usb_audio_quirk[]) {
+			{
+				.ifnum = 1,
+				.type = QUIRK_AUDIO_STANDARD_INTERFACE
+			},
+			{
+				.ifnum = 2,
+				.type = QUIRK_AUDIO_STANDARD_INTERFACE
+			},
+			{
+				.ifnum = 3,
+				.type = QUIRK_MIDI_YAMAHA
+			},
+			{
+				.ifnum = -1
+			}
+		}
+	}
+},
+{
+	USB_DEVICE(0x0499, 0x1507),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		/* .vendor_name = "Yamaha", */
+		/* .product_name = "THR10", */
+		.ifnum = QUIRK_ANY_INTERFACE,
+		.type = QUIRK_COMPOSITE,
+		.data = (const struct snd_usb_audio_quirk[]) {
+			{
+				.ifnum = 1,
+				.type = QUIRK_AUDIO_STANDARD_INTERFACE
+			},
+			{
+				.ifnum = 2,
+				.type = QUIRK_AUDIO_STANDARD_INTERFACE
+			},
+			{
+				.ifnum = 3,
+				.type = QUIRK_MIDI_YAMAHA
+			},
+			{
+				.ifnum = -1
+			}
+		}
+	}
+},
+{
+	USB_DEVICE(0x0499, 0x1509),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		/* .vendor_name = "Yamaha", */
+		/* .product_name = "Steinberg UR22", */
+		.ifnum = QUIRK_ANY_INTERFACE,
+		.type = QUIRK_COMPOSITE,
+		.data = (const struct snd_usb_audio_quirk[]) {
+			{
+				.ifnum = 1,
+				.type = QUIRK_AUDIO_STANDARD_INTERFACE
+			},
+			{
+				.ifnum = 2,
+				.type = QUIRK_AUDIO_STANDARD_INTERFACE
+			},
+			{
+				.ifnum = 3,
+				.type = QUIRK_MIDI_YAMAHA
+			},
+			{
+				.ifnum = 4,
+				.type = QUIRK_IGNORE_INTERFACE
+			},
+			{
+				.ifnum = -1
+			}
+		}
+	}
+},
+{
+	USB_DEVICE(0x0499, 0x150a),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		/* .vendor_name = "Yamaha", */
+		/* .product_name = "THR5A", */
+		.ifnum = QUIRK_ANY_INTERFACE,
+		.type = QUIRK_COMPOSITE,
+		.data = (const struct snd_usb_audio_quirk[]) {
+			{
+				.ifnum = 1,
+				.type = QUIRK_AUDIO_STANDARD_INTERFACE
+			},
+			{
+				.ifnum = 2,
+				.type = QUIRK_AUDIO_STANDARD_INTERFACE
+			},
+			{
+				.ifnum = 3,
+				.type = QUIRK_MIDI_YAMAHA
+			},
+			{
+				.ifnum = -1
+			}
+		}
+	}
+},
+{
+	USB_DEVICE(0x0499, 0x150c),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		/* .vendor_name = "Yamaha", */
+		/* .product_name = "THR10C", */
+		.ifnum = QUIRK_ANY_INTERFACE,
+		.type = QUIRK_COMPOSITE,
+		.data = (const struct snd_usb_audio_quirk[]) {
+			{
+				.ifnum = 1,
+				.type = QUIRK_AUDIO_STANDARD_INTERFACE
+			},
+			{
+				.ifnum = 2,
+				.type = QUIRK_AUDIO_STANDARD_INTERFACE
+			},
+			{
+				.ifnum = 3,
+				.type = QUIRK_MIDI_YAMAHA
+			},
+			{
+				.ifnum = -1
+			}
+		}
+	}
+},
+YAMAHA_DEVICE(0x2000, "DGP-7"),
+YAMAHA_DEVICE(0x2001, "DGP-5"),
+YAMAHA_DEVICE(0x2002, NULL),
+YAMAHA_DEVICE(0x2003, NULL),
+YAMAHA_DEVICE(0x5000, "CS1D"),
+YAMAHA_DEVICE(0x5001, "DSP1D"),
+YAMAHA_DEVICE(0x5002, "DME32"),
+YAMAHA_DEVICE(0x5003, "DM2000"),
+YAMAHA_DEVICE(0x5004, "02R96"),
+YAMAHA_DEVICE(0x5005, "ACU16-C"),
+YAMAHA_DEVICE(0x5006, "NHB32-C"),
+YAMAHA_DEVICE(0x5007, "DM1000"),
+YAMAHA_DEVICE(0x5008, "01V96"),
+YAMAHA_DEVICE(0x5009, "SPX2000"),
+YAMAHA_DEVICE(0x500a, "PM5D"),
+YAMAHA_DEVICE(0x500b, "DME64N"),
+YAMAHA_DEVICE(0x500c, "DME24N"),
+YAMAHA_DEVICE(0x500d, NULL),
+YAMAHA_DEVICE(0x500e, NULL),
+YAMAHA_DEVICE(0x500f, NULL),
+YAMAHA_DEVICE(0x7000, "DTX"),
+YAMAHA_DEVICE(0x7010, "UB99"),
+#undef YAMAHA_DEVICE
+#undef YAMAHA_INTERFACE
+/* this catches most recent vendor-specific Yamaha devices */
+{
+	.match_flags = USB_DEVICE_ID_MATCH_VENDOR |
+	               USB_DEVICE_ID_MATCH_INT_CLASS,
+	.idVendor = 0x0499,
+	.bInterfaceClass = USB_CLASS_VENDOR_SPEC,
+	.driver_info = (unsigned long) &(const struct snd_usb_audio_quirk) {
+		.ifnum = QUIRK_ANY_INTERFACE,
+		.type = QUIRK_AUTODETECT
+	}
+},
+
+/*
+ * Roland/RolandED/Edirol/BOSS devices
+ */
+{
+	USB_DEVICE(0x0582, 0x0000),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.vendor_name = "Roland",
+		.product_name = "UA-100",
+		.ifnum = QUIRK_ANY_INTERFACE,
+		.type = QUIRK_COMPOSITE,
+		.data = (const struct snd_usb_audio_quirk[]) {
+			{
+				.ifnum = 0,
+				.type = QUIRK_AUDIO_FIXED_ENDPOINT,
+				.data = & (const struct audioformat) {
+					.formats = SNDRV_PCM_FMTBIT_S16_LE,
+					.channels = 4,
+					.iface = 0,
+					.altsetting = 1,
+					.altset_idx = 1,
+					.attributes = 0,
+					.endpoint = 0x01,
+					.ep_attr = 0x09,
+					.rates = SNDRV_PCM_RATE_CONTINUOUS,
+					.rate_min = 44100,
+					.rate_max = 44100,
+				}
+			},
+			{
+				.ifnum = 1,
+				.type = QUIRK_AUDIO_FIXED_ENDPOINT,
+				.data = & (const struct audioformat) {
+					.formats = SNDRV_PCM_FMTBIT_S16_LE,
+					.channels = 2,
+					.iface = 1,
+					.altsetting = 1,
+					.altset_idx = 1,
+					.attributes = UAC_EP_CS_ATTR_FILL_MAX,
+					.endpoint = 0x81,
+					.ep_attr = 0x05,
+					.rates = SNDRV_PCM_RATE_CONTINUOUS,
+					.rate_min = 44100,
+					.rate_max = 44100,
+				}
+			},
+			{
+				.ifnum = 2,
+				.type = QUIRK_MIDI_FIXED_ENDPOINT,
+				.data = & (const struct snd_usb_midi_endpoint_info) {
+					.out_cables = 0x0007,
+					.in_cables  = 0x0007
+				}
+			},
+			{
+				.ifnum = -1
+			}
+		}
+	}
+},
+{
+	USB_DEVICE(0x0582, 0x0002),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.vendor_name = "EDIROL",
+		.product_name = "UM-4",
+		.ifnum = QUIRK_ANY_INTERFACE,
+		.type = QUIRK_COMPOSITE,
+		.data = (const struct snd_usb_audio_quirk[]) {
+			{
+				.ifnum = 0,
+				.type = QUIRK_IGNORE_INTERFACE
+			},
+			{
+				.ifnum = 1,
+				.type = QUIRK_IGNORE_INTERFACE
+			},
+			{
+				.ifnum = 2,
+				.type = QUIRK_MIDI_FIXED_ENDPOINT,
+				.data = & (const struct snd_usb_midi_endpoint_info) {
+					.out_cables = 0x000f,
+					.in_cables  = 0x000f
+				}
+			},
+			{
+				.ifnum = -1
+			}
+		}
+	}
+},
+{
+	USB_DEVICE(0x0582, 0x0003),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.vendor_name = "Roland",
+		.product_name = "SC-8850",
+		.ifnum = QUIRK_ANY_INTERFACE,
+		.type = QUIRK_COMPOSITE,
+		.data = (const struct snd_usb_audio_quirk[]) {
+			{
+				.ifnum = 0,
+				.type = QUIRK_IGNORE_INTERFACE
+			},
+			{
+				.ifnum = 1,
+				.type = QUIRK_IGNORE_INTERFACE
+			},
+			{
+				.ifnum = 2,
+				.type = QUIRK_MIDI_FIXED_ENDPOINT,
+				.data = & (const struct snd_usb_midi_endpoint_info) {
+					.out_cables = 0x003f,
+					.in_cables  = 0x003f
+				}
+			},
+			{
+				.ifnum = -1
+			}
+		}
+	}
+},
+{
+	USB_DEVICE(0x0582, 0x0004),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.vendor_name = "Roland",
+		.product_name = "U-8",
+		.ifnum = QUIRK_ANY_INTERFACE,
+		.type = QUIRK_COMPOSITE,
+		.data = (const struct snd_usb_audio_quirk[]) {
+			{
+				.ifnum = 0,
+				.type = QUIRK_IGNORE_INTERFACE
+			},
+			{
+				.ifnum = 1,
+				.type = QUIRK_IGNORE_INTERFACE
+			},
+			{
+				.ifnum = 2,
+				.type = QUIRK_MIDI_FIXED_ENDPOINT,
+				.data = & (const struct snd_usb_midi_endpoint_info) {
+					.out_cables = 0x0005,
+					.in_cables  = 0x0005
+				}
+			},
+			{
+				.ifnum = -1
+			}
+		}
+	}
+},
+{
+	/* Has ID 0x0099 when not in "Advanced Driver" mode.
+	 * The UM-2EX has only one input, but we cannot detect this. */
+	USB_DEVICE(0x0582, 0x0005),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.vendor_name = "EDIROL",
+		.product_name = "UM-2",
+		.ifnum = QUIRK_ANY_INTERFACE,
+		.type = QUIRK_COMPOSITE,
+		.data = (const struct snd_usb_audio_quirk[]) {
+			{
+				.ifnum = 0,
+				.type = QUIRK_IGNORE_INTERFACE
+			},
+			{
+				.ifnum = 1,
+				.type = QUIRK_IGNORE_INTERFACE
+			},
+			{
+				.ifnum = 2,
+				.type = QUIRK_MIDI_FIXED_ENDPOINT,
+				.data = & (const struct snd_usb_midi_endpoint_info) {
+					.out_cables = 0x0003,
+					.in_cables  = 0x0003
+				}
+			},
+			{
+				.ifnum = -1
+			}
+		}
+	}
+},
+{
+	USB_DEVICE(0x0582, 0x0007),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.vendor_name = "Roland",
+		.product_name = "SC-8820",
+		.ifnum = QUIRK_ANY_INTERFACE,
+		.type = QUIRK_COMPOSITE,
+		.data = (const struct snd_usb_audio_quirk[]) {
+			{
+				.ifnum = 0,
+				.type = QUIRK_IGNORE_INTERFACE
+			},
+			{
+				.ifnum = 1,
+				.type = QUIRK_IGNORE_INTERFACE
+			},
+			{
+				.ifnum = 2,
+				.type = QUIRK_MIDI_FIXED_ENDPOINT,
+				.data = & (const struct snd_usb_midi_endpoint_info) {
+					.out_cables = 0x0013,
+					.in_cables  = 0x0013
+				}
+			},
+			{
+				.ifnum = -1
+			}
+		}
+	}
+},
+{
+	USB_DEVICE(0x0582, 0x0008),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.vendor_name = "Roland",
+		.product_name = "PC-300",
+		.ifnum = QUIRK_ANY_INTERFACE,
+		.type = QUIRK_COMPOSITE,
+		.data = (const struct snd_usb_audio_quirk[]) {
+			{
+				.ifnum = 0,
+				.type = QUIRK_IGNORE_INTERFACE
+			},
+			{
+				.ifnum = 1,
+				.type = QUIRK_IGNORE_INTERFACE
+			},
+			{
+				.ifnum = 2,
+				.type = QUIRK_MIDI_FIXED_ENDPOINT,
+				.data = & (const struct snd_usb_midi_endpoint_info) {
+					.out_cables = 0x0001,
+					.in_cables  = 0x0001
+				}
+			},
+			{
+				.ifnum = -1
+			}
+		}
+	}
+},
+{
+	/* has ID 0x009d when not in "Advanced Driver" mode */
+	USB_DEVICE(0x0582, 0x0009),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.vendor_name = "EDIROL",
+		.product_name = "UM-1",
+		.ifnum = QUIRK_ANY_INTERFACE,
+		.type = QUIRK_COMPOSITE,
+		.data = (const struct snd_usb_audio_quirk[]) {
+			{
+				.ifnum = 0,
+				.type = QUIRK_IGNORE_INTERFACE
+			},
+			{
+				.ifnum = 1,
+				.type = QUIRK_IGNORE_INTERFACE
+			},
+			{
+				.ifnum = 2,
+				.type = QUIRK_MIDI_FIXED_ENDPOINT,
+				.data = & (const struct snd_usb_midi_endpoint_info) {
+					.out_cables = 0x0001,
+					.in_cables  = 0x0001
+				}
+			},
+			{
+				.ifnum = -1
+			}
+		}
+	}
+},
+{
+	USB_DEVICE(0x0582, 0x000b),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.vendor_name = "Roland",
+		.product_name = "SK-500",
+		.ifnum = QUIRK_ANY_INTERFACE,
+		.type = QUIRK_COMPOSITE,
+		.data = (const struct snd_usb_audio_quirk[]) {
+			{
+				.ifnum = 0,
+				.type = QUIRK_IGNORE_INTERFACE
+			},
+			{
+				.ifnum = 1,
+				.type = QUIRK_IGNORE_INTERFACE
+			},
+			{
+				.ifnum = 2,
+				.type = QUIRK_MIDI_FIXED_ENDPOINT,
+				.data = & (const struct snd_usb_midi_endpoint_info) {
+					.out_cables = 0x0013,
+					.in_cables  = 0x0013
+				}
+			},
+			{
+				.ifnum = -1
+			}
+		}
+	}
+},
+{
+	/* thanks to Emiliano Grilli <emillo@libero.it>
+	 * for helping researching this data */
+	USB_DEVICE(0x0582, 0x000c),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.vendor_name = "Roland",
+		.product_name = "SC-D70",
+		.ifnum = QUIRK_ANY_INTERFACE,
+		.type = QUIRK_COMPOSITE,
+		.data = (const struct snd_usb_audio_quirk[]) {
+			{
+				.ifnum = 0,
+				.type = QUIRK_AUDIO_STANDARD_INTERFACE
+			},
+			{
+				.ifnum = 1,
+				.type = QUIRK_AUDIO_STANDARD_INTERFACE
+			},
+			{
+				.ifnum = 2,
+				.type = QUIRK_MIDI_FIXED_ENDPOINT,
+				.data = & (const struct snd_usb_midi_endpoint_info) {
+					.out_cables = 0x0007,
+					.in_cables  = 0x0007
+				}
+			},
+			{
+				.ifnum = -1
+			}
+		}
+	}
+},
+{	/*
+	 * This quirk is for the "Advanced Driver" mode of the Edirol UA-5.
+	 * If the advanced mode switch at the back of the unit is off, the
+	 * UA-5 has ID 0x0582/0x0011 and is standard compliant (no quirks),
+	 * but offers only 16-bit PCM.
+	 * In advanced mode, the UA-5 will output S24_3LE samples (two
+	 * channels) at the rate indicated on the front switch, including
+	 * the 96kHz sample rate.
+	 */
+	USB_DEVICE(0x0582, 0x0010),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.vendor_name = "EDIROL",
+		.product_name = "UA-5",
+		.ifnum = QUIRK_ANY_INTERFACE,
+		.type = QUIRK_COMPOSITE,
+		.data = (const struct snd_usb_audio_quirk[]) {
+			{
+				.ifnum = 1,
+				.type = QUIRK_AUDIO_STANDARD_INTERFACE
+			},
+			{
+				.ifnum = 2,
+				.type = QUIRK_AUDIO_STANDARD_INTERFACE
+			},
+			{
+				.ifnum = -1
+			}
+		}
+	}
+},
+{
+	/* has ID 0x0013 when not in "Advanced Driver" mode */
+	USB_DEVICE(0x0582, 0x0012),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.vendor_name = "Roland",
+		.product_name = "XV-5050",
+		.ifnum = 0,
+		.type = QUIRK_MIDI_FIXED_ENDPOINT,
+		.data = & (const struct snd_usb_midi_endpoint_info) {
+			.out_cables = 0x0001,
+			.in_cables  = 0x0001
+		}
+	}
+},
+{
+	/* has ID 0x0015 when not in "Advanced Driver" mode */
+	USB_DEVICE(0x0582, 0x0014),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.vendor_name = "EDIROL",
+		.product_name = "UM-880",
+		.ifnum = 0,
+		.type = QUIRK_MIDI_FIXED_ENDPOINT,
+		.data = & (const struct snd_usb_midi_endpoint_info) {
+			.out_cables = 0x01ff,
+			.in_cables  = 0x01ff
+		}
+	}
+},
+{
+	/* has ID 0x0017 when not in "Advanced Driver" mode */
+	USB_DEVICE(0x0582, 0x0016),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.vendor_name = "EDIROL",
+		.product_name = "SD-90",
+		.ifnum = QUIRK_ANY_INTERFACE,
+		.type = QUIRK_COMPOSITE,
+		.data = (const struct snd_usb_audio_quirk[]) {
+			{
+				.ifnum = 0,
+				.type = QUIRK_AUDIO_STANDARD_INTERFACE
+			},
+			{
+				.ifnum = 1,
+				.type = QUIRK_AUDIO_STANDARD_INTERFACE
+			},
+			{
+				.ifnum = 2,
+				.type = QUIRK_MIDI_FIXED_ENDPOINT,
+				.data = & (const struct snd_usb_midi_endpoint_info) {
+					.out_cables = 0x000f,
+					.in_cables  = 0x000f
+				}
+			},
+			{
+				.ifnum = -1
+			}
+		}
+	}
+},
+{
+	/* has ID 0x001c when not in "Advanced Driver" mode */
+	USB_DEVICE(0x0582, 0x001b),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.vendor_name = "Roland",
+		.product_name = "MMP-2",
+		.ifnum = QUIRK_ANY_INTERFACE,
+		.type = QUIRK_COMPOSITE,
+		.data = (const struct snd_usb_audio_quirk[]) {
+			{
+				.ifnum = 0,
+				.type = QUIRK_IGNORE_INTERFACE
+			},
+			{
+				.ifnum = 1,
+				.type = QUIRK_IGNORE_INTERFACE
+			},
+			{
+				.ifnum = 2,
+				.type = QUIRK_MIDI_FIXED_ENDPOINT,
+				.data = & (const struct snd_usb_midi_endpoint_info) {
+					.out_cables = 0x0001,
+					.in_cables  = 0x0001
+				}
+			},
+			{
+				.ifnum = -1
+			}
+		}
+	}
+},
+{
+	/* has ID 0x001e when not in "Advanced Driver" mode */
+	USB_DEVICE(0x0582, 0x001d),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.vendor_name = "Roland",
+		.product_name = "V-SYNTH",
+		.ifnum = 0,
+		.type = QUIRK_MIDI_FIXED_ENDPOINT,
+		.data = & (const struct snd_usb_midi_endpoint_info) {
+			.out_cables = 0x0001,
+			.in_cables  = 0x0001
+		}
+	}
+},
+{
+	/* has ID 0x0024 when not in "Advanced Driver" mode */
+	USB_DEVICE(0x0582, 0x0023),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.vendor_name = "EDIROL",
+		.product_name = "UM-550",
+		.ifnum = 0,
+		.type = QUIRK_MIDI_FIXED_ENDPOINT,
+		.data = & (const struct snd_usb_midi_endpoint_info) {
+			.out_cables = 0x003f,
+			.in_cables  = 0x003f
+		}
+	}
+},
+{
+	/*
+	 * This quirk is for the "Advanced Driver" mode. If off, the UA-20
+	 * has ID 0x0026 and is standard compliant, but has only 16-bit PCM
+	 * and no MIDI.
+	 */
+	USB_DEVICE(0x0582, 0x0025),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.vendor_name = "EDIROL",
+		.product_name = "UA-20",
+		.ifnum = QUIRK_ANY_INTERFACE,
+		.type = QUIRK_COMPOSITE,
+		.data = (const struct snd_usb_audio_quirk[]) {
+			{
+				.ifnum = 0,
+				.type = QUIRK_IGNORE_INTERFACE
+			},
+			{
+				.ifnum = 1,
+				.type = QUIRK_AUDIO_FIXED_ENDPOINT,
+				.data = & (const struct audioformat) {
+					.formats = SNDRV_PCM_FMTBIT_S24_3LE,
+					.channels = 2,
+					.iface = 1,
+					.altsetting = 1,
+					.altset_idx = 1,
+					.attributes = 0,
+					.endpoint = 0x01,
+					.ep_attr = 0x01,
+					.rates = SNDRV_PCM_RATE_CONTINUOUS,
+					.rate_min = 44100,
+					.rate_max = 44100,
+				}
+			},
+			{
+				.ifnum = 2,
+				.type = QUIRK_AUDIO_FIXED_ENDPOINT,
+				.data = & (const struct audioformat) {
+					.formats = SNDRV_PCM_FMTBIT_S24_3LE,
+					.channels = 2,
+					.iface = 2,
+					.altsetting = 1,
+					.altset_idx = 1,
+					.attributes = 0,
+					.endpoint = 0x82,
+					.ep_attr = 0x01,
+					.rates = SNDRV_PCM_RATE_CONTINUOUS,
+					.rate_min = 44100,
+					.rate_max = 44100,
+				}
+			},
+			{
+				.ifnum = 3,
+				.type = QUIRK_MIDI_FIXED_ENDPOINT,
+				.data = & (const struct snd_usb_midi_endpoint_info) {
+					.out_cables = 0x0001,
+					.in_cables  = 0x0001
+				}
+			},
+			{
+				.ifnum = -1
+			}
+		}
+	}
+},
+{
+	/* has ID 0x0028 when not in "Advanced Driver" mode */
+	USB_DEVICE(0x0582, 0x0027),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.vendor_name = "EDIROL",
+		.product_name = "SD-20",
+		.ifnum = 0,
+		.type = QUIRK_MIDI_FIXED_ENDPOINT,
+		.data = & (const struct snd_usb_midi_endpoint_info) {
+			.out_cables = 0x0003,
+			.in_cables  = 0x0007
+		}
+	}
+},
+{
+	/* has ID 0x002a when not in "Advanced Driver" mode */
+	USB_DEVICE(0x0582, 0x0029),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.vendor_name = "EDIROL",
+		.product_name = "SD-80",
+		.ifnum = 0,
+		.type = QUIRK_MIDI_FIXED_ENDPOINT,
+		.data = & (const struct snd_usb_midi_endpoint_info) {
+			.out_cables = 0x000f,
+			.in_cables  = 0x000f
+		}
+	}
+},
+{	/*
+	 * This quirk is for the "Advanced" modes of the Edirol UA-700.
+	 * If the sample format switch is not in an advanced setting, the
+	 * UA-700 has ID 0x0582/0x002c and is standard compliant (no quirks),
+	 * but offers only 16-bit PCM and no MIDI.
+	 */
+	USB_DEVICE_VENDOR_SPEC(0x0582, 0x002b),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.vendor_name = "EDIROL",
+		.product_name = "UA-700",
+		.ifnum = QUIRK_ANY_INTERFACE,
+		.type = QUIRK_COMPOSITE,
+		.data = (const struct snd_usb_audio_quirk[]) {
+			{
+				.ifnum = 1,
+				.type = QUIRK_AUDIO_EDIROL_UAXX
+			},
+			{
+				.ifnum = 2,
+				.type = QUIRK_AUDIO_EDIROL_UAXX
+			},
+			{
+				.ifnum = 3,
+				.type = QUIRK_AUDIO_EDIROL_UAXX
+			},
+			{
+				.ifnum = -1
+			}
+		}
+	}
+},
+{
+	/* has ID 0x002e when not in "Advanced Driver" mode */
+	USB_DEVICE(0x0582, 0x002d),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.vendor_name = "Roland",
+		.product_name = "XV-2020",
+		.ifnum = 0,
+		.type = QUIRK_MIDI_FIXED_ENDPOINT,
+		.data = & (const struct snd_usb_midi_endpoint_info) {
+			.out_cables = 0x0001,
+			.in_cables  = 0x0001
+		}
+	}
+},
+{
+	/* has ID 0x0030 when not in "Advanced Driver" mode */
+	USB_DEVICE(0x0582, 0x002f),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.vendor_name = "Roland",
+		.product_name = "VariOS",
+		.ifnum = 0,
+		.type = QUIRK_MIDI_FIXED_ENDPOINT,
+		.data = & (const struct snd_usb_midi_endpoint_info) {
+			.out_cables = 0x0007,
+			.in_cables  = 0x0007
+		}
+	}
+},
+{
+	/* has ID 0x0034 when not in "Advanced Driver" mode */
+	USB_DEVICE(0x0582, 0x0033),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.vendor_name = "EDIROL",
+		.product_name = "PCR",
+		.ifnum = 0,
+		.type = QUIRK_MIDI_FIXED_ENDPOINT,
+		.data = & (const struct snd_usb_midi_endpoint_info) {
+			.out_cables = 0x0003,
+			.in_cables  = 0x0007
+		}
+	}
+},
+{
+	/*
+	 * Has ID 0x0038 when not in "Advanced Driver" mode;
+	 * later revisions use IDs 0x0054 and 0x00a2.
+	 */
+	USB_DEVICE(0x0582, 0x0037),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.vendor_name = "Roland",
+		.product_name = "Digital Piano",
+		.ifnum = 0,
+		.type = QUIRK_MIDI_FIXED_ENDPOINT,
+		.data = & (const struct snd_usb_midi_endpoint_info) {
+			.out_cables = 0x0001,
+			.in_cables  = 0x0001
+		}
+	}
+},
+{
+	/*
+	 * This quirk is for the "Advanced Driver" mode.  If off, the GS-10
+	 * has ID 0x003c and is standard compliant, but has only 16-bit PCM
+	 * and no MIDI.
+	 */
+	USB_DEVICE_VENDOR_SPEC(0x0582, 0x003b),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.vendor_name = "BOSS",
+		.product_name = "GS-10",
+		.ifnum = QUIRK_ANY_INTERFACE,
+		.type = QUIRK_COMPOSITE,
+		.data = & (const struct snd_usb_audio_quirk[]) {
+			{
+				.ifnum = 1,
+				.type = QUIRK_AUDIO_STANDARD_INTERFACE
+			},
+			{
+				.ifnum = 2,
+				.type = QUIRK_AUDIO_STANDARD_INTERFACE
+			},
+			{
+				.ifnum = 3,
+				.type = QUIRK_MIDI_STANDARD_INTERFACE
+			},
+			{
+				.ifnum = -1
+			}
+		}
+	}
+},
+{
+	/* has ID 0x0041 when not in "Advanced Driver" mode */
+	USB_DEVICE(0x0582, 0x0040),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.vendor_name = "Roland",
+		.product_name = "GI-20",
+		.ifnum = 0,
+		.type = QUIRK_MIDI_FIXED_ENDPOINT,
+		.data = & (const struct snd_usb_midi_endpoint_info) {
+			.out_cables = 0x0001,
+			.in_cables  = 0x0001
+		}
+	}
+},
+{
+	/* has ID 0x0043 when not in "Advanced Driver" mode */
+	USB_DEVICE(0x0582, 0x0042),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.vendor_name = "Roland",
+		.product_name = "RS-70",
+		.ifnum = 0,
+		.type = QUIRK_MIDI_FIXED_ENDPOINT,
+		.data = & (const struct snd_usb_midi_endpoint_info) {
+			.out_cables = 0x0001,
+			.in_cables  = 0x0001
+		}
+	}
+},
+{
+	/* has ID 0x0049 when not in "Advanced Driver" mode */
+	USB_DEVICE(0x0582, 0x0047),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		/* .vendor_name = "EDIROL", */
+		/* .product_name = "UR-80", */
+		.ifnum = QUIRK_ANY_INTERFACE,
+		.type = QUIRK_COMPOSITE,
+		.data = (const struct snd_usb_audio_quirk[]) {
+			/* in the 96 kHz modes, only interface 1 is there */
+			{
+				.ifnum = 1,
+				.type = QUIRK_AUDIO_STANDARD_INTERFACE
+			},
+			{
+				.ifnum = 2,
+				.type = QUIRK_AUDIO_STANDARD_INTERFACE
+			},
+			{
+				.ifnum = -1
+			}
+		}
+	}
+},
+{
+	/* has ID 0x004a when not in "Advanced Driver" mode */
+	USB_DEVICE(0x0582, 0x0048),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		/* .vendor_name = "EDIROL", */
+		/* .product_name = "UR-80", */
+		.ifnum = 0,
+		.type = QUIRK_MIDI_FIXED_ENDPOINT,
+		.data = & (const struct snd_usb_midi_endpoint_info) {
+			.out_cables = 0x0003,
+			.in_cables  = 0x0007
+		}
+	}
+},
+{
+	/* has ID 0x004e when not in "Advanced Driver" mode */
+	USB_DEVICE(0x0582, 0x004c),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.vendor_name = "EDIROL",
+		.product_name = "PCR-A",
+		.ifnum = QUIRK_ANY_INTERFACE,
+		.type = QUIRK_COMPOSITE,
+		.data = (const struct snd_usb_audio_quirk[]) {
+			{
+				.ifnum = 1,
+				.type = QUIRK_AUDIO_STANDARD_INTERFACE
+			},
+			{
+				.ifnum = 2,
+				.type = QUIRK_AUDIO_STANDARD_INTERFACE
+			},
+			{
+				.ifnum = -1
+			}
+		}
+	}
+},
+{
+	/* has ID 0x004f when not in "Advanced Driver" mode */
+	USB_DEVICE(0x0582, 0x004d),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.vendor_name = "EDIROL",
+		.product_name = "PCR-A",
+		.ifnum = 0,
+		.type = QUIRK_MIDI_FIXED_ENDPOINT,
+		.data = & (const struct snd_usb_midi_endpoint_info) {
+			.out_cables = 0x0003,
+			.in_cables  = 0x0007
+		}
+	}
+},
+{
+	/*
+	 * This quirk is for the "Advanced Driver" mode. If off, the UA-3FX
+	 * is standard compliant, but has only 16-bit PCM.
+	 */
+	USB_DEVICE(0x0582, 0x0050),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.vendor_name = "EDIROL",
+		.product_name = "UA-3FX",
+		.ifnum = QUIRK_ANY_INTERFACE,
+		.type = QUIRK_COMPOSITE,
+		.data = (const struct snd_usb_audio_quirk[]) {
+			{
+				.ifnum = 1,
+				.type = QUIRK_AUDIO_STANDARD_INTERFACE
+			},
+			{
+				.ifnum = 2,
+				.type = QUIRK_AUDIO_STANDARD_INTERFACE
+			},
+			{
+				.ifnum = -1
+			}
+		}
+	}
+},
+{
+	USB_DEVICE(0x0582, 0x0052),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.vendor_name = "EDIROL",
+		.product_name = "UM-1SX",
+		.ifnum = 0,
+		.type = QUIRK_MIDI_STANDARD_INTERFACE
+	}
+},
+{
+	USB_DEVICE(0x0582, 0x0060),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.vendor_name = "Roland",
+		.product_name = "EXR Series",
+		.ifnum = 0,
+		.type = QUIRK_MIDI_STANDARD_INTERFACE
+	}
+},
+{
+	/* has ID 0x0066 when not in "Advanced Driver" mode */
+	USB_DEVICE(0x0582, 0x0064),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		/* .vendor_name = "EDIROL", */
+		/* .product_name = "PCR-1", */
+		.ifnum = QUIRK_ANY_INTERFACE,
+		.type = QUIRK_COMPOSITE,
+		.data = (const struct snd_usb_audio_quirk[]) {
+			{
+				.ifnum = 1,
+				.type = QUIRK_AUDIO_STANDARD_INTERFACE
+			},
+			{
+				.ifnum = 2,
+				.type = QUIRK_AUDIO_STANDARD_INTERFACE
+			},
+			{
+				.ifnum = -1
+			}
+		}
+	}
+},
+{
+	/* has ID 0x0067 when not in "Advanced Driver" mode */
+	USB_DEVICE(0x0582, 0x0065),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		/* .vendor_name = "EDIROL", */
+		/* .product_name = "PCR-1", */
+		.ifnum = 0,
+		.type = QUIRK_MIDI_FIXED_ENDPOINT,
+		.data = & (const struct snd_usb_midi_endpoint_info) {
+			.out_cables = 0x0001,
+			.in_cables  = 0x0003
+		}
+	}
+},
+{
+	/* has ID 0x006e when not in "Advanced Driver" mode */
+	USB_DEVICE(0x0582, 0x006d),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.vendor_name = "Roland",
+		.product_name = "FANTOM-X",
+		.ifnum = 0,
+		.type = QUIRK_MIDI_FIXED_ENDPOINT,
+		.data = & (const struct snd_usb_midi_endpoint_info) {
+			.out_cables = 0x0001,
+			.in_cables  = 0x0001
+		}
+	}
+},
+{	/*
+	 * This quirk is for the "Advanced" modes of the Edirol UA-25.
+	 * If the switch is not in an advanced setting, the UA-25 has
+	 * ID 0x0582/0x0073 and is standard compliant (no quirks), but
+	 * offers only 16-bit PCM at 44.1 kHz and no MIDI.
+	 */
+	USB_DEVICE_VENDOR_SPEC(0x0582, 0x0074),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.vendor_name = "EDIROL",
+		.product_name = "UA-25",
+		.ifnum = QUIRK_ANY_INTERFACE,
+		.type = QUIRK_COMPOSITE,
+		.data = (const struct snd_usb_audio_quirk[]) {
+			{
+				.ifnum = 0,
+				.type = QUIRK_AUDIO_EDIROL_UAXX
+			},
+			{
+				.ifnum = 1,
+				.type = QUIRK_AUDIO_EDIROL_UAXX
+			},
+			{
+				.ifnum = 2,
+				.type = QUIRK_AUDIO_EDIROL_UAXX
+			},
+			{
+				.ifnum = -1
+			}
+		}
+	}
+},
+{
+	/* has ID 0x0076 when not in "Advanced Driver" mode */
+	USB_DEVICE(0x0582, 0x0075),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.vendor_name = "BOSS",
+		.product_name = "DR-880",
+		.ifnum = 0,
+		.type = QUIRK_MIDI_FIXED_ENDPOINT,
+		.data = & (const struct snd_usb_midi_endpoint_info) {
+			.out_cables = 0x0001,
+			.in_cables  = 0x0001
+		}
+	}
+},
+{
+	/* has ID 0x007b when not in "Advanced Driver" mode */
+	USB_DEVICE_VENDOR_SPEC(0x0582, 0x007a),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.vendor_name = "Roland",
+		/* "RD" or "RD-700SX"? */
+		.ifnum = 0,
+		.type = QUIRK_MIDI_FIXED_ENDPOINT,
+		.data = & (const struct snd_usb_midi_endpoint_info) {
+			.out_cables = 0x0003,
+			.in_cables  = 0x0003
+		}
+	}
+},
+{
+	/* has ID 0x0081 when not in "Advanced Driver" mode */
+	USB_DEVICE(0x0582, 0x0080),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.vendor_name = "Roland",
+		.product_name = "G-70",
+		.ifnum = 0,
+		.type = QUIRK_MIDI_FIXED_ENDPOINT,
+		.data = & (const struct snd_usb_midi_endpoint_info) {
+			.out_cables = 0x0001,
+			.in_cables  = 0x0001
+		}
+	}
+},
+{
+	/* has ID 0x008c when not in "Advanced Driver" mode */
+	USB_DEVICE(0x0582, 0x008b),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.vendor_name = "EDIROL",
+		.product_name = "PC-50",
+		.ifnum = 0,
+		.type = QUIRK_MIDI_FIXED_ENDPOINT,
+		.data = & (const struct snd_usb_midi_endpoint_info) {
+			.out_cables = 0x0001,
+			.in_cables  = 0x0001
+		}
+	}
+},
+{
+	/*
+	 * This quirk is for the "Advanced Driver" mode. If off, the UA-4FX
+	 * is standard compliant, but has only 16-bit PCM and no MIDI.
+	 */
+	USB_DEVICE(0x0582, 0x00a3),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.vendor_name = "EDIROL",
+		.product_name = "UA-4FX",
+		.ifnum = QUIRK_ANY_INTERFACE,
+		.type = QUIRK_COMPOSITE,
+		.data = (const struct snd_usb_audio_quirk[]) {
+			{
+				.ifnum = 0,
+				.type = QUIRK_AUDIO_EDIROL_UAXX
+			},
+			{
+				.ifnum = 1,
+				.type = QUIRK_AUDIO_EDIROL_UAXX
+			},
+			{
+				.ifnum = 2,
+				.type = QUIRK_AUDIO_EDIROL_UAXX
+			},
+			{
+				.ifnum = -1
+			}
+		}
+	}
+},
+{
+	/* Edirol M-16DX */
+	USB_DEVICE(0x0582, 0x00c4),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.ifnum = QUIRK_ANY_INTERFACE,
+		.type = QUIRK_COMPOSITE,
+		.data = (const struct snd_usb_audio_quirk[]) {
+			{
+				.ifnum = 0,
+				.type = QUIRK_AUDIO_STANDARD_INTERFACE
+			},
+			{
+				.ifnum = 1,
+				.type = QUIRK_AUDIO_STANDARD_INTERFACE
+			},
+			{
+				.ifnum = 2,
+				.type = QUIRK_MIDI_FIXED_ENDPOINT,
+				.data = & (const struct snd_usb_midi_endpoint_info) {
+					.out_cables = 0x0001,
+					.in_cables  = 0x0001
+				}
+			},
+			{
+				.ifnum = -1
+			}
+		}
+	}
+},
+{
+	/* Advanced modes of the Edirol UA-25EX.
+	 * For the standard mode, UA-25EX has ID 0582:00e7, which
+	 * offers only 16-bit PCM at 44.1 kHz and no MIDI.
+	 */
+	USB_DEVICE_VENDOR_SPEC(0x0582, 0x00e6),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.vendor_name = "EDIROL",
+		.product_name = "UA-25EX",
+		.ifnum = QUIRK_ANY_INTERFACE,
+		.type = QUIRK_COMPOSITE,
+		.data = (const struct snd_usb_audio_quirk[]) {
+			{
+				.ifnum = 0,
+				.type = QUIRK_AUDIO_EDIROL_UAXX
+			},
+			{
+				.ifnum = 1,
+				.type = QUIRK_AUDIO_EDIROL_UAXX
+			},
+			{
+				.ifnum = 2,
+				.type = QUIRK_AUDIO_EDIROL_UAXX
+			},
+			{
+				.ifnum = -1
+			}
+		}
+	}
+},
+{
+	/* Edirol UM-3G */
+	USB_DEVICE_VENDOR_SPEC(0x0582, 0x0108),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.ifnum = 0,
+		.type = QUIRK_MIDI_FIXED_ENDPOINT,
+		.data = & (const struct snd_usb_midi_endpoint_info) {
+			.out_cables = 0x0007,
+			.in_cables  = 0x0007
+		}
+	}
+},
+{
+	/* BOSS ME-25 */
+	USB_DEVICE(0x0582, 0x0113),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.ifnum = QUIRK_ANY_INTERFACE,
+		.type = QUIRK_COMPOSITE,
+		.data = (const struct snd_usb_audio_quirk[]) {
+			{
+				.ifnum = 0,
+				.type = QUIRK_AUDIO_STANDARD_INTERFACE
+			},
+			{
+				.ifnum = 1,
+				.type = QUIRK_AUDIO_STANDARD_INTERFACE
+			},
+			{
+				.ifnum = 2,
+				.type = QUIRK_MIDI_FIXED_ENDPOINT,
+				.data = & (const struct snd_usb_midi_endpoint_info) {
+					.out_cables = 0x0001,
+					.in_cables  = 0x0001
+				}
+			},
+			{
+				.ifnum = -1
+			}
+		}
+	}
+},
+{
+	/* only 44.1 kHz works at the moment */
+	USB_DEVICE(0x0582, 0x0120),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		/* .vendor_name = "Roland", */
+		/* .product_name = "OCTO-CAPTURE", */
+		.ifnum = QUIRK_ANY_INTERFACE,
+		.type = QUIRK_COMPOSITE,
+		.data = (const struct snd_usb_audio_quirk[]) {
+			{
+				.ifnum = 0,
+				.type = QUIRK_AUDIO_FIXED_ENDPOINT,
+				.data = & (const struct audioformat) {
+					.formats = SNDRV_PCM_FMTBIT_S32_LE,
+					.channels = 10,
+					.iface = 0,
+					.altsetting = 1,
+					.altset_idx = 1,
+					.endpoint = 0x05,
+					.ep_attr = 0x05,
+					.rates = SNDRV_PCM_RATE_44100,
+					.rate_min = 44100,
+					.rate_max = 44100,
+					.nr_rates = 1,
+					.rate_table = (unsigned int[]) { 44100 }
+				}
+			},
+			{
+				.ifnum = 1,
+				.type = QUIRK_AUDIO_FIXED_ENDPOINT,
+				.data = & (const struct audioformat) {
+					.formats = SNDRV_PCM_FMTBIT_S32_LE,
+					.channels = 12,
+					.iface = 1,
+					.altsetting = 1,
+					.altset_idx = 1,
+					.endpoint = 0x85,
+					.ep_attr = 0x25,
+					.rates = SNDRV_PCM_RATE_44100,
+					.rate_min = 44100,
+					.rate_max = 44100,
+					.nr_rates = 1,
+					.rate_table = (unsigned int[]) { 44100 }
+				}
+			},
+			{
+				.ifnum = 2,
+				.type = QUIRK_MIDI_FIXED_ENDPOINT,
+				.data = & (const struct snd_usb_midi_endpoint_info) {
+					.out_cables = 0x0001,
+					.in_cables  = 0x0001
+				}
+			},
+			{
+				.ifnum = 3,
+				.type = QUIRK_IGNORE_INTERFACE
+			},
+			{
+				.ifnum = 4,
+				.type = QUIRK_IGNORE_INTERFACE
+			},
+			{
+				.ifnum = -1
+			}
+		}
+	}
+},
+{
+	/* only 44.1 kHz works at the moment */
+	USB_DEVICE(0x0582, 0x012f),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		/* .vendor_name = "Roland", */
+		/* .product_name = "QUAD-CAPTURE", */
+		.ifnum = QUIRK_ANY_INTERFACE,
+		.type = QUIRK_COMPOSITE,
+		.data = (const struct snd_usb_audio_quirk[]) {
+			{
+				.ifnum = 0,
+				.type = QUIRK_AUDIO_FIXED_ENDPOINT,
+				.data = & (const struct audioformat) {
+					.formats = SNDRV_PCM_FMTBIT_S32_LE,
+					.channels = 4,
+					.iface = 0,
+					.altsetting = 1,
+					.altset_idx = 1,
+					.endpoint = 0x05,
+					.ep_attr = 0x05,
+					.rates = SNDRV_PCM_RATE_44100,
+					.rate_min = 44100,
+					.rate_max = 44100,
+					.nr_rates = 1,
+					.rate_table = (unsigned int[]) { 44100 }
+				}
+			},
+			{
+				.ifnum = 1,
+				.type = QUIRK_AUDIO_FIXED_ENDPOINT,
+				.data = & (const struct audioformat) {
+					.formats = SNDRV_PCM_FMTBIT_S32_LE,
+					.channels = 6,
+					.iface = 1,
+					.altsetting = 1,
+					.altset_idx = 1,
+					.endpoint = 0x85,
+					.ep_attr = 0x25,
+					.rates = SNDRV_PCM_RATE_44100,
+					.rate_min = 44100,
+					.rate_max = 44100,
+					.nr_rates = 1,
+					.rate_table = (unsigned int[]) { 44100 }
+				}
+			},
+			{
+				.ifnum = 2,
+				.type = QUIRK_MIDI_FIXED_ENDPOINT,
+				.data = & (const struct snd_usb_midi_endpoint_info) {
+					.out_cables = 0x0001,
+					.in_cables  = 0x0001
+				}
+			},
+			{
+				.ifnum = 3,
+				.type = QUIRK_IGNORE_INTERFACE
+			},
+			{
+				.ifnum = 4,
+				.type = QUIRK_IGNORE_INTERFACE
+			},
+			{
+				.ifnum = -1
+			}
+		}
+	}
+},
+{
+	USB_DEVICE(0x0582, 0x0159),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		/* .vendor_name = "Roland", */
+		/* .product_name = "UA-22", */
+		.ifnum = QUIRK_ANY_INTERFACE,
+		.type = QUIRK_COMPOSITE,
+		.data = (const struct snd_usb_audio_quirk[]) {
+			{
+				.ifnum = 0,
+				.type = QUIRK_AUDIO_STANDARD_INTERFACE
+			},
+			{
+				.ifnum = 1,
+				.type = QUIRK_AUDIO_STANDARD_INTERFACE
+			},
+			{
+				.ifnum = 2,
+				.type = QUIRK_MIDI_FIXED_ENDPOINT,
+				.data = & (const struct snd_usb_midi_endpoint_info) {
+					.out_cables = 0x0001,
+					.in_cables = 0x0001
+				}
+			},
+			{
+				.ifnum = -1
+			}
+		}
+	}
+},
+/* this catches most recent vendor-specific Roland devices */
+{
+	.match_flags = USB_DEVICE_ID_MATCH_VENDOR |
+	               USB_DEVICE_ID_MATCH_INT_CLASS,
+	.idVendor = 0x0582,
+	.bInterfaceClass = USB_CLASS_VENDOR_SPEC,
+	.driver_info = (unsigned long) &(const struct snd_usb_audio_quirk) {
+		.ifnum = QUIRK_ANY_INTERFACE,
+		.type = QUIRK_AUTODETECT
+	}
+},
+
+/* Guillemot devices */
+{
+	/*
+	 * This is for the "Windows Edition" where the external MIDI ports are
+	 * the only MIDI ports; the control data is reported through HID
+	 * interfaces.  The "Macintosh Edition" has ID 0xd002 and uses standard
+	 * compliant USB MIDI ports for external MIDI and controls.
+	 */
+	USB_DEVICE_VENDOR_SPEC(0x06f8, 0xb000),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.vendor_name = "Hercules",
+		.product_name = "DJ Console (WE)",
+		.ifnum = 4,
+		.type = QUIRK_MIDI_FIXED_ENDPOINT,
+		.data = & (const struct snd_usb_midi_endpoint_info) {
+			.out_cables = 0x0001,
+			.in_cables = 0x0001
+		}
+	}
+},
+
+/* Midiman/M-Audio devices */
+{
+	USB_DEVICE_VENDOR_SPEC(0x0763, 0x1002),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.vendor_name = "M-Audio",
+		.product_name = "MidiSport 2x2",
+		.ifnum = QUIRK_ANY_INTERFACE,
+		.type = QUIRK_MIDI_MIDIMAN,
+		.data = & (const struct snd_usb_midi_endpoint_info) {
+			.out_cables = 0x0003,
+			.in_cables  = 0x0003
+		}
+	}
+},
+{
+	USB_DEVICE_VENDOR_SPEC(0x0763, 0x1011),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.vendor_name = "M-Audio",
+		.product_name = "MidiSport 1x1",
+		.ifnum = QUIRK_ANY_INTERFACE,
+		.type = QUIRK_MIDI_MIDIMAN,
+		.data = & (const struct snd_usb_midi_endpoint_info) {
+			.out_cables = 0x0001,
+			.in_cables  = 0x0001
+		}
+	}
+},
+{
+	USB_DEVICE_VENDOR_SPEC(0x0763, 0x1015),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.vendor_name = "M-Audio",
+		.product_name = "Keystation",
+		.ifnum = QUIRK_ANY_INTERFACE,
+		.type = QUIRK_MIDI_MIDIMAN,
+		.data = & (const struct snd_usb_midi_endpoint_info) {
+			.out_cables = 0x0001,
+			.in_cables  = 0x0001
+		}
+	}
+},
+{
+	USB_DEVICE_VENDOR_SPEC(0x0763, 0x1021),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.vendor_name = "M-Audio",
+		.product_name = "MidiSport 4x4",
+		.ifnum = QUIRK_ANY_INTERFACE,
+		.type = QUIRK_MIDI_MIDIMAN,
+		.data = & (const struct snd_usb_midi_endpoint_info) {
+			.out_cables = 0x000f,
+			.in_cables  = 0x000f
+		}
+	}
+},
+{
+	/*
+	 * For hardware revision 1.05; in the later revisions (1.10 and
+	 * 1.21), 0x1031 is the ID for the device without firmware.
+	 * Thanks to Olaf Giesbrecht <Olaf_Giesbrecht@yahoo.de>
+	 */
+	USB_DEVICE_VER(0x0763, 0x1031, 0x0100, 0x0109),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.vendor_name = "M-Audio",
+		.product_name = "MidiSport 8x8",
+		.ifnum = QUIRK_ANY_INTERFACE,
+		.type = QUIRK_MIDI_MIDIMAN,
+		.data = & (const struct snd_usb_midi_endpoint_info) {
+			.out_cables = 0x01ff,
+			.in_cables  = 0x01ff
+		}
+	}
+},
+{
+	USB_DEVICE_VENDOR_SPEC(0x0763, 0x1033),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.vendor_name = "M-Audio",
+		.product_name = "MidiSport 8x8",
+		.ifnum = QUIRK_ANY_INTERFACE,
+		.type = QUIRK_MIDI_MIDIMAN,
+		.data = & (const struct snd_usb_midi_endpoint_info) {
+			.out_cables = 0x01ff,
+			.in_cables  = 0x01ff
+		}
+	}
+},
+{
+	USB_DEVICE_VENDOR_SPEC(0x0763, 0x1041),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.vendor_name = "M-Audio",
+		.product_name = "MidiSport 2x4",
+		.ifnum = QUIRK_ANY_INTERFACE,
+		.type = QUIRK_MIDI_MIDIMAN,
+		.data = & (const struct snd_usb_midi_endpoint_info) {
+			.out_cables = 0x000f,
+			.in_cables  = 0x0003
+		}
+	}
+},
+{
+	USB_DEVICE_VENDOR_SPEC(0x0763, 0x2001),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.vendor_name = "M-Audio",
+		.product_name = "Quattro",
+		.ifnum = QUIRK_ANY_INTERFACE,
+		.type = QUIRK_COMPOSITE,
+		.data = & (const struct snd_usb_audio_quirk[]) {
+			/*
+			 * Interfaces 0-2 are "Windows-compatible", 16-bit only,
+			 * and share endpoints with the other interfaces.
+			 * Ignore them.  The other interfaces can do 24 bits,
+			 * but captured samples are big-endian (see usbaudio.c).
+			 */
+			{
+				.ifnum = 0,
+				.type = QUIRK_IGNORE_INTERFACE
+			},
+			{
+				.ifnum = 1,
+				.type = QUIRK_IGNORE_INTERFACE
+			},
+			{
+				.ifnum = 2,
+				.type = QUIRK_IGNORE_INTERFACE
+			},
+			{
+				.ifnum = 3,
+				.type = QUIRK_IGNORE_INTERFACE
+			},
+			{
+				.ifnum = 4,
+				.type = QUIRK_AUDIO_STANDARD_INTERFACE
+			},
+			{
+				.ifnum = 5,
+				.type = QUIRK_AUDIO_STANDARD_INTERFACE
+			},
+			{
+				.ifnum = 6,
+				.type = QUIRK_IGNORE_INTERFACE
+			},
+			{
+				.ifnum = 7,
+				.type = QUIRK_AUDIO_STANDARD_INTERFACE
+			},
+			{
+				.ifnum = 8,
+				.type = QUIRK_AUDIO_STANDARD_INTERFACE
+			},
+			{
+				.ifnum = 9,
+				.type = QUIRK_MIDI_MIDIMAN,
+				.data = & (const struct snd_usb_midi_endpoint_info) {
+					.out_cables = 0x0001,
+					.in_cables  = 0x0001
+				}
+			},
+			{
+				.ifnum = -1
+			}
+		}
+	}
+},
+{
+	USB_DEVICE_VENDOR_SPEC(0x0763, 0x2003),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.vendor_name = "M-Audio",
+		.product_name = "AudioPhile",
+		.ifnum = 6,
+		.type = QUIRK_MIDI_MIDIMAN,
+		.data = & (const struct snd_usb_midi_endpoint_info) {
+			.out_cables = 0x0001,
+			.in_cables  = 0x0001
+		}
+	}
+},
+{
+	USB_DEVICE_VENDOR_SPEC(0x0763, 0x2008),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.vendor_name = "M-Audio",
+		.product_name = "Ozone",
+		.ifnum = 3,
+		.type = QUIRK_MIDI_MIDIMAN,
+		.data = & (const struct snd_usb_midi_endpoint_info) {
+			.out_cables = 0x0001,
+			.in_cables  = 0x0001
+		}
+	}
+},
+{
+	USB_DEVICE_VENDOR_SPEC(0x0763, 0x200d),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.vendor_name = "M-Audio",
+		.product_name = "OmniStudio",
+		.ifnum = QUIRK_ANY_INTERFACE,
+		.type = QUIRK_COMPOSITE,
+		.data = & (const struct snd_usb_audio_quirk[]) {
+			{
+				.ifnum = 0,
+				.type = QUIRK_IGNORE_INTERFACE
+			},
+			{
+				.ifnum = 1,
+				.type = QUIRK_IGNORE_INTERFACE
+			},
+			{
+				.ifnum = 2,
+				.type = QUIRK_IGNORE_INTERFACE
+			},
+			{
+				.ifnum = 3,
+				.type = QUIRK_IGNORE_INTERFACE
+			},
+			{
+				.ifnum = 4,
+				.type = QUIRK_AUDIO_STANDARD_INTERFACE
+			},
+			{
+				.ifnum = 5,
+				.type = QUIRK_AUDIO_STANDARD_INTERFACE
+			},
+			{
+				.ifnum = 6,
+				.type = QUIRK_IGNORE_INTERFACE
+			},
+			{
+				.ifnum = 7,
+				.type = QUIRK_AUDIO_STANDARD_INTERFACE
+			},
+			{
+				.ifnum = 8,
+				.type = QUIRK_AUDIO_STANDARD_INTERFACE
+			},
+			{
+				.ifnum = 9,
+				.type = QUIRK_MIDI_MIDIMAN,
+				.data = & (const struct snd_usb_midi_endpoint_info) {
+					.out_cables = 0x0001,
+					.in_cables  = 0x0001
+				}
+			},
+			{
+				.ifnum = -1
+			}
+		}
+	}
+},
+{
+	USB_DEVICE(0x0763, 0x2019),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		/* .vendor_name = "M-Audio", */
+		/* .product_name = "Ozone Academic", */
+		.ifnum = QUIRK_ANY_INTERFACE,
+		.type = QUIRK_COMPOSITE,
+		.data = & (const struct snd_usb_audio_quirk[]) {
+			{
+				.ifnum = 0,
+				.type = QUIRK_AUDIO_STANDARD_INTERFACE
+			},
+			{
+				.ifnum = 1,
+				.type = QUIRK_AUDIO_STANDARD_INTERFACE
+			},
+			{
+				.ifnum = 2,
+				.type = QUIRK_AUDIO_STANDARD_INTERFACE
+			},
+			{
+				.ifnum = 3,
+				.type = QUIRK_MIDI_MIDIMAN,
+				.data = & (const struct snd_usb_midi_endpoint_info) {
+					.out_cables = 0x0001,
+					.in_cables  = 0x0001
+				}
+			},
+			{
+				.ifnum = -1
+			}
+		}
+	}
+},
+{
+	USB_DEVICE_VENDOR_SPEC(0x0763, 0x2030),
+	.driver_info = (unsigned long) &(const struct snd_usb_audio_quirk) {
+		/* .vendor_name = "M-Audio", */
+		/* .product_name = "Fast Track C400", */
+		.ifnum = QUIRK_ANY_INTERFACE,
+		.type = QUIRK_COMPOSITE,
+		.data = &(const struct snd_usb_audio_quirk[]) {
+			{
+				.ifnum = 1,
+				.type = QUIRK_AUDIO_STANDARD_MIXER,
+			},
+			/* Playback */
+			{
+				.ifnum = 2,
+				.type = QUIRK_AUDIO_FIXED_ENDPOINT,
+				.data = &(const struct audioformat) {
+					.formats = SNDRV_PCM_FMTBIT_S24_3LE,
+					.channels = 6,
+					.iface = 2,
+					.altsetting = 1,
+					.altset_idx = 1,
+					.attributes = UAC_EP_CS_ATTR_SAMPLE_RATE,
+					.endpoint = 0x01,
+					.ep_attr = 0x09,
+					.rates = SNDRV_PCM_RATE_44100 |
+						 SNDRV_PCM_RATE_48000 |
+						 SNDRV_PCM_RATE_88200 |
+						 SNDRV_PCM_RATE_96000,
+					.rate_min = 44100,
+					.rate_max = 96000,
+					.nr_rates = 4,
+					.rate_table = (unsigned int[]) {
+							44100, 48000, 88200, 96000
+					},
+					.clock = 0x80,
+				}
+			},
+			/* Capture */
+			{
+				.ifnum = 3,
+				.type = QUIRK_AUDIO_FIXED_ENDPOINT,
+				.data = &(const struct audioformat) {
+					.formats = SNDRV_PCM_FMTBIT_S24_3LE,
+					.channels = 4,
+					.iface = 3,
+					.altsetting = 1,
+					.altset_idx = 1,
+					.attributes = UAC_EP_CS_ATTR_SAMPLE_RATE,
+					.endpoint = 0x81,
+					.ep_attr = 0x05,
+					.rates = SNDRV_PCM_RATE_44100 |
+						 SNDRV_PCM_RATE_48000 |
+						 SNDRV_PCM_RATE_88200 |
+						 SNDRV_PCM_RATE_96000,
+					.rate_min = 44100,
+					.rate_max = 96000,
+					.nr_rates = 4,
+					.rate_table = (unsigned int[]) {
+						44100, 48000, 88200, 96000
+					},
+					.clock = 0x80,
+				}
+			},
+			/* MIDI */
+			{
+				.ifnum = -1 /* Interface = 4 */
+			}
+		}
+	}
+},
+{
+	USB_DEVICE_VENDOR_SPEC(0x0763, 0x2031),
+	.driver_info = (unsigned long) &(const struct snd_usb_audio_quirk) {
+		/* .vendor_name = "M-Audio", */
+		/* .product_name = "Fast Track C600", */
+		.ifnum = QUIRK_ANY_INTERFACE,
+		.type = QUIRK_COMPOSITE,
+		.data = &(const struct snd_usb_audio_quirk[]) {
+			{
+				.ifnum = 1,
+				.type = QUIRK_AUDIO_STANDARD_MIXER,
+			},
+			/* Playback */
+			{
+				.ifnum = 2,
+				.type = QUIRK_AUDIO_FIXED_ENDPOINT,
+				.data = &(const struct audioformat) {
+					.formats = SNDRV_PCM_FMTBIT_S24_3LE,
+					.channels = 8,
+					.iface = 2,
+					.altsetting = 1,
+					.altset_idx = 1,
+					.attributes = UAC_EP_CS_ATTR_SAMPLE_RATE,
+					.endpoint = 0x01,
+					.ep_attr = 0x09,
+					.rates = SNDRV_PCM_RATE_44100 |
+						 SNDRV_PCM_RATE_48000 |
+						 SNDRV_PCM_RATE_88200 |
+						 SNDRV_PCM_RATE_96000,
+					.rate_min = 44100,
+					.rate_max = 96000,
+					.nr_rates = 4,
+					.rate_table = (unsigned int[]) {
+							44100, 48000, 88200, 96000
+					},
+					.clock = 0x80,
+				}
+			},
+			/* Capture */
+			{
+				.ifnum = 3,
+				.type = QUIRK_AUDIO_FIXED_ENDPOINT,
+				.data = &(const struct audioformat) {
+					.formats = SNDRV_PCM_FMTBIT_S24_3LE,
+					.channels = 6,
+					.iface = 3,
+					.altsetting = 1,
+					.altset_idx = 1,
+					.attributes = UAC_EP_CS_ATTR_SAMPLE_RATE,
+					.endpoint = 0x81,
+					.ep_attr = 0x05,
+					.rates = SNDRV_PCM_RATE_44100 |
+						 SNDRV_PCM_RATE_48000 |
+						 SNDRV_PCM_RATE_88200 |
+						 SNDRV_PCM_RATE_96000,
+					.rate_min = 44100,
+					.rate_max = 96000,
+					.nr_rates = 4,
+					.rate_table = (unsigned int[]) {
+						44100, 48000, 88200, 96000
+					},
+					.clock = 0x80,
+				}
+			},
+			/* MIDI */
+			{
+				.ifnum = -1 /* Interface = 4 */
+			}
+		}
+	}
+},
+{
+	USB_DEVICE_VENDOR_SPEC(0x0763, 0x2080),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		/* .vendor_name = "M-Audio", */
+		/* .product_name = "Fast Track Ultra", */
+		.ifnum = QUIRK_ANY_INTERFACE,
+		.type = QUIRK_COMPOSITE,
+		.data = & (const struct snd_usb_audio_quirk[]) {
+			{
+				.ifnum = 0,
+				.type = QUIRK_AUDIO_STANDARD_MIXER,
+			},
+			{
+				.ifnum = 1,
+				.type = QUIRK_AUDIO_FIXED_ENDPOINT,
+				.data = & (const struct audioformat) {
+					.formats = SNDRV_PCM_FMTBIT_S24_3LE,
+					.channels = 8,
+					.iface = 1,
+					.altsetting = 1,
+					.altset_idx = 1,
+					.attributes = UAC_EP_CS_ATTR_SAMPLE_RATE,
+					.endpoint = 0x01,
+					.ep_attr = 0x09,
+					.rates = SNDRV_PCM_RATE_44100 |
+						 SNDRV_PCM_RATE_48000 |
+						 SNDRV_PCM_RATE_88200 |
+						 SNDRV_PCM_RATE_96000,
+					.rate_min = 44100,
+					.rate_max = 96000,
+					.nr_rates = 4,
+					.rate_table = (unsigned int[]) {
+						44100, 48000, 88200, 96000
+					}
+				}
+			},
+			{
+				.ifnum = 2,
+				.type = QUIRK_AUDIO_FIXED_ENDPOINT,
+				.data = & (const struct audioformat) {
+					.formats = SNDRV_PCM_FMTBIT_S24_3LE,
+					.channels = 8,
+					.iface = 2,
+					.altsetting = 1,
+					.altset_idx = 1,
+					.attributes = UAC_EP_CS_ATTR_SAMPLE_RATE,
+					.endpoint = 0x81,
+					.ep_attr = 0x05,
+					.rates = SNDRV_PCM_RATE_44100 |
+						 SNDRV_PCM_RATE_48000 |
+						 SNDRV_PCM_RATE_88200 |
+						 SNDRV_PCM_RATE_96000,
+					.rate_min = 44100,
+					.rate_max = 96000,
+					.nr_rates = 4,
+					.rate_table = (unsigned int[]) {
+						44100, 48000, 88200, 96000
+					}
+				}
+			},
+			/* interface 3 (MIDI) is standard compliant */
+			{
+				.ifnum = -1
+			}
+		}
+	}
+},
+{
+	USB_DEVICE_VENDOR_SPEC(0x0763, 0x2081),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		/* .vendor_name = "M-Audio", */
+		/* .product_name = "Fast Track Ultra 8R", */
+		.ifnum = QUIRK_ANY_INTERFACE,
+		.type = QUIRK_COMPOSITE,
+		.data = & (const struct snd_usb_audio_quirk[]) {
+			{
+				.ifnum = 0,
+				.type = QUIRK_AUDIO_STANDARD_MIXER,
+			},
+			{
+				.ifnum = 1,
+				.type = QUIRK_AUDIO_FIXED_ENDPOINT,
+				.data = & (const struct audioformat) {
+					.formats = SNDRV_PCM_FMTBIT_S24_3LE,
+					.channels = 8,
+					.iface = 1,
+					.altsetting = 1,
+					.altset_idx = 1,
+					.attributes = UAC_EP_CS_ATTR_SAMPLE_RATE,
+					.endpoint = 0x01,
+					.ep_attr = 0x09,
+					.rates = SNDRV_PCM_RATE_44100 |
+						 SNDRV_PCM_RATE_48000 |
+						 SNDRV_PCM_RATE_88200 |
+						 SNDRV_PCM_RATE_96000,
+					.rate_min = 44100,
+					.rate_max = 96000,
+					.nr_rates = 4,
+					.rate_table = (unsigned int[]) {
+							44100, 48000, 88200, 96000
+					}
+				}
+			},
+			{
+				.ifnum = 2,
+				.type = QUIRK_AUDIO_FIXED_ENDPOINT,
+				.data = & (const struct audioformat) {
+					.formats = SNDRV_PCM_FMTBIT_S24_3LE,
+					.channels = 8,
+					.iface = 2,
+					.altsetting = 1,
+					.altset_idx = 1,
+					.attributes = UAC_EP_CS_ATTR_SAMPLE_RATE,
+					.endpoint = 0x81,
+					.ep_attr = 0x05,
+					.rates = SNDRV_PCM_RATE_44100 |
+						 SNDRV_PCM_RATE_48000 |
+						 SNDRV_PCM_RATE_88200 |
+						 SNDRV_PCM_RATE_96000,
+					.rate_min = 44100,
+					.rate_max = 96000,
+					.nr_rates = 4,
+					.rate_table = (unsigned int[]) {
+						44100, 48000, 88200, 96000
+					}
+				}
+			},
+			/* interface 3 (MIDI) is standard compliant */
+			{
+				.ifnum = -1
+			}
+		}
+	}
+},
+
+/* Casio devices */
+{
+	USB_DEVICE(0x07cf, 0x6801),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.vendor_name = "Casio",
+		.product_name = "PL-40R",
+		.ifnum = 0,
+		.type = QUIRK_MIDI_YAMAHA
+	}
+},
+{
+	/* this ID is used by several devices without a product ID */
+	USB_DEVICE(0x07cf, 0x6802),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.vendor_name = "Casio",
+		.product_name = "Keyboard",
+		.ifnum = 0,
+		.type = QUIRK_MIDI_YAMAHA
+	}
+},
+
+/* Mark of the Unicorn devices */
+{
+	/* thanks to Robert A. Lerche <ral 'at' msbit.com> */
+	.match_flags = USB_DEVICE_ID_MATCH_VENDOR |
+		       USB_DEVICE_ID_MATCH_PRODUCT |
+		       USB_DEVICE_ID_MATCH_DEV_SUBCLASS,
+	.idVendor = 0x07fd,
+	.idProduct = 0x0001,
+	.bDeviceSubClass = 2,
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.vendor_name = "MOTU",
+		.product_name = "Fastlane",
+		.ifnum = QUIRK_ANY_INTERFACE,
+		.type = QUIRK_COMPOSITE,
+		.data = & (const struct snd_usb_audio_quirk[]) {
+			{
+				.ifnum = 0,
+				.type = QUIRK_MIDI_RAW_BYTES
+			},
+			{
+				.ifnum = 1,
+				.type = QUIRK_IGNORE_INTERFACE
+			},
+			{
+				.ifnum = -1
+			}
+		}
+	}
+},
+
+/* Emagic devices */
+{
+	USB_DEVICE(0x086a, 0x0001),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.vendor_name = "Emagic",
+		/* .product_name = "Unitor8", */
+		.ifnum = 2,
+		.type = QUIRK_MIDI_EMAGIC,
+		.data = & (const struct snd_usb_midi_endpoint_info) {
+			.out_cables = 0x80ff,
+			.in_cables  = 0x80ff
+		}
+	}
+},
+{
+	USB_DEVICE(0x086a, 0x0002),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.vendor_name = "Emagic",
+		/* .product_name = "AMT8", */
+		.ifnum = 2,
+		.type = QUIRK_MIDI_EMAGIC,
+		.data = & (const struct snd_usb_midi_endpoint_info) {
+			.out_cables = 0x80ff,
+			.in_cables  = 0x80ff
+		}
+	}
+},
+{
+	USB_DEVICE(0x086a, 0x0003),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.vendor_name = "Emagic",
+		/* .product_name = "MT4", */
+		.ifnum = 2,
+		.type = QUIRK_MIDI_EMAGIC,
+		.data = & (const struct snd_usb_midi_endpoint_info) {
+			.out_cables = 0x800f,
+			.in_cables  = 0x8003
+		}
+	}
+},
+
+/* KORG devices */
+{
+	USB_DEVICE_VENDOR_SPEC(0x0944, 0x0200),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.vendor_name = "KORG, Inc.",
+		/* .product_name = "PANDORA PX5D", */
+		.ifnum = 3,
+		.type = QUIRK_MIDI_STANDARD_INTERFACE,
+	}
+},
+
+{
+	USB_DEVICE_VENDOR_SPEC(0x0944, 0x0201),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.vendor_name = "KORG, Inc.",
+		/* .product_name = "ToneLab ST", */
+		.ifnum = 3,
+		.type = QUIRK_MIDI_STANDARD_INTERFACE,
+	}
+},
+
+/* AKAI devices */
+{
+	USB_DEVICE(0x09e8, 0x0062),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.vendor_name = "AKAI",
+		.product_name = "MPD16",
+		.ifnum = 0,
+		.type = QUIRK_MIDI_AKAI,
+	}
+},
+
+{
+	/* Akai MPC Element */
+	USB_DEVICE(0x09e8, 0x0021),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.ifnum = QUIRK_ANY_INTERFACE,
+		.type = QUIRK_COMPOSITE,
+		.data = & (const struct snd_usb_audio_quirk[]) {
+			{
+				.ifnum = 0,
+				.type = QUIRK_IGNORE_INTERFACE
+			},
+			{
+				.ifnum = 1,
+				.type = QUIRK_MIDI_STANDARD_INTERFACE
+			},
+			{
+				.ifnum = -1
+			}
+		}
+	}
+},
+
+/* Steinberg devices */
+{
+	/* Steinberg MI2 */
+	USB_DEVICE_VENDOR_SPEC(0x0a4e, 0x2040),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.ifnum = QUIRK_ANY_INTERFACE,
+		.type = QUIRK_COMPOSITE,
+		.data = & (const struct snd_usb_audio_quirk[]) {
+			{
+				.ifnum = 0,
+				.type = QUIRK_AUDIO_STANDARD_INTERFACE
+			},
+			{
+				.ifnum = 1,
+				.type = QUIRK_AUDIO_STANDARD_INTERFACE
+			},
+			{
+				.ifnum = 2,
+				.type = QUIRK_AUDIO_STANDARD_INTERFACE
+			},
+			{
+				.ifnum = 3,
+				.type = QUIRK_MIDI_FIXED_ENDPOINT,
+				.data = &(const struct snd_usb_midi_endpoint_info) {
+					.out_cables = 0x0001,
+					.in_cables  = 0x0001
+				}
+			},
+			{
+				.ifnum = -1
+			}
+		}
+	}
+},
+{
+	/* Steinberg MI4 */
+	USB_DEVICE_VENDOR_SPEC(0x0a4e, 0x4040),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.ifnum = QUIRK_ANY_INTERFACE,
+		.type = QUIRK_COMPOSITE,
+		.data = & (const struct snd_usb_audio_quirk[]) {
+			{
+				.ifnum = 0,
+				.type = QUIRK_AUDIO_STANDARD_INTERFACE
+			},
+			{
+				.ifnum = 1,
+				.type = QUIRK_AUDIO_STANDARD_INTERFACE
+			},
+			{
+				.ifnum = 2,
+				.type = QUIRK_AUDIO_STANDARD_INTERFACE
+			},
+			{
+				.ifnum = 3,
+				.type = QUIRK_MIDI_FIXED_ENDPOINT,
+				.data = &(const struct snd_usb_midi_endpoint_info) {
+					.out_cables = 0x0001,
+					.in_cables  = 0x0001
+				}
+			},
+			{
+				.ifnum = -1
+			}
+		}
+	}
+},
+
+/* TerraTec devices */
+{
+	USB_DEVICE_VENDOR_SPEC(0x0ccd, 0x0012),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.vendor_name = "TerraTec",
+		.product_name = "PHASE 26",
+		.ifnum = 3,
+		.type = QUIRK_MIDI_STANDARD_INTERFACE
+	}
+},
+{
+	USB_DEVICE_VENDOR_SPEC(0x0ccd, 0x0013),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.vendor_name = "TerraTec",
+		.product_name = "PHASE 26",
+		.ifnum = 3,
+		.type = QUIRK_MIDI_STANDARD_INTERFACE
+	}
+},
+{
+	USB_DEVICE_VENDOR_SPEC(0x0ccd, 0x0014),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.vendor_name = "TerraTec",
+		.product_name = "PHASE 26",
+		.ifnum = 3,
+		.type = QUIRK_MIDI_STANDARD_INTERFACE
+	}
+},
+{
+	USB_DEVICE(0x0ccd, 0x0028),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.vendor_name = "TerraTec",
+		.product_name = "Aureon5.1MkII",
+		.ifnum = QUIRK_NO_INTERFACE
+	}
+},
+{
+	USB_DEVICE(0x0ccd, 0x0035),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.vendor_name = "Miditech",
+		.product_name = "Play'n Roll",
+		.ifnum = 0,
+		.type = QUIRK_MIDI_CME
+	}
+},
+
+/* Stanton/N2IT Final Scratch v1 device ('Scratchamp') */
+{
+	USB_DEVICE(0x103d, 0x0100),
+		.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.vendor_name = "Stanton",
+		.product_name = "ScratchAmp",
+		.ifnum = QUIRK_NO_INTERFACE
+	}
+},
+{
+	USB_DEVICE(0x103d, 0x0101),
+		.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.vendor_name = "Stanton",
+		.product_name = "ScratchAmp",
+		.ifnum = QUIRK_NO_INTERFACE
+	}
+},
+
+/* Novation EMS devices */
+{
+	USB_DEVICE_VENDOR_SPEC(0x1235, 0x0001),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.vendor_name = "Novation",
+		.product_name = "ReMOTE Audio/XStation",
+		.ifnum = 4,
+		.type = QUIRK_MIDI_NOVATION
+	}
+},
+{
+	USB_DEVICE_VENDOR_SPEC(0x1235, 0x0002),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.vendor_name = "Novation",
+		.product_name = "Speedio",
+		.ifnum = 3,
+		.type = QUIRK_MIDI_NOVATION
+	}
+},
+{
+	USB_DEVICE(0x1235, 0x000a),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		/* .vendor_name = "Novation", */
+		/* .product_name = "Nocturn", */
+		.ifnum = 0,
+		.type = QUIRK_MIDI_RAW_BYTES
+	}
+},
+{
+	USB_DEVICE(0x1235, 0x000e),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		/* .vendor_name = "Novation", */
+		/* .product_name = "Launchpad", */
+		.ifnum = 0,
+		.type = QUIRK_MIDI_RAW_BYTES
+	}
+},
+{
+	USB_DEVICE(0x1235, 0x0010),
+	.driver_info = (unsigned long) &(const struct snd_usb_audio_quirk) {
+		.vendor_name = "Focusrite",
+		.product_name = "Saffire 6 USB",
+		.ifnum = QUIRK_ANY_INTERFACE,
+		.type = QUIRK_COMPOSITE,
+		.data = (const struct snd_usb_audio_quirk[]) {
+			{
+				.ifnum = 0,
+				.type = QUIRK_AUDIO_FIXED_ENDPOINT,
+				.data = &(const struct audioformat) {
+					.formats = SNDRV_PCM_FMTBIT_S24_3LE,
+					.channels = 4,
+					.iface = 0,
+					.altsetting = 1,
+					.altset_idx = 1,
+					.attributes = UAC_EP_CS_ATTR_SAMPLE_RATE,
+					.endpoint = 0x01,
+					.ep_attr = USB_ENDPOINT_XFER_ISOC,
+					.rates = SNDRV_PCM_RATE_44100 |
+						 SNDRV_PCM_RATE_48000,
+					.rate_min = 44100,
+					.rate_max = 48000,
+					.nr_rates = 2,
+					.rate_table = (unsigned int[]) {
+						44100, 48000
+					}
+				}
+			},
+			{
+				.ifnum = 1,
+				.type = QUIRK_MIDI_RAW_BYTES
+			},
+			{
+				.ifnum = -1
+			}
+		}
+	}
+},
+{
+	USB_DEVICE(0x1235, 0x0018),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.vendor_name = "Novation",
+		.product_name = "Twitch",
+		.ifnum = QUIRK_ANY_INTERFACE,
+		.type = QUIRK_COMPOSITE,
+		.data = (const struct snd_usb_audio_quirk[]) {
+			{
+				.ifnum = 0,
+				.type = QUIRK_AUDIO_FIXED_ENDPOINT,
+				.data = & (const struct audioformat) {
+					.formats = SNDRV_PCM_FMTBIT_S24_3LE,
+					.channels = 4,
+					.iface = 0,
+					.altsetting = 1,
+					.altset_idx = 1,
+					.attributes = UAC_EP_CS_ATTR_SAMPLE_RATE,
+					.endpoint = 0x01,
+					.ep_attr = USB_ENDPOINT_XFER_ISOC,
+					.rates = SNDRV_PCM_RATE_44100 |
+						 SNDRV_PCM_RATE_48000,
+					.rate_min = 44100,
+					.rate_max = 48000,
+					.nr_rates = 2,
+					.rate_table = (unsigned int[]) {
+						44100, 48000
+					}
+				}
+			},
+			{
+				.ifnum = 1,
+				.type = QUIRK_MIDI_RAW_BYTES
+			},
+			{
+				.ifnum = -1
+			}
+		}
+	}
+},
+{
+	USB_DEVICE_VENDOR_SPEC(0x1235, 0x4661),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.vendor_name = "Novation",
+		.product_name = "ReMOTE25",
+		.ifnum = 0,
+		.type = QUIRK_MIDI_NOVATION
+	}
+},
+
+/* Access Music devices */
+{
+	/* VirusTI Desktop */
+	USB_DEVICE_VENDOR_SPEC(0x133e, 0x0815),
+	.driver_info = (unsigned long) &(const struct snd_usb_audio_quirk) {
+		.ifnum = QUIRK_ANY_INTERFACE,
+		.type = QUIRK_COMPOSITE,
+		.data = &(const struct snd_usb_audio_quirk[]) {
+			{
+				.ifnum = 3,
+				.type = QUIRK_MIDI_FIXED_ENDPOINT,
+				.data = &(const struct snd_usb_midi_endpoint_info) {
+					.out_cables = 0x0003,
+					.in_cables  = 0x0003
+				}
+			},
+			{
+				.ifnum = 4,
+				.type = QUIRK_IGNORE_INTERFACE
+			},
+			{
+				.ifnum = -1
+			}
+		}
+	}
+},
+
+/* */
+{
+	/* aka. Serato Scratch Live DJ Box */
+	USB_DEVICE(0x13e5, 0x0001),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.vendor_name = "Rane",
+		.product_name = "SL-1",
+		.ifnum = QUIRK_NO_INTERFACE
+	}
+},
+
+/* Native Instruments MK2 series */
+{
+	/* Komplete Audio 6 */
+	.match_flags = USB_DEVICE_ID_MATCH_DEVICE,
+	.idVendor = 0x17cc,
+	.idProduct = 0x1000,
+},
+{
+	/* Traktor Audio 6 */
+	.match_flags = USB_DEVICE_ID_MATCH_DEVICE,
+	.idVendor = 0x17cc,
+	.idProduct = 0x1010,
+},
+{
+	/* Traktor Audio 10 */
+	.match_flags = USB_DEVICE_ID_MATCH_DEVICE,
+	.idVendor = 0x17cc,
+	.idProduct = 0x1020,
+},
+
+/* QinHeng devices */
+{
+	USB_DEVICE(0x1a86, 0x752d),
+	.driver_info = (unsigned long) &(const struct snd_usb_audio_quirk) {
+		.vendor_name = "QinHeng",
+		.product_name = "CH345",
+		.ifnum = 1,
+		.type = QUIRK_MIDI_CH345
+	}
+},
+
+/* KeithMcMillen Stringport */
+{
+	USB_DEVICE(0x1f38, 0x0001),
+	.bInterfaceClass = USB_CLASS_AUDIO,
+},
+
+/* Miditech devices */
+{
+	USB_DEVICE(0x4752, 0x0011),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.vendor_name = "Miditech",
+		.product_name = "Midistart-2",
+		.ifnum = 0,
+		.type = QUIRK_MIDI_CME
+	}
+},
+
+/* Central Music devices */
+{
+	/* this ID used by both Miditech MidiStudio-2 and CME UF-x */
+	USB_DEVICE(0x7104, 0x2202),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.ifnum = 0,
+		.type = QUIRK_MIDI_CME
+	}
+},
+
+/*
+ * Auvitek au0828 devices with audio interface.
+ * This should be kept in sync with drivers/media/usb/au0828/au0828-cards.c
+ * Please notice that some drivers are DVB only, and don't need to be
+ * here. That's the case, for example, of DVICO_FUSIONHDTV7.
+ */
+
+#define AU0828_DEVICE(vid, pid, vname, pname) { \
+	.idVendor = vid, \
+	.idProduct = pid, \
+	.match_flags = USB_DEVICE_ID_MATCH_DEVICE | \
+		       USB_DEVICE_ID_MATCH_INT_CLASS | \
+		       USB_DEVICE_ID_MATCH_INT_SUBCLASS, \
+	.bInterfaceClass = USB_CLASS_AUDIO, \
+	.bInterfaceSubClass = USB_SUBCLASS_AUDIOCONTROL, \
+	.driver_info = (unsigned long) &(const struct snd_usb_audio_quirk) { \
+		.vendor_name = vname, \
+		.product_name = pname, \
+		.ifnum = QUIRK_ANY_INTERFACE, \
+		.type = QUIRK_AUDIO_ALIGN_TRANSFER, \
+	} \
+}
+
+AU0828_DEVICE(0x2040, 0x7200, "Hauppauge", "HVR-950Q"),
+AU0828_DEVICE(0x2040, 0x7240, "Hauppauge", "HVR-850"),
+AU0828_DEVICE(0x2040, 0x7210, "Hauppauge", "HVR-950Q"),
+AU0828_DEVICE(0x2040, 0x7217, "Hauppauge", "HVR-950Q"),
+AU0828_DEVICE(0x2040, 0x721b, "Hauppauge", "HVR-950Q"),
+AU0828_DEVICE(0x2040, 0x721e, "Hauppauge", "HVR-950Q"),
+AU0828_DEVICE(0x2040, 0x721f, "Hauppauge", "HVR-950Q"),
+AU0828_DEVICE(0x2040, 0x7280, "Hauppauge", "HVR-950Q"),
+AU0828_DEVICE(0x0fd9, 0x0008, "Hauppauge", "HVR-950Q"),
+AU0828_DEVICE(0x2040, 0x7201, "Hauppauge", "HVR-950Q-MXL"),
+AU0828_DEVICE(0x2040, 0x7211, "Hauppauge", "HVR-950Q-MXL"),
+AU0828_DEVICE(0x2040, 0x7281, "Hauppauge", "HVR-950Q-MXL"),
+AU0828_DEVICE(0x05e1, 0x0480, "Hauppauge", "Woodbury"),
+AU0828_DEVICE(0x2040, 0x8200, "Hauppauge", "Woodbury"),
+AU0828_DEVICE(0x2040, 0x7260, "Hauppauge", "HVR-950Q"),
+AU0828_DEVICE(0x2040, 0x7213, "Hauppauge", "HVR-950Q"),
+AU0828_DEVICE(0x2040, 0x7270, "Hauppauge", "HVR-950Q"),
+
+/* Syntek STK1160 */
+{
+	.match_flags = USB_DEVICE_ID_MATCH_DEVICE |
+		       USB_DEVICE_ID_MATCH_INT_CLASS |
+		       USB_DEVICE_ID_MATCH_INT_SUBCLASS,
+	.idVendor = 0x05e1,
+	.idProduct = 0x0408,
+	.bInterfaceClass = USB_CLASS_AUDIO,
+	.bInterfaceSubClass = USB_SUBCLASS_AUDIOCONTROL,
+	.driver_info = (unsigned long) &(const struct snd_usb_audio_quirk) {
+		.vendor_name = "Syntek",
+		.product_name = "STK1160",
+		.ifnum = QUIRK_ANY_INTERFACE,
+		.type = QUIRK_AUDIO_ALIGN_TRANSFER
+	}
+},
+
+/* Digidesign Mbox */
+{
+	/* Thanks to Clemens Ladisch <clemens@ladisch.de> */
+	USB_DEVICE(0x0dba, 0x1000),
+	.driver_info = (unsigned long) &(const struct snd_usb_audio_quirk) {
+		.vendor_name = "Digidesign",
+		.product_name = "MBox",
+		.ifnum = QUIRK_ANY_INTERFACE,
+		.type = QUIRK_COMPOSITE,
+		.data = (const struct snd_usb_audio_quirk[]){
+			{
+				.ifnum = 0,
+				.type = QUIRK_AUDIO_STANDARD_MIXER,
+			},
+			{
+				.ifnum = 1,
+				.type = QUIRK_AUDIO_FIXED_ENDPOINT,
+				.data = &(const struct audioformat) {
+					.formats = SNDRV_PCM_FMTBIT_S24_3BE,
+					.channels = 2,
+					.iface = 1,
+					.altsetting = 1,
+					.altset_idx = 1,
+					.attributes = 0x4,
+					.endpoint = 0x02,
+					.ep_attr = USB_ENDPOINT_XFER_ISOC |
+						USB_ENDPOINT_SYNC_SYNC,
+					.maxpacksize = 0x130,
+					.rates = SNDRV_PCM_RATE_48000,
+					.rate_min = 48000,
+					.rate_max = 48000,
+					.nr_rates = 1,
+					.rate_table = (unsigned int[]) {
+						48000
+					}
+				}
+			},
+			{
+				.ifnum = 1,
+				.type = QUIRK_AUDIO_FIXED_ENDPOINT,
+				.data = &(const struct audioformat) {
+					.formats = SNDRV_PCM_FMTBIT_S24_3BE,
+					.channels = 2,
+					.iface = 1,
+					.altsetting = 1,
+					.altset_idx = 1,
+					.attributes = 0x4,
+					.endpoint = 0x81,
+					.ep_attr = USB_ENDPOINT_XFER_ISOC |
+						USB_ENDPOINT_SYNC_ASYNC,
+					.maxpacksize = 0x130,
+					.rates = SNDRV_PCM_RATE_48000,
+					.rate_min = 48000,
+					.rate_max = 48000,
+					.nr_rates = 1,
+					.rate_table = (unsigned int[]) {
+						48000
+					}
+				}
+			},
+			{
+				.ifnum = -1
+			}
+		}
+	}
+},
+
+/* DIGIDESIGN MBOX 2 */
+{
+	USB_DEVICE(0x0dba, 0x3000),
+	.driver_info = (unsigned long) &(const struct snd_usb_audio_quirk) {
+		.vendor_name = "Digidesign",
+		.product_name = "Mbox 2",
+		.ifnum = QUIRK_ANY_INTERFACE,
+		.type = QUIRK_COMPOSITE,
+		.data = (const struct snd_usb_audio_quirk[]) {
+			{
+				.ifnum = 0,
+				.type = QUIRK_IGNORE_INTERFACE
+			},
+			{
+				.ifnum = 1,
+				.type = QUIRK_IGNORE_INTERFACE
+			},
+			{
+				.ifnum = 2,
+				.type = QUIRK_AUDIO_FIXED_ENDPOINT,
+				.data = &(const struct audioformat) {
+					.formats = SNDRV_PCM_FMTBIT_S24_3BE,
+					.channels = 2,
+					.iface = 2,
+					.altsetting = 2,
+					.altset_idx = 1,
+					.attributes = 0x00,
+					.endpoint = 0x03,
+					.ep_attr = USB_ENDPOINT_SYNC_ASYNC,
+					.rates = SNDRV_PCM_RATE_48000,
+					.rate_min = 48000,
+					.rate_max = 48000,
+					.nr_rates = 1,
+					.rate_table = (unsigned int[]) {
+						48000
+					}
+				}
+			},
+			{
+				.ifnum = 3,
+				.type = QUIRK_IGNORE_INTERFACE
+			},
+			{
+				.ifnum = 4,
+				.type = QUIRK_AUDIO_FIXED_ENDPOINT,
+				.data = &(const struct audioformat) {
+				.formats = SNDRV_PCM_FMTBIT_S24_3BE,
+					.channels = 2,
+					.iface = 4,
+					.altsetting = 2,
+					.altset_idx = 1,
+					.attributes = UAC_EP_CS_ATTR_SAMPLE_RATE,
+					.endpoint = 0x85,
+					.ep_attr = USB_ENDPOINT_SYNC_SYNC,
+					.rates = SNDRV_PCM_RATE_48000,
+					.rate_min = 48000,
+					.rate_max = 48000,
+					.nr_rates = 1,
+					.rate_table = (unsigned int[]) {
+						48000
+					}
+				}
+			},
+			{
+				.ifnum = 5,
+				.type = QUIRK_IGNORE_INTERFACE
+			},
+			{
+				.ifnum = 6,
+				.type = QUIRK_MIDI_MIDIMAN,
+				.data = &(const struct snd_usb_midi_endpoint_info) {
+					.out_ep =  0x02,
+					.out_cables = 0x0001,
+					.in_ep = 0x81,
+					.in_interval = 0x01,
+					.in_cables = 0x0001
+				}
+			},
+			{
+				.ifnum = -1
+			}
+		}
+	}
+},
+{
+	/* Tascam US122 MKII - playback-only support */
+	.match_flags = USB_DEVICE_ID_MATCH_DEVICE,
+	.idVendor = 0x0644,
+	.idProduct = 0x8021,
+	.bInterfaceClass = USB_CLASS_AUDIO,
+	.driver_info = (unsigned long) &(const struct snd_usb_audio_quirk) {
+		.vendor_name = "TASCAM",
+		.product_name = "US122 MKII",
+		.ifnum = QUIRK_ANY_INTERFACE,
+		.type = QUIRK_COMPOSITE,
+		.data = (const struct snd_usb_audio_quirk[]) {
+			{
+				.ifnum = 0,
+				.type = QUIRK_IGNORE_INTERFACE
+			},
+			{
+				.ifnum = 1,
+				.type = QUIRK_AUDIO_FIXED_ENDPOINT,
+				.data = &(const struct audioformat) {
+					.formats = SNDRV_PCM_FMTBIT_S24_3LE,
+					.channels = 2,
+					.iface = 1,
+					.altsetting = 1,
+					.altset_idx = 1,
+					.attributes = UAC_EP_CS_ATTR_SAMPLE_RATE,
+					.endpoint = 0x02,
+					.ep_attr = USB_ENDPOINT_XFER_ISOC,
+					.rates = SNDRV_PCM_RATE_44100 |
+						 SNDRV_PCM_RATE_48000 |
+						 SNDRV_PCM_RATE_88200 |
+						 SNDRV_PCM_RATE_96000,
+					.rate_min = 44100,
+					.rate_max = 96000,
+					.nr_rates = 4,
+					.rate_table = (unsigned int[]) {
+						44100, 48000, 88200, 96000
+					}
+				}
+			},
+			{
+				.ifnum = -1
+			}
+		}
+	}
+},
+
+/* Microsoft XboxLive Headset/Xbox Communicator */
+{
+	USB_DEVICE(0x045e, 0x0283),
+	.bInterfaceClass = USB_CLASS_PER_INTERFACE,
+	.driver_info = (unsigned long) &(const struct snd_usb_audio_quirk) {
+		.vendor_name = "Microsoft",
+		.product_name = "XboxLive Headset/Xbox Communicator",
+		.ifnum = QUIRK_ANY_INTERFACE,
+		.type = QUIRK_COMPOSITE,
+		.data = &(const struct snd_usb_audio_quirk[]) {
+			{
+				/* playback */
+				.ifnum = 0,
+				.type = QUIRK_AUDIO_FIXED_ENDPOINT,
+				.data = &(const struct audioformat) {
+					.formats = SNDRV_PCM_FMTBIT_S16_LE,
+					.channels = 1,
+					.iface = 0,
+					.altsetting = 0,
+					.altset_idx = 0,
+					.attributes = 0,
+					.endpoint = 0x04,
+					.ep_attr = 0x05,
+					.rates = SNDRV_PCM_RATE_CONTINUOUS,
+					.rate_min = 22050,
+					.rate_max = 22050
+				}
+			},
+			{
+				/* capture */
+				.ifnum = 1,
+				.type = QUIRK_AUDIO_FIXED_ENDPOINT,
+				.data = &(const struct audioformat) {
+					.formats = SNDRV_PCM_FMTBIT_S16_LE,
+					.channels = 1,
+					.iface = 1,
+					.altsetting = 0,
+					.altset_idx = 0,
+					.attributes = 0,
+					.endpoint = 0x85,
+					.ep_attr = 0x05,
+					.rates = SNDRV_PCM_RATE_CONTINUOUS,
+					.rate_min = 16000,
+					.rate_max = 16000
+				}
+			},
+			{
+				.ifnum = -1
+			}
+		}
+	}
+},
+
+/* Reloop Play */
+{
+	USB_DEVICE(0x200c, 0x100b),
+	.bInterfaceClass = USB_CLASS_PER_INTERFACE,
+	.driver_info = (unsigned long) &(const struct snd_usb_audio_quirk) {
+		.ifnum = QUIRK_ANY_INTERFACE,
+		.type = QUIRK_COMPOSITE,
+		.data = &(const struct snd_usb_audio_quirk[]) {
+			{
+				.ifnum = 0,
+				.type = QUIRK_AUDIO_STANDARD_MIXER,
+			},
+			{
+				.ifnum = 1,
+				.type = QUIRK_AUDIO_FIXED_ENDPOINT,
+				.data = &(const struct audioformat) {
+					.formats = SNDRV_PCM_FMTBIT_S24_3LE,
+					.channels = 4,
+					.iface = 1,
+					.altsetting = 1,
+					.altset_idx = 1,
+					.attributes = UAC_EP_CS_ATTR_SAMPLE_RATE,
+					.endpoint = 0x01,
+					.ep_attr = USB_ENDPOINT_SYNC_ADAPTIVE,
+					.rates = SNDRV_PCM_RATE_44100 |
+						 SNDRV_PCM_RATE_48000,
+					.rate_min = 44100,
+					.rate_max = 48000,
+					.nr_rates = 2,
+					.rate_table = (unsigned int[]) {
+						44100, 48000
+					}
+				}
+			},
+			{
+				.ifnum = -1
+			}
+		}
+	}
+},
+
+{
+	/*
+	 * ZOOM R16/24 in audio interface mode.
+	 * Playback requires an extra four byte LE length indicator
+	 * at the start of each isochronous packet. This quirk is
+	 * enabled in create_standard_audio_quirk().
+	 */
+	USB_DEVICE(0x1686, 0x00dd),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.ifnum = QUIRK_ANY_INTERFACE,
+		.type = QUIRK_COMPOSITE,
+		.data = (const struct snd_usb_audio_quirk[]) {
+			{
+				/* Playback  */
+				.ifnum = 1,
+				.type = QUIRK_AUDIO_STANDARD_INTERFACE,
+			},
+			{
+				/* Capture */
+				.ifnum = 2,
+				.type = QUIRK_AUDIO_STANDARD_INTERFACE,
+			},
+			{
+				/* Midi */
+				.ifnum = 3,
+				.type = QUIRK_MIDI_STANDARD_INTERFACE
+			},
+			{
+				.ifnum = -1
+			},
+		}
+	}
+},
+
+{
+	/*
+	 * Some USB MIDI devices don't have an audio control interface,
+	 * so we have to grab MIDI streaming interfaces here.
+	 */
+	.match_flags = USB_DEVICE_ID_MATCH_INT_CLASS |
+		       USB_DEVICE_ID_MATCH_INT_SUBCLASS,
+	.bInterfaceClass = USB_CLASS_AUDIO,
+	.bInterfaceSubClass = USB_SUBCLASS_MIDISTREAMING,
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.ifnum = QUIRK_ANY_INTERFACE,
+		.type = QUIRK_MIDI_STANDARD_INTERFACE
+	}
+},
+
+{
+	/*
+	 * The original product_name is "USB Sound Device", however this name
+	 * is also used by the CM106 based cards, so make it unique.
+	 */
+	USB_DEVICE(0x0d8c, 0x0103),
+	.driver_info = (unsigned long) &(const struct snd_usb_audio_quirk) {
+		.product_name = "Audio Advantage MicroII",
+		.ifnum = QUIRK_NO_INTERFACE
+	}
+},
+
+/* disabled due to regression for other devices;
+ * see https://bugzilla.kernel.org/show_bug.cgi?id=199905
+ */
+#if 0
+{
+	/*
+	 * Nura's first gen headphones use Cambridge Silicon Radio's vendor
+	 * ID, but it looks like the product ID actually is only for Nura.
+	 * The capture interface does not work at all (even on Windows),
+	 * and only the 48 kHz sample rate works for the playback interface.
+	 */
+	USB_DEVICE(0x0a12, 0x1243),
+	.driver_info = (unsigned long) &(const struct snd_usb_audio_quirk) {
+		.ifnum = QUIRK_ANY_INTERFACE,
+		.type = QUIRK_COMPOSITE,
+		.data = (const struct snd_usb_audio_quirk[]) {
+			{
+				.ifnum = 0,
+				.type = QUIRK_AUDIO_STANDARD_MIXER,
+			},
+			/* Capture */
+			{
+				.ifnum = 1,
+				.type = QUIRK_IGNORE_INTERFACE,
+			},
+			/* Playback */
+			{
+				.ifnum = 2,
+				.type = QUIRK_AUDIO_FIXED_ENDPOINT,
+				.data = &(const struct audioformat) {
+					.formats = SNDRV_PCM_FMTBIT_S16_LE,
+					.channels = 2,
+					.iface = 2,
+					.altsetting = 1,
+					.altset_idx = 1,
+					.attributes = UAC_EP_CS_ATTR_FILL_MAX |
+						UAC_EP_CS_ATTR_SAMPLE_RATE,
+					.endpoint = 0x03,
+					.ep_attr = USB_ENDPOINT_XFER_ISOC,
+					.rates = SNDRV_PCM_RATE_48000,
+					.rate_min = 48000,
+					.rate_max = 48000,
+					.nr_rates = 1,
+					.rate_table = (unsigned int[]) {
+						48000
+					}
+				}
+			},
+		}
+	}
+},
+#endif /* disabled */
+
+{
+	/*
+	 * Bower's & Wilkins PX headphones only support the 48 kHz sample rate
+	 * even though it advertises more. The capture interface doesn't work
+	 * even on windows.
+	 */
+	USB_DEVICE(0x19b5, 0x0021),
+	.driver_info = (unsigned long) &(const struct snd_usb_audio_quirk) {
+		.ifnum = QUIRK_ANY_INTERFACE,
+		.type = QUIRK_COMPOSITE,
+		.data = (const struct snd_usb_audio_quirk[]) {
+			{
+				.ifnum = 0,
+				.type = QUIRK_AUDIO_STANDARD_MIXER,
+			},
+			/* Capture */
+			{
+				.ifnum = 1,
+				.type = QUIRK_IGNORE_INTERFACE,
+			},
+			/* Playback */
+			{
+				.ifnum = 2,
+				.type = QUIRK_AUDIO_FIXED_ENDPOINT,
+				.data = &(const struct audioformat) {
+					.formats = SNDRV_PCM_FMTBIT_S16_LE,
+					.channels = 2,
+					.iface = 2,
+					.altsetting = 1,
+					.altset_idx = 1,
+					.attributes = UAC_EP_CS_ATTR_FILL_MAX |
+						UAC_EP_CS_ATTR_SAMPLE_RATE,
+					.endpoint = 0x03,
+					.ep_attr = USB_ENDPOINT_XFER_ISOC,
+					.rates = SNDRV_PCM_RATE_48000,
+					.rate_min = 48000,
+					.rate_max = 48000,
+					.nr_rates = 1,
+					.rate_table = (unsigned int[]) {
+						48000
+					}
+				}
+			},
+		}
+	}
+},
+/* Dell WD15 Dock */
+{
+	USB_DEVICE(0x0bda, 0x4014),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.vendor_name = "Dell",
+		.product_name = "WD15 Dock",
+		.profile_name = "Dell-WD15-Dock",
+		.ifnum = QUIRK_NO_INTERFACE
+	}
+},
+/* Dell WD19 Dock */
+{
+	USB_DEVICE(0x0bda, 0x402e),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.vendor_name = "Dell",
+		.product_name = "WD19 Dock",
+		.profile_name = "Dell-WD15-Dock",
+		.ifnum = QUIRK_NO_INTERFACE
+	}
+},
+
+#undef USB_DEVICE_VENDOR_SPEC
diff --git a/sound/usb/quirks.c b/sound/usb/quirks.c
new file mode 100644
index 0000000..6623caf
--- /dev/null
+++ b/sound/usb/quirks.c
@@ -0,0 +1,1493 @@
+/*
+ *   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 <linux/usb/midi.h>
+
+#include <sound/control.h>
+#include <sound/core.h>
+#include <sound/info.h>
+#include <sound/pcm.h>
+
+#include "usbaudio.h"
+#include "card.h"
+#include "mixer.h"
+#include "mixer_quirks.h"
+#include "midi.h"
+#include "quirks.h"
+#include "helper.h"
+#include "endpoint.h"
+#include "pcm.h"
+#include "clock.h"
+#include "stream.h"
+
+/*
+ * handle the quirks for the contained interfaces
+ */
+static int create_composite_quirk(struct snd_usb_audio *chip,
+				  struct usb_interface *iface,
+				  struct usb_driver *driver,
+				  const struct snd_usb_audio_quirk *quirk_comp)
+{
+	int probed_ifnum = get_iface_desc(iface->altsetting)->bInterfaceNumber;
+	const struct snd_usb_audio_quirk *quirk;
+	int err;
+
+	for (quirk = quirk_comp->data; quirk->ifnum >= 0; ++quirk) {
+		iface = usb_ifnum_to_if(chip->dev, quirk->ifnum);
+		if (!iface)
+			continue;
+		if (quirk->ifnum != probed_ifnum &&
+		    usb_interface_claimed(iface))
+			continue;
+		err = snd_usb_create_quirk(chip, iface, driver, quirk);
+		if (err < 0)
+			return err;
+	}
+
+	for (quirk = quirk_comp->data; quirk->ifnum >= 0; ++quirk) {
+		iface = usb_ifnum_to_if(chip->dev, quirk->ifnum);
+		if (!iface)
+			continue;
+		if (quirk->ifnum != probed_ifnum &&
+		    !usb_interface_claimed(iface))
+			usb_driver_claim_interface(driver, iface, (void *)-1L);
+	}
+
+	return 0;
+}
+
+static int ignore_interface_quirk(struct snd_usb_audio *chip,
+				  struct usb_interface *iface,
+				  struct usb_driver *driver,
+				  const struct snd_usb_audio_quirk *quirk)
+{
+	return 0;
+}
+
+
+/*
+ * Allow alignment on audio sub-slot (channel samples) rather than
+ * on audio slots (audio frames)
+ */
+static int create_align_transfer_quirk(struct snd_usb_audio *chip,
+				       struct usb_interface *iface,
+				       struct usb_driver *driver,
+				       const struct snd_usb_audio_quirk *quirk)
+{
+	chip->txfr_quirk = 1;
+	return 1;	/* Continue with creating streams and mixer */
+}
+
+static int create_any_midi_quirk(struct snd_usb_audio *chip,
+				 struct usb_interface *intf,
+				 struct usb_driver *driver,
+				 const struct snd_usb_audio_quirk *quirk)
+{
+	return snd_usbmidi_create(chip->card, intf, &chip->midi_list, quirk);
+}
+
+/*
+ * create a stream for an interface with proper descriptors
+ */
+static int create_standard_audio_quirk(struct snd_usb_audio *chip,
+				       struct usb_interface *iface,
+				       struct usb_driver *driver,
+				       const struct snd_usb_audio_quirk *quirk)
+{
+	struct usb_host_interface *alts;
+	struct usb_interface_descriptor *altsd;
+	int err;
+
+	if (chip->usb_id == USB_ID(0x1686, 0x00dd)) /* Zoom R16/24 */
+		chip->tx_length_quirk = 1;
+
+	alts = &iface->altsetting[0];
+	altsd = get_iface_desc(alts);
+	err = snd_usb_parse_audio_interface(chip, altsd->bInterfaceNumber);
+	if (err < 0) {
+		usb_audio_err(chip, "cannot setup if %d: error %d\n",
+			   altsd->bInterfaceNumber, err);
+		return err;
+	}
+	/* reset the current interface */
+	usb_set_interface(chip->dev, altsd->bInterfaceNumber, 0);
+	return 0;
+}
+
+/*
+ * create a stream for an endpoint/altsetting without proper descriptors
+ */
+static int create_fixed_stream_quirk(struct snd_usb_audio *chip,
+				     struct usb_interface *iface,
+				     struct usb_driver *driver,
+				     const struct snd_usb_audio_quirk *quirk)
+{
+	struct audioformat *fp;
+	struct usb_host_interface *alts;
+	struct usb_interface_descriptor *altsd;
+	int stream, err;
+	unsigned *rate_table = NULL;
+
+	fp = kmemdup(quirk->data, sizeof(*fp), GFP_KERNEL);
+	if (!fp)
+		return -ENOMEM;
+
+	INIT_LIST_HEAD(&fp->list);
+	if (fp->nr_rates > MAX_NR_RATES) {
+		kfree(fp);
+		return -EINVAL;
+	}
+	if (fp->nr_rates > 0) {
+		rate_table = kmemdup(fp->rate_table,
+				     sizeof(int) * fp->nr_rates, GFP_KERNEL);
+		if (!rate_table) {
+			kfree(fp);
+			return -ENOMEM;
+		}
+		fp->rate_table = rate_table;
+	}
+
+	stream = (fp->endpoint & USB_DIR_IN)
+		? SNDRV_PCM_STREAM_CAPTURE : SNDRV_PCM_STREAM_PLAYBACK;
+	err = snd_usb_add_audio_stream(chip, stream, fp);
+	if (err < 0)
+		goto error;
+	if (fp->iface != get_iface_desc(&iface->altsetting[0])->bInterfaceNumber ||
+	    fp->altset_idx >= iface->num_altsetting) {
+		err = -EINVAL;
+		goto error;
+	}
+	alts = &iface->altsetting[fp->altset_idx];
+	altsd = get_iface_desc(alts);
+	if (altsd->bNumEndpoints < 1) {
+		err = -EINVAL;
+		goto error;
+	}
+
+	fp->protocol = altsd->bInterfaceProtocol;
+
+	if (fp->datainterval == 0)
+		fp->datainterval = snd_usb_parse_datainterval(chip, alts);
+	if (fp->maxpacksize == 0)
+		fp->maxpacksize = le16_to_cpu(get_endpoint(alts, 0)->wMaxPacketSize);
+	usb_set_interface(chip->dev, fp->iface, 0);
+	snd_usb_init_pitch(chip, fp->iface, alts, fp);
+	snd_usb_init_sample_rate(chip, fp->iface, alts, fp, fp->rate_max);
+	return 0;
+
+ error:
+	list_del(&fp->list); /* unlink for avoiding double-free */
+	kfree(fp);
+	kfree(rate_table);
+	return err;
+}
+
+static int create_auto_pcm_quirk(struct snd_usb_audio *chip,
+				 struct usb_interface *iface,
+				 struct usb_driver *driver)
+{
+	struct usb_host_interface *alts;
+	struct usb_interface_descriptor *altsd;
+	struct usb_endpoint_descriptor *epd;
+	struct uac1_as_header_descriptor *ashd;
+	struct uac_format_type_i_discrete_descriptor *fmtd;
+
+	/*
+	 * Most Roland/Yamaha audio streaming interfaces have more or less
+	 * standard descriptors, but older devices might lack descriptors, and
+	 * future ones might change, so ensure that we fail silently if the
+	 * interface doesn't look exactly right.
+	 */
+
+	/* must have a non-zero altsetting for streaming */
+	if (iface->num_altsetting < 2)
+		return -ENODEV;
+	alts = &iface->altsetting[1];
+	altsd = get_iface_desc(alts);
+
+	/* must have an isochronous endpoint for streaming */
+	if (altsd->bNumEndpoints < 1)
+		return -ENODEV;
+	epd = get_endpoint(alts, 0);
+	if (!usb_endpoint_xfer_isoc(epd))
+		return -ENODEV;
+
+	/* must have format descriptors */
+	ashd = snd_usb_find_csint_desc(alts->extra, alts->extralen, NULL,
+				       UAC_AS_GENERAL);
+	fmtd = snd_usb_find_csint_desc(alts->extra, alts->extralen, NULL,
+				       UAC_FORMAT_TYPE);
+	if (!ashd || ashd->bLength < 7 ||
+	    !fmtd || fmtd->bLength < 8)
+		return -ENODEV;
+
+	return create_standard_audio_quirk(chip, iface, driver, NULL);
+}
+
+static int create_yamaha_midi_quirk(struct snd_usb_audio *chip,
+				    struct usb_interface *iface,
+				    struct usb_driver *driver,
+				    struct usb_host_interface *alts)
+{
+	static const struct snd_usb_audio_quirk yamaha_midi_quirk = {
+		.type = QUIRK_MIDI_YAMAHA
+	};
+	struct usb_midi_in_jack_descriptor *injd;
+	struct usb_midi_out_jack_descriptor *outjd;
+
+	/* must have some valid jack descriptors */
+	injd = snd_usb_find_csint_desc(alts->extra, alts->extralen,
+				       NULL, USB_MS_MIDI_IN_JACK);
+	outjd = snd_usb_find_csint_desc(alts->extra, alts->extralen,
+					NULL, USB_MS_MIDI_OUT_JACK);
+	if (!injd && !outjd)
+		return -ENODEV;
+	if (injd && (injd->bLength < 5 ||
+		     (injd->bJackType != USB_MS_EMBEDDED &&
+		      injd->bJackType != USB_MS_EXTERNAL)))
+		return -ENODEV;
+	if (outjd && (outjd->bLength < 6 ||
+		      (outjd->bJackType != USB_MS_EMBEDDED &&
+		       outjd->bJackType != USB_MS_EXTERNAL)))
+		return -ENODEV;
+	return create_any_midi_quirk(chip, iface, driver, &yamaha_midi_quirk);
+}
+
+static int create_roland_midi_quirk(struct snd_usb_audio *chip,
+				    struct usb_interface *iface,
+				    struct usb_driver *driver,
+				    struct usb_host_interface *alts)
+{
+	static const struct snd_usb_audio_quirk roland_midi_quirk = {
+		.type = QUIRK_MIDI_ROLAND
+	};
+	u8 *roland_desc = NULL;
+
+	/* might have a vendor-specific descriptor <06 24 F1 02 ...> */
+	for (;;) {
+		roland_desc = snd_usb_find_csint_desc(alts->extra,
+						      alts->extralen,
+						      roland_desc, 0xf1);
+		if (!roland_desc)
+			return -ENODEV;
+		if (roland_desc[0] < 6 || roland_desc[3] != 2)
+			continue;
+		return create_any_midi_quirk(chip, iface, driver,
+					     &roland_midi_quirk);
+	}
+}
+
+static int create_std_midi_quirk(struct snd_usb_audio *chip,
+				 struct usb_interface *iface,
+				 struct usb_driver *driver,
+				 struct usb_host_interface *alts)
+{
+	struct usb_ms_header_descriptor *mshd;
+	struct usb_ms_endpoint_descriptor *msepd;
+
+	/* must have the MIDIStreaming interface header descriptor*/
+	mshd = (struct usb_ms_header_descriptor *)alts->extra;
+	if (alts->extralen < 7 ||
+	    mshd->bLength < 7 ||
+	    mshd->bDescriptorType != USB_DT_CS_INTERFACE ||
+	    mshd->bDescriptorSubtype != USB_MS_HEADER)
+		return -ENODEV;
+	/* must have the MIDIStreaming endpoint descriptor*/
+	msepd = (struct usb_ms_endpoint_descriptor *)alts->endpoint[0].extra;
+	if (alts->endpoint[0].extralen < 4 ||
+	    msepd->bLength < 4 ||
+	    msepd->bDescriptorType != USB_DT_CS_ENDPOINT ||
+	    msepd->bDescriptorSubtype != UAC_MS_GENERAL ||
+	    msepd->bNumEmbMIDIJack < 1 ||
+	    msepd->bNumEmbMIDIJack > 16)
+		return -ENODEV;
+
+	return create_any_midi_quirk(chip, iface, driver, NULL);
+}
+
+static int create_auto_midi_quirk(struct snd_usb_audio *chip,
+				  struct usb_interface *iface,
+				  struct usb_driver *driver)
+{
+	struct usb_host_interface *alts;
+	struct usb_interface_descriptor *altsd;
+	struct usb_endpoint_descriptor *epd;
+	int err;
+
+	alts = &iface->altsetting[0];
+	altsd = get_iface_desc(alts);
+
+	/* must have at least one bulk/interrupt endpoint for streaming */
+	if (altsd->bNumEndpoints < 1)
+		return -ENODEV;
+	epd = get_endpoint(alts, 0);
+	if (!usb_endpoint_xfer_bulk(epd) &&
+	    !usb_endpoint_xfer_int(epd))
+		return -ENODEV;
+
+	switch (USB_ID_VENDOR(chip->usb_id)) {
+	case 0x0499: /* Yamaha */
+		err = create_yamaha_midi_quirk(chip, iface, driver, alts);
+		if (err != -ENODEV)
+			return err;
+		break;
+	case 0x0582: /* Roland */
+		err = create_roland_midi_quirk(chip, iface, driver, alts);
+		if (err != -ENODEV)
+			return err;
+		break;
+	}
+
+	return create_std_midi_quirk(chip, iface, driver, alts);
+}
+
+static int create_autodetect_quirk(struct snd_usb_audio *chip,
+				   struct usb_interface *iface,
+				   struct usb_driver *driver)
+{
+	int err;
+
+	err = create_auto_pcm_quirk(chip, iface, driver);
+	if (err == -ENODEV)
+		err = create_auto_midi_quirk(chip, iface, driver);
+	return err;
+}
+
+static int create_autodetect_quirks(struct snd_usb_audio *chip,
+				    struct usb_interface *iface,
+				    struct usb_driver *driver,
+				    const struct snd_usb_audio_quirk *quirk)
+{
+	int probed_ifnum = get_iface_desc(iface->altsetting)->bInterfaceNumber;
+	int ifcount, ifnum, err;
+
+	err = create_autodetect_quirk(chip, iface, driver);
+	if (err < 0)
+		return err;
+
+	/*
+	 * ALSA PCM playback/capture devices cannot be registered in two steps,
+	 * so we have to claim the other corresponding interface here.
+	 */
+	ifcount = chip->dev->actconfig->desc.bNumInterfaces;
+	for (ifnum = 0; ifnum < ifcount; ifnum++) {
+		if (ifnum == probed_ifnum || quirk->ifnum >= 0)
+			continue;
+		iface = usb_ifnum_to_if(chip->dev, ifnum);
+		if (!iface ||
+		    usb_interface_claimed(iface) ||
+		    get_iface_desc(iface->altsetting)->bInterfaceClass !=
+							USB_CLASS_VENDOR_SPEC)
+			continue;
+
+		err = create_autodetect_quirk(chip, iface, driver);
+		if (err >= 0)
+			usb_driver_claim_interface(driver, iface, (void *)-1L);
+	}
+
+	return 0;
+}
+
+/*
+ * Create a stream for an Edirol UA-700/UA-25/UA-4FX interface.  
+ * The only way to detect the sample rate is by looking at wMaxPacketSize.
+ */
+static int create_uaxx_quirk(struct snd_usb_audio *chip,
+			     struct usb_interface *iface,
+			     struct usb_driver *driver,
+			     const struct snd_usb_audio_quirk *quirk)
+{
+	static const struct audioformat ua_format = {
+		.formats = SNDRV_PCM_FMTBIT_S24_3LE,
+		.channels = 2,
+		.fmt_type = UAC_FORMAT_TYPE_I,
+		.altsetting = 1,
+		.altset_idx = 1,
+		.rates = SNDRV_PCM_RATE_CONTINUOUS,
+	};
+	struct usb_host_interface *alts;
+	struct usb_interface_descriptor *altsd;
+	struct audioformat *fp;
+	int stream, err;
+
+	/* both PCM and MIDI interfaces have 2 or more altsettings */
+	if (iface->num_altsetting < 2)
+		return -ENXIO;
+	alts = &iface->altsetting[1];
+	altsd = get_iface_desc(alts);
+
+	if (altsd->bNumEndpoints == 2) {
+		static const struct snd_usb_midi_endpoint_info ua700_ep = {
+			.out_cables = 0x0003,
+			.in_cables  = 0x0003
+		};
+		static const struct snd_usb_audio_quirk ua700_quirk = {
+			.type = QUIRK_MIDI_FIXED_ENDPOINT,
+			.data = &ua700_ep
+		};
+		static const struct snd_usb_midi_endpoint_info uaxx_ep = {
+			.out_cables = 0x0001,
+			.in_cables  = 0x0001
+		};
+		static const struct snd_usb_audio_quirk uaxx_quirk = {
+			.type = QUIRK_MIDI_FIXED_ENDPOINT,
+			.data = &uaxx_ep
+		};
+		const struct snd_usb_audio_quirk *quirk =
+			chip->usb_id == USB_ID(0x0582, 0x002b)
+			? &ua700_quirk : &uaxx_quirk;
+		return __snd_usbmidi_create(chip->card, iface,
+					  &chip->midi_list, quirk,
+					  chip->usb_id);
+	}
+
+	if (altsd->bNumEndpoints != 1)
+		return -ENXIO;
+
+	fp = kmemdup(&ua_format, sizeof(*fp), GFP_KERNEL);
+	if (!fp)
+		return -ENOMEM;
+
+	fp->iface = altsd->bInterfaceNumber;
+	fp->endpoint = get_endpoint(alts, 0)->bEndpointAddress;
+	fp->ep_attr = get_endpoint(alts, 0)->bmAttributes;
+	fp->datainterval = 0;
+	fp->maxpacksize = le16_to_cpu(get_endpoint(alts, 0)->wMaxPacketSize);
+	INIT_LIST_HEAD(&fp->list);
+
+	switch (fp->maxpacksize) {
+	case 0x120:
+		fp->rate_max = fp->rate_min = 44100;
+		break;
+	case 0x138:
+	case 0x140:
+		fp->rate_max = fp->rate_min = 48000;
+		break;
+	case 0x258:
+	case 0x260:
+		fp->rate_max = fp->rate_min = 96000;
+		break;
+	default:
+		usb_audio_err(chip, "unknown sample rate\n");
+		kfree(fp);
+		return -ENXIO;
+	}
+
+	stream = (fp->endpoint & USB_DIR_IN)
+		? SNDRV_PCM_STREAM_CAPTURE : SNDRV_PCM_STREAM_PLAYBACK;
+	err = snd_usb_add_audio_stream(chip, stream, fp);
+	if (err < 0) {
+		list_del(&fp->list); /* unlink for avoiding double-free */
+		kfree(fp);
+		return err;
+	}
+	usb_set_interface(chip->dev, fp->iface, 0);
+	return 0;
+}
+
+/*
+ * Create a standard mixer for the specified interface.
+ */
+static int create_standard_mixer_quirk(struct snd_usb_audio *chip,
+				       struct usb_interface *iface,
+				       struct usb_driver *driver,
+				       const struct snd_usb_audio_quirk *quirk)
+{
+	if (quirk->ifnum < 0)
+		return 0;
+
+	return snd_usb_create_mixer(chip, quirk->ifnum, 0);
+}
+
+/*
+ * audio-interface quirks
+ *
+ * returns zero if no standard audio/MIDI parsing is needed.
+ * returns a positive value if standard audio/midi interfaces are parsed
+ * after this.
+ * returns a negative value at error.
+ */
+int snd_usb_create_quirk(struct snd_usb_audio *chip,
+			 struct usb_interface *iface,
+			 struct usb_driver *driver,
+			 const struct snd_usb_audio_quirk *quirk)
+{
+	typedef int (*quirk_func_t)(struct snd_usb_audio *,
+				    struct usb_interface *,
+				    struct usb_driver *,
+				    const struct snd_usb_audio_quirk *);
+	static const quirk_func_t quirk_funcs[] = {
+		[QUIRK_IGNORE_INTERFACE] = ignore_interface_quirk,
+		[QUIRK_COMPOSITE] = create_composite_quirk,
+		[QUIRK_AUTODETECT] = create_autodetect_quirks,
+		[QUIRK_MIDI_STANDARD_INTERFACE] = create_any_midi_quirk,
+		[QUIRK_MIDI_FIXED_ENDPOINT] = create_any_midi_quirk,
+		[QUIRK_MIDI_YAMAHA] = create_any_midi_quirk,
+		[QUIRK_MIDI_ROLAND] = create_any_midi_quirk,
+		[QUIRK_MIDI_MIDIMAN] = create_any_midi_quirk,
+		[QUIRK_MIDI_NOVATION] = create_any_midi_quirk,
+		[QUIRK_MIDI_RAW_BYTES] = create_any_midi_quirk,
+		[QUIRK_MIDI_EMAGIC] = create_any_midi_quirk,
+		[QUIRK_MIDI_CME] = create_any_midi_quirk,
+		[QUIRK_MIDI_AKAI] = create_any_midi_quirk,
+		[QUIRK_MIDI_FTDI] = create_any_midi_quirk,
+		[QUIRK_MIDI_CH345] = create_any_midi_quirk,
+		[QUIRK_AUDIO_STANDARD_INTERFACE] = create_standard_audio_quirk,
+		[QUIRK_AUDIO_FIXED_ENDPOINT] = create_fixed_stream_quirk,
+		[QUIRK_AUDIO_EDIROL_UAXX] = create_uaxx_quirk,
+		[QUIRK_AUDIO_ALIGN_TRANSFER] = create_align_transfer_quirk,
+		[QUIRK_AUDIO_STANDARD_MIXER] = create_standard_mixer_quirk,
+	};
+
+	if (quirk->type < QUIRK_TYPE_COUNT) {
+		return quirk_funcs[quirk->type](chip, iface, driver, quirk);
+	} else {
+		usb_audio_err(chip, "invalid quirk type %d\n", quirk->type);
+		return -ENXIO;
+	}
+}
+
+/*
+ * boot quirks
+ */
+
+#define EXTIGY_FIRMWARE_SIZE_OLD 794
+#define EXTIGY_FIRMWARE_SIZE_NEW 483
+
+static int snd_usb_extigy_boot_quirk(struct usb_device *dev, struct usb_interface *intf)
+{
+	struct usb_host_config *config = dev->actconfig;
+	int err;
+
+	if (le16_to_cpu(get_cfg_desc(config)->wTotalLength) == EXTIGY_FIRMWARE_SIZE_OLD ||
+	    le16_to_cpu(get_cfg_desc(config)->wTotalLength) == EXTIGY_FIRMWARE_SIZE_NEW) {
+		dev_dbg(&dev->dev, "sending Extigy boot sequence...\n");
+		/* Send message to force it to reconnect with full interface. */
+		err = snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev,0),
+				      0x10, 0x43, 0x0001, 0x000a, NULL, 0);
+		if (err < 0)
+			dev_dbg(&dev->dev, "error sending boot message: %d\n", err);
+		err = usb_get_descriptor(dev, USB_DT_DEVICE, 0,
+				&dev->descriptor, sizeof(dev->descriptor));
+		config = dev->actconfig;
+		if (err < 0)
+			dev_dbg(&dev->dev, "error usb_get_descriptor: %d\n", err);
+		err = usb_reset_configuration(dev);
+		if (err < 0)
+			dev_dbg(&dev->dev, "error usb_reset_configuration: %d\n", err);
+		dev_dbg(&dev->dev, "extigy_boot: new boot length = %d\n",
+			    le16_to_cpu(get_cfg_desc(config)->wTotalLength));
+		return -ENODEV; /* quit this anyway */
+	}
+	return 0;
+}
+
+static int snd_usb_audigy2nx_boot_quirk(struct usb_device *dev)
+{
+	u8 buf = 1;
+
+	snd_usb_ctl_msg(dev, usb_rcvctrlpipe(dev, 0), 0x2a,
+			USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_OTHER,
+			0, 0, &buf, 1);
+	if (buf == 0) {
+		snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), 0x29,
+				USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_OTHER,
+				1, 2000, NULL, 0);
+		return -ENODEV;
+	}
+	return 0;
+}
+
+static int snd_usb_fasttrackpro_boot_quirk(struct usb_device *dev)
+{
+	int err;
+
+	if (dev->actconfig->desc.bConfigurationValue == 1) {
+		dev_info(&dev->dev,
+			   "Fast Track Pro switching to config #2\n");
+		/* This function has to be available by the usb core module.
+		 * if it is not avialable the boot quirk has to be left out
+		 * and the configuration has to be set by udev or hotplug
+		 * rules
+		 */
+		err = usb_driver_set_configuration(dev, 2);
+		if (err < 0)
+			dev_dbg(&dev->dev,
+				"error usb_driver_set_configuration: %d\n",
+				err);
+		/* Always return an error, so that we stop creating a device
+		   that will just be destroyed and recreated with a new
+		   configuration */
+		return -ENODEV;
+	} else
+		dev_info(&dev->dev, "Fast Track Pro config OK\n");
+
+	return 0;
+}
+
+/*
+ * C-Media CM106/CM106+ have four 16-bit internal registers that are nicely
+ * documented in the device's data sheet.
+ */
+static int snd_usb_cm106_write_int_reg(struct usb_device *dev, int reg, u16 value)
+{
+	u8 buf[4];
+	buf[0] = 0x20;
+	buf[1] = value & 0xff;
+	buf[2] = (value >> 8) & 0xff;
+	buf[3] = reg;
+	return snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), USB_REQ_SET_CONFIGURATION,
+			       USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_ENDPOINT,
+			       0, 0, &buf, 4);
+}
+
+static int snd_usb_cm106_boot_quirk(struct usb_device *dev)
+{
+	/*
+	 * Enable line-out driver mode, set headphone source to front
+	 * channels, enable stereo mic.
+	 */
+	return snd_usb_cm106_write_int_reg(dev, 2, 0x8004);
+}
+
+/*
+ * C-Media CM6206 is based on CM106 with two additional
+ * registers that are not documented in the data sheet.
+ * Values here are chosen based on sniffing USB traffic
+ * under Windows.
+ */
+static int snd_usb_cm6206_boot_quirk(struct usb_device *dev)
+{
+	int err  = 0, reg;
+	int val[] = {0x2004, 0x3000, 0xf800, 0x143f, 0x0000, 0x3000};
+
+	for (reg = 0; reg < ARRAY_SIZE(val); reg++) {
+		err = snd_usb_cm106_write_int_reg(dev, reg, val[reg]);
+		if (err < 0)
+			return err;
+	}
+
+	return err;
+}
+
+/* quirk for Plantronics GameCom 780 with CM6302 chip */
+static int snd_usb_gamecon780_boot_quirk(struct usb_device *dev)
+{
+	/* set the initial volume and don't change; other values are either
+	 * too loud or silent due to firmware bug (bko#65251)
+	 */
+	u8 buf[2] = { 0x74, 0xe3 };
+	return snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), UAC_SET_CUR,
+			USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_OUT,
+			UAC_FU_VOLUME << 8, 9 << 8, buf, 2);
+}
+
+/*
+ * Novation Twitch DJ controller
+ * Focusrite Novation Saffire 6 USB audio card
+ */
+static int snd_usb_novation_boot_quirk(struct usb_device *dev)
+{
+	/* preemptively set up the device because otherwise the
+	 * raw MIDI endpoints are not active */
+	usb_set_interface(dev, 0, 1);
+	return 0;
+}
+
+/*
+ * This call will put the synth in "USB send" mode, i.e it will send MIDI
+ * messages through USB (this is disabled at startup). The synth will
+ * acknowledge by sending a sysex on endpoint 0x85 and by displaying a USB
+ * sign on its LCD. Values here are chosen based on sniffing USB traffic
+ * under Windows.
+ */
+static int snd_usb_accessmusic_boot_quirk(struct usb_device *dev)
+{
+	int err, actual_length;
+
+	/* "midi send" enable */
+	static const u8 seq[] = { 0x4e, 0x73, 0x52, 0x01 };
+
+	void *buf = kmemdup(seq, ARRAY_SIZE(seq), GFP_KERNEL);
+	if (!buf)
+		return -ENOMEM;
+	err = usb_interrupt_msg(dev, usb_sndintpipe(dev, 0x05), buf,
+			ARRAY_SIZE(seq), &actual_length, 1000);
+	kfree(buf);
+	if (err < 0)
+		return err;
+
+	return 0;
+}
+
+/*
+ * Some sound cards from Native Instruments are in fact compliant to the USB
+ * audio standard of version 2 and other approved USB standards, even though
+ * they come up as vendor-specific device when first connected.
+ *
+ * However, they can be told to come up with a new set of descriptors
+ * upon their next enumeration, and the interfaces announced by the new
+ * descriptors will then be handled by the kernel's class drivers. As the
+ * product ID will also change, no further checks are required.
+ */
+
+static int snd_usb_nativeinstruments_boot_quirk(struct usb_device *dev)
+{
+	int ret = usb_control_msg(dev, usb_sndctrlpipe(dev, 0),
+				  0xaf, USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+				  1, 0, NULL, 0, 1000);
+
+	if (ret < 0)
+		return ret;
+
+	usb_reset_device(dev);
+
+	/* return -EAGAIN, so the creation of an audio interface for this
+	 * temporary device is aborted. The device will reconnect with a
+	 * new product ID */
+	return -EAGAIN;
+}
+
+static void mbox2_setup_48_24_magic(struct usb_device *dev)
+{
+	u8 srate[3];
+	u8 temp[12];
+
+	/* Choose 48000Hz permanently */
+	srate[0] = 0x80;
+	srate[1] = 0xbb;
+	srate[2] = 0x00;
+
+	/* Send the magic! */
+	snd_usb_ctl_msg(dev, usb_rcvctrlpipe(dev, 0),
+		0x01, 0x22, 0x0100, 0x0085, &temp, 0x0003);
+	snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0),
+		0x81, 0xa2, 0x0100, 0x0085, &srate, 0x0003);
+	snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0),
+		0x81, 0xa2, 0x0100, 0x0086, &srate, 0x0003);
+	snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0),
+		0x81, 0xa2, 0x0100, 0x0003, &srate, 0x0003);
+	return;
+}
+
+/* Digidesign Mbox 2 needs to load firmware onboard
+ * and driver must wait a few seconds for initialisation.
+ */
+
+#define MBOX2_FIRMWARE_SIZE    646
+#define MBOX2_BOOT_LOADING     0x01 /* Hard coded into the device */
+#define MBOX2_BOOT_READY       0x02 /* Hard coded into the device */
+
+static int snd_usb_mbox2_boot_quirk(struct usb_device *dev)
+{
+	struct usb_host_config *config = dev->actconfig;
+	int err;
+	u8 bootresponse[0x12];
+	int fwsize;
+	int count;
+
+	fwsize = le16_to_cpu(get_cfg_desc(config)->wTotalLength);
+
+	if (fwsize != MBOX2_FIRMWARE_SIZE) {
+		dev_err(&dev->dev, "Invalid firmware size=%d.\n", fwsize);
+		return -ENODEV;
+	}
+
+	dev_dbg(&dev->dev, "Sending Digidesign Mbox 2 boot sequence...\n");
+
+	count = 0;
+	bootresponse[0] = MBOX2_BOOT_LOADING;
+	while ((bootresponse[0] == MBOX2_BOOT_LOADING) && (count < 10)) {
+		msleep(500); /* 0.5 second delay */
+		snd_usb_ctl_msg(dev, usb_rcvctrlpipe(dev, 0),
+			/* Control magic - load onboard firmware */
+			0x85, 0xc0, 0x0001, 0x0000, &bootresponse, 0x0012);
+		if (bootresponse[0] == MBOX2_BOOT_READY)
+			break;
+		dev_dbg(&dev->dev, "device not ready, resending boot sequence...\n");
+		count++;
+	}
+
+	if (bootresponse[0] != MBOX2_BOOT_READY) {
+		dev_err(&dev->dev, "Unknown bootresponse=%d, or timed out, ignoring device.\n", bootresponse[0]);
+		return -ENODEV;
+	}
+
+	dev_dbg(&dev->dev, "device initialised!\n");
+
+	err = usb_get_descriptor(dev, USB_DT_DEVICE, 0,
+		&dev->descriptor, sizeof(dev->descriptor));
+	config = dev->actconfig;
+	if (err < 0)
+		dev_dbg(&dev->dev, "error usb_get_descriptor: %d\n", err);
+
+	err = usb_reset_configuration(dev);
+	if (err < 0)
+		dev_dbg(&dev->dev, "error usb_reset_configuration: %d\n", err);
+	dev_dbg(&dev->dev, "mbox2_boot: new boot length = %d\n",
+		le16_to_cpu(get_cfg_desc(config)->wTotalLength));
+
+	mbox2_setup_48_24_magic(dev);
+
+	dev_info(&dev->dev, "Digidesign Mbox 2: 24bit 48kHz");
+
+	return 0; /* Successful boot */
+}
+
+static int snd_usb_axefx3_boot_quirk(struct usb_device *dev)
+{
+	int err;
+
+	dev_dbg(&dev->dev, "Waiting for Axe-Fx III to boot up...\n");
+
+	/* If the Axe-Fx III has not fully booted, it will timeout when trying
+	 * to enable the audio streaming interface. A more generous timeout is
+	 * used here to detect when the Axe-Fx III has finished booting as the
+	 * set interface message will be acked once it has
+	 */
+	err = usb_control_msg(dev, usb_sndctrlpipe(dev, 0),
+				USB_REQ_SET_INTERFACE, USB_RECIP_INTERFACE,
+				1, 1, NULL, 0, 120000);
+	if (err < 0) {
+		dev_err(&dev->dev,
+			"failed waiting for Axe-Fx III to boot: %d\n", err);
+		return err;
+	}
+
+	dev_dbg(&dev->dev, "Axe-Fx III is now ready\n");
+
+	err = usb_set_interface(dev, 1, 0);
+	if (err < 0)
+		dev_dbg(&dev->dev,
+			"error stopping Axe-Fx III interface: %d\n", err);
+
+	return 0;
+}
+
+/*
+ * Setup quirks
+ */
+#define MAUDIO_SET		0x01 /* parse device_setup */
+#define MAUDIO_SET_COMPATIBLE	0x80 /* use only "win-compatible" interfaces */
+#define MAUDIO_SET_DTS		0x02 /* enable DTS Digital Output */
+#define MAUDIO_SET_96K		0x04 /* 48-96KHz rate if set, 8-48KHz otherwise */
+#define MAUDIO_SET_24B		0x08 /* 24bits sample if set, 16bits otherwise */
+#define MAUDIO_SET_DI		0x10 /* enable Digital Input */
+#define MAUDIO_SET_MASK		0x1f /* bit mask for setup value */
+#define MAUDIO_SET_24B_48K_DI	 0x19 /* 24bits+48KHz+Digital Input */
+#define MAUDIO_SET_24B_48K_NOTDI 0x09 /* 24bits+48KHz+No Digital Input */
+#define MAUDIO_SET_16B_48K_DI	 0x11 /* 16bits+48KHz+Digital Input */
+#define MAUDIO_SET_16B_48K_NOTDI 0x01 /* 16bits+48KHz+No Digital Input */
+
+static int quattro_skip_setting_quirk(struct snd_usb_audio *chip,
+				      int iface, int altno)
+{
+	/* Reset ALL ifaces to 0 altsetting.
+	 * Call it for every possible altsetting of every interface.
+	 */
+	usb_set_interface(chip->dev, iface, 0);
+	if (chip->setup & MAUDIO_SET) {
+		if (chip->setup & MAUDIO_SET_COMPATIBLE) {
+			if (iface != 1 && iface != 2)
+				return 1; /* skip all interfaces but 1 and 2 */
+		} else {
+			unsigned int mask;
+			if (iface == 1 || iface == 2)
+				return 1; /* skip interfaces 1 and 2 */
+			if ((chip->setup & MAUDIO_SET_96K) && altno != 1)
+				return 1; /* skip this altsetting */
+			mask = chip->setup & MAUDIO_SET_MASK;
+			if (mask == MAUDIO_SET_24B_48K_DI && altno != 2)
+				return 1; /* skip this altsetting */
+			if (mask == MAUDIO_SET_24B_48K_NOTDI && altno != 3)
+				return 1; /* skip this altsetting */
+			if (mask == MAUDIO_SET_16B_48K_NOTDI && altno != 4)
+				return 1; /* skip this altsetting */
+		}
+	}
+	usb_audio_dbg(chip,
+		    "using altsetting %d for interface %d config %d\n",
+		    altno, iface, chip->setup);
+	return 0; /* keep this altsetting */
+}
+
+static int audiophile_skip_setting_quirk(struct snd_usb_audio *chip,
+					 int iface,
+					 int altno)
+{
+	/* Reset ALL ifaces to 0 altsetting.
+	 * Call it for every possible altsetting of every interface.
+	 */
+	usb_set_interface(chip->dev, iface, 0);
+
+	if (chip->setup & MAUDIO_SET) {
+		unsigned int mask;
+		if ((chip->setup & MAUDIO_SET_DTS) && altno != 6)
+			return 1; /* skip this altsetting */
+		if ((chip->setup & MAUDIO_SET_96K) && altno != 1)
+			return 1; /* skip this altsetting */
+		mask = chip->setup & MAUDIO_SET_MASK;
+		if (mask == MAUDIO_SET_24B_48K_DI && altno != 2)
+			return 1; /* skip this altsetting */
+		if (mask == MAUDIO_SET_24B_48K_NOTDI && altno != 3)
+			return 1; /* skip this altsetting */
+		if (mask == MAUDIO_SET_16B_48K_DI && altno != 4)
+			return 1; /* skip this altsetting */
+		if (mask == MAUDIO_SET_16B_48K_NOTDI && altno != 5)
+			return 1; /* skip this altsetting */
+	}
+
+	return 0; /* keep this altsetting */
+}
+
+static int fasttrackpro_skip_setting_quirk(struct snd_usb_audio *chip,
+					   int iface, int altno)
+{
+	/* Reset ALL ifaces to 0 altsetting.
+	 * Call it for every possible altsetting of every interface.
+	 */
+	usb_set_interface(chip->dev, iface, 0);
+
+	/* possible configuration where both inputs and only one output is
+	 *used is not supported by the current setup
+	 */
+	if (chip->setup & (MAUDIO_SET | MAUDIO_SET_24B)) {
+		if (chip->setup & MAUDIO_SET_96K) {
+			if (altno != 3 && altno != 6)
+				return 1;
+		} else if (chip->setup & MAUDIO_SET_DI) {
+			if (iface == 4)
+				return 1; /* no analog input */
+			if (altno != 2 && altno != 5)
+				return 1; /* enable only altsets 2 and 5 */
+		} else {
+			if (iface == 5)
+				return 1; /* disable digialt input */
+			if (altno != 2 && altno != 5)
+				return 1; /* enalbe only altsets 2 and 5 */
+		}
+	} else {
+		/* keep only 16-Bit mode */
+		if (altno != 1)
+			return 1;
+	}
+
+	usb_audio_dbg(chip,
+		    "using altsetting %d for interface %d config %d\n",
+		    altno, iface, chip->setup);
+	return 0; /* keep this altsetting */
+}
+
+int snd_usb_apply_interface_quirk(struct snd_usb_audio *chip,
+				  int iface,
+				  int altno)
+{
+	/* audiophile usb: skip altsets incompatible with device_setup */
+	if (chip->usb_id == USB_ID(0x0763, 0x2003))
+		return audiophile_skip_setting_quirk(chip, iface, altno);
+	/* quattro usb: skip altsets incompatible with device_setup */
+	if (chip->usb_id == USB_ID(0x0763, 0x2001))
+		return quattro_skip_setting_quirk(chip, iface, altno);
+	/* fasttrackpro usb: skip altsets incompatible with device_setup */
+	if (chip->usb_id == USB_ID(0x0763, 0x2012))
+		return fasttrackpro_skip_setting_quirk(chip, iface, altno);
+
+	return 0;
+}
+
+int snd_usb_apply_boot_quirk(struct usb_device *dev,
+			     struct usb_interface *intf,
+			     const struct snd_usb_audio_quirk *quirk,
+			     unsigned int id)
+{
+	switch (id) {
+	case USB_ID(0x041e, 0x3000):
+		/* SB Extigy needs special boot-up sequence */
+		/* if more models come, this will go to the quirk list. */
+		return snd_usb_extigy_boot_quirk(dev, intf);
+
+	case USB_ID(0x041e, 0x3020):
+		/* SB Audigy 2 NX needs its own boot-up magic, too */
+		return snd_usb_audigy2nx_boot_quirk(dev);
+
+	case USB_ID(0x10f5, 0x0200):
+		/* C-Media CM106 / Turtle Beach Audio Advantage Roadie */
+		return snd_usb_cm106_boot_quirk(dev);
+
+	case USB_ID(0x0d8c, 0x0102):
+		/* C-Media CM6206 / CM106-Like Sound Device */
+	case USB_ID(0x0ccd, 0x00b1): /* Terratec Aureon 7.1 USB */
+		return snd_usb_cm6206_boot_quirk(dev);
+
+	case USB_ID(0x0dba, 0x3000):
+		/* Digidesign Mbox 2 */
+		return snd_usb_mbox2_boot_quirk(dev);
+
+	case USB_ID(0x1235, 0x0010): /* Focusrite Novation Saffire 6 USB */
+	case USB_ID(0x1235, 0x0018): /* Focusrite Novation Twitch */
+		return snd_usb_novation_boot_quirk(dev);
+
+	case USB_ID(0x133e, 0x0815):
+		/* Access Music VirusTI Desktop */
+		return snd_usb_accessmusic_boot_quirk(dev);
+
+	case USB_ID(0x17cc, 0x1000): /* Komplete Audio 6 */
+	case USB_ID(0x17cc, 0x1010): /* Traktor Audio 6 */
+	case USB_ID(0x17cc, 0x1020): /* Traktor Audio 10 */
+		return snd_usb_nativeinstruments_boot_quirk(dev);
+	case USB_ID(0x0763, 0x2012):  /* M-Audio Fast Track Pro USB */
+		return snd_usb_fasttrackpro_boot_quirk(dev);
+	case USB_ID(0x047f, 0xc010): /* Plantronics Gamecom 780 */
+		return snd_usb_gamecon780_boot_quirk(dev);
+	case USB_ID(0x2466, 0x8010): /* Fractal Audio Axe-Fx 3 */
+		return snd_usb_axefx3_boot_quirk(dev);
+	}
+
+	return 0;
+}
+
+/*
+ * check if the device uses big-endian samples
+ */
+int snd_usb_is_big_endian_format(struct snd_usb_audio *chip, struct audioformat *fp)
+{
+	/* it depends on altsetting whether the device is big-endian or not */
+	switch (chip->usb_id) {
+	case USB_ID(0x0763, 0x2001): /* M-Audio Quattro: captured data only */
+		if (fp->altsetting == 2 || fp->altsetting == 3 ||
+			fp->altsetting == 5 || fp->altsetting == 6)
+			return 1;
+		break;
+	case USB_ID(0x0763, 0x2003): /* M-Audio Audiophile USB */
+		if (chip->setup == 0x00 ||
+			fp->altsetting == 1 || fp->altsetting == 2 ||
+			fp->altsetting == 3)
+			return 1;
+		break;
+	case USB_ID(0x0763, 0x2012): /* M-Audio Fast Track Pro */
+		if (fp->altsetting == 2 || fp->altsetting == 3 ||
+			fp->altsetting == 5 || fp->altsetting == 6)
+			return 1;
+		break;
+	}
+	return 0;
+}
+
+/*
+ * For E-Mu 0404USB/0202USB/TrackerPre/0204 sample rate should be set for device,
+ * not for interface.
+ */
+
+enum {
+	EMU_QUIRK_SR_44100HZ = 0,
+	EMU_QUIRK_SR_48000HZ,
+	EMU_QUIRK_SR_88200HZ,
+	EMU_QUIRK_SR_96000HZ,
+	EMU_QUIRK_SR_176400HZ,
+	EMU_QUIRK_SR_192000HZ
+};
+
+static void set_format_emu_quirk(struct snd_usb_substream *subs,
+				 struct audioformat *fmt)
+{
+	unsigned char emu_samplerate_id = 0;
+
+	/* When capture is active
+	 * sample rate shouldn't be changed
+	 * by playback substream
+	 */
+	if (subs->direction == SNDRV_PCM_STREAM_PLAYBACK) {
+		if (subs->stream->substream[SNDRV_PCM_STREAM_CAPTURE].interface != -1)
+			return;
+	}
+
+	switch (fmt->rate_min) {
+	case 48000:
+		emu_samplerate_id = EMU_QUIRK_SR_48000HZ;
+		break;
+	case 88200:
+		emu_samplerate_id = EMU_QUIRK_SR_88200HZ;
+		break;
+	case 96000:
+		emu_samplerate_id = EMU_QUIRK_SR_96000HZ;
+		break;
+	case 176400:
+		emu_samplerate_id = EMU_QUIRK_SR_176400HZ;
+		break;
+	case 192000:
+		emu_samplerate_id = EMU_QUIRK_SR_192000HZ;
+		break;
+	default:
+		emu_samplerate_id = EMU_QUIRK_SR_44100HZ;
+		break;
+	}
+	snd_emuusb_set_samplerate(subs->stream->chip, emu_samplerate_id);
+	subs->pkt_offset_adj = (emu_samplerate_id >= EMU_QUIRK_SR_176400HZ) ? 4 : 0;
+}
+
+void snd_usb_set_format_quirk(struct snd_usb_substream *subs,
+			      struct audioformat *fmt)
+{
+	switch (subs->stream->chip->usb_id) {
+	case USB_ID(0x041e, 0x3f02): /* E-Mu 0202 USB */
+	case USB_ID(0x041e, 0x3f04): /* E-Mu 0404 USB */
+	case USB_ID(0x041e, 0x3f0a): /* E-Mu Tracker Pre */
+	case USB_ID(0x041e, 0x3f19): /* E-Mu 0204 USB */
+		set_format_emu_quirk(subs, fmt);
+		break;
+	}
+}
+
+bool snd_usb_get_sample_rate_quirk(struct snd_usb_audio *chip)
+{
+	/* devices which do not support reading the sample rate. */
+	switch (chip->usb_id) {
+	case USB_ID(0x041E, 0x4080): /* Creative Live Cam VF0610 */
+	case USB_ID(0x04D8, 0xFEEA): /* Benchmark DAC1 Pre */
+	case USB_ID(0x0556, 0x0014): /* Phoenix Audio TMX320VC */
+	case USB_ID(0x05A3, 0x9420): /* ELP HD USB Camera */
+	case USB_ID(0x074D, 0x3553): /* Outlaw RR2150 (Micronas UAC3553B) */
+	case USB_ID(0x1395, 0x740a): /* Sennheiser DECT */
+	case USB_ID(0x1901, 0x0191): /* GE B850V3 CP2114 audio interface */
+	case USB_ID(0x21B4, 0x0081): /* AudioQuest DragonFly */
+		return true;
+	}
+
+	/* devices of these vendors don't support reading rate, either */
+	switch (USB_ID_VENDOR(chip->usb_id)) {
+	case 0x045E: /* MS Lifecam */
+	case 0x047F: /* Plantronics */
+	case 0x1de7: /* Phoenix Audio */
+		return true;
+	}
+
+	return false;
+}
+
+/* ITF-USB DSD based DACs need a vendor cmd to switch
+ * between PCM and native DSD mode
+ */
+static bool is_itf_usb_dsd_dac(unsigned int id)
+{
+	switch (id) {
+	case USB_ID(0x154e, 0x1003): /* Denon DA-300USB */
+	case USB_ID(0x154e, 0x3005): /* Marantz HD-DAC1 */
+	case USB_ID(0x154e, 0x3006): /* Marantz SA-14S1 */
+	case USB_ID(0x1852, 0x5065): /* Luxman DA-06 */
+	case USB_ID(0x0644, 0x8043): /* TEAC UD-501/UD-501V2/UD-503/NT-503 */
+	case USB_ID(0x0644, 0x8044): /* Esoteric D-05X */
+	case USB_ID(0x0644, 0x804a): /* TEAC UD-301 */
+		return true;
+	}
+	return false;
+}
+
+int snd_usb_select_mode_quirk(struct snd_usb_substream *subs,
+			      struct audioformat *fmt)
+{
+	struct usb_device *dev = subs->dev;
+	int err;
+
+	if (is_itf_usb_dsd_dac(subs->stream->chip->usb_id)) {
+		/* First switch to alt set 0, otherwise the mode switch cmd
+		 * will not be accepted by the DAC
+		 */
+		err = usb_set_interface(dev, fmt->iface, 0);
+		if (err < 0)
+			return err;
+
+		msleep(20); /* Delay needed after setting the interface */
+
+		/* Vendor mode switch cmd is required. */
+		if (fmt->formats & SNDRV_PCM_FMTBIT_DSD_U32_BE) {
+			/* DSD mode (DSD_U32) requested */
+			err = snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), 0,
+					      USB_DIR_OUT|USB_TYPE_VENDOR|USB_RECIP_INTERFACE,
+					      1, 1, NULL, 0);
+			if (err < 0)
+				return err;
+
+		} else {
+			/* PCM or DOP mode (S32) requested */
+			/* PCM mode (S16) requested */
+			err = snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), 0,
+					      USB_DIR_OUT|USB_TYPE_VENDOR|USB_RECIP_INTERFACE,
+					      0, 1, NULL, 0);
+			if (err < 0)
+				return err;
+
+		}
+		msleep(20);
+	}
+	return 0;
+}
+
+void snd_usb_endpoint_start_quirk(struct snd_usb_endpoint *ep)
+{
+	/*
+	 * "Playback Design" products send bogus feedback data at the start
+	 * of the stream. Ignore them.
+	 */
+	if (USB_ID_VENDOR(ep->chip->usb_id) == 0x23ba &&
+	    ep->type == SND_USB_ENDPOINT_TYPE_SYNC)
+		ep->skip_packets = 4;
+
+	/*
+	 * M-Audio Fast Track C400/C600 - when packets are not skipped, real
+	 * world latency varies by approx. +/- 50 frames (at 96KHz) each time
+	 * the stream is (re)started. When skipping packets 16 at endpoint
+	 * start up, the real world latency is stable within +/- 1 frame (also
+	 * across power cycles).
+	 */
+	if ((ep->chip->usb_id == USB_ID(0x0763, 0x2030) ||
+	     ep->chip->usb_id == USB_ID(0x0763, 0x2031)) &&
+	    ep->type == SND_USB_ENDPOINT_TYPE_DATA)
+		ep->skip_packets = 16;
+
+	/* Work around devices that report unreasonable feedback data */
+	if ((ep->chip->usb_id == USB_ID(0x0644, 0x8038) ||  /* TEAC UD-H01 */
+	     ep->chip->usb_id == USB_ID(0x1852, 0x5034)) && /* T+A Dac8 */
+	    ep->syncmaxsize == 4)
+		ep->tenor_fb_quirk = 1;
+}
+
+void snd_usb_set_interface_quirk(struct usb_device *dev)
+{
+	struct snd_usb_audio *chip = dev_get_drvdata(&dev->dev);
+
+	if (!chip)
+		return;
+	/*
+	 * "Playback Design" products need a 50ms delay after setting the
+	 * USB interface.
+	 */
+	switch (USB_ID_VENDOR(chip->usb_id)) {
+	case 0x23ba: /* Playback Design */
+	case 0x0644: /* TEAC Corp. */
+		msleep(50);
+		break;
+	}
+}
+
+/* quirk applied after snd_usb_ctl_msg(); not applied during boot quirks */
+void snd_usb_ctl_msg_quirk(struct usb_device *dev, unsigned int pipe,
+			   __u8 request, __u8 requesttype, __u16 value,
+			   __u16 index, void *data, __u16 size)
+{
+	struct snd_usb_audio *chip = dev_get_drvdata(&dev->dev);
+
+	if (!chip)
+		return;
+	/*
+	 * "Playback Design" products need a 20ms delay after each
+	 * class compliant request
+	 */
+	if (USB_ID_VENDOR(chip->usb_id) == 0x23ba &&
+	    (requesttype & USB_TYPE_MASK) == USB_TYPE_CLASS)
+		msleep(20);
+
+	/*
+	 * "TEAC Corp." products need a 20ms delay after each
+	 * class compliant request
+	 */
+	if (USB_ID_VENDOR(chip->usb_id) == 0x0644 &&
+	    (requesttype & USB_TYPE_MASK) == USB_TYPE_CLASS)
+		msleep(20);
+
+	/* ITF-USB DSD based DACs functionality need a delay
+	 * after each class compliant request
+	 */
+	if (is_itf_usb_dsd_dac(chip->usb_id)
+	    && (requesttype & USB_TYPE_MASK) == USB_TYPE_CLASS)
+		msleep(20);
+
+	/* Zoom R16/24, Logitech H650e, Jabra 550a needs a tiny delay here,
+	 * otherwise requests like get/set frequency return as failed despite
+	 * actually succeeding.
+	 */
+	if ((chip->usb_id == USB_ID(0x1686, 0x00dd) ||
+	     chip->usb_id == USB_ID(0x046d, 0x0a46) ||
+	     chip->usb_id == USB_ID(0x0b0e, 0x0349)) &&
+	    (requesttype & USB_TYPE_MASK) == USB_TYPE_CLASS)
+		usleep_range(1000, 2000);
+}
+
+/*
+ * snd_usb_interface_dsd_format_quirks() is called from format.c to
+ * augment the PCM format bit-field for DSD types. The UAC standards
+ * don't have a designated bit field to denote DSD-capable interfaces,
+ * hence all hardware that is known to support this format has to be
+ * listed here.
+ */
+u64 snd_usb_interface_dsd_format_quirks(struct snd_usb_audio *chip,
+					struct audioformat *fp,
+					unsigned int sample_bytes)
+{
+	struct usb_interface *iface;
+
+	/* Playback Designs */
+	if (USB_ID_VENDOR(chip->usb_id) == 0x23ba) {
+		switch (fp->altsetting) {
+		case 1:
+			fp->dsd_dop = true;
+			return SNDRV_PCM_FMTBIT_DSD_U16_LE;
+		case 2:
+			fp->dsd_bitrev = true;
+			return SNDRV_PCM_FMTBIT_DSD_U8;
+		case 3:
+			fp->dsd_bitrev = true;
+			return SNDRV_PCM_FMTBIT_DSD_U16_LE;
+		}
+	}
+
+	/* XMOS based USB DACs */
+	switch (chip->usb_id) {
+	case USB_ID(0x1511, 0x0037): /* AURALiC VEGA */
+	case USB_ID(0x20b1, 0x0002): /* Wyred 4 Sound DAC-2 DSD */
+	case USB_ID(0x20b1, 0x2004): /* Matrix Audio X-SPDIF 2 */
+	case USB_ID(0x20b1, 0x2008): /* Matrix Audio X-Sabre */
+	case USB_ID(0x20b1, 0x300a): /* Matrix Audio Mini-i Pro */
+	case USB_ID(0x22d9, 0x0416): /* OPPO HA-1 */
+	case USB_ID(0x22d9, 0x0436): /* OPPO Sonica */
+	case USB_ID(0x22d9, 0x0461): /* OPPO UDP-205 */
+	case USB_ID(0x2522, 0x0012): /* LH Labs VI DAC Infinity */
+	case USB_ID(0x2772, 0x0230): /* Pro-Ject Pre Box S2 Digital */
+		if (fp->altsetting == 2)
+			return SNDRV_PCM_FMTBIT_DSD_U32_BE;
+		break;
+
+	case USB_ID(0x152a, 0x85de): /* SMSL D1 DAC */
+	case USB_ID(0x16d0, 0x09dd): /* Encore mDSD */
+	case USB_ID(0x0d8c, 0x0316): /* Hegel HD12 DSD */
+	case USB_ID(0x16b0, 0x06b2): /* NuPrime DAC-10 */
+	case USB_ID(0x16d0, 0x0733): /* Furutech ADL Stratos */
+	case USB_ID(0x16d0, 0x09db): /* NuPrime Audio DAC-9 */
+	case USB_ID(0x1db5, 0x0003): /* Bryston BDA3 */
+	case USB_ID(0x20b1, 0x000a): /* Gustard DAC-X20U */
+	case USB_ID(0x20b1, 0x2005): /* Denafrips Ares DAC */
+	case USB_ID(0x20b1, 0x2009): /* DIYINHK DSD DXD 384kHz USB to I2S/DSD */
+	case USB_ID(0x20b1, 0x2023): /* JLsounds I2SoverUSB */
+	case USB_ID(0x20b1, 0x3021): /* Eastern El. MiniMax Tube DAC Supreme */
+	case USB_ID(0x20b1, 0x3023): /* Aune X1S 32BIT/384 DSD DAC */
+	case USB_ID(0x20b1, 0x302d): /* Unison Research Unico CD Due */
+	case USB_ID(0x20b1, 0x307b): /* CH Precision C1 DAC */
+	case USB_ID(0x20b1, 0x3086): /* Singxer F-1 converter board */
+	case USB_ID(0x22d9, 0x0426): /* OPPO HA-2 */
+	case USB_ID(0x22e1, 0xca01): /* HDTA Serenade DSD */
+	case USB_ID(0x249c, 0x9326): /* M2Tech Young MkIII */
+	case USB_ID(0x2616, 0x0106): /* PS Audio NuWave DAC */
+	case USB_ID(0x2622, 0x0041): /* Audiolab M-DAC+ */
+	case USB_ID(0x27f7, 0x3002): /* W4S DAC-2v2SE */
+	case USB_ID(0x29a2, 0x0086): /* Mutec MC3+ USB */
+	case USB_ID(0x6b42, 0x0042): /* MSB Technology */
+		if (fp->altsetting == 3)
+			return SNDRV_PCM_FMTBIT_DSD_U32_BE;
+		break;
+
+	/* Amanero Combo384 USB based DACs with native DSD support */
+	case USB_ID(0x16d0, 0x071a):  /* Amanero - Combo384 */
+	case USB_ID(0x2ab6, 0x0004):  /* T+A DAC8DSD-V2.0, MP1000E-V2.0, MP2000R-V2.0, MP2500R-V2.0, MP3100HV-V2.0 */
+	case USB_ID(0x2ab6, 0x0005):  /* T+A USB HD Audio 1 */
+	case USB_ID(0x2ab6, 0x0006):  /* T+A USB HD Audio 2 */
+		if (fp->altsetting == 2) {
+			switch (le16_to_cpu(chip->dev->descriptor.bcdDevice)) {
+			case 0x199:
+				return SNDRV_PCM_FMTBIT_DSD_U32_LE;
+			case 0x19b:
+			case 0x203:
+				return SNDRV_PCM_FMTBIT_DSD_U32_BE;
+			default:
+				break;
+			}
+		}
+		break;
+	case USB_ID(0x16d0, 0x0a23):
+		if (fp->altsetting == 2)
+			return SNDRV_PCM_FMTBIT_DSD_U32_BE;
+		break;
+
+	default:
+		break;
+	}
+
+	/* ITF-USB DSD based DACs */
+	if (is_itf_usb_dsd_dac(chip->usb_id)) {
+		iface = usb_ifnum_to_if(chip->dev, fp->iface);
+
+		/* Altsetting 2 support native DSD if the num of altsets is
+		 * three (0-2),
+		 * Altsetting 3 support native DSD if the num of altsets is
+		 * four (0-3).
+		 */
+		if (fp->altsetting == iface->num_altsetting - 1)
+			return SNDRV_PCM_FMTBIT_DSD_U32_BE;
+	}
+
+	/* Mostly generic method to detect many DSD-capable implementations -
+	 * from XMOS/Thesycon
+	 */
+	switch (USB_ID_VENDOR(chip->usb_id)) {
+	case 0x20b1:  /* XMOS based devices */
+	case 0x152a:  /* Thesycon devices */
+	case 0x25ce:  /* Mytek devices */
+		if (fp->dsd_raw)
+			return SNDRV_PCM_FMTBIT_DSD_U32_BE;
+		break;
+	default:
+		break;
+
+	}
+
+	return 0;
+}
+
+void snd_usb_audioformat_attributes_quirk(struct snd_usb_audio *chip,
+					  struct audioformat *fp,
+					  int stream)
+{
+	switch (chip->usb_id) {
+	case USB_ID(0x0a92, 0x0053): /* AudioTrak Optoplay */
+		/* Optoplay sets the sample rate attribute although
+		 * it seems not supporting it in fact.
+		 */
+		fp->attributes &= ~UAC_EP_CS_ATTR_SAMPLE_RATE;
+		break;
+	case USB_ID(0x041e, 0x3020): /* Creative SB Audigy 2 NX */
+	case USB_ID(0x0763, 0x2003): /* M-Audio Audiophile USB */
+		/* doesn't set the sample rate attribute, but supports it */
+		fp->attributes |= UAC_EP_CS_ATTR_SAMPLE_RATE;
+		break;
+	case USB_ID(0x0763, 0x2001):  /* M-Audio Quattro USB */
+	case USB_ID(0x0763, 0x2012):  /* M-Audio Fast Track Pro USB */
+	case USB_ID(0x047f, 0x0ca1): /* plantronics headset */
+	case USB_ID(0x077d, 0x07af): /* Griffin iMic (note that there is
+					an older model 77d:223) */
+	/*
+	 * plantronics headset and Griffin iMic have set adaptive-in
+	 * although it's really not...
+	 */
+		fp->ep_attr &= ~USB_ENDPOINT_SYNCTYPE;
+		if (stream == SNDRV_PCM_STREAM_PLAYBACK)
+			fp->ep_attr |= USB_ENDPOINT_SYNC_ADAPTIVE;
+		else
+			fp->ep_attr |= USB_ENDPOINT_SYNC_SYNC;
+		break;
+	}
+}
diff --git a/sound/usb/quirks.h b/sound/usb/quirks.h
new file mode 100644
index 0000000..a80e0dd
--- /dev/null
+++ b/sound/usb/quirks.h
@@ -0,0 +1,49 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __USBAUDIO_QUIRKS_H
+#define __USBAUDIO_QUIRKS_H
+
+struct audioformat;
+struct snd_usb_endpoint;
+struct snd_usb_substream;
+
+int snd_usb_create_quirk(struct snd_usb_audio *chip,
+			 struct usb_interface *iface,
+			 struct usb_driver *driver,
+			 const struct snd_usb_audio_quirk *quirk);
+
+int snd_usb_apply_interface_quirk(struct snd_usb_audio *chip,
+				  int iface,
+				  int altno);
+
+int snd_usb_apply_boot_quirk(struct usb_device *dev,
+			     struct usb_interface *intf,
+			     const struct snd_usb_audio_quirk *quirk,
+			     unsigned int usb_id);
+
+void snd_usb_set_format_quirk(struct snd_usb_substream *subs,
+			      struct audioformat *fmt);
+
+bool snd_usb_get_sample_rate_quirk(struct snd_usb_audio *chip);
+
+int snd_usb_is_big_endian_format(struct snd_usb_audio *chip,
+				 struct audioformat *fp);
+
+void snd_usb_endpoint_start_quirk(struct snd_usb_endpoint *ep);
+
+void snd_usb_set_interface_quirk(struct usb_device *dev);
+void snd_usb_ctl_msg_quirk(struct usb_device *dev, unsigned int pipe,
+			   __u8 request, __u8 requesttype, __u16 value,
+			   __u16 index, void *data, __u16 size);
+
+int snd_usb_select_mode_quirk(struct snd_usb_substream *subs,
+			      struct audioformat *fmt);
+
+u64 snd_usb_interface_dsd_format_quirks(struct snd_usb_audio *chip,
+					struct audioformat *fp,
+					unsigned int sample_bytes);
+
+void snd_usb_audioformat_attributes_quirk(struct snd_usb_audio *chip,
+					  struct audioformat *fp,
+					  int stream);
+
+#endif /* __USBAUDIO_QUIRKS_H */
diff --git a/sound/usb/stream.c b/sound/usb/stream.c
new file mode 100644
index 0000000..67cf849
--- /dev/null
+++ b/sound/usb/stream.c
@@ -0,0 +1,1197 @@
+/*
+ *   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 <linux/usb/audio-v2.h>
+#include <linux/usb/audio-v3.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/control.h>
+#include <sound/tlv.h>
+
+#include "usbaudio.h"
+#include "card.h"
+#include "proc.h"
+#include "quirks.h"
+#include "endpoint.h"
+#include "pcm.h"
+#include "helper.h"
+#include "format.h"
+#include "clock.h"
+#include "stream.h"
+#include "power.h"
+
+/*
+ * free a substream
+ */
+static void free_substream(struct snd_usb_substream *subs)
+{
+	struct audioformat *fp, *n;
+
+	if (!subs->num_formats)
+		return; /* not initialized */
+	list_for_each_entry_safe(fp, n, &subs->fmt_list, list) {
+		kfree(fp->rate_table);
+		kfree(fp->chmap);
+		kfree(fp);
+	}
+	kfree(subs->rate_list.list);
+	kfree(subs->str_pd);
+}
+
+
+/*
+ * free a usb stream instance
+ */
+static void snd_usb_audio_stream_free(struct snd_usb_stream *stream)
+{
+	free_substream(&stream->substream[0]);
+	free_substream(&stream->substream[1]);
+	list_del(&stream->list);
+	kfree(stream);
+}
+
+static void snd_usb_audio_pcm_free(struct snd_pcm *pcm)
+{
+	struct snd_usb_stream *stream = pcm->private_data;
+	if (stream) {
+		stream->pcm = NULL;
+		snd_usb_audio_stream_free(stream);
+	}
+}
+
+/*
+ * initialize the substream instance.
+ */
+
+static void snd_usb_init_substream(struct snd_usb_stream *as,
+				   int stream,
+				   struct audioformat *fp,
+				   struct snd_usb_power_domain *pd)
+{
+	struct snd_usb_substream *subs = &as->substream[stream];
+
+	INIT_LIST_HEAD(&subs->fmt_list);
+	spin_lock_init(&subs->lock);
+
+	subs->stream = as;
+	subs->direction = stream;
+	subs->dev = as->chip->dev;
+	subs->txfr_quirk = as->chip->txfr_quirk;
+	subs->tx_length_quirk = as->chip->tx_length_quirk;
+	subs->speed = snd_usb_get_speed(subs->dev);
+	subs->pkt_offset_adj = 0;
+
+	snd_usb_set_pcm_ops(as->pcm, stream);
+
+	list_add_tail(&fp->list, &subs->fmt_list);
+	subs->formats |= fp->formats;
+	subs->num_formats++;
+	subs->fmt_type = fp->fmt_type;
+	subs->ep_num = fp->endpoint;
+	if (fp->channels > subs->channels_max)
+		subs->channels_max = fp->channels;
+
+	if (pd) {
+		subs->str_pd = pd;
+		/* Initialize Power Domain to idle status D1 */
+		snd_usb_power_domain_set(subs->stream->chip, pd,
+					 UAC3_PD_STATE_D1);
+	}
+
+	snd_usb_preallocate_buffer(subs);
+}
+
+/* kctl callbacks for usb-audio channel maps */
+static int usb_chmap_ctl_info(struct snd_kcontrol *kcontrol,
+			      struct snd_ctl_elem_info *uinfo)
+{
+	struct snd_pcm_chmap *info = snd_kcontrol_chip(kcontrol);
+	struct snd_usb_substream *subs = info->private_data;
+
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = subs->channels_max;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = SNDRV_CHMAP_LAST;
+	return 0;
+}
+
+/* check whether a duplicated entry exists in the audiofmt list */
+static bool have_dup_chmap(struct snd_usb_substream *subs,
+			   struct audioformat *fp)
+{
+	struct audioformat *prev = fp;
+
+	list_for_each_entry_continue_reverse(prev, &subs->fmt_list, list) {
+		if (prev->chmap &&
+		    !memcmp(prev->chmap, fp->chmap, sizeof(*fp->chmap)))
+			return true;
+	}
+	return false;
+}
+
+static int usb_chmap_ctl_tlv(struct snd_kcontrol *kcontrol, int op_flag,
+			     unsigned int size, unsigned int __user *tlv)
+{
+	struct snd_pcm_chmap *info = snd_kcontrol_chip(kcontrol);
+	struct snd_usb_substream *subs = info->private_data;
+	struct audioformat *fp;
+	unsigned int __user *dst;
+	int count = 0;
+
+	if (size < 8)
+		return -ENOMEM;
+	if (put_user(SNDRV_CTL_TLVT_CONTAINER, tlv))
+		return -EFAULT;
+	size -= 8;
+	dst = tlv + 2;
+	list_for_each_entry(fp, &subs->fmt_list, list) {
+		int i, ch_bytes;
+
+		if (!fp->chmap)
+			continue;
+		if (have_dup_chmap(subs, fp))
+			continue;
+		/* copy the entry */
+		ch_bytes = fp->chmap->channels * 4;
+		if (size < 8 + ch_bytes)
+			return -ENOMEM;
+		if (put_user(SNDRV_CTL_TLVT_CHMAP_FIXED, dst) ||
+		    put_user(ch_bytes, dst + 1))
+			return -EFAULT;
+		dst += 2;
+		for (i = 0; i < fp->chmap->channels; i++, dst++) {
+			if (put_user(fp->chmap->map[i], dst))
+				return -EFAULT;
+		}
+
+		count += 8 + ch_bytes;
+		size -= 8 + ch_bytes;
+	}
+	if (put_user(count, tlv + 1))
+		return -EFAULT;
+	return 0;
+}
+
+static int usb_chmap_ctl_get(struct snd_kcontrol *kcontrol,
+			     struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_pcm_chmap *info = snd_kcontrol_chip(kcontrol);
+	struct snd_usb_substream *subs = info->private_data;
+	struct snd_pcm_chmap_elem *chmap = NULL;
+	int i;
+
+	memset(ucontrol->value.integer.value, 0,
+	       sizeof(ucontrol->value.integer.value));
+	if (subs->cur_audiofmt)
+		chmap = subs->cur_audiofmt->chmap;
+	if (chmap) {
+		for (i = 0; i < chmap->channels; i++)
+			ucontrol->value.integer.value[i] = chmap->map[i];
+	}
+	return 0;
+}
+
+/* create a chmap kctl assigned to the given USB substream */
+static int add_chmap(struct snd_pcm *pcm, int stream,
+		     struct snd_usb_substream *subs)
+{
+	struct audioformat *fp;
+	struct snd_pcm_chmap *chmap;
+	struct snd_kcontrol *kctl;
+	int err;
+
+	list_for_each_entry(fp, &subs->fmt_list, list)
+		if (fp->chmap)
+			goto ok;
+	/* no chmap is found */
+	return 0;
+
+ ok:
+	err = snd_pcm_add_chmap_ctls(pcm, stream, NULL, 0, 0, &chmap);
+	if (err < 0)
+		return err;
+
+	/* override handlers */
+	chmap->private_data = subs;
+	kctl = chmap->kctl;
+	kctl->info = usb_chmap_ctl_info;
+	kctl->get = usb_chmap_ctl_get;
+	kctl->tlv.c = usb_chmap_ctl_tlv;
+
+	return 0;
+}
+
+/* convert from USB ChannelConfig bits to ALSA chmap element */
+static struct snd_pcm_chmap_elem *convert_chmap(int channels, unsigned int bits,
+						int protocol)
+{
+	static unsigned int uac1_maps[] = {
+		SNDRV_CHMAP_FL,		/* left front */
+		SNDRV_CHMAP_FR,		/* right front */
+		SNDRV_CHMAP_FC,		/* center front */
+		SNDRV_CHMAP_LFE,	/* LFE */
+		SNDRV_CHMAP_SL,		/* left surround */
+		SNDRV_CHMAP_SR,		/* right surround */
+		SNDRV_CHMAP_FLC,	/* left of center */
+		SNDRV_CHMAP_FRC,	/* right of center */
+		SNDRV_CHMAP_RC,		/* surround */
+		SNDRV_CHMAP_SL,		/* side left */
+		SNDRV_CHMAP_SR,		/* side right */
+		SNDRV_CHMAP_TC,		/* top */
+		0 /* terminator */
+	};
+	static unsigned int uac2_maps[] = {
+		SNDRV_CHMAP_FL,		/* front left */
+		SNDRV_CHMAP_FR,		/* front right */
+		SNDRV_CHMAP_FC,		/* front center */
+		SNDRV_CHMAP_LFE,	/* LFE */
+		SNDRV_CHMAP_RL,		/* back left */
+		SNDRV_CHMAP_RR,		/* back right */
+		SNDRV_CHMAP_FLC,	/* front left of center */
+		SNDRV_CHMAP_FRC,	/* front right of center */
+		SNDRV_CHMAP_RC,		/* back center */
+		SNDRV_CHMAP_SL,		/* side left */
+		SNDRV_CHMAP_SR,		/* side right */
+		SNDRV_CHMAP_TC,		/* top center */
+		SNDRV_CHMAP_TFL,	/* top front left */
+		SNDRV_CHMAP_TFC,	/* top front center */
+		SNDRV_CHMAP_TFR,	/* top front right */
+		SNDRV_CHMAP_TRL,	/* top back left */
+		SNDRV_CHMAP_TRC,	/* top back center */
+		SNDRV_CHMAP_TRR,	/* top back right */
+		SNDRV_CHMAP_TFLC,	/* top front left of center */
+		SNDRV_CHMAP_TFRC,	/* top front right of center */
+		SNDRV_CHMAP_LLFE,	/* left LFE */
+		SNDRV_CHMAP_RLFE,	/* right LFE */
+		SNDRV_CHMAP_TSL,	/* top side left */
+		SNDRV_CHMAP_TSR,	/* top side right */
+		SNDRV_CHMAP_BC,		/* bottom center */
+		SNDRV_CHMAP_RLC,	/* back left of center */
+		SNDRV_CHMAP_RRC,	/* back right of center */
+		0 /* terminator */
+	};
+	struct snd_pcm_chmap_elem *chmap;
+	const unsigned int *maps;
+	int c;
+
+	if (channels > ARRAY_SIZE(chmap->map))
+		return NULL;
+
+	chmap = kzalloc(sizeof(*chmap), GFP_KERNEL);
+	if (!chmap)
+		return NULL;
+
+	maps = protocol == UAC_VERSION_2 ? uac2_maps : uac1_maps;
+	chmap->channels = channels;
+	c = 0;
+
+	if (bits) {
+		for (; bits && *maps; maps++, bits >>= 1)
+			if (bits & 1)
+				chmap->map[c++] = *maps;
+	} else {
+		/* If we're missing wChannelConfig, then guess something
+		    to make sure the channel map is not skipped entirely */
+		if (channels == 1)
+			chmap->map[c++] = SNDRV_CHMAP_MONO;
+		else
+			for (; c < channels && *maps; maps++)
+				chmap->map[c++] = *maps;
+	}
+
+	for (; c < channels; c++)
+		chmap->map[c] = SNDRV_CHMAP_UNKNOWN;
+
+	return chmap;
+}
+
+/* UAC3 device stores channels information in Cluster Descriptors */
+static struct
+snd_pcm_chmap_elem *convert_chmap_v3(struct uac3_cluster_header_descriptor
+								*cluster)
+{
+	unsigned int channels = cluster->bNrChannels;
+	struct snd_pcm_chmap_elem *chmap;
+	void *p = cluster;
+	int len, c;
+
+	if (channels > ARRAY_SIZE(chmap->map))
+		return NULL;
+
+	chmap = kzalloc(sizeof(*chmap), GFP_KERNEL);
+	if (!chmap)
+		return NULL;
+
+	len = le16_to_cpu(cluster->wLength);
+	c = 0;
+	p += sizeof(struct uac3_cluster_header_descriptor);
+
+	while (((p - (void *)cluster) < len) && (c < channels)) {
+		struct uac3_cluster_segment_descriptor *cs_desc = p;
+		u16 cs_len;
+		u8 cs_type;
+
+		cs_len = le16_to_cpu(cs_desc->wLength);
+		cs_type = cs_desc->bSegmentType;
+
+		if (cs_type == UAC3_CHANNEL_INFORMATION) {
+			struct uac3_cluster_information_segment_descriptor *is = p;
+			unsigned char map;
+
+			/*
+			 * TODO: this conversion is not complete, update it
+			 * after adding UAC3 values to asound.h
+			 */
+			switch (is->bChRelationship) {
+			case UAC3_CH_MONO:
+				map = SNDRV_CHMAP_MONO;
+				break;
+			case UAC3_CH_LEFT:
+			case UAC3_CH_FRONT_LEFT:
+			case UAC3_CH_HEADPHONE_LEFT:
+				map = SNDRV_CHMAP_FL;
+				break;
+			case UAC3_CH_RIGHT:
+			case UAC3_CH_FRONT_RIGHT:
+			case UAC3_CH_HEADPHONE_RIGHT:
+				map = SNDRV_CHMAP_FR;
+				break;
+			case UAC3_CH_FRONT_CENTER:
+				map = SNDRV_CHMAP_FC;
+				break;
+			case UAC3_CH_FRONT_LEFT_OF_CENTER:
+				map = SNDRV_CHMAP_FLC;
+				break;
+			case UAC3_CH_FRONT_RIGHT_OF_CENTER:
+				map = SNDRV_CHMAP_FRC;
+				break;
+			case UAC3_CH_SIDE_LEFT:
+				map = SNDRV_CHMAP_SL;
+				break;
+			case UAC3_CH_SIDE_RIGHT:
+				map = SNDRV_CHMAP_SR;
+				break;
+			case UAC3_CH_BACK_LEFT:
+				map = SNDRV_CHMAP_RL;
+				break;
+			case UAC3_CH_BACK_RIGHT:
+				map = SNDRV_CHMAP_RR;
+				break;
+			case UAC3_CH_BACK_CENTER:
+				map = SNDRV_CHMAP_RC;
+				break;
+			case UAC3_CH_BACK_LEFT_OF_CENTER:
+				map = SNDRV_CHMAP_RLC;
+				break;
+			case UAC3_CH_BACK_RIGHT_OF_CENTER:
+				map = SNDRV_CHMAP_RRC;
+				break;
+			case UAC3_CH_TOP_CENTER:
+				map = SNDRV_CHMAP_TC;
+				break;
+			case UAC3_CH_TOP_FRONT_LEFT:
+				map = SNDRV_CHMAP_TFL;
+				break;
+			case UAC3_CH_TOP_FRONT_RIGHT:
+				map = SNDRV_CHMAP_TFR;
+				break;
+			case UAC3_CH_TOP_FRONT_CENTER:
+				map = SNDRV_CHMAP_TFC;
+				break;
+			case UAC3_CH_TOP_FRONT_LOC:
+				map = SNDRV_CHMAP_TFLC;
+				break;
+			case UAC3_CH_TOP_FRONT_ROC:
+				map = SNDRV_CHMAP_TFRC;
+				break;
+			case UAC3_CH_TOP_SIDE_LEFT:
+				map = SNDRV_CHMAP_TSL;
+				break;
+			case UAC3_CH_TOP_SIDE_RIGHT:
+				map = SNDRV_CHMAP_TSR;
+				break;
+			case UAC3_CH_TOP_BACK_LEFT:
+				map = SNDRV_CHMAP_TRL;
+				break;
+			case UAC3_CH_TOP_BACK_RIGHT:
+				map = SNDRV_CHMAP_TRR;
+				break;
+			case UAC3_CH_TOP_BACK_CENTER:
+				map = SNDRV_CHMAP_TRC;
+				break;
+			case UAC3_CH_BOTTOM_CENTER:
+				map = SNDRV_CHMAP_BC;
+				break;
+			case UAC3_CH_LOW_FREQUENCY_EFFECTS:
+				map = SNDRV_CHMAP_LFE;
+				break;
+			case UAC3_CH_LFE_LEFT:
+				map = SNDRV_CHMAP_LLFE;
+				break;
+			case UAC3_CH_LFE_RIGHT:
+				map = SNDRV_CHMAP_RLFE;
+				break;
+			case UAC3_CH_RELATIONSHIP_UNDEFINED:
+			default:
+				map = SNDRV_CHMAP_UNKNOWN;
+				break;
+			}
+			chmap->map[c++] = map;
+		}
+		p += cs_len;
+	}
+
+	if (channels < c)
+		pr_err("%s: channel number mismatch\n", __func__);
+
+	chmap->channels = channels;
+
+	for (; c < channels; c++)
+		chmap->map[c] = SNDRV_CHMAP_UNKNOWN;
+
+	return chmap;
+}
+
+/*
+ * add this endpoint to the chip instance.
+ * if a stream with the same endpoint already exists, append to it.
+ * if not, create a new pcm stream. note, fp is added to the substream
+ * fmt_list and will be freed on the chip instance release. do not free
+ * fp or do remove it from the substream fmt_list to avoid double-free.
+ */
+static int __snd_usb_add_audio_stream(struct snd_usb_audio *chip,
+				      int stream,
+				      struct audioformat *fp,
+				      struct snd_usb_power_domain *pd)
+
+{
+	struct snd_usb_stream *as;
+	struct snd_usb_substream *subs;
+	struct snd_pcm *pcm;
+	int err;
+
+	list_for_each_entry(as, &chip->pcm_list, list) {
+		if (as->fmt_type != fp->fmt_type)
+			continue;
+		subs = &as->substream[stream];
+		if (subs->ep_num == fp->endpoint) {
+			list_add_tail(&fp->list, &subs->fmt_list);
+			subs->num_formats++;
+			subs->formats |= fp->formats;
+			return 0;
+		}
+	}
+	/* look for an empty stream */
+	list_for_each_entry(as, &chip->pcm_list, list) {
+		if (as->fmt_type != fp->fmt_type)
+			continue;
+		subs = &as->substream[stream];
+		if (subs->ep_num)
+			continue;
+		err = snd_pcm_new_stream(as->pcm, stream, 1);
+		if (err < 0)
+			return err;
+		snd_usb_init_substream(as, stream, fp, pd);
+		return add_chmap(as->pcm, stream, subs);
+	}
+
+	/* create a new pcm */
+	as = kzalloc(sizeof(*as), GFP_KERNEL);
+	if (!as)
+		return -ENOMEM;
+	as->pcm_index = chip->pcm_devs;
+	as->chip = chip;
+	as->fmt_type = fp->fmt_type;
+	err = snd_pcm_new(chip->card, "USB Audio", chip->pcm_devs,
+			  stream == SNDRV_PCM_STREAM_PLAYBACK ? 1 : 0,
+			  stream == SNDRV_PCM_STREAM_PLAYBACK ? 0 : 1,
+			  &pcm);
+	if (err < 0) {
+		kfree(as);
+		return err;
+	}
+	as->pcm = pcm;
+	pcm->private_data = as;
+	pcm->private_free = snd_usb_audio_pcm_free;
+	pcm->info_flags = 0;
+	if (chip->pcm_devs > 0)
+		sprintf(pcm->name, "USB Audio #%d", chip->pcm_devs);
+	else
+		strcpy(pcm->name, "USB Audio");
+
+	snd_usb_init_substream(as, stream, fp, pd);
+
+	/*
+	 * Keep using head insertion for M-Audio Audiophile USB (tm) which has a
+	 * fix to swap capture stream order in conf/cards/USB-audio.conf
+	 */
+	if (chip->usb_id == USB_ID(0x0763, 0x2003))
+		list_add(&as->list, &chip->pcm_list);
+	else
+		list_add_tail(&as->list, &chip->pcm_list);
+
+	chip->pcm_devs++;
+
+	snd_usb_proc_pcm_format_add(as);
+
+	return add_chmap(pcm, stream, &as->substream[stream]);
+}
+
+int snd_usb_add_audio_stream(struct snd_usb_audio *chip,
+			     int stream,
+			     struct audioformat *fp)
+{
+	return __snd_usb_add_audio_stream(chip, stream, fp, NULL);
+}
+
+static int snd_usb_add_audio_stream_v3(struct snd_usb_audio *chip,
+				       int stream,
+				       struct audioformat *fp,
+				       struct snd_usb_power_domain *pd)
+{
+	return __snd_usb_add_audio_stream(chip, stream, fp, pd);
+}
+
+static int parse_uac_endpoint_attributes(struct snd_usb_audio *chip,
+					 struct usb_host_interface *alts,
+					 int protocol, int iface_no)
+{
+	/* parsed with a v1 header here. that's ok as we only look at the
+	 * header first which is the same for both versions */
+	struct uac_iso_endpoint_descriptor *csep;
+	struct usb_interface_descriptor *altsd = get_iface_desc(alts);
+	int attributes = 0;
+
+	csep = snd_usb_find_desc(alts->endpoint[0].extra, alts->endpoint[0].extralen, NULL, USB_DT_CS_ENDPOINT);
+
+	/* Creamware Noah has this descriptor after the 2nd endpoint */
+	if (!csep && altsd->bNumEndpoints >= 2)
+		csep = snd_usb_find_desc(alts->endpoint[1].extra, alts->endpoint[1].extralen, NULL, USB_DT_CS_ENDPOINT);
+
+	/*
+	 * If we can't locate the USB_DT_CS_ENDPOINT descriptor in the extra
+	 * bytes after the first endpoint, go search the entire interface.
+	 * Some devices have it directly *before* the standard endpoint.
+	 */
+	if (!csep)
+		csep = snd_usb_find_desc(alts->extra, alts->extralen, NULL, USB_DT_CS_ENDPOINT);
+
+	if (!csep || csep->bLength < 7 ||
+	    csep->bDescriptorSubtype != UAC_EP_GENERAL) {
+		usb_audio_warn(chip,
+			       "%u:%d : no or invalid class specific endpoint descriptor\n",
+			       iface_no, altsd->bAlternateSetting);
+		return 0;
+	}
+
+	if (protocol == UAC_VERSION_1) {
+		attributes = csep->bmAttributes;
+	} else if (protocol == UAC_VERSION_2) {
+		struct uac2_iso_endpoint_descriptor *csep2 =
+			(struct uac2_iso_endpoint_descriptor *) csep;
+
+		attributes = csep->bmAttributes & UAC_EP_CS_ATTR_FILL_MAX;
+
+		/* emulate the endpoint attributes of a v1 device */
+		if (csep2->bmControls & UAC2_CONTROL_PITCH)
+			attributes |= UAC_EP_CS_ATTR_PITCH_CONTROL;
+	} else { /* UAC_VERSION_3 */
+		struct uac3_iso_endpoint_descriptor *csep3 =
+			(struct uac3_iso_endpoint_descriptor *) csep;
+
+		/* emulate the endpoint attributes of a v1 device */
+		if (le32_to_cpu(csep3->bmControls) & UAC2_CONTROL_PITCH)
+			attributes |= UAC_EP_CS_ATTR_PITCH_CONTROL;
+	}
+
+	return attributes;
+}
+
+/* find an input terminal descriptor (either UAC1 or UAC2) with the given
+ * terminal id
+ */
+static void *
+snd_usb_find_input_terminal_descriptor(struct usb_host_interface *ctrl_iface,
+					       int terminal_id)
+{
+	struct uac2_input_terminal_descriptor *term = NULL;
+
+	while ((term = snd_usb_find_csint_desc(ctrl_iface->extra,
+					       ctrl_iface->extralen,
+					       term, UAC_INPUT_TERMINAL))) {
+		if (term->bTerminalID == terminal_id)
+			return term;
+	}
+
+	return NULL;
+}
+
+static void *
+snd_usb_find_output_terminal_descriptor(struct usb_host_interface *ctrl_iface,
+					int terminal_id)
+{
+	/* OK to use with both UAC2 and UAC3 */
+	struct uac2_output_terminal_descriptor *term = NULL;
+
+	while ((term = snd_usb_find_csint_desc(ctrl_iface->extra,
+					       ctrl_iface->extralen,
+					       term, UAC_OUTPUT_TERMINAL))) {
+		if (term->bTerminalID == terminal_id)
+			return term;
+	}
+
+	return NULL;
+}
+
+static struct audioformat *
+audio_format_alloc_init(struct snd_usb_audio *chip,
+		       struct usb_host_interface *alts,
+		       int protocol, int iface_no, int altset_idx,
+		       int altno, int num_channels, int clock)
+{
+	struct audioformat *fp;
+
+	fp = kzalloc(sizeof(*fp), GFP_KERNEL);
+	if (!fp)
+		return NULL;
+
+	fp->iface = iface_no;
+	fp->altsetting = altno;
+	fp->altset_idx = altset_idx;
+	fp->endpoint = get_endpoint(alts, 0)->bEndpointAddress;
+	fp->ep_attr = get_endpoint(alts, 0)->bmAttributes;
+	fp->datainterval = snd_usb_parse_datainterval(chip, alts);
+	fp->protocol = protocol;
+	fp->maxpacksize = le16_to_cpu(get_endpoint(alts, 0)->wMaxPacketSize);
+	fp->channels = num_channels;
+	if (snd_usb_get_speed(chip->dev) == USB_SPEED_HIGH)
+		fp->maxpacksize = (((fp->maxpacksize >> 11) & 3) + 1)
+				* (fp->maxpacksize & 0x7ff);
+	fp->clock = clock;
+	INIT_LIST_HEAD(&fp->list);
+
+	return fp;
+}
+
+static struct audioformat *
+snd_usb_get_audioformat_uac12(struct snd_usb_audio *chip,
+			      struct usb_host_interface *alts,
+			      int protocol, int iface_no, int altset_idx,
+			      int altno, int stream, int bm_quirk)
+{
+	struct usb_device *dev = chip->dev;
+	struct uac_format_type_i_continuous_descriptor *fmt;
+	unsigned int num_channels = 0, chconfig = 0;
+	struct audioformat *fp;
+	int clock = 0;
+	u64 format;
+
+	/* get audio formats */
+	if (protocol == UAC_VERSION_1) {
+		struct uac1_as_header_descriptor *as =
+			snd_usb_find_csint_desc(alts->extra, alts->extralen,
+						NULL, UAC_AS_GENERAL);
+		struct uac_input_terminal_descriptor *iterm;
+
+		if (!as) {
+			dev_err(&dev->dev,
+				"%u:%d : UAC_AS_GENERAL descriptor not found\n",
+				iface_no, altno);
+			return NULL;
+		}
+
+		if (as->bLength < sizeof(*as)) {
+			dev_err(&dev->dev,
+				"%u:%d : invalid UAC_AS_GENERAL desc\n",
+				iface_no, altno);
+			return NULL;
+		}
+
+		format = le16_to_cpu(as->wFormatTag); /* remember the format value */
+
+		iterm = snd_usb_find_input_terminal_descriptor(chip->ctrl_intf,
+							     as->bTerminalLink);
+		if (iterm) {
+			num_channels = iterm->bNrChannels;
+			chconfig = le16_to_cpu(iterm->wChannelConfig);
+		}
+	} else { /* UAC_VERSION_2 */
+		struct uac2_input_terminal_descriptor *input_term;
+		struct uac2_output_terminal_descriptor *output_term;
+		struct uac2_as_header_descriptor *as =
+			snd_usb_find_csint_desc(alts->extra, alts->extralen,
+						NULL, UAC_AS_GENERAL);
+
+		if (!as) {
+			dev_err(&dev->dev,
+				"%u:%d : UAC_AS_GENERAL descriptor not found\n",
+				iface_no, altno);
+			return NULL;
+		}
+
+		if (as->bLength < sizeof(*as)) {
+			dev_err(&dev->dev,
+				"%u:%d : invalid UAC_AS_GENERAL desc\n",
+				iface_no, altno);
+			return NULL;
+		}
+
+		num_channels = as->bNrChannels;
+		format = le32_to_cpu(as->bmFormats);
+		chconfig = le32_to_cpu(as->bmChannelConfig);
+
+		/*
+		 * lookup the terminal associated to this interface
+		 * to extract the clock
+		 */
+		input_term = snd_usb_find_input_terminal_descriptor(chip->ctrl_intf,
+								    as->bTerminalLink);
+		if (input_term) {
+			clock = input_term->bCSourceID;
+			if (!chconfig && (num_channels == input_term->bNrChannels))
+				chconfig = le32_to_cpu(input_term->bmChannelConfig);
+			goto found_clock;
+		}
+
+		output_term = snd_usb_find_output_terminal_descriptor(chip->ctrl_intf,
+								      as->bTerminalLink);
+		if (output_term) {
+			clock = output_term->bCSourceID;
+			goto found_clock;
+		}
+
+		dev_err(&dev->dev,
+			"%u:%d : bogus bTerminalLink %d\n",
+			iface_no, altno, as->bTerminalLink);
+		return NULL;
+	}
+
+found_clock:
+	/* get format type */
+	fmt = snd_usb_find_csint_desc(alts->extra, alts->extralen,
+				      NULL, UAC_FORMAT_TYPE);
+	if (!fmt) {
+		dev_err(&dev->dev,
+			"%u:%d : no UAC_FORMAT_TYPE desc\n",
+			iface_no, altno);
+		return NULL;
+	}
+	if (((protocol == UAC_VERSION_1) && (fmt->bLength < 8))
+			|| ((protocol == UAC_VERSION_2) &&
+					(fmt->bLength < 6))) {
+		dev_err(&dev->dev,
+			"%u:%d : invalid UAC_FORMAT_TYPE desc\n",
+			iface_no, altno);
+		return NULL;
+	}
+
+	/*
+	 * Blue Microphones workaround: The last altsetting is
+	 * identical with the previous one, except for a larger
+	 * packet size, but is actually a mislabeled two-channel
+	 * setting; ignore it.
+	 *
+	 * Part 2: analyze quirk flag and format
+	 */
+	if (bm_quirk && fmt->bNrChannels == 1 && fmt->bSubframeSize == 2)
+		return NULL;
+
+	fp = audio_format_alloc_init(chip, alts, protocol, iface_no,
+				     altset_idx, altno, num_channels, clock);
+	if (!fp)
+		return ERR_PTR(-ENOMEM);
+
+	fp->attributes = parse_uac_endpoint_attributes(chip, alts, protocol,
+						       iface_no);
+
+	/* some quirks for attributes here */
+	snd_usb_audioformat_attributes_quirk(chip, fp, stream);
+
+	/* ok, let's parse further... */
+	if (snd_usb_parse_audio_format(chip, fp, format,
+					fmt, stream) < 0) {
+		kfree(fp->rate_table);
+		kfree(fp);
+		return NULL;
+	}
+
+	/* Create chmap */
+	if (fp->channels != num_channels)
+		chconfig = 0;
+
+	fp->chmap = convert_chmap(fp->channels, chconfig, protocol);
+
+	return fp;
+}
+
+static struct audioformat *
+snd_usb_get_audioformat_uac3(struct snd_usb_audio *chip,
+			     struct usb_host_interface *alts,
+			     struct snd_usb_power_domain **pd_out,
+			     int iface_no, int altset_idx,
+			     int altno, int stream)
+{
+	struct usb_device *dev = chip->dev;
+	struct uac3_input_terminal_descriptor *input_term;
+	struct uac3_output_terminal_descriptor *output_term;
+	struct uac3_cluster_header_descriptor *cluster;
+	struct uac3_as_header_descriptor *as = NULL;
+	struct uac3_hc_descriptor_header hc_header;
+	struct snd_pcm_chmap_elem *chmap;
+	struct snd_usb_power_domain *pd;
+	unsigned char badd_profile;
+	u64 badd_formats = 0;
+	unsigned int num_channels;
+	struct audioformat *fp;
+	u16 cluster_id, wLength;
+	int clock = 0;
+	int err;
+
+	badd_profile = chip->badd_profile;
+
+	if (badd_profile >= UAC3_FUNCTION_SUBCLASS_GENERIC_IO) {
+		unsigned int maxpacksize =
+			le16_to_cpu(get_endpoint(alts, 0)->wMaxPacketSize);
+
+		switch (maxpacksize) {
+		default:
+			dev_err(&dev->dev,
+				"%u:%d : incorrect wMaxPacketSize for BADD profile\n",
+				iface_no, altno);
+			return NULL;
+		case UAC3_BADD_EP_MAXPSIZE_SYNC_MONO_16:
+		case UAC3_BADD_EP_MAXPSIZE_ASYNC_MONO_16:
+			badd_formats = SNDRV_PCM_FMTBIT_S16_LE;
+			num_channels = 1;
+			break;
+		case UAC3_BADD_EP_MAXPSIZE_SYNC_MONO_24:
+		case UAC3_BADD_EP_MAXPSIZE_ASYNC_MONO_24:
+			badd_formats = SNDRV_PCM_FMTBIT_S24_3LE;
+			num_channels = 1;
+			break;
+		case UAC3_BADD_EP_MAXPSIZE_SYNC_STEREO_16:
+		case UAC3_BADD_EP_MAXPSIZE_ASYNC_STEREO_16:
+			badd_formats = SNDRV_PCM_FMTBIT_S16_LE;
+			num_channels = 2;
+			break;
+		case UAC3_BADD_EP_MAXPSIZE_SYNC_STEREO_24:
+		case UAC3_BADD_EP_MAXPSIZE_ASYNC_STEREO_24:
+			badd_formats = SNDRV_PCM_FMTBIT_S24_3LE;
+			num_channels = 2;
+			break;
+		}
+
+		chmap = kzalloc(sizeof(*chmap), GFP_KERNEL);
+		if (!chmap)
+			return ERR_PTR(-ENOMEM);
+
+		if (num_channels == 1) {
+			chmap->map[0] = SNDRV_CHMAP_MONO;
+		} else {
+			chmap->map[0] = SNDRV_CHMAP_FL;
+			chmap->map[1] = SNDRV_CHMAP_FR;
+		}
+
+		chmap->channels = num_channels;
+		clock = UAC3_BADD_CS_ID9;
+		goto found_clock;
+	}
+
+	as = snd_usb_find_csint_desc(alts->extra, alts->extralen,
+				     NULL, UAC_AS_GENERAL);
+	if (!as) {
+		dev_err(&dev->dev,
+			"%u:%d : UAC_AS_GENERAL descriptor not found\n",
+			iface_no, altno);
+		return NULL;
+	}
+
+	if (as->bLength < sizeof(*as)) {
+		dev_err(&dev->dev,
+			"%u:%d : invalid UAC_AS_GENERAL desc\n",
+			iface_no, altno);
+		return NULL;
+	}
+
+	cluster_id = le16_to_cpu(as->wClusterDescrID);
+	if (!cluster_id) {
+		dev_err(&dev->dev,
+			"%u:%d : no cluster descriptor\n",
+			iface_no, altno);
+		return NULL;
+	}
+
+	/*
+	 * Get number of channels and channel map through
+	 * High Capability Cluster Descriptor
+	 *
+	 * First step: get High Capability header and
+	 * read size of Cluster Descriptor
+	 */
+	err = snd_usb_ctl_msg(chip->dev,
+			usb_rcvctrlpipe(chip->dev, 0),
+			UAC3_CS_REQ_HIGH_CAPABILITY_DESCRIPTOR,
+			USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_IN,
+			cluster_id,
+			snd_usb_ctrl_intf(chip),
+			&hc_header, sizeof(hc_header));
+	if (err < 0)
+		return ERR_PTR(err);
+	else if (err != sizeof(hc_header)) {
+		dev_err(&dev->dev,
+			"%u:%d : can't get High Capability descriptor\n",
+			iface_no, altno);
+		return ERR_PTR(-EIO);
+	}
+
+	/*
+	 * Second step: allocate needed amount of memory
+	 * and request Cluster Descriptor
+	 */
+	wLength = le16_to_cpu(hc_header.wLength);
+	cluster = kzalloc(wLength, GFP_KERNEL);
+	if (!cluster)
+		return ERR_PTR(-ENOMEM);
+	err = snd_usb_ctl_msg(chip->dev,
+			usb_rcvctrlpipe(chip->dev, 0),
+			UAC3_CS_REQ_HIGH_CAPABILITY_DESCRIPTOR,
+			USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_IN,
+			cluster_id,
+			snd_usb_ctrl_intf(chip),
+			cluster, wLength);
+	if (err < 0) {
+		kfree(cluster);
+		return ERR_PTR(err);
+	} else if (err != wLength) {
+		dev_err(&dev->dev,
+			"%u:%d : can't get Cluster Descriptor\n",
+			iface_no, altno);
+		kfree(cluster);
+		return ERR_PTR(-EIO);
+	}
+
+	num_channels = cluster->bNrChannels;
+	chmap = convert_chmap_v3(cluster);
+	kfree(cluster);
+
+	/*
+	 * lookup the terminal associated to this interface
+	 * to extract the clock
+	 */
+	input_term = snd_usb_find_input_terminal_descriptor(chip->ctrl_intf,
+							    as->bTerminalLink);
+	if (input_term) {
+		clock = input_term->bCSourceID;
+		goto found_clock;
+	}
+
+	output_term = snd_usb_find_output_terminal_descriptor(chip->ctrl_intf,
+							     as->bTerminalLink);
+	if (output_term) {
+		clock = output_term->bCSourceID;
+		goto found_clock;
+	}
+
+	dev_err(&dev->dev, "%u:%d : bogus bTerminalLink %d\n",
+			iface_no, altno, as->bTerminalLink);
+	kfree(chmap);
+	return NULL;
+
+found_clock:
+	fp = audio_format_alloc_init(chip, alts, UAC_VERSION_3, iface_no,
+				     altset_idx, altno, num_channels, clock);
+	if (!fp) {
+		kfree(chmap);
+		return ERR_PTR(-ENOMEM);
+	}
+
+	fp->chmap = chmap;
+
+	if (badd_profile >= UAC3_FUNCTION_SUBCLASS_GENERIC_IO) {
+		fp->attributes = 0; /* No attributes */
+
+		fp->fmt_type = UAC_FORMAT_TYPE_I;
+		fp->formats = badd_formats;
+
+		fp->nr_rates = 0;	/* SNDRV_PCM_RATE_CONTINUOUS */
+		fp->rate_min = UAC3_BADD_SAMPLING_RATE;
+		fp->rate_max = UAC3_BADD_SAMPLING_RATE;
+		fp->rates = SNDRV_PCM_RATE_CONTINUOUS;
+
+		pd = kzalloc(sizeof(*pd), GFP_KERNEL);
+		if (!pd) {
+			kfree(fp->rate_table);
+			kfree(fp);
+			return NULL;
+		}
+		pd->pd_id = (stream == SNDRV_PCM_STREAM_PLAYBACK) ?
+					UAC3_BADD_PD_ID10 : UAC3_BADD_PD_ID11;
+		pd->pd_d1d0_rec = UAC3_BADD_PD_RECOVER_D1D0;
+		pd->pd_d2d0_rec = UAC3_BADD_PD_RECOVER_D2D0;
+
+	} else {
+		fp->attributes = parse_uac_endpoint_attributes(chip, alts,
+							       UAC_VERSION_3,
+							       iface_no);
+
+		pd = snd_usb_find_power_domain(chip->ctrl_intf,
+					       as->bTerminalLink);
+
+		/* ok, let's parse further... */
+		if (snd_usb_parse_audio_format_v3(chip, fp, as, stream) < 0) {
+			kfree(pd);
+			kfree(fp->chmap);
+			kfree(fp->rate_table);
+			kfree(fp);
+			return NULL;
+		}
+	}
+
+	if (pd)
+		*pd_out = pd;
+
+	return fp;
+}
+
+int snd_usb_parse_audio_interface(struct snd_usb_audio *chip, int iface_no)
+{
+	struct usb_device *dev;
+	struct usb_interface *iface;
+	struct usb_host_interface *alts;
+	struct usb_interface_descriptor *altsd;
+	int i, altno, err, stream;
+	struct audioformat *fp = NULL;
+	struct snd_usb_power_domain *pd = NULL;
+	int num, protocol;
+
+	dev = chip->dev;
+
+	/* parse the interface's altsettings */
+	iface = usb_ifnum_to_if(dev, iface_no);
+
+	num = iface->num_altsetting;
+
+	/*
+	 * Dallas DS4201 workaround: It presents 5 altsettings, but the last
+	 * one misses syncpipe, and does not produce any sound.
+	 */
+	if (chip->usb_id == USB_ID(0x04fa, 0x4201))
+		num = 4;
+
+	for (i = 0; i < num; i++) {
+		alts = &iface->altsetting[i];
+		altsd = get_iface_desc(alts);
+		protocol = altsd->bInterfaceProtocol;
+		/* skip invalid one */
+		if (((altsd->bInterfaceClass != USB_CLASS_AUDIO ||
+		      (altsd->bInterfaceSubClass != USB_SUBCLASS_AUDIOSTREAMING &&
+		       altsd->bInterfaceSubClass != USB_SUBCLASS_VENDOR_SPEC)) &&
+		     altsd->bInterfaceClass != USB_CLASS_VENDOR_SPEC) ||
+		    altsd->bNumEndpoints < 1 ||
+		    le16_to_cpu(get_endpoint(alts, 0)->wMaxPacketSize) == 0)
+			continue;
+		/* must be isochronous */
+		if ((get_endpoint(alts, 0)->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) !=
+		    USB_ENDPOINT_XFER_ISOC)
+			continue;
+		/* check direction */
+		stream = (get_endpoint(alts, 0)->bEndpointAddress & USB_DIR_IN) ?
+			SNDRV_PCM_STREAM_CAPTURE : SNDRV_PCM_STREAM_PLAYBACK;
+		altno = altsd->bAlternateSetting;
+
+		if (snd_usb_apply_interface_quirk(chip, iface_no, altno))
+			continue;
+
+		/*
+		 * Roland audio streaming interfaces are marked with protocols
+		 * 0/1/2, but are UAC 1 compatible.
+		 */
+		if (USB_ID_VENDOR(chip->usb_id) == 0x0582 &&
+		    altsd->bInterfaceClass == USB_CLASS_VENDOR_SPEC &&
+		    protocol <= 2)
+			protocol = UAC_VERSION_1;
+
+		switch (protocol) {
+		default:
+			dev_dbg(&dev->dev, "%u:%d: unknown interface protocol %#02x, assuming v1\n",
+				iface_no, altno, protocol);
+			protocol = UAC_VERSION_1;
+			/* fall through */
+		case UAC_VERSION_1:
+			/* fall through */
+		case UAC_VERSION_2: {
+			int bm_quirk = 0;
+
+			/*
+			 * Blue Microphones workaround: The last altsetting is
+			 * identical with the previous one, except for a larger
+			 * packet size, but is actually a mislabeled two-channel
+			 * setting; ignore it.
+			 *
+			 * Part 1: prepare quirk flag
+			 */
+			if (altno == 2 && num == 3 &&
+			    fp && fp->altsetting == 1 && fp->channels == 1 &&
+			    fp->formats == SNDRV_PCM_FMTBIT_S16_LE &&
+			    protocol == UAC_VERSION_1 &&
+			    le16_to_cpu(get_endpoint(alts, 0)->wMaxPacketSize) ==
+							fp->maxpacksize * 2)
+				bm_quirk = 1;
+
+			fp = snd_usb_get_audioformat_uac12(chip, alts, protocol,
+							   iface_no, i, altno,
+							   stream, bm_quirk);
+			break;
+		}
+		case UAC_VERSION_3:
+			fp = snd_usb_get_audioformat_uac3(chip, alts, &pd,
+						iface_no, i, altno, stream);
+			break;
+		}
+
+		if (!fp)
+			continue;
+		else if (IS_ERR(fp))
+			return PTR_ERR(fp);
+
+		dev_dbg(&dev->dev, "%u:%d: add audio endpoint %#x\n", iface_no, altno, fp->endpoint);
+		if (protocol == UAC_VERSION_3)
+			err = snd_usb_add_audio_stream_v3(chip, stream, fp, pd);
+		else
+			err = snd_usb_add_audio_stream(chip, stream, fp);
+
+		if (err < 0) {
+			list_del(&fp->list); /* unlink for avoiding double-free */
+			kfree(pd);
+			kfree(fp->rate_table);
+			kfree(fp->chmap);
+			kfree(fp);
+			return err;
+		}
+		/* try to set the interface... */
+		usb_set_interface(chip->dev, iface_no, altno);
+		snd_usb_init_pitch(chip, iface_no, alts, fp);
+		snd_usb_init_sample_rate(chip, iface_no, alts, fp, fp->rate_max);
+	}
+	return 0;
+}
+
diff --git a/sound/usb/stream.h b/sound/usb/stream.h
new file mode 100644
index 0000000..d92e18d
--- /dev/null
+++ b/sound/usb/stream.h
@@ -0,0 +1,13 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __USBAUDIO_STREAM_H
+#define __USBAUDIO_STREAM_H
+
+int snd_usb_parse_audio_interface(struct snd_usb_audio *chip,
+				  int iface_no);
+
+int snd_usb_add_audio_stream(struct snd_usb_audio *chip,
+			     int stream,
+			     struct audioformat *fp);
+
+#endif /* __USBAUDIO_STREAM_H */
+
diff --git a/sound/usb/usbaudio.h b/sound/usb/usbaudio.h
new file mode 100644
index 0000000..b9faeca
--- /dev/null
+++ b/sound/usb/usbaudio.h
@@ -0,0 +1,132 @@
+#ifndef __USBAUDIO_H
+#define __USBAUDIO_H
+/*
+ *   (Tentative) USB Audio Driver for ALSA
+ *
+ *   Copyright (c) 2002 by Takashi Iwai <tiwai@suse.de>
+ *
+ *
+ *   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
+ */
+
+/* handling of USB vendor/product ID pairs as 32-bit numbers */
+#define USB_ID(vendor, product) (((vendor) << 16) | (product))
+#define USB_ID_VENDOR(id) ((id) >> 16)
+#define USB_ID_PRODUCT(id) ((u16)(id))
+
+/*
+ *
+ */
+
+struct snd_usb_audio {
+	int index;
+	struct usb_device *dev;
+	struct snd_card *card;
+	struct usb_interface *pm_intf;
+	u32 usb_id;
+	struct mutex mutex;
+	unsigned int autosuspended:1;	
+	atomic_t active;
+	atomic_t shutdown;
+	atomic_t usage_count;
+	wait_queue_head_t shutdown_wait;
+	unsigned int txfr_quirk:1; /* Subframe boundaries on transfers */
+	unsigned int tx_length_quirk:1; /* Put length specifier in transfers */
+	
+	int num_interfaces;
+	int num_suspended_intf;
+	int sample_rate_read_error;
+
+	int badd_profile;		/* UAC3 BADD profile */
+
+	struct list_head pcm_list;	/* list of pcm streams */
+	struct list_head ep_list;	/* list of audio-related endpoints */
+	int pcm_devs;
+
+	struct list_head midi_list;	/* list of midi interfaces */
+
+	struct list_head mixer_list;	/* list of mixer interfaces */
+
+	int setup;			/* from the 'device_setup' module param */
+	bool autoclock;			/* from the 'autoclock' module param */
+	bool keep_iface;		/* keep interface/altset after closing
+					 * or parameter change
+					 */
+
+	struct usb_host_interface *ctrl_intf;	/* the audio control interface */
+};
+
+#define usb_audio_err(chip, fmt, args...) \
+	dev_err(&(chip)->dev->dev, fmt, ##args)
+#define usb_audio_warn(chip, fmt, args...) \
+	dev_warn(&(chip)->dev->dev, fmt, ##args)
+#define usb_audio_info(chip, fmt, args...) \
+	dev_info(&(chip)->dev->dev, fmt, ##args)
+#define usb_audio_dbg(chip, fmt, args...) \
+	dev_dbg(&(chip)->dev->dev, fmt, ##args)
+
+/*
+ * Information about devices with broken descriptors
+ */
+
+/* special values for .ifnum */
+#define QUIRK_NO_INTERFACE		-2
+#define QUIRK_ANY_INTERFACE		-1
+
+enum quirk_type {
+	QUIRK_IGNORE_INTERFACE,
+	QUIRK_COMPOSITE,
+	QUIRK_AUTODETECT,
+	QUIRK_MIDI_STANDARD_INTERFACE,
+	QUIRK_MIDI_FIXED_ENDPOINT,
+	QUIRK_MIDI_YAMAHA,
+	QUIRK_MIDI_ROLAND,
+	QUIRK_MIDI_MIDIMAN,
+	QUIRK_MIDI_NOVATION,
+	QUIRK_MIDI_RAW_BYTES,
+	QUIRK_MIDI_EMAGIC,
+	QUIRK_MIDI_CME,
+	QUIRK_MIDI_AKAI,
+	QUIRK_MIDI_US122L,
+	QUIRK_MIDI_FTDI,
+	QUIRK_MIDI_CH345,
+	QUIRK_AUDIO_STANDARD_INTERFACE,
+	QUIRK_AUDIO_FIXED_ENDPOINT,
+	QUIRK_AUDIO_EDIROL_UAXX,
+	QUIRK_AUDIO_ALIGN_TRANSFER,
+	QUIRK_AUDIO_STANDARD_MIXER,
+
+	QUIRK_TYPE_COUNT
+};
+
+struct snd_usb_audio_quirk {
+	const char *vendor_name;
+	const char *product_name;
+	const char *profile_name;	/* override the card->longname */
+	int16_t ifnum;
+	uint16_t type;
+	const void *data;
+};
+
+#define combine_word(s)    ((*(s)) | ((unsigned int)(s)[1] << 8))
+#define combine_triple(s)  (combine_word(s) | ((unsigned int)(s)[2] << 16))
+#define combine_quad(s)    (combine_triple(s) | ((unsigned int)(s)[3] << 24))
+
+int snd_usb_lock_shutdown(struct snd_usb_audio *chip);
+void snd_usb_unlock_shutdown(struct snd_usb_audio *chip);
+
+extern bool snd_usb_use_vmalloc;
+
+#endif /* __USBAUDIO_H */
diff --git a/sound/usb/usx2y/Makefile b/sound/usb/usx2y/Makefile
new file mode 100644
index 0000000..cc4c2f1
--- /dev/null
+++ b/sound/usb/usx2y/Makefile
@@ -0,0 +1,6 @@
+# SPDX-License-Identifier: GPL-2.0
+snd-usb-usx2y-objs := usbusx2y.o usX2Yhwdep.o usx2yhwdeppcm.o
+snd-usb-us122l-objs := us122l.o
+
+obj-$(CONFIG_SND_USB_USX2Y) += snd-usb-usx2y.o
+obj-$(CONFIG_SND_USB_US122L) += snd-usb-us122l.o
diff --git a/sound/usb/usx2y/us122l.c b/sound/usb/usx2y/us122l.c
new file mode 100644
index 0000000..8082f7b
--- /dev/null
+++ b/sound/usb/usx2y/us122l.c
@@ -0,0 +1,772 @@
+/*
+ * Copyright (C) 2007, 2008 Karsten Wiese <fzu@wemgehoertderstaat.de>
+ *
+ * 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., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <linux/slab.h>
+#include <linux/usb.h>
+#include <linux/usb/audio.h>
+#include <linux/module.h>
+#include <sound/core.h>
+#include <sound/hwdep.h>
+#include <sound/pcm.h>
+#include <sound/initval.h>
+#define MODNAME "US122L"
+#include "usb_stream.c"
+#include "../usbaudio.h"
+#include "../midi.h"
+#include "us122l.h"
+
+MODULE_AUTHOR("Karsten Wiese <fzu@wemgehoertderstaat.de>");
+MODULE_DESCRIPTION("TASCAM "NAME_ALLCAPS" Version 0.5");
+MODULE_LICENSE("GPL");
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;	/* Index 0-max */
+static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;	/* Id for this card */
+							/* Enable this card */
+static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP;
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for "NAME_ALLCAPS".");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for "NAME_ALLCAPS".");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "Enable "NAME_ALLCAPS".");
+
+/* driver_info flags */
+#define US122L_FLAG_US144	BIT(0)
+
+static int snd_us122l_card_used[SNDRV_CARDS];
+
+static int us122l_create_usbmidi(struct snd_card *card)
+{
+	static struct snd_usb_midi_endpoint_info quirk_data = {
+		.out_ep = 4,
+		.in_ep = 3,
+		.out_cables =	0x001,
+		.in_cables =	0x001
+	};
+	static struct snd_usb_audio_quirk quirk = {
+		.vendor_name =	"US122L",
+		.product_name =	NAME_ALLCAPS,
+		.ifnum = 	1,
+		.type = QUIRK_MIDI_US122L,
+		.data = &quirk_data
+	};
+	struct usb_device *dev = US122L(card)->dev;
+	struct usb_interface *iface = usb_ifnum_to_if(dev, 1);
+
+	return snd_usbmidi_create(card, iface,
+				  &US122L(card)->midi_list, &quirk);
+}
+
+static int us144_create_usbmidi(struct snd_card *card)
+{
+	static struct snd_usb_midi_endpoint_info quirk_data = {
+		.out_ep = 4,
+		.in_ep = 3,
+		.out_cables =	0x001,
+		.in_cables =	0x001
+	};
+	static struct snd_usb_audio_quirk quirk = {
+		.vendor_name =	"US144",
+		.product_name =	NAME_ALLCAPS,
+		.ifnum = 	0,
+		.type = QUIRK_MIDI_US122L,
+		.data = &quirk_data
+	};
+	struct usb_device *dev = US122L(card)->dev;
+	struct usb_interface *iface = usb_ifnum_to_if(dev, 0);
+
+	return snd_usbmidi_create(card, iface,
+				  &US122L(card)->midi_list, &quirk);
+}
+
+/*
+ * Wrapper for usb_control_msg().
+ * Allocates a temp buffer to prevent dmaing from/to the stack.
+ */
+static int us122l_ctl_msg(struct usb_device *dev, unsigned int pipe,
+			  __u8 request, __u8 requesttype,
+			  __u16 value, __u16 index, void *data,
+			  __u16 size, int timeout)
+{
+	int err;
+	void *buf = NULL;
+
+	if (size > 0) {
+		buf = kmemdup(data, size, GFP_KERNEL);
+		if (!buf)
+			return -ENOMEM;
+	}
+	err = usb_control_msg(dev, pipe, request, requesttype,
+			      value, index, buf, size, timeout);
+	if (size > 0) {
+		memcpy(data, buf, size);
+		kfree(buf);
+	}
+	return err;
+}
+
+static void pt_info_set(struct usb_device *dev, u8 v)
+{
+	int ret;
+
+	ret = usb_control_msg(dev, usb_sndctrlpipe(dev, 0),
+			      'I',
+			      USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+			      v, 0, NULL, 0, 1000);
+	snd_printdd(KERN_DEBUG "%i\n", ret);
+}
+
+static void usb_stream_hwdep_vm_open(struct vm_area_struct *area)
+{
+	struct us122l *us122l = area->vm_private_data;
+	atomic_inc(&us122l->mmap_count);
+	snd_printdd(KERN_DEBUG "%i\n", atomic_read(&us122l->mmap_count));
+}
+
+static vm_fault_t usb_stream_hwdep_vm_fault(struct vm_fault *vmf)
+{
+	unsigned long offset;
+	struct page *page;
+	void *vaddr;
+	struct us122l *us122l = vmf->vma->vm_private_data;
+	struct usb_stream *s;
+
+	mutex_lock(&us122l->mutex);
+	s = us122l->sk.s;
+	if (!s)
+		goto unlock;
+
+	offset = vmf->pgoff << PAGE_SHIFT;
+	if (offset < PAGE_ALIGN(s->read_size))
+		vaddr = (char *)s + offset;
+	else {
+		offset -= PAGE_ALIGN(s->read_size);
+		if (offset >= PAGE_ALIGN(s->write_size))
+			goto unlock;
+
+		vaddr = us122l->sk.write_page + offset;
+	}
+	page = virt_to_page(vaddr);
+
+	get_page(page);
+	mutex_unlock(&us122l->mutex);
+
+	vmf->page = page;
+
+	return 0;
+unlock:
+	mutex_unlock(&us122l->mutex);
+	return VM_FAULT_SIGBUS;
+}
+
+static void usb_stream_hwdep_vm_close(struct vm_area_struct *area)
+{
+	struct us122l *us122l = area->vm_private_data;
+	atomic_dec(&us122l->mmap_count);
+	snd_printdd(KERN_DEBUG "%i\n", atomic_read(&us122l->mmap_count));
+}
+
+static const struct vm_operations_struct usb_stream_hwdep_vm_ops = {
+	.open = usb_stream_hwdep_vm_open,
+	.fault = usb_stream_hwdep_vm_fault,
+	.close = usb_stream_hwdep_vm_close,
+};
+
+
+static int usb_stream_hwdep_open(struct snd_hwdep *hw, struct file *file)
+{
+	struct us122l	*us122l = hw->private_data;
+	struct usb_interface *iface;
+	snd_printdd(KERN_DEBUG "%p %p\n", hw, file);
+	if (hw->used >= 2)
+		return -EBUSY;
+
+	if (!us122l->first)
+		us122l->first = file;
+
+	if (us122l->is_us144) {
+		iface = usb_ifnum_to_if(us122l->dev, 0);
+		usb_autopm_get_interface(iface);
+	}
+	iface = usb_ifnum_to_if(us122l->dev, 1);
+	usb_autopm_get_interface(iface);
+	return 0;
+}
+
+static int usb_stream_hwdep_release(struct snd_hwdep *hw, struct file *file)
+{
+	struct us122l	*us122l = hw->private_data;
+	struct usb_interface *iface;
+	snd_printdd(KERN_DEBUG "%p %p\n", hw, file);
+
+	if (us122l->is_us144) {
+		iface = usb_ifnum_to_if(us122l->dev, 0);
+		usb_autopm_put_interface(iface);
+	}
+	iface = usb_ifnum_to_if(us122l->dev, 1);
+	usb_autopm_put_interface(iface);
+	if (us122l->first == file)
+		us122l->first = NULL;
+	mutex_lock(&us122l->mutex);
+	if (us122l->master == file)
+		us122l->master = us122l->slave;
+
+	us122l->slave = NULL;
+	mutex_unlock(&us122l->mutex);
+	return 0;
+}
+
+static int usb_stream_hwdep_mmap(struct snd_hwdep *hw,
+				 struct file *filp, struct vm_area_struct *area)
+{
+	unsigned long	size = area->vm_end - area->vm_start;
+	struct us122l	*us122l = hw->private_data;
+	unsigned long offset;
+	struct usb_stream *s;
+	int err = 0;
+	bool read;
+
+	offset = area->vm_pgoff << PAGE_SHIFT;
+	mutex_lock(&us122l->mutex);
+	s = us122l->sk.s;
+	read = offset < s->read_size;
+	if (read && area->vm_flags & VM_WRITE) {
+		err = -EPERM;
+		goto out;
+	}
+	snd_printdd(KERN_DEBUG "%lu %u\n", size,
+		    read ? s->read_size : s->write_size);
+	/* if userspace tries to mmap beyond end of our buffer, fail */
+	if (size > PAGE_ALIGN(read ? s->read_size : s->write_size)) {
+		snd_printk(KERN_WARNING "%lu > %u\n", size,
+			   read ? s->read_size : s->write_size);
+		err = -EINVAL;
+		goto out;
+	}
+
+	area->vm_ops = &usb_stream_hwdep_vm_ops;
+	area->vm_flags |= VM_DONTDUMP;
+	if (!read)
+		area->vm_flags |= VM_DONTEXPAND;
+	area->vm_private_data = us122l;
+	atomic_inc(&us122l->mmap_count);
+out:
+	mutex_unlock(&us122l->mutex);
+	return err;
+}
+
+static __poll_t usb_stream_hwdep_poll(struct snd_hwdep *hw,
+					  struct file *file, poll_table *wait)
+{
+	struct us122l	*us122l = hw->private_data;
+	unsigned	*polled;
+	__poll_t	mask;
+
+	poll_wait(file, &us122l->sk.sleep, wait);
+
+	mask = EPOLLIN | EPOLLOUT | EPOLLWRNORM | EPOLLERR;
+	if (mutex_trylock(&us122l->mutex)) {
+		struct usb_stream *s = us122l->sk.s;
+		if (s && s->state == usb_stream_ready) {
+			if (us122l->first == file)
+				polled = &s->periods_polled;
+			else
+				polled = &us122l->second_periods_polled;
+			if (*polled != s->periods_done) {
+				*polled = s->periods_done;
+				mask = EPOLLIN | EPOLLOUT | EPOLLWRNORM;
+			} else
+				mask = 0;
+		}
+		mutex_unlock(&us122l->mutex);
+	}
+	return mask;
+}
+
+static void us122l_stop(struct us122l *us122l)
+{
+	struct list_head *p;
+	list_for_each(p, &us122l->midi_list)
+		snd_usbmidi_input_stop(p);
+
+	usb_stream_stop(&us122l->sk);
+	usb_stream_free(&us122l->sk);
+}
+
+static int us122l_set_sample_rate(struct usb_device *dev, int rate)
+{
+	unsigned int ep = 0x81;
+	unsigned char data[3];
+	int err;
+
+	data[0] = rate;
+	data[1] = rate >> 8;
+	data[2] = rate >> 16;
+	err = us122l_ctl_msg(dev, usb_sndctrlpipe(dev, 0), UAC_SET_CUR,
+			     USB_TYPE_CLASS|USB_RECIP_ENDPOINT|USB_DIR_OUT,
+			     UAC_EP_CS_ATTR_SAMPLE_RATE << 8, ep, data, 3, 1000);
+	if (err < 0)
+		snd_printk(KERN_ERR "%d: cannot set freq %d to ep 0x%x\n",
+			   dev->devnum, rate, ep);
+	return err;
+}
+
+static bool us122l_start(struct us122l *us122l,
+			 unsigned rate, unsigned period_frames)
+{
+	struct list_head *p;
+	int err;
+	unsigned use_packsize = 0;
+	bool success = false;
+
+	if (us122l->dev->speed == USB_SPEED_HIGH) {
+		/* The us-122l's descriptor defaults to iso max_packsize 78,
+		   which isn't needed for samplerates <= 48000.
+		   Lets save some memory:
+		*/
+		switch (rate) {
+		case 44100:
+			use_packsize = 36;
+			break;
+		case 48000:
+			use_packsize = 42;
+			break;
+		case 88200:
+			use_packsize = 72;
+			break;
+		}
+	}
+	if (!usb_stream_new(&us122l->sk, us122l->dev, 1, 2,
+			    rate, use_packsize, period_frames, 6))
+		goto out;
+
+	err = us122l_set_sample_rate(us122l->dev, rate);
+	if (err < 0) {
+		us122l_stop(us122l);
+		snd_printk(KERN_ERR "us122l_set_sample_rate error \n");
+		goto out;
+	}
+	err = usb_stream_start(&us122l->sk);
+	if (err < 0) {
+		us122l_stop(us122l);
+		snd_printk(KERN_ERR "us122l_start error %i \n", err);
+		goto out;
+	}
+	list_for_each(p, &us122l->midi_list)
+		snd_usbmidi_input_start(p);
+	success = true;
+out:
+	return success;
+}
+
+static int usb_stream_hwdep_ioctl(struct snd_hwdep *hw, struct file *file,
+				  unsigned cmd, unsigned long arg)
+{
+	struct usb_stream_config cfg;
+	struct us122l *us122l = hw->private_data;
+	struct usb_stream *s;
+	unsigned min_period_frames;
+	int err = 0;
+	bool high_speed;
+
+	if (cmd != SNDRV_USB_STREAM_IOCTL_SET_PARAMS)
+		return -ENOTTY;
+
+	if (copy_from_user(&cfg, (void __user *)arg, sizeof(cfg)))
+		return -EFAULT;
+
+	if (cfg.version != USB_STREAM_INTERFACE_VERSION)
+		return -ENXIO;
+
+	high_speed = us122l->dev->speed == USB_SPEED_HIGH;
+	if ((cfg.sample_rate != 44100 && cfg.sample_rate != 48000  &&
+	     (!high_speed ||
+	      (cfg.sample_rate != 88200 && cfg.sample_rate != 96000))) ||
+	    cfg.frame_size != 6 ||
+	    cfg.period_frames > 0x3000)
+		return -EINVAL;
+
+	switch (cfg.sample_rate) {
+	case 44100:
+		min_period_frames = 48;
+		break;
+	case 48000:
+		min_period_frames = 52;
+		break;
+	default:
+		min_period_frames = 104;
+		break;
+	}
+	if (!high_speed)
+		min_period_frames <<= 1;
+	if (cfg.period_frames < min_period_frames)
+		return -EINVAL;
+
+	snd_power_wait(hw->card, SNDRV_CTL_POWER_D0);
+
+	mutex_lock(&us122l->mutex);
+	s = us122l->sk.s;
+	if (!us122l->master)
+		us122l->master = file;
+	else if (us122l->master != file) {
+		if (!s || memcmp(&cfg, &s->cfg, sizeof(cfg))) {
+			err = -EIO;
+			goto unlock;
+		}
+		us122l->slave = file;
+	}
+	if (!s || memcmp(&cfg, &s->cfg, sizeof(cfg)) ||
+	    s->state == usb_stream_xrun) {
+		us122l_stop(us122l);
+		if (!us122l_start(us122l, cfg.sample_rate, cfg.period_frames))
+			err = -EIO;
+		else
+			err = 1;
+	}
+unlock:
+	mutex_unlock(&us122l->mutex);
+	wake_up_all(&us122l->sk.sleep);
+	return err;
+}
+
+#define SND_USB_STREAM_ID "USB STREAM"
+static int usb_stream_hwdep_new(struct snd_card *card)
+{
+	int err;
+	struct snd_hwdep *hw;
+	struct usb_device *dev = US122L(card)->dev;
+
+	err = snd_hwdep_new(card, SND_USB_STREAM_ID, 0, &hw);
+	if (err < 0)
+		return err;
+
+	hw->iface = SNDRV_HWDEP_IFACE_USB_STREAM;
+	hw->private_data = US122L(card);
+	hw->ops.open = usb_stream_hwdep_open;
+	hw->ops.release = usb_stream_hwdep_release;
+	hw->ops.ioctl = usb_stream_hwdep_ioctl;
+	hw->ops.ioctl_compat = usb_stream_hwdep_ioctl;
+	hw->ops.mmap = usb_stream_hwdep_mmap;
+	hw->ops.poll = usb_stream_hwdep_poll;
+
+	sprintf(hw->name, "/dev/bus/usb/%03d/%03d/hwdeppcm",
+		dev->bus->busnum, dev->devnum);
+	return 0;
+}
+
+
+static bool us122l_create_card(struct snd_card *card)
+{
+	int err;
+	struct us122l *us122l = US122L(card);
+
+	if (us122l->is_us144) {
+		err = usb_set_interface(us122l->dev, 0, 1);
+		if (err) {
+			snd_printk(KERN_ERR "usb_set_interface error \n");
+			return false;
+		}
+	}
+	err = usb_set_interface(us122l->dev, 1, 1);
+	if (err) {
+		snd_printk(KERN_ERR "usb_set_interface error \n");
+		return false;
+	}
+
+	pt_info_set(us122l->dev, 0x11);
+	pt_info_set(us122l->dev, 0x10);
+
+	if (!us122l_start(us122l, 44100, 256))
+		return false;
+
+	if (us122l->is_us144)
+		err = us144_create_usbmidi(card);
+	else
+		err = us122l_create_usbmidi(card);
+	if (err < 0) {
+		snd_printk(KERN_ERR "us122l_create_usbmidi error %i \n", err);
+		goto stop;
+	}
+	err = usb_stream_hwdep_new(card);
+	if (err < 0) {
+/* release the midi resources */
+		struct list_head *p;
+		list_for_each(p, &us122l->midi_list)
+			snd_usbmidi_disconnect(p);
+
+		goto stop;
+	}
+	return true;
+
+stop:
+	us122l_stop(us122l);
+	return false;
+}
+
+static void snd_us122l_free(struct snd_card *card)
+{
+	struct us122l	*us122l = US122L(card);
+	int		index = us122l->card_index;
+	if (index >= 0  &&  index < SNDRV_CARDS)
+		snd_us122l_card_used[index] = 0;
+}
+
+static int usx2y_create_card(struct usb_device *device,
+			     struct usb_interface *intf,
+			     struct snd_card **cardp,
+			     unsigned long flags)
+{
+	int		dev;
+	struct snd_card *card;
+	int err;
+
+	for (dev = 0; dev < SNDRV_CARDS; ++dev)
+		if (enable[dev] && !snd_us122l_card_used[dev])
+			break;
+	if (dev >= SNDRV_CARDS)
+		return -ENODEV;
+	err = snd_card_new(&intf->dev, index[dev], id[dev], THIS_MODULE,
+			   sizeof(struct us122l), &card);
+	if (err < 0)
+		return err;
+	snd_us122l_card_used[US122L(card)->card_index = dev] = 1;
+	card->private_free = snd_us122l_free;
+	US122L(card)->dev = device;
+	mutex_init(&US122L(card)->mutex);
+	init_waitqueue_head(&US122L(card)->sk.sleep);
+	US122L(card)->is_us144 = flags & US122L_FLAG_US144;
+	INIT_LIST_HEAD(&US122L(card)->midi_list);
+	strcpy(card->driver, "USB "NAME_ALLCAPS"");
+	sprintf(card->shortname, "TASCAM "NAME_ALLCAPS"");
+	sprintf(card->longname, "%s (%x:%x if %d at %03d/%03d)",
+		card->shortname,
+		le16_to_cpu(device->descriptor.idVendor),
+		le16_to_cpu(device->descriptor.idProduct),
+		0,
+		US122L(card)->dev->bus->busnum,
+		US122L(card)->dev->devnum
+		);
+	*cardp = card;
+	return 0;
+}
+
+static int us122l_usb_probe(struct usb_interface *intf,
+			    const struct usb_device_id *device_id,
+			    struct snd_card **cardp)
+{
+	struct usb_device *device = interface_to_usbdev(intf);
+	struct snd_card *card;
+	int err;
+
+	err = usx2y_create_card(device, intf, &card, device_id->driver_info);
+	if (err < 0)
+		return err;
+
+	if (!us122l_create_card(card)) {
+		snd_card_free(card);
+		return -EINVAL;
+	}
+
+	err = snd_card_register(card);
+	if (err < 0) {
+		snd_card_free(card);
+		return err;
+	}
+
+	usb_get_intf(usb_ifnum_to_if(device, 0));
+	usb_get_dev(device);
+	*cardp = card;
+	return 0;
+}
+
+static int snd_us122l_probe(struct usb_interface *intf,
+			    const struct usb_device_id *id)
+{
+	struct usb_device *device = interface_to_usbdev(intf);
+	struct snd_card *card;
+	int err;
+
+	if (id->driver_info & US122L_FLAG_US144 &&
+			device->speed == USB_SPEED_HIGH) {
+		snd_printk(KERN_ERR "disable ehci-hcd to run US-144 \n");
+		return -ENODEV;
+	}
+
+	snd_printdd(KERN_DEBUG"%p:%i\n",
+		    intf, intf->cur_altsetting->desc.bInterfaceNumber);
+	if (intf->cur_altsetting->desc.bInterfaceNumber != 1)
+		return 0;
+
+	err = us122l_usb_probe(usb_get_intf(intf), id, &card);
+	if (err < 0) {
+		usb_put_intf(intf);
+		return err;
+	}
+
+	usb_set_intfdata(intf, card);
+	return 0;
+}
+
+static void snd_us122l_disconnect(struct usb_interface *intf)
+{
+	struct snd_card *card;
+	struct us122l *us122l;
+	struct list_head *p;
+
+	card = usb_get_intfdata(intf);
+	if (!card)
+		return;
+
+	snd_card_disconnect(card);
+
+	us122l = US122L(card);
+	mutex_lock(&us122l->mutex);
+	us122l_stop(us122l);
+	mutex_unlock(&us122l->mutex);
+
+/* release the midi resources */
+	list_for_each(p, &us122l->midi_list) {
+		snd_usbmidi_disconnect(p);
+	}
+
+	usb_put_intf(usb_ifnum_to_if(us122l->dev, 0));
+	usb_put_intf(usb_ifnum_to_if(us122l->dev, 1));
+	usb_put_dev(us122l->dev);
+
+	while (atomic_read(&us122l->mmap_count))
+		msleep(500);
+
+	snd_card_free(card);
+}
+
+static int snd_us122l_suspend(struct usb_interface *intf, pm_message_t message)
+{
+	struct snd_card *card;
+	struct us122l *us122l;
+	struct list_head *p;
+
+	card = usb_get_intfdata(intf);
+	if (!card)
+		return 0;
+	snd_power_change_state(card, SNDRV_CTL_POWER_D3hot);
+
+	us122l = US122L(card);
+	if (!us122l)
+		return 0;
+
+	list_for_each(p, &us122l->midi_list)
+		snd_usbmidi_input_stop(p);
+
+	mutex_lock(&us122l->mutex);
+	usb_stream_stop(&us122l->sk);
+	mutex_unlock(&us122l->mutex);
+
+	return 0;
+}
+
+static int snd_us122l_resume(struct usb_interface *intf)
+{
+	struct snd_card *card;
+	struct us122l *us122l;
+	struct list_head *p;
+	int err;
+
+	card = usb_get_intfdata(intf);
+	if (!card)
+		return 0;
+
+	us122l = US122L(card);
+	if (!us122l)
+		return 0;
+
+	mutex_lock(&us122l->mutex);
+	/* needed, doesn't restart without: */
+	if (us122l->is_us144) {
+		err = usb_set_interface(us122l->dev, 0, 1);
+		if (err) {
+			snd_printk(KERN_ERR "usb_set_interface error \n");
+			goto unlock;
+		}
+	}
+	err = usb_set_interface(us122l->dev, 1, 1);
+	if (err) {
+		snd_printk(KERN_ERR "usb_set_interface error \n");
+		goto unlock;
+	}
+
+	pt_info_set(us122l->dev, 0x11);
+	pt_info_set(us122l->dev, 0x10);
+
+	err = us122l_set_sample_rate(us122l->dev,
+				     us122l->sk.s->cfg.sample_rate);
+	if (err < 0) {
+		snd_printk(KERN_ERR "us122l_set_sample_rate error \n");
+		goto unlock;
+	}
+	err = usb_stream_start(&us122l->sk);
+	if (err)
+		goto unlock;
+
+	list_for_each(p, &us122l->midi_list)
+		snd_usbmidi_input_start(p);
+unlock:
+	mutex_unlock(&us122l->mutex);
+	snd_power_change_state(card, SNDRV_CTL_POWER_D0);
+	return err;
+}
+
+static const struct usb_device_id snd_us122l_usb_id_table[] = {
+	{
+		.match_flags =	USB_DEVICE_ID_MATCH_DEVICE,
+		.idVendor =	0x0644,
+		.idProduct =	USB_ID_US122L
+	},
+	{	/* US-144 only works at USB1.1! Disable module ehci-hcd. */
+		.match_flags =	USB_DEVICE_ID_MATCH_DEVICE,
+		.idVendor =	0x0644,
+		.idProduct =	USB_ID_US144,
+		.driver_info =	US122L_FLAG_US144
+	},
+	{
+		.match_flags =	USB_DEVICE_ID_MATCH_DEVICE,
+		.idVendor =	0x0644,
+		.idProduct =	USB_ID_US122MKII
+	},
+	{
+		.match_flags =	USB_DEVICE_ID_MATCH_DEVICE,
+		.idVendor =	0x0644,
+		.idProduct =	USB_ID_US144MKII,
+		.driver_info =	US122L_FLAG_US144
+	},
+	{ /* terminator */ }
+};
+
+MODULE_DEVICE_TABLE(usb, snd_us122l_usb_id_table);
+static struct usb_driver snd_us122l_usb_driver = {
+	.name =		"snd-usb-us122l",
+	.probe =	snd_us122l_probe,
+	.disconnect =	snd_us122l_disconnect,
+	.suspend =	snd_us122l_suspend,
+	.resume =	snd_us122l_resume,
+	.reset_resume =	snd_us122l_resume,
+	.id_table =	snd_us122l_usb_id_table,
+	.supports_autosuspend = 1
+};
+
+module_usb_driver(snd_us122l_usb_driver);
diff --git a/sound/usb/usx2y/us122l.h b/sound/usb/usx2y/us122l.h
new file mode 100644
index 0000000..34bea99
--- /dev/null
+++ b/sound/usb/usx2y/us122l.h
@@ -0,0 +1,34 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef US122L_H
+#define US122L_H
+
+
+struct us122l {
+	struct usb_device	*dev;
+	int			card_index;
+	int			stride;
+	struct usb_stream_kernel sk;
+
+	struct mutex		mutex;
+	struct file		*first;
+	unsigned		second_periods_polled;
+	struct file		*master;
+	struct file		*slave;
+	struct list_head	midi_list;
+
+	atomic_t		mmap_count;
+
+	bool			is_us144;
+};
+
+
+#define US122L(c) ((struct us122l *)(c)->private_data)
+
+#define NAME_ALLCAPS "US-122L"
+
+#define USB_ID_US122L 0x800E
+#define USB_ID_US144 0x800F
+#define USB_ID_US122MKII 0x8021
+#define USB_ID_US144MKII 0x8020
+
+#endif
diff --git a/sound/usb/usx2y/usX2Yhwdep.c b/sound/usb/usx2y/usX2Yhwdep.c
new file mode 100644
index 0000000..c1dd9a7
--- /dev/null
+++ b/sound/usb/usx2y/usX2Yhwdep.c
@@ -0,0 +1,262 @@
+/*
+ * Driver for Tascam US-X2Y USB soundcards
+ *
+ * FPGA Loader + ALSA Startup
+ *
+ * Copyright (c) 2003 by Karsten Wiese <annabellesgarden@yahoo.de>
+ *
+ *   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/interrupt.h>
+#include <linux/slab.h>
+#include <linux/usb.h>
+#include <sound/core.h>
+#include <sound/memalloc.h>
+#include <sound/pcm.h>
+#include <sound/hwdep.h>
+#include "usx2y.h"
+#include "usbusx2y.h"
+#include "usX2Yhwdep.h"
+
+static vm_fault_t snd_us428ctls_vm_fault(struct vm_fault *vmf)
+{
+	unsigned long offset;
+	struct page * page;
+	void *vaddr;
+
+	snd_printdd("ENTER, start %lXh, pgoff %ld\n",
+		   vmf->vma->vm_start,
+		   vmf->pgoff);
+	
+	offset = vmf->pgoff << PAGE_SHIFT;
+	vaddr = (char *)((struct usX2Ydev *)vmf->vma->vm_private_data)->us428ctls_sharedmem + offset;
+	page = virt_to_page(vaddr);
+	get_page(page);
+	vmf->page = page;
+
+	snd_printdd("vaddr=%p made us428ctls_vm_fault() page %p\n",
+		    vaddr, page);
+
+	return 0;
+}
+
+static const struct vm_operations_struct us428ctls_vm_ops = {
+	.fault = snd_us428ctls_vm_fault,
+};
+
+static int snd_us428ctls_mmap(struct snd_hwdep * hw, struct file *filp, struct vm_area_struct *area)
+{
+	unsigned long	size = (unsigned long)(area->vm_end - area->vm_start);
+	struct usX2Ydev	*us428 = hw->private_data;
+
+	// FIXME this hwdep interface is used twice: fpga download and mmap for controlling Lights etc. Maybe better using 2 hwdep devs?
+	// so as long as the device isn't fully initialised yet we return -EBUSY here.
+ 	if (!(us428->chip_status & USX2Y_STAT_CHIP_INIT))
+		return -EBUSY;
+
+	/* if userspace tries to mmap beyond end of our buffer, fail */ 
+        if (size > PAGE_ALIGN(sizeof(struct us428ctls_sharedmem))) {
+		snd_printd( "%lu > %lu\n", size, (unsigned long)sizeof(struct us428ctls_sharedmem)); 
+                return -EINVAL;
+	}
+
+	if (!us428->us428ctls_sharedmem) {
+		init_waitqueue_head(&us428->us428ctls_wait_queue_head);
+		if(!(us428->us428ctls_sharedmem = snd_malloc_pages(sizeof(struct us428ctls_sharedmem), GFP_KERNEL)))
+			return -ENOMEM;
+		memset(us428->us428ctls_sharedmem, -1, sizeof(struct us428ctls_sharedmem));
+		us428->us428ctls_sharedmem->CtlSnapShotLast = -2;
+	}
+	area->vm_ops = &us428ctls_vm_ops;
+	area->vm_flags |= VM_DONTEXPAND | VM_DONTDUMP;
+	area->vm_private_data = hw->private_data;
+	return 0;
+}
+
+static __poll_t snd_us428ctls_poll(struct snd_hwdep *hw, struct file *file, poll_table *wait)
+{
+	__poll_t	mask = 0;
+	struct usX2Ydev	*us428 = hw->private_data;
+	struct us428ctls_sharedmem *shm = us428->us428ctls_sharedmem;
+	if (us428->chip_status & USX2Y_STAT_CHIP_HUP)
+		return EPOLLHUP;
+
+	poll_wait(file, &us428->us428ctls_wait_queue_head, wait);
+
+	if (shm != NULL && shm->CtlSnapShotLast != shm->CtlSnapShotRed)
+		mask |= EPOLLIN;
+
+	return mask;
+}
+
+
+static int snd_usX2Y_hwdep_dsp_status(struct snd_hwdep *hw,
+				      struct snd_hwdep_dsp_status *info)
+{
+	static char *type_ids[USX2Y_TYPE_NUMS] = {
+		[USX2Y_TYPE_122] = "us122",
+		[USX2Y_TYPE_224] = "us224",
+		[USX2Y_TYPE_428] = "us428",
+	};
+	struct usX2Ydev	*us428 = hw->private_data;
+	int id = -1;
+
+	switch (le16_to_cpu(us428->dev->descriptor.idProduct)) {
+	case USB_ID_US122:
+		id = USX2Y_TYPE_122;
+		break;
+	case USB_ID_US224:
+		id = USX2Y_TYPE_224;
+		break;
+	case USB_ID_US428:
+		id = USX2Y_TYPE_428;
+		break;
+	}
+	if (0 > id)
+		return -ENODEV;
+	strcpy(info->id, type_ids[id]);
+	info->num_dsps = 2;		// 0: Prepad Data, 1: FPGA Code
+	if (us428->chip_status & USX2Y_STAT_CHIP_INIT)
+		info->chip_ready = 1;
+ 	info->version = USX2Y_DRIVER_VERSION; 
+	return 0;
+}
+
+
+static int usX2Y_create_usbmidi(struct snd_card *card)
+{
+	static struct snd_usb_midi_endpoint_info quirk_data_1 = {
+		.out_ep = 0x06,
+		.in_ep = 0x06,
+		.out_cables =	0x001,
+		.in_cables =	0x001
+	};
+	static struct snd_usb_audio_quirk quirk_1 = {
+		.vendor_name =	"TASCAM",
+		.product_name =	NAME_ALLCAPS,
+		.ifnum = 	0,
+       		.type = QUIRK_MIDI_FIXED_ENDPOINT,
+		.data = &quirk_data_1
+	};
+	static struct snd_usb_midi_endpoint_info quirk_data_2 = {
+		.out_ep = 0x06,
+		.in_ep = 0x06,
+		.out_cables =	0x003,
+		.in_cables =	0x003
+	};
+	static struct snd_usb_audio_quirk quirk_2 = {
+		.vendor_name =	"TASCAM",
+		.product_name =	"US428",
+		.ifnum = 	0,
+       		.type = QUIRK_MIDI_FIXED_ENDPOINT,
+		.data = &quirk_data_2
+	};
+	struct usb_device *dev = usX2Y(card)->dev;
+	struct usb_interface *iface = usb_ifnum_to_if(dev, 0);
+	struct snd_usb_audio_quirk *quirk =
+		le16_to_cpu(dev->descriptor.idProduct) == USB_ID_US428 ?
+		&quirk_2 : &quirk_1;
+
+	snd_printdd("usX2Y_create_usbmidi \n");
+	return snd_usbmidi_create(card, iface, &usX2Y(card)->midi_list, quirk);
+}
+
+static int usX2Y_create_alsa_devices(struct snd_card *card)
+{
+	int err;
+
+	do {
+		if ((err = usX2Y_create_usbmidi(card)) < 0) {
+			snd_printk(KERN_ERR "usX2Y_create_alsa_devices: usX2Y_create_usbmidi error %i \n", err);
+			break;
+		}
+		if ((err = usX2Y_audio_create(card)) < 0) 
+			break;
+		if ((err = usX2Y_hwdep_pcm_new(card)) < 0)
+			break;
+		if ((err = snd_card_register(card)) < 0)
+			break;
+	} while (0);
+
+	return err;
+} 
+
+static int snd_usX2Y_hwdep_dsp_load(struct snd_hwdep *hw,
+				    struct snd_hwdep_dsp_image *dsp)
+{
+	struct usX2Ydev *priv = hw->private_data;
+	struct usb_device* dev = priv->dev;
+	int lret, err;
+	char *buf;
+
+	snd_printdd( "dsp_load %s\n", dsp->name);
+
+	buf = memdup_user(dsp->image, dsp->length);
+	if (IS_ERR(buf))
+		return PTR_ERR(buf);
+
+	err = usb_set_interface(dev, 0, 1);
+	if (err)
+		snd_printk(KERN_ERR "usb_set_interface error \n");
+	else
+		err = usb_bulk_msg(dev, usb_sndbulkpipe(dev, 2), buf, dsp->length, &lret, 6000);
+	kfree(buf);
+	if (err)
+		return err;
+	if (dsp->index == 1) {
+		msleep(250);				// give the device some time
+		err = usX2Y_AsyncSeq04_init(priv);
+		if (err) {
+			snd_printk(KERN_ERR "usX2Y_AsyncSeq04_init error \n");
+			return err;
+		}
+		err = usX2Y_In04_init(priv);
+		if (err) {
+			snd_printk(KERN_ERR "usX2Y_In04_init error \n");
+			return err;
+		}
+		err = usX2Y_create_alsa_devices(hw->card);
+		if (err) {
+			snd_printk(KERN_ERR "usX2Y_create_alsa_devices error %i \n", err);
+			snd_card_free(hw->card);
+			return err;
+		}
+		priv->chip_status |= USX2Y_STAT_CHIP_INIT; 
+		snd_printdd("%s: alsa all started\n", hw->name);
+	}
+	return err;
+}
+
+
+int usX2Y_hwdep_new(struct snd_card *card, struct usb_device* device)
+{
+	int err;
+	struct snd_hwdep *hw;
+
+	if ((err = snd_hwdep_new(card, SND_USX2Y_LOADER_ID, 0, &hw)) < 0)
+		return err;
+
+	hw->iface = SNDRV_HWDEP_IFACE_USX2Y;
+	hw->private_data = usX2Y(card);
+	hw->ops.dsp_status = snd_usX2Y_hwdep_dsp_status;
+	hw->ops.dsp_load = snd_usX2Y_hwdep_dsp_load;
+	hw->ops.mmap = snd_us428ctls_mmap;
+	hw->ops.poll = snd_us428ctls_poll;
+	hw->exclusive = 1;
+	sprintf(hw->name, "/dev/bus/usb/%03d/%03d", device->bus->busnum, device->devnum);
+	return 0;
+}
+
diff --git a/sound/usb/usx2y/usX2Yhwdep.h b/sound/usb/usx2y/usX2Yhwdep.h
new file mode 100644
index 0000000..457199b
--- /dev/null
+++ b/sound/usb/usx2y/usX2Yhwdep.h
@@ -0,0 +1,7 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef USX2YHWDEP_H
+#define USX2YHWDEP_H
+
+int usX2Y_hwdep_new(struct snd_card *card, struct usb_device* device);
+
+#endif
diff --git a/sound/usb/usx2y/usb_stream.c b/sound/usb/usx2y/usb_stream.c
new file mode 100644
index 0000000..b0f8979
--- /dev/null
+++ b/sound/usb/usx2y/usb_stream.c
@@ -0,0 +1,768 @@
+/*
+ * Copyright (C) 2007, 2008 Karsten Wiese <fzu@wemgehoertderstaat.de>
+ *
+ * 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., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <linux/usb.h>
+#include <linux/gfp.h>
+
+#include "usb_stream.h"
+
+
+/*                             setup                                  */
+
+static unsigned usb_stream_next_packet_size(struct usb_stream_kernel *sk)
+{
+	struct usb_stream *s = sk->s;
+	sk->out_phase_peeked = (sk->out_phase & 0xffff) + sk->freqn;
+	return (sk->out_phase_peeked >> 16) * s->cfg.frame_size;
+}
+
+static void playback_prep_freqn(struct usb_stream_kernel *sk, struct urb *urb)
+{
+	struct usb_stream *s = sk->s;
+	int pack, lb = 0;
+
+	for (pack = 0; pack < sk->n_o_ps; pack++) {
+		int l = usb_stream_next_packet_size(sk);
+		if (s->idle_outsize + lb + l > s->period_size)
+			goto check;
+
+		sk->out_phase = sk->out_phase_peeked;
+		urb->iso_frame_desc[pack].offset = lb;
+		urb->iso_frame_desc[pack].length = l;
+		lb += l;
+	}
+	snd_printdd(KERN_DEBUG "%i\n", lb);
+
+check:
+	urb->number_of_packets = pack;
+	urb->transfer_buffer_length = lb;
+	s->idle_outsize += lb - s->period_size;
+	snd_printdd(KERN_DEBUG "idle=%i ul=%i ps=%i\n", s->idle_outsize,
+		    lb, s->period_size);
+}
+
+static int init_pipe_urbs(struct usb_stream_kernel *sk, unsigned use_packsize,
+			   struct urb **urbs, char *transfer,
+			   struct usb_device *dev, int pipe)
+{
+	int u, p;
+	int maxpacket = use_packsize ?
+		use_packsize : usb_maxpacket(dev, pipe, usb_pipeout(pipe));
+	int transfer_length = maxpacket * sk->n_o_ps;
+
+	for (u = 0; u < USB_STREAM_NURBS;
+	     ++u, transfer += transfer_length) {
+		struct urb *urb = urbs[u];
+		struct usb_iso_packet_descriptor *desc;
+		urb->transfer_buffer = transfer;
+		urb->dev = dev;
+		urb->pipe = pipe;
+		urb->number_of_packets = sk->n_o_ps;
+		urb->context = sk;
+		urb->interval = 1;
+		if (usb_pipeout(pipe))
+			continue;
+		if (usb_urb_ep_type_check(urb))
+			return -EINVAL;
+
+		urb->transfer_buffer_length = transfer_length;
+		desc = urb->iso_frame_desc;
+		desc->offset = 0;
+		desc->length = maxpacket;
+		for (p = 1; p < sk->n_o_ps; ++p) {
+			desc[p].offset = desc[p - 1].offset + maxpacket;
+			desc[p].length = maxpacket;
+		}
+	}
+
+	return 0;
+}
+
+static int init_urbs(struct usb_stream_kernel *sk, unsigned use_packsize,
+		      struct usb_device *dev, int in_pipe, int out_pipe)
+{
+	struct usb_stream	*s = sk->s;
+	char			*indata = (char *)s + sizeof(*s) +
+					sizeof(struct usb_stream_packet) *
+					s->inpackets;
+	int			u;
+
+	for (u = 0; u < USB_STREAM_NURBS; ++u) {
+		sk->inurb[u] = usb_alloc_urb(sk->n_o_ps, GFP_KERNEL);
+		sk->outurb[u] = usb_alloc_urb(sk->n_o_ps, GFP_KERNEL);
+	}
+
+	if (init_pipe_urbs(sk, use_packsize, sk->inurb, indata, dev, in_pipe) ||
+	    init_pipe_urbs(sk, use_packsize, sk->outurb, sk->write_page, dev,
+			   out_pipe))
+		return -EINVAL;
+
+	return 0;
+}
+
+
+/*
+ * convert a sampling rate into our full speed format (fs/1000 in Q16.16)
+ * this will overflow at approx 524 kHz
+ */
+static inline unsigned get_usb_full_speed_rate(unsigned rate)
+{
+	return ((rate << 13) + 62) / 125;
+}
+
+/*
+ * convert a sampling rate into USB high speed format (fs/8000 in Q16.16)
+ * this will overflow at approx 4 MHz
+ */
+static inline unsigned get_usb_high_speed_rate(unsigned rate)
+{
+	return ((rate << 10) + 62) / 125;
+}
+
+void usb_stream_free(struct usb_stream_kernel *sk)
+{
+	struct usb_stream *s;
+	unsigned u;
+
+	for (u = 0; u < USB_STREAM_NURBS; ++u) {
+		usb_free_urb(sk->inurb[u]);
+		sk->inurb[u] = NULL;
+		usb_free_urb(sk->outurb[u]);
+		sk->outurb[u] = NULL;
+	}
+
+	s = sk->s;
+	if (!s)
+		return;
+
+	free_pages((unsigned long)sk->write_page, get_order(s->write_size));
+	sk->write_page = NULL;
+	free_pages((unsigned long)s, get_order(s->read_size));
+	sk->s = NULL;
+}
+
+struct usb_stream *usb_stream_new(struct usb_stream_kernel *sk,
+				  struct usb_device *dev,
+				  unsigned in_endpoint, unsigned out_endpoint,
+				  unsigned sample_rate, unsigned use_packsize,
+				  unsigned period_frames, unsigned frame_size)
+{
+	int packets, max_packsize;
+	int in_pipe, out_pipe;
+	int read_size = sizeof(struct usb_stream);
+	int write_size;
+	int usb_frames = dev->speed == USB_SPEED_HIGH ? 8000 : 1000;
+	int pg;
+
+	in_pipe = usb_rcvisocpipe(dev, in_endpoint);
+	out_pipe = usb_sndisocpipe(dev, out_endpoint);
+
+	max_packsize = use_packsize ?
+		use_packsize : usb_maxpacket(dev, in_pipe, 0);
+
+	/*
+		t_period = period_frames / sample_rate
+		iso_packs = t_period / t_iso_frame
+			= (period_frames / sample_rate) * (1 / t_iso_frame)
+	*/
+
+	packets = period_frames * usb_frames / sample_rate + 1;
+
+	if (dev->speed == USB_SPEED_HIGH)
+		packets = (packets + 7) & ~7;
+
+	read_size += packets * USB_STREAM_URBDEPTH *
+		(max_packsize + sizeof(struct usb_stream_packet));
+
+	max_packsize = usb_maxpacket(dev, out_pipe, 1);
+	write_size = max_packsize * packets * USB_STREAM_URBDEPTH;
+
+	if (read_size >= 256*PAGE_SIZE || write_size >= 256*PAGE_SIZE) {
+		snd_printk(KERN_WARNING "a size exceeds 128*PAGE_SIZE\n");
+		goto out;
+	}
+
+	pg = get_order(read_size);
+	sk->s = (void *) __get_free_pages(GFP_KERNEL|__GFP_COMP|__GFP_ZERO|
+					  __GFP_NOWARN, pg);
+	if (!sk->s) {
+		snd_printk(KERN_WARNING "couldn't __get_free_pages()\n");
+		goto out;
+	}
+	sk->s->cfg.version = USB_STREAM_INTERFACE_VERSION;
+
+	sk->s->read_size = read_size;
+
+	sk->s->cfg.sample_rate = sample_rate;
+	sk->s->cfg.frame_size = frame_size;
+	sk->n_o_ps = packets;
+	sk->s->inpackets = packets * USB_STREAM_URBDEPTH;
+	sk->s->cfg.period_frames = period_frames;
+	sk->s->period_size = frame_size * period_frames;
+
+	sk->s->write_size = write_size;
+	pg = get_order(write_size);
+
+	sk->write_page =
+		(void *)__get_free_pages(GFP_KERNEL|__GFP_COMP|__GFP_ZERO|
+					 __GFP_NOWARN, pg);
+	if (!sk->write_page) {
+		snd_printk(KERN_WARNING "couldn't __get_free_pages()\n");
+		usb_stream_free(sk);
+		return NULL;
+	}
+
+	/* calculate the frequency in 16.16 format */
+	if (dev->speed == USB_SPEED_FULL)
+		sk->freqn = get_usb_full_speed_rate(sample_rate);
+	else
+		sk->freqn = get_usb_high_speed_rate(sample_rate);
+
+	if (init_urbs(sk, use_packsize, dev, in_pipe, out_pipe) < 0) {
+		usb_stream_free(sk);
+		return NULL;
+	}
+
+	sk->s->state = usb_stream_stopped;
+out:
+	return sk->s;
+}
+
+
+/*                             start                                  */
+
+static bool balance_check(struct usb_stream_kernel *sk, struct urb *urb)
+{
+	bool r;
+	if (unlikely(urb->status)) {
+		if (urb->status != -ESHUTDOWN && urb->status != -ENOENT)
+			snd_printk(KERN_WARNING "status=%i\n", urb->status);
+		sk->iso_frame_balance = 0x7FFFFFFF;
+		return false;
+	}
+	r = sk->iso_frame_balance == 0;
+	if (!r)
+		sk->i_urb = urb;
+	return r;
+}
+
+static bool balance_playback(struct usb_stream_kernel *sk, struct urb *urb)
+{
+	sk->iso_frame_balance += urb->number_of_packets;
+	return balance_check(sk, urb);
+}
+
+static bool balance_capture(struct usb_stream_kernel *sk, struct urb *urb)
+{
+	sk->iso_frame_balance -= urb->number_of_packets;
+	return balance_check(sk, urb);
+}
+
+static void subs_set_complete(struct urb **urbs, void (*complete)(struct urb *))
+{
+	int u;
+
+	for (u = 0; u < USB_STREAM_NURBS; u++) {
+		struct urb *urb = urbs[u];
+		urb->complete = complete;
+	}
+}
+
+static int usb_stream_prepare_playback(struct usb_stream_kernel *sk,
+		struct urb *inurb)
+{
+	struct usb_stream *s = sk->s;
+	struct urb *io;
+	struct usb_iso_packet_descriptor *id, *od;
+	int p = 0, lb = 0, l = 0;
+
+	io = sk->idle_outurb;
+	od = io->iso_frame_desc;
+
+	for (; s->sync_packet < 0; ++p, ++s->sync_packet) {
+		struct urb *ii = sk->completed_inurb;
+		id = ii->iso_frame_desc +
+			ii->number_of_packets + s->sync_packet;
+		l = id->actual_length;
+
+		od[p].length = l;
+		od[p].offset = lb;
+		lb += l;
+	}
+
+	for (;
+	     s->sync_packet < inurb->number_of_packets && p < sk->n_o_ps;
+	     ++p, ++s->sync_packet) {
+		l = inurb->iso_frame_desc[s->sync_packet].actual_length;
+
+		if (s->idle_outsize + lb + l > s->period_size)
+			goto check_ok;
+
+		od[p].length = l;
+		od[p].offset = lb;
+		lb += l;
+	}
+
+check_ok:
+	s->sync_packet -= inurb->number_of_packets;
+	if (unlikely(s->sync_packet < -2 || s->sync_packet > 0)) {
+		snd_printk(KERN_WARNING "invalid sync_packet = %i;"
+			   " p=%i nop=%i %i %x %x %x > %x\n",
+			   s->sync_packet, p, inurb->number_of_packets,
+			   s->idle_outsize + lb + l,
+			   s->idle_outsize, lb,  l,
+			   s->period_size);
+		return -1;
+	}
+	if (unlikely(lb % s->cfg.frame_size)) {
+		snd_printk(KERN_WARNING"invalid outsize = %i\n",
+			   lb);
+		return -1;
+	}
+	s->idle_outsize += lb - s->period_size;
+	io->number_of_packets = p;
+	io->transfer_buffer_length = lb;
+	if (s->idle_outsize <= 0)
+		return 0;
+
+	snd_printk(KERN_WARNING "idle=%i\n", s->idle_outsize);
+	return -1;
+}
+
+static void prepare_inurb(int number_of_packets, struct urb *iu)
+{
+	struct usb_iso_packet_descriptor *id;
+	int p;
+
+	iu->number_of_packets = number_of_packets;
+	id = iu->iso_frame_desc;
+	id->offset = 0;
+	for (p = 0; p < iu->number_of_packets - 1; ++p)
+		id[p + 1].offset = id[p].offset + id[p].length;
+
+	iu->transfer_buffer_length =
+		id[0].length * iu->number_of_packets;
+}
+
+static int submit_urbs(struct usb_stream_kernel *sk,
+		       struct urb *inurb, struct urb *outurb)
+{
+	int err;
+	prepare_inurb(sk->idle_outurb->number_of_packets, sk->idle_inurb);
+	err = usb_submit_urb(sk->idle_inurb, GFP_ATOMIC);
+	if (err < 0)
+		goto report_failure;
+
+	sk->idle_inurb = sk->completed_inurb;
+	sk->completed_inurb = inurb;
+	err = usb_submit_urb(sk->idle_outurb, GFP_ATOMIC);
+	if (err < 0)
+		goto report_failure;
+
+	sk->idle_outurb = sk->completed_outurb;
+	sk->completed_outurb = outurb;
+	return 0;
+
+report_failure:
+	snd_printk(KERN_ERR "%i\n", err);
+	return err;
+}
+
+#ifdef DEBUG_LOOP_BACK
+/*
+  This loop_back() shows how to read/write the period data.
+ */
+static void loop_back(struct usb_stream *s)
+{
+	char *i, *o;
+	int il, ol, l, p;
+	struct urb *iu;
+	struct usb_iso_packet_descriptor *id;
+
+	o = s->playback1st_to;
+	ol = s->playback1st_size;
+	l = 0;
+
+	if (s->insplit_pack >= 0) {
+		iu = sk->idle_inurb;
+		id = iu->iso_frame_desc;
+		p = s->insplit_pack;
+	} else
+		goto second;
+loop:
+	for (; p < iu->number_of_packets && l < s->period_size; ++p) {
+		i = iu->transfer_buffer + id[p].offset;
+		il = id[p].actual_length;
+		if (l + il > s->period_size)
+			il = s->period_size - l;
+		if (il <= ol) {
+			memcpy(o, i, il);
+			o += il;
+			ol -= il;
+		} else {
+			memcpy(o, i, ol);
+			singen_6pack(o, ol);
+			o = s->playback_to;
+			memcpy(o, i + ol, il - ol);
+			o += il - ol;
+			ol = s->period_size - s->playback1st_size;
+		}
+		l += il;
+	}
+	if (iu == sk->completed_inurb) {
+		if (l != s->period_size)
+			printk(KERN_DEBUG"%s:%i %i\n", __func__, __LINE__,
+			       l/(int)s->cfg.frame_size);
+
+		return;
+	}
+second:
+	iu = sk->completed_inurb;
+	id = iu->iso_frame_desc;
+	p = 0;
+	goto loop;
+
+}
+#else
+static void loop_back(struct usb_stream *s)
+{
+}
+#endif
+
+static void stream_idle(struct usb_stream_kernel *sk,
+			struct urb *inurb, struct urb *outurb)
+{
+	struct usb_stream *s = sk->s;
+	int l, p;
+	int insize = s->idle_insize;
+	int urb_size = 0;
+
+	s->inpacket_split = s->next_inpacket_split;
+	s->inpacket_split_at = s->next_inpacket_split_at;
+	s->next_inpacket_split = -1;
+	s->next_inpacket_split_at = 0;
+
+	for (p = 0; p < inurb->number_of_packets; ++p) {
+		struct usb_iso_packet_descriptor *id = inurb->iso_frame_desc;
+		l = id[p].actual_length;
+		if (unlikely(l == 0 || id[p].status)) {
+			snd_printk(KERN_WARNING "underrun, status=%u\n",
+				   id[p].status);
+			goto err_out;
+		}
+		s->inpacket_head++;
+		s->inpacket_head %= s->inpackets;
+		if (s->inpacket_split == -1)
+			s->inpacket_split = s->inpacket_head;
+
+		s->inpacket[s->inpacket_head].offset =
+			id[p].offset + (inurb->transfer_buffer - (void *)s);
+		s->inpacket[s->inpacket_head].length = l;
+		if (insize + l > s->period_size &&
+		    s->next_inpacket_split == -1) {
+			s->next_inpacket_split = s->inpacket_head;
+			s->next_inpacket_split_at = s->period_size - insize;
+		}
+		insize += l;
+		urb_size += l;
+	}
+	s->idle_insize += urb_size - s->period_size;
+	if (s->idle_insize < 0) {
+		snd_printk(KERN_WARNING "%i\n",
+			   (s->idle_insize)/(int)s->cfg.frame_size);
+		goto err_out;
+	}
+	s->insize_done += urb_size;
+
+	l = s->idle_outsize;
+	s->outpacket[0].offset = (sk->idle_outurb->transfer_buffer -
+				  sk->write_page) - l;
+
+	if (usb_stream_prepare_playback(sk, inurb) < 0)
+		goto err_out;
+
+	s->outpacket[0].length = sk->idle_outurb->transfer_buffer_length + l;
+	s->outpacket[1].offset = sk->completed_outurb->transfer_buffer -
+		sk->write_page;
+
+	if (submit_urbs(sk, inurb, outurb) < 0)
+		goto err_out;
+
+	loop_back(s);
+	s->periods_done++;
+	wake_up_all(&sk->sleep);
+	return;
+err_out:
+	s->state = usb_stream_xrun;
+	wake_up_all(&sk->sleep);
+}
+
+static void i_capture_idle(struct urb *urb)
+{
+	struct usb_stream_kernel *sk = urb->context;
+	if (balance_capture(sk, urb))
+		stream_idle(sk, urb, sk->i_urb);
+}
+
+static void i_playback_idle(struct urb *urb)
+{
+	struct usb_stream_kernel *sk = urb->context;
+	if (balance_playback(sk, urb))
+		stream_idle(sk, sk->i_urb, urb);
+}
+
+static void stream_start(struct usb_stream_kernel *sk,
+			 struct urb *inurb, struct urb *outurb)
+{
+	struct usb_stream *s = sk->s;
+	if (s->state >= usb_stream_sync1) {
+		int l, p, max_diff, max_diff_0;
+		int urb_size = 0;
+		unsigned frames_per_packet, min_frames = 0;
+		frames_per_packet = (s->period_size - s->idle_insize);
+		frames_per_packet <<= 8;
+		frames_per_packet /=
+			s->cfg.frame_size * inurb->number_of_packets;
+		frames_per_packet++;
+
+		max_diff_0 = s->cfg.frame_size;
+		if (s->cfg.period_frames >= 256)
+			max_diff_0 <<= 1;
+		if (s->cfg.period_frames >= 1024)
+			max_diff_0 <<= 1;
+		max_diff = max_diff_0;
+		for (p = 0; p < inurb->number_of_packets; ++p) {
+			int diff;
+			l = inurb->iso_frame_desc[p].actual_length;
+			urb_size += l;
+
+			min_frames += frames_per_packet;
+			diff = urb_size -
+				(min_frames >> 8) * s->cfg.frame_size;
+			if (diff < max_diff) {
+				snd_printdd(KERN_DEBUG "%i %i %i %i\n",
+					    s->insize_done,
+					    urb_size / (int)s->cfg.frame_size,
+					    inurb->number_of_packets, diff);
+				max_diff = diff;
+			}
+		}
+		s->idle_insize -= max_diff - max_diff_0;
+		s->idle_insize += urb_size - s->period_size;
+		if (s->idle_insize < 0) {
+			snd_printk(KERN_WARNING "%i %i %i\n",
+				   s->idle_insize, urb_size, s->period_size);
+			return;
+		} else if (s->idle_insize == 0) {
+			s->next_inpacket_split =
+				(s->inpacket_head + 1) % s->inpackets;
+			s->next_inpacket_split_at = 0;
+		} else {
+			unsigned split = s->inpacket_head;
+			l = s->idle_insize;
+			while (l > s->inpacket[split].length) {
+				l -= s->inpacket[split].length;
+				if (split == 0)
+					split = s->inpackets - 1;
+				else
+					split--;
+			}
+			s->next_inpacket_split = split;
+			s->next_inpacket_split_at =
+				s->inpacket[split].length - l;
+		}
+
+		s->insize_done += urb_size;
+
+		if (usb_stream_prepare_playback(sk, inurb) < 0)
+			return;
+
+	} else
+		playback_prep_freqn(sk, sk->idle_outurb);
+
+	if (submit_urbs(sk, inurb, outurb) < 0)
+		return;
+
+	if (s->state == usb_stream_sync1 && s->insize_done > 360000) {
+		/* just guesswork                            ^^^^^^ */
+		s->state = usb_stream_ready;
+		subs_set_complete(sk->inurb, i_capture_idle);
+		subs_set_complete(sk->outurb, i_playback_idle);
+	}
+}
+
+static void i_capture_start(struct urb *urb)
+{
+	struct usb_iso_packet_descriptor *id = urb->iso_frame_desc;
+	struct usb_stream_kernel *sk = urb->context;
+	struct usb_stream *s = sk->s;
+	int p;
+	int empty = 0;
+
+	if (urb->status) {
+		snd_printk(KERN_WARNING "status=%i\n", urb->status);
+		return;
+	}
+
+	for (p = 0; p < urb->number_of_packets; ++p) {
+		int l = id[p].actual_length;
+		if (l < s->cfg.frame_size) {
+			++empty;
+			if (s->state >= usb_stream_sync0) {
+				snd_printk(KERN_WARNING "%i\n", l);
+				return;
+			}
+		}
+		s->inpacket_head++;
+		s->inpacket_head %= s->inpackets;
+		s->inpacket[s->inpacket_head].offset =
+			id[p].offset + (urb->transfer_buffer - (void *)s);
+		s->inpacket[s->inpacket_head].length = l;
+	}
+#ifdef SHOW_EMPTY
+	if (empty) {
+		printk(KERN_DEBUG"%s:%i: %i", __func__, __LINE__,
+		       urb->iso_frame_desc[0].actual_length);
+		for (pack = 1; pack < urb->number_of_packets; ++pack) {
+			int l = urb->iso_frame_desc[pack].actual_length;
+			printk(KERN_CONT " %i", l);
+		}
+		printk(KERN_CONT "\n");
+	}
+#endif
+	if (!empty && s->state < usb_stream_sync1)
+		++s->state;
+
+	if (balance_capture(sk, urb))
+		stream_start(sk, urb, sk->i_urb);
+}
+
+static void i_playback_start(struct urb *urb)
+{
+	struct usb_stream_kernel *sk = urb->context;
+	if (balance_playback(sk, urb))
+		stream_start(sk, sk->i_urb, urb);
+}
+
+int usb_stream_start(struct usb_stream_kernel *sk)
+{
+	struct usb_stream *s = sk->s;
+	int frame = 0, iters = 0;
+	int u, err;
+	int try = 0;
+
+	if (s->state != usb_stream_stopped)
+		return -EAGAIN;
+
+	subs_set_complete(sk->inurb, i_capture_start);
+	subs_set_complete(sk->outurb, i_playback_start);
+	memset(sk->write_page, 0, s->write_size);
+dotry:
+	s->insize_done = 0;
+	s->idle_insize = 0;
+	s->idle_outsize = 0;
+	s->sync_packet = -1;
+	s->inpacket_head = -1;
+	sk->iso_frame_balance = 0;
+	++try;
+	for (u = 0; u < 2; u++) {
+		struct urb *inurb = sk->inurb[u];
+		struct urb *outurb = sk->outurb[u];
+		playback_prep_freqn(sk, outurb);
+		inurb->number_of_packets = outurb->number_of_packets;
+		inurb->transfer_buffer_length =
+			inurb->number_of_packets *
+			inurb->iso_frame_desc[0].length;
+
+		if (u == 0) {
+			int now;
+			struct usb_device *dev = inurb->dev;
+			frame = usb_get_current_frame_number(dev);
+			do {
+				now = usb_get_current_frame_number(dev);
+				++iters;
+			} while (now > -1 && now == frame);
+		}
+		err = usb_submit_urb(inurb, GFP_ATOMIC);
+		if (err < 0) {
+			snd_printk(KERN_ERR"usb_submit_urb(sk->inurb[%i])"
+				   " returned %i\n", u, err);
+			return err;
+		}
+		err = usb_submit_urb(outurb, GFP_ATOMIC);
+		if (err < 0) {
+			snd_printk(KERN_ERR"usb_submit_urb(sk->outurb[%i])"
+				   " returned %i\n", u, err);
+			return err;
+		}
+
+		if (inurb->start_frame != outurb->start_frame) {
+			snd_printd(KERN_DEBUG
+				   "u[%i] start_frames differ in:%u out:%u\n",
+				   u, inurb->start_frame, outurb->start_frame);
+			goto check_retry;
+		}
+	}
+	snd_printdd(KERN_DEBUG "%i %i\n", frame, iters);
+	try = 0;
+check_retry:
+	if (try) {
+		usb_stream_stop(sk);
+		if (try < 5) {
+			msleep(1500);
+			snd_printd(KERN_DEBUG "goto dotry;\n");
+			goto dotry;
+		}
+		snd_printk(KERN_WARNING"couldn't start"
+			   " all urbs on the same start_frame.\n");
+		return -EFAULT;
+	}
+
+	sk->idle_inurb = sk->inurb[USB_STREAM_NURBS - 2];
+	sk->idle_outurb = sk->outurb[USB_STREAM_NURBS - 2];
+	sk->completed_inurb = sk->inurb[USB_STREAM_NURBS - 1];
+	sk->completed_outurb = sk->outurb[USB_STREAM_NURBS - 1];
+
+/* wait, check */
+	{
+		int wait_ms = 3000;
+		while (s->state != usb_stream_ready && wait_ms > 0) {
+			snd_printdd(KERN_DEBUG "%i\n", s->state);
+			msleep(200);
+			wait_ms -= 200;
+		}
+	}
+
+	return s->state == usb_stream_ready ? 0 : -EFAULT;
+}
+
+
+/*                             stop                                   */
+
+void usb_stream_stop(struct usb_stream_kernel *sk)
+{
+	int u;
+	if (!sk->s)
+		return;
+	for (u = 0; u < USB_STREAM_NURBS; ++u) {
+		usb_kill_urb(sk->inurb[u]);
+		usb_kill_urb(sk->outurb[u]);
+	}
+	sk->s->state = usb_stream_stopped;
+	msleep(400);
+}
diff --git a/sound/usb/usx2y/usb_stream.h b/sound/usb/usx2y/usb_stream.h
new file mode 100644
index 0000000..851358a
--- /dev/null
+++ b/sound/usb/usx2y/usb_stream.h
@@ -0,0 +1,43 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __USB_STREAM_H
+#define __USB_STREAM_H
+
+#include <uapi/sound/usb_stream.h>
+
+#define USB_STREAM_NURBS 4
+#define USB_STREAM_URBDEPTH 4
+
+struct usb_stream_kernel {
+	struct usb_stream *s;
+
+	void *write_page;
+
+	unsigned n_o_ps;
+
+	struct urb *inurb[USB_STREAM_NURBS];
+	struct urb *idle_inurb;
+	struct urb *completed_inurb;
+	struct urb *outurb[USB_STREAM_NURBS];
+	struct urb *idle_outurb;
+	struct urb *completed_outurb;
+	struct urb *i_urb;
+
+	int iso_frame_balance;
+
+	wait_queue_head_t sleep;
+
+	unsigned out_phase;
+	unsigned out_phase_peeked;
+	unsigned freqn;
+};
+
+struct usb_stream *usb_stream_new(struct usb_stream_kernel *sk,
+				  struct usb_device *dev,
+				  unsigned in_endpoint, unsigned out_endpoint,
+				  unsigned sample_rate, unsigned use_packsize,
+				  unsigned period_frames, unsigned frame_size);
+void usb_stream_free(struct usb_stream_kernel *);
+int usb_stream_start(struct usb_stream_kernel *);
+void usb_stream_stop(struct usb_stream_kernel *);
+
+#endif /* __USB_STREAM_H */
diff --git a/sound/usb/usx2y/usbus428ctldefs.h b/sound/usb/usx2y/usbus428ctldefs.h
new file mode 100644
index 0000000..b864e7e
--- /dev/null
+++ b/sound/usb/usx2y/usbus428ctldefs.h
@@ -0,0 +1,104 @@
+/*
+ *
+ * Copyright (c) 2003 by Karsten Wiese <annabellesgarden@yahoo.de>
+ *
+ *   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
+ */
+
+enum E_In84{
+	eFader0 = 0,
+	eFader1,
+	eFader2,
+	eFader3,
+	eFader4,
+	eFader5,
+	eFader6,
+	eFader7,
+	eFaderM,
+	eTransport,
+	eModifier = 10,
+	eFilterSelect,
+	eSelect,
+	eMute,
+
+	eSwitch   = 15,
+	eWheelGain,
+	eWheelFreq,
+	eWheelQ,
+	eWheelPan,
+	eWheel    = 20
+};
+
+#define T_RECORD   1
+#define T_PLAY     2
+#define T_STOP     4
+#define T_F_FWD    8
+#define T_REW   0x10
+#define T_SOLO  0x20
+#define T_REC   0x40
+#define T_NULL  0x80
+
+
+struct us428_ctls {
+	unsigned char   Fader[9];
+	unsigned char 	Transport;
+	unsigned char 	Modifier;
+	unsigned char 	FilterSelect;
+	unsigned char 	Select;
+	unsigned char   Mute;
+	unsigned char   UNKNOWN;
+	unsigned char   Switch;	     
+	unsigned char   Wheel[5];
+};
+
+struct us428_setByte {
+	unsigned char Offset,
+		Value;
+};
+
+enum {
+	eLT_Volume = 0,
+	eLT_Light
+};
+
+struct usX2Y_volume {
+	unsigned char Channel,
+		LH,
+		LL,
+		RH,
+		RL;
+};
+
+struct us428_lights {
+	struct us428_setByte Light[7];
+};
+
+struct us428_p4out {
+	char type;
+	union {
+		struct usX2Y_volume vol;
+		struct us428_lights lights;
+	} val;
+};
+
+#define N_us428_ctl_BUFS 16
+#define N_us428_p4out_BUFS 16
+struct us428ctls_sharedmem{
+	struct us428_ctls	CtlSnapShot[N_us428_ctl_BUFS];
+	int			CtlSnapShotDiffersAt[N_us428_ctl_BUFS];
+	int			CtlSnapShotLast, CtlSnapShotRed;
+	struct us428_p4out	p4out[N_us428_p4out_BUFS];
+	int			p4outLast, p4outSent;
+};
diff --git a/sound/usb/usx2y/usbusx2y.c b/sound/usb/usx2y/usbusx2y.c
new file mode 100644
index 0000000..da4a5a5
--- /dev/null
+++ b/sound/usb/usx2y/usbusx2y.c
@@ -0,0 +1,468 @@
+/*
+ * usbusy2y.c - ALSA USB US-428 Driver
+ *
+2005-04-14 Karsten Wiese
+	Version 0.8.7.2:
+	Call snd_card_free() instead of snd_card_free_in_thread() to prevent oops with dead keyboard symptom.
+	Tested ok with kernel 2.6.12-rc2.
+
+2004-12-14 Karsten Wiese
+	Version 0.8.7.1:
+	snd_pcm_open for rawusb pcm-devices now returns -EBUSY if called without rawusb's hwdep device being open.
+
+2004-12-02 Karsten Wiese
+	Version 0.8.7:
+	Use macro usb_maxpacket() for portability.
+
+2004-10-26 Karsten Wiese
+	Version 0.8.6:
+	wake_up() process waiting in usX2Y_urbs_start() on error.
+
+2004-10-21 Karsten Wiese
+	Version 0.8.5:
+	nrpacks is runtime or compiletime configurable now with tested values from 1 to 4.
+
+2004-10-03 Karsten Wiese
+	Version 0.8.2:
+	Avoid any possible racing while in prepare callback.
+
+2004-09-30 Karsten Wiese
+	Version 0.8.0:
+	Simplified things and made ohci work again.
+
+2004-09-20 Karsten Wiese
+	Version 0.7.3:
+	Use usb_kill_urb() instead of deprecated (kernel 2.6.9) usb_unlink_urb().
+
+2004-07-13 Karsten Wiese
+	Version 0.7.1:
+	Don't sleep in START/STOP callbacks anymore.
+	us428 channels C/D not handled just for this version, sorry.
+
+2004-06-21 Karsten Wiese
+	Version 0.6.4:
+	Temporarely suspend midi input
+	to sanely call usb_set_interface() when setting format.
+
+2004-06-12 Karsten Wiese
+	Version 0.6.3:
+	Made it thus the following rule is enforced:
+	"All pcm substreams of one usX2Y have to operate at the same rate & format."
+
+2004-04-06 Karsten Wiese
+	Version 0.6.0:
+	Runs on 2.6.5 kernel without any "--with-debug=" things.
+	us224 reported running.
+
+2004-01-14 Karsten Wiese
+	Version 0.5.1:
+	Runs with 2.6.1 kernel.
+
+2003-12-30 Karsten Wiese
+	Version 0.4.1:
+	Fix 24Bit 4Channel capturing for the us428.
+
+2003-11-27 Karsten Wiese, Martin Langer
+	Version 0.4:
+	us122 support.
+	us224 could be tested by uncommenting the sections containing USB_ID_US224
+
+2003-11-03 Karsten Wiese
+	Version 0.3:
+	24Bit support. 
+	"arecord -D hw:1 -c 2 -r 48000 -M -f S24_3LE|aplay -D hw:1 -c 2 -r 48000 -M -f S24_3LE" works.
+
+2003-08-22 Karsten Wiese
+	Version 0.0.8:
+	Removed EZUSB Firmware. First Stage Firmwaredownload is now done by tascam-firmware downloader.
+	See:
+	http://usb-midi-fw.sourceforge.net/tascam-firmware.tar.gz
+
+2003-06-18 Karsten Wiese
+	Version 0.0.5:
+	changed to compile with kernel 2.4.21 and alsa 0.9.4
+
+2002-10-16 Karsten Wiese
+	Version 0.0.4:
+	compiles again with alsa-current.
+	USB_ISO_ASAP not used anymore (most of the time), instead
+	urb->start_frame is calculated here now, some calls inside usb-driver don't need to happen anymore.
+
+	To get the best out of this:
+	Disable APM-support in the kernel as APM-BIOS calls (once each second) hard disable interrupt for many precious milliseconds.
+	This helped me much on my slowish PII 400 & PIII 500.
+	ACPI yet untested but might cause the same bad behaviour.
+	Use a kernel with lowlatency and preemptiv patches applied.
+	To autoload snd-usb-midi append a line 
+		post-install snd-usb-us428 modprobe snd-usb-midi
+	to /etc/modules.conf.
+
+	known problems:
+	sliders, knobs, lights not yet handled except MASTER Volume slider.
+       	"pcm -c 2" doesn't work. "pcm -c 2 -m direct_interleaved" does.
+	KDE3: "Enable full duplex operation" deadlocks.
+
+	
+2002-08-31 Karsten Wiese
+	Version 0.0.3: audio also simplex;
+	simplifying: iso urbs only 1 packet, melted structs.
+	ASYNC_UNLINK not used anymore: no more crashes so far.....
+	for alsa 0.9 rc3.
+
+2002-08-09 Karsten Wiese
+	Version 0.0.2: midi works with snd-usb-midi, audio (only fullduplex now) with i.e. bristol.
+	The firmware has been sniffed from win2k us-428 driver 3.09.
+
+ *   Copyright (c) 2002 - 2004 Karsten Wiese
+ *
+ *   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/module.h>
+#include <linux/moduleparam.h>
+#include <linux/slab.h>
+#include <linux/interrupt.h>
+#include <linux/usb.h>
+#include <sound/core.h>
+#include <sound/initval.h>
+#include <sound/pcm.h>
+
+#include <sound/rawmidi.h>
+#include "usx2y.h"
+#include "usbusx2y.h"
+#include "usX2Yhwdep.h"
+
+
+
+MODULE_AUTHOR("Karsten Wiese <annabellesgarden@yahoo.de>");
+MODULE_DESCRIPTION("TASCAM "NAME_ALLCAPS" Version 0.8.7.2");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("{{TASCAM(0x1604),"NAME_ALLCAPS"(0x8001)(0x8005)(0x8007)}}");
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 0-max */
+static char* id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* Id for this card */
+static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP; /* Enable this card */
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for "NAME_ALLCAPS".");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for "NAME_ALLCAPS".");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "Enable "NAME_ALLCAPS".");
+
+
+static int snd_usX2Y_card_used[SNDRV_CARDS];
+
+static void usX2Y_usb_disconnect(struct usb_device* usb_device, void* ptr);
+static void snd_usX2Y_card_private_free(struct snd_card *card);
+
+/* 
+ * pipe 4 is used for switching the lamps, setting samplerate, volumes ....   
+ */
+static void i_usX2Y_Out04Int(struct urb *urb)
+{
+#ifdef CONFIG_SND_DEBUG
+	if (urb->status) {
+		int 		i;
+		struct usX2Ydev *usX2Y = urb->context;
+		for (i = 0; i < 10 && usX2Y->AS04.urb[i] != urb; i++);
+		snd_printdd("i_usX2Y_Out04Int() urb %i status=%i\n", i, urb->status);
+	}
+#endif
+}
+
+static void i_usX2Y_In04Int(struct urb *urb)
+{
+	int			err = 0;
+	struct usX2Ydev		*usX2Y = urb->context;
+	struct us428ctls_sharedmem	*us428ctls = usX2Y->us428ctls_sharedmem;
+
+	usX2Y->In04IntCalls++;
+
+	if (urb->status) {
+		snd_printdd("Interrupt Pipe 4 came back with status=%i\n", urb->status);
+		return;
+	}
+
+	//	printk("%i:0x%02X ", 8, (int)((unsigned char*)usX2Y->In04Buf)[8]); Master volume shows 0 here if fader is at max during boot ?!?
+	if (us428ctls) {
+		int diff = -1;
+		if (-2 == us428ctls->CtlSnapShotLast) {
+			diff = 0;
+			memcpy(usX2Y->In04Last, usX2Y->In04Buf, sizeof(usX2Y->In04Last));
+			us428ctls->CtlSnapShotLast = -1;
+		} else {
+			int i;
+			for (i = 0; i < 21; i++) {
+				if (usX2Y->In04Last[i] != ((char*)usX2Y->In04Buf)[i]) {
+					if (diff < 0)
+						diff = i;
+					usX2Y->In04Last[i] = ((char*)usX2Y->In04Buf)[i];
+				}
+			}
+		}
+		if (0 <= diff) {
+			int n = us428ctls->CtlSnapShotLast + 1;
+			if (n >= N_us428_ctl_BUFS  ||  n < 0)
+				n = 0;
+			memcpy(us428ctls->CtlSnapShot + n, usX2Y->In04Buf, sizeof(us428ctls->CtlSnapShot[0]));
+			us428ctls->CtlSnapShotDiffersAt[n] = diff;
+			us428ctls->CtlSnapShotLast = n;
+			wake_up(&usX2Y->us428ctls_wait_queue_head);
+		}
+	}
+	
+	
+	if (usX2Y->US04) {
+		if (0 == usX2Y->US04->submitted)
+			do {
+				err = usb_submit_urb(usX2Y->US04->urb[usX2Y->US04->submitted++], GFP_ATOMIC);
+			} while (!err && usX2Y->US04->submitted < usX2Y->US04->len);
+	} else
+		if (us428ctls && us428ctls->p4outLast >= 0 && us428ctls->p4outLast < N_us428_p4out_BUFS) {
+			if (us428ctls->p4outLast != us428ctls->p4outSent) {
+				int j, send = us428ctls->p4outSent + 1;
+				if (send >= N_us428_p4out_BUFS)
+					send = 0;
+				for (j = 0; j < URBS_AsyncSeq  &&  !err; ++j)
+					if (0 == usX2Y->AS04.urb[j]->status) {
+						struct us428_p4out *p4out = us428ctls->p4out + send;	// FIXME if more than 1 p4out is new, 1 gets lost.
+						usb_fill_bulk_urb(usX2Y->AS04.urb[j], usX2Y->dev,
+								  usb_sndbulkpipe(usX2Y->dev, 0x04), &p4out->val.vol,
+								  p4out->type == eLT_Light ? sizeof(struct us428_lights) : 5,
+								  i_usX2Y_Out04Int, usX2Y);
+						err = usb_submit_urb(usX2Y->AS04.urb[j], GFP_ATOMIC);
+						us428ctls->p4outSent = send;
+						break;
+					}
+			}
+		}
+
+	if (err)
+		snd_printk(KERN_ERR "In04Int() usb_submit_urb err=%i\n", err);
+
+	urb->dev = usX2Y->dev;
+	usb_submit_urb(urb, GFP_ATOMIC);
+}
+
+/*
+ * Prepare some urbs
+ */
+int usX2Y_AsyncSeq04_init(struct usX2Ydev *usX2Y)
+{
+	int	err = 0,
+		i;
+
+	usX2Y->AS04.buffer = kmalloc_array(URBS_AsyncSeq,
+					   URB_DataLen_AsyncSeq, GFP_KERNEL);
+	if (NULL == usX2Y->AS04.buffer) {
+		err = -ENOMEM;
+	} else
+		for (i = 0; i < URBS_AsyncSeq; ++i) {
+			if (NULL == (usX2Y->AS04.urb[i] = usb_alloc_urb(0, GFP_KERNEL))) {
+				err = -ENOMEM;
+				break;
+			}
+			usb_fill_bulk_urb(	usX2Y->AS04.urb[i], usX2Y->dev,
+						usb_sndbulkpipe(usX2Y->dev, 0x04),
+						usX2Y->AS04.buffer + URB_DataLen_AsyncSeq*i, 0,
+						i_usX2Y_Out04Int, usX2Y
+				);
+			err = usb_urb_ep_type_check(usX2Y->AS04.urb[i]);
+			if (err < 0)
+				break;
+		}
+	return err;
+}
+
+int usX2Y_In04_init(struct usX2Ydev *usX2Y)
+{
+	if (! (usX2Y->In04urb = usb_alloc_urb(0, GFP_KERNEL)))
+		return -ENOMEM;
+
+	if (! (usX2Y->In04Buf = kmalloc(21, GFP_KERNEL))) {
+		usb_free_urb(usX2Y->In04urb);
+		return -ENOMEM;
+	}
+	 
+	init_waitqueue_head(&usX2Y->In04WaitQueue);
+	usb_fill_int_urb(usX2Y->In04urb, usX2Y->dev, usb_rcvintpipe(usX2Y->dev, 0x4),
+			 usX2Y->In04Buf, 21,
+			 i_usX2Y_In04Int, usX2Y,
+			 10);
+	if (usb_urb_ep_type_check(usX2Y->In04urb))
+		return -EINVAL;
+	return usb_submit_urb(usX2Y->In04urb, GFP_KERNEL);
+}
+
+static void usX2Y_unlinkSeq(struct snd_usX2Y_AsyncSeq *S)
+{
+	int	i;
+	for (i = 0; i < URBS_AsyncSeq; ++i) {
+		usb_kill_urb(S->urb[i]);
+		usb_free_urb(S->urb[i]);
+		S->urb[i] = NULL;
+	}
+	kfree(S->buffer);
+}
+
+
+static const struct usb_device_id snd_usX2Y_usb_id_table[] = {
+	{
+		.match_flags =	USB_DEVICE_ID_MATCH_DEVICE,
+		.idVendor =	0x1604,
+		.idProduct =	USB_ID_US428 
+	},
+	{
+		.match_flags =	USB_DEVICE_ID_MATCH_DEVICE,
+		.idVendor =	0x1604,
+		.idProduct =	USB_ID_US122 
+	},
+ 	{
+		.match_flags =	USB_DEVICE_ID_MATCH_DEVICE,
+		.idVendor =	0x1604,
+		.idProduct =	USB_ID_US224
+	},
+	{ /* terminator */ }
+};
+
+static int usX2Y_create_card(struct usb_device *device,
+			     struct usb_interface *intf,
+			     struct snd_card **cardp)
+{
+	int		dev;
+	struct snd_card *	card;
+	int err;
+
+	for (dev = 0; dev < SNDRV_CARDS; ++dev)
+		if (enable[dev] && !snd_usX2Y_card_used[dev])
+			break;
+	if (dev >= SNDRV_CARDS)
+		return -ENODEV;
+	err = snd_card_new(&intf->dev, index[dev], id[dev], THIS_MODULE,
+			   sizeof(struct usX2Ydev), &card);
+	if (err < 0)
+		return err;
+	snd_usX2Y_card_used[usX2Y(card)->card_index = dev] = 1;
+	card->private_free = snd_usX2Y_card_private_free;
+	usX2Y(card)->dev = device;
+	init_waitqueue_head(&usX2Y(card)->prepare_wait_queue);
+	mutex_init(&usX2Y(card)->pcm_mutex);
+	INIT_LIST_HEAD(&usX2Y(card)->midi_list);
+	strcpy(card->driver, "USB "NAME_ALLCAPS"");
+	sprintf(card->shortname, "TASCAM "NAME_ALLCAPS"");
+	sprintf(card->longname, "%s (%x:%x if %d at %03d/%03d)",
+		card->shortname, 
+		le16_to_cpu(device->descriptor.idVendor),
+		le16_to_cpu(device->descriptor.idProduct),
+		0,//us428(card)->usbmidi.ifnum,
+		usX2Y(card)->dev->bus->busnum, usX2Y(card)->dev->devnum
+		);
+	*cardp = card;
+	return 0;
+}
+
+
+static int usX2Y_usb_probe(struct usb_device *device,
+			   struct usb_interface *intf,
+			   const struct usb_device_id *device_id,
+			   struct snd_card **cardp)
+{
+	int		err;
+	struct snd_card *	card;
+
+	*cardp = NULL;
+	if (le16_to_cpu(device->descriptor.idVendor) != 0x1604 ||
+	    (le16_to_cpu(device->descriptor.idProduct) != USB_ID_US122 &&
+	     le16_to_cpu(device->descriptor.idProduct) != USB_ID_US224 &&
+	     le16_to_cpu(device->descriptor.idProduct) != USB_ID_US428))
+		return -EINVAL;
+
+	err = usX2Y_create_card(device, intf, &card);
+	if (err < 0)
+		return err;
+	if ((err = usX2Y_hwdep_new(card, device)) < 0  ||
+	    (err = snd_card_register(card)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	*cardp = card;
+	return 0;
+}
+
+/*
+ * new 2.5 USB kernel API
+ */
+static int snd_usX2Y_probe(struct usb_interface *intf, const struct usb_device_id *id)
+{
+	struct snd_card *card;
+	int err;
+
+	err = usX2Y_usb_probe(interface_to_usbdev(intf), intf, id, &card);
+	if (err < 0)
+		return err;
+	dev_set_drvdata(&intf->dev, card);
+	return 0;
+}
+
+static void snd_usX2Y_disconnect(struct usb_interface *intf)
+{
+	usX2Y_usb_disconnect(interface_to_usbdev(intf),
+				 usb_get_intfdata(intf));
+}
+
+MODULE_DEVICE_TABLE(usb, snd_usX2Y_usb_id_table);
+static struct usb_driver snd_usX2Y_usb_driver = {
+	.name =		"snd-usb-usx2y",
+	.probe =	snd_usX2Y_probe,
+	.disconnect =	snd_usX2Y_disconnect,
+	.id_table =	snd_usX2Y_usb_id_table,
+};
+
+static void snd_usX2Y_card_private_free(struct snd_card *card)
+{
+	kfree(usX2Y(card)->In04Buf);
+	usb_free_urb(usX2Y(card)->In04urb);
+	if (usX2Y(card)->us428ctls_sharedmem)
+		snd_free_pages(usX2Y(card)->us428ctls_sharedmem, sizeof(*usX2Y(card)->us428ctls_sharedmem));
+	if (usX2Y(card)->card_index >= 0  &&  usX2Y(card)->card_index < SNDRV_CARDS)
+		snd_usX2Y_card_used[usX2Y(card)->card_index] = 0;
+}
+
+/*
+ * Frees the device.
+ */
+static void usX2Y_usb_disconnect(struct usb_device *device, void* ptr)
+{
+	if (ptr) {
+		struct snd_card *card = ptr;
+		struct usX2Ydev *usX2Y = usX2Y(card);
+		struct list_head *p;
+		usX2Y->chip_status = USX2Y_STAT_CHIP_HUP;
+		usX2Y_unlinkSeq(&usX2Y->AS04);
+		usb_kill_urb(usX2Y->In04urb);
+		snd_card_disconnect(card);
+		/* release the midi resources */
+		list_for_each(p, &usX2Y->midi_list) {
+			snd_usbmidi_disconnect(p);
+		}
+		if (usX2Y->us428ctls_sharedmem) 
+			wake_up(&usX2Y->us428ctls_wait_queue_head);
+		snd_card_free(card);
+	}
+}
+
+module_usb_driver(snd_usX2Y_usb_driver);
diff --git a/sound/usb/usx2y/usbusx2y.h b/sound/usb/usx2y/usbusx2y.h
new file mode 100644
index 0000000..e0f7717
--- /dev/null
+++ b/sound/usb/usx2y/usbusx2y.h
@@ -0,0 +1,89 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef USBUSX2Y_H
+#define USBUSX2Y_H
+#include "../usbaudio.h"
+#include "../midi.h"
+#include "usbus428ctldefs.h" 
+
+#define NRURBS	        2	
+
+
+#define URBS_AsyncSeq 10
+#define URB_DataLen_AsyncSeq 32
+struct snd_usX2Y_AsyncSeq {
+	struct urb	*urb[URBS_AsyncSeq];
+	char		*buffer;
+};
+
+struct snd_usX2Y_urbSeq {
+	int	submitted;
+	int	len;
+	struct urb	*urb[0];
+};
+
+#include "usx2yhwdeppcm.h"
+
+struct usX2Ydev {
+	struct usb_device	*dev;
+	int			card_index;
+	int			stride;
+	struct urb		*In04urb;
+	void			*In04Buf;
+	char			In04Last[24];
+	unsigned		In04IntCalls;
+	struct snd_usX2Y_urbSeq	*US04;
+	wait_queue_head_t	In04WaitQueue;
+	struct snd_usX2Y_AsyncSeq	AS04;
+	unsigned int		rate,
+				format;
+	int			chip_status;
+	struct mutex		pcm_mutex;
+	struct us428ctls_sharedmem	*us428ctls_sharedmem;
+	int			wait_iso_frame;
+	wait_queue_head_t	us428ctls_wait_queue_head;
+	struct snd_usX2Y_hwdep_pcm_shm	*hwdep_pcm_shm;
+	struct snd_usX2Y_substream	*subs[4];
+	struct snd_usX2Y_substream	* volatile  prepare_subs;
+	wait_queue_head_t	prepare_wait_queue;
+	struct list_head	midi_list;
+	struct list_head	pcm_list;
+	int			pcm_devs;
+};
+
+
+struct snd_usX2Y_substream {
+	struct usX2Ydev	*usX2Y;
+	struct snd_pcm_substream *pcm_substream;
+
+	int			endpoint;		
+	unsigned int		maxpacksize;		/* max packet size in bytes */
+
+	atomic_t		state;
+#define state_STOPPED	0
+#define state_STARTING1 1
+#define state_STARTING2 2
+#define state_STARTING3 3
+#define state_PREPARED	4
+#define state_PRERUNNING  6
+#define state_RUNNING	8
+
+	int			hwptr;			/* free frame position in the buffer (only for playback) */
+	int			hwptr_done;		/* processed frame position in the buffer */
+	int			transfer_done;		/* processed frames since last period update */
+
+	struct urb		*urb[NRURBS];	/* data urb table */
+	struct urb		*completed_urb;
+	char			*tmpbuf;			/* temporary buffer for playback */
+};
+
+
+#define usX2Y(c) ((struct usX2Ydev *)(c)->private_data)
+
+int usX2Y_audio_create(struct snd_card *card);
+
+int usX2Y_AsyncSeq04_init(struct usX2Ydev *usX2Y);
+int usX2Y_In04_init(struct usX2Ydev *usX2Y);
+
+#define NAME_ALLCAPS "US-X2Y"
+
+#endif
diff --git a/sound/usb/usx2y/usbusx2yaudio.c b/sound/usb/usx2y/usbusx2yaudio.c
new file mode 100644
index 0000000..2b83305
--- /dev/null
+++ b/sound/usb/usx2y/usbusx2yaudio.c
@@ -0,0 +1,1018 @@
+/*
+ *   US-X2Y AUDIO
+ *   Copyright (c) 2002-2004 by Karsten Wiese
+ *
+ *   based on
+ *
+ *   (Tentative) USB Audio Driver for ALSA
+ *
+ *   Main and PCM part
+ *
+ *   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/interrupt.h>
+#include <linux/slab.h>
+#include <linux/usb.h>
+#include <linux/moduleparam.h>
+#include <sound/core.h>
+#include <sound/info.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include "usx2y.h"
+#include "usbusx2y.h"
+
+#define USX2Y_NRPACKS 4			/* Default value used for nr of packs per urb.
+					  1 to 4 have been tested ok on uhci.
+					  To use 3 on ohci, you'd need a patch:
+					  look for "0000425-linux-2.6.9-rc4-mm1_ohci-hcd.patch.gz" on
+					  "https://bugtrack.alsa-project.org/alsa-bug/bug_view_page.php?bug_id=0000425"
+					  .
+					  1, 2 and 4 work out of the box on ohci, if I recall correctly.
+					  Bigger is safer operation,
+					  smaller gives lower latencies.
+					*/
+#define USX2Y_NRPACKS_VARIABLE y	/* If your system works ok with this module's parameter
+					   nrpacks set to 1, you might as well comment 
+					   this #define out, and thereby produce smaller, faster code.
+					   You'd also set USX2Y_NRPACKS to 1 then.
+					*/
+
+#ifdef USX2Y_NRPACKS_VARIABLE
+ static int nrpacks = USX2Y_NRPACKS; /* number of packets per urb */
+ #define  nr_of_packs() nrpacks
+ module_param(nrpacks, int, 0444);
+ MODULE_PARM_DESC(nrpacks, "Number of packets per URB.");
+#else
+ #define nr_of_packs() USX2Y_NRPACKS
+#endif
+
+
+static int usX2Y_urb_capt_retire(struct snd_usX2Y_substream *subs)
+{
+	struct urb	*urb = subs->completed_urb;
+	struct snd_pcm_runtime *runtime = subs->pcm_substream->runtime;
+	unsigned char	*cp;
+	int 		i, len, lens = 0, hwptr_done = subs->hwptr_done;
+	struct usX2Ydev	*usX2Y = subs->usX2Y;
+
+	for (i = 0; i < nr_of_packs(); i++) {
+		cp = (unsigned char*)urb->transfer_buffer + urb->iso_frame_desc[i].offset;
+		if (urb->iso_frame_desc[i].status) { /* active? hmm, skip this */
+			snd_printk(KERN_ERR "active frame status %i. "
+				   "Most probably some hardware problem.\n",
+				   urb->iso_frame_desc[i].status);
+			return urb->iso_frame_desc[i].status;
+		}
+		len = urb->iso_frame_desc[i].actual_length / usX2Y->stride;
+		if (! len) {
+			snd_printd("0 == len ERROR!\n");
+			continue;
+		}
+
+		/* copy a data chunk */
+		if ((hwptr_done + len) > runtime->buffer_size) {
+			int cnt = runtime->buffer_size - hwptr_done;
+			int blen = cnt * usX2Y->stride;
+			memcpy(runtime->dma_area + hwptr_done * usX2Y->stride, cp, blen);
+			memcpy(runtime->dma_area, cp + blen, len * usX2Y->stride - blen);
+		} else {
+			memcpy(runtime->dma_area + hwptr_done * usX2Y->stride, cp,
+			       len * usX2Y->stride);
+		}
+		lens += len;
+		if ((hwptr_done += len) >= runtime->buffer_size)
+			hwptr_done -= runtime->buffer_size;
+	}
+
+	subs->hwptr_done = hwptr_done;
+	subs->transfer_done += lens;
+	/* update the pointer, call callback if necessary */
+	if (subs->transfer_done >= runtime->period_size) {
+		subs->transfer_done -= runtime->period_size;
+		snd_pcm_period_elapsed(subs->pcm_substream);
+	}
+	return 0;
+}
+/*
+ * prepare urb for playback data pipe
+ *
+ * we copy the data directly from the pcm buffer.
+ * the current position to be copied is held in hwptr field.
+ * since a urb can handle only a single linear buffer, if the total
+ * transferred area overflows the buffer boundary, we cannot send
+ * it directly from the buffer.  thus the data is once copied to
+ * a temporary buffer and urb points to that.
+ */
+static int usX2Y_urb_play_prepare(struct snd_usX2Y_substream *subs,
+				  struct urb *cap_urb,
+				  struct urb *urb)
+{
+	int count, counts, pack;
+	struct usX2Ydev *usX2Y = subs->usX2Y;
+	struct snd_pcm_runtime *runtime = subs->pcm_substream->runtime;
+
+	count = 0;
+	for (pack = 0; pack <  nr_of_packs(); pack++) {
+		/* calculate the size of a packet */
+		counts = cap_urb->iso_frame_desc[pack].actual_length / usX2Y->stride;
+		count += counts;
+		if (counts < 43 || counts > 50) {
+			snd_printk(KERN_ERR "should not be here with counts=%i\n", counts);
+			return -EPIPE;
+		}
+		/* set up descriptor */
+		urb->iso_frame_desc[pack].offset = pack ?
+			urb->iso_frame_desc[pack - 1].offset +
+			urb->iso_frame_desc[pack - 1].length :
+			0;
+		urb->iso_frame_desc[pack].length = cap_urb->iso_frame_desc[pack].actual_length;
+	}
+	if (atomic_read(&subs->state) >= state_PRERUNNING)
+		if (subs->hwptr + count > runtime->buffer_size) {
+			/* err, the transferred area goes over buffer boundary.
+			 * copy the data to the temp buffer.
+			 */
+			int len;
+			len = runtime->buffer_size - subs->hwptr;
+			urb->transfer_buffer = subs->tmpbuf;
+			memcpy(subs->tmpbuf, runtime->dma_area +
+			       subs->hwptr * usX2Y->stride, len * usX2Y->stride);
+			memcpy(subs->tmpbuf + len * usX2Y->stride,
+			       runtime->dma_area, (count - len) * usX2Y->stride);
+			subs->hwptr += count;
+			subs->hwptr -= runtime->buffer_size;
+		} else {
+			/* set the buffer pointer */
+			urb->transfer_buffer = runtime->dma_area + subs->hwptr * usX2Y->stride;
+			if ((subs->hwptr += count) >= runtime->buffer_size)
+				subs->hwptr -= runtime->buffer_size;
+		}
+	else
+		urb->transfer_buffer = subs->tmpbuf;
+	urb->transfer_buffer_length = count * usX2Y->stride;
+	return 0;
+}
+
+/*
+ * process after playback data complete
+ *
+ * update the current position and call callback if a period is processed.
+ */
+static void usX2Y_urb_play_retire(struct snd_usX2Y_substream *subs, struct urb *urb)
+{
+	struct snd_pcm_runtime *runtime = subs->pcm_substream->runtime;
+	int		len = urb->actual_length / subs->usX2Y->stride;
+
+	subs->transfer_done += len;
+	subs->hwptr_done +=  len;
+	if (subs->hwptr_done >= runtime->buffer_size)
+		subs->hwptr_done -= runtime->buffer_size;
+	if (subs->transfer_done >= runtime->period_size) {
+		subs->transfer_done -= runtime->period_size;
+		snd_pcm_period_elapsed(subs->pcm_substream);
+	}
+}
+
+static int usX2Y_urb_submit(struct snd_usX2Y_substream *subs, struct urb *urb, int frame)
+{
+	int err;
+	if (!urb)
+		return -ENODEV;
+	urb->start_frame = (frame + NRURBS * nr_of_packs());  // let hcd do rollover sanity checks
+	urb->hcpriv = NULL;
+	urb->dev = subs->usX2Y->dev; /* we need to set this at each time */
+	if ((err = usb_submit_urb(urb, GFP_ATOMIC)) < 0) {
+		snd_printk(KERN_ERR "usb_submit_urb() returned %i\n", err);
+		return err;
+	}
+	return 0;
+}
+
+static inline int usX2Y_usbframe_complete(struct snd_usX2Y_substream *capsubs,
+					  struct snd_usX2Y_substream *playbacksubs,
+					  int frame)
+{
+	int err, state;
+	struct urb *urb = playbacksubs->completed_urb;
+
+	state = atomic_read(&playbacksubs->state);
+	if (NULL != urb) {
+		if (state == state_RUNNING)
+			usX2Y_urb_play_retire(playbacksubs, urb);
+		else if (state >= state_PRERUNNING)
+			atomic_inc(&playbacksubs->state);
+	} else {
+		switch (state) {
+		case state_STARTING1:
+			urb = playbacksubs->urb[0];
+			atomic_inc(&playbacksubs->state);
+			break;
+		case state_STARTING2:
+			urb = playbacksubs->urb[1];
+			atomic_inc(&playbacksubs->state);
+			break;
+		}
+	}
+	if (urb) {
+		if ((err = usX2Y_urb_play_prepare(playbacksubs, capsubs->completed_urb, urb)) ||
+		    (err = usX2Y_urb_submit(playbacksubs, urb, frame))) {
+			return err;
+		}
+	}
+
+	playbacksubs->completed_urb = NULL;
+
+	state = atomic_read(&capsubs->state);
+	if (state >= state_PREPARED) {
+		if (state == state_RUNNING) {
+			if ((err = usX2Y_urb_capt_retire(capsubs)))
+				return err;
+		} else if (state >= state_PRERUNNING)
+			atomic_inc(&capsubs->state);
+		if ((err = usX2Y_urb_submit(capsubs, capsubs->completed_urb, frame)))
+			return err;
+	}
+	capsubs->completed_urb = NULL;
+	return 0;
+}
+
+
+static void usX2Y_clients_stop(struct usX2Ydev *usX2Y)
+{
+	int s, u;
+
+	for (s = 0; s < 4; s++) {
+		struct snd_usX2Y_substream *subs = usX2Y->subs[s];
+		if (subs) {
+			snd_printdd("%i %p state=%i\n", s, subs, atomic_read(&subs->state));
+			atomic_set(&subs->state, state_STOPPED);
+		}
+	}
+	for (s = 0; s < 4; s++) {
+		struct snd_usX2Y_substream *subs = usX2Y->subs[s];
+		if (subs) {
+			if (atomic_read(&subs->state) >= state_PRERUNNING)
+				snd_pcm_stop_xrun(subs->pcm_substream);
+			for (u = 0; u < NRURBS; u++) {
+				struct urb *urb = subs->urb[u];
+				if (NULL != urb)
+					snd_printdd("%i status=%i start_frame=%i\n",
+						    u, urb->status, urb->start_frame);
+			}
+		}
+	}
+	usX2Y->prepare_subs = NULL;
+	wake_up(&usX2Y->prepare_wait_queue);
+}
+
+static void usX2Y_error_urb_status(struct usX2Ydev *usX2Y,
+				   struct snd_usX2Y_substream *subs, struct urb *urb)
+{
+	snd_printk(KERN_ERR "ep=%i stalled with status=%i\n", subs->endpoint, urb->status);
+	urb->status = 0;
+	usX2Y_clients_stop(usX2Y);
+}
+
+static void i_usX2Y_urb_complete(struct urb *urb)
+{
+	struct snd_usX2Y_substream *subs = urb->context;
+	struct usX2Ydev *usX2Y = subs->usX2Y;
+
+	if (unlikely(atomic_read(&subs->state) < state_PREPARED)) {
+		snd_printdd("hcd_frame=%i ep=%i%s status=%i start_frame=%i\n",
+			    usb_get_current_frame_number(usX2Y->dev),
+			    subs->endpoint, usb_pipein(urb->pipe) ? "in" : "out",
+			    urb->status, urb->start_frame);
+		return;
+	}
+	if (unlikely(urb->status)) {
+		usX2Y_error_urb_status(usX2Y, subs, urb);
+		return;
+	}
+
+	subs->completed_urb = urb;
+
+	{
+		struct snd_usX2Y_substream *capsubs = usX2Y->subs[SNDRV_PCM_STREAM_CAPTURE],
+			*playbacksubs = usX2Y->subs[SNDRV_PCM_STREAM_PLAYBACK];
+		if (capsubs->completed_urb &&
+		    atomic_read(&capsubs->state) >= state_PREPARED &&
+		    (playbacksubs->completed_urb ||
+		     atomic_read(&playbacksubs->state) < state_PREPARED)) {
+			if (!usX2Y_usbframe_complete(capsubs, playbacksubs, urb->start_frame))
+				usX2Y->wait_iso_frame += nr_of_packs();
+			else {
+				snd_printdd("\n");
+				usX2Y_clients_stop(usX2Y);
+			}
+		}
+	}
+}
+
+static void usX2Y_urbs_set_complete(struct usX2Ydev * usX2Y,
+				    void (*complete)(struct urb *))
+{
+	int s, u;
+	for (s = 0; s < 4; s++) {
+		struct snd_usX2Y_substream *subs = usX2Y->subs[s];
+		if (NULL != subs)
+			for (u = 0; u < NRURBS; u++) {
+				struct urb * urb = subs->urb[u];
+				if (NULL != urb)
+					urb->complete = complete;
+			}
+	}
+}
+
+static void usX2Y_subs_startup_finish(struct usX2Ydev * usX2Y)
+{
+	usX2Y_urbs_set_complete(usX2Y, i_usX2Y_urb_complete);
+	usX2Y->prepare_subs = NULL;
+}
+
+static void i_usX2Y_subs_startup(struct urb *urb)
+{
+	struct snd_usX2Y_substream *subs = urb->context;
+	struct usX2Ydev *usX2Y = subs->usX2Y;
+	struct snd_usX2Y_substream *prepare_subs = usX2Y->prepare_subs;
+	if (NULL != prepare_subs)
+		if (urb->start_frame == prepare_subs->urb[0]->start_frame) {
+			usX2Y_subs_startup_finish(usX2Y);
+			atomic_inc(&prepare_subs->state);
+			wake_up(&usX2Y->prepare_wait_queue);
+		}
+
+	i_usX2Y_urb_complete(urb);
+}
+
+static void usX2Y_subs_prepare(struct snd_usX2Y_substream *subs)
+{
+	snd_printdd("usX2Y_substream_prepare(%p) ep=%i urb0=%p urb1=%p\n",
+		    subs, subs->endpoint, subs->urb[0], subs->urb[1]);
+	/* reset the pointer */
+	subs->hwptr = 0;
+	subs->hwptr_done = 0;
+	subs->transfer_done = 0;
+}
+
+
+static void usX2Y_urb_release(struct urb **urb, int free_tb)
+{
+	if (*urb) {
+		usb_kill_urb(*urb);
+		if (free_tb)
+			kfree((*urb)->transfer_buffer);
+		usb_free_urb(*urb);
+		*urb = NULL;
+	}
+}
+/*
+ * release a substreams urbs
+ */
+static void usX2Y_urbs_release(struct snd_usX2Y_substream *subs)
+{
+	int i;
+	snd_printdd("usX2Y_urbs_release() %i\n", subs->endpoint);
+	for (i = 0; i < NRURBS; i++)
+		usX2Y_urb_release(subs->urb + i,
+				  subs != subs->usX2Y->subs[SNDRV_PCM_STREAM_PLAYBACK]);
+
+	kfree(subs->tmpbuf);
+	subs->tmpbuf = NULL;
+}
+/*
+ * initialize a substream's urbs
+ */
+static int usX2Y_urbs_allocate(struct snd_usX2Y_substream *subs)
+{
+	int i;
+	unsigned int pipe;
+	int is_playback = subs == subs->usX2Y->subs[SNDRV_PCM_STREAM_PLAYBACK];
+	struct usb_device *dev = subs->usX2Y->dev;
+
+	pipe = is_playback ? usb_sndisocpipe(dev, subs->endpoint) :
+			usb_rcvisocpipe(dev, subs->endpoint);
+	subs->maxpacksize = usb_maxpacket(dev, pipe, is_playback);
+	if (!subs->maxpacksize)
+		return -EINVAL;
+
+	if (is_playback && NULL == subs->tmpbuf) {	/* allocate a temporary buffer for playback */
+		subs->tmpbuf = kcalloc(nr_of_packs(), subs->maxpacksize, GFP_KERNEL);
+		if (!subs->tmpbuf)
+			return -ENOMEM;
+	}
+	/* allocate and initialize data urbs */
+	for (i = 0; i < NRURBS; i++) {
+		struct urb **purb = subs->urb + i;
+		if (*purb) {
+			usb_kill_urb(*purb);
+			continue;
+		}
+		*purb = usb_alloc_urb(nr_of_packs(), GFP_KERNEL);
+		if (NULL == *purb) {
+			usX2Y_urbs_release(subs);
+			return -ENOMEM;
+		}
+		if (!is_playback && !(*purb)->transfer_buffer) {
+			/* allocate a capture buffer per urb */
+			(*purb)->transfer_buffer =
+				kmalloc_array(subs->maxpacksize,
+					      nr_of_packs(), GFP_KERNEL);
+			if (NULL == (*purb)->transfer_buffer) {
+				usX2Y_urbs_release(subs);
+				return -ENOMEM;
+			}
+		}
+		(*purb)->dev = dev;
+		(*purb)->pipe = pipe;
+		(*purb)->number_of_packets = nr_of_packs();
+		(*purb)->context = subs;
+		(*purb)->interval = 1;
+		(*purb)->complete = i_usX2Y_subs_startup;
+	}
+	return 0;
+}
+
+static void usX2Y_subs_startup(struct snd_usX2Y_substream *subs)
+{
+	struct usX2Ydev *usX2Y = subs->usX2Y;
+	usX2Y->prepare_subs = subs;
+	subs->urb[0]->start_frame = -1;
+	wmb();
+	usX2Y_urbs_set_complete(usX2Y, i_usX2Y_subs_startup);
+}
+
+static int usX2Y_urbs_start(struct snd_usX2Y_substream *subs)
+{
+	int i, err;
+	struct usX2Ydev *usX2Y = subs->usX2Y;
+
+	if ((err = usX2Y_urbs_allocate(subs)) < 0)
+		return err;
+	subs->completed_urb = NULL;
+	for (i = 0; i < 4; i++) {
+		struct snd_usX2Y_substream *subs = usX2Y->subs[i];
+		if (subs != NULL && atomic_read(&subs->state) >= state_PREPARED)
+			goto start;
+	}
+
+ start:
+	usX2Y_subs_startup(subs);
+	for (i = 0; i < NRURBS; i++) {
+		struct urb *urb = subs->urb[i];
+		if (usb_pipein(urb->pipe)) {
+			unsigned long pack;
+			if (0 == i)
+				atomic_set(&subs->state, state_STARTING3);
+			urb->dev = usX2Y->dev;
+			for (pack = 0; pack < nr_of_packs(); pack++) {
+				urb->iso_frame_desc[pack].offset = subs->maxpacksize * pack;
+				urb->iso_frame_desc[pack].length = subs->maxpacksize;
+			}
+			urb->transfer_buffer_length = subs->maxpacksize * nr_of_packs(); 
+			if ((err = usb_submit_urb(urb, GFP_ATOMIC)) < 0) {
+				snd_printk (KERN_ERR "cannot submit datapipe for urb %d, err = %d\n", i, err);
+				err = -EPIPE;
+				goto cleanup;
+			} else
+				if (i == 0)
+					usX2Y->wait_iso_frame = urb->start_frame;
+			urb->transfer_flags = 0;
+		} else {
+			atomic_set(&subs->state, state_STARTING1);
+			break;
+		}
+	}
+	err = 0;
+	wait_event(usX2Y->prepare_wait_queue, NULL == usX2Y->prepare_subs);
+	if (atomic_read(&subs->state) != state_PREPARED)
+		err = -EPIPE;
+
+ cleanup:
+	if (err) {
+		usX2Y_subs_startup_finish(usX2Y);
+		usX2Y_clients_stop(usX2Y);		// something is completely wroong > stop evrything
+	}
+	return err;
+}
+
+/*
+ * return the current pcm pointer.  just return the hwptr_done value.
+ */
+static snd_pcm_uframes_t snd_usX2Y_pcm_pointer(struct snd_pcm_substream *substream)
+{
+	struct snd_usX2Y_substream *subs = substream->runtime->private_data;
+	return subs->hwptr_done;
+}
+/*
+ * start/stop substream
+ */
+static int snd_usX2Y_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+	struct snd_usX2Y_substream *subs = substream->runtime->private_data;
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+		snd_printdd("snd_usX2Y_pcm_trigger(START)\n");
+		if (atomic_read(&subs->state) == state_PREPARED &&
+		    atomic_read(&subs->usX2Y->subs[SNDRV_PCM_STREAM_CAPTURE]->state) >= state_PREPARED) {
+			atomic_set(&subs->state, state_PRERUNNING);
+		} else {
+			snd_printdd("\n");
+			return -EPIPE;
+		}
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+		snd_printdd("snd_usX2Y_pcm_trigger(STOP)\n");
+		if (atomic_read(&subs->state) >= state_PRERUNNING)
+			atomic_set(&subs->state, state_PREPARED);
+		break;
+	default:
+		return -EINVAL;
+	}
+	return 0;
+}
+
+
+/*
+ * allocate a buffer, setup samplerate
+ *
+ * so far we use a physically linear buffer although packetize transfer
+ * doesn't need a continuous area.
+ * if sg buffer is supported on the later version of alsa, we'll follow
+ * that.
+ */
+static struct s_c2
+{
+	char c1, c2;
+}
+	SetRate44100[] =
+{
+	{ 0x14, 0x08},	// this line sets 44100, well actually a little less
+	{ 0x18, 0x40},	// only tascam / frontier design knows the further lines .......
+	{ 0x18, 0x42},
+	{ 0x18, 0x45},
+	{ 0x18, 0x46},
+	{ 0x18, 0x48},
+	{ 0x18, 0x4A},
+	{ 0x18, 0x4C},
+	{ 0x18, 0x4E},
+	{ 0x18, 0x50},
+	{ 0x18, 0x52},
+	{ 0x18, 0x54},
+	{ 0x18, 0x56},
+	{ 0x18, 0x58},
+	{ 0x18, 0x5A},
+	{ 0x18, 0x5C},
+	{ 0x18, 0x5E},
+	{ 0x18, 0x60},
+	{ 0x18, 0x62},
+	{ 0x18, 0x64},
+	{ 0x18, 0x66},
+	{ 0x18, 0x68},
+	{ 0x18, 0x6A},
+	{ 0x18, 0x6C},
+	{ 0x18, 0x6E},
+	{ 0x18, 0x70},
+	{ 0x18, 0x72},
+	{ 0x18, 0x74},
+	{ 0x18, 0x76},
+	{ 0x18, 0x78},
+	{ 0x18, 0x7A},
+	{ 0x18, 0x7C},
+	{ 0x18, 0x7E}
+};
+static struct s_c2 SetRate48000[] =
+{
+	{ 0x14, 0x09},	// this line sets 48000, well actually a little less
+	{ 0x18, 0x40},	// only tascam / frontier design knows the further lines .......
+	{ 0x18, 0x42},
+	{ 0x18, 0x45},
+	{ 0x18, 0x46},
+	{ 0x18, 0x48},
+	{ 0x18, 0x4A},
+	{ 0x18, 0x4C},
+	{ 0x18, 0x4E},
+	{ 0x18, 0x50},
+	{ 0x18, 0x52},
+	{ 0x18, 0x54},
+	{ 0x18, 0x56},
+	{ 0x18, 0x58},
+	{ 0x18, 0x5A},
+	{ 0x18, 0x5C},
+	{ 0x18, 0x5E},
+	{ 0x18, 0x60},
+	{ 0x18, 0x62},
+	{ 0x18, 0x64},
+	{ 0x18, 0x66},
+	{ 0x18, 0x68},
+	{ 0x18, 0x6A},
+	{ 0x18, 0x6C},
+	{ 0x18, 0x6E},
+	{ 0x18, 0x70},
+	{ 0x18, 0x73},
+	{ 0x18, 0x74},
+	{ 0x18, 0x76},
+	{ 0x18, 0x78},
+	{ 0x18, 0x7A},
+	{ 0x18, 0x7C},
+	{ 0x18, 0x7E}
+};
+#define NOOF_SETRATE_URBS ARRAY_SIZE(SetRate48000)
+
+static void i_usX2Y_04Int(struct urb *urb)
+{
+	struct usX2Ydev *usX2Y = urb->context;
+	
+	if (urb->status)
+		snd_printk(KERN_ERR "snd_usX2Y_04Int() urb->status=%i\n", urb->status);
+	if (0 == --usX2Y->US04->len)
+		wake_up(&usX2Y->In04WaitQueue);
+}
+
+static int usX2Y_rate_set(struct usX2Ydev *usX2Y, int rate)
+{
+	int			err = 0, i;
+	struct snd_usX2Y_urbSeq	*us = NULL;
+	int			*usbdata = NULL;
+	struct s_c2		*ra = rate == 48000 ? SetRate48000 : SetRate44100;
+
+	if (usX2Y->rate != rate) {
+		us = kzalloc(sizeof(*us) + sizeof(struct urb*) * NOOF_SETRATE_URBS, GFP_KERNEL);
+		if (NULL == us) {
+			err = -ENOMEM;
+			goto cleanup;
+		}
+		usbdata = kmalloc_array(NOOF_SETRATE_URBS, sizeof(int),
+					GFP_KERNEL);
+		if (NULL == usbdata) {
+			err = -ENOMEM;
+			goto cleanup;
+		}
+		for (i = 0; i < NOOF_SETRATE_URBS; ++i) {
+			if (NULL == (us->urb[i] = usb_alloc_urb(0, GFP_KERNEL))) {
+				err = -ENOMEM;
+				goto cleanup;
+			}
+			((char*)(usbdata + i))[0] = ra[i].c1;
+			((char*)(usbdata + i))[1] = ra[i].c2;
+			usb_fill_bulk_urb(us->urb[i], usX2Y->dev, usb_sndbulkpipe(usX2Y->dev, 4),
+					  usbdata + i, 2, i_usX2Y_04Int, usX2Y);
+		}
+		err = usb_urb_ep_type_check(us->urb[0]);
+		if (err < 0)
+			goto cleanup;
+		us->submitted =	0;
+		us->len =	NOOF_SETRATE_URBS;
+		usX2Y->US04 =	us;
+		wait_event_timeout(usX2Y->In04WaitQueue, 0 == us->len, HZ);
+		usX2Y->US04 =	NULL;
+		if (us->len)
+			err = -ENODEV;
+	cleanup:
+		if (us) {
+			us->submitted =	2*NOOF_SETRATE_URBS;
+			for (i = 0; i < NOOF_SETRATE_URBS; ++i) {
+				struct urb *urb = us->urb[i];
+				if (urb->status) {
+					if (!err)
+						err = -ENODEV;
+					usb_kill_urb(urb);
+				}
+				usb_free_urb(urb);
+			}
+			usX2Y->US04 = NULL;
+			kfree(usbdata);
+			kfree(us);
+			if (!err)
+				usX2Y->rate = rate;
+		}
+	}
+
+	return err;
+}
+
+
+static int usX2Y_format_set(struct usX2Ydev *usX2Y, snd_pcm_format_t format)
+{
+	int alternate, err;
+	struct list_head* p;
+	if (format == SNDRV_PCM_FORMAT_S24_3LE) {
+		alternate = 2;
+		usX2Y->stride = 6;
+	} else {
+		alternate = 1;
+		usX2Y->stride = 4;
+	}
+	list_for_each(p, &usX2Y->midi_list) {
+		snd_usbmidi_input_stop(p);
+	}
+	usb_kill_urb(usX2Y->In04urb);
+	if ((err = usb_set_interface(usX2Y->dev, 0, alternate))) {
+		snd_printk(KERN_ERR "usb_set_interface error \n");
+		return err;
+	}
+	usX2Y->In04urb->dev = usX2Y->dev;
+	err = usb_submit_urb(usX2Y->In04urb, GFP_KERNEL);
+	list_for_each(p, &usX2Y->midi_list) {
+		snd_usbmidi_input_start(p);
+	}
+	usX2Y->format = format;
+	usX2Y->rate = 0;
+	return err;
+}
+
+
+static int snd_usX2Y_pcm_hw_params(struct snd_pcm_substream *substream,
+				   struct snd_pcm_hw_params *hw_params)
+{
+	int			err = 0;
+	unsigned int		rate = params_rate(hw_params);
+	snd_pcm_format_t	format = params_format(hw_params);
+	struct snd_card *card = substream->pstr->pcm->card;
+	struct usX2Ydev	*dev = usX2Y(card);
+	int i;
+
+	mutex_lock(&usX2Y(card)->pcm_mutex);
+	snd_printdd("snd_usX2Y_hw_params(%p, %p)\n", substream, hw_params);
+	/* all pcm substreams off one usX2Y have to operate at the same
+	 * rate & format
+	 */
+	for (i = 0; i < dev->pcm_devs * 2; i++) {
+		struct snd_usX2Y_substream *subs = dev->subs[i];
+		struct snd_pcm_substream *test_substream;
+
+		if (!subs)
+			continue;
+		test_substream = subs->pcm_substream;
+		if (!test_substream || test_substream == substream ||
+		    !test_substream->runtime)
+			continue;
+		if ((test_substream->runtime->format &&
+		     test_substream->runtime->format != format) ||
+		    (test_substream->runtime->rate &&
+		     test_substream->runtime->rate != rate)) {
+			err = -EINVAL;
+			goto error;
+		}
+	}
+
+	err = snd_pcm_lib_malloc_pages(substream,
+				       params_buffer_bytes(hw_params));
+	if (err < 0) {
+		snd_printk(KERN_ERR "snd_pcm_lib_malloc_pages(%p, %i) returned %i\n",
+			   substream, params_buffer_bytes(hw_params), err);
+		goto error;
+	}
+
+ error:
+	mutex_unlock(&usX2Y(card)->pcm_mutex);
+	return err;
+}
+
+/*
+ * free the buffer
+ */
+static int snd_usX2Y_pcm_hw_free(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct snd_usX2Y_substream *subs = runtime->private_data;
+	mutex_lock(&subs->usX2Y->pcm_mutex);
+	snd_printdd("snd_usX2Y_hw_free(%p)\n", substream);
+
+	if (SNDRV_PCM_STREAM_PLAYBACK == substream->stream) {
+		struct snd_usX2Y_substream *cap_subs = subs->usX2Y->subs[SNDRV_PCM_STREAM_CAPTURE];
+		atomic_set(&subs->state, state_STOPPED);
+		usX2Y_urbs_release(subs);
+		if (!cap_subs->pcm_substream ||
+		    !cap_subs->pcm_substream->runtime ||
+		    !cap_subs->pcm_substream->runtime->status ||
+		    cap_subs->pcm_substream->runtime->status->state < SNDRV_PCM_STATE_PREPARED) {
+			atomic_set(&cap_subs->state, state_STOPPED);
+			usX2Y_urbs_release(cap_subs);
+		}
+	} else {
+		struct snd_usX2Y_substream *playback_subs = subs->usX2Y->subs[SNDRV_PCM_STREAM_PLAYBACK];
+		if (atomic_read(&playback_subs->state) < state_PREPARED) {
+			atomic_set(&subs->state, state_STOPPED);
+			usX2Y_urbs_release(subs);
+		}
+	}
+	mutex_unlock(&subs->usX2Y->pcm_mutex);
+	return snd_pcm_lib_free_pages(substream);
+}
+/*
+ * prepare callback
+ *
+ * set format and initialize urbs
+ */
+static int snd_usX2Y_pcm_prepare(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct snd_usX2Y_substream *subs = runtime->private_data;
+	struct usX2Ydev *usX2Y = subs->usX2Y;
+	struct snd_usX2Y_substream *capsubs = subs->usX2Y->subs[SNDRV_PCM_STREAM_CAPTURE];
+	int err = 0;
+	snd_printdd("snd_usX2Y_pcm_prepare(%p)\n", substream);
+
+	mutex_lock(&usX2Y->pcm_mutex);
+	usX2Y_subs_prepare(subs);
+// Start hardware streams
+// SyncStream first....
+	if (atomic_read(&capsubs->state) < state_PREPARED) {
+		if (usX2Y->format != runtime->format)
+			if ((err = usX2Y_format_set(usX2Y, runtime->format)) < 0)
+				goto up_prepare_mutex;
+		if (usX2Y->rate != runtime->rate)
+			if ((err = usX2Y_rate_set(usX2Y, runtime->rate)) < 0)
+				goto up_prepare_mutex;
+		snd_printdd("starting capture pipe for %s\n", subs == capsubs ? "self" : "playpipe");
+		if (0 > (err = usX2Y_urbs_start(capsubs)))
+			goto up_prepare_mutex;
+	}
+
+	if (subs != capsubs && atomic_read(&subs->state) < state_PREPARED)
+		err = usX2Y_urbs_start(subs);
+
+ up_prepare_mutex:
+	mutex_unlock(&usX2Y->pcm_mutex);
+	return err;
+}
+
+static struct snd_pcm_hardware snd_usX2Y_2c =
+{
+	.info =			(SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
+				 SNDRV_PCM_INFO_BLOCK_TRANSFER |
+				 SNDRV_PCM_INFO_MMAP_VALID |
+				 SNDRV_PCM_INFO_BATCH),
+	.formats =                 SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_3LE,
+	.rates =                   SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000,
+	.rate_min =                44100,
+	.rate_max =                48000,
+	.channels_min =            2,
+	.channels_max =            2,
+	.buffer_bytes_max =	(2*128*1024),
+	.period_bytes_min =	64,
+	.period_bytes_max =	(128*1024),
+	.periods_min =		2,
+	.periods_max =		1024,
+	.fifo_size =              0
+};
+
+
+
+static int snd_usX2Y_pcm_open(struct snd_pcm_substream *substream)
+{
+	struct snd_usX2Y_substream	*subs = ((struct snd_usX2Y_substream **)
+					 snd_pcm_substream_chip(substream))[substream->stream];
+	struct snd_pcm_runtime	*runtime = substream->runtime;
+
+	if (subs->usX2Y->chip_status & USX2Y_STAT_CHIP_MMAP_PCM_URBS)
+		return -EBUSY;
+
+	runtime->hw = snd_usX2Y_2c;
+	runtime->private_data = subs;
+	subs->pcm_substream = substream;
+	snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_PERIOD_TIME, 1000, 200000);
+	return 0;
+}
+
+
+
+static int snd_usX2Y_pcm_close(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct snd_usX2Y_substream *subs = runtime->private_data;
+
+	subs->pcm_substream = NULL;
+
+	return 0;
+}
+
+
+static const struct snd_pcm_ops snd_usX2Y_pcm_ops =
+{
+	.open =		snd_usX2Y_pcm_open,
+	.close =	snd_usX2Y_pcm_close,
+	.ioctl =	snd_pcm_lib_ioctl,
+	.hw_params =	snd_usX2Y_pcm_hw_params,
+	.hw_free =	snd_usX2Y_pcm_hw_free,
+	.prepare =	snd_usX2Y_pcm_prepare,
+	.trigger =	snd_usX2Y_pcm_trigger,
+	.pointer =	snd_usX2Y_pcm_pointer,
+};
+
+
+/*
+ * free a usb stream instance
+ */
+static void usX2Y_audio_stream_free(struct snd_usX2Y_substream **usX2Y_substream)
+{
+	kfree(usX2Y_substream[SNDRV_PCM_STREAM_PLAYBACK]);
+	usX2Y_substream[SNDRV_PCM_STREAM_PLAYBACK] = NULL;
+
+	kfree(usX2Y_substream[SNDRV_PCM_STREAM_CAPTURE]);
+	usX2Y_substream[SNDRV_PCM_STREAM_CAPTURE] = NULL;
+}
+
+static void snd_usX2Y_pcm_private_free(struct snd_pcm *pcm)
+{
+	struct snd_usX2Y_substream **usX2Y_stream = pcm->private_data;
+	if (usX2Y_stream)
+		usX2Y_audio_stream_free(usX2Y_stream);
+}
+
+static int usX2Y_audio_stream_new(struct snd_card *card, int playback_endpoint, int capture_endpoint)
+{
+	struct snd_pcm *pcm;
+	int err, i;
+	struct snd_usX2Y_substream **usX2Y_substream =
+		usX2Y(card)->subs + 2 * usX2Y(card)->pcm_devs;
+
+	for (i = playback_endpoint ? SNDRV_PCM_STREAM_PLAYBACK : SNDRV_PCM_STREAM_CAPTURE;
+	     i <= SNDRV_PCM_STREAM_CAPTURE; ++i) {
+		usX2Y_substream[i] = kzalloc(sizeof(struct snd_usX2Y_substream), GFP_KERNEL);
+		if (!usX2Y_substream[i])
+			return -ENOMEM;
+
+		usX2Y_substream[i]->usX2Y = usX2Y(card);
+	}
+
+	if (playback_endpoint)
+		usX2Y_substream[SNDRV_PCM_STREAM_PLAYBACK]->endpoint = playback_endpoint;
+	usX2Y_substream[SNDRV_PCM_STREAM_CAPTURE]->endpoint = capture_endpoint;
+
+	err = snd_pcm_new(card, NAME_ALLCAPS" Audio", usX2Y(card)->pcm_devs,
+			  playback_endpoint ? 1 : 0, 1,
+			  &pcm);
+	if (err < 0) {
+		usX2Y_audio_stream_free(usX2Y_substream);
+		return err;
+	}
+
+	if (playback_endpoint)
+		snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_usX2Y_pcm_ops);
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_usX2Y_pcm_ops);
+
+	pcm->private_data = usX2Y_substream;
+	pcm->private_free = snd_usX2Y_pcm_private_free;
+	pcm->info_flags = 0;
+
+	sprintf(pcm->name, NAME_ALLCAPS" Audio #%d", usX2Y(card)->pcm_devs);
+
+	if ((playback_endpoint &&
+	     0 > (err = snd_pcm_lib_preallocate_pages(pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream,
+						     SNDRV_DMA_TYPE_CONTINUOUS,
+						     snd_dma_continuous_data(GFP_KERNEL),
+						     64*1024, 128*1024))) ||
+	    0 > (err = snd_pcm_lib_preallocate_pages(pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream,
+	    					     SNDRV_DMA_TYPE_CONTINUOUS,
+	    					     snd_dma_continuous_data(GFP_KERNEL),
+						     64*1024, 128*1024))) {
+		snd_usX2Y_pcm_private_free(pcm);
+		return err;
+	}
+	usX2Y(card)->pcm_devs++;
+
+	return 0;
+}
+
+/*
+ * create a chip instance and set its names.
+ */
+int usX2Y_audio_create(struct snd_card *card)
+{
+	int err = 0;
+	
+	INIT_LIST_HEAD(&usX2Y(card)->pcm_list);
+
+	if (0 > (err = usX2Y_audio_stream_new(card, 0xA, 0x8)))
+		return err;
+	if (le16_to_cpu(usX2Y(card)->dev->descriptor.idProduct) == USB_ID_US428)
+	     if (0 > (err = usX2Y_audio_stream_new(card, 0, 0xA)))
+		     return err;
+	if (le16_to_cpu(usX2Y(card)->dev->descriptor.idProduct) != USB_ID_US122)
+		err = usX2Y_rate_set(usX2Y(card), 44100);	// Lets us428 recognize output-volume settings, disturbs us122.
+	return err;
+}
diff --git a/sound/usb/usx2y/usx2y.h b/sound/usb/usx2y/usx2y.h
new file mode 100644
index 0000000..7e59263
--- /dev/null
+++ b/sound/usb/usx2y/usx2y.h
@@ -0,0 +1,51 @@
+/*
+ * Driver for Tascam US-X2Y USB soundcards
+ *
+ * Copyright (c) 2003 by Karsten Wiese <annabellesgarden@yahoo.de>
+ *
+ *   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
+ */
+
+#ifndef __SOUND_USX2Y_COMMON_H
+#define __SOUND_USX2Y_COMMON_H
+
+
+#define USX2Y_DRIVER_VERSION	0x0100	/* 0.1.0 */
+
+
+/* hwdep id string */
+#define SND_USX2Y_LOADER_ID		"USX2Y Loader"
+#define SND_USX2Y_USBPCM_ID		"USX2Y USBPCM"
+
+/* hardware type */
+enum {
+	USX2Y_TYPE_122,
+	USX2Y_TYPE_224,
+	USX2Y_TYPE_428,
+	USX2Y_TYPE_NUMS
+};
+
+#define USB_ID_US122 0x8007
+#define USB_ID_US224 0x8005
+#define USB_ID_US428 0x8001
+
+/* chip status */
+enum {
+	USX2Y_STAT_CHIP_INIT	=	(1 << 0),	/* all operational */
+	USX2Y_STAT_CHIP_MMAP_PCM_URBS = (1 << 1),	/* pcm transport over mmaped urbs */
+	USX2Y_STAT_CHIP_HUP	=	(1 << 31),	/* all operational */
+};
+
+#endif /* __SOUND_USX2Y_COMMON_H */
diff --git a/sound/usb/usx2y/usx2yhwdeppcm.c b/sound/usb/usx2y/usx2yhwdeppcm.c
new file mode 100644
index 0000000..4fd9276
--- /dev/null
+++ b/sound/usb/usx2y/usx2yhwdeppcm.c
@@ -0,0 +1,761 @@
+/*
+ *   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
+ */
+
+/* USX2Y "rawusb" aka hwdep_pcm implementation
+
+ Its usb's unableness to atomically handle power of 2 period sized data chuncs
+ at standard samplerates,
+ what led to this part of the usx2y module: 
+ It provides the alsa kernel half of the usx2y-alsa-jack driver pair.
+ The pair uses a hardware dependent alsa-device for mmaped pcm transport.
+ Advantage achieved:
+         The usb_hc moves pcm data from/into memory via DMA.
+         That memory is mmaped by jack's usx2y driver.
+         Jack's usx2y driver is the first/last to read/write pcm data.
+         Read/write is a combination of power of 2 period shaping and
+         float/int conversation.
+         Compared to mainline alsa/jack we leave out power of 2 period shaping inside
+         snd-usb-usx2y which needs memcpy() and additional buffers.
+         As a side effect possible unwanted pcm-data coruption resulting of
+         standard alsa's snd-usb-usx2y period shaping scheme falls away.
+         Result is sane jack operation at buffering schemes down to 128frames,
+         2 periods.
+         plain usx2y alsa mode is able to achieve 64frames, 4periods, but only at the
+         cost of easier triggered i.e. aeolus xruns (128 or 256frames,
+         2periods works but is useless cause of crackling).
+
+ This is a first "proof of concept" implementation.
+ Later, functionalities should migrate to more appropriate places:
+ Userland:
+ - The jackd could mmap its float-pcm buffers directly from alsa-lib.
+ - alsa-lib could provide power of 2 period sized shaping combined with int/float
+   conversation.
+   Currently the usx2y jack driver provides above 2 services.
+ Kernel:
+ - rawusb dma pcm buffer transport should go to snd-usb-lib, so also snd-usb-audio
+   devices can use it.
+   Currently rawusb dma pcm buffer transport (this file) is only available to snd-usb-usx2y. 
+*/
+
+#include <linux/delay.h>
+#include <linux/gfp.h>
+#include "usbusx2yaudio.c"
+
+#if defined(USX2Y_NRPACKS_VARIABLE) || USX2Y_NRPACKS == 1
+
+#include <sound/hwdep.h>
+
+
+static int usX2Y_usbpcm_urb_capt_retire(struct snd_usX2Y_substream *subs)
+{
+	struct urb	*urb = subs->completed_urb;
+	struct snd_pcm_runtime *runtime = subs->pcm_substream->runtime;
+	int 		i, lens = 0, hwptr_done = subs->hwptr_done;
+	struct usX2Ydev	*usX2Y = subs->usX2Y;
+	if (0 > usX2Y->hwdep_pcm_shm->capture_iso_start) { //FIXME
+		int head = usX2Y->hwdep_pcm_shm->captured_iso_head + 1;
+		if (head >= ARRAY_SIZE(usX2Y->hwdep_pcm_shm->captured_iso))
+			head = 0;
+		usX2Y->hwdep_pcm_shm->capture_iso_start = head;
+		snd_printdd("cap start %i\n", head);
+	}
+	for (i = 0; i < nr_of_packs(); i++) {
+		if (urb->iso_frame_desc[i].status) { /* active? hmm, skip this */
+			snd_printk(KERN_ERR "active frame status %i. Most probably some hardware problem.\n", urb->iso_frame_desc[i].status);
+			return urb->iso_frame_desc[i].status;
+		}
+		lens += urb->iso_frame_desc[i].actual_length / usX2Y->stride;
+	}
+	if ((hwptr_done += lens) >= runtime->buffer_size)
+		hwptr_done -= runtime->buffer_size;
+	subs->hwptr_done = hwptr_done;
+	subs->transfer_done += lens;
+	/* update the pointer, call callback if necessary */
+	if (subs->transfer_done >= runtime->period_size) {
+		subs->transfer_done -= runtime->period_size;
+		snd_pcm_period_elapsed(subs->pcm_substream);
+	}
+	return 0;
+}
+
+static inline int usX2Y_iso_frames_per_buffer(struct snd_pcm_runtime *runtime,
+					      struct usX2Ydev * usX2Y)
+{
+	return (runtime->buffer_size * 1000) / usX2Y->rate + 1;	//FIXME: so far only correct period_size == 2^x ?
+}
+
+/*
+ * prepare urb for playback data pipe
+ *
+ * we copy the data directly from the pcm buffer.
+ * the current position to be copied is held in hwptr field.
+ * since a urb can handle only a single linear buffer, if the total
+ * transferred area overflows the buffer boundary, we cannot send
+ * it directly from the buffer.  thus the data is once copied to
+ * a temporary buffer and urb points to that.
+ */
+static int usX2Y_hwdep_urb_play_prepare(struct snd_usX2Y_substream *subs,
+					struct urb *urb)
+{
+	int count, counts, pack;
+	struct usX2Ydev *usX2Y = subs->usX2Y;
+	struct snd_usX2Y_hwdep_pcm_shm *shm = usX2Y->hwdep_pcm_shm;
+	struct snd_pcm_runtime *runtime = subs->pcm_substream->runtime;
+
+	if (0 > shm->playback_iso_start) {
+		shm->playback_iso_start = shm->captured_iso_head -
+			usX2Y_iso_frames_per_buffer(runtime, usX2Y);
+		if (0 > shm->playback_iso_start)
+			shm->playback_iso_start += ARRAY_SIZE(shm->captured_iso);
+		shm->playback_iso_head = shm->playback_iso_start;
+	}
+
+	count = 0;
+	for (pack = 0; pack < nr_of_packs(); pack++) {
+		/* calculate the size of a packet */
+		counts = shm->captured_iso[shm->playback_iso_head].length / usX2Y->stride;
+		if (counts < 43 || counts > 50) {
+			snd_printk(KERN_ERR "should not be here with counts=%i\n", counts);
+			return -EPIPE;
+		}
+		/* set up descriptor */
+		urb->iso_frame_desc[pack].offset = shm->captured_iso[shm->playback_iso_head].offset;
+		urb->iso_frame_desc[pack].length = shm->captured_iso[shm->playback_iso_head].length;
+		if (atomic_read(&subs->state) != state_RUNNING)
+			memset((char *)urb->transfer_buffer + urb->iso_frame_desc[pack].offset, 0,
+			       urb->iso_frame_desc[pack].length);
+		if (++shm->playback_iso_head >= ARRAY_SIZE(shm->captured_iso))
+			shm->playback_iso_head = 0;
+		count += counts;
+	}
+	urb->transfer_buffer_length = count * usX2Y->stride;
+	return 0;
+}
+
+
+static inline void usX2Y_usbpcm_urb_capt_iso_advance(struct snd_usX2Y_substream *subs,
+						     struct urb *urb)
+{
+	int pack;
+	for (pack = 0; pack < nr_of_packs(); ++pack) {
+		struct usb_iso_packet_descriptor *desc = urb->iso_frame_desc + pack;
+		if (NULL != subs) {
+			struct snd_usX2Y_hwdep_pcm_shm *shm = subs->usX2Y->hwdep_pcm_shm;
+			int head = shm->captured_iso_head + 1;
+			if (head >= ARRAY_SIZE(shm->captured_iso))
+				head = 0;
+			shm->captured_iso[head].frame = urb->start_frame + pack;
+			shm->captured_iso[head].offset = desc->offset;
+			shm->captured_iso[head].length = desc->actual_length;
+			shm->captured_iso_head = head;
+			shm->captured_iso_frames++;
+		}
+		if ((desc->offset += desc->length * NRURBS*nr_of_packs()) +
+		    desc->length >= SSS)
+			desc->offset -= (SSS - desc->length);
+	}
+}
+
+static inline int usX2Y_usbpcm_usbframe_complete(struct snd_usX2Y_substream *capsubs,
+						 struct snd_usX2Y_substream *capsubs2,
+						 struct snd_usX2Y_substream *playbacksubs,
+						 int frame)
+{
+	int err, state;
+	struct urb *urb = playbacksubs->completed_urb;
+
+	state = atomic_read(&playbacksubs->state);
+	if (NULL != urb) {
+		if (state == state_RUNNING)
+			usX2Y_urb_play_retire(playbacksubs, urb);
+		else if (state >= state_PRERUNNING)
+			atomic_inc(&playbacksubs->state);
+	} else {
+		switch (state) {
+		case state_STARTING1:
+			urb = playbacksubs->urb[0];
+			atomic_inc(&playbacksubs->state);
+			break;
+		case state_STARTING2:
+			urb = playbacksubs->urb[1];
+			atomic_inc(&playbacksubs->state);
+			break;
+		}
+	}
+	if (urb) {
+		if ((err = usX2Y_hwdep_urb_play_prepare(playbacksubs, urb)) ||
+		    (err = usX2Y_urb_submit(playbacksubs, urb, frame))) {
+			return err;
+		}
+	}
+	
+	playbacksubs->completed_urb = NULL;
+
+	state = atomic_read(&capsubs->state);
+	if (state >= state_PREPARED) {
+		if (state == state_RUNNING) {
+			if ((err = usX2Y_usbpcm_urb_capt_retire(capsubs)))
+				return err;
+		} else if (state >= state_PRERUNNING)
+			atomic_inc(&capsubs->state);
+		usX2Y_usbpcm_urb_capt_iso_advance(capsubs, capsubs->completed_urb);
+		if (NULL != capsubs2)
+			usX2Y_usbpcm_urb_capt_iso_advance(NULL, capsubs2->completed_urb);
+		if ((err = usX2Y_urb_submit(capsubs, capsubs->completed_urb, frame)))
+			return err;
+		if (NULL != capsubs2)
+			if ((err = usX2Y_urb_submit(capsubs2, capsubs2->completed_urb, frame)))
+				return err;
+	}
+	capsubs->completed_urb = NULL;
+	if (NULL != capsubs2)
+		capsubs2->completed_urb = NULL;
+	return 0;
+}
+
+
+static void i_usX2Y_usbpcm_urb_complete(struct urb *urb)
+{
+	struct snd_usX2Y_substream *subs = urb->context;
+	struct usX2Ydev *usX2Y = subs->usX2Y;
+	struct snd_usX2Y_substream *capsubs, *capsubs2, *playbacksubs;
+
+	if (unlikely(atomic_read(&subs->state) < state_PREPARED)) {
+		snd_printdd("hcd_frame=%i ep=%i%s status=%i start_frame=%i\n",
+			    usb_get_current_frame_number(usX2Y->dev),
+			    subs->endpoint, usb_pipein(urb->pipe) ? "in" : "out",
+			    urb->status, urb->start_frame);
+		return;
+	}
+	if (unlikely(urb->status)) {
+		usX2Y_error_urb_status(usX2Y, subs, urb);
+		return;
+	}
+
+	subs->completed_urb = urb;
+	capsubs = usX2Y->subs[SNDRV_PCM_STREAM_CAPTURE];
+	capsubs2 = usX2Y->subs[SNDRV_PCM_STREAM_CAPTURE + 2];
+	playbacksubs = usX2Y->subs[SNDRV_PCM_STREAM_PLAYBACK];
+	if (capsubs->completed_urb && atomic_read(&capsubs->state) >= state_PREPARED &&
+	    (NULL == capsubs2 || capsubs2->completed_urb) &&
+	    (playbacksubs->completed_urb || atomic_read(&playbacksubs->state) < state_PREPARED)) {
+		if (!usX2Y_usbpcm_usbframe_complete(capsubs, capsubs2, playbacksubs, urb->start_frame))
+			usX2Y->wait_iso_frame += nr_of_packs();
+		else {
+			snd_printdd("\n");
+			usX2Y_clients_stop(usX2Y);
+		}
+	}
+}
+
+
+static void usX2Y_hwdep_urb_release(struct urb **urb)
+{
+	usb_kill_urb(*urb);
+	usb_free_urb(*urb);
+	*urb = NULL;
+}
+
+/*
+ * release a substream
+ */
+static void usX2Y_usbpcm_urbs_release(struct snd_usX2Y_substream *subs)
+{
+	int i;
+	snd_printdd("snd_usX2Y_urbs_release() %i\n", subs->endpoint);
+	for (i = 0; i < NRURBS; i++)
+		usX2Y_hwdep_urb_release(subs->urb + i);
+}
+
+static void usX2Y_usbpcm_subs_startup_finish(struct usX2Ydev * usX2Y)
+{
+	usX2Y_urbs_set_complete(usX2Y, i_usX2Y_usbpcm_urb_complete);
+	usX2Y->prepare_subs = NULL;
+}
+
+static void i_usX2Y_usbpcm_subs_startup(struct urb *urb)
+{
+	struct snd_usX2Y_substream *subs = urb->context;
+	struct usX2Ydev *usX2Y = subs->usX2Y;
+	struct snd_usX2Y_substream *prepare_subs = usX2Y->prepare_subs;
+	if (NULL != prepare_subs &&
+	    urb->start_frame == prepare_subs->urb[0]->start_frame) {
+		atomic_inc(&prepare_subs->state);
+		if (prepare_subs == usX2Y->subs[SNDRV_PCM_STREAM_CAPTURE]) {
+			struct snd_usX2Y_substream *cap_subs2 = usX2Y->subs[SNDRV_PCM_STREAM_CAPTURE + 2];
+			if (cap_subs2 != NULL)
+				atomic_inc(&cap_subs2->state);
+		}
+		usX2Y_usbpcm_subs_startup_finish(usX2Y);
+		wake_up(&usX2Y->prepare_wait_queue);
+	}
+
+	i_usX2Y_usbpcm_urb_complete(urb);
+}
+
+/*
+ * initialize a substream's urbs
+ */
+static int usX2Y_usbpcm_urbs_allocate(struct snd_usX2Y_substream *subs)
+{
+	int i;
+	unsigned int pipe;
+	int is_playback = subs == subs->usX2Y->subs[SNDRV_PCM_STREAM_PLAYBACK];
+	struct usb_device *dev = subs->usX2Y->dev;
+
+	pipe = is_playback ? usb_sndisocpipe(dev, subs->endpoint) :
+			usb_rcvisocpipe(dev, subs->endpoint);
+	subs->maxpacksize = usb_maxpacket(dev, pipe, is_playback);
+	if (!subs->maxpacksize)
+		return -EINVAL;
+
+	/* allocate and initialize data urbs */
+	for (i = 0; i < NRURBS; i++) {
+		struct urb **purb = subs->urb + i;
+		if (*purb) {
+			usb_kill_urb(*purb);
+			continue;
+		}
+		*purb = usb_alloc_urb(nr_of_packs(), GFP_KERNEL);
+		if (NULL == *purb) {
+			usX2Y_usbpcm_urbs_release(subs);
+			return -ENOMEM;
+		}
+		(*purb)->transfer_buffer = is_playback ?
+			subs->usX2Y->hwdep_pcm_shm->playback : (
+				subs->endpoint == 0x8 ?
+				subs->usX2Y->hwdep_pcm_shm->capture0x8 :
+				subs->usX2Y->hwdep_pcm_shm->capture0xA);
+
+		(*purb)->dev = dev;
+		(*purb)->pipe = pipe;
+		(*purb)->number_of_packets = nr_of_packs();
+		(*purb)->context = subs;
+		(*purb)->interval = 1;
+		(*purb)->complete = i_usX2Y_usbpcm_subs_startup;
+	}
+	return 0;
+}
+
+/*
+ * free the buffer
+ */
+static int snd_usX2Y_usbpcm_hw_free(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct snd_usX2Y_substream *subs = runtime->private_data,
+		*cap_subs2 = subs->usX2Y->subs[SNDRV_PCM_STREAM_CAPTURE + 2];
+	mutex_lock(&subs->usX2Y->pcm_mutex);
+	snd_printdd("snd_usX2Y_usbpcm_hw_free(%p)\n", substream);
+
+	if (SNDRV_PCM_STREAM_PLAYBACK == substream->stream) {
+		struct snd_usX2Y_substream *cap_subs = subs->usX2Y->subs[SNDRV_PCM_STREAM_CAPTURE];
+		atomic_set(&subs->state, state_STOPPED);
+		usX2Y_usbpcm_urbs_release(subs);
+		if (!cap_subs->pcm_substream ||
+		    !cap_subs->pcm_substream->runtime ||
+		    !cap_subs->pcm_substream->runtime->status ||
+		    cap_subs->pcm_substream->runtime->status->state < SNDRV_PCM_STATE_PREPARED) {
+			atomic_set(&cap_subs->state, state_STOPPED);
+			if (NULL != cap_subs2)
+				atomic_set(&cap_subs2->state, state_STOPPED);
+			usX2Y_usbpcm_urbs_release(cap_subs);
+			if (NULL != cap_subs2)
+				usX2Y_usbpcm_urbs_release(cap_subs2);
+		}
+	} else {
+		struct snd_usX2Y_substream *playback_subs = subs->usX2Y->subs[SNDRV_PCM_STREAM_PLAYBACK];
+		if (atomic_read(&playback_subs->state) < state_PREPARED) {
+			atomic_set(&subs->state, state_STOPPED);
+			if (NULL != cap_subs2)
+				atomic_set(&cap_subs2->state, state_STOPPED);
+			usX2Y_usbpcm_urbs_release(subs);
+			if (NULL != cap_subs2)
+				usX2Y_usbpcm_urbs_release(cap_subs2);
+		}
+	}
+	mutex_unlock(&subs->usX2Y->pcm_mutex);
+	return snd_pcm_lib_free_pages(substream);
+}
+
+static void usX2Y_usbpcm_subs_startup(struct snd_usX2Y_substream *subs)
+{
+	struct usX2Ydev * usX2Y = subs->usX2Y;
+	usX2Y->prepare_subs = subs;
+	subs->urb[0]->start_frame = -1;
+	smp_wmb();	// Make sure above modifications are seen by i_usX2Y_subs_startup()
+	usX2Y_urbs_set_complete(usX2Y, i_usX2Y_usbpcm_subs_startup);
+}
+
+static int usX2Y_usbpcm_urbs_start(struct snd_usX2Y_substream *subs)
+{
+	int	p, u, err,
+		stream = subs->pcm_substream->stream;
+	struct usX2Ydev *usX2Y = subs->usX2Y;
+
+	if (SNDRV_PCM_STREAM_CAPTURE == stream) {
+		usX2Y->hwdep_pcm_shm->captured_iso_head = -1;
+		usX2Y->hwdep_pcm_shm->captured_iso_frames = 0;
+	}
+
+	for (p = 0; 3 >= (stream + p); p += 2) {
+		struct snd_usX2Y_substream *subs = usX2Y->subs[stream + p];
+		if (subs != NULL) {
+			if ((err = usX2Y_usbpcm_urbs_allocate(subs)) < 0)
+				return err;
+			subs->completed_urb = NULL;
+		}
+	}
+
+	for (p = 0; p < 4; p++) {
+		struct snd_usX2Y_substream *subs = usX2Y->subs[p];
+		if (subs != NULL && atomic_read(&subs->state) >= state_PREPARED)
+			goto start;
+	}
+
+ start:
+	usX2Y_usbpcm_subs_startup(subs);
+	for (u = 0; u < NRURBS; u++) {
+		for (p = 0; 3 >= (stream + p); p += 2) {
+			struct snd_usX2Y_substream *subs = usX2Y->subs[stream + p];
+			if (subs != NULL) {
+				struct urb *urb = subs->urb[u];
+				if (usb_pipein(urb->pipe)) {
+					unsigned long pack;
+					if (0 == u)
+						atomic_set(&subs->state, state_STARTING3);
+					urb->dev = usX2Y->dev;
+					for (pack = 0; pack < nr_of_packs(); pack++) {
+						urb->iso_frame_desc[pack].offset = subs->maxpacksize * (pack + u * nr_of_packs());
+						urb->iso_frame_desc[pack].length = subs->maxpacksize;
+					}
+					urb->transfer_buffer_length = subs->maxpacksize * nr_of_packs(); 
+					if ((err = usb_submit_urb(urb, GFP_KERNEL)) < 0) {
+						snd_printk (KERN_ERR "cannot usb_submit_urb() for urb %d, err = %d\n", u, err);
+						err = -EPIPE;
+						goto cleanup;
+					}  else {
+						snd_printdd("%i\n", urb->start_frame);
+						if (u == 0)
+							usX2Y->wait_iso_frame = urb->start_frame;
+					}
+					urb->transfer_flags = 0;
+				} else {
+					atomic_set(&subs->state, state_STARTING1);
+					break;
+				}			
+			}
+		}
+	}
+	err = 0;
+	wait_event(usX2Y->prepare_wait_queue, NULL == usX2Y->prepare_subs);
+	if (atomic_read(&subs->state) != state_PREPARED)
+		err = -EPIPE;
+		
+ cleanup:
+	if (err) {
+		usX2Y_subs_startup_finish(usX2Y);	// Call it now
+		usX2Y_clients_stop(usX2Y);		// something is completely wroong > stop evrything			
+	}
+	return err;
+}
+
+/*
+ * prepare callback
+ *
+ * set format and initialize urbs
+ */
+static int snd_usX2Y_usbpcm_prepare(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct snd_usX2Y_substream *subs = runtime->private_data;
+	struct usX2Ydev *usX2Y = subs->usX2Y;
+	struct snd_usX2Y_substream *capsubs = subs->usX2Y->subs[SNDRV_PCM_STREAM_CAPTURE];
+	int err = 0;
+	snd_printdd("snd_usX2Y_pcm_prepare(%p)\n", substream);
+
+	if (NULL == usX2Y->hwdep_pcm_shm) {
+		if (NULL == (usX2Y->hwdep_pcm_shm = snd_malloc_pages(sizeof(struct snd_usX2Y_hwdep_pcm_shm), GFP_KERNEL)))
+			return -ENOMEM;
+		memset(usX2Y->hwdep_pcm_shm, 0, sizeof(struct snd_usX2Y_hwdep_pcm_shm));
+	}
+
+	mutex_lock(&usX2Y->pcm_mutex);
+	usX2Y_subs_prepare(subs);
+// Start hardware streams
+// SyncStream first....
+	if (atomic_read(&capsubs->state) < state_PREPARED) {
+		if (usX2Y->format != runtime->format)
+			if ((err = usX2Y_format_set(usX2Y, runtime->format)) < 0)
+				goto up_prepare_mutex;
+		if (usX2Y->rate != runtime->rate)
+			if ((err = usX2Y_rate_set(usX2Y, runtime->rate)) < 0)
+				goto up_prepare_mutex;
+		snd_printdd("starting capture pipe for %s\n", subs == capsubs ?
+			    "self" : "playpipe");
+		if (0 > (err = usX2Y_usbpcm_urbs_start(capsubs)))
+			goto up_prepare_mutex;
+	}
+
+	if (subs != capsubs) {
+		usX2Y->hwdep_pcm_shm->playback_iso_start = -1;
+		if (atomic_read(&subs->state) < state_PREPARED) {
+			while (usX2Y_iso_frames_per_buffer(runtime, usX2Y) >
+			       usX2Y->hwdep_pcm_shm->captured_iso_frames) {
+				snd_printdd("Wait: iso_frames_per_buffer=%i,"
+					    "captured_iso_frames=%i\n",
+					    usX2Y_iso_frames_per_buffer(runtime, usX2Y),
+					    usX2Y->hwdep_pcm_shm->captured_iso_frames);
+				if (msleep_interruptible(10)) {
+					err = -ERESTARTSYS;
+					goto up_prepare_mutex;
+				}
+			} 
+			if (0 > (err = usX2Y_usbpcm_urbs_start(subs)))
+				goto up_prepare_mutex;
+		}
+		snd_printdd("Ready: iso_frames_per_buffer=%i,captured_iso_frames=%i\n",
+			    usX2Y_iso_frames_per_buffer(runtime, usX2Y),
+			    usX2Y->hwdep_pcm_shm->captured_iso_frames);
+	} else
+		usX2Y->hwdep_pcm_shm->capture_iso_start = -1;
+
+ up_prepare_mutex:
+	mutex_unlock(&usX2Y->pcm_mutex);
+	return err;
+}
+
+static struct snd_pcm_hardware snd_usX2Y_4c =
+{
+	.info =			(SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
+				 SNDRV_PCM_INFO_BLOCK_TRANSFER |
+				 SNDRV_PCM_INFO_MMAP_VALID),
+	.formats =                 SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_3LE,
+	.rates =                   SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000,
+	.rate_min =                44100,
+	.rate_max =                48000,
+	.channels_min =            2,
+	.channels_max =            4,
+	.buffer_bytes_max =	(2*128*1024),
+	.period_bytes_min =	64,
+	.period_bytes_max =	(128*1024),
+	.periods_min =		2,
+	.periods_max =		1024,
+	.fifo_size =              0
+};
+
+
+
+static int snd_usX2Y_usbpcm_open(struct snd_pcm_substream *substream)
+{
+	struct snd_usX2Y_substream	*subs = ((struct snd_usX2Y_substream **)
+					 snd_pcm_substream_chip(substream))[substream->stream];
+	struct snd_pcm_runtime	*runtime = substream->runtime;
+
+	if (!(subs->usX2Y->chip_status & USX2Y_STAT_CHIP_MMAP_PCM_URBS))
+		return -EBUSY;
+
+	runtime->hw = SNDRV_PCM_STREAM_PLAYBACK == substream->stream ? snd_usX2Y_2c :
+		(subs->usX2Y->subs[3] ? snd_usX2Y_4c : snd_usX2Y_2c);
+	runtime->private_data = subs;
+	subs->pcm_substream = substream;
+	snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_PERIOD_TIME, 1000, 200000);
+	return 0;
+}
+
+
+static int snd_usX2Y_usbpcm_close(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct snd_usX2Y_substream *subs = runtime->private_data;
+
+	subs->pcm_substream = NULL;
+	return 0;
+}
+
+
+static const struct snd_pcm_ops snd_usX2Y_usbpcm_ops =
+{
+	.open =		snd_usX2Y_usbpcm_open,
+	.close =	snd_usX2Y_usbpcm_close,
+	.ioctl =	snd_pcm_lib_ioctl,
+	.hw_params =	snd_usX2Y_pcm_hw_params,
+	.hw_free =	snd_usX2Y_usbpcm_hw_free,
+	.prepare =	snd_usX2Y_usbpcm_prepare,
+	.trigger =	snd_usX2Y_pcm_trigger,
+	.pointer =	snd_usX2Y_pcm_pointer,
+};
+
+
+static int usX2Y_pcms_busy_check(struct snd_card *card)
+{
+	struct usX2Ydev	*dev = usX2Y(card);
+	int i;
+
+	for (i = 0; i < dev->pcm_devs * 2; i++) {
+		struct snd_usX2Y_substream *subs = dev->subs[i];
+		if (subs && subs->pcm_substream &&
+		    SUBSTREAM_BUSY(subs->pcm_substream))
+			return -EBUSY;
+	}
+	return 0;
+}
+
+static int snd_usX2Y_hwdep_pcm_open(struct snd_hwdep *hw, struct file *file)
+{
+	struct snd_card *card = hw->card;
+	int err;
+
+	mutex_lock(&usX2Y(card)->pcm_mutex);
+	err = usX2Y_pcms_busy_check(card);
+	if (!err)
+		usX2Y(card)->chip_status |= USX2Y_STAT_CHIP_MMAP_PCM_URBS;
+	mutex_unlock(&usX2Y(card)->pcm_mutex);
+	return err;
+}
+
+
+static int snd_usX2Y_hwdep_pcm_release(struct snd_hwdep *hw, struct file *file)
+{
+	struct snd_card *card = hw->card;
+	int err;
+
+	mutex_lock(&usX2Y(card)->pcm_mutex);
+	err = usX2Y_pcms_busy_check(card);
+	if (!err)
+		usX2Y(hw->card)->chip_status &= ~USX2Y_STAT_CHIP_MMAP_PCM_URBS;
+	mutex_unlock(&usX2Y(card)->pcm_mutex);
+	return err;
+}
+
+
+static void snd_usX2Y_hwdep_pcm_vm_open(struct vm_area_struct *area)
+{
+}
+
+
+static void snd_usX2Y_hwdep_pcm_vm_close(struct vm_area_struct *area)
+{
+}
+
+
+static vm_fault_t snd_usX2Y_hwdep_pcm_vm_fault(struct vm_fault *vmf)
+{
+	unsigned long offset;
+	void *vaddr;
+
+	offset = vmf->pgoff << PAGE_SHIFT;
+	vaddr = (char *)((struct usX2Ydev *)vmf->vma->vm_private_data)->hwdep_pcm_shm + offset;
+	vmf->page = virt_to_page(vaddr);
+	get_page(vmf->page);
+	return 0;
+}
+
+
+static const struct vm_operations_struct snd_usX2Y_hwdep_pcm_vm_ops = {
+	.open = snd_usX2Y_hwdep_pcm_vm_open,
+	.close = snd_usX2Y_hwdep_pcm_vm_close,
+	.fault = snd_usX2Y_hwdep_pcm_vm_fault,
+};
+
+
+static int snd_usX2Y_hwdep_pcm_mmap(struct snd_hwdep * hw, struct file *filp, struct vm_area_struct *area)
+{
+	unsigned long	size = (unsigned long)(area->vm_end - area->vm_start);
+	struct usX2Ydev	*usX2Y = hw->private_data;
+
+	if (!(usX2Y->chip_status & USX2Y_STAT_CHIP_INIT))
+		return -EBUSY;
+
+	/* if userspace tries to mmap beyond end of our buffer, fail */ 
+	if (size > PAGE_ALIGN(sizeof(struct snd_usX2Y_hwdep_pcm_shm))) {
+		snd_printd("%lu > %lu\n", size, (unsigned long)sizeof(struct snd_usX2Y_hwdep_pcm_shm)); 
+		return -EINVAL;
+	}
+
+	if (!usX2Y->hwdep_pcm_shm) {
+		return -ENODEV;
+	}
+	area->vm_ops = &snd_usX2Y_hwdep_pcm_vm_ops;
+	area->vm_flags |= VM_DONTEXPAND | VM_DONTDUMP;
+	area->vm_private_data = hw->private_data;
+	return 0;
+}
+
+
+static void snd_usX2Y_hwdep_pcm_private_free(struct snd_hwdep *hwdep)
+{
+	struct usX2Ydev *usX2Y = hwdep->private_data;
+	if (NULL != usX2Y->hwdep_pcm_shm)
+		snd_free_pages(usX2Y->hwdep_pcm_shm, sizeof(struct snd_usX2Y_hwdep_pcm_shm));
+}
+
+
+int usX2Y_hwdep_pcm_new(struct snd_card *card)
+{
+	int err;
+	struct snd_hwdep *hw;
+	struct snd_pcm *pcm;
+	struct usb_device *dev = usX2Y(card)->dev;
+	if (1 != nr_of_packs())
+		return 0;
+
+	if ((err = snd_hwdep_new(card, SND_USX2Y_USBPCM_ID, 1, &hw)) < 0)
+		return err;
+
+	hw->iface = SNDRV_HWDEP_IFACE_USX2Y_PCM;
+	hw->private_data = usX2Y(card);
+	hw->private_free = snd_usX2Y_hwdep_pcm_private_free;
+	hw->ops.open = snd_usX2Y_hwdep_pcm_open;
+	hw->ops.release = snd_usX2Y_hwdep_pcm_release;
+	hw->ops.mmap = snd_usX2Y_hwdep_pcm_mmap;
+	hw->exclusive = 1;
+	sprintf(hw->name, "/dev/bus/usb/%03d/%03d/hwdeppcm", dev->bus->busnum, dev->devnum);
+
+	err = snd_pcm_new(card, NAME_ALLCAPS" hwdep Audio", 2, 1, 1, &pcm);
+	if (err < 0) {
+		return err;
+	}
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_usX2Y_usbpcm_ops);
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_usX2Y_usbpcm_ops);
+
+	pcm->private_data = usX2Y(card)->subs;
+	pcm->info_flags = 0;
+
+	sprintf(pcm->name, NAME_ALLCAPS" hwdep Audio");
+	if (0 > (err = snd_pcm_lib_preallocate_pages(pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream,
+						     SNDRV_DMA_TYPE_CONTINUOUS,
+						     snd_dma_continuous_data(GFP_KERNEL),
+						     64*1024, 128*1024)) ||
+	    0 > (err = snd_pcm_lib_preallocate_pages(pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream,
+	    					     SNDRV_DMA_TYPE_CONTINUOUS,
+	    					     snd_dma_continuous_data(GFP_KERNEL),
+						     64*1024, 128*1024))) {
+		return err;
+	}
+
+
+	return 0;
+}
+
+#else
+
+int usX2Y_hwdep_pcm_new(struct snd_card *card)
+{
+	return 0;
+}
+
+#endif
diff --git a/sound/usb/usx2y/usx2yhwdeppcm.h b/sound/usb/usx2y/usx2yhwdeppcm.h
new file mode 100644
index 0000000..eb5a464
--- /dev/null
+++ b/sound/usb/usx2y/usx2yhwdeppcm.h
@@ -0,0 +1,23 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#define MAXPACK 50
+#define MAXBUFFERMS 100
+#define MAXSTRIDE 3
+
+#define SSS (((MAXPACK*MAXBUFFERMS*MAXSTRIDE + 4096) / 4096) * 4096)
+struct snd_usX2Y_hwdep_pcm_shm {
+	char playback[SSS];
+	char capture0x8[SSS];
+	char capture0xA[SSS];
+	volatile int playback_iso_head;
+	int playback_iso_start;
+	struct {
+		int	frame,
+			offset,
+			length;
+	} captured_iso[128];
+	volatile int captured_iso_head;
+	volatile unsigned captured_iso_frames;
+	int capture_iso_start;
+};
+
+int usX2Y_hwdep_pcm_new(struct snd_card *card);