Linux-2.6.12-rc2

Initial git repository build. I'm not bothering with the full history,
even though we have it. We can create a separate "historical" git
archive of that later if we want to, and in the meantime it's about
3.2GB when imported into git - space that would just make the early
git days unnecessarily complicated, when we don't have a lot of good
infrastructure for it.

Let it rip!
diff --git a/sound/usb/Kconfig b/sound/usb/Kconfig
new file mode 100644
index 0000000..9329e99
--- /dev/null
+++ b/sound/usb/Kconfig
@@ -0,0 +1,32 @@
+# ALSA USB drivers
+
+menu "USB devices"
+	depends on SND!=n && USB!=n
+
+config SND_USB_AUDIO
+	tristate "USB Audio/MIDI driver"
+	depends on SND && USB
+	select SND_RAWMIDI
+	select SND_PCM
+	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_USX2Y
+	tristate "Tascam US-122, US-224 and US-428 USB driver"
+	depends on SND && USB && (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.
+
+endmenu
+
diff --git a/sound/usb/Makefile b/sound/usb/Makefile
new file mode 100644
index 0000000..2c1dc11
--- /dev/null
+++ b/sound/usb/Makefile
@@ -0,0 +1,12 @@
+#
+# Makefile for ALSA
+#
+
+snd-usb-audio-objs := usbaudio.o usbmixer.o
+snd-usb-lib-objs := usbmidi.o
+
+# Toplevel Module Dependency
+obj-$(CONFIG_SND_USB_AUDIO) += snd-usb-audio.o snd-usb-lib.o
+obj-$(CONFIG_SND_USB_USX2Y) += snd-usb-lib.o
+
+obj-$(CONFIG_SND) += usx2y/
diff --git a/sound/usb/usbaudio.c b/sound/usb/usbaudio.c
new file mode 100644
index 0000000..84b0bbd
--- /dev/null
+++ b/sound/usb/usbaudio.c
@@ -0,0 +1,3337 @@
+/*
+ *   (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
+ *
+ *
+ *  NOTES:
+ *
+ *   - async unlink should be used for avoiding the sleep inside lock.
+ *     2.4.22 usb-uhci seems buggy for async unlinking and results in
+ *     oops.  in such a cse, pass async_unlink=0 option.
+ *   - 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 <sound/driver.h>
+#include <linux/bitops.h>
+#include <linux/init.h>
+#include <linux/list.h>
+#include <linux/slab.h>
+#include <linux/string.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 <sound/initval.h>
+
+#include "usbaudio.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 int enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP;	/* Enable this card */
+static int vid[SNDRV_CARDS] = { [0 ... (SNDRV_CARDS-1)] = -1 }; /* Vendor ID for this card */
+static int pid[SNDRV_CARDS] = { [0 ... (SNDRV_CARDS-1)] = -1 }; /* Product ID for this card */
+static int nrpacks = 4;		/* max. number of packets per urb */
+static int async_unlink = 1;
+
+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(nrpacks, int, 0444);
+MODULE_PARM_DESC(nrpacks, "Max. number of packets per URB.");
+module_param(async_unlink, bool, 0444);
+MODULE_PARM_DESC(async_unlink, "Use async unlink mode.");
+
+
+/*
+ * debug the h/w constraints
+ */
+/* #define HW_CONST_DEBUG */
+
+
+/*
+ *
+ */
+
+#define MAX_PACKS	10
+#define MAX_PACKS_HS	(MAX_PACKS * 8)	/* in high speed mode */
+#define MAX_URBS	5	/* max. 20ms long packets */
+#define SYNC_URBS	2	/* always two urbs for sync */
+#define MIN_PACKS_URB	1	/* minimum 1 packet per urb */
+
+typedef struct snd_usb_substream snd_usb_substream_t;
+typedef struct snd_usb_stream snd_usb_stream_t;
+typedef struct snd_urb_ctx snd_urb_ctx_t;
+
+struct audioformat {
+	struct list_head list;
+	snd_pcm_format_t format;	/* format type */
+	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 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 */
+};
+
+struct snd_urb_ctx {
+	struct urb *urb;
+	snd_usb_substream_t *subs;
+	int index;	/* index for urb array */
+	int packets;	/* number of packets per urb */
+	int transfer;	/* transferred size */
+	char *buf;	/* buffer for capture */
+};
+
+struct snd_urb_ops {
+	int (*prepare)(snd_usb_substream_t *subs, snd_pcm_runtime_t *runtime, struct urb *u);
+	int (*retire)(snd_usb_substream_t *subs, snd_pcm_runtime_t *runtime, struct urb *u);
+	int (*prepare_sync)(snd_usb_substream_t *subs, snd_pcm_runtime_t *runtime, struct urb *u);
+	int (*retire_sync)(snd_usb_substream_t *subs, snd_pcm_runtime_t *runtime, struct urb *u);
+};
+
+struct snd_usb_substream {
+	snd_usb_stream_t *stream;
+	struct usb_device *dev;
+	snd_pcm_substream_t *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) */
+	unsigned int cur_rate;		/* current rate (for hw_params callback) */
+	unsigned int period_bytes;	/* current period bytes (for hw_params callback) */
+	unsigned int format;     /* USB data format */
+	unsigned int datapipe;   /* the data i/o pipe */
+	unsigned int syncpipe;   /* 1 - async out or adaptive in */
+	unsigned int syncinterval;  /* P for adaptive mode, 0 otherwise */
+	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 */
+	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 curpacksize;	/* current packet size in bytes (for capture) */
+	unsigned int curframesize;	/* current packet size in frames (for capture) */
+	unsigned int fill_max: 1;	/* fill max packet size always */
+	unsigned int fmt_type;		/* USB audio format type (1-3) */
+
+	unsigned int running: 1;	/* running status */
+
+	unsigned int hwptr;			/* free frame position in the buffer (only for playback) */
+	unsigned int hwptr_done;			/* processed frame position in the buffer */
+	unsigned int transfer_sched;		/* scheduled frames since last period (for playback) */
+	unsigned int transfer_done;		/* processed frames since last period update */
+	unsigned long active_mask;	/* bitmask of active urbs */
+	unsigned long unlink_mask;	/* bitmask of unlinked urbs */
+
+	unsigned int nurbs;			/* # urbs */
+	snd_urb_ctx_t dataurb[MAX_URBS];	/* data urb table */
+	snd_urb_ctx_t syncurb[SYNC_URBS];	/* sync urb table */
+	char syncbuf[SYNC_URBS * MAX_PACKS * 4]; /* sync buffer; it's so small - let's get static */
+	char *tmpbuf;			/* temporary buffer for playback */
+
+	u64 formats;			/* format bitmasks (all or'ed) */
+	unsigned int num_formats;		/* number of supported audio formats (list) */
+	struct list_head fmt_list;	/* format list */
+	spinlock_t lock;
+
+	struct snd_urb_ops ops;		/* callbacks (must be filled at init) */
+};
+
+
+struct snd_usb_stream {
+	snd_usb_audio_t *chip;
+	snd_pcm_t *pcm;
+	int pcm_index;
+	unsigned int fmt_type;		/* USB audio format type (1-3) */
+	snd_usb_substream_t substream[2];
+	struct list_head list;
+};
+
+
+/*
+ * we keep the snd_usb_audio_t instances by ourselves for merging
+ * the all interfaces on the same card as one sound device.
+ */
+
+static DECLARE_MUTEX(register_mutex);
+static snd_usb_audio_t *usb_chip[SNDRV_CARDS];
+
+
+/*
+ * convert a sampling rate into our full speed format (fs/1000 in Q16.16)
+ * this will overflow at approx 524 kHz
+ */
+inline static 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
+ */
+inline static unsigned get_usb_high_speed_rate(unsigned int rate)
+{
+	return ((rate << 10) + 62) / 125;
+}
+
+/* convert our full speed USB rate into sampling rate in Hz */
+inline static 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 */
+inline static unsigned get_high_speed_hz(unsigned int usb_rate)
+{
+	return (usb_rate * 125 + (1 << 9)) >> 10;
+}
+
+
+/*
+ * prepare urb for full speed capture sync pipe
+ *
+ * fill the length and offset of each urb descriptor.
+ * the fixed 10.14 frequency is passed through the pipe.
+ */
+static int prepare_capture_sync_urb(snd_usb_substream_t *subs,
+				    snd_pcm_runtime_t *runtime,
+				    struct urb *urb)
+{
+	unsigned char *cp = urb->transfer_buffer;
+	snd_urb_ctx_t *ctx = (snd_urb_ctx_t *)urb->context;
+	int i, offs;
+
+	urb->number_of_packets = ctx->packets;
+	urb->dev = ctx->subs->dev; /* we need to set this at each time */
+	for (i = offs = 0; i < urb->number_of_packets; i++, offs += 4, cp += 4) {
+		urb->iso_frame_desc[i].length = 3;
+		urb->iso_frame_desc[i].offset = offs;
+		cp[0] = subs->freqn >> 2;
+		cp[1] = subs->freqn >> 10;
+		cp[2] = subs->freqn >> 18;
+	}
+	return 0;
+}
+
+/*
+ * prepare urb for high speed capture sync pipe
+ *
+ * fill the length and offset of each urb descriptor.
+ * the fixed 12.13 frequency is passed as 16.16 through the pipe.
+ */
+static int prepare_capture_sync_urb_hs(snd_usb_substream_t *subs,
+				       snd_pcm_runtime_t *runtime,
+				       struct urb *urb)
+{
+	unsigned char *cp = urb->transfer_buffer;
+	snd_urb_ctx_t *ctx = (snd_urb_ctx_t *)urb->context;
+	int i, offs;
+
+	urb->number_of_packets = ctx->packets;
+	urb->dev = ctx->subs->dev; /* we need to set this at each time */
+	for (i = offs = 0; i < urb->number_of_packets; i++, offs += 4, cp += 4) {
+		urb->iso_frame_desc[i].length = 4;
+		urb->iso_frame_desc[i].offset = offs;
+		cp[0] = subs->freqn;
+		cp[1] = subs->freqn >> 8;
+		cp[2] = subs->freqn >> 16;
+		cp[3] = subs->freqn >> 24;
+	}
+	return 0;
+}
+
+/*
+ * process after capture sync complete
+ * - nothing to do
+ */
+static int retire_capture_sync_urb(snd_usb_substream_t *subs,
+				   snd_pcm_runtime_t *runtime,
+				   struct urb *urb)
+{
+	return 0;
+}
+
+/*
+ * prepare urb for capture data pipe
+ *
+ * fill the offset and length of each descriptor.
+ *
+ * we use a temporary buffer to write the captured data.
+ * since the length of written data is determined by host, we cannot
+ * write onto the pcm buffer directly...  the data is thus copied
+ * later at complete callback to the global buffer.
+ */
+static int prepare_capture_urb(snd_usb_substream_t *subs,
+			       snd_pcm_runtime_t *runtime,
+			       struct urb *urb)
+{
+	int i, offs;
+	unsigned long flags;
+	snd_urb_ctx_t *ctx = (snd_urb_ctx_t *)urb->context;
+
+	offs = 0;
+	urb->dev = ctx->subs->dev; /* we need to set this at each time */
+	urb->number_of_packets = 0;
+	spin_lock_irqsave(&subs->lock, flags);
+	for (i = 0; i < ctx->packets; i++) {
+		urb->iso_frame_desc[i].offset = offs;
+		urb->iso_frame_desc[i].length = subs->curpacksize;
+		offs += subs->curpacksize;
+		urb->number_of_packets++;
+		subs->transfer_sched += subs->curframesize;
+		if (subs->transfer_sched >= runtime->period_size) {
+			subs->transfer_sched -= runtime->period_size;
+			break;
+		}
+	}
+	spin_unlock_irqrestore(&subs->lock, flags);
+	urb->transfer_buffer = ctx->buf;
+	urb->transfer_buffer_length = offs;
+#if 0 // for check
+	if (! urb->bandwidth) {
+		int bustime;
+		bustime = usb_check_bandwidth(urb->dev, urb);
+		if (bustime < 0)
+			return bustime;
+		printk("urb %d: bandwidth = %d (packets = %d)\n", ctx->index, bustime, urb->number_of_packets);
+		usb_claim_bandwidth(urb->dev, urb, bustime, 1);
+	}
+#endif // for check
+	return 0;
+}
+
+/*
+ * process after capture complete
+ *
+ * copy the data from each desctiptor to the pcm buffer, and
+ * update the current position.
+ */
+static int retire_capture_urb(snd_usb_substream_t *subs,
+			      snd_pcm_runtime_t *runtime,
+			      struct urb *urb)
+{
+	unsigned long flags;
+	unsigned char *cp;
+	int i;
+	unsigned int stride, len, oldptr;
+
+	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;
+		if (urb->iso_frame_desc[i].status) {
+			snd_printd(KERN_ERR "frame %d active: %d\n", i, urb->iso_frame_desc[i].status);
+			// continue;
+		}
+		len = urb->iso_frame_desc[i].actual_length / stride;
+		if (! len)
+			continue;
+		/* update the current pointer */
+		spin_lock_irqsave(&subs->lock, flags);
+		oldptr = subs->hwptr_done;
+		subs->hwptr_done += len;
+		if (subs->hwptr_done >= runtime->buffer_size)
+			subs->hwptr_done -= runtime->buffer_size;
+		subs->transfer_done += len;
+		spin_unlock_irqrestore(&subs->lock, flags);
+		/* copy a data chunk */
+		if (oldptr + len > runtime->buffer_size) {
+			unsigned int cnt = runtime->buffer_size - oldptr;
+			unsigned int blen = cnt * stride;
+			memcpy(runtime->dma_area + oldptr * stride, cp, blen);
+			memcpy(runtime->dma_area, cp + blen, len * stride - blen);
+		} else {
+			memcpy(runtime->dma_area + oldptr * stride, cp, len * stride);
+		}
+		/* update the pointer, call callback if necessary */
+		spin_lock_irqsave(&subs->lock, flags);
+		if (subs->transfer_done >= runtime->period_size) {
+			subs->transfer_done -= runtime->period_size;
+			spin_unlock_irqrestore(&subs->lock, flags);
+			snd_pcm_period_elapsed(subs->pcm_substream);
+		} else
+			spin_unlock_irqrestore(&subs->lock, flags);
+	}
+	return 0;
+}
+
+
+/*
+ * prepare urb for full speed playback sync pipe
+ *
+ * set up the offset and length to receive the current frequency.
+ */
+
+static int prepare_playback_sync_urb(snd_usb_substream_t *subs,
+				     snd_pcm_runtime_t *runtime,
+				     struct urb *urb)
+{
+	int i, offs;
+	snd_urb_ctx_t *ctx = (snd_urb_ctx_t *)urb->context;
+
+	urb->number_of_packets = ctx->packets;
+	urb->dev = ctx->subs->dev; /* we need to set this at each time */
+	for (i = offs = 0; i < urb->number_of_packets; i++, offs += 4) {
+		urb->iso_frame_desc[i].length = 3;
+		urb->iso_frame_desc[i].offset = offs;
+	}
+	return 0;
+}
+
+/*
+ * prepare urb for high speed playback sync pipe
+ *
+ * set up the offset and length to receive the current frequency.
+ */
+
+static int prepare_playback_sync_urb_hs(snd_usb_substream_t *subs,
+					snd_pcm_runtime_t *runtime,
+					struct urb *urb)
+{
+	int i, offs;
+	snd_urb_ctx_t *ctx = (snd_urb_ctx_t *)urb->context;
+
+	urb->number_of_packets = ctx->packets;
+	urb->dev = ctx->subs->dev; /* we need to set this at each time */
+	for (i = offs = 0; i < urb->number_of_packets; i++, offs += 4) {
+		urb->iso_frame_desc[i].length = 4;
+		urb->iso_frame_desc[i].offset = offs;
+	}
+	return 0;
+}
+
+/*
+ * process after full speed playback sync complete
+ *
+ * retrieve the current 10.14 frequency from pipe, and set it.
+ * the value is referred in prepare_playback_urb().
+ */
+static int retire_playback_sync_urb(snd_usb_substream_t *subs,
+				    snd_pcm_runtime_t *runtime,
+				    struct urb *urb)
+{
+	int i;
+	unsigned int f, found;
+	unsigned char *cp = urb->transfer_buffer;
+	unsigned long flags;
+
+	found = 0;
+	for (i = 0; i < urb->number_of_packets; i++, cp += 4) {
+		if (urb->iso_frame_desc[i].status ||
+		    urb->iso_frame_desc[i].actual_length < 3)
+			continue;
+		f = combine_triple(cp) << 2;
+#if 0
+		if (f < subs->freqn - (subs->freqn>>3) || f > subs->freqmax) {
+			snd_printd(KERN_WARNING "requested frequency %d (%u,%03uHz) out of range (current nominal %d (%u,%03uHz))\n",
+				   f, f >> 14, (f & ((1 << 14) - 1) * 1000) / ((1 << 14) - 1),
+				   subs->freqn, subs->freqn >> 14, (subs->freqn & ((1 << 14) - 1) * 1000) / ((1 << 14) - 1));
+			continue;
+		}
+#endif
+		found = f;
+	}
+	if (found) {
+		spin_lock_irqsave(&subs->lock, flags);
+		subs->freqm = found;
+		spin_unlock_irqrestore(&subs->lock, flags);
+	}
+
+	return 0;
+}
+
+/*
+ * process after high speed playback sync complete
+ *
+ * retrieve the current 12.13 frequency from pipe, and set it.
+ * the value is referred in prepare_playback_urb().
+ */
+static int retire_playback_sync_urb_hs(snd_usb_substream_t *subs,
+				       snd_pcm_runtime_t *runtime,
+				       struct urb *urb)
+{
+	int i;
+	unsigned int found;
+	unsigned char *cp = urb->transfer_buffer;
+	unsigned long flags;
+
+	found = 0;
+	for (i = 0; i < urb->number_of_packets; i++, cp += 4) {
+		if (urb->iso_frame_desc[i].status ||
+		    urb->iso_frame_desc[i].actual_length < 4)
+			continue;
+		found = combine_quad(cp) & 0x0fffffff;
+	}
+	if (found) {
+		spin_lock_irqsave(&subs->lock, flags);
+		subs->freqm = found;
+		spin_unlock_irqrestore(&subs->lock, flags);
+	}
+
+	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 prepare_playback_urb(snd_usb_substream_t *subs,
+				snd_pcm_runtime_t *runtime,
+				struct urb *urb)
+{
+	int i, stride, offs;
+	unsigned int counts;
+	unsigned long flags;
+	snd_urb_ctx_t *ctx = (snd_urb_ctx_t *)urb->context;
+
+	stride = runtime->frame_bits >> 3;
+
+	offs = 0;
+	urb->dev = ctx->subs->dev; /* we need to set this at each time */
+	urb->number_of_packets = 0;
+	spin_lock_irqsave(&subs->lock, flags);
+	for (i = 0; i < ctx->packets; i++) {
+		/* calculate the size of a packet */
+		if (subs->fill_max)
+			counts = subs->maxframesize; /* fixed */
+		else {
+			subs->phase = (subs->phase & 0xffff) + subs->freqm;
+			counts = subs->phase >> 16;
+			if (counts > subs->maxframesize)
+				counts = subs->maxframesize;
+		}
+		/* set up descriptor */
+		urb->iso_frame_desc[i].offset = offs * stride;
+		urb->iso_frame_desc[i].length = counts * stride;
+		offs += counts;
+		urb->number_of_packets++;
+		subs->transfer_sched += counts;
+		if (subs->transfer_sched >= runtime->period_size) {
+			subs->transfer_sched -= runtime->period_size;
+			if (subs->fmt_type == USB_FORMAT_TYPE_II) {
+				if (subs->transfer_sched > 0) {
+					/* FIXME: fill-max mode is not supported yet */
+					offs -= subs->transfer_sched;
+					counts -= subs->transfer_sched;
+					urb->iso_frame_desc[i].length = counts * stride;
+					subs->transfer_sched = 0;
+				}
+				i++;
+				if (i < ctx->packets) {
+					/* add a transfer delimiter */
+					urb->iso_frame_desc[i].offset = offs * stride;
+					urb->iso_frame_desc[i].length = 0;
+					urb->number_of_packets++;
+				}
+			}
+			break;
+ 		}
+	}
+	if (subs->hwptr + offs > 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 * stride, len * stride);
+		memcpy(subs->tmpbuf + len * stride, runtime->dma_area, (offs - len) * stride);
+		subs->hwptr += offs;
+		subs->hwptr -= runtime->buffer_size;
+	} else {
+		/* set the buffer pointer */
+		urb->transfer_buffer = runtime->dma_area + subs->hwptr * stride;
+		subs->hwptr += offs;
+	}
+	spin_unlock_irqrestore(&subs->lock, flags);
+	urb->transfer_buffer_length = offs * stride;
+	ctx->transfer = offs;
+
+	return 0;
+}
+
+/*
+ * process after playback data complete
+ *
+ * update the current position and call callback if a period is processed.
+ */
+static int retire_playback_urb(snd_usb_substream_t *subs,
+			       snd_pcm_runtime_t *runtime,
+			       struct urb *urb)
+{
+	unsigned long flags;
+	snd_urb_ctx_t *ctx = (snd_urb_ctx_t *)urb->context;
+
+	spin_lock_irqsave(&subs->lock, flags);
+	subs->transfer_done += ctx->transfer;
+	subs->hwptr_done += ctx->transfer;
+	ctx->transfer = 0;
+	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;
+		spin_unlock_irqrestore(&subs->lock, flags);
+		snd_pcm_period_elapsed(subs->pcm_substream);
+	} else
+		spin_unlock_irqrestore(&subs->lock, flags);
+	return 0;
+}
+
+
+/*
+ */
+static struct snd_urb_ops audio_urb_ops[2] = {
+	{
+		.prepare =	prepare_playback_urb,
+		.retire =	retire_playback_urb,
+		.prepare_sync =	prepare_playback_sync_urb,
+		.retire_sync =	retire_playback_sync_urb,
+	},
+	{
+		.prepare =	prepare_capture_urb,
+		.retire =	retire_capture_urb,
+		.prepare_sync =	prepare_capture_sync_urb,
+		.retire_sync =	retire_capture_sync_urb,
+	},
+};
+
+static struct snd_urb_ops audio_urb_ops_high_speed[2] = {
+	{
+		.prepare =	prepare_playback_urb,
+		.retire =	retire_playback_urb,
+		.prepare_sync =	prepare_playback_sync_urb_hs,
+		.retire_sync =	retire_playback_sync_urb_hs,
+	},
+	{
+		.prepare =	prepare_capture_urb,
+		.retire =	retire_capture_urb,
+		.prepare_sync =	prepare_capture_sync_urb_hs,
+		.retire_sync =	retire_capture_sync_urb,
+	},
+};
+
+/*
+ * complete callback from data urb
+ */
+static void snd_complete_urb(struct urb *urb, struct pt_regs *regs)
+{
+	snd_urb_ctx_t *ctx = (snd_urb_ctx_t *)urb->context;
+	snd_usb_substream_t *subs = ctx->subs;
+	snd_pcm_substream_t *substream = ctx->subs->pcm_substream;
+	int err = 0;
+
+	if ((subs->running && subs->ops.retire(subs, substream->runtime, urb)) ||
+	    ! subs->running || /* can be stopped during retire callback */
+	    (err = subs->ops.prepare(subs, substream->runtime, urb)) < 0 ||
+	    (err = usb_submit_urb(urb, GFP_ATOMIC)) < 0) {
+		clear_bit(ctx->index, &subs->active_mask);
+		if (err < 0) {
+			snd_printd(KERN_ERR "cannot submit urb (err = %d)\n", err);
+			snd_pcm_stop(substream, SNDRV_PCM_STATE_XRUN);
+		}
+	}
+}
+
+
+/*
+ * complete callback from sync urb
+ */
+static void snd_complete_sync_urb(struct urb *urb, struct pt_regs *regs)
+{
+	snd_urb_ctx_t *ctx = (snd_urb_ctx_t *)urb->context;
+	snd_usb_substream_t *subs = ctx->subs;
+	snd_pcm_substream_t *substream = ctx->subs->pcm_substream;
+	int err = 0;
+
+	if ((subs->running && subs->ops.retire_sync(subs, substream->runtime, urb)) ||
+	    ! subs->running || /* can be stopped during retire callback */
+	    (err = subs->ops.prepare_sync(subs, substream->runtime, urb)) < 0 ||
+	    (err = usb_submit_urb(urb, GFP_ATOMIC)) < 0) {
+		clear_bit(ctx->index + 16, &subs->active_mask);
+		if (err < 0) {
+			snd_printd(KERN_ERR "cannot submit sync urb (err = %d)\n", err);
+			snd_pcm_stop(substream, SNDRV_PCM_STATE_XRUN);
+		}
+	}
+}
+
+
+/*
+ * unlink active urbs.
+ */
+static int deactivate_urbs(snd_usb_substream_t *subs, int force, int can_sleep)
+{
+	unsigned int i;
+	int async;
+
+	subs->running = 0;
+
+	if (!force && subs->stream->chip->shutdown) /* to be sure... */
+		return -EBADFD;
+
+	async = !can_sleep && async_unlink;
+
+	if (! async && in_interrupt())
+		return 0;
+
+	for (i = 0; i < subs->nurbs; i++) {
+		if (test_bit(i, &subs->active_mask)) {
+			if (! test_and_set_bit(i, &subs->unlink_mask)) {
+				struct urb *u = subs->dataurb[i].urb;
+				if (async) {
+					u->transfer_flags |= URB_ASYNC_UNLINK;
+					usb_unlink_urb(u);
+				} else
+					usb_kill_urb(u);
+			}
+		}
+	}
+	if (subs->syncpipe) {
+		for (i = 0; i < SYNC_URBS; i++) {
+			if (test_bit(i+16, &subs->active_mask)) {
+ 				if (! test_and_set_bit(i+16, &subs->unlink_mask)) {
+					struct urb *u = subs->syncurb[i].urb;
+					if (async) {
+						u->transfer_flags |= URB_ASYNC_UNLINK;
+						usb_unlink_urb(u);
+					} else
+						usb_kill_urb(u);
+				}
+			}
+		}
+	}
+	return 0;
+}
+
+
+/*
+ * set up and start data/sync urbs
+ */
+static int start_urbs(snd_usb_substream_t *subs, snd_pcm_runtime_t *runtime)
+{
+	unsigned int i;
+	int err;
+
+	if (subs->stream->chip->shutdown)
+		return -EBADFD;
+
+	for (i = 0; i < subs->nurbs; i++) {
+		snd_assert(subs->dataurb[i].urb, return -EINVAL);
+		if (subs->ops.prepare(subs, runtime, subs->dataurb[i].urb) < 0) {
+			snd_printk(KERN_ERR "cannot prepare datapipe for urb %d\n", i);
+			goto __error;
+		}
+	}
+	if (subs->syncpipe) {
+		for (i = 0; i < SYNC_URBS; i++) {
+			snd_assert(subs->syncurb[i].urb, return -EINVAL);
+			if (subs->ops.prepare_sync(subs, runtime, subs->syncurb[i].urb) < 0) {
+				snd_printk(KERN_ERR "cannot prepare syncpipe for urb %d\n", i);
+				goto __error;
+			}
+		}
+	}
+
+	subs->active_mask = 0;
+	subs->unlink_mask = 0;
+	subs->running = 1;
+	for (i = 0; i < subs->nurbs; i++) {
+		if ((err = usb_submit_urb(subs->dataurb[i].urb, GFP_ATOMIC)) < 0) {
+			snd_printk(KERN_ERR "cannot submit datapipe for urb %d, err = %d\n", i, err);
+			goto __error;
+		}
+		set_bit(i, &subs->active_mask);
+	}
+	if (subs->syncpipe) {
+		for (i = 0; i < SYNC_URBS; i++) {
+			if ((err = usb_submit_urb(subs->syncurb[i].urb, GFP_ATOMIC)) < 0) {
+				snd_printk(KERN_ERR "cannot submit syncpipe for urb %d, err = %d\n", i, err);
+				goto __error;
+			}
+			set_bit(i + 16, &subs->active_mask);
+		}
+	}
+	return 0;
+
+ __error:
+	// snd_pcm_stop(subs->pcm_substream, SNDRV_PCM_STATE_XRUN);
+	deactivate_urbs(subs, 0, 0);
+	return -EPIPE;
+}
+
+
+/*
+ *  wait until all urbs are processed.
+ */
+static int wait_clear_urbs(snd_usb_substream_t *subs)
+{
+	int timeout = HZ;
+	unsigned int i;
+	int alive;
+
+	do {
+		alive = 0;
+		for (i = 0; i < subs->nurbs; i++) {
+			if (test_bit(i, &subs->active_mask))
+				alive++;
+		}
+		if (subs->syncpipe) {
+			for (i = 0; i < SYNC_URBS; i++) {
+				if (test_bit(i + 16, &subs->active_mask))
+					alive++;
+			}
+		}
+		if (! alive)
+			break;
+		set_current_state(TASK_UNINTERRUPTIBLE);
+		schedule_timeout(1);
+	} while (--timeout > 0);
+	if (alive)
+		snd_printk(KERN_ERR "timeout: still %d active urbs..\n", alive);
+	return 0;
+}
+
+
+/*
+ * return the current pcm pointer.  just return the hwptr_done value.
+ */
+static snd_pcm_uframes_t snd_usb_pcm_pointer(snd_pcm_substream_t *substream)
+{
+	snd_usb_substream_t *subs = (snd_usb_substream_t *)substream->runtime->private_data;
+	return subs->hwptr_done;
+}
+
+
+/*
+ * start/stop substream
+ */
+static int snd_usb_pcm_trigger(snd_pcm_substream_t *substream, int cmd)
+{
+	snd_usb_substream_t *subs = (snd_usb_substream_t *)substream->runtime->private_data;
+	int err;
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+		err = start_urbs(subs, substream->runtime);
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+		err = deactivate_urbs(subs, 0, 0);
+		break;
+	default:
+		err = -EINVAL;
+		break;
+	}
+	return err < 0 ? err : 0;
+}
+
+
+/*
+ * release a urb data
+ */
+static void release_urb_ctx(snd_urb_ctx_t *u)
+{
+	if (u->urb) {
+		usb_free_urb(u->urb);
+		u->urb = NULL;
+	}
+	if (u->buf) {
+		kfree(u->buf);
+		u->buf = NULL;
+	}
+}
+
+/*
+ * release a substream
+ */
+static void release_substream_urbs(snd_usb_substream_t *subs, int force)
+{
+	int i;
+
+	/* stop urbs (to be sure) */
+	deactivate_urbs(subs, force, 1);
+	wait_clear_urbs(subs);
+
+	for (i = 0; i < MAX_URBS; i++)
+		release_urb_ctx(&subs->dataurb[i]);
+	for (i = 0; i < SYNC_URBS; i++)
+		release_urb_ctx(&subs->syncurb[i]);
+	if (subs->tmpbuf) {
+		kfree(subs->tmpbuf);
+		subs->tmpbuf = NULL;
+	}
+	subs->nurbs = 0;
+}
+
+/*
+ * initialize a substream for plaback/capture
+ */
+static int init_substream_urbs(snd_usb_substream_t *subs, unsigned int period_bytes,
+			       unsigned int rate, unsigned int frame_bits)
+{
+	unsigned int maxsize, n, i;
+	int is_playback = subs->direction == SNDRV_PCM_STREAM_PLAYBACK;
+	unsigned int npacks[MAX_URBS], urb_packs, total_packs;
+
+	/* calculate the frequency in 16.16 format */
+	if (snd_usb_get_speed(subs->dev) == USB_SPEED_FULL)
+		subs->freqn = get_usb_full_speed_rate(rate);
+	else
+		subs->freqn = get_usb_high_speed_rate(rate);
+	subs->freqm = subs->freqn;
+	subs->freqmax = subs->freqn + (subs->freqn >> 2); /* max. allowed frequency */
+	subs->phase = 0;
+
+	/* calculate the max. size of packet */
+	maxsize = ((subs->freqmax + 0xffff) * (frame_bits >> 3)) >> 16;
+	if (subs->maxpacksize && maxsize > subs->maxpacksize) {
+		//snd_printd(KERN_DEBUG "maxsize %d is greater than defined size %d\n",
+		//	   maxsize, subs->maxpacksize);
+		maxsize = subs->maxpacksize;
+	}
+
+	if (subs->fill_max)
+		subs->curpacksize = subs->maxpacksize;
+	else
+		subs->curpacksize = maxsize;
+
+	if (snd_usb_get_speed(subs->dev) == USB_SPEED_FULL)
+		urb_packs = nrpacks;
+	else
+		urb_packs = nrpacks * 8;
+
+	/* allocate a temporary buffer for playback */
+	if (is_playback) {
+		subs->tmpbuf = kmalloc(maxsize * urb_packs, GFP_KERNEL);
+		if (! subs->tmpbuf) {
+			snd_printk(KERN_ERR "cannot malloc tmpbuf\n");
+			return -ENOMEM;
+		}
+	}
+
+	/* decide how many packets to be used */
+	total_packs = (period_bytes + maxsize - 1) / maxsize;
+	if (total_packs < 2 * MIN_PACKS_URB)
+		total_packs = 2 * MIN_PACKS_URB;
+	subs->nurbs = (total_packs + urb_packs - 1) / urb_packs;
+	if (subs->nurbs > MAX_URBS) {
+		/* too much... */
+		subs->nurbs = MAX_URBS;
+		total_packs = MAX_URBS * urb_packs;
+	}
+	n = total_packs;
+	for (i = 0; i < subs->nurbs; i++) {
+		npacks[i] = n > urb_packs ? urb_packs : n;
+		n -= urb_packs;
+	}
+	if (subs->nurbs <= 1) {
+		/* too little - we need at least two packets
+		 * to ensure contiguous playback/capture
+		 */
+		subs->nurbs = 2;
+		npacks[0] = (total_packs + 1) / 2;
+		npacks[1] = total_packs - npacks[0];
+	} else if (npacks[subs->nurbs-1] < MIN_PACKS_URB) {
+		/* the last packet is too small.. */
+		if (subs->nurbs > 2) {
+			/* merge to the first one */
+			npacks[0] += npacks[subs->nurbs - 1];
+			subs->nurbs--;
+		} else {
+			/* divide to two */
+			subs->nurbs = 2;
+			npacks[0] = (total_packs + 1) / 2;
+			npacks[1] = total_packs - npacks[0];
+		}
+	}
+
+	/* allocate and initialize data urbs */
+	for (i = 0; i < subs->nurbs; i++) {
+		snd_urb_ctx_t *u = &subs->dataurb[i];
+		u->index = i;
+		u->subs = subs;
+		u->transfer = 0;
+		u->packets = npacks[i];
+		if (subs->fmt_type == USB_FORMAT_TYPE_II)
+			u->packets++; /* for transfer delimiter */
+		if (! is_playback) {
+			/* allocate a capture buffer per urb */
+			u->buf = kmalloc(maxsize * u->packets, GFP_KERNEL);
+			if (! u->buf) {
+				release_substream_urbs(subs, 0);
+				return -ENOMEM;
+			}
+		}
+		u->urb = usb_alloc_urb(u->packets, GFP_KERNEL);
+		if (! u->urb) {
+			release_substream_urbs(subs, 0);
+			return -ENOMEM;
+		}
+		u->urb->dev = subs->dev;
+		u->urb->pipe = subs->datapipe;
+		u->urb->transfer_flags = URB_ISO_ASAP;
+		u->urb->number_of_packets = u->packets;
+		u->urb->interval = 1;
+		u->urb->context = u;
+		u->urb->complete = snd_usb_complete_callback(snd_complete_urb);
+	}
+
+	if (subs->syncpipe) {
+		/* allocate and initialize sync urbs */
+		for (i = 0; i < SYNC_URBS; i++) {
+			snd_urb_ctx_t *u = &subs->syncurb[i];
+			u->index = i;
+			u->subs = subs;
+			u->packets = nrpacks;
+			u->urb = usb_alloc_urb(u->packets, GFP_KERNEL);
+			if (! u->urb) {
+				release_substream_urbs(subs, 0);
+				return -ENOMEM;
+			}
+			u->urb->transfer_buffer = subs->syncbuf + i * nrpacks * 4;
+			u->urb->transfer_buffer_length = nrpacks * 4;
+			u->urb->dev = subs->dev;
+			u->urb->pipe = subs->syncpipe;
+			u->urb->transfer_flags = URB_ISO_ASAP;
+			u->urb->number_of_packets = u->packets;
+			if (snd_usb_get_speed(subs->dev) == USB_SPEED_HIGH)
+				u->urb->interval = 8;
+			else
+				u->urb->interval = 1;
+			u->urb->context = u;
+			u->urb->complete = snd_usb_complete_callback(snd_complete_sync_urb);
+		}
+	}
+	return 0;
+}
+
+
+/*
+ * find a matching audio format
+ */
+static struct audioformat *find_format(snd_usb_substream_t *subs, unsigned int format,
+				       unsigned int rate, unsigned int channels)
+{
+	struct list_head *p;
+	struct audioformat *found = NULL;
+	int cur_attr = 0, attr;
+
+	list_for_each(p, &subs->fmt_list) {
+		struct audioformat *fp;
+		fp = list_entry(p, struct audioformat, list);
+		if (fp->format != format || fp->channels != channels)
+			continue;
+		if (rate < fp->rate_min || 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] == rate)
+					break;
+			if (i >= fp->nr_rates)
+				continue;
+		}
+		attr = fp->ep_attr & EP_ATTR_MASK;
+		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 == EP_ATTR_ASYNC &&
+			     subs->direction == SNDRV_PCM_STREAM_PLAYBACK) ||
+			    (attr == EP_ATTR_ADAPTIVE &&
+			     subs->direction == SNDRV_PCM_STREAM_CAPTURE))
+				continue;
+			if ((cur_attr == EP_ATTR_ASYNC &&
+			     subs->direction == SNDRV_PCM_STREAM_PLAYBACK) ||
+			    (cur_attr == EP_ATTR_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;
+}
+
+
+/*
+ * initialize the picth control and sample rate
+ */
+static int init_usb_pitch(struct usb_device *dev, int iface,
+			  struct usb_host_interface *alts,
+			  struct audioformat *fmt)
+{
+	unsigned int ep;
+	unsigned char data[1];
+	int err;
+
+	ep = get_endpoint(alts, 0)->bEndpointAddress;
+	/* if endpoint has pitch control, enable it */
+	if (fmt->attributes & EP_CS_ATTR_PITCH_CONTROL) {
+		data[0] = 1;
+		if ((err = snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), SET_CUR,
+					   USB_TYPE_CLASS|USB_RECIP_ENDPOINT|USB_DIR_OUT,
+					   PITCH_CONTROL << 8, ep, data, 1, 1000)) < 0) {
+			snd_printk(KERN_ERR "%d:%d:%d: cannot set enable PITCH\n",
+				   dev->devnum, iface, ep);
+			return err;
+		}
+	}
+	return 0;
+}
+
+static int init_usb_sample_rate(struct usb_device *dev, int iface,
+				struct usb_host_interface *alts,
+				struct audioformat *fmt, int rate)
+{
+	unsigned int ep;
+	unsigned char data[3];
+	int err;
+
+	ep = get_endpoint(alts, 0)->bEndpointAddress;
+	/* if endpoint has sampling rate control, set it */
+	if (fmt->attributes & EP_CS_ATTR_SAMPLE_RATE) {
+		int crate;
+		data[0] = rate;
+		data[1] = rate >> 8;
+		data[2] = rate >> 16;
+		if ((err = snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), SET_CUR,
+					   USB_TYPE_CLASS|USB_RECIP_ENDPOINT|USB_DIR_OUT,
+					   SAMPLING_FREQ_CONTROL << 8, ep, data, 3, 1000)) < 0) {
+			snd_printk(KERN_ERR "%d:%d:%d: cannot set freq %d to ep 0x%x\n",
+				   dev->devnum, iface, fmt->altsetting, rate, ep);
+			return err;
+		}
+		if ((err = snd_usb_ctl_msg(dev, usb_rcvctrlpipe(dev, 0), GET_CUR,
+					   USB_TYPE_CLASS|USB_RECIP_ENDPOINT|USB_DIR_IN,
+					   SAMPLING_FREQ_CONTROL << 8, ep, data, 3, 1000)) < 0) {
+			snd_printk(KERN_WARNING "%d:%d:%d: cannot get freq at ep 0x%x\n",
+				   dev->devnum, iface, fmt->altsetting, ep);
+			return 0; /* some devices don't support reading */
+		}
+		crate = data[0] | (data[1] << 8) | (data[2] << 16);
+		if (crate != rate) {
+			snd_printd(KERN_WARNING "current rate %d is different from the runtime rate %d\n", crate, rate);
+			// runtime->rate = crate;
+		}
+	}
+	return 0;
+}
+
+/*
+ * find a matching format and set up the interface
+ */
+static int set_format(snd_usb_substream_t *subs, struct audioformat *fmt)
+{
+	struct usb_device *dev = subs->dev;
+	struct usb_host_interface *alts;
+	struct usb_interface_descriptor *altsd;
+	struct usb_interface *iface;
+	unsigned int ep, attr;
+	int is_playback = subs->direction == SNDRV_PCM_STREAM_PLAYBACK;
+	int err;
+
+	iface = usb_ifnum_to_if(dev, fmt->iface);
+	snd_assert(iface, return -EINVAL);
+	alts = &iface->altsetting[fmt->altset_idx];
+	altsd = get_iface_desc(alts);
+	snd_assert(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) {
+		usb_set_interface(subs->dev, subs->interface, 0);
+		subs->interface = -1;
+		subs->format = 0;
+	}
+
+	/* set interface */
+	if (subs->interface != fmt->iface || subs->format != fmt->altset_idx) {
+		if (usb_set_interface(dev, fmt->iface, fmt->altsetting) < 0) {
+			snd_printk(KERN_ERR "%d:%d:%d: usb_set_interface failed\n",
+				   dev->devnum, fmt->iface, fmt->altsetting);
+			return -EIO;
+		}
+		snd_printdd(KERN_INFO "setting usb interface %d:%d\n", fmt->iface, fmt->altsetting);
+		subs->interface = fmt->iface;
+		subs->format = fmt->altset_idx;
+	}
+
+	/* create a data pipe */
+	ep = fmt->endpoint & USB_ENDPOINT_NUMBER_MASK;
+	if (is_playback)
+		subs->datapipe = usb_sndisocpipe(dev, ep);
+	else
+		subs->datapipe = usb_rcvisocpipe(dev, ep);
+	subs->syncpipe = subs->syncinterval = 0;
+	subs->maxpacksize = fmt->maxpacksize;
+	subs->fill_max = 0;
+
+	/* 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 & EP_ATTR_MASK;
+	if (((is_playback && attr == EP_ATTR_ASYNC) ||
+	     (! is_playback && attr == EP_ATTR_ADAPTIVE)) &&
+	    altsd->bNumEndpoints >= 2) {
+		/* 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) != 0x01 ||
+		    (get_endpoint(alts, 1)->bLength >= USB_DT_ENDPOINT_AUDIO_SIZE &&
+		     get_endpoint(alts, 1)->bSynchAddress != 0)) {
+			snd_printk(KERN_ERR "%d:%d:%d : invalid synch pipe\n",
+				   dev->devnum, fmt->iface, fmt->altsetting);
+			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)))) {
+			snd_printk(KERN_ERR "%d:%d:%d : invalid synch pipe\n",
+				   dev->devnum, fmt->iface, fmt->altsetting);
+			return -EINVAL;
+		}
+		ep &= USB_ENDPOINT_NUMBER_MASK;
+		if (is_playback)
+			subs->syncpipe = usb_rcvisocpipe(dev, ep);
+		else
+			subs->syncpipe = usb_sndisocpipe(dev, ep);
+		subs->syncinterval = get_endpoint(alts, 1)->bRefresh;
+	}
+
+	/* always fill max packet size */
+	if (fmt->attributes & EP_CS_ATTR_FILL_MAX)
+		subs->fill_max = 1;
+
+	if ((err = init_usb_pitch(dev, subs->interface, alts, fmt)) < 0)
+		return err;
+
+	subs->cur_audiofmt = fmt;
+
+#if 0
+	printk("setting done: format = %d, rate = %d, channels = %d\n",
+	       fmt->format, fmt->rate, fmt->channels);
+	printk("  datapipe = 0x%0x, syncpipe = 0x%0x\n",
+	       subs->datapipe, subs->syncpipe);
+#endif
+
+	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(snd_pcm_substream_t *substream,
+			     snd_pcm_hw_params_t *hw_params)
+{
+	snd_usb_substream_t *subs = (snd_usb_substream_t *)substream->runtime->private_data;
+	struct audioformat *fmt;
+	unsigned int channels, rate, format;
+	int ret, changed;
+
+	ret = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params));
+	if (ret < 0)
+		return ret;
+
+	format = params_format(hw_params);
+	rate = params_rate(hw_params);
+	channels = params_channels(hw_params);
+	fmt = find_format(subs, format, rate, channels);
+	if (! fmt) {
+		snd_printd(KERN_DEBUG "cannot set format: format = %s, rate = %d, channels = %d\n",
+			   snd_pcm_format_name(format), rate, channels);
+		return -EINVAL;
+	}
+
+	changed = subs->cur_audiofmt != fmt ||
+		subs->period_bytes != params_period_bytes(hw_params) ||
+		subs->cur_rate != rate;
+	if ((ret = set_format(subs, fmt)) < 0)
+		return ret;
+
+	if (subs->cur_rate != rate) {
+		struct usb_host_interface *alts;
+		struct usb_interface *iface;
+		iface = usb_ifnum_to_if(subs->dev, fmt->iface);
+		alts = &iface->altsetting[fmt->altset_idx];
+		ret = init_usb_sample_rate(subs->dev, subs->interface, alts, fmt, rate);
+		if (ret < 0)
+			return ret;
+		subs->cur_rate = rate;
+	}
+
+	if (changed) {
+		/* format changed */
+		release_substream_urbs(subs, 0);
+		/* influenced: period_bytes, channels, rate, format, */
+		ret = init_substream_urbs(subs, params_period_bytes(hw_params),
+					  params_rate(hw_params),
+					  snd_pcm_format_physical_width(params_format(hw_params)) * params_channels(hw_params));
+	}
+
+	return ret;
+}
+
+/*
+ * hw_free callback
+ *
+ * reset the audio format and release the buffer
+ */
+static int snd_usb_hw_free(snd_pcm_substream_t *substream)
+{
+	snd_usb_substream_t *subs = (snd_usb_substream_t *)substream->runtime->private_data;
+
+	subs->cur_audiofmt = NULL;
+	subs->cur_rate = 0;
+	subs->period_bytes = 0;
+	release_substream_urbs(subs, 0);
+	return snd_pcm_lib_free_pages(substream);
+}
+
+/*
+ * prepare callback
+ *
+ * only a few subtle things...
+ */
+static int snd_usb_pcm_prepare(snd_pcm_substream_t *substream)
+{
+	snd_pcm_runtime_t *runtime = substream->runtime;
+	snd_usb_substream_t *subs = (snd_usb_substream_t *)runtime->private_data;
+
+	if (! subs->cur_audiofmt) {
+		snd_printk(KERN_ERR "usbaudio: no format is specified!\n");
+		return -ENXIO;
+	}
+
+	/* some unit conversions in runtime */
+	subs->maxframesize = bytes_to_frames(runtime, subs->maxpacksize);
+	subs->curframesize = bytes_to_frames(runtime, subs->curpacksize);
+
+	/* reset the pointer */
+	subs->hwptr = 0;
+	subs->hwptr_done = 0;
+	subs->transfer_sched = 0;
+	subs->transfer_done = 0;
+	subs->phase = 0;
+
+	/* clear urbs (to be sure) */
+	deactivate_urbs(subs, 0, 1);
+	wait_clear_urbs(subs);
+
+	return 0;
+}
+
+static snd_pcm_hardware_t snd_usb_playback =
+{
+	.info =			(SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
+				 SNDRV_PCM_INFO_BLOCK_TRANSFER |
+				 SNDRV_PCM_INFO_MMAP_VALID),
+	.buffer_bytes_max =	(128*1024),
+	.period_bytes_min =	64,
+	.period_bytes_max =	(128*1024),
+	.periods_min =		2,
+	.periods_max =		1024,
+};
+
+static snd_pcm_hardware_t snd_usb_capture =
+{
+	.info =			(SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
+				 SNDRV_PCM_INFO_BLOCK_TRANSFER |
+				 SNDRV_PCM_INFO_MMAP_VALID),
+	.buffer_bytes_max =	(128*1024),
+	.period_bytes_min =	64,
+	.period_bytes_max =	(128*1024),
+	.periods_min =		2,
+	.periods_max =		1024,
+};
+
+/*
+ * h/w constraints
+ */
+
+#ifdef HW_CONST_DEBUG
+#define hwc_debug(fmt, args...) printk(KERN_DEBUG fmt, ##args)
+#else
+#define hwc_debug(fmt, args...) /**/
+#endif
+
+static int hw_check_valid_format(snd_pcm_hw_params_t *params, struct audioformat *fp)
+{
+	snd_interval_t *it = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE);
+	snd_interval_t *ct = hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS);
+	snd_mask_t *fmts = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT);
+
+	/* check the format */
+	if (! snd_mask_test(fmts, fp->format)) {
+		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;
+	}
+	return 1;
+}
+
+static int hw_rule_rate(snd_pcm_hw_params_t *params,
+			snd_pcm_hw_rule_t *rule)
+{
+	snd_usb_substream_t *subs = rule->private;
+	struct list_head *p;
+	snd_interval_t *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(p, &subs->fmt_list) {
+		struct audioformat *fp;
+		fp = list_entry(p, struct audioformat, list);
+		if (! hw_check_valid_format(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(snd_pcm_hw_params_t *params,
+			    snd_pcm_hw_rule_t *rule)
+{
+	snd_usb_substream_t *subs = rule->private;
+	struct list_head *p;
+	snd_interval_t *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(p, &subs->fmt_list) {
+		struct audioformat *fp;
+		fp = list_entry(p, struct audioformat, list);
+		if (! hw_check_valid_format(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(snd_pcm_hw_params_t *params,
+			  snd_pcm_hw_rule_t *rule)
+{
+	snd_usb_substream_t *subs = rule->private;
+	struct list_head *p;
+	snd_mask_t *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(p, &subs->fmt_list) {
+		struct audioformat *fp;
+		fp = list_entry(p, struct audioformat, list);
+		if (! hw_check_valid_format(params, fp))
+			continue;
+		fbits |= (1ULL << fp->format);
+	}
+
+	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;
+}
+
+#define MAX_MASK	64
+
+/*
+ * check whether the registered audio formats need special hw-constraints
+ */
+static int check_hw_params_convention(snd_usb_substream_t *subs)
+{
+	int i;
+	u32 *channels;
+	u32 *rates;
+	u32 cmaster, rmaster;
+	u32 rate_min = 0, rate_max = 0;
+	struct list_head *p;
+	int err = 1;
+
+	channels = kcalloc(MAX_MASK, sizeof(u32), GFP_KERNEL);
+	rates = kcalloc(MAX_MASK, sizeof(u32), GFP_KERNEL);
+
+	list_for_each(p, &subs->fmt_list) {
+		struct audioformat *f;
+		f = list_entry(p, struct audioformat, list);
+		/* unconventional channels? */
+		if (f->channels > 32)
+			goto __out;
+		/* continuous rate min/max matches? */
+		if (f->rates & SNDRV_PCM_RATE_CONTINUOUS) {
+			if (rate_min && f->rate_min != rate_min)
+				goto __out;
+			if (rate_max && f->rate_max != rate_max)
+				goto __out;
+			rate_min = f->rate_min;
+			rate_max = f->rate_max;
+		}
+		/* combination of continuous rates and fixed rates? */
+		if (rates[f->format] & SNDRV_PCM_RATE_CONTINUOUS) {
+			if (f->rates != rates[f->format])
+				goto __out;
+		}
+		if (f->rates & SNDRV_PCM_RATE_CONTINUOUS) {
+			if (rates[f->format] && rates[f->format] != f->rates)
+				goto __out;
+		}
+		channels[f->format] |= (1 << f->channels);
+		rates[f->format] |= f->rates;
+	}
+	/* check whether channels and rates match for all formats */
+	cmaster = rmaster = 0;
+	for (i = 0; i < MAX_MASK; i++) {
+		if (cmaster != channels[i] && cmaster && channels[i])
+			goto __out;
+		if (rmaster != rates[i] && rmaster && rates[i])
+			goto __out;
+		if (channels[i])
+			cmaster = channels[i];
+		if (rates[i])
+			rmaster = rates[i];
+	}
+	/* check whether channels match for all distinct rates */
+	memset(channels, 0, MAX_MASK * sizeof(u32));
+	list_for_each(p, &subs->fmt_list) {
+		struct audioformat *f;
+		f = list_entry(p, struct audioformat, list);
+		if (f->rates & SNDRV_PCM_RATE_CONTINUOUS)
+			continue;
+		for (i = 0; i < 32; i++) {
+			if (f->rates & (1 << i))
+				channels[i] |= (1 << f->channels);
+		}
+	}
+	cmaster = 0;
+	for (i = 0; i < 32; i++) {
+		if (cmaster != channels[i] && cmaster && channels[i])
+			goto __out;
+		if (channels[i])
+			cmaster = channels[i];
+	}
+	err = 0;
+
+ __out:
+	kfree(channels);
+	kfree(rates);
+	return err;
+}
+
+
+/*
+ * set up the runtime hardware information.
+ */
+
+static int setup_hw_info(snd_pcm_runtime_t *runtime, snd_usb_substream_t *subs)
+{
+	struct list_head *p;
+	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;
+	/* check min/max rates and channels */
+	list_for_each(p, &subs->fmt_list) {
+		struct audioformat *fp;
+		fp = list_entry(p, struct audioformat, 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 == USB_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;
+		}
+	}
+
+	/* set the period time minimum 1ms */
+	snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_PERIOD_TIME,
+				     1000 * MIN_PACKS_URB,
+				     /*(nrpacks * MAX_URBS) * 1000*/ UINT_MAX);
+
+	if (check_hw_params_convention(subs)) {
+		hwc_debug("setting extra hw constraints...\n");
+		if ((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,
+					       -1)) < 0)
+			return err;
+		if ((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,
+					       -1)) < 0)
+			return err;
+		if ((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,
+					       -1)) < 0)
+			return err;
+	}
+	return 0;
+}
+
+static int snd_usb_pcm_open(snd_pcm_substream_t *substream, int direction,
+			    snd_pcm_hardware_t *hw)
+{
+	snd_usb_stream_t *as = snd_pcm_substream_chip(substream);
+	snd_pcm_runtime_t *runtime = substream->runtime;
+	snd_usb_substream_t *subs = &as->substream[direction];
+
+	subs->interface = -1;
+	subs->format = 0;
+	runtime->hw = *hw;
+	runtime->private_data = subs;
+	subs->pcm_substream = substream;
+	return setup_hw_info(runtime, subs);
+}
+
+static int snd_usb_pcm_close(snd_pcm_substream_t *substream, int direction)
+{
+	snd_usb_stream_t *as = snd_pcm_substream_chip(substream);
+	snd_usb_substream_t *subs = &as->substream[direction];
+
+	if (subs->interface >= 0) {
+		usb_set_interface(subs->dev, subs->interface, 0);
+		subs->interface = -1;
+	}
+	subs->pcm_substream = NULL;
+	return 0;
+}
+
+static int snd_usb_playback_open(snd_pcm_substream_t *substream)
+{
+	return snd_usb_pcm_open(substream, SNDRV_PCM_STREAM_PLAYBACK, &snd_usb_playback);
+}
+
+static int snd_usb_playback_close(snd_pcm_substream_t *substream)
+{
+	return snd_usb_pcm_close(substream, SNDRV_PCM_STREAM_PLAYBACK);
+}
+
+static int snd_usb_capture_open(snd_pcm_substream_t *substream)
+{
+	return snd_usb_pcm_open(substream, SNDRV_PCM_STREAM_CAPTURE, &snd_usb_capture);
+}
+
+static int snd_usb_capture_close(snd_pcm_substream_t *substream)
+{
+	return snd_usb_pcm_close(substream, SNDRV_PCM_STREAM_CAPTURE);
+}
+
+static snd_pcm_ops_t snd_usb_playback_ops = {
+	.open =		snd_usb_playback_open,
+	.close =	snd_usb_playback_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_pcm_trigger,
+	.pointer =	snd_usb_pcm_pointer,
+};
+
+static snd_pcm_ops_t snd_usb_capture_ops = {
+	.open =		snd_usb_capture_open,
+	.close =	snd_usb_capture_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_pcm_trigger,
+	.pointer =	snd_usb_pcm_pointer,
+};
+
+
+
+/*
+ * helper functions
+ */
+
+/*
+ * 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 timeout)
+{
+	int err;
+	void *buf = NULL;
+
+	if (size > 0) {
+		buf = kmalloc(size, GFP_KERNEL);
+		if (!buf)
+			return -ENOMEM;
+		memcpy(buf, data, size);
+	}
+	err = usb_control_msg(dev, pipe, request, requesttype,
+			      value, index, buf, size, timeout);
+	if (size > 0) {
+		memcpy(data, buf, size);
+		kfree(buf);
+	}
+	return err;
+}
+
+
+/*
+ * entry point for linux usb interface
+ */
+
+static int usb_audio_probe(struct usb_interface *intf,
+			   const struct usb_device_id *id);
+static void usb_audio_disconnect(struct usb_interface *intf);
+
+static struct usb_device_id usb_audio_ids [] = {
+#include "usbquirks.h"
+    { .match_flags = (USB_DEVICE_ID_MATCH_INT_CLASS | USB_DEVICE_ID_MATCH_INT_SUBCLASS),
+      .bInterfaceClass = USB_CLASS_AUDIO,
+      .bInterfaceSubClass = USB_SUBCLASS_AUDIO_CONTROL },
+    { }						/* Terminating entry */
+};
+
+MODULE_DEVICE_TABLE (usb, usb_audio_ids);
+
+static struct usb_driver usb_audio_driver = {
+	.owner =	THIS_MODULE,
+	.name =		"snd-usb-audio",
+	.probe =	usb_audio_probe,
+	.disconnect =	usb_audio_disconnect,
+	.id_table =	usb_audio_ids,
+};
+
+
+/*
+ * proc interface for list the supported pcm formats
+ */
+static void proc_dump_substream_formats(snd_usb_substream_t *subs, snd_info_buffer_t *buffer)
+{
+	struct list_head *p;
+	static char *sync_types[4] = {
+		"NONE", "ASYNC", "ADAPTIVE", "SYNC"
+	};
+
+	list_for_each(p, &subs->fmt_list) {
+		struct audioformat *fp;
+		fp = list_entry(p, struct audioformat, list);
+		snd_iprintf(buffer, "  Interface %d\n", fp->iface);
+		snd_iprintf(buffer, "    Altset %d\n", fp->altsetting);
+		snd_iprintf(buffer, "    Format: %s\n", snd_pcm_format_name(fp->format));
+		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 & EP_ATTR_MASK) >> 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");
+		}
+		// snd_iprintf(buffer, "    Max Packet Size = %d\n", fp->maxpacksize);
+		// snd_iprintf(buffer, "    EP Attribute = 0x%x\n", fp->attributes);
+	}
+}
+
+static void proc_dump_substream_status(snd_usb_substream_t *subs, snd_info_buffer_t *buffer)
+{
+	if (subs->running) {
+		unsigned int i;
+		snd_iprintf(buffer, "  Status: Running\n");
+		snd_iprintf(buffer, "    Interface = %d\n", subs->interface);
+		snd_iprintf(buffer, "    Altset = %d\n", subs->format);
+		snd_iprintf(buffer, "    URBs = %d [ ", subs->nurbs);
+		for (i = 0; i < subs->nurbs; i++)
+			snd_iprintf(buffer, "%d ", subs->dataurb[i].packets);
+		snd_iprintf(buffer, "]\n");
+		snd_iprintf(buffer, "    Packet Size = %d\n", subs->curpacksize);
+		snd_iprintf(buffer, "    Momentary freq = %u Hz\n",
+			    snd_usb_get_speed(subs->dev) == USB_SPEED_FULL
+			    ? get_full_speed_hz(subs->freqm)
+			    : get_high_speed_hz(subs->freqm));
+	} else {
+		snd_iprintf(buffer, "  Status: Stop\n");
+	}
+}
+
+static void proc_pcm_format_read(snd_info_entry_t *entry, snd_info_buffer_t *buffer)
+{
+	snd_usb_stream_t *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);
+	}
+}
+
+static void proc_pcm_format_add(snd_usb_stream_t *stream)
+{
+	snd_info_entry_t *entry;
+	char name[32];
+	snd_card_t *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, 1024, proc_pcm_format_read);
+}
+
+
+/*
+ * initialize the substream instance.
+ */
+
+static void init_substream(snd_usb_stream_t *as, int stream, struct audioformat *fp)
+{
+	snd_usb_substream_t *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;
+	if (snd_usb_get_speed(subs->dev) == USB_SPEED_FULL)
+		subs->ops = audio_urb_ops[stream];
+	else
+		subs->ops = audio_urb_ops_high_speed[stream];
+	snd_pcm_lib_preallocate_pages(as->pcm->streams[stream].substream,
+				      SNDRV_DMA_TYPE_CONTINUOUS,
+				      snd_dma_continuous_data(GFP_KERNEL),
+				      64 * 1024, 128 * 1024);
+	snd_pcm_set_ops(as->pcm, stream,
+			stream == SNDRV_PCM_STREAM_PLAYBACK ?
+			&snd_usb_playback_ops : &snd_usb_capture_ops);
+
+	list_add_tail(&fp->list, &subs->fmt_list);
+	subs->formats |= 1ULL << fp->format;
+	subs->endpoint = fp->endpoint;
+	subs->num_formats++;
+	subs->fmt_type = fp->fmt_type;
+}
+
+
+/*
+ * free a substream
+ */
+static void free_substream(snd_usb_substream_t *subs)
+{
+	struct list_head *p, *n;
+
+	if (! subs->num_formats)
+		return; /* not initialized */
+	list_for_each_safe(p, n, &subs->fmt_list) {
+		struct audioformat *fp = list_entry(p, struct audioformat, list);
+		kfree(fp->rate_table);
+		kfree(fp);
+	}
+}
+
+
+/*
+ * free a usb stream instance
+ */
+static void snd_usb_audio_stream_free(snd_usb_stream_t *stream)
+{
+	free_substream(&stream->substream[0]);
+	free_substream(&stream->substream[1]);
+	list_del(&stream->list);
+	kfree(stream);
+}
+
+static void snd_usb_audio_pcm_free(snd_pcm_t *pcm)
+{
+	snd_usb_stream_t *stream = pcm->private_data;
+	if (stream) {
+		stream->pcm = NULL;
+		snd_pcm_lib_preallocate_free_for_all(pcm);
+		snd_usb_audio_stream_free(stream);
+	}
+}
+
+
+/*
+ * 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.
+ */
+static int add_audio_endpoint(snd_usb_audio_t *chip, int stream, struct audioformat *fp)
+{
+	struct list_head *p;
+	snd_usb_stream_t *as;
+	snd_usb_substream_t *subs;
+	snd_pcm_t *pcm;
+	int err;
+
+	list_for_each(p, &chip->pcm_list) {
+		as = list_entry(p, snd_usb_stream_t, list);
+		if (as->fmt_type != fp->fmt_type)
+			continue;
+		subs = &as->substream[stream];
+		if (! subs->endpoint)
+			continue;
+		if (subs->endpoint == fp->endpoint) {
+			list_add_tail(&fp->list, &subs->fmt_list);
+			subs->num_formats++;
+			subs->formats |= 1ULL << fp->format;
+			return 0;
+		}
+	}
+	/* look for an empty stream */
+	list_for_each(p, &chip->pcm_list) {
+		as = list_entry(p, snd_usb_stream_t, list);
+		if (as->fmt_type != fp->fmt_type)
+			continue;
+		subs = &as->substream[stream];
+		if (subs->endpoint)
+			continue;
+		err = snd_pcm_new_stream(as->pcm, stream, 1);
+		if (err < 0)
+			return err;
+		init_substream(as, stream, fp);
+		return 0;
+	}
+
+	/* create a new pcm */
+	as = kmalloc(sizeof(*as), GFP_KERNEL);
+	if (! as)
+		return -ENOMEM;
+	memset(as, 0, sizeof(*as));
+	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");
+
+	init_substream(as, stream, fp);
+
+	list_add(&as->list, &chip->pcm_list);
+	chip->pcm_devs++;
+
+	proc_pcm_format_add(as);
+
+	return 0;
+}
+
+
+/*
+ * check if the device uses big-endian samples
+ */
+static int is_big_endian_format(struct usb_device *dev, struct audioformat *fp)
+{
+	/* M-Audio */
+	if (le16_to_cpu(dev->descriptor.idVendor) == 0x0763) {
+		/* Quattro: captured data only */
+		if (le16_to_cpu(dev->descriptor.idProduct) == 0x2001 &&
+		    fp->endpoint & USB_DIR_IN)
+			return 1;
+		/* Audiophile USB */
+		if (le16_to_cpu(dev->descriptor.idProduct) == 0x2003)
+			return 1;
+	}
+	return 0;
+}
+
+/*
+ * 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
+ */
+static int parse_audio_format_i_type(struct usb_device *dev, struct audioformat *fp,
+				     int format, unsigned char *fmt)
+{
+	int pcm_format;
+	int sample_width, sample_bytes;
+
+	/* FIXME: correct endianess and sign? */
+	pcm_format = -1;
+	sample_width = fmt[6];
+	sample_bytes = fmt[5];
+	switch (format) {
+	case 0: /* some devices don't define this correctly... */
+		snd_printdd(KERN_INFO "%d:%u:%d : format type 0 is detected, processed as PCM\n",
+			    dev->devnum, fp->iface, fp->altsetting);
+		/* fall-through */
+	case USB_AUDIO_FORMAT_PCM:
+		if (sample_width > sample_bytes * 8) {
+			snd_printk(KERN_INFO "%d:%u:%d : sample bitwidth %d in over sample bytes %d\n",
+				   dev->devnum, fp->iface, fp->altsetting,
+				   sample_width, sample_bytes);
+		}
+		/* check the format byte size */
+		switch (fmt[5]) {
+		case 1:
+			pcm_format = SNDRV_PCM_FORMAT_S8;
+			break;
+		case 2:
+			if (is_big_endian_format(dev, fp))
+				pcm_format = SNDRV_PCM_FORMAT_S16_BE; /* grrr, big endian!! */
+			else
+				pcm_format = SNDRV_PCM_FORMAT_S16_LE;
+			break;
+		case 3:
+			if (is_big_endian_format(dev, fp))
+				pcm_format = SNDRV_PCM_FORMAT_S24_3BE; /* grrr, big endian!! */
+			else
+				pcm_format = SNDRV_PCM_FORMAT_S24_3LE;
+			break;
+		case 4:
+			pcm_format = SNDRV_PCM_FORMAT_S32_LE;
+			break;
+		default:
+			snd_printk(KERN_INFO "%d:%u:%d : unsupported sample bitwidth %d in %d bytes\n",
+				   dev->devnum, fp->iface, fp->altsetting, sample_width, sample_bytes);
+			break;
+		}
+		break;
+	case USB_AUDIO_FORMAT_PCM8:
+		/* Dallas DS4201 workaround */
+		if (le16_to_cpu(dev->descriptor.idVendor) == 0x04fa &&
+		    le16_to_cpu(dev->descriptor.idProduct) == 0x4201)
+			pcm_format = SNDRV_PCM_FORMAT_S8;
+		else
+			pcm_format = SNDRV_PCM_FORMAT_U8;
+		break;
+	case USB_AUDIO_FORMAT_IEEE_FLOAT:
+		pcm_format = SNDRV_PCM_FORMAT_FLOAT_LE;
+		break;
+	case USB_AUDIO_FORMAT_ALAW:
+		pcm_format = SNDRV_PCM_FORMAT_A_LAW;
+		break;
+	case USB_AUDIO_FORMAT_MU_LAW:
+		pcm_format = SNDRV_PCM_FORMAT_MU_LAW;
+		break;
+	default:
+		snd_printk(KERN_INFO "%d:%u:%d : unsupported format type %d\n",
+			   dev->devnum, fp->iface, fp->altsetting, format);
+		break;
+	}
+	return pcm_format;
+}
+
+
+/*
+ * parse the format descriptor and stores the possible sample rates
+ * on the audioformat table.
+ *
+ * @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(struct usb_device *dev, struct audioformat *fp,
+				    unsigned char *fmt, int offset)
+{
+	int nr_rates = fmt[offset];
+	if (fmt[0] < offset + 1 + 3 * (nr_rates ? nr_rates : 2)) {
+		snd_printk(KERN_ERR "%d:%u:%d : invalid FORMAT_TYPE desc\n",
+				   dev->devnum, fp->iface, fp->altsetting);
+		return -1;
+	}
+
+	if (nr_rates) {
+		/*
+		 * build the rate table and bitmap flags
+		 */
+		int r, idx, c;
+		/* this table corresponds to the SNDRV_PCM_RATE_XXX bit */
+		static unsigned int conv_rates[] = {
+			5512, 8000, 11025, 16000, 22050, 32000, 44100, 48000,
+			64000, 88200, 96000, 176400, 192000
+		};
+		fp->rate_table = kmalloc(sizeof(int) * nr_rates, GFP_KERNEL);
+		if (fp->rate_table == NULL) {
+			snd_printk(KERN_ERR "cannot malloc\n");
+			return -1;
+		}
+
+		fp->nr_rates = nr_rates;
+		fp->rate_min = fp->rate_max = combine_triple(&fmt[8]);
+		for (r = 0, idx = offset + 1; r < nr_rates; r++, idx += 3) {
+			unsigned int rate = fp->rate_table[r] = combine_triple(&fmt[idx]);
+			if (rate < fp->rate_min)
+				fp->rate_min = rate;
+			else if (rate > fp->rate_max)
+				fp->rate_max = rate;
+			for (c = 0; c < (int)ARRAY_SIZE(conv_rates); c++) {
+				if (rate == conv_rates[c]) {
+					fp->rates |= (1 << c);
+					break;
+				}
+			}
+		}
+	} 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;
+}
+
+/*
+ * parse the format type I and III descriptors
+ */
+static int parse_audio_format_i(struct usb_device *dev, struct audioformat *fp,
+				int format, unsigned char *fmt)
+{
+	int pcm_format;
+
+	if (fmt[3] == USB_FORMAT_TYPE_III) {
+		/* FIXME: the format type is really IECxxx
+		 *        but we give normal PCM format to get the existing
+		 *        apps working...
+		 */
+		pcm_format = SNDRV_PCM_FORMAT_S16_LE;
+	} else {
+		pcm_format = parse_audio_format_i_type(dev, fp, format, fmt);
+		if (pcm_format < 0)
+			return -1;
+	}
+	fp->format = pcm_format;
+	fp->channels = fmt[4];
+	if (fp->channels < 1) {
+		snd_printk(KERN_ERR "%d:%u:%d : invalid channels %d\n",
+			   dev->devnum, fp->iface, fp->altsetting, fp->channels);
+		return -1;
+	}
+	return parse_audio_format_rates(dev, fp, fmt, 7);
+}
+
+/*
+ * prase the format type II descriptor
+ */
+static int parse_audio_format_ii(struct usb_device *dev, struct audioformat *fp,
+				 int format, unsigned char *fmt)
+{
+	int brate, framesize;
+	switch (format) {
+	case USB_AUDIO_FORMAT_AC3:
+		/* FIXME: there is no AC3 format defined yet */
+		// fp->format = SNDRV_PCM_FORMAT_AC3;
+		fp->format = SNDRV_PCM_FORMAT_U8; /* temporarily hack to receive byte streams */
+		break;
+	case USB_AUDIO_FORMAT_MPEG:
+		fp->format = SNDRV_PCM_FORMAT_MPEG;
+		break;
+	default:
+		snd_printd(KERN_INFO "%d:%u:%d : unknown format tag 0x%x is detected.  processed as MPEG.\n",
+			   dev->devnum, fp->iface, fp->altsetting, format);
+		fp->format = SNDRV_PCM_FORMAT_MPEG;
+		break;
+	}
+	fp->channels = 1;
+	brate = combine_word(&fmt[4]); 	/* fmt[4,5] : wMaxBitRate (in kbps) */
+	framesize = combine_word(&fmt[6]); /* fmt[6,7]: wSamplesPerFrame */
+	snd_printd(KERN_INFO "found format II with max.bitrate = %d, frame size=%d\n", brate, framesize);
+	fp->frame_size = framesize;
+	return parse_audio_format_rates(dev, fp, fmt, 8); /* fmt[8..] sample rates */
+}
+
+static int parse_audio_format(struct usb_device *dev, struct audioformat *fp,
+			      int format, unsigned char *fmt, int stream)
+{
+	int err;
+
+	switch (fmt[3]) {
+	case USB_FORMAT_TYPE_I:
+	case USB_FORMAT_TYPE_III:
+		err = parse_audio_format_i(dev, fp, format, fmt);
+		break;
+	case USB_FORMAT_TYPE_II:
+		err = parse_audio_format_ii(dev, fp, format, fmt);
+		break;
+	default:
+		snd_printd(KERN_INFO "%d:%u:%d : format type %d is not supported yet\n",
+			   dev->devnum, fp->iface, fp->altsetting, fmt[3]);
+		return -1;
+	}
+	fp->fmt_type = fmt[3];
+	if (err < 0)
+		return err;
+#if 1
+	/* FIXME: temporary hack for extigy */
+	/* extigy apparently supports sample rates other than 48k
+	 * but not in ordinary way.  so we enable only 48k atm.
+	 */
+	if (le16_to_cpu(dev->descriptor.idVendor) == 0x041e && 
+	    le16_to_cpu(dev->descriptor.idProduct) == 0x3000) {
+		if (fmt[3] == USB_FORMAT_TYPE_I &&
+		    stream == SNDRV_PCM_STREAM_PLAYBACK &&
+		    fp->rates != SNDRV_PCM_RATE_48000)
+			return -1; /* use 48k only */
+	}
+#endif
+	return 0;
+}
+
+static int parse_audio_endpoints(snd_usb_audio_t *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;
+	int format;
+	struct audioformat *fp;
+	unsigned char *fmt, *csep;
+
+	dev = chip->dev;
+
+	/* parse the interface's altsettings */
+	iface = usb_ifnum_to_if(dev, iface_no);
+	for (i = 0; i < iface->num_altsetting; i++) {
+		alts = &iface->altsetting[i];
+		altsd = get_iface_desc(alts);
+		/* skip invalid one */
+		if ((altsd->bInterfaceClass != USB_CLASS_AUDIO &&
+		     altsd->bInterfaceClass != USB_CLASS_VENDOR_SPEC) ||
+		    (altsd->bInterfaceSubClass != USB_SUBCLASS_AUDIO_STREAMING &&
+		     altsd->bInterfaceSubClass != USB_SUBCLASS_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;
+
+		/* get audio formats */
+		fmt = snd_usb_find_csint_desc(alts->extra, alts->extralen, NULL, AS_GENERAL);
+		if (!fmt) {
+			snd_printk(KERN_ERR "%d:%u:%d : AS_GENERAL descriptor not found\n",
+				   dev->devnum, iface_no, altno);
+			continue;
+		}
+
+		if (fmt[0] < 7) {
+			snd_printk(KERN_ERR "%d:%u:%d : invalid AS_GENERAL desc\n",
+				   dev->devnum, iface_no, altno);
+			continue;
+		}
+
+		format = (fmt[6] << 8) | fmt[5]; /* remember the format value */
+
+		/* get format type */
+		fmt = snd_usb_find_csint_desc(alts->extra, alts->extralen, NULL, FORMAT_TYPE);
+		if (!fmt) {
+			snd_printk(KERN_ERR "%d:%u:%d : no FORMAT_TYPE desc\n",
+				   dev->devnum, iface_no, altno);
+			continue;
+		}
+		if (fmt[0] < 8) {
+			snd_printk(KERN_ERR "%d:%u:%d : invalid FORMAT_TYPE desc\n",
+				   dev->devnum, iface_no, altno);
+			continue;
+		}
+
+		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 (!csep || csep[0] < 7 || csep[2] != EP_GENERAL) {
+			snd_printk(KERN_ERR "%d:%u:%d : no or invalid class specific endpoint descriptor\n",
+				   dev->devnum, iface_no, altno);
+			continue;
+		}
+
+		fp = kmalloc(sizeof(*fp), GFP_KERNEL);
+		if (! fp) {
+			snd_printk(KERN_ERR "cannot malloc\n");
+			return -ENOMEM;
+		}
+
+		memset(fp, 0, sizeof(*fp));
+		fp->iface = iface_no;
+		fp->altsetting = altno;
+		fp->altset_idx = i;
+		fp->endpoint = get_endpoint(alts, 0)->bEndpointAddress;
+		fp->ep_attr = get_endpoint(alts, 0)->bmAttributes;
+		/* FIXME: decode wMaxPacketSize of high bandwith endpoints */
+		fp->maxpacksize = le16_to_cpu(get_endpoint(alts, 0)->wMaxPacketSize);
+		fp->attributes = csep[3];
+
+		/* some quirks for attributes here */
+
+		/* workaround for AudioTrak Optoplay */
+		if (le16_to_cpu(dev->descriptor.idVendor) == 0x0a92 &&
+		    le16_to_cpu(dev->descriptor.idProduct) == 0x0053) {
+			/* Optoplay sets the sample rate attribute although
+			 * it seems not supporting it in fact.
+			 */
+			fp->attributes &= ~EP_CS_ATTR_SAMPLE_RATE;
+		}
+
+		/* workaround for M-Audio Audiophile USB */
+		if (le16_to_cpu(dev->descriptor.idVendor) == 0x0763 &&
+		    le16_to_cpu(dev->descriptor.idProduct) == 0x2003) {
+			/* doesn't set the sample rate attribute, but supports it */
+			fp->attributes |= EP_CS_ATTR_SAMPLE_RATE;
+		}
+
+		/*
+		 * plantronics headset and Griffin iMic have set adaptive-in
+		 * although it's really not...
+		 */
+		if ((le16_to_cpu(dev->descriptor.idVendor) == 0x047f &&
+		     le16_to_cpu(dev->descriptor.idProduct) == 0x0ca1) ||
+		    /* Griffin iMic (note that there is an older model 77d:223) */
+		    (le16_to_cpu(dev->descriptor.idVendor) == 0x077d &&
+		     le16_to_cpu(dev->descriptor.idProduct) == 0x07af)) {
+			fp->ep_attr &= ~EP_ATTR_MASK;
+			if (stream == SNDRV_PCM_STREAM_PLAYBACK)
+				fp->ep_attr |= EP_ATTR_ADAPTIVE;
+			else
+				fp->ep_attr |= EP_ATTR_SYNC;
+		}
+
+		/* ok, let's parse further... */
+		if (parse_audio_format(dev, fp, format, fmt, stream) < 0) {
+			kfree(fp->rate_table);
+			kfree(fp);
+			continue;
+		}
+
+		snd_printdd(KERN_INFO "%d:%u:%d: add audio endpoint 0x%x\n", dev->devnum, iface_no, i, fp->endpoint);
+		err = add_audio_endpoint(chip, stream, fp);
+		if (err < 0) {
+			kfree(fp->rate_table);
+			kfree(fp);
+			return err;
+		}
+		/* try to set the interface... */
+		usb_set_interface(chip->dev, iface_no, altno);
+		init_usb_pitch(chip->dev, iface_no, alts, fp);
+		init_usb_sample_rate(chip->dev, iface_no, alts, fp, fp->rate_max);
+	}
+	return 0;
+}
+
+
+/*
+ * disconnect streams
+ * called from snd_usb_audio_disconnect()
+ */
+static void snd_usb_stream_disconnect(struct list_head *head, struct usb_driver *driver)
+{
+	int idx;
+	snd_usb_stream_t *as;
+	snd_usb_substream_t *subs;
+
+	as = list_entry(head, snd_usb_stream_t, list);
+	for (idx = 0; idx < 2; idx++) {
+		subs = &as->substream[idx];
+		if (!subs->num_formats)
+			return;
+		release_substream_urbs(subs, 1);
+		subs->interface = -1;
+	}
+}
+
+/*
+ * parse audio control descriptor and create pcm/midi streams
+ */
+static int snd_usb_create_streams(snd_usb_audio_t *chip, int ctrlif)
+{
+	struct usb_device *dev = chip->dev;
+	struct usb_host_interface *host_iface;
+	struct usb_interface *iface;
+	unsigned char *p1;
+	int i, j;
+
+	/* find audiocontrol interface */
+	host_iface = &usb_ifnum_to_if(dev, ctrlif)->altsetting[0];
+	if (!(p1 = snd_usb_find_csint_desc(host_iface->extra, host_iface->extralen, NULL, HEADER))) {
+		snd_printk(KERN_ERR "cannot find HEADER\n");
+		return -EINVAL;
+	}
+	if (! p1[7] || p1[0] < 8 + p1[7]) {
+		snd_printk(KERN_ERR "invalid HEADER\n");
+		return -EINVAL;
+	}
+
+	/*
+	 * parse all USB audio streaming interfaces
+	 */
+	for (i = 0; i < p1[7]; i++) {
+		struct usb_host_interface *alts;
+		struct usb_interface_descriptor *altsd;
+		j = p1[8 + i];
+		iface = usb_ifnum_to_if(dev, j);
+		if (!iface) {
+			snd_printk(KERN_ERR "%d:%u:%d : does not exist\n",
+				   dev->devnum, ctrlif, j);
+			continue;
+		}
+		if (usb_interface_claimed(iface)) {
+			snd_printdd(KERN_INFO "%d:%d:%d: skipping, already claimed\n", dev->devnum, ctrlif, j);
+			continue;
+		}
+		alts = &iface->altsetting[0];
+		altsd = get_iface_desc(alts);
+		if ((altsd->bInterfaceClass == USB_CLASS_AUDIO ||
+		     altsd->bInterfaceClass == USB_CLASS_VENDOR_SPEC) &&
+		    altsd->bInterfaceSubClass == USB_SUBCLASS_MIDI_STREAMING) {
+			if (snd_usb_create_midi_interface(chip, iface, NULL) < 0) {
+				snd_printk(KERN_ERR "%d:%u:%d: cannot create sequencer device\n", dev->devnum, ctrlif, j);
+				continue;
+			}
+			usb_driver_claim_interface(&usb_audio_driver, iface, (void *)-1L);
+			continue;
+		}
+		if ((altsd->bInterfaceClass != USB_CLASS_AUDIO &&
+		     altsd->bInterfaceClass != USB_CLASS_VENDOR_SPEC) ||
+		    altsd->bInterfaceSubClass != USB_SUBCLASS_AUDIO_STREAMING) {
+			snd_printdd(KERN_ERR "%d:%u:%d: skipping non-supported interface %d\n", dev->devnum, ctrlif, j, altsd->bInterfaceClass);
+			/* skip non-supported classes */
+			continue;
+		}
+		if (! parse_audio_endpoints(chip, j)) {
+			usb_set_interface(dev, j, 0); /* reset the current interface */
+			usb_driver_claim_interface(&usb_audio_driver, iface, (void *)-1L);
+		}
+	}
+
+	return 0;
+}
+
+/*
+ * create a stream for an endpoint/altsetting without proper descriptors
+ */
+static int create_fixed_stream_quirk(snd_usb_audio_t *chip,
+				     struct usb_interface *iface,
+				     const snd_usb_audio_quirk_t *quirk)
+{
+	struct audioformat *fp;
+	struct usb_host_interface *alts;
+	int stream, err;
+	int *rate_table = NULL;
+
+	fp = kmalloc(sizeof(*fp), GFP_KERNEL);
+	if (! fp) {
+		snd_printk(KERN_ERR "cannot malloc\n");
+		return -ENOMEM;
+	}
+	memcpy(fp, quirk->data, sizeof(*fp));
+	if (fp->nr_rates > 0) {
+		rate_table = kmalloc(sizeof(int) * fp->nr_rates, GFP_KERNEL);
+		if (!rate_table) {
+			kfree(fp);
+			return -ENOMEM;
+		}
+		memcpy(rate_table, fp->rate_table, sizeof(int) * fp->nr_rates);
+		fp->rate_table = rate_table;
+	}
+
+	stream = (fp->endpoint & USB_DIR_IN)
+		? SNDRV_PCM_STREAM_CAPTURE : SNDRV_PCM_STREAM_PLAYBACK;
+	err = add_audio_endpoint(chip, stream, fp);
+	if (err < 0) {
+		kfree(fp);
+		kfree(rate_table);
+		return err;
+	}
+	if (fp->iface != get_iface_desc(&iface->altsetting[0])->bInterfaceNumber ||
+	    fp->altset_idx >= iface->num_altsetting) {
+		kfree(fp);
+		kfree(rate_table);
+		return -EINVAL;
+	}
+	alts = &iface->altsetting[fp->altset_idx];
+	usb_set_interface(chip->dev, fp->iface, 0);
+	init_usb_pitch(chip->dev, fp->iface, alts, fp);
+	init_usb_sample_rate(chip->dev, fp->iface, alts, fp, fp->rate_max);
+	return 0;
+}
+
+/*
+ * create a stream for an interface with proper descriptors
+ */
+static int create_standard_interface_quirk(snd_usb_audio_t *chip,
+					   struct usb_interface *iface,
+					   const snd_usb_audio_quirk_t *quirk)
+{
+	struct usb_host_interface *alts;
+	struct usb_interface_descriptor *altsd;
+	int err;
+
+	alts = &iface->altsetting[0];
+	altsd = get_iface_desc(alts);
+	switch (quirk->type) {
+	case QUIRK_AUDIO_STANDARD_INTERFACE:
+		err = parse_audio_endpoints(chip, altsd->bInterfaceNumber);
+		if (!err)
+			usb_set_interface(chip->dev, altsd->bInterfaceNumber, 0); /* reset the current interface */
+		break;
+	case QUIRK_MIDI_STANDARD_INTERFACE:
+		err = snd_usb_create_midi_interface(chip, iface, NULL);
+		break;
+	default:
+		snd_printd(KERN_ERR "invalid quirk type %d\n", quirk->type);
+		return -ENXIO;
+	}
+	if (err < 0) {
+		snd_printk(KERN_ERR "cannot setup if %d: error %d\n",
+			   altsd->bInterfaceNumber, err);
+		return err;
+	}
+	return 0;
+}
+
+/*
+ * Create a stream for an Edirol UA-700/UA-25 interface.  The only way
+ * to detect the sample rate is by looking at wMaxPacketSize.
+ */
+static int create_ua700_ua25_quirk(snd_usb_audio_t *chip,
+				   struct usb_interface *iface)
+{
+	static const struct audioformat ua_format = {
+		.format = SNDRV_PCM_FORMAT_S24_3LE,
+		.channels = 2,
+		.fmt_type = USB_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 altsettings */
+	if (iface->num_altsetting != 2)
+		return -ENXIO;
+	alts = &iface->altsetting[1];
+	altsd = get_iface_desc(alts);
+
+	if (altsd->bNumEndpoints == 2) {
+		static const snd_usb_midi_endpoint_info_t ua700_ep = {
+			.out_cables = 0x0003,
+			.in_cables  = 0x0003
+		};
+		static const snd_usb_audio_quirk_t ua700_quirk = {
+			.type = QUIRK_MIDI_FIXED_ENDPOINT,
+			.data = &ua700_ep
+		};
+		static const snd_usb_midi_endpoint_info_t ua25_ep = {
+			.out_cables = 0x0001,
+			.in_cables  = 0x0001
+		};
+		static const snd_usb_audio_quirk_t ua25_quirk = {
+			.type = QUIRK_MIDI_FIXED_ENDPOINT,
+			.data = &ua25_ep
+		};
+		if (le16_to_cpu(chip->dev->descriptor.idProduct) == 0x002b)
+			return snd_usb_create_midi_interface(chip, iface,
+							     &ua700_quirk);
+		else
+			return snd_usb_create_midi_interface(chip, iface,
+							     &ua25_quirk);
+	}
+
+	if (altsd->bNumEndpoints != 1)
+		return -ENXIO;
+
+	fp = kmalloc(sizeof(*fp), GFP_KERNEL);
+	if (!fp)
+		return -ENOMEM;
+	memcpy(fp, &ua_format, sizeof(*fp));
+
+	fp->iface = altsd->bInterfaceNumber;
+	fp->endpoint = get_endpoint(alts, 0)->bEndpointAddress;
+	fp->ep_attr = get_endpoint(alts, 0)->bmAttributes;
+	fp->maxpacksize = le16_to_cpu(get_endpoint(alts, 0)->wMaxPacketSize);
+
+	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:
+		snd_printk(KERN_ERR "unknown sample rate\n");
+		kfree(fp);
+		return -ENXIO;
+	}
+
+	stream = (fp->endpoint & USB_DIR_IN)
+		? SNDRV_PCM_STREAM_CAPTURE : SNDRV_PCM_STREAM_PLAYBACK;
+	err = add_audio_endpoint(chip, stream, fp);
+	if (err < 0) {
+		kfree(fp);
+		return err;
+	}
+	usb_set_interface(chip->dev, fp->iface, 0);
+	return 0;
+}
+
+/*
+ * Create a stream for an Edirol UA-1000 interface.
+ */
+static int create_ua1000_quirk(snd_usb_audio_t *chip, struct usb_interface *iface)
+{
+	static const struct audioformat ua1000_format = {
+		.format = SNDRV_PCM_FORMAT_S32_LE,
+		.fmt_type = USB_FORMAT_TYPE_I,
+		.altsetting = 1,
+		.altset_idx = 1,
+		.attributes = 0,
+		.rates = SNDRV_PCM_RATE_CONTINUOUS,
+	};
+	struct usb_host_interface *alts;
+	struct usb_interface_descriptor *altsd;
+	struct audioformat *fp;
+	int stream, err;
+
+	if (iface->num_altsetting != 2)
+		return -ENXIO;
+	alts = &iface->altsetting[1];
+	altsd = get_iface_desc(alts);
+	if (alts->extralen != 11 || alts->extra[1] != CS_AUDIO_INTERFACE ||
+	    altsd->bNumEndpoints != 1)
+		return -ENXIO;
+
+	fp = kmalloc(sizeof(*fp), GFP_KERNEL);
+	if (!fp)
+		return -ENOMEM;
+	memcpy(fp, &ua1000_format, sizeof(*fp));
+
+	fp->channels = alts->extra[4];
+	fp->iface = altsd->bInterfaceNumber;
+	fp->endpoint = get_endpoint(alts, 0)->bEndpointAddress;
+	fp->ep_attr = get_endpoint(alts, 0)->bmAttributes;
+	fp->maxpacksize = le16_to_cpu(get_endpoint(alts, 0)->wMaxPacketSize);
+	fp->rate_max = fp->rate_min = combine_triple(&alts->extra[8]);
+
+	stream = (fp->endpoint & USB_DIR_IN)
+		? SNDRV_PCM_STREAM_CAPTURE : SNDRV_PCM_STREAM_PLAYBACK;
+	err = add_audio_endpoint(chip, stream, fp);
+	if (err < 0) {
+		kfree(fp);
+		return err;
+	}
+	/* FIXME: playback must be synchronized to capture */
+	usb_set_interface(chip->dev, fp->iface, 0);
+	return 0;
+}
+
+static int snd_usb_create_quirk(snd_usb_audio_t *chip,
+				struct usb_interface *iface,
+				const snd_usb_audio_quirk_t *quirk);
+
+/*
+ * handle the quirks for the contained interfaces
+ */
+static int create_composite_quirk(snd_usb_audio_t *chip,
+				  struct usb_interface *iface,
+				  const snd_usb_audio_quirk_t *quirk)
+{
+	int probed_ifnum = get_iface_desc(iface->altsetting)->bInterfaceNumber;
+	int err;
+
+	for (quirk = quirk->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, quirk);
+		if (err < 0)
+			return err;
+		if (quirk->ifnum != probed_ifnum)
+			usb_driver_claim_interface(&usb_audio_driver, iface, (void *)-1L);
+	}
+	return 0;
+}
+
+
+/*
+ * 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) {
+		snd_printdd("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, 1000);
+		if (err < 0) snd_printdd("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) snd_printdd("error usb_get_descriptor: %d\n", err);
+		err = usb_reset_configuration(dev);
+		if (err < 0) snd_printdd("error usb_reset_configuration: %d\n", err);
+		snd_printdd("extigy_boot: new boot length = %d\n",
+			    le16_to_cpu(get_cfg_desc(config)->wTotalLength));
+		return -ENODEV; /* quit this anyway */
+	}
+	return 0;
+}
+
+
+/*
+ * audio-interface quirks
+ *
+ * returns zero if no standard audio/MIDI parsing is needed.
+ * returns a postive value if standard audio/midi interfaces are parsed
+ * after this.
+ * returns a negative value at error.
+ */
+static int snd_usb_create_quirk(snd_usb_audio_t *chip,
+				struct usb_interface *iface,
+				const snd_usb_audio_quirk_t *quirk)
+{
+	switch (quirk->type) {
+	case QUIRK_MIDI_FIXED_ENDPOINT:
+	case QUIRK_MIDI_YAMAHA:
+	case QUIRK_MIDI_MIDIMAN:
+	case QUIRK_MIDI_NOVATION:
+	case QUIRK_MIDI_MOTU:
+	case QUIRK_MIDI_EMAGIC:
+		return snd_usb_create_midi_interface(chip, iface, quirk);
+	case QUIRK_COMPOSITE:
+		return create_composite_quirk(chip, iface, quirk);
+	case QUIRK_AUDIO_FIXED_ENDPOINT:
+		return create_fixed_stream_quirk(chip, iface, quirk);
+	case QUIRK_AUDIO_STANDARD_INTERFACE:
+	case QUIRK_MIDI_STANDARD_INTERFACE:
+		return create_standard_interface_quirk(chip, iface, quirk);
+	case QUIRK_AUDIO_EDIROL_UA700_UA25:
+		return create_ua700_ua25_quirk(chip, iface);
+	case QUIRK_AUDIO_EDIROL_UA1000:
+		return create_ua1000_quirk(chip, iface);
+	case QUIRK_IGNORE_INTERFACE:
+		return 0;
+	default:
+		snd_printd(KERN_ERR "invalid quirk type %d\n", quirk->type);
+		return -ENXIO;
+	}
+}
+
+
+/*
+ * common proc files to show the usb device info
+ */
+static void proc_audio_usbbus_read(snd_info_entry_t *entry, snd_info_buffer_t *buffer)
+{
+	snd_usb_audio_t *chip = entry->private_data;
+	if (! chip->shutdown)
+		snd_iprintf(buffer, "%03d/%03d\n", chip->dev->bus->busnum, chip->dev->devnum);
+}
+
+static void proc_audio_usbid_read(snd_info_entry_t *entry, snd_info_buffer_t *buffer)
+{
+	snd_usb_audio_t *chip = entry->private_data;
+	if (! chip->shutdown)
+		snd_iprintf(buffer, "%04x:%04x\n", 
+			    le16_to_cpu(chip->dev->descriptor.idVendor),
+			    le16_to_cpu(chip->dev->descriptor.idProduct));
+}
+
+static void snd_usb_audio_create_proc(snd_usb_audio_t *chip)
+{
+	snd_info_entry_t *entry;
+	if (! snd_card_proc_new(chip->card, "usbbus", &entry))
+		snd_info_set_text_ops(entry, chip, 1024, proc_audio_usbbus_read);
+	if (! snd_card_proc_new(chip->card, "usbid", &entry))
+		snd_info_set_text_ops(entry, chip, 1024, proc_audio_usbid_read);
+}
+
+/*
+ * free the chip instance
+ *
+ * here we have to do not much, since pcm and controls are already freed
+ *
+ */
+
+static int snd_usb_audio_free(snd_usb_audio_t *chip)
+{
+	kfree(chip);
+	return 0;
+}
+
+static int snd_usb_audio_dev_free(snd_device_t *device)
+{
+	snd_usb_audio_t *chip = device->device_data;
+	return snd_usb_audio_free(chip);
+}
+
+
+/*
+ * create a chip instance and set its names.
+ */
+static int snd_usb_audio_create(struct usb_device *dev, int idx,
+				const snd_usb_audio_quirk_t *quirk,
+				snd_usb_audio_t **rchip)
+{
+	snd_card_t *card;
+	snd_usb_audio_t *chip;
+	int err, len;
+	char component[14];
+	static snd_device_ops_t ops = {
+		.dev_free =	snd_usb_audio_dev_free,
+	};
+
+	*rchip = NULL;
+
+	if (snd_usb_get_speed(dev) != USB_SPEED_FULL &&
+	    snd_usb_get_speed(dev) != USB_SPEED_HIGH) {
+		snd_printk(KERN_ERR "unknown device speed %d\n", snd_usb_get_speed(dev));
+		return -ENXIO;
+	}
+
+	card = snd_card_new(index[idx], id[idx], THIS_MODULE, 0);
+	if (card == NULL) {
+		snd_printk(KERN_ERR "cannot create card instance %d\n", idx);
+		return -ENOMEM;
+	}
+
+	chip = kcalloc(1, sizeof(*chip), GFP_KERNEL);
+	if (! chip) {
+		snd_card_free(card);
+		return -ENOMEM;
+	}
+
+	chip->index = idx;
+	chip->dev = dev;
+	chip->card = card;
+	INIT_LIST_HEAD(&chip->pcm_list);
+	INIT_LIST_HEAD(&chip->midi_list);
+
+	if ((err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops)) < 0) {
+		snd_usb_audio_free(chip);
+		snd_card_free(card);
+		return err;
+	}
+
+	strcpy(card->driver, "USB-Audio");
+	sprintf(component, "USB%04x:%04x",
+		le16_to_cpu(dev->descriptor.idVendor),
+		le16_to_cpu(dev->descriptor.idProduct));
+	snd_component_add(card, component);
+
+	/* retrieve the device string as shortname */
+ 	if (quirk && quirk->product_name) {
+		strlcpy(card->shortname, quirk->product_name, sizeof(card->shortname));
+	} else {
+		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",
+				le16_to_cpu(dev->descriptor.idVendor),
+				le16_to_cpu(dev->descriptor.idProduct));
+		}
+	}
+
+	/* retrieve the vendor and device strings as longname */
+	if (quirk && quirk->vendor_name) {
+		len = strlcpy(card->longname, quirk->vendor_name, sizeof(card->longname));
+	} else {
+		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)
+		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);
+
+	strlcat(card->longname,
+		snd_usb_get_speed(dev) == USB_SPEED_FULL ? ", full speed" : ", high speed",
+		sizeof(card->longname));
+
+	snd_usb_audio_create_proc(chip);
+
+	snd_card_set_dev(card, &dev->dev);
+
+	*rchip = chip;
+	return 0;
+}
+
+
+/*
+ * 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 void *snd_usb_audio_probe(struct usb_device *dev,
+				 struct usb_interface *intf,
+				 const struct usb_device_id *usb_id)
+{
+	struct usb_host_config *config = dev->actconfig;
+	const snd_usb_audio_quirk_t *quirk = (const snd_usb_audio_quirk_t *)usb_id->driver_info;
+	int i, err;
+	snd_usb_audio_t *chip;
+	struct usb_host_interface *alts;
+	int ifnum;
+
+	alts = &intf->altsetting[0];
+	ifnum = get_iface_desc(alts)->bInterfaceNumber;
+
+	if (quirk && quirk->ifnum >= 0 && ifnum != quirk->ifnum)
+		goto __err_val;
+
+	/* SB Extigy needs special boot-up sequence */
+	/* if more models come, this will go to the quirk list. */
+	if (le16_to_cpu(dev->descriptor.idVendor) == 0x041e && 
+	    le16_to_cpu(dev->descriptor.idProduct) == 0x3000) {
+		if (snd_usb_extigy_boot_quirk(dev, intf) < 0)
+			goto __err_val;
+		config = dev->actconfig;
+	}
+
+	/*
+	 * found a config.  now register to ALSA
+	 */
+
+	/* check whether it's already registered */
+	chip = NULL;
+	down(&register_mutex);
+	for (i = 0; i < SNDRV_CARDS; i++) {
+		if (usb_chip[i] && usb_chip[i]->dev == dev) {
+			if (usb_chip[i]->shutdown) {
+				snd_printk(KERN_ERR "USB device is in the shutdown state, cannot create a card instance\n");
+				goto __error;
+			}
+			chip = usb_chip[i];
+			break;
+		}
+	}
+	if (! chip) {
+		/* it's a fresh one.
+		 * now look for an empty slot and create a new card instance
+		 */
+		/* first, set the current configuration for this device */
+		if (usb_reset_configuration(dev) < 0) {
+			snd_printk(KERN_ERR "cannot reset configuration (value 0x%x)\n", get_cfg_desc(config)->bConfigurationValue);
+			goto __error;
+		}
+		for (i = 0; i < SNDRV_CARDS; i++)
+			if (enable[i] && ! usb_chip[i] &&
+			    (vid[i] == -1 || vid[i] == le16_to_cpu(dev->descriptor.idVendor)) &&
+			    (pid[i] == -1 || pid[i] == le16_to_cpu(dev->descriptor.idProduct))) {
+				if (snd_usb_audio_create(dev, i, quirk, &chip) < 0) {
+					goto __error;
+				}
+				break;
+			}
+		if (! chip) {
+			snd_printk(KERN_ERR "no available usb audio device\n");
+			goto __error;
+		}
+	}
+
+	err = 1; /* continue */
+	if (quirk && quirk->ifnum != QUIRK_NO_INTERFACE) {
+		/* need some special handlings */
+		if ((err = snd_usb_create_quirk(chip, intf, quirk)) < 0)
+			goto __error;
+	}
+
+	if (err > 0) {
+		/* create normal USB audio interfaces */
+		if (snd_usb_create_streams(chip, ifnum) < 0 ||
+		    snd_usb_create_mixer(chip, ifnum) < 0) {
+			goto __error;
+		}
+	}
+
+	/* we are allowed to call snd_card_register() many times */
+	if (snd_card_register(chip->card) < 0) {
+		goto __error;
+	}
+
+	usb_chip[chip->index] = chip;
+	chip->num_interfaces++;
+	up(&register_mutex);
+	return chip;
+
+ __error:
+	if (chip && !chip->num_interfaces)
+		snd_card_free(chip->card);
+	up(&register_mutex);
+ __err_val:
+	return NULL;
+}
+
+/*
+ * we need to take care of counter, since disconnection can be called also
+ * many times as well as usb_audio_probe().
+ */
+static void snd_usb_audio_disconnect(struct usb_device *dev, void *ptr)
+{
+	snd_usb_audio_t *chip;
+	snd_card_t *card;
+	struct list_head *p;
+
+	if (ptr == (void *)-1L)
+		return;
+
+	chip = ptr;
+	card = chip->card;
+	down(&register_mutex);
+	chip->shutdown = 1;
+	chip->num_interfaces--;
+	if (chip->num_interfaces <= 0) {
+		snd_card_disconnect(card);
+		/* release the pcm resources */
+		list_for_each(p, &chip->pcm_list) {
+			snd_usb_stream_disconnect(p, &usb_audio_driver);
+		}
+		/* release the midi resources */
+		list_for_each(p, &chip->midi_list) {
+			snd_usbmidi_disconnect(p, &usb_audio_driver);
+		}
+		usb_chip[chip->index] = NULL;
+		up(&register_mutex);
+		snd_card_free_in_thread(card);
+	} else {
+		up(&register_mutex);
+	}
+}
+
+/*
+ * new 2.5 USB kernel API
+ */
+static int usb_audio_probe(struct usb_interface *intf,
+			   const struct usb_device_id *id)
+{
+	void *chip;
+	chip = snd_usb_audio_probe(interface_to_usbdev(intf), intf, id);
+	if (chip) {
+		dev_set_drvdata(&intf->dev, chip);
+		return 0;
+	} else
+		return -EIO;
+}
+
+static void usb_audio_disconnect(struct usb_interface *intf)
+{
+	snd_usb_audio_disconnect(interface_to_usbdev(intf),
+				 dev_get_drvdata(&intf->dev));
+}
+
+
+static int __init snd_usb_audio_init(void)
+{
+	if (nrpacks < MIN_PACKS_URB || nrpacks > MAX_PACKS) {
+		printk(KERN_WARNING "invalid nrpacks value.\n");
+		return -EINVAL;
+	}
+	usb_register(&usb_audio_driver);
+	return 0;
+}
+
+
+static void __exit snd_usb_audio_cleanup(void)
+{
+	usb_deregister(&usb_audio_driver);
+}
+
+module_init(snd_usb_audio_init);
+module_exit(snd_usb_audio_cleanup);
diff --git a/sound/usb/usbaudio.h b/sound/usb/usbaudio.h
new file mode 100644
index 0000000..eecbf19
--- /dev/null
+++ b/sound/usb/usbaudio.h
@@ -0,0 +1,251 @@
+#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
+ */
+
+
+/*
+ */
+
+#define USB_SUBCLASS_AUDIO_CONTROL	0x01
+#define USB_SUBCLASS_AUDIO_STREAMING	0x02
+#define USB_SUBCLASS_MIDI_STREAMING	0x03
+#define USB_SUBCLASS_VENDOR_SPEC	0xff
+
+#define CS_AUDIO_UNDEFINED		0x20
+#define CS_AUDIO_DEVICE			0x21
+#define CS_AUDIO_CONFIGURATION		0x22
+#define CS_AUDIO_STRING			0x23
+#define CS_AUDIO_INTERFACE		0x24
+#define CS_AUDIO_ENDPOINT		0x25
+
+#define HEADER				0x01
+#define INPUT_TERMINAL			0x02
+#define OUTPUT_TERMINAL			0x03
+#define MIXER_UNIT			0x04
+#define SELECTOR_UNIT			0x05
+#define FEATURE_UNIT			0x06
+#define PROCESSING_UNIT			0x07
+#define EXTENSION_UNIT			0x08
+
+#define AS_GENERAL			0x01
+#define FORMAT_TYPE			0x02
+#define FORMAT_SPECIFIC			0x03
+
+#define EP_GENERAL			0x01
+
+#define MS_GENERAL			0x01
+#define MIDI_IN_JACK			0x02
+#define MIDI_OUT_JACK			0x03
+
+/* endpoint attributes */
+#define EP_ATTR_MASK			0x0c
+#define EP_ATTR_ASYNC			0x04
+#define EP_ATTR_ADAPTIVE		0x08
+#define EP_ATTR_SYNC			0x0c
+
+/* cs endpoint attributes */
+#define EP_CS_ATTR_SAMPLE_RATE		0x01
+#define EP_CS_ATTR_PITCH_CONTROL	0x02
+#define EP_CS_ATTR_FILL_MAX		0x80
+
+/* Audio Class specific Request Codes */
+
+#define SET_CUR    0x01
+#define GET_CUR    0x81
+#define SET_MIN    0x02
+#define GET_MIN    0x82
+#define SET_MAX    0x03
+#define GET_MAX    0x83
+#define SET_RES    0x04
+#define GET_RES    0x84
+#define SET_MEM    0x05
+#define GET_MEM    0x85
+#define GET_STAT   0xff
+
+/* Terminal Control Selectors */
+
+#define COPY_PROTECT_CONTROL       0x01
+
+/* Endpoint Control Selectors */
+
+#define SAMPLING_FREQ_CONTROL      0x01
+#define PITCH_CONTROL              0x02
+
+/* Format Types */
+#define USB_FORMAT_TYPE_I	0x01
+#define USB_FORMAT_TYPE_II	0x02
+#define USB_FORMAT_TYPE_III	0x03
+
+/* type I */
+#define USB_AUDIO_FORMAT_PCM	0x01
+#define USB_AUDIO_FORMAT_PCM8	0x02
+#define USB_AUDIO_FORMAT_IEEE_FLOAT	0x03
+#define USB_AUDIO_FORMAT_ALAW	0x04
+#define USB_AUDIO_FORMAT_MU_LAW	0x05
+
+/* type II */
+#define USB_AUDIO_FORMAT_MPEG	0x1001
+#define USB_AUDIO_FORMAT_AC3	0x1002
+
+/* type III */
+#define USB_AUDIO_FORMAT_IEC1937_AC3	0x2001
+#define USB_AUDIO_FORMAT_IEC1937_MPEG1_LAYER1	0x2002
+#define USB_AUDIO_FORMAT_IEC1937_MPEG2_NOEXT	0x2003
+#define USB_AUDIO_FORMAT_IEC1937_MPEG2_EXT	0x2004
+#define USB_AUDIO_FORMAT_IEC1937_MPEG2_LAYER1_LS	0x2005
+#define USB_AUDIO_FORMAT_IEC1937_MPEG2_LAYER23_LS	0x2006
+
+
+/* maximum number of endpoints per interface */
+#define MIDI_MAX_ENDPOINTS 2
+
+/*
+ */
+
+typedef struct snd_usb_audio snd_usb_audio_t;
+
+struct snd_usb_audio {
+	int index;
+	struct usb_device *dev;
+	snd_card_t *card;
+	int shutdown;
+	int num_interfaces;
+
+	struct list_head pcm_list;	/* list of pcm streams */
+	int pcm_devs;
+
+	struct list_head midi_list;	/* list of midi interfaces */
+	int next_midi_device;
+
+	unsigned int ignore_ctl_error;	/* for mixer */
+};
+
+/*
+ * Information about devices with broken descriptors
+ */
+
+/* special values for .ifnum */
+#define QUIRK_NO_INTERFACE		-2
+#define QUIRK_ANY_INTERFACE		-1
+
+/* quirk type */
+#define QUIRK_MIDI_FIXED_ENDPOINT	0
+#define QUIRK_MIDI_YAMAHA		1
+#define QUIRK_MIDI_MIDIMAN		2
+#define QUIRK_COMPOSITE			3
+#define QUIRK_AUDIO_FIXED_ENDPOINT	4
+#define QUIRK_AUDIO_STANDARD_INTERFACE	5
+#define QUIRK_MIDI_STANDARD_INTERFACE	6
+#define QUIRK_AUDIO_EDIROL_UA700_UA25	7
+#define QUIRK_AUDIO_EDIROL_UA1000	8
+#define QUIRK_IGNORE_INTERFACE		9
+#define QUIRK_MIDI_NOVATION		10
+#define QUIRK_MIDI_MOTU			11
+#define QUIRK_MIDI_EMAGIC		12
+
+typedef struct snd_usb_audio_quirk snd_usb_audio_quirk_t;
+typedef struct snd_usb_midi_endpoint_info snd_usb_midi_endpoint_info_t;
+
+struct snd_usb_audio_quirk {
+	const char *vendor_name;
+	const char *product_name;
+	int16_t ifnum;
+	int16_t type;
+	const void *data;
+};
+
+/* 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 _MOTU, data is NULL */
+
+/* for QUIRK_MIDI_EMAGIC, data points to a snd_usb_midi_endpoint_info
+ * structure (out_cables and in_cables only) */
+
+/*
+ */
+
+#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))
+
+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, int timeout);
+
+int snd_usb_create_mixer(snd_usb_audio_t *chip, int ctrlif);
+
+int snd_usb_create_midi_interface(snd_usb_audio_t *chip, struct usb_interface *iface, const snd_usb_audio_quirk_t *quirk);
+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, struct usb_driver *driver);
+
+/*
+ * retrieve usb_interface descriptor from the host interface
+ * (conditional for compatibility with the older API)
+ */
+#ifndef get_iface_desc
+#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)
+#endif
+
+#ifndef usb_pipe_needs_resubmit
+#define usb_pipe_needs_resubmit(pipe) 1
+#endif
+
+#ifndef snd_usb_complete_callback
+#define snd_usb_complete_callback(x) (x)
+#endif
+
+#ifndef snd_usb_get_speed
+#define snd_usb_get_speed(dev) ((dev)->speed)
+#endif
+
+#endif /* __USBAUDIO_H */
diff --git a/sound/usb/usbmidi.c b/sound/usb/usbmidi.c
new file mode 100644
index 0000000..5d32857
--- /dev/null
+++ b/sound/usb/usbmidi.c
@@ -0,0 +1,1564 @@
+/*
+ * usbmidi.c - ALSA USB MIDI driver
+ *
+ * Copyright (c) 2002-2005 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 <sound/driver.h>
+#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/usb.h>
+#include <sound/core.h>
+#include <sound/minors.h>
+#include <sound/rawmidi.h>
+#include "usbaudio.h"
+
+
+/*
+ * define this to log all USB packets
+ */
+/* #define DUMP_PACKETS */
+
+
+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));
+
+typedef struct snd_usb_midi snd_usb_midi_t;
+typedef struct snd_usb_midi_endpoint snd_usb_midi_endpoint_t;
+typedef struct snd_usb_midi_out_endpoint snd_usb_midi_out_endpoint_t;
+typedef struct snd_usb_midi_in_endpoint snd_usb_midi_in_endpoint_t;
+typedef struct usbmidi_out_port usbmidi_out_port_t;
+typedef struct usbmidi_in_port usbmidi_in_port_t;
+
+struct usb_protocol_ops {
+	void (*input)(snd_usb_midi_in_endpoint_t*, uint8_t*, int);
+	void (*output)(snd_usb_midi_out_endpoint_t*);
+	void (*output_packet)(struct urb*, uint8_t, uint8_t, uint8_t, uint8_t);
+	void (*init_out_endpoint)(snd_usb_midi_out_endpoint_t*);
+	void (*finish_out_endpoint)(snd_usb_midi_out_endpoint_t*);
+};
+
+struct snd_usb_midi {
+	snd_usb_audio_t *chip;
+	struct usb_interface *iface;
+	const snd_usb_audio_quirk_t *quirk;
+	snd_rawmidi_t* rmidi;
+	struct usb_protocol_ops* usb_protocol_ops;
+	struct list_head list;
+
+	struct snd_usb_midi_endpoint {
+		snd_usb_midi_out_endpoint_t *out;
+		snd_usb_midi_in_endpoint_t *in;
+	} endpoints[MIDI_MAX_ENDPOINTS];
+	unsigned long input_triggered;
+};
+
+struct snd_usb_midi_out_endpoint {
+	snd_usb_midi_t* umidi;
+	struct urb* urb;
+	int urb_active;
+	int max_transfer;		/* size of urb buffer */
+	struct tasklet_struct tasklet;
+
+	spinlock_t buffer_lock;
+
+	struct usbmidi_out_port {
+		snd_usb_midi_out_endpoint_t* ep;
+		snd_rawmidi_substream_t* 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;
+};
+
+struct snd_usb_midi_in_endpoint {
+	snd_usb_midi_t* umidi;
+	struct urb* urb;
+	struct usbmidi_in_port {
+		snd_rawmidi_substream_t* substream;
+	} ports[0x10];
+	int seen_f5;
+	int current_port;
+};
+
+static void snd_usbmidi_do_output(snd_usb_midi_out_endpoint_t* 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, int flags)
+{
+	int err = usb_submit_urb(urb, flags);
+	if (err < 0 && err != -ENODEV)
+		snd_printk(KERN_ERR "usb_submit_urb: %d\n", err);
+	return err;
+}
+
+/*
+ * Error handling for URB completion functions.
+ */
+static int snd_usbmidi_urb_error(int status)
+{
+	if (status == -ENOENT)
+		return status; /* killed */
+	if (status == -EILSEQ ||
+	    status == -ECONNRESET ||
+	    status == -ETIMEDOUT)
+		return -ENODEV; /* device removed/shutdown */
+	snd_printk(KERN_ERR "urb status %d\n", status);
+	return 0; /* continue */
+}
+
+/*
+ * Receives a chunk of MIDI data.
+ */
+static void snd_usbmidi_input_data(snd_usb_midi_in_endpoint_t* ep, int portidx,
+				   uint8_t* data, int length)
+{
+	usbmidi_in_port_t* port = &ep->ports[portidx];
+
+	if (!port->substream) {
+		snd_printd("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(" %02x", *data);
+	printk(" ]\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 pt_regs *regs)
+{
+	snd_usb_midi_in_endpoint_t* 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 {
+		if (snd_usbmidi_urb_error(urb->status) < 0)
+			return;
+	}
+
+	if (usb_pipe_needs_resubmit(urb->pipe)) {
+		urb->dev = ep->umidi->chip->dev;
+		snd_usbmidi_submit_urb(urb, GFP_ATOMIC);
+	}
+}
+
+static void snd_usbmidi_out_urb_complete(struct urb* urb, struct pt_regs *regs)
+{
+	snd_usb_midi_out_endpoint_t* ep = urb->context;
+
+	spin_lock(&ep->buffer_lock);
+	ep->urb_active = 0;
+	spin_unlock(&ep->buffer_lock);
+	if (urb->status < 0) {
+		if (snd_usbmidi_urb_error(urb->status) < 0)
+			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(snd_usb_midi_out_endpoint_t* ep)
+{
+	struct urb* urb = ep->urb;
+	unsigned long flags;
+
+	spin_lock_irqsave(&ep->buffer_lock, flags);
+	if (ep->urb_active || ep->umidi->chip->shutdown) {
+		spin_unlock_irqrestore(&ep->buffer_lock, flags);
+		return;
+	}
+
+	urb->transfer_buffer_length = 0;
+	ep->umidi->usb_protocol_ops->output(ep);
+
+	if (urb->transfer_buffer_length > 0) {
+		dump_urb("sending", urb->transfer_buffer,
+			 urb->transfer_buffer_length);
+		urb->dev = ep->umidi->chip->dev;
+		ep->urb_active = snd_usbmidi_submit_urb(urb, GFP_ATOMIC) >= 0;
+	}
+	spin_unlock_irqrestore(&ep->buffer_lock, flags);
+}
+
+static void snd_usbmidi_out_tasklet(unsigned long data)
+{
+	snd_usb_midi_out_endpoint_t* ep = (snd_usb_midi_out_endpoint_t *) data;
+
+	snd_usbmidi_do_output(ep);
+}
+
+/* helper function to send static data that may not DMA-able */
+static int send_bulk_static_data(snd_usb_midi_out_endpoint_t* ep,
+				 const void *data, int len)
+{
+	int err;
+	void *buf = kmalloc(len, GFP_KERNEL);
+	if (!buf)
+		return -ENOMEM;
+	memcpy(buf, data, len);
+	dump_urb("sending", buf, len);
+	err = usb_bulk_msg(ep->umidi->chip->dev, ep->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(snd_usb_midi_in_endpoint_t* 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(snd_usb_midi_in_endpoint_t* 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);
+		}
+}
+
+/*
+ * 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(usbmidi_out_port_t* 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(snd_usb_midi_out_endpoint_t* ep)
+{
+	struct urb* urb = ep->urb;
+	int p;
+
+	/* FIXME: lower-numbered ports can starve higher-numbered ports */
+	for (p = 0; p < 0x10; ++p) {
+		usbmidi_out_port_t* 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 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 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,
+};
+
+/*
+ * 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(snd_usb_midi_in_endpoint_t* 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(snd_usb_midi_out_endpoint_t* ep)
+{
+	uint8_t* transfer_buffer;
+	int count;
+
+	if (!ep->ports[0].active)
+		return;
+	transfer_buffer = ep->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;
+	ep->urb->transfer_buffer_length = 2 + count;
+}
+
+static struct usb_protocol_ops snd_usbmidi_novation_ops = {
+	.input = snd_usbmidi_novation_input,
+	.output = snd_usbmidi_novation_output,
+};
+
+/*
+ * Mark of the Unicorn USB MIDI protocol: raw MIDI.
+ */
+
+static void snd_usbmidi_motu_input(snd_usb_midi_in_endpoint_t* ep,
+				   uint8_t* buffer, int buffer_length)
+{
+	snd_usbmidi_input_data(ep, 0, buffer, buffer_length);
+}
+
+static void snd_usbmidi_motu_output(snd_usb_midi_out_endpoint_t* ep)
+{
+	int count;
+
+	if (!ep->ports[0].active)
+		return;
+	count = snd_rawmidi_transmit(ep->ports[0].substream,
+				     ep->urb->transfer_buffer,
+				     ep->max_transfer);
+	if (count < 1) {
+		ep->ports[0].active = 0;
+		return;
+	}
+	ep->urb->transfer_buffer_length = count;
+}
+
+static struct usb_protocol_ops snd_usbmidi_motu_ops = {
+	.input = snd_usbmidi_motu_input,
+	.output = snd_usbmidi_motu_output,
+};
+
+/*
+ * Emagic USB MIDI protocol: raw MIDI with "F5 xx" port switching.
+ */
+
+static void snd_usbmidi_emagic_init_out(snd_usb_midi_out_endpoint_t* 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(snd_usb_midi_out_endpoint_t* 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(snd_usb_midi_in_endpoint_t* ep,
+				     uint8_t* buffer, int buffer_length)
+{
+	/* ignore padding bytes at end of buffer */
+	while (buffer_length > 0 && buffer[buffer_length - 1] == 0xff)
+		--buffer_length;
+
+	/* handle F5 at end of last buffer */
+	if (ep->seen_f5)
+		goto switch_port;
+
+	while (buffer_length > 0) {
+		int i;
+
+		/* 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(snd_usb_midi_out_endpoint_t* ep)
+{
+	int port0 = ep->current_port;
+	uint8_t* buf = ep->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;
+		usbmidi_out_port_t* 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;
+		}
+	}
+	ep->urb->transfer_buffer_length = ep->max_transfer - buf_free;
+}
+
+static 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 int snd_usbmidi_output_open(snd_rawmidi_substream_t* substream)
+{
+	snd_usb_midi_t* umidi = substream->rmidi->private_data;
+	usbmidi_out_port_t* 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 0;
+}
+
+static int snd_usbmidi_output_close(snd_rawmidi_substream_t* substream)
+{
+	return 0;
+}
+
+static void snd_usbmidi_output_trigger(snd_rawmidi_substream_t* substream, int up)
+{
+	usbmidi_out_port_t* port = (usbmidi_out_port_t*)substream->runtime->private_data;
+
+	port->active = up;
+	if (up) {
+		if (port->ep->umidi->chip->shutdown) {
+			/* 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_hi_schedule(&port->ep->tasklet);
+	}
+}
+
+static int snd_usbmidi_input_open(snd_rawmidi_substream_t* substream)
+{
+	return 0;
+}
+
+static int snd_usbmidi_input_close(snd_rawmidi_substream_t* substream)
+{
+	return 0;
+}
+
+static void snd_usbmidi_input_trigger(snd_rawmidi_substream_t* substream, int up)
+{
+	snd_usb_midi_t* umidi = substream->rmidi->private_data;
+
+	if (up)
+		set_bit(substream->number, &umidi->input_triggered);
+	else
+		clear_bit(substream->number, &umidi->input_triggered);
+}
+
+static snd_rawmidi_ops_t snd_usbmidi_output_ops = {
+	.open = snd_usbmidi_output_open,
+	.close = snd_usbmidi_output_close,
+	.trigger = snd_usbmidi_output_trigger,
+};
+
+static snd_rawmidi_ops_t snd_usbmidi_input_ops = {
+	.open = snd_usbmidi_input_open,
+	.close = snd_usbmidi_input_close,
+	.trigger = snd_usbmidi_input_trigger
+};
+
+/*
+ * Frees an input endpoint.
+ * May be called when ep hasn't been initialized completely.
+ */
+static void snd_usbmidi_in_endpoint_delete(snd_usb_midi_in_endpoint_t* ep)
+{
+	if (ep->urb) {
+		kfree(ep->urb->transfer_buffer);
+		usb_free_urb(ep->urb);
+	}
+	kfree(ep);
+}
+
+/*
+ * Creates an input endpoint.
+ */
+static int snd_usbmidi_in_endpoint_create(snd_usb_midi_t* umidi,
+					  snd_usb_midi_endpoint_info_t* ep_info,
+					  snd_usb_midi_endpoint_t* rep)
+{
+	snd_usb_midi_in_endpoint_t* ep;
+	void* buffer;
+	unsigned int pipe;
+	int length;
+
+	rep->in = NULL;
+	ep = kcalloc(1, sizeof(*ep), GFP_KERNEL);
+	if (!ep)
+		return -ENOMEM;
+	ep->umidi = umidi;
+
+	ep->urb = usb_alloc_urb(0, GFP_KERNEL);
+	if (!ep->urb) {
+		snd_usbmidi_in_endpoint_delete(ep);
+		return -ENOMEM;
+	}
+	if (ep_info->in_interval)
+		pipe = usb_rcvintpipe(umidi->chip->dev, ep_info->in_ep);
+	else
+		pipe = usb_rcvbulkpipe(umidi->chip->dev, ep_info->in_ep);
+	length = usb_maxpacket(umidi->chip->dev, pipe, 0);
+	buffer = kmalloc(length, GFP_KERNEL);
+	if (!buffer) {
+		snd_usbmidi_in_endpoint_delete(ep);
+		return -ENOMEM;
+	}
+	if (ep_info->in_interval)
+		usb_fill_int_urb(ep->urb, umidi->chip->dev, pipe, buffer, length,
+				 snd_usb_complete_callback(snd_usbmidi_in_urb_complete),
+				 ep, ep_info->in_interval);
+	else
+		usb_fill_bulk_urb(ep->urb, umidi->chip->dev, pipe, buffer, length,
+				  snd_usb_complete_callback(snd_usbmidi_in_urb_complete),
+				  ep);
+
+	rep->in = ep;
+	return 0;
+}
+
+static unsigned int snd_usbmidi_count_bits(unsigned int x)
+{
+	unsigned int bits = 0;
+
+	for (; x; x >>= 1)
+		bits += x & 1;
+	return bits;
+}
+
+/*
+ * Frees an output endpoint.
+ * May be called when ep hasn't been initialized completely.
+ */
+static void snd_usbmidi_out_endpoint_delete(snd_usb_midi_out_endpoint_t* ep)
+{
+	if (ep->tasklet.func)
+		tasklet_kill(&ep->tasklet);
+	if (ep->urb) {
+		kfree(ep->urb->transfer_buffer);
+		usb_free_urb(ep->urb);
+	}
+	kfree(ep);
+}
+
+/*
+ * Creates an output endpoint, and initializes output ports.
+ */
+static int snd_usbmidi_out_endpoint_create(snd_usb_midi_t* umidi,
+					   snd_usb_midi_endpoint_info_t* ep_info,
+			 		   snd_usb_midi_endpoint_t* rep)
+{
+	snd_usb_midi_out_endpoint_t* ep;
+	int i;
+	unsigned int pipe;
+	void* buffer;
+
+	rep->out = NULL;
+	ep = kcalloc(1, sizeof(*ep), GFP_KERNEL);
+	if (!ep)
+		return -ENOMEM;
+	ep->umidi = umidi;
+
+	ep->urb = usb_alloc_urb(0, GFP_KERNEL);
+	if (!ep->urb) {
+		snd_usbmidi_out_endpoint_delete(ep);
+		return -ENOMEM;
+	}
+	/* we never use interrupt output pipes */
+	pipe = usb_sndbulkpipe(umidi->chip->dev, ep_info->out_ep);
+	ep->max_transfer = usb_maxpacket(umidi->chip->dev, pipe, 1);
+	buffer = kmalloc(ep->max_transfer, GFP_KERNEL);
+	if (!buffer) {
+		snd_usbmidi_out_endpoint_delete(ep);
+		return -ENOMEM;
+	}
+	usb_fill_bulk_urb(ep->urb, umidi->chip->dev, pipe, buffer,
+			  ep->max_transfer,
+			  snd_usb_complete_callback(snd_usbmidi_out_urb_complete), ep);
+
+	spin_lock_init(&ep->buffer_lock);
+	tasklet_init(&ep->tasklet, snd_usbmidi_out_tasklet, (unsigned long)ep);
+
+	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;
+}
+
+/*
+ * Frees everything.
+ */
+static void snd_usbmidi_free(snd_usb_midi_t* umidi)
+{
+	int i;
+
+	for (i = 0; i < MIDI_MAX_ENDPOINTS; ++i) {
+		snd_usb_midi_endpoint_t* ep = &umidi->endpoints[i];
+		if (ep->out)
+			snd_usbmidi_out_endpoint_delete(ep->out);
+		if (ep->in)
+			snd_usbmidi_in_endpoint_delete(ep->in);
+	}
+	kfree(umidi);
+}
+
+/*
+ * Unlinks all URBs (must be done before the usb_device is deleted).
+ */
+void snd_usbmidi_disconnect(struct list_head* p, struct usb_driver *driver)
+{
+	snd_usb_midi_t* umidi;
+	int i;
+
+	umidi = list_entry(p, snd_usb_midi_t, list);
+	for (i = 0; i < MIDI_MAX_ENDPOINTS; ++i) {
+		snd_usb_midi_endpoint_t* ep = &umidi->endpoints[i];
+		if (ep->out && ep->out->urb) {
+			usb_kill_urb(ep->out->urb);
+			if (umidi->usb_protocol_ops->finish_out_endpoint)
+				umidi->usb_protocol_ops->finish_out_endpoint(ep->out);
+		}
+		if (ep->in && ep->in->urb)
+			usb_kill_urb(ep->in->urb);
+	}
+}
+
+static void snd_usbmidi_rawmidi_free(snd_rawmidi_t* rmidi)
+{
+	snd_usb_midi_t* umidi = rmidi->private_data;
+	snd_usbmidi_free(umidi);
+}
+
+static snd_rawmidi_substream_t* snd_usbmidi_find_substream(snd_usb_midi_t* umidi,
+							   int stream, int number)
+{
+	struct list_head* list;
+
+	list_for_each(list, &umidi->rmidi->streams[stream].substreams) {
+		snd_rawmidi_substream_t* substream = list_entry(list, snd_rawmidi_substream_t, 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 {
+	__u16 vendor;
+	__u16 product;
+	int port;
+	const char *name_format;
+} snd_usbmidi_port_names[] = {
+	/* Roland UA-100 */
+	{0x0582, 0x0000, 2, "%s Control"},
+	/* Roland SC-8850 */
+	{0x0582, 0x0003, 0, "%s Part A"},
+	{0x0582, 0x0003, 1, "%s Part B"},
+	{0x0582, 0x0003, 2, "%s Part C"},
+	{0x0582, 0x0003, 3, "%s Part D"},
+	{0x0582, 0x0003, 4, "%s MIDI 1"},
+	{0x0582, 0x0003, 5, "%s MIDI 2"},
+	/* Roland U-8 */
+	{0x0582, 0x0004, 0, "%s MIDI"},
+	{0x0582, 0x0004, 1, "%s Control"},
+	/* Roland SC-8820 */
+	{0x0582, 0x0007, 0, "%s Part A"},
+	{0x0582, 0x0007, 1, "%s Part B"},
+	{0x0582, 0x0007, 2, "%s MIDI"},
+	/* Roland SK-500 */
+	{0x0582, 0x000b, 0, "%s Part A"},
+	{0x0582, 0x000b, 1, "%s Part B"},
+	{0x0582, 0x000b, 2, "%s MIDI"},
+	/* Roland SC-D70 */
+	{0x0582, 0x000c, 0, "%s Part A"},
+	{0x0582, 0x000c, 1, "%s Part B"},
+	{0x0582, 0x000c, 2, "%s MIDI"},
+	/* Edirol UM-880 */
+	{0x0582, 0x0014, 8, "%s Control"},
+	/* Edirol SD-90 */
+	{0x0582, 0x0016, 0, "%s Part A"},
+	{0x0582, 0x0016, 1, "%s Part B"},
+	{0x0582, 0x0016, 2, "%s MIDI 1"},
+	{0x0582, 0x0016, 3, "%s MIDI 2"},
+	/* Edirol UM-550 */
+	{0x0582, 0x0023, 5, "%s Control"},
+	/* Edirol SD-20 */
+	{0x0582, 0x0027, 0, "%s Part A"},
+	{0x0582, 0x0027, 1, "%s Part B"},
+	{0x0582, 0x0027, 2, "%s MIDI"},
+	/* Edirol SD-80 */
+	{0x0582, 0x0029, 0, "%s Part A"},
+	{0x0582, 0x0029, 1, "%s Part B"},
+	{0x0582, 0x0029, 2, "%s MIDI 1"},
+	{0x0582, 0x0029, 3, "%s MIDI 2"},
+	/* Edirol UA-700 */
+	{0x0582, 0x002b, 0, "%s MIDI"},
+	{0x0582, 0x002b, 1, "%s Control"},
+	/* Roland VariOS */
+	{0x0582, 0x002f, 0, "%s MIDI"},
+	{0x0582, 0x002f, 1, "%s External MIDI"},
+	{0x0582, 0x002f, 2, "%s Sync"},
+	/* Edirol PCR */
+	{0x0582, 0x0033, 0, "%s MIDI"},
+	{0x0582, 0x0033, 1, "%s 1"},
+	{0x0582, 0x0033, 2, "%s 2"},
+	/* BOSS GS-10 */
+	{0x0582, 0x003b, 0, "%s MIDI"},
+	{0x0582, 0x003b, 1, "%s Control"},
+	/* Edirol UA-1000 */
+	{0x0582, 0x0044, 0, "%s MIDI"},
+	{0x0582, 0x0044, 1, "%s Control"},
+	/* Edirol UR-80 */
+	{0x0582, 0x0048, 0, "%s MIDI"},
+	{0x0582, 0x0048, 1, "%s 1"},
+	{0x0582, 0x0048, 2, "%s 2"},
+	/* Edirol PCR-A */
+	{0x0582, 0x004d, 0, "%s MIDI"},
+	{0x0582, 0x004d, 1, "%s 1"},
+	{0x0582, 0x004d, 2, "%s 2"},
+	/* M-Audio MidiSport 8x8 */
+	{0x0763, 0x1031, 8, "%s Control"},
+	{0x0763, 0x1033, 8, "%s Control"},
+	/* MOTU Fastlane */
+	{0x07fd, 0x0001, 0, "%s MIDI A"},
+	{0x07fd, 0x0001, 1, "%s MIDI B"},
+	/* Emagic Unitor8/AMT8/MT4 */
+	{0x086a, 0x0001, 8, "%s Broadcast"},
+	{0x086a, 0x0002, 8, "%s Broadcast"},
+	{0x086a, 0x0003, 4, "%s Broadcast"},
+};
+
+static void snd_usbmidi_init_substream(snd_usb_midi_t* umidi,
+				       int stream, int number,
+				       snd_rawmidi_substream_t** rsubstream)
+{
+	int i;
+	__u16 vendor, product;
+	const char *name_format;
+
+	snd_rawmidi_substream_t* substream = snd_usbmidi_find_substream(umidi, stream, number);
+	if (!substream) {
+		snd_printd(KERN_ERR "substream %d:%d not found\n", stream, number);
+		return;
+	}
+
+	/* TODO: read port name from jack descriptor */
+	name_format = "%s MIDI %d";
+	vendor = le16_to_cpu(umidi->chip->dev->descriptor.idVendor);
+	product = le16_to_cpu(umidi->chip->dev->descriptor.idProduct);
+	for (i = 0; i < ARRAY_SIZE(snd_usbmidi_port_names); ++i) {
+		if (snd_usbmidi_port_names[i].vendor == vendor &&
+		    snd_usbmidi_port_names[i].product == product &&
+		    snd_usbmidi_port_names[i].port == number) {
+			name_format = snd_usbmidi_port_names[i].name_format;
+			break;
+		}
+	}
+	snprintf(substream->name, sizeof(substream->name),
+		 name_format, umidi->chip->card->shortname, number + 1);
+
+	*rsubstream = substream;
+}
+
+/*
+ * Creates the endpoints and their ports.
+ */
+static int snd_usbmidi_create_endpoints(snd_usb_midi_t* umidi,
+					snd_usb_midi_endpoint_info_t* 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;
+			}
+		}
+	}
+	snd_printdd(KERN_INFO "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(snd_usb_midi_t* umidi,
+			   	   snd_usb_midi_endpoint_info_t* 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 == HEADER)
+		snd_printdd(KERN_INFO "MIDIStreaming version %02x.%02x\n",
+			    ms_header->bcdMSC[1], ms_header->bcdMSC[0]);
+	else
+		snd_printk(KERN_WARNING "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 ((ep->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) != USB_ENDPOINT_XFER_BULK &&
+		    (ep->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) != USB_ENDPOINT_XFER_INT)
+			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 != MS_GENERAL)
+			continue;
+		if ((ep->bEndpointAddress & USB_ENDPOINT_DIR_MASK) == USB_DIR_OUT) {
+			if (endpoints[epidx].out_ep) {
+				if (++epidx >= MIDI_MAX_ENDPOINTS) {
+					snd_printk(KERN_WARNING "too many endpoints\n");
+					break;
+				}
+			}
+			endpoints[epidx].out_ep = ep->bEndpointAddress & USB_ENDPOINT_NUMBER_MASK;
+			if ((ep->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) == USB_ENDPOINT_XFER_INT)
+				endpoints[epidx].out_interval = ep->bInterval;
+			endpoints[epidx].out_cables = (1 << ms_ep->bNumEmbMIDIJack) - 1;
+			snd_printdd(KERN_INFO "EP %02X: %d jack(s)\n",
+				    ep->bEndpointAddress, ms_ep->bNumEmbMIDIJack);
+		} else {
+			if (endpoints[epidx].in_ep) {
+				if (++epidx >= MIDI_MAX_ENDPOINTS) {
+					snd_printk(KERN_WARNING "too many endpoints\n");
+					break;
+				}
+			}
+			endpoints[epidx].in_ep = ep->bEndpointAddress & USB_ENDPOINT_NUMBER_MASK;
+			if ((ep->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) == USB_ENDPOINT_XFER_INT)
+				endpoints[epidx].in_interval = ep->bInterval;
+			endpoints[epidx].in_cables = (1 << ms_ep->bNumEmbMIDIJack) - 1;
+			snd_printdd(KERN_INFO "EP %02X: %d jack(s)\n",
+				    ep->bEndpointAddress, ms_ep->bNumEmbMIDIJack);
+		}
+	}
+	return 0;
+}
+
+/*
+ * On Roland devices, use the second alternate setting to be able to use
+ * the interrupt input endpoint.
+ */
+static void snd_usbmidi_switch_roland_altsetting(snd_usb_midi_t* 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 (intfd->bNumEndpoints != 2 ||
+	    (get_endpoint(hostif, 0)->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) != USB_ENDPOINT_XFER_BULK ||
+	    (get_endpoint(hostif, 1)->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) != USB_ENDPOINT_XFER_INT)
+		return;
+
+	snd_printdd(KERN_INFO "switching to altsetting %d with int ep\n",
+		    intfd->bAlternateSetting);
+	usb_set_interface(umidi->chip->dev, intfd->bInterfaceNumber,
+			  intfd->bAlternateSetting);
+}
+
+/*
+ * Try to find any usable endpoints in the interface.
+ */
+static int snd_usbmidi_detect_endpoints(snd_usb_midi_t* umidi,
+					snd_usb_midi_endpoint_info_t* 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 (le16_to_cpu(umidi->chip->dev->descriptor.idVendor) == 0x0582)
+		snd_usbmidi_switch_roland_altsetting(umidi);
+
+	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 ((epd->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) != USB_ENDPOINT_XFER_BULK &&
+		    (epd->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) != USB_ENDPOINT_XFER_INT)
+			continue;
+		if (out_eps < max_endpoints &&
+		    (epd->bEndpointAddress & USB_ENDPOINT_DIR_MASK) == USB_DIR_OUT) {
+			endpoint[out_eps].out_ep = epd->bEndpointAddress & USB_ENDPOINT_NUMBER_MASK;
+			if ((epd->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) == USB_ENDPOINT_XFER_INT)
+				endpoint[out_eps].out_interval = epd->bInterval;
+			++out_eps;
+		}
+		if (in_eps < max_endpoints &&
+		    (epd->bEndpointAddress & USB_ENDPOINT_DIR_MASK) == USB_DIR_IN) {
+			endpoint[in_eps].in_ep = epd->bEndpointAddress & USB_ENDPOINT_NUMBER_MASK;
+			if ((epd->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) == USB_ENDPOINT_XFER_INT)
+				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(snd_usb_midi_t* umidi,
+						 snd_usb_midi_endpoint_info_t* 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(snd_usb_midi_t* umidi,
+				     snd_usb_midi_endpoint_info_t* 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] == CS_AUDIO_INTERFACE) {
+			if (cs_desc[2] == MIDI_IN_JACK)
+				endpoint->in_cables = (endpoint->in_cables << 1) | 1;
+			else if (cs_desc[2] == 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);
+}
+
+/*
+ * Creates the endpoints and their ports for Midiman devices.
+ */
+static int snd_usbmidi_create_endpoints_midiman(snd_usb_midi_t* umidi,
+						snd_usb_midi_endpoint_info_t* endpoint)
+{
+	snd_usb_midi_endpoint_info_t 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)) {
+		snd_printdd(KERN_ERR "not enough endpoints\n");
+		return -ENOENT;
+	}
+
+	epd = get_endpoint(hostif, 0);
+	if ((epd->bEndpointAddress & USB_ENDPOINT_DIR_MASK) != USB_DIR_IN ||
+	    (epd->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) != USB_ENDPOINT_XFER_INT) {
+		snd_printdd(KERN_ERR "endpoint[0] isn't interrupt\n");
+		return -ENXIO;
+	}
+	epd = get_endpoint(hostif, 2);
+	if ((epd->bEndpointAddress & USB_ENDPOINT_DIR_MASK) != USB_DIR_OUT ||
+	    (epd->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) != USB_ENDPOINT_XFER_BULK) {
+		snd_printdd(KERN_ERR "endpoint[2] isn't bulk output\n");
+		return -ENXIO;
+	}
+	if (endpoint->out_cables > 0x0001) {
+		epd = get_endpoint(hostif, 4);
+		if ((epd->bEndpointAddress & USB_ENDPOINT_DIR_MASK) != USB_DIR_OUT ||
+		    (epd->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) != USB_ENDPOINT_XFER_BULK) {
+			snd_printdd(KERN_ERR "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_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 int snd_usbmidi_create_rawmidi(snd_usb_midi_t* umidi,
+				      int out_ports, int in_ports)
+{
+	snd_rawmidi_t* rmidi;
+	int err;
+
+	err = snd_rawmidi_new(umidi->chip->card, "USB MIDI",
+			      umidi->chip->next_midi_device++,
+			      out_ports, in_ports, &rmidi);
+	if (err < 0)
+		return err;
+	strcpy(rmidi->name, umidi->chip->card->shortname);
+	rmidi->info_flags = SNDRV_RAWMIDI_INFO_OUTPUT |
+			    SNDRV_RAWMIDI_INFO_INPUT |
+			    SNDRV_RAWMIDI_INFO_DUPLEX;
+	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)
+{
+	snd_usb_midi_t* umidi;
+	int i;
+
+	umidi = list_entry(p, snd_usb_midi_t, list);
+	for (i = 0; i < MIDI_MAX_ENDPOINTS; ++i) {
+		snd_usb_midi_endpoint_t* ep = &umidi->endpoints[i];
+		if (ep->in)
+			usb_kill_urb(ep->in->urb);
+	}
+}
+
+static void snd_usbmidi_input_start_ep(snd_usb_midi_in_endpoint_t* ep)
+{
+	if (ep) {
+		struct urb* urb = ep->urb;
+		urb->dev = ep->umidi->chip->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)
+{
+	snd_usb_midi_t* umidi;
+	int i;
+
+	umidi = list_entry(p, snd_usb_midi_t, list);
+	for (i = 0; i < MIDI_MAX_ENDPOINTS; ++i)
+		snd_usbmidi_input_start_ep(umidi->endpoints[i].in);
+}
+
+/*
+ * Creates and registers everything needed for a MIDI streaming interface.
+ */
+int snd_usb_create_midi_interface(snd_usb_audio_t* chip,
+				  struct usb_interface* iface,
+				  const snd_usb_audio_quirk_t* quirk)
+{
+	snd_usb_midi_t* umidi;
+	snd_usb_midi_endpoint_info_t endpoints[MIDI_MAX_ENDPOINTS];
+	int out_ports, in_ports;
+	int i, err;
+
+	umidi = kcalloc(1, sizeof(*umidi), GFP_KERNEL);
+	if (!umidi)
+		return -ENOMEM;
+	umidi->chip = chip;
+	umidi->iface = iface;
+	umidi->quirk = quirk;
+	umidi->usb_protocol_ops = &snd_usbmidi_standard_ops;
+
+	/* detect the endpoint(s) to use */
+	memset(endpoints, 0, sizeof(endpoints));
+	if (!quirk) {
+		err = snd_usbmidi_get_ms_info(umidi, endpoints);
+	} else {
+		switch (quirk->type) {
+		case QUIRK_MIDI_FIXED_ENDPOINT:
+			memcpy(&endpoints[0], quirk->data,
+			       sizeof(snd_usb_midi_endpoint_info_t));
+			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_MIDIMAN:
+			umidi->usb_protocol_ops = &snd_usbmidi_midiman_ops;
+			memcpy(&endpoints[0], quirk->data,
+			       sizeof(snd_usb_midi_endpoint_info_t));
+			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_MOTU:
+			umidi->usb_protocol_ops = &snd_usbmidi_motu_ops;
+			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(snd_usb_midi_endpoint_info_t));
+			err = snd_usbmidi_detect_endpoints(umidi, &endpoints[0], 1);
+			break;
+		default:
+			snd_printd(KERN_ERR "invalid quirk type %d\n", quirk->type);
+			err = -ENXIO;
+			break;
+		}
+	}
+	if (err < 0) {
+		kfree(umidi);
+		return err;
+	}
+
+	/* create rawmidi device */
+	out_ports = 0;
+	in_ports = 0;
+	for (i = 0; i < MIDI_MAX_ENDPOINTS; ++i) {
+		out_ports += snd_usbmidi_count_bits(endpoints[i].out_cables);
+		in_ports += snd_usbmidi_count_bits(endpoints[i].in_cables);
+	}
+	err = snd_usbmidi_create_rawmidi(umidi, out_ports, in_ports);
+	if (err < 0) {
+		kfree(umidi);
+		return err;
+	}
+
+	/* 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) {
+		snd_usbmidi_free(umidi);
+		return err;
+	}
+
+	list_add(&umidi->list, &umidi->chip->midi_list);
+
+	for (i = 0; i < MIDI_MAX_ENDPOINTS; ++i)
+		snd_usbmidi_input_start_ep(umidi->endpoints[i].in);
+	return 0;
+}
+
+EXPORT_SYMBOL(snd_usb_create_midi_interface);
+EXPORT_SYMBOL(snd_usbmidi_input_stop);
+EXPORT_SYMBOL(snd_usbmidi_input_start);
+EXPORT_SYMBOL(snd_usbmidi_disconnect);
diff --git a/sound/usb/usbmixer.c b/sound/usb/usbmixer.c
new file mode 100644
index 0000000..5f19069
--- /dev/null
+++ b/sound/usb/usbmixer.c
@@ -0,0 +1,1545 @@
+/*
+ *   (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
+ *
+ */
+
+#include <sound/driver.h>
+#include <linux/bitops.h>
+#include <linux/init.h>
+#include <linux/list.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <linux/usb.h>
+#include <sound/core.h>
+#include <sound/control.h>
+
+#include "usbaudio.h"
+
+
+/*
+ */
+
+/* ignore error from controls - for debugging */
+/* #define IGNORE_CTL_ERROR */
+
+typedef struct usb_mixer_build mixer_build_t;
+typedef struct usb_audio_term usb_audio_term_t;
+typedef struct usb_mixer_elem_info usb_mixer_elem_info_t;
+
+
+struct usb_audio_term {
+	int id;
+	int type;
+	int channels;
+	unsigned int chconfig;
+	int name;
+};
+
+struct usbmix_name_map;
+
+struct usb_mixer_build {
+	snd_usb_audio_t *chip;
+	unsigned char *buffer;
+	unsigned int buflen;
+	unsigned int ctrlif;
+	unsigned short vendor;
+	unsigned short product;
+	DECLARE_BITMAP(unitbitmap, 32*32);
+	usb_audio_term_t oterm;
+	const struct usbmix_name_map *map;
+};
+
+struct usb_mixer_elem_info {
+	snd_usb_audio_t *chip;
+	unsigned int ctrlif;
+	unsigned int id;
+	unsigned int control;	/* CS or ICN (high byte) */
+	unsigned int cmask; /* channel mask bitmap: 0 = master */
+	int channels;
+	int val_type;
+	int min, max, res;
+	unsigned int initialized: 1;
+};
+
+
+enum {
+	USB_FEATURE_NONE = 0,
+	USB_FEATURE_MUTE = 1,
+	USB_FEATURE_VOLUME,
+	USB_FEATURE_BASS,
+	USB_FEATURE_MID,
+	USB_FEATURE_TREBLE,
+	USB_FEATURE_GEQ,
+	USB_FEATURE_AGC,
+	USB_FEATURE_DELAY,
+	USB_FEATURE_BASSBOOST,
+	USB_FEATURE_LOUDNESS
+};
+
+enum {
+	USB_MIXER_BOOLEAN,
+	USB_MIXER_INV_BOOLEAN,
+	USB_MIXER_S8,
+	USB_MIXER_U8,
+	USB_MIXER_S16,
+	USB_MIXER_U16,
+};
+
+enum {
+	USB_PROC_UPDOWN = 1,
+	USB_PROC_UPDOWN_SWITCH = 1,
+	USB_PROC_UPDOWN_MODE_SEL = 2,
+
+	USB_PROC_PROLOGIC = 2,
+	USB_PROC_PROLOGIC_SWITCH = 1,
+	USB_PROC_PROLOGIC_MODE_SEL = 2,
+
+	USB_PROC_3DENH = 3,
+	USB_PROC_3DENH_SWITCH = 1,
+	USB_PROC_3DENH_SPACE = 2,
+
+	USB_PROC_REVERB = 4,
+	USB_PROC_REVERB_SWITCH = 1,
+	USB_PROC_REVERB_LEVEL = 2,
+	USB_PROC_REVERB_TIME = 3,
+	USB_PROC_REVERB_DELAY = 4,
+
+	USB_PROC_CHORUS = 5,
+	USB_PROC_CHORUS_SWITCH = 1,
+	USB_PROC_CHORUS_LEVEL = 2,
+	USB_PROC_CHORUS_RATE = 3,
+	USB_PROC_CHORUS_DEPTH = 4,
+
+	USB_PROC_DCR = 6,
+	USB_PROC_DCR_SWITCH = 1,
+	USB_PROC_DCR_RATIO = 2,
+	USB_PROC_DCR_MAX_AMP = 3,
+	USB_PROC_DCR_THRESHOLD = 4,
+	USB_PROC_DCR_ATTACK = 5,
+	USB_PROC_DCR_RELEASE = 6,
+};
+
+#define MAX_CHANNELS	10	/* max logical channels */
+
+
+/*
+ * 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 "usbmixer_maps.c"
+
+/* get the mapped name if the unit matches */
+static int check_mapped_name(mixer_build_t *state, int unitid, int control, char *buf, int buflen)
+{
+	const struct usbmix_name_map *p;
+
+	if (! state->map)
+		return 0;
+
+	for (p = state->map; p->id; p++) {
+		if (p->id == unitid && p->name &&
+		    (! control || ! p->control || control == p->control)) {
+			buflen--;
+			return strlcpy(buf, p->name, buflen);
+		}
+	}
+	return 0;
+}
+
+/* check whether the control should be ignored */
+static int check_ignored_ctl(mixer_build_t *state, int unitid, int control)
+{
+	const struct usbmix_name_map *p;
+
+	if (! state->map)
+		return 0;
+	for (p = state->map; p->id; p++) {
+		if (p->id == unitid && ! p->name &&
+		    (! control || ! p->control || control == p->control)) {
+			// printk("ignored control %d:%d\n", unitid, control);
+			return 1;
+		}
+	}
+	return 0;
+}
+
+/*
+ * find an audio control unit with the given unit id
+ */
+static void *find_audio_control_unit(mixer_build_t *state, unsigned char unit)
+{
+	unsigned char *p;
+
+	p = NULL;
+	while ((p = snd_usb_find_desc(state->buffer, state->buflen, p,
+				      USB_DT_CS_INTERFACE)) != NULL) {
+		if (p[0] >= 4 && p[2] >= INPUT_TERMINAL && p[2] <= EXTENSION_UNIT && p[3] == unit)
+			return p;
+	}
+	return NULL;
+}
+
+
+/*
+ * copy a string with the given id
+ */
+static int snd_usb_copy_string_desc(mixer_build_t *state, int index, char *buf, int maxlen)
+{
+	int len = usb_string(state->chip->dev, index, buf, maxlen - 1);
+	buf[len] = 0;
+	return len;
+}
+
+/*
+ * convert from the byte/word on usb descriptor to the zero-based integer
+ */
+static int convert_signed_value(usb_mixer_elem_info_t *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(usb_mixer_elem_info_t *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(usb_mixer_elem_info_t *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;
+	else
+		return (val - cval->min) / cval->res;
+}
+
+static int get_abs_value(usb_mixer_elem_info_t *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;
+}
+
+
+/*
+ * retrieve a mixer value
+ */
+
+static int get_ctl_value(usb_mixer_elem_info_t *cval, int request, int validx, int *value_ret)
+{
+	unsigned char buf[2];
+	int val_len = cval->val_type >= USB_MIXER_S16 ? 2 : 1;
+	int timeout = 10;
+
+	while (timeout-- > 0) {
+		if (snd_usb_ctl_msg(cval->chip->dev, usb_rcvctrlpipe(cval->chip->dev, 0),
+				    request,
+				    USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_IN,
+				    validx, cval->ctrlif | (cval->id << 8),
+				    buf, val_len, 100) >= 0) {
+			*value_ret = convert_signed_value(cval, snd_usb_combine_bytes(buf, val_len));
+			return 0;
+		}
+	}
+	snd_printdd(KERN_ERR "cannot get ctl value: req = 0x%x, wValue = 0x%x, wIndex = 0x%x, type = %d\n", request, validx, cval->ctrlif | (cval->id << 8), cval->val_type);
+	return -EINVAL;
+}
+
+static int get_cur_ctl_value(usb_mixer_elem_info_t *cval, int validx, int *value)
+{
+	return get_ctl_value(cval, GET_CUR, validx, value);
+}
+
+/* channel = 0: master, 1 = first channel */
+inline static int get_cur_mix_value(usb_mixer_elem_info_t *cval, int channel, int *value)
+{
+	return get_ctl_value(cval, GET_CUR, (cval->control << 8) | channel, value);
+}
+
+/*
+ * set a mixer value
+ */
+
+static int set_ctl_value(usb_mixer_elem_info_t *cval, int request, int validx, int value_set)
+{
+	unsigned char buf[2];
+	int val_len = cval->val_type >= USB_MIXER_S16 ? 2 : 1;
+	int timeout = 10;
+
+	value_set = convert_bytes_value(cval, value_set);
+	buf[0] = value_set & 0xff;
+	buf[1] = (value_set >> 8) & 0xff;
+	while (timeout -- > 0)
+		if (snd_usb_ctl_msg(cval->chip->dev, usb_sndctrlpipe(cval->chip->dev, 0),
+				    request,
+				    USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_OUT,
+				    validx, cval->ctrlif | (cval->id << 8),
+				    buf, val_len, 100) >= 0)
+			return 0;
+	snd_printdd(KERN_ERR "cannot set ctl value: req = 0x%x, wValue = 0x%x, wIndex = 0x%x, type = %d, data = 0x%x/0x%x\n", request, validx, cval->ctrlif | (cval->id << 8), cval->val_type, buf[0], buf[1]);
+	return -EINVAL;
+}
+
+static int set_cur_ctl_value(usb_mixer_elem_info_t *cval, int validx, int value)
+{
+	return set_ctl_value(cval, SET_CUR, validx, value);
+}
+
+inline static int set_cur_mix_value(usb_mixer_elem_info_t *cval, int channel, int value)
+{
+	return set_ctl_value(cval, SET_CUR, (cval->control << 8) | channel, value);
+}
+
+
+/*
+ * parser routines begin here...
+ */
+
+static int parse_audio_unit(mixer_build_t *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.
+ */
+
+static int add_control_to_empty(snd_card_t *card, snd_kcontrol_t *kctl)
+{
+	int err;
+	while (snd_ctl_find_id(card, &kctl->id))
+		kctl->id.index++;
+	if ((err = snd_ctl_add(card, kctl)) < 0) {
+		snd_printd(KERN_ERR "cannot add control (err = %d)\n", err);
+		snd_ctl_free_one(kctl);
+	}
+	return err;
+}
+
+
+/*
+ * 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(mixer_build_t *state, usb_audio_term_t *iterm,
+			 unsigned char *name, int maxlen, int term_only)
+{
+	struct iterm_name_combo *names;
+
+	if (iterm->name)
+		return snd_usb_copy_string_desc(state, iterm->name, name, maxlen);
+
+	/* virtual type - not a real terminal */
+	if (iterm->type >> 16) {
+		if (term_only)
+			return 0;
+		switch (iterm->type >> 16) {
+		case SELECTOR_UNIT:
+			strcpy(name, "Selector"); return 8;
+		case PROCESSING_UNIT:
+			strcpy(name, "Process Unit"); return 12;
+		case EXTENSION_UNIT:
+			strcpy(name, "Ext Unit"); return 8;
+		case 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;
+}
+
+
+/*
+ * parse the source unit recursively until it reaches to a terminal
+ * or a branched unit.
+ */
+static int check_input_term(mixer_build_t *state, int id, usb_audio_term_t *term)
+{
+	unsigned char *p1;
+
+	memset(term, 0, sizeof(*term));
+	while ((p1 = find_audio_control_unit(state, id)) != NULL) {
+		term->id = id;
+		switch (p1[2]) {
+		case INPUT_TERMINAL:
+			term->type = combine_word(p1 + 4);
+			term->channels = p1[7];
+			term->chconfig = combine_word(p1 + 8);
+			term->name = p1[11];
+			return 0;
+		case FEATURE_UNIT:
+			id = p1[4];
+			break; /* continue to parse */
+		case MIXER_UNIT:
+			term->type = p1[2] << 16; /* virtual type */
+			term->channels = p1[5 + p1[4]];
+			term->chconfig = combine_word(p1 + 6 + p1[4]);
+			term->name = p1[p1[0] - 1];
+			return 0;
+		case SELECTOR_UNIT:
+			/* call recursively to retrieve the channel info */
+			if (check_input_term(state, p1[5], term) < 0)
+				return -ENODEV;
+			term->type = p1[2] << 16; /* virtual type */
+			term->id = id;
+			term->name = p1[9 + p1[0] - 1];
+			return 0;
+		case PROCESSING_UNIT:
+		case EXTENSION_UNIT:
+			if (p1[6] == 1) {
+				id = p1[7];
+				break; /* continue to parse */
+			}
+			term->type = p1[2] << 16; /* virtual type */
+			term->channels = p1[7 + p1[6]];
+			term->chconfig = combine_word(p1 + 8 + p1[6]);
+			term->name = p1[12 + p1[6] + p1[11 + p1[6]]];
+			return 0;
+		default:
+			return -ENODEV;
+		}
+	}
+	return -ENODEV;
+}
+
+
+/*
+ * Feature Unit
+ */
+
+/* feature unit control information */
+struct usb_feature_control_info {
+	const char *name;
+	unsigned int type;	/* control type (mute, volume, etc.) */
+};
+
+static struct usb_feature_control_info audio_feature_info[] = {
+	{ "Mute",		USB_MIXER_INV_BOOLEAN },
+	{ "Volume",		USB_MIXER_S16 },
+	{ "Tone Control - Bass",	USB_MIXER_S8 },
+	{ "Tone Control - Mid",		USB_MIXER_S8 },
+	{ "Tone Control - Treble",	USB_MIXER_S8 },
+	{ "Graphic Equalizer",		USB_MIXER_S8 }, /* FIXME: not implemeted yet */
+	{ "Auto Gain Control",	USB_MIXER_BOOLEAN },
+	{ "Delay Control",	USB_MIXER_U16 },
+	{ "Bass Boost",		USB_MIXER_BOOLEAN },
+	{ "Loudness",		USB_MIXER_BOOLEAN },
+};
+
+
+/* private_free callback */
+static void usb_mixer_elem_free(snd_kcontrol_t *kctl)
+{
+	if (kctl->private_data) {
+		kfree(kctl->private_data);
+		kctl->private_data = NULL;
+	}
+}
+
+
+/*
+ * interface to ALSA control for feature/mixer units
+ */
+
+/*
+ * retrieve the minimum and maximum values for the specified control
+ */
+static int get_min_max(usb_mixer_elem_info_t *cval, int default_min)
+{
+	/* for failsafe */
+	cval->min = default_min;
+	cval->max = cval->min + 1;
+	cval->res = 1;
+
+	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, GET_MAX, (cval->control << 8) | minchn, &cval->max) < 0 ||
+		    get_ctl_value(cval, GET_MIN, (cval->control << 8) | minchn, &cval->min) < 0) {
+			snd_printd(KERN_ERR "%d:%d: cannot get min/max values for control %d (id %d)\n", cval->id, cval->ctrlif, cval->control, cval->id);
+			return -EINVAL;
+		}
+		if (get_ctl_value(cval, GET_RES, (cval->control << 8) | minchn, &cval->res) < 0) {
+			cval->res = 1;
+		} else {
+			int last_valid_res = cval->res;
+
+			while (cval->res > 1) {
+				if (set_ctl_value(cval, SET_RES, (cval->control << 8) | minchn, cval->res / 2) < 0)
+					break;
+				cval->res /= 2;
+			}
+			if (get_ctl_value(cval, GET_RES, (cval->control << 8) | minchn, &cval->res) < 0)
+				cval->res = last_valid_res;
+		}
+		if (cval->res == 0)
+			cval->res = 1;
+		cval->initialized = 1;
+	}
+	return 0;
+}
+
+
+/* get a feature/mixer unit info */
+static int mixer_ctl_feature_info(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t *uinfo)
+{
+	usb_mixer_elem_info_t *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(cval,  0);
+		uinfo->value.integer.min = 0;
+		uinfo->value.integer.max = (cval->max - cval->min) / cval->res;
+	}
+	return 0;
+}
+
+/* get the current value from feature/mixer unit */
+static int mixer_ctl_feature_get(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol)
+{
+	usb_mixer_elem_info_t *cval = kcontrol->private_data;
+	int c, cnt, val, err;
+
+	if (cval->cmask) {
+		cnt = 0;
+		for (c = 0; c < MAX_CHANNELS; c++) {
+			if (cval->cmask & (1 << c)) {
+				err = get_cur_mix_value(cval, c + 1, &val);
+				if (err < 0) {
+					if (cval->chip->ignore_ctl_error) {
+						ucontrol->value.integer.value[0] = cval->min;
+						return 0;
+					}
+					snd_printd(KERN_ERR "cannot get current value for control %d ch %d: err = %d\n", cval->control, c + 1, err);
+					return err;
+				}
+				val = get_relative_value(cval, val);
+				ucontrol->value.integer.value[cnt] = val;
+				cnt++;
+			}
+		}
+	} else {
+		/* master channel */
+		err = get_cur_mix_value(cval, 0, &val);
+		if (err < 0) {
+			if (cval->chip->ignore_ctl_error) {
+				ucontrol->value.integer.value[0] = cval->min;
+				return 0;
+			}
+			snd_printd(KERN_ERR "cannot get current value for control %d master ch: err = %d\n", cval->control, err);
+			return 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(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol)
+{
+	usb_mixer_elem_info_t *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)) {
+				err = get_cur_mix_value(cval, c + 1, &oval);
+				if (err < 0) {
+					if (cval->chip->ignore_ctl_error)
+						return 0;
+					return err;
+				}
+				val = ucontrol->value.integer.value[cnt];
+				val = get_abs_value(cval, val);
+				if (oval != val) {
+					set_cur_mix_value(cval, c + 1, val);
+					changed = 1;
+				}
+				get_cur_mix_value(cval, c + 1, &val);
+				cnt++;
+			}
+		}
+	} else {
+		/* master channel */
+		err = get_cur_mix_value(cval, 0, &oval);
+		if (err < 0 && cval->chip->ignore_ctl_error)
+			return 0;
+		if (err < 0)
+			return err;
+		val = ucontrol->value.integer.value[0];
+		val = get_abs_value(cval, val);
+		if (val != oval) {
+			set_cur_mix_value(cval, 0, val);
+			changed = 1;
+		}
+	}
+	return changed;
+}
+
+static snd_kcontrol_new_t 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,
+};
+
+
+/*
+ * build a feature control
+ */
+
+static void build_feature_ctl(mixer_build_t *state, unsigned char *desc,
+			      unsigned int ctl_mask, int control,
+			      usb_audio_term_t *iterm, int unitid)
+{
+	unsigned int len = 0;
+	int mapped_name = 0;
+	int nameid = desc[desc[0] - 1];
+	snd_kcontrol_t *kctl;
+	usb_mixer_elem_info_t *cval;
+
+	control++; /* change from zero-based to 1-based value */
+
+	if (control == USB_FEATURE_GEQ) {
+		/* FIXME: not supported yet */
+		return;
+	}
+
+	if (check_ignored_ctl(state, unitid, control))
+		return;
+
+	cval = kcalloc(1, sizeof(*cval), GFP_KERNEL);
+	if (! cval) {
+		snd_printk(KERN_ERR "cannot malloc kcontrol\n");
+		return;
+	}
+	cval->chip = state->chip;
+	cval->ctrlif = state->ctrlif;
+	cval->id = unitid;
+	cval->control = control;
+	cval->cmask = ctl_mask;
+	cval->val_type = audio_feature_info[control-1].type;
+	if (ctl_mask == 0)
+		cval->channels = 1;	/* master channel */
+	else {
+		int i, c = 0;
+		for (i = 0; i < 16; i++)
+			if (ctl_mask & (1 << i))
+				c++;
+		cval->channels = c;
+	}
+
+	/* get min/max values */
+	get_min_max(cval, 0);
+
+	kctl = snd_ctl_new1(&usb_feature_unit_ctl, cval);
+	if (! kctl) {
+		snd_printk(KERN_ERR "cannot malloc kcontrol\n");
+		kfree(cval);
+		return;
+	}
+	kctl->private_free = usb_mixer_elem_free;
+
+	len = check_mapped_name(state, unitid, control, kctl->id.name, sizeof(kctl->id.name));
+	mapped_name = len != 0;
+	if (! len && nameid)
+		len = snd_usb_copy_string_desc(state, nameid, kctl->id.name, sizeof(kctl->id.name));
+
+	switch (control) {
+	case USB_FEATURE_MUTE:
+	case USB_FEATURE_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) {
+			len = get_term_name(state, iterm, kctl->id.name, sizeof(kctl->id.name), 1);
+			if (! len)
+				len = get_term_name(state, &state->oterm, kctl->id.name, sizeof(kctl->id.name), 1);
+			if (! len)
+				len = snprintf(kctl->id.name, sizeof(kctl->id.name),
+					       "Feature %d", unitid);
+		}
+		/* 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 && ! (state->oterm.type >> 16)) {
+			if ((state->oterm.type & 0xff00) == 0x0100) {
+				len = strlcat(kctl->id.name, " Capture", sizeof(kctl->id.name));
+			} else {
+				len = strlcat(kctl->id.name + len, " Playback", sizeof(kctl->id.name));
+			}
+		}
+		strlcat(kctl->id.name + len, control == USB_FEATURE_MUTE ? " Switch" : " Volume",
+			sizeof(kctl->id.name));
+		break;
+
+	default:
+		if (! len)
+			strlcpy(kctl->id.name, audio_feature_info[control-1].name,
+				sizeof(kctl->id.name));
+		break;
+	}
+
+	/* 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 (((state->vendor == 0x471 && (state->product == 0x104 || state->product == 0x105 || state->product == 0x101)) ||
+	     (state->vendor == 0x672 && state->product == 0x1041)) && !strcmp(kctl->id.name, "PCM Playback Volume") &&
+	     cval->min == -15616) {
+		snd_printk("USB Audio: using volume control quirk for the UDA1321/N101 chip\n");
+		cval->max = -256;
+	}
+
+	snd_printdd(KERN_INFO "[%d] FU [%s] ch = %d, val = %d/%d/%d\n",
+		    cval->id, kctl->id.name, cval->channels, cval->min, cval->max, cval->res);
+	add_control_to_empty(state->chip->card, kctl);
+}
+
+
+
+/*
+ * parse a feature unit
+ *
+ * most of controlls are defined here.
+ */
+static int parse_audio_feature_unit(mixer_build_t *state, int unitid, unsigned char *ftr)
+{
+	int channels, i, j;
+	usb_audio_term_t iterm;
+	unsigned int master_bits, first_ch_bits;
+	int err, csize;
+
+	if (ftr[0] < 7 || ! (csize = ftr[5]) || ftr[0] < 7 + csize) {
+		snd_printk(KERN_ERR "usbaudio: unit %u: invalid FEATURE_UNIT descriptor\n", unitid);
+		return -EINVAL;
+	}
+
+	/* parse the source unit */
+	if ((err = parse_audio_unit(state, ftr[4])) < 0)
+		return err;
+
+	/* determine the input source type and name */
+	if (check_input_term(state, ftr[4], &iterm) < 0)
+		return -EINVAL;
+
+	channels = (ftr[0] - 7) / csize - 1;
+
+	master_bits = snd_usb_combine_bytes(ftr + 6, csize);
+	if (channels > 0)
+		first_ch_bits = snd_usb_combine_bytes(ftr + 6 + csize, csize);
+	else
+		first_ch_bits = 0;
+	/* check all control types */
+	for (i = 0; i < 10; i++) {
+		unsigned int ch_bits = 0;
+		for (j = 0; j < channels; j++) {
+			unsigned int mask = snd_usb_combine_bytes(ftr + 6 + csize * (j+1), csize);
+			if (mask & (1 << i))
+				ch_bits |= (1 << j);
+		}
+		if (ch_bits & 1) /* the first channel must be set (for ease of programming) */
+			build_feature_ctl(state, ftr, ch_bits, i, &iterm, unitid);
+		if (master_bits & (1 << i))
+			build_feature_ctl(state, ftr, 0, i, &iterm, unitid);
+	}
+
+	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(mixer_build_t *state, unsigned char *desc,
+				 int in_pin, int in_ch, int unitid,
+				 usb_audio_term_t *iterm)
+{
+	usb_mixer_elem_info_t *cval;
+	unsigned int input_pins = desc[4];
+	unsigned int num_outs = desc[5 + input_pins];
+	unsigned int i, len;
+	snd_kcontrol_t *kctl;
+
+	if (check_ignored_ctl(state, unitid, 0))
+		return;
+
+	cval = kcalloc(1, sizeof(*cval), GFP_KERNEL);
+	if (! cval)
+		return;
+
+	cval->chip = state->chip;
+	cval->ctrlif = state->ctrlif;
+	cval->id = unitid;
+	cval->control = in_ch + 1; /* based on 1 */
+	cval->val_type = USB_MIXER_S16;
+	for (i = 0; i < num_outs; i++) {
+		if (check_matrix_bitmap(desc + 9 + input_pins, 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) {
+		snd_printk(KERN_ERR "cannot malloc kcontrol\n");
+		kfree(cval);
+		return;
+	}
+	kctl->private_free = usb_mixer_elem_free;
+
+	len = check_mapped_name(state, unitid, 0, kctl->id.name, sizeof(kctl->id.name));
+	if (! len)
+		len = get_term_name(state, iterm, kctl->id.name, sizeof(kctl->id.name), 0);
+	if (! len)
+		len = sprintf(kctl->id.name, "Mixer Source %d", in_ch + 1);
+	strlcat(kctl->id.name + len, " Volume", sizeof(kctl->id.name));
+
+	snd_printdd(KERN_INFO "[%d] MU [%s] ch = %d, val = %d/%d\n",
+		    cval->id, kctl->id.name, cval->channels, cval->min, cval->max);
+	add_control_to_empty(state->chip->card, kctl);
+}
+
+
+/*
+ * parse a mixer unit
+ */
+static int parse_audio_mixer_unit(mixer_build_t *state, int unitid, unsigned char *desc)
+{
+	usb_audio_term_t iterm;
+	int input_pins, num_ins, num_outs;
+	int pin, ich, err;
+
+	if (desc[0] < 11 || ! (input_pins = desc[4]) || ! (num_outs = desc[5 + input_pins])) {
+		snd_printk(KERN_ERR "invalid MIXER UNIT descriptor %d\n", unitid);
+		return -EINVAL;
+	}
+	/* no bmControls field (e.g. Maya44) -> ignore */
+	if (desc[0] <= 10 + input_pins) {
+		snd_printdd(KERN_INFO "MU %d has no bmControls field\n", unitid);
+		return 0;
+	}
+
+	num_ins = 0;
+	ich = 0;
+	for (pin = 0; pin < input_pins; pin++) {
+		err = parse_audio_unit(state, desc[5 + pin]);
+		if (err < 0)
+			return err;
+		err = check_input_term(state, desc[5 + 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) {
+				if (check_matrix_bitmap(desc + 9 + input_pins,
+							ich, och, num_outs)) {
+					ich_has_controls = 1;
+					break;
+				}
+			}
+			if (ich_has_controls)
+				build_mixer_unit_ctl(state, desc, pin, ich,
+						     unitid, &iterm);
+		}
+	}
+	return 0;
+}
+
+
+/*
+ * Processing Unit / Extension Unit
+ */
+
+/* get callback for processing/extension unit */
+static int mixer_ctl_procunit_get(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol)
+{
+	usb_mixer_elem_info_t *cval = kcontrol->private_data;
+	int err, val;
+
+	err = get_cur_ctl_value(cval, cval->control << 8, &val);
+	if (err < 0 && cval->chip->ignore_ctl_error) {
+		ucontrol->value.integer.value[0] = cval->min;
+		return 0;
+	}
+	if (err < 0)
+		return 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(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol)
+{
+	usb_mixer_elem_info_t *cval = kcontrol->private_data;
+	int val, oval, err;
+
+	err = get_cur_ctl_value(cval, cval->control << 8, &oval);
+	if (err < 0) {
+		if (cval->chip->ignore_ctl_error)
+			return 0;
+		return 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 snd_kcontrol_new_t 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 updown_proc_info[] = {
+	{ USB_PROC_UPDOWN_SWITCH, "Switch", USB_MIXER_BOOLEAN },
+	{ USB_PROC_UPDOWN_MODE_SEL, "Mode Select", USB_MIXER_U8, 1 },
+	{ 0 }
+};
+static struct procunit_value_info prologic_proc_info[] = {
+	{ USB_PROC_PROLOGIC_SWITCH, "Switch", USB_MIXER_BOOLEAN },
+	{ USB_PROC_PROLOGIC_MODE_SEL, "Mode Select", USB_MIXER_U8, 1 },
+	{ 0 }
+};
+static struct procunit_value_info threed_enh_proc_info[] = {
+	{ USB_PROC_3DENH_SWITCH, "Switch", USB_MIXER_BOOLEAN },
+	{ USB_PROC_3DENH_SPACE, "Spaciousness", USB_MIXER_U8 },
+	{ 0 }
+};
+static struct procunit_value_info reverb_proc_info[] = {
+	{ USB_PROC_REVERB_SWITCH, "Switch", USB_MIXER_BOOLEAN },
+	{ USB_PROC_REVERB_LEVEL, "Level", USB_MIXER_U8 },
+	{ USB_PROC_REVERB_TIME, "Time", USB_MIXER_U16 },
+	{ USB_PROC_REVERB_DELAY, "Delay", USB_MIXER_U8 },
+	{ 0 }
+};
+static struct procunit_value_info chorus_proc_info[] = {
+	{ USB_PROC_CHORUS_SWITCH, "Switch", USB_MIXER_BOOLEAN },
+	{ USB_PROC_CHORUS_LEVEL, "Level", USB_MIXER_U8 },
+	{ USB_PROC_CHORUS_RATE, "Rate", USB_MIXER_U16 },
+	{ USB_PROC_CHORUS_DEPTH, "Depth", USB_MIXER_U16 },
+	{ 0 }
+};
+static struct procunit_value_info dcr_proc_info[] = {
+	{ USB_PROC_DCR_SWITCH, "Switch", USB_MIXER_BOOLEAN },
+	{ USB_PROC_DCR_RATIO, "Ratio", USB_MIXER_U16 },
+	{ USB_PROC_DCR_MAX_AMP, "Max Amp", USB_MIXER_S16 },
+	{ USB_PROC_DCR_THRESHOLD, "Threshold", USB_MIXER_S16 },
+	{ USB_PROC_DCR_ATTACK, "Attack Time", USB_MIXER_U16 },
+	{ USB_PROC_DCR_RELEASE, "Release Time", USB_MIXER_U16 },
+	{ 0 }
+};
+
+static struct procunit_info procunits[] = {
+	{ USB_PROC_UPDOWN, "Up Down", updown_proc_info },
+	{ USB_PROC_PROLOGIC, "Dolby Prologic", prologic_proc_info },
+	{ USB_PROC_3DENH, "3D Stereo Extender", threed_enh_proc_info },
+	{ USB_PROC_REVERB, "Reverb", reverb_proc_info },
+	{ USB_PROC_CHORUS, "Chorus", chorus_proc_info },
+	{ USB_PROC_DCR, "DCR", dcr_proc_info },
+	{ 0 },
+};
+
+/*
+ * build a processing/extension unit
+ */
+static int build_audio_procunit(mixer_build_t *state, int unitid, unsigned char *dsc, struct procunit_info *list, char *name)
+{
+	int num_ins = dsc[6];
+	usb_mixer_elem_info_t *cval;
+	snd_kcontrol_t *kctl;
+	int i, err, nameid, type, len;
+	struct procunit_info *info;
+	struct procunit_value_info *valinfo;
+	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 (dsc[0] < 13 || dsc[0] < 13 + num_ins || dsc[0] < num_ins + dsc[11 + num_ins]) {
+		snd_printk(KERN_ERR "invalid %s descriptor (id %d)\n", name, unitid);
+		return -EINVAL;
+	}
+
+	for (i = 0; i < num_ins; i++) {
+		if ((err = parse_audio_unit(state, dsc[7 + i])) < 0)
+			return err;
+	}
+
+	type = combine_word(&dsc[4]);
+	if (! type)
+		return 0; /* undefined? */
+
+	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++) {
+		/* FIXME: bitmap might be longer than 8bit */
+		if (! (dsc[12 + num_ins] & (1 << (valinfo->control - 1))))
+			continue;
+		if (check_ignored_ctl(state, unitid, valinfo->control))
+			continue;
+		cval = kcalloc(1, sizeof(*cval), GFP_KERNEL);
+		if (! cval) {
+			snd_printk(KERN_ERR "cannot malloc kcontrol\n");
+			return -ENOMEM;
+		}
+		cval->chip = state->chip;
+		cval->ctrlif = state->ctrlif;
+		cval->id = unitid;
+		cval->control = valinfo->control;
+		cval->val_type = valinfo->val_type;
+		cval->channels = 1;
+
+		/* get min/max values */
+		if (type == USB_PROC_UPDOWN && cval->control == USB_PROC_UPDOWN_MODE_SEL) {
+			/* FIXME: hard-coded */
+			cval->min = 1;
+			cval->max = dsc[15];
+			cval->res = 1;
+			cval->initialized = 1;
+		} else
+			get_min_max(cval, valinfo->min_value);
+
+		kctl = snd_ctl_new1(&mixer_procunit_ctl, cval);
+		if (! kctl) {
+			snd_printk(KERN_ERR "cannot malloc kcontrol\n");
+			kfree(cval);
+			return -ENOMEM;
+		}
+		kctl->private_free = usb_mixer_elem_free;
+
+		if (check_mapped_name(state, unitid, cval->control, kctl->id.name, sizeof(kctl->id.name)))
+			;
+		else if (info->name)
+			strlcpy(kctl->id.name, info->name, sizeof(kctl->id.name));
+		else {
+			nameid = dsc[12 + num_ins + dsc[11 + num_ins]];
+			len = 0;
+			if (nameid)
+				len = snd_usb_copy_string_desc(state, nameid, kctl->id.name, sizeof(kctl->id.name));
+			if (! len)
+				strlcpy(kctl->id.name, name, sizeof(kctl->id.name));
+		}
+		strlcat(kctl->id.name, " ", sizeof(kctl->id.name));
+		strlcat(kctl->id.name, valinfo->suffix, sizeof(kctl->id.name));
+
+		snd_printdd(KERN_INFO "[%d] PU [%s] ch = %d, val = %d/%d\n",
+			    cval->id, kctl->id.name, cval->channels, cval->min, cval->max);
+		if ((err = add_control_to_empty(state->chip->card, kctl)) < 0)
+			return err;
+	}
+	return 0;
+}
+
+
+static int parse_audio_processing_unit(mixer_build_t *state, int unitid, unsigned char *desc)
+{
+	return build_audio_procunit(state, unitid, desc, procunits, "Processing Unit");
+}
+
+static int parse_audio_extension_unit(mixer_build_t *state, int unitid, unsigned char *desc)
+{
+	return build_audio_procunit(state, unitid, desc, NULL, "Extension Unit");
+}
+
+
+/*
+ * Selector Unit
+ */
+
+/* info callback for selector unit
+ * use an enumerator type for routing
+ */
+static int mixer_ctl_selector_info(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t *uinfo)
+{
+	usb_mixer_elem_info_t *cval = kcontrol->private_data;
+	char **itemlist = (char **)kcontrol->private_value;
+
+	snd_assert(itemlist, return -EINVAL);
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	uinfo->count = 1;
+	uinfo->value.enumerated.items = cval->max;
+	if ((int)uinfo->value.enumerated.item >= cval->max)
+		uinfo->value.enumerated.item = cval->max - 1;
+	strcpy(uinfo->value.enumerated.name, itemlist[uinfo->value.enumerated.item]);
+	return 0;
+}
+
+/* get callback for selector unit */
+static int mixer_ctl_selector_get(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol)
+{
+	usb_mixer_elem_info_t *cval = kcontrol->private_data;
+	int val, err;
+
+	err = get_cur_ctl_value(cval, 0, &val);
+	if (err < 0) {
+		if (cval->chip->ignore_ctl_error) {
+			ucontrol->value.enumerated.item[0] = 0;
+			return 0;
+		}
+		return 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(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol)
+{
+	usb_mixer_elem_info_t *cval = kcontrol->private_data;
+	int val, oval, err;
+
+	err = get_cur_ctl_value(cval, 0, &oval);
+	if (err < 0) {
+		if (cval->chip->ignore_ctl_error)
+			return 0;
+		return err;
+	}
+	val = ucontrol->value.enumerated.item[0];
+	val = get_abs_value(cval, val);
+	if (val != oval) {
+		set_cur_ctl_value(cval, 0, val);
+		return 1;
+	}
+	return 0;
+}
+
+/* alsa control interface for selector unit */
+static snd_kcontrol_new_t 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(snd_kcontrol_t *kctl)
+{
+	int i, num_ins = 0;
+
+	if (kctl->private_data) {
+		usb_mixer_elem_info_t *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(mixer_build_t *state, int unitid, unsigned char *desc)
+{
+	unsigned int num_ins = desc[4];
+	unsigned int i, nameid, len;
+	int err;
+	usb_mixer_elem_info_t *cval;
+	snd_kcontrol_t *kctl;
+	char **namelist;
+
+	if (! num_ins || desc[0] < 6 + num_ins) {
+		snd_printk(KERN_ERR "invalid SELECTOR UNIT descriptor %d\n", unitid);
+		return -EINVAL;
+	}
+
+	for (i = 0; i < num_ins; i++) {
+		if ((err = parse_audio_unit(state, desc[5 + i])) < 0)
+			return err;
+	}
+
+	if (num_ins == 1) /* only one ? nonsense! */
+		return 0;
+
+	if (check_ignored_ctl(state, unitid, 0))
+		return 0;
+
+	cval = kcalloc(1, sizeof(*cval), GFP_KERNEL);
+	if (! cval) {
+		snd_printk(KERN_ERR "cannot malloc kcontrol\n");
+		return -ENOMEM;
+	}
+	cval->chip = state->chip;
+	cval->ctrlif = state->ctrlif;
+	cval->id = unitid;
+	cval->val_type = USB_MIXER_U8;
+	cval->channels = 1;
+	cval->min = 1;
+	cval->max = num_ins;
+	cval->res = 1;
+	cval->initialized = 1;
+
+	namelist = kmalloc(sizeof(char *) * num_ins, GFP_KERNEL);
+	if (! namelist) {
+		snd_printk(KERN_ERR "cannot malloc\n");
+		kfree(cval);
+		return -ENOMEM;
+	}
+#define MAX_ITEM_NAME_LEN	64
+	for (i = 0; i < num_ins; i++) {
+		usb_audio_term_t iterm;
+		len = 0;
+		namelist[i] = kmalloc(MAX_ITEM_NAME_LEN, GFP_KERNEL);
+		if (! namelist[i]) {
+			snd_printk(KERN_ERR "cannot malloc\n");
+			while (--i > 0)
+				kfree(namelist[i]);
+			kfree(namelist);
+			kfree(cval);
+			return -ENOMEM;
+		}
+		if (check_input_term(state, desc[5 + i], &iterm) >= 0)
+			len = get_term_name(state, &iterm, namelist[i], MAX_ITEM_NAME_LEN, 0);
+		if (! len)
+			sprintf(namelist[i], "Input %d", i);
+	}
+
+	kctl = snd_ctl_new1(&mixer_selectunit_ctl, cval);
+	if (! kctl) {
+		snd_printk(KERN_ERR "cannot malloc kcontrol\n");
+		kfree(cval);
+		return -ENOMEM;
+	}
+	kctl->private_value = (unsigned long)namelist;
+	kctl->private_free = usb_mixer_selector_elem_free;
+
+	nameid = desc[desc[0] - 1];
+	len = check_mapped_name(state, unitid, 0, kctl->id.name, sizeof(kctl->id.name));
+	if (len)
+		;
+	else if (nameid)
+		snd_usb_copy_string_desc(state, nameid, kctl->id.name, sizeof(kctl->id.name));
+	else {
+		len = get_term_name(state, &state->oterm,
+				    kctl->id.name, sizeof(kctl->id.name), 0);
+		if (! len)
+			strlcpy(kctl->id.name, "USB", sizeof(kctl->id.name));
+
+		if ((state->oterm.type & 0xff00) == 0x0100)
+			strlcat(kctl->id.name, " Capture Source", sizeof(kctl->id.name));
+		else
+			strlcat(kctl->id.name, " Playback Source", sizeof(kctl->id.name));
+	}
+
+	snd_printdd(KERN_INFO "[%d] SU [%s] items = %d\n",
+		    cval->id, kctl->id.name, num_ins);
+	if ((err = add_control_to_empty(state->chip->card, kctl)) < 0)
+		return err;
+
+	return 0;
+}
+
+
+/*
+ * parse an audio unit recursively
+ */
+
+static int parse_audio_unit(mixer_build_t *state, int unitid)
+{
+	unsigned char *p1;
+
+	if (test_and_set_bit(unitid, state->unitbitmap))
+		return 0; /* the unit already visited */
+
+	p1 = find_audio_control_unit(state, unitid);
+	if (!p1) {
+		snd_printk(KERN_ERR "usbaudio: unit %d not found!\n", unitid);
+		return -EINVAL;
+	}
+
+	switch (p1[2]) {
+	case INPUT_TERMINAL:
+		return 0; /* NOP */
+	case MIXER_UNIT:
+		return parse_audio_mixer_unit(state, unitid, p1);
+	case SELECTOR_UNIT:
+		return parse_audio_selector_unit(state, unitid, p1);
+	case FEATURE_UNIT:
+		return parse_audio_feature_unit(state, unitid, p1);
+	case PROCESSING_UNIT:
+		return parse_audio_processing_unit(state, unitid, p1);
+	case EXTENSION_UNIT:
+		return parse_audio_extension_unit(state, unitid, p1);
+	default:
+		snd_printk(KERN_ERR "usbaudio: unit %u: unexpected type 0x%02x\n", unitid, p1[2]);
+		return -EINVAL;
+	}
+}
+
+/*
+ * create mixer controls
+ *
+ * walk through all OUTPUT_TERMINAL descriptors to search for mixers
+ */
+int snd_usb_create_mixer(snd_usb_audio_t *chip, int ctrlif)
+{
+	unsigned char *desc;
+	mixer_build_t state;
+	int err;
+	const struct usbmix_ctl_map *map;
+	struct usb_device_descriptor *dev = &chip->dev->descriptor;
+	struct usb_host_interface *hostif = &usb_ifnum_to_if(chip->dev, ctrlif)->altsetting[0];
+
+	strcpy(chip->card->mixername, "USB Mixer");
+
+	memset(&state, 0, sizeof(state));
+	state.chip = chip;
+	state.buffer = hostif->extra;
+	state.buflen = hostif->extralen;
+	state.ctrlif = ctrlif;
+	state.vendor = le16_to_cpu(dev->idVendor);
+	state.product = le16_to_cpu(dev->idProduct);
+
+	/* check the mapping table */
+	for (map = usbmix_ctl_maps; map->vendor; map++) {
+		if (map->vendor == state.vendor && map->product == state.product) {
+			state.map = map->map;
+			chip->ignore_ctl_error = map->ignore_ctl_error;
+			break;
+		}
+	}
+#ifdef IGNORE_CTL_ERROR
+	chip->ignore_ctl_error = 1;
+#endif
+
+	desc = NULL;
+	while ((desc = snd_usb_find_csint_desc(hostif->extra, hostif->extralen, desc, OUTPUT_TERMINAL)) != NULL) {
+		if (desc[0] < 9)
+			continue; /* invalid descriptor? */
+		set_bit(desc[3], state.unitbitmap);  /* mark terminal ID as visited */
+		state.oterm.id = desc[3];
+		state.oterm.type = combine_word(&desc[4]);
+		state.oterm.name = desc[8];
+		err = parse_audio_unit(&state, desc[7]);
+		if (err < 0)
+			return err;
+	}
+	return 0;
+}
diff --git a/sound/usb/usbmixer_maps.c b/sound/usb/usbmixer_maps.c
new file mode 100644
index 0000000..c69b4b0
--- /dev/null
+++ b/sound/usb/usbmixer_maps.c
@@ -0,0 +1,135 @@
+/*
+ *   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_name_map {
+	int id;
+	const char *name;
+	int control;
+};
+
+struct usbmix_ctl_map {
+	int vendor;
+	int product;
+	const struct usbmix_name_map *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 Input 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", USB_FEATURE_BASS }, /* FU */
+	{ 18, "Tone Control - Treble", USB_FEATURE_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 */
+};
+
+/* 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 */
+};
+
+/* 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 */
+};
+
+/*
+ * Control map entries
+ */
+
+static struct usbmix_ctl_map usbmix_ctl_maps[] = {
+	{ 0x41e, 0x3000, extigy_map, 1 },
+	{ 0x8bb, 0x2702, linex_map, 1 },
+	{ 0xc45, 0x1158, justlink_map, 0 },
+	{ 0 } /* terminator */
+};
+
diff --git a/sound/usb/usbquirks.h b/sound/usb/usbquirks.h
new file mode 100644
index 0000000..88bbd94
--- /dev/null
+++ b/sound/usb/usbquirks.h
@@ -0,0 +1,1202 @@
+/*
+ * 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
+
+/*
+ * Yamaha devices
+ */
+
+#define YAMAHA_DEVICE(id, name) { \
+	USB_DEVICE(0x0499, id), \
+	.driver_info = (unsigned long) & (const snd_usb_audio_quirk_t) { \
+		.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 snd_usb_audio_quirk_t) { \
+		.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(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(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(0x2000, "DGP-7"),
+YAMAHA_DEVICE(0x2001, "DGP-5"),
+YAMAHA_DEVICE(0x2002, 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(0x7000, "DTX"),
+YAMAHA_DEVICE(0x7010, "UB99"),
+#undef YAMAHA_DEVICE
+#undef YAMAHA_INTERFACE
+
+/*
+ * Roland/RolandED/Edirol/BOSS devices
+ */
+{
+	USB_DEVICE(0x0582, 0x0000),
+	.driver_info = (unsigned long) & (const snd_usb_audio_quirk_t) {
+		.vendor_name = "Roland",
+		.product_name = "UA-100",
+		.ifnum = QUIRK_ANY_INTERFACE,
+		.type = QUIRK_COMPOSITE,
+		.data = (const snd_usb_audio_quirk_t[]) {
+			{
+				.ifnum = 0,
+				.type = QUIRK_AUDIO_FIXED_ENDPOINT,
+				.data = & (const struct audioformat) {
+					.format = SNDRV_PCM_FORMAT_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) {
+					.format = SNDRV_PCM_FORMAT_S16_LE,
+					.channels = 2,
+					.iface = 1,
+					.altsetting = 1,
+					.altset_idx = 1,
+					.attributes = 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 snd_usb_midi_endpoint_info_t) {
+					.out_cables = 0x0007,
+					.in_cables  = 0x0007
+				}
+			},
+			{
+				.ifnum = -1
+			}
+		}
+	}
+},
+{
+	USB_DEVICE(0x0582, 0x0002),
+	.driver_info = (unsigned long) & (const snd_usb_audio_quirk_t) {
+		.vendor_name = "EDIROL",
+		.product_name = "UM-4",
+		.ifnum = 2,
+		.type = QUIRK_MIDI_FIXED_ENDPOINT,
+		.data = & (const snd_usb_midi_endpoint_info_t) {
+			.out_cables = 0x000f,
+			.in_cables  = 0x000f
+		}
+	}
+},
+{
+	USB_DEVICE(0x0582, 0x0003),
+	.driver_info = (unsigned long) & (const snd_usb_audio_quirk_t) {
+		.vendor_name = "Roland",
+		.product_name = "SC-8850",
+		.ifnum = 2,
+		.type = QUIRK_MIDI_FIXED_ENDPOINT,
+		.data = & (const snd_usb_midi_endpoint_info_t) {
+			.out_cables = 0x003f,
+			.in_cables  = 0x003f
+		}
+	}
+},
+{
+	USB_DEVICE(0x0582, 0x0004),
+	.driver_info = (unsigned long) & (const snd_usb_audio_quirk_t) {
+		.vendor_name = "Roland",
+		.product_name = "U-8",
+		.ifnum = 2,
+		.type = QUIRK_MIDI_FIXED_ENDPOINT,
+		.data = & (const snd_usb_midi_endpoint_info_t) {
+			.out_cables = 0x0005,
+			.in_cables  = 0x0005
+		}
+	}
+},
+{
+	USB_DEVICE(0x0582, 0x0005),
+	.driver_info = (unsigned long) & (const snd_usb_audio_quirk_t) {
+		.vendor_name = "EDIROL",
+		.product_name = "UM-2",
+		.ifnum = 2,
+		.type = QUIRK_MIDI_FIXED_ENDPOINT,
+		.data = & (const snd_usb_midi_endpoint_info_t) {
+			.out_cables = 0x0003,
+			.in_cables  = 0x0003
+		}
+	}
+},
+{
+	USB_DEVICE(0x0582, 0x0007),
+	.driver_info = (unsigned long) & (const snd_usb_audio_quirk_t) {
+		.vendor_name = "Roland",
+		.product_name = "SC-8820",
+		.ifnum = 2,
+		.type = QUIRK_MIDI_FIXED_ENDPOINT,
+		.data = & (const snd_usb_midi_endpoint_info_t) {
+			.out_cables = 0x0013,
+			.in_cables  = 0x0013
+		}
+	}
+},
+{
+	USB_DEVICE(0x0582, 0x0008),
+	.driver_info = (unsigned long) & (const snd_usb_audio_quirk_t) {
+		.vendor_name = "Roland",
+		.product_name = "PC-300",
+		.ifnum = 2,
+		.type = QUIRK_MIDI_FIXED_ENDPOINT,
+		.data = & (const snd_usb_midi_endpoint_info_t) {
+			.out_cables = 0x0001,
+			.in_cables  = 0x0001
+		}
+	}
+},
+{
+	USB_DEVICE(0x0582, 0x0009),
+	.driver_info = (unsigned long) & (const snd_usb_audio_quirk_t) {
+		.vendor_name = "EDIROL",
+		.product_name = "UM-1",
+		.ifnum = 2,
+		.type = QUIRK_MIDI_FIXED_ENDPOINT,
+		.data = & (const snd_usb_midi_endpoint_info_t) {
+			.out_cables = 0x0001,
+			.in_cables  = 0x0001
+		}
+	}
+},
+{
+	USB_DEVICE(0x0582, 0x000b),
+	.driver_info = (unsigned long) & (const snd_usb_audio_quirk_t) {
+		.vendor_name = "Roland",
+		.product_name = "SK-500",
+		.ifnum = 2,
+		.type = QUIRK_MIDI_FIXED_ENDPOINT,
+		.data = & (const snd_usb_midi_endpoint_info_t) {
+			.out_cables = 0x0013,
+			.in_cables  = 0x0013
+		}
+	}
+},
+{
+	/* thanks to Emiliano Grilli <emillo@libero.it>
+	 * for helping researching this data */
+	USB_DEVICE(0x0582, 0x000c),
+	.driver_info = (unsigned long) & (const snd_usb_audio_quirk_t) {
+		.vendor_name = "Roland",
+		.product_name = "SC-D70",
+		.ifnum = QUIRK_ANY_INTERFACE,
+		.type = QUIRK_COMPOSITE,
+		.data = (const snd_usb_audio_quirk_t[]) {
+			{
+				.ifnum = 0,
+				.type = QUIRK_AUDIO_FIXED_ENDPOINT,
+				.data = & (const struct audioformat) {
+					.format = SNDRV_PCM_FORMAT_S24_3LE,
+					.channels = 2,
+					.iface = 0,
+					.altsetting = 1,
+					.altset_idx = 1,
+					.attributes = 0,
+					.endpoint = 0x01,
+					.ep_attr = 0x01,
+					.rates = SNDRV_PCM_RATE_CONTINUOUS,
+					.rate_min = 44100,
+					.rate_max = 44100,
+				}
+			},
+			{
+				.ifnum = 1,
+				.type = QUIRK_AUDIO_FIXED_ENDPOINT,
+				.data = & (const struct audioformat) {
+					.format = SNDRV_PCM_FORMAT_S24_3LE,
+					.channels = 2,
+					.iface = 1,
+					.altsetting = 1,
+					.altset_idx = 1,
+					.attributes = 0,
+					.endpoint = 0x81,
+					.ep_attr = 0x01,
+					.rates = SNDRV_PCM_RATE_CONTINUOUS,
+					.rate_min = 44100,
+					.rate_max = 44100,
+				}
+			},
+			{
+				.ifnum = 2,
+				.type = QUIRK_MIDI_FIXED_ENDPOINT,
+				.data = & (const snd_usb_midi_endpoint_info_t) {
+					.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 snd_usb_audio_quirk_t) {
+		.vendor_name = "EDIROL",
+		.product_name = "UA-5",
+		.ifnum = QUIRK_ANY_INTERFACE,
+		.type = QUIRK_COMPOSITE,
+		.data = (const snd_usb_audio_quirk_t[]) {
+			{
+				.ifnum = 1,
+				.type = QUIRK_AUDIO_STANDARD_INTERFACE
+			},
+			{
+				.ifnum = 2,
+				.type = QUIRK_AUDIO_STANDARD_INTERFACE
+			},
+			{
+				.ifnum = -1
+			}
+		}
+	}
+},
+{
+	USB_DEVICE(0x0582, 0x0012),
+	.driver_info = (unsigned long) & (const snd_usb_audio_quirk_t) {
+		.vendor_name = "Roland",
+		.product_name = "XV-5050",
+		.ifnum = 0,
+		.type = QUIRK_MIDI_FIXED_ENDPOINT,
+		.data = & (const snd_usb_midi_endpoint_info_t) {
+			.out_cables = 0x0001,
+			.in_cables  = 0x0001
+		}
+	}
+},
+{
+	USB_DEVICE(0x0582, 0x0014),
+	.driver_info = (unsigned long) & (const snd_usb_audio_quirk_t) {
+		.vendor_name = "EDIROL",
+		.product_name = "UM-880",
+		.ifnum = 0,
+		.type = QUIRK_MIDI_FIXED_ENDPOINT,
+		.data = & (const snd_usb_midi_endpoint_info_t) {
+			.out_cables = 0x01ff,
+			.in_cables  = 0x01ff
+		}
+	}
+},
+{
+	USB_DEVICE(0x0582, 0x0016),
+	.driver_info = (unsigned long) & (const snd_usb_audio_quirk_t) {
+		.vendor_name = "EDIROL",
+		.product_name = "SD-90",
+		.ifnum = 2,
+		.type = QUIRK_MIDI_FIXED_ENDPOINT,
+		.data = & (const snd_usb_midi_endpoint_info_t) {
+			.out_cables = 0x000f,
+			.in_cables  = 0x000f
+		}
+	}
+},
+{
+	USB_DEVICE(0x0582, 0x001b),
+	.driver_info = (unsigned long) & (const snd_usb_audio_quirk_t) {
+		.vendor_name = "Roland",
+		.product_name = "MMP-2",
+		.ifnum = 2,
+		.type = QUIRK_MIDI_FIXED_ENDPOINT,
+		.data = & (const snd_usb_midi_endpoint_info_t) {
+			.out_cables = 0x0001,
+			.in_cables  = 0x0001
+		}
+	}
+},
+{
+	USB_DEVICE(0x0582, 0x001d),
+	.driver_info = (unsigned long) & (const snd_usb_audio_quirk_t) {
+		.vendor_name = "Roland",
+		.product_name = "V-SYNTH",
+		.ifnum = 0,
+		.type = QUIRK_MIDI_FIXED_ENDPOINT,
+		.data = & (const snd_usb_midi_endpoint_info_t) {
+			.out_cables = 0x0001,
+			.in_cables  = 0x0001
+		}
+	}
+},
+{
+	USB_DEVICE(0x0582, 0x0023),
+	.driver_info = (unsigned long) & (const snd_usb_audio_quirk_t) {
+		.vendor_name = "EDIROL",
+		.product_name = "UM-550",
+		.ifnum = 0,
+		.type = QUIRK_MIDI_FIXED_ENDPOINT,
+		.data = & (const snd_usb_midi_endpoint_info_t) {
+			.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 snd_usb_audio_quirk_t) {
+		.vendor_name = "EDIROL",
+		.product_name = "UA-20",
+		.ifnum = QUIRK_ANY_INTERFACE,
+		.type = QUIRK_COMPOSITE,
+		.data = (const snd_usb_audio_quirk_t[]) {
+			{
+				.ifnum = 1,
+				.type = QUIRK_AUDIO_STANDARD_INTERFACE
+			},
+			{
+				.ifnum = 2,
+				.type = QUIRK_AUDIO_STANDARD_INTERFACE
+			},
+			{
+				.ifnum = 3,
+				.type = QUIRK_MIDI_STANDARD_INTERFACE
+			},
+			{
+				.ifnum = -1
+			}
+		}
+	}
+},
+{
+	USB_DEVICE(0x0582, 0x0027),
+	.driver_info = (unsigned long) & (const snd_usb_audio_quirk_t) {
+		.vendor_name = "EDIROL",
+		.product_name = "SD-20",
+		.ifnum = 0,
+		.type = QUIRK_MIDI_FIXED_ENDPOINT,
+		.data = & (const snd_usb_midi_endpoint_info_t) {
+			.out_cables = 0x0003,
+			.in_cables  = 0x0007
+		}
+	}
+},
+{
+	USB_DEVICE(0x0582, 0x0029),
+	.driver_info = (unsigned long) & (const snd_usb_audio_quirk_t) {
+		.vendor_name = "EDIROL",
+		.product_name = "SD-80",
+		.ifnum = 0,
+		.type = QUIRK_MIDI_FIXED_ENDPOINT,
+		.data = & (const snd_usb_midi_endpoint_info_t) {
+			.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 snd_usb_audio_quirk_t) {
+		.vendor_name = "EDIROL",
+		.product_name = "UA-700",
+		.ifnum = QUIRK_ANY_INTERFACE,
+		.type = QUIRK_COMPOSITE,
+		.data = (const snd_usb_audio_quirk_t[]) {
+			{
+				.ifnum = 1,
+				.type = QUIRK_AUDIO_EDIROL_UA700_UA25
+			},
+			{
+				.ifnum = 2,
+				.type = QUIRK_AUDIO_EDIROL_UA700_UA25
+			},
+			{
+				.ifnum = 3,
+				.type = QUIRK_AUDIO_EDIROL_UA700_UA25
+			},
+			{
+				.ifnum = -1
+			}
+		}
+	}
+},
+{
+	USB_DEVICE(0x0582, 0x002d),
+	.driver_info = (unsigned long) & (const snd_usb_audio_quirk_t) {
+		.vendor_name = "Roland",
+		.product_name = "XV-2020",
+		.ifnum = 0,
+		.type = QUIRK_MIDI_FIXED_ENDPOINT,
+		.data = & (const snd_usb_midi_endpoint_info_t) {
+			.out_cables = 0x0001,
+			.in_cables  = 0x0001
+		}
+	}
+},
+{
+	USB_DEVICE(0x0582, 0x002f),
+	.driver_info = (unsigned long) & (const snd_usb_audio_quirk_t) {
+		.vendor_name = "Roland",
+		.product_name = "VariOS",
+		.ifnum = 0,
+		.type = QUIRK_MIDI_FIXED_ENDPOINT,
+		.data = & (const snd_usb_midi_endpoint_info_t) {
+			.out_cables = 0x0007,
+			.in_cables  = 0x0007
+		}
+	}
+},
+{
+	USB_DEVICE(0x0582, 0x0033),
+	.driver_info = (unsigned long) & (const snd_usb_audio_quirk_t) {
+		.vendor_name = "EDIROL",
+		.product_name = "PCR",
+		.ifnum = 0,
+		.type = QUIRK_MIDI_FIXED_ENDPOINT,
+		.data = & (const snd_usb_midi_endpoint_info_t) {
+			.out_cables = 0x0003,
+			.in_cables  = 0x0007
+		}
+	}
+},
+{
+	USB_DEVICE(0x0582, 0x0037),
+	.driver_info = (unsigned long) & (const snd_usb_audio_quirk_t) {
+		.vendor_name = "Roland",
+		.product_name = "Digital Piano",
+		.ifnum = 0,
+		.type = QUIRK_MIDI_FIXED_ENDPOINT,
+		.data = & (const snd_usb_midi_endpoint_info_t) {
+			.out_cables = 0x0001,
+			.in_cables  = 0x0001
+		}
+	}
+},
+{
+	USB_DEVICE_VENDOR_SPEC(0x0582, 0x003b),
+	.driver_info = (unsigned long) & (const snd_usb_audio_quirk_t) {
+		.vendor_name = "BOSS",
+		.product_name = "GS-10",
+		.ifnum = 3,
+		.type = QUIRK_MIDI_FIXED_ENDPOINT,
+		.data = & (const snd_usb_midi_endpoint_info_t) {
+			.out_cables = 0x0003,
+			.in_cables  = 0x0003
+		}
+	}
+},
+{
+	USB_DEVICE(0x0582, 0x0040),
+	.driver_info = (unsigned long) & (const snd_usb_audio_quirk_t) {
+		.vendor_name = "Roland",
+		.product_name = "GI-20",
+		.ifnum = 0,
+		.type = QUIRK_MIDI_FIXED_ENDPOINT,
+		.data = & (const snd_usb_midi_endpoint_info_t) {
+			.out_cables = 0x0001,
+			.in_cables  = 0x0001
+		}
+	}
+},
+{
+	USB_DEVICE(0x0582, 0x0042),
+	.driver_info = (unsigned long) & (const snd_usb_audio_quirk_t) {
+		.vendor_name = "Roland",
+		.product_name = "RS-70",
+		.ifnum = 0,
+		.type = QUIRK_MIDI_FIXED_ENDPOINT,
+		.data = & (const snd_usb_midi_endpoint_info_t) {
+			.out_cables = 0x0001,
+			.in_cables  = 0x0001
+		}
+	}
+},
+{
+	USB_DEVICE(0x0582, 0x0044),
+	.driver_info = (unsigned long) & (const snd_usb_audio_quirk_t) {
+		.vendor_name = "Roland",
+		.product_name = "UA-1000",
+		.ifnum = QUIRK_ANY_INTERFACE,
+		.type = QUIRK_COMPOSITE,
+		.data = (const snd_usb_audio_quirk_t[]) {
+			{
+				.ifnum = 1,
+				.type = QUIRK_AUDIO_EDIROL_UA1000
+			},
+			{
+				.ifnum = 2,
+				.type = QUIRK_AUDIO_EDIROL_UA1000
+			},
+			{
+				.ifnum = 3,
+				.type = QUIRK_MIDI_FIXED_ENDPOINT,
+				.data = & (const snd_usb_midi_endpoint_info_t) {
+					.out_cables = 0x0003,
+					.in_cables  = 0x0003
+				}
+			},
+			{
+				.ifnum = -1
+			}
+		}
+	}
+},
+{
+	USB_DEVICE(0x0582, 0x0048),
+	.driver_info = (unsigned long) & (const snd_usb_audio_quirk_t) {
+		.vendor_name = "EDIROL",
+		.product_name = "UR-80",
+		.ifnum = 0,
+		.type = QUIRK_MIDI_FIXED_ENDPOINT,
+		.data = & (const snd_usb_midi_endpoint_info_t) {
+			.out_cables = 0x0003,
+			.in_cables  = 0x0007
+		}
+	}
+},
+{
+	USB_DEVICE(0x0582, 0x004d),
+	.driver_info = (unsigned long) & (const snd_usb_audio_quirk_t) {
+		.vendor_name = "EDIROL",
+		.product_name = "PCR-A",
+		.ifnum = 0,
+		.type = QUIRK_MIDI_FIXED_ENDPOINT,
+		.data = & (const snd_usb_midi_endpoint_info_t) {
+			.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 snd_usb_audio_quirk_t) {
+		.vendor_name = "EDIROL",
+		.product_name = "UA-3FX",
+		.ifnum = QUIRK_ANY_INTERFACE,
+		.type = QUIRK_COMPOSITE,
+		.data = (const snd_usb_audio_quirk_t[]) {
+			{
+				.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 snd_usb_audio_quirk_t) {
+		.vendor_name = "EDIROL",
+		.product_name = "UM-1SX",
+		.ifnum = 0,
+		.type = QUIRK_MIDI_STANDARD_INTERFACE
+	}
+},
+{
+	USB_DEVICE(0x0582, 0x0065),
+	.driver_info = (unsigned long) & (const snd_usb_audio_quirk_t) {
+		.vendor_name = "EDIROL",
+		.product_name = "PCR-1",
+		.ifnum = 0,
+		.type = QUIRK_MIDI_FIXED_ENDPOINT,
+		.data = & (const snd_usb_midi_endpoint_info_t) {
+			.out_cables = 0x0001,
+			.in_cables  = 0x0003
+		}
+	}
+},
+{
+	USB_DEVICE_VENDOR_SPEC(0x0582, 0x006a),
+	.driver_info = (unsigned long) & (const snd_usb_audio_quirk_t) {
+		.vendor_name = "Roland",
+		.product_name = "SP-606",
+		.ifnum = 3,
+		.type = QUIRK_MIDI_FIXED_ENDPOINT,
+		.data = & (const snd_usb_midi_endpoint_info_t) {
+			.out_cables = 0x0001,
+			.in_cables  = 0x0001
+		}
+	}
+},
+{
+	USB_DEVICE(0x0582, 0x006d),
+	.driver_info = (unsigned long) & (const snd_usb_audio_quirk_t) {
+		.vendor_name = "Roland",
+		.product_name = "FANTOM-X",
+		.ifnum = 0,
+		.type = QUIRK_MIDI_FIXED_ENDPOINT,
+		.data = & (const snd_usb_midi_endpoint_info_t) {
+			.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 snd_usb_audio_quirk_t) {
+		.vendor_name = "EDIROL",
+		.product_name = "UA-25",
+		.ifnum = QUIRK_ANY_INTERFACE,
+		.type = QUIRK_COMPOSITE,
+		.data = (const snd_usb_audio_quirk_t[]) {
+			{
+				.ifnum = 0,
+				.type = QUIRK_AUDIO_EDIROL_UA700_UA25
+			},
+			{
+				.ifnum = 1,
+				.type = QUIRK_AUDIO_EDIROL_UA700_UA25
+			},
+			{
+				.ifnum = 2,
+				.type = QUIRK_AUDIO_EDIROL_UA700_UA25
+			},
+			{
+				.ifnum = -1
+			}
+		}
+	}
+},
+{
+	USB_DEVICE(0x0582, 0x0075),
+	.driver_info = (unsigned long) & (const snd_usb_audio_quirk_t) {
+		.vendor_name = "BOSS",
+		.product_name = "DR-880",
+		.ifnum = 0,
+		.type = QUIRK_MIDI_FIXED_ENDPOINT,
+		.data = & (const snd_usb_midi_endpoint_info_t) {
+			.out_cables = 0x0001,
+			.in_cables  = 0x0001
+		}
+	}
+},
+
+/* Midiman/M-Audio devices */
+{
+	USB_DEVICE_VENDOR_SPEC(0x0763, 0x1002),
+	.driver_info = (unsigned long) & (const snd_usb_audio_quirk_t) {
+		.vendor_name = "M-Audio",
+		.product_name = "MidiSport 2x2",
+		.ifnum = QUIRK_ANY_INTERFACE,
+		.type = QUIRK_MIDI_MIDIMAN,
+		.data = & (const snd_usb_midi_endpoint_info_t) {
+			.out_cables = 0x0003,
+			.in_cables  = 0x0003
+		}
+	}
+},
+{
+	USB_DEVICE_VENDOR_SPEC(0x0763, 0x1011),
+	.driver_info = (unsigned long) & (const snd_usb_audio_quirk_t) {
+		.vendor_name = "M-Audio",
+		.product_name = "MidiSport 1x1",
+		.ifnum = QUIRK_ANY_INTERFACE,
+		.type = QUIRK_MIDI_MIDIMAN,
+		.data = & (const snd_usb_midi_endpoint_info_t) {
+			.out_cables = 0x0001,
+			.in_cables  = 0x0001
+		}
+	}
+},
+{
+	USB_DEVICE_VENDOR_SPEC(0x0763, 0x1015),
+	.driver_info = (unsigned long) & (const snd_usb_audio_quirk_t) {
+		.vendor_name = "M-Audio",
+		.product_name = "Keystation",
+		.ifnum = QUIRK_ANY_INTERFACE,
+		.type = QUIRK_MIDI_MIDIMAN,
+		.data = & (const snd_usb_midi_endpoint_info_t) {
+			.out_cables = 0x0001,
+			.in_cables  = 0x0001
+		}
+	}
+},
+{
+	USB_DEVICE_VENDOR_SPEC(0x0763, 0x1021),
+	.driver_info = (unsigned long) & (const snd_usb_audio_quirk_t) {
+		.vendor_name = "M-Audio",
+		.product_name = "MidiSport 4x4",
+		.ifnum = QUIRK_ANY_INTERFACE,
+		.type = QUIRK_MIDI_MIDIMAN,
+		.data = & (const snd_usb_midi_endpoint_info_t) {
+			.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 snd_usb_audio_quirk_t) {
+		.vendor_name = "M-Audio",
+		.product_name = "MidiSport 8x8",
+		.ifnum = QUIRK_ANY_INTERFACE,
+		.type = QUIRK_MIDI_MIDIMAN,
+		.data = & (const snd_usb_midi_endpoint_info_t) {
+			.out_cables = 0x01ff,
+			.in_cables  = 0x01ff
+		}
+	}
+},
+{
+	USB_DEVICE_VENDOR_SPEC(0x0763, 0x1033),
+	.driver_info = (unsigned long) & (const snd_usb_audio_quirk_t) {
+		.vendor_name = "M-Audio",
+		.product_name = "MidiSport 8x8",
+		.ifnum = QUIRK_ANY_INTERFACE,
+		.type = QUIRK_MIDI_MIDIMAN,
+		.data = & (const snd_usb_midi_endpoint_info_t) {
+			.out_cables = 0x01ff,
+			.in_cables  = 0x01ff
+		}
+	}
+},
+{
+	USB_DEVICE_VENDOR_SPEC(0x0763, 0x1041),
+	.driver_info = (unsigned long) & (const snd_usb_audio_quirk_t) {
+		.vendor_name = "M-Audio",
+		.product_name = "MidiSport 2x4",
+		.ifnum = QUIRK_ANY_INTERFACE,
+		.type = QUIRK_MIDI_MIDIMAN,
+		.data = & (const snd_usb_midi_endpoint_info_t) {
+			.out_cables = 0x000f,
+			.in_cables  = 0x0003
+		}
+	}
+},
+{
+	USB_DEVICE_VENDOR_SPEC(0x0763, 0x2001),
+	.driver_info = (unsigned long) & (const snd_usb_audio_quirk_t) {
+		.vendor_name = "M-Audio",
+		.product_name = "Quattro",
+		.ifnum = QUIRK_ANY_INTERFACE,
+		.type = QUIRK_COMPOSITE,
+		.data = & (const snd_usb_audio_quirk_t[]) {
+			/*
+			 * 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 snd_usb_midi_endpoint_info_t) {
+					.out_cables = 0x0001,
+					.in_cables  = 0x0001
+				}
+			},
+			{
+				.ifnum = -1
+			}
+		}
+	}
+},
+{
+	USB_DEVICE_VENDOR_SPEC(0x0763, 0x2003),
+	.driver_info = (unsigned long) & (const snd_usb_audio_quirk_t) {
+		.vendor_name = "M-Audio",
+		.product_name = "AudioPhile",
+		.ifnum = 6,
+		.type = QUIRK_MIDI_MIDIMAN,
+		.data = & (const snd_usb_midi_endpoint_info_t) {
+			.out_cables = 0x0001,
+			.in_cables  = 0x0001
+		}
+	}
+},
+{
+	USB_DEVICE_VENDOR_SPEC(0x0763, 0x2008),
+	.driver_info = (unsigned long) & (const snd_usb_audio_quirk_t) {
+		.vendor_name = "M-Audio",
+		.product_name = "Ozone",
+		.ifnum = 3,
+		.type = QUIRK_MIDI_MIDIMAN,
+		.data = & (const snd_usb_midi_endpoint_info_t) {
+			.out_cables = 0x0001,
+			.in_cables  = 0x0001
+		}
+	}
+},
+{
+	USB_DEVICE_VENDOR_SPEC(0x0763, 0x200d),
+	.driver_info = (unsigned long) & (const snd_usb_audio_quirk_t) {
+		.vendor_name = "M-Audio",
+		.product_name = "OmniStudio",
+		.ifnum = QUIRK_ANY_INTERFACE,
+		.type = QUIRK_COMPOSITE,
+		.data = & (const snd_usb_audio_quirk_t[]) {
+			{
+				.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 snd_usb_midi_endpoint_info_t) {
+					.out_cables = 0x0001,
+					.in_cables  = 0x0001
+				}
+			},
+			{
+				.ifnum = -1
+			}
+		}
+	}
+},
+
+/* Mark of the Unicorn devices */
+{
+	/* thanks to Robert A. Lerche <ral 'at' msbit.com> */
+	USB_DEVICE(0x07fd, 0x0001),
+	.driver_info = (unsigned long) & (const snd_usb_audio_quirk_t) {
+		.vendor_name = "MOTU",
+		.product_name = "Fastlane",
+		.ifnum = QUIRK_ANY_INTERFACE,
+		.type = QUIRK_COMPOSITE,
+		.data = & (const snd_usb_audio_quirk_t[]) {
+			{
+				.ifnum = 0,
+				.type = QUIRK_MIDI_MOTU
+			},
+			{
+				.ifnum = 1,
+				.type = QUIRK_IGNORE_INTERFACE
+			},
+			{
+				.ifnum = -1
+			}
+		}
+	}
+},
+
+{
+	/* Creative Sound Blaster MP3+ */
+	USB_DEVICE(0x041e, 0x3010),
+	.driver_info = (unsigned long) & (const snd_usb_audio_quirk_t) {
+		.vendor_name = "Creative Labs",
+		.product_name = "Sound Blaster MP3+",
+		.ifnum = QUIRK_NO_INTERFACE
+	}
+	
+},
+
+/* Emagic devices */
+{
+	USB_DEVICE(0x086a, 0x0001),
+	.driver_info = (unsigned long) & (const snd_usb_audio_quirk_t) {
+		.vendor_name = "Emagic",
+		/* .product_name = "Unitor8", */
+		.ifnum = 2,
+		.type = QUIRK_MIDI_EMAGIC,
+		.data = & (const snd_usb_midi_endpoint_info_t) {
+			.out_cables = 0x80ff,
+			.in_cables  = 0x80ff
+		}
+	}
+},
+{
+	USB_DEVICE(0x086a, 0x0002),
+	.driver_info = (unsigned long) & (const snd_usb_audio_quirk_t) {
+		.vendor_name = "Emagic",
+		/* .product_name = "AMT8", */
+		.ifnum = 2,
+		.type = QUIRK_MIDI_EMAGIC,
+		.data = & (const snd_usb_midi_endpoint_info_t) {
+			.out_cables = 0x80ff,
+			.in_cables  = 0x80ff
+		}
+	}
+},
+{
+	USB_DEVICE(0x086a, 0x0003),
+	.driver_info = (unsigned long) & (const snd_usb_audio_quirk_t) {
+		.vendor_name = "Emagic",
+		/* .product_name = "MT4", */
+		.ifnum = 2,
+		.type = QUIRK_MIDI_EMAGIC,
+		.data = & (const snd_usb_midi_endpoint_info_t) {
+			.out_cables = 0x800f,
+			.in_cables  = 0x8003
+		}
+	}
+},
+
+{
+	USB_DEVICE_VENDOR_SPEC(0x0ccd, 0x0013),
+	.driver_info = (unsigned long) & (const snd_usb_audio_quirk_t) {
+		.vendor_name = "Terratec",
+		.product_name = "PHASE 26",
+		.ifnum = 3,
+		.type = QUIRK_MIDI_STANDARD_INTERFACE
+	}
+},
+
+/* Novation EMS devices */
+{
+	USB_DEVICE_VENDOR_SPEC(0x1235, 0x0001),
+	.driver_info = (unsigned long) & (const snd_usb_audio_quirk_t) {
+		.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 snd_usb_audio_quirk_t) {
+		.vendor_name = "Novation",
+		.product_name = "Speedio",
+		.ifnum = 3,
+		.type = QUIRK_MIDI_NOVATION
+	}
+},
+{
+	USB_DEVICE_VENDOR_SPEC(0x1235, 0x4661),
+	.driver_info = (unsigned long) & (const snd_usb_audio_quirk_t) {
+		.vendor_name = "Novation",
+		.product_name = "ReMOTE25",
+		.ifnum = 0,
+		.type = QUIRK_MIDI_NOVATION
+	}
+},
+
+{
+	/*
+	 * 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_MIDI_STREAMING,
+	.driver_info = (unsigned long) & (const snd_usb_audio_quirk_t) {
+		.ifnum = QUIRK_ANY_INTERFACE,
+		.type = QUIRK_MIDI_STANDARD_INTERFACE
+	}
+},
+
+#undef USB_DEVICE_VENDOR_SPEC
diff --git a/sound/usb/usx2y/Makefile b/sound/usb/usx2y/Makefile
new file mode 100644
index 0000000..9ac22bc
--- /dev/null
+++ b/sound/usb/usx2y/Makefile
@@ -0,0 +1,3 @@
+snd-usb-usx2y-objs := usbusx2y.o usX2Yhwdep.o usx2yhwdeppcm.o
+
+obj-$(CONFIG_SND_USB_USX2Y) += snd-usb-usx2y.o
diff --git a/sound/usb/usx2y/usX2Yhwdep.c b/sound/usb/usx2y/usX2Yhwdep.c
new file mode 100644
index 0000000..bef9b0c
--- /dev/null
+++ b/sound/usb/usx2y/usX2Yhwdep.c
@@ -0,0 +1,280 @@
+/*
+ * 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 <sound/driver.h>
+#include <linux/interrupt.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"
+
+int usX2Y_hwdep_pcm_new(snd_card_t* card);
+
+
+static struct page * snd_us428ctls_vm_nopage(struct vm_area_struct *area, unsigned long address, int *type)
+{
+	unsigned long offset;
+	struct page * page;
+	void *vaddr;
+
+	snd_printdd("ENTER, start %lXh, ofs %lXh, pgoff %ld, addr %lXh\n",
+		   area->vm_start,
+		   address - area->vm_start,
+		   (address - area->vm_start) >> PAGE_SHIFT,
+		   address);
+	
+	offset = area->vm_pgoff << PAGE_SHIFT;
+	offset += address - area->vm_start;
+	snd_assert((offset % PAGE_SIZE) == 0, return NOPAGE_OOM);
+	vaddr = (char*)((usX2Ydev_t*)area->vm_private_data)->us428ctls_sharedmem + offset;
+	page = virt_to_page(vaddr);
+	get_page(page);
+	snd_printdd( "vaddr=%p made us428ctls_vm_nopage() return %p; offset=%lX\n", vaddr, page, offset);
+
+	if (type)
+		*type = VM_FAULT_MINOR;
+
+	return page;
+}
+
+static struct vm_operations_struct us428ctls_vm_ops = {
+	.nopage = snd_us428ctls_vm_nopage,
+};
+
+static int snd_us428ctls_mmap(snd_hwdep_t * hw, struct file *filp, struct vm_area_struct *area)
+{
+	unsigned long	size = (unsigned long)(area->vm_end - area->vm_start);
+	usX2Ydev_t	*us428 = (usX2Ydev_t*)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 (!(((usX2Ydev_t*)hw->private_data)->chip_status & USX2Y_STAT_CHIP_INIT))
+		return -EBUSY;
+
+	/* if userspace tries to mmap beyond end of our buffer, fail */ 
+        if (size > ((PAGE_SIZE - 1 + sizeof(us428ctls_sharedmem_t)) / PAGE_SIZE) * PAGE_SIZE) {
+		snd_printd( "%lu > %lu\n", size, (unsigned long)sizeof(us428ctls_sharedmem_t)); 
+                return -EINVAL;
+	}
+
+	if (!us428->us428ctls_sharedmem) {
+		init_waitqueue_head(&us428->us428ctls_wait_queue_head);
+		if(!(us428->us428ctls_sharedmem = snd_malloc_pages(sizeof(us428ctls_sharedmem_t), GFP_KERNEL)))
+			return -ENOMEM;
+		memset(us428->us428ctls_sharedmem, -1, sizeof(us428ctls_sharedmem_t));
+		us428->us428ctls_sharedmem->CtlSnapShotLast = -2;
+	}
+	area->vm_ops = &us428ctls_vm_ops;
+	area->vm_flags |= VM_RESERVED;
+	area->vm_private_data = hw->private_data;
+	return 0;
+}
+
+static unsigned int snd_us428ctls_poll(snd_hwdep_t *hw, struct file *file, poll_table *wait)
+{
+	unsigned int	mask = 0;
+	usX2Ydev_t	*us428 = (usX2Ydev_t*)hw->private_data;
+	us428ctls_sharedmem_t *shm = us428->us428ctls_sharedmem;
+	if (us428->chip_status & USX2Y_STAT_CHIP_HUP)
+		return POLLHUP;
+
+	poll_wait(file, &us428->us428ctls_wait_queue_head, wait);
+
+	if (shm != NULL && shm->CtlSnapShotLast != shm->CtlSnapShotRed)
+		mask |= POLLIN;
+
+	return mask;
+}
+
+
+static int snd_usX2Y_hwdep_open(snd_hwdep_t *hw, struct file *file)
+{
+	return 0;
+}
+
+static int snd_usX2Y_hwdep_release(snd_hwdep_t *hw, struct file *file)
+{
+	return 0;
+}
+
+static int snd_usX2Y_hwdep_dsp_status(snd_hwdep_t *hw, snd_hwdep_dsp_status_t *info)
+{
+	static char *type_ids[USX2Y_TYPE_NUMS] = {
+		[USX2Y_TYPE_122] = "us122",
+		[USX2Y_TYPE_224] = "us224",
+		[USX2Y_TYPE_428] = "us428",
+	};
+	int id = -1;
+
+	switch (le16_to_cpu(((usX2Ydev_t*)hw->private_data)->chip.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 (((usX2Ydev_t*)hw->private_data)->chip_status & USX2Y_STAT_CHIP_INIT) 
+		info->chip_ready = 1;
+ 	info->version = USX2Y_DRIVER_VERSION; 
+	return 0;
+}
+
+
+static int usX2Y_create_usbmidi(snd_card_t* card )
+{
+	static snd_usb_midi_endpoint_info_t quirk_data_1 = {
+		.out_ep =0x06,
+		.in_ep = 0x06,
+		.out_cables =	0x001,
+		.in_cables =	0x001
+	};
+	static snd_usb_audio_quirk_t quirk_1 = {
+		.vendor_name =	"TASCAM",
+		.product_name =	NAME_ALLCAPS,
+		.ifnum = 	0,
+       		.type = QUIRK_MIDI_FIXED_ENDPOINT,
+		.data = &quirk_data_1
+	};
+	static snd_usb_midi_endpoint_info_t quirk_data_2 = {
+		.out_ep =0x06,
+		.in_ep = 0x06,
+		.out_cables =	0x003,
+		.in_cables =	0x003
+	};
+	static snd_usb_audio_quirk_t 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)->chip.dev;
+	struct usb_interface *iface = usb_ifnum_to_if(dev, 0);
+	snd_usb_audio_quirk_t *quirk = le16_to_cpu(dev->descriptor.idProduct) == USB_ID_US428 ? &quirk_2 : &quirk_1;
+
+	snd_printdd("usX2Y_create_usbmidi \n");
+	return snd_usb_create_midi_interface(&usX2Y(card)->chip, iface, quirk);
+}
+
+static int usX2Y_create_alsa_devices(snd_card_t* card)
+{
+	int err;
+
+	do {
+		if ((err = usX2Y_create_usbmidi(card)) < 0) {
+			snd_printk("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(snd_hwdep_t *hw, snd_hwdep_dsp_image_t *dsp)
+{
+	usX2Ydev_t *priv = hw->private_data;
+	int	lret, err = -EINVAL;
+	snd_printdd( "dsp_load %s\n", dsp->name);
+
+	if (access_ok(VERIFY_READ, dsp->image, dsp->length)) {
+		struct usb_device* dev = priv->chip.dev;
+		char *buf = kmalloc(dsp->length, GFP_KERNEL);
+		if (!buf)
+			return -ENOMEM;
+		if (copy_from_user(buf, dsp->image, dsp->length)) {
+			kfree(buf);
+			return -EFAULT;
+		}
+		err = usb_set_interface(dev, 0, 1);
+		if (err)
+			snd_printk("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) {
+		set_current_state(TASK_UNINTERRUPTIBLE);
+		schedule_timeout(HZ/4);			// give the device some time 
+		err = usX2Y_AsyncSeq04_init(priv);
+		if (err) {
+			snd_printk("usX2Y_AsyncSeq04_init error \n");
+			return err;
+		}
+		err = usX2Y_In04_init(priv);
+		if (err) {
+			snd_printk("usX2Y_In04_init error \n");
+			return err;
+		}
+		err = usX2Y_create_alsa_devices(hw->card);
+		if (err) {
+			snd_printk("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(snd_card_t* card, struct usb_device* device)
+{
+	int err;
+	snd_hwdep_t *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.open = snd_usX2Y_hwdep_open;
+	hw->ops.release = snd_usX2Y_hwdep_release;
+	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, "/proc/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..d612a26
--- /dev/null
+++ b/sound/usb/usx2y/usX2Yhwdep.h
@@ -0,0 +1,6 @@
+#ifndef USX2YHWDEP_H
+#define USX2YHWDEP_H
+
+int usX2Y_hwdep_new(snd_card_t* card, struct usb_device* device);
+
+#endif
diff --git a/sound/usb/usx2y/usbus428ctldefs.h b/sound/usb/usx2y/usbus428ctldefs.h
new file mode 100644
index 0000000..6af1643
--- /dev/null
+++ b/sound/usb/usx2y/usbus428ctldefs.h
@@ -0,0 +1,108 @@
+/*
+ *
+ * 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];
+};
+
+typedef struct us428_ctls us428_ctls_t;
+
+typedef struct us428_setByte{
+	unsigned char Offset,
+		Value;
+}us428_setByte_t;
+
+enum {
+	eLT_Volume = 0,
+	eLT_Light
+};
+
+typedef struct usX2Y_volume {
+	unsigned char Channel,
+		LH,
+		LL,
+		RH,
+		RL;
+} usX2Y_volume_t;
+
+struct us428_lights{
+	us428_setByte_t Light[7];
+};
+typedef struct us428_lights us428_lights_t;
+
+typedef struct {
+	char type;
+	union {
+		usX2Y_volume_t	vol;
+		us428_lights_t  lights;
+	} val;
+} us428_p4out_t;
+
+#define N_us428_ctl_BUFS 16
+#define N_us428_p4out_BUFS 16
+struct us428ctls_sharedmem{
+	us428_ctls_t	CtlSnapShot[N_us428_ctl_BUFS];
+	int		CtlSnapShotDiffersAt[N_us428_ctl_BUFS];
+	int		CtlSnapShotLast, CtlSnapShotRed;
+	us428_p4out_t	p4out[N_us428_p4out_BUFS];
+	int		p4outLast, p4outSent;
+};
+typedef struct us428ctls_sharedmem us428ctls_sharedmem_t;
diff --git a/sound/usb/usx2y/usbusx2y.c b/sound/usb/usx2y/usbusx2y.c
new file mode 100644
index 0000000..b06a267
--- /dev/null
+++ b/sound/usb/usx2y/usbusx2y.c
@@ -0,0 +1,461 @@
+/*
+ * usbusy2y.c - ALSA USB US-428 Driver
+ *
+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 <sound/driver.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/moduleparam.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.1");
+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 int 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(snd_card_t *card);
+
+/* 
+ * pipe 4 is used for switching the lamps, setting samplerate, volumes ....   
+ */
+static void i_usX2Y_Out04Int(struct urb* urb, struct pt_regs *regs)
+{
+#ifdef CONFIG_SND_DEBUG
+	if (urb->status) {
+		int 		i;
+		usX2Ydev_t*	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, struct pt_regs *regs)
+{
+	int			err = 0;
+	usX2Ydev_t		*usX2Y = urb->context;
+	us428ctls_sharedmem_t	*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) {
+						us428_p4out_t *p4out = us428ctls->p4out + send;	// FIXME if more then 1 p4out is new, 1 gets lost.
+						usb_fill_bulk_urb(usX2Y->AS04.urb[j], usX2Y->chip.dev,
+								  usb_sndbulkpipe(usX2Y->chip.dev, 0x04), &p4out->val.vol, 
+								  p4out->type == eLT_Light ? sizeof(us428_lights_t) : 5,
+								  i_usX2Y_Out04Int, usX2Y);
+						err = usb_submit_urb(usX2Y->AS04.urb[j], GFP_ATOMIC);
+						us428ctls->p4outSent = send;
+						break;
+					}
+			}
+		}
+
+	if (err) {
+		snd_printk("In04Int() usb_submit_urb err=%i\n", err);
+	}
+
+	urb->dev = usX2Y->chip.dev;
+	usb_submit_urb(urb, GFP_ATOMIC);
+}
+
+/*
+ * Prepare some urbs
+ */
+int usX2Y_AsyncSeq04_init(usX2Ydev_t* usX2Y)
+{
+	int	err = 0,
+		i;
+
+	if (NULL == (usX2Y->AS04.buffer = kmalloc(URB_DataLen_AsyncSeq*URBS_AsyncSeq, GFP_KERNEL))) {
+		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->chip.dev,
+						usb_sndbulkpipe(usX2Y->chip.dev, 0x04),
+						usX2Y->AS04.buffer + URB_DataLen_AsyncSeq*i, 0,
+						i_usX2Y_Out04Int, usX2Y
+				);
+		}
+	return err;
+}
+
+int usX2Y_In04_init(usX2Ydev_t* usX2Y)
+{
+	int	err = 0;
+	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->chip.dev, usb_rcvintpipe(usX2Y->chip.dev, 0x4),
+			 usX2Y->In04Buf, 21,
+			 i_usX2Y_In04Int, usX2Y,
+			 10);
+	err = usb_submit_urb(usX2Y->In04urb, GFP_KERNEL);
+	return err;
+}
+
+static void usX2Y_unlinkSeq(snd_usX2Y_AsyncSeq_t* S)
+{
+	int	i;
+	for (i = 0; i < URBS_AsyncSeq; ++i) {
+		if (S[i].urb) {
+			usb_kill_urb(S->urb[i]);
+			usb_free_urb(S->urb[i]);
+			S->urb[i] = NULL;
+		}
+	}
+	kfree(S->buffer);
+}
+
+
+static 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 snd_card_t* usX2Y_create_card(struct usb_device* device)
+{
+	int		dev;
+	snd_card_t*	card;
+	for (dev = 0; dev < SNDRV_CARDS; ++dev)
+		if (enable[dev] && !snd_usX2Y_card_used[dev])
+			break;
+	if (dev >= SNDRV_CARDS)
+		return NULL;
+	card = snd_card_new(index[dev], id[dev], THIS_MODULE, sizeof(usX2Ydev_t));
+	if (!card)
+		return NULL;
+	snd_usX2Y_card_used[usX2Y(card)->chip.index = dev] = 1;
+	card->private_free = snd_usX2Y_card_private_free;
+	usX2Y(card)->chip.dev = device;
+	usX2Y(card)->chip.card = card;
+	init_waitqueue_head(&usX2Y(card)->prepare_wait_queue);
+	init_MUTEX (&usX2Y(card)->prepare_mutex);
+	INIT_LIST_HEAD(&usX2Y(card)->chip.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)->chip.dev->bus->busnum, usX2Y(card)->chip.dev->devnum
+		);
+	snd_card_set_dev(card, &device->dev);
+	return card;
+}
+
+
+static void* usX2Y_usb_probe(struct usb_device* device, struct usb_interface *intf, const struct usb_device_id* device_id)
+{
+	int		err;
+	snd_card_t*	card;
+	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) ||
+	    !(card = usX2Y_create_card(device)))
+		return NULL;
+	if ((err = usX2Y_hwdep_new(card, device)) < 0  ||
+	    (err = snd_card_register(card)) < 0) {
+		snd_card_free(card);
+		return NULL;
+	}
+	return card;
+}
+
+/*
+ * new 2.5 USB kernel API
+ */
+static int snd_usX2Y_probe(struct usb_interface *intf, const struct usb_device_id *id)
+{
+	void *chip;
+	chip = usX2Y_usb_probe(interface_to_usbdev(intf), intf, id);
+	if (chip) {
+		dev_set_drvdata(&intf->dev, chip);
+		return 0;
+	} else
+		return -EIO;
+}
+
+static void snd_usX2Y_disconnect(struct usb_interface *intf)
+{
+	usX2Y_usb_disconnect(interface_to_usbdev(intf),
+				 dev_get_drvdata(&intf->dev));
+}
+
+MODULE_DEVICE_TABLE(usb, snd_usX2Y_usb_id_table);
+static struct usb_driver snd_usX2Y_usb_driver = {
+ 	.owner =	THIS_MODULE,
+	.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(snd_card_t *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)->chip.index >= 0  &&  usX2Y(card)->chip.index < SNDRV_CARDS)
+		snd_usX2Y_card_used[usX2Y(card)->chip.index] = 0;
+}
+
+/*
+ * Frees the device.
+ */
+static void usX2Y_usb_disconnect(struct usb_device* device, void* ptr)
+{
+	if (ptr) {
+		usX2Ydev_t* usX2Y = usX2Y((snd_card_t*)ptr);
+		struct list_head* p;
+		if (usX2Y->chip_status == USX2Y_STAT_CHIP_HUP)	// on 2.6.1 kernel snd_usbmidi_disconnect()
+			return;					// calls us back. better leave :-) .
+		usX2Y->chip.shutdown = 1;
+		usX2Y->chip_status = USX2Y_STAT_CHIP_HUP;
+		usX2Y_unlinkSeq(&usX2Y->AS04);
+		usb_kill_urb(usX2Y->In04urb);
+		snd_card_disconnect((snd_card_t*)ptr);
+		/* release the midi resources */
+		list_for_each(p, &usX2Y->chip.midi_list) {
+			snd_usbmidi_disconnect(p, &snd_usX2Y_usb_driver);
+		}
+		if (usX2Y->us428ctls_sharedmem) 
+			wake_up(&usX2Y->us428ctls_wait_queue_head);
+		snd_card_free_in_thread((snd_card_t*)ptr);
+	}
+}
+
+static int __init snd_usX2Y_module_init(void)
+{
+	return usb_register(&snd_usX2Y_usb_driver);
+}
+
+static void __exit snd_usX2Y_module_exit(void)
+{
+	usb_deregister(&snd_usX2Y_usb_driver);
+}
+
+module_init(snd_usX2Y_module_init)
+module_exit(snd_usX2Y_module_exit)
diff --git a/sound/usb/usx2y/usbusx2y.h b/sound/usb/usx2y/usbusx2y.h
new file mode 100644
index 0000000..f65f3a7
--- /dev/null
+++ b/sound/usb/usx2y/usbusx2y.h
@@ -0,0 +1,84 @@
+#ifndef USBUSX2Y_H
+#define USBUSX2Y_H
+#include "../usbaudio.h"
+#include "usbus428ctldefs.h" 
+
+#define NRURBS	        2	
+
+
+#define URBS_AsyncSeq 10
+#define URB_DataLen_AsyncSeq 32
+typedef struct {
+	struct urb*	urb[URBS_AsyncSeq];
+	char*   buffer;
+} snd_usX2Y_AsyncSeq_t;
+
+typedef struct {
+	int	submitted;
+	int	len;
+	struct urb*	urb[0];
+} snd_usX2Y_urbSeq_t;
+
+typedef struct snd_usX2Y_substream snd_usX2Y_substream_t;
+#include "usx2yhwdeppcm.h"
+
+typedef struct {
+	snd_usb_audio_t 	chip;
+	int			stride;
+	struct urb		*In04urb;
+	void			*In04Buf;
+	char			In04Last[24];
+	unsigned		In04IntCalls;
+	snd_usX2Y_urbSeq_t	*US04;
+	wait_queue_head_t	In04WaitQueue;
+	snd_usX2Y_AsyncSeq_t	AS04;
+	unsigned int		rate,
+				format;
+	int			chip_status;
+	struct semaphore	prepare_mutex;
+	us428ctls_sharedmem_t	*us428ctls_sharedmem;
+	int			wait_iso_frame;
+	wait_queue_head_t	us428ctls_wait_queue_head;
+	snd_usX2Y_hwdep_pcm_shm_t	*hwdep_pcm_shm;
+	snd_usX2Y_substream_t	*subs[4];
+	snd_usX2Y_substream_t	* volatile  prepare_subs;
+	wait_queue_head_t	prepare_wait_queue;
+} usX2Ydev_t;
+
+
+struct snd_usX2Y_substream {
+	usX2Ydev_t	*usX2Y;
+	snd_pcm_substream_t *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) ((usX2Ydev_t*)(c)->private_data)
+
+int usX2Y_audio_create(snd_card_t* card);
+
+int usX2Y_AsyncSeq04_init(usX2Ydev_t* usX2Y);
+int usX2Y_In04_init(usX2Ydev_t* 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..4c292e0
--- /dev/null
+++ b/sound/usb/usx2y/usbusx2yaudio.c
@@ -0,0 +1,1026 @@
+/*
+ *   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 <sound/driver.h>
+#include <linux/interrupt.h>
+#include <linux/usb.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(snd_usX2Y_substream_t *subs)
+{
+	struct urb	*urb = subs->completed_urb;
+	snd_pcm_runtime_t *runtime = subs->pcm_substream->runtime;
+	unsigned char	*cp;
+	int 		i, len, lens = 0, hwptr_done = subs->hwptr_done;
+	usX2Ydev_t	*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("activ frame status %i. Most propably 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(snd_usX2Y_substream_t *subs,
+				  struct urb *cap_urb,
+				  struct urb *urb)
+{
+	int count, counts, pack;
+	usX2Ydev_t* usX2Y = subs->usX2Y;
+	snd_pcm_runtime_t *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("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(snd_usX2Y_substream_t *subs, struct urb *urb)
+{
+	snd_pcm_runtime_t *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(snd_usX2Y_substream_t *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->chip.dev; /* we need to set this at each time */
+	if ((err = usb_submit_urb(urb, GFP_ATOMIC)) < 0) {
+		snd_printk("usb_submit_urb() returned %i\n", err);
+		return err;
+	}
+	return 0;
+}
+
+static inline int usX2Y_usbframe_complete(snd_usX2Y_substream_t *capsubs, snd_usX2Y_substream_t *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(usX2Ydev_t *usX2Y)
+{
+	int s, u;
+	for (s = 0; s < 4; s++) {
+		snd_usX2Y_substream_t *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++) {
+		snd_usX2Y_substream_t *subs = usX2Y->subs[s];
+		if (subs) {
+			if (atomic_read(&subs->state) >= state_PRERUNNING) {
+				snd_pcm_stop(subs->pcm_substream, SNDRV_PCM_STATE_XRUN);
+			}
+			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(usX2Ydev_t *usX2Y, snd_usX2Y_substream_t *subs, struct urb *urb)
+{
+	snd_printk("ep=%i stalled with status=%i\n", subs->endpoint, urb->status);
+	urb->status = 0;
+	usX2Y_clients_stop(usX2Y);
+}
+
+static void usX2Y_error_sequence(usX2Ydev_t *usX2Y, snd_usX2Y_substream_t *subs, struct urb *urb)
+{
+	snd_printk("Sequence Error!(hcd_frame=%i ep=%i%s;wait=%i,frame=%i).\n"
+		   "Most propably some urb of usb-frame %i is still missing.\n"
+		   "Cause could be too long delays in usb-hcd interrupt handling.\n",
+		   usb_get_current_frame_number(usX2Y->chip.dev),
+		   subs->endpoint, usb_pipein(urb->pipe) ? "in" : "out", usX2Y->wait_iso_frame, urb->start_frame, usX2Y->wait_iso_frame);
+	usX2Y_clients_stop(usX2Y);
+}
+
+static void i_usX2Y_urb_complete(struct urb *urb, struct pt_regs *regs)
+{
+	snd_usX2Y_substream_t *subs = (snd_usX2Y_substream_t*)urb->context;
+	usX2Ydev_t *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->chip.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;
+	}
+	if (likely((0xFFFF & urb->start_frame) == usX2Y->wait_iso_frame))
+		subs->completed_urb = urb;
+	else {
+		usX2Y_error_sequence(usX2Y, subs, urb);
+		return;
+	}
+	{
+		snd_usX2Y_substream_t *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)) {
+				if (nr_of_packs() <= urb->start_frame &&
+				    urb->start_frame <= (2 * nr_of_packs() - 1))	// uhci and ohci
+					usX2Y->wait_iso_frame = urb->start_frame - nr_of_packs();
+				else
+					usX2Y->wait_iso_frame +=  nr_of_packs();
+			} else {
+				snd_printdd("\n");
+				usX2Y_clients_stop(usX2Y);
+			}
+		}
+	}
+}
+
+static void usX2Y_urbs_set_complete(usX2Ydev_t * usX2Y, void (*complete)(struct urb *, struct pt_regs *))
+{
+	int s, u;
+	for (s = 0; s < 4; s++) {
+		snd_usX2Y_substream_t *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(usX2Ydev_t * usX2Y)
+{
+	usX2Y_urbs_set_complete(usX2Y, i_usX2Y_urb_complete);
+	usX2Y->prepare_subs = NULL;
+}
+
+static void i_usX2Y_subs_startup(struct urb *urb, struct pt_regs *regs)
+{
+	snd_usX2Y_substream_t *subs = (snd_usX2Y_substream_t*)urb->context;
+	usX2Ydev_t *usX2Y = subs->usX2Y;
+	snd_usX2Y_substream_t *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, regs);
+}
+
+static void usX2Y_subs_prepare(snd_usX2Y_substream_t *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(snd_usX2Y_substream_t *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]);
+
+	if (subs->tmpbuf) {
+		kfree(subs->tmpbuf);
+		subs->tmpbuf = NULL;
+	}
+}
+/*
+ * initialize a substream's urbs
+ */
+static int usX2Y_urbs_allocate(snd_usX2Y_substream_t *subs)
+{
+	int i;
+	unsigned int pipe;
+	int is_playback = subs == subs->usX2Y->subs[SNDRV_PCM_STREAM_PLAYBACK];
+	struct usb_device *dev = subs->usX2Y->chip.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 (NULL == subs->tmpbuf) {
+			snd_printk(KERN_ERR "cannot malloc tmpbuf\n");
+			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(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(snd_usX2Y_substream_t *subs)
+{
+	usX2Ydev_t *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(snd_usX2Y_substream_t *subs)
+{
+	int i, err;
+	usX2Ydev_t *usX2Y = subs->usX2Y;
+
+	if ((err = usX2Y_urbs_allocate(subs)) < 0)
+		return err;
+	subs->completed_urb = NULL;
+	for (i = 0; i < 4; i++) {
+		snd_usX2Y_substream_t *subs = usX2Y->subs[i];
+		if (subs != NULL && atomic_read(&subs->state) >= state_PREPARED)
+			goto start;
+	}
+	usX2Y->wait_iso_frame = -1;
+ 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->chip.dev;
+				urb->transfer_flags = URB_ISO_ASAP;
+				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 (0 > usX2Y->wait_iso_frame)
+						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(snd_pcm_substream_t *substream)
+{
+	snd_usX2Y_substream_t *subs = (snd_usX2Y_substream_t *)substream->runtime->private_data;
+	return subs->hwptr_done;
+}
+/*
+ * start/stop substream
+ */
+static int snd_usX2Y_pcm_trigger(snd_pcm_substream_t *substream, int cmd)
+{
+	snd_usX2Y_substream_t *subs = (snd_usX2Y_substream_t *)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 pt_regs *regs)
+{
+	usX2Ydev_t*	usX2Y = urb->context;
+	
+	if (urb->status) {
+		snd_printk("snd_usX2Y_04Int() urb->status=%i\n", urb->status);
+	}
+	if (0 == --usX2Y->US04->len)
+		wake_up(&usX2Y->In04WaitQueue);
+}
+
+static int usX2Y_rate_set(usX2Ydev_t *usX2Y, int rate)
+{
+	int			err = 0, i;
+	snd_usX2Y_urbSeq_t	*us = NULL;
+	int			*usbdata = NULL;
+	struct s_c2		*ra = rate == 48000 ? SetRate48000 : SetRate44100;
+
+	if (usX2Y->rate != rate) {
+		us = kmalloc(sizeof(*us) + sizeof(struct urb*) * NOOF_SETRATE_URBS, GFP_KERNEL);
+		if (NULL == us) {
+			err = -ENOMEM;
+			goto cleanup;
+		}
+		memset(us, 0, sizeof(*us) + sizeof(struct urb*) * NOOF_SETRATE_URBS); 
+		usbdata = kmalloc(sizeof(int)*NOOF_SETRATE_URBS, 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->chip.dev, usb_sndbulkpipe(usX2Y->chip.dev, 4),
+					  usbdata + i, 2, i_usX2Y_04Int, usX2Y);
+#ifdef OLD_USB
+			us->urb[i]->transfer_flags = USB_QUEUE_BULK;
+#endif
+		}
+		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(usX2Ydev_t *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->chip.midi_list) {
+		snd_usbmidi_input_stop(p);
+	}
+	usb_kill_urb(usX2Y->In04urb);
+	if ((err = usb_set_interface(usX2Y->chip.dev, 0, alternate))) {
+		snd_printk("usb_set_interface error \n");
+		return err;
+	}
+	usX2Y->In04urb->dev = usX2Y->chip.dev;
+	err = usb_submit_urb(usX2Y->In04urb, GFP_KERNEL);
+	list_for_each(p, &usX2Y->chip.midi_list) {
+		snd_usbmidi_input_start(p);
+	}
+	usX2Y->format = format;
+	usX2Y->rate = 0;
+	return err;
+}
+
+
+static int snd_usX2Y_pcm_hw_params(snd_pcm_substream_t *substream,
+				   snd_pcm_hw_params_t *hw_params)
+{
+	int			err = 0;
+	unsigned int		rate = params_rate(hw_params);
+	snd_pcm_format_t	format = params_format(hw_params);
+	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
+		snd_card_t *card = substream->pstr->pcm->card;
+		struct list_head *list;
+		list_for_each(list, &card->devices) {
+			snd_device_t *dev;
+			snd_pcm_t *pcm;
+			int s;
+			dev = snd_device(list);
+			if (dev->type != SNDRV_DEV_PCM)
+				continue;
+			pcm = dev->device_data;
+			for (s = 0; s < 2; ++s) {
+				snd_pcm_substream_t *test_substream;
+				test_substream = pcm->streams[s].substream;
+				if (test_substream && test_substream != substream  &&
+				    test_substream->runtime &&
+				    ((test_substream->runtime->format &&
+				      test_substream->runtime->format != format) ||
+				     (test_substream->runtime->rate &&
+				      test_substream->runtime->rate != rate)))
+					return -EINVAL;
+			}
+		}
+	}
+	if (0 > (err = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params)))) {
+		snd_printk("snd_pcm_lib_malloc_pages(%p, %i) returned %i\n", substream, params_buffer_bytes(hw_params), err);
+		return err;
+	}
+	return 0;
+}
+
+/*
+ * free the buffer
+ */
+static int snd_usX2Y_pcm_hw_free(snd_pcm_substream_t *substream)
+{
+	snd_pcm_runtime_t *runtime = substream->runtime;
+	snd_usX2Y_substream_t *subs = (snd_usX2Y_substream_t *)runtime->private_data;
+	down(&subs->usX2Y->prepare_mutex);
+	snd_printdd("snd_usX2Y_hw_free(%p)\n", substream);
+
+	if (SNDRV_PCM_STREAM_PLAYBACK == substream->stream) {
+		snd_usX2Y_substream_t *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 {
+		snd_usX2Y_substream_t *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);
+		}
+	}
+	up(&subs->usX2Y->prepare_mutex);
+	return snd_pcm_lib_free_pages(substream);
+}
+/*
+ * prepare callback
+ *
+ * set format and initialize urbs
+ */
+static int snd_usX2Y_pcm_prepare(snd_pcm_substream_t *substream)
+{
+	snd_pcm_runtime_t *runtime = substream->runtime;
+	snd_usX2Y_substream_t *subs = (snd_usX2Y_substream_t *)runtime->private_data;
+	usX2Ydev_t *usX2Y = subs->usX2Y;
+	snd_usX2Y_substream_t *capsubs = subs->usX2Y->subs[SNDRV_PCM_STREAM_CAPTURE];
+	int err = 0;
+	snd_printdd("snd_usX2Y_pcm_prepare(%p)\n", substream);
+
+	down(&usX2Y->prepare_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:
+	up(&usX2Y->prepare_mutex);
+	return err;
+}
+
+static snd_pcm_hardware_t snd_usX2Y_2c =
+{
+	.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 =            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(snd_pcm_substream_t *substream)
+{
+	snd_usX2Y_substream_t	*subs = ((snd_usX2Y_substream_t **)
+					 snd_pcm_substream_chip(substream))[substream->stream];
+	snd_pcm_runtime_t	*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(snd_pcm_substream_t *substream)
+{
+	snd_pcm_runtime_t *runtime = substream->runtime;
+	snd_usX2Y_substream_t *subs = (snd_usX2Y_substream_t *)runtime->private_data;
+	int err = 0;
+
+	subs->pcm_substream = NULL;
+
+	return err;
+}
+
+
+static snd_pcm_ops_t 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(snd_usX2Y_substream_t **usX2Y_substream)
+{
+	if (NULL != usX2Y_substream[SNDRV_PCM_STREAM_PLAYBACK]) {
+		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(snd_pcm_t *pcm)
+{
+	snd_usX2Y_substream_t **usX2Y_stream = pcm->private_data;
+	if (usX2Y_stream) {
+		snd_pcm_lib_preallocate_free_for_all(pcm);
+		usX2Y_audio_stream_free(usX2Y_stream);
+	}
+}
+
+static int usX2Y_audio_stream_new(snd_card_t *card, int playback_endpoint, int capture_endpoint)
+{
+	snd_pcm_t *pcm;
+	int err, i;
+	snd_usX2Y_substream_t **usX2Y_substream =
+		usX2Y(card)->subs + 2 * usX2Y(card)->chip.pcm_devs;
+
+	for (i = playback_endpoint ? SNDRV_PCM_STREAM_PLAYBACK : SNDRV_PCM_STREAM_CAPTURE;
+	     i <= SNDRV_PCM_STREAM_CAPTURE; ++i) {
+		usX2Y_substream[i] = kcalloc(1, sizeof(snd_usX2Y_substream_t), GFP_KERNEL);
+		if (NULL == usX2Y_substream[i]) {
+			snd_printk(KERN_ERR "cannot malloc\n");
+			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)->chip.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)->chip.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)->chip.pcm_devs++;
+
+	return 0;
+}
+
+/*
+ * create a chip instance and set its names.
+ */
+int usX2Y_audio_create(snd_card_t* card)
+{
+	int err = 0;
+	
+	INIT_LIST_HEAD(&usX2Y(card)->chip.pcm_list);
+
+	if (0 > (err = usX2Y_audio_stream_new(card, 0xA, 0x8)))
+		return err;
+	if (le16_to_cpu(usX2Y(card)->chip.dev->descriptor.idProduct) == USB_ID_US428)
+	     if (0 > (err = usX2Y_audio_stream_new(card, 0, 0xA)))
+		     return err;
+	if (le16_to_cpu(usX2Y(card)->chip.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..bb2c8e9
--- /dev/null
+++ b/sound/usb/usx2y/usx2yhwdeppcm.c
@@ -0,0 +1,807 @@
+/*
+ *   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 dependant 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, funcionalities should migrate to more apropriate 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 "usbusx2yaudio.c"
+
+#if defined(USX2Y_NRPACKS_VARIABLE) || (!defined(USX2Y_NRPACKS_VARIABLE) &&  USX2Y_NRPACKS == 1)
+
+#include <sound/hwdep.h>
+
+
+static int usX2Y_usbpcm_urb_capt_retire(snd_usX2Y_substream_t *subs)
+{
+	struct urb	*urb = subs->completed_urb;
+	snd_pcm_runtime_t *runtime = subs->pcm_substream->runtime;
+	int 		i, lens = 0, hwptr_done = subs->hwptr_done;
+	usX2Ydev_t	*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("activ frame status %i. Most propably 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(snd_pcm_runtime_t *runtime, usX2Ydev_t * 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(snd_usX2Y_substream_t *subs,
+				  struct urb *urb)
+{
+	int count, counts, pack;
+	usX2Ydev_t *usX2Y = subs->usX2Y;
+	struct snd_usX2Y_hwdep_pcm_shm *shm = usX2Y->hwdep_pcm_shm;
+	snd_pcm_runtime_t *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("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(snd_usX2Y_substream_t *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) {
+			snd_usX2Y_hwdep_pcm_shm_t *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(snd_usX2Y_substream_t *capsubs,
+					   snd_usX2Y_substream_t *capsubs2,
+					   snd_usX2Y_substream_t *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 pt_regs *regs)
+{
+	snd_usX2Y_substream_t *subs = (snd_usX2Y_substream_t*)urb->context;
+	usX2Ydev_t *usX2Y = subs->usX2Y;
+	snd_usX2Y_substream_t *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->chip.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;
+	}
+	if (likely((0xFFFF & urb->start_frame) == usX2Y->wait_iso_frame))
+		subs->completed_urb = urb;
+	else {
+		usX2Y_error_sequence(usX2Y, subs, urb);
+		return;
+	}
+
+	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)) {
+			if (nr_of_packs() <= urb->start_frame &&
+			    urb->start_frame <= (2 * nr_of_packs() - 1))	// uhci and ohci
+				usX2Y->wait_iso_frame = urb->start_frame - nr_of_packs();
+			else
+				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(snd_usX2Y_substream_t *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(usX2Ydev_t * 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 pt_regs *regs)
+{
+	snd_usX2Y_substream_t *subs = (snd_usX2Y_substream_t*)urb->context;
+	usX2Ydev_t *usX2Y = subs->usX2Y;
+	snd_usX2Y_substream_t *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]) {
+			snd_usX2Y_substream_t *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, regs);
+}
+
+/*
+ * initialize a substream's urbs
+ */
+static int usX2Y_usbpcm_urbs_allocate(snd_usX2Y_substream_t *subs)
+{
+	int i;
+	unsigned int pipe;
+	int is_playback = subs == subs->usX2Y->subs[SNDRV_PCM_STREAM_PLAYBACK];
+	struct usb_device *dev = subs->usX2Y->chip.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(snd_pcm_substream_t *substream)
+{
+	snd_pcm_runtime_t *runtime = substream->runtime;
+	snd_usX2Y_substream_t *subs = (snd_usX2Y_substream_t *)runtime->private_data,
+		*cap_subs2 = subs->usX2Y->subs[SNDRV_PCM_STREAM_CAPTURE + 2];
+	down(&subs->usX2Y->prepare_mutex);
+	snd_printdd("snd_usX2Y_usbpcm_hw_free(%p)\n", substream);
+
+	if (SNDRV_PCM_STREAM_PLAYBACK == substream->stream) {
+		snd_usX2Y_substream_t *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 {
+		snd_usX2Y_substream_t *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);
+		}
+	}
+	up(&subs->usX2Y->prepare_mutex);
+	return snd_pcm_lib_free_pages(substream);
+}
+
+static void usX2Y_usbpcm_subs_startup(snd_usX2Y_substream_t *subs)
+{
+	usX2Ydev_t * usX2Y = subs->usX2Y;
+	usX2Y->prepare_subs = subs;
+	subs->urb[0]->start_frame = -1;
+	smp_wmb();	// Make shure 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(snd_usX2Y_substream_t *subs)
+{
+	int	p, u, err,
+		stream = subs->pcm_substream->stream;
+	usX2Ydev_t *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) {
+		snd_usX2Y_substream_t *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++) {
+		snd_usX2Y_substream_t *subs = usX2Y->subs[p];
+		if (subs != NULL && atomic_read(&subs->state) >= state_PREPARED)
+			goto start;
+	}
+	usX2Y->wait_iso_frame = -1;
+
+ start:
+	usX2Y_usbpcm_subs_startup(subs);
+	for (u = 0; u < NRURBS; u++) {
+		for (p = 0; 3 >= (stream + p); p += 2) {
+			snd_usX2Y_substream_t *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->chip.dev;
+					urb->transfer_flags = URB_ISO_ASAP;
+					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 (0 > usX2Y->wait_iso_frame)
+							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(snd_pcm_substream_t *substream)
+{
+	snd_pcm_runtime_t *runtime = substream->runtime;
+	snd_usX2Y_substream_t *subs = (snd_usX2Y_substream_t *)runtime->private_data;
+	usX2Ydev_t *usX2Y = subs->usX2Y;
+	snd_usX2Y_substream_t *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(snd_usX2Y_hwdep_pcm_shm_t), GFP_KERNEL)))
+			return -ENOMEM;
+		memset(usX2Y->hwdep_pcm_shm, 0, sizeof(snd_usX2Y_hwdep_pcm_shm_t));
+	}
+
+	down(&usX2Y->prepare_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) {
+				signed long timeout;
+				snd_printd("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);
+				set_current_state(TASK_INTERRUPTIBLE);
+				timeout = schedule_timeout(HZ/100 + 1);
+				if (signal_pending(current)) {
+					err = -ERESTARTSYS;
+					goto up_prepare_mutex;
+				}
+			} 
+			if (0 > (err = usX2Y_usbpcm_urbs_start(subs)))
+				goto up_prepare_mutex;
+		}
+		snd_printd("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:
+	up(&usX2Y->prepare_mutex);
+	return err;
+}
+
+static snd_pcm_hardware_t 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(snd_pcm_substream_t *substream)
+{
+	snd_usX2Y_substream_t	*subs = ((snd_usX2Y_substream_t **)
+					 snd_pcm_substream_chip(substream))[substream->stream];
+	snd_pcm_runtime_t	*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(snd_pcm_substream_t *substream)
+{
+	snd_pcm_runtime_t *runtime = substream->runtime;
+	snd_usX2Y_substream_t *subs = (snd_usX2Y_substream_t *)runtime->private_data;
+	int err = 0;
+	snd_printd("\n");
+	subs->pcm_substream = NULL;
+	return err;
+}
+
+
+static snd_pcm_ops_t 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_lock_check(snd_card_t *card)
+{
+	struct list_head *list;
+	snd_device_t *dev;
+	snd_pcm_t *pcm;
+	int err = 0;
+	list_for_each(list, &card->devices) {
+		dev = snd_device(list);
+		if (dev->type != SNDRV_DEV_PCM)
+			continue;
+		pcm = dev->device_data;
+		down(&pcm->open_mutex);
+	}
+	list_for_each(list, &card->devices) {
+		int s;
+		dev = snd_device(list);
+		if (dev->type != SNDRV_DEV_PCM)
+			continue;
+		pcm = dev->device_data;
+		for (s = 0; s < 2; ++s) {
+			snd_pcm_substream_t *substream;
+			substream = pcm->streams[s].substream;
+			if (substream && substream->open_flag)
+				err = -EBUSY;
+		}
+	}
+	return err;
+}
+
+
+static void usX2Y_pcms_unlock(snd_card_t *card)
+{
+	struct list_head *list;
+	snd_device_t *dev;
+	snd_pcm_t *pcm;
+	list_for_each(list, &card->devices) {
+		dev = snd_device(list);
+		if (dev->type != SNDRV_DEV_PCM)
+			continue;
+		pcm = dev->device_data;
+		up(&pcm->open_mutex);
+	}
+}
+
+
+static int snd_usX2Y_hwdep_pcm_open(snd_hwdep_t *hw, struct file *file)
+{
+	// we need to be the first 
+	snd_card_t *card = hw->card;
+	int err = usX2Y_pcms_lock_check(card);
+	if (0 == err)
+		usX2Y(card)->chip_status |= USX2Y_STAT_CHIP_MMAP_PCM_URBS;
+	usX2Y_pcms_unlock(card);
+	return err;
+}
+
+
+static int snd_usX2Y_hwdep_pcm_release(snd_hwdep_t *hw, struct file *file)
+{
+	snd_card_t *card = hw->card;
+	int err = usX2Y_pcms_lock_check(card);
+	if (0 == err)
+		usX2Y(hw->card)->chip_status &= ~USX2Y_STAT_CHIP_MMAP_PCM_URBS;
+	usX2Y_pcms_unlock(card);
+	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 struct page * snd_usX2Y_hwdep_pcm_vm_nopage(struct vm_area_struct *area, unsigned long address, int *type)
+{
+	unsigned long offset;
+	struct page *page;
+	void *vaddr;
+
+	offset = area->vm_pgoff << PAGE_SHIFT;
+	offset += address - area->vm_start;
+	snd_assert((offset % PAGE_SIZE) == 0, return NOPAGE_OOM);
+	vaddr = (char*)((usX2Ydev_t*)area->vm_private_data)->hwdep_pcm_shm + offset;
+	page = virt_to_page(vaddr);
+
+	if (type)
+		*type = VM_FAULT_MINOR;
+
+	return page;
+}
+
+
+static struct vm_operations_struct snd_usX2Y_hwdep_pcm_vm_ops = {
+	.open = snd_usX2Y_hwdep_pcm_vm_open,
+	.close = snd_usX2Y_hwdep_pcm_vm_close,
+	.nopage = snd_usX2Y_hwdep_pcm_vm_nopage,
+};
+
+
+static int snd_usX2Y_hwdep_pcm_mmap(snd_hwdep_t * hw, struct file *filp, struct vm_area_struct *area)
+{
+	unsigned long	size = (unsigned long)(area->vm_end - area->vm_start);
+	usX2Ydev_t	*usX2Y = (usX2Ydev_t*)hw->private_data;
+
+	if (!(((usX2Ydev_t*)hw->private_data)->chip_status & USX2Y_STAT_CHIP_INIT))
+		return -EBUSY;
+
+	/* if userspace tries to mmap beyond end of our buffer, fail */ 
+	if (size > PAGE_ALIGN(sizeof(snd_usX2Y_hwdep_pcm_shm_t))) {
+		snd_printd("%lu > %lu\n", size, (unsigned long)sizeof(snd_usX2Y_hwdep_pcm_shm_t)); 
+		return -EINVAL;
+	}
+
+	if (!usX2Y->hwdep_pcm_shm) {
+		return -ENODEV;
+	}
+	area->vm_ops = &snd_usX2Y_hwdep_pcm_vm_ops;
+	area->vm_flags |= VM_RESERVED;
+	snd_printd("vm_flags=0x%lX\n", area->vm_flags);
+	area->vm_private_data = hw->private_data;
+	return 0;
+}
+
+
+static void snd_usX2Y_hwdep_pcm_private_free(snd_hwdep_t *hwdep)
+{
+	usX2Ydev_t *usX2Y = (usX2Ydev_t *)hwdep->private_data;
+	if (NULL != usX2Y->hwdep_pcm_shm)
+		snd_free_pages(usX2Y->hwdep_pcm_shm, sizeof(snd_usX2Y_hwdep_pcm_shm_t));
+}
+
+
+static void snd_usX2Y_usbpcm_private_free(snd_pcm_t *pcm)
+{
+	snd_pcm_lib_preallocate_free_for_all(pcm);
+}
+
+
+int usX2Y_hwdep_pcm_new(snd_card_t* card)
+{
+	int err;
+	snd_hwdep_t *hw;
+	snd_pcm_t *pcm;
+	struct usb_device *dev = usX2Y(card)->chip.dev;
+	if (1 != nr_of_packs())
+		return 0;
+
+	if ((err = snd_hwdep_new(card, SND_USX2Y_USBPCM_ID, 1, &hw)) < 0) {
+		snd_printd("\n");
+		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, "/proc/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->private_free = snd_usX2Y_usbpcm_private_free;
+	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))) {
+		snd_usX2Y_usbpcm_private_free(pcm);
+		return err;
+	}
+
+
+	return 0;
+}
+
+#else
+
+int usX2Y_hwdep_pcm_new(snd_card_t* card)
+{
+	return 0;
+}
+
+#endif
diff --git a/sound/usb/usx2y/usx2yhwdeppcm.h b/sound/usb/usx2y/usx2yhwdeppcm.h
new file mode 100644
index 0000000..d68f0cb
--- /dev/null
+++ b/sound/usb/usx2y/usx2yhwdeppcm.h
@@ -0,0 +1,21 @@
+#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;
+};
+typedef struct snd_usX2Y_hwdep_pcm_shm snd_usX2Y_hwdep_pcm_shm_t;