summaryrefslogtreecommitdiffstats
path: root/sound/usb
diff options
context:
space:
mode:
Diffstat (limited to 'sound/usb')
-rw-r--r--sound/usb/Kconfig32
-rw-r--r--sound/usb/Makefile12
-rw-r--r--sound/usb/usbaudio.c3337
-rw-r--r--sound/usb/usbaudio.h251
-rw-r--r--sound/usb/usbmidi.c1564
-rw-r--r--sound/usb/usbmixer.c1545
-rw-r--r--sound/usb/usbmixer_maps.c135
-rw-r--r--sound/usb/usbquirks.h1202
-rw-r--r--sound/usb/usx2y/Makefile3
-rw-r--r--sound/usb/usx2y/usX2Yhwdep.c280
-rw-r--r--sound/usb/usx2y/usX2Yhwdep.h6
-rw-r--r--sound/usb/usx2y/usbus428ctldefs.h108
-rw-r--r--sound/usb/usx2y/usbusx2y.c461
-rw-r--r--sound/usb/usx2y/usbusx2y.h84
-rw-r--r--sound/usb/usx2y/usbusx2yaudio.c1026
-rw-r--r--sound/usb/usx2y/usx2y.h51
-rw-r--r--sound/usb/usx2y/usx2yhwdeppcm.c807
-rw-r--r--sound/usb/usx2y/usx2yhwdeppcm.h21
18 files changed, 10925 insertions, 0 deletions
diff --git a/sound/usb/Kconfig b/sound/usb/Kconfig
new file mode 100644
index 00000000000..9329e992c84
--- /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 00000000000..2c1dc11a72e
--- /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 00000000000..84b0bbddbd2
--- /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 00000000000..eecbf19fcb6
--- /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 00000000000..5d32857ff95
--- /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 00000000000..5f1906915aa
--- /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 00000000000..c69b4b0875f
--- /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 00000000000..88bbd944d4b
--- /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 00000000000..9ac22bce112
--- /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 00000000000..bef9b0c142c
--- /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 00000000000..d612a26eb77
--- /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 00000000000..6af16438d2c
--- /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 00000000000..b06a267e5da
--- /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 00000000000..f65f3a7194c
--- /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 00000000000..4c292e09006
--- /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 00000000000..7e59263dd89
--- /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 00000000000..bb2c8e9000c
--- /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 00000000000..d68f0cbdbbe
--- /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;