summaryrefslogtreecommitdiff
path: root/sound/usb
diff options
context:
space:
mode:
authorAndré Fabian Silva Delgado <emulatorman@parabola.nu>2015-08-05 17:04:01 -0300
committerAndré Fabian Silva Delgado <emulatorman@parabola.nu>2015-08-05 17:04:01 -0300
commit57f0f512b273f60d52568b8c6b77e17f5636edc0 (patch)
tree5e910f0e82173f4ef4f51111366a3f1299037a7b /sound/usb
Initial import
Diffstat (limited to 'sound/usb')
-rw-r--r--sound/usb/6fire/Makefile3
-rw-r--r--sound/usb/6fire/chip.c219
-rw-r--r--sound/usb/6fire/chip.h31
-rw-r--r--sound/usb/6fire/comm.c204
-rw-r--r--sound/usb/6fire/comm.h43
-rw-r--r--sound/usb/6fire/common.h29
-rw-r--r--sound/usb/6fire/control.c621
-rw-r--r--sound/usb/6fire/control.h57
-rw-r--r--sound/usb/6fire/firmware.c419
-rw-r--r--sound/usb/6fire/firmware.h27
-rw-r--r--sound/usb/6fire/midi.c218
-rw-r--r--sound/usb/6fire/midi.h41
-rw-r--r--sound/usb/6fire/pcm.c708
-rw-r--r--sound/usb/6fire/pcm.h75
-rw-r--r--sound/usb/Kconfig166
-rw-r--r--sound/usb/Makefile28
-rw-r--r--sound/usb/bcd2000/Makefile3
-rw-r--r--sound/usb/bcd2000/bcd2000.c461
-rw-r--r--sound/usb/caiaq/Makefile4
-rw-r--r--sound/usb/caiaq/audio.c906
-rw-r--r--sound/usb/caiaq/audio.h7
-rw-r--r--sound/usb/caiaq/control.c656
-rw-r--r--sound/usb/caiaq/control.h6
-rw-r--r--sound/usb/caiaq/device.c570
-rw-r--r--sound/usb/caiaq/device.h137
-rw-r--r--sound/usb/caiaq/input.c846
-rw-r--r--sound/usb/caiaq/input.h8
-rw-r--r--sound/usb/caiaq/midi.c175
-rw-r--r--sound/usb/caiaq/midi.h9
-rw-r--r--sound/usb/card.c771
-rw-r--r--sound/usb/card.h169
-rw-r--r--sound/usb/clock.c427
-rw-r--r--sound/usb/clock.h11
-rw-r--r--sound/usb/debug.h15
-rw-r--r--sound/usb/endpoint.c1170
-rw-r--r--sound/usb/endpoint.h36
-rw-r--r--sound/usb/format.c524
-rw-r--r--sound/usb/format.h9
-rw-r--r--sound/usb/helper.c132
-rw-r--r--sound/usb/helper.h36
-rw-r--r--sound/usb/hiface/Makefile2
-rw-r--r--sound/usb/hiface/chip.c297
-rw-r--r--sound/usb/hiface/chip.h30
-rw-r--r--sound/usb/hiface/pcm.c621
-rw-r--r--sound/usb/hiface/pcm.h24
-rw-r--r--sound/usb/line6/Kconfig42
-rw-r--r--sound/usb/line6/Makefile18
-rw-r--r--sound/usb/line6/capture.c275
-rw-r--r--sound/usb/line6/capture.h29
-rw-r--r--sound/usb/line6/driver.c672
-rw-r--r--sound/usb/line6/driver.h181
-rw-r--r--sound/usb/line6/midi.c292
-rw-r--r--sound/usb/line6/midi.h51
-rw-r--r--sound/usb/line6/midibuf.c252
-rw-r--r--sound/usb/line6/midibuf.h35
-rw-r--r--sound/usb/line6/pcm.c588
-rw-r--r--sound/usb/line6/pcm.h197
-rw-r--r--sound/usb/line6/playback.c429
-rw-r--r--sound/usb/line6/playback.h35
-rw-r--r--sound/usb/line6/pod.c584
-rw-r--r--sound/usb/line6/podhd.c192
-rw-r--r--sound/usb/line6/toneport.c580
-rw-r--r--sound/usb/line6/variax.c306
-rw-r--r--sound/usb/midi.c2417
-rw-r--r--sound/usb/midi.h52
-rw-r--r--sound/usb/misc/Makefile2
-rw-r--r--sound/usb/misc/ua101.c1388
-rw-r--r--sound/usb/mixer.c2578
-rw-r--r--sound/usb/mixer.h100
-rw-r--r--sound/usb/mixer_maps.c456
-rw-r--r--sound/usb/mixer_quirks.c1845
-rw-r--r--sound/usb/mixer_quirks.h13
-rw-r--r--sound/usb/mixer_scarlett.c1004
-rw-r--r--sound/usb/mixer_scarlett.h6
-rw-r--r--sound/usb/pcm.c1645
-rw-r--r--sound/usb/pcm.h14
-rw-r--r--sound/usb/power.h17
-rw-r--r--sound/usb/proc.c178
-rw-r--r--sound/usb/proc.h8
-rw-r--r--sound/usb/quirks-table.h3181
-rw-r--r--sound/usb/quirks.c1287
-rw-r--r--sound/usb/quirks.h43
-rw-r--r--sound/usb/stream.c731
-rw-r--r--sound/usb/stream.h12
-rw-r--r--sound/usb/usbaudio.h118
-rw-r--r--sound/usb/usx2y/Makefile5
-rw-r--r--sound/usb/usx2y/us122l.c778
-rw-r--r--sound/usb/usx2y/us122l.h31
-rw-r--r--sound/usb/usx2y/usX2Yhwdep.c265
-rw-r--r--sound/usb/usx2y/usX2Yhwdep.h6
-rw-r--r--sound/usb/usx2y/usb_stream.c753
-rw-r--r--sound/usb/usx2y/usb_stream.h42
-rw-r--r--sound/usb/usx2y/usbus428ctldefs.h104
-rw-r--r--sound/usb/usx2y/usbusx2y.c461
-rw-r--r--sound/usb/usx2y/usbusx2y.h88
-rw-r--r--sound/usb/usx2y/usbusx2yaudio.c1015
-rw-r--r--sound/usb/usx2y/usx2y.h51
-rw-r--r--sound/usb/usx2y/usx2yhwdeppcm.c762
-rw-r--r--sound/usb/usx2y/usx2yhwdeppcm.h22
99 files changed, 37187 insertions, 0 deletions
diff --git a/sound/usb/6fire/Makefile b/sound/usb/6fire/Makefile
new file mode 100644
index 000000000..dfce6ec53
--- /dev/null
+++ b/sound/usb/6fire/Makefile
@@ -0,0 +1,3 @@
+snd-usb-6fire-objs += chip.o comm.o midi.o control.o firmware.o pcm.o
+obj-$(CONFIG_SND_USB_6FIRE) += snd-usb-6fire.o
+
diff --git a/sound/usb/6fire/chip.c b/sound/usb/6fire/chip.c
new file mode 100644
index 000000000..dcddfc354
--- /dev/null
+++ b/sound/usb/6fire/chip.c
@@ -0,0 +1,219 @@
+/*
+ * Linux driver for TerraTec DMX 6Fire USB
+ *
+ * Main routines and module definitions.
+ *
+ * Author: Torsten Schenk <torsten.schenk@zoho.com>
+ * Created: Jan 01, 2011
+ * Copyright: (C) Torsten Schenk
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#include "chip.h"
+#include "firmware.h"
+#include "pcm.h"
+#include "control.h"
+#include "comm.h"
+#include "midi.h"
+
+#include <linux/moduleparam.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/gfp.h>
+#include <sound/initval.h>
+
+MODULE_AUTHOR("Torsten Schenk <torsten.schenk@zoho.com>");
+MODULE_DESCRIPTION("TerraTec DMX 6Fire USB audio driver");
+MODULE_LICENSE("GPL v2");
+MODULE_SUPPORTED_DEVICE("{{TerraTec,DMX 6Fire USB}}");
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 0-max */
+static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* Id for card */
+static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP; /* Enable card */
+static struct sfire_chip *chips[SNDRV_CARDS] = SNDRV_DEFAULT_PTR;
+static struct usb_device *devices[SNDRV_CARDS] = SNDRV_DEFAULT_PTR;
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for the 6fire sound device");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for the 6fire sound device.");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "Enable the 6fire sound device.");
+
+static DEFINE_MUTEX(register_mutex);
+
+static void usb6fire_chip_abort(struct sfire_chip *chip)
+{
+ if (chip) {
+ if (chip->pcm)
+ usb6fire_pcm_abort(chip);
+ if (chip->midi)
+ usb6fire_midi_abort(chip);
+ if (chip->comm)
+ usb6fire_comm_abort(chip);
+ if (chip->control)
+ usb6fire_control_abort(chip);
+ if (chip->card) {
+ snd_card_disconnect(chip->card);
+ snd_card_free_when_closed(chip->card);
+ chip->card = NULL;
+ }
+ }
+}
+
+static void usb6fire_chip_destroy(struct sfire_chip *chip)
+{
+ if (chip) {
+ if (chip->pcm)
+ usb6fire_pcm_destroy(chip);
+ if (chip->midi)
+ usb6fire_midi_destroy(chip);
+ if (chip->comm)
+ usb6fire_comm_destroy(chip);
+ if (chip->control)
+ usb6fire_control_destroy(chip);
+ if (chip->card)
+ snd_card_free(chip->card);
+ }
+}
+
+static int usb6fire_chip_probe(struct usb_interface *intf,
+ const struct usb_device_id *usb_id)
+{
+ int ret;
+ int i;
+ struct sfire_chip *chip = NULL;
+ struct usb_device *device = interface_to_usbdev(intf);
+ int regidx = -1; /* index in module parameter array */
+ struct snd_card *card = NULL;
+
+ /* look if we already serve this card and return if so */
+ mutex_lock(&register_mutex);
+ for (i = 0; i < SNDRV_CARDS; i++) {
+ if (devices[i] == device) {
+ if (chips[i])
+ chips[i]->intf_count++;
+ usb_set_intfdata(intf, chips[i]);
+ mutex_unlock(&register_mutex);
+ return 0;
+ } else if (!devices[i] && regidx < 0)
+ regidx = i;
+ }
+ if (regidx < 0) {
+ mutex_unlock(&register_mutex);
+ dev_err(&intf->dev, "too many cards registered.\n");
+ return -ENODEV;
+ }
+ devices[regidx] = device;
+ mutex_unlock(&register_mutex);
+
+ /* check, if firmware is present on device, upload it if not */
+ ret = usb6fire_fw_init(intf);
+ if (ret < 0)
+ return ret;
+ else if (ret == FW_NOT_READY) /* firmware update performed */
+ return 0;
+
+ /* if we are here, card can be registered in alsa. */
+ if (usb_set_interface(device, 0, 0) != 0) {
+ dev_err(&intf->dev, "can't set first interface.\n");
+ return -EIO;
+ }
+ ret = snd_card_new(&intf->dev, index[regidx], id[regidx],
+ THIS_MODULE, sizeof(struct sfire_chip), &card);
+ if (ret < 0) {
+ dev_err(&intf->dev, "cannot create alsa card.\n");
+ return ret;
+ }
+ strcpy(card->driver, "6FireUSB");
+ strcpy(card->shortname, "TerraTec DMX6FireUSB");
+ sprintf(card->longname, "%s at %d:%d", card->shortname,
+ device->bus->busnum, device->devnum);
+
+ chip = card->private_data;
+ chips[regidx] = chip;
+ chip->dev = device;
+ chip->regidx = regidx;
+ chip->intf_count = 1;
+ chip->card = card;
+
+ ret = usb6fire_comm_init(chip);
+ if (ret < 0) {
+ usb6fire_chip_destroy(chip);
+ return ret;
+ }
+
+ ret = usb6fire_midi_init(chip);
+ if (ret < 0) {
+ usb6fire_chip_destroy(chip);
+ return ret;
+ }
+
+ ret = usb6fire_pcm_init(chip);
+ if (ret < 0) {
+ usb6fire_chip_destroy(chip);
+ return ret;
+ }
+
+ ret = usb6fire_control_init(chip);
+ if (ret < 0) {
+ usb6fire_chip_destroy(chip);
+ return ret;
+ }
+
+ ret = snd_card_register(card);
+ if (ret < 0) {
+ dev_err(&intf->dev, "cannot register card.");
+ usb6fire_chip_destroy(chip);
+ return ret;
+ }
+ usb_set_intfdata(intf, chip);
+ return 0;
+}
+
+static void usb6fire_chip_disconnect(struct usb_interface *intf)
+{
+ struct sfire_chip *chip;
+ struct snd_card *card;
+
+ chip = usb_get_intfdata(intf);
+ if (chip) { /* if !chip, fw upload has been performed */
+ card = chip->card;
+ chip->intf_count--;
+ if (!chip->intf_count) {
+ mutex_lock(&register_mutex);
+ devices[chip->regidx] = NULL;
+ chips[chip->regidx] = NULL;
+ mutex_unlock(&register_mutex);
+
+ chip->shutdown = true;
+ usb6fire_chip_abort(chip);
+ usb6fire_chip_destroy(chip);
+ }
+ }
+}
+
+static struct usb_device_id device_table[] = {
+ {
+ .match_flags = USB_DEVICE_ID_MATCH_DEVICE,
+ .idVendor = 0x0ccd,
+ .idProduct = 0x0080
+ },
+ {}
+};
+
+MODULE_DEVICE_TABLE(usb, device_table);
+
+static struct usb_driver usb_driver = {
+ .name = "snd-usb-6fire",
+ .probe = usb6fire_chip_probe,
+ .disconnect = usb6fire_chip_disconnect,
+ .id_table = device_table,
+};
+
+module_usb_driver(usb_driver);
diff --git a/sound/usb/6fire/chip.h b/sound/usb/6fire/chip.h
new file mode 100644
index 000000000..bde02d105
--- /dev/null
+++ b/sound/usb/6fire/chip.h
@@ -0,0 +1,31 @@
+/*
+ * Linux driver for TerraTec DMX 6Fire USB
+ *
+ * Author: Torsten Schenk <torsten.schenk@zoho.com>
+ * Created: Jan 01, 2011
+ * Copyright: (C) Torsten Schenk
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+#ifndef USB6FIRE_CHIP_H
+#define USB6FIRE_CHIP_H
+
+#include "common.h"
+
+struct sfire_chip {
+ struct usb_device *dev;
+ struct snd_card *card;
+ int intf_count; /* number of registered interfaces */
+ int regidx; /* index in module parameter arrays */
+ bool shutdown;
+
+ struct midi_runtime *midi;
+ struct pcm_runtime *pcm;
+ struct control_runtime *control;
+ struct comm_runtime *comm;
+};
+#endif /* USB6FIRE_CHIP_H */
+
diff --git a/sound/usb/6fire/comm.c b/sound/usb/6fire/comm.c
new file mode 100644
index 000000000..161215d78
--- /dev/null
+++ b/sound/usb/6fire/comm.c
@@ -0,0 +1,204 @@
+/*
+ * Linux driver for TerraTec DMX 6Fire USB
+ *
+ * Device communications
+ *
+ * Author: Torsten Schenk <torsten.schenk@zoho.com>
+ * Created: Jan 01, 2011
+ * Copyright: (C) Torsten Schenk
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#include "comm.h"
+#include "chip.h"
+#include "midi.h"
+
+enum {
+ COMM_EP = 1,
+ COMM_FPGA_EP = 2
+};
+
+static void usb6fire_comm_init_urb(struct comm_runtime *rt, struct urb *urb,
+ u8 *buffer, void *context, void(*handler)(struct urb *urb))
+{
+ usb_init_urb(urb);
+ urb->transfer_buffer = buffer;
+ urb->pipe = usb_sndintpipe(rt->chip->dev, COMM_EP);
+ urb->complete = handler;
+ urb->context = context;
+ urb->interval = 1;
+ urb->dev = rt->chip->dev;
+}
+
+static void usb6fire_comm_receiver_handler(struct urb *urb)
+{
+ struct comm_runtime *rt = urb->context;
+ struct midi_runtime *midi_rt = rt->chip->midi;
+
+ if (!urb->status) {
+ if (rt->receiver_buffer[0] == 0x10) /* midi in event */
+ if (midi_rt)
+ midi_rt->in_received(midi_rt,
+ rt->receiver_buffer + 2,
+ rt->receiver_buffer[1]);
+ }
+
+ if (!rt->chip->shutdown) {
+ urb->status = 0;
+ urb->actual_length = 0;
+ if (usb_submit_urb(urb, GFP_ATOMIC) < 0)
+ dev_warn(&urb->dev->dev,
+ "comm data receiver aborted.\n");
+ }
+}
+
+static void usb6fire_comm_init_buffer(u8 *buffer, u8 id, u8 request,
+ u8 reg, u8 vl, u8 vh)
+{
+ buffer[0] = 0x01;
+ buffer[2] = request;
+ buffer[3] = id;
+ switch (request) {
+ case 0x02:
+ buffer[1] = 0x05; /* length (starting at buffer[2]) */
+ buffer[4] = reg;
+ buffer[5] = vl;
+ buffer[6] = vh;
+ break;
+
+ case 0x12:
+ buffer[1] = 0x0b; /* length (starting at buffer[2]) */
+ buffer[4] = 0x00;
+ buffer[5] = 0x18;
+ buffer[6] = 0x05;
+ buffer[7] = 0x00;
+ buffer[8] = 0x01;
+ buffer[9] = 0x00;
+ buffer[10] = 0x9e;
+ buffer[11] = reg;
+ buffer[12] = vl;
+ break;
+
+ case 0x20:
+ case 0x21:
+ case 0x22:
+ buffer[1] = 0x04;
+ buffer[4] = reg;
+ buffer[5] = vl;
+ break;
+ }
+}
+
+static int usb6fire_comm_send_buffer(u8 *buffer, struct usb_device *dev)
+{
+ int ret;
+ int actual_len;
+
+ ret = usb_interrupt_msg(dev, usb_sndintpipe(dev, COMM_EP),
+ buffer, buffer[1] + 2, &actual_len, HZ);
+ if (ret < 0)
+ return ret;
+ else if (actual_len != buffer[1] + 2)
+ return -EIO;
+ return 0;
+}
+
+static int usb6fire_comm_write8(struct comm_runtime *rt, u8 request,
+ u8 reg, u8 value)
+{
+ u8 *buffer;
+ int ret;
+
+ /* 13: maximum length of message */
+ buffer = kmalloc(13, GFP_KERNEL);
+ if (!buffer)
+ return -ENOMEM;
+
+ usb6fire_comm_init_buffer(buffer, 0x00, request, reg, value, 0x00);
+ ret = usb6fire_comm_send_buffer(buffer, rt->chip->dev);
+
+ kfree(buffer);
+ return ret;
+}
+
+static int usb6fire_comm_write16(struct comm_runtime *rt, u8 request,
+ u8 reg, u8 vl, u8 vh)
+{
+ u8 *buffer;
+ int ret;
+
+ /* 13: maximum length of message */
+ buffer = kmalloc(13, GFP_KERNEL);
+ if (!buffer)
+ return -ENOMEM;
+
+ usb6fire_comm_init_buffer(buffer, 0x00, request, reg, vl, vh);
+ ret = usb6fire_comm_send_buffer(buffer, rt->chip->dev);
+
+ kfree(buffer);
+ return ret;
+}
+
+int usb6fire_comm_init(struct sfire_chip *chip)
+{
+ struct comm_runtime *rt = kzalloc(sizeof(struct comm_runtime),
+ GFP_KERNEL);
+ struct urb *urb;
+ int ret;
+
+ if (!rt)
+ return -ENOMEM;
+
+ rt->receiver_buffer = kzalloc(COMM_RECEIVER_BUFSIZE, GFP_KERNEL);
+ if (!rt->receiver_buffer) {
+ kfree(rt);
+ return -ENOMEM;
+ }
+
+ urb = &rt->receiver;
+ rt->serial = 1;
+ rt->chip = chip;
+ usb_init_urb(urb);
+ rt->init_urb = usb6fire_comm_init_urb;
+ rt->write8 = usb6fire_comm_write8;
+ rt->write16 = usb6fire_comm_write16;
+
+ /* submit an urb that receives communication data from device */
+ urb->transfer_buffer = rt->receiver_buffer;
+ urb->transfer_buffer_length = COMM_RECEIVER_BUFSIZE;
+ urb->pipe = usb_rcvintpipe(chip->dev, COMM_EP);
+ urb->dev = chip->dev;
+ urb->complete = usb6fire_comm_receiver_handler;
+ urb->context = rt;
+ urb->interval = 1;
+ ret = usb_submit_urb(urb, GFP_KERNEL);
+ if (ret < 0) {
+ kfree(rt->receiver_buffer);
+ kfree(rt);
+ dev_err(&chip->dev->dev, "cannot create comm data receiver.");
+ return ret;
+ }
+ chip->comm = rt;
+ return 0;
+}
+
+void usb6fire_comm_abort(struct sfire_chip *chip)
+{
+ struct comm_runtime *rt = chip->comm;
+
+ if (rt)
+ usb_poison_urb(&rt->receiver);
+}
+
+void usb6fire_comm_destroy(struct sfire_chip *chip)
+{
+ struct comm_runtime *rt = chip->comm;
+
+ kfree(rt->receiver_buffer);
+ kfree(rt);
+ chip->comm = NULL;
+}
diff --git a/sound/usb/6fire/comm.h b/sound/usb/6fire/comm.h
new file mode 100644
index 000000000..780d5ed8e
--- /dev/null
+++ b/sound/usb/6fire/comm.h
@@ -0,0 +1,43 @@
+/*
+ * Linux driver for TerraTec DMX 6Fire USB
+ *
+ * Author: Torsten Schenk <torsten.schenk@zoho.com>
+ * Created: Jan 01, 2011
+ * Copyright: (C) Torsten Schenk
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+#ifndef USB6FIRE_COMM_H
+#define USB6FIRE_COMM_H
+
+#include "common.h"
+
+enum /* settings for comm */
+{
+ COMM_RECEIVER_BUFSIZE = 64,
+};
+
+struct comm_runtime {
+ struct sfire_chip *chip;
+
+ struct urb receiver;
+ u8 *receiver_buffer;
+
+ u8 serial; /* urb serial */
+
+ void (*init_urb)(struct comm_runtime *rt, struct urb *urb, u8 *buffer,
+ void *context, void(*handler)(struct urb *urb));
+ /* writes control data to the device */
+ int (*write8)(struct comm_runtime *rt, u8 request, u8 reg, u8 value);
+ int (*write16)(struct comm_runtime *rt, u8 request, u8 reg,
+ u8 vh, u8 vl);
+};
+
+int usb6fire_comm_init(struct sfire_chip *chip);
+void usb6fire_comm_abort(struct sfire_chip *chip);
+void usb6fire_comm_destroy(struct sfire_chip *chip);
+#endif /* USB6FIRE_COMM_H */
+
diff --git a/sound/usb/6fire/common.h b/sound/usb/6fire/common.h
new file mode 100644
index 000000000..b6eb03ed1
--- /dev/null
+++ b/sound/usb/6fire/common.h
@@ -0,0 +1,29 @@
+/*
+ * Linux driver for TerraTec DMX 6Fire USB
+ *
+ * Author: Torsten Schenk <torsten.schenk@zoho.com>
+ * Created: Jan 01, 2011
+ * Copyright: (C) Torsten Schenk
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#ifndef USB6FIRE_COMMON_H
+#define USB6FIRE_COMMON_H
+
+#include <linux/slab.h>
+#include <linux/usb.h>
+#include <sound/core.h>
+
+#define PREFIX "6fire: "
+
+struct sfire_chip;
+struct midi_runtime;
+struct pcm_runtime;
+struct control_runtime;
+struct comm_runtime;
+#endif /* USB6FIRE_COMMON_H */
+
diff --git a/sound/usb/6fire/control.c b/sound/usb/6fire/control.c
new file mode 100644
index 000000000..54656eed6
--- /dev/null
+++ b/sound/usb/6fire/control.c
@@ -0,0 +1,621 @@
+/*
+ * Linux driver for TerraTec DMX 6Fire USB
+ *
+ * Mixer control
+ *
+ * Author: Torsten Schenk <torsten.schenk@zoho.com>
+ * Created: Jan 01, 2011
+ * Copyright: (C) Torsten Schenk
+ *
+ * Thanks to:
+ * - Holger Ruckdeschel: he found out how to control individual channel
+ * volumes and introduced mute switch
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#include <linux/interrupt.h>
+#include <sound/control.h>
+#include <sound/tlv.h>
+
+#include "control.h"
+#include "comm.h"
+#include "chip.h"
+
+static const char * const opt_coax_texts[2] = { "Optical", "Coax" };
+static const char * const line_phono_texts[2] = { "Line", "Phono" };
+
+/*
+ * data that needs to be sent to device. sets up card internal stuff.
+ * values dumped from windows driver and filtered by trial'n'error.
+ */
+static const struct {
+ u8 type;
+ u8 reg;
+ u8 value;
+}
+init_data[] = {
+ { 0x22, 0x00, 0x00 }, { 0x20, 0x00, 0x08 }, { 0x22, 0x01, 0x01 },
+ { 0x20, 0x01, 0x08 }, { 0x22, 0x02, 0x00 }, { 0x20, 0x02, 0x08 },
+ { 0x22, 0x03, 0x00 }, { 0x20, 0x03, 0x08 }, { 0x22, 0x04, 0x00 },
+ { 0x20, 0x04, 0x08 }, { 0x22, 0x05, 0x01 }, { 0x20, 0x05, 0x08 },
+ { 0x22, 0x04, 0x01 }, { 0x12, 0x04, 0x00 }, { 0x12, 0x05, 0x00 },
+ { 0x12, 0x0d, 0x38 }, { 0x12, 0x21, 0x82 }, { 0x12, 0x22, 0x80 },
+ { 0x12, 0x23, 0x00 }, { 0x12, 0x06, 0x02 }, { 0x12, 0x03, 0x00 },
+ { 0x12, 0x02, 0x00 }, { 0x22, 0x03, 0x01 },
+ { 0 } /* TERMINATING ENTRY */
+};
+
+static const int rates_altsetting[] = { 1, 1, 2, 2, 3, 3 };
+/* values to write to soundcard register for all samplerates */
+static const u16 rates_6fire_vl[] = {0x00, 0x01, 0x00, 0x01, 0x00, 0x01};
+static const u16 rates_6fire_vh[] = {0x11, 0x11, 0x10, 0x10, 0x00, 0x00};
+
+static DECLARE_TLV_DB_MINMAX(tlv_output, -9000, 0);
+static DECLARE_TLV_DB_MINMAX(tlv_input, -1500, 1500);
+
+enum {
+ DIGITAL_THRU_ONLY_SAMPLERATE = 3
+};
+
+static void usb6fire_control_output_vol_update(struct control_runtime *rt)
+{
+ struct comm_runtime *comm_rt = rt->chip->comm;
+ int i;
+
+ if (comm_rt)
+ for (i = 0; i < 6; i++)
+ if (!(rt->ovol_updated & (1 << i))) {
+ comm_rt->write8(comm_rt, 0x12, 0x0f + i,
+ 180 - rt->output_vol[i]);
+ rt->ovol_updated |= 1 << i;
+ }
+}
+
+static void usb6fire_control_output_mute_update(struct control_runtime *rt)
+{
+ struct comm_runtime *comm_rt = rt->chip->comm;
+
+ if (comm_rt)
+ comm_rt->write8(comm_rt, 0x12, 0x0e, ~rt->output_mute);
+}
+
+static void usb6fire_control_input_vol_update(struct control_runtime *rt)
+{
+ struct comm_runtime *comm_rt = rt->chip->comm;
+ int i;
+
+ if (comm_rt)
+ for (i = 0; i < 2; i++)
+ if (!(rt->ivol_updated & (1 << i))) {
+ comm_rt->write8(comm_rt, 0x12, 0x1c + i,
+ rt->input_vol[i] & 0x3f);
+ rt->ivol_updated |= 1 << i;
+ }
+}
+
+static void usb6fire_control_line_phono_update(struct control_runtime *rt)
+{
+ struct comm_runtime *comm_rt = rt->chip->comm;
+ if (comm_rt) {
+ comm_rt->write8(comm_rt, 0x22, 0x02, rt->line_phono_switch);
+ comm_rt->write8(comm_rt, 0x21, 0x02, rt->line_phono_switch);
+ }
+}
+
+static void usb6fire_control_opt_coax_update(struct control_runtime *rt)
+{
+ struct comm_runtime *comm_rt = rt->chip->comm;
+ if (comm_rt) {
+ comm_rt->write8(comm_rt, 0x22, 0x00, rt->opt_coax_switch);
+ comm_rt->write8(comm_rt, 0x21, 0x00, rt->opt_coax_switch);
+ }
+}
+
+static int usb6fire_control_set_rate(struct control_runtime *rt, int rate)
+{
+ int ret;
+ struct usb_device *device = rt->chip->dev;
+ struct comm_runtime *comm_rt = rt->chip->comm;
+
+ if (rate < 0 || rate >= CONTROL_N_RATES)
+ return -EINVAL;
+
+ ret = usb_set_interface(device, 1, rates_altsetting[rate]);
+ if (ret < 0)
+ return ret;
+
+ /* set soundcard clock */
+ ret = comm_rt->write16(comm_rt, 0x02, 0x01, rates_6fire_vl[rate],
+ rates_6fire_vh[rate]);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static int usb6fire_control_set_channels(
+ struct control_runtime *rt, int n_analog_out,
+ int n_analog_in, bool spdif_out, bool spdif_in)
+{
+ int ret;
+ struct comm_runtime *comm_rt = rt->chip->comm;
+
+ /* enable analog inputs and outputs
+ * (one bit per stereo-channel) */
+ ret = comm_rt->write16(comm_rt, 0x02, 0x02,
+ (1 << (n_analog_out / 2)) - 1,
+ (1 << (n_analog_in / 2)) - 1);
+ if (ret < 0)
+ return ret;
+
+ /* disable digital inputs and outputs */
+ /* TODO: use spdif_x to enable/disable digital channels */
+ ret = comm_rt->write16(comm_rt, 0x02, 0x03, 0x00, 0x00);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static int usb6fire_control_streaming_update(struct control_runtime *rt)
+{
+ struct comm_runtime *comm_rt = rt->chip->comm;
+
+ if (comm_rt) {
+ if (!rt->usb_streaming && rt->digital_thru_switch)
+ usb6fire_control_set_rate(rt,
+ DIGITAL_THRU_ONLY_SAMPLERATE);
+ return comm_rt->write16(comm_rt, 0x02, 0x00, 0x00,
+ (rt->usb_streaming ? 0x01 : 0x00) |
+ (rt->digital_thru_switch ? 0x08 : 0x00));
+ }
+ return -EINVAL;
+}
+
+static int usb6fire_control_output_vol_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = 2;
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = 180;
+ return 0;
+}
+
+static int usb6fire_control_output_vol_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct control_runtime *rt = snd_kcontrol_chip(kcontrol);
+ unsigned int ch = kcontrol->private_value;
+ int changed = 0;
+
+ if (ch > 4) {
+ dev_err(&rt->chip->dev->dev,
+ "Invalid channel in volume control.");
+ return -EINVAL;
+ }
+
+ if (rt->output_vol[ch] != ucontrol->value.integer.value[0]) {
+ rt->output_vol[ch] = ucontrol->value.integer.value[0];
+ rt->ovol_updated &= ~(1 << ch);
+ changed = 1;
+ }
+ if (rt->output_vol[ch + 1] != ucontrol->value.integer.value[1]) {
+ rt->output_vol[ch + 1] = ucontrol->value.integer.value[1];
+ rt->ovol_updated &= ~(2 << ch);
+ changed = 1;
+ }
+
+ if (changed)
+ usb6fire_control_output_vol_update(rt);
+
+ return changed;
+}
+
+static int usb6fire_control_output_vol_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct control_runtime *rt = snd_kcontrol_chip(kcontrol);
+ unsigned int ch = kcontrol->private_value;
+
+ if (ch > 4) {
+ dev_err(&rt->chip->dev->dev,
+ "Invalid channel in volume control.");
+ return -EINVAL;
+ }
+
+ ucontrol->value.integer.value[0] = rt->output_vol[ch];
+ ucontrol->value.integer.value[1] = rt->output_vol[ch + 1];
+ return 0;
+}
+
+static int usb6fire_control_output_mute_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct control_runtime *rt = snd_kcontrol_chip(kcontrol);
+ unsigned int ch = kcontrol->private_value;
+ u8 old = rt->output_mute;
+ u8 value = 0;
+
+ if (ch > 4) {
+ dev_err(&rt->chip->dev->dev,
+ "Invalid channel in volume control.");
+ return -EINVAL;
+ }
+
+ rt->output_mute &= ~(3 << ch);
+ if (ucontrol->value.integer.value[0])
+ value |= 1;
+ if (ucontrol->value.integer.value[1])
+ value |= 2;
+ rt->output_mute |= value << ch;
+
+ if (rt->output_mute != old)
+ usb6fire_control_output_mute_update(rt);
+
+ return rt->output_mute != old;
+}
+
+static int usb6fire_control_output_mute_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct control_runtime *rt = snd_kcontrol_chip(kcontrol);
+ unsigned int ch = kcontrol->private_value;
+ u8 value = rt->output_mute >> ch;
+
+ if (ch > 4) {
+ dev_err(&rt->chip->dev->dev,
+ "Invalid channel in volume control.");
+ return -EINVAL;
+ }
+
+ ucontrol->value.integer.value[0] = 1 & value;
+ value >>= 1;
+ ucontrol->value.integer.value[1] = 1 & value;
+
+ return 0;
+}
+
+static int usb6fire_control_input_vol_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = 2;
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = 30;
+ return 0;
+}
+
+static int usb6fire_control_input_vol_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct control_runtime *rt = snd_kcontrol_chip(kcontrol);
+ int changed = 0;
+
+ if (rt->input_vol[0] != ucontrol->value.integer.value[0]) {
+ rt->input_vol[0] = ucontrol->value.integer.value[0] - 15;
+ rt->ivol_updated &= ~(1 << 0);
+ changed = 1;
+ }
+ if (rt->input_vol[1] != ucontrol->value.integer.value[1]) {
+ rt->input_vol[1] = ucontrol->value.integer.value[1] - 15;
+ rt->ivol_updated &= ~(1 << 1);
+ changed = 1;
+ }
+
+ if (changed)
+ usb6fire_control_input_vol_update(rt);
+
+ return changed;
+}
+
+static int usb6fire_control_input_vol_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct control_runtime *rt = snd_kcontrol_chip(kcontrol);
+
+ ucontrol->value.integer.value[0] = rt->input_vol[0] + 15;
+ ucontrol->value.integer.value[1] = rt->input_vol[1] + 15;
+
+ return 0;
+}
+
+static int usb6fire_control_line_phono_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ return snd_ctl_enum_info(uinfo, 1, 2, line_phono_texts);
+}
+
+static int usb6fire_control_line_phono_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct control_runtime *rt = snd_kcontrol_chip(kcontrol);
+ int changed = 0;
+ if (rt->line_phono_switch != ucontrol->value.integer.value[0]) {
+ rt->line_phono_switch = ucontrol->value.integer.value[0];
+ usb6fire_control_line_phono_update(rt);
+ changed = 1;
+ }
+ return changed;
+}
+
+static int usb6fire_control_line_phono_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct control_runtime *rt = snd_kcontrol_chip(kcontrol);
+ ucontrol->value.integer.value[0] = rt->line_phono_switch;
+ return 0;
+}
+
+static int usb6fire_control_opt_coax_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ return snd_ctl_enum_info(uinfo, 1, 2, opt_coax_texts);
+}
+
+static int usb6fire_control_opt_coax_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct control_runtime *rt = snd_kcontrol_chip(kcontrol);
+ int changed = 0;
+
+ if (rt->opt_coax_switch != ucontrol->value.enumerated.item[0]) {
+ rt->opt_coax_switch = ucontrol->value.enumerated.item[0];
+ usb6fire_control_opt_coax_update(rt);
+ changed = 1;
+ }
+ return changed;
+}
+
+static int usb6fire_control_opt_coax_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct control_runtime *rt = snd_kcontrol_chip(kcontrol);
+ ucontrol->value.enumerated.item[0] = rt->opt_coax_switch;
+ return 0;
+}
+
+static int usb6fire_control_digital_thru_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct control_runtime *rt = snd_kcontrol_chip(kcontrol);
+ int changed = 0;
+
+ if (rt->digital_thru_switch != ucontrol->value.integer.value[0]) {
+ rt->digital_thru_switch = ucontrol->value.integer.value[0];
+ usb6fire_control_streaming_update(rt);
+ changed = 1;
+ }
+ return changed;
+}
+
+static int usb6fire_control_digital_thru_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct control_runtime *rt = snd_kcontrol_chip(kcontrol);
+ ucontrol->value.integer.value[0] = rt->digital_thru_switch;
+ return 0;
+}
+
+static struct snd_kcontrol_new vol_elements[] = {
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Analog Playback Volume",
+ .index = 0,
+ .private_value = 0,
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE |
+ SNDRV_CTL_ELEM_ACCESS_TLV_READ,
+ .info = usb6fire_control_output_vol_info,
+ .get = usb6fire_control_output_vol_get,
+ .put = usb6fire_control_output_vol_put,
+ .tlv = { .p = tlv_output }
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Analog Playback Volume",
+ .index = 1,
+ .private_value = 2,
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE |
+ SNDRV_CTL_ELEM_ACCESS_TLV_READ,
+ .info = usb6fire_control_output_vol_info,
+ .get = usb6fire_control_output_vol_get,
+ .put = usb6fire_control_output_vol_put,
+ .tlv = { .p = tlv_output }
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Analog Playback Volume",
+ .index = 2,
+ .private_value = 4,
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE |
+ SNDRV_CTL_ELEM_ACCESS_TLV_READ,
+ .info = usb6fire_control_output_vol_info,
+ .get = usb6fire_control_output_vol_get,
+ .put = usb6fire_control_output_vol_put,
+ .tlv = { .p = tlv_output }
+ },
+ {}
+};
+
+static struct snd_kcontrol_new mute_elements[] = {
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Analog Playback Switch",
+ .index = 0,
+ .private_value = 0,
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+ .info = snd_ctl_boolean_stereo_info,
+ .get = usb6fire_control_output_mute_get,
+ .put = usb6fire_control_output_mute_put,
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Analog Playback Switch",
+ .index = 1,
+ .private_value = 2,
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+ .info = snd_ctl_boolean_stereo_info,
+ .get = usb6fire_control_output_mute_get,
+ .put = usb6fire_control_output_mute_put,
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Analog Playback Switch",
+ .index = 2,
+ .private_value = 4,
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+ .info = snd_ctl_boolean_stereo_info,
+ .get = usb6fire_control_output_mute_get,
+ .put = usb6fire_control_output_mute_put,
+ },
+ {}
+};
+
+static struct snd_kcontrol_new elements[] = {
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Line/Phono Capture Route",
+ .index = 0,
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+ .info = usb6fire_control_line_phono_info,
+ .get = usb6fire_control_line_phono_get,
+ .put = usb6fire_control_line_phono_put
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Opt/Coax Capture Route",
+ .index = 0,
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+ .info = usb6fire_control_opt_coax_info,
+ .get = usb6fire_control_opt_coax_get,
+ .put = usb6fire_control_opt_coax_put
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Digital Thru Playback Route",
+ .index = 0,
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+ .info = snd_ctl_boolean_mono_info,
+ .get = usb6fire_control_digital_thru_get,
+ .put = usb6fire_control_digital_thru_put
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Analog Capture Volume",
+ .index = 0,
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE |
+ SNDRV_CTL_ELEM_ACCESS_TLV_READ,
+ .info = usb6fire_control_input_vol_info,
+ .get = usb6fire_control_input_vol_get,
+ .put = usb6fire_control_input_vol_put,
+ .tlv = { .p = tlv_input }
+ },
+ {}
+};
+
+static int usb6fire_control_add_virtual(
+ struct control_runtime *rt,
+ struct snd_card *card,
+ char *name,
+ struct snd_kcontrol_new *elems)
+{
+ int ret;
+ int i;
+ struct snd_kcontrol *vmaster =
+ snd_ctl_make_virtual_master(name, tlv_output);
+ struct snd_kcontrol *control;
+
+ if (!vmaster)
+ return -ENOMEM;
+ ret = snd_ctl_add(card, vmaster);
+ if (ret < 0)
+ return ret;
+
+ i = 0;
+ while (elems[i].name) {
+ control = snd_ctl_new1(&elems[i], rt);
+ if (!control)
+ return -ENOMEM;
+ ret = snd_ctl_add(card, control);
+ if (ret < 0)
+ return ret;
+ ret = snd_ctl_add_slave(vmaster, control);
+ if (ret < 0)
+ return ret;
+ i++;
+ }
+ return 0;
+}
+
+int usb6fire_control_init(struct sfire_chip *chip)
+{
+ int i;
+ int ret;
+ struct control_runtime *rt = kzalloc(sizeof(struct control_runtime),
+ GFP_KERNEL);
+ struct comm_runtime *comm_rt = chip->comm;
+
+ if (!rt)
+ return -ENOMEM;
+
+ rt->chip = chip;
+ rt->update_streaming = usb6fire_control_streaming_update;
+ rt->set_rate = usb6fire_control_set_rate;
+ rt->set_channels = usb6fire_control_set_channels;
+
+ i = 0;
+ while (init_data[i].type) {
+ comm_rt->write8(comm_rt, init_data[i].type, init_data[i].reg,
+ init_data[i].value);
+ i++;
+ }
+
+ usb6fire_control_opt_coax_update(rt);
+ usb6fire_control_line_phono_update(rt);
+ usb6fire_control_output_vol_update(rt);
+ usb6fire_control_output_mute_update(rt);
+ usb6fire_control_input_vol_update(rt);
+ usb6fire_control_streaming_update(rt);
+
+ ret = usb6fire_control_add_virtual(rt, chip->card,
+ "Master Playback Volume", vol_elements);
+ if (ret) {
+ dev_err(&chip->dev->dev, "cannot add control.\n");
+ kfree(rt);
+ return ret;
+ }
+ ret = usb6fire_control_add_virtual(rt, chip->card,
+ "Master Playback Switch", mute_elements);
+ if (ret) {
+ dev_err(&chip->dev->dev, "cannot add control.\n");
+ kfree(rt);
+ return ret;
+ }
+
+ i = 0;
+ while (elements[i].name) {
+ ret = snd_ctl_add(chip->card, snd_ctl_new1(&elements[i], rt));
+ if (ret < 0) {
+ kfree(rt);
+ dev_err(&chip->dev->dev, "cannot add control.\n");
+ return ret;
+ }
+ i++;
+ }
+
+ chip->control = rt;
+ return 0;
+}
+
+void usb6fire_control_abort(struct sfire_chip *chip)
+{}
+
+void usb6fire_control_destroy(struct sfire_chip *chip)
+{
+ kfree(chip->control);
+ chip->control = NULL;
+}
diff --git a/sound/usb/6fire/control.h b/sound/usb/6fire/control.h
new file mode 100644
index 000000000..5a40ba143
--- /dev/null
+++ b/sound/usb/6fire/control.h
@@ -0,0 +1,57 @@
+/*
+ * Linux driver for TerraTec DMX 6Fire USB
+ *
+ * Author: Torsten Schenk <torsten.schenk@zoho.com>
+ * Created: Jan 01, 2011
+ * Copyright: (C) Torsten Schenk
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#ifndef USB6FIRE_CONTROL_H
+#define USB6FIRE_CONTROL_H
+
+#include "common.h"
+
+enum {
+ CONTROL_MAX_ELEMENTS = 32
+};
+
+enum {
+ CONTROL_RATE_44KHZ,
+ CONTROL_RATE_48KHZ,
+ CONTROL_RATE_88KHZ,
+ CONTROL_RATE_96KHZ,
+ CONTROL_RATE_176KHZ,
+ CONTROL_RATE_192KHZ,
+ CONTROL_N_RATES
+};
+
+struct control_runtime {
+ int (*update_streaming)(struct control_runtime *rt);
+ int (*set_rate)(struct control_runtime *rt, int rate);
+ int (*set_channels)(struct control_runtime *rt, int n_analog_out,
+ int n_analog_in, bool spdif_out, bool spdif_in);
+
+ struct sfire_chip *chip;
+
+ struct snd_kcontrol *element[CONTROL_MAX_ELEMENTS];
+ bool opt_coax_switch;
+ bool line_phono_switch;
+ bool digital_thru_switch;
+ bool usb_streaming;
+ u8 output_vol[6];
+ u8 ovol_updated;
+ u8 output_mute;
+ s8 input_vol[2];
+ u8 ivol_updated;
+};
+
+int usb6fire_control_init(struct sfire_chip *chip);
+void usb6fire_control_abort(struct sfire_chip *chip);
+void usb6fire_control_destroy(struct sfire_chip *chip);
+#endif /* USB6FIRE_CONTROL_H */
+
diff --git a/sound/usb/6fire/firmware.c b/sound/usb/6fire/firmware.c
new file mode 100644
index 000000000..e4db9a8de
--- /dev/null
+++ b/sound/usb/6fire/firmware.c
@@ -0,0 +1,419 @@
+/*
+ * Linux driver for TerraTec DMX 6Fire USB
+ *
+ * Firmware loader
+ *
+ * Author: Torsten Schenk <torsten.schenk@zoho.com>
+ * Created: Jan 01, 2011
+ * Copyright: (C) Torsten Schenk
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#include <linux/firmware.h>
+#include <linux/module.h>
+#include <linux/bitrev.h>
+#include <linux/kernel.h>
+
+#include "firmware.h"
+#include "chip.h"
+
+/*(DEBLOBBED)*/
+
+enum {
+ FPGA_BUFSIZE = 512, FPGA_EP = 2
+};
+
+/*
+ * wMaxPacketSize of pcm endpoints.
+ * keep synced with rates_in_packet_size and rates_out_packet_size in pcm.c
+ * fpp: frames per isopacket
+ *
+ * CAUTION: keep sizeof <= buffer[] in usb6fire_fw_init
+ */
+static const u8 ep_w_max_packet_size[] = {
+ 0xe4, 0x00, 0xe4, 0x00, /* alt 1: 228 EP2 and EP6 (7 fpp) */
+ 0xa4, 0x01, 0xa4, 0x01, /* alt 2: 420 EP2 and EP6 (13 fpp)*/
+ 0x94, 0x01, 0x5c, 0x02 /* alt 3: 404 EP2 and 604 EP6 (25 fpp) */
+};
+
+static const u8 known_fw_versions[][2] = {
+ { 0x03, 0x01 }
+};
+
+struct ihex_record {
+ u16 address;
+ u8 len;
+ u8 data[256];
+ char error; /* true if an error occurred parsing this record */
+
+ u8 max_len; /* maximum record length in whole ihex */
+
+ /* private */
+ const char *txt_data;
+ unsigned int txt_length;
+ unsigned int txt_offset; /* current position in txt_data */
+};
+
+static u8 usb6fire_fw_ihex_hex(const u8 *data, u8 *crc)
+{
+ u8 val = 0;
+ int hval;
+
+ hval = hex_to_bin(data[0]);
+ if (hval >= 0)
+ val |= (hval << 4);
+
+ hval = hex_to_bin(data[1]);
+ if (hval >= 0)
+ val |= hval;
+
+ *crc += val;
+ return val;
+}
+
+/*
+ * returns true if record is available, false otherwise.
+ * iff an error occurred, false will be returned and record->error will be true.
+ */
+static bool usb6fire_fw_ihex_next_record(struct ihex_record *record)
+{
+ u8 crc = 0;
+ u8 type;
+ int i;
+
+ record->error = false;
+
+ /* find begin of record (marked by a colon) */
+ while (record->txt_offset < record->txt_length
+ && record->txt_data[record->txt_offset] != ':')
+ record->txt_offset++;
+ if (record->txt_offset == record->txt_length)
+ return false;
+
+ /* number of characters needed for len, addr and type entries */
+ record->txt_offset++;
+ if (record->txt_offset + 8 > record->txt_length) {
+ record->error = true;
+ return false;
+ }
+
+ record->len = usb6fire_fw_ihex_hex(record->txt_data +
+ record->txt_offset, &crc);
+ record->txt_offset += 2;
+ record->address = usb6fire_fw_ihex_hex(record->txt_data +
+ record->txt_offset, &crc) << 8;
+ record->txt_offset += 2;
+ record->address |= usb6fire_fw_ihex_hex(record->txt_data +
+ record->txt_offset, &crc);
+ record->txt_offset += 2;
+ type = usb6fire_fw_ihex_hex(record->txt_data +
+ record->txt_offset, &crc);
+ record->txt_offset += 2;
+
+ /* number of characters needed for data and crc entries */
+ if (record->txt_offset + 2 * (record->len + 1) > record->txt_length) {
+ record->error = true;
+ return false;
+ }
+ for (i = 0; i < record->len; i++) {
+ record->data[i] = usb6fire_fw_ihex_hex(record->txt_data
+ + record->txt_offset, &crc);
+ record->txt_offset += 2;
+ }
+ usb6fire_fw_ihex_hex(record->txt_data + record->txt_offset, &crc);
+ if (crc) {
+ record->error = true;
+ return false;
+ }
+
+ if (type == 1 || !record->len) /* eof */
+ return false;
+ else if (type == 0)
+ return true;
+ else {
+ record->error = true;
+ return false;
+ }
+}
+
+static int usb6fire_fw_ihex_init(const struct firmware *fw,
+ struct ihex_record *record)
+{
+ record->txt_data = fw->data;
+ record->txt_length = fw->size;
+ record->txt_offset = 0;
+ record->max_len = 0;
+ /* read all records, if loop ends, record->error indicates,
+ * whether ihex is valid. */
+ while (usb6fire_fw_ihex_next_record(record))
+ record->max_len = max(record->len, record->max_len);
+ if (record->error)
+ return -EINVAL;
+ record->txt_offset = 0;
+ return 0;
+}
+
+static int usb6fire_fw_ezusb_write(struct usb_device *device,
+ int type, int value, char *data, int len)
+{
+ int ret;
+
+ ret = usb_control_msg(device, usb_sndctrlpipe(device, 0), type,
+ USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+ value, 0, data, len, HZ);
+ if (ret < 0)
+ return ret;
+ else if (ret != len)
+ return -EIO;
+ return 0;
+}
+
+static int usb6fire_fw_ezusb_read(struct usb_device *device,
+ int type, int value, char *data, int len)
+{
+ int ret = usb_control_msg(device, usb_rcvctrlpipe(device, 0), type,
+ USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE, value,
+ 0, data, len, HZ);
+ if (ret < 0)
+ return ret;
+ else if (ret != len)
+ return -EIO;
+ return 0;
+}
+
+static int usb6fire_fw_fpga_write(struct usb_device *device,
+ char *data, int len)
+{
+ int actual_len;
+ int ret;
+
+ ret = usb_bulk_msg(device, usb_sndbulkpipe(device, FPGA_EP), data, len,
+ &actual_len, HZ);
+ if (ret < 0)
+ return ret;
+ else if (actual_len != len)
+ return -EIO;
+ return 0;
+}
+
+static int usb6fire_fw_ezusb_upload(
+ struct usb_interface *intf, const char *fwname,
+ unsigned int postaddr, u8 *postdata, unsigned int postlen)
+{
+ int ret;
+ u8 data;
+ struct usb_device *device = interface_to_usbdev(intf);
+ const struct firmware *fw = NULL;
+ struct ihex_record *rec = kmalloc(sizeof(struct ihex_record),
+ GFP_KERNEL);
+
+ if (!rec)
+ return -ENOMEM;
+
+ ret = reject_firmware(&fw, fwname, &device->dev);
+ if (ret < 0) {
+ kfree(rec);
+ dev_err(&intf->dev,
+ "error requesting ezusb firmware %s.\n", fwname);
+ return ret;
+ }
+ ret = usb6fire_fw_ihex_init(fw, rec);
+ if (ret < 0) {
+ kfree(rec);
+ release_firmware(fw);
+ dev_err(&intf->dev,
+ "error validating ezusb firmware %s.\n", fwname);
+ return ret;
+ }
+ /* upload firmware image */
+ data = 0x01; /* stop ezusb cpu */
+ ret = usb6fire_fw_ezusb_write(device, 0xa0, 0xe600, &data, 1);
+ if (ret < 0) {
+ kfree(rec);
+ release_firmware(fw);
+ dev_err(&intf->dev,
+ "unable to upload ezusb firmware %s: begin message.\n",
+ fwname);
+ return ret;
+ }
+
+ while (usb6fire_fw_ihex_next_record(rec)) { /* write firmware */
+ ret = usb6fire_fw_ezusb_write(device, 0xa0, rec->address,
+ rec->data, rec->len);
+ if (ret < 0) {
+ kfree(rec);
+ release_firmware(fw);
+ dev_err(&intf->dev,
+ "unable to upload ezusb firmware %s: data urb.\n",
+ fwname);
+ return ret;
+ }
+ }
+
+ release_firmware(fw);
+ kfree(rec);
+ if (postdata) { /* write data after firmware has been uploaded */
+ ret = usb6fire_fw_ezusb_write(device, 0xa0, postaddr,
+ postdata, postlen);
+ if (ret < 0) {
+ dev_err(&intf->dev,
+ "unable to upload ezusb firmware %s: post urb.\n",
+ fwname);
+ return ret;
+ }
+ }
+
+ data = 0x00; /* resume ezusb cpu */
+ ret = usb6fire_fw_ezusb_write(device, 0xa0, 0xe600, &data, 1);
+ if (ret < 0) {
+ dev_err(&intf->dev,
+ "unable to upload ezusb firmware %s: end message.\n",
+ fwname);
+ return ret;
+ }
+ return 0;
+}
+
+static int usb6fire_fw_fpga_upload(
+ struct usb_interface *intf, const char *fwname)
+{
+ int ret;
+ int i;
+ struct usb_device *device = interface_to_usbdev(intf);
+ u8 *buffer = kmalloc(FPGA_BUFSIZE, GFP_KERNEL);
+ const char *c;
+ const char *end;
+ const struct firmware *fw;
+
+ if (!buffer)
+ return -ENOMEM;
+
+ ret = reject_firmware(&fw, fwname, &device->dev);
+ if (ret < 0) {
+ dev_err(&intf->dev, "unable to get fpga firmware %s.\n",
+ fwname);
+ kfree(buffer);
+ return -EIO;
+ }
+
+ c = fw->data;
+ end = fw->data + fw->size;
+
+ ret = usb6fire_fw_ezusb_write(device, 8, 0, NULL, 0);
+ if (ret < 0) {
+ kfree(buffer);
+ release_firmware(fw);
+ dev_err(&intf->dev,
+ "unable to upload fpga firmware: begin urb.\n");
+ return ret;
+ }
+
+ while (c != end) {
+ for (i = 0; c != end && i < FPGA_BUFSIZE; i++, c++)
+ buffer[i] = bitrev8((u8)*c);
+
+ ret = usb6fire_fw_fpga_write(device, buffer, i);
+ if (ret < 0) {
+ release_firmware(fw);
+ kfree(buffer);
+ dev_err(&intf->dev,
+ "unable to upload fpga firmware: fw urb.\n");
+ return ret;
+ }
+ }
+ release_firmware(fw);
+ kfree(buffer);
+
+ ret = usb6fire_fw_ezusb_write(device, 9, 0, NULL, 0);
+ if (ret < 0) {
+ dev_err(&intf->dev,
+ "unable to upload fpga firmware: end urb.\n");
+ return ret;
+ }
+ return 0;
+}
+
+/* check, if the firmware version the devices has currently loaded
+ * is known by this driver. 'version' needs to have 4 bytes version
+ * info data. */
+static int usb6fire_fw_check(struct usb_interface *intf, const u8 *version)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(known_fw_versions); i++)
+ if (!memcmp(version, known_fw_versions + i, 2))
+ return 0;
+
+ dev_err(&intf->dev, "invalid fimware version in device: %4ph. "
+ "please reconnect to power. if this failure "
+ "still happens, check your firmware installation.",
+ version);
+ return -EINVAL;
+}
+
+int usb6fire_fw_init(struct usb_interface *intf)
+{
+ int i;
+ int ret;
+ struct usb_device *device = interface_to_usbdev(intf);
+ /* buffer: 8 receiving bytes from device and
+ * sizeof(EP_W_MAX_PACKET_SIZE) bytes for non-const copy */
+ u8 buffer[12];
+
+ ret = usb6fire_fw_ezusb_read(device, 1, 0, buffer, 8);
+ if (ret < 0) {
+ dev_err(&intf->dev,
+ "unable to receive device firmware state.\n");
+ return ret;
+ }
+ if (buffer[0] != 0xeb || buffer[1] != 0xaa || buffer[2] != 0x55) {
+ dev_err(&intf->dev,
+ "unknown device firmware state received from device:");
+ for (i = 0; i < 8; i++)
+ printk(KERN_CONT "%02x ", buffer[i]);
+ printk(KERN_CONT "\n");
+ return -EIO;
+ }
+ /* do we need fpga loader ezusb firmware? */
+ if (buffer[3] == 0x01) {
+ ret = usb6fire_fw_ezusb_upload(intf,
+ "/*(DEBLOBBED)*/", 0, NULL, 0);
+ if (ret < 0)
+ return ret;
+ return FW_NOT_READY;
+ }
+ /* do we need fpga firmware and application ezusb firmware? */
+ else if (buffer[3] == 0x02) {
+ ret = usb6fire_fw_check(intf, buffer + 4);
+ if (ret < 0)
+ return ret;
+ ret = usb6fire_fw_fpga_upload(intf, "/*(DEBLOBBED)*/");
+ if (ret < 0)
+ return ret;
+ memcpy(buffer, ep_w_max_packet_size,
+ sizeof(ep_w_max_packet_size));
+ ret = usb6fire_fw_ezusb_upload(intf, "/*(DEBLOBBED)*/",
+ 0x0003, buffer, sizeof(ep_w_max_packet_size));
+ if (ret < 0)
+ return ret;
+ return FW_NOT_READY;
+ }
+ /* all fw loaded? */
+ else if (buffer[3] == 0x03)
+ return usb6fire_fw_check(intf, buffer + 4);
+ /* unknown data? */
+ else {
+ dev_err(&intf->dev,
+ "unknown device firmware state received from device: ");
+ for (i = 0; i < 8; i++)
+ printk(KERN_CONT "%02x ", buffer[i]);
+ printk(KERN_CONT "\n");
+ return -EIO;
+ }
+ return 0;
+}
+
diff --git a/sound/usb/6fire/firmware.h b/sound/usb/6fire/firmware.h
new file mode 100644
index 000000000..c109c4f75
--- /dev/null
+++ b/sound/usb/6fire/firmware.h
@@ -0,0 +1,27 @@
+/*
+ * Linux driver for TerraTec DMX 6Fire USB
+ *
+ * Author: Torsten Schenk
+ * Created: Jan 01, 2011
+ * Copyright: (C) Torsten Schenk
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#ifndef USB6FIRE_FIRMWARE_H
+#define USB6FIRE_FIRMWARE_H
+
+#include "common.h"
+
+enum /* firmware state of device */
+{
+ FW_READY = 0,
+ FW_NOT_READY = 1
+};
+
+int usb6fire_fw_init(struct usb_interface *intf);
+#endif /* USB6FIRE_FIRMWARE_H */
+
diff --git a/sound/usb/6fire/midi.c b/sound/usb/6fire/midi.c
new file mode 100644
index 000000000..3d4109695
--- /dev/null
+++ b/sound/usb/6fire/midi.c
@@ -0,0 +1,218 @@
+/*
+ * Linux driver for TerraTec DMX 6Fire USB
+ *
+ * Rawmidi driver
+ *
+ * Author: Torsten Schenk <torsten.schenk@zoho.com>
+ * Created: Jan 01, 2011
+ * Copyright: (C) Torsten Schenk
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#include <sound/rawmidi.h>
+
+#include "midi.h"
+#include "chip.h"
+#include "comm.h"
+
+enum {
+ MIDI_BUFSIZE = 64
+};
+
+static void usb6fire_midi_out_handler(struct urb *urb)
+{
+ struct midi_runtime *rt = urb->context;
+ int ret;
+ unsigned long flags;
+
+ spin_lock_irqsave(&rt->out_lock, flags);
+
+ if (rt->out) {
+ ret = snd_rawmidi_transmit(rt->out, rt->out_buffer + 4,
+ MIDI_BUFSIZE - 4);
+ if (ret > 0) { /* more data available, send next packet */
+ rt->out_buffer[1] = ret + 2;
+ rt->out_buffer[3] = rt->out_serial++;
+ urb->transfer_buffer_length = ret + 4;
+
+ ret = usb_submit_urb(urb, GFP_ATOMIC);
+ if (ret < 0)
+ dev_err(&urb->dev->dev,
+ "midi out urb submit failed: %d\n",
+ ret);
+ } else /* no more data to transmit */
+ rt->out = NULL;
+ }
+ spin_unlock_irqrestore(&rt->out_lock, flags);
+}
+
+static void usb6fire_midi_in_received(
+ struct midi_runtime *rt, u8 *data, int length)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&rt->in_lock, flags);
+ if (rt->in)
+ snd_rawmidi_receive(rt->in, data, length);
+ spin_unlock_irqrestore(&rt->in_lock, flags);
+}
+
+static int usb6fire_midi_out_open(struct snd_rawmidi_substream *alsa_sub)
+{
+ return 0;
+}
+
+static int usb6fire_midi_out_close(struct snd_rawmidi_substream *alsa_sub)
+{
+ return 0;
+}
+
+static void usb6fire_midi_out_trigger(
+ struct snd_rawmidi_substream *alsa_sub, int up)
+{
+ struct midi_runtime *rt = alsa_sub->rmidi->private_data;
+ struct urb *urb = &rt->out_urb;
+ __s8 ret;
+ unsigned long flags;
+
+ spin_lock_irqsave(&rt->out_lock, flags);
+ if (up) { /* start transfer */
+ if (rt->out) { /* we are already transmitting so just return */
+ spin_unlock_irqrestore(&rt->out_lock, flags);
+ return;
+ }
+
+ ret = snd_rawmidi_transmit(alsa_sub, rt->out_buffer + 4,
+ MIDI_BUFSIZE - 4);
+ if (ret > 0) {
+ rt->out_buffer[1] = ret + 2;
+ rt->out_buffer[3] = rt->out_serial++;
+ urb->transfer_buffer_length = ret + 4;
+
+ ret = usb_submit_urb(urb, GFP_ATOMIC);
+ if (ret < 0)
+ dev_err(&urb->dev->dev,
+ "midi out urb submit failed: %d\n",
+ ret);
+ else
+ rt->out = alsa_sub;
+ }
+ } else if (rt->out == alsa_sub)
+ rt->out = NULL;
+ spin_unlock_irqrestore(&rt->out_lock, flags);
+}
+
+static void usb6fire_midi_out_drain(struct snd_rawmidi_substream *alsa_sub)
+{
+ struct midi_runtime *rt = alsa_sub->rmidi->private_data;
+ int retry = 0;
+
+ while (rt->out && retry++ < 100)
+ msleep(10);
+}
+
+static int usb6fire_midi_in_open(struct snd_rawmidi_substream *alsa_sub)
+{
+ return 0;
+}
+
+static int usb6fire_midi_in_close(struct snd_rawmidi_substream *alsa_sub)
+{
+ return 0;
+}
+
+static void usb6fire_midi_in_trigger(
+ struct snd_rawmidi_substream *alsa_sub, int up)
+{
+ struct midi_runtime *rt = alsa_sub->rmidi->private_data;
+ unsigned long flags;
+
+ spin_lock_irqsave(&rt->in_lock, flags);
+ if (up)
+ rt->in = alsa_sub;
+ else
+ rt->in = NULL;
+ spin_unlock_irqrestore(&rt->in_lock, flags);
+}
+
+static struct snd_rawmidi_ops out_ops = {
+ .open = usb6fire_midi_out_open,
+ .close = usb6fire_midi_out_close,
+ .trigger = usb6fire_midi_out_trigger,
+ .drain = usb6fire_midi_out_drain
+};
+
+static struct snd_rawmidi_ops in_ops = {
+ .open = usb6fire_midi_in_open,
+ .close = usb6fire_midi_in_close,
+ .trigger = usb6fire_midi_in_trigger
+};
+
+int usb6fire_midi_init(struct sfire_chip *chip)
+{
+ int ret;
+ struct midi_runtime *rt = kzalloc(sizeof(struct midi_runtime),
+ GFP_KERNEL);
+ struct comm_runtime *comm_rt = chip->comm;
+
+ if (!rt)
+ return -ENOMEM;
+
+ rt->out_buffer = kzalloc(MIDI_BUFSIZE, GFP_KERNEL);
+ if (!rt->out_buffer) {
+ kfree(rt);
+ return -ENOMEM;
+ }
+
+ rt->chip = chip;
+ rt->in_received = usb6fire_midi_in_received;
+ rt->out_buffer[0] = 0x80; /* 'send midi' command */
+ rt->out_buffer[1] = 0x00; /* size of data */
+ rt->out_buffer[2] = 0x00; /* always 0 */
+ spin_lock_init(&rt->in_lock);
+ spin_lock_init(&rt->out_lock);
+
+ comm_rt->init_urb(comm_rt, &rt->out_urb, rt->out_buffer, rt,
+ usb6fire_midi_out_handler);
+
+ ret = snd_rawmidi_new(chip->card, "6FireUSB", 0, 1, 1, &rt->instance);
+ if (ret < 0) {
+ kfree(rt->out_buffer);
+ kfree(rt);
+ dev_err(&chip->dev->dev, "unable to create midi.\n");
+ return ret;
+ }
+ rt->instance->private_data = rt;
+ strcpy(rt->instance->name, "DMX6FireUSB MIDI");
+ rt->instance->info_flags = SNDRV_RAWMIDI_INFO_OUTPUT |
+ SNDRV_RAWMIDI_INFO_INPUT |
+ SNDRV_RAWMIDI_INFO_DUPLEX;
+ snd_rawmidi_set_ops(rt->instance, SNDRV_RAWMIDI_STREAM_OUTPUT,
+ &out_ops);
+ snd_rawmidi_set_ops(rt->instance, SNDRV_RAWMIDI_STREAM_INPUT,
+ &in_ops);
+
+ chip->midi = rt;
+ return 0;
+}
+
+void usb6fire_midi_abort(struct sfire_chip *chip)
+{
+ struct midi_runtime *rt = chip->midi;
+
+ if (rt)
+ usb_poison_urb(&rt->out_urb);
+}
+
+void usb6fire_midi_destroy(struct sfire_chip *chip)
+{
+ struct midi_runtime *rt = chip->midi;
+
+ kfree(rt->out_buffer);
+ kfree(rt);
+ chip->midi = NULL;
+}
diff --git a/sound/usb/6fire/midi.h b/sound/usb/6fire/midi.h
new file mode 100644
index 000000000..84851b9f5
--- /dev/null
+++ b/sound/usb/6fire/midi.h
@@ -0,0 +1,41 @@
+/*
+ * Linux driver for TerraTec DMX 6Fire USB
+ *
+ * Author: Torsten Schenk <torsten.schenk@zoho.com>
+ * Created: Jan 01, 2011
+ * Copyright: (C) Torsten Schenk
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#ifndef USB6FIRE_MIDI_H
+#define USB6FIRE_MIDI_H
+
+#include "common.h"
+
+struct midi_runtime {
+ struct sfire_chip *chip;
+ struct snd_rawmidi *instance;
+
+ struct snd_rawmidi_substream *in;
+ char in_active;
+
+ spinlock_t in_lock;
+ spinlock_t out_lock;
+ struct snd_rawmidi_substream *out;
+ struct urb out_urb;
+ u8 out_serial; /* serial number of out packet */
+ u8 *out_buffer;
+ int buffer_offset;
+
+ void (*in_received)(struct midi_runtime *rt, u8 *data, int length);
+};
+
+int usb6fire_midi_init(struct sfire_chip *chip);
+void usb6fire_midi_abort(struct sfire_chip *chip);
+void usb6fire_midi_destroy(struct sfire_chip *chip);
+#endif /* USB6FIRE_MIDI_H */
+
diff --git a/sound/usb/6fire/pcm.c b/sound/usb/6fire/pcm.c
new file mode 100644
index 000000000..36f4115eb
--- /dev/null
+++ b/sound/usb/6fire/pcm.c
@@ -0,0 +1,708 @@
+/*
+ * Linux driver for TerraTec DMX 6Fire USB
+ *
+ * PCM driver
+ *
+ * Author: Torsten Schenk <torsten.schenk@zoho.com>
+ * Created: Jan 01, 2011
+ * Copyright: (C) Torsten Schenk
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#include "pcm.h"
+#include "chip.h"
+#include "comm.h"
+#include "control.h"
+
+enum {
+ OUT_N_CHANNELS = 6, IN_N_CHANNELS = 4
+};
+
+/* keep next two synced with
+ * FW_EP_W_MAX_PACKET_SIZE[] and RATES_MAX_PACKET_SIZE
+ * and CONTROL_RATE_XXX in control.h */
+static const int rates_in_packet_size[] = { 228, 228, 420, 420, 404, 404 };
+static const int rates_out_packet_size[] = { 228, 228, 420, 420, 604, 604 };
+static const int rates[] = { 44100, 48000, 88200, 96000, 176400, 192000 };
+static const int rates_alsaid[] = {
+ SNDRV_PCM_RATE_44100, SNDRV_PCM_RATE_48000,
+ SNDRV_PCM_RATE_88200, SNDRV_PCM_RATE_96000,
+ SNDRV_PCM_RATE_176400, SNDRV_PCM_RATE_192000 };
+
+enum { /* settings for pcm */
+ OUT_EP = 6, IN_EP = 2, MAX_BUFSIZE = 128 * 1024
+};
+
+enum { /* pcm streaming states */
+ STREAM_DISABLED, /* no pcm streaming */
+ STREAM_STARTING, /* pcm streaming requested, waiting to become ready */
+ STREAM_RUNNING, /* pcm streaming running */
+ STREAM_STOPPING
+};
+
+static const struct snd_pcm_hardware pcm_hw = {
+ .info = SNDRV_PCM_INFO_MMAP |
+ SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_BLOCK_TRANSFER |
+ SNDRV_PCM_INFO_MMAP_VALID |
+ SNDRV_PCM_INFO_BATCH,
+
+ .formats = SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE,
+
+ .rates = SNDRV_PCM_RATE_44100 |
+ SNDRV_PCM_RATE_48000 |
+ SNDRV_PCM_RATE_88200 |
+ SNDRV_PCM_RATE_96000 |
+ SNDRV_PCM_RATE_176400 |
+ SNDRV_PCM_RATE_192000,
+
+ .rate_min = 44100,
+ .rate_max = 192000,
+ .channels_min = 1,
+ .channels_max = 0, /* set in pcm_open, depending on capture/playback */
+ .buffer_bytes_max = MAX_BUFSIZE,
+ .period_bytes_min = PCM_N_PACKETS_PER_URB * (PCM_MAX_PACKET_SIZE - 4),
+ .period_bytes_max = MAX_BUFSIZE,
+ .periods_min = 2,
+ .periods_max = 1024
+};
+
+static int usb6fire_pcm_set_rate(struct pcm_runtime *rt)
+{
+ int ret;
+ struct control_runtime *ctrl_rt = rt->chip->control;
+
+ ctrl_rt->usb_streaming = false;
+ ret = ctrl_rt->update_streaming(ctrl_rt);
+ if (ret < 0) {
+ dev_err(&rt->chip->dev->dev,
+ "error stopping streaming while setting samplerate %d.\n",
+ rates[rt->rate]);
+ return ret;
+ }
+
+ ret = ctrl_rt->set_rate(ctrl_rt, rt->rate);
+ if (ret < 0) {
+ dev_err(&rt->chip->dev->dev,
+ "error setting samplerate %d.\n",
+ rates[rt->rate]);
+ return ret;
+ }
+
+ ret = ctrl_rt->set_channels(ctrl_rt, OUT_N_CHANNELS, IN_N_CHANNELS,
+ false, false);
+ if (ret < 0) {
+ dev_err(&rt->chip->dev->dev,
+ "error initializing channels while setting samplerate %d.\n",
+ rates[rt->rate]);
+ return ret;
+ }
+
+ ctrl_rt->usb_streaming = true;
+ ret = ctrl_rt->update_streaming(ctrl_rt);
+ if (ret < 0) {
+ dev_err(&rt->chip->dev->dev,
+ "error starting streaming while setting samplerate %d.\n",
+ rates[rt->rate]);
+ return ret;
+ }
+
+ rt->in_n_analog = IN_N_CHANNELS;
+ rt->out_n_analog = OUT_N_CHANNELS;
+ rt->in_packet_size = rates_in_packet_size[rt->rate];
+ rt->out_packet_size = rates_out_packet_size[rt->rate];
+ return 0;
+}
+
+static struct pcm_substream *usb6fire_pcm_get_substream(
+ struct snd_pcm_substream *alsa_sub)
+{
+ struct pcm_runtime *rt = snd_pcm_substream_chip(alsa_sub);
+
+ if (alsa_sub->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ return &rt->playback;
+ else if (alsa_sub->stream == SNDRV_PCM_STREAM_CAPTURE)
+ return &rt->capture;
+ dev_err(&rt->chip->dev->dev, "error getting pcm substream slot.\n");
+ return NULL;
+}
+
+/* call with stream_mutex locked */
+static void usb6fire_pcm_stream_stop(struct pcm_runtime *rt)
+{
+ int i;
+ struct control_runtime *ctrl_rt = rt->chip->control;
+
+ if (rt->stream_state != STREAM_DISABLED) {
+
+ rt->stream_state = STREAM_STOPPING;
+
+ for (i = 0; i < PCM_N_URBS; i++) {
+ usb_kill_urb(&rt->in_urbs[i].instance);
+ usb_kill_urb(&rt->out_urbs[i].instance);
+ }
+ ctrl_rt->usb_streaming = false;
+ ctrl_rt->update_streaming(ctrl_rt);
+ rt->stream_state = STREAM_DISABLED;
+ }
+}
+
+/* call with stream_mutex locked */
+static int usb6fire_pcm_stream_start(struct pcm_runtime *rt)
+{
+ int ret;
+ int i;
+ int k;
+ struct usb_iso_packet_descriptor *packet;
+
+ if (rt->stream_state == STREAM_DISABLED) {
+ /* submit our in urbs */
+ rt->stream_wait_cond = false;
+ rt->stream_state = STREAM_STARTING;
+ for (i = 0; i < PCM_N_URBS; i++) {
+ for (k = 0; k < PCM_N_PACKETS_PER_URB; k++) {
+ packet = &rt->in_urbs[i].packets[k];
+ packet->offset = k * rt->in_packet_size;
+ packet->length = rt->in_packet_size;
+ packet->actual_length = 0;
+ packet->status = 0;
+ }
+ ret = usb_submit_urb(&rt->in_urbs[i].instance,
+ GFP_ATOMIC);
+ if (ret) {
+ usb6fire_pcm_stream_stop(rt);
+ return ret;
+ }
+ }
+
+ /* wait for first out urb to return (sent in in urb handler) */
+ wait_event_timeout(rt->stream_wait_queue, rt->stream_wait_cond,
+ HZ);
+ if (rt->stream_wait_cond)
+ rt->stream_state = STREAM_RUNNING;
+ else {
+ usb6fire_pcm_stream_stop(rt);
+ return -EIO;
+ }
+ }
+ return 0;
+}
+
+/* call with substream locked */
+static void usb6fire_pcm_capture(struct pcm_substream *sub, struct pcm_urb *urb)
+{
+ int i;
+ int frame;
+ int frame_count;
+ unsigned int total_length = 0;
+ struct pcm_runtime *rt = snd_pcm_substream_chip(sub->instance);
+ struct snd_pcm_runtime *alsa_rt = sub->instance->runtime;
+ u32 *src = NULL;
+ u32 *dest = (u32 *) (alsa_rt->dma_area + sub->dma_off
+ * (alsa_rt->frame_bits >> 3));
+ u32 *dest_end = (u32 *) (alsa_rt->dma_area + alsa_rt->buffer_size
+ * (alsa_rt->frame_bits >> 3));
+ int bytes_per_frame = alsa_rt->channels << 2;
+
+ for (i = 0; i < PCM_N_PACKETS_PER_URB; i++) {
+ /* at least 4 header bytes for valid packet.
+ * after that: 32 bits per sample for analog channels */
+ if (urb->packets[i].actual_length > 4)
+ frame_count = (urb->packets[i].actual_length - 4)
+ / (rt->in_n_analog << 2);
+ else
+ frame_count = 0;
+
+ if (alsa_rt->format == SNDRV_PCM_FORMAT_S24_LE)
+ src = (u32 *) (urb->buffer + total_length);
+ else if (alsa_rt->format == SNDRV_PCM_FORMAT_S32_LE)
+ src = (u32 *) (urb->buffer - 1 + total_length);
+ else
+ return;
+ src++; /* skip leading 4 bytes of every packet */
+ total_length += urb->packets[i].length;
+ for (frame = 0; frame < frame_count; frame++) {
+ memcpy(dest, src, bytes_per_frame);
+ dest += alsa_rt->channels;
+ src += rt->in_n_analog;
+ sub->dma_off++;
+ sub->period_off++;
+ if (dest == dest_end) {
+ sub->dma_off = 0;
+ dest = (u32 *) alsa_rt->dma_area;
+ }
+ }
+ }
+}
+
+/* call with substream locked */
+static void usb6fire_pcm_playback(struct pcm_substream *sub,
+ struct pcm_urb *urb)
+{
+ int i;
+ int frame;
+ int frame_count;
+ struct pcm_runtime *rt = snd_pcm_substream_chip(sub->instance);
+ struct snd_pcm_runtime *alsa_rt = sub->instance->runtime;
+ u32 *src = (u32 *) (alsa_rt->dma_area + sub->dma_off
+ * (alsa_rt->frame_bits >> 3));
+ u32 *src_end = (u32 *) (alsa_rt->dma_area + alsa_rt->buffer_size
+ * (alsa_rt->frame_bits >> 3));
+ u32 *dest;
+ int bytes_per_frame = alsa_rt->channels << 2;
+
+ if (alsa_rt->format == SNDRV_PCM_FORMAT_S32_LE)
+ dest = (u32 *) (urb->buffer - 1);
+ else if (alsa_rt->format == SNDRV_PCM_FORMAT_S24_LE)
+ dest = (u32 *) (urb->buffer);
+ else {
+ dev_err(&rt->chip->dev->dev, "Unknown sample format.");
+ return;
+ }
+
+ for (i = 0; i < PCM_N_PACKETS_PER_URB; i++) {
+ /* at least 4 header bytes for valid packet.
+ * after that: 32 bits per sample for analog channels */
+ if (urb->packets[i].length > 4)
+ frame_count = (urb->packets[i].length - 4)
+ / (rt->out_n_analog << 2);
+ else
+ frame_count = 0;
+ dest++; /* skip leading 4 bytes of every frame */
+ for (frame = 0; frame < frame_count; frame++) {
+ memcpy(dest, src, bytes_per_frame);
+ src += alsa_rt->channels;
+ dest += rt->out_n_analog;
+ sub->dma_off++;
+ sub->period_off++;
+ if (src == src_end) {
+ src = (u32 *) alsa_rt->dma_area;
+ sub->dma_off = 0;
+ }
+ }
+ }
+}
+
+static void usb6fire_pcm_in_urb_handler(struct urb *usb_urb)
+{
+ struct pcm_urb *in_urb = usb_urb->context;
+ struct pcm_urb *out_urb = in_urb->peer;
+ struct pcm_runtime *rt = in_urb->chip->pcm;
+ struct pcm_substream *sub;
+ unsigned long flags;
+ int total_length = 0;
+ int frame_count;
+ int frame;
+ int channel;
+ int i;
+ u8 *dest;
+
+ if (usb_urb->status || rt->panic || rt->stream_state == STREAM_STOPPING)
+ return;
+ for (i = 0; i < PCM_N_PACKETS_PER_URB; i++)
+ if (in_urb->packets[i].status) {
+ rt->panic = true;
+ return;
+ }
+
+ if (rt->stream_state == STREAM_DISABLED) {
+ dev_err(&rt->chip->dev->dev,
+ "internal error: stream disabled in in-urb handler.\n");
+ return;
+ }
+
+ /* receive our capture data */
+ sub = &rt->capture;
+ spin_lock_irqsave(&sub->lock, flags);
+ if (sub->active) {
+ usb6fire_pcm_capture(sub, in_urb);
+ if (sub->period_off >= sub->instance->runtime->period_size) {
+ sub->period_off %= sub->instance->runtime->period_size;
+ spin_unlock_irqrestore(&sub->lock, flags);
+ snd_pcm_period_elapsed(sub->instance);
+ } else
+ spin_unlock_irqrestore(&sub->lock, flags);
+ } else
+ spin_unlock_irqrestore(&sub->lock, flags);
+
+ /* setup out urb structure */
+ for (i = 0; i < PCM_N_PACKETS_PER_URB; i++) {
+ out_urb->packets[i].offset = total_length;
+ out_urb->packets[i].length = (in_urb->packets[i].actual_length
+ - 4) / (rt->in_n_analog << 2)
+ * (rt->out_n_analog << 2) + 4;
+ out_urb->packets[i].status = 0;
+ total_length += out_urb->packets[i].length;
+ }
+ memset(out_urb->buffer, 0, total_length);
+
+ /* now send our playback data (if a free out urb was found) */
+ sub = &rt->playback;
+ spin_lock_irqsave(&sub->lock, flags);
+ if (sub->active) {
+ usb6fire_pcm_playback(sub, out_urb);
+ if (sub->period_off >= sub->instance->runtime->period_size) {
+ sub->period_off %= sub->instance->runtime->period_size;
+ spin_unlock_irqrestore(&sub->lock, flags);
+ snd_pcm_period_elapsed(sub->instance);
+ } else
+ spin_unlock_irqrestore(&sub->lock, flags);
+ } else
+ spin_unlock_irqrestore(&sub->lock, flags);
+
+ /* setup the 4th byte of each sample (0x40 for analog channels) */
+ dest = out_urb->buffer;
+ for (i = 0; i < PCM_N_PACKETS_PER_URB; i++)
+ if (out_urb->packets[i].length >= 4) {
+ frame_count = (out_urb->packets[i].length - 4)
+ / (rt->out_n_analog << 2);
+ *(dest++) = 0xaa;
+ *(dest++) = 0xaa;
+ *(dest++) = frame_count;
+ *(dest++) = 0x00;
+ for (frame = 0; frame < frame_count; frame++)
+ for (channel = 0;
+ channel < rt->out_n_analog;
+ channel++) {
+ dest += 3; /* skip sample data */
+ *(dest++) = 0x40;
+ }
+ }
+ usb_submit_urb(&out_urb->instance, GFP_ATOMIC);
+ usb_submit_urb(&in_urb->instance, GFP_ATOMIC);
+}
+
+static void usb6fire_pcm_out_urb_handler(struct urb *usb_urb)
+{
+ struct pcm_urb *urb = usb_urb->context;
+ struct pcm_runtime *rt = urb->chip->pcm;
+
+ if (rt->stream_state == STREAM_STARTING) {
+ rt->stream_wait_cond = true;
+ wake_up(&rt->stream_wait_queue);
+ }
+}
+
+static int usb6fire_pcm_open(struct snd_pcm_substream *alsa_sub)
+{
+ struct pcm_runtime *rt = snd_pcm_substream_chip(alsa_sub);
+ struct pcm_substream *sub = NULL;
+ struct snd_pcm_runtime *alsa_rt = alsa_sub->runtime;
+
+ if (rt->panic)
+ return -EPIPE;
+
+ mutex_lock(&rt->stream_mutex);
+ alsa_rt->hw = pcm_hw;
+
+ if (alsa_sub->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ if (rt->rate < ARRAY_SIZE(rates))
+ alsa_rt->hw.rates = rates_alsaid[rt->rate];
+ alsa_rt->hw.channels_max = OUT_N_CHANNELS;
+ sub = &rt->playback;
+ } else if (alsa_sub->stream == SNDRV_PCM_STREAM_CAPTURE) {
+ if (rt->rate < ARRAY_SIZE(rates))
+ alsa_rt->hw.rates = rates_alsaid[rt->rate];
+ alsa_rt->hw.channels_max = IN_N_CHANNELS;
+ sub = &rt->capture;
+ }
+
+ if (!sub) {
+ mutex_unlock(&rt->stream_mutex);
+ dev_err(&rt->chip->dev->dev, "invalid stream type.\n");
+ return -EINVAL;
+ }
+
+ sub->instance = alsa_sub;
+ sub->active = false;
+ mutex_unlock(&rt->stream_mutex);
+ return 0;
+}
+
+static int usb6fire_pcm_close(struct snd_pcm_substream *alsa_sub)
+{
+ struct pcm_runtime *rt = snd_pcm_substream_chip(alsa_sub);
+ struct pcm_substream *sub = usb6fire_pcm_get_substream(alsa_sub);
+ unsigned long flags;
+
+ if (rt->panic)
+ return 0;
+
+ mutex_lock(&rt->stream_mutex);
+ if (sub) {
+ /* deactivate substream */
+ spin_lock_irqsave(&sub->lock, flags);
+ sub->instance = NULL;
+ sub->active = false;
+ spin_unlock_irqrestore(&sub->lock, flags);
+
+ /* all substreams closed? if so, stop streaming */
+ if (!rt->playback.instance && !rt->capture.instance) {
+ usb6fire_pcm_stream_stop(rt);
+ rt->rate = ARRAY_SIZE(rates);
+ }
+ }
+ mutex_unlock(&rt->stream_mutex);
+ return 0;
+}
+
+static int usb6fire_pcm_hw_params(struct snd_pcm_substream *alsa_sub,
+ struct snd_pcm_hw_params *hw_params)
+{
+ return snd_pcm_lib_alloc_vmalloc_buffer(alsa_sub,
+ params_buffer_bytes(hw_params));
+}
+
+static int usb6fire_pcm_hw_free(struct snd_pcm_substream *alsa_sub)
+{
+ return snd_pcm_lib_free_vmalloc_buffer(alsa_sub);
+}
+
+static int usb6fire_pcm_prepare(struct snd_pcm_substream *alsa_sub)
+{
+ struct pcm_runtime *rt = snd_pcm_substream_chip(alsa_sub);
+ struct pcm_substream *sub = usb6fire_pcm_get_substream(alsa_sub);
+ struct snd_pcm_runtime *alsa_rt = alsa_sub->runtime;
+ int ret;
+
+ if (rt->panic)
+ return -EPIPE;
+ if (!sub)
+ return -ENODEV;
+
+ mutex_lock(&rt->stream_mutex);
+ sub->dma_off = 0;
+ sub->period_off = 0;
+
+ if (rt->stream_state == STREAM_DISABLED) {
+ for (rt->rate = 0; rt->rate < ARRAY_SIZE(rates); rt->rate++)
+ if (alsa_rt->rate == rates[rt->rate])
+ break;
+ if (rt->rate == ARRAY_SIZE(rates)) {
+ mutex_unlock(&rt->stream_mutex);
+ dev_err(&rt->chip->dev->dev,
+ "invalid rate %d in prepare.\n",
+ alsa_rt->rate);
+ return -EINVAL;
+ }
+
+ ret = usb6fire_pcm_set_rate(rt);
+ if (ret) {
+ mutex_unlock(&rt->stream_mutex);
+ return ret;
+ }
+ ret = usb6fire_pcm_stream_start(rt);
+ if (ret) {
+ mutex_unlock(&rt->stream_mutex);
+ dev_err(&rt->chip->dev->dev,
+ "could not start pcm stream.\n");
+ return ret;
+ }
+ }
+ mutex_unlock(&rt->stream_mutex);
+ return 0;
+}
+
+static int usb6fire_pcm_trigger(struct snd_pcm_substream *alsa_sub, int cmd)
+{
+ struct pcm_substream *sub = usb6fire_pcm_get_substream(alsa_sub);
+ struct pcm_runtime *rt = snd_pcm_substream_chip(alsa_sub);
+ unsigned long flags;
+
+ if (rt->panic)
+ return -EPIPE;
+ if (!sub)
+ return -ENODEV;
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ spin_lock_irqsave(&sub->lock, flags);
+ sub->active = true;
+ spin_unlock_irqrestore(&sub->lock, flags);
+ return 0;
+
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ spin_lock_irqsave(&sub->lock, flags);
+ sub->active = false;
+ spin_unlock_irqrestore(&sub->lock, flags);
+ return 0;
+
+ default:
+ return -EINVAL;
+ }
+}
+
+static snd_pcm_uframes_t usb6fire_pcm_pointer(
+ struct snd_pcm_substream *alsa_sub)
+{
+ struct pcm_substream *sub = usb6fire_pcm_get_substream(alsa_sub);
+ struct pcm_runtime *rt = snd_pcm_substream_chip(alsa_sub);
+ unsigned long flags;
+ snd_pcm_uframes_t ret;
+
+ if (rt->panic || !sub)
+ return SNDRV_PCM_POS_XRUN;
+
+ spin_lock_irqsave(&sub->lock, flags);
+ ret = sub->dma_off;
+ spin_unlock_irqrestore(&sub->lock, flags);
+ return ret;
+}
+
+static struct snd_pcm_ops pcm_ops = {
+ .open = usb6fire_pcm_open,
+ .close = usb6fire_pcm_close,
+ .ioctl = snd_pcm_lib_ioctl,
+ .hw_params = usb6fire_pcm_hw_params,
+ .hw_free = usb6fire_pcm_hw_free,
+ .prepare = usb6fire_pcm_prepare,
+ .trigger = usb6fire_pcm_trigger,
+ .pointer = usb6fire_pcm_pointer,
+ .page = snd_pcm_lib_get_vmalloc_page,
+ .mmap = snd_pcm_lib_mmap_vmalloc,
+};
+
+static void usb6fire_pcm_init_urb(struct pcm_urb *urb,
+ struct sfire_chip *chip, bool in, int ep,
+ void (*handler)(struct urb *))
+{
+ urb->chip = chip;
+ usb_init_urb(&urb->instance);
+ urb->instance.transfer_buffer = urb->buffer;
+ urb->instance.transfer_buffer_length =
+ PCM_N_PACKETS_PER_URB * PCM_MAX_PACKET_SIZE;
+ urb->instance.dev = chip->dev;
+ urb->instance.pipe = in ? usb_rcvisocpipe(chip->dev, ep)
+ : usb_sndisocpipe(chip->dev, ep);
+ urb->instance.interval = 1;
+ urb->instance.complete = handler;
+ urb->instance.context = urb;
+ urb->instance.number_of_packets = PCM_N_PACKETS_PER_URB;
+}
+
+static int usb6fire_pcm_buffers_init(struct pcm_runtime *rt)
+{
+ int i;
+
+ for (i = 0; i < PCM_N_URBS; i++) {
+ rt->out_urbs[i].buffer = kzalloc(PCM_N_PACKETS_PER_URB
+ * PCM_MAX_PACKET_SIZE, GFP_KERNEL);
+ if (!rt->out_urbs[i].buffer)
+ return -ENOMEM;
+ rt->in_urbs[i].buffer = kzalloc(PCM_N_PACKETS_PER_URB
+ * PCM_MAX_PACKET_SIZE, GFP_KERNEL);
+ if (!rt->in_urbs[i].buffer)
+ return -ENOMEM;
+ }
+ return 0;
+}
+
+static void usb6fire_pcm_buffers_destroy(struct pcm_runtime *rt)
+{
+ int i;
+
+ for (i = 0; i < PCM_N_URBS; i++) {
+ kfree(rt->out_urbs[i].buffer);
+ kfree(rt->in_urbs[i].buffer);
+ }
+}
+
+int usb6fire_pcm_init(struct sfire_chip *chip)
+{
+ int i;
+ int ret;
+ struct snd_pcm *pcm;
+ struct pcm_runtime *rt =
+ kzalloc(sizeof(struct pcm_runtime), GFP_KERNEL);
+
+ if (!rt)
+ return -ENOMEM;
+
+ ret = usb6fire_pcm_buffers_init(rt);
+ if (ret) {
+ usb6fire_pcm_buffers_destroy(rt);
+ kfree(rt);
+ return ret;
+ }
+
+ rt->chip = chip;
+ rt->stream_state = STREAM_DISABLED;
+ rt->rate = ARRAY_SIZE(rates);
+ init_waitqueue_head(&rt->stream_wait_queue);
+ mutex_init(&rt->stream_mutex);
+
+ spin_lock_init(&rt->playback.lock);
+ spin_lock_init(&rt->capture.lock);
+
+ for (i = 0; i < PCM_N_URBS; i++) {
+ usb6fire_pcm_init_urb(&rt->in_urbs[i], chip, true, IN_EP,
+ usb6fire_pcm_in_urb_handler);
+ usb6fire_pcm_init_urb(&rt->out_urbs[i], chip, false, OUT_EP,
+ usb6fire_pcm_out_urb_handler);
+
+ rt->in_urbs[i].peer = &rt->out_urbs[i];
+ rt->out_urbs[i].peer = &rt->in_urbs[i];
+ }
+
+ ret = snd_pcm_new(chip->card, "DMX6FireUSB", 0, 1, 1, &pcm);
+ if (ret < 0) {
+ usb6fire_pcm_buffers_destroy(rt);
+ kfree(rt);
+ dev_err(&chip->dev->dev, "cannot create pcm instance.\n");
+ return ret;
+ }
+
+ pcm->private_data = rt;
+ strcpy(pcm->name, "DMX 6Fire USB");
+ snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &pcm_ops);
+ snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &pcm_ops);
+
+ if (ret) {
+ usb6fire_pcm_buffers_destroy(rt);
+ kfree(rt);
+ dev_err(&chip->dev->dev,
+ "error preallocating pcm buffers.\n");
+ return ret;
+ }
+ rt->instance = pcm;
+
+ chip->pcm = rt;
+ return 0;
+}
+
+void usb6fire_pcm_abort(struct sfire_chip *chip)
+{
+ struct pcm_runtime *rt = chip->pcm;
+ int i;
+
+ if (rt) {
+ rt->panic = true;
+
+ if (rt->playback.instance)
+ snd_pcm_stop_xrun(rt->playback.instance);
+
+ if (rt->capture.instance)
+ snd_pcm_stop_xrun(rt->capture.instance);
+
+ for (i = 0; i < PCM_N_URBS; i++) {
+ usb_poison_urb(&rt->in_urbs[i].instance);
+ usb_poison_urb(&rt->out_urbs[i].instance);
+ }
+
+ }
+}
+
+void usb6fire_pcm_destroy(struct sfire_chip *chip)
+{
+ struct pcm_runtime *rt = chip->pcm;
+
+ usb6fire_pcm_buffers_destroy(rt);
+ kfree(rt);
+ chip->pcm = NULL;
+}
diff --git a/sound/usb/6fire/pcm.h b/sound/usb/6fire/pcm.h
new file mode 100644
index 000000000..f5779d618
--- /dev/null
+++ b/sound/usb/6fire/pcm.h
@@ -0,0 +1,75 @@
+/*
+ * Linux driver for TerraTec DMX 6Fire USB
+ *
+ * Author: Torsten Schenk <torsten.schenk@zoho.com>
+ * Created: Jan 01, 2011
+ * Copyright: (C) Torsten Schenk
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#ifndef USB6FIRE_PCM_H
+#define USB6FIRE_PCM_H
+
+#include <sound/pcm.h>
+#include <linux/mutex.h>
+
+#include "common.h"
+
+enum /* settings for pcm */
+{
+ /* maximum of EP_W_MAX_PACKET_SIZE[] (see firmware.c) */
+ PCM_N_URBS = 16, PCM_N_PACKETS_PER_URB = 8, PCM_MAX_PACKET_SIZE = 604
+};
+
+struct pcm_urb {
+ struct sfire_chip *chip;
+
+ /* BEGIN DO NOT SEPARATE */
+ struct urb instance;
+ struct usb_iso_packet_descriptor packets[PCM_N_PACKETS_PER_URB];
+ /* END DO NOT SEPARATE */
+ u8 *buffer;
+
+ struct pcm_urb *peer;
+};
+
+struct pcm_substream {
+ spinlock_t lock;
+ struct snd_pcm_substream *instance;
+
+ bool active;
+
+ snd_pcm_uframes_t dma_off; /* current position in alsa dma_area */
+ snd_pcm_uframes_t period_off; /* current position in current period */
+};
+
+struct pcm_runtime {
+ struct sfire_chip *chip;
+ struct snd_pcm *instance;
+
+ struct pcm_substream playback;
+ struct pcm_substream capture;
+ bool panic; /* if set driver won't do anymore pcm on device */
+
+ struct pcm_urb in_urbs[PCM_N_URBS];
+ struct pcm_urb out_urbs[PCM_N_URBS];
+ int in_packet_size;
+ int out_packet_size;
+ int in_n_analog; /* number of analog channels soundcard sends */
+ int out_n_analog; /* number of analog channels soundcard receives */
+
+ struct mutex stream_mutex;
+ u8 stream_state; /* one of STREAM_XXX (pcm.c) */
+ u8 rate; /* one of PCM_RATE_XXX */
+ wait_queue_head_t stream_wait_queue;
+ bool stream_wait_cond;
+};
+
+int usb6fire_pcm_init(struct sfire_chip *chip);
+void usb6fire_pcm_abort(struct sfire_chip *chip);
+void usb6fire_pcm_destroy(struct sfire_chip *chip);
+#endif /* USB6FIRE_PCM_H */
diff --git a/sound/usb/Kconfig b/sound/usb/Kconfig
new file mode 100644
index 000000000..a452ad7ce
--- /dev/null
+++ b/sound/usb/Kconfig
@@ -0,0 +1,166 @@
+# ALSA USB drivers
+
+menuconfig SND_USB
+ bool "USB sound devices"
+ depends on USB
+ default y
+ help
+ Support for sound devices connected via the USB bus.
+
+if SND_USB && USB
+
+config SND_USB_AUDIO
+ tristate "USB Audio/MIDI driver"
+ select SND_HWDEP
+ select SND_RAWMIDI
+ select SND_PCM
+ select BITREVERSE
+ help
+ Say Y here to include support for USB audio and USB MIDI
+ devices.
+
+ To compile this driver as a module, choose M here: the module
+ will be called snd-usb-audio.
+
+config SND_USB_UA101
+ tristate "Edirol UA-101/UA-1000 driver"
+ select SND_PCM
+ select SND_RAWMIDI
+ help
+ Say Y here to include support for the Edirol UA-101 and UA-1000
+ audio/MIDI interfaces.
+
+ To compile this driver as a module, choose M here: the module
+ will be called snd-ua101.
+
+config SND_USB_USX2Y
+ tristate "Tascam US-122, US-224 and US-428 USB driver"
+ depends on X86 || PPC || ALPHA
+ select SND_HWDEP
+ select SND_RAWMIDI
+ select SND_PCM
+ help
+ Say Y here to include support for Tascam USB Audio/MIDI
+ interfaces or controllers US-122, US-224 and US-428.
+
+ To compile this driver as a module, choose M here: the module
+ will be called snd-usb-usx2y.
+
+config SND_USB_CAIAQ
+ tristate "Native Instruments USB audio devices"
+ select SND_HWDEP
+ select SND_RAWMIDI
+ select SND_PCM
+ help
+ Say Y here to include support for caiaq USB audio interfaces,
+ namely:
+
+ * Native Instruments RigKontrol2
+ * Native Instruments RigKontrol3
+ * Native Instruments Kore Controller
+ * Native Instruments Kore Controller 2
+ * Native Instruments Audio Kontrol 1
+ * Native Instruments Audio 2 DJ
+ * Native Instruments Audio 4 DJ
+ * Native Instruments Audio 8 DJ
+ * Native Instruments Traktor Audio 2
+ * Native Instruments Guitar Rig Session I/O
+ * Native Instruments Guitar Rig mobile
+ * Native Instruments Traktor Kontrol X1
+ * Native Instruments Traktor Kontrol S4
+ * Native Instruments Maschine Controller
+
+ To compile this driver as a module, choose M here: the module
+ will be called snd-usb-caiaq.
+
+config SND_USB_CAIAQ_INPUT
+ bool "enable input device for controllers"
+ depends on SND_USB_CAIAQ
+ depends on INPUT=y || INPUT=SND_USB_CAIAQ
+ help
+ Say Y here to support input controllers like buttons, knobs,
+ alpha dials and analog pedals on the following products:
+
+ * Native Instruments RigKontrol2
+ * Native Instruments RigKontrol3
+ * Native Instruments Kore Controller
+ * Native Instruments Kore Controller 2
+ * Native Instruments Audio Kontrol 1
+ * Native Instruments Traktor Kontrol S4
+ * Native Instruments Maschine Controller
+
+config SND_USB_US122L
+ tristate "Tascam US-122L USB driver"
+ depends on X86
+ select SND_HWDEP
+ select SND_RAWMIDI
+ help
+ Say Y here to include support for Tascam US-122L USB Audio/MIDI
+ interfaces.
+
+ To compile this driver as a module, choose M here: the module
+ will be called snd-usb-us122l.
+
+config SND_USB_6FIRE
+ tristate "TerraTec DMX 6Fire USB"
+ select FW_LOADER
+ select BITREVERSE
+ select SND_RAWMIDI
+ select SND_PCM
+ select SND_VMASTER
+ help
+ Say Y here to include support for TerraTec 6fire DMX USB interface.
+
+ You will need firmware files in order to be able to use the device
+ after it has been coldstarted. An install script for the firmware
+ and further help can be found at
+ http://sixfireusb.sourceforge.net
+
+config SND_USB_HIFACE
+ tristate "M2Tech hiFace USB-SPDIF driver"
+ select SND_PCM
+ help
+ Select this option to include support for M2Tech hiFace USB-SPDIF
+ interface.
+
+ This driver supports the original M2Tech hiFace and some other
+ compatible devices. The supported products are:
+
+ * M2Tech Young
+ * M2Tech hiFace
+ * M2Tech North Star
+ * M2Tech W4S Young
+ * M2Tech Corrson
+ * M2Tech AUDIA
+ * M2Tech SL Audio
+ * M2Tech Empirical
+ * M2Tech Rockna
+ * M2Tech Pathos
+ * M2Tech Metronome
+ * M2Tech CAD
+ * M2Tech Audio Esclusive
+ * M2Tech Rotel
+ * M2Tech Eeaudio
+ * The Chord Company CHORD
+ * AVA Group A/S Vitus
+
+ To compile this driver as a module, choose M here: the module
+ will be called snd-usb-hiface.
+
+config SND_BCD2000
+ tristate "Behringer BCD2000 MIDI driver"
+ select SND_RAWMIDI
+ help
+ Say Y here to include MIDI support for the Behringer BCD2000 DJ
+ controller.
+
+ Audio support is still work-in-progress at
+ https://github.com/anyc/snd-usb-bcd2000
+
+ To compile this driver as a module, choose M here: the module
+ will be called snd-bcd2000.
+
+source "sound/usb/line6/Kconfig"
+
+endif # SND_USB
+
diff --git a/sound/usb/Makefile b/sound/usb/Makefile
new file mode 100644
index 000000000..2d2d122b0
--- /dev/null
+++ b/sound/usb/Makefile
@@ -0,0 +1,28 @@
+#
+# Makefile for ALSA
+#
+
+snd-usb-audio-objs := card.o \
+ clock.o \
+ endpoint.o \
+ format.o \
+ helper.o \
+ mixer.o \
+ mixer_quirks.o \
+ mixer_scarlett.o \
+ pcm.o \
+ proc.o \
+ quirks.o \
+ stream.o
+
+snd-usbmidi-lib-objs := midi.o
+
+# Toplevel Module Dependency
+obj-$(CONFIG_SND_USB_AUDIO) += snd-usb-audio.o snd-usbmidi-lib.o
+
+obj-$(CONFIG_SND_USB_UA101) += snd-usbmidi-lib.o
+obj-$(CONFIG_SND_USB_USX2Y) += snd-usbmidi-lib.o
+obj-$(CONFIG_SND_USB_US122L) += snd-usbmidi-lib.o
+
+obj-$(CONFIG_SND) += misc/ usx2y/ caiaq/ 6fire/ hiface/ bcd2000/
+obj-$(CONFIG_SND_USB_LINE6) += line6/
diff --git a/sound/usb/bcd2000/Makefile b/sound/usb/bcd2000/Makefile
new file mode 100644
index 000000000..f09ccc0af
--- /dev/null
+++ b/sound/usb/bcd2000/Makefile
@@ -0,0 +1,3 @@
+snd-bcd2000-y := bcd2000.o
+
+obj-$(CONFIG_SND_BCD2000) += snd-bcd2000.o \ No newline at end of file
diff --git a/sound/usb/bcd2000/bcd2000.c b/sound/usb/bcd2000/bcd2000.c
new file mode 100644
index 000000000..820d6ca8c
--- /dev/null
+++ b/sound/usb/bcd2000/bcd2000.c
@@ -0,0 +1,461 @@
+/*
+ * Behringer BCD2000 driver
+ *
+ * Copyright (C) 2014 Mario Kicherer (dev@kicherer.org)
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/bitmap.h>
+#include <linux/usb.h>
+#include <linux/usb/audio.h>
+#include <sound/core.h>
+#include <sound/initval.h>
+#include <sound/rawmidi.h>
+
+#define PREFIX "snd-bcd2000: "
+#define BUFSIZE 64
+
+static struct usb_device_id id_table[] = {
+ { USB_DEVICE(0x1397, 0x00bd) },
+ { },
+};
+
+static unsigned char device_cmd_prefix[] = {0x03, 0x00};
+
+static unsigned char bcd2000_init_sequence[] = {
+ 0x07, 0x00, 0x00, 0x00, 0x78, 0x48, 0x1c, 0x81,
+ 0xc4, 0x00, 0x00, 0x00, 0x5e, 0x53, 0x4a, 0xf7,
+ 0x18, 0xfa, 0x11, 0xff, 0x6c, 0xf3, 0x90, 0xff,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x18, 0xfa, 0x11, 0xff, 0x14, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0xf2, 0x34, 0x4a, 0xf7,
+ 0x18, 0xfa, 0x11, 0xff
+};
+
+struct bcd2000 {
+ struct usb_device *dev;
+ struct snd_card *card;
+ struct usb_interface *intf;
+ int card_index;
+
+ int midi_out_active;
+ struct snd_rawmidi *rmidi;
+ struct snd_rawmidi_substream *midi_receive_substream;
+ struct snd_rawmidi_substream *midi_out_substream;
+
+ unsigned char midi_in_buf[BUFSIZE];
+ unsigned char midi_out_buf[BUFSIZE];
+
+ struct urb *midi_out_urb;
+ struct urb *midi_in_urb;
+
+ struct usb_anchor anchor;
+};
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;
+static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;
+
+static DEFINE_MUTEX(devices_mutex);
+DECLARE_BITMAP(devices_used, SNDRV_CARDS);
+static struct usb_driver bcd2000_driver;
+
+#ifdef CONFIG_SND_DEBUG
+static void bcd2000_dump_buffer(const char *prefix, const char *buf, int len)
+{
+ print_hex_dump(KERN_DEBUG, prefix,
+ DUMP_PREFIX_NONE, 16, 1,
+ buf, len, false);
+}
+#else
+static void bcd2000_dump_buffer(const char *prefix, const char *buf, int len) {}
+#endif
+
+static int bcd2000_midi_input_open(struct snd_rawmidi_substream *substream)
+{
+ return 0;
+}
+
+static int bcd2000_midi_input_close(struct snd_rawmidi_substream *substream)
+{
+ return 0;
+}
+
+/* (de)register midi substream from client */
+static void bcd2000_midi_input_trigger(struct snd_rawmidi_substream *substream,
+ int up)
+{
+ struct bcd2000 *bcd2k = substream->rmidi->private_data;
+ bcd2k->midi_receive_substream = up ? substream : NULL;
+}
+
+static void bcd2000_midi_handle_input(struct bcd2000 *bcd2k,
+ const unsigned char *buf, unsigned int buf_len)
+{
+ unsigned int payload_length, tocopy;
+ struct snd_rawmidi_substream *midi_receive_substream;
+
+ midi_receive_substream = ACCESS_ONCE(bcd2k->midi_receive_substream);
+ if (!midi_receive_substream)
+ return;
+
+ bcd2000_dump_buffer(PREFIX "received from device: ", buf, buf_len);
+
+ if (buf_len < 2)
+ return;
+
+ payload_length = buf[0];
+
+ /* ignore packets without payload */
+ if (payload_length == 0)
+ return;
+
+ tocopy = min(payload_length, buf_len-1);
+
+ bcd2000_dump_buffer(PREFIX "sending to userspace: ",
+ &buf[1], tocopy);
+
+ snd_rawmidi_receive(midi_receive_substream,
+ &buf[1], tocopy);
+}
+
+static void bcd2000_midi_send(struct bcd2000 *bcd2k)
+{
+ int len, ret;
+ struct snd_rawmidi_substream *midi_out_substream;
+
+ BUILD_BUG_ON(sizeof(device_cmd_prefix) >= BUFSIZE);
+
+ midi_out_substream = ACCESS_ONCE(bcd2k->midi_out_substream);
+ if (!midi_out_substream)
+ return;
+
+ /* copy command prefix bytes */
+ memcpy(bcd2k->midi_out_buf, device_cmd_prefix,
+ sizeof(device_cmd_prefix));
+
+ /*
+ * get MIDI packet and leave space for command prefix
+ * and payload length
+ */
+ len = snd_rawmidi_transmit(midi_out_substream,
+ bcd2k->midi_out_buf + 3, BUFSIZE - 3);
+
+ if (len < 0)
+ dev_err(&bcd2k->dev->dev, "%s: snd_rawmidi_transmit error %d\n",
+ __func__, len);
+
+ if (len <= 0)
+ return;
+
+ /* set payload length */
+ bcd2k->midi_out_buf[2] = len;
+ bcd2k->midi_out_urb->transfer_buffer_length = BUFSIZE;
+
+ bcd2000_dump_buffer(PREFIX "sending to device: ",
+ bcd2k->midi_out_buf, len+3);
+
+ /* send packet to the BCD2000 */
+ ret = usb_submit_urb(bcd2k->midi_out_urb, GFP_ATOMIC);
+ if (ret < 0)
+ dev_err(&bcd2k->dev->dev, PREFIX
+ "%s (%p): usb_submit_urb() failed, ret=%d, len=%d\n",
+ __func__, midi_out_substream, ret, len);
+ else
+ bcd2k->midi_out_active = 1;
+}
+
+static int bcd2000_midi_output_open(struct snd_rawmidi_substream *substream)
+{
+ return 0;
+}
+
+static int bcd2000_midi_output_close(struct snd_rawmidi_substream *substream)
+{
+ struct bcd2000 *bcd2k = substream->rmidi->private_data;
+
+ if (bcd2k->midi_out_active) {
+ usb_kill_urb(bcd2k->midi_out_urb);
+ bcd2k->midi_out_active = 0;
+ }
+
+ return 0;
+}
+
+/* (de)register midi substream from client */
+static void bcd2000_midi_output_trigger(struct snd_rawmidi_substream *substream,
+ int up)
+{
+ struct bcd2000 *bcd2k = substream->rmidi->private_data;
+
+ if (up) {
+ bcd2k->midi_out_substream = substream;
+ /* check if there is data userspace wants to send */
+ if (!bcd2k->midi_out_active)
+ bcd2000_midi_send(bcd2k);
+ } else {
+ bcd2k->midi_out_substream = NULL;
+ }
+}
+
+static void bcd2000_output_complete(struct urb *urb)
+{
+ struct bcd2000 *bcd2k = urb->context;
+
+ bcd2k->midi_out_active = 0;
+
+ if (urb->status)
+ dev_warn(&urb->dev->dev,
+ PREFIX "output urb->status: %d\n", urb->status);
+
+ if (urb->status == -ESHUTDOWN)
+ return;
+
+ /* check if there is more data userspace wants to send */
+ bcd2000_midi_send(bcd2k);
+}
+
+static void bcd2000_input_complete(struct urb *urb)
+{
+ int ret;
+ struct bcd2000 *bcd2k = urb->context;
+
+ if (urb->status)
+ dev_warn(&urb->dev->dev,
+ PREFIX "input urb->status: %i\n", urb->status);
+
+ if (!bcd2k || urb->status == -ESHUTDOWN)
+ return;
+
+ if (urb->actual_length > 0)
+ bcd2000_midi_handle_input(bcd2k, urb->transfer_buffer,
+ urb->actual_length);
+
+ /* return URB to device */
+ ret = usb_submit_urb(bcd2k->midi_in_urb, GFP_ATOMIC);
+ if (ret < 0)
+ dev_err(&bcd2k->dev->dev, PREFIX
+ "%s: usb_submit_urb() failed, ret=%d\n",
+ __func__, ret);
+}
+
+static struct snd_rawmidi_ops bcd2000_midi_output = {
+ .open = bcd2000_midi_output_open,
+ .close = bcd2000_midi_output_close,
+ .trigger = bcd2000_midi_output_trigger,
+};
+
+static struct snd_rawmidi_ops bcd2000_midi_input = {
+ .open = bcd2000_midi_input_open,
+ .close = bcd2000_midi_input_close,
+ .trigger = bcd2000_midi_input_trigger,
+};
+
+static void bcd2000_init_device(struct bcd2000 *bcd2k)
+{
+ int ret;
+
+ init_usb_anchor(&bcd2k->anchor);
+ usb_anchor_urb(bcd2k->midi_out_urb, &bcd2k->anchor);
+ usb_anchor_urb(bcd2k->midi_in_urb, &bcd2k->anchor);
+
+ /* copy init sequence into buffer */
+ memcpy(bcd2k->midi_out_buf, bcd2000_init_sequence, 52);
+ bcd2k->midi_out_urb->transfer_buffer_length = 52;
+
+ /* submit sequence */
+ ret = usb_submit_urb(bcd2k->midi_out_urb, GFP_KERNEL);
+ if (ret < 0)
+ dev_err(&bcd2k->dev->dev, PREFIX
+ "%s: usb_submit_urb() out failed, ret=%d: ",
+ __func__, ret);
+ else
+ bcd2k->midi_out_active = 1;
+
+ /* pass URB to device to enable button and controller events */
+ ret = usb_submit_urb(bcd2k->midi_in_urb, GFP_KERNEL);
+ if (ret < 0)
+ dev_err(&bcd2k->dev->dev, PREFIX
+ "%s: usb_submit_urb() in failed, ret=%d: ",
+ __func__, ret);
+
+ /* ensure initialization is finished */
+ usb_wait_anchor_empty_timeout(&bcd2k->anchor, 1000);
+}
+
+static int bcd2000_init_midi(struct bcd2000 *bcd2k)
+{
+ int ret;
+ struct snd_rawmidi *rmidi;
+
+ ret = snd_rawmidi_new(bcd2k->card, bcd2k->card->shortname, 0,
+ 1, /* output */
+ 1, /* input */
+ &rmidi);
+
+ if (ret < 0)
+ return ret;
+
+ strlcpy(rmidi->name, bcd2k->card->shortname, sizeof(rmidi->name));
+
+ rmidi->info_flags = SNDRV_RAWMIDI_INFO_DUPLEX;
+ rmidi->private_data = bcd2k;
+
+ rmidi->info_flags |= SNDRV_RAWMIDI_INFO_OUTPUT;
+ snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT,
+ &bcd2000_midi_output);
+
+ rmidi->info_flags |= SNDRV_RAWMIDI_INFO_INPUT;
+ snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT,
+ &bcd2000_midi_input);
+
+ bcd2k->rmidi = rmidi;
+
+ bcd2k->midi_in_urb = usb_alloc_urb(0, GFP_KERNEL);
+ bcd2k->midi_out_urb = usb_alloc_urb(0, GFP_KERNEL);
+
+ if (!bcd2k->midi_in_urb || !bcd2k->midi_out_urb) {
+ dev_err(&bcd2k->dev->dev, PREFIX "usb_alloc_urb failed\n");
+ return -ENOMEM;
+ }
+
+ usb_fill_int_urb(bcd2k->midi_in_urb, bcd2k->dev,
+ usb_rcvintpipe(bcd2k->dev, 0x81),
+ bcd2k->midi_in_buf, BUFSIZE,
+ bcd2000_input_complete, bcd2k, 1);
+
+ usb_fill_int_urb(bcd2k->midi_out_urb, bcd2k->dev,
+ usb_sndintpipe(bcd2k->dev, 0x1),
+ bcd2k->midi_out_buf, BUFSIZE,
+ bcd2000_output_complete, bcd2k, 1);
+
+ bcd2000_init_device(bcd2k);
+
+ return 0;
+}
+
+static void bcd2000_free_usb_related_resources(struct bcd2000 *bcd2k,
+ struct usb_interface *interface)
+{
+ /* usb_kill_urb not necessary, urb is aborted automatically */
+
+ usb_free_urb(bcd2k->midi_out_urb);
+ usb_free_urb(bcd2k->midi_in_urb);
+
+ if (bcd2k->intf) {
+ usb_set_intfdata(bcd2k->intf, NULL);
+ bcd2k->intf = NULL;
+ }
+}
+
+static int bcd2000_probe(struct usb_interface *interface,
+ const struct usb_device_id *usb_id)
+{
+ struct snd_card *card;
+ struct bcd2000 *bcd2k;
+ unsigned int card_index;
+ char usb_path[32];
+ int err;
+
+ mutex_lock(&devices_mutex);
+
+ for (card_index = 0; card_index < SNDRV_CARDS; ++card_index)
+ if (!test_bit(card_index, devices_used))
+ break;
+
+ if (card_index >= SNDRV_CARDS) {
+ mutex_unlock(&devices_mutex);
+ return -ENOENT;
+ }
+
+ err = snd_card_new(&interface->dev, index[card_index], id[card_index],
+ THIS_MODULE, sizeof(*bcd2k), &card);
+ if (err < 0) {
+ mutex_unlock(&devices_mutex);
+ return err;
+ }
+
+ bcd2k = card->private_data;
+ bcd2k->dev = interface_to_usbdev(interface);
+ bcd2k->card = card;
+ bcd2k->card_index = card_index;
+ bcd2k->intf = interface;
+
+ snd_card_set_dev(card, &interface->dev);
+
+ strncpy(card->driver, "snd-bcd2000", sizeof(card->driver));
+ strncpy(card->shortname, "BCD2000", sizeof(card->shortname));
+ usb_make_path(bcd2k->dev, usb_path, sizeof(usb_path));
+ snprintf(bcd2k->card->longname, sizeof(bcd2k->card->longname),
+ "Behringer BCD2000 at %s",
+ usb_path);
+
+ err = bcd2000_init_midi(bcd2k);
+ if (err < 0)
+ goto probe_error;
+
+ err = snd_card_register(card);
+ if (err < 0)
+ goto probe_error;
+
+ usb_set_intfdata(interface, bcd2k);
+ set_bit(card_index, devices_used);
+
+ mutex_unlock(&devices_mutex);
+ return 0;
+
+probe_error:
+ dev_info(&bcd2k->dev->dev, PREFIX "error during probing");
+ bcd2000_free_usb_related_resources(bcd2k, interface);
+ snd_card_free(card);
+ mutex_unlock(&devices_mutex);
+ return err;
+}
+
+static void bcd2000_disconnect(struct usb_interface *interface)
+{
+ struct bcd2000 *bcd2k = usb_get_intfdata(interface);
+
+ if (!bcd2k)
+ return;
+
+ mutex_lock(&devices_mutex);
+
+ /* make sure that userspace cannot create new requests */
+ snd_card_disconnect(bcd2k->card);
+
+ bcd2000_free_usb_related_resources(bcd2k, interface);
+
+ clear_bit(bcd2k->card_index, devices_used);
+
+ snd_card_free_when_closed(bcd2k->card);
+
+ mutex_unlock(&devices_mutex);
+}
+
+static struct usb_driver bcd2000_driver = {
+ .name = "snd-bcd2000",
+ .probe = bcd2000_probe,
+ .disconnect = bcd2000_disconnect,
+ .id_table = id_table,
+};
+
+module_usb_driver(bcd2000_driver);
+
+MODULE_DEVICE_TABLE(usb, id_table);
+MODULE_AUTHOR("Mario Kicherer, dev@kicherer.org");
+MODULE_DESCRIPTION("Behringer BCD2000 driver");
+MODULE_LICENSE("GPL");
diff --git a/sound/usb/caiaq/Makefile b/sound/usb/caiaq/Makefile
new file mode 100644
index 000000000..388999653
--- /dev/null
+++ b/sound/usb/caiaq/Makefile
@@ -0,0 +1,4 @@
+snd-usb-caiaq-y := device.o audio.o midi.o control.o
+snd-usb-caiaq-$(CONFIG_SND_USB_CAIAQ_INPUT) += input.o
+
+obj-$(CONFIG_SND_USB_CAIAQ) += snd-usb-caiaq.o
diff --git a/sound/usb/caiaq/audio.c b/sound/usb/caiaq/audio.c
new file mode 100644
index 000000000..327f8642c
--- /dev/null
+++ b/sound/usb/caiaq/audio.c
@@ -0,0 +1,906 @@
+/*
+ * Copyright (c) 2006-2008 Daniel Mack, Karsten Wiese
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*/
+
+#include <linux/device.h>
+#include <linux/spinlock.h>
+#include <linux/slab.h>
+#include <linux/init.h>
+#include <linux/usb.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+
+#include "device.h"
+#include "audio.h"
+
+#define N_URBS 32
+#define CLOCK_DRIFT_TOLERANCE 5
+#define FRAMES_PER_URB 8
+#define BYTES_PER_FRAME 512
+#define CHANNELS_PER_STREAM 2
+#define BYTES_PER_SAMPLE 3
+#define BYTES_PER_SAMPLE_USB 4
+#define MAX_BUFFER_SIZE (128*1024)
+#define MAX_ENDPOINT_SIZE 512
+
+#define ENDPOINT_CAPTURE 2
+#define ENDPOINT_PLAYBACK 6
+
+#define MAKE_CHECKBYTE(cdev,stream,i) \
+ (stream << 1) | (~(i / (cdev->n_streams * BYTES_PER_SAMPLE_USB)) & 1)
+
+static struct snd_pcm_hardware snd_usb_caiaq_pcm_hardware = {
+ .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_BLOCK_TRANSFER),
+ .formats = SNDRV_PCM_FMTBIT_S24_3BE,
+ .rates = (SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 |
+ SNDRV_PCM_RATE_96000),
+ .rate_min = 44100,
+ .rate_max = 0, /* will overwrite later */
+ .channels_min = CHANNELS_PER_STREAM,
+ .channels_max = CHANNELS_PER_STREAM,
+ .buffer_bytes_max = MAX_BUFFER_SIZE,
+ .period_bytes_min = 128,
+ .period_bytes_max = MAX_BUFFER_SIZE,
+ .periods_min = 1,
+ .periods_max = 1024,
+};
+
+static void
+activate_substream(struct snd_usb_caiaqdev *cdev,
+ struct snd_pcm_substream *sub)
+{
+ spin_lock(&cdev->spinlock);
+
+ if (sub->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ cdev->sub_playback[sub->number] = sub;
+ else
+ cdev->sub_capture[sub->number] = sub;
+
+ spin_unlock(&cdev->spinlock);
+}
+
+static void
+deactivate_substream(struct snd_usb_caiaqdev *cdev,
+ struct snd_pcm_substream *sub)
+{
+ unsigned long flags;
+ spin_lock_irqsave(&cdev->spinlock, flags);
+
+ if (sub->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ cdev->sub_playback[sub->number] = NULL;
+ else
+ cdev->sub_capture[sub->number] = NULL;
+
+ spin_unlock_irqrestore(&cdev->spinlock, flags);
+}
+
+static int
+all_substreams_zero(struct snd_pcm_substream **subs)
+{
+ int i;
+ for (i = 0; i < MAX_STREAMS; i++)
+ if (subs[i] != NULL)
+ return 0;
+ return 1;
+}
+
+static int stream_start(struct snd_usb_caiaqdev *cdev)
+{
+ int i, ret;
+ struct device *dev = caiaqdev_to_dev(cdev);
+
+ dev_dbg(dev, "%s(%p)\n", __func__, cdev);
+
+ if (cdev->streaming)
+ return -EINVAL;
+
+ memset(cdev->sub_playback, 0, sizeof(cdev->sub_playback));
+ memset(cdev->sub_capture, 0, sizeof(cdev->sub_capture));
+ cdev->input_panic = 0;
+ cdev->output_panic = 0;
+ cdev->first_packet = 4;
+ cdev->streaming = 1;
+ cdev->warned = 0;
+
+ for (i = 0; i < N_URBS; i++) {
+ ret = usb_submit_urb(cdev->data_urbs_in[i], GFP_ATOMIC);
+ if (ret) {
+ dev_err(dev, "unable to trigger read #%d! (ret %d)\n",
+ i, ret);
+ cdev->streaming = 0;
+ return -EPIPE;
+ }
+ }
+
+ return 0;
+}
+
+static void stream_stop(struct snd_usb_caiaqdev *cdev)
+{
+ int i;
+ struct device *dev = caiaqdev_to_dev(cdev);
+
+ dev_dbg(dev, "%s(%p)\n", __func__, cdev);
+ if (!cdev->streaming)
+ return;
+
+ cdev->streaming = 0;
+
+ for (i = 0; i < N_URBS; i++) {
+ usb_kill_urb(cdev->data_urbs_in[i]);
+
+ if (test_bit(i, &cdev->outurb_active_mask))
+ usb_kill_urb(cdev->data_urbs_out[i]);
+ }
+
+ cdev->outurb_active_mask = 0;
+}
+
+static int snd_usb_caiaq_substream_open(struct snd_pcm_substream *substream)
+{
+ struct snd_usb_caiaqdev *cdev = snd_pcm_substream_chip(substream);
+ struct device *dev = caiaqdev_to_dev(cdev);
+
+ dev_dbg(dev, "%s(%p)\n", __func__, substream);
+ substream->runtime->hw = cdev->pcm_info;
+ snd_pcm_limit_hw_rates(substream->runtime);
+
+ return 0;
+}
+
+static int snd_usb_caiaq_substream_close(struct snd_pcm_substream *substream)
+{
+ struct snd_usb_caiaqdev *cdev = snd_pcm_substream_chip(substream);
+ struct device *dev = caiaqdev_to_dev(cdev);
+
+ dev_dbg(dev, "%s(%p)\n", __func__, substream);
+ if (all_substreams_zero(cdev->sub_playback) &&
+ all_substreams_zero(cdev->sub_capture)) {
+ /* when the last client has stopped streaming,
+ * all sample rates are allowed again */
+ stream_stop(cdev);
+ cdev->pcm_info.rates = cdev->samplerates;
+ }
+
+ return 0;
+}
+
+static int snd_usb_caiaq_pcm_hw_params(struct snd_pcm_substream *sub,
+ struct snd_pcm_hw_params *hw_params)
+{
+ return snd_pcm_lib_alloc_vmalloc_buffer(sub,
+ params_buffer_bytes(hw_params));
+}
+
+static int snd_usb_caiaq_pcm_hw_free(struct snd_pcm_substream *sub)
+{
+ struct snd_usb_caiaqdev *cdev = snd_pcm_substream_chip(sub);
+ deactivate_substream(cdev, sub);
+ return snd_pcm_lib_free_vmalloc_buffer(sub);
+}
+
+/* this should probably go upstream */
+#if SNDRV_PCM_RATE_5512 != 1 << 0 || SNDRV_PCM_RATE_192000 != 1 << 12
+#error "Change this table"
+#endif
+
+static unsigned int rates[] = { 5512, 8000, 11025, 16000, 22050, 32000, 44100,
+ 48000, 64000, 88200, 96000, 176400, 192000 };
+
+static int snd_usb_caiaq_pcm_prepare(struct snd_pcm_substream *substream)
+{
+ int bytes_per_sample, bpp, ret, i;
+ int index = substream->number;
+ struct snd_usb_caiaqdev *cdev = snd_pcm_substream_chip(substream);
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct device *dev = caiaqdev_to_dev(cdev);
+
+ dev_dbg(dev, "%s(%p)\n", __func__, substream);
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ int out_pos;
+
+ switch (cdev->spec.data_alignment) {
+ case 0:
+ case 2:
+ out_pos = BYTES_PER_SAMPLE + 1;
+ break;
+ case 3:
+ default:
+ out_pos = 0;
+ break;
+ }
+
+ cdev->period_out_count[index] = out_pos;
+ cdev->audio_out_buf_pos[index] = out_pos;
+ } else {
+ int in_pos;
+
+ switch (cdev->spec.data_alignment) {
+ case 0:
+ in_pos = BYTES_PER_SAMPLE + 2;
+ break;
+ case 2:
+ in_pos = BYTES_PER_SAMPLE;
+ break;
+ case 3:
+ default:
+ in_pos = 0;
+ break;
+ }
+
+ cdev->period_in_count[index] = in_pos;
+ cdev->audio_in_buf_pos[index] = in_pos;
+ }
+
+ if (cdev->streaming)
+ return 0;
+
+ /* the first client that opens a stream defines the sample rate
+ * setting for all subsequent calls, until the last client closed. */
+ for (i=0; i < ARRAY_SIZE(rates); i++)
+ if (runtime->rate == rates[i])
+ cdev->pcm_info.rates = 1 << i;
+
+ snd_pcm_limit_hw_rates(runtime);
+
+ bytes_per_sample = BYTES_PER_SAMPLE;
+ if (cdev->spec.data_alignment >= 2)
+ bytes_per_sample++;
+
+ bpp = ((runtime->rate / 8000) + CLOCK_DRIFT_TOLERANCE)
+ * bytes_per_sample * CHANNELS_PER_STREAM * cdev->n_streams;
+
+ if (bpp > MAX_ENDPOINT_SIZE)
+ bpp = MAX_ENDPOINT_SIZE;
+
+ ret = snd_usb_caiaq_set_audio_params(cdev, runtime->rate,
+ runtime->sample_bits, bpp);
+ if (ret)
+ return ret;
+
+ ret = stream_start(cdev);
+ if (ret)
+ return ret;
+
+ cdev->output_running = 0;
+ wait_event_timeout(cdev->prepare_wait_queue, cdev->output_running, HZ);
+ if (!cdev->output_running) {
+ stream_stop(cdev);
+ return -EPIPE;
+ }
+
+ return 0;
+}
+
+static int snd_usb_caiaq_pcm_trigger(struct snd_pcm_substream *sub, int cmd)
+{
+ struct snd_usb_caiaqdev *cdev = snd_pcm_substream_chip(sub);
+ struct device *dev = caiaqdev_to_dev(cdev);
+
+ dev_dbg(dev, "%s(%p) cmd %d\n", __func__, sub, cmd);
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ activate_substream(cdev, sub);
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ deactivate_substream(cdev, sub);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static snd_pcm_uframes_t
+snd_usb_caiaq_pcm_pointer(struct snd_pcm_substream *sub)
+{
+ int index = sub->number;
+ struct snd_usb_caiaqdev *cdev = snd_pcm_substream_chip(sub);
+ snd_pcm_uframes_t ptr;
+
+ spin_lock(&cdev->spinlock);
+
+ if (cdev->input_panic || cdev->output_panic) {
+ ptr = SNDRV_PCM_POS_XRUN;
+ goto unlock;
+ }
+
+ if (sub->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ ptr = bytes_to_frames(sub->runtime,
+ cdev->audio_out_buf_pos[index]);
+ else
+ ptr = bytes_to_frames(sub->runtime,
+ cdev->audio_in_buf_pos[index]);
+
+unlock:
+ spin_unlock(&cdev->spinlock);
+ return ptr;
+}
+
+/* operators for both playback and capture */
+static struct snd_pcm_ops snd_usb_caiaq_ops = {
+ .open = snd_usb_caiaq_substream_open,
+ .close = snd_usb_caiaq_substream_close,
+ .ioctl = snd_pcm_lib_ioctl,
+ .hw_params = snd_usb_caiaq_pcm_hw_params,
+ .hw_free = snd_usb_caiaq_pcm_hw_free,
+ .prepare = snd_usb_caiaq_pcm_prepare,
+ .trigger = snd_usb_caiaq_pcm_trigger,
+ .pointer = snd_usb_caiaq_pcm_pointer,
+ .page = snd_pcm_lib_get_vmalloc_page,
+ .mmap = snd_pcm_lib_mmap_vmalloc,
+};
+
+static void check_for_elapsed_periods(struct snd_usb_caiaqdev *cdev,
+ struct snd_pcm_substream **subs)
+{
+ int stream, pb, *cnt;
+ struct snd_pcm_substream *sub;
+
+ for (stream = 0; stream < cdev->n_streams; stream++) {
+ sub = subs[stream];
+ if (!sub)
+ continue;
+
+ pb = snd_pcm_lib_period_bytes(sub);
+ cnt = (sub->stream == SNDRV_PCM_STREAM_PLAYBACK) ?
+ &cdev->period_out_count[stream] :
+ &cdev->period_in_count[stream];
+
+ if (*cnt >= pb) {
+ snd_pcm_period_elapsed(sub);
+ *cnt %= pb;
+ }
+ }
+}
+
+static void read_in_urb_mode0(struct snd_usb_caiaqdev *cdev,
+ const struct urb *urb,
+ const struct usb_iso_packet_descriptor *iso)
+{
+ unsigned char *usb_buf = urb->transfer_buffer + iso->offset;
+ struct snd_pcm_substream *sub;
+ int stream, i;
+
+ if (all_substreams_zero(cdev->sub_capture))
+ return;
+
+ for (i = 0; i < iso->actual_length;) {
+ for (stream = 0; stream < cdev->n_streams; stream++, i++) {
+ sub = cdev->sub_capture[stream];
+ if (sub) {
+ struct snd_pcm_runtime *rt = sub->runtime;
+ char *audio_buf = rt->dma_area;
+ int sz = frames_to_bytes(rt, rt->buffer_size);
+ audio_buf[cdev->audio_in_buf_pos[stream]++]
+ = usb_buf[i];
+ cdev->period_in_count[stream]++;
+ if (cdev->audio_in_buf_pos[stream] == sz)
+ cdev->audio_in_buf_pos[stream] = 0;
+ }
+ }
+ }
+}
+
+static void read_in_urb_mode2(struct snd_usb_caiaqdev *cdev,
+ const struct urb *urb,
+ const struct usb_iso_packet_descriptor *iso)
+{
+ unsigned char *usb_buf = urb->transfer_buffer + iso->offset;
+ unsigned char check_byte;
+ struct snd_pcm_substream *sub;
+ int stream, i;
+
+ for (i = 0; i < iso->actual_length;) {
+ if (i % (cdev->n_streams * BYTES_PER_SAMPLE_USB) == 0) {
+ for (stream = 0;
+ stream < cdev->n_streams;
+ stream++, i++) {
+ if (cdev->first_packet)
+ continue;
+
+ check_byte = MAKE_CHECKBYTE(cdev, stream, i);
+
+ if ((usb_buf[i] & 0x3f) != check_byte)
+ cdev->input_panic = 1;
+
+ if (usb_buf[i] & 0x80)
+ cdev->output_panic = 1;
+ }
+ }
+ cdev->first_packet = 0;
+
+ for (stream = 0; stream < cdev->n_streams; stream++, i++) {
+ sub = cdev->sub_capture[stream];
+ if (cdev->input_panic)
+ usb_buf[i] = 0;
+
+ if (sub) {
+ struct snd_pcm_runtime *rt = sub->runtime;
+ char *audio_buf = rt->dma_area;
+ int sz = frames_to_bytes(rt, rt->buffer_size);
+ audio_buf[cdev->audio_in_buf_pos[stream]++] =
+ usb_buf[i];
+ cdev->period_in_count[stream]++;
+ if (cdev->audio_in_buf_pos[stream] == sz)
+ cdev->audio_in_buf_pos[stream] = 0;
+ }
+ }
+ }
+}
+
+static void read_in_urb_mode3(struct snd_usb_caiaqdev *cdev,
+ const struct urb *urb,
+ const struct usb_iso_packet_descriptor *iso)
+{
+ unsigned char *usb_buf = urb->transfer_buffer + iso->offset;
+ struct device *dev = caiaqdev_to_dev(cdev);
+ int stream, i;
+
+ /* paranoia check */
+ if (iso->actual_length % (BYTES_PER_SAMPLE_USB * CHANNELS_PER_STREAM))
+ return;
+
+ for (i = 0; i < iso->actual_length;) {
+ for (stream = 0; stream < cdev->n_streams; stream++) {
+ struct snd_pcm_substream *sub = cdev->sub_capture[stream];
+ char *audio_buf = NULL;
+ int c, n, sz = 0;
+
+ if (sub && !cdev->input_panic) {
+ struct snd_pcm_runtime *rt = sub->runtime;
+ audio_buf = rt->dma_area;
+ sz = frames_to_bytes(rt, rt->buffer_size);
+ }
+
+ for (c = 0; c < CHANNELS_PER_STREAM; c++) {
+ /* 3 audio data bytes, followed by 1 check byte */
+ if (audio_buf) {
+ for (n = 0; n < BYTES_PER_SAMPLE; n++) {
+ audio_buf[cdev->audio_in_buf_pos[stream]++] = usb_buf[i+n];
+
+ if (cdev->audio_in_buf_pos[stream] == sz)
+ cdev->audio_in_buf_pos[stream] = 0;
+ }
+
+ cdev->period_in_count[stream] += BYTES_PER_SAMPLE;
+ }
+
+ i += BYTES_PER_SAMPLE;
+
+ if (usb_buf[i] != ((stream << 1) | c) &&
+ !cdev->first_packet) {
+ if (!cdev->input_panic)
+ dev_warn(dev, " EXPECTED: %02x got %02x, c %d, stream %d, i %d\n",
+ ((stream << 1) | c), usb_buf[i], c, stream, i);
+ cdev->input_panic = 1;
+ }
+
+ i++;
+ }
+ }
+ }
+
+ if (cdev->first_packet > 0)
+ cdev->first_packet--;
+}
+
+static void read_in_urb(struct snd_usb_caiaqdev *cdev,
+ const struct urb *urb,
+ const struct usb_iso_packet_descriptor *iso)
+{
+ struct device *dev = caiaqdev_to_dev(cdev);
+
+ if (!cdev->streaming)
+ return;
+
+ if (iso->actual_length < cdev->bpp)
+ return;
+
+ switch (cdev->spec.data_alignment) {
+ case 0:
+ read_in_urb_mode0(cdev, urb, iso);
+ break;
+ case 2:
+ read_in_urb_mode2(cdev, urb, iso);
+ break;
+ case 3:
+ read_in_urb_mode3(cdev, urb, iso);
+ break;
+ }
+
+ if ((cdev->input_panic || cdev->output_panic) && !cdev->warned) {
+ dev_warn(dev, "streaming error detected %s %s\n",
+ cdev->input_panic ? "(input)" : "",
+ cdev->output_panic ? "(output)" : "");
+ cdev->warned = 1;
+ }
+}
+
+static void fill_out_urb_mode_0(struct snd_usb_caiaqdev *cdev,
+ struct urb *urb,
+ const struct usb_iso_packet_descriptor *iso)
+{
+ unsigned char *usb_buf = urb->transfer_buffer + iso->offset;
+ struct snd_pcm_substream *sub;
+ int stream, i;
+
+ for (i = 0; i < iso->length;) {
+ for (stream = 0; stream < cdev->n_streams; stream++, i++) {
+ sub = cdev->sub_playback[stream];
+ if (sub) {
+ struct snd_pcm_runtime *rt = sub->runtime;
+ char *audio_buf = rt->dma_area;
+ int sz = frames_to_bytes(rt, rt->buffer_size);
+ usb_buf[i] =
+ audio_buf[cdev->audio_out_buf_pos[stream]];
+ cdev->period_out_count[stream]++;
+ cdev->audio_out_buf_pos[stream]++;
+ if (cdev->audio_out_buf_pos[stream] == sz)
+ cdev->audio_out_buf_pos[stream] = 0;
+ } else
+ usb_buf[i] = 0;
+ }
+
+ /* fill in the check bytes */
+ if (cdev->spec.data_alignment == 2 &&
+ i % (cdev->n_streams * BYTES_PER_SAMPLE_USB) ==
+ (cdev->n_streams * CHANNELS_PER_STREAM))
+ for (stream = 0; stream < cdev->n_streams; stream++, i++)
+ usb_buf[i] = MAKE_CHECKBYTE(cdev, stream, i);
+ }
+}
+
+static void fill_out_urb_mode_3(struct snd_usb_caiaqdev *cdev,
+ struct urb *urb,
+ const struct usb_iso_packet_descriptor *iso)
+{
+ unsigned char *usb_buf = urb->transfer_buffer + iso->offset;
+ int stream, i;
+
+ for (i = 0; i < iso->length;) {
+ for (stream = 0; stream < cdev->n_streams; stream++) {
+ struct snd_pcm_substream *sub = cdev->sub_playback[stream];
+ char *audio_buf = NULL;
+ int c, n, sz = 0;
+
+ if (sub) {
+ struct snd_pcm_runtime *rt = sub->runtime;
+ audio_buf = rt->dma_area;
+ sz = frames_to_bytes(rt, rt->buffer_size);
+ }
+
+ for (c = 0; c < CHANNELS_PER_STREAM; c++) {
+ for (n = 0; n < BYTES_PER_SAMPLE; n++) {
+ if (audio_buf) {
+ usb_buf[i+n] = audio_buf[cdev->audio_out_buf_pos[stream]++];
+
+ if (cdev->audio_out_buf_pos[stream] == sz)
+ cdev->audio_out_buf_pos[stream] = 0;
+ } else {
+ usb_buf[i+n] = 0;
+ }
+ }
+
+ if (audio_buf)
+ cdev->period_out_count[stream] += BYTES_PER_SAMPLE;
+
+ i += BYTES_PER_SAMPLE;
+
+ /* fill in the check byte pattern */
+ usb_buf[i++] = (stream << 1) | c;
+ }
+ }
+ }
+}
+
+static inline void fill_out_urb(struct snd_usb_caiaqdev *cdev,
+ struct urb *urb,
+ const struct usb_iso_packet_descriptor *iso)
+{
+ switch (cdev->spec.data_alignment) {
+ case 0:
+ case 2:
+ fill_out_urb_mode_0(cdev, urb, iso);
+ break;
+ case 3:
+ fill_out_urb_mode_3(cdev, urb, iso);
+ break;
+ }
+}
+
+static void read_completed(struct urb *urb)
+{
+ struct snd_usb_caiaq_cb_info *info = urb->context;
+ struct snd_usb_caiaqdev *cdev;
+ struct device *dev;
+ struct urb *out = NULL;
+ int i, frame, len, send_it = 0, outframe = 0;
+ size_t offset = 0;
+
+ if (urb->status || !info)
+ return;
+
+ cdev = info->cdev;
+ dev = caiaqdev_to_dev(cdev);
+
+ if (!cdev->streaming)
+ return;
+
+ /* find an unused output urb that is unused */
+ for (i = 0; i < N_URBS; i++)
+ if (test_and_set_bit(i, &cdev->outurb_active_mask) == 0) {
+ out = cdev->data_urbs_out[i];
+ break;
+ }
+
+ if (!out) {
+ dev_err(dev, "Unable to find an output urb to use\n");
+ goto requeue;
+ }
+
+ /* read the recently received packet and send back one which has
+ * the same layout */
+ for (frame = 0; frame < FRAMES_PER_URB; frame++) {
+ if (urb->iso_frame_desc[frame].status)
+ continue;
+
+ len = urb->iso_frame_desc[outframe].actual_length;
+ out->iso_frame_desc[outframe].length = len;
+ out->iso_frame_desc[outframe].actual_length = 0;
+ out->iso_frame_desc[outframe].offset = offset;
+ offset += len;
+
+ if (len > 0) {
+ spin_lock(&cdev->spinlock);
+ fill_out_urb(cdev, out, &out->iso_frame_desc[outframe]);
+ read_in_urb(cdev, urb, &urb->iso_frame_desc[frame]);
+ spin_unlock(&cdev->spinlock);
+ check_for_elapsed_periods(cdev, cdev->sub_playback);
+ check_for_elapsed_periods(cdev, cdev->sub_capture);
+ send_it = 1;
+ }
+
+ outframe++;
+ }
+
+ if (send_it) {
+ out->number_of_packets = outframe;
+ usb_submit_urb(out, GFP_ATOMIC);
+ } else {
+ struct snd_usb_caiaq_cb_info *oinfo = out->context;
+ clear_bit(oinfo->index, &cdev->outurb_active_mask);
+ }
+
+requeue:
+ /* re-submit inbound urb */
+ for (frame = 0; frame < FRAMES_PER_URB; frame++) {
+ urb->iso_frame_desc[frame].offset = BYTES_PER_FRAME * frame;
+ urb->iso_frame_desc[frame].length = BYTES_PER_FRAME;
+ urb->iso_frame_desc[frame].actual_length = 0;
+ }
+
+ urb->number_of_packets = FRAMES_PER_URB;
+ usb_submit_urb(urb, GFP_ATOMIC);
+}
+
+static void write_completed(struct urb *urb)
+{
+ struct snd_usb_caiaq_cb_info *info = urb->context;
+ struct snd_usb_caiaqdev *cdev = info->cdev;
+
+ if (!cdev->output_running) {
+ cdev->output_running = 1;
+ wake_up(&cdev->prepare_wait_queue);
+ }
+
+ clear_bit(info->index, &cdev->outurb_active_mask);
+}
+
+static struct urb **alloc_urbs(struct snd_usb_caiaqdev *cdev, int dir, int *ret)
+{
+ int i, frame;
+ struct urb **urbs;
+ struct usb_device *usb_dev = cdev->chip.dev;
+ struct device *dev = caiaqdev_to_dev(cdev);
+ unsigned int pipe;
+
+ pipe = (dir == SNDRV_PCM_STREAM_PLAYBACK) ?
+ usb_sndisocpipe(usb_dev, ENDPOINT_PLAYBACK) :
+ usb_rcvisocpipe(usb_dev, ENDPOINT_CAPTURE);
+
+ urbs = kmalloc(N_URBS * sizeof(*urbs), GFP_KERNEL);
+ if (!urbs) {
+ dev_err(dev, "unable to kmalloc() urbs, OOM!?\n");
+ *ret = -ENOMEM;
+ return NULL;
+ }
+
+ for (i = 0; i < N_URBS; i++) {
+ urbs[i] = usb_alloc_urb(FRAMES_PER_URB, GFP_KERNEL);
+ if (!urbs[i]) {
+ dev_err(dev, "unable to usb_alloc_urb(), OOM!?\n");
+ *ret = -ENOMEM;
+ return urbs;
+ }
+
+ urbs[i]->transfer_buffer =
+ kmalloc(FRAMES_PER_URB * BYTES_PER_FRAME, GFP_KERNEL);
+ if (!urbs[i]->transfer_buffer) {
+ dev_err(dev, "unable to kmalloc() transfer buffer, OOM!?\n");
+ *ret = -ENOMEM;
+ return urbs;
+ }
+
+ for (frame = 0; frame < FRAMES_PER_URB; frame++) {
+ struct usb_iso_packet_descriptor *iso =
+ &urbs[i]->iso_frame_desc[frame];
+
+ iso->offset = BYTES_PER_FRAME * frame;
+ iso->length = BYTES_PER_FRAME;
+ }
+
+ urbs[i]->dev = usb_dev;
+ urbs[i]->pipe = pipe;
+ urbs[i]->transfer_buffer_length = FRAMES_PER_URB
+ * BYTES_PER_FRAME;
+ urbs[i]->context = &cdev->data_cb_info[i];
+ urbs[i]->interval = 1;
+ urbs[i]->number_of_packets = FRAMES_PER_URB;
+ urbs[i]->complete = (dir == SNDRV_PCM_STREAM_CAPTURE) ?
+ read_completed : write_completed;
+ }
+
+ *ret = 0;
+ return urbs;
+}
+
+static void free_urbs(struct urb **urbs)
+{
+ int i;
+
+ if (!urbs)
+ return;
+
+ for (i = 0; i < N_URBS; i++) {
+ if (!urbs[i])
+ continue;
+
+ usb_kill_urb(urbs[i]);
+ kfree(urbs[i]->transfer_buffer);
+ usb_free_urb(urbs[i]);
+ }
+
+ kfree(urbs);
+}
+
+int snd_usb_caiaq_audio_init(struct snd_usb_caiaqdev *cdev)
+{
+ int i, ret;
+ struct device *dev = caiaqdev_to_dev(cdev);
+
+ cdev->n_audio_in = max(cdev->spec.num_analog_audio_in,
+ cdev->spec.num_digital_audio_in) /
+ CHANNELS_PER_STREAM;
+ cdev->n_audio_out = max(cdev->spec.num_analog_audio_out,
+ cdev->spec.num_digital_audio_out) /
+ CHANNELS_PER_STREAM;
+ cdev->n_streams = max(cdev->n_audio_in, cdev->n_audio_out);
+
+ dev_dbg(dev, "cdev->n_audio_in = %d\n", cdev->n_audio_in);
+ dev_dbg(dev, "cdev->n_audio_out = %d\n", cdev->n_audio_out);
+ dev_dbg(dev, "cdev->n_streams = %d\n", cdev->n_streams);
+
+ if (cdev->n_streams > MAX_STREAMS) {
+ dev_err(dev, "unable to initialize device, too many streams.\n");
+ return -EINVAL;
+ }
+
+ if (cdev->n_streams < 1) {
+ dev_err(dev, "bogus number of streams: %d\n", cdev->n_streams);
+ return -EINVAL;
+ }
+
+ ret = snd_pcm_new(cdev->chip.card, cdev->product_name, 0,
+ cdev->n_audio_out, cdev->n_audio_in, &cdev->pcm);
+
+ if (ret < 0) {
+ dev_err(dev, "snd_pcm_new() returned %d\n", ret);
+ return ret;
+ }
+
+ cdev->pcm->private_data = cdev;
+ strlcpy(cdev->pcm->name, cdev->product_name, sizeof(cdev->pcm->name));
+
+ memset(cdev->sub_playback, 0, sizeof(cdev->sub_playback));
+ memset(cdev->sub_capture, 0, sizeof(cdev->sub_capture));
+
+ memcpy(&cdev->pcm_info, &snd_usb_caiaq_pcm_hardware,
+ sizeof(snd_usb_caiaq_pcm_hardware));
+
+ /* setup samplerates */
+ cdev->samplerates = cdev->pcm_info.rates;
+ switch (cdev->chip.usb_id) {
+ case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_AK1):
+ case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_RIGKONTROL3):
+ case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_SESSIONIO):
+ case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_GUITARRIGMOBILE):
+ cdev->samplerates |= SNDRV_PCM_RATE_192000;
+ /* fall thru */
+ case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_AUDIO2DJ):
+ case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_AUDIO4DJ):
+ case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_AUDIO8DJ):
+ case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_TRAKTORAUDIO2):
+ cdev->samplerates |= SNDRV_PCM_RATE_88200;
+ break;
+ }
+
+ snd_pcm_set_ops(cdev->pcm, SNDRV_PCM_STREAM_PLAYBACK,
+ &snd_usb_caiaq_ops);
+ snd_pcm_set_ops(cdev->pcm, SNDRV_PCM_STREAM_CAPTURE,
+ &snd_usb_caiaq_ops);
+
+ cdev->data_cb_info =
+ kmalloc(sizeof(struct snd_usb_caiaq_cb_info) * N_URBS,
+ GFP_KERNEL);
+
+ if (!cdev->data_cb_info)
+ return -ENOMEM;
+
+ cdev->outurb_active_mask = 0;
+ BUILD_BUG_ON(N_URBS > (sizeof(cdev->outurb_active_mask) * 8));
+
+ for (i = 0; i < N_URBS; i++) {
+ cdev->data_cb_info[i].cdev = cdev;
+ cdev->data_cb_info[i].index = i;
+ }
+
+ cdev->data_urbs_in = alloc_urbs(cdev, SNDRV_PCM_STREAM_CAPTURE, &ret);
+ if (ret < 0) {
+ kfree(cdev->data_cb_info);
+ free_urbs(cdev->data_urbs_in);
+ return ret;
+ }
+
+ cdev->data_urbs_out = alloc_urbs(cdev, SNDRV_PCM_STREAM_PLAYBACK, &ret);
+ if (ret < 0) {
+ kfree(cdev->data_cb_info);
+ free_urbs(cdev->data_urbs_in);
+ free_urbs(cdev->data_urbs_out);
+ return ret;
+ }
+
+ return 0;
+}
+
+void snd_usb_caiaq_audio_free(struct snd_usb_caiaqdev *cdev)
+{
+ struct device *dev = caiaqdev_to_dev(cdev);
+
+ dev_dbg(dev, "%s(%p)\n", __func__, cdev);
+ stream_stop(cdev);
+ free_urbs(cdev->data_urbs_in);
+ free_urbs(cdev->data_urbs_out);
+ kfree(cdev->data_cb_info);
+}
+
diff --git a/sound/usb/caiaq/audio.h b/sound/usb/caiaq/audio.h
new file mode 100644
index 000000000..bdf155300
--- /dev/null
+++ b/sound/usb/caiaq/audio.h
@@ -0,0 +1,7 @@
+#ifndef CAIAQ_AUDIO_H
+#define CAIAQ_AUDIO_H
+
+int snd_usb_caiaq_audio_init(struct snd_usb_caiaqdev *cdev);
+void snd_usb_caiaq_audio_free(struct snd_usb_caiaqdev *cdev);
+
+#endif /* CAIAQ_AUDIO_H */
diff --git a/sound/usb/caiaq/control.c b/sound/usb/caiaq/control.c
new file mode 100644
index 000000000..b7a7c805d
--- /dev/null
+++ b/sound/usb/caiaq/control.c
@@ -0,0 +1,656 @@
+/*
+ * Copyright (c) 2007 Daniel Mack
+ * friendly supported by NI.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include <linux/device.h>
+#include <linux/init.h>
+#include <linux/usb.h>
+#include <sound/control.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+
+#include "device.h"
+#include "control.h"
+
+#define CNT_INTVAL 0x10000
+#define MASCHINE_BANK_SIZE 32
+
+static int control_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ struct snd_usb_audio *chip = snd_kcontrol_chip(kcontrol);
+ struct snd_usb_caiaqdev *cdev = caiaqdev(chip->card);
+ int pos = kcontrol->private_value;
+ int is_intval = pos & CNT_INTVAL;
+ int maxval = 63;
+
+ uinfo->count = 1;
+ pos &= ~CNT_INTVAL;
+
+ switch (cdev->chip.usb_id) {
+ case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_AUDIO8DJ):
+ case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_AUDIO4DJ):
+ if (pos == 0) {
+ /* current input mode of A8DJ and A4DJ */
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = 2;
+ return 0;
+ }
+ break;
+
+ case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_TRAKTORKONTROLX1):
+ maxval = 127;
+ break;
+
+ case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_TRAKTORKONTROLS4):
+ maxval = 31;
+ break;
+ }
+
+ if (is_intval) {
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = maxval;
+ } else {
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = 1;
+ }
+
+ return 0;
+}
+
+static int control_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_usb_audio *chip = snd_kcontrol_chip(kcontrol);
+ struct snd_usb_caiaqdev *cdev = caiaqdev(chip->card);
+ int pos = kcontrol->private_value;
+
+ if (pos & CNT_INTVAL)
+ ucontrol->value.integer.value[0]
+ = cdev->control_state[pos & ~CNT_INTVAL];
+ else
+ ucontrol->value.integer.value[0]
+ = !!(cdev->control_state[pos / 8] & (1 << pos % 8));
+
+ return 0;
+}
+
+static int control_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_usb_audio *chip = snd_kcontrol_chip(kcontrol);
+ struct snd_usb_caiaqdev *cdev = caiaqdev(chip->card);
+ int pos = kcontrol->private_value;
+ int v = ucontrol->value.integer.value[0];
+ unsigned char cmd;
+
+ switch (cdev->chip.usb_id) {
+ case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_MASCHINECONTROLLER):
+ case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_TRAKTORKONTROLX1):
+ case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_KORECONTROLLER2):
+ case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_KORECONTROLLER):
+ cmd = EP1_CMD_DIMM_LEDS;
+ break;
+ default:
+ cmd = EP1_CMD_WRITE_IO;
+ break;
+ }
+
+ if (pos & CNT_INTVAL) {
+ int i = pos & ~CNT_INTVAL;
+
+ cdev->control_state[i] = v;
+
+ if (cdev->chip.usb_id ==
+ USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_TRAKTORKONTROLS4)) {
+ int actual_len;
+
+ cdev->ep8_out_buf[0] = i;
+ cdev->ep8_out_buf[1] = v;
+
+ usb_bulk_msg(cdev->chip.dev,
+ usb_sndbulkpipe(cdev->chip.dev, 8),
+ cdev->ep8_out_buf, sizeof(cdev->ep8_out_buf),
+ &actual_len, 200);
+ } else if (cdev->chip.usb_id ==
+ USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_MASCHINECONTROLLER)) {
+
+ int bank = 0;
+ int offset = 0;
+
+ if (i >= MASCHINE_BANK_SIZE) {
+ bank = 0x1e;
+ offset = MASCHINE_BANK_SIZE;
+ }
+
+ snd_usb_caiaq_send_command_bank(cdev, cmd, bank,
+ cdev->control_state + offset,
+ MASCHINE_BANK_SIZE);
+ } else {
+ snd_usb_caiaq_send_command(cdev, cmd,
+ cdev->control_state, sizeof(cdev->control_state));
+ }
+ } else {
+ if (v)
+ cdev->control_state[pos / 8] |= 1 << (pos % 8);
+ else
+ cdev->control_state[pos / 8] &= ~(1 << (pos % 8));
+
+ snd_usb_caiaq_send_command(cdev, cmd,
+ cdev->control_state, sizeof(cdev->control_state));
+ }
+
+ return 1;
+}
+
+static struct snd_kcontrol_new kcontrol_template = {
+ .iface = SNDRV_CTL_ELEM_IFACE_HWDEP,
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+ .index = 0,
+ .info = control_info,
+ .get = control_get,
+ .put = control_put,
+ /* name and private_value filled later */
+};
+
+struct caiaq_controller {
+ char *name;
+ int index;
+};
+
+static struct caiaq_controller ak1_controller[] = {
+ { "LED left", 2 },
+ { "LED middle", 1 },
+ { "LED right", 0 },
+ { "LED ring", 3 }
+};
+
+static struct caiaq_controller rk2_controller[] = {
+ { "LED 1", 5 },
+ { "LED 2", 4 },
+ { "LED 3", 3 },
+ { "LED 4", 2 },
+ { "LED 5", 1 },
+ { "LED 6", 0 },
+ { "LED pedal", 6 },
+ { "LED 7seg_1b", 8 },
+ { "LED 7seg_1c", 9 },
+ { "LED 7seg_2a", 10 },
+ { "LED 7seg_2b", 11 },
+ { "LED 7seg_2c", 12 },
+ { "LED 7seg_2d", 13 },
+ { "LED 7seg_2e", 14 },
+ { "LED 7seg_2f", 15 },
+ { "LED 7seg_2g", 16 },
+ { "LED 7seg_3a", 17 },
+ { "LED 7seg_3b", 18 },
+ { "LED 7seg_3c", 19 },
+ { "LED 7seg_3d", 20 },
+ { "LED 7seg_3e", 21 },
+ { "LED 7seg_3f", 22 },
+ { "LED 7seg_3g", 23 }
+};
+
+static struct caiaq_controller rk3_controller[] = {
+ { "LED 7seg_1a", 0 + 0 },
+ { "LED 7seg_1b", 0 + 1 },
+ { "LED 7seg_1c", 0 + 2 },
+ { "LED 7seg_1d", 0 + 3 },
+ { "LED 7seg_1e", 0 + 4 },
+ { "LED 7seg_1f", 0 + 5 },
+ { "LED 7seg_1g", 0 + 6 },
+ { "LED 7seg_1p", 0 + 7 },
+
+ { "LED 7seg_2a", 8 + 0 },
+ { "LED 7seg_2b", 8 + 1 },
+ { "LED 7seg_2c", 8 + 2 },
+ { "LED 7seg_2d", 8 + 3 },
+ { "LED 7seg_2e", 8 + 4 },
+ { "LED 7seg_2f", 8 + 5 },
+ { "LED 7seg_2g", 8 + 6 },
+ { "LED 7seg_2p", 8 + 7 },
+
+ { "LED 7seg_3a", 16 + 0 },
+ { "LED 7seg_3b", 16 + 1 },
+ { "LED 7seg_3c", 16 + 2 },
+ { "LED 7seg_3d", 16 + 3 },
+ { "LED 7seg_3e", 16 + 4 },
+ { "LED 7seg_3f", 16 + 5 },
+ { "LED 7seg_3g", 16 + 6 },
+ { "LED 7seg_3p", 16 + 7 },
+
+ { "LED 7seg_4a", 24 + 0 },
+ { "LED 7seg_4b", 24 + 1 },
+ { "LED 7seg_4c", 24 + 2 },
+ { "LED 7seg_4d", 24 + 3 },
+ { "LED 7seg_4e", 24 + 4 },
+ { "LED 7seg_4f", 24 + 5 },
+ { "LED 7seg_4g", 24 + 6 },
+ { "LED 7seg_4p", 24 + 7 },
+
+ { "LED 1", 32 + 0 },
+ { "LED 2", 32 + 1 },
+ { "LED 3", 32 + 2 },
+ { "LED 4", 32 + 3 },
+ { "LED 5", 32 + 4 },
+ { "LED 6", 32 + 5 },
+ { "LED 7", 32 + 6 },
+ { "LED 8", 32 + 7 },
+ { "LED pedal", 32 + 8 }
+};
+
+static struct caiaq_controller kore_controller[] = {
+ { "LED F1", 8 | CNT_INTVAL },
+ { "LED F2", 12 | CNT_INTVAL },
+ { "LED F3", 0 | CNT_INTVAL },
+ { "LED F4", 4 | CNT_INTVAL },
+ { "LED F5", 11 | CNT_INTVAL },
+ { "LED F6", 15 | CNT_INTVAL },
+ { "LED F7", 3 | CNT_INTVAL },
+ { "LED F8", 7 | CNT_INTVAL },
+ { "LED touch1", 10 | CNT_INTVAL },
+ { "LED touch2", 14 | CNT_INTVAL },
+ { "LED touch3", 2 | CNT_INTVAL },
+ { "LED touch4", 6 | CNT_INTVAL },
+ { "LED touch5", 9 | CNT_INTVAL },
+ { "LED touch6", 13 | CNT_INTVAL },
+ { "LED touch7", 1 | CNT_INTVAL },
+ { "LED touch8", 5 | CNT_INTVAL },
+ { "LED left", 18 | CNT_INTVAL },
+ { "LED right", 22 | CNT_INTVAL },
+ { "LED up", 16 | CNT_INTVAL },
+ { "LED down", 20 | CNT_INTVAL },
+ { "LED stop", 23 | CNT_INTVAL },
+ { "LED play", 21 | CNT_INTVAL },
+ { "LED record", 19 | CNT_INTVAL },
+ { "LED listen", 17 | CNT_INTVAL },
+ { "LED lcd", 30 | CNT_INTVAL },
+ { "LED menu", 28 | CNT_INTVAL },
+ { "LED sound", 31 | CNT_INTVAL },
+ { "LED esc", 29 | CNT_INTVAL },
+ { "LED view", 27 | CNT_INTVAL },
+ { "LED enter", 24 | CNT_INTVAL },
+ { "LED control", 26 | CNT_INTVAL }
+};
+
+static struct caiaq_controller a8dj_controller[] = {
+ { "Current input mode", 0 | CNT_INTVAL },
+ { "GND lift for TC Vinyl mode", 24 + 0 },
+ { "GND lift for TC CD/Line mode", 24 + 1 },
+ { "GND lift for phono mode", 24 + 2 },
+ { "Software lock", 40 }
+};
+
+static struct caiaq_controller a4dj_controller[] = {
+ { "Current input mode", 0 | CNT_INTVAL }
+};
+
+static struct caiaq_controller kontrolx1_controller[] = {
+ { "LED FX A: ON", 7 | CNT_INTVAL },
+ { "LED FX A: 1", 6 | CNT_INTVAL },
+ { "LED FX A: 2", 5 | CNT_INTVAL },
+ { "LED FX A: 3", 4 | CNT_INTVAL },
+ { "LED FX B: ON", 3 | CNT_INTVAL },
+ { "LED FX B: 1", 2 | CNT_INTVAL },
+ { "LED FX B: 2", 1 | CNT_INTVAL },
+ { "LED FX B: 3", 0 | CNT_INTVAL },
+
+ { "LED Hotcue", 28 | CNT_INTVAL },
+ { "LED Shift (white)", 29 | CNT_INTVAL },
+ { "LED Shift (green)", 30 | CNT_INTVAL },
+
+ { "LED Deck A: FX1", 24 | CNT_INTVAL },
+ { "LED Deck A: FX2", 25 | CNT_INTVAL },
+ { "LED Deck A: IN", 17 | CNT_INTVAL },
+ { "LED Deck A: OUT", 16 | CNT_INTVAL },
+ { "LED Deck A: < BEAT", 19 | CNT_INTVAL },
+ { "LED Deck A: BEAT >", 18 | CNT_INTVAL },
+ { "LED Deck A: CUE/ABS", 21 | CNT_INTVAL },
+ { "LED Deck A: CUP/REL", 20 | CNT_INTVAL },
+ { "LED Deck A: PLAY", 23 | CNT_INTVAL },
+ { "LED Deck A: SYNC", 22 | CNT_INTVAL },
+
+ { "LED Deck B: FX1", 26 | CNT_INTVAL },
+ { "LED Deck B: FX2", 27 | CNT_INTVAL },
+ { "LED Deck B: IN", 15 | CNT_INTVAL },
+ { "LED Deck B: OUT", 14 | CNT_INTVAL },
+ { "LED Deck B: < BEAT", 13 | CNT_INTVAL },
+ { "LED Deck B: BEAT >", 12 | CNT_INTVAL },
+ { "LED Deck B: CUE/ABS", 11 | CNT_INTVAL },
+ { "LED Deck B: CUP/REL", 10 | CNT_INTVAL },
+ { "LED Deck B: PLAY", 9 | CNT_INTVAL },
+ { "LED Deck B: SYNC", 8 | CNT_INTVAL },
+};
+
+static struct caiaq_controller kontrols4_controller[] = {
+ { "LED: Master: Quant", 10 | CNT_INTVAL },
+ { "LED: Master: Headphone", 11 | CNT_INTVAL },
+ { "LED: Master: Master", 12 | CNT_INTVAL },
+ { "LED: Master: Snap", 14 | CNT_INTVAL },
+ { "LED: Master: Warning", 15 | CNT_INTVAL },
+ { "LED: Master: Master button", 112 | CNT_INTVAL },
+ { "LED: Master: Snap button", 113 | CNT_INTVAL },
+ { "LED: Master: Rec", 118 | CNT_INTVAL },
+ { "LED: Master: Size", 119 | CNT_INTVAL },
+ { "LED: Master: Quant button", 120 | CNT_INTVAL },
+ { "LED: Master: Browser button", 121 | CNT_INTVAL },
+ { "LED: Master: Play button", 126 | CNT_INTVAL },
+ { "LED: Master: Undo button", 127 | CNT_INTVAL },
+
+ { "LED: Channel A: >", 4 | CNT_INTVAL },
+ { "LED: Channel A: <", 5 | CNT_INTVAL },
+ { "LED: Channel A: Meter 1", 97 | CNT_INTVAL },
+ { "LED: Channel A: Meter 2", 98 | CNT_INTVAL },
+ { "LED: Channel A: Meter 3", 99 | CNT_INTVAL },
+ { "LED: Channel A: Meter 4", 100 | CNT_INTVAL },
+ { "LED: Channel A: Meter 5", 101 | CNT_INTVAL },
+ { "LED: Channel A: Meter 6", 102 | CNT_INTVAL },
+ { "LED: Channel A: Meter clip", 103 | CNT_INTVAL },
+ { "LED: Channel A: Active", 114 | CNT_INTVAL },
+ { "LED: Channel A: Cue", 116 | CNT_INTVAL },
+ { "LED: Channel A: FX1", 149 | CNT_INTVAL },
+ { "LED: Channel A: FX2", 148 | CNT_INTVAL },
+
+ { "LED: Channel B: >", 2 | CNT_INTVAL },
+ { "LED: Channel B: <", 3 | CNT_INTVAL },
+ { "LED: Channel B: Meter 1", 89 | CNT_INTVAL },
+ { "LED: Channel B: Meter 2", 90 | CNT_INTVAL },
+ { "LED: Channel B: Meter 3", 91 | CNT_INTVAL },
+ { "LED: Channel B: Meter 4", 92 | CNT_INTVAL },
+ { "LED: Channel B: Meter 5", 93 | CNT_INTVAL },
+ { "LED: Channel B: Meter 6", 94 | CNT_INTVAL },
+ { "LED: Channel B: Meter clip", 95 | CNT_INTVAL },
+ { "LED: Channel B: Active", 122 | CNT_INTVAL },
+ { "LED: Channel B: Cue", 125 | CNT_INTVAL },
+ { "LED: Channel B: FX1", 147 | CNT_INTVAL },
+ { "LED: Channel B: FX2", 146 | CNT_INTVAL },
+
+ { "LED: Channel C: >", 6 | CNT_INTVAL },
+ { "LED: Channel C: <", 7 | CNT_INTVAL },
+ { "LED: Channel C: Meter 1", 105 | CNT_INTVAL },
+ { "LED: Channel C: Meter 2", 106 | CNT_INTVAL },
+ { "LED: Channel C: Meter 3", 107 | CNT_INTVAL },
+ { "LED: Channel C: Meter 4", 108 | CNT_INTVAL },
+ { "LED: Channel C: Meter 5", 109 | CNT_INTVAL },
+ { "LED: Channel C: Meter 6", 110 | CNT_INTVAL },
+ { "LED: Channel C: Meter clip", 111 | CNT_INTVAL },
+ { "LED: Channel C: Active", 115 | CNT_INTVAL },
+ { "LED: Channel C: Cue", 117 | CNT_INTVAL },
+ { "LED: Channel C: FX1", 151 | CNT_INTVAL },
+ { "LED: Channel C: FX2", 150 | CNT_INTVAL },
+
+ { "LED: Channel D: >", 0 | CNT_INTVAL },
+ { "LED: Channel D: <", 1 | CNT_INTVAL },
+ { "LED: Channel D: Meter 1", 81 | CNT_INTVAL },
+ { "LED: Channel D: Meter 2", 82 | CNT_INTVAL },
+ { "LED: Channel D: Meter 3", 83 | CNT_INTVAL },
+ { "LED: Channel D: Meter 4", 84 | CNT_INTVAL },
+ { "LED: Channel D: Meter 5", 85 | CNT_INTVAL },
+ { "LED: Channel D: Meter 6", 86 | CNT_INTVAL },
+ { "LED: Channel D: Meter clip", 87 | CNT_INTVAL },
+ { "LED: Channel D: Active", 123 | CNT_INTVAL },
+ { "LED: Channel D: Cue", 124 | CNT_INTVAL },
+ { "LED: Channel D: FX1", 145 | CNT_INTVAL },
+ { "LED: Channel D: FX2", 144 | CNT_INTVAL },
+
+ { "LED: Deck A: 1 (blue)", 22 | CNT_INTVAL },
+ { "LED: Deck A: 1 (green)", 23 | CNT_INTVAL },
+ { "LED: Deck A: 2 (blue)", 20 | CNT_INTVAL },
+ { "LED: Deck A: 2 (green)", 21 | CNT_INTVAL },
+ { "LED: Deck A: 3 (blue)", 18 | CNT_INTVAL },
+ { "LED: Deck A: 3 (green)", 19 | CNT_INTVAL },
+ { "LED: Deck A: 4 (blue)", 16 | CNT_INTVAL },
+ { "LED: Deck A: 4 (green)", 17 | CNT_INTVAL },
+ { "LED: Deck A: Load", 44 | CNT_INTVAL },
+ { "LED: Deck A: Deck C button", 45 | CNT_INTVAL },
+ { "LED: Deck A: In", 47 | CNT_INTVAL },
+ { "LED: Deck A: Out", 46 | CNT_INTVAL },
+ { "LED: Deck A: Shift", 24 | CNT_INTVAL },
+ { "LED: Deck A: Sync", 27 | CNT_INTVAL },
+ { "LED: Deck A: Cue", 26 | CNT_INTVAL },
+ { "LED: Deck A: Play", 25 | CNT_INTVAL },
+ { "LED: Deck A: Tempo up", 33 | CNT_INTVAL },
+ { "LED: Deck A: Tempo down", 32 | CNT_INTVAL },
+ { "LED: Deck A: Master", 34 | CNT_INTVAL },
+ { "LED: Deck A: Keylock", 35 | CNT_INTVAL },
+ { "LED: Deck A: Deck A", 37 | CNT_INTVAL },
+ { "LED: Deck A: Deck C", 36 | CNT_INTVAL },
+ { "LED: Deck A: Samples", 38 | CNT_INTVAL },
+ { "LED: Deck A: On Air", 39 | CNT_INTVAL },
+ { "LED: Deck A: Sample 1", 31 | CNT_INTVAL },
+ { "LED: Deck A: Sample 2", 30 | CNT_INTVAL },
+ { "LED: Deck A: Sample 3", 29 | CNT_INTVAL },
+ { "LED: Deck A: Sample 4", 28 | CNT_INTVAL },
+ { "LED: Deck A: Digit 1 - A", 55 | CNT_INTVAL },
+ { "LED: Deck A: Digit 1 - B", 54 | CNT_INTVAL },
+ { "LED: Deck A: Digit 1 - C", 53 | CNT_INTVAL },
+ { "LED: Deck A: Digit 1 - D", 52 | CNT_INTVAL },
+ { "LED: Deck A: Digit 1 - E", 51 | CNT_INTVAL },
+ { "LED: Deck A: Digit 1 - F", 50 | CNT_INTVAL },
+ { "LED: Deck A: Digit 1 - G", 49 | CNT_INTVAL },
+ { "LED: Deck A: Digit 1 - dot", 48 | CNT_INTVAL },
+ { "LED: Deck A: Digit 2 - A", 63 | CNT_INTVAL },
+ { "LED: Deck A: Digit 2 - B", 62 | CNT_INTVAL },
+ { "LED: Deck A: Digit 2 - C", 61 | CNT_INTVAL },
+ { "LED: Deck A: Digit 2 - D", 60 | CNT_INTVAL },
+ { "LED: Deck A: Digit 2 - E", 59 | CNT_INTVAL },
+ { "LED: Deck A: Digit 2 - F", 58 | CNT_INTVAL },
+ { "LED: Deck A: Digit 2 - G", 57 | CNT_INTVAL },
+ { "LED: Deck A: Digit 2 - dot", 56 | CNT_INTVAL },
+
+ { "LED: Deck B: 1 (blue)", 78 | CNT_INTVAL },
+ { "LED: Deck B: 1 (green)", 79 | CNT_INTVAL },
+ { "LED: Deck B: 2 (blue)", 76 | CNT_INTVAL },
+ { "LED: Deck B: 2 (green)", 77 | CNT_INTVAL },
+ { "LED: Deck B: 3 (blue)", 74 | CNT_INTVAL },
+ { "LED: Deck B: 3 (green)", 75 | CNT_INTVAL },
+ { "LED: Deck B: 4 (blue)", 72 | CNT_INTVAL },
+ { "LED: Deck B: 4 (green)", 73 | CNT_INTVAL },
+ { "LED: Deck B: Load", 180 | CNT_INTVAL },
+ { "LED: Deck B: Deck D button", 181 | CNT_INTVAL },
+ { "LED: Deck B: In", 183 | CNT_INTVAL },
+ { "LED: Deck B: Out", 182 | CNT_INTVAL },
+ { "LED: Deck B: Shift", 64 | CNT_INTVAL },
+ { "LED: Deck B: Sync", 67 | CNT_INTVAL },
+ { "LED: Deck B: Cue", 66 | CNT_INTVAL },
+ { "LED: Deck B: Play", 65 | CNT_INTVAL },
+ { "LED: Deck B: Tempo up", 185 | CNT_INTVAL },
+ { "LED: Deck B: Tempo down", 184 | CNT_INTVAL },
+ { "LED: Deck B: Master", 186 | CNT_INTVAL },
+ { "LED: Deck B: Keylock", 187 | CNT_INTVAL },
+ { "LED: Deck B: Deck B", 189 | CNT_INTVAL },
+ { "LED: Deck B: Deck D", 188 | CNT_INTVAL },
+ { "LED: Deck B: Samples", 190 | CNT_INTVAL },
+ { "LED: Deck B: On Air", 191 | CNT_INTVAL },
+ { "LED: Deck B: Sample 1", 71 | CNT_INTVAL },
+ { "LED: Deck B: Sample 2", 70 | CNT_INTVAL },
+ { "LED: Deck B: Sample 3", 69 | CNT_INTVAL },
+ { "LED: Deck B: Sample 4", 68 | CNT_INTVAL },
+ { "LED: Deck B: Digit 1 - A", 175 | CNT_INTVAL },
+ { "LED: Deck B: Digit 1 - B", 174 | CNT_INTVAL },
+ { "LED: Deck B: Digit 1 - C", 173 | CNT_INTVAL },
+ { "LED: Deck B: Digit 1 - D", 172 | CNT_INTVAL },
+ { "LED: Deck B: Digit 1 - E", 171 | CNT_INTVAL },
+ { "LED: Deck B: Digit 1 - F", 170 | CNT_INTVAL },
+ { "LED: Deck B: Digit 1 - G", 169 | CNT_INTVAL },
+ { "LED: Deck B: Digit 1 - dot", 168 | CNT_INTVAL },
+ { "LED: Deck B: Digit 2 - A", 167 | CNT_INTVAL },
+ { "LED: Deck B: Digit 2 - B", 166 | CNT_INTVAL },
+ { "LED: Deck B: Digit 2 - C", 165 | CNT_INTVAL },
+ { "LED: Deck B: Digit 2 - D", 164 | CNT_INTVAL },
+ { "LED: Deck B: Digit 2 - E", 163 | CNT_INTVAL },
+ { "LED: Deck B: Digit 2 - F", 162 | CNT_INTVAL },
+ { "LED: Deck B: Digit 2 - G", 161 | CNT_INTVAL },
+ { "LED: Deck B: Digit 2 - dot", 160 | CNT_INTVAL },
+
+ { "LED: FX1: dry/wet", 153 | CNT_INTVAL },
+ { "LED: FX1: 1", 154 | CNT_INTVAL },
+ { "LED: FX1: 2", 155 | CNT_INTVAL },
+ { "LED: FX1: 3", 156 | CNT_INTVAL },
+ { "LED: FX1: Mode", 157 | CNT_INTVAL },
+ { "LED: FX2: dry/wet", 129 | CNT_INTVAL },
+ { "LED: FX2: 1", 130 | CNT_INTVAL },
+ { "LED: FX2: 2", 131 | CNT_INTVAL },
+ { "LED: FX2: 3", 132 | CNT_INTVAL },
+ { "LED: FX2: Mode", 133 | CNT_INTVAL },
+};
+
+static struct caiaq_controller maschine_controller[] = {
+ { "LED: Pad 1", 3 | CNT_INTVAL },
+ { "LED: Pad 2", 2 | CNT_INTVAL },
+ { "LED: Pad 3", 1 | CNT_INTVAL },
+ { "LED: Pad 4", 0 | CNT_INTVAL },
+ { "LED: Pad 5", 7 | CNT_INTVAL },
+ { "LED: Pad 6", 6 | CNT_INTVAL },
+ { "LED: Pad 7", 5 | CNT_INTVAL },
+ { "LED: Pad 8", 4 | CNT_INTVAL },
+ { "LED: Pad 9", 11 | CNT_INTVAL },
+ { "LED: Pad 10", 10 | CNT_INTVAL },
+ { "LED: Pad 11", 9 | CNT_INTVAL },
+ { "LED: Pad 12", 8 | CNT_INTVAL },
+ { "LED: Pad 13", 15 | CNT_INTVAL },
+ { "LED: Pad 14", 14 | CNT_INTVAL },
+ { "LED: Pad 15", 13 | CNT_INTVAL },
+ { "LED: Pad 16", 12 | CNT_INTVAL },
+
+ { "LED: Mute", 16 | CNT_INTVAL },
+ { "LED: Solo", 17 | CNT_INTVAL },
+ { "LED: Select", 18 | CNT_INTVAL },
+ { "LED: Duplicate", 19 | CNT_INTVAL },
+ { "LED: Navigate", 20 | CNT_INTVAL },
+ { "LED: Pad Mode", 21 | CNT_INTVAL },
+ { "LED: Pattern", 22 | CNT_INTVAL },
+ { "LED: Scene", 23 | CNT_INTVAL },
+
+ { "LED: Shift", 24 | CNT_INTVAL },
+ { "LED: Erase", 25 | CNT_INTVAL },
+ { "LED: Grid", 26 | CNT_INTVAL },
+ { "LED: Right Bottom", 27 | CNT_INTVAL },
+ { "LED: Rec", 28 | CNT_INTVAL },
+ { "LED: Play", 29 | CNT_INTVAL },
+ { "LED: Left Bottom", 32 | CNT_INTVAL },
+ { "LED: Restart", 33 | CNT_INTVAL },
+
+ { "LED: Group A", 41 | CNT_INTVAL },
+ { "LED: Group B", 40 | CNT_INTVAL },
+ { "LED: Group C", 37 | CNT_INTVAL },
+ { "LED: Group D", 36 | CNT_INTVAL },
+ { "LED: Group E", 39 | CNT_INTVAL },
+ { "LED: Group F", 38 | CNT_INTVAL },
+ { "LED: Group G", 35 | CNT_INTVAL },
+ { "LED: Group H", 34 | CNT_INTVAL },
+
+ { "LED: Auto Write", 42 | CNT_INTVAL },
+ { "LED: Snap", 43 | CNT_INTVAL },
+ { "LED: Right Top", 44 | CNT_INTVAL },
+ { "LED: Left Top", 45 | CNT_INTVAL },
+ { "LED: Sampling", 46 | CNT_INTVAL },
+ { "LED: Browse", 47 | CNT_INTVAL },
+ { "LED: Step", 48 | CNT_INTVAL },
+ { "LED: Control", 49 | CNT_INTVAL },
+
+ { "LED: Top Button 1", 57 | CNT_INTVAL },
+ { "LED: Top Button 2", 56 | CNT_INTVAL },
+ { "LED: Top Button 3", 55 | CNT_INTVAL },
+ { "LED: Top Button 4", 54 | CNT_INTVAL },
+ { "LED: Top Button 5", 53 | CNT_INTVAL },
+ { "LED: Top Button 6", 52 | CNT_INTVAL },
+ { "LED: Top Button 7", 51 | CNT_INTVAL },
+ { "LED: Top Button 8", 50 | CNT_INTVAL },
+
+ { "LED: Note Repeat", 58 | CNT_INTVAL },
+
+ { "Backlight Display", 59 | CNT_INTVAL }
+};
+
+static int add_controls(struct caiaq_controller *c, int num,
+ struct snd_usb_caiaqdev *cdev)
+{
+ int i, ret;
+ struct snd_kcontrol *kc;
+
+ for (i = 0; i < num; i++, c++) {
+ kcontrol_template.name = c->name;
+ kcontrol_template.private_value = c->index;
+ kc = snd_ctl_new1(&kcontrol_template, cdev);
+ ret = snd_ctl_add(cdev->chip.card, kc);
+ if (ret < 0)
+ return ret;
+ }
+
+ return 0;
+}
+
+int snd_usb_caiaq_control_init(struct snd_usb_caiaqdev *cdev)
+{
+ int ret = 0;
+
+ switch (cdev->chip.usb_id) {
+ case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_AK1):
+ ret = add_controls(ak1_controller,
+ ARRAY_SIZE(ak1_controller), cdev);
+ break;
+
+ case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_RIGKONTROL2):
+ ret = add_controls(rk2_controller,
+ ARRAY_SIZE(rk2_controller), cdev);
+ break;
+
+ case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_RIGKONTROL3):
+ ret = add_controls(rk3_controller,
+ ARRAY_SIZE(rk3_controller), cdev);
+ break;
+
+ case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_KORECONTROLLER):
+ case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_KORECONTROLLER2):
+ ret = add_controls(kore_controller,
+ ARRAY_SIZE(kore_controller), cdev);
+ break;
+
+ case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_AUDIO8DJ):
+ ret = add_controls(a8dj_controller,
+ ARRAY_SIZE(a8dj_controller), cdev);
+ break;
+
+ case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_AUDIO4DJ):
+ ret = add_controls(a4dj_controller,
+ ARRAY_SIZE(a4dj_controller), cdev);
+ break;
+
+ case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_TRAKTORKONTROLX1):
+ ret = add_controls(kontrolx1_controller,
+ ARRAY_SIZE(kontrolx1_controller), cdev);
+ break;
+
+ case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_TRAKTORKONTROLS4):
+ ret = add_controls(kontrols4_controller,
+ ARRAY_SIZE(kontrols4_controller), cdev);
+ break;
+
+ case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_MASCHINECONTROLLER):
+ ret = add_controls(maschine_controller,
+ ARRAY_SIZE(maschine_controller), cdev);
+ break;
+ }
+
+ return ret;
+}
+
diff --git a/sound/usb/caiaq/control.h b/sound/usb/caiaq/control.h
new file mode 100644
index 000000000..501c4883a
--- /dev/null
+++ b/sound/usb/caiaq/control.h
@@ -0,0 +1,6 @@
+#ifndef CAIAQ_CONTROL_H
+#define CAIAQ_CONTROL_H
+
+int snd_usb_caiaq_control_init(struct snd_usb_caiaqdev *cdev);
+
+#endif /* CAIAQ_CONTROL_H */
diff --git a/sound/usb/caiaq/device.c b/sound/usb/caiaq/device.c
new file mode 100644
index 000000000..b871ba407
--- /dev/null
+++ b/sound/usb/caiaq/device.c
@@ -0,0 +1,570 @@
+/*
+ * caiaq.c: ALSA driver for caiaq/NativeInstruments devices
+ *
+ * Copyright (c) 2007 Daniel Mack <daniel@caiaq.de>
+ * Karsten Wiese <fzu@wemgehoertderstaat.de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*/
+
+#include <linux/moduleparam.h>
+#include <linux/device.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/gfp.h>
+#include <linux/usb.h>
+#include <sound/initval.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+
+#include "device.h"
+#include "audio.h"
+#include "midi.h"
+#include "control.h"
+#include "input.h"
+
+MODULE_AUTHOR("Daniel Mack <daniel@caiaq.de>");
+MODULE_DESCRIPTION("caiaq USB audio");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("{{Native Instruments,RigKontrol2},"
+ "{Native Instruments,RigKontrol3},"
+ "{Native Instruments,Kore Controller},"
+ "{Native Instruments,Kore Controller 2},"
+ "{Native Instruments,Audio Kontrol 1},"
+ "{Native Instruments,Audio 2 DJ},"
+ "{Native Instruments,Audio 4 DJ},"
+ "{Native Instruments,Audio 8 DJ},"
+ "{Native Instruments,Traktor Audio 2},"
+ "{Native Instruments,Session I/O},"
+ "{Native Instruments,GuitarRig mobile},"
+ "{Native Instruments,Traktor Kontrol X1},"
+ "{Native Instruments,Traktor Kontrol S4},"
+ "{Native Instruments,Maschine Controller}}");
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 0-max */
+static char* id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* Id for this card */
+static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP; /* Enable this card */
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for the caiaq sound device");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for the caiaq soundcard.");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "Enable the caiaq soundcard.");
+
+enum {
+ SAMPLERATE_44100 = 0,
+ SAMPLERATE_48000 = 1,
+ SAMPLERATE_96000 = 2,
+ SAMPLERATE_192000 = 3,
+ SAMPLERATE_88200 = 4,
+ SAMPLERATE_INVALID = 0xff
+};
+
+enum {
+ DEPTH_NONE = 0,
+ DEPTH_16 = 1,
+ DEPTH_24 = 2,
+ DEPTH_32 = 3
+};
+
+static struct usb_device_id snd_usb_id_table[] = {
+ {
+ .match_flags = USB_DEVICE_ID_MATCH_DEVICE,
+ .idVendor = USB_VID_NATIVEINSTRUMENTS,
+ .idProduct = USB_PID_RIGKONTROL2
+ },
+ {
+ .match_flags = USB_DEVICE_ID_MATCH_DEVICE,
+ .idVendor = USB_VID_NATIVEINSTRUMENTS,
+ .idProduct = USB_PID_RIGKONTROL3
+ },
+ {
+ .match_flags = USB_DEVICE_ID_MATCH_DEVICE,
+ .idVendor = USB_VID_NATIVEINSTRUMENTS,
+ .idProduct = USB_PID_KORECONTROLLER
+ },
+ {
+ .match_flags = USB_DEVICE_ID_MATCH_DEVICE,
+ .idVendor = USB_VID_NATIVEINSTRUMENTS,
+ .idProduct = USB_PID_KORECONTROLLER2
+ },
+ {
+ .match_flags = USB_DEVICE_ID_MATCH_DEVICE,
+ .idVendor = USB_VID_NATIVEINSTRUMENTS,
+ .idProduct = USB_PID_AK1
+ },
+ {
+ .match_flags = USB_DEVICE_ID_MATCH_DEVICE,
+ .idVendor = USB_VID_NATIVEINSTRUMENTS,
+ .idProduct = USB_PID_AUDIO8DJ
+ },
+ {
+ .match_flags = USB_DEVICE_ID_MATCH_DEVICE,
+ .idVendor = USB_VID_NATIVEINSTRUMENTS,
+ .idProduct = USB_PID_SESSIONIO
+ },
+ {
+ .match_flags = USB_DEVICE_ID_MATCH_DEVICE,
+ .idVendor = USB_VID_NATIVEINSTRUMENTS,
+ .idProduct = USB_PID_GUITARRIGMOBILE
+ },
+ {
+ .match_flags = USB_DEVICE_ID_MATCH_DEVICE,
+ .idVendor = USB_VID_NATIVEINSTRUMENTS,
+ .idProduct = USB_PID_AUDIO4DJ
+ },
+ {
+ .match_flags = USB_DEVICE_ID_MATCH_DEVICE,
+ .idVendor = USB_VID_NATIVEINSTRUMENTS,
+ .idProduct = USB_PID_AUDIO2DJ
+ },
+ {
+ .match_flags = USB_DEVICE_ID_MATCH_DEVICE,
+ .idVendor = USB_VID_NATIVEINSTRUMENTS,
+ .idProduct = USB_PID_TRAKTORKONTROLX1
+ },
+ {
+ .match_flags = USB_DEVICE_ID_MATCH_DEVICE,
+ .idVendor = USB_VID_NATIVEINSTRUMENTS,
+ .idProduct = USB_PID_TRAKTORKONTROLS4
+ },
+ {
+ .match_flags = USB_DEVICE_ID_MATCH_DEVICE,
+ .idVendor = USB_VID_NATIVEINSTRUMENTS,
+ .idProduct = USB_PID_TRAKTORAUDIO2
+ },
+ {
+ .match_flags = USB_DEVICE_ID_MATCH_DEVICE,
+ .idVendor = USB_VID_NATIVEINSTRUMENTS,
+ .idProduct = USB_PID_MASCHINECONTROLLER
+ },
+ { /* terminator */ }
+};
+
+static void usb_ep1_command_reply_dispatch (struct urb* urb)
+{
+ int ret;
+ struct device *dev = &urb->dev->dev;
+ struct snd_usb_caiaqdev *cdev = urb->context;
+ unsigned char *buf = urb->transfer_buffer;
+
+ if (urb->status || !cdev) {
+ dev_warn(dev, "received EP1 urb->status = %i\n", urb->status);
+ return;
+ }
+
+ switch(buf[0]) {
+ case EP1_CMD_GET_DEVICE_INFO:
+ memcpy(&cdev->spec, buf+1, sizeof(struct caiaq_device_spec));
+ cdev->spec.fw_version = le16_to_cpu(cdev->spec.fw_version);
+ dev_dbg(dev, "device spec (firmware %d): audio: %d in, %d out, "
+ "MIDI: %d in, %d out, data alignment %d\n",
+ cdev->spec.fw_version,
+ cdev->spec.num_analog_audio_in,
+ cdev->spec.num_analog_audio_out,
+ cdev->spec.num_midi_in,
+ cdev->spec.num_midi_out,
+ cdev->spec.data_alignment);
+
+ cdev->spec_received++;
+ wake_up(&cdev->ep1_wait_queue);
+ break;
+ case EP1_CMD_AUDIO_PARAMS:
+ cdev->audio_parm_answer = buf[1];
+ wake_up(&cdev->ep1_wait_queue);
+ break;
+ case EP1_CMD_MIDI_READ:
+ snd_usb_caiaq_midi_handle_input(cdev, buf[1], buf + 3, buf[2]);
+ break;
+ case EP1_CMD_READ_IO:
+ if (cdev->chip.usb_id ==
+ USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_AUDIO8DJ)) {
+ if (urb->actual_length > sizeof(cdev->control_state))
+ urb->actual_length = sizeof(cdev->control_state);
+ memcpy(cdev->control_state, buf + 1, urb->actual_length);
+ wake_up(&cdev->ep1_wait_queue);
+ break;
+ }
+#ifdef CONFIG_SND_USB_CAIAQ_INPUT
+ case EP1_CMD_READ_ERP:
+ case EP1_CMD_READ_ANALOG:
+ snd_usb_caiaq_input_dispatch(cdev, buf, urb->actual_length);
+#endif
+ break;
+ }
+
+ cdev->ep1_in_urb.actual_length = 0;
+ ret = usb_submit_urb(&cdev->ep1_in_urb, GFP_ATOMIC);
+ if (ret < 0)
+ dev_err(dev, "unable to submit urb. OOM!?\n");
+}
+
+int snd_usb_caiaq_send_command(struct snd_usb_caiaqdev *cdev,
+ unsigned char command,
+ const unsigned char *buffer,
+ int len)
+{
+ int actual_len;
+ struct usb_device *usb_dev = cdev->chip.dev;
+
+ if (!usb_dev)
+ return -EIO;
+
+ if (len > EP1_BUFSIZE - 1)
+ len = EP1_BUFSIZE - 1;
+
+ if (buffer && len > 0)
+ memcpy(cdev->ep1_out_buf+1, buffer, len);
+
+ cdev->ep1_out_buf[0] = command;
+ return usb_bulk_msg(usb_dev, usb_sndbulkpipe(usb_dev, 1),
+ cdev->ep1_out_buf, len+1, &actual_len, 200);
+}
+
+int snd_usb_caiaq_send_command_bank(struct snd_usb_caiaqdev *cdev,
+ unsigned char command,
+ unsigned char bank,
+ const unsigned char *buffer,
+ int len)
+{
+ int actual_len;
+ struct usb_device *usb_dev = cdev->chip.dev;
+
+ if (!usb_dev)
+ return -EIO;
+
+ if (len > EP1_BUFSIZE - 2)
+ len = EP1_BUFSIZE - 2;
+
+ if (buffer && len > 0)
+ memcpy(cdev->ep1_out_buf+2, buffer, len);
+
+ cdev->ep1_out_buf[0] = command;
+ cdev->ep1_out_buf[1] = bank;
+
+ return usb_bulk_msg(usb_dev, usb_sndbulkpipe(usb_dev, 1),
+ cdev->ep1_out_buf, len+2, &actual_len, 200);
+}
+
+int snd_usb_caiaq_set_audio_params (struct snd_usb_caiaqdev *cdev,
+ int rate, int depth, int bpp)
+{
+ int ret;
+ char tmp[5];
+ struct device *dev = caiaqdev_to_dev(cdev);
+
+ switch (rate) {
+ case 44100: tmp[0] = SAMPLERATE_44100; break;
+ case 48000: tmp[0] = SAMPLERATE_48000; break;
+ case 88200: tmp[0] = SAMPLERATE_88200; break;
+ case 96000: tmp[0] = SAMPLERATE_96000; break;
+ case 192000: tmp[0] = SAMPLERATE_192000; break;
+ default: return -EINVAL;
+ }
+
+ switch (depth) {
+ case 16: tmp[1] = DEPTH_16; break;
+ case 24: tmp[1] = DEPTH_24; break;
+ default: return -EINVAL;
+ }
+
+ tmp[2] = bpp & 0xff;
+ tmp[3] = bpp >> 8;
+ tmp[4] = 1; /* packets per microframe */
+
+ dev_dbg(dev, "setting audio params: %d Hz, %d bits, %d bpp\n",
+ rate, depth, bpp);
+
+ cdev->audio_parm_answer = -1;
+ ret = snd_usb_caiaq_send_command(cdev, EP1_CMD_AUDIO_PARAMS,
+ tmp, sizeof(tmp));
+
+ if (ret)
+ return ret;
+
+ if (!wait_event_timeout(cdev->ep1_wait_queue,
+ cdev->audio_parm_answer >= 0, HZ))
+ return -EPIPE;
+
+ if (cdev->audio_parm_answer != 1)
+ dev_dbg(dev, "unable to set the device's audio params\n");
+ else
+ cdev->bpp = bpp;
+
+ return cdev->audio_parm_answer == 1 ? 0 : -EINVAL;
+}
+
+int snd_usb_caiaq_set_auto_msg(struct snd_usb_caiaqdev *cdev,
+ int digital, int analog, int erp)
+{
+ char tmp[3] = { digital, analog, erp };
+ return snd_usb_caiaq_send_command(cdev, EP1_CMD_AUTO_MSG,
+ tmp, sizeof(tmp));
+}
+
+static void setup_card(struct snd_usb_caiaqdev *cdev)
+{
+ int ret;
+ char val[4];
+ struct device *dev = caiaqdev_to_dev(cdev);
+
+ /* device-specific startup specials */
+ switch (cdev->chip.usb_id) {
+ case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_RIGKONTROL2):
+ /* RigKontrol2 - display centered dash ('-') */
+ val[0] = 0x00;
+ val[1] = 0x00;
+ val[2] = 0x01;
+ snd_usb_caiaq_send_command(cdev, EP1_CMD_WRITE_IO, val, 3);
+ break;
+ case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_RIGKONTROL3):
+ /* RigKontrol2 - display two centered dashes ('--') */
+ val[0] = 0x00;
+ val[1] = 0x40;
+ val[2] = 0x40;
+ val[3] = 0x00;
+ snd_usb_caiaq_send_command(cdev, EP1_CMD_WRITE_IO, val, 4);
+ break;
+ case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_AK1):
+ /* Audio Kontrol 1 - make USB-LED stop blinking */
+ val[0] = 0x00;
+ snd_usb_caiaq_send_command(cdev, EP1_CMD_WRITE_IO, val, 1);
+ break;
+ case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_AUDIO8DJ):
+ /* Audio 8 DJ - trigger read of current settings */
+ cdev->control_state[0] = 0xff;
+ snd_usb_caiaq_set_auto_msg(cdev, 1, 0, 0);
+ snd_usb_caiaq_send_command(cdev, EP1_CMD_READ_IO, NULL, 0);
+
+ if (!wait_event_timeout(cdev->ep1_wait_queue,
+ cdev->control_state[0] != 0xff, HZ))
+ return;
+
+ /* fix up some defaults */
+ if ((cdev->control_state[1] != 2) ||
+ (cdev->control_state[2] != 3) ||
+ (cdev->control_state[4] != 2)) {
+ cdev->control_state[1] = 2;
+ cdev->control_state[2] = 3;
+ cdev->control_state[4] = 2;
+ snd_usb_caiaq_send_command(cdev,
+ EP1_CMD_WRITE_IO, cdev->control_state, 6);
+ }
+
+ break;
+ }
+
+ if (cdev->spec.num_analog_audio_out +
+ cdev->spec.num_analog_audio_in +
+ cdev->spec.num_digital_audio_out +
+ cdev->spec.num_digital_audio_in > 0) {
+ ret = snd_usb_caiaq_audio_init(cdev);
+ if (ret < 0)
+ dev_err(dev, "Unable to set up audio system (ret=%d)\n", ret);
+ }
+
+ if (cdev->spec.num_midi_in +
+ cdev->spec.num_midi_out > 0) {
+ ret = snd_usb_caiaq_midi_init(cdev);
+ if (ret < 0)
+ dev_err(dev, "Unable to set up MIDI system (ret=%d)\n", ret);
+ }
+
+#ifdef CONFIG_SND_USB_CAIAQ_INPUT
+ ret = snd_usb_caiaq_input_init(cdev);
+ if (ret < 0)
+ dev_err(dev, "Unable to set up input system (ret=%d)\n", ret);
+#endif
+
+ /* finally, register the card and all its sub-instances */
+ ret = snd_card_register(cdev->chip.card);
+ if (ret < 0) {
+ dev_err(dev, "snd_card_register() returned %d\n", ret);
+ snd_card_free(cdev->chip.card);
+ }
+
+ ret = snd_usb_caiaq_control_init(cdev);
+ if (ret < 0)
+ dev_err(dev, "Unable to set up control system (ret=%d)\n", ret);
+}
+
+static int create_card(struct usb_device *usb_dev,
+ struct usb_interface *intf,
+ struct snd_card **cardp)
+{
+ int devnum;
+ int err;
+ struct snd_card *card;
+ struct snd_usb_caiaqdev *cdev;
+
+ for (devnum = 0; devnum < SNDRV_CARDS; devnum++)
+ if (enable[devnum])
+ break;
+
+ if (devnum >= SNDRV_CARDS)
+ return -ENODEV;
+
+ err = snd_card_new(&intf->dev,
+ index[devnum], id[devnum], THIS_MODULE,
+ sizeof(struct snd_usb_caiaqdev), &card);
+ if (err < 0)
+ return err;
+
+ cdev = caiaqdev(card);
+ cdev->chip.dev = usb_dev;
+ cdev->chip.card = card;
+ cdev->chip.usb_id = USB_ID(le16_to_cpu(usb_dev->descriptor.idVendor),
+ le16_to_cpu(usb_dev->descriptor.idProduct));
+ spin_lock_init(&cdev->spinlock);
+
+ *cardp = card;
+ return 0;
+}
+
+static int init_card(struct snd_usb_caiaqdev *cdev)
+{
+ char *c, usbpath[32];
+ struct usb_device *usb_dev = cdev->chip.dev;
+ struct snd_card *card = cdev->chip.card;
+ struct device *dev = caiaqdev_to_dev(cdev);
+ int err, len;
+
+ if (usb_set_interface(usb_dev, 0, 1) != 0) {
+ dev_err(dev, "can't set alt interface.\n");
+ return -EIO;
+ }
+
+ usb_init_urb(&cdev->ep1_in_urb);
+ usb_init_urb(&cdev->midi_out_urb);
+
+ usb_fill_bulk_urb(&cdev->ep1_in_urb, usb_dev,
+ usb_rcvbulkpipe(usb_dev, 0x1),
+ cdev->ep1_in_buf, EP1_BUFSIZE,
+ usb_ep1_command_reply_dispatch, cdev);
+
+ usb_fill_bulk_urb(&cdev->midi_out_urb, usb_dev,
+ usb_sndbulkpipe(usb_dev, 0x1),
+ cdev->midi_out_buf, EP1_BUFSIZE,
+ snd_usb_caiaq_midi_output_done, cdev);
+
+ init_waitqueue_head(&cdev->ep1_wait_queue);
+ init_waitqueue_head(&cdev->prepare_wait_queue);
+
+ if (usb_submit_urb(&cdev->ep1_in_urb, GFP_KERNEL) != 0)
+ return -EIO;
+
+ err = snd_usb_caiaq_send_command(cdev, EP1_CMD_GET_DEVICE_INFO, NULL, 0);
+ if (err)
+ return err;
+
+ if (!wait_event_timeout(cdev->ep1_wait_queue, cdev->spec_received, HZ))
+ return -ENODEV;
+
+ usb_string(usb_dev, usb_dev->descriptor.iManufacturer,
+ cdev->vendor_name, CAIAQ_USB_STR_LEN);
+
+ usb_string(usb_dev, usb_dev->descriptor.iProduct,
+ cdev->product_name, CAIAQ_USB_STR_LEN);
+
+ strlcpy(card->driver, MODNAME, sizeof(card->driver));
+ strlcpy(card->shortname, cdev->product_name, sizeof(card->shortname));
+ strlcpy(card->mixername, cdev->product_name, sizeof(card->mixername));
+
+ /* if the id was not passed as module option, fill it with a shortened
+ * version of the product string which does not contain any
+ * whitespaces */
+
+ if (*card->id == '\0') {
+ char id[sizeof(card->id)];
+
+ memset(id, 0, sizeof(id));
+
+ for (c = card->shortname, len = 0;
+ *c && len < sizeof(card->id); c++)
+ if (*c != ' ')
+ id[len++] = *c;
+
+ snd_card_set_id(card, id);
+ }
+
+ usb_make_path(usb_dev, usbpath, sizeof(usbpath));
+ snprintf(card->longname, sizeof(card->longname), "%s %s (%s)",
+ cdev->vendor_name, cdev->product_name, usbpath);
+
+ setup_card(cdev);
+ return 0;
+}
+
+static int snd_probe(struct usb_interface *intf,
+ const struct usb_device_id *id)
+{
+ int ret;
+ struct snd_card *card = NULL;
+ struct usb_device *usb_dev = interface_to_usbdev(intf);
+
+ ret = create_card(usb_dev, intf, &card);
+
+ if (ret < 0)
+ return ret;
+
+ usb_set_intfdata(intf, card);
+ ret = init_card(caiaqdev(card));
+ if (ret < 0) {
+ dev_err(&usb_dev->dev, "unable to init card! (ret=%d)\n", ret);
+ snd_card_free(card);
+ return ret;
+ }
+
+ return 0;
+}
+
+static void snd_disconnect(struct usb_interface *intf)
+{
+ struct snd_card *card = usb_get_intfdata(intf);
+ struct device *dev = intf->usb_dev;
+ struct snd_usb_caiaqdev *cdev;
+
+ if (!card)
+ return;
+
+ cdev = caiaqdev(card);
+ dev_dbg(dev, "%s(%p)\n", __func__, intf);
+
+ snd_card_disconnect(card);
+
+#ifdef CONFIG_SND_USB_CAIAQ_INPUT
+ snd_usb_caiaq_input_free(cdev);
+#endif
+ snd_usb_caiaq_audio_free(cdev);
+
+ usb_kill_urb(&cdev->ep1_in_urb);
+ usb_kill_urb(&cdev->midi_out_urb);
+
+ snd_card_free(card);
+ usb_reset_device(interface_to_usbdev(intf));
+}
+
+
+MODULE_DEVICE_TABLE(usb, snd_usb_id_table);
+static struct usb_driver snd_usb_driver = {
+ .name = MODNAME,
+ .probe = snd_probe,
+ .disconnect = snd_disconnect,
+ .id_table = snd_usb_id_table,
+};
+
+module_usb_driver(snd_usb_driver);
diff --git a/sound/usb/caiaq/device.h b/sound/usb/caiaq/device.h
new file mode 100644
index 000000000..ab0f7520a
--- /dev/null
+++ b/sound/usb/caiaq/device.h
@@ -0,0 +1,137 @@
+#ifndef CAIAQ_DEVICE_H
+#define CAIAQ_DEVICE_H
+
+#include "../usbaudio.h"
+
+#define USB_VID_NATIVEINSTRUMENTS 0x17cc
+
+#define USB_PID_RIGKONTROL2 0x1969
+#define USB_PID_RIGKONTROL3 0x1940
+#define USB_PID_KORECONTROLLER 0x4711
+#define USB_PID_KORECONTROLLER2 0x4712
+#define USB_PID_AK1 0x0815
+#define USB_PID_AUDIO2DJ 0x041c
+#define USB_PID_AUDIO4DJ 0x0839
+#define USB_PID_AUDIO8DJ 0x1978
+#define USB_PID_SESSIONIO 0x1915
+#define USB_PID_GUITARRIGMOBILE 0x0d8d
+#define USB_PID_TRAKTORKONTROLX1 0x2305
+#define USB_PID_TRAKTORKONTROLS4 0xbaff
+#define USB_PID_TRAKTORAUDIO2 0x041d
+#define USB_PID_MASCHINECONTROLLER 0x0808
+
+#define EP1_BUFSIZE 64
+#define EP4_BUFSIZE 512
+#define CAIAQ_USB_STR_LEN 0xff
+#define MAX_STREAMS 32
+
+#define MODNAME "snd-usb-caiaq"
+
+#define EP1_CMD_GET_DEVICE_INFO 0x1
+#define EP1_CMD_READ_ERP 0x2
+#define EP1_CMD_READ_ANALOG 0x3
+#define EP1_CMD_READ_IO 0x4
+#define EP1_CMD_WRITE_IO 0x5
+#define EP1_CMD_MIDI_READ 0x6
+#define EP1_CMD_MIDI_WRITE 0x7
+#define EP1_CMD_AUDIO_PARAMS 0x9
+#define EP1_CMD_AUTO_MSG 0xb
+#define EP1_CMD_DIMM_LEDS 0xc
+
+struct caiaq_device_spec {
+ unsigned short fw_version;
+ unsigned char hw_subtype;
+ unsigned char num_erp;
+ unsigned char num_analog_in;
+ unsigned char num_digital_in;
+ unsigned char num_digital_out;
+ unsigned char num_analog_audio_out;
+ unsigned char num_analog_audio_in;
+ unsigned char num_digital_audio_out;
+ unsigned char num_digital_audio_in;
+ unsigned char num_midi_out;
+ unsigned char num_midi_in;
+ unsigned char data_alignment;
+} __attribute__ ((packed));
+
+struct snd_usb_caiaq_cb_info;
+
+struct snd_usb_caiaqdev {
+ struct snd_usb_audio chip;
+
+ struct urb ep1_in_urb;
+ struct urb midi_out_urb;
+ struct urb **data_urbs_in;
+ struct urb **data_urbs_out;
+ struct snd_usb_caiaq_cb_info *data_cb_info;
+
+ unsigned char ep1_in_buf[EP1_BUFSIZE];
+ unsigned char ep1_out_buf[EP1_BUFSIZE];
+ unsigned char midi_out_buf[EP1_BUFSIZE];
+
+ struct caiaq_device_spec spec;
+ spinlock_t spinlock;
+ wait_queue_head_t ep1_wait_queue;
+ wait_queue_head_t prepare_wait_queue;
+ int spec_received, audio_parm_answer;
+ int midi_out_active;
+
+ char vendor_name[CAIAQ_USB_STR_LEN];
+ char product_name[CAIAQ_USB_STR_LEN];
+
+ int n_streams, n_audio_in, n_audio_out;
+ int streaming, first_packet, output_running;
+ int audio_in_buf_pos[MAX_STREAMS];
+ int audio_out_buf_pos[MAX_STREAMS];
+ int period_in_count[MAX_STREAMS];
+ int period_out_count[MAX_STREAMS];
+ int input_panic, output_panic, warned;
+ char *audio_in_buf, *audio_out_buf;
+ unsigned int samplerates, bpp;
+ unsigned long outurb_active_mask;
+
+ struct snd_pcm_substream *sub_playback[MAX_STREAMS];
+ struct snd_pcm_substream *sub_capture[MAX_STREAMS];
+
+ /* Controls */
+ unsigned char control_state[256];
+ unsigned char ep8_out_buf[2];
+
+ /* Linux input */
+#ifdef CONFIG_SND_USB_CAIAQ_INPUT
+ struct input_dev *input_dev;
+ char phys[64]; /* physical device path */
+ unsigned short keycode[128];
+ struct urb *ep4_in_urb;
+ unsigned char ep4_in_buf[EP4_BUFSIZE];
+#endif
+
+ /* ALSA */
+ struct snd_pcm *pcm;
+ struct snd_pcm_hardware pcm_info;
+ struct snd_rawmidi *rmidi;
+ struct snd_rawmidi_substream *midi_receive_substream;
+ struct snd_rawmidi_substream *midi_out_substream;
+};
+
+struct snd_usb_caiaq_cb_info {
+ struct snd_usb_caiaqdev *cdev;
+ int index;
+};
+
+#define caiaqdev(c) ((struct snd_usb_caiaqdev*)(c)->private_data)
+#define caiaqdev_to_dev(d) (d->chip.card->dev)
+
+int snd_usb_caiaq_set_audio_params (struct snd_usb_caiaqdev *cdev, int rate, int depth, int bbp);
+int snd_usb_caiaq_set_auto_msg (struct snd_usb_caiaqdev *cdev, int digital, int analog, int erp);
+int snd_usb_caiaq_send_command(struct snd_usb_caiaqdev *cdev,
+ unsigned char command,
+ const unsigned char *buffer,
+ int len);
+int snd_usb_caiaq_send_command_bank(struct snd_usb_caiaqdev *cdev,
+ unsigned char command,
+ unsigned char bank,
+ const unsigned char *buffer,
+ int len);
+
+#endif /* CAIAQ_DEVICE_H */
diff --git a/sound/usb/caiaq/input.c b/sound/usb/caiaq/input.c
new file mode 100644
index 000000000..4b3fb91de
--- /dev/null
+++ b/sound/usb/caiaq/input.c
@@ -0,0 +1,846 @@
+/*
+ * Copyright (c) 2006,2007 Daniel Mack, Tim Ruetz
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*/
+
+#include <linux/device.h>
+#include <linux/gfp.h>
+#include <linux/init.h>
+#include <linux/usb.h>
+#include <linux/usb/input.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+
+#include "device.h"
+#include "input.h"
+
+static unsigned short keycode_ak1[] = { KEY_C, KEY_B, KEY_A };
+static unsigned short keycode_rk2[] = { KEY_1, KEY_2, KEY_3, KEY_4,
+ KEY_5, KEY_6, KEY_7 };
+static unsigned short keycode_rk3[] = { KEY_1, KEY_2, KEY_3, KEY_4,
+ KEY_5, KEY_6, KEY_7, KEY_8, KEY_9 };
+
+static unsigned short keycode_kore[] = {
+ KEY_FN_F1, /* "menu" */
+ KEY_FN_F7, /* "lcd backlight */
+ KEY_FN_F2, /* "control" */
+ KEY_FN_F3, /* "enter" */
+ KEY_FN_F4, /* "view" */
+ KEY_FN_F5, /* "esc" */
+ KEY_FN_F6, /* "sound" */
+ KEY_FN_F8, /* array spacer, never triggered. */
+ KEY_RIGHT,
+ KEY_DOWN,
+ KEY_UP,
+ KEY_LEFT,
+ KEY_SOUND, /* "listen" */
+ KEY_RECORD,
+ KEY_PLAYPAUSE,
+ KEY_STOP,
+ BTN_4, /* 8 softkeys */
+ BTN_3,
+ BTN_2,
+ BTN_1,
+ BTN_8,
+ BTN_7,
+ BTN_6,
+ BTN_5,
+ KEY_BRL_DOT4, /* touch sensitive knobs */
+ KEY_BRL_DOT3,
+ KEY_BRL_DOT2,
+ KEY_BRL_DOT1,
+ KEY_BRL_DOT8,
+ KEY_BRL_DOT7,
+ KEY_BRL_DOT6,
+ KEY_BRL_DOT5
+};
+
+#define MASCHINE_BUTTONS (42)
+#define MASCHINE_BUTTON(X) ((X) + BTN_MISC)
+#define MASCHINE_PADS (16)
+#define MASCHINE_PAD(X) ((X) + ABS_PRESSURE)
+
+static unsigned short keycode_maschine[] = {
+ MASCHINE_BUTTON(40), /* mute */
+ MASCHINE_BUTTON(39), /* solo */
+ MASCHINE_BUTTON(38), /* select */
+ MASCHINE_BUTTON(37), /* duplicate */
+ MASCHINE_BUTTON(36), /* navigate */
+ MASCHINE_BUTTON(35), /* pad mode */
+ MASCHINE_BUTTON(34), /* pattern */
+ MASCHINE_BUTTON(33), /* scene */
+ KEY_RESERVED, /* spacer */
+
+ MASCHINE_BUTTON(30), /* rec */
+ MASCHINE_BUTTON(31), /* erase */
+ MASCHINE_BUTTON(32), /* shift */
+ MASCHINE_BUTTON(28), /* grid */
+ MASCHINE_BUTTON(27), /* > */
+ MASCHINE_BUTTON(26), /* < */
+ MASCHINE_BUTTON(25), /* restart */
+
+ MASCHINE_BUTTON(21), /* E */
+ MASCHINE_BUTTON(22), /* F */
+ MASCHINE_BUTTON(23), /* G */
+ MASCHINE_BUTTON(24), /* H */
+ MASCHINE_BUTTON(20), /* D */
+ MASCHINE_BUTTON(19), /* C */
+ MASCHINE_BUTTON(18), /* B */
+ MASCHINE_BUTTON(17), /* A */
+
+ MASCHINE_BUTTON(0), /* control */
+ MASCHINE_BUTTON(2), /* browse */
+ MASCHINE_BUTTON(4), /* < */
+ MASCHINE_BUTTON(6), /* snap */
+ MASCHINE_BUTTON(7), /* autowrite */
+ MASCHINE_BUTTON(5), /* > */
+ MASCHINE_BUTTON(3), /* sampling */
+ MASCHINE_BUTTON(1), /* step */
+
+ MASCHINE_BUTTON(15), /* 8 softkeys */
+ MASCHINE_BUTTON(14),
+ MASCHINE_BUTTON(13),
+ MASCHINE_BUTTON(12),
+ MASCHINE_BUTTON(11),
+ MASCHINE_BUTTON(10),
+ MASCHINE_BUTTON(9),
+ MASCHINE_BUTTON(8),
+
+ MASCHINE_BUTTON(16), /* note repeat */
+ MASCHINE_BUTTON(29) /* play */
+};
+
+#define KONTROLX1_INPUTS (40)
+#define KONTROLS4_BUTTONS (12 * 8)
+#define KONTROLS4_AXIS (46)
+
+#define KONTROLS4_BUTTON(X) ((X) + BTN_MISC)
+#define KONTROLS4_ABS(X) ((X) + ABS_HAT0X)
+
+#define DEG90 (range / 2)
+#define DEG180 (range)
+#define DEG270 (DEG90 + DEG180)
+#define DEG360 (DEG180 * 2)
+#define HIGH_PEAK (268)
+#define LOW_PEAK (-7)
+
+/* some of these devices have endless rotation potentiometers
+ * built in which use two tapers, 90 degrees phase shifted.
+ * this algorithm decodes them to one single value, ranging
+ * from 0 to 999 */
+static unsigned int decode_erp(unsigned char a, unsigned char b)
+{
+ int weight_a, weight_b;
+ int pos_a, pos_b;
+ int ret;
+ int range = HIGH_PEAK - LOW_PEAK;
+ int mid_value = (HIGH_PEAK + LOW_PEAK) / 2;
+
+ weight_b = abs(mid_value - a) - (range / 2 - 100) / 2;
+
+ if (weight_b < 0)
+ weight_b = 0;
+
+ if (weight_b > 100)
+ weight_b = 100;
+
+ weight_a = 100 - weight_b;
+
+ if (a < mid_value) {
+ /* 0..90 and 270..360 degrees */
+ pos_b = b - LOW_PEAK + DEG270;
+ if (pos_b >= DEG360)
+ pos_b -= DEG360;
+ } else
+ /* 90..270 degrees */
+ pos_b = HIGH_PEAK - b + DEG90;
+
+
+ if (b > mid_value)
+ /* 0..180 degrees */
+ pos_a = a - LOW_PEAK;
+ else
+ /* 180..360 degrees */
+ pos_a = HIGH_PEAK - a + DEG180;
+
+ /* interpolate both slider values, depending on weight factors */
+ /* 0..99 x DEG360 */
+ ret = pos_a * weight_a + pos_b * weight_b;
+
+ /* normalize to 0..999 */
+ ret *= 10;
+ ret /= DEG360;
+
+ if (ret < 0)
+ ret += 1000;
+
+ if (ret >= 1000)
+ ret -= 1000;
+
+ return ret;
+}
+
+#undef DEG90
+#undef DEG180
+#undef DEG270
+#undef DEG360
+#undef HIGH_PEAK
+#undef LOW_PEAK
+
+static inline void snd_caiaq_input_report_abs(struct snd_usb_caiaqdev *cdev,
+ int axis, const unsigned char *buf,
+ int offset)
+{
+ input_report_abs(cdev->input_dev, axis,
+ (buf[offset * 2] << 8) | buf[offset * 2 + 1]);
+}
+
+static void snd_caiaq_input_read_analog(struct snd_usb_caiaqdev *cdev,
+ const unsigned char *buf,
+ unsigned int len)
+{
+ struct input_dev *input_dev = cdev->input_dev;
+
+ switch (cdev->chip.usb_id) {
+ case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_RIGKONTROL2):
+ snd_caiaq_input_report_abs(cdev, ABS_X, buf, 2);
+ snd_caiaq_input_report_abs(cdev, ABS_Y, buf, 0);
+ snd_caiaq_input_report_abs(cdev, ABS_Z, buf, 1);
+ break;
+ case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_RIGKONTROL3):
+ case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_KORECONTROLLER):
+ case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_KORECONTROLLER2):
+ snd_caiaq_input_report_abs(cdev, ABS_X, buf, 0);
+ snd_caiaq_input_report_abs(cdev, ABS_Y, buf, 1);
+ snd_caiaq_input_report_abs(cdev, ABS_Z, buf, 2);
+ break;
+ case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_TRAKTORKONTROLX1):
+ snd_caiaq_input_report_abs(cdev, ABS_HAT0X, buf, 4);
+ snd_caiaq_input_report_abs(cdev, ABS_HAT0Y, buf, 2);
+ snd_caiaq_input_report_abs(cdev, ABS_HAT1X, buf, 6);
+ snd_caiaq_input_report_abs(cdev, ABS_HAT1Y, buf, 1);
+ snd_caiaq_input_report_abs(cdev, ABS_HAT2X, buf, 7);
+ snd_caiaq_input_report_abs(cdev, ABS_HAT2Y, buf, 0);
+ snd_caiaq_input_report_abs(cdev, ABS_HAT3X, buf, 5);
+ snd_caiaq_input_report_abs(cdev, ABS_HAT3Y, buf, 3);
+ break;
+ }
+
+ input_sync(input_dev);
+}
+
+static void snd_caiaq_input_read_erp(struct snd_usb_caiaqdev *cdev,
+ const char *buf, unsigned int len)
+{
+ struct input_dev *input_dev = cdev->input_dev;
+ int i;
+
+ switch (cdev->chip.usb_id) {
+ case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_AK1):
+ i = decode_erp(buf[0], buf[1]);
+ input_report_abs(input_dev, ABS_X, i);
+ input_sync(input_dev);
+ break;
+ case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_KORECONTROLLER):
+ case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_KORECONTROLLER2):
+ i = decode_erp(buf[7], buf[5]);
+ input_report_abs(input_dev, ABS_HAT0X, i);
+ i = decode_erp(buf[12], buf[14]);
+ input_report_abs(input_dev, ABS_HAT0Y, i);
+ i = decode_erp(buf[15], buf[13]);
+ input_report_abs(input_dev, ABS_HAT1X, i);
+ i = decode_erp(buf[0], buf[2]);
+ input_report_abs(input_dev, ABS_HAT1Y, i);
+ i = decode_erp(buf[3], buf[1]);
+ input_report_abs(input_dev, ABS_HAT2X, i);
+ i = decode_erp(buf[8], buf[10]);
+ input_report_abs(input_dev, ABS_HAT2Y, i);
+ i = decode_erp(buf[11], buf[9]);
+ input_report_abs(input_dev, ABS_HAT3X, i);
+ i = decode_erp(buf[4], buf[6]);
+ input_report_abs(input_dev, ABS_HAT3Y, i);
+ input_sync(input_dev);
+ break;
+
+ case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_MASCHINECONTROLLER):
+ /* 4 under the left screen */
+ input_report_abs(input_dev, ABS_HAT0X, decode_erp(buf[21], buf[20]));
+ input_report_abs(input_dev, ABS_HAT0Y, decode_erp(buf[15], buf[14]));
+ input_report_abs(input_dev, ABS_HAT1X, decode_erp(buf[9], buf[8]));
+ input_report_abs(input_dev, ABS_HAT1Y, decode_erp(buf[3], buf[2]));
+
+ /* 4 under the right screen */
+ input_report_abs(input_dev, ABS_HAT2X, decode_erp(buf[19], buf[18]));
+ input_report_abs(input_dev, ABS_HAT2Y, decode_erp(buf[13], buf[12]));
+ input_report_abs(input_dev, ABS_HAT3X, decode_erp(buf[7], buf[6]));
+ input_report_abs(input_dev, ABS_HAT3Y, decode_erp(buf[1], buf[0]));
+
+ /* volume */
+ input_report_abs(input_dev, ABS_RX, decode_erp(buf[17], buf[16]));
+ /* tempo */
+ input_report_abs(input_dev, ABS_RY, decode_erp(buf[11], buf[10]));
+ /* swing */
+ input_report_abs(input_dev, ABS_RZ, decode_erp(buf[5], buf[4]));
+
+ input_sync(input_dev);
+ break;
+ }
+}
+
+static void snd_caiaq_input_read_io(struct snd_usb_caiaqdev *cdev,
+ unsigned char *buf, unsigned int len)
+{
+ struct input_dev *input_dev = cdev->input_dev;
+ unsigned short *keycode = input_dev->keycode;
+ int i;
+
+ if (!keycode)
+ return;
+
+ if (input_dev->id.product == USB_PID_RIGKONTROL2)
+ for (i = 0; i < len; i++)
+ buf[i] = ~buf[i];
+
+ for (i = 0; i < input_dev->keycodemax && i < len * 8; i++)
+ input_report_key(input_dev, keycode[i],
+ buf[i / 8] & (1 << (i % 8)));
+
+ switch (cdev->chip.usb_id) {
+ case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_KORECONTROLLER):
+ case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_KORECONTROLLER2):
+ input_report_abs(cdev->input_dev, ABS_MISC, 255 - buf[4]);
+ break;
+ case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_TRAKTORKONTROLX1):
+ /* rotary encoders */
+ input_report_abs(cdev->input_dev, ABS_X, buf[5] & 0xf);
+ input_report_abs(cdev->input_dev, ABS_Y, buf[5] >> 4);
+ input_report_abs(cdev->input_dev, ABS_Z, buf[6] & 0xf);
+ input_report_abs(cdev->input_dev, ABS_MISC, buf[6] >> 4);
+ break;
+ }
+
+ input_sync(input_dev);
+}
+
+#define TKS4_MSGBLOCK_SIZE 16
+
+static void snd_usb_caiaq_tks4_dispatch(struct snd_usb_caiaqdev *cdev,
+ const unsigned char *buf,
+ unsigned int len)
+{
+ struct device *dev = caiaqdev_to_dev(cdev);
+
+ while (len) {
+ unsigned int i, block_id = (buf[0] << 8) | buf[1];
+
+ switch (block_id) {
+ case 0:
+ /* buttons */
+ for (i = 0; i < KONTROLS4_BUTTONS; i++)
+ input_report_key(cdev->input_dev, KONTROLS4_BUTTON(i),
+ (buf[4 + (i / 8)] >> (i % 8)) & 1);
+ break;
+
+ case 1:
+ /* left wheel */
+ input_report_abs(cdev->input_dev, KONTROLS4_ABS(36), buf[9] | ((buf[8] & 0x3) << 8));
+ /* right wheel */
+ input_report_abs(cdev->input_dev, KONTROLS4_ABS(37), buf[13] | ((buf[12] & 0x3) << 8));
+
+ /* rotary encoders */
+ input_report_abs(cdev->input_dev, KONTROLS4_ABS(38), buf[3] & 0xf);
+ input_report_abs(cdev->input_dev, KONTROLS4_ABS(39), buf[4] >> 4);
+ input_report_abs(cdev->input_dev, KONTROLS4_ABS(40), buf[4] & 0xf);
+ input_report_abs(cdev->input_dev, KONTROLS4_ABS(41), buf[5] >> 4);
+ input_report_abs(cdev->input_dev, KONTROLS4_ABS(42), buf[5] & 0xf);
+ input_report_abs(cdev->input_dev, KONTROLS4_ABS(43), buf[6] >> 4);
+ input_report_abs(cdev->input_dev, KONTROLS4_ABS(44), buf[6] & 0xf);
+ input_report_abs(cdev->input_dev, KONTROLS4_ABS(45), buf[7] >> 4);
+ input_report_abs(cdev->input_dev, KONTROLS4_ABS(46), buf[7] & 0xf);
+
+ break;
+ case 2:
+ /* Volume Fader Channel D */
+ snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(0), buf, 1);
+ /* Volume Fader Channel B */
+ snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(1), buf, 2);
+ /* Volume Fader Channel A */
+ snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(2), buf, 3);
+ /* Volume Fader Channel C */
+ snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(3), buf, 4);
+ /* Loop Volume */
+ snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(4), buf, 6);
+ /* Crossfader */
+ snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(7), buf, 7);
+
+ break;
+
+ case 3:
+ /* Tempo Fader R */
+ snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(6), buf, 3);
+ /* Tempo Fader L */
+ snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(5), buf, 4);
+ /* Mic Volume */
+ snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(8), buf, 6);
+ /* Cue Mix */
+ snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(9), buf, 7);
+
+ break;
+
+ case 4:
+ /* Wheel distance sensor L */
+ snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(10), buf, 1);
+ /* Wheel distance sensor R */
+ snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(11), buf, 2);
+ /* Channel D EQ - Filter */
+ snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(12), buf, 3);
+ /* Channel D EQ - Low */
+ snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(13), buf, 4);
+ /* Channel D EQ - Mid */
+ snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(14), buf, 5);
+ /* Channel D EQ - Hi */
+ snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(15), buf, 6);
+ /* FX2 - dry/wet */
+ snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(16), buf, 7);
+
+ break;
+
+ case 5:
+ /* FX2 - 1 */
+ snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(17), buf, 1);
+ /* FX2 - 2 */
+ snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(18), buf, 2);
+ /* FX2 - 3 */
+ snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(19), buf, 3);
+ /* Channel B EQ - Filter */
+ snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(20), buf, 4);
+ /* Channel B EQ - Low */
+ snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(21), buf, 5);
+ /* Channel B EQ - Mid */
+ snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(22), buf, 6);
+ /* Channel B EQ - Hi */
+ snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(23), buf, 7);
+
+ break;
+
+ case 6:
+ /* Channel A EQ - Filter */
+ snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(24), buf, 1);
+ /* Channel A EQ - Low */
+ snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(25), buf, 2);
+ /* Channel A EQ - Mid */
+ snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(26), buf, 3);
+ /* Channel A EQ - Hi */
+ snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(27), buf, 4);
+ /* Channel C EQ - Filter */
+ snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(28), buf, 5);
+ /* Channel C EQ - Low */
+ snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(29), buf, 6);
+ /* Channel C EQ - Mid */
+ snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(30), buf, 7);
+
+ break;
+
+ case 7:
+ /* Channel C EQ - Hi */
+ snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(31), buf, 1);
+ /* FX1 - wet/dry */
+ snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(32), buf, 2);
+ /* FX1 - 1 */
+ snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(33), buf, 3);
+ /* FX1 - 2 */
+ snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(34), buf, 4);
+ /* FX1 - 3 */
+ snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(35), buf, 5);
+
+ break;
+
+ default:
+ dev_dbg(dev, "%s(): bogus block (id %d)\n",
+ __func__, block_id);
+ return;
+ }
+
+ len -= TKS4_MSGBLOCK_SIZE;
+ buf += TKS4_MSGBLOCK_SIZE;
+ }
+
+ input_sync(cdev->input_dev);
+}
+
+#define MASCHINE_MSGBLOCK_SIZE 2
+
+static void snd_usb_caiaq_maschine_dispatch(struct snd_usb_caiaqdev *cdev,
+ const unsigned char *buf,
+ unsigned int len)
+{
+ unsigned int i, pad_id;
+ __le16 *pressure = (__le16 *) buf;
+
+ for (i = 0; i < MASCHINE_PADS; i++) {
+ pad_id = le16_to_cpu(*pressure) >> 12;
+ input_report_abs(cdev->input_dev, MASCHINE_PAD(pad_id),
+ le16_to_cpu(*pressure) & 0xfff);
+ pressure++;
+ }
+
+ input_sync(cdev->input_dev);
+}
+
+static void snd_usb_caiaq_ep4_reply_dispatch(struct urb *urb)
+{
+ struct snd_usb_caiaqdev *cdev = urb->context;
+ unsigned char *buf = urb->transfer_buffer;
+ struct device *dev = &urb->dev->dev;
+ int ret;
+
+ if (urb->status || !cdev || urb != cdev->ep4_in_urb)
+ return;
+
+ switch (cdev->chip.usb_id) {
+ case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_TRAKTORKONTROLX1):
+ if (urb->actual_length < 24)
+ goto requeue;
+
+ if (buf[0] & 0x3)
+ snd_caiaq_input_read_io(cdev, buf + 1, 7);
+
+ if (buf[0] & 0x4)
+ snd_caiaq_input_read_analog(cdev, buf + 8, 16);
+
+ break;
+
+ case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_TRAKTORKONTROLS4):
+ snd_usb_caiaq_tks4_dispatch(cdev, buf, urb->actual_length);
+ break;
+
+ case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_MASCHINECONTROLLER):
+ if (urb->actual_length < (MASCHINE_PADS * MASCHINE_MSGBLOCK_SIZE))
+ goto requeue;
+
+ snd_usb_caiaq_maschine_dispatch(cdev, buf, urb->actual_length);
+ break;
+ }
+
+requeue:
+ cdev->ep4_in_urb->actual_length = 0;
+ ret = usb_submit_urb(cdev->ep4_in_urb, GFP_ATOMIC);
+ if (ret < 0)
+ dev_err(dev, "unable to submit urb. OOM!?\n");
+}
+
+static int snd_usb_caiaq_input_open(struct input_dev *idev)
+{
+ struct snd_usb_caiaqdev *cdev = input_get_drvdata(idev);
+
+ if (!cdev)
+ return -EINVAL;
+
+ switch (cdev->chip.usb_id) {
+ case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_TRAKTORKONTROLX1):
+ case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_TRAKTORKONTROLS4):
+ case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_MASCHINECONTROLLER):
+ if (usb_submit_urb(cdev->ep4_in_urb, GFP_KERNEL) != 0)
+ return -EIO;
+ break;
+ }
+
+ return 0;
+}
+
+static void snd_usb_caiaq_input_close(struct input_dev *idev)
+{
+ struct snd_usb_caiaqdev *cdev = input_get_drvdata(idev);
+
+ if (!cdev)
+ return;
+
+ switch (cdev->chip.usb_id) {
+ case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_TRAKTORKONTROLX1):
+ case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_TRAKTORKONTROLS4):
+ case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_MASCHINECONTROLLER):
+ usb_kill_urb(cdev->ep4_in_urb);
+ break;
+ }
+}
+
+void snd_usb_caiaq_input_dispatch(struct snd_usb_caiaqdev *cdev,
+ char *buf,
+ unsigned int len)
+{
+ if (!cdev->input_dev || len < 1)
+ return;
+
+ switch (buf[0]) {
+ case EP1_CMD_READ_ANALOG:
+ snd_caiaq_input_read_analog(cdev, buf + 1, len - 1);
+ break;
+ case EP1_CMD_READ_ERP:
+ snd_caiaq_input_read_erp(cdev, buf + 1, len - 1);
+ break;
+ case EP1_CMD_READ_IO:
+ snd_caiaq_input_read_io(cdev, buf + 1, len - 1);
+ break;
+ }
+}
+
+int snd_usb_caiaq_input_init(struct snd_usb_caiaqdev *cdev)
+{
+ struct usb_device *usb_dev = cdev->chip.dev;
+ struct input_dev *input;
+ int i, ret = 0;
+
+ input = input_allocate_device();
+ if (!input)
+ return -ENOMEM;
+
+ usb_make_path(usb_dev, cdev->phys, sizeof(cdev->phys));
+ strlcat(cdev->phys, "/input0", sizeof(cdev->phys));
+
+ input->name = cdev->product_name;
+ input->phys = cdev->phys;
+ usb_to_input_id(usb_dev, &input->id);
+ input->dev.parent = &usb_dev->dev;
+
+ input_set_drvdata(input, cdev);
+
+ switch (cdev->chip.usb_id) {
+ case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_RIGKONTROL2):
+ input->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS);
+ input->absbit[0] = BIT_MASK(ABS_X) | BIT_MASK(ABS_Y) |
+ BIT_MASK(ABS_Z);
+ BUILD_BUG_ON(sizeof(cdev->keycode) < sizeof(keycode_rk2));
+ memcpy(cdev->keycode, keycode_rk2, sizeof(keycode_rk2));
+ input->keycodemax = ARRAY_SIZE(keycode_rk2);
+ input_set_abs_params(input, ABS_X, 0, 4096, 0, 10);
+ input_set_abs_params(input, ABS_Y, 0, 4096, 0, 10);
+ input_set_abs_params(input, ABS_Z, 0, 4096, 0, 10);
+ snd_usb_caiaq_set_auto_msg(cdev, 1, 10, 0);
+ break;
+ case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_RIGKONTROL3):
+ input->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS);
+ input->absbit[0] = BIT_MASK(ABS_X) | BIT_MASK(ABS_Y) |
+ BIT_MASK(ABS_Z);
+ BUILD_BUG_ON(sizeof(cdev->keycode) < sizeof(keycode_rk3));
+ memcpy(cdev->keycode, keycode_rk3, sizeof(keycode_rk3));
+ input->keycodemax = ARRAY_SIZE(keycode_rk3);
+ input_set_abs_params(input, ABS_X, 0, 1024, 0, 10);
+ input_set_abs_params(input, ABS_Y, 0, 1024, 0, 10);
+ input_set_abs_params(input, ABS_Z, 0, 1024, 0, 10);
+ snd_usb_caiaq_set_auto_msg(cdev, 1, 10, 0);
+ break;
+ case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_AK1):
+ input->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS);
+ input->absbit[0] = BIT_MASK(ABS_X);
+ BUILD_BUG_ON(sizeof(cdev->keycode) < sizeof(keycode_ak1));
+ memcpy(cdev->keycode, keycode_ak1, sizeof(keycode_ak1));
+ input->keycodemax = ARRAY_SIZE(keycode_ak1);
+ input_set_abs_params(input, ABS_X, 0, 999, 0, 10);
+ snd_usb_caiaq_set_auto_msg(cdev, 1, 0, 5);
+ break;
+ case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_KORECONTROLLER):
+ case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_KORECONTROLLER2):
+ input->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS);
+ input->absbit[0] = BIT_MASK(ABS_HAT0X) | BIT_MASK(ABS_HAT0Y) |
+ BIT_MASK(ABS_HAT1X) | BIT_MASK(ABS_HAT1Y) |
+ BIT_MASK(ABS_HAT2X) | BIT_MASK(ABS_HAT2Y) |
+ BIT_MASK(ABS_HAT3X) | BIT_MASK(ABS_HAT3Y) |
+ BIT_MASK(ABS_X) | BIT_MASK(ABS_Y) |
+ BIT_MASK(ABS_Z);
+ input->absbit[BIT_WORD(ABS_MISC)] |= BIT_MASK(ABS_MISC);
+ BUILD_BUG_ON(sizeof(cdev->keycode) < sizeof(keycode_kore));
+ memcpy(cdev->keycode, keycode_kore, sizeof(keycode_kore));
+ input->keycodemax = ARRAY_SIZE(keycode_kore);
+ input_set_abs_params(input, ABS_HAT0X, 0, 999, 0, 10);
+ input_set_abs_params(input, ABS_HAT0Y, 0, 999, 0, 10);
+ input_set_abs_params(input, ABS_HAT1X, 0, 999, 0, 10);
+ input_set_abs_params(input, ABS_HAT1Y, 0, 999, 0, 10);
+ input_set_abs_params(input, ABS_HAT2X, 0, 999, 0, 10);
+ input_set_abs_params(input, ABS_HAT2Y, 0, 999, 0, 10);
+ input_set_abs_params(input, ABS_HAT3X, 0, 999, 0, 10);
+ input_set_abs_params(input, ABS_HAT3Y, 0, 999, 0, 10);
+ input_set_abs_params(input, ABS_X, 0, 4096, 0, 10);
+ input_set_abs_params(input, ABS_Y, 0, 4096, 0, 10);
+ input_set_abs_params(input, ABS_Z, 0, 4096, 0, 10);
+ input_set_abs_params(input, ABS_MISC, 0, 255, 0, 1);
+ snd_usb_caiaq_set_auto_msg(cdev, 1, 10, 5);
+ break;
+ case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_TRAKTORKONTROLX1):
+ input->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS);
+ input->absbit[0] = BIT_MASK(ABS_HAT0X) | BIT_MASK(ABS_HAT0Y) |
+ BIT_MASK(ABS_HAT1X) | BIT_MASK(ABS_HAT1Y) |
+ BIT_MASK(ABS_HAT2X) | BIT_MASK(ABS_HAT2Y) |
+ BIT_MASK(ABS_HAT3X) | BIT_MASK(ABS_HAT3Y) |
+ BIT_MASK(ABS_X) | BIT_MASK(ABS_Y) |
+ BIT_MASK(ABS_Z);
+ input->absbit[BIT_WORD(ABS_MISC)] |= BIT_MASK(ABS_MISC);
+ BUILD_BUG_ON(sizeof(cdev->keycode) < KONTROLX1_INPUTS);
+ for (i = 0; i < KONTROLX1_INPUTS; i++)
+ cdev->keycode[i] = BTN_MISC + i;
+ input->keycodemax = KONTROLX1_INPUTS;
+
+ /* analog potentiometers */
+ input_set_abs_params(input, ABS_HAT0X, 0, 4096, 0, 10);
+ input_set_abs_params(input, ABS_HAT0Y, 0, 4096, 0, 10);
+ input_set_abs_params(input, ABS_HAT1X, 0, 4096, 0, 10);
+ input_set_abs_params(input, ABS_HAT1Y, 0, 4096, 0, 10);
+ input_set_abs_params(input, ABS_HAT2X, 0, 4096, 0, 10);
+ input_set_abs_params(input, ABS_HAT2Y, 0, 4096, 0, 10);
+ input_set_abs_params(input, ABS_HAT3X, 0, 4096, 0, 10);
+ input_set_abs_params(input, ABS_HAT3Y, 0, 4096, 0, 10);
+
+ /* rotary encoders */
+ input_set_abs_params(input, ABS_X, 0, 0xf, 0, 1);
+ input_set_abs_params(input, ABS_Y, 0, 0xf, 0, 1);
+ input_set_abs_params(input, ABS_Z, 0, 0xf, 0, 1);
+ input_set_abs_params(input, ABS_MISC, 0, 0xf, 0, 1);
+
+ cdev->ep4_in_urb = usb_alloc_urb(0, GFP_KERNEL);
+ if (!cdev->ep4_in_urb) {
+ ret = -ENOMEM;
+ goto exit_free_idev;
+ }
+
+ usb_fill_bulk_urb(cdev->ep4_in_urb, usb_dev,
+ usb_rcvbulkpipe(usb_dev, 0x4),
+ cdev->ep4_in_buf, EP4_BUFSIZE,
+ snd_usb_caiaq_ep4_reply_dispatch, cdev);
+
+ snd_usb_caiaq_set_auto_msg(cdev, 1, 10, 5);
+
+ break;
+
+ case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_TRAKTORKONTROLS4):
+ input->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS);
+ BUILD_BUG_ON(sizeof(cdev->keycode) < KONTROLS4_BUTTONS);
+ for (i = 0; i < KONTROLS4_BUTTONS; i++)
+ cdev->keycode[i] = KONTROLS4_BUTTON(i);
+ input->keycodemax = KONTROLS4_BUTTONS;
+
+ for (i = 0; i < KONTROLS4_AXIS; i++) {
+ int axis = KONTROLS4_ABS(i);
+ input->absbit[BIT_WORD(axis)] |= BIT_MASK(axis);
+ }
+
+ /* 36 analog potentiometers and faders */
+ for (i = 0; i < 36; i++)
+ input_set_abs_params(input, KONTROLS4_ABS(i), 0, 0xfff, 0, 10);
+
+ /* 2 encoder wheels */
+ input_set_abs_params(input, KONTROLS4_ABS(36), 0, 0x3ff, 0, 1);
+ input_set_abs_params(input, KONTROLS4_ABS(37), 0, 0x3ff, 0, 1);
+
+ /* 9 rotary encoders */
+ for (i = 0; i < 9; i++)
+ input_set_abs_params(input, KONTROLS4_ABS(38+i), 0, 0xf, 0, 1);
+
+ cdev->ep4_in_urb = usb_alloc_urb(0, GFP_KERNEL);
+ if (!cdev->ep4_in_urb) {
+ ret = -ENOMEM;
+ goto exit_free_idev;
+ }
+
+ usb_fill_bulk_urb(cdev->ep4_in_urb, usb_dev,
+ usb_rcvbulkpipe(usb_dev, 0x4),
+ cdev->ep4_in_buf, EP4_BUFSIZE,
+ snd_usb_caiaq_ep4_reply_dispatch, cdev);
+
+ snd_usb_caiaq_set_auto_msg(cdev, 1, 10, 5);
+
+ break;
+
+ case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_MASCHINECONTROLLER):
+ input->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS);
+ input->absbit[0] = BIT_MASK(ABS_HAT0X) | BIT_MASK(ABS_HAT0Y) |
+ BIT_MASK(ABS_HAT1X) | BIT_MASK(ABS_HAT1Y) |
+ BIT_MASK(ABS_HAT2X) | BIT_MASK(ABS_HAT2Y) |
+ BIT_MASK(ABS_HAT3X) | BIT_MASK(ABS_HAT3Y) |
+ BIT_MASK(ABS_RX) | BIT_MASK(ABS_RY) |
+ BIT_MASK(ABS_RZ);
+
+ BUILD_BUG_ON(sizeof(cdev->keycode) < sizeof(keycode_maschine));
+ memcpy(cdev->keycode, keycode_maschine, sizeof(keycode_maschine));
+ input->keycodemax = ARRAY_SIZE(keycode_maschine);
+
+ for (i = 0; i < MASCHINE_PADS; i++) {
+ input->absbit[0] |= MASCHINE_PAD(i);
+ input_set_abs_params(input, MASCHINE_PAD(i), 0, 0xfff, 5, 10);
+ }
+
+ input_set_abs_params(input, ABS_HAT0X, 0, 999, 0, 10);
+ input_set_abs_params(input, ABS_HAT0Y, 0, 999, 0, 10);
+ input_set_abs_params(input, ABS_HAT1X, 0, 999, 0, 10);
+ input_set_abs_params(input, ABS_HAT1Y, 0, 999, 0, 10);
+ input_set_abs_params(input, ABS_HAT2X, 0, 999, 0, 10);
+ input_set_abs_params(input, ABS_HAT2Y, 0, 999, 0, 10);
+ input_set_abs_params(input, ABS_HAT3X, 0, 999, 0, 10);
+ input_set_abs_params(input, ABS_HAT3Y, 0, 999, 0, 10);
+ input_set_abs_params(input, ABS_RX, 0, 999, 0, 10);
+ input_set_abs_params(input, ABS_RY, 0, 999, 0, 10);
+ input_set_abs_params(input, ABS_RZ, 0, 999, 0, 10);
+
+ cdev->ep4_in_urb = usb_alloc_urb(0, GFP_KERNEL);
+ if (!cdev->ep4_in_urb) {
+ ret = -ENOMEM;
+ goto exit_free_idev;
+ }
+
+ usb_fill_bulk_urb(cdev->ep4_in_urb, usb_dev,
+ usb_rcvbulkpipe(usb_dev, 0x4),
+ cdev->ep4_in_buf, EP4_BUFSIZE,
+ snd_usb_caiaq_ep4_reply_dispatch, cdev);
+
+ snd_usb_caiaq_set_auto_msg(cdev, 1, 10, 5);
+ break;
+
+ default:
+ /* no input methods supported on this device */
+ goto exit_free_idev;
+ }
+
+ input->open = snd_usb_caiaq_input_open;
+ input->close = snd_usb_caiaq_input_close;
+ input->keycode = cdev->keycode;
+ input->keycodesize = sizeof(unsigned short);
+ for (i = 0; i < input->keycodemax; i++)
+ __set_bit(cdev->keycode[i], input->keybit);
+
+ cdev->input_dev = input;
+
+ ret = input_register_device(input);
+ if (ret < 0)
+ goto exit_free_idev;
+
+ return 0;
+
+exit_free_idev:
+ input_free_device(input);
+ cdev->input_dev = NULL;
+ return ret;
+}
+
+void snd_usb_caiaq_input_free(struct snd_usb_caiaqdev *cdev)
+{
+ if (!cdev || !cdev->input_dev)
+ return;
+
+ usb_kill_urb(cdev->ep4_in_urb);
+ usb_free_urb(cdev->ep4_in_urb);
+ cdev->ep4_in_urb = NULL;
+
+ input_unregister_device(cdev->input_dev);
+ cdev->input_dev = NULL;
+}
diff --git a/sound/usb/caiaq/input.h b/sound/usb/caiaq/input.h
new file mode 100644
index 000000000..6014e2713
--- /dev/null
+++ b/sound/usb/caiaq/input.h
@@ -0,0 +1,8 @@
+#ifndef CAIAQ_INPUT_H
+#define CAIAQ_INPUT_H
+
+void snd_usb_caiaq_input_dispatch(struct snd_usb_caiaqdev *cdev, char *buf, unsigned int len);
+int snd_usb_caiaq_input_init(struct snd_usb_caiaqdev *cdev);
+void snd_usb_caiaq_input_free(struct snd_usb_caiaqdev *cdev);
+
+#endif
diff --git a/sound/usb/caiaq/midi.c b/sound/usb/caiaq/midi.c
new file mode 100644
index 000000000..2d7588461
--- /dev/null
+++ b/sound/usb/caiaq/midi.c
@@ -0,0 +1,175 @@
+/*
+ * Copyright (c) 2006,2007 Daniel Mack
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*/
+
+#include <linux/device.h>
+#include <linux/usb.h>
+#include <linux/gfp.h>
+#include <sound/rawmidi.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+
+#include "device.h"
+#include "midi.h"
+
+static int snd_usb_caiaq_midi_input_open(struct snd_rawmidi_substream *substream)
+{
+ return 0;
+}
+
+static int snd_usb_caiaq_midi_input_close(struct snd_rawmidi_substream *substream)
+{
+ return 0;
+}
+
+static void snd_usb_caiaq_midi_input_trigger(struct snd_rawmidi_substream *substream, int up)
+{
+ struct snd_usb_caiaqdev *cdev = substream->rmidi->private_data;
+
+ if (!cdev)
+ return;
+
+ cdev->midi_receive_substream = up ? substream : NULL;
+}
+
+
+static int snd_usb_caiaq_midi_output_open(struct snd_rawmidi_substream *substream)
+{
+ return 0;
+}
+
+static int snd_usb_caiaq_midi_output_close(struct snd_rawmidi_substream *substream)
+{
+ struct snd_usb_caiaqdev *cdev = substream->rmidi->private_data;
+ if (cdev->midi_out_active) {
+ usb_kill_urb(&cdev->midi_out_urb);
+ cdev->midi_out_active = 0;
+ }
+ return 0;
+}
+
+static void snd_usb_caiaq_midi_send(struct snd_usb_caiaqdev *cdev,
+ struct snd_rawmidi_substream *substream)
+{
+ int len, ret;
+ struct device *dev = caiaqdev_to_dev(cdev);
+
+ cdev->midi_out_buf[0] = EP1_CMD_MIDI_WRITE;
+ cdev->midi_out_buf[1] = 0; /* port */
+ len = snd_rawmidi_transmit(substream, cdev->midi_out_buf + 3,
+ EP1_BUFSIZE - 3);
+
+ if (len <= 0)
+ return;
+
+ cdev->midi_out_buf[2] = len;
+ cdev->midi_out_urb.transfer_buffer_length = len+3;
+
+ ret = usb_submit_urb(&cdev->midi_out_urb, GFP_ATOMIC);
+ if (ret < 0)
+ dev_err(dev,
+ "snd_usb_caiaq_midi_send(%p): usb_submit_urb() failed,"
+ "ret=%d, len=%d\n", substream, ret, len);
+ else
+ cdev->midi_out_active = 1;
+}
+
+static void snd_usb_caiaq_midi_output_trigger(struct snd_rawmidi_substream *substream, int up)
+{
+ struct snd_usb_caiaqdev *cdev = substream->rmidi->private_data;
+
+ if (up) {
+ cdev->midi_out_substream = substream;
+ if (!cdev->midi_out_active)
+ snd_usb_caiaq_midi_send(cdev, substream);
+ } else {
+ cdev->midi_out_substream = NULL;
+ }
+}
+
+
+static struct snd_rawmidi_ops snd_usb_caiaq_midi_output =
+{
+ .open = snd_usb_caiaq_midi_output_open,
+ .close = snd_usb_caiaq_midi_output_close,
+ .trigger = snd_usb_caiaq_midi_output_trigger,
+};
+
+static struct snd_rawmidi_ops snd_usb_caiaq_midi_input =
+{
+ .open = snd_usb_caiaq_midi_input_open,
+ .close = snd_usb_caiaq_midi_input_close,
+ .trigger = snd_usb_caiaq_midi_input_trigger,
+};
+
+void snd_usb_caiaq_midi_handle_input(struct snd_usb_caiaqdev *cdev,
+ int port, const char *buf, int len)
+{
+ if (!cdev->midi_receive_substream)
+ return;
+
+ snd_rawmidi_receive(cdev->midi_receive_substream, buf, len);
+}
+
+int snd_usb_caiaq_midi_init(struct snd_usb_caiaqdev *device)
+{
+ int ret;
+ struct snd_rawmidi *rmidi;
+
+ ret = snd_rawmidi_new(device->chip.card, device->product_name, 0,
+ device->spec.num_midi_out,
+ device->spec.num_midi_in,
+ &rmidi);
+
+ if (ret < 0)
+ return ret;
+
+ strlcpy(rmidi->name, device->product_name, sizeof(rmidi->name));
+
+ rmidi->info_flags = SNDRV_RAWMIDI_INFO_DUPLEX;
+ rmidi->private_data = device;
+
+ if (device->spec.num_midi_out > 0) {
+ rmidi->info_flags |= SNDRV_RAWMIDI_INFO_OUTPUT;
+ snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT,
+ &snd_usb_caiaq_midi_output);
+ }
+
+ if (device->spec.num_midi_in > 0) {
+ rmidi->info_flags |= SNDRV_RAWMIDI_INFO_INPUT;
+ snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT,
+ &snd_usb_caiaq_midi_input);
+ }
+
+ device->rmidi = rmidi;
+
+ return 0;
+}
+
+void snd_usb_caiaq_midi_output_done(struct urb* urb)
+{
+ struct snd_usb_caiaqdev *cdev = urb->context;
+
+ cdev->midi_out_active = 0;
+ if (urb->status != 0)
+ return;
+
+ if (!cdev->midi_out_substream)
+ return;
+
+ snd_usb_caiaq_midi_send(cdev, cdev->midi_out_substream);
+}
diff --git a/sound/usb/caiaq/midi.h b/sound/usb/caiaq/midi.h
new file mode 100644
index 000000000..60bf3442b
--- /dev/null
+++ b/sound/usb/caiaq/midi.h
@@ -0,0 +1,9 @@
+#ifndef CAIAQ_MIDI_H
+#define CAIAQ_MIDI_H
+
+int snd_usb_caiaq_midi_init(struct snd_usb_caiaqdev *cdev);
+void snd_usb_caiaq_midi_handle_input(struct snd_usb_caiaqdev *cdev,
+ int port, const char *buf, int len);
+void snd_usb_caiaq_midi_output_done(struct urb *urb);
+
+#endif /* CAIAQ_MIDI_H */
diff --git a/sound/usb/card.c b/sound/usb/card.c
new file mode 100644
index 000000000..1fab97788
--- /dev/null
+++ b/sound/usb/card.c
@@ -0,0 +1,771 @@
+/*
+ * (Tentative) USB Audio Driver for ALSA
+ *
+ * Copyright (c) 2002 by Takashi Iwai <tiwai@suse.de>
+ *
+ * Many codes borrowed from audio.c by
+ * Alan Cox (alan@lxorguk.ukuu.org.uk)
+ * Thomas Sailer (sailer@ife.ee.ethz.ch)
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ *
+ * NOTES:
+ *
+ * - the linked URBs would be preferred but not used so far because of
+ * the instability of unlinking.
+ * - type II is not supported properly. there is no device which supports
+ * this type *correctly*. SB extigy looks as if it supports, but it's
+ * indeed an AC3 stream packed in SPDIF frames (i.e. no real AC3 stream).
+ */
+
+
+#include <linux/bitops.h>
+#include <linux/init.h>
+#include <linux/list.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <linux/ctype.h>
+#include <linux/usb.h>
+#include <linux/moduleparam.h>
+#include <linux/mutex.h>
+#include <linux/usb/audio.h>
+#include <linux/usb/audio-v2.h>
+#include <linux/module.h>
+
+#include <sound/control.h>
+#include <sound/core.h>
+#include <sound/info.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/initval.h>
+
+#include "usbaudio.h"
+#include "card.h"
+#include "midi.h"
+#include "mixer.h"
+#include "proc.h"
+#include "quirks.h"
+#include "endpoint.h"
+#include "helper.h"
+#include "debug.h"
+#include "pcm.h"
+#include "format.h"
+#include "power.h"
+#include "stream.h"
+
+MODULE_AUTHOR("Takashi Iwai <tiwai@suse.de>");
+MODULE_DESCRIPTION("USB Audio");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("{{Generic,USB Audio}}");
+
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 0-MAX */
+static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* ID for this card */
+static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP;/* Enable this card */
+/* Vendor/product IDs for this card */
+static int vid[SNDRV_CARDS] = { [0 ... (SNDRV_CARDS-1)] = -1 };
+static int pid[SNDRV_CARDS] = { [0 ... (SNDRV_CARDS-1)] = -1 };
+static int device_setup[SNDRV_CARDS]; /* device parameter for this card */
+static bool ignore_ctl_error;
+static bool autoclock = true;
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for the USB audio adapter.");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for the USB audio adapter.");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "Enable USB audio adapter.");
+module_param_array(vid, int, NULL, 0444);
+MODULE_PARM_DESC(vid, "Vendor ID for the USB audio device.");
+module_param_array(pid, int, NULL, 0444);
+MODULE_PARM_DESC(pid, "Product ID for the USB audio device.");
+module_param_array(device_setup, int, NULL, 0444);
+MODULE_PARM_DESC(device_setup, "Specific device setup (if needed).");
+module_param(ignore_ctl_error, bool, 0444);
+MODULE_PARM_DESC(ignore_ctl_error,
+ "Ignore errors from USB controller for mixer interfaces.");
+module_param(autoclock, bool, 0444);
+MODULE_PARM_DESC(autoclock, "Enable auto-clock selection for UAC2 devices (default: yes).");
+
+/*
+ * we keep the snd_usb_audio_t instances by ourselves for merging
+ * the all interfaces on the same card as one sound device.
+ */
+
+static DEFINE_MUTEX(register_mutex);
+static struct snd_usb_audio *usb_chip[SNDRV_CARDS];
+static struct usb_driver usb_audio_driver;
+
+/*
+ * disconnect streams
+ * called from usb_audio_disconnect()
+ */
+static void snd_usb_stream_disconnect(struct snd_usb_stream *as)
+{
+ int idx;
+ struct snd_usb_substream *subs;
+
+ for (idx = 0; idx < 2; idx++) {
+ subs = &as->substream[idx];
+ if (!subs->num_formats)
+ continue;
+ subs->interface = -1;
+ subs->data_endpoint = NULL;
+ subs->sync_endpoint = NULL;
+ }
+}
+
+static int snd_usb_create_stream(struct snd_usb_audio *chip, int ctrlif, int interface)
+{
+ struct usb_device *dev = chip->dev;
+ struct usb_host_interface *alts;
+ struct usb_interface_descriptor *altsd;
+ struct usb_interface *iface = usb_ifnum_to_if(dev, interface);
+
+ if (!iface) {
+ dev_err(&dev->dev, "%u:%d : does not exist\n",
+ ctrlif, interface);
+ return -EINVAL;
+ }
+
+ alts = &iface->altsetting[0];
+ altsd = get_iface_desc(alts);
+
+ /*
+ * Android with both accessory and audio interfaces enabled gets the
+ * interface numbers wrong.
+ */
+ if ((chip->usb_id == USB_ID(0x18d1, 0x2d04) ||
+ chip->usb_id == USB_ID(0x18d1, 0x2d05)) &&
+ interface == 0 &&
+ altsd->bInterfaceClass == USB_CLASS_VENDOR_SPEC &&
+ altsd->bInterfaceSubClass == USB_SUBCLASS_VENDOR_SPEC) {
+ interface = 2;
+ iface = usb_ifnum_to_if(dev, interface);
+ if (!iface)
+ return -EINVAL;
+ alts = &iface->altsetting[0];
+ altsd = get_iface_desc(alts);
+ }
+
+ if (usb_interface_claimed(iface)) {
+ dev_dbg(&dev->dev, "%d:%d: skipping, already claimed\n",
+ ctrlif, interface);
+ return -EINVAL;
+ }
+
+ if ((altsd->bInterfaceClass == USB_CLASS_AUDIO ||
+ altsd->bInterfaceClass == USB_CLASS_VENDOR_SPEC) &&
+ altsd->bInterfaceSubClass == USB_SUBCLASS_MIDISTREAMING) {
+ int err = snd_usbmidi_create(chip->card, iface,
+ &chip->midi_list, NULL);
+ if (err < 0) {
+ dev_err(&dev->dev,
+ "%u:%d: cannot create sequencer device\n",
+ ctrlif, interface);
+ return -EINVAL;
+ }
+ usb_driver_claim_interface(&usb_audio_driver, iface, (void *)-1L);
+
+ return 0;
+ }
+
+ if ((altsd->bInterfaceClass != USB_CLASS_AUDIO &&
+ altsd->bInterfaceClass != USB_CLASS_VENDOR_SPEC) ||
+ altsd->bInterfaceSubClass != USB_SUBCLASS_AUDIOSTREAMING) {
+ dev_dbg(&dev->dev,
+ "%u:%d: skipping non-supported interface %d\n",
+ ctrlif, interface, altsd->bInterfaceClass);
+ /* skip non-supported classes */
+ return -EINVAL;
+ }
+
+ if (snd_usb_get_speed(dev) == USB_SPEED_LOW) {
+ dev_err(&dev->dev, "low speed audio streaming not supported\n");
+ return -EINVAL;
+ }
+
+ if (! snd_usb_parse_audio_interface(chip, interface)) {
+ usb_set_interface(dev, interface, 0); /* reset the current interface */
+ usb_driver_claim_interface(&usb_audio_driver, iface, (void *)-1L);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+/*
+ * parse audio control descriptor and create pcm/midi streams
+ */
+static int snd_usb_create_streams(struct snd_usb_audio *chip, int ctrlif)
+{
+ struct usb_device *dev = chip->dev;
+ struct usb_host_interface *host_iface;
+ struct usb_interface_descriptor *altsd;
+ void *control_header;
+ int i, protocol;
+
+ /* find audiocontrol interface */
+ host_iface = &usb_ifnum_to_if(dev, ctrlif)->altsetting[0];
+ control_header = snd_usb_find_csint_desc(host_iface->extra,
+ host_iface->extralen,
+ NULL, UAC_HEADER);
+ altsd = get_iface_desc(host_iface);
+ protocol = altsd->bInterfaceProtocol;
+
+ if (!control_header) {
+ dev_err(&dev->dev, "cannot find UAC_HEADER\n");
+ return -EINVAL;
+ }
+
+ switch (protocol) {
+ default:
+ dev_warn(&dev->dev,
+ "unknown interface protocol %#02x, assuming v1\n",
+ protocol);
+ /* fall through */
+
+ case UAC_VERSION_1: {
+ struct uac1_ac_header_descriptor *h1 = control_header;
+
+ if (!h1->bInCollection) {
+ dev_info(&dev->dev, "skipping empty audio interface (v1)\n");
+ return -EINVAL;
+ }
+
+ if (h1->bLength < sizeof(*h1) + h1->bInCollection) {
+ dev_err(&dev->dev, "invalid UAC_HEADER (v1)\n");
+ return -EINVAL;
+ }
+
+ for (i = 0; i < h1->bInCollection; i++)
+ snd_usb_create_stream(chip, ctrlif, h1->baInterfaceNr[i]);
+
+ break;
+ }
+
+ case UAC_VERSION_2: {
+ struct usb_interface_assoc_descriptor *assoc =
+ usb_ifnum_to_if(dev, ctrlif)->intf_assoc;
+
+ if (!assoc) {
+ /*
+ * Firmware writers cannot count to three. So to find
+ * the IAD on the NuForce UDH-100, also check the next
+ * interface.
+ */
+ struct usb_interface *iface =
+ usb_ifnum_to_if(dev, ctrlif + 1);
+ if (iface &&
+ iface->intf_assoc &&
+ iface->intf_assoc->bFunctionClass == USB_CLASS_AUDIO &&
+ iface->intf_assoc->bFunctionProtocol == UAC_VERSION_2)
+ assoc = iface->intf_assoc;
+ }
+
+ if (!assoc) {
+ dev_err(&dev->dev, "Audio class v2 interfaces need an interface association\n");
+ return -EINVAL;
+ }
+
+ for (i = 0; i < assoc->bInterfaceCount; i++) {
+ int intf = assoc->bFirstInterface + i;
+
+ if (intf != ctrlif)
+ snd_usb_create_stream(chip, ctrlif, intf);
+ }
+
+ break;
+ }
+ }
+
+ return 0;
+}
+
+/*
+ * free the chip instance
+ *
+ * here we have to do not much, since pcm and controls are already freed
+ *
+ */
+
+static int snd_usb_audio_free(struct snd_usb_audio *chip)
+{
+ struct snd_usb_endpoint *ep, *n;
+
+ list_for_each_entry_safe(ep, n, &chip->ep_list, list)
+ snd_usb_endpoint_free(ep);
+
+ mutex_destroy(&chip->mutex);
+ kfree(chip);
+ return 0;
+}
+
+static int snd_usb_audio_dev_free(struct snd_device *device)
+{
+ struct snd_usb_audio *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_interface *intf,
+ struct usb_device *dev, int idx,
+ const struct snd_usb_audio_quirk *quirk,
+ struct snd_usb_audio **rchip)
+{
+ struct snd_card *card;
+ struct snd_usb_audio *chip;
+ int err, len;
+ char component[14];
+ static struct snd_device_ops ops = {
+ .dev_free = snd_usb_audio_dev_free,
+ };
+
+ *rchip = NULL;
+
+ switch (snd_usb_get_speed(dev)) {
+ case USB_SPEED_LOW:
+ case USB_SPEED_FULL:
+ case USB_SPEED_HIGH:
+ case USB_SPEED_WIRELESS:
+ case USB_SPEED_SUPER:
+ break;
+ default:
+ dev_err(&dev->dev, "unknown device speed %d\n", snd_usb_get_speed(dev));
+ return -ENXIO;
+ }
+
+ err = snd_card_new(&intf->dev, index[idx], id[idx], THIS_MODULE,
+ 0, &card);
+ if (err < 0) {
+ dev_err(&dev->dev, "cannot create card instance %d\n", idx);
+ return err;
+ }
+
+ chip = kzalloc(sizeof(*chip), GFP_KERNEL);
+ if (! chip) {
+ snd_card_free(card);
+ return -ENOMEM;
+ }
+
+ mutex_init(&chip->mutex);
+ init_rwsem(&chip->shutdown_rwsem);
+ chip->index = idx;
+ chip->dev = dev;
+ chip->card = card;
+ chip->setup = device_setup[idx];
+ chip->autoclock = autoclock;
+ chip->probing = 1;
+
+ chip->usb_id = USB_ID(le16_to_cpu(dev->descriptor.idVendor),
+ le16_to_cpu(dev->descriptor.idProduct));
+ INIT_LIST_HEAD(&chip->pcm_list);
+ INIT_LIST_HEAD(&chip->ep_list);
+ INIT_LIST_HEAD(&chip->midi_list);
+ INIT_LIST_HEAD(&chip->mixer_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",
+ USB_ID_VENDOR(chip->usb_id), USB_ID_PRODUCT(chip->usb_id));
+ snd_component_add(card, component);
+
+ /* retrieve the device string as shortname */
+ if (quirk && quirk->product_name && *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",
+ USB_ID_VENDOR(chip->usb_id),
+ USB_ID_PRODUCT(chip->usb_id));
+ }
+ }
+ strim(card->shortname);
+
+ /* retrieve the vendor and device strings as longname */
+ if (quirk && quirk->vendor_name && *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) {
+ strim(card->longname);
+ if (*card->longname)
+ strlcat(card->longname, " ", sizeof(card->longname));
+ }
+
+ strlcat(card->longname, card->shortname, sizeof(card->longname));
+
+ len = strlcat(card->longname, " at ", sizeof(card->longname));
+
+ if (len < sizeof(card->longname))
+ usb_make_path(dev, card->longname + len, sizeof(card->longname) - len);
+
+ switch (snd_usb_get_speed(dev)) {
+ case USB_SPEED_LOW:
+ strlcat(card->longname, ", low speed", sizeof(card->longname));
+ break;
+ case USB_SPEED_FULL:
+ strlcat(card->longname, ", full speed", sizeof(card->longname));
+ break;
+ case USB_SPEED_HIGH:
+ strlcat(card->longname, ", high speed", sizeof(card->longname));
+ break;
+ case USB_SPEED_SUPER:
+ strlcat(card->longname, ", super speed", sizeof(card->longname));
+ break;
+ default:
+ break;
+ }
+
+ snd_usb_audio_create_proc(chip);
+
+ *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 int usb_audio_probe(struct usb_interface *intf,
+ const struct usb_device_id *usb_id)
+{
+ struct usb_device *dev = interface_to_usbdev(intf);
+ const struct snd_usb_audio_quirk *quirk =
+ (const struct snd_usb_audio_quirk *)usb_id->driver_info;
+ struct snd_usb_audio *chip;
+ int i, err;
+ struct usb_host_interface *alts;
+ int ifnum;
+ u32 id;
+
+ alts = &intf->altsetting[0];
+ ifnum = get_iface_desc(alts)->bInterfaceNumber;
+ id = USB_ID(le16_to_cpu(dev->descriptor.idVendor),
+ le16_to_cpu(dev->descriptor.idProduct));
+ if (quirk && quirk->ifnum >= 0 && ifnum != quirk->ifnum)
+ return -ENXIO;
+
+ err = snd_usb_apply_boot_quirk(dev, intf, quirk);
+ if (err < 0)
+ return err;
+
+ /*
+ * found a config. now register to ALSA
+ */
+
+ /* check whether it's already registered */
+ chip = NULL;
+ mutex_lock(&register_mutex);
+ for (i = 0; i < SNDRV_CARDS; i++) {
+ if (usb_chip[i] && usb_chip[i]->dev == dev) {
+ if (usb_chip[i]->shutdown) {
+ dev_err(&dev->dev, "USB device is in the shutdown state, cannot create a card instance\n");
+ err = -EIO;
+ goto __error;
+ }
+ chip = usb_chip[i];
+ chip->probing = 1;
+ break;
+ }
+ }
+ if (! chip) {
+ /* it's a fresh one.
+ * now look for an empty slot and create a new card instance
+ */
+ for (i = 0; i < SNDRV_CARDS; i++)
+ if (enable[i] && ! usb_chip[i] &&
+ (vid[i] == -1 || vid[i] == USB_ID_VENDOR(id)) &&
+ (pid[i] == -1 || pid[i] == USB_ID_PRODUCT(id))) {
+ err = snd_usb_audio_create(intf, dev, i, quirk,
+ &chip);
+ if (err < 0)
+ goto __error;
+ chip->pm_intf = intf;
+ break;
+ }
+ if (!chip) {
+ dev_err(&dev->dev, "no available usb audio device\n");
+ err = -ENODEV;
+ goto __error;
+ }
+ }
+
+ /*
+ * For devices with more than one control interface, we assume the
+ * first contains the audio controls. We might need a more specific
+ * check here in the future.
+ */
+ if (!chip->ctrl_intf)
+ chip->ctrl_intf = alts;
+
+ chip->txfr_quirk = 0;
+ err = 1; /* continue */
+ if (quirk && quirk->ifnum != QUIRK_NO_INTERFACE) {
+ /* need some special handlings */
+ err = snd_usb_create_quirk(chip, intf, &usb_audio_driver, quirk);
+ if (err < 0)
+ goto __error;
+ }
+
+ if (err > 0) {
+ /* create normal USB audio interfaces */
+ err = snd_usb_create_streams(chip, ifnum);
+ if (err < 0)
+ goto __error;
+ err = snd_usb_create_mixer(chip, ifnum, ignore_ctl_error);
+ if (err < 0)
+ goto __error;
+ }
+
+ /* we are allowed to call snd_card_register() many times */
+ err = snd_card_register(chip->card);
+ if (err < 0)
+ goto __error;
+
+ usb_chip[chip->index] = chip;
+ chip->num_interfaces++;
+ chip->probing = 0;
+ usb_set_intfdata(intf, chip);
+ mutex_unlock(&register_mutex);
+ return 0;
+
+ __error:
+ if (chip) {
+ if (!chip->num_interfaces)
+ snd_card_free(chip->card);
+ chip->probing = 0;
+ }
+ mutex_unlock(&register_mutex);
+ return err;
+}
+
+/*
+ * we need to take care of counter, since disconnection can be called also
+ * many times as well as usb_audio_probe().
+ */
+static void usb_audio_disconnect(struct usb_interface *intf)
+{
+ struct snd_usb_audio *chip = usb_get_intfdata(intf);
+ struct snd_card *card;
+ struct list_head *p;
+ bool was_shutdown;
+
+ if (chip == (void *)-1L)
+ return;
+
+ card = chip->card;
+ down_write(&chip->shutdown_rwsem);
+ was_shutdown = chip->shutdown;
+ chip->shutdown = 1;
+ up_write(&chip->shutdown_rwsem);
+
+ mutex_lock(&register_mutex);
+ if (!was_shutdown) {
+ struct snd_usb_stream *as;
+ struct snd_usb_endpoint *ep;
+ struct usb_mixer_interface *mixer;
+
+ snd_card_disconnect(card);
+ /* release the pcm resources */
+ list_for_each_entry(as, &chip->pcm_list, list) {
+ snd_usb_stream_disconnect(as);
+ }
+ /* release the endpoint resources */
+ list_for_each_entry(ep, &chip->ep_list, list) {
+ snd_usb_endpoint_release(ep);
+ }
+ /* release the midi resources */
+ list_for_each(p, &chip->midi_list) {
+ snd_usbmidi_disconnect(p);
+ }
+ /* release mixer resources */
+ list_for_each_entry(mixer, &chip->mixer_list, list) {
+ snd_usb_mixer_disconnect(mixer);
+ }
+ }
+
+ chip->num_interfaces--;
+ if (chip->num_interfaces <= 0) {
+ usb_chip[chip->index] = NULL;
+ mutex_unlock(&register_mutex);
+ snd_card_free_when_closed(card);
+ } else {
+ mutex_unlock(&register_mutex);
+ }
+}
+
+#ifdef CONFIG_PM
+
+int snd_usb_autoresume(struct snd_usb_audio *chip)
+{
+ int err = -ENODEV;
+
+ down_read(&chip->shutdown_rwsem);
+ if (chip->probing && chip->in_pm)
+ err = 0;
+ else if (!chip->shutdown)
+ err = usb_autopm_get_interface(chip->pm_intf);
+ up_read(&chip->shutdown_rwsem);
+
+ return err;
+}
+
+void snd_usb_autosuspend(struct snd_usb_audio *chip)
+{
+ down_read(&chip->shutdown_rwsem);
+ if (!chip->shutdown && !chip->probing && !chip->in_pm)
+ usb_autopm_put_interface(chip->pm_intf);
+ up_read(&chip->shutdown_rwsem);
+}
+
+static int usb_audio_suspend(struct usb_interface *intf, pm_message_t message)
+{
+ struct snd_usb_audio *chip = usb_get_intfdata(intf);
+ struct snd_usb_stream *as;
+ struct usb_mixer_interface *mixer;
+ struct list_head *p;
+
+ if (chip == (void *)-1L)
+ return 0;
+
+ if (!PMSG_IS_AUTO(message)) {
+ snd_power_change_state(chip->card, SNDRV_CTL_POWER_D3hot);
+ if (!chip->num_suspended_intf++) {
+ list_for_each_entry(as, &chip->pcm_list, list) {
+ snd_pcm_suspend_all(as->pcm);
+ as->substream[0].need_setup_ep =
+ as->substream[1].need_setup_ep = true;
+ }
+ list_for_each(p, &chip->midi_list) {
+ snd_usbmidi_suspend(p);
+ }
+ }
+ } else {
+ /*
+ * otherwise we keep the rest of the system in the dark
+ * to keep this transparent
+ */
+ if (!chip->num_suspended_intf++)
+ chip->autosuspended = 1;
+ }
+
+ if (chip->num_suspended_intf == 1)
+ list_for_each_entry(mixer, &chip->mixer_list, list)
+ snd_usb_mixer_suspend(mixer);
+
+ return 0;
+}
+
+static int __usb_audio_resume(struct usb_interface *intf, bool reset_resume)
+{
+ struct snd_usb_audio *chip = usb_get_intfdata(intf);
+ struct usb_mixer_interface *mixer;
+ struct list_head *p;
+ int err = 0;
+
+ if (chip == (void *)-1L)
+ return 0;
+ if (--chip->num_suspended_intf)
+ return 0;
+
+ chip->in_pm = 1;
+ /*
+ * ALSA leaves material resumption to user space
+ * we just notify and restart the mixers
+ */
+ list_for_each_entry(mixer, &chip->mixer_list, list) {
+ err = snd_usb_mixer_resume(mixer, reset_resume);
+ if (err < 0)
+ goto err_out;
+ }
+
+ list_for_each(p, &chip->midi_list) {
+ snd_usbmidi_resume(p);
+ }
+
+ if (!chip->autosuspended)
+ snd_power_change_state(chip->card, SNDRV_CTL_POWER_D0);
+ chip->autosuspended = 0;
+
+err_out:
+ chip->in_pm = 0;
+ return err;
+}
+
+static int usb_audio_resume(struct usb_interface *intf)
+{
+ return __usb_audio_resume(intf, false);
+}
+
+static int usb_audio_reset_resume(struct usb_interface *intf)
+{
+ return __usb_audio_resume(intf, true);
+}
+#else
+#define usb_audio_suspend NULL
+#define usb_audio_resume NULL
+#define usb_audio_reset_resume NULL
+#endif /* CONFIG_PM */
+
+static struct usb_device_id usb_audio_ids [] = {
+#include "quirks-table.h"
+ { .match_flags = (USB_DEVICE_ID_MATCH_INT_CLASS | USB_DEVICE_ID_MATCH_INT_SUBCLASS),
+ .bInterfaceClass = USB_CLASS_AUDIO,
+ .bInterfaceSubClass = USB_SUBCLASS_AUDIOCONTROL },
+ { } /* Terminating entry */
+};
+MODULE_DEVICE_TABLE(usb, usb_audio_ids);
+
+/*
+ * entry point for linux usb interface
+ */
+
+static struct usb_driver usb_audio_driver = {
+ .name = "snd-usb-audio",
+ .probe = usb_audio_probe,
+ .disconnect = usb_audio_disconnect,
+ .suspend = usb_audio_suspend,
+ .resume = usb_audio_resume,
+ .reset_resume = usb_audio_reset_resume,
+ .id_table = usb_audio_ids,
+ .supports_autosuspend = 1,
+};
+
+module_usb_driver(usb_audio_driver);
diff --git a/sound/usb/card.h b/sound/usb/card.h
new file mode 100644
index 000000000..ef580b43f
--- /dev/null
+++ b/sound/usb/card.h
@@ -0,0 +1,169 @@
+#ifndef __USBAUDIO_CARD_H
+#define __USBAUDIO_CARD_H
+
+#define MAX_NR_RATES 1024
+#define MAX_PACKS 6 /* per URB */
+#define MAX_PACKS_HS (MAX_PACKS * 8) /* in high speed mode */
+#define MAX_URBS 12
+#define SYNC_URBS 4 /* always four urbs for sync */
+#define MAX_QUEUE 18 /* try not to exceed this queue length, in ms */
+
+struct audioformat {
+ struct list_head list;
+ u64 formats; /* ALSA format bits */
+ unsigned int channels; /* # channels */
+ unsigned int fmt_type; /* USB audio format type (1-3) */
+ unsigned int frame_size; /* samples per frame for non-audio */
+ int iface; /* interface number */
+ unsigned char altsetting; /* corresponding alternate setting */
+ unsigned char altset_idx; /* array index of altenate setting */
+ unsigned char attributes; /* corresponding attributes of cs endpoint */
+ unsigned char endpoint; /* endpoint */
+ unsigned char ep_attr; /* endpoint attributes */
+ unsigned char datainterval; /* log_2 of data packet interval */
+ unsigned char protocol; /* UAC_VERSION_1/2 */
+ unsigned int maxpacksize; /* max. packet size */
+ unsigned int rates; /* rate bitmasks */
+ unsigned int rate_min, rate_max; /* min/max rates */
+ unsigned int nr_rates; /* number of rate table entries */
+ unsigned int *rate_table; /* rate table */
+ unsigned char clock; /* associated clock */
+ struct snd_pcm_chmap_elem *chmap; /* (optional) channel map */
+ bool dsd_dop; /* add DOP headers in case of DSD samples */
+ bool dsd_bitrev; /* reverse the bits of each DSD sample */
+};
+
+struct snd_usb_substream;
+struct snd_usb_endpoint;
+
+struct snd_urb_ctx {
+ struct urb *urb;
+ unsigned int buffer_size; /* size of data buffer, if data URB */
+ struct snd_usb_substream *subs;
+ struct snd_usb_endpoint *ep;
+ int index; /* index for urb array */
+ int packets; /* number of packets per urb */
+ int packet_size[MAX_PACKS_HS]; /* size of packets for next submission */
+ struct list_head ready_list;
+};
+
+struct snd_usb_endpoint {
+ struct snd_usb_audio *chip;
+
+ int use_count;
+ int ep_num; /* the referenced endpoint number */
+ int type; /* SND_USB_ENDPOINT_TYPE_* */
+ unsigned long flags;
+
+ void (*prepare_data_urb) (struct snd_usb_substream *subs,
+ struct urb *urb);
+ void (*retire_data_urb) (struct snd_usb_substream *subs,
+ struct urb *urb);
+
+ struct snd_usb_substream *data_subs;
+ struct snd_usb_endpoint *sync_master;
+ struct snd_usb_endpoint *sync_slave;
+
+ struct snd_urb_ctx urb[MAX_URBS];
+
+ struct snd_usb_packet_info {
+ uint32_t packet_size[MAX_PACKS_HS];
+ int packets;
+ } next_packet[MAX_URBS];
+ int next_packet_read_pos, next_packet_write_pos;
+ struct list_head ready_playback_urbs;
+
+ unsigned int nurbs; /* # urbs */
+ unsigned long active_mask; /* bitmask of active urbs */
+ unsigned long unlink_mask; /* bitmask of unlinked urbs */
+ char *syncbuf; /* sync buffer for all sync URBs */
+ dma_addr_t sync_dma; /* DMA address of syncbuf */
+
+ unsigned int pipe; /* the data i/o pipe */
+ unsigned int freqn; /* nominal sampling rate in fs/fps in Q16.16 format */
+ unsigned int freqm; /* momentary sampling rate in fs/fps in Q16.16 format */
+ int freqshift; /* how much to shift the feedback value to get Q16.16 */
+ unsigned int freqmax; /* maximum sampling rate, used for buffer management */
+ unsigned int phase; /* phase accumulator */
+ unsigned int maxpacksize; /* max packet size in bytes */
+ unsigned int maxframesize; /* max packet size in frames */
+ unsigned int max_urb_frames; /* max URB size in frames */
+ unsigned int curpacksize; /* current packet size in bytes (for capture) */
+ unsigned int curframesize; /* current packet size in frames (for capture) */
+ unsigned int syncmaxsize; /* sync endpoint packet size */
+ unsigned int fill_max:1; /* fill max packet size always */
+ unsigned int udh01_fb_quirk:1; /* corrupted feedback data */
+ unsigned int datainterval; /* log_2 of data packet interval */
+ unsigned int syncinterval; /* P for adaptive mode, 0 otherwise */
+ unsigned char silence_value;
+ unsigned int stride;
+ int iface, altsetting;
+ int skip_packets; /* quirks for devices to ignore the first n packets
+ in a stream */
+
+ spinlock_t lock;
+ struct list_head list;
+};
+
+struct snd_usb_substream {
+ struct snd_usb_stream *stream;
+ struct usb_device *dev;
+ struct snd_pcm_substream *pcm_substream;
+ int direction; /* playback or capture */
+ int interface; /* current interface */
+ int endpoint; /* assigned endpoint */
+ struct audioformat *cur_audiofmt; /* current audioformat pointer (for hw_params callback) */
+ snd_pcm_format_t pcm_format; /* current audio format (for hw_params callback) */
+ unsigned int channels; /* current number of channels (for hw_params callback) */
+ unsigned int channels_max; /* max channels in the all audiofmts */
+ unsigned int cur_rate; /* current rate (for hw_params callback) */
+ unsigned int period_bytes; /* current period bytes (for hw_params callback) */
+ unsigned int period_frames; /* current frames per period */
+ unsigned int buffer_periods; /* current periods per buffer */
+ unsigned int altset_idx; /* USB data format: index of alternate setting */
+ unsigned int txfr_quirk:1; /* allow sub-frame alignment */
+ unsigned int fmt_type; /* USB audio format type (1-3) */
+ unsigned int pkt_offset_adj; /* Bytes to drop from beginning of packets (for non-compliant devices) */
+
+ unsigned int running: 1; /* running status */
+
+ unsigned int hwptr_done; /* processed byte position in the buffer */
+ unsigned int transfer_done; /* processed frames since last period update */
+ unsigned int frame_limit; /* limits number of packets in URB */
+
+ /* data and sync endpoints for this stream */
+ unsigned int ep_num; /* the endpoint number */
+ struct snd_usb_endpoint *data_endpoint;
+ struct snd_usb_endpoint *sync_endpoint;
+ unsigned long flags;
+ bool need_setup_ep; /* (re)configure EP at prepare? */
+ unsigned int speed; /* USB_SPEED_XXX */
+
+ u64 formats; /* format bitmasks (all or'ed) */
+ unsigned int num_formats; /* number of supported audio formats (list) */
+ struct list_head fmt_list; /* format list */
+ struct snd_pcm_hw_constraint_list rate_list; /* limited rates */
+ spinlock_t lock;
+
+ int last_frame_number; /* stored frame number */
+ int last_delay; /* stored delay */
+
+ struct {
+ int marker;
+ int channel;
+ int byte_idx;
+ } dsd_dop;
+
+ bool trigger_tstamp_pending_update; /* trigger timestamp being updated from initial estimate */
+};
+
+struct snd_usb_stream {
+ struct snd_usb_audio *chip;
+ struct snd_pcm *pcm;
+ int pcm_index;
+ unsigned int fmt_type; /* USB audio format type (1-3) */
+ struct snd_usb_substream substream[2];
+ struct list_head list;
+};
+
+#endif /* __USBAUDIO_CARD_H */
diff --git a/sound/usb/clock.c b/sound/usb/clock.c
new file mode 100644
index 000000000..2ed260b10
--- /dev/null
+++ b/sound/usb/clock.c
@@ -0,0 +1,427 @@
+/*
+ * Clock domain and sample rate management functions
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+
+#include <linux/bitops.h>
+#include <linux/init.h>
+#include <linux/string.h>
+#include <linux/usb.h>
+#include <linux/usb/audio.h>
+#include <linux/usb/audio-v2.h>
+
+#include <sound/core.h>
+#include <sound/info.h>
+#include <sound/pcm.h>
+
+#include "usbaudio.h"
+#include "card.h"
+#include "helper.h"
+#include "clock.h"
+#include "quirks.h"
+
+static struct uac_clock_source_descriptor *
+ snd_usb_find_clock_source(struct usb_host_interface *ctrl_iface,
+ int clock_id)
+{
+ struct uac_clock_source_descriptor *cs = NULL;
+
+ while ((cs = snd_usb_find_csint_desc(ctrl_iface->extra,
+ ctrl_iface->extralen,
+ cs, UAC2_CLOCK_SOURCE))) {
+ if (cs->bClockID == clock_id)
+ return cs;
+ }
+
+ return NULL;
+}
+
+static struct uac_clock_selector_descriptor *
+ snd_usb_find_clock_selector(struct usb_host_interface *ctrl_iface,
+ int clock_id)
+{
+ struct uac_clock_selector_descriptor *cs = NULL;
+
+ while ((cs = snd_usb_find_csint_desc(ctrl_iface->extra,
+ ctrl_iface->extralen,
+ cs, UAC2_CLOCK_SELECTOR))) {
+ if (cs->bClockID == clock_id)
+ return cs;
+ }
+
+ return NULL;
+}
+
+static struct uac_clock_multiplier_descriptor *
+ snd_usb_find_clock_multiplier(struct usb_host_interface *ctrl_iface,
+ int clock_id)
+{
+ struct uac_clock_multiplier_descriptor *cs = NULL;
+
+ while ((cs = snd_usb_find_csint_desc(ctrl_iface->extra,
+ ctrl_iface->extralen,
+ cs, UAC2_CLOCK_MULTIPLIER))) {
+ if (cs->bClockID == clock_id)
+ return cs;
+ }
+
+ return NULL;
+}
+
+static int uac_clock_selector_get_val(struct snd_usb_audio *chip, int selector_id)
+{
+ unsigned char buf;
+ int ret;
+
+ ret = snd_usb_ctl_msg(chip->dev, usb_rcvctrlpipe(chip->dev, 0),
+ UAC2_CS_CUR,
+ USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_IN,
+ UAC2_CX_CLOCK_SELECTOR << 8,
+ snd_usb_ctrl_intf(chip) | (selector_id << 8),
+ &buf, sizeof(buf));
+
+ if (ret < 0)
+ return ret;
+
+ return buf;
+}
+
+static int uac_clock_selector_set_val(struct snd_usb_audio *chip, int selector_id,
+ unsigned char pin)
+{
+ int ret;
+
+ ret = snd_usb_ctl_msg(chip->dev, usb_sndctrlpipe(chip->dev, 0),
+ UAC2_CS_CUR,
+ USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_OUT,
+ UAC2_CX_CLOCK_SELECTOR << 8,
+ snd_usb_ctrl_intf(chip) | (selector_id << 8),
+ &pin, sizeof(pin));
+ if (ret < 0)
+ return ret;
+
+ if (ret != sizeof(pin)) {
+ usb_audio_err(chip,
+ "setting selector (id %d) unexpected length %d\n",
+ selector_id, ret);
+ return -EINVAL;
+ }
+
+ ret = uac_clock_selector_get_val(chip, selector_id);
+ if (ret < 0)
+ return ret;
+
+ if (ret != pin) {
+ usb_audio_err(chip,
+ "setting selector (id %d) to %x failed (current: %d)\n",
+ selector_id, pin, ret);
+ return -EINVAL;
+ }
+
+ return ret;
+}
+
+static bool uac_clock_source_is_valid(struct snd_usb_audio *chip, int source_id)
+{
+ int err;
+ unsigned char data;
+ struct usb_device *dev = chip->dev;
+ struct uac_clock_source_descriptor *cs_desc =
+ snd_usb_find_clock_source(chip->ctrl_intf, source_id);
+
+ if (!cs_desc)
+ return 0;
+
+ /* If a clock source can't tell us whether it's valid, we assume it is */
+ if (!uac2_control_is_readable(cs_desc->bmControls,
+ UAC2_CS_CONTROL_CLOCK_VALID - 1))
+ return 1;
+
+ err = snd_usb_ctl_msg(dev, usb_rcvctrlpipe(dev, 0), UAC2_CS_CUR,
+ USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_IN,
+ UAC2_CS_CONTROL_CLOCK_VALID << 8,
+ snd_usb_ctrl_intf(chip) | (source_id << 8),
+ &data, sizeof(data));
+
+ if (err < 0) {
+ dev_warn(&dev->dev,
+ "%s(): cannot get clock validity for id %d\n",
+ __func__, source_id);
+ return 0;
+ }
+
+ return !!data;
+}
+
+static int __uac_clock_find_source(struct snd_usb_audio *chip,
+ int entity_id, unsigned long *visited,
+ bool validate)
+{
+ struct uac_clock_source_descriptor *source;
+ struct uac_clock_selector_descriptor *selector;
+ struct uac_clock_multiplier_descriptor *multiplier;
+
+ entity_id &= 0xff;
+
+ if (test_and_set_bit(entity_id, visited)) {
+ usb_audio_warn(chip,
+ "%s(): recursive clock topology detected, id %d.\n",
+ __func__, entity_id);
+ return -EINVAL;
+ }
+
+ /* first, see if the ID we're looking for is a clock source already */
+ source = snd_usb_find_clock_source(chip->ctrl_intf, entity_id);
+ if (source) {
+ entity_id = source->bClockID;
+ if (validate && !uac_clock_source_is_valid(chip, entity_id)) {
+ usb_audio_err(chip,
+ "clock source %d is not valid, cannot use\n",
+ entity_id);
+ return -ENXIO;
+ }
+ return entity_id;
+ }
+
+ selector = snd_usb_find_clock_selector(chip->ctrl_intf, entity_id);
+ if (selector) {
+ int ret, i, cur;
+
+ /* the entity ID we are looking for is a selector.
+ * find out what it currently selects */
+ ret = uac_clock_selector_get_val(chip, selector->bClockID);
+ if (ret < 0)
+ return ret;
+
+ /* Selector values are one-based */
+
+ if (ret > selector->bNrInPins || ret < 1) {
+ usb_audio_err(chip,
+ "%s(): selector reported illegal value, id %d, ret %d\n",
+ __func__, selector->bClockID, ret);
+
+ return -EINVAL;
+ }
+
+ cur = ret;
+ ret = __uac_clock_find_source(chip, selector->baCSourceID[ret - 1],
+ visited, validate);
+ if (!validate || ret > 0 || !chip->autoclock)
+ return ret;
+
+ /* The current clock source is invalid, try others. */
+ for (i = 1; i <= selector->bNrInPins; i++) {
+ int err;
+
+ if (i == cur)
+ continue;
+
+ ret = __uac_clock_find_source(chip, selector->baCSourceID[i - 1],
+ visited, true);
+ if (ret < 0)
+ continue;
+
+ err = uac_clock_selector_set_val(chip, entity_id, i);
+ if (err < 0)
+ continue;
+
+ usb_audio_info(chip,
+ "found and selected valid clock source %d\n",
+ ret);
+ return ret;
+ }
+
+ return -ENXIO;
+ }
+
+ /* FIXME: multipliers only act as pass-thru element for now */
+ multiplier = snd_usb_find_clock_multiplier(chip->ctrl_intf, entity_id);
+ if (multiplier)
+ return __uac_clock_find_source(chip, multiplier->bCSourceID,
+ visited, validate);
+
+ return -EINVAL;
+}
+
+/*
+ * For all kinds of sample rate settings and other device queries,
+ * the clock source (end-leaf) must be used. However, clock selectors,
+ * clock multipliers and sample rate converters may be specified as
+ * clock source input to terminal. This functions walks the clock path
+ * to its end and tries to find the source.
+ *
+ * The 'visited' bitfield is used internally to detect recursive loops.
+ *
+ * Returns the clock source UnitID (>=0) on success, or an error.
+ */
+int snd_usb_clock_find_source(struct snd_usb_audio *chip, int entity_id,
+ bool validate)
+{
+ DECLARE_BITMAP(visited, 256);
+ memset(visited, 0, sizeof(visited));
+ return __uac_clock_find_source(chip, entity_id, visited, validate);
+}
+
+static int set_sample_rate_v1(struct snd_usb_audio *chip, int iface,
+ struct usb_host_interface *alts,
+ struct audioformat *fmt, int rate)
+{
+ struct usb_device *dev = chip->dev;
+ unsigned int ep;
+ unsigned char data[3];
+ int err, crate;
+
+ ep = get_endpoint(alts, 0)->bEndpointAddress;
+
+ /* if endpoint doesn't have sampling rate control, bail out */
+ if (!(fmt->attributes & UAC_EP_CS_ATTR_SAMPLE_RATE))
+ return 0;
+
+ data[0] = rate;
+ data[1] = rate >> 8;
+ data[2] = rate >> 16;
+ if ((err = snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), UAC_SET_CUR,
+ USB_TYPE_CLASS | USB_RECIP_ENDPOINT | USB_DIR_OUT,
+ UAC_EP_CS_ATTR_SAMPLE_RATE << 8, ep,
+ data, sizeof(data))) < 0) {
+ dev_err(&dev->dev, "%d:%d: cannot set freq %d to ep %#x\n",
+ iface, fmt->altsetting, rate, ep);
+ return err;
+ }
+
+ /* Don't check the sample rate for devices which we know don't
+ * support reading */
+ if (snd_usb_get_sample_rate_quirk(chip))
+ return 0;
+
+ if ((err = snd_usb_ctl_msg(dev, usb_rcvctrlpipe(dev, 0), UAC_GET_CUR,
+ USB_TYPE_CLASS | USB_RECIP_ENDPOINT | USB_DIR_IN,
+ UAC_EP_CS_ATTR_SAMPLE_RATE << 8, ep,
+ data, sizeof(data))) < 0) {
+ dev_err(&dev->dev, "%d:%d: cannot get freq at ep %#x\n",
+ iface, fmt->altsetting, ep);
+ return 0; /* some devices don't support reading */
+ }
+
+ crate = data[0] | (data[1] << 8) | (data[2] << 16);
+ if (crate != rate) {
+ dev_warn(&dev->dev, "current rate %d is different from the runtime rate %d\n", crate, rate);
+ // runtime->rate = crate;
+ }
+
+ return 0;
+}
+
+static int get_sample_rate_v2(struct snd_usb_audio *chip, int iface,
+ int altsetting, int clock)
+{
+ struct usb_device *dev = chip->dev;
+ __le32 data;
+ int err;
+
+ err = snd_usb_ctl_msg(dev, usb_rcvctrlpipe(dev, 0), UAC2_CS_CUR,
+ USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_IN,
+ UAC2_CS_CONTROL_SAM_FREQ << 8,
+ snd_usb_ctrl_intf(chip) | (clock << 8),
+ &data, sizeof(data));
+ if (err < 0) {
+ dev_warn(&dev->dev, "%d:%d: cannot get freq (v2): err %d\n",
+ iface, altsetting, err);
+ return 0;
+ }
+
+ return le32_to_cpu(data);
+}
+
+static int set_sample_rate_v2(struct snd_usb_audio *chip, int iface,
+ struct usb_host_interface *alts,
+ struct audioformat *fmt, int rate)
+{
+ struct usb_device *dev = chip->dev;
+ __le32 data;
+ int err, cur_rate, prev_rate;
+ int clock;
+ bool writeable;
+ struct uac_clock_source_descriptor *cs_desc;
+
+ clock = snd_usb_clock_find_source(chip, fmt->clock, true);
+ if (clock < 0)
+ return clock;
+
+ prev_rate = get_sample_rate_v2(chip, iface, fmt->altsetting, clock);
+ if (prev_rate == rate)
+ return 0;
+
+ cs_desc = snd_usb_find_clock_source(chip->ctrl_intf, clock);
+ writeable = uac2_control_is_writeable(cs_desc->bmControls, UAC2_CS_CONTROL_SAM_FREQ - 1);
+ if (writeable) {
+ data = cpu_to_le32(rate);
+ err = snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), UAC2_CS_CUR,
+ USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_OUT,
+ UAC2_CS_CONTROL_SAM_FREQ << 8,
+ snd_usb_ctrl_intf(chip) | (clock << 8),
+ &data, sizeof(data));
+ if (err < 0) {
+ usb_audio_err(chip,
+ "%d:%d: cannot set freq %d (v2): err %d\n",
+ iface, fmt->altsetting, rate, err);
+ return err;
+ }
+
+ cur_rate = get_sample_rate_v2(chip, iface, fmt->altsetting, clock);
+ } else {
+ cur_rate = prev_rate;
+ }
+
+ if (cur_rate != rate) {
+ if (!writeable) {
+ usb_audio_warn(chip,
+ "%d:%d: freq mismatch (RO clock): req %d, clock runs @%d\n",
+ iface, fmt->altsetting, rate, cur_rate);
+ return -ENXIO;
+ }
+ usb_audio_dbg(chip,
+ "current rate %d is different from the runtime rate %d\n",
+ cur_rate, rate);
+ }
+
+ /* Some devices doesn't respond to sample rate changes while the
+ * interface is active. */
+ if (rate != prev_rate) {
+ usb_set_interface(dev, iface, 0);
+ snd_usb_set_interface_quirk(dev);
+ usb_set_interface(dev, iface, fmt->altsetting);
+ snd_usb_set_interface_quirk(dev);
+ }
+
+ return 0;
+}
+
+int snd_usb_init_sample_rate(struct snd_usb_audio *chip, int iface,
+ struct usb_host_interface *alts,
+ struct audioformat *fmt, int rate)
+{
+ switch (fmt->protocol) {
+ case UAC_VERSION_1:
+ default:
+ return set_sample_rate_v1(chip, iface, alts, fmt, rate);
+
+ case UAC_VERSION_2:
+ return set_sample_rate_v2(chip, iface, alts, fmt, rate);
+ }
+}
+
diff --git a/sound/usb/clock.h b/sound/usb/clock.h
new file mode 100644
index 000000000..d592e4a29
--- /dev/null
+++ b/sound/usb/clock.h
@@ -0,0 +1,11 @@
+#ifndef __USBAUDIO_CLOCK_H
+#define __USBAUDIO_CLOCK_H
+
+int snd_usb_init_sample_rate(struct snd_usb_audio *chip, int iface,
+ struct usb_host_interface *alts,
+ struct audioformat *fmt, int rate);
+
+int snd_usb_clock_find_source(struct snd_usb_audio *chip, int entity_id,
+ bool validate);
+
+#endif /* __USBAUDIO_CLOCK_H */
diff --git a/sound/usb/debug.h b/sound/usb/debug.h
new file mode 100644
index 000000000..58030176f
--- /dev/null
+++ b/sound/usb/debug.h
@@ -0,0 +1,15 @@
+#ifndef __USBAUDIO_DEBUG_H
+#define __USBAUDIO_DEBUG_H
+
+/*
+ * h/w constraints
+ */
+
+#ifdef HW_CONST_DEBUG
+#define hwc_debug(fmt, args...) printk(KERN_DEBUG fmt, ##args)
+#else
+#define hwc_debug(fmt, args...) do { } while(0)
+#endif
+
+#endif /* __USBAUDIO_DEBUG_H */
+
diff --git a/sound/usb/endpoint.c b/sound/usb/endpoint.c
new file mode 100644
index 000000000..03b074419
--- /dev/null
+++ b/sound/usb/endpoint.c
@@ -0,0 +1,1170 @@
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+
+#include <linux/gfp.h>
+#include <linux/init.h>
+#include <linux/ratelimit.h>
+#include <linux/usb.h>
+#include <linux/usb/audio.h>
+#include <linux/slab.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+
+#include "usbaudio.h"
+#include "helper.h"
+#include "card.h"
+#include "endpoint.h"
+#include "pcm.h"
+#include "quirks.h"
+
+#define EP_FLAG_RUNNING 1
+#define EP_FLAG_STOPPING 2
+
+/*
+ * snd_usb_endpoint is a model that abstracts everything related to an
+ * USB endpoint and its streaming.
+ *
+ * There are functions to activate and deactivate the streaming URBs and
+ * optional callbacks to let the pcm logic handle the actual content of the
+ * packets for playback and record. Thus, the bus streaming and the audio
+ * handlers are fully decoupled.
+ *
+ * There are two different types of endpoints in audio applications.
+ *
+ * SND_USB_ENDPOINT_TYPE_DATA handles full audio data payload for both
+ * inbound and outbound traffic.
+ *
+ * SND_USB_ENDPOINT_TYPE_SYNC endpoints are for inbound traffic only and
+ * expect the payload to carry Q10.14 / Q16.16 formatted sync information
+ * (3 or 4 bytes).
+ *
+ * Each endpoint has to be configured prior to being used by calling
+ * snd_usb_endpoint_set_params().
+ *
+ * The model incorporates a reference counting, so that multiple users
+ * can call snd_usb_endpoint_start() and snd_usb_endpoint_stop(), and
+ * only the first user will effectively start the URBs, and only the last
+ * one to stop it will tear the URBs down again.
+ */
+
+/*
+ * convert a sampling rate into our full speed format (fs/1000 in Q16.16)
+ * this will overflow at approx 524 kHz
+ */
+static inline unsigned get_usb_full_speed_rate(unsigned int rate)
+{
+ return ((rate << 13) + 62) / 125;
+}
+
+/*
+ * convert a sampling rate into USB high speed format (fs/8000 in Q16.16)
+ * this will overflow at approx 4 MHz
+ */
+static inline unsigned get_usb_high_speed_rate(unsigned int rate)
+{
+ return ((rate << 10) + 62) / 125;
+}
+
+/*
+ * release a urb data
+ */
+static void release_urb_ctx(struct snd_urb_ctx *u)
+{
+ if (u->buffer_size)
+ usb_free_coherent(u->ep->chip->dev, u->buffer_size,
+ u->urb->transfer_buffer,
+ u->urb->transfer_dma);
+ usb_free_urb(u->urb);
+ u->urb = NULL;
+}
+
+static const char *usb_error_string(int err)
+{
+ switch (err) {
+ case -ENODEV:
+ return "no device";
+ case -ENOENT:
+ return "endpoint not enabled";
+ case -EPIPE:
+ return "endpoint stalled";
+ case -ENOSPC:
+ return "not enough bandwidth";
+ case -ESHUTDOWN:
+ return "device disabled";
+ case -EHOSTUNREACH:
+ return "device suspended";
+ case -EINVAL:
+ case -EAGAIN:
+ case -EFBIG:
+ case -EMSGSIZE:
+ return "internal error";
+ default:
+ return "unknown error";
+ }
+}
+
+/**
+ * snd_usb_endpoint_implicit_feedback_sink: Report endpoint usage type
+ *
+ * @ep: The snd_usb_endpoint
+ *
+ * Determine whether an endpoint is driven by an implicit feedback
+ * data endpoint source.
+ */
+int snd_usb_endpoint_implicit_feedback_sink(struct snd_usb_endpoint *ep)
+{
+ return ep->sync_master &&
+ ep->sync_master->type == SND_USB_ENDPOINT_TYPE_DATA &&
+ ep->type == SND_USB_ENDPOINT_TYPE_DATA &&
+ usb_pipeout(ep->pipe);
+}
+
+/*
+ * For streaming based on information derived from sync endpoints,
+ * prepare_outbound_urb_sizes() will call next_packet_size() to
+ * determine the number of samples to be sent in the next packet.
+ *
+ * For implicit feedback, next_packet_size() is unused.
+ */
+int snd_usb_endpoint_next_packet_size(struct snd_usb_endpoint *ep)
+{
+ unsigned long flags;
+ int ret;
+
+ if (ep->fill_max)
+ return ep->maxframesize;
+
+ spin_lock_irqsave(&ep->lock, flags);
+ ep->phase = (ep->phase & 0xffff)
+ + (ep->freqm << ep->datainterval);
+ ret = min(ep->phase >> 16, ep->maxframesize);
+ spin_unlock_irqrestore(&ep->lock, flags);
+
+ return ret;
+}
+
+static void retire_outbound_urb(struct snd_usb_endpoint *ep,
+ struct snd_urb_ctx *urb_ctx)
+{
+ if (ep->retire_data_urb)
+ ep->retire_data_urb(ep->data_subs, urb_ctx->urb);
+}
+
+static void retire_inbound_urb(struct snd_usb_endpoint *ep,
+ struct snd_urb_ctx *urb_ctx)
+{
+ struct urb *urb = urb_ctx->urb;
+
+ if (unlikely(ep->skip_packets > 0)) {
+ ep->skip_packets--;
+ return;
+ }
+
+ if (ep->sync_slave)
+ snd_usb_handle_sync_urb(ep->sync_slave, ep, urb);
+
+ if (ep->retire_data_urb)
+ ep->retire_data_urb(ep->data_subs, urb);
+}
+
+/*
+ * Prepare a PLAYBACK urb for submission to the bus.
+ */
+static void prepare_outbound_urb(struct snd_usb_endpoint *ep,
+ struct snd_urb_ctx *ctx)
+{
+ int i;
+ struct urb *urb = ctx->urb;
+ unsigned char *cp = urb->transfer_buffer;
+
+ urb->dev = ep->chip->dev; /* we need to set this at each time */
+
+ switch (ep->type) {
+ case SND_USB_ENDPOINT_TYPE_DATA:
+ if (ep->prepare_data_urb) {
+ ep->prepare_data_urb(ep->data_subs, urb);
+ } else {
+ /* no data provider, so send silence */
+ unsigned int offs = 0;
+ for (i = 0; i < ctx->packets; ++i) {
+ int counts;
+
+ if (ctx->packet_size[i])
+ counts = ctx->packet_size[i];
+ else
+ counts = snd_usb_endpoint_next_packet_size(ep);
+
+ urb->iso_frame_desc[i].offset = offs * ep->stride;
+ urb->iso_frame_desc[i].length = counts * ep->stride;
+ offs += counts;
+ }
+
+ urb->number_of_packets = ctx->packets;
+ urb->transfer_buffer_length = offs * ep->stride;
+ memset(urb->transfer_buffer, ep->silence_value,
+ offs * ep->stride);
+ }
+ break;
+
+ case SND_USB_ENDPOINT_TYPE_SYNC:
+ if (snd_usb_get_speed(ep->chip->dev) >= USB_SPEED_HIGH) {
+ /*
+ * fill the length and offset of each urb descriptor.
+ * the fixed 12.13 frequency is passed as 16.16 through the pipe.
+ */
+ urb->iso_frame_desc[0].length = 4;
+ urb->iso_frame_desc[0].offset = 0;
+ cp[0] = ep->freqn;
+ cp[1] = ep->freqn >> 8;
+ cp[2] = ep->freqn >> 16;
+ cp[3] = ep->freqn >> 24;
+ } else {
+ /*
+ * fill the length and offset of each urb descriptor.
+ * the fixed 10.14 frequency is passed through the pipe.
+ */
+ urb->iso_frame_desc[0].length = 3;
+ urb->iso_frame_desc[0].offset = 0;
+ cp[0] = ep->freqn >> 2;
+ cp[1] = ep->freqn >> 10;
+ cp[2] = ep->freqn >> 18;
+ }
+
+ break;
+ }
+}
+
+/*
+ * Prepare a CAPTURE or SYNC urb for submission to the bus.
+ */
+static inline void prepare_inbound_urb(struct snd_usb_endpoint *ep,
+ struct snd_urb_ctx *urb_ctx)
+{
+ int i, offs;
+ struct urb *urb = urb_ctx->urb;
+
+ urb->dev = ep->chip->dev; /* we need to set this at each time */
+
+ switch (ep->type) {
+ case SND_USB_ENDPOINT_TYPE_DATA:
+ offs = 0;
+ for (i = 0; i < urb_ctx->packets; i++) {
+ urb->iso_frame_desc[i].offset = offs;
+ urb->iso_frame_desc[i].length = ep->curpacksize;
+ offs += ep->curpacksize;
+ }
+
+ urb->transfer_buffer_length = offs;
+ urb->number_of_packets = urb_ctx->packets;
+ break;
+
+ case SND_USB_ENDPOINT_TYPE_SYNC:
+ urb->iso_frame_desc[0].length = min(4u, ep->syncmaxsize);
+ urb->iso_frame_desc[0].offset = 0;
+ break;
+ }
+}
+
+/*
+ * Send output urbs that have been prepared previously. URBs are dequeued
+ * from ep->ready_playback_urbs and in case there there aren't any available
+ * or there are no packets that have been prepared, this function does
+ * nothing.
+ *
+ * The reason why the functionality of sending and preparing URBs is separated
+ * is that host controllers don't guarantee the order in which they return
+ * inbound and outbound packets to their submitters.
+ *
+ * This function is only used for implicit feedback endpoints. For endpoints
+ * driven by dedicated sync endpoints, URBs are immediately re-submitted
+ * from their completion handler.
+ */
+static void queue_pending_output_urbs(struct snd_usb_endpoint *ep)
+{
+ while (test_bit(EP_FLAG_RUNNING, &ep->flags)) {
+
+ unsigned long flags;
+ struct snd_usb_packet_info *uninitialized_var(packet);
+ struct snd_urb_ctx *ctx = NULL;
+ struct urb *urb;
+ int err, i;
+
+ spin_lock_irqsave(&ep->lock, flags);
+ if (ep->next_packet_read_pos != ep->next_packet_write_pos) {
+ packet = ep->next_packet + ep->next_packet_read_pos;
+ ep->next_packet_read_pos++;
+ ep->next_packet_read_pos %= MAX_URBS;
+
+ /* take URB out of FIFO */
+ if (!list_empty(&ep->ready_playback_urbs))
+ ctx = list_first_entry(&ep->ready_playback_urbs,
+ struct snd_urb_ctx, ready_list);
+ }
+ spin_unlock_irqrestore(&ep->lock, flags);
+
+ if (ctx == NULL)
+ return;
+
+ list_del_init(&ctx->ready_list);
+ urb = ctx->urb;
+
+ /* copy over the length information */
+ for (i = 0; i < packet->packets; i++)
+ ctx->packet_size[i] = packet->packet_size[i];
+
+ /* call the data handler to fill in playback data */
+ prepare_outbound_urb(ep, ctx);
+
+ err = usb_submit_urb(ctx->urb, GFP_ATOMIC);
+ if (err < 0)
+ usb_audio_err(ep->chip,
+ "Unable to submit urb #%d: %d (urb %p)\n",
+ ctx->index, err, ctx->urb);
+ else
+ set_bit(ctx->index, &ep->active_mask);
+ }
+}
+
+/*
+ * complete callback for urbs
+ */
+static void snd_complete_urb(struct urb *urb)
+{
+ struct snd_urb_ctx *ctx = urb->context;
+ struct snd_usb_endpoint *ep = ctx->ep;
+ struct snd_pcm_substream *substream;
+ unsigned long flags;
+ int err;
+
+ if (unlikely(urb->status == -ENOENT || /* unlinked */
+ urb->status == -ENODEV || /* device removed */
+ urb->status == -ECONNRESET || /* unlinked */
+ urb->status == -ESHUTDOWN || /* device disabled */
+ ep->chip->shutdown)) /* device disconnected */
+ goto exit_clear;
+
+ if (usb_pipeout(ep->pipe)) {
+ retire_outbound_urb(ep, ctx);
+ /* can be stopped during retire callback */
+ if (unlikely(!test_bit(EP_FLAG_RUNNING, &ep->flags)))
+ goto exit_clear;
+
+ if (snd_usb_endpoint_implicit_feedback_sink(ep)) {
+ spin_lock_irqsave(&ep->lock, flags);
+ list_add_tail(&ctx->ready_list, &ep->ready_playback_urbs);
+ spin_unlock_irqrestore(&ep->lock, flags);
+ queue_pending_output_urbs(ep);
+
+ goto exit_clear;
+ }
+
+ prepare_outbound_urb(ep, ctx);
+ } else {
+ retire_inbound_urb(ep, ctx);
+ /* can be stopped during retire callback */
+ if (unlikely(!test_bit(EP_FLAG_RUNNING, &ep->flags)))
+ goto exit_clear;
+
+ prepare_inbound_urb(ep, ctx);
+ }
+
+ err = usb_submit_urb(urb, GFP_ATOMIC);
+ if (err == 0)
+ return;
+
+ usb_audio_err(ep->chip, "cannot submit urb (err = %d)\n", err);
+ if (ep->data_subs && ep->data_subs->pcm_substream) {
+ substream = ep->data_subs->pcm_substream;
+ snd_pcm_stop_xrun(substream);
+ }
+
+exit_clear:
+ clear_bit(ctx->index, &ep->active_mask);
+}
+
+/**
+ * snd_usb_add_endpoint: Add an endpoint to an USB audio chip
+ *
+ * @chip: The chip
+ * @alts: The USB host interface
+ * @ep_num: The number of the endpoint to use
+ * @direction: SNDRV_PCM_STREAM_PLAYBACK or SNDRV_PCM_STREAM_CAPTURE
+ * @type: SND_USB_ENDPOINT_TYPE_DATA or SND_USB_ENDPOINT_TYPE_SYNC
+ *
+ * If the requested endpoint has not been added to the given chip before,
+ * a new instance is created. Otherwise, a pointer to the previoulsy
+ * created instance is returned. In case of any error, NULL is returned.
+ *
+ * New endpoints will be added to chip->ep_list and must be freed by
+ * calling snd_usb_endpoint_free().
+ */
+struct snd_usb_endpoint *snd_usb_add_endpoint(struct snd_usb_audio *chip,
+ struct usb_host_interface *alts,
+ int ep_num, int direction, int type)
+{
+ struct snd_usb_endpoint *ep;
+ int is_playback = direction == SNDRV_PCM_STREAM_PLAYBACK;
+
+ if (WARN_ON(!alts))
+ return NULL;
+
+ mutex_lock(&chip->mutex);
+
+ list_for_each_entry(ep, &chip->ep_list, list) {
+ if (ep->ep_num == ep_num &&
+ ep->iface == alts->desc.bInterfaceNumber &&
+ ep->altsetting == alts->desc.bAlternateSetting) {
+ usb_audio_dbg(ep->chip,
+ "Re-using EP %x in iface %d,%d @%p\n",
+ ep_num, ep->iface, ep->altsetting, ep);
+ goto __exit_unlock;
+ }
+ }
+
+ usb_audio_dbg(chip, "Creating new %s %s endpoint #%x\n",
+ is_playback ? "playback" : "capture",
+ type == SND_USB_ENDPOINT_TYPE_DATA ? "data" : "sync",
+ ep_num);
+
+ ep = kzalloc(sizeof(*ep), GFP_KERNEL);
+ if (!ep)
+ goto __exit_unlock;
+
+ ep->chip = chip;
+ spin_lock_init(&ep->lock);
+ ep->type = type;
+ ep->ep_num = ep_num;
+ ep->iface = alts->desc.bInterfaceNumber;
+ ep->altsetting = alts->desc.bAlternateSetting;
+ INIT_LIST_HEAD(&ep->ready_playback_urbs);
+ ep_num &= USB_ENDPOINT_NUMBER_MASK;
+
+ if (is_playback)
+ ep->pipe = usb_sndisocpipe(chip->dev, ep_num);
+ else
+ ep->pipe = usb_rcvisocpipe(chip->dev, ep_num);
+
+ if (type == SND_USB_ENDPOINT_TYPE_SYNC) {
+ if (get_endpoint(alts, 1)->bLength >= USB_DT_ENDPOINT_AUDIO_SIZE &&
+ get_endpoint(alts, 1)->bRefresh >= 1 &&
+ get_endpoint(alts, 1)->bRefresh <= 9)
+ ep->syncinterval = get_endpoint(alts, 1)->bRefresh;
+ else if (snd_usb_get_speed(chip->dev) == USB_SPEED_FULL)
+ ep->syncinterval = 1;
+ else if (get_endpoint(alts, 1)->bInterval >= 1 &&
+ get_endpoint(alts, 1)->bInterval <= 16)
+ ep->syncinterval = get_endpoint(alts, 1)->bInterval - 1;
+ else
+ ep->syncinterval = 3;
+
+ ep->syncmaxsize = le16_to_cpu(get_endpoint(alts, 1)->wMaxPacketSize);
+
+ if (chip->usb_id == USB_ID(0x0644, 0x8038) /* TEAC UD-H01 */ &&
+ ep->syncmaxsize == 4)
+ ep->udh01_fb_quirk = 1;
+ }
+
+ list_add_tail(&ep->list, &chip->ep_list);
+
+__exit_unlock:
+ mutex_unlock(&chip->mutex);
+
+ return ep;
+}
+
+/*
+ * wait until all urbs are processed.
+ */
+static int wait_clear_urbs(struct snd_usb_endpoint *ep)
+{
+ unsigned long end_time = jiffies + msecs_to_jiffies(1000);
+ int alive;
+
+ do {
+ alive = bitmap_weight(&ep->active_mask, ep->nurbs);
+ if (!alive)
+ break;
+
+ schedule_timeout_uninterruptible(1);
+ } while (time_before(jiffies, end_time));
+
+ if (alive)
+ usb_audio_err(ep->chip,
+ "timeout: still %d active urbs on EP #%x\n",
+ alive, ep->ep_num);
+ clear_bit(EP_FLAG_STOPPING, &ep->flags);
+
+ return 0;
+}
+
+/* sync the pending stop operation;
+ * this function itself doesn't trigger the stop operation
+ */
+void snd_usb_endpoint_sync_pending_stop(struct snd_usb_endpoint *ep)
+{
+ if (ep && test_bit(EP_FLAG_STOPPING, &ep->flags))
+ wait_clear_urbs(ep);
+}
+
+/*
+ * unlink active urbs.
+ */
+static int deactivate_urbs(struct snd_usb_endpoint *ep, bool force)
+{
+ unsigned int i;
+
+ if (!force && ep->chip->shutdown) /* to be sure... */
+ return -EBADFD;
+
+ clear_bit(EP_FLAG_RUNNING, &ep->flags);
+
+ INIT_LIST_HEAD(&ep->ready_playback_urbs);
+ ep->next_packet_read_pos = 0;
+ ep->next_packet_write_pos = 0;
+
+ for (i = 0; i < ep->nurbs; i++) {
+ if (test_bit(i, &ep->active_mask)) {
+ if (!test_and_set_bit(i, &ep->unlink_mask)) {
+ struct urb *u = ep->urb[i].urb;
+ usb_unlink_urb(u);
+ }
+ }
+ }
+
+ return 0;
+}
+
+/*
+ * release an endpoint's urbs
+ */
+static void release_urbs(struct snd_usb_endpoint *ep, int force)
+{
+ int i;
+
+ /* route incoming urbs to nirvana */
+ ep->retire_data_urb = NULL;
+ ep->prepare_data_urb = NULL;
+
+ /* stop urbs */
+ deactivate_urbs(ep, force);
+ wait_clear_urbs(ep);
+
+ for (i = 0; i < ep->nurbs; i++)
+ release_urb_ctx(&ep->urb[i]);
+
+ if (ep->syncbuf)
+ usb_free_coherent(ep->chip->dev, SYNC_URBS * 4,
+ ep->syncbuf, ep->sync_dma);
+
+ ep->syncbuf = NULL;
+ ep->nurbs = 0;
+}
+
+/*
+ * configure a data endpoint
+ */
+static int data_ep_set_params(struct snd_usb_endpoint *ep,
+ snd_pcm_format_t pcm_format,
+ unsigned int channels,
+ unsigned int period_bytes,
+ unsigned int frames_per_period,
+ unsigned int periods_per_buffer,
+ struct audioformat *fmt,
+ struct snd_usb_endpoint *sync_ep)
+{
+ unsigned int maxsize, minsize, packs_per_ms, max_packs_per_urb;
+ unsigned int max_packs_per_period, urbs_per_period, urb_packs;
+ unsigned int max_urbs, i;
+ int frame_bits = snd_pcm_format_physical_width(pcm_format) * channels;
+
+ if (pcm_format == SNDRV_PCM_FORMAT_DSD_U16_LE && fmt->dsd_dop) {
+ /*
+ * When operating in DSD DOP mode, the size of a sample frame
+ * in hardware differs from the actual physical format width
+ * because we need to make room for the DOP markers.
+ */
+ frame_bits += channels << 3;
+ }
+
+ ep->datainterval = fmt->datainterval;
+ ep->stride = frame_bits >> 3;
+ ep->silence_value = pcm_format == SNDRV_PCM_FORMAT_U8 ? 0x80 : 0;
+
+ /* assume max. frequency is 25% higher than nominal */
+ ep->freqmax = ep->freqn + (ep->freqn >> 2);
+ maxsize = ((ep->freqmax + 0xffff) * (frame_bits >> 3))
+ >> (16 - ep->datainterval);
+ /* but wMaxPacketSize might reduce this */
+ if (ep->maxpacksize && ep->maxpacksize < maxsize) {
+ /* whatever fits into a max. size packet */
+ maxsize = ep->maxpacksize;
+ ep->freqmax = (maxsize / (frame_bits >> 3))
+ << (16 - ep->datainterval);
+ }
+
+ if (ep->fill_max)
+ ep->curpacksize = ep->maxpacksize;
+ else
+ ep->curpacksize = maxsize;
+
+ if (snd_usb_get_speed(ep->chip->dev) != USB_SPEED_FULL) {
+ packs_per_ms = 8 >> ep->datainterval;
+ max_packs_per_urb = MAX_PACKS_HS;
+ } else {
+ packs_per_ms = 1;
+ max_packs_per_urb = MAX_PACKS;
+ }
+ if (sync_ep && !snd_usb_endpoint_implicit_feedback_sink(ep))
+ max_packs_per_urb = min(max_packs_per_urb,
+ 1U << sync_ep->syncinterval);
+ max_packs_per_urb = max(1u, max_packs_per_urb >> ep->datainterval);
+
+ /*
+ * Capture endpoints need to use small URBs because there's no way
+ * to tell in advance where the next period will end, and we don't
+ * want the next URB to complete much after the period ends.
+ *
+ * Playback endpoints with implicit sync much use the same parameters
+ * as their corresponding capture endpoint.
+ */
+ if (usb_pipein(ep->pipe) ||
+ snd_usb_endpoint_implicit_feedback_sink(ep)) {
+
+ urb_packs = packs_per_ms;
+ /*
+ * Wireless devices can poll at a max rate of once per 4ms.
+ * For dataintervals less than 5, increase the packet count to
+ * allow the host controller to use bursting to fill in the
+ * gaps.
+ */
+ if (snd_usb_get_speed(ep->chip->dev) == USB_SPEED_WIRELESS) {
+ int interval = ep->datainterval;
+ while (interval < 5) {
+ urb_packs <<= 1;
+ ++interval;
+ }
+ }
+ /* make capture URBs <= 1 ms and smaller than a period */
+ urb_packs = min(max_packs_per_urb, urb_packs);
+ while (urb_packs > 1 && urb_packs * maxsize >= period_bytes)
+ urb_packs >>= 1;
+ ep->nurbs = MAX_URBS;
+
+ /*
+ * Playback endpoints without implicit sync are adjusted so that
+ * a period fits as evenly as possible in the smallest number of
+ * URBs. The total number of URBs is adjusted to the size of the
+ * ALSA buffer, subject to the MAX_URBS and MAX_QUEUE limits.
+ */
+ } else {
+ /* determine how small a packet can be */
+ minsize = (ep->freqn >> (16 - ep->datainterval)) *
+ (frame_bits >> 3);
+ /* with sync from device, assume it can be 12% lower */
+ if (sync_ep)
+ minsize -= minsize >> 3;
+ minsize = max(minsize, 1u);
+
+ /* how many packets will contain an entire ALSA period? */
+ max_packs_per_period = DIV_ROUND_UP(period_bytes, minsize);
+
+ /* how many URBs will contain a period? */
+ urbs_per_period = DIV_ROUND_UP(max_packs_per_period,
+ max_packs_per_urb);
+ /* how many packets are needed in each URB? */
+ urb_packs = DIV_ROUND_UP(max_packs_per_period, urbs_per_period);
+
+ /* limit the number of frames in a single URB */
+ ep->max_urb_frames = DIV_ROUND_UP(frames_per_period,
+ urbs_per_period);
+
+ /* try to use enough URBs to contain an entire ALSA buffer */
+ max_urbs = min((unsigned) MAX_URBS,
+ MAX_QUEUE * packs_per_ms / urb_packs);
+ ep->nurbs = min(max_urbs, urbs_per_period * periods_per_buffer);
+ }
+
+ /* allocate and initialize data urbs */
+ for (i = 0; i < ep->nurbs; i++) {
+ struct snd_urb_ctx *u = &ep->urb[i];
+ u->index = i;
+ u->ep = ep;
+ u->packets = urb_packs;
+ u->buffer_size = maxsize * u->packets;
+
+ if (fmt->fmt_type == UAC_FORMAT_TYPE_II)
+ u->packets++; /* for transfer delimiter */
+ u->urb = usb_alloc_urb(u->packets, GFP_KERNEL);
+ if (!u->urb)
+ goto out_of_memory;
+
+ u->urb->transfer_buffer =
+ usb_alloc_coherent(ep->chip->dev, u->buffer_size,
+ GFP_KERNEL, &u->urb->transfer_dma);
+ if (!u->urb->transfer_buffer)
+ goto out_of_memory;
+ u->urb->pipe = ep->pipe;
+ u->urb->transfer_flags = URB_NO_TRANSFER_DMA_MAP;
+ u->urb->interval = 1 << ep->datainterval;
+ u->urb->context = u;
+ u->urb->complete = snd_complete_urb;
+ INIT_LIST_HEAD(&u->ready_list);
+ }
+
+ return 0;
+
+out_of_memory:
+ release_urbs(ep, 0);
+ return -ENOMEM;
+}
+
+/*
+ * configure a sync endpoint
+ */
+static int sync_ep_set_params(struct snd_usb_endpoint *ep)
+{
+ int i;
+
+ ep->syncbuf = usb_alloc_coherent(ep->chip->dev, SYNC_URBS * 4,
+ GFP_KERNEL, &ep->sync_dma);
+ if (!ep->syncbuf)
+ return -ENOMEM;
+
+ for (i = 0; i < SYNC_URBS; i++) {
+ struct snd_urb_ctx *u = &ep->urb[i];
+ u->index = i;
+ u->ep = ep;
+ u->packets = 1;
+ u->urb = usb_alloc_urb(1, GFP_KERNEL);
+ if (!u->urb)
+ goto out_of_memory;
+ u->urb->transfer_buffer = ep->syncbuf + i * 4;
+ u->urb->transfer_dma = ep->sync_dma + i * 4;
+ u->urb->transfer_buffer_length = 4;
+ u->urb->pipe = ep->pipe;
+ u->urb->transfer_flags = URB_NO_TRANSFER_DMA_MAP;
+ u->urb->number_of_packets = 1;
+ u->urb->interval = 1 << ep->syncinterval;
+ u->urb->context = u;
+ u->urb->complete = snd_complete_urb;
+ }
+
+ ep->nurbs = SYNC_URBS;
+
+ return 0;
+
+out_of_memory:
+ release_urbs(ep, 0);
+ return -ENOMEM;
+}
+
+/**
+ * snd_usb_endpoint_set_params: configure an snd_usb_endpoint
+ *
+ * @ep: the snd_usb_endpoint to configure
+ * @pcm_format: the audio fomat.
+ * @channels: the number of audio channels.
+ * @period_bytes: the number of bytes in one alsa period.
+ * @period_frames: the number of frames in one alsa period.
+ * @buffer_periods: the number of periods in one alsa buffer.
+ * @rate: the frame rate.
+ * @fmt: the USB audio format information
+ * @sync_ep: the sync endpoint to use, if any
+ *
+ * Determine the number of URBs to be used on this endpoint.
+ * An endpoint must be configured before it can be started.
+ * An endpoint that is already running can not be reconfigured.
+ */
+int snd_usb_endpoint_set_params(struct snd_usb_endpoint *ep,
+ snd_pcm_format_t pcm_format,
+ unsigned int channels,
+ unsigned int period_bytes,
+ unsigned int period_frames,
+ unsigned int buffer_periods,
+ unsigned int rate,
+ struct audioformat *fmt,
+ struct snd_usb_endpoint *sync_ep)
+{
+ int err;
+
+ if (ep->use_count != 0) {
+ usb_audio_warn(ep->chip,
+ "Unable to change format on ep #%x: already in use\n",
+ ep->ep_num);
+ return -EBUSY;
+ }
+
+ /* release old buffers, if any */
+ release_urbs(ep, 0);
+
+ ep->datainterval = fmt->datainterval;
+ ep->maxpacksize = fmt->maxpacksize;
+ ep->fill_max = !!(fmt->attributes & UAC_EP_CS_ATTR_FILL_MAX);
+
+ if (snd_usb_get_speed(ep->chip->dev) == USB_SPEED_FULL)
+ ep->freqn = get_usb_full_speed_rate(rate);
+ else
+ ep->freqn = get_usb_high_speed_rate(rate);
+
+ /* calculate the frequency in 16.16 format */
+ ep->freqm = ep->freqn;
+ ep->freqshift = INT_MIN;
+
+ ep->phase = 0;
+
+ switch (ep->type) {
+ case SND_USB_ENDPOINT_TYPE_DATA:
+ err = data_ep_set_params(ep, pcm_format, channels,
+ period_bytes, period_frames,
+ buffer_periods, fmt, sync_ep);
+ break;
+ case SND_USB_ENDPOINT_TYPE_SYNC:
+ err = sync_ep_set_params(ep);
+ break;
+ default:
+ err = -EINVAL;
+ }
+
+ usb_audio_dbg(ep->chip,
+ "Setting params for ep #%x (type %d, %d urbs), ret=%d\n",
+ ep->ep_num, ep->type, ep->nurbs, err);
+
+ return err;
+}
+
+/**
+ * snd_usb_endpoint_start: start an snd_usb_endpoint
+ *
+ * @ep: the endpoint to start
+ * @can_sleep: flag indicating whether the operation is executed in
+ * non-atomic context
+ *
+ * A call to this function will increment the use count of the endpoint.
+ * In case it is not already running, the URBs for this endpoint will be
+ * submitted. Otherwise, this function does nothing.
+ *
+ * Must be balanced to calls of snd_usb_endpoint_stop().
+ *
+ * Returns an error if the URB submission failed, 0 in all other cases.
+ */
+int snd_usb_endpoint_start(struct snd_usb_endpoint *ep, bool can_sleep)
+{
+ int err;
+ unsigned int i;
+
+ if (ep->chip->shutdown)
+ return -EBADFD;
+
+ /* already running? */
+ if (++ep->use_count != 1)
+ return 0;
+
+ /* just to be sure */
+ deactivate_urbs(ep, false);
+ if (can_sleep)
+ wait_clear_urbs(ep);
+
+ ep->active_mask = 0;
+ ep->unlink_mask = 0;
+ ep->phase = 0;
+
+ snd_usb_endpoint_start_quirk(ep);
+
+ /*
+ * If this endpoint has a data endpoint as implicit feedback source,
+ * don't start the urbs here. Instead, mark them all as available,
+ * wait for the record urbs to return and queue the playback urbs
+ * from that context.
+ */
+
+ set_bit(EP_FLAG_RUNNING, &ep->flags);
+
+ if (snd_usb_endpoint_implicit_feedback_sink(ep)) {
+ for (i = 0; i < ep->nurbs; i++) {
+ struct snd_urb_ctx *ctx = ep->urb + i;
+ list_add_tail(&ctx->ready_list, &ep->ready_playback_urbs);
+ }
+
+ return 0;
+ }
+
+ for (i = 0; i < ep->nurbs; i++) {
+ struct urb *urb = ep->urb[i].urb;
+
+ if (snd_BUG_ON(!urb))
+ goto __error;
+
+ if (usb_pipeout(ep->pipe)) {
+ prepare_outbound_urb(ep, urb->context);
+ } else {
+ prepare_inbound_urb(ep, urb->context);
+ }
+
+ err = usb_submit_urb(urb, GFP_ATOMIC);
+ if (err < 0) {
+ usb_audio_err(ep->chip,
+ "cannot submit urb %d, error %d: %s\n",
+ i, err, usb_error_string(err));
+ goto __error;
+ }
+ set_bit(i, &ep->active_mask);
+ }
+
+ return 0;
+
+__error:
+ clear_bit(EP_FLAG_RUNNING, &ep->flags);
+ ep->use_count--;
+ deactivate_urbs(ep, false);
+ return -EPIPE;
+}
+
+/**
+ * snd_usb_endpoint_stop: stop an snd_usb_endpoint
+ *
+ * @ep: the endpoint to stop (may be NULL)
+ *
+ * A call to this function will decrement the use count of the endpoint.
+ * In case the last user has requested the endpoint stop, the URBs will
+ * actually be deactivated.
+ *
+ * Must be balanced to calls of snd_usb_endpoint_start().
+ *
+ * The caller needs to synchronize the pending stop operation via
+ * snd_usb_endpoint_sync_pending_stop().
+ */
+void snd_usb_endpoint_stop(struct snd_usb_endpoint *ep)
+{
+ if (!ep)
+ return;
+
+ if (snd_BUG_ON(ep->use_count == 0))
+ return;
+
+ if (--ep->use_count == 0) {
+ deactivate_urbs(ep, false);
+ ep->data_subs = NULL;
+ ep->sync_slave = NULL;
+ ep->retire_data_urb = NULL;
+ ep->prepare_data_urb = NULL;
+ set_bit(EP_FLAG_STOPPING, &ep->flags);
+ }
+}
+
+/**
+ * snd_usb_endpoint_deactivate: deactivate an snd_usb_endpoint
+ *
+ * @ep: the endpoint to deactivate
+ *
+ * If the endpoint is not currently in use, this functions will
+ * deactivate its associated URBs.
+ *
+ * In case of any active users, this functions does nothing.
+ */
+void snd_usb_endpoint_deactivate(struct snd_usb_endpoint *ep)
+{
+ if (!ep)
+ return;
+
+ if (ep->use_count != 0)
+ return;
+
+ deactivate_urbs(ep, true);
+ wait_clear_urbs(ep);
+}
+
+/**
+ * snd_usb_endpoint_release: Tear down an snd_usb_endpoint
+ *
+ * @ep: the endpoint to release
+ *
+ * This function does not care for the endpoint's use count but will tear
+ * down all the streaming URBs immediately.
+ */
+void snd_usb_endpoint_release(struct snd_usb_endpoint *ep)
+{
+ release_urbs(ep, 1);
+}
+
+/**
+ * snd_usb_endpoint_free: Free the resources of an snd_usb_endpoint
+ *
+ * @ep: the endpoint to free
+ *
+ * This free all resources of the given ep.
+ */
+void snd_usb_endpoint_free(struct snd_usb_endpoint *ep)
+{
+ kfree(ep);
+}
+
+/**
+ * snd_usb_handle_sync_urb: parse an USB sync packet
+ *
+ * @ep: the endpoint to handle the packet
+ * @sender: the sending endpoint
+ * @urb: the received packet
+ *
+ * This function is called from the context of an endpoint that received
+ * the packet and is used to let another endpoint object handle the payload.
+ */
+void snd_usb_handle_sync_urb(struct snd_usb_endpoint *ep,
+ struct snd_usb_endpoint *sender,
+ const struct urb *urb)
+{
+ int shift;
+ unsigned int f;
+ unsigned long flags;
+
+ snd_BUG_ON(ep == sender);
+
+ /*
+ * In case the endpoint is operating in implicit feedback mode, prepare
+ * a new outbound URB that has the same layout as the received packet
+ * and add it to the list of pending urbs. queue_pending_output_urbs()
+ * will take care of them later.
+ */
+ if (snd_usb_endpoint_implicit_feedback_sink(ep) &&
+ ep->use_count != 0) {
+
+ /* implicit feedback case */
+ int i, bytes = 0;
+ struct snd_urb_ctx *in_ctx;
+ struct snd_usb_packet_info *out_packet;
+
+ in_ctx = urb->context;
+
+ /* Count overall packet size */
+ for (i = 0; i < in_ctx->packets; i++)
+ if (urb->iso_frame_desc[i].status == 0)
+ bytes += urb->iso_frame_desc[i].actual_length;
+
+ /*
+ * skip empty packets. At least M-Audio's Fast Track Ultra stops
+ * streaming once it received a 0-byte OUT URB
+ */
+ if (bytes == 0)
+ return;
+
+ spin_lock_irqsave(&ep->lock, flags);
+ out_packet = ep->next_packet + ep->next_packet_write_pos;
+
+ /*
+ * Iterate through the inbound packet and prepare the lengths
+ * for the output packet. The OUT packet we are about to send
+ * will have the same amount of payload bytes per stride as the
+ * IN packet we just received. Since the actual size is scaled
+ * by the stride, use the sender stride to calculate the length
+ * in case the number of channels differ between the implicitly
+ * fed-back endpoint and the synchronizing endpoint.
+ */
+
+ out_packet->packets = in_ctx->packets;
+ for (i = 0; i < in_ctx->packets; i++) {
+ if (urb->iso_frame_desc[i].status == 0)
+ out_packet->packet_size[i] =
+ urb->iso_frame_desc[i].actual_length / sender->stride;
+ else
+ out_packet->packet_size[i] = 0;
+ }
+
+ ep->next_packet_write_pos++;
+ ep->next_packet_write_pos %= MAX_URBS;
+ spin_unlock_irqrestore(&ep->lock, flags);
+ queue_pending_output_urbs(ep);
+
+ return;
+ }
+
+ /*
+ * process after playback sync complete
+ *
+ * Full speed devices report feedback values in 10.14 format as samples
+ * per frame, high speed devices in 16.16 format as samples per
+ * microframe.
+ *
+ * Because the Audio Class 1 spec was written before USB 2.0, many high
+ * speed devices use a wrong interpretation, some others use an
+ * entirely different format.
+ *
+ * Therefore, we cannot predict what format any particular device uses
+ * and must detect it automatically.
+ */
+
+ if (urb->iso_frame_desc[0].status != 0 ||
+ urb->iso_frame_desc[0].actual_length < 3)
+ return;
+
+ f = le32_to_cpup(urb->transfer_buffer);
+ if (urb->iso_frame_desc[0].actual_length == 3)
+ f &= 0x00ffffff;
+ else
+ f &= 0x0fffffff;
+
+ if (f == 0)
+ return;
+
+ if (unlikely(sender->udh01_fb_quirk)) {
+ /*
+ * The TEAC UD-H01 firmware sometimes changes the feedback value
+ * by +/- 0x1.0000.
+ */
+ if (f < ep->freqn - 0x8000)
+ f += 0x10000;
+ else if (f > ep->freqn + 0x8000)
+ f -= 0x10000;
+ } else if (unlikely(ep->freqshift == INT_MIN)) {
+ /*
+ * The first time we see a feedback value, determine its format
+ * by shifting it left or right until it matches the nominal
+ * frequency value. This assumes that the feedback does not
+ * differ from the nominal value more than +50% or -25%.
+ */
+ shift = 0;
+ while (f < ep->freqn - ep->freqn / 4) {
+ f <<= 1;
+ shift++;
+ }
+ while (f > ep->freqn + ep->freqn / 2) {
+ f >>= 1;
+ shift--;
+ }
+ ep->freqshift = shift;
+ } else if (ep->freqshift >= 0)
+ f <<= ep->freqshift;
+ else
+ f >>= -ep->freqshift;
+
+ if (likely(f >= ep->freqn - ep->freqn / 8 && f <= ep->freqmax)) {
+ /*
+ * If the frequency looks valid, set it.
+ * This value is referred to in prepare_playback_urb().
+ */
+ spin_lock_irqsave(&ep->lock, flags);
+ ep->freqm = f;
+ spin_unlock_irqrestore(&ep->lock, flags);
+ } else {
+ /*
+ * Out of range; maybe the shift value is wrong.
+ * Reset it so that we autodetect again the next time.
+ */
+ ep->freqshift = INT_MIN;
+ }
+}
+
diff --git a/sound/usb/endpoint.h b/sound/usb/endpoint.h
new file mode 100644
index 000000000..6428392d8
--- /dev/null
+++ b/sound/usb/endpoint.h
@@ -0,0 +1,36 @@
+#ifndef __USBAUDIO_ENDPOINT_H
+#define __USBAUDIO_ENDPOINT_H
+
+#define SND_USB_ENDPOINT_TYPE_DATA 0
+#define SND_USB_ENDPOINT_TYPE_SYNC 1
+
+struct snd_usb_endpoint *snd_usb_add_endpoint(struct snd_usb_audio *chip,
+ struct usb_host_interface *alts,
+ int ep_num, int direction, int type);
+
+int snd_usb_endpoint_set_params(struct snd_usb_endpoint *ep,
+ snd_pcm_format_t pcm_format,
+ unsigned int channels,
+ unsigned int period_bytes,
+ unsigned int period_frames,
+ unsigned int buffer_periods,
+ unsigned int rate,
+ struct audioformat *fmt,
+ struct snd_usb_endpoint *sync_ep);
+
+int snd_usb_endpoint_start(struct snd_usb_endpoint *ep, bool can_sleep);
+void snd_usb_endpoint_stop(struct snd_usb_endpoint *ep);
+void snd_usb_endpoint_sync_pending_stop(struct snd_usb_endpoint *ep);
+int snd_usb_endpoint_activate(struct snd_usb_endpoint *ep);
+void snd_usb_endpoint_deactivate(struct snd_usb_endpoint *ep);
+void snd_usb_endpoint_release(struct snd_usb_endpoint *ep);
+void snd_usb_endpoint_free(struct snd_usb_endpoint *ep);
+
+int snd_usb_endpoint_implicit_feedback_sink(struct snd_usb_endpoint *ep);
+int snd_usb_endpoint_next_packet_size(struct snd_usb_endpoint *ep);
+
+void snd_usb_handle_sync_urb(struct snd_usb_endpoint *ep,
+ struct snd_usb_endpoint *sender,
+ const struct urb *urb);
+
+#endif /* __USBAUDIO_ENDPOINT_H */
diff --git a/sound/usb/format.c b/sound/usb/format.c
new file mode 100644
index 000000000..789d19ec0
--- /dev/null
+++ b/sound/usb/format.c
@@ -0,0 +1,524 @@
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/usb.h>
+#include <linux/usb/audio.h>
+#include <linux/usb/audio-v2.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+
+#include "usbaudio.h"
+#include "card.h"
+#include "quirks.h"
+#include "helper.h"
+#include "debug.h"
+#include "clock.h"
+#include "format.h"
+
+/*
+ * parse the audio format type I descriptor
+ * and returns the corresponding pcm format
+ *
+ * @dev: usb device
+ * @fp: audioformat record
+ * @format: the format tag (wFormatTag)
+ * @fmt: the format type descriptor
+ */
+static u64 parse_audio_format_i_type(struct snd_usb_audio *chip,
+ struct audioformat *fp,
+ unsigned int format, void *_fmt)
+{
+ int sample_width, sample_bytes;
+ u64 pcm_formats = 0;
+
+ switch (fp->protocol) {
+ case UAC_VERSION_1:
+ default: {
+ struct uac_format_type_i_discrete_descriptor *fmt = _fmt;
+ sample_width = fmt->bBitResolution;
+ sample_bytes = fmt->bSubframeSize;
+ format = 1 << format;
+ break;
+ }
+
+ case UAC_VERSION_2: {
+ struct uac_format_type_i_ext_descriptor *fmt = _fmt;
+ sample_width = fmt->bBitResolution;
+ sample_bytes = fmt->bSubslotSize;
+
+ if (format & UAC2_FORMAT_TYPE_I_RAW_DATA)
+ pcm_formats |= SNDRV_PCM_FMTBIT_SPECIAL;
+
+ format <<= 1;
+ break;
+ }
+ }
+
+ if ((pcm_formats == 0) &&
+ (format == 0 || format == (1 << UAC_FORMAT_TYPE_I_UNDEFINED))) {
+ /* some devices don't define this correctly... */
+ usb_audio_info(chip, "%u:%d : format type 0 is detected, processed as PCM\n",
+ fp->iface, fp->altsetting);
+ format = 1 << UAC_FORMAT_TYPE_I_PCM;
+ }
+ if (format & (1 << UAC_FORMAT_TYPE_I_PCM)) {
+ if (((chip->usb_id == USB_ID(0x0582, 0x0016)) ||
+ /* Edirol SD-90 */
+ (chip->usb_id == USB_ID(0x0582, 0x000c))) &&
+ /* Roland SC-D70 */
+ sample_width == 24 && sample_bytes == 2)
+ sample_bytes = 3;
+ else if (sample_width > sample_bytes * 8) {
+ usb_audio_info(chip, "%u:%d : sample bitwidth %d in over sample bytes %d\n",
+ fp->iface, fp->altsetting,
+ sample_width, sample_bytes);
+ }
+ /* check the format byte size */
+ switch (sample_bytes) {
+ case 1:
+ pcm_formats |= SNDRV_PCM_FMTBIT_S8;
+ break;
+ case 2:
+ if (snd_usb_is_big_endian_format(chip, fp))
+ pcm_formats |= SNDRV_PCM_FMTBIT_S16_BE; /* grrr, big endian!! */
+ else
+ pcm_formats |= SNDRV_PCM_FMTBIT_S16_LE;
+ break;
+ case 3:
+ if (snd_usb_is_big_endian_format(chip, fp))
+ pcm_formats |= SNDRV_PCM_FMTBIT_S24_3BE; /* grrr, big endian!! */
+ else
+ pcm_formats |= SNDRV_PCM_FMTBIT_S24_3LE;
+ break;
+ case 4:
+ pcm_formats |= SNDRV_PCM_FMTBIT_S32_LE;
+ break;
+ default:
+ usb_audio_info(chip,
+ "%u:%d : unsupported sample bitwidth %d in %d bytes\n",
+ fp->iface, fp->altsetting,
+ sample_width, sample_bytes);
+ break;
+ }
+ }
+ if (format & (1 << UAC_FORMAT_TYPE_I_PCM8)) {
+ /* Dallas DS4201 workaround: it advertises U8 format, but really
+ supports S8. */
+ if (chip->usb_id == USB_ID(0x04fa, 0x4201))
+ pcm_formats |= SNDRV_PCM_FMTBIT_S8;
+ else
+ pcm_formats |= SNDRV_PCM_FMTBIT_U8;
+ }
+ if (format & (1 << UAC_FORMAT_TYPE_I_IEEE_FLOAT)) {
+ pcm_formats |= SNDRV_PCM_FMTBIT_FLOAT_LE;
+ }
+ if (format & (1 << UAC_FORMAT_TYPE_I_ALAW)) {
+ pcm_formats |= SNDRV_PCM_FMTBIT_A_LAW;
+ }
+ if (format & (1 << UAC_FORMAT_TYPE_I_MULAW)) {
+ pcm_formats |= SNDRV_PCM_FMTBIT_MU_LAW;
+ }
+ if (format & ~0x3f) {
+ usb_audio_info(chip,
+ "%u:%d : unsupported format bits %#x\n",
+ fp->iface, fp->altsetting, format);
+ }
+
+ pcm_formats |= snd_usb_interface_dsd_format_quirks(chip, fp, sample_bytes);
+
+ return pcm_formats;
+}
+
+
+/*
+ * parse the format descriptor and stores the possible sample rates
+ * on the audioformat table (audio class v1).
+ *
+ * @dev: usb device
+ * @fp: audioformat record
+ * @fmt: the format descriptor
+ * @offset: the start offset of descriptor pointing the rate type
+ * (7 for type I and II, 8 for type II)
+ */
+static int parse_audio_format_rates_v1(struct snd_usb_audio *chip, struct audioformat *fp,
+ unsigned char *fmt, int offset)
+{
+ int nr_rates = fmt[offset];
+
+ if (fmt[0] < offset + 1 + 3 * (nr_rates ? nr_rates : 2)) {
+ usb_audio_err(chip,
+ "%u:%d : invalid UAC_FORMAT_TYPE desc\n",
+ fp->iface, fp->altsetting);
+ return -EINVAL;
+ }
+
+ if (nr_rates) {
+ /*
+ * build the rate table and bitmap flags
+ */
+ int r, idx;
+
+ fp->rate_table = kmalloc(sizeof(int) * nr_rates, GFP_KERNEL);
+ if (fp->rate_table == NULL) {
+ usb_audio_err(chip, "cannot malloc\n");
+ return -ENOMEM;
+ }
+
+ fp->nr_rates = 0;
+ fp->rate_min = fp->rate_max = 0;
+ for (r = 0, idx = offset + 1; r < nr_rates; r++, idx += 3) {
+ unsigned int rate = combine_triple(&fmt[idx]);
+ if (!rate)
+ continue;
+ /* C-Media CM6501 mislabels its 96 kHz altsetting */
+ /* Terratec Aureon 7.1 USB C-Media 6206, too */
+ if (rate == 48000 && nr_rates == 1 &&
+ (chip->usb_id == USB_ID(0x0d8c, 0x0201) ||
+ chip->usb_id == USB_ID(0x0d8c, 0x0102) ||
+ chip->usb_id == USB_ID(0x0ccd, 0x00b1)) &&
+ fp->altsetting == 5 && fp->maxpacksize == 392)
+ rate = 96000;
+ /* Creative VF0420/VF0470 Live Cams report 16 kHz instead of 8kHz */
+ if (rate == 16000 &&
+ (chip->usb_id == USB_ID(0x041e, 0x4064) ||
+ chip->usb_id == USB_ID(0x041e, 0x4068)))
+ rate = 8000;
+
+ fp->rate_table[fp->nr_rates] = rate;
+ if (!fp->rate_min || rate < fp->rate_min)
+ fp->rate_min = rate;
+ if (!fp->rate_max || rate > fp->rate_max)
+ fp->rate_max = rate;
+ fp->rates |= snd_pcm_rate_to_rate_bit(rate);
+ fp->nr_rates++;
+ }
+ if (!fp->nr_rates) {
+ hwc_debug("All rates were zero. Skipping format!\n");
+ return -EINVAL;
+ }
+ } else {
+ /* continuous rates */
+ fp->rates = SNDRV_PCM_RATE_CONTINUOUS;
+ fp->rate_min = combine_triple(&fmt[offset + 1]);
+ fp->rate_max = combine_triple(&fmt[offset + 4]);
+ }
+ return 0;
+}
+
+/*
+ * Helper function to walk the array of sample rate triplets reported by
+ * the device. The problem is that we need to parse whole array first to
+ * get to know how many sample rates we have to expect.
+ * Then fp->rate_table can be allocated and filled.
+ */
+static int parse_uac2_sample_rate_range(struct snd_usb_audio *chip,
+ struct audioformat *fp, int nr_triplets,
+ const unsigned char *data)
+{
+ int i, nr_rates = 0;
+
+ fp->rates = fp->rate_min = fp->rate_max = 0;
+
+ for (i = 0; i < nr_triplets; i++) {
+ int min = combine_quad(&data[2 + 12 * i]);
+ int max = combine_quad(&data[6 + 12 * i]);
+ int res = combine_quad(&data[10 + 12 * i]);
+ unsigned int rate;
+
+ if ((max < 0) || (min < 0) || (res < 0) || (max < min))
+ continue;
+
+ /*
+ * for ranges with res == 1, we announce a continuous sample
+ * rate range, and this function should return 0 for no further
+ * parsing.
+ */
+ if (res == 1) {
+ fp->rate_min = min;
+ fp->rate_max = max;
+ fp->rates = SNDRV_PCM_RATE_CONTINUOUS;
+ return 0;
+ }
+
+ for (rate = min; rate <= max; rate += res) {
+ if (fp->rate_table)
+ fp->rate_table[nr_rates] = rate;
+ if (!fp->rate_min || rate < fp->rate_min)
+ fp->rate_min = rate;
+ if (!fp->rate_max || rate > fp->rate_max)
+ fp->rate_max = rate;
+ fp->rates |= snd_pcm_rate_to_rate_bit(rate);
+
+ nr_rates++;
+ if (nr_rates >= MAX_NR_RATES) {
+ usb_audio_err(chip, "invalid uac2 rates\n");
+ break;
+ }
+
+ /* avoid endless loop */
+ if (res == 0)
+ break;
+ }
+ }
+
+ return nr_rates;
+}
+
+/*
+ * parse the format descriptor and stores the possible sample rates
+ * on the audioformat table (audio class v2).
+ */
+static int parse_audio_format_rates_v2(struct snd_usb_audio *chip,
+ struct audioformat *fp)
+{
+ struct usb_device *dev = chip->dev;
+ unsigned char tmp[2], *data;
+ int nr_triplets, data_size, ret = 0;
+ int clock = snd_usb_clock_find_source(chip, fp->clock, false);
+
+ if (clock < 0) {
+ dev_err(&dev->dev,
+ "%s(): unable to find clock source (clock %d)\n",
+ __func__, clock);
+ goto err;
+ }
+
+ /* get the number of sample rates first by only fetching 2 bytes */
+ ret = snd_usb_ctl_msg(dev, usb_rcvctrlpipe(dev, 0), UAC2_CS_RANGE,
+ USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_IN,
+ UAC2_CS_CONTROL_SAM_FREQ << 8,
+ snd_usb_ctrl_intf(chip) | (clock << 8),
+ tmp, sizeof(tmp));
+
+ if (ret < 0) {
+ dev_err(&dev->dev,
+ "%s(): unable to retrieve number of sample rates (clock %d)\n",
+ __func__, clock);
+ goto err;
+ }
+
+ nr_triplets = (tmp[1] << 8) | tmp[0];
+ data_size = 2 + 12 * nr_triplets;
+ data = kzalloc(data_size, GFP_KERNEL);
+ if (!data) {
+ ret = -ENOMEM;
+ goto err;
+ }
+
+ /* now get the full information */
+ ret = snd_usb_ctl_msg(dev, usb_rcvctrlpipe(dev, 0), UAC2_CS_RANGE,
+ USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_IN,
+ UAC2_CS_CONTROL_SAM_FREQ << 8,
+ snd_usb_ctrl_intf(chip) | (clock << 8),
+ data, data_size);
+
+ if (ret < 0) {
+ dev_err(&dev->dev,
+ "%s(): unable to retrieve sample rate range (clock %d)\n",
+ __func__, clock);
+ ret = -EINVAL;
+ goto err_free;
+ }
+
+ /* Call the triplet parser, and make sure fp->rate_table is NULL.
+ * We just use the return value to know how many sample rates we
+ * will have to deal with. */
+ kfree(fp->rate_table);
+ fp->rate_table = NULL;
+ fp->nr_rates = parse_uac2_sample_rate_range(chip, fp, nr_triplets, data);
+
+ if (fp->nr_rates == 0) {
+ /* SNDRV_PCM_RATE_CONTINUOUS */
+ ret = 0;
+ goto err_free;
+ }
+
+ fp->rate_table = kmalloc(sizeof(int) * fp->nr_rates, GFP_KERNEL);
+ if (!fp->rate_table) {
+ ret = -ENOMEM;
+ goto err_free;
+ }
+
+ /* Call the triplet parser again, but this time, fp->rate_table is
+ * allocated, so the rates will be stored */
+ parse_uac2_sample_rate_range(chip, fp, nr_triplets, data);
+
+err_free:
+ kfree(data);
+err:
+ return ret;
+}
+
+/*
+ * parse the format type I and III descriptors
+ */
+static int parse_audio_format_i(struct snd_usb_audio *chip,
+ struct audioformat *fp, unsigned int format,
+ struct uac_format_type_i_continuous_descriptor *fmt)
+{
+ snd_pcm_format_t pcm_format;
+ int ret;
+
+ if (fmt->bFormatType == UAC_FORMAT_TYPE_III) {
+ /* FIXME: the format type is really IECxxx
+ * but we give normal PCM format to get the existing
+ * apps working...
+ */
+ switch (chip->usb_id) {
+
+ case USB_ID(0x0763, 0x2003): /* M-Audio Audiophile USB */
+ if (chip->setup == 0x00 &&
+ fp->altsetting == 6)
+ pcm_format = SNDRV_PCM_FORMAT_S16_BE;
+ else
+ pcm_format = SNDRV_PCM_FORMAT_S16_LE;
+ break;
+ default:
+ pcm_format = SNDRV_PCM_FORMAT_S16_LE;
+ }
+ fp->formats = pcm_format_to_bits(pcm_format);
+ } else {
+ fp->formats = parse_audio_format_i_type(chip, fp, format, fmt);
+ if (!fp->formats)
+ return -EINVAL;
+ }
+
+ /* gather possible sample rates */
+ /* audio class v1 reports possible sample rates as part of the
+ * proprietary class specific descriptor.
+ * audio class v2 uses class specific EP0 range requests for that.
+ */
+ switch (fp->protocol) {
+ default:
+ case UAC_VERSION_1:
+ fp->channels = fmt->bNrChannels;
+ ret = parse_audio_format_rates_v1(chip, fp, (unsigned char *) fmt, 7);
+ break;
+ case UAC_VERSION_2:
+ /* fp->channels is already set in this case */
+ ret = parse_audio_format_rates_v2(chip, fp);
+ break;
+ }
+
+ if (fp->channels < 1) {
+ usb_audio_err(chip,
+ "%u:%d : invalid channels %d\n",
+ fp->iface, fp->altsetting, fp->channels);
+ return -EINVAL;
+ }
+
+ return ret;
+}
+
+/*
+ * parse the format type II descriptor
+ */
+static int parse_audio_format_ii(struct snd_usb_audio *chip,
+ struct audioformat *fp,
+ int format, void *_fmt)
+{
+ int brate, framesize, ret;
+
+ switch (format) {
+ case UAC_FORMAT_TYPE_II_AC3:
+ /* FIXME: there is no AC3 format defined yet */
+ // fp->formats = SNDRV_PCM_FMTBIT_AC3;
+ fp->formats = SNDRV_PCM_FMTBIT_U8; /* temporary hack to receive byte streams */
+ break;
+ case UAC_FORMAT_TYPE_II_MPEG:
+ fp->formats = SNDRV_PCM_FMTBIT_MPEG;
+ break;
+ default:
+ usb_audio_info(chip,
+ "%u:%d : unknown format tag %#x is detected. processed as MPEG.\n",
+ fp->iface, fp->altsetting, format);
+ fp->formats = SNDRV_PCM_FMTBIT_MPEG;
+ break;
+ }
+
+ fp->channels = 1;
+
+ switch (fp->protocol) {
+ default:
+ case UAC_VERSION_1: {
+ struct uac_format_type_ii_discrete_descriptor *fmt = _fmt;
+ brate = le16_to_cpu(fmt->wMaxBitRate);
+ framesize = le16_to_cpu(fmt->wSamplesPerFrame);
+ usb_audio_info(chip, "found format II with max.bitrate = %d, frame size=%d\n", brate, framesize);
+ fp->frame_size = framesize;
+ ret = parse_audio_format_rates_v1(chip, fp, _fmt, 8); /* fmt[8..] sample rates */
+ break;
+ }
+ case UAC_VERSION_2: {
+ struct uac_format_type_ii_ext_descriptor *fmt = _fmt;
+ brate = le16_to_cpu(fmt->wMaxBitRate);
+ framesize = le16_to_cpu(fmt->wSamplesPerFrame);
+ usb_audio_info(chip, "found format II with max.bitrate = %d, frame size=%d\n", brate, framesize);
+ fp->frame_size = framesize;
+ ret = parse_audio_format_rates_v2(chip, fp);
+ break;
+ }
+ }
+
+ return ret;
+}
+
+int snd_usb_parse_audio_format(struct snd_usb_audio *chip,
+ struct audioformat *fp, unsigned int format,
+ struct uac_format_type_i_continuous_descriptor *fmt,
+ int stream)
+{
+ int err;
+
+ switch (fmt->bFormatType) {
+ case UAC_FORMAT_TYPE_I:
+ case UAC_FORMAT_TYPE_III:
+ err = parse_audio_format_i(chip, fp, format, fmt);
+ break;
+ case UAC_FORMAT_TYPE_II:
+ err = parse_audio_format_ii(chip, fp, format, fmt);
+ break;
+ default:
+ usb_audio_info(chip,
+ "%u:%d : format type %d is not supported yet\n",
+ fp->iface, fp->altsetting,
+ fmt->bFormatType);
+ return -ENOTSUPP;
+ }
+ fp->fmt_type = fmt->bFormatType;
+ if (err < 0)
+ return err;
+#if 1
+ /* FIXME: temporary hack for extigy/audigy 2 nx/zs */
+ /* extigy apparently supports sample rates other than 48k
+ * but not in ordinary way. so we enable only 48k atm.
+ */
+ if (chip->usb_id == USB_ID(0x041e, 0x3000) ||
+ chip->usb_id == USB_ID(0x041e, 0x3020) ||
+ chip->usb_id == USB_ID(0x041e, 0x3061)) {
+ if (fmt->bFormatType == UAC_FORMAT_TYPE_I &&
+ fp->rates != SNDRV_PCM_RATE_48000 &&
+ fp->rates != SNDRV_PCM_RATE_96000)
+ return -ENOTSUPP;
+ }
+#endif
+ return 0;
+}
+
diff --git a/sound/usb/format.h b/sound/usb/format.h
new file mode 100644
index 000000000..4b8a01129
--- /dev/null
+++ b/sound/usb/format.h
@@ -0,0 +1,9 @@
+#ifndef __USBAUDIO_FORMAT_H
+#define __USBAUDIO_FORMAT_H
+
+int snd_usb_parse_audio_format(struct snd_usb_audio *chip,
+ struct audioformat *fp, unsigned int format,
+ struct uac_format_type_i_continuous_descriptor *fmt,
+ int stream);
+
+#endif /* __USBAUDIO_FORMAT_H */
diff --git a/sound/usb/helper.c b/sound/usb/helper.c
new file mode 100644
index 000000000..51ed1ac82
--- /dev/null
+++ b/sound/usb/helper.c
@@ -0,0 +1,132 @@
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/usb.h>
+
+#include "usbaudio.h"
+#include "helper.h"
+#include "quirks.h"
+
+/*
+ * combine bytes and get an integer value
+ */
+unsigned int snd_usb_combine_bytes(unsigned char *bytes, int size)
+{
+ switch (size) {
+ case 1: return *bytes;
+ case 2: return combine_word(bytes);
+ case 3: return combine_triple(bytes);
+ case 4: return combine_quad(bytes);
+ default: return 0;
+ }
+}
+
+/*
+ * parse descriptor buffer and return the pointer starting the given
+ * descriptor type.
+ */
+void *snd_usb_find_desc(void *descstart, int desclen, void *after, u8 dtype)
+{
+ u8 *p, *end, *next;
+
+ p = descstart;
+ end = p + desclen;
+ for (; p < end;) {
+ if (p[0] < 2)
+ return NULL;
+ next = p + p[0];
+ if (next > end)
+ return NULL;
+ if (p[1] == dtype && (!after || (void *)p > after)) {
+ return p;
+ }
+ p = next;
+ }
+ return NULL;
+}
+
+/*
+ * find a class-specified interface descriptor with the given subtype.
+ */
+void *snd_usb_find_csint_desc(void *buffer, int buflen, void *after, u8 dsubtype)
+{
+ unsigned char *p = after;
+
+ while ((p = snd_usb_find_desc(buffer, buflen, p,
+ USB_DT_CS_INTERFACE)) != NULL) {
+ if (p[0] >= 3 && p[2] == dsubtype)
+ return p;
+ }
+ return NULL;
+}
+
+/*
+ * Wrapper for usb_control_msg().
+ * Allocates a temp buffer to prevent dmaing from/to the stack.
+ */
+int snd_usb_ctl_msg(struct usb_device *dev, unsigned int pipe, __u8 request,
+ __u8 requesttype, __u16 value, __u16 index, void *data,
+ __u16 size)
+{
+ int err;
+ void *buf = NULL;
+ int timeout;
+
+ if (size > 0) {
+ buf = kmemdup(data, size, GFP_KERNEL);
+ if (!buf)
+ return -ENOMEM;
+ }
+
+ if (requesttype & USB_DIR_IN)
+ timeout = USB_CTRL_GET_TIMEOUT;
+ else
+ timeout = USB_CTRL_SET_TIMEOUT;
+
+ err = usb_control_msg(dev, pipe, request, requesttype,
+ value, index, buf, size, timeout);
+
+ if (size > 0) {
+ memcpy(data, buf, size);
+ kfree(buf);
+ }
+
+ snd_usb_ctl_msg_quirk(dev, pipe, request, requesttype,
+ value, index, data, size);
+
+ return err;
+}
+
+unsigned char snd_usb_parse_datainterval(struct snd_usb_audio *chip,
+ struct usb_host_interface *alts)
+{
+ switch (snd_usb_get_speed(chip->dev)) {
+ case USB_SPEED_HIGH:
+ case USB_SPEED_WIRELESS:
+ case USB_SPEED_SUPER:
+ if (get_endpoint(alts, 0)->bInterval >= 1 &&
+ get_endpoint(alts, 0)->bInterval <= 4)
+ return get_endpoint(alts, 0)->bInterval - 1;
+ break;
+ default:
+ break;
+ }
+ return 0;
+}
+
diff --git a/sound/usb/helper.h b/sound/usb/helper.h
new file mode 100644
index 000000000..805c300dd
--- /dev/null
+++ b/sound/usb/helper.h
@@ -0,0 +1,36 @@
+#ifndef __USBAUDIO_HELPER_H
+#define __USBAUDIO_HELPER_H
+
+unsigned int snd_usb_combine_bytes(unsigned char *bytes, int size);
+
+void *snd_usb_find_desc(void *descstart, int desclen, void *after, u8 dtype);
+void *snd_usb_find_csint_desc(void *descstart, int desclen, void *after, u8 dsubtype);
+
+int snd_usb_ctl_msg(struct usb_device *dev, unsigned int pipe,
+ __u8 request, __u8 requesttype, __u16 value, __u16 index,
+ void *data, __u16 size);
+
+unsigned char snd_usb_parse_datainterval(struct snd_usb_audio *chip,
+ struct usb_host_interface *alts);
+
+/*
+ * retrieve usb_interface descriptor from the host interface
+ * (conditional for compatibility with the older API)
+ */
+#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 snd_usb_get_speed
+#define snd_usb_get_speed(dev) ((dev)->speed)
+#endif
+
+static inline int snd_usb_ctrl_intf(struct snd_usb_audio *chip)
+{
+ return get_iface_desc(chip->ctrl_intf)->bInterfaceNumber;
+}
+
+#endif /* __USBAUDIO_HELPER_H */
diff --git a/sound/usb/hiface/Makefile b/sound/usb/hiface/Makefile
new file mode 100644
index 000000000..463b136d1
--- /dev/null
+++ b/sound/usb/hiface/Makefile
@@ -0,0 +1,2 @@
+snd-usb-hiface-objs := chip.o pcm.o
+obj-$(CONFIG_SND_USB_HIFACE) += snd-usb-hiface.o
diff --git a/sound/usb/hiface/chip.c b/sound/usb/hiface/chip.c
new file mode 100644
index 000000000..2670d646b
--- /dev/null
+++ b/sound/usb/hiface/chip.c
@@ -0,0 +1,297 @@
+/*
+ * Linux driver for M2Tech hiFace compatible devices
+ *
+ * Copyright 2012-2013 (C) M2TECH S.r.l and Amarula Solutions B.V.
+ *
+ * Authors: Michael Trimarchi <michael@amarulasolutions.com>
+ * Antonio Ospite <ao2@amarulasolutions.com>
+ *
+ * The driver is based on the work done in TerraTec DMX 6Fire USB
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <sound/initval.h>
+
+#include "chip.h"
+#include "pcm.h"
+
+MODULE_AUTHOR("Michael Trimarchi <michael@amarulasolutions.com>");
+MODULE_AUTHOR("Antonio Ospite <ao2@amarulasolutions.com>");
+MODULE_DESCRIPTION("M2Tech hiFace USB-SPDIF audio driver");
+MODULE_LICENSE("GPL v2");
+MODULE_SUPPORTED_DEVICE("{{M2Tech,Young},"
+ "{M2Tech,hiFace},"
+ "{M2Tech,North Star},"
+ "{M2Tech,W4S Young},"
+ "{M2Tech,Corrson},"
+ "{M2Tech,AUDIA},"
+ "{M2Tech,SL Audio},"
+ "{M2Tech,Empirical},"
+ "{M2Tech,Rockna},"
+ "{M2Tech,Pathos},"
+ "{M2Tech,Metronome},"
+ "{M2Tech,CAD},"
+ "{M2Tech,Audio Esclusive},"
+ "{M2Tech,Rotel},"
+ "{M2Tech,Eeaudio},"
+ "{The Chord Company,CHORD},"
+ "{AVA Group A/S,Vitus}}");
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 0-max */
+static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* Id for card */
+static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP; /* Enable this card */
+
+#define DRIVER_NAME "snd-usb-hiface"
+#define CARD_NAME "hiFace"
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for " CARD_NAME " soundcard.");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for " CARD_NAME " soundcard.");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "Enable " CARD_NAME " soundcard.");
+
+static DEFINE_MUTEX(register_mutex);
+
+struct hiface_vendor_quirk {
+ const char *device_name;
+ u8 extra_freq;
+};
+
+static int hiface_chip_create(struct usb_interface *intf,
+ struct usb_device *device, int idx,
+ const struct hiface_vendor_quirk *quirk,
+ struct hiface_chip **rchip)
+{
+ struct snd_card *card = NULL;
+ struct hiface_chip *chip;
+ int ret;
+ int len;
+
+ *rchip = NULL;
+
+ /* if we are here, card can be registered in alsa. */
+ ret = snd_card_new(&intf->dev, index[idx], id[idx], THIS_MODULE,
+ sizeof(*chip), &card);
+ if (ret < 0) {
+ dev_err(&device->dev, "cannot create alsa card.\n");
+ return ret;
+ }
+
+ strlcpy(card->driver, DRIVER_NAME, sizeof(card->driver));
+
+ if (quirk && quirk->device_name)
+ strlcpy(card->shortname, quirk->device_name, sizeof(card->shortname));
+ else
+ strlcpy(card->shortname, "M2Tech generic audio", sizeof(card->shortname));
+
+ strlcat(card->longname, card->shortname, sizeof(card->longname));
+ len = strlcat(card->longname, " at ", sizeof(card->longname));
+ if (len < sizeof(card->longname))
+ usb_make_path(device, card->longname + len,
+ sizeof(card->longname) - len);
+
+ chip = card->private_data;
+ chip->dev = device;
+ chip->card = card;
+
+ *rchip = chip;
+ return 0;
+}
+
+static int hiface_chip_probe(struct usb_interface *intf,
+ const struct usb_device_id *usb_id)
+{
+ const struct hiface_vendor_quirk *quirk = (struct hiface_vendor_quirk *)usb_id->driver_info;
+ int ret;
+ int i;
+ struct hiface_chip *chip;
+ struct usb_device *device = interface_to_usbdev(intf);
+
+ ret = usb_set_interface(device, 0, 0);
+ if (ret != 0) {
+ dev_err(&device->dev, "can't set first interface for " CARD_NAME " device.\n");
+ return -EIO;
+ }
+
+ /* check whether the card is already registered */
+ chip = NULL;
+ mutex_lock(&register_mutex);
+
+ for (i = 0; i < SNDRV_CARDS; i++)
+ if (enable[i])
+ break;
+
+ if (i >= SNDRV_CARDS) {
+ dev_err(&device->dev, "no available " CARD_NAME " audio device\n");
+ ret = -ENODEV;
+ goto err;
+ }
+
+ ret = hiface_chip_create(intf, device, i, quirk, &chip);
+ if (ret < 0)
+ goto err;
+
+ ret = hiface_pcm_init(chip, quirk ? quirk->extra_freq : 0);
+ if (ret < 0)
+ goto err_chip_destroy;
+
+ ret = snd_card_register(chip->card);
+ if (ret < 0) {
+ dev_err(&device->dev, "cannot register " CARD_NAME " card\n");
+ goto err_chip_destroy;
+ }
+
+ mutex_unlock(&register_mutex);
+
+ usb_set_intfdata(intf, chip);
+ return 0;
+
+err_chip_destroy:
+ snd_card_free(chip->card);
+err:
+ mutex_unlock(&register_mutex);
+ return ret;
+}
+
+static void hiface_chip_disconnect(struct usb_interface *intf)
+{
+ struct hiface_chip *chip;
+ struct snd_card *card;
+
+ chip = usb_get_intfdata(intf);
+ if (!chip)
+ return;
+
+ card = chip->card;
+
+ /* Make sure that the userspace cannot create new request */
+ snd_card_disconnect(card);
+
+ hiface_pcm_abort(chip);
+ snd_card_free_when_closed(card);
+}
+
+static const struct usb_device_id device_table[] = {
+ {
+ USB_DEVICE(0x04b4, 0x0384),
+ .driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
+ .device_name = "Young",
+ .extra_freq = 1,
+ }
+ },
+ {
+ USB_DEVICE(0x04b4, 0x930b),
+ .driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
+ .device_name = "hiFace",
+ }
+ },
+ {
+ USB_DEVICE(0x04b4, 0x931b),
+ .driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
+ .device_name = "North Star",
+ }
+ },
+ {
+ USB_DEVICE(0x04b4, 0x931c),
+ .driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
+ .device_name = "W4S Young",
+ }
+ },
+ {
+ USB_DEVICE(0x04b4, 0x931d),
+ .driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
+ .device_name = "Corrson",
+ }
+ },
+ {
+ USB_DEVICE(0x04b4, 0x931e),
+ .driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
+ .device_name = "AUDIA",
+ }
+ },
+ {
+ USB_DEVICE(0x04b4, 0x931f),
+ .driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
+ .device_name = "SL Audio",
+ }
+ },
+ {
+ USB_DEVICE(0x04b4, 0x9320),
+ .driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
+ .device_name = "Empirical",
+ }
+ },
+ {
+ USB_DEVICE(0x04b4, 0x9321),
+ .driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
+ .device_name = "Rockna",
+ }
+ },
+ {
+ USB_DEVICE(0x249c, 0x9001),
+ .driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
+ .device_name = "Pathos",
+ }
+ },
+ {
+ USB_DEVICE(0x249c, 0x9002),
+ .driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
+ .device_name = "Metronome",
+ }
+ },
+ {
+ USB_DEVICE(0x249c, 0x9006),
+ .driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
+ .device_name = "CAD",
+ }
+ },
+ {
+ USB_DEVICE(0x249c, 0x9008),
+ .driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
+ .device_name = "Audio Esclusive",
+ }
+ },
+ {
+ USB_DEVICE(0x249c, 0x931c),
+ .driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
+ .device_name = "Rotel",
+ }
+ },
+ {
+ USB_DEVICE(0x249c, 0x932c),
+ .driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
+ .device_name = "Eeaudio",
+ }
+ },
+ {
+ USB_DEVICE(0x245f, 0x931c),
+ .driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
+ .device_name = "CHORD",
+ }
+ },
+ {
+ USB_DEVICE(0x25c6, 0x9002),
+ .driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
+ .device_name = "Vitus",
+ }
+ },
+ {}
+};
+
+MODULE_DEVICE_TABLE(usb, device_table);
+
+static struct usb_driver hiface_usb_driver = {
+ .name = DRIVER_NAME,
+ .probe = hiface_chip_probe,
+ .disconnect = hiface_chip_disconnect,
+ .id_table = device_table,
+};
+
+module_usb_driver(hiface_usb_driver);
diff --git a/sound/usb/hiface/chip.h b/sound/usb/hiface/chip.h
new file mode 100644
index 000000000..189a1371b
--- /dev/null
+++ b/sound/usb/hiface/chip.h
@@ -0,0 +1,30 @@
+/*
+ * Linux driver for M2Tech hiFace compatible devices
+ *
+ * Copyright 2012-2013 (C) M2TECH S.r.l and Amarula Solutions B.V.
+ *
+ * Authors: Michael Trimarchi <michael@amarulasolutions.com>
+ * Antonio Ospite <ao2@amarulasolutions.com>
+ *
+ * The driver is based on the work done in TerraTec DMX 6Fire USB
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#ifndef HIFACE_CHIP_H
+#define HIFACE_CHIP_H
+
+#include <linux/usb.h>
+#include <sound/core.h>
+
+struct pcm_runtime;
+
+struct hiface_chip {
+ struct usb_device *dev;
+ struct snd_card *card;
+ struct pcm_runtime *pcm;
+};
+#endif /* HIFACE_CHIP_H */
diff --git a/sound/usb/hiface/pcm.c b/sound/usb/hiface/pcm.c
new file mode 100644
index 000000000..2c44139b4
--- /dev/null
+++ b/sound/usb/hiface/pcm.c
@@ -0,0 +1,621 @@
+/*
+ * Linux driver for M2Tech hiFace compatible devices
+ *
+ * Copyright 2012-2013 (C) M2TECH S.r.l and Amarula Solutions B.V.
+ *
+ * Authors: Michael Trimarchi <michael@amarulasolutions.com>
+ * Antonio Ospite <ao2@amarulasolutions.com>
+ *
+ * The driver is based on the work done in TerraTec DMX 6Fire USB
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#include <linux/slab.h>
+#include <sound/pcm.h>
+
+#include "pcm.h"
+#include "chip.h"
+
+#define OUT_EP 0x2
+#define PCM_N_URBS 8
+#define PCM_PACKET_SIZE 4096
+#define PCM_BUFFER_SIZE (2 * PCM_N_URBS * PCM_PACKET_SIZE)
+
+struct pcm_urb {
+ struct hiface_chip *chip;
+
+ struct urb instance;
+ struct usb_anchor submitted;
+ u8 *buffer;
+};
+
+struct pcm_substream {
+ spinlock_t lock;
+ struct snd_pcm_substream *instance;
+
+ bool active;
+ snd_pcm_uframes_t dma_off; /* current position in alsa dma_area */
+ snd_pcm_uframes_t period_off; /* current position in current period */
+};
+
+enum { /* pcm streaming states */
+ STREAM_DISABLED, /* no pcm streaming */
+ STREAM_STARTING, /* pcm streaming requested, waiting to become ready */
+ STREAM_RUNNING, /* pcm streaming running */
+ STREAM_STOPPING
+};
+
+struct pcm_runtime {
+ struct hiface_chip *chip;
+ struct snd_pcm *instance;
+
+ struct pcm_substream playback;
+ bool panic; /* if set driver won't do anymore pcm on device */
+
+ struct pcm_urb out_urbs[PCM_N_URBS];
+
+ struct mutex stream_mutex;
+ u8 stream_state; /* one of STREAM_XXX */
+ u8 extra_freq;
+ wait_queue_head_t stream_wait_queue;
+ bool stream_wait_cond;
+};
+
+static const unsigned int rates[] = { 44100, 48000, 88200, 96000, 176400, 192000,
+ 352800, 384000 };
+static const struct snd_pcm_hw_constraint_list constraints_extra_rates = {
+ .count = ARRAY_SIZE(rates),
+ .list = rates,
+ .mask = 0,
+};
+
+static const struct snd_pcm_hardware pcm_hw = {
+ .info = SNDRV_PCM_INFO_MMAP |
+ SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_BLOCK_TRANSFER |
+ SNDRV_PCM_INFO_PAUSE |
+ SNDRV_PCM_INFO_MMAP_VALID |
+ SNDRV_PCM_INFO_BATCH,
+
+ .formats = SNDRV_PCM_FMTBIT_S32_LE,
+
+ .rates = SNDRV_PCM_RATE_44100 |
+ SNDRV_PCM_RATE_48000 |
+ SNDRV_PCM_RATE_88200 |
+ SNDRV_PCM_RATE_96000 |
+ SNDRV_PCM_RATE_176400 |
+ SNDRV_PCM_RATE_192000,
+
+ .rate_min = 44100,
+ .rate_max = 192000, /* changes in hiface_pcm_open to support extra rates */
+ .channels_min = 2,
+ .channels_max = 2,
+ .buffer_bytes_max = PCM_BUFFER_SIZE,
+ .period_bytes_min = PCM_PACKET_SIZE,
+ .period_bytes_max = PCM_BUFFER_SIZE,
+ .periods_min = 2,
+ .periods_max = 1024
+};
+
+/* message values used to change the sample rate */
+#define HIFACE_SET_RATE_REQUEST 0xb0
+
+#define HIFACE_RATE_44100 0x43
+#define HIFACE_RATE_48000 0x4b
+#define HIFACE_RATE_88200 0x42
+#define HIFACE_RATE_96000 0x4a
+#define HIFACE_RATE_176400 0x40
+#define HIFACE_RATE_192000 0x48
+#define HIFACE_RATE_352800 0x58
+#define HIFACE_RATE_384000 0x68
+
+static int hiface_pcm_set_rate(struct pcm_runtime *rt, unsigned int rate)
+{
+ struct usb_device *device = rt->chip->dev;
+ u16 rate_value;
+ int ret;
+
+ /* We are already sure that the rate is supported here thanks to
+ * ALSA constraints
+ */
+ switch (rate) {
+ case 44100:
+ rate_value = HIFACE_RATE_44100;
+ break;
+ case 48000:
+ rate_value = HIFACE_RATE_48000;
+ break;
+ case 88200:
+ rate_value = HIFACE_RATE_88200;
+ break;
+ case 96000:
+ rate_value = HIFACE_RATE_96000;
+ break;
+ case 176400:
+ rate_value = HIFACE_RATE_176400;
+ break;
+ case 192000:
+ rate_value = HIFACE_RATE_192000;
+ break;
+ case 352800:
+ rate_value = HIFACE_RATE_352800;
+ break;
+ case 384000:
+ rate_value = HIFACE_RATE_384000;
+ break;
+ default:
+ dev_err(&device->dev, "Unsupported rate %d\n", rate);
+ return -EINVAL;
+ }
+
+ /*
+ * USBIO: Vendor 0xb0(wValue=0x0043, wIndex=0x0000)
+ * 43 b0 43 00 00 00 00 00
+ * USBIO: Vendor 0xb0(wValue=0x004b, wIndex=0x0000)
+ * 43 b0 4b 00 00 00 00 00
+ * This control message doesn't have any ack from the
+ * other side
+ */
+ ret = usb_control_msg(device, usb_sndctrlpipe(device, 0),
+ HIFACE_SET_RATE_REQUEST,
+ USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_OTHER,
+ rate_value, 0, NULL, 0, 100);
+ if (ret < 0) {
+ dev_err(&device->dev, "Error setting samplerate %d.\n", rate);
+ return ret;
+ }
+
+ return 0;
+}
+
+static struct pcm_substream *hiface_pcm_get_substream(struct snd_pcm_substream
+ *alsa_sub)
+{
+ struct pcm_runtime *rt = snd_pcm_substream_chip(alsa_sub);
+ struct device *device = &rt->chip->dev->dev;
+
+ if (alsa_sub->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ return &rt->playback;
+
+ dev_err(device, "Error getting pcm substream slot.\n");
+ return NULL;
+}
+
+/* call with stream_mutex locked */
+static void hiface_pcm_stream_stop(struct pcm_runtime *rt)
+{
+ int i, time;
+
+ if (rt->stream_state != STREAM_DISABLED) {
+ rt->stream_state = STREAM_STOPPING;
+
+ for (i = 0; i < PCM_N_URBS; i++) {
+ time = usb_wait_anchor_empty_timeout(
+ &rt->out_urbs[i].submitted, 100);
+ if (!time)
+ usb_kill_anchored_urbs(
+ &rt->out_urbs[i].submitted);
+ usb_kill_urb(&rt->out_urbs[i].instance);
+ }
+
+ rt->stream_state = STREAM_DISABLED;
+ }
+}
+
+/* call with stream_mutex locked */
+static int hiface_pcm_stream_start(struct pcm_runtime *rt)
+{
+ int ret = 0;
+ int i;
+
+ if (rt->stream_state == STREAM_DISABLED) {
+
+ /* reset panic state when starting a new stream */
+ rt->panic = false;
+
+ /* submit our out urbs zero init */
+ rt->stream_state = STREAM_STARTING;
+ for (i = 0; i < PCM_N_URBS; i++) {
+ memset(rt->out_urbs[i].buffer, 0, PCM_PACKET_SIZE);
+ usb_anchor_urb(&rt->out_urbs[i].instance,
+ &rt->out_urbs[i].submitted);
+ ret = usb_submit_urb(&rt->out_urbs[i].instance,
+ GFP_ATOMIC);
+ if (ret) {
+ hiface_pcm_stream_stop(rt);
+ return ret;
+ }
+ }
+
+ /* wait for first out urb to return (sent in in urb handler) */
+ wait_event_timeout(rt->stream_wait_queue, rt->stream_wait_cond,
+ HZ);
+ if (rt->stream_wait_cond) {
+ struct device *device = &rt->chip->dev->dev;
+ dev_dbg(device, "%s: Stream is running wakeup event\n",
+ __func__);
+ rt->stream_state = STREAM_RUNNING;
+ } else {
+ hiface_pcm_stream_stop(rt);
+ return -EIO;
+ }
+ }
+ return ret;
+}
+
+/* The hardware wants word-swapped 32-bit values */
+static void memcpy_swahw32(u8 *dest, u8 *src, unsigned int n)
+{
+ unsigned int i;
+
+ for (i = 0; i < n / 4; i++)
+ ((u32 *)dest)[i] = swahw32(((u32 *)src)[i]);
+}
+
+/* call with substream locked */
+/* returns true if a period elapsed */
+static bool hiface_pcm_playback(struct pcm_substream *sub, struct pcm_urb *urb)
+{
+ struct snd_pcm_runtime *alsa_rt = sub->instance->runtime;
+ struct device *device = &urb->chip->dev->dev;
+ u8 *source;
+ unsigned int pcm_buffer_size;
+
+ WARN_ON(alsa_rt->format != SNDRV_PCM_FORMAT_S32_LE);
+
+ pcm_buffer_size = snd_pcm_lib_buffer_bytes(sub->instance);
+
+ if (sub->dma_off + PCM_PACKET_SIZE <= pcm_buffer_size) {
+ dev_dbg(device, "%s: (1) buffer_size %#x dma_offset %#x\n", __func__,
+ (unsigned int) pcm_buffer_size,
+ (unsigned int) sub->dma_off);
+
+ source = alsa_rt->dma_area + sub->dma_off;
+ memcpy_swahw32(urb->buffer, source, PCM_PACKET_SIZE);
+ } else {
+ /* wrap around at end of ring buffer */
+ unsigned int len;
+
+ dev_dbg(device, "%s: (2) buffer_size %#x dma_offset %#x\n", __func__,
+ (unsigned int) pcm_buffer_size,
+ (unsigned int) sub->dma_off);
+
+ len = pcm_buffer_size - sub->dma_off;
+
+ source = alsa_rt->dma_area + sub->dma_off;
+ memcpy_swahw32(urb->buffer, source, len);
+
+ source = alsa_rt->dma_area;
+ memcpy_swahw32(urb->buffer + len, source,
+ PCM_PACKET_SIZE - len);
+ }
+ sub->dma_off += PCM_PACKET_SIZE;
+ if (sub->dma_off >= pcm_buffer_size)
+ sub->dma_off -= pcm_buffer_size;
+
+ sub->period_off += PCM_PACKET_SIZE;
+ if (sub->period_off >= alsa_rt->period_size) {
+ sub->period_off %= alsa_rt->period_size;
+ return true;
+ }
+ return false;
+}
+
+static void hiface_pcm_out_urb_handler(struct urb *usb_urb)
+{
+ struct pcm_urb *out_urb = usb_urb->context;
+ struct pcm_runtime *rt = out_urb->chip->pcm;
+ struct pcm_substream *sub;
+ bool do_period_elapsed = false;
+ unsigned long flags;
+ int ret;
+
+ if (rt->panic || rt->stream_state == STREAM_STOPPING)
+ return;
+
+ if (unlikely(usb_urb->status == -ENOENT || /* unlinked */
+ usb_urb->status == -ENODEV || /* device removed */
+ usb_urb->status == -ECONNRESET || /* unlinked */
+ usb_urb->status == -ESHUTDOWN)) { /* device disabled */
+ goto out_fail;
+ }
+
+ if (rt->stream_state == STREAM_STARTING) {
+ rt->stream_wait_cond = true;
+ wake_up(&rt->stream_wait_queue);
+ }
+
+ /* now send our playback data (if a free out urb was found) */
+ sub = &rt->playback;
+ spin_lock_irqsave(&sub->lock, flags);
+ if (sub->active)
+ do_period_elapsed = hiface_pcm_playback(sub, out_urb);
+ else
+ memset(out_urb->buffer, 0, PCM_PACKET_SIZE);
+
+ spin_unlock_irqrestore(&sub->lock, flags);
+
+ if (do_period_elapsed)
+ snd_pcm_period_elapsed(sub->instance);
+
+ ret = usb_submit_urb(&out_urb->instance, GFP_ATOMIC);
+ if (ret < 0)
+ goto out_fail;
+
+ return;
+
+out_fail:
+ rt->panic = true;
+}
+
+static int hiface_pcm_open(struct snd_pcm_substream *alsa_sub)
+{
+ struct pcm_runtime *rt = snd_pcm_substream_chip(alsa_sub);
+ struct pcm_substream *sub = NULL;
+ struct snd_pcm_runtime *alsa_rt = alsa_sub->runtime;
+ int ret;
+
+ if (rt->panic)
+ return -EPIPE;
+
+ mutex_lock(&rt->stream_mutex);
+ alsa_rt->hw = pcm_hw;
+
+ if (alsa_sub->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ sub = &rt->playback;
+
+ if (!sub) {
+ struct device *device = &rt->chip->dev->dev;
+ mutex_unlock(&rt->stream_mutex);
+ dev_err(device, "Invalid stream type\n");
+ return -EINVAL;
+ }
+
+ if (rt->extra_freq) {
+ alsa_rt->hw.rates |= SNDRV_PCM_RATE_KNOT;
+ alsa_rt->hw.rate_max = 384000;
+
+ /* explicit constraints needed as we added SNDRV_PCM_RATE_KNOT */
+ ret = snd_pcm_hw_constraint_list(alsa_sub->runtime, 0,
+ SNDRV_PCM_HW_PARAM_RATE,
+ &constraints_extra_rates);
+ if (ret < 0) {
+ mutex_unlock(&rt->stream_mutex);
+ return ret;
+ }
+ }
+
+ sub->instance = alsa_sub;
+ sub->active = false;
+ mutex_unlock(&rt->stream_mutex);
+ return 0;
+}
+
+static int hiface_pcm_close(struct snd_pcm_substream *alsa_sub)
+{
+ struct pcm_runtime *rt = snd_pcm_substream_chip(alsa_sub);
+ struct pcm_substream *sub = hiface_pcm_get_substream(alsa_sub);
+ unsigned long flags;
+
+ if (rt->panic)
+ return 0;
+
+ mutex_lock(&rt->stream_mutex);
+ if (sub) {
+ hiface_pcm_stream_stop(rt);
+
+ /* deactivate substream */
+ spin_lock_irqsave(&sub->lock, flags);
+ sub->instance = NULL;
+ sub->active = false;
+ spin_unlock_irqrestore(&sub->lock, flags);
+
+ }
+ mutex_unlock(&rt->stream_mutex);
+ return 0;
+}
+
+static int hiface_pcm_hw_params(struct snd_pcm_substream *alsa_sub,
+ struct snd_pcm_hw_params *hw_params)
+{
+ return snd_pcm_lib_alloc_vmalloc_buffer(alsa_sub,
+ params_buffer_bytes(hw_params));
+}
+
+static int hiface_pcm_hw_free(struct snd_pcm_substream *alsa_sub)
+{
+ return snd_pcm_lib_free_vmalloc_buffer(alsa_sub);
+}
+
+static int hiface_pcm_prepare(struct snd_pcm_substream *alsa_sub)
+{
+ struct pcm_runtime *rt = snd_pcm_substream_chip(alsa_sub);
+ struct pcm_substream *sub = hiface_pcm_get_substream(alsa_sub);
+ struct snd_pcm_runtime *alsa_rt = alsa_sub->runtime;
+ int ret;
+
+ if (rt->panic)
+ return -EPIPE;
+ if (!sub)
+ return -ENODEV;
+
+ mutex_lock(&rt->stream_mutex);
+
+ sub->dma_off = 0;
+ sub->period_off = 0;
+
+ if (rt->stream_state == STREAM_DISABLED) {
+
+ ret = hiface_pcm_set_rate(rt, alsa_rt->rate);
+ if (ret) {
+ mutex_unlock(&rt->stream_mutex);
+ return ret;
+ }
+ ret = hiface_pcm_stream_start(rt);
+ if (ret) {
+ mutex_unlock(&rt->stream_mutex);
+ return ret;
+ }
+ }
+ mutex_unlock(&rt->stream_mutex);
+ return 0;
+}
+
+static int hiface_pcm_trigger(struct snd_pcm_substream *alsa_sub, int cmd)
+{
+ struct pcm_substream *sub = hiface_pcm_get_substream(alsa_sub);
+ struct pcm_runtime *rt = snd_pcm_substream_chip(alsa_sub);
+
+ if (rt->panic)
+ return -EPIPE;
+ if (!sub)
+ return -ENODEV;
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ spin_lock_irq(&sub->lock);
+ sub->active = true;
+ spin_unlock_irq(&sub->lock);
+ return 0;
+
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ spin_lock_irq(&sub->lock);
+ sub->active = false;
+ spin_unlock_irq(&sub->lock);
+ return 0;
+
+ default:
+ return -EINVAL;
+ }
+}
+
+static snd_pcm_uframes_t hiface_pcm_pointer(struct snd_pcm_substream *alsa_sub)
+{
+ struct pcm_substream *sub = hiface_pcm_get_substream(alsa_sub);
+ struct pcm_runtime *rt = snd_pcm_substream_chip(alsa_sub);
+ unsigned long flags;
+ snd_pcm_uframes_t dma_offset;
+
+ if (rt->panic || !sub)
+ return SNDRV_PCM_POS_XRUN;
+
+ spin_lock_irqsave(&sub->lock, flags);
+ dma_offset = sub->dma_off;
+ spin_unlock_irqrestore(&sub->lock, flags);
+ return bytes_to_frames(alsa_sub->runtime, dma_offset);
+}
+
+static struct snd_pcm_ops pcm_ops = {
+ .open = hiface_pcm_open,
+ .close = hiface_pcm_close,
+ .ioctl = snd_pcm_lib_ioctl,
+ .hw_params = hiface_pcm_hw_params,
+ .hw_free = hiface_pcm_hw_free,
+ .prepare = hiface_pcm_prepare,
+ .trigger = hiface_pcm_trigger,
+ .pointer = hiface_pcm_pointer,
+ .page = snd_pcm_lib_get_vmalloc_page,
+ .mmap = snd_pcm_lib_mmap_vmalloc,
+};
+
+static int hiface_pcm_init_urb(struct pcm_urb *urb,
+ struct hiface_chip *chip,
+ unsigned int ep,
+ void (*handler)(struct urb *))
+{
+ urb->chip = chip;
+ usb_init_urb(&urb->instance);
+
+ urb->buffer = kzalloc(PCM_PACKET_SIZE, GFP_KERNEL);
+ if (!urb->buffer)
+ return -ENOMEM;
+
+ usb_fill_bulk_urb(&urb->instance, chip->dev,
+ usb_sndbulkpipe(chip->dev, ep), (void *)urb->buffer,
+ PCM_PACKET_SIZE, handler, urb);
+ init_usb_anchor(&urb->submitted);
+
+ return 0;
+}
+
+void hiface_pcm_abort(struct hiface_chip *chip)
+{
+ struct pcm_runtime *rt = chip->pcm;
+
+ if (rt) {
+ rt->panic = true;
+
+ mutex_lock(&rt->stream_mutex);
+ hiface_pcm_stream_stop(rt);
+ mutex_unlock(&rt->stream_mutex);
+ }
+}
+
+static void hiface_pcm_destroy(struct hiface_chip *chip)
+{
+ struct pcm_runtime *rt = chip->pcm;
+ int i;
+
+ for (i = 0; i < PCM_N_URBS; i++)
+ kfree(rt->out_urbs[i].buffer);
+
+ kfree(chip->pcm);
+ chip->pcm = NULL;
+}
+
+static void hiface_pcm_free(struct snd_pcm *pcm)
+{
+ struct pcm_runtime *rt = pcm->private_data;
+
+ if (rt)
+ hiface_pcm_destroy(rt->chip);
+}
+
+int hiface_pcm_init(struct hiface_chip *chip, u8 extra_freq)
+{
+ int i;
+ int ret;
+ struct snd_pcm *pcm;
+ struct pcm_runtime *rt;
+
+ rt = kzalloc(sizeof(*rt), GFP_KERNEL);
+ if (!rt)
+ return -ENOMEM;
+
+ rt->chip = chip;
+ rt->stream_state = STREAM_DISABLED;
+ if (extra_freq)
+ rt->extra_freq = 1;
+
+ init_waitqueue_head(&rt->stream_wait_queue);
+ mutex_init(&rt->stream_mutex);
+ spin_lock_init(&rt->playback.lock);
+
+ for (i = 0; i < PCM_N_URBS; i++)
+ hiface_pcm_init_urb(&rt->out_urbs[i], chip, OUT_EP,
+ hiface_pcm_out_urb_handler);
+
+ ret = snd_pcm_new(chip->card, "USB-SPDIF Audio", 0, 1, 0, &pcm);
+ if (ret < 0) {
+ kfree(rt);
+ dev_err(&chip->dev->dev, "Cannot create pcm instance\n");
+ return ret;
+ }
+
+ pcm->private_data = rt;
+ pcm->private_free = hiface_pcm_free;
+
+ strlcpy(pcm->name, "USB-SPDIF Audio", sizeof(pcm->name));
+ snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &pcm_ops);
+
+ rt->instance = pcm;
+
+ chip->pcm = rt;
+ return 0;
+}
diff --git a/sound/usb/hiface/pcm.h b/sound/usb/hiface/pcm.h
new file mode 100644
index 000000000..77edd7c12
--- /dev/null
+++ b/sound/usb/hiface/pcm.h
@@ -0,0 +1,24 @@
+/*
+ * Linux driver for M2Tech hiFace compatible devices
+ *
+ * Copyright 2012-2013 (C) M2TECH S.r.l and Amarula Solutions B.V.
+ *
+ * Authors: Michael Trimarchi <michael@amarulasolutions.com>
+ * Antonio Ospite <ao2@amarulasolutions.com>
+ *
+ * The driver is based on the work done in TerraTec DMX 6Fire USB
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#ifndef HIFACE_PCM_H
+#define HIFACE_PCM_H
+
+struct hiface_chip;
+
+int hiface_pcm_init(struct hiface_chip *chip, u8 extra_freq);
+void hiface_pcm_abort(struct hiface_chip *chip);
+#endif /* HIFACE_PCM_H */
diff --git a/sound/usb/line6/Kconfig b/sound/usb/line6/Kconfig
new file mode 100644
index 000000000..f4585d378
--- /dev/null
+++ b/sound/usb/line6/Kconfig
@@ -0,0 +1,42 @@
+config SND_USB_LINE6
+ tristate
+ select SND_RAWMIDI
+ select SND_PCM
+
+config SND_USB_POD
+ tristate "Line 6 POD USB support"
+ select SND_USB_LINE6
+ help
+ This is a driver for PODxt and other similar devices,
+ supporting the following features:
+ * Reading/writing individual parameters
+ * Reading/writing complete channel, effects setup, and amp
+ setup data
+ * Channel switching
+ * Virtual MIDI interface
+ * Tuner access
+ * Playback/capture/mixer device for any ALSA-compatible PCM
+ audio application
+ * Signal routing (record clean/processed guitar signal,
+ re-amping)
+
+config SND_USB_PODHD
+ tristate "Line 6 POD HD300/400/500 USB support"
+ select SND_USB_LINE6
+ help
+ This is a driver for POD HD300, 400 and 500 devices.
+
+config SND_USB_TONEPORT
+ tristate "TonePort GX, UX1 and UX2 USB support"
+ select SND_USB_LINE6
+ select NEW_LEDS
+ select LEDS_CLASS
+ help
+ This is a driver for TonePort GX, UX1 and UX2 devices.
+
+config SND_USB_VARIAX
+ tristate "Variax Workbench USB support"
+ select SND_USB_LINE6
+ help
+ This is a driver for Variax Workbench device.
+
diff --git a/sound/usb/line6/Makefile b/sound/usb/line6/Makefile
new file mode 100644
index 000000000..b8b3b2a54
--- /dev/null
+++ b/sound/usb/line6/Makefile
@@ -0,0 +1,18 @@
+snd-usb-line6-y := \
+ capture.o \
+ driver.o \
+ midi.o \
+ midibuf.o \
+ pcm.o \
+ playback.o
+
+snd-usb-pod-y := pod.o
+snd-usb-podhd-y := podhd.o
+snd-usb-toneport-y := toneport.o
+snd-usb-variax-y := variax.o
+
+obj-$(CONFIG_SND_USB_LINE6) += snd-usb-line6.o
+obj-$(CONFIG_SND_USB_POD) += snd-usb-pod.o
+obj-$(CONFIG_SND_USB_PODHD) += snd-usb-podhd.o
+obj-$(CONFIG_SND_USB_TONEPORT) += snd-usb-toneport.o
+obj-$(CONFIG_SND_USB_VARIAX) += snd-usb-variax.o
diff --git a/sound/usb/line6/capture.c b/sound/usb/line6/capture.c
new file mode 100644
index 000000000..f518fbbe8
--- /dev/null
+++ b/sound/usb/line6/capture.c
@@ -0,0 +1,275 @@
+/*
+ * Line 6 Linux USB driver
+ *
+ * Copyright (C) 2004-2010 Markus Grabner (grabner@icg.tugraz.at)
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, version 2.
+ *
+ */
+
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+
+#include "capture.h"
+#include "driver.h"
+#include "pcm.h"
+
+/*
+ Find a free URB and submit it.
+ must be called in line6pcm->in.lock context
+*/
+static int submit_audio_in_urb(struct snd_line6_pcm *line6pcm)
+{
+ int index;
+ int i, urb_size;
+ int ret;
+ struct urb *urb_in;
+
+ index =
+ find_first_zero_bit(&line6pcm->in.active_urbs, LINE6_ISO_BUFFERS);
+
+ if (index < 0 || index >= LINE6_ISO_BUFFERS) {
+ dev_err(line6pcm->line6->ifcdev, "no free URB found\n");
+ return -EINVAL;
+ }
+
+ urb_in = line6pcm->in.urbs[index];
+ urb_size = 0;
+
+ for (i = 0; i < LINE6_ISO_PACKETS; ++i) {
+ struct usb_iso_packet_descriptor *fin =
+ &urb_in->iso_frame_desc[i];
+ fin->offset = urb_size;
+ fin->length = line6pcm->max_packet_size;
+ urb_size += line6pcm->max_packet_size;
+ }
+
+ urb_in->transfer_buffer =
+ line6pcm->in.buffer +
+ index * LINE6_ISO_PACKETS * line6pcm->max_packet_size;
+ urb_in->transfer_buffer_length = urb_size;
+ urb_in->context = line6pcm;
+
+ ret = usb_submit_urb(urb_in, GFP_ATOMIC);
+
+ if (ret == 0)
+ set_bit(index, &line6pcm->in.active_urbs);
+ else
+ dev_err(line6pcm->line6->ifcdev,
+ "URB in #%d submission failed (%d)\n", index, ret);
+
+ return 0;
+}
+
+/*
+ Submit all currently available capture URBs.
+ must be called in line6pcm->in.lock context
+*/
+int line6_submit_audio_in_all_urbs(struct snd_line6_pcm *line6pcm)
+{
+ int ret = 0, i;
+
+ for (i = 0; i < LINE6_ISO_BUFFERS; ++i) {
+ ret = submit_audio_in_urb(line6pcm);
+ if (ret < 0)
+ break;
+ }
+
+ return ret;
+}
+
+/*
+ Copy data into ALSA capture buffer.
+*/
+void line6_capture_copy(struct snd_line6_pcm *line6pcm, char *fbuf, int fsize)
+{
+ struct snd_pcm_substream *substream =
+ get_substream(line6pcm, SNDRV_PCM_STREAM_CAPTURE);
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ const int bytes_per_frame = line6pcm->properties->bytes_per_frame;
+ int frames = fsize / bytes_per_frame;
+
+ if (runtime == NULL)
+ return;
+
+ if (line6pcm->in.pos_done + frames > runtime->buffer_size) {
+ /*
+ The transferred area goes over buffer boundary,
+ copy two separate chunks.
+ */
+ int len;
+
+ len = runtime->buffer_size - line6pcm->in.pos_done;
+
+ if (len > 0) {
+ memcpy(runtime->dma_area +
+ line6pcm->in.pos_done * bytes_per_frame, fbuf,
+ len * bytes_per_frame);
+ memcpy(runtime->dma_area, fbuf + len * bytes_per_frame,
+ (frames - len) * bytes_per_frame);
+ } else {
+ /* this is somewhat paranoid */
+ dev_err(line6pcm->line6->ifcdev,
+ "driver bug: len = %d\n", len);
+ }
+ } else {
+ /* copy single chunk */
+ memcpy(runtime->dma_area +
+ line6pcm->in.pos_done * bytes_per_frame, fbuf, fsize);
+ }
+
+ line6pcm->in.pos_done += frames;
+ if (line6pcm->in.pos_done >= runtime->buffer_size)
+ line6pcm->in.pos_done -= runtime->buffer_size;
+}
+
+void line6_capture_check_period(struct snd_line6_pcm *line6pcm, int length)
+{
+ struct snd_pcm_substream *substream =
+ get_substream(line6pcm, SNDRV_PCM_STREAM_CAPTURE);
+
+ line6pcm->in.bytes += length;
+ if (line6pcm->in.bytes >= line6pcm->in.period) {
+ line6pcm->in.bytes %= line6pcm->in.period;
+ spin_unlock(&line6pcm->in.lock);
+ snd_pcm_period_elapsed(substream);
+ spin_lock(&line6pcm->in.lock);
+ }
+}
+
+/*
+ * Callback for completed capture URB.
+ */
+static void audio_in_callback(struct urb *urb)
+{
+ int i, index, length = 0, shutdown = 0;
+ unsigned long flags;
+
+ struct snd_line6_pcm *line6pcm = (struct snd_line6_pcm *)urb->context;
+
+ line6pcm->in.last_frame = urb->start_frame;
+
+ /* find index of URB */
+ for (index = 0; index < LINE6_ISO_BUFFERS; ++index)
+ if (urb == line6pcm->in.urbs[index])
+ break;
+
+ spin_lock_irqsave(&line6pcm->in.lock, flags);
+
+ for (i = 0; i < LINE6_ISO_PACKETS; ++i) {
+ char *fbuf;
+ int fsize;
+ struct usb_iso_packet_descriptor *fin = &urb->iso_frame_desc[i];
+
+ if (fin->status == -EXDEV) {
+ shutdown = 1;
+ break;
+ }
+
+ fbuf = urb->transfer_buffer + fin->offset;
+ fsize = fin->actual_length;
+
+ if (fsize > line6pcm->max_packet_size) {
+ dev_err(line6pcm->line6->ifcdev,
+ "driver and/or device bug: packet too large (%d > %d)\n",
+ fsize, line6pcm->max_packet_size);
+ }
+
+ length += fsize;
+
+ /* the following assumes LINE6_ISO_PACKETS == 1: */
+ line6pcm->prev_fbuf = fbuf;
+ line6pcm->prev_fsize = fsize;
+
+ if (!test_bit(LINE6_STREAM_IMPULSE, &line6pcm->in.running) &&
+ test_bit(LINE6_STREAM_PCM, &line6pcm->in.running) &&
+ fsize > 0)
+ line6_capture_copy(line6pcm, fbuf, fsize);
+ }
+
+ clear_bit(index, &line6pcm->in.active_urbs);
+
+ if (test_and_clear_bit(index, &line6pcm->in.unlink_urbs))
+ shutdown = 1;
+
+ if (!shutdown) {
+ submit_audio_in_urb(line6pcm);
+
+ if (!test_bit(LINE6_STREAM_IMPULSE, &line6pcm->in.running) &&
+ test_bit(LINE6_STREAM_PCM, &line6pcm->in.running))
+ line6_capture_check_period(line6pcm, length);
+ }
+
+ spin_unlock_irqrestore(&line6pcm->in.lock, flags);
+}
+
+/* open capture callback */
+static int snd_line6_capture_open(struct snd_pcm_substream *substream)
+{
+ int err;
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct snd_line6_pcm *line6pcm = snd_pcm_substream_chip(substream);
+
+ err = snd_pcm_hw_constraint_ratdens(runtime, 0,
+ SNDRV_PCM_HW_PARAM_RATE,
+ &line6pcm->properties->rates);
+ if (err < 0)
+ return err;
+
+ runtime->hw = line6pcm->properties->capture_hw;
+ return 0;
+}
+
+/* close capture callback */
+static int snd_line6_capture_close(struct snd_pcm_substream *substream)
+{
+ return 0;
+}
+
+/* capture operators */
+struct snd_pcm_ops snd_line6_capture_ops = {
+ .open = snd_line6_capture_open,
+ .close = snd_line6_capture_close,
+ .ioctl = snd_pcm_lib_ioctl,
+ .hw_params = snd_line6_hw_params,
+ .hw_free = snd_line6_hw_free,
+ .prepare = snd_line6_prepare,
+ .trigger = snd_line6_trigger,
+ .pointer = snd_line6_pointer,
+};
+
+int line6_create_audio_in_urbs(struct snd_line6_pcm *line6pcm)
+{
+ struct usb_line6 *line6 = line6pcm->line6;
+ int i;
+
+ /* create audio URBs and fill in constant values: */
+ for (i = 0; i < LINE6_ISO_BUFFERS; ++i) {
+ struct urb *urb;
+
+ /* URB for audio in: */
+ urb = line6pcm->in.urbs[i] =
+ usb_alloc_urb(LINE6_ISO_PACKETS, GFP_KERNEL);
+
+ if (urb == NULL)
+ return -ENOMEM;
+
+ urb->dev = line6->usbdev;
+ urb->pipe =
+ usb_rcvisocpipe(line6->usbdev,
+ line6->properties->ep_audio_r &
+ USB_ENDPOINT_NUMBER_MASK);
+ urb->transfer_flags = URB_ISO_ASAP;
+ urb->start_frame = -1;
+ urb->number_of_packets = LINE6_ISO_PACKETS;
+ urb->interval = LINE6_ISO_INTERVAL;
+ urb->error_count = 0;
+ urb->complete = audio_in_callback;
+ }
+
+ return 0;
+}
diff --git a/sound/usb/line6/capture.h b/sound/usb/line6/capture.h
new file mode 100644
index 000000000..890b21bff
--- /dev/null
+++ b/sound/usb/line6/capture.h
@@ -0,0 +1,29 @@
+/*
+ * Line 6 Linux USB driver
+ *
+ * Copyright (C) 2004-2010 Markus Grabner (grabner@icg.tugraz.at)
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, version 2.
+ *
+ */
+
+#ifndef CAPTURE_H
+#define CAPTURE_H
+
+#include <sound/pcm.h>
+
+#include "driver.h"
+#include "pcm.h"
+
+extern struct snd_pcm_ops snd_line6_capture_ops;
+
+extern void line6_capture_copy(struct snd_line6_pcm *line6pcm, char *fbuf,
+ int fsize);
+extern void line6_capture_check_period(struct snd_line6_pcm *line6pcm,
+ int length);
+extern int line6_create_audio_in_urbs(struct snd_line6_pcm *line6pcm);
+extern int line6_submit_audio_in_all_urbs(struct snd_line6_pcm *line6pcm);
+
+#endif
diff --git a/sound/usb/line6/driver.c b/sound/usb/line6/driver.c
new file mode 100644
index 000000000..81b7da8e5
--- /dev/null
+++ b/sound/usb/line6/driver.c
@@ -0,0 +1,672 @@
+/*
+ * Line 6 Linux USB driver
+ *
+ * Copyright (C) 2004-2010 Markus Grabner (grabner@icg.tugraz.at)
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, version 2.
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/export.h>
+#include <linux/slab.h>
+#include <linux/usb.h>
+
+#include <sound/core.h>
+#include <sound/initval.h>
+
+#include "capture.h"
+#include "driver.h"
+#include "midi.h"
+#include "playback.h"
+
+#define DRIVER_AUTHOR "Markus Grabner <grabner@icg.tugraz.at>"
+#define DRIVER_DESC "Line 6 USB Driver"
+
+/*
+ This is Line 6's MIDI manufacturer ID.
+*/
+const unsigned char line6_midi_id[] = {
+ 0x00, 0x01, 0x0c
+};
+EXPORT_SYMBOL_GPL(line6_midi_id);
+
+/*
+ Code to request version of POD, Variax interface
+ (and maybe other devices).
+*/
+static const char line6_request_version[] = {
+ 0xf0, 0x7e, 0x7f, 0x06, 0x01, 0xf7
+};
+
+/*
+ Class for asynchronous messages.
+*/
+struct message {
+ struct usb_line6 *line6;
+ const char *buffer;
+ int size;
+ int done;
+};
+
+/*
+ Forward declarations.
+*/
+static void line6_data_received(struct urb *urb);
+static int line6_send_raw_message_async_part(struct message *msg,
+ struct urb *urb);
+
+/*
+ Start to listen on endpoint.
+*/
+static int line6_start_listen(struct usb_line6 *line6)
+{
+ int err;
+
+ usb_fill_int_urb(line6->urb_listen, line6->usbdev,
+ usb_rcvintpipe(line6->usbdev, line6->properties->ep_ctrl_r),
+ line6->buffer_listen, LINE6_BUFSIZE_LISTEN,
+ line6_data_received, line6, line6->interval);
+ line6->urb_listen->actual_length = 0;
+ err = usb_submit_urb(line6->urb_listen, GFP_ATOMIC);
+ return err;
+}
+
+/*
+ Stop listening on endpoint.
+*/
+static void line6_stop_listen(struct usb_line6 *line6)
+{
+ usb_kill_urb(line6->urb_listen);
+}
+
+/*
+ Send raw message in pieces of wMaxPacketSize bytes.
+*/
+static int line6_send_raw_message(struct usb_line6 *line6, const char *buffer,
+ int size)
+{
+ int i, done = 0;
+
+ for (i = 0; i < size; i += line6->max_packet_size) {
+ int partial;
+ const char *frag_buf = buffer + i;
+ int frag_size = min(line6->max_packet_size, size - i);
+ int retval;
+
+ retval = usb_interrupt_msg(line6->usbdev,
+ usb_sndintpipe(line6->usbdev,
+ line6->properties->ep_ctrl_w),
+ (char *)frag_buf, frag_size,
+ &partial, LINE6_TIMEOUT * HZ);
+
+ if (retval) {
+ dev_err(line6->ifcdev,
+ "usb_interrupt_msg failed (%d)\n", retval);
+ break;
+ }
+
+ done += frag_size;
+ }
+
+ return done;
+}
+
+/*
+ Notification of completion of asynchronous request transmission.
+*/
+static void line6_async_request_sent(struct urb *urb)
+{
+ struct message *msg = (struct message *)urb->context;
+
+ if (msg->done >= msg->size) {
+ usb_free_urb(urb);
+ kfree(msg);
+ } else
+ line6_send_raw_message_async_part(msg, urb);
+}
+
+/*
+ Asynchronously send part of a raw message.
+*/
+static int line6_send_raw_message_async_part(struct message *msg,
+ struct urb *urb)
+{
+ int retval;
+ struct usb_line6 *line6 = msg->line6;
+ int done = msg->done;
+ int bytes = min(msg->size - done, line6->max_packet_size);
+
+ usb_fill_int_urb(urb, line6->usbdev,
+ usb_sndintpipe(line6->usbdev, line6->properties->ep_ctrl_w),
+ (char *)msg->buffer + done, bytes,
+ line6_async_request_sent, msg, line6->interval);
+
+ msg->done += bytes;
+ retval = usb_submit_urb(urb, GFP_ATOMIC);
+
+ if (retval < 0) {
+ dev_err(line6->ifcdev, "%s: usb_submit_urb failed (%d)\n",
+ __func__, retval);
+ usb_free_urb(urb);
+ kfree(msg);
+ return retval;
+ }
+
+ return 0;
+}
+
+/*
+ Setup and start timer.
+*/
+void line6_start_timer(struct timer_list *timer, unsigned long msecs,
+ void (*function)(unsigned long), unsigned long data)
+{
+ setup_timer(timer, function, data);
+ mod_timer(timer, jiffies + msecs_to_jiffies(msecs));
+}
+EXPORT_SYMBOL_GPL(line6_start_timer);
+
+/*
+ Asynchronously send raw message.
+*/
+int line6_send_raw_message_async(struct usb_line6 *line6, const char *buffer,
+ int size)
+{
+ struct message *msg;
+ struct urb *urb;
+
+ /* create message: */
+ msg = kmalloc(sizeof(struct message), GFP_ATOMIC);
+ if (msg == NULL)
+ return -ENOMEM;
+
+ /* create URB: */
+ urb = usb_alloc_urb(0, GFP_ATOMIC);
+
+ if (urb == NULL) {
+ kfree(msg);
+ return -ENOMEM;
+ }
+
+ /* set message data: */
+ msg->line6 = line6;
+ msg->buffer = buffer;
+ msg->size = size;
+ msg->done = 0;
+
+ /* start sending: */
+ return line6_send_raw_message_async_part(msg, urb);
+}
+EXPORT_SYMBOL_GPL(line6_send_raw_message_async);
+
+/*
+ Send asynchronous device version request.
+*/
+int line6_version_request_async(struct usb_line6 *line6)
+{
+ char *buffer;
+ int retval;
+
+ buffer = kmemdup(line6_request_version,
+ sizeof(line6_request_version), GFP_ATOMIC);
+ if (buffer == NULL)
+ return -ENOMEM;
+
+ retval = line6_send_raw_message_async(line6, buffer,
+ sizeof(line6_request_version));
+ kfree(buffer);
+ return retval;
+}
+EXPORT_SYMBOL_GPL(line6_version_request_async);
+
+/*
+ Send sysex message in pieces of wMaxPacketSize bytes.
+*/
+int line6_send_sysex_message(struct usb_line6 *line6, const char *buffer,
+ int size)
+{
+ return line6_send_raw_message(line6, buffer,
+ size + SYSEX_EXTRA_SIZE) -
+ SYSEX_EXTRA_SIZE;
+}
+EXPORT_SYMBOL_GPL(line6_send_sysex_message);
+
+/*
+ Allocate buffer for sysex message and prepare header.
+ @param code sysex message code
+ @param size number of bytes between code and sysex end
+*/
+char *line6_alloc_sysex_buffer(struct usb_line6 *line6, int code1, int code2,
+ int size)
+{
+ char *buffer = kmalloc(size + SYSEX_EXTRA_SIZE, GFP_ATOMIC);
+
+ if (!buffer)
+ return NULL;
+
+ buffer[0] = LINE6_SYSEX_BEGIN;
+ memcpy(buffer + 1, line6_midi_id, sizeof(line6_midi_id));
+ buffer[sizeof(line6_midi_id) + 1] = code1;
+ buffer[sizeof(line6_midi_id) + 2] = code2;
+ buffer[sizeof(line6_midi_id) + 3 + size] = LINE6_SYSEX_END;
+ return buffer;
+}
+EXPORT_SYMBOL_GPL(line6_alloc_sysex_buffer);
+
+/*
+ Notification of data received from the Line 6 device.
+*/
+static void line6_data_received(struct urb *urb)
+{
+ struct usb_line6 *line6 = (struct usb_line6 *)urb->context;
+ struct midi_buffer *mb = &line6->line6midi->midibuf_in;
+ int done;
+
+ if (urb->status == -ESHUTDOWN)
+ return;
+
+ done =
+ line6_midibuf_write(mb, urb->transfer_buffer, urb->actual_length);
+
+ if (done < urb->actual_length) {
+ line6_midibuf_ignore(mb, done);
+ dev_dbg(line6->ifcdev, "%d %d buffer overflow - message skipped\n",
+ done, urb->actual_length);
+ }
+
+ for (;;) {
+ done =
+ line6_midibuf_read(mb, line6->buffer_message,
+ LINE6_MESSAGE_MAXLEN);
+
+ if (done == 0)
+ break;
+
+ line6->message_length = done;
+ line6_midi_receive(line6, line6->buffer_message, done);
+
+ if (line6->process_message)
+ line6->process_message(line6);
+ }
+
+ line6_start_listen(line6);
+}
+
+#define LINE6_READ_WRITE_STATUS_DELAY 2 /* milliseconds */
+#define LINE6_READ_WRITE_MAX_RETRIES 50
+
+/*
+ Read data from device.
+*/
+int line6_read_data(struct usb_line6 *line6, unsigned address, void *data,
+ unsigned datalen)
+{
+ struct usb_device *usbdev = line6->usbdev;
+ int ret;
+ unsigned char len;
+ unsigned count;
+
+ if (address > 0xffff || datalen > 0xff)
+ return -EINVAL;
+
+ /* query the serial number: */
+ ret = usb_control_msg(usbdev, usb_sndctrlpipe(usbdev, 0), 0x67,
+ USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_OUT,
+ (datalen << 8) | 0x21, address,
+ NULL, 0, LINE6_TIMEOUT * HZ);
+
+ if (ret < 0) {
+ dev_err(line6->ifcdev, "read request failed (error %d)\n", ret);
+ return ret;
+ }
+
+ /* Wait for data length. We'll get 0xff until length arrives. */
+ for (count = 0; count < LINE6_READ_WRITE_MAX_RETRIES; count++) {
+ mdelay(LINE6_READ_WRITE_STATUS_DELAY);
+
+ ret = usb_control_msg(usbdev, usb_rcvctrlpipe(usbdev, 0), 0x67,
+ USB_TYPE_VENDOR | USB_RECIP_DEVICE |
+ USB_DIR_IN,
+ 0x0012, 0x0000, &len, 1,
+ LINE6_TIMEOUT * HZ);
+ if (ret < 0) {
+ dev_err(line6->ifcdev,
+ "receive length failed (error %d)\n", ret);
+ return ret;
+ }
+
+ if (len != 0xff)
+ break;
+ }
+
+ if (len == 0xff) {
+ dev_err(line6->ifcdev, "read failed after %d retries\n",
+ count);
+ return -EIO;
+ } else if (len != datalen) {
+ /* should be equal or something went wrong */
+ dev_err(line6->ifcdev,
+ "length mismatch (expected %d, got %d)\n",
+ (int)datalen, (int)len);
+ return -EIO;
+ }
+
+ /* receive the result: */
+ ret = usb_control_msg(usbdev, usb_rcvctrlpipe(usbdev, 0), 0x67,
+ USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_IN,
+ 0x0013, 0x0000, data, datalen,
+ LINE6_TIMEOUT * HZ);
+
+ if (ret < 0) {
+ dev_err(line6->ifcdev, "read failed (error %d)\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(line6_read_data);
+
+/*
+ Write data to device.
+*/
+int line6_write_data(struct usb_line6 *line6, unsigned address, void *data,
+ unsigned datalen)
+{
+ struct usb_device *usbdev = line6->usbdev;
+ int ret;
+ unsigned char status;
+ int count;
+
+ if (address > 0xffff || datalen > 0xffff)
+ return -EINVAL;
+
+ ret = usb_control_msg(usbdev, usb_sndctrlpipe(usbdev, 0), 0x67,
+ USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_OUT,
+ 0x0022, address, data, datalen,
+ LINE6_TIMEOUT * HZ);
+
+ if (ret < 0) {
+ dev_err(line6->ifcdev,
+ "write request failed (error %d)\n", ret);
+ return ret;
+ }
+
+ for (count = 0; count < LINE6_READ_WRITE_MAX_RETRIES; count++) {
+ mdelay(LINE6_READ_WRITE_STATUS_DELAY);
+
+ ret = usb_control_msg(usbdev, usb_rcvctrlpipe(usbdev, 0),
+ 0x67,
+ USB_TYPE_VENDOR | USB_RECIP_DEVICE |
+ USB_DIR_IN,
+ 0x0012, 0x0000,
+ &status, 1, LINE6_TIMEOUT * HZ);
+
+ if (ret < 0) {
+ dev_err(line6->ifcdev,
+ "receiving status failed (error %d)\n", ret);
+ return ret;
+ }
+
+ if (status != 0xff)
+ break;
+ }
+
+ if (status == 0xff) {
+ dev_err(line6->ifcdev, "write failed after %d retries\n",
+ count);
+ return -EIO;
+ } else if (status != 0) {
+ dev_err(line6->ifcdev, "write failed (error %d)\n", ret);
+ return -EIO;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(line6_write_data);
+
+/*
+ Read Line 6 device serial number.
+ (POD, TonePort, GuitarPort)
+*/
+int line6_read_serial_number(struct usb_line6 *line6, u32 *serial_number)
+{
+ return line6_read_data(line6, 0x80d0, serial_number,
+ sizeof(*serial_number));
+}
+EXPORT_SYMBOL_GPL(line6_read_serial_number);
+
+/*
+ Card destructor.
+*/
+static void line6_destruct(struct snd_card *card)
+{
+ struct usb_line6 *line6 = card->private_data;
+ struct usb_device *usbdev = line6->usbdev;
+
+ /* free buffer memory first: */
+ kfree(line6->buffer_message);
+ kfree(line6->buffer_listen);
+
+ /* then free URBs: */
+ usb_free_urb(line6->urb_listen);
+
+ /* decrement reference counters: */
+ usb_put_dev(usbdev);
+}
+
+/* get data from endpoint descriptor (see usb_maxpacket): */
+static void line6_get_interval(struct usb_line6 *line6)
+{
+ struct usb_device *usbdev = line6->usbdev;
+ struct usb_host_endpoint *ep;
+ unsigned pipe = usb_rcvintpipe(usbdev, line6->properties->ep_ctrl_r);
+ unsigned epnum = usb_pipeendpoint(pipe);
+
+ ep = usbdev->ep_in[epnum];
+ if (ep) {
+ line6->interval = ep->desc.bInterval;
+ line6->max_packet_size = le16_to_cpu(ep->desc.wMaxPacketSize);
+ } else {
+ dev_err(line6->ifcdev,
+ "endpoint not available, using fallback values");
+ line6->interval = LINE6_FALLBACK_INTERVAL;
+ line6->max_packet_size = LINE6_FALLBACK_MAXPACKETSIZE;
+ }
+}
+
+static int line6_init_cap_control(struct usb_line6 *line6)
+{
+ int ret;
+
+ /* initialize USB buffers: */
+ line6->buffer_listen = kmalloc(LINE6_BUFSIZE_LISTEN, GFP_KERNEL);
+ if (!line6->buffer_listen)
+ return -ENOMEM;
+
+ line6->buffer_message = kmalloc(LINE6_MESSAGE_MAXLEN, GFP_KERNEL);
+ if (!line6->buffer_message)
+ return -ENOMEM;
+
+ line6->urb_listen = usb_alloc_urb(0, GFP_KERNEL);
+ if (!line6->urb_listen)
+ return -ENOMEM;
+
+ ret = line6_start_listen(line6);
+ if (ret < 0) {
+ dev_err(line6->ifcdev, "cannot start listening: %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+/*
+ Probe USB device.
+*/
+int line6_probe(struct usb_interface *interface,
+ const struct usb_device_id *id,
+ const char *driver_name,
+ const struct line6_properties *properties,
+ int (*private_init)(struct usb_line6 *, const struct usb_device_id *id),
+ size_t data_size)
+{
+ struct usb_device *usbdev = interface_to_usbdev(interface);
+ struct snd_card *card;
+ struct usb_line6 *line6;
+ int interface_number;
+ int ret;
+
+ if (WARN_ON(data_size < sizeof(*line6)))
+ return -EINVAL;
+
+ /* we don't handle multiple configurations */
+ if (usbdev->descriptor.bNumConfigurations != 1)
+ return -ENODEV;
+
+ ret = snd_card_new(&interface->dev,
+ SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1,
+ THIS_MODULE, data_size, &card);
+ if (ret < 0)
+ return ret;
+
+ /* store basic data: */
+ line6 = card->private_data;
+ line6->card = card;
+ line6->properties = properties;
+ line6->usbdev = usbdev;
+ line6->ifcdev = &interface->dev;
+
+ strcpy(card->id, properties->id);
+ strcpy(card->driver, driver_name);
+ strcpy(card->shortname, properties->name);
+ sprintf(card->longname, "Line 6 %s at USB %s", properties->name,
+ dev_name(line6->ifcdev));
+ card->private_free = line6_destruct;
+
+ usb_set_intfdata(interface, line6);
+
+ /* increment reference counters: */
+ usb_get_dev(usbdev);
+
+ /* initialize device info: */
+ dev_info(&interface->dev, "Line 6 %s found\n", properties->name);
+
+ /* query interface number */
+ interface_number = interface->cur_altsetting->desc.bInterfaceNumber;
+
+ ret = usb_set_interface(usbdev, interface_number,
+ properties->altsetting);
+ if (ret < 0) {
+ dev_err(&interface->dev, "set_interface failed\n");
+ goto error;
+ }
+
+ line6_get_interval(line6);
+
+ if (properties->capabilities & LINE6_CAP_CONTROL) {
+ ret = line6_init_cap_control(line6);
+ if (ret < 0)
+ goto error;
+ }
+
+ /* initialize device data based on device: */
+ ret = private_init(line6, id);
+ if (ret < 0)
+ goto error;
+
+ /* creation of additional special files should go here */
+
+ dev_info(&interface->dev, "Line 6 %s now attached\n",
+ properties->name);
+
+ return 0;
+
+ error:
+ if (line6->disconnect)
+ line6->disconnect(line6);
+ snd_card_free(card);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(line6_probe);
+
+/*
+ Line 6 device disconnected.
+*/
+void line6_disconnect(struct usb_interface *interface)
+{
+ struct usb_line6 *line6 = usb_get_intfdata(interface);
+ struct usb_device *usbdev = interface_to_usbdev(interface);
+
+ if (!line6)
+ return;
+
+ if (WARN_ON(usbdev != line6->usbdev))
+ return;
+
+ if (line6->urb_listen != NULL)
+ line6_stop_listen(line6);
+
+ snd_card_disconnect(line6->card);
+ if (line6->line6pcm)
+ line6_pcm_disconnect(line6->line6pcm);
+ if (line6->disconnect)
+ line6->disconnect(line6);
+
+ dev_info(&interface->dev, "Line 6 %s now disconnected\n",
+ line6->properties->name);
+
+ /* make sure the device isn't destructed twice: */
+ usb_set_intfdata(interface, NULL);
+
+ snd_card_free_when_closed(line6->card);
+}
+EXPORT_SYMBOL_GPL(line6_disconnect);
+
+#ifdef CONFIG_PM
+
+/*
+ Suspend Line 6 device.
+*/
+int line6_suspend(struct usb_interface *interface, pm_message_t message)
+{
+ struct usb_line6 *line6 = usb_get_intfdata(interface);
+ struct snd_line6_pcm *line6pcm = line6->line6pcm;
+
+ snd_power_change_state(line6->card, SNDRV_CTL_POWER_D3hot);
+
+ if (line6->properties->capabilities & LINE6_CAP_CONTROL)
+ line6_stop_listen(line6);
+
+ if (line6pcm != NULL) {
+ snd_pcm_suspend_all(line6pcm->pcm);
+ line6pcm->flags = 0;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(line6_suspend);
+
+/*
+ Resume Line 6 device.
+*/
+int line6_resume(struct usb_interface *interface)
+{
+ struct usb_line6 *line6 = usb_get_intfdata(interface);
+
+ if (line6->properties->capabilities & LINE6_CAP_CONTROL)
+ line6_start_listen(line6);
+
+ snd_power_change_state(line6->card, SNDRV_CTL_POWER_D0);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(line6_resume);
+
+#endif /* CONFIG_PM */
+
+MODULE_AUTHOR(DRIVER_AUTHOR);
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_LICENSE("GPL");
diff --git a/sound/usb/line6/driver.h b/sound/usb/line6/driver.h
new file mode 100644
index 000000000..7da643e79
--- /dev/null
+++ b/sound/usb/line6/driver.h
@@ -0,0 +1,181 @@
+/*
+ * Line 6 Linux USB driver
+ *
+ * Copyright (C) 2004-2010 Markus Grabner (grabner@icg.tugraz.at)
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, version 2.
+ *
+ */
+
+#ifndef DRIVER_H
+#define DRIVER_H
+
+#include <linux/spinlock.h>
+#include <linux/usb.h>
+#include <sound/core.h>
+
+#include "midi.h"
+
+#define USB_INTERVALS_PER_SECOND 1000
+
+/* Fallback USB interval and max packet size values */
+#define LINE6_FALLBACK_INTERVAL 10
+#define LINE6_FALLBACK_MAXPACKETSIZE 16
+
+#define LINE6_TIMEOUT 1
+#define LINE6_BUFSIZE_LISTEN 32
+#define LINE6_MESSAGE_MAXLEN 256
+
+/*
+ Line 6 MIDI control commands
+*/
+#define LINE6_PARAM_CHANGE 0xb0
+#define LINE6_PROGRAM_CHANGE 0xc0
+#define LINE6_SYSEX_BEGIN 0xf0
+#define LINE6_SYSEX_END 0xf7
+#define LINE6_RESET 0xff
+
+/*
+ MIDI channel for messages initiated by the host
+ (and eventually echoed back by the device)
+*/
+#define LINE6_CHANNEL_HOST 0x00
+
+/*
+ MIDI channel for messages initiated by the device
+*/
+#define LINE6_CHANNEL_DEVICE 0x02
+
+#define LINE6_CHANNEL_UNKNOWN 5 /* don't know yet what this is good for */
+
+#define LINE6_CHANNEL_MASK 0x0f
+
+#define CHECK_STARTUP_PROGRESS(x, n) \
+do { \
+ if ((x) >= (n)) \
+ return; \
+ x = (n); \
+} while (0)
+
+extern const unsigned char line6_midi_id[3];
+
+static const int SYSEX_DATA_OFS = sizeof(line6_midi_id) + 3;
+static const int SYSEX_EXTRA_SIZE = sizeof(line6_midi_id) + 4;
+
+/*
+ Common properties of Line 6 devices.
+*/
+struct line6_properties {
+ /* Card id string (maximum 16 characters).
+ * This can be used to address the device in ALSA programs as
+ * "default:CARD=<id>"
+ */
+ const char *id;
+
+ /* Card short name (maximum 32 characters) */
+ const char *name;
+
+ /* Bit vector defining this device's capabilities in line6usb driver */
+ int capabilities;
+
+ int altsetting;
+
+ unsigned ep_ctrl_r;
+ unsigned ep_ctrl_w;
+ unsigned ep_audio_r;
+ unsigned ep_audio_w;
+};
+
+/* Capability bits */
+enum {
+ /* device supports settings parameter via USB */
+ LINE6_CAP_CONTROL = 1 << 0,
+ /* device supports PCM input/output via USB */
+ LINE6_CAP_PCM = 1 << 1,
+ /* device support hardware monitoring */
+ LINE6_CAP_HWMON = 1 << 2,
+};
+
+/*
+ Common data shared by all Line 6 devices.
+ Corresponds to a pair of USB endpoints.
+*/
+struct usb_line6 {
+ /* USB device */
+ struct usb_device *usbdev;
+
+ /* Properties */
+ const struct line6_properties *properties;
+
+ /* Interval (ms) */
+ int interval;
+
+ /* Maximum size of USB packet */
+ int max_packet_size;
+
+ /* Device representing the USB interface */
+ struct device *ifcdev;
+
+ /* Line 6 sound card data structure.
+ * Each device has at least MIDI or PCM.
+ */
+ struct snd_card *card;
+
+ /* Line 6 PCM device data structure */
+ struct snd_line6_pcm *line6pcm;
+
+ /* Line 6 MIDI device data structure */
+ struct snd_line6_midi *line6midi;
+
+ /* URB for listening to PODxt Pro control endpoint */
+ struct urb *urb_listen;
+
+ /* Buffer for listening to PODxt Pro control endpoint */
+ unsigned char *buffer_listen;
+
+ /* Buffer for message to be processed */
+ unsigned char *buffer_message;
+
+ /* Length of message to be processed */
+ int message_length;
+
+ void (*process_message)(struct usb_line6 *);
+ void (*disconnect)(struct usb_line6 *line6);
+};
+
+extern char *line6_alloc_sysex_buffer(struct usb_line6 *line6, int code1,
+ int code2, int size);
+extern int line6_read_data(struct usb_line6 *line6, unsigned address,
+ void *data, unsigned datalen);
+extern int line6_read_serial_number(struct usb_line6 *line6,
+ u32 *serial_number);
+extern int line6_send_raw_message_async(struct usb_line6 *line6,
+ const char *buffer, int size);
+extern int line6_send_sysex_message(struct usb_line6 *line6,
+ const char *buffer, int size);
+extern ssize_t line6_set_raw(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count);
+extern void line6_start_timer(struct timer_list *timer, unsigned long msecs,
+ void (*function)(unsigned long),
+ unsigned long data);
+extern int line6_version_request_async(struct usb_line6 *line6);
+extern int line6_write_data(struct usb_line6 *line6, unsigned address,
+ void *data, unsigned datalen);
+
+int line6_probe(struct usb_interface *interface,
+ const struct usb_device_id *id,
+ const char *driver_name,
+ const struct line6_properties *properties,
+ int (*private_init)(struct usb_line6 *, const struct usb_device_id *id),
+ size_t data_size);
+
+void line6_disconnect(struct usb_interface *interface);
+
+#ifdef CONFIG_PM
+int line6_suspend(struct usb_interface *interface, pm_message_t message);
+int line6_resume(struct usb_interface *interface);
+#endif
+
+#endif
diff --git a/sound/usb/line6/midi.c b/sound/usb/line6/midi.c
new file mode 100644
index 000000000..cebea9b7f
--- /dev/null
+++ b/sound/usb/line6/midi.c
@@ -0,0 +1,292 @@
+/*
+ * Line 6 Linux USB driver
+ *
+ * Copyright (C) 2004-2010 Markus Grabner (grabner@icg.tugraz.at)
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, version 2.
+ *
+ */
+
+#include <linux/slab.h>
+#include <linux/usb.h>
+#include <linux/export.h>
+#include <sound/core.h>
+#include <sound/rawmidi.h>
+
+#include "driver.h"
+#include "midi.h"
+
+#define line6_rawmidi_substream_midi(substream) \
+ ((struct snd_line6_midi *)((substream)->rmidi->private_data))
+
+static int send_midi_async(struct usb_line6 *line6, unsigned char *data,
+ int length);
+
+/*
+ Pass data received via USB to MIDI.
+*/
+void line6_midi_receive(struct usb_line6 *line6, unsigned char *data,
+ int length)
+{
+ if (line6->line6midi->substream_receive)
+ snd_rawmidi_receive(line6->line6midi->substream_receive,
+ data, length);
+}
+
+/*
+ Read data from MIDI buffer and transmit them via USB.
+*/
+static void line6_midi_transmit(struct snd_rawmidi_substream *substream)
+{
+ struct usb_line6 *line6 =
+ line6_rawmidi_substream_midi(substream)->line6;
+ struct snd_line6_midi *line6midi = line6->line6midi;
+ struct midi_buffer *mb = &line6midi->midibuf_out;
+ unsigned char chunk[LINE6_FALLBACK_MAXPACKETSIZE];
+ int req, done;
+
+ for (;;) {
+ req = min(line6_midibuf_bytes_free(mb), line6->max_packet_size);
+ done = snd_rawmidi_transmit_peek(substream, chunk, req);
+
+ if (done == 0)
+ break;
+
+ line6_midibuf_write(mb, chunk, done);
+ snd_rawmidi_transmit_ack(substream, done);
+ }
+
+ for (;;) {
+ done = line6_midibuf_read(mb, chunk,
+ LINE6_FALLBACK_MAXPACKETSIZE);
+
+ if (done == 0)
+ break;
+
+ send_midi_async(line6, chunk, done);
+ }
+}
+
+/*
+ Notification of completion of MIDI transmission.
+*/
+static void midi_sent(struct urb *urb)
+{
+ unsigned long flags;
+ int status;
+ int num;
+ struct usb_line6 *line6 = (struct usb_line6 *)urb->context;
+
+ status = urb->status;
+ kfree(urb->transfer_buffer);
+ usb_free_urb(urb);
+
+ if (status == -ESHUTDOWN)
+ return;
+
+ spin_lock_irqsave(&line6->line6midi->lock, flags);
+ num = --line6->line6midi->num_active_send_urbs;
+
+ if (num == 0) {
+ line6_midi_transmit(line6->line6midi->substream_transmit);
+ num = line6->line6midi->num_active_send_urbs;
+ }
+
+ if (num == 0)
+ wake_up(&line6->line6midi->send_wait);
+
+ spin_unlock_irqrestore(&line6->line6midi->lock, flags);
+}
+
+/*
+ Send an asynchronous MIDI message.
+ Assumes that line6->line6midi->lock is held
+ (i.e., this function is serialized).
+*/
+static int send_midi_async(struct usb_line6 *line6, unsigned char *data,
+ int length)
+{
+ struct urb *urb;
+ int retval;
+ unsigned char *transfer_buffer;
+
+ urb = usb_alloc_urb(0, GFP_ATOMIC);
+
+ if (urb == NULL)
+ return -ENOMEM;
+
+ transfer_buffer = kmemdup(data, length, GFP_ATOMIC);
+
+ if (transfer_buffer == NULL) {
+ usb_free_urb(urb);
+ return -ENOMEM;
+ }
+
+ usb_fill_int_urb(urb, line6->usbdev,
+ usb_sndbulkpipe(line6->usbdev,
+ line6->properties->ep_ctrl_w),
+ transfer_buffer, length, midi_sent, line6,
+ line6->interval);
+ urb->actual_length = 0;
+ retval = usb_submit_urb(urb, GFP_ATOMIC);
+
+ if (retval < 0) {
+ dev_err(line6->ifcdev, "usb_submit_urb failed\n");
+ usb_free_urb(urb);
+ return retval;
+ }
+
+ ++line6->line6midi->num_active_send_urbs;
+ return 0;
+}
+
+static int line6_midi_output_open(struct snd_rawmidi_substream *substream)
+{
+ return 0;
+}
+
+static int line6_midi_output_close(struct snd_rawmidi_substream *substream)
+{
+ return 0;
+}
+
+static void line6_midi_output_trigger(struct snd_rawmidi_substream *substream,
+ int up)
+{
+ unsigned long flags;
+ struct usb_line6 *line6 =
+ line6_rawmidi_substream_midi(substream)->line6;
+
+ line6->line6midi->substream_transmit = substream;
+ spin_lock_irqsave(&line6->line6midi->lock, flags);
+
+ if (line6->line6midi->num_active_send_urbs == 0)
+ line6_midi_transmit(substream);
+
+ spin_unlock_irqrestore(&line6->line6midi->lock, flags);
+}
+
+static void line6_midi_output_drain(struct snd_rawmidi_substream *substream)
+{
+ struct usb_line6 *line6 =
+ line6_rawmidi_substream_midi(substream)->line6;
+ struct snd_line6_midi *midi = line6->line6midi;
+
+ wait_event_interruptible(midi->send_wait,
+ midi->num_active_send_urbs == 0);
+}
+
+static int line6_midi_input_open(struct snd_rawmidi_substream *substream)
+{
+ return 0;
+}
+
+static int line6_midi_input_close(struct snd_rawmidi_substream *substream)
+{
+ return 0;
+}
+
+static void line6_midi_input_trigger(struct snd_rawmidi_substream *substream,
+ int up)
+{
+ struct usb_line6 *line6 =
+ line6_rawmidi_substream_midi(substream)->line6;
+
+ if (up)
+ line6->line6midi->substream_receive = substream;
+ else
+ line6->line6midi->substream_receive = NULL;
+}
+
+static struct snd_rawmidi_ops line6_midi_output_ops = {
+ .open = line6_midi_output_open,
+ .close = line6_midi_output_close,
+ .trigger = line6_midi_output_trigger,
+ .drain = line6_midi_output_drain,
+};
+
+static struct snd_rawmidi_ops line6_midi_input_ops = {
+ .open = line6_midi_input_open,
+ .close = line6_midi_input_close,
+ .trigger = line6_midi_input_trigger,
+};
+
+/* Create a MIDI device */
+static int snd_line6_new_midi(struct usb_line6 *line6,
+ struct snd_rawmidi **rmidi_ret)
+{
+ struct snd_rawmidi *rmidi;
+ int err;
+
+ err = snd_rawmidi_new(line6->card, "Line 6 MIDI", 0, 1, 1, rmidi_ret);
+ if (err < 0)
+ return err;
+
+ rmidi = *rmidi_ret;
+ strcpy(rmidi->id, line6->properties->id);
+ strcpy(rmidi->name, line6->properties->name);
+
+ rmidi->info_flags =
+ SNDRV_RAWMIDI_INFO_OUTPUT |
+ SNDRV_RAWMIDI_INFO_INPUT | SNDRV_RAWMIDI_INFO_DUPLEX;
+
+ snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT,
+ &line6_midi_output_ops);
+ snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT,
+ &line6_midi_input_ops);
+ return 0;
+}
+
+/* MIDI device destructor */
+static void snd_line6_midi_free(struct snd_rawmidi *rmidi)
+{
+ struct snd_line6_midi *line6midi = rmidi->private_data;
+
+ line6_midibuf_destroy(&line6midi->midibuf_in);
+ line6_midibuf_destroy(&line6midi->midibuf_out);
+ kfree(line6midi);
+}
+
+/*
+ Initialize the Line 6 MIDI subsystem.
+*/
+int line6_init_midi(struct usb_line6 *line6)
+{
+ int err;
+ struct snd_rawmidi *rmidi;
+ struct snd_line6_midi *line6midi;
+
+ if (!(line6->properties->capabilities & LINE6_CAP_CONTROL)) {
+ /* skip MIDI initialization and report success */
+ return 0;
+ }
+
+ err = snd_line6_new_midi(line6, &rmidi);
+ if (err < 0)
+ return err;
+
+ line6midi = kzalloc(sizeof(struct snd_line6_midi), GFP_KERNEL);
+ if (!line6midi)
+ return -ENOMEM;
+
+ rmidi->private_data = line6midi;
+ rmidi->private_free = snd_line6_midi_free;
+
+ init_waitqueue_head(&line6midi->send_wait);
+ spin_lock_init(&line6midi->lock);
+ line6midi->line6 = line6;
+
+ err = line6_midibuf_init(&line6midi->midibuf_in, MIDI_BUFFER_SIZE, 0);
+ if (err < 0)
+ return err;
+
+ err = line6_midibuf_init(&line6midi->midibuf_out, MIDI_BUFFER_SIZE, 1);
+ if (err < 0)
+ return err;
+
+ line6->line6midi = line6midi;
+ return 0;
+}
+EXPORT_SYMBOL_GPL(line6_init_midi);
diff --git a/sound/usb/line6/midi.h b/sound/usb/line6/midi.h
new file mode 100644
index 000000000..cf82d69e2
--- /dev/null
+++ b/sound/usb/line6/midi.h
@@ -0,0 +1,51 @@
+/*
+ * Line 6 Linux USB driver
+ *
+ * Copyright (C) 2004-2010 Markus Grabner (grabner@icg.tugraz.at)
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, version 2.
+ *
+ */
+
+#ifndef MIDI_H
+#define MIDI_H
+
+#include <sound/rawmidi.h>
+
+#include "midibuf.h"
+
+#define MIDI_BUFFER_SIZE 1024
+
+struct snd_line6_midi {
+ /* Pointer back to the Line 6 driver data structure */
+ struct usb_line6 *line6;
+
+ /* MIDI substream for receiving (or NULL if not active) */
+ struct snd_rawmidi_substream *substream_receive;
+
+ /* MIDI substream for transmitting (or NULL if not active) */
+ struct snd_rawmidi_substream *substream_transmit;
+
+ /* Number of currently active MIDI send URBs */
+ int num_active_send_urbs;
+
+ /* Spin lock to protect MIDI buffer handling */
+ spinlock_t lock;
+
+ /* Wait queue for MIDI transmission */
+ wait_queue_head_t send_wait;
+
+ /* Buffer for incoming MIDI stream */
+ struct midi_buffer midibuf_in;
+
+ /* Buffer for outgoing MIDI stream */
+ struct midi_buffer midibuf_out;
+};
+
+extern int line6_init_midi(struct usb_line6 *line6);
+extern void line6_midi_receive(struct usb_line6 *line6, unsigned char *data,
+ int length);
+
+#endif
diff --git a/sound/usb/line6/midibuf.c b/sound/usb/line6/midibuf.c
new file mode 100644
index 000000000..36a610ba3
--- /dev/null
+++ b/sound/usb/line6/midibuf.c
@@ -0,0 +1,252 @@
+/*
+ * Line 6 Linux USB driver
+ *
+ * Copyright (C) 2004-2010 Markus Grabner (grabner@icg.tugraz.at)
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, version 2.
+ *
+ */
+
+#include <linux/slab.h>
+
+#include "midibuf.h"
+
+static int midibuf_message_length(unsigned char code)
+{
+ int message_length;
+
+ if (code < 0x80)
+ message_length = -1;
+ else if (code < 0xf0) {
+ static const int length[] = { 3, 3, 3, 3, 2, 2, 3 };
+
+ message_length = length[(code >> 4) - 8];
+ } else {
+ /*
+ Note that according to the MIDI specification 0xf2 is
+ the "Song Position Pointer", but this is used by Line 6
+ to send sysex messages to the host.
+ */
+ static const int length[] = { -1, 2, -1, 2, -1, -1, 1, 1, 1, 1,
+ 1, 1, 1, -1, 1, 1
+ };
+ message_length = length[code & 0x0f];
+ }
+
+ return message_length;
+}
+
+static int midibuf_is_empty(struct midi_buffer *this)
+{
+ return (this->pos_read == this->pos_write) && !this->full;
+}
+
+static int midibuf_is_full(struct midi_buffer *this)
+{
+ return this->full;
+}
+
+void line6_midibuf_reset(struct midi_buffer *this)
+{
+ this->pos_read = this->pos_write = this->full = 0;
+ this->command_prev = -1;
+}
+
+int line6_midibuf_init(struct midi_buffer *this, int size, int split)
+{
+ this->buf = kmalloc(size, GFP_KERNEL);
+
+ if (this->buf == NULL)
+ return -ENOMEM;
+
+ this->size = size;
+ this->split = split;
+ line6_midibuf_reset(this);
+ return 0;
+}
+
+int line6_midibuf_bytes_free(struct midi_buffer *this)
+{
+ return
+ midibuf_is_full(this) ?
+ 0 :
+ (this->pos_read - this->pos_write + this->size - 1) % this->size +
+ 1;
+}
+
+int line6_midibuf_bytes_used(struct midi_buffer *this)
+{
+ return
+ midibuf_is_empty(this) ?
+ 0 :
+ (this->pos_write - this->pos_read + this->size - 1) % this->size +
+ 1;
+}
+
+int line6_midibuf_write(struct midi_buffer *this, unsigned char *data,
+ int length)
+{
+ int bytes_free;
+ int length1, length2;
+ int skip_active_sense = 0;
+
+ if (midibuf_is_full(this) || (length <= 0))
+ return 0;
+
+ /* skip trailing active sense */
+ if (data[length - 1] == 0xfe) {
+ --length;
+ skip_active_sense = 1;
+ }
+
+ bytes_free = line6_midibuf_bytes_free(this);
+
+ if (length > bytes_free)
+ length = bytes_free;
+
+ if (length > 0) {
+ length1 = this->size - this->pos_write;
+
+ if (length < length1) {
+ /* no buffer wraparound */
+ memcpy(this->buf + this->pos_write, data, length);
+ this->pos_write += length;
+ } else {
+ /* buffer wraparound */
+ length2 = length - length1;
+ memcpy(this->buf + this->pos_write, data, length1);
+ memcpy(this->buf, data + length1, length2);
+ this->pos_write = length2;
+ }
+
+ if (this->pos_write == this->pos_read)
+ this->full = 1;
+ }
+
+ return length + skip_active_sense;
+}
+
+int line6_midibuf_read(struct midi_buffer *this, unsigned char *data,
+ int length)
+{
+ int bytes_used;
+ int length1, length2;
+ int command;
+ int midi_length;
+ int repeat = 0;
+ int i;
+
+ /* we need to be able to store at least a 3 byte MIDI message */
+ if (length < 3)
+ return -EINVAL;
+
+ if (midibuf_is_empty(this))
+ return 0;
+
+ bytes_used = line6_midibuf_bytes_used(this);
+
+ if (length > bytes_used)
+ length = bytes_used;
+
+ length1 = this->size - this->pos_read;
+
+ /* check MIDI command length */
+ command = this->buf[this->pos_read];
+
+ if (command & 0x80) {
+ midi_length = midibuf_message_length(command);
+ this->command_prev = command;
+ } else {
+ if (this->command_prev > 0) {
+ int midi_length_prev =
+ midibuf_message_length(this->command_prev);
+
+ if (midi_length_prev > 0) {
+ midi_length = midi_length_prev - 1;
+ repeat = 1;
+ } else
+ midi_length = -1;
+ } else
+ midi_length = -1;
+ }
+
+ if (midi_length < 0) {
+ /* search for end of message */
+ if (length < length1) {
+ /* no buffer wraparound */
+ for (i = 1; i < length; ++i)
+ if (this->buf[this->pos_read + i] & 0x80)
+ break;
+
+ midi_length = i;
+ } else {
+ /* buffer wraparound */
+ length2 = length - length1;
+
+ for (i = 1; i < length1; ++i)
+ if (this->buf[this->pos_read + i] & 0x80)
+ break;
+
+ if (i < length1)
+ midi_length = i;
+ else {
+ for (i = 0; i < length2; ++i)
+ if (this->buf[i] & 0x80)
+ break;
+
+ midi_length = length1 + i;
+ }
+ }
+
+ if (midi_length == length)
+ midi_length = -1; /* end of message not found */
+ }
+
+ if (midi_length < 0) {
+ if (!this->split)
+ return 0; /* command is not yet complete */
+ } else {
+ if (length < midi_length)
+ return 0; /* command is not yet complete */
+
+ length = midi_length;
+ }
+
+ if (length < length1) {
+ /* no buffer wraparound */
+ memcpy(data + repeat, this->buf + this->pos_read, length);
+ this->pos_read += length;
+ } else {
+ /* buffer wraparound */
+ length2 = length - length1;
+ memcpy(data + repeat, this->buf + this->pos_read, length1);
+ memcpy(data + repeat + length1, this->buf, length2);
+ this->pos_read = length2;
+ }
+
+ if (repeat)
+ data[0] = this->command_prev;
+
+ this->full = 0;
+ return length + repeat;
+}
+
+int line6_midibuf_ignore(struct midi_buffer *this, int length)
+{
+ int bytes_used = line6_midibuf_bytes_used(this);
+
+ if (length > bytes_used)
+ length = bytes_used;
+
+ this->pos_read = (this->pos_read + length) % this->size;
+ this->full = 0;
+ return length;
+}
+
+void line6_midibuf_destroy(struct midi_buffer *this)
+{
+ kfree(this->buf);
+ this->buf = NULL;
+}
diff --git a/sound/usb/line6/midibuf.h b/sound/usb/line6/midibuf.h
new file mode 100644
index 000000000..6ea21ffb6
--- /dev/null
+++ b/sound/usb/line6/midibuf.h
@@ -0,0 +1,35 @@
+/*
+ * Line 6 Linux USB driver
+ *
+ * Copyright (C) 2004-2010 Markus Grabner (grabner@icg.tugraz.at)
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, version 2.
+ *
+ */
+
+#ifndef MIDIBUF_H
+#define MIDIBUF_H
+
+struct midi_buffer {
+ unsigned char *buf;
+ int size;
+ int split;
+ int pos_read, pos_write;
+ int full;
+ int command_prev;
+};
+
+extern int line6_midibuf_bytes_used(struct midi_buffer *mb);
+extern int line6_midibuf_bytes_free(struct midi_buffer *mb);
+extern void line6_midibuf_destroy(struct midi_buffer *mb);
+extern int line6_midibuf_ignore(struct midi_buffer *mb, int length);
+extern int line6_midibuf_init(struct midi_buffer *mb, int size, int split);
+extern int line6_midibuf_read(struct midi_buffer *mb, unsigned char *data,
+ int length);
+extern void line6_midibuf_reset(struct midi_buffer *mb);
+extern int line6_midibuf_write(struct midi_buffer *mb, unsigned char *data,
+ int length);
+
+#endif
diff --git a/sound/usb/line6/pcm.c b/sound/usb/line6/pcm.c
new file mode 100644
index 000000000..8461d6bf9
--- /dev/null
+++ b/sound/usb/line6/pcm.c
@@ -0,0 +1,588 @@
+/*
+ * Line 6 Linux USB driver
+ *
+ * Copyright (C) 2004-2010 Markus Grabner (grabner@icg.tugraz.at)
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, version 2.
+ *
+ */
+
+#include <linux/slab.h>
+#include <linux/export.h>
+#include <sound/core.h>
+#include <sound/control.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+
+#include "capture.h"
+#include "driver.h"
+#include "playback.h"
+
+/* impulse response volume controls */
+static int snd_line6_impulse_volume_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = 1;
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = 255;
+ return 0;
+}
+
+static int snd_line6_impulse_volume_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_line6_pcm *line6pcm = snd_kcontrol_chip(kcontrol);
+
+ ucontrol->value.integer.value[0] = line6pcm->impulse_volume;
+ return 0;
+}
+
+static int snd_line6_impulse_volume_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_line6_pcm *line6pcm = snd_kcontrol_chip(kcontrol);
+ int value = ucontrol->value.integer.value[0];
+ int err;
+
+ if (line6pcm->impulse_volume == value)
+ return 0;
+
+ line6pcm->impulse_volume = value;
+ if (value > 0) {
+ err = line6_pcm_acquire(line6pcm, LINE6_STREAM_IMPULSE);
+ if (err < 0) {
+ line6pcm->impulse_volume = 0;
+ line6_pcm_release(line6pcm, LINE6_STREAM_IMPULSE);
+ return err;
+ }
+ } else {
+ line6_pcm_release(line6pcm, LINE6_STREAM_IMPULSE);
+ }
+ return 1;
+}
+
+/* impulse response period controls */
+static int snd_line6_impulse_period_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = 1;
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = 2000;
+ return 0;
+}
+
+static int snd_line6_impulse_period_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_line6_pcm *line6pcm = snd_kcontrol_chip(kcontrol);
+
+ ucontrol->value.integer.value[0] = line6pcm->impulse_period;
+ return 0;
+}
+
+static int snd_line6_impulse_period_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_line6_pcm *line6pcm = snd_kcontrol_chip(kcontrol);
+ int value = ucontrol->value.integer.value[0];
+
+ if (line6pcm->impulse_period == value)
+ return 0;
+
+ line6pcm->impulse_period = value;
+ return 1;
+}
+
+/*
+ Unlink all currently active URBs.
+*/
+static void line6_unlink_audio_urbs(struct snd_line6_pcm *line6pcm,
+ struct line6_pcm_stream *pcms)
+{
+ int i;
+
+ for (i = 0; i < LINE6_ISO_BUFFERS; i++) {
+ if (test_bit(i, &pcms->active_urbs)) {
+ if (!test_and_set_bit(i, &pcms->unlink_urbs))
+ usb_unlink_urb(pcms->urbs[i]);
+ }
+ }
+}
+
+/*
+ Wait until unlinking of all currently active URBs has been finished.
+*/
+static void line6_wait_clear_audio_urbs(struct snd_line6_pcm *line6pcm,
+ struct line6_pcm_stream *pcms)
+{
+ int timeout = HZ;
+ int i;
+ int alive;
+
+ do {
+ alive = 0;
+ for (i = 0; i < LINE6_ISO_BUFFERS; i++) {
+ if (test_bit(i, &pcms->active_urbs))
+ alive++;
+ }
+ if (!alive)
+ break;
+ set_current_state(TASK_UNINTERRUPTIBLE);
+ schedule_timeout(1);
+ } while (--timeout > 0);
+ if (alive)
+ dev_err(line6pcm->line6->ifcdev,
+ "timeout: still %d active urbs..\n", alive);
+}
+
+static inline struct line6_pcm_stream *
+get_stream(struct snd_line6_pcm *line6pcm, int direction)
+{
+ return (direction == SNDRV_PCM_STREAM_PLAYBACK) ?
+ &line6pcm->out : &line6pcm->in;
+}
+
+/* allocate a buffer if not opened yet;
+ * call this in line6pcm.state_change mutex
+ */
+static int line6_buffer_acquire(struct snd_line6_pcm *line6pcm,
+ struct line6_pcm_stream *pstr, int type)
+{
+ /* Invoked multiple times in a row so allocate once only */
+ if (!test_and_set_bit(type, &pstr->opened) && !pstr->buffer) {
+ pstr->buffer = kmalloc(LINE6_ISO_BUFFERS * LINE6_ISO_PACKETS *
+ line6pcm->max_packet_size, GFP_KERNEL);
+ if (!pstr->buffer)
+ return -ENOMEM;
+ }
+ return 0;
+}
+
+/* free a buffer if all streams are closed;
+ * call this in line6pcm.state_change mutex
+ */
+static void line6_buffer_release(struct snd_line6_pcm *line6pcm,
+ struct line6_pcm_stream *pstr, int type)
+{
+
+ clear_bit(type, &pstr->opened);
+ if (!pstr->opened) {
+ line6_wait_clear_audio_urbs(line6pcm, pstr);
+ kfree(pstr->buffer);
+ pstr->buffer = NULL;
+ }
+}
+
+/* start a PCM stream */
+static int line6_stream_start(struct snd_line6_pcm *line6pcm, int direction,
+ int type)
+{
+ unsigned long flags;
+ struct line6_pcm_stream *pstr = get_stream(line6pcm, direction);
+ int ret = 0;
+
+ spin_lock_irqsave(&pstr->lock, flags);
+ if (!test_and_set_bit(type, &pstr->running)) {
+ if (pstr->active_urbs || pstr->unlink_urbs) {
+ ret = -EBUSY;
+ goto error;
+ }
+
+ pstr->count = 0;
+ /* Submit all currently available URBs */
+ if (direction == SNDRV_PCM_STREAM_PLAYBACK)
+ ret = line6_submit_audio_out_all_urbs(line6pcm);
+ else
+ ret = line6_submit_audio_in_all_urbs(line6pcm);
+ }
+ error:
+ if (ret < 0)
+ clear_bit(type, &pstr->running);
+ spin_unlock_irqrestore(&pstr->lock, flags);
+ return ret;
+}
+
+/* stop a PCM stream; this doesn't sync with the unlinked URBs */
+static void line6_stream_stop(struct snd_line6_pcm *line6pcm, int direction,
+ int type)
+{
+ unsigned long flags;
+ struct line6_pcm_stream *pstr = get_stream(line6pcm, direction);
+
+ spin_lock_irqsave(&pstr->lock, flags);
+ clear_bit(type, &pstr->running);
+ if (!pstr->running) {
+ line6_unlink_audio_urbs(line6pcm, pstr);
+ if (direction == SNDRV_PCM_STREAM_CAPTURE) {
+ line6pcm->prev_fbuf = NULL;
+ line6pcm->prev_fsize = 0;
+ }
+ }
+ spin_unlock_irqrestore(&pstr->lock, flags);
+}
+
+/* common PCM trigger callback */
+int snd_line6_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+ struct snd_line6_pcm *line6pcm = snd_pcm_substream_chip(substream);
+ struct snd_pcm_substream *s;
+ int err;
+
+ clear_bit(LINE6_FLAG_PREPARED, &line6pcm->flags);
+
+ snd_pcm_group_for_each_entry(s, substream) {
+ if (s->pcm->card != substream->pcm->card)
+ continue;
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ err = line6_stream_start(line6pcm, s->stream,
+ LINE6_STREAM_PCM);
+ if (err < 0)
+ return err;
+ break;
+
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ line6_stream_stop(line6pcm, s->stream,
+ LINE6_STREAM_PCM);
+ break;
+
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ if (s->stream != SNDRV_PCM_STREAM_PLAYBACK)
+ return -EINVAL;
+ set_bit(LINE6_FLAG_PAUSE_PLAYBACK, &line6pcm->flags);
+ break;
+
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ if (s->stream != SNDRV_PCM_STREAM_PLAYBACK)
+ return -EINVAL;
+ clear_bit(LINE6_FLAG_PAUSE_PLAYBACK, &line6pcm->flags);
+ break;
+
+ default:
+ return -EINVAL;
+ }
+ }
+
+ return 0;
+}
+
+/* common PCM pointer callback */
+snd_pcm_uframes_t snd_line6_pointer(struct snd_pcm_substream *substream)
+{
+ struct snd_line6_pcm *line6pcm = snd_pcm_substream_chip(substream);
+ struct line6_pcm_stream *pstr = get_stream(line6pcm, substream->stream);
+
+ return pstr->pos_done;
+}
+
+/* Acquire and start duplex streams:
+ * type is either LINE6_STREAM_IMPULSE or LINE6_STREAM_MONITOR
+ */
+int line6_pcm_acquire(struct snd_line6_pcm *line6pcm, int type)
+{
+ struct line6_pcm_stream *pstr;
+ int ret = 0, dir;
+
+ mutex_lock(&line6pcm->state_mutex);
+ for (dir = 0; dir < 2; dir++) {
+ pstr = get_stream(line6pcm, dir);
+ ret = line6_buffer_acquire(line6pcm, pstr, type);
+ if (ret < 0)
+ goto error;
+ if (!pstr->running)
+ line6_wait_clear_audio_urbs(line6pcm, pstr);
+ }
+ for (dir = 0; dir < 2; dir++) {
+ ret = line6_stream_start(line6pcm, dir, type);
+ if (ret < 0)
+ goto error;
+ }
+ error:
+ mutex_unlock(&line6pcm->state_mutex);
+ if (ret < 0)
+ line6_pcm_release(line6pcm, type);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(line6_pcm_acquire);
+
+/* Stop and release duplex streams */
+void line6_pcm_release(struct snd_line6_pcm *line6pcm, int type)
+{
+ struct line6_pcm_stream *pstr;
+ int dir;
+
+ mutex_lock(&line6pcm->state_mutex);
+ for (dir = 0; dir < 2; dir++)
+ line6_stream_stop(line6pcm, dir, type);
+ for (dir = 0; dir < 2; dir++) {
+ pstr = get_stream(line6pcm, dir);
+ line6_buffer_release(line6pcm, pstr, type);
+ }
+ mutex_unlock(&line6pcm->state_mutex);
+}
+EXPORT_SYMBOL_GPL(line6_pcm_release);
+
+/* common PCM hw_params callback */
+int snd_line6_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *hw_params)
+{
+ int ret;
+ struct snd_line6_pcm *line6pcm = snd_pcm_substream_chip(substream);
+ struct line6_pcm_stream *pstr = get_stream(line6pcm, substream->stream);
+
+ mutex_lock(&line6pcm->state_mutex);
+ ret = line6_buffer_acquire(line6pcm, pstr, LINE6_STREAM_PCM);
+ if (ret < 0)
+ goto error;
+
+ ret = snd_pcm_lib_malloc_pages(substream,
+ params_buffer_bytes(hw_params));
+ if (ret < 0) {
+ line6_buffer_release(line6pcm, pstr, LINE6_STREAM_PCM);
+ goto error;
+ }
+
+ pstr->period = params_period_bytes(hw_params);
+ error:
+ mutex_unlock(&line6pcm->state_mutex);
+ return ret;
+}
+
+/* common PCM hw_free callback */
+int snd_line6_hw_free(struct snd_pcm_substream *substream)
+{
+ struct snd_line6_pcm *line6pcm = snd_pcm_substream_chip(substream);
+ struct line6_pcm_stream *pstr = get_stream(line6pcm, substream->stream);
+
+ mutex_lock(&line6pcm->state_mutex);
+ line6_buffer_release(line6pcm, pstr, LINE6_STREAM_PCM);
+ mutex_unlock(&line6pcm->state_mutex);
+ return snd_pcm_lib_free_pages(substream);
+}
+
+
+/* control info callback */
+static int snd_line6_control_playback_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = 2;
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = 256;
+ return 0;
+}
+
+/* control get callback */
+static int snd_line6_control_playback_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ int i;
+ struct snd_line6_pcm *line6pcm = snd_kcontrol_chip(kcontrol);
+
+ for (i = 0; i < 2; i++)
+ ucontrol->value.integer.value[i] = line6pcm->volume_playback[i];
+
+ return 0;
+}
+
+/* control put callback */
+static int snd_line6_control_playback_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ int i, changed = 0;
+ struct snd_line6_pcm *line6pcm = snd_kcontrol_chip(kcontrol);
+
+ for (i = 0; i < 2; i++)
+ if (line6pcm->volume_playback[i] !=
+ ucontrol->value.integer.value[i]) {
+ line6pcm->volume_playback[i] =
+ ucontrol->value.integer.value[i];
+ changed = 1;
+ }
+
+ return changed;
+}
+
+/* control definition */
+static struct snd_kcontrol_new line6_controls[] = {
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "PCM Playback Volume",
+ .info = snd_line6_control_playback_info,
+ .get = snd_line6_control_playback_get,
+ .put = snd_line6_control_playback_put
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Impulse Response Volume",
+ .info = snd_line6_impulse_volume_info,
+ .get = snd_line6_impulse_volume_get,
+ .put = snd_line6_impulse_volume_put
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Impulse Response Period",
+ .info = snd_line6_impulse_period_info,
+ .get = snd_line6_impulse_period_get,
+ .put = snd_line6_impulse_period_put
+ },
+};
+
+/*
+ Cleanup the PCM device.
+*/
+static void cleanup_urbs(struct line6_pcm_stream *pcms)
+{
+ int i;
+
+ for (i = 0; i < LINE6_ISO_BUFFERS; i++) {
+ if (pcms->urbs[i]) {
+ usb_kill_urb(pcms->urbs[i]);
+ usb_free_urb(pcms->urbs[i]);
+ }
+ }
+}
+
+static void line6_cleanup_pcm(struct snd_pcm *pcm)
+{
+ struct snd_line6_pcm *line6pcm = snd_pcm_chip(pcm);
+
+ cleanup_urbs(&line6pcm->out);
+ cleanup_urbs(&line6pcm->in);
+ kfree(line6pcm);
+}
+
+/* create a PCM device */
+static int snd_line6_new_pcm(struct usb_line6 *line6, struct snd_pcm **pcm_ret)
+{
+ struct snd_pcm *pcm;
+ int err;
+
+ err = snd_pcm_new(line6->card, (char *)line6->properties->name,
+ 0, 1, 1, pcm_ret);
+ if (err < 0)
+ return err;
+ pcm = *pcm_ret;
+ strcpy(pcm->name, line6->properties->name);
+
+ /* set operators */
+ snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK,
+ &snd_line6_playback_ops);
+ snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_line6_capture_ops);
+
+ /* pre-allocation of buffers */
+ snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_CONTINUOUS,
+ snd_dma_continuous_data
+ (GFP_KERNEL), 64 * 1024,
+ 128 * 1024);
+ return 0;
+}
+
+/*
+ Sync with PCM stream stops.
+*/
+void line6_pcm_disconnect(struct snd_line6_pcm *line6pcm)
+{
+ line6_unlink_audio_urbs(line6pcm, &line6pcm->out);
+ line6_unlink_audio_urbs(line6pcm, &line6pcm->in);
+ line6_wait_clear_audio_urbs(line6pcm, &line6pcm->out);
+ line6_wait_clear_audio_urbs(line6pcm, &line6pcm->in);
+}
+
+/*
+ Create and register the PCM device and mixer entries.
+ Create URBs for playback and capture.
+*/
+int line6_init_pcm(struct usb_line6 *line6,
+ struct line6_pcm_properties *properties)
+{
+ int i, err;
+ unsigned ep_read = line6->properties->ep_audio_r;
+ unsigned ep_write = line6->properties->ep_audio_w;
+ struct snd_pcm *pcm;
+ struct snd_line6_pcm *line6pcm;
+
+ if (!(line6->properties->capabilities & LINE6_CAP_PCM))
+ return 0; /* skip PCM initialization and report success */
+
+ err = snd_line6_new_pcm(line6, &pcm);
+ if (err < 0)
+ return err;
+
+ line6pcm = kzalloc(sizeof(*line6pcm), GFP_KERNEL);
+ if (!line6pcm)
+ return -ENOMEM;
+
+ mutex_init(&line6pcm->state_mutex);
+ line6pcm->pcm = pcm;
+ line6pcm->properties = properties;
+ line6pcm->volume_playback[0] = line6pcm->volume_playback[1] = 255;
+ line6pcm->volume_monitor = 255;
+ line6pcm->line6 = line6;
+
+ /* Read and write buffers are sized identically, so choose minimum */
+ line6pcm->max_packet_size = min(
+ usb_maxpacket(line6->usbdev,
+ usb_rcvisocpipe(line6->usbdev, ep_read), 0),
+ usb_maxpacket(line6->usbdev,
+ usb_sndisocpipe(line6->usbdev, ep_write), 1));
+
+ spin_lock_init(&line6pcm->out.lock);
+ spin_lock_init(&line6pcm->in.lock);
+ line6pcm->impulse_period = LINE6_IMPULSE_DEFAULT_PERIOD;
+
+ line6->line6pcm = line6pcm;
+
+ pcm->private_data = line6pcm;
+ pcm->private_free = line6_cleanup_pcm;
+
+ err = line6_create_audio_out_urbs(line6pcm);
+ if (err < 0)
+ return err;
+
+ err = line6_create_audio_in_urbs(line6pcm);
+ if (err < 0)
+ return err;
+
+ /* mixer: */
+ for (i = 0; i < ARRAY_SIZE(line6_controls); i++) {
+ err = snd_ctl_add(line6->card,
+ snd_ctl_new1(&line6_controls[i], line6pcm));
+ if (err < 0)
+ return err;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(line6_init_pcm);
+
+/* prepare pcm callback */
+int snd_line6_prepare(struct snd_pcm_substream *substream)
+{
+ struct snd_line6_pcm *line6pcm = snd_pcm_substream_chip(substream);
+ struct line6_pcm_stream *pstr = get_stream(line6pcm, substream->stream);
+
+ mutex_lock(&line6pcm->state_mutex);
+ if (!pstr->running)
+ line6_wait_clear_audio_urbs(line6pcm, pstr);
+
+ if (!test_and_set_bit(LINE6_FLAG_PREPARED, &line6pcm->flags)) {
+ line6pcm->out.count = 0;
+ line6pcm->out.pos = 0;
+ line6pcm->out.pos_done = 0;
+ line6pcm->out.bytes = 0;
+ line6pcm->in.count = 0;
+ line6pcm->in.pos_done = 0;
+ line6pcm->in.bytes = 0;
+ }
+
+ mutex_unlock(&line6pcm->state_mutex);
+ return 0;
+}
diff --git a/sound/usb/line6/pcm.h b/sound/usb/line6/pcm.h
new file mode 100644
index 000000000..508410adb
--- /dev/null
+++ b/sound/usb/line6/pcm.h
@@ -0,0 +1,197 @@
+/*
+ * Line 6 Linux USB driver
+ *
+ * Copyright (C) 2004-2010 Markus Grabner (grabner@icg.tugraz.at)
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, version 2.
+ *
+ */
+
+/*
+ PCM interface to POD series devices.
+*/
+
+#ifndef PCM_H
+#define PCM_H
+
+#include <sound/pcm.h>
+
+#include "driver.h"
+
+/* number of URBs */
+#define LINE6_ISO_BUFFERS 2
+
+/*
+ number of USB frames per URB
+ The Line 6 Windows driver always transmits two frames per packet, but
+ the Linux driver performs significantly better (i.e., lower latency)
+ with only one frame per packet.
+*/
+#define LINE6_ISO_PACKETS 1
+
+/* in a "full speed" device (such as the PODxt Pro) this means 1ms */
+#define LINE6_ISO_INTERVAL 1
+
+#define LINE6_IMPULSE_DEFAULT_PERIOD 100
+
+/*
+ Get substream from Line 6 PCM data structure
+*/
+#define get_substream(line6pcm, stream) \
+ (line6pcm->pcm->streams[stream].substream)
+
+/*
+ PCM mode bits.
+
+ There are several features of the Line 6 USB driver which require PCM
+ data to be exchanged with the device:
+ *) PCM playback and capture via ALSA
+ *) software monitoring (for devices without hardware monitoring)
+ *) optional impulse response measurement
+ However, from the device's point of view, there is just a single
+ capture and playback stream, which must be shared between these
+ subsystems. It is therefore necessary to maintain the state of the
+ subsystems with respect to PCM usage.
+
+ We define two bit flags, "opened" and "running", for each playback
+ or capture stream. Both can contain the bit flag corresponding to
+ LINE6_STREAM_* type,
+ LINE6_STREAM_PCM = ALSA PCM playback or capture
+ LINE6_STREAM_MONITOR = software monitoring
+ IMPULSE = optional impulse response measurement
+ The opened flag indicates whether the buffer is allocated while
+ the running flag indicates whether the stream is running.
+
+ For monitor or impulse operations, the driver needs to call
+ line6_pcm_acquire() or line6_pcm_release() with the appropriate
+ LINE6_STREAM_* flag.
+*/
+
+/* stream types */
+enum {
+ LINE6_STREAM_PCM,
+ LINE6_STREAM_MONITOR,
+ LINE6_STREAM_IMPULSE,
+};
+
+/* misc bit flags for PCM operation */
+enum {
+ LINE6_FLAG_PAUSE_PLAYBACK,
+ LINE6_FLAG_PREPARED,
+};
+
+struct line6_pcm_properties {
+ struct snd_pcm_hardware playback_hw, capture_hw;
+ struct snd_pcm_hw_constraint_ratdens rates;
+ int bytes_per_frame;
+};
+
+struct line6_pcm_stream {
+ /* allocated URBs */
+ struct urb *urbs[LINE6_ISO_BUFFERS];
+
+ /* Temporary buffer;
+ * Since the packet size is not known in advance, this buffer is
+ * large enough to store maximum size packets.
+ */
+ unsigned char *buffer;
+
+ /* Free frame position in the buffer. */
+ snd_pcm_uframes_t pos;
+
+ /* Count processed bytes;
+ * This is modulo period size (to determine when a period is finished).
+ */
+ unsigned bytes;
+
+ /* Counter to create desired sample rate */
+ unsigned count;
+
+ /* period size in bytes */
+ unsigned period;
+
+ /* Processed frame position in the buffer;
+ * The contents of the ring buffer have been consumed by the USB
+ * subsystem (i.e., sent to the USB device) up to this position.
+ */
+ snd_pcm_uframes_t pos_done;
+
+ /* Bit mask of active URBs */
+ unsigned long active_urbs;
+
+ /* Bit mask of URBs currently being unlinked */
+ unsigned long unlink_urbs;
+
+ /* Spin lock to protect updates of the buffer positions (not contents)
+ */
+ spinlock_t lock;
+
+ /* Bit flags for operational stream types */
+ unsigned long opened;
+
+ /* Bit flags for running stream types */
+ unsigned long running;
+
+ int last_frame;
+};
+
+struct snd_line6_pcm {
+ /* Pointer back to the Line 6 driver data structure */
+ struct usb_line6 *line6;
+
+ /* Properties. */
+ struct line6_pcm_properties *properties;
+
+ /* ALSA pcm stream */
+ struct snd_pcm *pcm;
+
+ /* protection to state changes of in/out streams */
+ struct mutex state_mutex;
+
+ /* Capture and playback streams */
+ struct line6_pcm_stream in;
+ struct line6_pcm_stream out;
+
+ /* Previously captured frame (for software monitoring) */
+ unsigned char *prev_fbuf;
+
+ /* Size of previously captured frame (for software monitoring) */
+ int prev_fsize;
+
+ /* Maximum size of USB packet */
+ int max_packet_size;
+
+ /* PCM playback volume (left and right) */
+ int volume_playback[2];
+
+ /* PCM monitor volume */
+ int volume_monitor;
+
+ /* Volume of impulse response test signal (if zero, test is disabled) */
+ int impulse_volume;
+
+ /* Period of impulse response test signal */
+ int impulse_period;
+
+ /* Counter for impulse response test signal */
+ int impulse_count;
+
+ /* Several status bits (see LINE6_FLAG_*) */
+ unsigned long flags;
+};
+
+extern int line6_init_pcm(struct usb_line6 *line6,
+ struct line6_pcm_properties *properties);
+extern int snd_line6_trigger(struct snd_pcm_substream *substream, int cmd);
+extern int snd_line6_prepare(struct snd_pcm_substream *substream);
+extern int snd_line6_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *hw_params);
+extern int snd_line6_hw_free(struct snd_pcm_substream *substream);
+extern snd_pcm_uframes_t snd_line6_pointer(struct snd_pcm_substream *substream);
+extern void line6_pcm_disconnect(struct snd_line6_pcm *line6pcm);
+extern int line6_pcm_acquire(struct snd_line6_pcm *line6pcm, int type);
+extern void line6_pcm_release(struct snd_line6_pcm *line6pcm, int type);
+
+#endif
diff --git a/sound/usb/line6/playback.c b/sound/usb/line6/playback.c
new file mode 100644
index 000000000..97ed593f6
--- /dev/null
+++ b/sound/usb/line6/playback.c
@@ -0,0 +1,429 @@
+/*
+ * Line 6 Linux USB driver
+ *
+ * Copyright (C) 2004-2010 Markus Grabner (grabner@icg.tugraz.at)
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, version 2.
+ *
+ */
+
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+
+#include "capture.h"
+#include "driver.h"
+#include "pcm.h"
+#include "playback.h"
+
+/*
+ Software stereo volume control.
+*/
+static void change_volume(struct urb *urb_out, int volume[],
+ int bytes_per_frame)
+{
+ int chn = 0;
+
+ if (volume[0] == 256 && volume[1] == 256)
+ return; /* maximum volume - no change */
+
+ if (bytes_per_frame == 4) {
+ __le16 *p, *buf_end;
+
+ p = (__le16 *)urb_out->transfer_buffer;
+ buf_end = p + urb_out->transfer_buffer_length / sizeof(*p);
+
+ for (; p < buf_end; ++p) {
+ short pv = le16_to_cpu(*p);
+ int val = (pv * volume[chn & 1]) >> 8;
+ pv = clamp(val, -0x8000, 0x7fff);
+ *p = cpu_to_le16(pv);
+ ++chn;
+ }
+ } else if (bytes_per_frame == 6) {
+ unsigned char *p, *buf_end;
+
+ p = (unsigned char *)urb_out->transfer_buffer;
+ buf_end = p + urb_out->transfer_buffer_length;
+
+ for (; p < buf_end; p += 3) {
+ int val;
+
+ val = p[0] + (p[1] << 8) + ((signed char)p[2] << 16);
+ val = (val * volume[chn & 1]) >> 8;
+ val = clamp(val, -0x800000, 0x7fffff);
+ p[0] = val;
+ p[1] = val >> 8;
+ p[2] = val >> 16;
+ ++chn;
+ }
+ }
+}
+
+/*
+ Create signal for impulse response test.
+*/
+static void create_impulse_test_signal(struct snd_line6_pcm *line6pcm,
+ struct urb *urb_out, int bytes_per_frame)
+{
+ int frames = urb_out->transfer_buffer_length / bytes_per_frame;
+
+ if (bytes_per_frame == 4) {
+ int i;
+ short *pi = (short *)line6pcm->prev_fbuf;
+ short *po = (short *)urb_out->transfer_buffer;
+
+ for (i = 0; i < frames; ++i) {
+ po[0] = pi[0];
+ po[1] = 0;
+ pi += 2;
+ po += 2;
+ }
+ } else if (bytes_per_frame == 6) {
+ int i, j;
+ unsigned char *pi = line6pcm->prev_fbuf;
+ unsigned char *po = urb_out->transfer_buffer;
+
+ for (i = 0; i < frames; ++i) {
+ for (j = 0; j < bytes_per_frame / 2; ++j)
+ po[j] = pi[j];
+
+ for (; j < bytes_per_frame; ++j)
+ po[j] = 0;
+
+ pi += bytes_per_frame;
+ po += bytes_per_frame;
+ }
+ }
+ if (--line6pcm->impulse_count <= 0) {
+ ((unsigned char *)(urb_out->transfer_buffer))[bytes_per_frame -
+ 1] =
+ line6pcm->impulse_volume;
+ line6pcm->impulse_count = line6pcm->impulse_period;
+ }
+}
+
+/*
+ Add signal to buffer for software monitoring.
+*/
+static void add_monitor_signal(struct urb *urb_out, unsigned char *signal,
+ int volume, int bytes_per_frame)
+{
+ if (volume == 0)
+ return; /* zero volume - no change */
+
+ if (bytes_per_frame == 4) {
+ __le16 *pi, *po, *buf_end;
+
+ pi = (__le16 *)signal;
+ po = (__le16 *)urb_out->transfer_buffer;
+ buf_end = po + urb_out->transfer_buffer_length / sizeof(*po);
+
+ for (; po < buf_end; ++pi, ++po) {
+ short pov = le16_to_cpu(*po);
+ short piv = le16_to_cpu(*pi);
+ int val = pov + ((piv * volume) >> 8);
+ pov = clamp(val, -0x8000, 0x7fff);
+ *po = cpu_to_le16(pov);
+ }
+ }
+
+ /*
+ We don't need to handle devices with 6 bytes per frame here
+ since they all support hardware monitoring.
+ */
+}
+
+/*
+ Find a free URB, prepare audio data, and submit URB.
+ must be called in line6pcm->out.lock context
+*/
+static int submit_audio_out_urb(struct snd_line6_pcm *line6pcm)
+{
+ int index;
+ int i, urb_size, urb_frames;
+ int ret;
+ const int bytes_per_frame = line6pcm->properties->bytes_per_frame;
+ const int frame_increment =
+ line6pcm->properties->rates.rats[0].num_min;
+ const int frame_factor =
+ line6pcm->properties->rates.rats[0].den *
+ (USB_INTERVALS_PER_SECOND / LINE6_ISO_INTERVAL);
+ struct urb *urb_out;
+
+ index =
+ find_first_zero_bit(&line6pcm->out.active_urbs, LINE6_ISO_BUFFERS);
+
+ if (index < 0 || index >= LINE6_ISO_BUFFERS) {
+ dev_err(line6pcm->line6->ifcdev, "no free URB found\n");
+ return -EINVAL;
+ }
+
+ urb_out = line6pcm->out.urbs[index];
+ urb_size = 0;
+
+ for (i = 0; i < LINE6_ISO_PACKETS; ++i) {
+ /* compute frame size for given sampling rate */
+ int fsize = 0;
+ struct usb_iso_packet_descriptor *fout =
+ &urb_out->iso_frame_desc[i];
+
+ fsize = line6pcm->prev_fsize;
+ if (fsize == 0) {
+ int n;
+
+ line6pcm->out.count += frame_increment;
+ n = line6pcm->out.count / frame_factor;
+ line6pcm->out.count -= n * frame_factor;
+ fsize = n * bytes_per_frame;
+ }
+
+ fout->offset = urb_size;
+ fout->length = fsize;
+ urb_size += fsize;
+ }
+
+ if (urb_size == 0) {
+ /* can't determine URB size */
+ dev_err(line6pcm->line6->ifcdev, "driver bug: urb_size = 0\n");
+ return -EINVAL;
+ }
+
+ urb_frames = urb_size / bytes_per_frame;
+ urb_out->transfer_buffer =
+ line6pcm->out.buffer +
+ index * LINE6_ISO_PACKETS * line6pcm->max_packet_size;
+ urb_out->transfer_buffer_length = urb_size;
+ urb_out->context = line6pcm;
+
+ if (test_bit(LINE6_STREAM_PCM, &line6pcm->out.running) &&
+ !test_bit(LINE6_FLAG_PAUSE_PLAYBACK, &line6pcm->flags)) {
+ struct snd_pcm_runtime *runtime =
+ get_substream(line6pcm, SNDRV_PCM_STREAM_PLAYBACK)->runtime;
+
+ if (line6pcm->out.pos + urb_frames > runtime->buffer_size) {
+ /*
+ The transferred area goes over buffer boundary,
+ copy the data to the temp buffer.
+ */
+ int len;
+
+ len = runtime->buffer_size - line6pcm->out.pos;
+
+ if (len > 0) {
+ memcpy(urb_out->transfer_buffer,
+ runtime->dma_area +
+ line6pcm->out.pos * bytes_per_frame,
+ len * bytes_per_frame);
+ memcpy(urb_out->transfer_buffer +
+ len * bytes_per_frame, runtime->dma_area,
+ (urb_frames - len) * bytes_per_frame);
+ } else
+ dev_err(line6pcm->line6->ifcdev, "driver bug: len = %d\n",
+ len);
+ } else {
+ memcpy(urb_out->transfer_buffer,
+ runtime->dma_area +
+ line6pcm->out.pos * bytes_per_frame,
+ urb_out->transfer_buffer_length);
+ }
+
+ line6pcm->out.pos += urb_frames;
+ if (line6pcm->out.pos >= runtime->buffer_size)
+ line6pcm->out.pos -= runtime->buffer_size;
+
+ change_volume(urb_out, line6pcm->volume_playback,
+ bytes_per_frame);
+ } else {
+ memset(urb_out->transfer_buffer, 0,
+ urb_out->transfer_buffer_length);
+ }
+
+ spin_lock_nested(&line6pcm->in.lock, SINGLE_DEPTH_NESTING);
+ if (line6pcm->prev_fbuf) {
+ if (test_bit(LINE6_STREAM_IMPULSE, &line6pcm->out.running)) {
+ create_impulse_test_signal(line6pcm, urb_out,
+ bytes_per_frame);
+ if (test_bit(LINE6_STREAM_PCM, &line6pcm->in.running)) {
+ line6_capture_copy(line6pcm,
+ urb_out->transfer_buffer,
+ urb_out->
+ transfer_buffer_length);
+ line6_capture_check_period(line6pcm,
+ urb_out->transfer_buffer_length);
+ }
+ } else {
+ if (!(line6pcm->line6->properties->capabilities & LINE6_CAP_HWMON)
+ && line6pcm->out.running && line6pcm->in.running)
+ add_monitor_signal(urb_out, line6pcm->prev_fbuf,
+ line6pcm->volume_monitor,
+ bytes_per_frame);
+ }
+ line6pcm->prev_fbuf = NULL;
+ line6pcm->prev_fsize = 0;
+ }
+ spin_unlock(&line6pcm->in.lock);
+
+ ret = usb_submit_urb(urb_out, GFP_ATOMIC);
+
+ if (ret == 0)
+ set_bit(index, &line6pcm->out.active_urbs);
+ else
+ dev_err(line6pcm->line6->ifcdev,
+ "URB out #%d submission failed (%d)\n", index, ret);
+
+ return 0;
+}
+
+/*
+ Submit all currently available playback URBs.
+ must be called in line6pcm->out.lock context
+ */
+int line6_submit_audio_out_all_urbs(struct snd_line6_pcm *line6pcm)
+{
+ int ret = 0, i;
+
+ for (i = 0; i < LINE6_ISO_BUFFERS; ++i) {
+ ret = submit_audio_out_urb(line6pcm);
+ if (ret < 0)
+ break;
+ }
+
+ return ret;
+}
+
+/*
+ Callback for completed playback URB.
+*/
+static void audio_out_callback(struct urb *urb)
+{
+ int i, index, length = 0, shutdown = 0;
+ unsigned long flags;
+ struct snd_line6_pcm *line6pcm = (struct snd_line6_pcm *)urb->context;
+ struct snd_pcm_substream *substream =
+ get_substream(line6pcm, SNDRV_PCM_STREAM_PLAYBACK);
+
+#if USE_CLEAR_BUFFER_WORKAROUND
+ memset(urb->transfer_buffer, 0, urb->transfer_buffer_length);
+#endif
+
+ line6pcm->out.last_frame = urb->start_frame;
+
+ /* find index of URB */
+ for (index = 0; index < LINE6_ISO_BUFFERS; index++)
+ if (urb == line6pcm->out.urbs[index])
+ break;
+
+ if (index >= LINE6_ISO_BUFFERS)
+ return; /* URB has been unlinked asynchronously */
+
+ for (i = 0; i < LINE6_ISO_PACKETS; i++)
+ length += urb->iso_frame_desc[i].length;
+
+ spin_lock_irqsave(&line6pcm->out.lock, flags);
+
+ if (test_bit(LINE6_STREAM_PCM, &line6pcm->out.running)) {
+ struct snd_pcm_runtime *runtime = substream->runtime;
+
+ line6pcm->out.pos_done +=
+ length / line6pcm->properties->bytes_per_frame;
+
+ if (line6pcm->out.pos_done >= runtime->buffer_size)
+ line6pcm->out.pos_done -= runtime->buffer_size;
+ }
+
+ clear_bit(index, &line6pcm->out.active_urbs);
+
+ for (i = 0; i < LINE6_ISO_PACKETS; i++)
+ if (urb->iso_frame_desc[i].status == -EXDEV) {
+ shutdown = 1;
+ break;
+ }
+
+ if (test_and_clear_bit(index, &line6pcm->out.unlink_urbs))
+ shutdown = 1;
+
+ if (!shutdown) {
+ submit_audio_out_urb(line6pcm);
+
+ if (test_bit(LINE6_STREAM_PCM, &line6pcm->out.running)) {
+ line6pcm->out.bytes += length;
+ if (line6pcm->out.bytes >= line6pcm->out.period) {
+ line6pcm->out.bytes %= line6pcm->out.period;
+ spin_unlock(&line6pcm->out.lock);
+ snd_pcm_period_elapsed(substream);
+ spin_lock(&line6pcm->out.lock);
+ }
+ }
+ }
+ spin_unlock_irqrestore(&line6pcm->out.lock, flags);
+}
+
+/* open playback callback */
+static int snd_line6_playback_open(struct snd_pcm_substream *substream)
+{
+ int err;
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct snd_line6_pcm *line6pcm = snd_pcm_substream_chip(substream);
+
+ err = snd_pcm_hw_constraint_ratdens(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
+ &line6pcm->properties->rates);
+ if (err < 0)
+ return err;
+
+ runtime->hw = line6pcm->properties->playback_hw;
+ return 0;
+}
+
+/* close playback callback */
+static int snd_line6_playback_close(struct snd_pcm_substream *substream)
+{
+ return 0;
+}
+
+/* playback operators */
+struct snd_pcm_ops snd_line6_playback_ops = {
+ .open = snd_line6_playback_open,
+ .close = snd_line6_playback_close,
+ .ioctl = snd_pcm_lib_ioctl,
+ .hw_params = snd_line6_hw_params,
+ .hw_free = snd_line6_hw_free,
+ .prepare = snd_line6_prepare,
+ .trigger = snd_line6_trigger,
+ .pointer = snd_line6_pointer,
+};
+
+int line6_create_audio_out_urbs(struct snd_line6_pcm *line6pcm)
+{
+ struct usb_line6 *line6 = line6pcm->line6;
+ int i;
+
+ /* create audio URBs and fill in constant values: */
+ for (i = 0; i < LINE6_ISO_BUFFERS; ++i) {
+ struct urb *urb;
+
+ /* URB for audio out: */
+ urb = line6pcm->out.urbs[i] =
+ usb_alloc_urb(LINE6_ISO_PACKETS, GFP_KERNEL);
+
+ if (urb == NULL)
+ return -ENOMEM;
+
+ urb->dev = line6->usbdev;
+ urb->pipe =
+ usb_sndisocpipe(line6->usbdev,
+ line6->properties->ep_audio_w &
+ USB_ENDPOINT_NUMBER_MASK);
+ urb->transfer_flags = URB_ISO_ASAP;
+ urb->start_frame = -1;
+ urb->number_of_packets = LINE6_ISO_PACKETS;
+ urb->interval = LINE6_ISO_INTERVAL;
+ urb->error_count = 0;
+ urb->complete = audio_out_callback;
+ }
+
+ return 0;
+}
diff --git a/sound/usb/line6/playback.h b/sound/usb/line6/playback.h
new file mode 100644
index 000000000..51fce29e8
--- /dev/null
+++ b/sound/usb/line6/playback.h
@@ -0,0 +1,35 @@
+/*
+ * Line 6 Linux USB driver
+ *
+ * Copyright (C) 2004-2010 Markus Grabner (grabner@icg.tugraz.at)
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, version 2.
+ *
+ */
+
+#ifndef PLAYBACK_H
+#define PLAYBACK_H
+
+#include <sound/pcm.h>
+
+#include "driver.h"
+
+/*
+ * When the TonePort is used with jack in full duplex mode and the outputs are
+ * not connected, the software monitor produces an ugly noise since everything
+ * written to the output buffer (i.e., the input signal) will be repeated in
+ * the next period (sounds like a delay effect). As a workaround, the output
+ * buffer is cleared after the data have been read, but there must be a better
+ * solution. Until one is found, this workaround can be used to fix the
+ * problem.
+ */
+#define USE_CLEAR_BUFFER_WORKAROUND 1
+
+extern struct snd_pcm_ops snd_line6_playback_ops;
+
+extern int line6_create_audio_out_urbs(struct snd_line6_pcm *line6pcm);
+extern int line6_submit_audio_out_all_urbs(struct snd_line6_pcm *line6pcm);
+
+#endif
diff --git a/sound/usb/line6/pod.c b/sound/usb/line6/pod.c
new file mode 100644
index 000000000..daf81d169
--- /dev/null
+++ b/sound/usb/line6/pod.c
@@ -0,0 +1,584 @@
+/*
+ * Line 6 Linux USB driver
+ *
+ * Copyright (C) 2004-2010 Markus Grabner (grabner@icg.tugraz.at)
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, version 2.
+ *
+ */
+
+#include <linux/slab.h>
+#include <linux/wait.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/usb.h>
+
+#include <sound/core.h>
+#include <sound/control.h>
+
+#include "capture.h"
+#include "driver.h"
+#include "playback.h"
+
+/*
+ Locate name in binary program dump
+*/
+#define POD_NAME_OFFSET 0
+#define POD_NAME_LENGTH 16
+
+/*
+ Other constants
+*/
+#define POD_CONTROL_SIZE 0x80
+#define POD_BUFSIZE_DUMPREQ 7
+#define POD_STARTUP_DELAY 1000
+
+/*
+ Stages of POD startup procedure
+*/
+enum {
+ POD_STARTUP_INIT = 1,
+ POD_STARTUP_VERSIONREQ,
+ POD_STARTUP_WORKQUEUE,
+ POD_STARTUP_SETUP,
+ POD_STARTUP_LAST = POD_STARTUP_SETUP - 1
+};
+
+enum {
+ LINE6_BASSPODXT,
+ LINE6_BASSPODXTLIVE,
+ LINE6_BASSPODXTPRO,
+ LINE6_POCKETPOD,
+ LINE6_PODXT,
+ LINE6_PODXTLIVE_POD,
+ LINE6_PODXTPRO,
+};
+
+struct usb_line6_pod {
+ /* Generic Line 6 USB data */
+ struct usb_line6 line6;
+
+ /* Instrument monitor level */
+ int monitor_level;
+
+ /* Timer for device initialization */
+ struct timer_list startup_timer;
+
+ /* Work handler for device initialization */
+ struct work_struct startup_work;
+
+ /* Current progress in startup procedure */
+ int startup_progress;
+
+ /* Serial number of device */
+ u32 serial_number;
+
+ /* Firmware version (x 100) */
+ int firmware_version;
+
+ /* Device ID */
+ int device_id;
+};
+
+#define POD_SYSEX_CODE 3
+#define POD_BYTES_PER_FRAME 6 /* 24bit audio (stereo) */
+
+/* *INDENT-OFF* */
+
+enum {
+ POD_SYSEX_SAVE = 0x24,
+ POD_SYSEX_SYSTEM = 0x56,
+ POD_SYSEX_SYSTEMREQ = 0x57,
+ /* POD_SYSEX_UPDATE = 0x6c, */ /* software update! */
+ POD_SYSEX_STORE = 0x71,
+ POD_SYSEX_FINISH = 0x72,
+ POD_SYSEX_DUMPMEM = 0x73,
+ POD_SYSEX_DUMP = 0x74,
+ POD_SYSEX_DUMPREQ = 0x75
+
+ /* dumps entire internal memory of PODxt Pro */
+ /* POD_SYSEX_DUMPMEM2 = 0x76 */
+};
+
+enum {
+ POD_MONITOR_LEVEL = 0x04,
+ POD_SYSTEM_INVALID = 0x10000
+};
+
+/* *INDENT-ON* */
+
+enum {
+ POD_DUMP_MEMORY = 2
+};
+
+enum {
+ POD_BUSY_READ,
+ POD_BUSY_WRITE,
+ POD_CHANNEL_DIRTY,
+ POD_SAVE_PRESSED,
+ POD_BUSY_MIDISEND
+};
+
+static struct snd_ratden pod_ratden = {
+ .num_min = 78125,
+ .num_max = 78125,
+ .num_step = 1,
+ .den = 2
+};
+
+static struct line6_pcm_properties pod_pcm_properties = {
+ .playback_hw = {
+ .info = (SNDRV_PCM_INFO_MMAP |
+ SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_BLOCK_TRANSFER |
+ SNDRV_PCM_INFO_MMAP_VALID |
+ SNDRV_PCM_INFO_PAUSE |
+ SNDRV_PCM_INFO_SYNC_START),
+ .formats = SNDRV_PCM_FMTBIT_S24_3LE,
+ .rates = SNDRV_PCM_RATE_KNOT,
+ .rate_min = 39062,
+ .rate_max = 39063,
+ .channels_min = 2,
+ .channels_max = 2,
+ .buffer_bytes_max = 60000,
+ .period_bytes_min = 64,
+ .period_bytes_max = 8192,
+ .periods_min = 1,
+ .periods_max = 1024},
+ .capture_hw = {
+ .info = (SNDRV_PCM_INFO_MMAP |
+ SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_BLOCK_TRANSFER |
+ SNDRV_PCM_INFO_MMAP_VALID |
+ SNDRV_PCM_INFO_SYNC_START),
+ .formats = SNDRV_PCM_FMTBIT_S24_3LE,
+ .rates = SNDRV_PCM_RATE_KNOT,
+ .rate_min = 39062,
+ .rate_max = 39063,
+ .channels_min = 2,
+ .channels_max = 2,
+ .buffer_bytes_max = 60000,
+ .period_bytes_min = 64,
+ .period_bytes_max = 8192,
+ .periods_min = 1,
+ .periods_max = 1024},
+ .rates = {
+ .nrats = 1,
+ .rats = &pod_ratden},
+ .bytes_per_frame = POD_BYTES_PER_FRAME
+};
+
+static const char pod_version_header[] = {
+ 0xf2, 0x7e, 0x7f, 0x06, 0x02
+};
+
+/* forward declarations: */
+static void pod_startup2(unsigned long data);
+static void pod_startup3(struct usb_line6_pod *pod);
+
+static char *pod_alloc_sysex_buffer(struct usb_line6_pod *pod, int code,
+ int size)
+{
+ return line6_alloc_sysex_buffer(&pod->line6, POD_SYSEX_CODE, code,
+ size);
+}
+
+/*
+ Process a completely received message.
+*/
+static void line6_pod_process_message(struct usb_line6 *line6)
+{
+ struct usb_line6_pod *pod = (struct usb_line6_pod *) line6;
+ const unsigned char *buf = pod->line6.buffer_message;
+
+ if (memcmp(buf, pod_version_header, sizeof(pod_version_header)) == 0) {
+ pod->firmware_version = buf[13] * 100 + buf[14] * 10 + buf[15];
+ pod->device_id = ((int)buf[8] << 16) | ((int)buf[9] << 8) |
+ (int) buf[10];
+ pod_startup3(pod);
+ return;
+ }
+
+ /* Only look for sysex messages from this device */
+ if (buf[0] != (LINE6_SYSEX_BEGIN | LINE6_CHANNEL_DEVICE) &&
+ buf[0] != (LINE6_SYSEX_BEGIN | LINE6_CHANNEL_UNKNOWN)) {
+ return;
+ }
+ if (memcmp(buf + 1, line6_midi_id, sizeof(line6_midi_id)) != 0)
+ return;
+
+ if (buf[5] == POD_SYSEX_SYSTEM && buf[6] == POD_MONITOR_LEVEL) {
+ short value = ((int)buf[7] << 12) | ((int)buf[8] << 8) |
+ ((int)buf[9] << 4) | (int)buf[10];
+ pod->monitor_level = value;
+ }
+}
+
+/*
+ Send system parameter (from integer).
+*/
+static int pod_set_system_param_int(struct usb_line6_pod *pod, int value,
+ int code)
+{
+ char *sysex;
+ static const int size = 5;
+
+ sysex = pod_alloc_sysex_buffer(pod, POD_SYSEX_SYSTEM, size);
+ if (!sysex)
+ return -ENOMEM;
+ sysex[SYSEX_DATA_OFS] = code;
+ sysex[SYSEX_DATA_OFS + 1] = (value >> 12) & 0x0f;
+ sysex[SYSEX_DATA_OFS + 2] = (value >> 8) & 0x0f;
+ sysex[SYSEX_DATA_OFS + 3] = (value >> 4) & 0x0f;
+ sysex[SYSEX_DATA_OFS + 4] = (value) & 0x0f;
+ line6_send_sysex_message(&pod->line6, sysex, size);
+ kfree(sysex);
+ return 0;
+}
+
+/*
+ "read" request on "serial_number" special file.
+*/
+static ssize_t serial_number_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct usb_interface *interface = to_usb_interface(dev);
+ struct usb_line6_pod *pod = usb_get_intfdata(interface);
+
+ return sprintf(buf, "%u\n", pod->serial_number);
+}
+
+/*
+ "read" request on "firmware_version" special file.
+*/
+static ssize_t firmware_version_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct usb_interface *interface = to_usb_interface(dev);
+ struct usb_line6_pod *pod = usb_get_intfdata(interface);
+
+ return sprintf(buf, "%d.%02d\n", pod->firmware_version / 100,
+ pod->firmware_version % 100);
+}
+
+/*
+ "read" request on "device_id" special file.
+*/
+static ssize_t device_id_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct usb_interface *interface = to_usb_interface(dev);
+ struct usb_line6_pod *pod = usb_get_intfdata(interface);
+
+ return sprintf(buf, "%d\n", pod->device_id);
+}
+
+/*
+ POD startup procedure.
+ This is a sequence of functions with special requirements (e.g., must
+ not run immediately after initialization, must not run in interrupt
+ context). After the last one has finished, the device is ready to use.
+*/
+
+static void pod_startup1(struct usb_line6_pod *pod)
+{
+ CHECK_STARTUP_PROGRESS(pod->startup_progress, POD_STARTUP_INIT);
+
+ /* delay startup procedure: */
+ line6_start_timer(&pod->startup_timer, POD_STARTUP_DELAY, pod_startup2,
+ (unsigned long)pod);
+}
+
+static void pod_startup2(unsigned long data)
+{
+ struct usb_line6_pod *pod = (struct usb_line6_pod *)data;
+ struct usb_line6 *line6 = &pod->line6;
+
+ CHECK_STARTUP_PROGRESS(pod->startup_progress, POD_STARTUP_VERSIONREQ);
+
+ /* request firmware version: */
+ line6_version_request_async(line6);
+}
+
+static void pod_startup3(struct usb_line6_pod *pod)
+{
+ CHECK_STARTUP_PROGRESS(pod->startup_progress, POD_STARTUP_WORKQUEUE);
+
+ /* schedule work for global work queue: */
+ schedule_work(&pod->startup_work);
+}
+
+static void pod_startup4(struct work_struct *work)
+{
+ struct usb_line6_pod *pod =
+ container_of(work, struct usb_line6_pod, startup_work);
+ struct usb_line6 *line6 = &pod->line6;
+
+ CHECK_STARTUP_PROGRESS(pod->startup_progress, POD_STARTUP_SETUP);
+
+ /* serial number: */
+ line6_read_serial_number(&pod->line6, &pod->serial_number);
+
+ /* ALSA audio interface: */
+ snd_card_register(line6->card);
+}
+
+/* POD special files: */
+static DEVICE_ATTR_RO(device_id);
+static DEVICE_ATTR_RO(firmware_version);
+static DEVICE_ATTR_RO(serial_number);
+
+static struct attribute *pod_dev_attrs[] = {
+ &dev_attr_device_id.attr,
+ &dev_attr_firmware_version.attr,
+ &dev_attr_serial_number.attr,
+ NULL
+};
+
+static const struct attribute_group pod_dev_attr_group = {
+ .name = "pod",
+ .attrs = pod_dev_attrs,
+};
+
+/* control info callback */
+static int snd_pod_control_monitor_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = 1;
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = 65535;
+ return 0;
+}
+
+/* control get callback */
+static int snd_pod_control_monitor_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_line6_pcm *line6pcm = snd_kcontrol_chip(kcontrol);
+ struct usb_line6_pod *pod = (struct usb_line6_pod *)line6pcm->line6;
+
+ ucontrol->value.integer.value[0] = pod->monitor_level;
+ return 0;
+}
+
+/* control put callback */
+static int snd_pod_control_monitor_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_line6_pcm *line6pcm = snd_kcontrol_chip(kcontrol);
+ struct usb_line6_pod *pod = (struct usb_line6_pod *)line6pcm->line6;
+
+ if (ucontrol->value.integer.value[0] == pod->monitor_level)
+ return 0;
+
+ pod->monitor_level = ucontrol->value.integer.value[0];
+ pod_set_system_param_int(pod, ucontrol->value.integer.value[0],
+ POD_MONITOR_LEVEL);
+ return 1;
+}
+
+/* control definition */
+static struct snd_kcontrol_new pod_control_monitor = {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Monitor Playback Volume",
+ .index = 0,
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+ .info = snd_pod_control_monitor_info,
+ .get = snd_pod_control_monitor_get,
+ .put = snd_pod_control_monitor_put
+};
+
+/*
+ POD device disconnected.
+*/
+static void line6_pod_disconnect(struct usb_line6 *line6)
+{
+ struct usb_line6_pod *pod = (struct usb_line6_pod *)line6;
+
+ del_timer_sync(&pod->startup_timer);
+ cancel_work_sync(&pod->startup_work);
+}
+
+/*
+ Try to init POD device.
+*/
+static int pod_init(struct usb_line6 *line6,
+ const struct usb_device_id *id)
+{
+ int err;
+ struct usb_line6_pod *pod = (struct usb_line6_pod *) line6;
+
+ line6->process_message = line6_pod_process_message;
+ line6->disconnect = line6_pod_disconnect;
+
+ init_timer(&pod->startup_timer);
+ INIT_WORK(&pod->startup_work, pod_startup4);
+
+ /* create sysfs entries: */
+ err = snd_card_add_dev_attr(line6->card, &pod_dev_attr_group);
+ if (err < 0)
+ return err;
+
+ /* initialize MIDI subsystem: */
+ err = line6_init_midi(line6);
+ if (err < 0)
+ return err;
+
+ /* initialize PCM subsystem: */
+ err = line6_init_pcm(line6, &pod_pcm_properties);
+ if (err < 0)
+ return err;
+
+ /* register monitor control: */
+ err = snd_ctl_add(line6->card,
+ snd_ctl_new1(&pod_control_monitor, line6->line6pcm));
+ if (err < 0)
+ return err;
+
+ /*
+ When the sound card is registered at this point, the PODxt Live
+ displays "Invalid Code Error 07", so we do it later in the event
+ handler.
+ */
+
+ if (pod->line6.properties->capabilities & LINE6_CAP_CONTROL) {
+ pod->monitor_level = POD_SYSTEM_INVALID;
+
+ /* initiate startup procedure: */
+ pod_startup1(pod);
+ }
+
+ return 0;
+}
+
+#define LINE6_DEVICE(prod) USB_DEVICE(0x0e41, prod)
+#define LINE6_IF_NUM(prod, n) USB_DEVICE_INTERFACE_NUMBER(0x0e41, prod, n)
+
+/* table of devices that work with this driver */
+static const struct usb_device_id pod_id_table[] = {
+ { LINE6_DEVICE(0x4250), .driver_info = LINE6_BASSPODXT },
+ { LINE6_DEVICE(0x4642), .driver_info = LINE6_BASSPODXTLIVE },
+ { LINE6_DEVICE(0x4252), .driver_info = LINE6_BASSPODXTPRO },
+ { LINE6_IF_NUM(0x5051, 1), .driver_info = LINE6_POCKETPOD },
+ { LINE6_DEVICE(0x5044), .driver_info = LINE6_PODXT },
+ { LINE6_IF_NUM(0x4650, 0), .driver_info = LINE6_PODXTLIVE_POD },
+ { LINE6_DEVICE(0x5050), .driver_info = LINE6_PODXTPRO },
+ {}
+};
+
+MODULE_DEVICE_TABLE(usb, pod_id_table);
+
+static const struct line6_properties pod_properties_table[] = {
+ [LINE6_BASSPODXT] = {
+ .id = "BassPODxt",
+ .name = "BassPODxt",
+ .capabilities = LINE6_CAP_CONTROL
+ | LINE6_CAP_PCM
+ | LINE6_CAP_HWMON,
+ .altsetting = 5,
+ .ep_ctrl_r = 0x84,
+ .ep_ctrl_w = 0x03,
+ .ep_audio_r = 0x82,
+ .ep_audio_w = 0x01,
+ },
+ [LINE6_BASSPODXTLIVE] = {
+ .id = "BassPODxtLive",
+ .name = "BassPODxt Live",
+ .capabilities = LINE6_CAP_CONTROL
+ | LINE6_CAP_PCM
+ | LINE6_CAP_HWMON,
+ .altsetting = 1,
+ .ep_ctrl_r = 0x84,
+ .ep_ctrl_w = 0x03,
+ .ep_audio_r = 0x82,
+ .ep_audio_w = 0x01,
+ },
+ [LINE6_BASSPODXTPRO] = {
+ .id = "BassPODxtPro",
+ .name = "BassPODxt Pro",
+ .capabilities = LINE6_CAP_CONTROL
+ | LINE6_CAP_PCM
+ | LINE6_CAP_HWMON,
+ .altsetting = 5,
+ .ep_ctrl_r = 0x84,
+ .ep_ctrl_w = 0x03,
+ .ep_audio_r = 0x82,
+ .ep_audio_w = 0x01,
+ },
+ [LINE6_POCKETPOD] = {
+ .id = "PocketPOD",
+ .name = "Pocket POD",
+ .capabilities = LINE6_CAP_CONTROL,
+ .altsetting = 0,
+ .ep_ctrl_r = 0x82,
+ .ep_ctrl_w = 0x02,
+ /* no audio channel */
+ },
+ [LINE6_PODXT] = {
+ .id = "PODxt",
+ .name = "PODxt",
+ .capabilities = LINE6_CAP_CONTROL
+ | LINE6_CAP_PCM
+ | LINE6_CAP_HWMON,
+ .altsetting = 5,
+ .ep_ctrl_r = 0x84,
+ .ep_ctrl_w = 0x03,
+ .ep_audio_r = 0x82,
+ .ep_audio_w = 0x01,
+ },
+ [LINE6_PODXTLIVE_POD] = {
+ .id = "PODxtLive",
+ .name = "PODxt Live",
+ .capabilities = LINE6_CAP_CONTROL
+ | LINE6_CAP_PCM
+ | LINE6_CAP_HWMON,
+ .altsetting = 1,
+ .ep_ctrl_r = 0x84,
+ .ep_ctrl_w = 0x03,
+ .ep_audio_r = 0x82,
+ .ep_audio_w = 0x01,
+ },
+ [LINE6_PODXTPRO] = {
+ .id = "PODxtPro",
+ .name = "PODxt Pro",
+ .capabilities = LINE6_CAP_CONTROL
+ | LINE6_CAP_PCM
+ | LINE6_CAP_HWMON,
+ .altsetting = 5,
+ .ep_ctrl_r = 0x84,
+ .ep_ctrl_w = 0x03,
+ .ep_audio_r = 0x82,
+ .ep_audio_w = 0x01,
+ },
+};
+
+/*
+ Probe USB device.
+*/
+static int pod_probe(struct usb_interface *interface,
+ const struct usb_device_id *id)
+{
+ return line6_probe(interface, id, "Line6-POD",
+ &pod_properties_table[id->driver_info],
+ pod_init, sizeof(struct usb_line6_pod));
+}
+
+static struct usb_driver pod_driver = {
+ .name = KBUILD_MODNAME,
+ .probe = pod_probe,
+ .disconnect = line6_disconnect,
+#ifdef CONFIG_PM
+ .suspend = line6_suspend,
+ .resume = line6_resume,
+ .reset_resume = line6_resume,
+#endif
+ .id_table = pod_id_table,
+};
+
+module_usb_driver(pod_driver);
+
+MODULE_DESCRIPTION("Line 6 POD USB driver");
+MODULE_LICENSE("GPL");
diff --git a/sound/usb/line6/podhd.c b/sound/usb/line6/podhd.c
new file mode 100644
index 000000000..63dcaef41
--- /dev/null
+++ b/sound/usb/line6/podhd.c
@@ -0,0 +1,192 @@
+/*
+ * Line 6 Pod HD
+ *
+ * Copyright (C) 2011 Stefan Hajnoczi <stefanha@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, version 2.
+ *
+ */
+
+#include <linux/usb.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+
+#include "driver.h"
+#include "pcm.h"
+
+enum {
+ LINE6_PODHD300,
+ LINE6_PODHD400,
+ LINE6_PODHD500_0,
+ LINE6_PODHD500_1,
+};
+
+#define PODHD_BYTES_PER_FRAME 6 /* 24bit audio (stereo) */
+
+static struct snd_ratden podhd_ratden = {
+ .num_min = 48000,
+ .num_max = 48000,
+ .num_step = 1,
+ .den = 1,
+};
+
+static struct line6_pcm_properties podhd_pcm_properties = {
+ .playback_hw = {
+ .info = (SNDRV_PCM_INFO_MMAP |
+ SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_BLOCK_TRANSFER |
+ SNDRV_PCM_INFO_MMAP_VALID |
+ SNDRV_PCM_INFO_PAUSE |
+ SNDRV_PCM_INFO_SYNC_START),
+ .formats = SNDRV_PCM_FMTBIT_S24_3LE,
+ .rates = SNDRV_PCM_RATE_48000,
+ .rate_min = 48000,
+ .rate_max = 48000,
+ .channels_min = 2,
+ .channels_max = 2,
+ .buffer_bytes_max = 60000,
+ .period_bytes_min = 64,
+ .period_bytes_max = 8192,
+ .periods_min = 1,
+ .periods_max = 1024},
+ .capture_hw = {
+ .info = (SNDRV_PCM_INFO_MMAP |
+ SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_BLOCK_TRANSFER |
+ SNDRV_PCM_INFO_MMAP_VALID |
+ SNDRV_PCM_INFO_SYNC_START),
+ .formats = SNDRV_PCM_FMTBIT_S24_3LE,
+ .rates = SNDRV_PCM_RATE_48000,
+ .rate_min = 48000,
+ .rate_max = 48000,
+ .channels_min = 2,
+ .channels_max = 2,
+ .buffer_bytes_max = 60000,
+ .period_bytes_min = 64,
+ .period_bytes_max = 8192,
+ .periods_min = 1,
+ .periods_max = 1024},
+ .rates = {
+ .nrats = 1,
+ .rats = &podhd_ratden},
+ .bytes_per_frame = PODHD_BYTES_PER_FRAME
+};
+
+/*
+ Try to init POD HD device.
+*/
+static int podhd_init(struct usb_line6 *line6,
+ const struct usb_device_id *id)
+{
+ int err;
+
+ /* initialize MIDI subsystem: */
+ err = line6_init_midi(line6);
+ if (err < 0)
+ return err;
+
+ /* initialize PCM subsystem: */
+ err = line6_init_pcm(line6, &podhd_pcm_properties);
+ if (err < 0)
+ return err;
+
+ /* register USB audio system: */
+ return snd_card_register(line6->card);
+}
+
+#define LINE6_DEVICE(prod) USB_DEVICE(0x0e41, prod)
+#define LINE6_IF_NUM(prod, n) USB_DEVICE_INTERFACE_NUMBER(0x0e41, prod, n)
+
+/* table of devices that work with this driver */
+static const struct usb_device_id podhd_id_table[] = {
+ { LINE6_DEVICE(0x5057), .driver_info = LINE6_PODHD300 },
+ { LINE6_DEVICE(0x5058), .driver_info = LINE6_PODHD400 },
+ { LINE6_IF_NUM(0x414D, 0), .driver_info = LINE6_PODHD500_0 },
+ { LINE6_IF_NUM(0x414D, 1), .driver_info = LINE6_PODHD500_1 },
+ {}
+};
+
+MODULE_DEVICE_TABLE(usb, podhd_id_table);
+
+static const struct line6_properties podhd_properties_table[] = {
+ [LINE6_PODHD300] = {
+ .id = "PODHD300",
+ .name = "POD HD300",
+ .capabilities = LINE6_CAP_CONTROL
+ | LINE6_CAP_PCM
+ | LINE6_CAP_HWMON,
+ .altsetting = 5,
+ .ep_ctrl_r = 0x84,
+ .ep_ctrl_w = 0x03,
+ .ep_audio_r = 0x82,
+ .ep_audio_w = 0x01,
+ },
+ [LINE6_PODHD400] = {
+ .id = "PODHD400",
+ .name = "POD HD400",
+ .capabilities = LINE6_CAP_CONTROL
+ | LINE6_CAP_PCM
+ | LINE6_CAP_HWMON,
+ .altsetting = 5,
+ .ep_ctrl_r = 0x84,
+ .ep_ctrl_w = 0x03,
+ .ep_audio_r = 0x82,
+ .ep_audio_w = 0x01,
+ },
+ [LINE6_PODHD500_0] = {
+ .id = "PODHD500",
+ .name = "POD HD500",
+ .capabilities = LINE6_CAP_CONTROL
+ | LINE6_CAP_PCM
+ | LINE6_CAP_HWMON,
+ .altsetting = 1,
+ .ep_ctrl_r = 0x81,
+ .ep_ctrl_w = 0x01,
+ .ep_audio_r = 0x86,
+ .ep_audio_w = 0x02,
+ },
+ [LINE6_PODHD500_1] = {
+ .id = "PODHD500",
+ .name = "POD HD500",
+ .capabilities = LINE6_CAP_CONTROL
+ | LINE6_CAP_PCM
+ | LINE6_CAP_HWMON,
+ .altsetting = 1,
+ .ep_ctrl_r = 0x81,
+ .ep_ctrl_w = 0x01,
+ .ep_audio_r = 0x86,
+ .ep_audio_w = 0x02,
+ },
+};
+
+/*
+ Probe USB device.
+*/
+static int podhd_probe(struct usb_interface *interface,
+ const struct usb_device_id *id)
+{
+ return line6_probe(interface, id, "Line6-PODHD",
+ &podhd_properties_table[id->driver_info],
+ podhd_init, sizeof(struct usb_line6));
+}
+
+static struct usb_driver podhd_driver = {
+ .name = KBUILD_MODNAME,
+ .probe = podhd_probe,
+ .disconnect = line6_disconnect,
+#ifdef CONFIG_PM
+ .suspend = line6_suspend,
+ .resume = line6_resume,
+ .reset_resume = line6_resume,
+#endif
+ .id_table = podhd_id_table,
+};
+
+module_usb_driver(podhd_driver);
+
+MODULE_DESCRIPTION("Line 6 PODHD USB driver");
+MODULE_LICENSE("GPL");
diff --git a/sound/usb/line6/toneport.c b/sound/usb/line6/toneport.c
new file mode 100644
index 000000000..6d4c50c9b
--- /dev/null
+++ b/sound/usb/line6/toneport.c
@@ -0,0 +1,580 @@
+/*
+ * Line 6 Linux USB driver
+ *
+ * Copyright (C) 2004-2010 Markus Grabner (grabner@icg.tugraz.at)
+ * Emil Myhrman (emil.myhrman@gmail.com)
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, version 2.
+ *
+ */
+
+#include <linux/wait.h>
+#include <linux/usb.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/leds.h>
+#include <sound/core.h>
+#include <sound/control.h>
+
+#include "capture.h"
+#include "driver.h"
+#include "playback.h"
+
+enum line6_device_type {
+ LINE6_GUITARPORT,
+ LINE6_PODSTUDIO_GX,
+ LINE6_PODSTUDIO_UX1,
+ LINE6_PODSTUDIO_UX2,
+ LINE6_TONEPORT_GX,
+ LINE6_TONEPORT_UX1,
+ LINE6_TONEPORT_UX2,
+};
+
+struct usb_line6_toneport;
+
+struct toneport_led {
+ struct led_classdev dev;
+ char name[64];
+ struct usb_line6_toneport *toneport;
+ bool registered;
+};
+
+struct usb_line6_toneport {
+ /* Generic Line 6 USB data */
+ struct usb_line6 line6;
+
+ /* Source selector */
+ int source;
+
+ /* Serial number of device */
+ u32 serial_number;
+
+ /* Firmware version (x 100) */
+ u8 firmware_version;
+
+ /* Timer for delayed PCM startup */
+ struct timer_list timer;
+
+ /* Device type */
+ enum line6_device_type type;
+
+ /* LED instances */
+ struct toneport_led leds[2];
+};
+
+static int toneport_send_cmd(struct usb_device *usbdev, int cmd1, int cmd2);
+
+#define TONEPORT_PCM_DELAY 1
+
+static struct snd_ratden toneport_ratden = {
+ .num_min = 44100,
+ .num_max = 44100,
+ .num_step = 1,
+ .den = 1
+};
+
+static struct line6_pcm_properties toneport_pcm_properties = {
+ .playback_hw = {
+ .info = (SNDRV_PCM_INFO_MMAP |
+ SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_BLOCK_TRANSFER |
+ SNDRV_PCM_INFO_MMAP_VALID |
+ SNDRV_PCM_INFO_PAUSE |
+ SNDRV_PCM_INFO_SYNC_START),
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ .rates = SNDRV_PCM_RATE_KNOT,
+ .rate_min = 44100,
+ .rate_max = 44100,
+ .channels_min = 2,
+ .channels_max = 2,
+ .buffer_bytes_max = 60000,
+ .period_bytes_min = 64,
+ .period_bytes_max = 8192,
+ .periods_min = 1,
+ .periods_max = 1024},
+ .capture_hw = {
+ .info = (SNDRV_PCM_INFO_MMAP |
+ SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_BLOCK_TRANSFER |
+ SNDRV_PCM_INFO_MMAP_VALID |
+ SNDRV_PCM_INFO_SYNC_START),
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ .rates = SNDRV_PCM_RATE_KNOT,
+ .rate_min = 44100,
+ .rate_max = 44100,
+ .channels_min = 2,
+ .channels_max = 2,
+ .buffer_bytes_max = 60000,
+ .period_bytes_min = 64,
+ .period_bytes_max = 8192,
+ .periods_min = 1,
+ .periods_max = 1024},
+ .rates = {
+ .nrats = 1,
+ .rats = &toneport_ratden},
+ .bytes_per_frame = 4
+};
+
+static const struct {
+ const char *name;
+ int code;
+} toneport_source_info[] = {
+ {"Microphone", 0x0a01},
+ {"Line", 0x0801},
+ {"Instrument", 0x0b01},
+ {"Inst & Mic", 0x0901}
+};
+
+static int toneport_send_cmd(struct usb_device *usbdev, int cmd1, int cmd2)
+{
+ int ret;
+
+ ret = usb_control_msg(usbdev, usb_sndctrlpipe(usbdev, 0), 0x67,
+ USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_OUT,
+ cmd1, cmd2, NULL, 0, LINE6_TIMEOUT * HZ);
+
+ if (ret < 0) {
+ dev_err(&usbdev->dev, "send failed (error %d)\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+/* monitor info callback */
+static int snd_toneport_monitor_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = 1;
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = 256;
+ return 0;
+}
+
+/* monitor get callback */
+static int snd_toneport_monitor_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_line6_pcm *line6pcm = snd_kcontrol_chip(kcontrol);
+
+ ucontrol->value.integer.value[0] = line6pcm->volume_monitor;
+ return 0;
+}
+
+/* monitor put callback */
+static int snd_toneport_monitor_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_line6_pcm *line6pcm = snd_kcontrol_chip(kcontrol);
+ int err;
+
+ if (ucontrol->value.integer.value[0] == line6pcm->volume_monitor)
+ return 0;
+
+ line6pcm->volume_monitor = ucontrol->value.integer.value[0];
+
+ if (line6pcm->volume_monitor > 0) {
+ err = line6_pcm_acquire(line6pcm, LINE6_STREAM_MONITOR);
+ if (err < 0) {
+ line6pcm->volume_monitor = 0;
+ line6_pcm_release(line6pcm, LINE6_STREAM_MONITOR);
+ return err;
+ }
+ } else {
+ line6_pcm_release(line6pcm, LINE6_STREAM_MONITOR);
+ }
+
+ return 1;
+}
+
+/* source info callback */
+static int snd_toneport_source_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ const int size = ARRAY_SIZE(toneport_source_info);
+
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+ uinfo->count = 1;
+ uinfo->value.enumerated.items = size;
+
+ if (uinfo->value.enumerated.item >= size)
+ uinfo->value.enumerated.item = size - 1;
+
+ strcpy(uinfo->value.enumerated.name,
+ toneport_source_info[uinfo->value.enumerated.item].name);
+
+ return 0;
+}
+
+/* source get callback */
+static int snd_toneport_source_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_line6_pcm *line6pcm = snd_kcontrol_chip(kcontrol);
+ struct usb_line6_toneport *toneport =
+ (struct usb_line6_toneport *)line6pcm->line6;
+ ucontrol->value.enumerated.item[0] = toneport->source;
+ return 0;
+}
+
+/* source put callback */
+static int snd_toneport_source_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_line6_pcm *line6pcm = snd_kcontrol_chip(kcontrol);
+ struct usb_line6_toneport *toneport =
+ (struct usb_line6_toneport *)line6pcm->line6;
+ unsigned int source;
+
+ source = ucontrol->value.enumerated.item[0];
+ if (source >= ARRAY_SIZE(toneport_source_info))
+ return -EINVAL;
+ if (source == toneport->source)
+ return 0;
+
+ toneport->source = source;
+ toneport_send_cmd(toneport->line6.usbdev,
+ toneport_source_info[source].code, 0x0000);
+ return 1;
+}
+
+static void toneport_start_pcm(unsigned long arg)
+{
+ struct usb_line6_toneport *toneport = (struct usb_line6_toneport *)arg;
+ struct usb_line6 *line6 = &toneport->line6;
+
+ line6_pcm_acquire(line6->line6pcm, LINE6_STREAM_MONITOR);
+}
+
+/* control definition */
+static struct snd_kcontrol_new toneport_control_monitor = {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Monitor Playback Volume",
+ .index = 0,
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+ .info = snd_toneport_monitor_info,
+ .get = snd_toneport_monitor_get,
+ .put = snd_toneport_monitor_put
+};
+
+/* source selector definition */
+static struct snd_kcontrol_new toneport_control_source = {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "PCM Capture Source",
+ .index = 0,
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+ .info = snd_toneport_source_info,
+ .get = snd_toneport_source_get,
+ .put = snd_toneport_source_put
+};
+
+/*
+ For the led on Guitarport.
+ Brightness goes from 0x00 to 0x26. Set a value above this to have led
+ blink.
+ (void cmd_0x02(byte red, byte green)
+*/
+
+static bool toneport_has_led(struct usb_line6_toneport *toneport)
+{
+ switch (toneport->type) {
+ case LINE6_GUITARPORT:
+ case LINE6_TONEPORT_GX:
+ /* add your device here if you are missing support for the LEDs */
+ return true;
+
+ default:
+ return false;
+ }
+}
+
+static const char * const led_colors[2] = { "red", "green" };
+static const int led_init_vals[2] = { 0x00, 0x26 };
+
+static void toneport_update_led(struct usb_line6_toneport *toneport)
+{
+ toneport_send_cmd(toneport->line6.usbdev,
+ (toneport->leds[0].dev.brightness << 8) | 0x0002,
+ toneport->leds[1].dev.brightness);
+}
+
+static void toneport_led_brightness_set(struct led_classdev *led_cdev,
+ enum led_brightness brightness)
+{
+ struct toneport_led *leds =
+ container_of(led_cdev, struct toneport_led, dev);
+ toneport_update_led(leds->toneport);
+}
+
+static int toneport_init_leds(struct usb_line6_toneport *toneport)
+{
+ struct device *dev = &toneport->line6.usbdev->dev;
+ int i, err;
+
+ for (i = 0; i < 2; i++) {
+ struct toneport_led *led = &toneport->leds[i];
+ struct led_classdev *leddev = &led->dev;
+
+ led->toneport = toneport;
+ snprintf(led->name, sizeof(led->name), "%s::%s",
+ dev_name(dev), led_colors[i]);
+ leddev->name = led->name;
+ leddev->brightness = led_init_vals[i];
+ leddev->max_brightness = 0x26;
+ leddev->brightness_set = toneport_led_brightness_set;
+ err = led_classdev_register(dev, leddev);
+ if (err)
+ return err;
+ led->registered = true;
+ }
+
+ return 0;
+}
+
+static void toneport_remove_leds(struct usb_line6_toneport *toneport)
+{
+ struct toneport_led *led;
+ int i;
+
+ for (i = 0; i < 2; i++) {
+ led = &toneport->leds[i];
+ if (!led->registered)
+ break;
+ led_classdev_unregister(&led->dev);
+ led->registered = false;
+ }
+}
+
+static bool toneport_has_source_select(struct usb_line6_toneport *toneport)
+{
+ switch (toneport->type) {
+ case LINE6_TONEPORT_UX1:
+ case LINE6_TONEPORT_UX2:
+ case LINE6_PODSTUDIO_UX1:
+ case LINE6_PODSTUDIO_UX2:
+ return true;
+
+ default:
+ return false;
+ }
+}
+
+/*
+ Setup Toneport device.
+*/
+static void toneport_setup(struct usb_line6_toneport *toneport)
+{
+ int ticks;
+ struct usb_line6 *line6 = &toneport->line6;
+ struct usb_device *usbdev = line6->usbdev;
+
+ /* sync time on device with host: */
+ ticks = (int)get_seconds();
+ line6_write_data(line6, 0x80c6, &ticks, 4);
+
+ /* enable device: */
+ toneport_send_cmd(usbdev, 0x0301, 0x0000);
+
+ /* initialize source select: */
+ if (toneport_has_source_select(toneport))
+ toneport_send_cmd(usbdev,
+ toneport_source_info[toneport->source].code,
+ 0x0000);
+
+ if (toneport_has_led(toneport))
+ toneport_update_led(toneport);
+
+ mod_timer(&toneport->timer, jiffies + TONEPORT_PCM_DELAY * HZ);
+}
+
+/*
+ Toneport device disconnected.
+*/
+static void line6_toneport_disconnect(struct usb_line6 *line6)
+{
+ struct usb_line6_toneport *toneport =
+ (struct usb_line6_toneport *)line6;
+
+ del_timer_sync(&toneport->timer);
+
+ if (toneport_has_led(toneport))
+ toneport_remove_leds(toneport);
+}
+
+
+/*
+ Try to init Toneport device.
+*/
+static int toneport_init(struct usb_line6 *line6,
+ const struct usb_device_id *id)
+{
+ int err;
+ struct usb_line6_toneport *toneport = (struct usb_line6_toneport *) line6;
+
+ toneport->type = id->driver_info;
+ setup_timer(&toneport->timer, toneport_start_pcm,
+ (unsigned long)toneport);
+
+ line6->disconnect = line6_toneport_disconnect;
+
+ /* initialize PCM subsystem: */
+ err = line6_init_pcm(line6, &toneport_pcm_properties);
+ if (err < 0)
+ return err;
+
+ /* register monitor control: */
+ err = snd_ctl_add(line6->card,
+ snd_ctl_new1(&toneport_control_monitor,
+ line6->line6pcm));
+ if (err < 0)
+ return err;
+
+ /* register source select control: */
+ if (toneport_has_source_select(toneport)) {
+ err =
+ snd_ctl_add(line6->card,
+ snd_ctl_new1(&toneport_control_source,
+ line6->line6pcm));
+ if (err < 0)
+ return err;
+ }
+
+ line6_read_serial_number(line6, &toneport->serial_number);
+ line6_read_data(line6, 0x80c2, &toneport->firmware_version, 1);
+
+ if (toneport_has_led(toneport)) {
+ err = toneport_init_leds(toneport);
+ if (err < 0)
+ return err;
+ }
+
+ toneport_setup(toneport);
+
+ /* register audio system: */
+ return snd_card_register(line6->card);
+}
+
+#ifdef CONFIG_PM
+/*
+ Resume Toneport device after reset.
+*/
+static int toneport_reset_resume(struct usb_interface *interface)
+{
+ toneport_setup(usb_get_intfdata(interface));
+ return line6_resume(interface);
+}
+#endif
+
+#define LINE6_DEVICE(prod) USB_DEVICE(0x0e41, prod)
+#define LINE6_IF_NUM(prod, n) USB_DEVICE_INTERFACE_NUMBER(0x0e41, prod, n)
+
+/* table of devices that work with this driver */
+static const struct usb_device_id toneport_id_table[] = {
+ { LINE6_DEVICE(0x4750), .driver_info = LINE6_GUITARPORT },
+ { LINE6_DEVICE(0x4153), .driver_info = LINE6_PODSTUDIO_GX },
+ { LINE6_DEVICE(0x4150), .driver_info = LINE6_PODSTUDIO_UX1 },
+ { LINE6_IF_NUM(0x4151, 0), .driver_info = LINE6_PODSTUDIO_UX2 },
+ { LINE6_DEVICE(0x4147), .driver_info = LINE6_TONEPORT_GX },
+ { LINE6_DEVICE(0x4141), .driver_info = LINE6_TONEPORT_UX1 },
+ { LINE6_IF_NUM(0x4142, 0), .driver_info = LINE6_TONEPORT_UX2 },
+ {}
+};
+
+MODULE_DEVICE_TABLE(usb, toneport_id_table);
+
+static const struct line6_properties toneport_properties_table[] = {
+ [LINE6_GUITARPORT] = {
+ .id = "GuitarPort",
+ .name = "GuitarPort",
+ .capabilities = LINE6_CAP_PCM,
+ .altsetting = 2, /* 1..4 seem to be ok */
+ /* no control channel */
+ .ep_audio_r = 0x82,
+ .ep_audio_w = 0x01,
+ },
+ [LINE6_PODSTUDIO_GX] = {
+ .id = "PODStudioGX",
+ .name = "POD Studio GX",
+ .capabilities = LINE6_CAP_PCM,
+ .altsetting = 2, /* 1..4 seem to be ok */
+ /* no control channel */
+ .ep_audio_r = 0x82,
+ .ep_audio_w = 0x01,
+ },
+ [LINE6_PODSTUDIO_UX1] = {
+ .id = "PODStudioUX1",
+ .name = "POD Studio UX1",
+ .capabilities = LINE6_CAP_PCM,
+ .altsetting = 2, /* 1..4 seem to be ok */
+ /* no control channel */
+ .ep_audio_r = 0x82,
+ .ep_audio_w = 0x01,
+ },
+ [LINE6_PODSTUDIO_UX2] = {
+ .id = "PODStudioUX2",
+ .name = "POD Studio UX2",
+ .capabilities = LINE6_CAP_PCM,
+ .altsetting = 2, /* defaults to 44.1kHz, 16-bit */
+ /* no control channel */
+ .ep_audio_r = 0x82,
+ .ep_audio_w = 0x01,
+ },
+ [LINE6_TONEPORT_GX] = {
+ .id = "TonePortGX",
+ .name = "TonePort GX",
+ .capabilities = LINE6_CAP_PCM,
+ .altsetting = 2, /* 1..4 seem to be ok */
+ /* no control channel */
+ .ep_audio_r = 0x82,
+ .ep_audio_w = 0x01,
+ },
+ [LINE6_TONEPORT_UX1] = {
+ .id = "TonePortUX1",
+ .name = "TonePort UX1",
+ .capabilities = LINE6_CAP_PCM,
+ .altsetting = 2, /* 1..4 seem to be ok */
+ /* no control channel */
+ .ep_audio_r = 0x82,
+ .ep_audio_w = 0x01,
+ },
+ [LINE6_TONEPORT_UX2] = {
+ .id = "TonePortUX2",
+ .name = "TonePort UX2",
+ .capabilities = LINE6_CAP_PCM,
+ .altsetting = 2, /* defaults to 44.1kHz, 16-bit */
+ /* no control channel */
+ .ep_audio_r = 0x82,
+ .ep_audio_w = 0x01,
+ },
+};
+
+/*
+ Probe USB device.
+*/
+static int toneport_probe(struct usb_interface *interface,
+ const struct usb_device_id *id)
+{
+ return line6_probe(interface, id, "Line6-TonePort",
+ &toneport_properties_table[id->driver_info],
+ toneport_init, sizeof(struct usb_line6_toneport));
+}
+
+static struct usb_driver toneport_driver = {
+ .name = KBUILD_MODNAME,
+ .probe = toneport_probe,
+ .disconnect = line6_disconnect,
+#ifdef CONFIG_PM
+ .suspend = line6_suspend,
+ .resume = line6_resume,
+ .reset_resume = toneport_reset_resume,
+#endif
+ .id_table = toneport_id_table,
+};
+
+module_usb_driver(toneport_driver);
+
+MODULE_DESCRIPTION("TonePort USB driver");
+MODULE_LICENSE("GPL");
diff --git a/sound/usb/line6/variax.c b/sound/usb/line6/variax.c
new file mode 100644
index 000000000..ddc23ddf0
--- /dev/null
+++ b/sound/usb/line6/variax.c
@@ -0,0 +1,306 @@
+/*
+ * Line 6 Linux USB driver
+ *
+ * Copyright (C) 2004-2010 Markus Grabner (grabner@icg.tugraz.at)
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, version 2.
+ *
+ */
+
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <linux/usb.h>
+#include <linux/wait.h>
+#include <linux/module.h>
+#include <sound/core.h>
+
+#include "driver.h"
+
+#define VARIAX_STARTUP_DELAY1 1000
+#define VARIAX_STARTUP_DELAY3 100
+#define VARIAX_STARTUP_DELAY4 100
+
+/*
+ Stages of Variax startup procedure
+*/
+enum {
+ VARIAX_STARTUP_INIT = 1,
+ VARIAX_STARTUP_VERSIONREQ,
+ VARIAX_STARTUP_WAIT,
+ VARIAX_STARTUP_ACTIVATE,
+ VARIAX_STARTUP_WORKQUEUE,
+ VARIAX_STARTUP_SETUP,
+ VARIAX_STARTUP_LAST = VARIAX_STARTUP_SETUP - 1
+};
+
+enum {
+ LINE6_PODXTLIVE_VARIAX,
+ LINE6_VARIAX
+};
+
+struct usb_line6_variax {
+ /* Generic Line 6 USB data */
+ struct usb_line6 line6;
+
+ /* Buffer for activation code */
+ unsigned char *buffer_activate;
+
+ /* Handler for device initialization */
+ struct work_struct startup_work;
+
+ /* Timers for device initialization */
+ struct timer_list startup_timer1;
+ struct timer_list startup_timer2;
+
+ /* Current progress in startup procedure */
+ int startup_progress;
+};
+
+#define VARIAX_OFFSET_ACTIVATE 7
+
+/*
+ This message is sent by the device during initialization and identifies
+ the connected guitar version.
+*/
+static const char variax_init_version[] = {
+ 0xf0, 0x7e, 0x7f, 0x06, 0x02, 0x00, 0x01, 0x0c,
+ 0x07, 0x00, 0x00, 0x00
+};
+
+/*
+ This message is the last one sent by the device during initialization.
+*/
+static const char variax_init_done[] = {
+ 0xf0, 0x00, 0x01, 0x0c, 0x07, 0x00, 0x6b
+};
+
+static const char variax_activate[] = {
+ 0xf0, 0x00, 0x01, 0x0c, 0x07, 0x00, 0x2a, 0x01,
+ 0xf7
+};
+
+/* forward declarations: */
+static void variax_startup2(unsigned long data);
+static void variax_startup4(unsigned long data);
+static void variax_startup5(unsigned long data);
+
+static void variax_activate_async(struct usb_line6_variax *variax, int a)
+{
+ variax->buffer_activate[VARIAX_OFFSET_ACTIVATE] = a;
+ line6_send_raw_message_async(&variax->line6, variax->buffer_activate,
+ sizeof(variax_activate));
+}
+
+/*
+ Variax startup procedure.
+ This is a sequence of functions with special requirements (e.g., must
+ not run immediately after initialization, must not run in interrupt
+ context). After the last one has finished, the device is ready to use.
+*/
+
+static void variax_startup1(struct usb_line6_variax *variax)
+{
+ CHECK_STARTUP_PROGRESS(variax->startup_progress, VARIAX_STARTUP_INIT);
+
+ /* delay startup procedure: */
+ line6_start_timer(&variax->startup_timer1, VARIAX_STARTUP_DELAY1,
+ variax_startup2, (unsigned long)variax);
+}
+
+static void variax_startup2(unsigned long data)
+{
+ struct usb_line6_variax *variax = (struct usb_line6_variax *)data;
+ struct usb_line6 *line6 = &variax->line6;
+
+ /* schedule another startup procedure until startup is complete: */
+ if (variax->startup_progress >= VARIAX_STARTUP_LAST)
+ return;
+
+ variax->startup_progress = VARIAX_STARTUP_VERSIONREQ;
+ line6_start_timer(&variax->startup_timer1, VARIAX_STARTUP_DELAY1,
+ variax_startup2, (unsigned long)variax);
+
+ /* request firmware version: */
+ line6_version_request_async(line6);
+}
+
+static void variax_startup3(struct usb_line6_variax *variax)
+{
+ CHECK_STARTUP_PROGRESS(variax->startup_progress, VARIAX_STARTUP_WAIT);
+
+ /* delay startup procedure: */
+ line6_start_timer(&variax->startup_timer2, VARIAX_STARTUP_DELAY3,
+ variax_startup4, (unsigned long)variax);
+}
+
+static void variax_startup4(unsigned long data)
+{
+ struct usb_line6_variax *variax = (struct usb_line6_variax *)data;
+
+ CHECK_STARTUP_PROGRESS(variax->startup_progress,
+ VARIAX_STARTUP_ACTIVATE);
+
+ /* activate device: */
+ variax_activate_async(variax, 1);
+ line6_start_timer(&variax->startup_timer2, VARIAX_STARTUP_DELAY4,
+ variax_startup5, (unsigned long)variax);
+}
+
+static void variax_startup5(unsigned long data)
+{
+ struct usb_line6_variax *variax = (struct usb_line6_variax *)data;
+
+ CHECK_STARTUP_PROGRESS(variax->startup_progress,
+ VARIAX_STARTUP_WORKQUEUE);
+
+ /* schedule work for global work queue: */
+ schedule_work(&variax->startup_work);
+}
+
+static void variax_startup6(struct work_struct *work)
+{
+ struct usb_line6_variax *variax =
+ container_of(work, struct usb_line6_variax, startup_work);
+
+ CHECK_STARTUP_PROGRESS(variax->startup_progress, VARIAX_STARTUP_SETUP);
+
+ /* ALSA audio interface: */
+ snd_card_register(variax->line6.card);
+}
+
+/*
+ Process a completely received message.
+*/
+static void line6_variax_process_message(struct usb_line6 *line6)
+{
+ struct usb_line6_variax *variax = (struct usb_line6_variax *) line6;
+ const unsigned char *buf = variax->line6.buffer_message;
+
+ switch (buf[0]) {
+ case LINE6_RESET:
+ dev_info(variax->line6.ifcdev, "VARIAX reset\n");
+ break;
+
+ case LINE6_SYSEX_BEGIN:
+ if (memcmp(buf + 1, variax_init_version + 1,
+ sizeof(variax_init_version) - 1) == 0) {
+ variax_startup3(variax);
+ } else if (memcmp(buf + 1, variax_init_done + 1,
+ sizeof(variax_init_done) - 1) == 0) {
+ /* notify of complete initialization: */
+ variax_startup4((unsigned long)variax);
+ }
+ break;
+ }
+}
+
+/*
+ Variax destructor.
+*/
+static void line6_variax_disconnect(struct usb_line6 *line6)
+{
+ struct usb_line6_variax *variax = (struct usb_line6_variax *)line6;
+
+ del_timer(&variax->startup_timer1);
+ del_timer(&variax->startup_timer2);
+ cancel_work_sync(&variax->startup_work);
+
+ kfree(variax->buffer_activate);
+}
+
+/*
+ Try to init workbench device.
+*/
+static int variax_init(struct usb_line6 *line6,
+ const struct usb_device_id *id)
+{
+ struct usb_line6_variax *variax = (struct usb_line6_variax *) line6;
+ int err;
+
+ line6->process_message = line6_variax_process_message;
+ line6->disconnect = line6_variax_disconnect;
+
+ init_timer(&variax->startup_timer1);
+ init_timer(&variax->startup_timer2);
+ INIT_WORK(&variax->startup_work, variax_startup6);
+
+ /* initialize USB buffers: */
+ variax->buffer_activate = kmemdup(variax_activate,
+ sizeof(variax_activate), GFP_KERNEL);
+
+ if (variax->buffer_activate == NULL)
+ return -ENOMEM;
+
+ /* initialize MIDI subsystem: */
+ err = line6_init_midi(&variax->line6);
+ if (err < 0)
+ return err;
+
+ /* initiate startup procedure: */
+ variax_startup1(variax);
+ return 0;
+}
+
+#define LINE6_DEVICE(prod) USB_DEVICE(0x0e41, prod)
+#define LINE6_IF_NUM(prod, n) USB_DEVICE_INTERFACE_NUMBER(0x0e41, prod, n)
+
+/* table of devices that work with this driver */
+static const struct usb_device_id variax_id_table[] = {
+ { LINE6_IF_NUM(0x4650, 1), .driver_info = LINE6_PODXTLIVE_VARIAX },
+ { LINE6_DEVICE(0x534d), .driver_info = LINE6_VARIAX },
+ {}
+};
+
+MODULE_DEVICE_TABLE(usb, variax_id_table);
+
+static const struct line6_properties variax_properties_table[] = {
+ [LINE6_PODXTLIVE_VARIAX] = {
+ .id = "PODxtLive",
+ .name = "PODxt Live",
+ .capabilities = LINE6_CAP_CONTROL,
+ .altsetting = 1,
+ .ep_ctrl_r = 0x86,
+ .ep_ctrl_w = 0x05,
+ .ep_audio_r = 0x82,
+ .ep_audio_w = 0x01,
+ },
+ [LINE6_VARIAX] = {
+ .id = "Variax",
+ .name = "Variax Workbench",
+ .capabilities = LINE6_CAP_CONTROL,
+ .altsetting = 1,
+ .ep_ctrl_r = 0x82,
+ .ep_ctrl_w = 0x01,
+ /* no audio channel */
+ }
+};
+
+/*
+ Probe USB device.
+*/
+static int variax_probe(struct usb_interface *interface,
+ const struct usb_device_id *id)
+{
+ return line6_probe(interface, id, "Line6-Variax",
+ &variax_properties_table[id->driver_info],
+ variax_init, sizeof(struct usb_line6_variax));
+}
+
+static struct usb_driver variax_driver = {
+ .name = KBUILD_MODNAME,
+ .probe = variax_probe,
+ .disconnect = line6_disconnect,
+#ifdef CONFIG_PM
+ .suspend = line6_suspend,
+ .resume = line6_resume,
+ .reset_resume = line6_resume,
+#endif
+ .id_table = variax_id_table,
+};
+
+module_usb_driver(variax_driver);
+
+MODULE_DESCRIPTION("Vairax Workbench USB driver");
+MODULE_LICENSE("GPL");
diff --git a/sound/usb/midi.c b/sound/usb/midi.c
new file mode 100644
index 000000000..417ebb11c
--- /dev/null
+++ b/sound/usb/midi.c
@@ -0,0 +1,2417 @@
+/*
+ * usbmidi.c - ALSA USB MIDI driver
+ *
+ * Copyright (c) 2002-2009 Clemens Ladisch
+ * All rights reserved.
+ *
+ * Based on the OSS usb-midi driver by NAGANO Daisuke,
+ * NetBSD's umidi driver by Takuya SHIOZAKI,
+ * the "USB Device Class Definition for MIDI Devices" by Roland
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions, and the following disclaimer,
+ * without modification.
+ * 2. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * Alternatively, this software may be distributed and/or modified under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <linux/kernel.h>
+#include <linux/types.h>
+#include <linux/bitops.h>
+#include <linux/interrupt.h>
+#include <linux/spinlock.h>
+#include <linux/string.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/timer.h>
+#include <linux/usb.h>
+#include <linux/wait.h>
+#include <linux/usb/audio.h>
+#include <linux/module.h>
+
+#include <sound/core.h>
+#include <sound/control.h>
+#include <sound/rawmidi.h>
+#include <sound/asequencer.h>
+#include "usbaudio.h"
+#include "midi.h"
+#include "power.h"
+#include "helper.h"
+
+/*
+ * define this to log all USB packets
+ */
+/* #define DUMP_PACKETS */
+
+/*
+ * how long to wait after some USB errors, so that hub_wq can disconnect() us
+ * without too many spurious errors
+ */
+#define ERROR_DELAY_JIFFIES (HZ / 10)
+
+#define OUTPUT_URBS 7
+#define INPUT_URBS 7
+
+
+MODULE_AUTHOR("Clemens Ladisch <clemens@ladisch.de>");
+MODULE_DESCRIPTION("USB Audio/MIDI helper module");
+MODULE_LICENSE("Dual BSD/GPL");
+
+
+struct usb_ms_header_descriptor {
+ __u8 bLength;
+ __u8 bDescriptorType;
+ __u8 bDescriptorSubtype;
+ __u8 bcdMSC[2];
+ __le16 wTotalLength;
+} __attribute__ ((packed));
+
+struct usb_ms_endpoint_descriptor {
+ __u8 bLength;
+ __u8 bDescriptorType;
+ __u8 bDescriptorSubtype;
+ __u8 bNumEmbMIDIJack;
+ __u8 baAssocJackID[0];
+} __attribute__ ((packed));
+
+struct snd_usb_midi_in_endpoint;
+struct snd_usb_midi_out_endpoint;
+struct snd_usb_midi_endpoint;
+
+struct usb_protocol_ops {
+ void (*input)(struct snd_usb_midi_in_endpoint*, uint8_t*, int);
+ void (*output)(struct snd_usb_midi_out_endpoint *ep, struct urb *urb);
+ void (*output_packet)(struct urb*, uint8_t, uint8_t, uint8_t, uint8_t);
+ void (*init_out_endpoint)(struct snd_usb_midi_out_endpoint *);
+ void (*finish_out_endpoint)(struct snd_usb_midi_out_endpoint *);
+};
+
+struct snd_usb_midi {
+ struct usb_device *dev;
+ struct snd_card *card;
+ struct usb_interface *iface;
+ const struct snd_usb_audio_quirk *quirk;
+ struct snd_rawmidi *rmidi;
+ struct usb_protocol_ops *usb_protocol_ops;
+ struct list_head list;
+ struct timer_list error_timer;
+ spinlock_t disc_lock;
+ struct rw_semaphore disc_rwsem;
+ struct mutex mutex;
+ u32 usb_id;
+ int next_midi_device;
+
+ struct snd_usb_midi_endpoint {
+ struct snd_usb_midi_out_endpoint *out;
+ struct snd_usb_midi_in_endpoint *in;
+ } endpoints[MIDI_MAX_ENDPOINTS];
+ unsigned long input_triggered;
+ unsigned int opened[2];
+ unsigned char disconnected;
+ unsigned char input_running;
+
+ struct snd_kcontrol *roland_load_ctl;
+};
+
+struct snd_usb_midi_out_endpoint {
+ struct snd_usb_midi *umidi;
+ struct out_urb_context {
+ struct urb *urb;
+ struct snd_usb_midi_out_endpoint *ep;
+ } urbs[OUTPUT_URBS];
+ unsigned int active_urbs;
+ unsigned int drain_urbs;
+ int max_transfer; /* size of urb buffer */
+ struct tasklet_struct tasklet;
+ unsigned int next_urb;
+ spinlock_t buffer_lock;
+
+ struct usbmidi_out_port {
+ struct snd_usb_midi_out_endpoint *ep;
+ struct snd_rawmidi_substream *substream;
+ int active;
+ uint8_t cable; /* cable number << 4 */
+ uint8_t state;
+#define STATE_UNKNOWN 0
+#define STATE_1PARAM 1
+#define STATE_2PARAM_1 2
+#define STATE_2PARAM_2 3
+#define STATE_SYSEX_0 4
+#define STATE_SYSEX_1 5
+#define STATE_SYSEX_2 6
+ uint8_t data[2];
+ } ports[0x10];
+ int current_port;
+
+ wait_queue_head_t drain_wait;
+};
+
+struct snd_usb_midi_in_endpoint {
+ struct snd_usb_midi *umidi;
+ struct urb *urbs[INPUT_URBS];
+ struct usbmidi_in_port {
+ struct snd_rawmidi_substream *substream;
+ u8 running_status_length;
+ } ports[0x10];
+ u8 seen_f5;
+ u8 error_resubmit;
+ int current_port;
+};
+
+static void snd_usbmidi_do_output(struct snd_usb_midi_out_endpoint *ep);
+
+static const uint8_t snd_usbmidi_cin_length[] = {
+ 0, 0, 2, 3, 3, 1, 2, 3, 3, 3, 3, 3, 2, 2, 3, 1
+};
+
+/*
+ * Submits the URB, with error handling.
+ */
+static int snd_usbmidi_submit_urb(struct urb *urb, gfp_t flags)
+{
+ int err = usb_submit_urb(urb, flags);
+ if (err < 0 && err != -ENODEV)
+ dev_err(&urb->dev->dev, "usb_submit_urb: %d\n", err);
+ return err;
+}
+
+/*
+ * Error handling for URB completion functions.
+ */
+static int snd_usbmidi_urb_error(const struct urb *urb)
+{
+ switch (urb->status) {
+ /* manually unlinked, or device gone */
+ case -ENOENT:
+ case -ECONNRESET:
+ case -ESHUTDOWN:
+ case -ENODEV:
+ return -ENODEV;
+ /* errors that might occur during unplugging */
+ case -EPROTO:
+ case -ETIME:
+ case -EILSEQ:
+ return -EIO;
+ default:
+ dev_err(&urb->dev->dev, "urb status %d\n", urb->status);
+ return 0; /* continue */
+ }
+}
+
+/*
+ * Receives a chunk of MIDI data.
+ */
+static void snd_usbmidi_input_data(struct snd_usb_midi_in_endpoint *ep,
+ int portidx, uint8_t *data, int length)
+{
+ struct usbmidi_in_port *port = &ep->ports[portidx];
+
+ if (!port->substream) {
+ dev_dbg(&ep->umidi->dev->dev, "unexpected port %d!\n", portidx);
+ return;
+ }
+ if (!test_bit(port->substream->number, &ep->umidi->input_triggered))
+ return;
+ snd_rawmidi_receive(port->substream, data, length);
+}
+
+#ifdef DUMP_PACKETS
+static void dump_urb(const char *type, const u8 *data, int length)
+{
+ snd_printk(KERN_DEBUG "%s packet: [", type);
+ for (; length > 0; ++data, --length)
+ printk(" %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 snd_usb_midi_in_endpoint *ep = urb->context;
+
+ if (urb->status == 0) {
+ dump_urb("received", urb->transfer_buffer, urb->actual_length);
+ ep->umidi->usb_protocol_ops->input(ep, urb->transfer_buffer,
+ urb->actual_length);
+ } else {
+ int err = snd_usbmidi_urb_error(urb);
+ if (err < 0) {
+ if (err != -ENODEV) {
+ ep->error_resubmit = 1;
+ mod_timer(&ep->umidi->error_timer,
+ jiffies + ERROR_DELAY_JIFFIES);
+ }
+ return;
+ }
+ }
+
+ urb->dev = ep->umidi->dev;
+ snd_usbmidi_submit_urb(urb, GFP_ATOMIC);
+}
+
+static void snd_usbmidi_out_urb_complete(struct urb *urb)
+{
+ struct out_urb_context *context = urb->context;
+ struct snd_usb_midi_out_endpoint *ep = context->ep;
+ unsigned int urb_index;
+
+ spin_lock(&ep->buffer_lock);
+ urb_index = context - ep->urbs;
+ ep->active_urbs &= ~(1 << urb_index);
+ if (unlikely(ep->drain_urbs)) {
+ ep->drain_urbs &= ~(1 << urb_index);
+ wake_up(&ep->drain_wait);
+ }
+ spin_unlock(&ep->buffer_lock);
+ if (urb->status < 0) {
+ int err = snd_usbmidi_urb_error(urb);
+ if (err < 0) {
+ if (err != -ENODEV)
+ mod_timer(&ep->umidi->error_timer,
+ jiffies + ERROR_DELAY_JIFFIES);
+ return;
+ }
+ }
+ snd_usbmidi_do_output(ep);
+}
+
+/*
+ * This is called when some data should be transferred to the device
+ * (from one or more substreams).
+ */
+static void snd_usbmidi_do_output(struct snd_usb_midi_out_endpoint *ep)
+{
+ unsigned int urb_index;
+ struct urb *urb;
+ unsigned long flags;
+
+ spin_lock_irqsave(&ep->buffer_lock, flags);
+ if (ep->umidi->disconnected) {
+ spin_unlock_irqrestore(&ep->buffer_lock, flags);
+ return;
+ }
+
+ urb_index = ep->next_urb;
+ for (;;) {
+ if (!(ep->active_urbs & (1 << urb_index))) {
+ urb = ep->urbs[urb_index].urb;
+ urb->transfer_buffer_length = 0;
+ ep->umidi->usb_protocol_ops->output(ep, urb);
+ if (urb->transfer_buffer_length == 0)
+ break;
+
+ dump_urb("sending", urb->transfer_buffer,
+ urb->transfer_buffer_length);
+ urb->dev = ep->umidi->dev;
+ if (snd_usbmidi_submit_urb(urb, GFP_ATOMIC) < 0)
+ break;
+ ep->active_urbs |= 1 << urb_index;
+ }
+ if (++urb_index >= OUTPUT_URBS)
+ urb_index = 0;
+ if (urb_index == ep->next_urb)
+ break;
+ }
+ ep->next_urb = urb_index;
+ spin_unlock_irqrestore(&ep->buffer_lock, flags);
+}
+
+static void snd_usbmidi_out_tasklet(unsigned long data)
+{
+ struct snd_usb_midi_out_endpoint *ep =
+ (struct snd_usb_midi_out_endpoint *) data;
+
+ snd_usbmidi_do_output(ep);
+}
+
+/* called after transfers had been interrupted due to some USB error */
+static void snd_usbmidi_error_timer(unsigned long data)
+{
+ struct snd_usb_midi *umidi = (struct snd_usb_midi *)data;
+ unsigned int i, j;
+
+ spin_lock(&umidi->disc_lock);
+ if (umidi->disconnected) {
+ spin_unlock(&umidi->disc_lock);
+ return;
+ }
+ for (i = 0; i < MIDI_MAX_ENDPOINTS; ++i) {
+ struct snd_usb_midi_in_endpoint *in = umidi->endpoints[i].in;
+ if (in && in->error_resubmit) {
+ in->error_resubmit = 0;
+ for (j = 0; j < INPUT_URBS; ++j) {
+ if (atomic_read(&in->urbs[j]->use_count))
+ continue;
+ in->urbs[j]->dev = umidi->dev;
+ snd_usbmidi_submit_urb(in->urbs[j], GFP_ATOMIC);
+ }
+ }
+ if (umidi->endpoints[i].out)
+ snd_usbmidi_do_output(umidi->endpoints[i].out);
+ }
+ spin_unlock(&umidi->disc_lock);
+}
+
+/* helper function to send static data that may not DMA-able */
+static int send_bulk_static_data(struct snd_usb_midi_out_endpoint *ep,
+ const void *data, int len)
+{
+ int err = 0;
+ void *buf = kmemdup(data, len, GFP_KERNEL);
+ if (!buf)
+ return -ENOMEM;
+ dump_urb("sending", buf, len);
+ if (ep->urbs[0].urb)
+ err = usb_bulk_msg(ep->umidi->dev, ep->urbs[0].urb->pipe,
+ buf, len, NULL, 250);
+ kfree(buf);
+ return err;
+}
+
+/*
+ * Standard USB MIDI protocol: see the spec.
+ * Midiman protocol: like the standard protocol, but the control byte is the
+ * fourth byte in each packet, and uses length instead of CIN.
+ */
+
+static void snd_usbmidi_standard_input(struct snd_usb_midi_in_endpoint *ep,
+ uint8_t *buffer, int buffer_length)
+{
+ int i;
+
+ for (i = 0; i + 3 < buffer_length; i += 4)
+ if (buffer[i] != 0) {
+ int cable = buffer[i] >> 4;
+ int length = snd_usbmidi_cin_length[buffer[i] & 0x0f];
+ snd_usbmidi_input_data(ep, cable, &buffer[i + 1],
+ length);
+ }
+}
+
+static void snd_usbmidi_midiman_input(struct snd_usb_midi_in_endpoint *ep,
+ uint8_t *buffer, int buffer_length)
+{
+ int i;
+
+ for (i = 0; i + 3 < buffer_length; i += 4)
+ if (buffer[i + 3] != 0) {
+ int port = buffer[i + 3] >> 4;
+ int length = buffer[i + 3] & 3;
+ snd_usbmidi_input_data(ep, port, &buffer[i], length);
+ }
+}
+
+/*
+ * Buggy M-Audio device: running status on input results in a packet that has
+ * the data bytes but not the status byte and that is marked with CIN 4.
+ */
+static void snd_usbmidi_maudio_broken_running_status_input(
+ struct snd_usb_midi_in_endpoint *ep,
+ uint8_t *buffer, int buffer_length)
+{
+ int i;
+
+ for (i = 0; i + 3 < buffer_length; i += 4)
+ if (buffer[i] != 0) {
+ int cable = buffer[i] >> 4;
+ u8 cin = buffer[i] & 0x0f;
+ struct usbmidi_in_port *port = &ep->ports[cable];
+ int length;
+
+ length = snd_usbmidi_cin_length[cin];
+ if (cin == 0xf && buffer[i + 1] >= 0xf8)
+ ; /* realtime msg: no running status change */
+ else if (cin >= 0x8 && cin <= 0xe)
+ /* channel msg */
+ port->running_status_length = length - 1;
+ else if (cin == 0x4 &&
+ port->running_status_length != 0 &&
+ buffer[i + 1] < 0x80)
+ /* CIN 4 that is not a SysEx */
+ length = port->running_status_length;
+ else
+ /*
+ * All other msgs cannot begin running status.
+ * (A channel msg sent as two or three CIN 0xF
+ * packets could in theory, but this device
+ * doesn't use this format.)
+ */
+ port->running_status_length = 0;
+ snd_usbmidi_input_data(ep, cable, &buffer[i + 1],
+ length);
+ }
+}
+
+/*
+ * CME protocol: like the standard protocol, but SysEx commands are sent as a
+ * single USB packet preceded by a 0x0F byte.
+ */
+static void snd_usbmidi_cme_input(struct snd_usb_midi_in_endpoint *ep,
+ uint8_t *buffer, int buffer_length)
+{
+ if (buffer_length < 2 || (buffer[0] & 0x0f) != 0x0f)
+ snd_usbmidi_standard_input(ep, buffer, buffer_length);
+ else
+ snd_usbmidi_input_data(ep, buffer[0] >> 4,
+ &buffer[1], buffer_length - 1);
+}
+
+/*
+ * Adds one USB MIDI packet to the output buffer.
+ */
+static void snd_usbmidi_output_standard_packet(struct urb *urb, uint8_t p0,
+ uint8_t p1, uint8_t p2,
+ uint8_t p3)
+{
+
+ uint8_t *buf =
+ (uint8_t *)urb->transfer_buffer + urb->transfer_buffer_length;
+ buf[0] = p0;
+ buf[1] = p1;
+ buf[2] = p2;
+ buf[3] = p3;
+ urb->transfer_buffer_length += 4;
+}
+
+/*
+ * Adds one Midiman packet to the output buffer.
+ */
+static void snd_usbmidi_output_midiman_packet(struct urb *urb, uint8_t p0,
+ uint8_t p1, uint8_t p2,
+ uint8_t p3)
+{
+
+ uint8_t *buf =
+ (uint8_t *)urb->transfer_buffer + urb->transfer_buffer_length;
+ buf[0] = p1;
+ buf[1] = p2;
+ buf[2] = p3;
+ buf[3] = (p0 & 0xf0) | snd_usbmidi_cin_length[p0 & 0x0f];
+ urb->transfer_buffer_length += 4;
+}
+
+/*
+ * Converts MIDI commands to USB MIDI packets.
+ */
+static void snd_usbmidi_transmit_byte(struct usbmidi_out_port *port,
+ uint8_t b, struct urb *urb)
+{
+ uint8_t p0 = port->cable;
+ void (*output_packet)(struct urb*, uint8_t, uint8_t, uint8_t, uint8_t) =
+ port->ep->umidi->usb_protocol_ops->output_packet;
+
+ if (b >= 0xf8) {
+ output_packet(urb, p0 | 0x0f, b, 0, 0);
+ } else if (b >= 0xf0) {
+ switch (b) {
+ case 0xf0:
+ port->data[0] = b;
+ port->state = STATE_SYSEX_1;
+ break;
+ case 0xf1:
+ case 0xf3:
+ port->data[0] = b;
+ port->state = STATE_1PARAM;
+ break;
+ case 0xf2:
+ port->data[0] = b;
+ port->state = STATE_2PARAM_1;
+ break;
+ case 0xf4:
+ case 0xf5:
+ port->state = STATE_UNKNOWN;
+ break;
+ case 0xf6:
+ output_packet(urb, p0 | 0x05, 0xf6, 0, 0);
+ port->state = STATE_UNKNOWN;
+ break;
+ case 0xf7:
+ switch (port->state) {
+ case STATE_SYSEX_0:
+ output_packet(urb, p0 | 0x05, 0xf7, 0, 0);
+ break;
+ case STATE_SYSEX_1:
+ output_packet(urb, p0 | 0x06, port->data[0],
+ 0xf7, 0);
+ break;
+ case STATE_SYSEX_2:
+ output_packet(urb, p0 | 0x07, port->data[0],
+ port->data[1], 0xf7);
+ break;
+ }
+ port->state = STATE_UNKNOWN;
+ break;
+ }
+ } else if (b >= 0x80) {
+ port->data[0] = b;
+ if (b >= 0xc0 && b <= 0xdf)
+ port->state = STATE_1PARAM;
+ else
+ port->state = STATE_2PARAM_1;
+ } else { /* b < 0x80 */
+ switch (port->state) {
+ case STATE_1PARAM:
+ if (port->data[0] < 0xf0) {
+ p0 |= port->data[0] >> 4;
+ } else {
+ p0 |= 0x02;
+ port->state = STATE_UNKNOWN;
+ }
+ output_packet(urb, p0, port->data[0], b, 0);
+ break;
+ case STATE_2PARAM_1:
+ port->data[1] = b;
+ port->state = STATE_2PARAM_2;
+ break;
+ case STATE_2PARAM_2:
+ if (port->data[0] < 0xf0) {
+ p0 |= port->data[0] >> 4;
+ port->state = STATE_2PARAM_1;
+ } else {
+ p0 |= 0x03;
+ port->state = STATE_UNKNOWN;
+ }
+ output_packet(urb, p0, port->data[0], port->data[1], b);
+ break;
+ case STATE_SYSEX_0:
+ port->data[0] = b;
+ port->state = STATE_SYSEX_1;
+ break;
+ case STATE_SYSEX_1:
+ port->data[1] = b;
+ port->state = STATE_SYSEX_2;
+ break;
+ case STATE_SYSEX_2:
+ output_packet(urb, p0 | 0x04, port->data[0],
+ port->data[1], b);
+ port->state = STATE_SYSEX_0;
+ break;
+ }
+ }
+}
+
+static void snd_usbmidi_standard_output(struct snd_usb_midi_out_endpoint *ep,
+ struct urb *urb)
+{
+ int p;
+
+ /* FIXME: lower-numbered ports can starve higher-numbered ports */
+ for (p = 0; p < 0x10; ++p) {
+ struct usbmidi_out_port *port = &ep->ports[p];
+ if (!port->active)
+ continue;
+ while (urb->transfer_buffer_length + 3 < ep->max_transfer) {
+ uint8_t b;
+ if (snd_rawmidi_transmit(port->substream, &b, 1) != 1) {
+ port->active = 0;
+ break;
+ }
+ snd_usbmidi_transmit_byte(port, b, urb);
+ }
+ }
+}
+
+static 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,
+};
+
+static struct usb_protocol_ops snd_usbmidi_maudio_broken_running_status_ops = {
+ .input = snd_usbmidi_maudio_broken_running_status_input,
+ .output = snd_usbmidi_standard_output,
+ .output_packet = snd_usbmidi_output_standard_packet,
+};
+
+static struct usb_protocol_ops snd_usbmidi_cme_ops = {
+ .input = snd_usbmidi_cme_input,
+ .output = snd_usbmidi_standard_output,
+ .output_packet = snd_usbmidi_output_standard_packet,
+};
+
+/*
+ * AKAI MPD16 protocol:
+ *
+ * For control port (endpoint 1):
+ * ==============================
+ * One or more chunks consisting of first byte of (0x10 | msg_len) and then a
+ * SysEx message (msg_len=9 bytes long).
+ *
+ * For data port (endpoint 2):
+ * ===========================
+ * One or more chunks consisting of first byte of (0x20 | msg_len) and then a
+ * MIDI message (msg_len bytes long)
+ *
+ * Messages sent: Active Sense, Note On, Poly Pressure, Control Change.
+ */
+static void snd_usbmidi_akai_input(struct snd_usb_midi_in_endpoint *ep,
+ uint8_t *buffer, int buffer_length)
+{
+ unsigned int pos = 0;
+ unsigned int len = (unsigned int)buffer_length;
+ while (pos < len) {
+ unsigned int port = (buffer[pos] >> 4) - 1;
+ unsigned int msg_len = buffer[pos] & 0x0f;
+ pos++;
+ if (pos + msg_len <= len && port < 2)
+ snd_usbmidi_input_data(ep, 0, &buffer[pos], msg_len);
+ pos += msg_len;
+ }
+}
+
+#define MAX_AKAI_SYSEX_LEN 9
+
+static void snd_usbmidi_akai_output(struct snd_usb_midi_out_endpoint *ep,
+ struct urb *urb)
+{
+ uint8_t *msg;
+ int pos, end, count, buf_end;
+ uint8_t tmp[MAX_AKAI_SYSEX_LEN];
+ struct snd_rawmidi_substream *substream = ep->ports[0].substream;
+
+ if (!ep->ports[0].active)
+ return;
+
+ msg = urb->transfer_buffer + urb->transfer_buffer_length;
+ buf_end = ep->max_transfer - MAX_AKAI_SYSEX_LEN - 1;
+
+ /* only try adding more data when there's space for at least 1 SysEx */
+ while (urb->transfer_buffer_length < buf_end) {
+ count = snd_rawmidi_transmit_peek(substream,
+ tmp, MAX_AKAI_SYSEX_LEN);
+ if (!count) {
+ ep->ports[0].active = 0;
+ return;
+ }
+ /* try to skip non-SysEx data */
+ for (pos = 0; pos < count && tmp[pos] != 0xF0; pos++)
+ ;
+
+ if (pos > 0) {
+ snd_rawmidi_transmit_ack(substream, pos);
+ continue;
+ }
+
+ /* look for the start or end marker */
+ for (end = 1; end < count && tmp[end] < 0xF0; end++)
+ ;
+
+ /* next SysEx started before the end of current one */
+ if (end < count && tmp[end] == 0xF0) {
+ /* it's incomplete - drop it */
+ snd_rawmidi_transmit_ack(substream, end);
+ continue;
+ }
+ /* SysEx complete */
+ if (end < count && tmp[end] == 0xF7) {
+ /* queue it, ack it, and get the next one */
+ count = end + 1;
+ msg[0] = 0x10 | count;
+ memcpy(&msg[1], tmp, count);
+ snd_rawmidi_transmit_ack(substream, count);
+ urb->transfer_buffer_length += count + 1;
+ msg += count + 1;
+ continue;
+ }
+ /* less than 9 bytes and no end byte - wait for more */
+ if (count < MAX_AKAI_SYSEX_LEN) {
+ ep->ports[0].active = 0;
+ return;
+ }
+ /* 9 bytes and no end marker in sight - malformed, skip it */
+ snd_rawmidi_transmit_ack(substream, count);
+ }
+}
+
+static struct usb_protocol_ops snd_usbmidi_akai_ops = {
+ .input = snd_usbmidi_akai_input,
+ .output = snd_usbmidi_akai_output,
+};
+
+/*
+ * Novation USB MIDI protocol: number of data bytes is in the first byte
+ * (when receiving) (+1!) or in the second byte (when sending); data begins
+ * at the third byte.
+ */
+
+static void snd_usbmidi_novation_input(struct snd_usb_midi_in_endpoint *ep,
+ uint8_t *buffer, int buffer_length)
+{
+ if (buffer_length < 2 || !buffer[0] || buffer_length < buffer[0] + 1)
+ return;
+ snd_usbmidi_input_data(ep, 0, &buffer[2], buffer[0] - 1);
+}
+
+static void snd_usbmidi_novation_output(struct snd_usb_midi_out_endpoint *ep,
+ struct urb *urb)
+{
+ uint8_t *transfer_buffer;
+ int count;
+
+ if (!ep->ports[0].active)
+ return;
+ transfer_buffer = urb->transfer_buffer;
+ count = snd_rawmidi_transmit(ep->ports[0].substream,
+ &transfer_buffer[2],
+ ep->max_transfer - 2);
+ if (count < 1) {
+ ep->ports[0].active = 0;
+ return;
+ }
+ transfer_buffer[0] = 0;
+ transfer_buffer[1] = count;
+ urb->transfer_buffer_length = 2 + count;
+}
+
+static struct usb_protocol_ops snd_usbmidi_novation_ops = {
+ .input = snd_usbmidi_novation_input,
+ .output = snd_usbmidi_novation_output,
+};
+
+/*
+ * "raw" protocol: just move raw MIDI bytes from/to the endpoint
+ */
+
+static void snd_usbmidi_raw_input(struct snd_usb_midi_in_endpoint *ep,
+ uint8_t *buffer, int buffer_length)
+{
+ snd_usbmidi_input_data(ep, 0, buffer, buffer_length);
+}
+
+static void snd_usbmidi_raw_output(struct snd_usb_midi_out_endpoint *ep,
+ struct urb *urb)
+{
+ int count;
+
+ if (!ep->ports[0].active)
+ return;
+ count = snd_rawmidi_transmit(ep->ports[0].substream,
+ urb->transfer_buffer,
+ ep->max_transfer);
+ if (count < 1) {
+ ep->ports[0].active = 0;
+ return;
+ }
+ urb->transfer_buffer_length = count;
+}
+
+static struct usb_protocol_ops snd_usbmidi_raw_ops = {
+ .input = snd_usbmidi_raw_input,
+ .output = snd_usbmidi_raw_output,
+};
+
+/*
+ * FTDI protocol: raw MIDI bytes, but input packets have two modem status bytes.
+ */
+
+static void snd_usbmidi_ftdi_input(struct snd_usb_midi_in_endpoint *ep,
+ uint8_t *buffer, int buffer_length)
+{
+ if (buffer_length > 2)
+ snd_usbmidi_input_data(ep, 0, buffer + 2, buffer_length - 2);
+}
+
+static struct usb_protocol_ops snd_usbmidi_ftdi_ops = {
+ .input = snd_usbmidi_ftdi_input,
+ .output = snd_usbmidi_raw_output,
+};
+
+static void snd_usbmidi_us122l_input(struct snd_usb_midi_in_endpoint *ep,
+ uint8_t *buffer, int buffer_length)
+{
+ if (buffer_length != 9)
+ return;
+ buffer_length = 8;
+ while (buffer_length && buffer[buffer_length - 1] == 0xFD)
+ buffer_length--;
+ if (buffer_length)
+ snd_usbmidi_input_data(ep, 0, buffer, buffer_length);
+}
+
+static void snd_usbmidi_us122l_output(struct snd_usb_midi_out_endpoint *ep,
+ struct urb *urb)
+{
+ int count;
+
+ if (!ep->ports[0].active)
+ return;
+ switch (snd_usb_get_speed(ep->umidi->dev)) {
+ case USB_SPEED_HIGH:
+ case USB_SPEED_SUPER:
+ count = 1;
+ break;
+ default:
+ count = 2;
+ }
+ count = snd_rawmidi_transmit(ep->ports[0].substream,
+ urb->transfer_buffer,
+ count);
+ if (count < 1) {
+ ep->ports[0].active = 0;
+ return;
+ }
+
+ memset(urb->transfer_buffer + count, 0xFD, ep->max_transfer - count);
+ urb->transfer_buffer_length = ep->max_transfer;
+}
+
+static struct usb_protocol_ops snd_usbmidi_122l_ops = {
+ .input = snd_usbmidi_us122l_input,
+ .output = snd_usbmidi_us122l_output,
+};
+
+/*
+ * Emagic USB MIDI protocol: raw MIDI with "F5 xx" port switching.
+ */
+
+static void snd_usbmidi_emagic_init_out(struct snd_usb_midi_out_endpoint *ep)
+{
+ static const u8 init_data[] = {
+ /* initialization magic: "get version" */
+ 0xf0,
+ 0x00, 0x20, 0x31, /* Emagic */
+ 0x64, /* Unitor8 */
+ 0x0b, /* version number request */
+ 0x00, /* command version */
+ 0x00, /* EEPROM, box 0 */
+ 0xf7
+ };
+ send_bulk_static_data(ep, init_data, sizeof(init_data));
+ /* while we're at it, pour on more magic */
+ send_bulk_static_data(ep, init_data, sizeof(init_data));
+}
+
+static void snd_usbmidi_emagic_finish_out(struct snd_usb_midi_out_endpoint *ep)
+{
+ static const u8 finish_data[] = {
+ /* switch to patch mode with last preset */
+ 0xf0,
+ 0x00, 0x20, 0x31, /* Emagic */
+ 0x64, /* Unitor8 */
+ 0x10, /* patch switch command */
+ 0x00, /* command version */
+ 0x7f, /* to all boxes */
+ 0x40, /* last preset in EEPROM */
+ 0xf7
+ };
+ send_bulk_static_data(ep, finish_data, sizeof(finish_data));
+}
+
+static void snd_usbmidi_emagic_input(struct snd_usb_midi_in_endpoint *ep,
+ uint8_t *buffer, int buffer_length)
+{
+ int i;
+
+ /* FF indicates end of valid data */
+ for (i = 0; i < buffer_length; ++i)
+ if (buffer[i] == 0xff) {
+ buffer_length = i;
+ break;
+ }
+
+ /* handle F5 at end of last buffer */
+ if (ep->seen_f5)
+ goto switch_port;
+
+ while (buffer_length > 0) {
+ /* determine size of data until next F5 */
+ for (i = 0; i < buffer_length; ++i)
+ if (buffer[i] == 0xf5)
+ break;
+ snd_usbmidi_input_data(ep, ep->current_port, buffer, i);
+ buffer += i;
+ buffer_length -= i;
+
+ if (buffer_length <= 0)
+ break;
+ /* assert(buffer[0] == 0xf5); */
+ ep->seen_f5 = 1;
+ ++buffer;
+ --buffer_length;
+
+ switch_port:
+ if (buffer_length <= 0)
+ break;
+ if (buffer[0] < 0x80) {
+ ep->current_port = (buffer[0] - 1) & 15;
+ ++buffer;
+ --buffer_length;
+ }
+ ep->seen_f5 = 0;
+ }
+}
+
+static void snd_usbmidi_emagic_output(struct snd_usb_midi_out_endpoint *ep,
+ struct urb *urb)
+{
+ int port0 = ep->current_port;
+ uint8_t *buf = urb->transfer_buffer;
+ int buf_free = ep->max_transfer;
+ int length, i;
+
+ for (i = 0; i < 0x10; ++i) {
+ /* round-robin, starting at the last current port */
+ int portnum = (port0 + i) & 15;
+ struct usbmidi_out_port *port = &ep->ports[portnum];
+
+ if (!port->active)
+ continue;
+ if (snd_rawmidi_transmit_peek(port->substream, buf, 1) != 1) {
+ port->active = 0;
+ continue;
+ }
+
+ if (portnum != ep->current_port) {
+ if (buf_free < 2)
+ break;
+ ep->current_port = portnum;
+ buf[0] = 0xf5;
+ buf[1] = (portnum + 1) & 15;
+ buf += 2;
+ buf_free -= 2;
+ }
+
+ if (buf_free < 1)
+ break;
+ length = snd_rawmidi_transmit(port->substream, buf, buf_free);
+ if (length > 0) {
+ buf += length;
+ buf_free -= length;
+ if (buf_free < 1)
+ break;
+ }
+ }
+ if (buf_free < ep->max_transfer && buf_free > 0) {
+ *buf = 0xff;
+ --buf_free;
+ }
+ urb->transfer_buffer_length = ep->max_transfer - buf_free;
+}
+
+static struct usb_protocol_ops snd_usbmidi_emagic_ops = {
+ .input = snd_usbmidi_emagic_input,
+ .output = snd_usbmidi_emagic_output,
+ .init_out_endpoint = snd_usbmidi_emagic_init_out,
+ .finish_out_endpoint = snd_usbmidi_emagic_finish_out,
+};
+
+
+static void update_roland_altsetting(struct snd_usb_midi *umidi)
+{
+ struct usb_interface *intf;
+ struct usb_host_interface *hostif;
+ struct usb_interface_descriptor *intfd;
+ int is_light_load;
+
+ intf = umidi->iface;
+ is_light_load = intf->cur_altsetting != intf->altsetting;
+ if (umidi->roland_load_ctl->private_value == is_light_load)
+ return;
+ hostif = &intf->altsetting[umidi->roland_load_ctl->private_value];
+ intfd = get_iface_desc(hostif);
+ snd_usbmidi_input_stop(&umidi->list);
+ usb_set_interface(umidi->dev, intfd->bInterfaceNumber,
+ intfd->bAlternateSetting);
+ snd_usbmidi_input_start(&umidi->list);
+}
+
+static int substream_open(struct snd_rawmidi_substream *substream, int dir,
+ int open)
+{
+ struct snd_usb_midi *umidi = substream->rmidi->private_data;
+ struct snd_kcontrol *ctl;
+
+ down_read(&umidi->disc_rwsem);
+ if (umidi->disconnected) {
+ up_read(&umidi->disc_rwsem);
+ return open ? -ENODEV : 0;
+ }
+
+ mutex_lock(&umidi->mutex);
+ if (open) {
+ if (!umidi->opened[0] && !umidi->opened[1]) {
+ if (umidi->roland_load_ctl) {
+ ctl = umidi->roland_load_ctl;
+ ctl->vd[0].access |=
+ SNDRV_CTL_ELEM_ACCESS_INACTIVE;
+ snd_ctl_notify(umidi->card,
+ SNDRV_CTL_EVENT_MASK_INFO, &ctl->id);
+ update_roland_altsetting(umidi);
+ }
+ }
+ umidi->opened[dir]++;
+ if (umidi->opened[1])
+ snd_usbmidi_input_start(&umidi->list);
+ } else {
+ umidi->opened[dir]--;
+ if (!umidi->opened[1])
+ snd_usbmidi_input_stop(&umidi->list);
+ if (!umidi->opened[0] && !umidi->opened[1]) {
+ if (umidi->roland_load_ctl) {
+ ctl = umidi->roland_load_ctl;
+ ctl->vd[0].access &=
+ ~SNDRV_CTL_ELEM_ACCESS_INACTIVE;
+ snd_ctl_notify(umidi->card,
+ SNDRV_CTL_EVENT_MASK_INFO, &ctl->id);
+ }
+ }
+ }
+ mutex_unlock(&umidi->mutex);
+ up_read(&umidi->disc_rwsem);
+ return 0;
+}
+
+static int snd_usbmidi_output_open(struct snd_rawmidi_substream *substream)
+{
+ struct snd_usb_midi *umidi = substream->rmidi->private_data;
+ struct usbmidi_out_port *port = NULL;
+ int i, j;
+
+ for (i = 0; i < MIDI_MAX_ENDPOINTS; ++i)
+ if (umidi->endpoints[i].out)
+ for (j = 0; j < 0x10; ++j)
+ if (umidi->endpoints[i].out->ports[j].substream == substream) {
+ port = &umidi->endpoints[i].out->ports[j];
+ break;
+ }
+ if (!port) {
+ snd_BUG();
+ return -ENXIO;
+ }
+
+ substream->runtime->private_data = port;
+ port->state = STATE_UNKNOWN;
+ return substream_open(substream, 0, 1);
+}
+
+static int snd_usbmidi_output_close(struct snd_rawmidi_substream *substream)
+{
+ return substream_open(substream, 0, 0);
+}
+
+static void snd_usbmidi_output_trigger(struct snd_rawmidi_substream *substream,
+ int up)
+{
+ struct usbmidi_out_port *port =
+ (struct usbmidi_out_port *)substream->runtime->private_data;
+
+ port->active = up;
+ if (up) {
+ if (port->ep->umidi->disconnected) {
+ /* gobble up remaining bytes to prevent wait in
+ * snd_rawmidi_drain_output */
+ while (!snd_rawmidi_transmit_empty(substream))
+ snd_rawmidi_transmit_ack(substream, 1);
+ return;
+ }
+ tasklet_schedule(&port->ep->tasklet);
+ }
+}
+
+static void snd_usbmidi_output_drain(struct snd_rawmidi_substream *substream)
+{
+ struct usbmidi_out_port *port = substream->runtime->private_data;
+ struct snd_usb_midi_out_endpoint *ep = port->ep;
+ unsigned int drain_urbs;
+ DEFINE_WAIT(wait);
+ long timeout = msecs_to_jiffies(50);
+
+ if (ep->umidi->disconnected)
+ return;
+ /*
+ * The substream buffer is empty, but some data might still be in the
+ * currently active URBs, so we have to wait for those to complete.
+ */
+ spin_lock_irq(&ep->buffer_lock);
+ drain_urbs = ep->active_urbs;
+ if (drain_urbs) {
+ ep->drain_urbs |= drain_urbs;
+ do {
+ prepare_to_wait(&ep->drain_wait, &wait,
+ TASK_UNINTERRUPTIBLE);
+ spin_unlock_irq(&ep->buffer_lock);
+ timeout = schedule_timeout(timeout);
+ spin_lock_irq(&ep->buffer_lock);
+ drain_urbs &= ep->drain_urbs;
+ } while (drain_urbs && timeout);
+ finish_wait(&ep->drain_wait, &wait);
+ }
+ spin_unlock_irq(&ep->buffer_lock);
+}
+
+static int snd_usbmidi_input_open(struct snd_rawmidi_substream *substream)
+{
+ return substream_open(substream, 1, 1);
+}
+
+static int snd_usbmidi_input_close(struct snd_rawmidi_substream *substream)
+{
+ return substream_open(substream, 1, 0);
+}
+
+static void snd_usbmidi_input_trigger(struct snd_rawmidi_substream *substream,
+ int up)
+{
+ struct snd_usb_midi *umidi = substream->rmidi->private_data;
+
+ if (up)
+ set_bit(substream->number, &umidi->input_triggered);
+ else
+ clear_bit(substream->number, &umidi->input_triggered);
+}
+
+static struct snd_rawmidi_ops snd_usbmidi_output_ops = {
+ .open = snd_usbmidi_output_open,
+ .close = snd_usbmidi_output_close,
+ .trigger = snd_usbmidi_output_trigger,
+ .drain = snd_usbmidi_output_drain,
+};
+
+static struct snd_rawmidi_ops snd_usbmidi_input_ops = {
+ .open = snd_usbmidi_input_open,
+ .close = snd_usbmidi_input_close,
+ .trigger = snd_usbmidi_input_trigger
+};
+
+static void free_urb_and_buffer(struct snd_usb_midi *umidi, struct urb *urb,
+ unsigned int buffer_length)
+{
+ usb_free_coherent(umidi->dev, buffer_length,
+ urb->transfer_buffer, urb->transfer_dma);
+ usb_free_urb(urb);
+}
+
+/*
+ * Frees an input endpoint.
+ * May be called when ep hasn't been initialized completely.
+ */
+static void snd_usbmidi_in_endpoint_delete(struct snd_usb_midi_in_endpoint *ep)
+{
+ unsigned int i;
+
+ for (i = 0; i < INPUT_URBS; ++i)
+ if (ep->urbs[i])
+ free_urb_and_buffer(ep->umidi, ep->urbs[i],
+ ep->urbs[i]->transfer_buffer_length);
+ kfree(ep);
+}
+
+/*
+ * Creates an input endpoint.
+ */
+static int snd_usbmidi_in_endpoint_create(struct snd_usb_midi *umidi,
+ struct snd_usb_midi_endpoint_info *ep_info,
+ struct snd_usb_midi_endpoint *rep)
+{
+ struct snd_usb_midi_in_endpoint *ep;
+ void *buffer;
+ unsigned int pipe;
+ int length;
+ unsigned int i;
+
+ rep->in = NULL;
+ ep = kzalloc(sizeof(*ep), GFP_KERNEL);
+ if (!ep)
+ return -ENOMEM;
+ ep->umidi = umidi;
+
+ for (i = 0; i < INPUT_URBS; ++i) {
+ ep->urbs[i] = usb_alloc_urb(0, GFP_KERNEL);
+ if (!ep->urbs[i]) {
+ snd_usbmidi_in_endpoint_delete(ep);
+ return -ENOMEM;
+ }
+ }
+ if (ep_info->in_interval)
+ pipe = usb_rcvintpipe(umidi->dev, ep_info->in_ep);
+ else
+ pipe = usb_rcvbulkpipe(umidi->dev, ep_info->in_ep);
+ length = usb_maxpacket(umidi->dev, pipe, 0);
+ for (i = 0; i < INPUT_URBS; ++i) {
+ buffer = usb_alloc_coherent(umidi->dev, length, GFP_KERNEL,
+ &ep->urbs[i]->transfer_dma);
+ if (!buffer) {
+ snd_usbmidi_in_endpoint_delete(ep);
+ return -ENOMEM;
+ }
+ if (ep_info->in_interval)
+ usb_fill_int_urb(ep->urbs[i], umidi->dev,
+ pipe, buffer, length,
+ snd_usbmidi_in_urb_complete,
+ ep, ep_info->in_interval);
+ else
+ usb_fill_bulk_urb(ep->urbs[i], umidi->dev,
+ pipe, buffer, length,
+ snd_usbmidi_in_urb_complete, ep);
+ ep->urbs[i]->transfer_flags = URB_NO_TRANSFER_DMA_MAP;
+ }
+
+ rep->in = ep;
+ return 0;
+}
+
+/*
+ * Frees an output endpoint.
+ * May be called when ep hasn't been initialized completely.
+ */
+static void snd_usbmidi_out_endpoint_clear(struct snd_usb_midi_out_endpoint *ep)
+{
+ unsigned int i;
+
+ for (i = 0; i < OUTPUT_URBS; ++i)
+ if (ep->urbs[i].urb) {
+ free_urb_and_buffer(ep->umidi, ep->urbs[i].urb,
+ ep->max_transfer);
+ ep->urbs[i].urb = NULL;
+ }
+}
+
+static void snd_usbmidi_out_endpoint_delete(struct snd_usb_midi_out_endpoint *ep)
+{
+ snd_usbmidi_out_endpoint_clear(ep);
+ kfree(ep);
+}
+
+/*
+ * Creates an output endpoint, and initializes output ports.
+ */
+static int snd_usbmidi_out_endpoint_create(struct snd_usb_midi *umidi,
+ struct snd_usb_midi_endpoint_info *ep_info,
+ struct snd_usb_midi_endpoint *rep)
+{
+ struct snd_usb_midi_out_endpoint *ep;
+ unsigned int i;
+ unsigned int pipe;
+ void *buffer;
+
+ rep->out = NULL;
+ ep = kzalloc(sizeof(*ep), GFP_KERNEL);
+ if (!ep)
+ return -ENOMEM;
+ ep->umidi = umidi;
+
+ for (i = 0; i < OUTPUT_URBS; ++i) {
+ ep->urbs[i].urb = usb_alloc_urb(0, GFP_KERNEL);
+ if (!ep->urbs[i].urb) {
+ snd_usbmidi_out_endpoint_delete(ep);
+ return -ENOMEM;
+ }
+ ep->urbs[i].ep = ep;
+ }
+ if (ep_info->out_interval)
+ pipe = usb_sndintpipe(umidi->dev, ep_info->out_ep);
+ else
+ pipe = usb_sndbulkpipe(umidi->dev, ep_info->out_ep);
+ switch (umidi->usb_id) {
+ default:
+ ep->max_transfer = usb_maxpacket(umidi->dev, pipe, 1);
+ break;
+ /*
+ * Various chips declare a packet size larger than 4 bytes, but
+ * do not actually work with larger packets:
+ */
+ case USB_ID(0x0a92, 0x1020): /* ESI M4U */
+ case USB_ID(0x1430, 0x474b): /* RedOctane GH MIDI INTERFACE */
+ case USB_ID(0x15ca, 0x0101): /* Textech USB Midi Cable */
+ case USB_ID(0x15ca, 0x1806): /* Textech USB Midi Cable */
+ case USB_ID(0x1a86, 0x752d): /* QinHeng CH345 "USB2.0-MIDI" */
+ case USB_ID(0xfc08, 0x0101): /* Unknown vendor Cable */
+ ep->max_transfer = 4;
+ break;
+ /*
+ * Some devices only work with 9 bytes packet size:
+ */
+ case USB_ID(0x0644, 0x800E): /* Tascam US-122L */
+ case USB_ID(0x0644, 0x800F): /* Tascam US-144 */
+ ep->max_transfer = 9;
+ break;
+ }
+ for (i = 0; i < OUTPUT_URBS; ++i) {
+ buffer = usb_alloc_coherent(umidi->dev,
+ ep->max_transfer, GFP_KERNEL,
+ &ep->urbs[i].urb->transfer_dma);
+ if (!buffer) {
+ snd_usbmidi_out_endpoint_delete(ep);
+ return -ENOMEM;
+ }
+ if (ep_info->out_interval)
+ usb_fill_int_urb(ep->urbs[i].urb, umidi->dev,
+ pipe, buffer, ep->max_transfer,
+ snd_usbmidi_out_urb_complete,
+ &ep->urbs[i], ep_info->out_interval);
+ else
+ usb_fill_bulk_urb(ep->urbs[i].urb, umidi->dev,
+ pipe, buffer, ep->max_transfer,
+ snd_usbmidi_out_urb_complete,
+ &ep->urbs[i]);
+ ep->urbs[i].urb->transfer_flags = URB_NO_TRANSFER_DMA_MAP;
+ }
+
+ spin_lock_init(&ep->buffer_lock);
+ tasklet_init(&ep->tasklet, snd_usbmidi_out_tasklet, (unsigned long)ep);
+ init_waitqueue_head(&ep->drain_wait);
+
+ for (i = 0; i < 0x10; ++i)
+ if (ep_info->out_cables & (1 << i)) {
+ ep->ports[i].ep = ep;
+ ep->ports[i].cable = i << 4;
+ }
+
+ if (umidi->usb_protocol_ops->init_out_endpoint)
+ umidi->usb_protocol_ops->init_out_endpoint(ep);
+
+ rep->out = ep;
+ return 0;
+}
+
+/*
+ * Frees everything.
+ */
+static void snd_usbmidi_free(struct snd_usb_midi *umidi)
+{
+ int i;
+
+ for (i = 0; i < MIDI_MAX_ENDPOINTS; ++i) {
+ struct snd_usb_midi_endpoint *ep = &umidi->endpoints[i];
+ if (ep->out)
+ snd_usbmidi_out_endpoint_delete(ep->out);
+ if (ep->in)
+ snd_usbmidi_in_endpoint_delete(ep->in);
+ }
+ mutex_destroy(&umidi->mutex);
+ kfree(umidi);
+}
+
+/*
+ * Unlinks all URBs (must be done before the usb_device is deleted).
+ */
+void snd_usbmidi_disconnect(struct list_head *p)
+{
+ struct snd_usb_midi *umidi;
+ unsigned int i, j;
+
+ umidi = list_entry(p, struct snd_usb_midi, list);
+ /*
+ * an URB's completion handler may start the timer and
+ * a timer may submit an URB. To reliably break the cycle
+ * a flag under lock must be used
+ */
+ down_write(&umidi->disc_rwsem);
+ spin_lock_irq(&umidi->disc_lock);
+ umidi->disconnected = 1;
+ spin_unlock_irq(&umidi->disc_lock);
+ up_write(&umidi->disc_rwsem);
+
+ for (i = 0; i < MIDI_MAX_ENDPOINTS; ++i) {
+ struct snd_usb_midi_endpoint *ep = &umidi->endpoints[i];
+ if (ep->out)
+ tasklet_kill(&ep->out->tasklet);
+ if (ep->out) {
+ for (j = 0; j < OUTPUT_URBS; ++j)
+ usb_kill_urb(ep->out->urbs[j].urb);
+ if (umidi->usb_protocol_ops->finish_out_endpoint)
+ umidi->usb_protocol_ops->finish_out_endpoint(ep->out);
+ ep->out->active_urbs = 0;
+ if (ep->out->drain_urbs) {
+ ep->out->drain_urbs = 0;
+ wake_up(&ep->out->drain_wait);
+ }
+ }
+ if (ep->in)
+ for (j = 0; j < INPUT_URBS; ++j)
+ usb_kill_urb(ep->in->urbs[j]);
+ /* free endpoints here; later call can result in Oops */
+ if (ep->out)
+ snd_usbmidi_out_endpoint_clear(ep->out);
+ if (ep->in) {
+ snd_usbmidi_in_endpoint_delete(ep->in);
+ ep->in = NULL;
+ }
+ }
+ del_timer_sync(&umidi->error_timer);
+}
+EXPORT_SYMBOL(snd_usbmidi_disconnect);
+
+static void snd_usbmidi_rawmidi_free(struct snd_rawmidi *rmidi)
+{
+ struct snd_usb_midi *umidi = rmidi->private_data;
+ snd_usbmidi_free(umidi);
+}
+
+static struct snd_rawmidi_substream *snd_usbmidi_find_substream(struct snd_usb_midi *umidi,
+ int stream,
+ int number)
+{
+ struct snd_rawmidi_substream *substream;
+
+ list_for_each_entry(substream, &umidi->rmidi->streams[stream].substreams,
+ list) {
+ if (substream->number == number)
+ return substream;
+ }
+ return NULL;
+}
+
+/*
+ * This list specifies names for ports that do not fit into the standard
+ * "(product) MIDI (n)" schema because they aren't external MIDI ports,
+ * such as internal control or synthesizer ports.
+ */
+static struct port_info {
+ u32 id;
+ short int port;
+ short int voices;
+ const char *name;
+ unsigned int seq_flags;
+} snd_usbmidi_port_info[] = {
+#define PORT_INFO(vendor, product, num, name_, voices_, flags) \
+ { .id = USB_ID(vendor, product), \
+ .port = num, .voices = voices_, \
+ .name = name_, .seq_flags = flags }
+#define EXTERNAL_PORT(vendor, product, num, name) \
+ PORT_INFO(vendor, product, num, name, 0, \
+ SNDRV_SEQ_PORT_TYPE_MIDI_GENERIC | \
+ SNDRV_SEQ_PORT_TYPE_HARDWARE | \
+ SNDRV_SEQ_PORT_TYPE_PORT)
+#define CONTROL_PORT(vendor, product, num, name) \
+ PORT_INFO(vendor, product, num, name, 0, \
+ SNDRV_SEQ_PORT_TYPE_MIDI_GENERIC | \
+ SNDRV_SEQ_PORT_TYPE_HARDWARE)
+#define GM_SYNTH_PORT(vendor, product, num, name, voices) \
+ PORT_INFO(vendor, product, num, name, voices, \
+ SNDRV_SEQ_PORT_TYPE_MIDI_GENERIC | \
+ SNDRV_SEQ_PORT_TYPE_MIDI_GM | \
+ SNDRV_SEQ_PORT_TYPE_HARDWARE | \
+ SNDRV_SEQ_PORT_TYPE_SYNTHESIZER)
+#define ROLAND_SYNTH_PORT(vendor, product, num, name, voices) \
+ PORT_INFO(vendor, product, num, name, voices, \
+ SNDRV_SEQ_PORT_TYPE_MIDI_GENERIC | \
+ SNDRV_SEQ_PORT_TYPE_MIDI_GM | \
+ SNDRV_SEQ_PORT_TYPE_MIDI_GM2 | \
+ SNDRV_SEQ_PORT_TYPE_MIDI_GS | \
+ SNDRV_SEQ_PORT_TYPE_MIDI_XG | \
+ SNDRV_SEQ_PORT_TYPE_HARDWARE | \
+ SNDRV_SEQ_PORT_TYPE_SYNTHESIZER)
+#define SOUNDCANVAS_PORT(vendor, product, num, name, voices) \
+ PORT_INFO(vendor, product, num, name, voices, \
+ SNDRV_SEQ_PORT_TYPE_MIDI_GENERIC | \
+ SNDRV_SEQ_PORT_TYPE_MIDI_GM | \
+ SNDRV_SEQ_PORT_TYPE_MIDI_GM2 | \
+ SNDRV_SEQ_PORT_TYPE_MIDI_GS | \
+ SNDRV_SEQ_PORT_TYPE_MIDI_XG | \
+ SNDRV_SEQ_PORT_TYPE_MIDI_MT32 | \
+ SNDRV_SEQ_PORT_TYPE_HARDWARE | \
+ SNDRV_SEQ_PORT_TYPE_SYNTHESIZER)
+ /* Yamaha MOTIF XF */
+ GM_SYNTH_PORT(0x0499, 0x105c, 0, "%s Tone Generator", 128),
+ CONTROL_PORT(0x0499, 0x105c, 1, "%s Remote Control"),
+ EXTERNAL_PORT(0x0499, 0x105c, 2, "%s Thru"),
+ CONTROL_PORT(0x0499, 0x105c, 3, "%s Editor"),
+ /* Roland UA-100 */
+ CONTROL_PORT(0x0582, 0x0000, 2, "%s Control"),
+ /* Roland SC-8850 */
+ SOUNDCANVAS_PORT(0x0582, 0x0003, 0, "%s Part A", 128),
+ SOUNDCANVAS_PORT(0x0582, 0x0003, 1, "%s Part B", 128),
+ SOUNDCANVAS_PORT(0x0582, 0x0003, 2, "%s Part C", 128),
+ SOUNDCANVAS_PORT(0x0582, 0x0003, 3, "%s Part D", 128),
+ EXTERNAL_PORT(0x0582, 0x0003, 4, "%s MIDI 1"),
+ EXTERNAL_PORT(0x0582, 0x0003, 5, "%s MIDI 2"),
+ /* Roland U-8 */
+ EXTERNAL_PORT(0x0582, 0x0004, 0, "%s MIDI"),
+ CONTROL_PORT(0x0582, 0x0004, 1, "%s Control"),
+ /* Roland SC-8820 */
+ SOUNDCANVAS_PORT(0x0582, 0x0007, 0, "%s Part A", 64),
+ SOUNDCANVAS_PORT(0x0582, 0x0007, 1, "%s Part B", 64),
+ EXTERNAL_PORT(0x0582, 0x0007, 2, "%s MIDI"),
+ /* Roland SK-500 */
+ SOUNDCANVAS_PORT(0x0582, 0x000b, 0, "%s Part A", 64),
+ SOUNDCANVAS_PORT(0x0582, 0x000b, 1, "%s Part B", 64),
+ EXTERNAL_PORT(0x0582, 0x000b, 2, "%s MIDI"),
+ /* Roland SC-D70 */
+ SOUNDCANVAS_PORT(0x0582, 0x000c, 0, "%s Part A", 64),
+ SOUNDCANVAS_PORT(0x0582, 0x000c, 1, "%s Part B", 64),
+ EXTERNAL_PORT(0x0582, 0x000c, 2, "%s MIDI"),
+ /* Edirol UM-880 */
+ CONTROL_PORT(0x0582, 0x0014, 8, "%s Control"),
+ /* Edirol SD-90 */
+ ROLAND_SYNTH_PORT(0x0582, 0x0016, 0, "%s Part A", 128),
+ ROLAND_SYNTH_PORT(0x0582, 0x0016, 1, "%s Part B", 128),
+ EXTERNAL_PORT(0x0582, 0x0016, 2, "%s MIDI 1"),
+ EXTERNAL_PORT(0x0582, 0x0016, 3, "%s MIDI 2"),
+ /* Edirol UM-550 */
+ CONTROL_PORT(0x0582, 0x0023, 5, "%s Control"),
+ /* Edirol SD-20 */
+ ROLAND_SYNTH_PORT(0x0582, 0x0027, 0, "%s Part A", 64),
+ ROLAND_SYNTH_PORT(0x0582, 0x0027, 1, "%s Part B", 64),
+ EXTERNAL_PORT(0x0582, 0x0027, 2, "%s MIDI"),
+ /* Edirol SD-80 */
+ ROLAND_SYNTH_PORT(0x0582, 0x0029, 0, "%s Part A", 128),
+ ROLAND_SYNTH_PORT(0x0582, 0x0029, 1, "%s Part B", 128),
+ EXTERNAL_PORT(0x0582, 0x0029, 2, "%s MIDI 1"),
+ EXTERNAL_PORT(0x0582, 0x0029, 3, "%s MIDI 2"),
+ /* Edirol UA-700 */
+ EXTERNAL_PORT(0x0582, 0x002b, 0, "%s MIDI"),
+ CONTROL_PORT(0x0582, 0x002b, 1, "%s Control"),
+ /* Roland VariOS */
+ EXTERNAL_PORT(0x0582, 0x002f, 0, "%s MIDI"),
+ EXTERNAL_PORT(0x0582, 0x002f, 1, "%s External MIDI"),
+ EXTERNAL_PORT(0x0582, 0x002f, 2, "%s Sync"),
+ /* Edirol PCR */
+ EXTERNAL_PORT(0x0582, 0x0033, 0, "%s MIDI"),
+ EXTERNAL_PORT(0x0582, 0x0033, 1, "%s 1"),
+ EXTERNAL_PORT(0x0582, 0x0033, 2, "%s 2"),
+ /* BOSS GS-10 */
+ EXTERNAL_PORT(0x0582, 0x003b, 0, "%s MIDI"),
+ CONTROL_PORT(0x0582, 0x003b, 1, "%s Control"),
+ /* Edirol UA-1000 */
+ EXTERNAL_PORT(0x0582, 0x0044, 0, "%s MIDI"),
+ CONTROL_PORT(0x0582, 0x0044, 1, "%s Control"),
+ /* Edirol UR-80 */
+ EXTERNAL_PORT(0x0582, 0x0048, 0, "%s MIDI"),
+ EXTERNAL_PORT(0x0582, 0x0048, 1, "%s 1"),
+ EXTERNAL_PORT(0x0582, 0x0048, 2, "%s 2"),
+ /* Edirol PCR-A */
+ EXTERNAL_PORT(0x0582, 0x004d, 0, "%s MIDI"),
+ EXTERNAL_PORT(0x0582, 0x004d, 1, "%s 1"),
+ EXTERNAL_PORT(0x0582, 0x004d, 2, "%s 2"),
+ /* BOSS GT-PRO */
+ CONTROL_PORT(0x0582, 0x0089, 0, "%s Control"),
+ /* Edirol UM-3EX */
+ CONTROL_PORT(0x0582, 0x009a, 3, "%s Control"),
+ /* Roland VG-99 */
+ CONTROL_PORT(0x0582, 0x00b2, 0, "%s Control"),
+ EXTERNAL_PORT(0x0582, 0x00b2, 1, "%s MIDI"),
+ /* Cakewalk Sonar V-Studio 100 */
+ EXTERNAL_PORT(0x0582, 0x00eb, 0, "%s MIDI"),
+ CONTROL_PORT(0x0582, 0x00eb, 1, "%s Control"),
+ /* Roland VB-99 */
+ CONTROL_PORT(0x0582, 0x0102, 0, "%s Control"),
+ EXTERNAL_PORT(0x0582, 0x0102, 1, "%s MIDI"),
+ /* Roland A-PRO */
+ EXTERNAL_PORT(0x0582, 0x010f, 0, "%s MIDI"),
+ CONTROL_PORT(0x0582, 0x010f, 1, "%s 1"),
+ CONTROL_PORT(0x0582, 0x010f, 2, "%s 2"),
+ /* Roland SD-50 */
+ ROLAND_SYNTH_PORT(0x0582, 0x0114, 0, "%s Synth", 128),
+ EXTERNAL_PORT(0x0582, 0x0114, 1, "%s MIDI"),
+ CONTROL_PORT(0x0582, 0x0114, 2, "%s Control"),
+ /* Roland OCTA-CAPTURE */
+ EXTERNAL_PORT(0x0582, 0x0120, 0, "%s MIDI"),
+ CONTROL_PORT(0x0582, 0x0120, 1, "%s Control"),
+ EXTERNAL_PORT(0x0582, 0x0121, 0, "%s MIDI"),
+ CONTROL_PORT(0x0582, 0x0121, 1, "%s Control"),
+ /* Roland SPD-SX */
+ CONTROL_PORT(0x0582, 0x0145, 0, "%s Control"),
+ EXTERNAL_PORT(0x0582, 0x0145, 1, "%s MIDI"),
+ /* Roland A-Series */
+ CONTROL_PORT(0x0582, 0x0156, 0, "%s Keyboard"),
+ EXTERNAL_PORT(0x0582, 0x0156, 1, "%s MIDI"),
+ /* Roland INTEGRA-7 */
+ ROLAND_SYNTH_PORT(0x0582, 0x015b, 0, "%s Synth", 128),
+ CONTROL_PORT(0x0582, 0x015b, 1, "%s Control"),
+ /* M-Audio MidiSport 8x8 */
+ CONTROL_PORT(0x0763, 0x1031, 8, "%s Control"),
+ CONTROL_PORT(0x0763, 0x1033, 8, "%s Control"),
+ /* MOTU Fastlane */
+ EXTERNAL_PORT(0x07fd, 0x0001, 0, "%s MIDI A"),
+ EXTERNAL_PORT(0x07fd, 0x0001, 1, "%s MIDI B"),
+ /* Emagic Unitor8/AMT8/MT4 */
+ EXTERNAL_PORT(0x086a, 0x0001, 8, "%s Broadcast"),
+ EXTERNAL_PORT(0x086a, 0x0002, 8, "%s Broadcast"),
+ EXTERNAL_PORT(0x086a, 0x0003, 4, "%s Broadcast"),
+ /* Akai MPD16 */
+ CONTROL_PORT(0x09e8, 0x0062, 0, "%s Control"),
+ PORT_INFO(0x09e8, 0x0062, 1, "%s MIDI", 0,
+ SNDRV_SEQ_PORT_TYPE_MIDI_GENERIC |
+ SNDRV_SEQ_PORT_TYPE_HARDWARE),
+ /* Access Music Virus TI */
+ EXTERNAL_PORT(0x133e, 0x0815, 0, "%s MIDI"),
+ PORT_INFO(0x133e, 0x0815, 1, "%s Synth", 0,
+ SNDRV_SEQ_PORT_TYPE_MIDI_GENERIC |
+ SNDRV_SEQ_PORT_TYPE_HARDWARE |
+ SNDRV_SEQ_PORT_TYPE_SYNTHESIZER),
+};
+
+static struct port_info *find_port_info(struct snd_usb_midi *umidi, int number)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(snd_usbmidi_port_info); ++i) {
+ if (snd_usbmidi_port_info[i].id == umidi->usb_id &&
+ snd_usbmidi_port_info[i].port == number)
+ return &snd_usbmidi_port_info[i];
+ }
+ return NULL;
+}
+
+static void snd_usbmidi_get_port_info(struct snd_rawmidi *rmidi, int number,
+ struct snd_seq_port_info *seq_port_info)
+{
+ struct snd_usb_midi *umidi = rmidi->private_data;
+ struct port_info *port_info;
+
+ /* TODO: read port flags from descriptors */
+ port_info = find_port_info(umidi, number);
+ if (port_info) {
+ seq_port_info->type = port_info->seq_flags;
+ seq_port_info->midi_voices = port_info->voices;
+ }
+}
+
+static void snd_usbmidi_init_substream(struct snd_usb_midi *umidi,
+ int stream, int number,
+ struct snd_rawmidi_substream **rsubstream)
+{
+ struct port_info *port_info;
+ const char *name_format;
+
+ struct snd_rawmidi_substream *substream =
+ snd_usbmidi_find_substream(umidi, stream, number);
+ if (!substream) {
+ dev_err(&umidi->dev->dev, "substream %d:%d not found\n", stream,
+ number);
+ return;
+ }
+
+ /* TODO: read port name from jack descriptor */
+ port_info = find_port_info(umidi, number);
+ name_format = port_info ? port_info->name : "%s MIDI %d";
+ snprintf(substream->name, sizeof(substream->name),
+ name_format, umidi->card->shortname, number + 1);
+
+ *rsubstream = substream;
+}
+
+/*
+ * Creates the endpoints and their ports.
+ */
+static int snd_usbmidi_create_endpoints(struct snd_usb_midi *umidi,
+ struct snd_usb_midi_endpoint_info *endpoints)
+{
+ int i, j, err;
+ int out_ports = 0, in_ports = 0;
+
+ for (i = 0; i < MIDI_MAX_ENDPOINTS; ++i) {
+ if (endpoints[i].out_cables) {
+ err = snd_usbmidi_out_endpoint_create(umidi,
+ &endpoints[i],
+ &umidi->endpoints[i]);
+ if (err < 0)
+ return err;
+ }
+ if (endpoints[i].in_cables) {
+ err = snd_usbmidi_in_endpoint_create(umidi,
+ &endpoints[i],
+ &umidi->endpoints[i]);
+ if (err < 0)
+ return err;
+ }
+
+ for (j = 0; j < 0x10; ++j) {
+ if (endpoints[i].out_cables & (1 << j)) {
+ snd_usbmidi_init_substream(umidi,
+ SNDRV_RAWMIDI_STREAM_OUTPUT,
+ out_ports,
+ &umidi->endpoints[i].out->ports[j].substream);
+ ++out_ports;
+ }
+ if (endpoints[i].in_cables & (1 << j)) {
+ snd_usbmidi_init_substream(umidi,
+ SNDRV_RAWMIDI_STREAM_INPUT,
+ in_ports,
+ &umidi->endpoints[i].in->ports[j].substream);
+ ++in_ports;
+ }
+ }
+ }
+ dev_dbg(&umidi->dev->dev, "created %d output and %d input ports\n",
+ out_ports, in_ports);
+ return 0;
+}
+
+/*
+ * Returns MIDIStreaming device capabilities.
+ */
+static int snd_usbmidi_get_ms_info(struct snd_usb_midi *umidi,
+ struct snd_usb_midi_endpoint_info *endpoints)
+{
+ struct usb_interface *intf;
+ struct usb_host_interface *hostif;
+ struct usb_interface_descriptor *intfd;
+ struct usb_ms_header_descriptor *ms_header;
+ struct usb_host_endpoint *hostep;
+ struct usb_endpoint_descriptor *ep;
+ struct usb_ms_endpoint_descriptor *ms_ep;
+ int i, epidx;
+
+ intf = umidi->iface;
+ if (!intf)
+ return -ENXIO;
+ hostif = &intf->altsetting[0];
+ intfd = get_iface_desc(hostif);
+ ms_header = (struct usb_ms_header_descriptor *)hostif->extra;
+ if (hostif->extralen >= 7 &&
+ ms_header->bLength >= 7 &&
+ ms_header->bDescriptorType == USB_DT_CS_INTERFACE &&
+ ms_header->bDescriptorSubtype == UAC_HEADER)
+ dev_dbg(&umidi->dev->dev, "MIDIStreaming version %02x.%02x\n",
+ ms_header->bcdMSC[1], ms_header->bcdMSC[0]);
+ else
+ dev_warn(&umidi->dev->dev,
+ "MIDIStreaming interface descriptor not found\n");
+
+ epidx = 0;
+ for (i = 0; i < intfd->bNumEndpoints; ++i) {
+ hostep = &hostif->endpoint[i];
+ ep = get_ep_desc(hostep);
+ if (!usb_endpoint_xfer_bulk(ep) && !usb_endpoint_xfer_int(ep))
+ continue;
+ ms_ep = (struct usb_ms_endpoint_descriptor *)hostep->extra;
+ if (hostep->extralen < 4 ||
+ ms_ep->bLength < 4 ||
+ ms_ep->bDescriptorType != USB_DT_CS_ENDPOINT ||
+ ms_ep->bDescriptorSubtype != UAC_MS_GENERAL)
+ continue;
+ if (usb_endpoint_dir_out(ep)) {
+ if (endpoints[epidx].out_ep) {
+ if (++epidx >= MIDI_MAX_ENDPOINTS) {
+ dev_warn(&umidi->dev->dev,
+ "too many endpoints\n");
+ break;
+ }
+ }
+ endpoints[epidx].out_ep = usb_endpoint_num(ep);
+ if (usb_endpoint_xfer_int(ep))
+ endpoints[epidx].out_interval = ep->bInterval;
+ else if (snd_usb_get_speed(umidi->dev) == USB_SPEED_LOW)
+ /*
+ * Low speed bulk transfers don't exist, so
+ * force interrupt transfers for devices like
+ * ESI MIDI Mate that try to use them anyway.
+ */
+ endpoints[epidx].out_interval = 1;
+ endpoints[epidx].out_cables =
+ (1 << ms_ep->bNumEmbMIDIJack) - 1;
+ dev_dbg(&umidi->dev->dev, "EP %02X: %d jack(s)\n",
+ ep->bEndpointAddress, ms_ep->bNumEmbMIDIJack);
+ } else {
+ if (endpoints[epidx].in_ep) {
+ if (++epidx >= MIDI_MAX_ENDPOINTS) {
+ dev_warn(&umidi->dev->dev,
+ "too many endpoints\n");
+ break;
+ }
+ }
+ endpoints[epidx].in_ep = usb_endpoint_num(ep);
+ if (usb_endpoint_xfer_int(ep))
+ endpoints[epidx].in_interval = ep->bInterval;
+ else if (snd_usb_get_speed(umidi->dev) == USB_SPEED_LOW)
+ endpoints[epidx].in_interval = 1;
+ endpoints[epidx].in_cables =
+ (1 << ms_ep->bNumEmbMIDIJack) - 1;
+ dev_dbg(&umidi->dev->dev, "EP %02X: %d jack(s)\n",
+ ep->bEndpointAddress, ms_ep->bNumEmbMIDIJack);
+ }
+ }
+ return 0;
+}
+
+static int roland_load_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *info)
+{
+ static const char *const names[] = { "High Load", "Light Load" };
+
+ return snd_ctl_enum_info(info, 1, 2, names);
+}
+
+static int roland_load_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *value)
+{
+ value->value.enumerated.item[0] = kcontrol->private_value;
+ return 0;
+}
+
+static int roland_load_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *value)
+{
+ struct snd_usb_midi *umidi = kcontrol->private_data;
+ int changed;
+
+ if (value->value.enumerated.item[0] > 1)
+ return -EINVAL;
+ mutex_lock(&umidi->mutex);
+ changed = value->value.enumerated.item[0] != kcontrol->private_value;
+ if (changed)
+ kcontrol->private_value = value->value.enumerated.item[0];
+ mutex_unlock(&umidi->mutex);
+ return changed;
+}
+
+static struct snd_kcontrol_new roland_load_ctl = {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "MIDI Input Mode",
+ .info = roland_load_info,
+ .get = roland_load_get,
+ .put = roland_load_put,
+ .private_value = 1,
+};
+
+/*
+ * On Roland devices, use the second alternate setting to be able to use
+ * the interrupt input endpoint.
+ */
+static void snd_usbmidi_switch_roland_altsetting(struct snd_usb_midi *umidi)
+{
+ struct usb_interface *intf;
+ struct usb_host_interface *hostif;
+ struct usb_interface_descriptor *intfd;
+
+ intf = umidi->iface;
+ if (!intf || intf->num_altsetting != 2)
+ return;
+
+ hostif = &intf->altsetting[1];
+ intfd = get_iface_desc(hostif);
+ if (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;
+
+ dev_dbg(&umidi->dev->dev, "switching to altsetting %d with int ep\n",
+ intfd->bAlternateSetting);
+ usb_set_interface(umidi->dev, intfd->bInterfaceNumber,
+ intfd->bAlternateSetting);
+
+ umidi->roland_load_ctl = snd_ctl_new1(&roland_load_ctl, umidi);
+ if (snd_ctl_add(umidi->card, umidi->roland_load_ctl) < 0)
+ umidi->roland_load_ctl = NULL;
+}
+
+/*
+ * Try to find any usable endpoints in the interface.
+ */
+static int snd_usbmidi_detect_endpoints(struct snd_usb_midi *umidi,
+ struct snd_usb_midi_endpoint_info *endpoint,
+ int max_endpoints)
+{
+ struct usb_interface *intf;
+ struct usb_host_interface *hostif;
+ struct usb_interface_descriptor *intfd;
+ struct usb_endpoint_descriptor *epd;
+ int i, out_eps = 0, in_eps = 0;
+
+ if (USB_ID_VENDOR(umidi->usb_id) == 0x0582)
+ snd_usbmidi_switch_roland_altsetting(umidi);
+
+ if (endpoint[0].out_ep || endpoint[0].in_ep)
+ return 0;
+
+ intf = umidi->iface;
+ if (!intf || intf->num_altsetting < 1)
+ return -ENOENT;
+ hostif = intf->cur_altsetting;
+ intfd = get_iface_desc(hostif);
+
+ for (i = 0; i < intfd->bNumEndpoints; ++i) {
+ epd = get_endpoint(hostif, i);
+ if (!usb_endpoint_xfer_bulk(epd) &&
+ !usb_endpoint_xfer_int(epd))
+ continue;
+ if (out_eps < max_endpoints &&
+ usb_endpoint_dir_out(epd)) {
+ endpoint[out_eps].out_ep = usb_endpoint_num(epd);
+ if (usb_endpoint_xfer_int(epd))
+ endpoint[out_eps].out_interval = epd->bInterval;
+ ++out_eps;
+ }
+ if (in_eps < max_endpoints &&
+ usb_endpoint_dir_in(epd)) {
+ endpoint[in_eps].in_ep = usb_endpoint_num(epd);
+ if (usb_endpoint_xfer_int(epd))
+ endpoint[in_eps].in_interval = epd->bInterval;
+ ++in_eps;
+ }
+ }
+ return (out_eps || in_eps) ? 0 : -ENOENT;
+}
+
+/*
+ * Detects the endpoints for one-port-per-endpoint protocols.
+ */
+static int snd_usbmidi_detect_per_port_endpoints(struct snd_usb_midi *umidi,
+ struct snd_usb_midi_endpoint_info *endpoints)
+{
+ int err, i;
+
+ err = snd_usbmidi_detect_endpoints(umidi, endpoints, MIDI_MAX_ENDPOINTS);
+ for (i = 0; i < MIDI_MAX_ENDPOINTS; ++i) {
+ if (endpoints[i].out_ep)
+ endpoints[i].out_cables = 0x0001;
+ if (endpoints[i].in_ep)
+ endpoints[i].in_cables = 0x0001;
+ }
+ return err;
+}
+
+/*
+ * Detects the endpoints and ports of Yamaha devices.
+ */
+static int snd_usbmidi_detect_yamaha(struct snd_usb_midi *umidi,
+ struct snd_usb_midi_endpoint_info *endpoint)
+{
+ struct usb_interface *intf;
+ struct usb_host_interface *hostif;
+ struct usb_interface_descriptor *intfd;
+ uint8_t *cs_desc;
+
+ intf = umidi->iface;
+ if (!intf)
+ return -ENOENT;
+ hostif = intf->altsetting;
+ intfd = get_iface_desc(hostif);
+ if (intfd->bNumEndpoints < 1)
+ return -ENOENT;
+
+ /*
+ * For each port there is one MIDI_IN/OUT_JACK descriptor, not
+ * necessarily with any useful contents. So simply count 'em.
+ */
+ for (cs_desc = hostif->extra;
+ cs_desc < hostif->extra + hostif->extralen && cs_desc[0] >= 2;
+ cs_desc += cs_desc[0]) {
+ if (cs_desc[1] == USB_DT_CS_INTERFACE) {
+ if (cs_desc[2] == UAC_MIDI_IN_JACK)
+ endpoint->in_cables =
+ (endpoint->in_cables << 1) | 1;
+ else if (cs_desc[2] == UAC_MIDI_OUT_JACK)
+ endpoint->out_cables =
+ (endpoint->out_cables << 1) | 1;
+ }
+ }
+ if (!endpoint->in_cables && !endpoint->out_cables)
+ return -ENOENT;
+
+ return snd_usbmidi_detect_endpoints(umidi, endpoint, 1);
+}
+
+/*
+ * Detects the endpoints and ports of Roland devices.
+ */
+static int snd_usbmidi_detect_roland(struct snd_usb_midi *umidi,
+ struct snd_usb_midi_endpoint_info *endpoint)
+{
+ struct usb_interface *intf;
+ struct usb_host_interface *hostif;
+ u8 *cs_desc;
+
+ intf = umidi->iface;
+ if (!intf)
+ return -ENOENT;
+ hostif = intf->altsetting;
+ /*
+ * Some devices have a descriptor <06 24 F1 02 <inputs> <outputs>>,
+ * some have standard class descriptors, or both kinds, or neither.
+ */
+ for (cs_desc = hostif->extra;
+ cs_desc < hostif->extra + hostif->extralen && cs_desc[0] >= 2;
+ cs_desc += cs_desc[0]) {
+ if (cs_desc[0] >= 6 &&
+ cs_desc[1] == USB_DT_CS_INTERFACE &&
+ cs_desc[2] == 0xf1 &&
+ cs_desc[3] == 0x02) {
+ endpoint->in_cables = (1 << cs_desc[4]) - 1;
+ endpoint->out_cables = (1 << cs_desc[5]) - 1;
+ return snd_usbmidi_detect_endpoints(umidi, endpoint, 1);
+ } else if (cs_desc[0] >= 7 &&
+ cs_desc[1] == USB_DT_CS_INTERFACE &&
+ cs_desc[2] == UAC_HEADER) {
+ return snd_usbmidi_get_ms_info(umidi, endpoint);
+ }
+ }
+
+ return -ENODEV;
+}
+
+/*
+ * Creates the endpoints and their ports for Midiman devices.
+ */
+static int snd_usbmidi_create_endpoints_midiman(struct snd_usb_midi *umidi,
+ struct snd_usb_midi_endpoint_info *endpoint)
+{
+ struct snd_usb_midi_endpoint_info ep_info;
+ struct usb_interface *intf;
+ struct usb_host_interface *hostif;
+ struct usb_interface_descriptor *intfd;
+ struct usb_endpoint_descriptor *epd;
+ int cable, err;
+
+ intf = umidi->iface;
+ if (!intf)
+ return -ENOENT;
+ hostif = intf->altsetting;
+ intfd = get_iface_desc(hostif);
+ /*
+ * The various MidiSport devices have more or less random endpoint
+ * numbers, so we have to identify the endpoints by their index in
+ * the descriptor array, like the driver for that other OS does.
+ *
+ * There is one interrupt input endpoint for all input ports, one
+ * bulk output endpoint for even-numbered ports, and one for odd-
+ * numbered ports. Both bulk output endpoints have corresponding
+ * input bulk endpoints (at indices 1 and 3) which aren't used.
+ */
+ if (intfd->bNumEndpoints < (endpoint->out_cables > 0x0001 ? 5 : 3)) {
+ dev_dbg(&umidi->dev->dev, "not enough endpoints\n");
+ return -ENOENT;
+ }
+
+ epd = get_endpoint(hostif, 0);
+ if (!usb_endpoint_dir_in(epd) || !usb_endpoint_xfer_int(epd)) {
+ dev_dbg(&umidi->dev->dev, "endpoint[0] isn't interrupt\n");
+ return -ENXIO;
+ }
+ epd = get_endpoint(hostif, 2);
+ if (!usb_endpoint_dir_out(epd) || !usb_endpoint_xfer_bulk(epd)) {
+ dev_dbg(&umidi->dev->dev, "endpoint[2] isn't bulk output\n");
+ return -ENXIO;
+ }
+ if (endpoint->out_cables > 0x0001) {
+ epd = get_endpoint(hostif, 4);
+ if (!usb_endpoint_dir_out(epd) ||
+ !usb_endpoint_xfer_bulk(epd)) {
+ dev_dbg(&umidi->dev->dev,
+ "endpoint[4] isn't bulk output\n");
+ return -ENXIO;
+ }
+ }
+
+ ep_info.out_ep = get_endpoint(hostif, 2)->bEndpointAddress &
+ USB_ENDPOINT_NUMBER_MASK;
+ ep_info.out_interval = 0;
+ ep_info.out_cables = endpoint->out_cables & 0x5555;
+ err = snd_usbmidi_out_endpoint_create(umidi, &ep_info,
+ &umidi->endpoints[0]);
+ if (err < 0)
+ return err;
+
+ ep_info.in_ep = get_endpoint(hostif, 0)->bEndpointAddress &
+ USB_ENDPOINT_NUMBER_MASK;
+ ep_info.in_interval = get_endpoint(hostif, 0)->bInterval;
+ ep_info.in_cables = endpoint->in_cables;
+ err = snd_usbmidi_in_endpoint_create(umidi, &ep_info,
+ &umidi->endpoints[0]);
+ if (err < 0)
+ return err;
+
+ if (endpoint->out_cables > 0x0001) {
+ ep_info.out_ep = get_endpoint(hostif, 4)->bEndpointAddress &
+ USB_ENDPOINT_NUMBER_MASK;
+ ep_info.out_cables = endpoint->out_cables & 0xaaaa;
+ err = snd_usbmidi_out_endpoint_create(umidi, &ep_info,
+ &umidi->endpoints[1]);
+ if (err < 0)
+ return err;
+ }
+
+ for (cable = 0; cable < 0x10; ++cable) {
+ if (endpoint->out_cables & (1 << cable))
+ snd_usbmidi_init_substream(umidi,
+ SNDRV_RAWMIDI_STREAM_OUTPUT,
+ cable,
+ &umidi->endpoints[cable & 1].out->ports[cable].substream);
+ if (endpoint->in_cables & (1 << cable))
+ snd_usbmidi_init_substream(umidi,
+ SNDRV_RAWMIDI_STREAM_INPUT,
+ cable,
+ &umidi->endpoints[0].in->ports[cable].substream);
+ }
+ return 0;
+}
+
+static struct snd_rawmidi_global_ops snd_usbmidi_ops = {
+ .get_port_info = snd_usbmidi_get_port_info,
+};
+
+static int snd_usbmidi_create_rawmidi(struct snd_usb_midi *umidi,
+ int out_ports, int in_ports)
+{
+ struct snd_rawmidi *rmidi;
+ int err;
+
+ err = snd_rawmidi_new(umidi->card, "USB MIDI",
+ umidi->next_midi_device++,
+ out_ports, in_ports, &rmidi);
+ if (err < 0)
+ return err;
+ strcpy(rmidi->name, umidi->card->shortname);
+ rmidi->info_flags = SNDRV_RAWMIDI_INFO_OUTPUT |
+ SNDRV_RAWMIDI_INFO_INPUT |
+ SNDRV_RAWMIDI_INFO_DUPLEX;
+ rmidi->ops = &snd_usbmidi_ops;
+ rmidi->private_data = umidi;
+ rmidi->private_free = snd_usbmidi_rawmidi_free;
+ snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT,
+ &snd_usbmidi_output_ops);
+ snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT,
+ &snd_usbmidi_input_ops);
+
+ umidi->rmidi = rmidi;
+ return 0;
+}
+
+/*
+ * Temporarily stop input.
+ */
+void snd_usbmidi_input_stop(struct list_head *p)
+{
+ struct snd_usb_midi *umidi;
+ unsigned int i, j;
+
+ umidi = list_entry(p, struct snd_usb_midi, list);
+ if (!umidi->input_running)
+ return;
+ for (i = 0; i < MIDI_MAX_ENDPOINTS; ++i) {
+ struct snd_usb_midi_endpoint *ep = &umidi->endpoints[i];
+ if (ep->in)
+ for (j = 0; j < INPUT_URBS; ++j)
+ usb_kill_urb(ep->in->urbs[j]);
+ }
+ umidi->input_running = 0;
+}
+EXPORT_SYMBOL(snd_usbmidi_input_stop);
+
+static void snd_usbmidi_input_start_ep(struct snd_usb_midi_in_endpoint *ep)
+{
+ unsigned int i;
+
+ if (!ep)
+ return;
+ for (i = 0; i < INPUT_URBS; ++i) {
+ struct urb *urb = ep->urbs[i];
+ urb->dev = ep->umidi->dev;
+ snd_usbmidi_submit_urb(urb, GFP_KERNEL);
+ }
+}
+
+/*
+ * Resume input after a call to snd_usbmidi_input_stop().
+ */
+void snd_usbmidi_input_start(struct list_head *p)
+{
+ struct snd_usb_midi *umidi;
+ int i;
+
+ umidi = list_entry(p, struct snd_usb_midi, list);
+ if (umidi->input_running || !umidi->opened[1])
+ return;
+ for (i = 0; i < MIDI_MAX_ENDPOINTS; ++i)
+ snd_usbmidi_input_start_ep(umidi->endpoints[i].in);
+ umidi->input_running = 1;
+}
+EXPORT_SYMBOL(snd_usbmidi_input_start);
+
+/*
+ * Prepare for suspend. Typically called from the USB suspend callback.
+ */
+void snd_usbmidi_suspend(struct list_head *p)
+{
+ struct snd_usb_midi *umidi;
+
+ umidi = list_entry(p, struct snd_usb_midi, list);
+ mutex_lock(&umidi->mutex);
+ snd_usbmidi_input_stop(p);
+ mutex_unlock(&umidi->mutex);
+}
+EXPORT_SYMBOL(snd_usbmidi_suspend);
+
+/*
+ * Resume. Typically called from the USB resume callback.
+ */
+void snd_usbmidi_resume(struct list_head *p)
+{
+ struct snd_usb_midi *umidi;
+
+ umidi = list_entry(p, struct snd_usb_midi, list);
+ mutex_lock(&umidi->mutex);
+ snd_usbmidi_input_start(p);
+ mutex_unlock(&umidi->mutex);
+}
+EXPORT_SYMBOL(snd_usbmidi_resume);
+
+/*
+ * Creates and registers everything needed for a MIDI streaming interface.
+ */
+int snd_usbmidi_create(struct snd_card *card,
+ struct usb_interface *iface,
+ struct list_head *midi_list,
+ const struct snd_usb_audio_quirk *quirk)
+{
+ struct snd_usb_midi *umidi;
+ struct snd_usb_midi_endpoint_info endpoints[MIDI_MAX_ENDPOINTS];
+ int out_ports, in_ports;
+ int i, err;
+
+ umidi = kzalloc(sizeof(*umidi), GFP_KERNEL);
+ if (!umidi)
+ return -ENOMEM;
+ umidi->dev = interface_to_usbdev(iface);
+ umidi->card = card;
+ umidi->iface = iface;
+ umidi->quirk = quirk;
+ umidi->usb_protocol_ops = &snd_usbmidi_standard_ops;
+ spin_lock_init(&umidi->disc_lock);
+ init_rwsem(&umidi->disc_rwsem);
+ mutex_init(&umidi->mutex);
+ umidi->usb_id = USB_ID(le16_to_cpu(umidi->dev->descriptor.idVendor),
+ le16_to_cpu(umidi->dev->descriptor.idProduct));
+ setup_timer(&umidi->error_timer, snd_usbmidi_error_timer,
+ (unsigned long)umidi);
+
+ /* detect the endpoint(s) to use */
+ memset(endpoints, 0, sizeof(endpoints));
+ switch (quirk ? quirk->type : QUIRK_MIDI_STANDARD_INTERFACE) {
+ case QUIRK_MIDI_STANDARD_INTERFACE:
+ err = snd_usbmidi_get_ms_info(umidi, endpoints);
+ if (umidi->usb_id == USB_ID(0x0763, 0x0150)) /* M-Audio Uno */
+ umidi->usb_protocol_ops =
+ &snd_usbmidi_maudio_broken_running_status_ops;
+ break;
+ case QUIRK_MIDI_US122L:
+ umidi->usb_protocol_ops = &snd_usbmidi_122l_ops;
+ /* fall through */
+ case QUIRK_MIDI_FIXED_ENDPOINT:
+ memcpy(&endpoints[0], quirk->data,
+ sizeof(struct snd_usb_midi_endpoint_info));
+ err = snd_usbmidi_detect_endpoints(umidi, &endpoints[0], 1);
+ break;
+ case QUIRK_MIDI_YAMAHA:
+ err = snd_usbmidi_detect_yamaha(umidi, &endpoints[0]);
+ break;
+ case QUIRK_MIDI_ROLAND:
+ err = snd_usbmidi_detect_roland(umidi, &endpoints[0]);
+ break;
+ case QUIRK_MIDI_MIDIMAN:
+ umidi->usb_protocol_ops = &snd_usbmidi_midiman_ops;
+ memcpy(&endpoints[0], quirk->data,
+ sizeof(struct snd_usb_midi_endpoint_info));
+ err = 0;
+ break;
+ case QUIRK_MIDI_NOVATION:
+ umidi->usb_protocol_ops = &snd_usbmidi_novation_ops;
+ err = snd_usbmidi_detect_per_port_endpoints(umidi, endpoints);
+ break;
+ case QUIRK_MIDI_RAW_BYTES:
+ umidi->usb_protocol_ops = &snd_usbmidi_raw_ops;
+ /*
+ * Interface 1 contains isochronous endpoints, but with the same
+ * numbers as in interface 0. Since it is interface 1 that the
+ * USB core has most recently seen, these descriptors are now
+ * associated with the endpoint numbers. This will foul up our
+ * attempts to submit bulk/interrupt URBs to the endpoints in
+ * interface 0, so we have to make sure that the USB core looks
+ * again at interface 0 by calling usb_set_interface() on it.
+ */
+ if (umidi->usb_id == USB_ID(0x07fd, 0x0001)) /* MOTU Fastlane */
+ usb_set_interface(umidi->dev, 0, 0);
+ err = snd_usbmidi_detect_per_port_endpoints(umidi, endpoints);
+ break;
+ case QUIRK_MIDI_EMAGIC:
+ umidi->usb_protocol_ops = &snd_usbmidi_emagic_ops;
+ memcpy(&endpoints[0], quirk->data,
+ sizeof(struct snd_usb_midi_endpoint_info));
+ err = snd_usbmidi_detect_endpoints(umidi, &endpoints[0], 1);
+ break;
+ case QUIRK_MIDI_CME:
+ umidi->usb_protocol_ops = &snd_usbmidi_cme_ops;
+ err = snd_usbmidi_detect_per_port_endpoints(umidi, endpoints);
+ break;
+ case QUIRK_MIDI_AKAI:
+ umidi->usb_protocol_ops = &snd_usbmidi_akai_ops;
+ err = snd_usbmidi_detect_per_port_endpoints(umidi, endpoints);
+ /* endpoint 1 is input-only */
+ endpoints[1].out_cables = 0;
+ break;
+ case QUIRK_MIDI_FTDI:
+ umidi->usb_protocol_ops = &snd_usbmidi_ftdi_ops;
+
+ /* set baud rate to 31250 (48 MHz / 16 / 96) */
+ err = usb_control_msg(umidi->dev, usb_sndctrlpipe(umidi->dev, 0),
+ 3, 0x40, 0x60, 0, NULL, 0, 1000);
+ if (err < 0)
+ break;
+
+ err = snd_usbmidi_detect_per_port_endpoints(umidi, endpoints);
+ break;
+ default:
+ dev_err(&umidi->dev->dev, "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 += hweight16(endpoints[i].out_cables);
+ in_ports += hweight16(endpoints[i].in_cables);
+ }
+ err = snd_usbmidi_create_rawmidi(umidi, out_ports, in_ports);
+ if (err < 0) {
+ 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;
+ }
+
+ usb_autopm_get_interface_no_resume(umidi->iface);
+
+ list_add_tail(&umidi->list, midi_list);
+ return 0;
+}
+EXPORT_SYMBOL(snd_usbmidi_create);
diff --git a/sound/usb/midi.h b/sound/usb/midi.h
new file mode 100644
index 000000000..ad8a3211f
--- /dev/null
+++ b/sound/usb/midi.h
@@ -0,0 +1,52 @@
+#ifndef __USBMIDI_H
+#define __USBMIDI_H
+
+/* maximum number of endpoints per interface */
+#define MIDI_MAX_ENDPOINTS 2
+
+/* data for QUIRK_MIDI_FIXED_ENDPOINT */
+struct snd_usb_midi_endpoint_info {
+ int8_t out_ep; /* ep number, 0 autodetect */
+ uint8_t out_interval; /* interval for interrupt endpoints */
+ int8_t in_ep;
+ uint8_t in_interval;
+ uint16_t out_cables; /* bitmask */
+ uint16_t in_cables; /* bitmask */
+};
+
+/* for QUIRK_MIDI_YAMAHA, data is NULL */
+
+/* for QUIRK_MIDI_MIDIMAN, data points to a snd_usb_midi_endpoint_info
+ * structure (out_cables and in_cables only) */
+
+/* for QUIRK_COMPOSITE, data points to an array of snd_usb_audio_quirk
+ * structures, terminated with .ifnum = -1 */
+
+/* for QUIRK_AUDIO_FIXED_ENDPOINT, data points to an audioformat structure */
+
+/* for QUIRK_AUDIO/MIDI_STANDARD_INTERFACE, data is NULL */
+
+/* for QUIRK_AUDIO_EDIROL_UA700_UA25/UA1000, data is NULL */
+
+/* for QUIRK_IGNORE_INTERFACE, data is NULL */
+
+/* for QUIRK_MIDI_NOVATION and _RAW, data is NULL */
+
+/* for QUIRK_MIDI_EMAGIC, data points to a snd_usb_midi_endpoint_info
+ * structure (out_cables and in_cables only) */
+
+/* for QUIRK_MIDI_CME, data is NULL */
+
+/* for QUIRK_MIDI_AKAI, data is NULL */
+
+int snd_usbmidi_create(struct snd_card *card,
+ struct usb_interface *iface,
+ struct list_head *midi_list,
+ const struct snd_usb_audio_quirk *quirk);
+void snd_usbmidi_input_stop(struct list_head *p);
+void snd_usbmidi_input_start(struct list_head *p);
+void snd_usbmidi_disconnect(struct list_head *p);
+void snd_usbmidi_suspend(struct list_head *p);
+void snd_usbmidi_resume(struct list_head *p);
+
+#endif /* __USBMIDI_H */
diff --git a/sound/usb/misc/Makefile b/sound/usb/misc/Makefile
new file mode 100644
index 000000000..ccefd8158
--- /dev/null
+++ b/sound/usb/misc/Makefile
@@ -0,0 +1,2 @@
+snd-ua101-objs := ua101.o
+obj-$(CONFIG_SND_USB_UA101) += snd-ua101.o
diff --git a/sound/usb/misc/ua101.c b/sound/usb/misc/ua101.c
new file mode 100644
index 000000000..9581089c2
--- /dev/null
+++ b/sound/usb/misc/ua101.c
@@ -0,0 +1,1388 @@
+/*
+ * Edirol UA-101/UA-1000 driver
+ * Copyright (c) Clemens Ladisch <clemens@ladisch.de>
+ *
+ * This driver is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License, version 2.
+ *
+ * This driver is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this driver. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/usb.h>
+#include <linux/usb/audio.h>
+#include <sound/core.h>
+#include <sound/initval.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include "../usbaudio.h"
+#include "../midi.h"
+
+MODULE_DESCRIPTION("Edirol UA-101/1000 driver");
+MODULE_AUTHOR("Clemens Ladisch <clemens@ladisch.de>");
+MODULE_LICENSE("GPL v2");
+MODULE_SUPPORTED_DEVICE("{{Edirol,UA-101},{Edirol,UA-1000}}");
+
+/*
+ * Should not be lower than the minimum scheduling delay of the host
+ * controller. Some Intel controllers need more than one frame; as long as
+ * that driver doesn't tell us about this, use 1.5 frames just to be sure.
+ */
+#define MIN_QUEUE_LENGTH 12
+/* Somewhat random. */
+#define MAX_QUEUE_LENGTH 30
+/*
+ * This magic value optimizes memory usage efficiency for the UA-101's packet
+ * sizes at all sample rates, taking into account the stupid cache pool sizes
+ * that usb_alloc_coherent() uses.
+ */
+#define DEFAULT_QUEUE_LENGTH 21
+
+#define MAX_PACKET_SIZE 672 /* hardware specific */
+#define MAX_MEMORY_BUFFERS DIV_ROUND_UP(MAX_QUEUE_LENGTH, \
+ PAGE_SIZE / MAX_PACKET_SIZE)
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;
+static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;
+static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP;
+static unsigned int queue_length = 21;
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "card index");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "enable card");
+module_param(queue_length, uint, 0644);
+MODULE_PARM_DESC(queue_length, "USB queue length in microframes, "
+ __stringify(MIN_QUEUE_LENGTH)"-"__stringify(MAX_QUEUE_LENGTH));
+
+enum {
+ INTF_PLAYBACK,
+ INTF_CAPTURE,
+ INTF_MIDI,
+
+ INTF_COUNT
+};
+
+/* bits in struct ua101::states */
+enum {
+ USB_CAPTURE_RUNNING,
+ USB_PLAYBACK_RUNNING,
+ ALSA_CAPTURE_OPEN,
+ ALSA_PLAYBACK_OPEN,
+ ALSA_CAPTURE_RUNNING,
+ ALSA_PLAYBACK_RUNNING,
+ CAPTURE_URB_COMPLETED,
+ PLAYBACK_URB_COMPLETED,
+ DISCONNECTED,
+};
+
+struct ua101 {
+ struct usb_device *dev;
+ struct snd_card *card;
+ struct usb_interface *intf[INTF_COUNT];
+ int card_index;
+ struct snd_pcm *pcm;
+ struct list_head midi_list;
+ u64 format_bit;
+ unsigned int rate;
+ unsigned int packets_per_second;
+ spinlock_t lock;
+ struct mutex mutex;
+ unsigned long states;
+
+ /* FIFO to synchronize playback rate to capture rate */
+ unsigned int rate_feedback_start;
+ unsigned int rate_feedback_count;
+ u8 rate_feedback[MAX_QUEUE_LENGTH];
+
+ struct list_head ready_playback_urbs;
+ struct tasklet_struct playback_tasklet;
+ wait_queue_head_t alsa_capture_wait;
+ wait_queue_head_t rate_feedback_wait;
+ wait_queue_head_t alsa_playback_wait;
+ struct ua101_stream {
+ struct snd_pcm_substream *substream;
+ unsigned int usb_pipe;
+ unsigned int channels;
+ unsigned int frame_bytes;
+ unsigned int max_packet_bytes;
+ unsigned int period_pos;
+ unsigned int buffer_pos;
+ unsigned int queue_length;
+ struct ua101_urb {
+ struct urb urb;
+ struct usb_iso_packet_descriptor iso_frame_desc[1];
+ struct list_head ready_list;
+ } *urbs[MAX_QUEUE_LENGTH];
+ struct {
+ unsigned int size;
+ void *addr;
+ dma_addr_t dma;
+ } buffers[MAX_MEMORY_BUFFERS];
+ } capture, playback;
+};
+
+static DEFINE_MUTEX(devices_mutex);
+static unsigned int devices_used;
+static struct usb_driver ua101_driver;
+
+static void abort_alsa_playback(struct ua101 *ua);
+static void abort_alsa_capture(struct ua101 *ua);
+
+static const char *usb_error_string(int err)
+{
+ switch (err) {
+ case -ENODEV:
+ return "no device";
+ case -ENOENT:
+ return "endpoint not enabled";
+ case -EPIPE:
+ return "endpoint stalled";
+ case -ENOSPC:
+ return "not enough bandwidth";
+ case -ESHUTDOWN:
+ return "device disabled";
+ case -EHOSTUNREACH:
+ return "device suspended";
+ case -EINVAL:
+ case -EAGAIN:
+ case -EFBIG:
+ case -EMSGSIZE:
+ return "internal error";
+ default:
+ return "unknown error";
+ }
+}
+
+static void abort_usb_capture(struct ua101 *ua)
+{
+ if (test_and_clear_bit(USB_CAPTURE_RUNNING, &ua->states)) {
+ wake_up(&ua->alsa_capture_wait);
+ wake_up(&ua->rate_feedback_wait);
+ }
+}
+
+static void abort_usb_playback(struct ua101 *ua)
+{
+ if (test_and_clear_bit(USB_PLAYBACK_RUNNING, &ua->states))
+ wake_up(&ua->alsa_playback_wait);
+}
+
+static void playback_urb_complete(struct urb *usb_urb)
+{
+ struct ua101_urb *urb = (struct ua101_urb *)usb_urb;
+ struct ua101 *ua = urb->urb.context;
+ unsigned long flags;
+
+ if (unlikely(urb->urb.status == -ENOENT || /* unlinked */
+ urb->urb.status == -ENODEV || /* device removed */
+ urb->urb.status == -ECONNRESET || /* unlinked */
+ urb->urb.status == -ESHUTDOWN)) { /* device disabled */
+ abort_usb_playback(ua);
+ abort_alsa_playback(ua);
+ return;
+ }
+
+ if (test_bit(USB_PLAYBACK_RUNNING, &ua->states)) {
+ /* append URB to FIFO */
+ spin_lock_irqsave(&ua->lock, flags);
+ list_add_tail(&urb->ready_list, &ua->ready_playback_urbs);
+ if (ua->rate_feedback_count > 0)
+ tasklet_schedule(&ua->playback_tasklet);
+ ua->playback.substream->runtime->delay -=
+ urb->urb.iso_frame_desc[0].length /
+ ua->playback.frame_bytes;
+ spin_unlock_irqrestore(&ua->lock, flags);
+ }
+}
+
+static void first_playback_urb_complete(struct urb *urb)
+{
+ struct ua101 *ua = urb->context;
+
+ urb->complete = playback_urb_complete;
+ playback_urb_complete(urb);
+
+ set_bit(PLAYBACK_URB_COMPLETED, &ua->states);
+ wake_up(&ua->alsa_playback_wait);
+}
+
+/* copy data from the ALSA ring buffer into the URB buffer */
+static bool copy_playback_data(struct ua101_stream *stream, struct urb *urb,
+ unsigned int frames)
+{
+ struct snd_pcm_runtime *runtime;
+ unsigned int frame_bytes, frames1;
+ const u8 *source;
+
+ runtime = stream->substream->runtime;
+ frame_bytes = stream->frame_bytes;
+ source = runtime->dma_area + stream->buffer_pos * frame_bytes;
+ if (stream->buffer_pos + frames <= runtime->buffer_size) {
+ memcpy(urb->transfer_buffer, source, frames * frame_bytes);
+ } else {
+ /* wrap around at end of ring buffer */
+ frames1 = runtime->buffer_size - stream->buffer_pos;
+ memcpy(urb->transfer_buffer, source, frames1 * frame_bytes);
+ memcpy(urb->transfer_buffer + frames1 * frame_bytes,
+ runtime->dma_area, (frames - frames1) * frame_bytes);
+ }
+
+ stream->buffer_pos += frames;
+ if (stream->buffer_pos >= runtime->buffer_size)
+ stream->buffer_pos -= runtime->buffer_size;
+ stream->period_pos += frames;
+ if (stream->period_pos >= runtime->period_size) {
+ stream->period_pos -= runtime->period_size;
+ return true;
+ }
+ return false;
+}
+
+static inline void add_with_wraparound(struct ua101 *ua,
+ unsigned int *value, unsigned int add)
+{
+ *value += add;
+ if (*value >= ua->playback.queue_length)
+ *value -= ua->playback.queue_length;
+}
+
+static void playback_tasklet(unsigned long data)
+{
+ struct ua101 *ua = (void *)data;
+ unsigned long flags;
+ unsigned int frames;
+ struct ua101_urb *urb;
+ bool do_period_elapsed = false;
+ int err;
+
+ if (unlikely(!test_bit(USB_PLAYBACK_RUNNING, &ua->states)))
+ return;
+
+ /*
+ * Synchronizing the playback rate to the capture rate is done by using
+ * the same sequence of packet sizes for both streams.
+ * Submitting a playback URB therefore requires both a ready URB and
+ * the size of the corresponding capture packet, i.e., both playback
+ * and capture URBs must have been completed. Since the USB core does
+ * not guarantee that playback and capture complete callbacks are
+ * called alternately, we use two FIFOs for packet sizes and read URBs;
+ * submitting playback URBs is possible as long as both FIFOs are
+ * nonempty.
+ */
+ spin_lock_irqsave(&ua->lock, flags);
+ while (ua->rate_feedback_count > 0 &&
+ !list_empty(&ua->ready_playback_urbs)) {
+ /* take packet size out of FIFO */
+ frames = ua->rate_feedback[ua->rate_feedback_start];
+ add_with_wraparound(ua, &ua->rate_feedback_start, 1);
+ ua->rate_feedback_count--;
+
+ /* take URB out of FIFO */
+ urb = list_first_entry(&ua->ready_playback_urbs,
+ struct ua101_urb, ready_list);
+ list_del(&urb->ready_list);
+
+ /* fill packet with data or silence */
+ urb->urb.iso_frame_desc[0].length =
+ frames * ua->playback.frame_bytes;
+ if (test_bit(ALSA_PLAYBACK_RUNNING, &ua->states))
+ do_period_elapsed |= copy_playback_data(&ua->playback,
+ &urb->urb,
+ frames);
+ else
+ memset(urb->urb.transfer_buffer, 0,
+ urb->urb.iso_frame_desc[0].length);
+
+ /* and off you go ... */
+ err = usb_submit_urb(&urb->urb, GFP_ATOMIC);
+ if (unlikely(err < 0)) {
+ spin_unlock_irqrestore(&ua->lock, flags);
+ abort_usb_playback(ua);
+ abort_alsa_playback(ua);
+ dev_err(&ua->dev->dev, "USB request error %d: %s\n",
+ err, usb_error_string(err));
+ return;
+ }
+ ua->playback.substream->runtime->delay += frames;
+ }
+ spin_unlock_irqrestore(&ua->lock, flags);
+ if (do_period_elapsed)
+ snd_pcm_period_elapsed(ua->playback.substream);
+}
+
+/* copy data from the URB buffer into the ALSA ring buffer */
+static bool copy_capture_data(struct ua101_stream *stream, struct urb *urb,
+ unsigned int frames)
+{
+ struct snd_pcm_runtime *runtime;
+ unsigned int frame_bytes, frames1;
+ u8 *dest;
+
+ runtime = stream->substream->runtime;
+ frame_bytes = stream->frame_bytes;
+ dest = runtime->dma_area + stream->buffer_pos * frame_bytes;
+ if (stream->buffer_pos + frames <= runtime->buffer_size) {
+ memcpy(dest, urb->transfer_buffer, frames * frame_bytes);
+ } else {
+ /* wrap around at end of ring buffer */
+ frames1 = runtime->buffer_size - stream->buffer_pos;
+ memcpy(dest, urb->transfer_buffer, frames1 * frame_bytes);
+ memcpy(runtime->dma_area,
+ urb->transfer_buffer + frames1 * frame_bytes,
+ (frames - frames1) * frame_bytes);
+ }
+
+ stream->buffer_pos += frames;
+ if (stream->buffer_pos >= runtime->buffer_size)
+ stream->buffer_pos -= runtime->buffer_size;
+ stream->period_pos += frames;
+ if (stream->period_pos >= runtime->period_size) {
+ stream->period_pos -= runtime->period_size;
+ return true;
+ }
+ return false;
+}
+
+static void capture_urb_complete(struct urb *urb)
+{
+ struct ua101 *ua = urb->context;
+ struct ua101_stream *stream = &ua->capture;
+ unsigned long flags;
+ unsigned int frames, write_ptr;
+ bool do_period_elapsed;
+ int err;
+
+ if (unlikely(urb->status == -ENOENT || /* unlinked */
+ urb->status == -ENODEV || /* device removed */
+ urb->status == -ECONNRESET || /* unlinked */
+ urb->status == -ESHUTDOWN)) /* device disabled */
+ goto stream_stopped;
+
+ if (urb->status >= 0 && urb->iso_frame_desc[0].status >= 0)
+ frames = urb->iso_frame_desc[0].actual_length /
+ stream->frame_bytes;
+ else
+ frames = 0;
+
+ spin_lock_irqsave(&ua->lock, flags);
+
+ if (frames > 0 && test_bit(ALSA_CAPTURE_RUNNING, &ua->states))
+ do_period_elapsed = copy_capture_data(stream, urb, frames);
+ else
+ do_period_elapsed = false;
+
+ if (test_bit(USB_CAPTURE_RUNNING, &ua->states)) {
+ err = usb_submit_urb(urb, GFP_ATOMIC);
+ if (unlikely(err < 0)) {
+ spin_unlock_irqrestore(&ua->lock, flags);
+ dev_err(&ua->dev->dev, "USB request error %d: %s\n",
+ err, usb_error_string(err));
+ goto stream_stopped;
+ }
+
+ /* append packet size to FIFO */
+ write_ptr = ua->rate_feedback_start;
+ add_with_wraparound(ua, &write_ptr, ua->rate_feedback_count);
+ ua->rate_feedback[write_ptr] = frames;
+ if (ua->rate_feedback_count < ua->playback.queue_length) {
+ ua->rate_feedback_count++;
+ if (ua->rate_feedback_count ==
+ ua->playback.queue_length)
+ wake_up(&ua->rate_feedback_wait);
+ } else {
+ /*
+ * Ring buffer overflow; this happens when the playback
+ * stream is not running. Throw away the oldest entry,
+ * so that the playback stream, when it starts, sees
+ * the most recent packet sizes.
+ */
+ add_with_wraparound(ua, &ua->rate_feedback_start, 1);
+ }
+ if (test_bit(USB_PLAYBACK_RUNNING, &ua->states) &&
+ !list_empty(&ua->ready_playback_urbs))
+ tasklet_schedule(&ua->playback_tasklet);
+ }
+
+ spin_unlock_irqrestore(&ua->lock, flags);
+
+ if (do_period_elapsed)
+ snd_pcm_period_elapsed(stream->substream);
+
+ return;
+
+stream_stopped:
+ abort_usb_playback(ua);
+ abort_usb_capture(ua);
+ abort_alsa_playback(ua);
+ abort_alsa_capture(ua);
+}
+
+static void first_capture_urb_complete(struct urb *urb)
+{
+ struct ua101 *ua = urb->context;
+
+ urb->complete = capture_urb_complete;
+ capture_urb_complete(urb);
+
+ set_bit(CAPTURE_URB_COMPLETED, &ua->states);
+ wake_up(&ua->alsa_capture_wait);
+}
+
+static int submit_stream_urbs(struct ua101 *ua, struct ua101_stream *stream)
+{
+ unsigned int i;
+
+ for (i = 0; i < stream->queue_length; ++i) {
+ int err = usb_submit_urb(&stream->urbs[i]->urb, GFP_KERNEL);
+ if (err < 0) {
+ dev_err(&ua->dev->dev, "USB request error %d: %s\n",
+ err, usb_error_string(err));
+ return err;
+ }
+ }
+ return 0;
+}
+
+static void kill_stream_urbs(struct ua101_stream *stream)
+{
+ unsigned int i;
+
+ for (i = 0; i < stream->queue_length; ++i)
+ if (stream->urbs[i])
+ usb_kill_urb(&stream->urbs[i]->urb);
+}
+
+static int enable_iso_interface(struct ua101 *ua, unsigned int intf_index)
+{
+ struct usb_host_interface *alts;
+
+ alts = ua->intf[intf_index]->cur_altsetting;
+ if (alts->desc.bAlternateSetting != 1) {
+ int err = usb_set_interface(ua->dev,
+ alts->desc.bInterfaceNumber, 1);
+ if (err < 0) {
+ dev_err(&ua->dev->dev,
+ "cannot initialize interface; error %d: %s\n",
+ err, usb_error_string(err));
+ return err;
+ }
+ }
+ return 0;
+}
+
+static void disable_iso_interface(struct ua101 *ua, unsigned int intf_index)
+{
+ struct usb_host_interface *alts;
+
+ if (!ua->intf[intf_index])
+ return;
+
+ alts = ua->intf[intf_index]->cur_altsetting;
+ if (alts->desc.bAlternateSetting != 0) {
+ int err = usb_set_interface(ua->dev,
+ alts->desc.bInterfaceNumber, 0);
+ if (err < 0 && !test_bit(DISCONNECTED, &ua->states))
+ dev_warn(&ua->dev->dev,
+ "interface reset failed; error %d: %s\n",
+ err, usb_error_string(err));
+ }
+}
+
+static void stop_usb_capture(struct ua101 *ua)
+{
+ clear_bit(USB_CAPTURE_RUNNING, &ua->states);
+
+ kill_stream_urbs(&ua->capture);
+
+ disable_iso_interface(ua, INTF_CAPTURE);
+}
+
+static int start_usb_capture(struct ua101 *ua)
+{
+ int err;
+
+ if (test_bit(DISCONNECTED, &ua->states))
+ return -ENODEV;
+
+ if (test_bit(USB_CAPTURE_RUNNING, &ua->states))
+ return 0;
+
+ kill_stream_urbs(&ua->capture);
+
+ err = enable_iso_interface(ua, INTF_CAPTURE);
+ if (err < 0)
+ return err;
+
+ clear_bit(CAPTURE_URB_COMPLETED, &ua->states);
+ ua->capture.urbs[0]->urb.complete = first_capture_urb_complete;
+ ua->rate_feedback_start = 0;
+ ua->rate_feedback_count = 0;
+
+ set_bit(USB_CAPTURE_RUNNING, &ua->states);
+ err = submit_stream_urbs(ua, &ua->capture);
+ if (err < 0)
+ stop_usb_capture(ua);
+ return err;
+}
+
+static void stop_usb_playback(struct ua101 *ua)
+{
+ clear_bit(USB_PLAYBACK_RUNNING, &ua->states);
+
+ kill_stream_urbs(&ua->playback);
+
+ tasklet_kill(&ua->playback_tasklet);
+
+ disable_iso_interface(ua, INTF_PLAYBACK);
+}
+
+static int start_usb_playback(struct ua101 *ua)
+{
+ unsigned int i, frames;
+ struct urb *urb;
+ int err = 0;
+
+ if (test_bit(DISCONNECTED, &ua->states))
+ return -ENODEV;
+
+ if (test_bit(USB_PLAYBACK_RUNNING, &ua->states))
+ return 0;
+
+ kill_stream_urbs(&ua->playback);
+ tasklet_kill(&ua->playback_tasklet);
+
+ err = enable_iso_interface(ua, INTF_PLAYBACK);
+ if (err < 0)
+ return err;
+
+ clear_bit(PLAYBACK_URB_COMPLETED, &ua->states);
+ ua->playback.urbs[0]->urb.complete =
+ first_playback_urb_complete;
+ spin_lock_irq(&ua->lock);
+ INIT_LIST_HEAD(&ua->ready_playback_urbs);
+ spin_unlock_irq(&ua->lock);
+
+ /*
+ * We submit the initial URBs all at once, so we have to wait for the
+ * packet size FIFO to be full.
+ */
+ wait_event(ua->rate_feedback_wait,
+ ua->rate_feedback_count >= ua->playback.queue_length ||
+ !test_bit(USB_CAPTURE_RUNNING, &ua->states) ||
+ test_bit(DISCONNECTED, &ua->states));
+ if (test_bit(DISCONNECTED, &ua->states)) {
+ stop_usb_playback(ua);
+ return -ENODEV;
+ }
+ if (!test_bit(USB_CAPTURE_RUNNING, &ua->states)) {
+ stop_usb_playback(ua);
+ return -EIO;
+ }
+
+ for (i = 0; i < ua->playback.queue_length; ++i) {
+ /* all initial URBs contain silence */
+ spin_lock_irq(&ua->lock);
+ frames = ua->rate_feedback[ua->rate_feedback_start];
+ add_with_wraparound(ua, &ua->rate_feedback_start, 1);
+ ua->rate_feedback_count--;
+ spin_unlock_irq(&ua->lock);
+ urb = &ua->playback.urbs[i]->urb;
+ urb->iso_frame_desc[0].length =
+ frames * ua->playback.frame_bytes;
+ memset(urb->transfer_buffer, 0,
+ urb->iso_frame_desc[0].length);
+ }
+
+ set_bit(USB_PLAYBACK_RUNNING, &ua->states);
+ err = submit_stream_urbs(ua, &ua->playback);
+ if (err < 0)
+ stop_usb_playback(ua);
+ return err;
+}
+
+static void abort_alsa_capture(struct ua101 *ua)
+{
+ if (test_bit(ALSA_CAPTURE_RUNNING, &ua->states))
+ snd_pcm_stop_xrun(ua->capture.substream);
+}
+
+static void abort_alsa_playback(struct ua101 *ua)
+{
+ if (test_bit(ALSA_PLAYBACK_RUNNING, &ua->states))
+ snd_pcm_stop_xrun(ua->playback.substream);
+}
+
+static int set_stream_hw(struct ua101 *ua, struct snd_pcm_substream *substream,
+ unsigned int channels)
+{
+ int err;
+
+ substream->runtime->hw.info =
+ SNDRV_PCM_INFO_MMAP |
+ SNDRV_PCM_INFO_MMAP_VALID |
+ SNDRV_PCM_INFO_BATCH |
+ SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_BLOCK_TRANSFER |
+ SNDRV_PCM_INFO_FIFO_IN_FRAMES;
+ substream->runtime->hw.formats = ua->format_bit;
+ substream->runtime->hw.rates = snd_pcm_rate_to_rate_bit(ua->rate);
+ substream->runtime->hw.rate_min = ua->rate;
+ substream->runtime->hw.rate_max = ua->rate;
+ substream->runtime->hw.channels_min = channels;
+ substream->runtime->hw.channels_max = channels;
+ substream->runtime->hw.buffer_bytes_max = 45000 * 1024;
+ substream->runtime->hw.period_bytes_min = 1;
+ substream->runtime->hw.period_bytes_max = UINT_MAX;
+ substream->runtime->hw.periods_min = 2;
+ substream->runtime->hw.periods_max = UINT_MAX;
+ err = snd_pcm_hw_constraint_minmax(substream->runtime,
+ SNDRV_PCM_HW_PARAM_PERIOD_TIME,
+ 1500000 / ua->packets_per_second,
+ UINT_MAX);
+ if (err < 0)
+ return err;
+ err = snd_pcm_hw_constraint_msbits(substream->runtime, 0, 32, 24);
+ return err;
+}
+
+static int capture_pcm_open(struct snd_pcm_substream *substream)
+{
+ struct ua101 *ua = substream->private_data;
+ int err;
+
+ ua->capture.substream = substream;
+ err = set_stream_hw(ua, substream, ua->capture.channels);
+ if (err < 0)
+ return err;
+ substream->runtime->hw.fifo_size =
+ DIV_ROUND_CLOSEST(ua->rate, ua->packets_per_second);
+ substream->runtime->delay = substream->runtime->hw.fifo_size;
+
+ mutex_lock(&ua->mutex);
+ err = start_usb_capture(ua);
+ if (err >= 0)
+ set_bit(ALSA_CAPTURE_OPEN, &ua->states);
+ mutex_unlock(&ua->mutex);
+ return err;
+}
+
+static int playback_pcm_open(struct snd_pcm_substream *substream)
+{
+ struct ua101 *ua = substream->private_data;
+ int err;
+
+ ua->playback.substream = substream;
+ err = set_stream_hw(ua, substream, ua->playback.channels);
+ if (err < 0)
+ return err;
+ substream->runtime->hw.fifo_size =
+ DIV_ROUND_CLOSEST(ua->rate * ua->playback.queue_length,
+ ua->packets_per_second);
+
+ mutex_lock(&ua->mutex);
+ err = start_usb_capture(ua);
+ if (err < 0)
+ goto error;
+ err = start_usb_playback(ua);
+ if (err < 0) {
+ if (!test_bit(ALSA_CAPTURE_OPEN, &ua->states))
+ stop_usb_capture(ua);
+ goto error;
+ }
+ set_bit(ALSA_PLAYBACK_OPEN, &ua->states);
+error:
+ mutex_unlock(&ua->mutex);
+ return err;
+}
+
+static int capture_pcm_close(struct snd_pcm_substream *substream)
+{
+ struct ua101 *ua = substream->private_data;
+
+ mutex_lock(&ua->mutex);
+ clear_bit(ALSA_CAPTURE_OPEN, &ua->states);
+ if (!test_bit(ALSA_PLAYBACK_OPEN, &ua->states))
+ stop_usb_capture(ua);
+ mutex_unlock(&ua->mutex);
+ return 0;
+}
+
+static int playback_pcm_close(struct snd_pcm_substream *substream)
+{
+ struct ua101 *ua = substream->private_data;
+
+ mutex_lock(&ua->mutex);
+ stop_usb_playback(ua);
+ clear_bit(ALSA_PLAYBACK_OPEN, &ua->states);
+ if (!test_bit(ALSA_CAPTURE_OPEN, &ua->states))
+ stop_usb_capture(ua);
+ mutex_unlock(&ua->mutex);
+ return 0;
+}
+
+static int capture_pcm_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *hw_params)
+{
+ struct ua101 *ua = substream->private_data;
+ int err;
+
+ mutex_lock(&ua->mutex);
+ err = start_usb_capture(ua);
+ mutex_unlock(&ua->mutex);
+ if (err < 0)
+ return err;
+
+ return snd_pcm_lib_alloc_vmalloc_buffer(substream,
+ params_buffer_bytes(hw_params));
+}
+
+static int playback_pcm_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *hw_params)
+{
+ struct ua101 *ua = substream->private_data;
+ int err;
+
+ mutex_lock(&ua->mutex);
+ err = start_usb_capture(ua);
+ if (err >= 0)
+ err = start_usb_playback(ua);
+ mutex_unlock(&ua->mutex);
+ if (err < 0)
+ return err;
+
+ return snd_pcm_lib_alloc_vmalloc_buffer(substream,
+ params_buffer_bytes(hw_params));
+}
+
+static int ua101_pcm_hw_free(struct snd_pcm_substream *substream)
+{
+ return snd_pcm_lib_free_vmalloc_buffer(substream);
+}
+
+static int capture_pcm_prepare(struct snd_pcm_substream *substream)
+{
+ struct ua101 *ua = substream->private_data;
+ int err;
+
+ mutex_lock(&ua->mutex);
+ err = start_usb_capture(ua);
+ mutex_unlock(&ua->mutex);
+ if (err < 0)
+ return err;
+
+ /*
+ * The EHCI driver schedules the first packet of an iso stream at 10 ms
+ * in the future, i.e., no data is actually captured for that long.
+ * Take the wait here so that the stream is known to be actually
+ * running when the start trigger has been called.
+ */
+ wait_event(ua->alsa_capture_wait,
+ test_bit(CAPTURE_URB_COMPLETED, &ua->states) ||
+ !test_bit(USB_CAPTURE_RUNNING, &ua->states));
+ if (test_bit(DISCONNECTED, &ua->states))
+ return -ENODEV;
+ if (!test_bit(USB_CAPTURE_RUNNING, &ua->states))
+ return -EIO;
+
+ ua->capture.period_pos = 0;
+ ua->capture.buffer_pos = 0;
+ return 0;
+}
+
+static int playback_pcm_prepare(struct snd_pcm_substream *substream)
+{
+ struct ua101 *ua = substream->private_data;
+ int err;
+
+ mutex_lock(&ua->mutex);
+ err = start_usb_capture(ua);
+ if (err >= 0)
+ err = start_usb_playback(ua);
+ mutex_unlock(&ua->mutex);
+ if (err < 0)
+ return err;
+
+ /* see the comment in capture_pcm_prepare() */
+ wait_event(ua->alsa_playback_wait,
+ test_bit(PLAYBACK_URB_COMPLETED, &ua->states) ||
+ !test_bit(USB_PLAYBACK_RUNNING, &ua->states));
+ if (test_bit(DISCONNECTED, &ua->states))
+ return -ENODEV;
+ if (!test_bit(USB_PLAYBACK_RUNNING, &ua->states))
+ return -EIO;
+
+ substream->runtime->delay = 0;
+ ua->playback.period_pos = 0;
+ ua->playback.buffer_pos = 0;
+ return 0;
+}
+
+static int capture_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+ struct ua101 *ua = substream->private_data;
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ if (!test_bit(USB_CAPTURE_RUNNING, &ua->states))
+ return -EIO;
+ set_bit(ALSA_CAPTURE_RUNNING, &ua->states);
+ return 0;
+ case SNDRV_PCM_TRIGGER_STOP:
+ clear_bit(ALSA_CAPTURE_RUNNING, &ua->states);
+ return 0;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int playback_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+ struct ua101 *ua = substream->private_data;
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ if (!test_bit(USB_PLAYBACK_RUNNING, &ua->states))
+ return -EIO;
+ set_bit(ALSA_PLAYBACK_RUNNING, &ua->states);
+ return 0;
+ case SNDRV_PCM_TRIGGER_STOP:
+ clear_bit(ALSA_PLAYBACK_RUNNING, &ua->states);
+ return 0;
+ default:
+ return -EINVAL;
+ }
+}
+
+static inline snd_pcm_uframes_t ua101_pcm_pointer(struct ua101 *ua,
+ struct ua101_stream *stream)
+{
+ unsigned long flags;
+ unsigned int pos;
+
+ spin_lock_irqsave(&ua->lock, flags);
+ pos = stream->buffer_pos;
+ spin_unlock_irqrestore(&ua->lock, flags);
+ return pos;
+}
+
+static snd_pcm_uframes_t capture_pcm_pointer(struct snd_pcm_substream *subs)
+{
+ struct ua101 *ua = subs->private_data;
+
+ return ua101_pcm_pointer(ua, &ua->capture);
+}
+
+static snd_pcm_uframes_t playback_pcm_pointer(struct snd_pcm_substream *subs)
+{
+ struct ua101 *ua = subs->private_data;
+
+ return ua101_pcm_pointer(ua, &ua->playback);
+}
+
+static struct snd_pcm_ops capture_pcm_ops = {
+ .open = capture_pcm_open,
+ .close = capture_pcm_close,
+ .ioctl = snd_pcm_lib_ioctl,
+ .hw_params = capture_pcm_hw_params,
+ .hw_free = ua101_pcm_hw_free,
+ .prepare = capture_pcm_prepare,
+ .trigger = capture_pcm_trigger,
+ .pointer = capture_pcm_pointer,
+ .page = snd_pcm_lib_get_vmalloc_page,
+ .mmap = snd_pcm_lib_mmap_vmalloc,
+};
+
+static struct snd_pcm_ops playback_pcm_ops = {
+ .open = playback_pcm_open,
+ .close = playback_pcm_close,
+ .ioctl = snd_pcm_lib_ioctl,
+ .hw_params = playback_pcm_hw_params,
+ .hw_free = ua101_pcm_hw_free,
+ .prepare = playback_pcm_prepare,
+ .trigger = playback_pcm_trigger,
+ .pointer = playback_pcm_pointer,
+ .page = snd_pcm_lib_get_vmalloc_page,
+ .mmap = snd_pcm_lib_mmap_vmalloc,
+};
+
+static const struct uac_format_type_i_discrete_descriptor *
+find_format_descriptor(struct usb_interface *interface)
+{
+ struct usb_host_interface *alt;
+ u8 *extra;
+ int extralen;
+
+ if (interface->num_altsetting != 2) {
+ dev_err(&interface->dev, "invalid num_altsetting\n");
+ return NULL;
+ }
+
+ alt = &interface->altsetting[0];
+ if (alt->desc.bNumEndpoints != 0) {
+ dev_err(&interface->dev, "invalid bNumEndpoints\n");
+ return NULL;
+ }
+
+ alt = &interface->altsetting[1];
+ if (alt->desc.bNumEndpoints != 1) {
+ dev_err(&interface->dev, "invalid bNumEndpoints\n");
+ return NULL;
+ }
+
+ extra = alt->extra;
+ extralen = alt->extralen;
+ while (extralen >= sizeof(struct usb_descriptor_header)) {
+ struct uac_format_type_i_discrete_descriptor *desc;
+
+ desc = (struct uac_format_type_i_discrete_descriptor *)extra;
+ if (desc->bLength > extralen) {
+ dev_err(&interface->dev, "descriptor overflow\n");
+ return NULL;
+ }
+ if (desc->bLength == UAC_FORMAT_TYPE_I_DISCRETE_DESC_SIZE(1) &&
+ desc->bDescriptorType == USB_DT_CS_INTERFACE &&
+ desc->bDescriptorSubtype == UAC_FORMAT_TYPE) {
+ if (desc->bFormatType != UAC_FORMAT_TYPE_I_PCM ||
+ desc->bSamFreqType != 1) {
+ dev_err(&interface->dev,
+ "invalid format type\n");
+ return NULL;
+ }
+ return desc;
+ }
+ extralen -= desc->bLength;
+ extra += desc->bLength;
+ }
+ dev_err(&interface->dev, "sample format descriptor not found\n");
+ return NULL;
+}
+
+static int detect_usb_format(struct ua101 *ua)
+{
+ const struct uac_format_type_i_discrete_descriptor *fmt_capture;
+ const struct uac_format_type_i_discrete_descriptor *fmt_playback;
+ const struct usb_endpoint_descriptor *epd;
+ unsigned int rate2;
+
+ fmt_capture = find_format_descriptor(ua->intf[INTF_CAPTURE]);
+ fmt_playback = find_format_descriptor(ua->intf[INTF_PLAYBACK]);
+ if (!fmt_capture || !fmt_playback)
+ return -ENXIO;
+
+ switch (fmt_capture->bSubframeSize) {
+ case 3:
+ ua->format_bit = SNDRV_PCM_FMTBIT_S24_3LE;
+ break;
+ case 4:
+ ua->format_bit = SNDRV_PCM_FMTBIT_S32_LE;
+ break;
+ default:
+ dev_err(&ua->dev->dev, "sample width is not 24 or 32 bits\n");
+ return -ENXIO;
+ }
+ if (fmt_capture->bSubframeSize != fmt_playback->bSubframeSize) {
+ dev_err(&ua->dev->dev,
+ "playback/capture sample widths do not match\n");
+ return -ENXIO;
+ }
+
+ if (fmt_capture->bBitResolution != 24 ||
+ fmt_playback->bBitResolution != 24) {
+ dev_err(&ua->dev->dev, "sample width is not 24 bits\n");
+ return -ENXIO;
+ }
+
+ ua->rate = combine_triple(fmt_capture->tSamFreq[0]);
+ rate2 = combine_triple(fmt_playback->tSamFreq[0]);
+ if (ua->rate != rate2) {
+ dev_err(&ua->dev->dev,
+ "playback/capture rates do not match: %u/%u\n",
+ rate2, ua->rate);
+ return -ENXIO;
+ }
+
+ switch (ua->dev->speed) {
+ case USB_SPEED_FULL:
+ ua->packets_per_second = 1000;
+ break;
+ case USB_SPEED_HIGH:
+ ua->packets_per_second = 8000;
+ break;
+ default:
+ dev_err(&ua->dev->dev, "unknown device speed\n");
+ return -ENXIO;
+ }
+
+ ua->capture.channels = fmt_capture->bNrChannels;
+ ua->playback.channels = fmt_playback->bNrChannels;
+ ua->capture.frame_bytes =
+ fmt_capture->bSubframeSize * ua->capture.channels;
+ ua->playback.frame_bytes =
+ fmt_playback->bSubframeSize * ua->playback.channels;
+
+ epd = &ua->intf[INTF_CAPTURE]->altsetting[1].endpoint[0].desc;
+ if (!usb_endpoint_is_isoc_in(epd)) {
+ dev_err(&ua->dev->dev, "invalid capture endpoint\n");
+ return -ENXIO;
+ }
+ ua->capture.usb_pipe = usb_rcvisocpipe(ua->dev, usb_endpoint_num(epd));
+ ua->capture.max_packet_bytes = le16_to_cpu(epd->wMaxPacketSize);
+
+ epd = &ua->intf[INTF_PLAYBACK]->altsetting[1].endpoint[0].desc;
+ if (!usb_endpoint_is_isoc_out(epd)) {
+ dev_err(&ua->dev->dev, "invalid playback endpoint\n");
+ return -ENXIO;
+ }
+ ua->playback.usb_pipe = usb_sndisocpipe(ua->dev, usb_endpoint_num(epd));
+ ua->playback.max_packet_bytes = le16_to_cpu(epd->wMaxPacketSize);
+ return 0;
+}
+
+static int alloc_stream_buffers(struct ua101 *ua, struct ua101_stream *stream)
+{
+ unsigned int remaining_packets, packets, packets_per_page, i;
+ size_t size;
+
+ stream->queue_length = queue_length;
+ stream->queue_length = max(stream->queue_length,
+ (unsigned int)MIN_QUEUE_LENGTH);
+ stream->queue_length = min(stream->queue_length,
+ (unsigned int)MAX_QUEUE_LENGTH);
+
+ /*
+ * The cache pool sizes used by usb_alloc_coherent() (128, 512, 2048) are
+ * quite bad when used with the packet sizes of this device (e.g. 280,
+ * 520, 624). Therefore, we allocate and subdivide entire pages, using
+ * a smaller buffer only for the last chunk.
+ */
+ remaining_packets = stream->queue_length;
+ packets_per_page = PAGE_SIZE / stream->max_packet_bytes;
+ for (i = 0; i < ARRAY_SIZE(stream->buffers); ++i) {
+ packets = min(remaining_packets, packets_per_page);
+ size = packets * stream->max_packet_bytes;
+ stream->buffers[i].addr =
+ usb_alloc_coherent(ua->dev, size, GFP_KERNEL,
+ &stream->buffers[i].dma);
+ if (!stream->buffers[i].addr)
+ return -ENOMEM;
+ stream->buffers[i].size = size;
+ remaining_packets -= packets;
+ if (!remaining_packets)
+ break;
+ }
+ if (remaining_packets) {
+ dev_err(&ua->dev->dev, "too many packets\n");
+ return -ENXIO;
+ }
+ return 0;
+}
+
+static void free_stream_buffers(struct ua101 *ua, struct ua101_stream *stream)
+{
+ unsigned int i;
+
+ for (i = 0; i < ARRAY_SIZE(stream->buffers); ++i)
+ usb_free_coherent(ua->dev,
+ stream->buffers[i].size,
+ stream->buffers[i].addr,
+ stream->buffers[i].dma);
+}
+
+static int alloc_stream_urbs(struct ua101 *ua, struct ua101_stream *stream,
+ void (*urb_complete)(struct urb *))
+{
+ unsigned max_packet_size = stream->max_packet_bytes;
+ struct ua101_urb *urb;
+ unsigned int b, u = 0;
+
+ for (b = 0; b < ARRAY_SIZE(stream->buffers); ++b) {
+ unsigned int size = stream->buffers[b].size;
+ u8 *addr = stream->buffers[b].addr;
+ dma_addr_t dma = stream->buffers[b].dma;
+
+ while (size >= max_packet_size) {
+ if (u >= stream->queue_length)
+ goto bufsize_error;
+ urb = kmalloc(sizeof(*urb), GFP_KERNEL);
+ if (!urb)
+ return -ENOMEM;
+ usb_init_urb(&urb->urb);
+ urb->urb.dev = ua->dev;
+ urb->urb.pipe = stream->usb_pipe;
+ urb->urb.transfer_flags = URB_NO_TRANSFER_DMA_MAP;
+ urb->urb.transfer_buffer = addr;
+ urb->urb.transfer_dma = dma;
+ urb->urb.transfer_buffer_length = max_packet_size;
+ urb->urb.number_of_packets = 1;
+ urb->urb.interval = 1;
+ urb->urb.context = ua;
+ urb->urb.complete = urb_complete;
+ urb->urb.iso_frame_desc[0].offset = 0;
+ urb->urb.iso_frame_desc[0].length = max_packet_size;
+ stream->urbs[u++] = urb;
+ size -= max_packet_size;
+ addr += max_packet_size;
+ dma += max_packet_size;
+ }
+ }
+ if (u == stream->queue_length)
+ return 0;
+bufsize_error:
+ dev_err(&ua->dev->dev, "internal buffer size error\n");
+ return -ENXIO;
+}
+
+static void free_stream_urbs(struct ua101_stream *stream)
+{
+ unsigned int i;
+
+ for (i = 0; i < stream->queue_length; ++i) {
+ kfree(stream->urbs[i]);
+ stream->urbs[i] = NULL;
+ }
+}
+
+static void free_usb_related_resources(struct ua101 *ua,
+ struct usb_interface *interface)
+{
+ unsigned int i;
+ struct usb_interface *intf;
+
+ mutex_lock(&ua->mutex);
+ free_stream_urbs(&ua->capture);
+ free_stream_urbs(&ua->playback);
+ mutex_unlock(&ua->mutex);
+ free_stream_buffers(ua, &ua->capture);
+ free_stream_buffers(ua, &ua->playback);
+
+ for (i = 0; i < ARRAY_SIZE(ua->intf); ++i) {
+ mutex_lock(&ua->mutex);
+ intf = ua->intf[i];
+ ua->intf[i] = NULL;
+ mutex_unlock(&ua->mutex);
+ if (intf) {
+ usb_set_intfdata(intf, NULL);
+ if (intf != interface)
+ usb_driver_release_interface(&ua101_driver,
+ intf);
+ }
+ }
+}
+
+static void ua101_card_free(struct snd_card *card)
+{
+ struct ua101 *ua = card->private_data;
+
+ mutex_destroy(&ua->mutex);
+}
+
+static int ua101_probe(struct usb_interface *interface,
+ const struct usb_device_id *usb_id)
+{
+ static const struct snd_usb_midi_endpoint_info midi_ep = {
+ .out_cables = 0x0001,
+ .in_cables = 0x0001
+ };
+ static const struct snd_usb_audio_quirk midi_quirk = {
+ .type = QUIRK_MIDI_FIXED_ENDPOINT,
+ .data = &midi_ep
+ };
+ static const int intf_numbers[2][3] = {
+ { /* UA-101 */
+ [INTF_PLAYBACK] = 0,
+ [INTF_CAPTURE] = 1,
+ [INTF_MIDI] = 2,
+ },
+ { /* UA-1000 */
+ [INTF_CAPTURE] = 1,
+ [INTF_PLAYBACK] = 2,
+ [INTF_MIDI] = 3,
+ },
+ };
+ struct snd_card *card;
+ struct ua101 *ua;
+ unsigned int card_index, i;
+ int is_ua1000;
+ const char *name;
+ char usb_path[32];
+ int err;
+
+ is_ua1000 = usb_id->idProduct == 0x0044;
+
+ if (interface->altsetting->desc.bInterfaceNumber !=
+ intf_numbers[is_ua1000][0])
+ return -ENODEV;
+
+ mutex_lock(&devices_mutex);
+
+ for (card_index = 0; card_index < SNDRV_CARDS; ++card_index)
+ if (enable[card_index] && !(devices_used & (1 << card_index)))
+ break;
+ if (card_index >= SNDRV_CARDS) {
+ mutex_unlock(&devices_mutex);
+ return -ENOENT;
+ }
+ err = snd_card_new(&interface->dev,
+ index[card_index], id[card_index], THIS_MODULE,
+ sizeof(*ua), &card);
+ if (err < 0) {
+ mutex_unlock(&devices_mutex);
+ return err;
+ }
+ card->private_free = ua101_card_free;
+ ua = card->private_data;
+ ua->dev = interface_to_usbdev(interface);
+ ua->card = card;
+ ua->card_index = card_index;
+ INIT_LIST_HEAD(&ua->midi_list);
+ spin_lock_init(&ua->lock);
+ mutex_init(&ua->mutex);
+ INIT_LIST_HEAD(&ua->ready_playback_urbs);
+ tasklet_init(&ua->playback_tasklet,
+ playback_tasklet, (unsigned long)ua);
+ init_waitqueue_head(&ua->alsa_capture_wait);
+ init_waitqueue_head(&ua->rate_feedback_wait);
+ init_waitqueue_head(&ua->alsa_playback_wait);
+
+ ua->intf[0] = interface;
+ for (i = 1; i < ARRAY_SIZE(ua->intf); ++i) {
+ ua->intf[i] = usb_ifnum_to_if(ua->dev,
+ intf_numbers[is_ua1000][i]);
+ if (!ua->intf[i]) {
+ dev_err(&ua->dev->dev, "interface %u not found\n",
+ intf_numbers[is_ua1000][i]);
+ err = -ENXIO;
+ goto probe_error;
+ }
+ err = usb_driver_claim_interface(&ua101_driver,
+ ua->intf[i], ua);
+ if (err < 0) {
+ ua->intf[i] = NULL;
+ err = -EBUSY;
+ goto probe_error;
+ }
+ }
+
+ err = detect_usb_format(ua);
+ if (err < 0)
+ goto probe_error;
+
+ name = usb_id->idProduct == 0x0044 ? "UA-1000" : "UA-101";
+ strcpy(card->driver, "UA-101");
+ strcpy(card->shortname, name);
+ usb_make_path(ua->dev, usb_path, sizeof(usb_path));
+ snprintf(ua->card->longname, sizeof(ua->card->longname),
+ "EDIROL %s (serial %s), %u Hz at %s, %s speed", name,
+ ua->dev->serial ? ua->dev->serial : "?", ua->rate, usb_path,
+ ua->dev->speed == USB_SPEED_HIGH ? "high" : "full");
+
+ err = alloc_stream_buffers(ua, &ua->capture);
+ if (err < 0)
+ goto probe_error;
+ err = alloc_stream_buffers(ua, &ua->playback);
+ if (err < 0)
+ goto probe_error;
+
+ err = alloc_stream_urbs(ua, &ua->capture, capture_urb_complete);
+ if (err < 0)
+ goto probe_error;
+ err = alloc_stream_urbs(ua, &ua->playback, playback_urb_complete);
+ if (err < 0)
+ goto probe_error;
+
+ err = snd_pcm_new(card, name, 0, 1, 1, &ua->pcm);
+ if (err < 0)
+ goto probe_error;
+ ua->pcm->private_data = ua;
+ strcpy(ua->pcm->name, name);
+ snd_pcm_set_ops(ua->pcm, SNDRV_PCM_STREAM_PLAYBACK, &playback_pcm_ops);
+ snd_pcm_set_ops(ua->pcm, SNDRV_PCM_STREAM_CAPTURE, &capture_pcm_ops);
+
+ err = snd_usbmidi_create(card, ua->intf[INTF_MIDI],
+ &ua->midi_list, &midi_quirk);
+ if (err < 0)
+ goto probe_error;
+
+ err = snd_card_register(card);
+ if (err < 0)
+ goto probe_error;
+
+ usb_set_intfdata(interface, ua);
+ devices_used |= 1 << card_index;
+
+ mutex_unlock(&devices_mutex);
+ return 0;
+
+probe_error:
+ free_usb_related_resources(ua, interface);
+ snd_card_free(card);
+ mutex_unlock(&devices_mutex);
+ return err;
+}
+
+static void ua101_disconnect(struct usb_interface *interface)
+{
+ struct ua101 *ua = usb_get_intfdata(interface);
+ struct list_head *midi;
+
+ if (!ua)
+ return;
+
+ mutex_lock(&devices_mutex);
+
+ set_bit(DISCONNECTED, &ua->states);
+ wake_up(&ua->rate_feedback_wait);
+
+ /* make sure that userspace cannot create new requests */
+ snd_card_disconnect(ua->card);
+
+ /* make sure that there are no pending USB requests */
+ list_for_each(midi, &ua->midi_list)
+ snd_usbmidi_disconnect(midi);
+ abort_alsa_playback(ua);
+ abort_alsa_capture(ua);
+ mutex_lock(&ua->mutex);
+ stop_usb_playback(ua);
+ stop_usb_capture(ua);
+ mutex_unlock(&ua->mutex);
+
+ free_usb_related_resources(ua, interface);
+
+ devices_used &= ~(1 << ua->card_index);
+
+ snd_card_free_when_closed(ua->card);
+
+ mutex_unlock(&devices_mutex);
+}
+
+static struct usb_device_id ua101_ids[] = {
+ { USB_DEVICE(0x0582, 0x0044) }, /* UA-1000 high speed */
+ { USB_DEVICE(0x0582, 0x007d) }, /* UA-101 high speed */
+ { USB_DEVICE(0x0582, 0x008d) }, /* UA-101 full speed */
+ { }
+};
+MODULE_DEVICE_TABLE(usb, ua101_ids);
+
+static struct usb_driver ua101_driver = {
+ .name = "snd-ua101",
+ .id_table = ua101_ids,
+ .probe = ua101_probe,
+ .disconnect = ua101_disconnect,
+#if 0
+ .suspend = ua101_suspend,
+ .resume = ua101_resume,
+#endif
+};
+
+module_usb_driver(ua101_driver);
diff --git a/sound/usb/mixer.c b/sound/usb/mixer.c
new file mode 100644
index 000000000..8b7e391dd
--- /dev/null
+++ b/sound/usb/mixer.c
@@ -0,0 +1,2578 @@
+/*
+ * (Tentative) USB Audio Driver for ALSA
+ *
+ * Mixer control part
+ *
+ * Copyright (c) 2002 by Takashi Iwai <tiwai@suse.de>
+ *
+ * Many codes borrowed from audio.c by
+ * Alan Cox (alan@lxorguk.ukuu.org.uk)
+ * Thomas Sailer (sailer@ife.ee.ethz.ch)
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+
+/*
+ * TODOs, for both the mixer and the streaming interfaces:
+ *
+ * - support for UAC2 effect units
+ * - support for graphical equalizers
+ * - RANGE and MEM set commands (UAC2)
+ * - RANGE and MEM interrupt dispatchers (UAC2)
+ * - audio channel clustering (UAC2)
+ * - audio sample rate converter units (UAC2)
+ * - proper handling of clock multipliers (UAC2)
+ * - dispatch clock change notifications (UAC2)
+ * - stop PCM streams which use a clock that became invalid
+ * - stop PCM streams which use a clock selector that has changed
+ * - parse available sample rates again when clock sources changed
+ */
+
+#include <linux/bitops.h>
+#include <linux/init.h>
+#include <linux/list.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <linux/usb.h>
+#include <linux/usb/audio.h>
+#include <linux/usb/audio-v2.h>
+
+#include <sound/core.h>
+#include <sound/control.h>
+#include <sound/hwdep.h>
+#include <sound/info.h>
+#include <sound/tlv.h>
+
+#include "usbaudio.h"
+#include "mixer.h"
+#include "helper.h"
+#include "mixer_quirks.h"
+#include "power.h"
+
+#define MAX_ID_ELEMS 256
+
+struct usb_audio_term {
+ int id;
+ int type;
+ int channels;
+ unsigned int chconfig;
+ int name;
+};
+
+struct usbmix_name_map;
+
+struct mixer_build {
+ struct snd_usb_audio *chip;
+ struct usb_mixer_interface *mixer;
+ unsigned char *buffer;
+ unsigned int buflen;
+ DECLARE_BITMAP(unitbitmap, MAX_ID_ELEMS);
+ struct usb_audio_term oterm;
+ const struct usbmix_name_map *map;
+ const struct usbmix_selector_map *selector_map;
+};
+
+/*E-mu 0202/0404/0204 eXtension Unit(XU) control*/
+enum {
+ USB_XU_CLOCK_RATE = 0xe301,
+ USB_XU_CLOCK_SOURCE = 0xe302,
+ USB_XU_DIGITAL_IO_STATUS = 0xe303,
+ USB_XU_DEVICE_OPTIONS = 0xe304,
+ USB_XU_DIRECT_MONITORING = 0xe305,
+ USB_XU_METERING = 0xe306
+};
+enum {
+ USB_XU_CLOCK_SOURCE_SELECTOR = 0x02, /* clock source*/
+ USB_XU_CLOCK_RATE_SELECTOR = 0x03, /* clock rate */
+ USB_XU_DIGITAL_FORMAT_SELECTOR = 0x01, /* the spdif format */
+ USB_XU_SOFT_LIMIT_SELECTOR = 0x03 /* soft limiter */
+};
+
+/*
+ * manual mapping of mixer names
+ * if the mixer topology is too complicated and the parsed names are
+ * ambiguous, add the entries in usbmixer_maps.c.
+ */
+#include "mixer_maps.c"
+
+static const struct usbmix_name_map *
+find_map(struct mixer_build *state, int unitid, int control)
+{
+ const struct usbmix_name_map *p = state->map;
+
+ if (!p)
+ return NULL;
+
+ for (p = state->map; p->id; p++) {
+ if (p->id == unitid &&
+ (!control || !p->control || control == p->control))
+ return p;
+ }
+ return NULL;
+}
+
+/* get the mapped name if the unit matches */
+static int
+check_mapped_name(const struct usbmix_name_map *p, char *buf, int buflen)
+{
+ if (!p || !p->name)
+ return 0;
+
+ buflen--;
+ return strlcpy(buf, p->name, buflen);
+}
+
+/* ignore the error value if ignore_ctl_error flag is set */
+#define filter_error(cval, err) \
+ ((cval)->head.mixer->ignore_ctl_error ? 0 : (err))
+
+/* check whether the control should be ignored */
+static inline int
+check_ignored_ctl(const struct usbmix_name_map *p)
+{
+ if (!p || p->name || p->dB)
+ return 0;
+ return 1;
+}
+
+/* dB mapping */
+static inline void check_mapped_dB(const struct usbmix_name_map *p,
+ struct usb_mixer_elem_info *cval)
+{
+ if (p && p->dB) {
+ cval->dBmin = p->dB->min;
+ cval->dBmax = p->dB->max;
+ cval->initialized = 1;
+ }
+}
+
+/* get the mapped selector source name */
+static int check_mapped_selector_name(struct mixer_build *state, int unitid,
+ int index, char *buf, int buflen)
+{
+ const struct usbmix_selector_map *p;
+
+ if (!state->selector_map)
+ return 0;
+ for (p = state->selector_map; p->id; p++) {
+ if (p->id == unitid && index < p->count)
+ return strlcpy(buf, p->names[index], buflen);
+ }
+ return 0;
+}
+
+/*
+ * find an audio control unit with the given unit id
+ */
+static void *find_audio_control_unit(struct mixer_build *state,
+ unsigned char unit)
+{
+ /* we just parse the header */
+ struct uac_feature_unit_descriptor *hdr = NULL;
+
+ while ((hdr = snd_usb_find_desc(state->buffer, state->buflen, hdr,
+ USB_DT_CS_INTERFACE)) != NULL) {
+ if (hdr->bLength >= 4 &&
+ hdr->bDescriptorSubtype >= UAC_INPUT_TERMINAL &&
+ hdr->bDescriptorSubtype <= UAC2_SAMPLE_RATE_CONVERTER &&
+ hdr->bUnitID == unit)
+ return hdr;
+ }
+
+ return NULL;
+}
+
+/*
+ * copy a string with the given id
+ */
+static int snd_usb_copy_string_desc(struct mixer_build *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(struct usb_mixer_elem_info *cval, int val)
+{
+ switch (cval->val_type) {
+ case USB_MIXER_BOOLEAN:
+ return !!val;
+ case USB_MIXER_INV_BOOLEAN:
+ return !val;
+ case USB_MIXER_U8:
+ val &= 0xff;
+ break;
+ case USB_MIXER_S8:
+ val &= 0xff;
+ if (val >= 0x80)
+ val -= 0x100;
+ break;
+ case USB_MIXER_U16:
+ val &= 0xffff;
+ break;
+ case USB_MIXER_S16:
+ val &= 0xffff;
+ if (val >= 0x8000)
+ val -= 0x10000;
+ break;
+ }
+ return val;
+}
+
+/*
+ * convert from the zero-based int to the byte/word for usb descriptor
+ */
+static int convert_bytes_value(struct usb_mixer_elem_info *cval, int val)
+{
+ switch (cval->val_type) {
+ case USB_MIXER_BOOLEAN:
+ return !!val;
+ case USB_MIXER_INV_BOOLEAN:
+ return !val;
+ case USB_MIXER_S8:
+ case USB_MIXER_U8:
+ return val & 0xff;
+ case USB_MIXER_S16:
+ case USB_MIXER_U16:
+ return val & 0xffff;
+ }
+ return 0; /* not reached */
+}
+
+static int get_relative_value(struct usb_mixer_elem_info *cval, int val)
+{
+ if (!cval->res)
+ cval->res = 1;
+ if (val < cval->min)
+ return 0;
+ else if (val >= cval->max)
+ return (cval->max - cval->min + cval->res - 1) / cval->res;
+ else
+ return (val - cval->min) / cval->res;
+}
+
+static int get_abs_value(struct usb_mixer_elem_info *cval, int val)
+{
+ if (val < 0)
+ return cval->min;
+ if (!cval->res)
+ cval->res = 1;
+ val *= cval->res;
+ val += cval->min;
+ if (val > cval->max)
+ return cval->max;
+ return val;
+}
+
+
+/*
+ * retrieve a mixer value
+ */
+
+static int get_ctl_value_v1(struct usb_mixer_elem_info *cval, int request,
+ int validx, int *value_ret)
+{
+ struct snd_usb_audio *chip = cval->head.mixer->chip;
+ unsigned char buf[2];
+ int val_len = cval->val_type >= USB_MIXER_S16 ? 2 : 1;
+ int timeout = 10;
+ int idx = 0, err;
+
+ err = snd_usb_autoresume(chip);
+ if (err < 0)
+ return -EIO;
+
+ down_read(&chip->shutdown_rwsem);
+ while (timeout-- > 0) {
+ if (chip->shutdown)
+ break;
+ idx = snd_usb_ctrl_intf(chip) | (cval->head.id << 8);
+ if (snd_usb_ctl_msg(chip->dev, usb_rcvctrlpipe(chip->dev, 0), request,
+ USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_IN,
+ validx, idx, buf, val_len) >= val_len) {
+ *value_ret = convert_signed_value(cval, snd_usb_combine_bytes(buf, val_len));
+ err = 0;
+ goto out;
+ }
+ }
+ usb_audio_dbg(chip,
+ "cannot get ctl value: req = %#x, wValue = %#x, wIndex = %#x, type = %d\n",
+ request, validx, idx, cval->val_type);
+ err = -EINVAL;
+
+ out:
+ up_read(&chip->shutdown_rwsem);
+ snd_usb_autosuspend(chip);
+ return err;
+}
+
+static int get_ctl_value_v2(struct usb_mixer_elem_info *cval, int request,
+ int validx, int *value_ret)
+{
+ struct snd_usb_audio *chip = cval->head.mixer->chip;
+ unsigned char buf[2 + 3 * sizeof(__u16)]; /* enough space for one range */
+ unsigned char *val;
+ int idx = 0, ret, size;
+ __u8 bRequest;
+
+ if (request == UAC_GET_CUR) {
+ bRequest = UAC2_CS_CUR;
+ size = sizeof(__u16);
+ } else {
+ bRequest = UAC2_CS_RANGE;
+ size = sizeof(buf);
+ }
+
+ memset(buf, 0, sizeof(buf));
+
+ ret = snd_usb_autoresume(chip) ? -EIO : 0;
+ if (ret)
+ goto error;
+
+ down_read(&chip->shutdown_rwsem);
+ if (chip->shutdown) {
+ ret = -ENODEV;
+ } else {
+ idx = snd_usb_ctrl_intf(chip) | (cval->head.id << 8);
+ ret = snd_usb_ctl_msg(chip->dev, usb_rcvctrlpipe(chip->dev, 0), bRequest,
+ USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_IN,
+ validx, idx, buf, size);
+ }
+ up_read(&chip->shutdown_rwsem);
+ snd_usb_autosuspend(chip);
+
+ if (ret < 0) {
+error:
+ usb_audio_err(chip,
+ "cannot get ctl value: req = %#x, wValue = %#x, wIndex = %#x, type = %d\n",
+ request, validx, idx, cval->val_type);
+ return ret;
+ }
+
+ /* FIXME: how should we handle multiple triplets here? */
+
+ switch (request) {
+ case UAC_GET_CUR:
+ val = buf;
+ break;
+ case UAC_GET_MIN:
+ val = buf + sizeof(__u16);
+ break;
+ case UAC_GET_MAX:
+ val = buf + sizeof(__u16) * 2;
+ break;
+ case UAC_GET_RES:
+ val = buf + sizeof(__u16) * 3;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ *value_ret = convert_signed_value(cval, snd_usb_combine_bytes(val, sizeof(__u16)));
+
+ return 0;
+}
+
+static int get_ctl_value(struct usb_mixer_elem_info *cval, int request,
+ int validx, int *value_ret)
+{
+ validx += cval->idx_off;
+
+ return (cval->head.mixer->protocol == UAC_VERSION_1) ?
+ get_ctl_value_v1(cval, request, validx, value_ret) :
+ get_ctl_value_v2(cval, request, validx, value_ret);
+}
+
+static int get_cur_ctl_value(struct usb_mixer_elem_info *cval,
+ int validx, int *value)
+{
+ return get_ctl_value(cval, UAC_GET_CUR, validx, value);
+}
+
+/* channel = 0: master, 1 = first channel */
+static inline int get_cur_mix_raw(struct usb_mixer_elem_info *cval,
+ int channel, int *value)
+{
+ return get_ctl_value(cval, UAC_GET_CUR,
+ (cval->control << 8) | channel,
+ value);
+}
+
+int snd_usb_get_cur_mix_value(struct usb_mixer_elem_info *cval,
+ int channel, int index, int *value)
+{
+ int err;
+
+ if (cval->cached & (1 << channel)) {
+ *value = cval->cache_val[index];
+ return 0;
+ }
+ err = get_cur_mix_raw(cval, channel, value);
+ if (err < 0) {
+ if (!cval->head.mixer->ignore_ctl_error)
+ usb_audio_dbg(cval->head.mixer->chip,
+ "cannot get current value for control %d ch %d: err = %d\n",
+ cval->control, channel, err);
+ return err;
+ }
+ cval->cached |= 1 << channel;
+ cval->cache_val[index] = *value;
+ return 0;
+}
+
+/*
+ * set a mixer value
+ */
+
+int snd_usb_mixer_set_ctl_value(struct usb_mixer_elem_info *cval,
+ int request, int validx, int value_set)
+{
+ struct snd_usb_audio *chip = cval->head.mixer->chip;
+ unsigned char buf[2];
+ int idx = 0, val_len, err, timeout = 10;
+
+ validx += cval->idx_off;
+
+ if (cval->head.mixer->protocol == UAC_VERSION_1) {
+ val_len = cval->val_type >= USB_MIXER_S16 ? 2 : 1;
+ } else { /* UAC_VERSION_2 */
+ /* audio class v2 controls are always 2 bytes in size */
+ val_len = sizeof(__u16);
+
+ /* FIXME */
+ if (request != UAC_SET_CUR) {
+ usb_audio_dbg(chip, "RANGE setting not yet supported\n");
+ return -EINVAL;
+ }
+
+ request = UAC2_CS_CUR;
+ }
+
+ value_set = convert_bytes_value(cval, value_set);
+ buf[0] = value_set & 0xff;
+ buf[1] = (value_set >> 8) & 0xff;
+ err = snd_usb_autoresume(chip);
+ if (err < 0)
+ return -EIO;
+ down_read(&chip->shutdown_rwsem);
+ while (timeout-- > 0) {
+ if (chip->shutdown)
+ break;
+ idx = snd_usb_ctrl_intf(chip) | (cval->head.id << 8);
+ if (snd_usb_ctl_msg(chip->dev,
+ usb_sndctrlpipe(chip->dev, 0), request,
+ USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_OUT,
+ validx, idx, buf, val_len) >= 0) {
+ err = 0;
+ goto out;
+ }
+ }
+ usb_audio_dbg(chip, "cannot set ctl value: req = %#x, wValue = %#x, wIndex = %#x, type = %d, data = %#x/%#x\n",
+ request, validx, idx, cval->val_type, buf[0], buf[1]);
+ err = -EINVAL;
+
+ out:
+ up_read(&chip->shutdown_rwsem);
+ snd_usb_autosuspend(chip);
+ return err;
+}
+
+static int set_cur_ctl_value(struct usb_mixer_elem_info *cval,
+ int validx, int value)
+{
+ return snd_usb_mixer_set_ctl_value(cval, UAC_SET_CUR, validx, value);
+}
+
+int snd_usb_set_cur_mix_value(struct usb_mixer_elem_info *cval, int channel,
+ int index, int value)
+{
+ int err;
+ unsigned int read_only = (channel == 0) ?
+ cval->master_readonly :
+ cval->ch_readonly & (1 << (channel - 1));
+
+ if (read_only) {
+ usb_audio_dbg(cval->head.mixer->chip,
+ "%s(): channel %d of control %d is read_only\n",
+ __func__, channel, cval->control);
+ return 0;
+ }
+
+ err = snd_usb_mixer_set_ctl_value(cval,
+ UAC_SET_CUR, (cval->control << 8) | channel,
+ value);
+ if (err < 0)
+ return err;
+ cval->cached |= 1 << channel;
+ cval->cache_val[index] = value;
+ return 0;
+}
+
+/*
+ * TLV callback for mixer volume controls
+ */
+int snd_usb_mixer_vol_tlv(struct snd_kcontrol *kcontrol, int op_flag,
+ unsigned int size, unsigned int __user *_tlv)
+{
+ struct usb_mixer_elem_info *cval = kcontrol->private_data;
+ DECLARE_TLV_DB_MINMAX(scale, 0, 0);
+
+ if (size < sizeof(scale))
+ return -ENOMEM;
+ scale[2] = cval->dBmin;
+ scale[3] = cval->dBmax;
+ if (copy_to_user(_tlv, scale, sizeof(scale)))
+ return -EFAULT;
+ return 0;
+}
+
+/*
+ * parser routines begin here...
+ */
+
+static int parse_audio_unit(struct mixer_build *state, int unitid);
+
+
+/*
+ * check if the input/output channel routing is enabled on the given bitmap.
+ * used for mixer unit parser
+ */
+static int check_matrix_bitmap(unsigned char *bmap,
+ int ich, int och, int num_outs)
+{
+ int idx = ich * num_outs + och;
+ return bmap[idx >> 3] & (0x80 >> (idx & 7));
+}
+
+/*
+ * add an alsa control element
+ * search and increment the index until an empty slot is found.
+ *
+ * if failed, give up and free the control instance.
+ */
+
+int snd_usb_mixer_add_control(struct usb_mixer_elem_list *list,
+ struct snd_kcontrol *kctl)
+{
+ struct usb_mixer_interface *mixer = list->mixer;
+ int err;
+
+ while (snd_ctl_find_id(mixer->chip->card, &kctl->id))
+ kctl->id.index++;
+ if ((err = snd_ctl_add(mixer->chip->card, kctl)) < 0) {
+ usb_audio_dbg(mixer->chip, "cannot add control (err = %d)\n",
+ err);
+ return err;
+ }
+ list->kctl = kctl;
+ list->next_id_elem = mixer->id_elems[list->id];
+ mixer->id_elems[list->id] = list;
+ return 0;
+}
+
+/*
+ * get a terminal name string
+ */
+
+static struct iterm_name_combo {
+ int type;
+ char *name;
+} iterm_names[] = {
+ { 0x0300, "Output" },
+ { 0x0301, "Speaker" },
+ { 0x0302, "Headphone" },
+ { 0x0303, "HMD Audio" },
+ { 0x0304, "Desktop Speaker" },
+ { 0x0305, "Room Speaker" },
+ { 0x0306, "Com Speaker" },
+ { 0x0307, "LFE" },
+ { 0x0600, "External In" },
+ { 0x0601, "Analog In" },
+ { 0x0602, "Digital In" },
+ { 0x0603, "Line" },
+ { 0x0604, "Legacy In" },
+ { 0x0605, "IEC958 In" },
+ { 0x0606, "1394 DA Stream" },
+ { 0x0607, "1394 DV Stream" },
+ { 0x0700, "Embedded" },
+ { 0x0701, "Noise Source" },
+ { 0x0702, "Equalization Noise" },
+ { 0x0703, "CD" },
+ { 0x0704, "DAT" },
+ { 0x0705, "DCC" },
+ { 0x0706, "MiniDisk" },
+ { 0x0707, "Analog Tape" },
+ { 0x0708, "Phonograph" },
+ { 0x0709, "VCR Audio" },
+ { 0x070a, "Video Disk Audio" },
+ { 0x070b, "DVD Audio" },
+ { 0x070c, "TV Tuner Audio" },
+ { 0x070d, "Satellite Rec Audio" },
+ { 0x070e, "Cable Tuner Audio" },
+ { 0x070f, "DSS Audio" },
+ { 0x0710, "Radio Receiver" },
+ { 0x0711, "Radio Transmitter" },
+ { 0x0712, "Multi-Track Recorder" },
+ { 0x0713, "Synthesizer" },
+ { 0 },
+};
+
+static int get_term_name(struct mixer_build *state, struct usb_audio_term *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 UAC_SELECTOR_UNIT:
+ strcpy(name, "Selector");
+ return 8;
+ case UAC1_PROCESSING_UNIT:
+ strcpy(name, "Process Unit");
+ return 12;
+ case UAC1_EXTENSION_UNIT:
+ strcpy(name, "Ext Unit");
+ return 8;
+ case UAC_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(struct mixer_build *state, int id,
+ struct usb_audio_term *term)
+{
+ int err;
+ void *p1;
+
+ memset(term, 0, sizeof(*term));
+ while ((p1 = find_audio_control_unit(state, id)) != NULL) {
+ unsigned char *hdr = p1;
+ term->id = id;
+ switch (hdr[2]) {
+ case UAC_INPUT_TERMINAL:
+ if (state->mixer->protocol == UAC_VERSION_1) {
+ struct uac_input_terminal_descriptor *d = p1;
+ term->type = le16_to_cpu(d->wTerminalType);
+ term->channels = d->bNrChannels;
+ term->chconfig = le16_to_cpu(d->wChannelConfig);
+ term->name = d->iTerminal;
+ } else { /* UAC_VERSION_2 */
+ struct uac2_input_terminal_descriptor *d = p1;
+ term->type = le16_to_cpu(d->wTerminalType);
+ term->channels = d->bNrChannels;
+ term->chconfig = le32_to_cpu(d->bmChannelConfig);
+ term->name = d->iTerminal;
+
+ /* call recursively to get the clock selectors */
+ err = check_input_term(state, d->bCSourceID, term);
+ if (err < 0)
+ return err;
+ }
+ return 0;
+ case UAC_FEATURE_UNIT: {
+ /* the header is the same for v1 and v2 */
+ struct uac_feature_unit_descriptor *d = p1;
+ id = d->bSourceID;
+ break; /* continue to parse */
+ }
+ case UAC_MIXER_UNIT: {
+ struct uac_mixer_unit_descriptor *d = p1;
+ term->type = d->bDescriptorSubtype << 16; /* virtual type */
+ term->channels = uac_mixer_unit_bNrChannels(d);
+ term->chconfig = uac_mixer_unit_wChannelConfig(d, state->mixer->protocol);
+ term->name = uac_mixer_unit_iMixer(d);
+ return 0;
+ }
+ case UAC_SELECTOR_UNIT:
+ case UAC2_CLOCK_SELECTOR: {
+ struct uac_selector_unit_descriptor *d = p1;
+ /* call recursively to retrieve the channel info */
+ err = check_input_term(state, d->baSourceID[0], term);
+ if (err < 0)
+ return err;
+ term->type = d->bDescriptorSubtype << 16; /* virtual type */
+ term->id = id;
+ term->name = uac_selector_unit_iSelector(d);
+ return 0;
+ }
+ case UAC1_PROCESSING_UNIT:
+ case UAC1_EXTENSION_UNIT:
+ /* UAC2_PROCESSING_UNIT_V2 */
+ /* UAC2_EFFECT_UNIT */
+ case UAC2_EXTENSION_UNIT_V2: {
+ struct uac_processing_unit_descriptor *d = p1;
+
+ if (state->mixer->protocol == UAC_VERSION_2 &&
+ hdr[2] == UAC2_EFFECT_UNIT) {
+ /* UAC2/UAC1 unit IDs overlap here in an
+ * uncompatible way. Ignore this unit for now.
+ */
+ return 0;
+ }
+
+ if (d->bNrInPins) {
+ id = d->baSourceID[0];
+ break; /* continue to parse */
+ }
+ term->type = d->bDescriptorSubtype << 16; /* virtual type */
+ term->channels = uac_processing_unit_bNrChannels(d);
+ term->chconfig = uac_processing_unit_wChannelConfig(d, state->mixer->protocol);
+ term->name = uac_processing_unit_iProcessing(d, state->mixer->protocol);
+ return 0;
+ }
+ case UAC2_CLOCK_SOURCE: {
+ struct uac_clock_source_descriptor *d = p1;
+ term->type = d->bDescriptorSubtype << 16; /* virtual type */
+ term->id = id;
+ term->name = d->iClockSource;
+ 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 },
+ /* UAC2 specific */
+ { "Input Gain Control", USB_MIXER_U16 },
+ { "Input Gain Pad Control", USB_MIXER_BOOLEAN },
+ { "Phase Inverter Control", USB_MIXER_BOOLEAN },
+};
+
+/* private_free callback */
+void snd_usb_mixer_elem_free(struct snd_kcontrol *kctl)
+{
+ kfree(kctl->private_data);
+ kctl->private_data = NULL;
+}
+
+/*
+ * interface to ALSA control for feature/mixer units
+ */
+
+/* volume control quirks */
+static void volume_control_quirks(struct usb_mixer_elem_info *cval,
+ struct snd_kcontrol *kctl)
+{
+ struct snd_usb_audio *chip = cval->head.mixer->chip;
+ switch (chip->usb_id) {
+ case USB_ID(0x0763, 0x2030): /* M-Audio Fast Track C400 */
+ case USB_ID(0x0763, 0x2031): /* M-Audio Fast Track C600 */
+ if (strcmp(kctl->id.name, "Effect Duration") == 0) {
+ cval->min = 0x0000;
+ cval->max = 0xffff;
+ cval->res = 0x00e6;
+ break;
+ }
+ if (strcmp(kctl->id.name, "Effect Volume") == 0 ||
+ strcmp(kctl->id.name, "Effect Feedback Volume") == 0) {
+ cval->min = 0x00;
+ cval->max = 0xff;
+ break;
+ }
+ if (strstr(kctl->id.name, "Effect Return") != NULL) {
+ cval->min = 0xb706;
+ cval->max = 0xff7b;
+ cval->res = 0x0073;
+ break;
+ }
+ if ((strstr(kctl->id.name, "Playback Volume") != NULL) ||
+ (strstr(kctl->id.name, "Effect Send") != NULL)) {
+ cval->min = 0xb5fb; /* -73 dB = 0xb6ff */
+ cval->max = 0xfcfe;
+ cval->res = 0x0073;
+ }
+ break;
+
+ case USB_ID(0x0763, 0x2081): /* M-Audio Fast Track Ultra 8R */
+ case USB_ID(0x0763, 0x2080): /* M-Audio Fast Track Ultra */
+ if (strcmp(kctl->id.name, "Effect Duration") == 0) {
+ usb_audio_info(chip,
+ "set quirk for FTU Effect Duration\n");
+ cval->min = 0x0000;
+ cval->max = 0x7f00;
+ cval->res = 0x0100;
+ break;
+ }
+ if (strcmp(kctl->id.name, "Effect Volume") == 0 ||
+ strcmp(kctl->id.name, "Effect Feedback Volume") == 0) {
+ usb_audio_info(chip,
+ "set quirks for FTU Effect Feedback/Volume\n");
+ cval->min = 0x00;
+ cval->max = 0x7f;
+ break;
+ }
+ break;
+
+ case USB_ID(0x0471, 0x0101):
+ case USB_ID(0x0471, 0x0104):
+ case USB_ID(0x0471, 0x0105):
+ case USB_ID(0x0672, 0x1041):
+ /* quirk for UDA1321/N101.
+ * note that detection between firmware 2.1.1.7 (N101)
+ * and later 2.1.1.21 is not very clear from datasheets.
+ * I hope that the min value is -15360 for newer firmware --jk
+ */
+ if (!strcmp(kctl->id.name, "PCM Playback Volume") &&
+ cval->min == -15616) {
+ usb_audio_info(chip,
+ "set volume quirk for UDA1321/N101 chip\n");
+ cval->max = -256;
+ }
+ break;
+
+ case USB_ID(0x046d, 0x09a4):
+ if (!strcmp(kctl->id.name, "Mic Capture Volume")) {
+ usb_audio_info(chip,
+ "set volume quirk for QuickCam E3500\n");
+ cval->min = 6080;
+ cval->max = 8768;
+ cval->res = 192;
+ }
+ break;
+
+ case USB_ID(0x046d, 0x0807): /* Logitech Webcam C500 */
+ case USB_ID(0x046d, 0x0808):
+ case USB_ID(0x046d, 0x0809):
+ case USB_ID(0x046d, 0x0819): /* Logitech Webcam C210 */
+ case USB_ID(0x046d, 0x081b): /* HD Webcam c310 */
+ case USB_ID(0x046d, 0x081d): /* HD Webcam c510 */
+ case USB_ID(0x046d, 0x0825): /* HD Webcam c270 */
+ case USB_ID(0x046d, 0x0826): /* HD Webcam c525 */
+ case USB_ID(0x046d, 0x08ca): /* Logitech Quickcam Fusion */
+ case USB_ID(0x046d, 0x0991):
+ /* Most audio usb devices lie about volume resolution.
+ * Most Logitech webcams have res = 384.
+ * Proboly there is some logitech magic behind this number --fishor
+ */
+ if (!strcmp(kctl->id.name, "Mic Capture Volume")) {
+ usb_audio_info(chip,
+ "set resolution quirk: cval->res = 384\n");
+ cval->res = 384;
+ }
+ break;
+ }
+}
+
+/*
+ * retrieve the minimum and maximum values for the specified control
+ */
+static int get_min_max_with_quirks(struct usb_mixer_elem_info *cval,
+ int default_min, struct snd_kcontrol *kctl)
+{
+ /* for failsafe */
+ cval->min = default_min;
+ cval->max = cval->min + 1;
+ cval->res = 1;
+ cval->dBmin = cval->dBmax = 0;
+
+ if (cval->val_type == USB_MIXER_BOOLEAN ||
+ cval->val_type == USB_MIXER_INV_BOOLEAN) {
+ cval->initialized = 1;
+ } else {
+ int minchn = 0;
+ if (cval->cmask) {
+ int i;
+ for (i = 0; i < MAX_CHANNELS; i++)
+ if (cval->cmask & (1 << i)) {
+ minchn = i + 1;
+ break;
+ }
+ }
+ if (get_ctl_value(cval, UAC_GET_MAX, (cval->control << 8) | minchn, &cval->max) < 0 ||
+ get_ctl_value(cval, UAC_GET_MIN, (cval->control << 8) | minchn, &cval->min) < 0) {
+ usb_audio_err(cval->head.mixer->chip,
+ "%d:%d: cannot get min/max values for control %d (id %d)\n",
+ cval->head.id, snd_usb_ctrl_intf(cval->head.mixer->chip),
+ cval->control, cval->head.id);
+ return -EINVAL;
+ }
+ if (get_ctl_value(cval, UAC_GET_RES,
+ (cval->control << 8) | minchn,
+ &cval->res) < 0) {
+ cval->res = 1;
+ } else {
+ int last_valid_res = cval->res;
+
+ while (cval->res > 1) {
+ if (snd_usb_mixer_set_ctl_value(cval, UAC_SET_RES,
+ (cval->control << 8) | minchn,
+ cval->res / 2) < 0)
+ break;
+ cval->res /= 2;
+ }
+ if (get_ctl_value(cval, UAC_GET_RES,
+ (cval->control << 8) | minchn, &cval->res) < 0)
+ cval->res = last_valid_res;
+ }
+ if (cval->res == 0)
+ cval->res = 1;
+
+ /* Additional checks for the proper resolution
+ *
+ * Some devices report smaller resolutions than actually
+ * reacting. They don't return errors but simply clip
+ * to the lower aligned value.
+ */
+ if (cval->min + cval->res < cval->max) {
+ int last_valid_res = cval->res;
+ int saved, test, check;
+ get_cur_mix_raw(cval, minchn, &saved);
+ for (;;) {
+ test = saved;
+ if (test < cval->max)
+ test += cval->res;
+ else
+ test -= cval->res;
+ if (test < cval->min || test > cval->max ||
+ snd_usb_set_cur_mix_value(cval, minchn, 0, test) ||
+ get_cur_mix_raw(cval, minchn, &check)) {
+ cval->res = last_valid_res;
+ break;
+ }
+ if (test == check)
+ break;
+ cval->res *= 2;
+ }
+ snd_usb_set_cur_mix_value(cval, minchn, 0, saved);
+ }
+
+ cval->initialized = 1;
+ }
+
+ if (kctl)
+ volume_control_quirks(cval, kctl);
+
+ /* USB descriptions contain the dB scale in 1/256 dB unit
+ * while ALSA TLV contains in 1/100 dB unit
+ */
+ cval->dBmin = (convert_signed_value(cval, cval->min) * 100) / 256;
+ cval->dBmax = (convert_signed_value(cval, cval->max) * 100) / 256;
+ if (cval->dBmin > cval->dBmax) {
+ /* something is wrong; assume it's either from/to 0dB */
+ if (cval->dBmin < 0)
+ cval->dBmax = 0;
+ else if (cval->dBmin > 0)
+ cval->dBmin = 0;
+ if (cval->dBmin > cval->dBmax) {
+ /* totally crap, return an error */
+ return -EINVAL;
+ }
+ }
+
+ return 0;
+}
+
+#define get_min_max(cval, def) get_min_max_with_quirks(cval, def, NULL)
+
+/* get a feature/mixer unit info */
+static int mixer_ctl_feature_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ struct usb_mixer_elem_info *cval = kcontrol->private_data;
+
+ if (cval->val_type == USB_MIXER_BOOLEAN ||
+ cval->val_type == USB_MIXER_INV_BOOLEAN)
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
+ else
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = cval->channels;
+ if (cval->val_type == USB_MIXER_BOOLEAN ||
+ cval->val_type == USB_MIXER_INV_BOOLEAN) {
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = 1;
+ } else {
+ if (!cval->initialized) {
+ get_min_max_with_quirks(cval, 0, kcontrol);
+ if (cval->initialized && cval->dBmin >= cval->dBmax) {
+ kcontrol->vd[0].access &=
+ ~(SNDRV_CTL_ELEM_ACCESS_TLV_READ |
+ SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK);
+ snd_ctl_notify(cval->head.mixer->chip->card,
+ SNDRV_CTL_EVENT_MASK_INFO,
+ &kcontrol->id);
+ }
+ }
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max =
+ (cval->max - cval->min + cval->res - 1) / cval->res;
+ }
+ return 0;
+}
+
+/* get the current value from feature/mixer unit */
+static int mixer_ctl_feature_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct usb_mixer_elem_info *cval = kcontrol->private_data;
+ int c, cnt, val, err;
+
+ ucontrol->value.integer.value[0] = cval->min;
+ if (cval->cmask) {
+ cnt = 0;
+ for (c = 0; c < MAX_CHANNELS; c++) {
+ if (!(cval->cmask & (1 << c)))
+ continue;
+ err = snd_usb_get_cur_mix_value(cval, c + 1, cnt, &val);
+ if (err < 0)
+ return filter_error(cval, err);
+ val = get_relative_value(cval, val);
+ ucontrol->value.integer.value[cnt] = val;
+ cnt++;
+ }
+ return 0;
+ } else {
+ /* master channel */
+ err = snd_usb_get_cur_mix_value(cval, 0, 0, &val);
+ if (err < 0)
+ return filter_error(cval, err);
+ val = get_relative_value(cval, val);
+ ucontrol->value.integer.value[0] = val;
+ }
+ return 0;
+}
+
+/* put the current value to feature/mixer unit */
+static int mixer_ctl_feature_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct usb_mixer_elem_info *cval = kcontrol->private_data;
+ int c, cnt, val, oval, err;
+ int changed = 0;
+
+ if (cval->cmask) {
+ cnt = 0;
+ for (c = 0; c < MAX_CHANNELS; c++) {
+ if (!(cval->cmask & (1 << c)))
+ continue;
+ err = snd_usb_get_cur_mix_value(cval, c + 1, cnt, &oval);
+ if (err < 0)
+ return filter_error(cval, err);
+ val = ucontrol->value.integer.value[cnt];
+ val = get_abs_value(cval, val);
+ if (oval != val) {
+ snd_usb_set_cur_mix_value(cval, c + 1, cnt, val);
+ changed = 1;
+ }
+ cnt++;
+ }
+ } else {
+ /* master channel */
+ err = snd_usb_get_cur_mix_value(cval, 0, 0, &oval);
+ if (err < 0)
+ return filter_error(cval, err);
+ val = ucontrol->value.integer.value[0];
+ val = get_abs_value(cval, val);
+ if (val != oval) {
+ snd_usb_set_cur_mix_value(cval, 0, 0, val);
+ changed = 1;
+ }
+ }
+ return changed;
+}
+
+static struct snd_kcontrol_new usb_feature_unit_ctl = {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "", /* will be filled later manually */
+ .info = mixer_ctl_feature_info,
+ .get = mixer_ctl_feature_get,
+ .put = mixer_ctl_feature_put,
+};
+
+/* the read-only variant */
+static struct snd_kcontrol_new usb_feature_unit_ctl_ro = {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "", /* will be filled later manually */
+ .info = mixer_ctl_feature_info,
+ .get = mixer_ctl_feature_get,
+ .put = NULL,
+};
+
+/*
+ * This symbol is exported in order to allow the mixer quirks to
+ * hook up to the standard feature unit control mechanism
+ */
+struct snd_kcontrol_new *snd_usb_feature_unit_ctl = &usb_feature_unit_ctl;
+
+/*
+ * build a feature control
+ */
+static size_t append_ctl_name(struct snd_kcontrol *kctl, const char *str)
+{
+ return strlcat(kctl->id.name, str, sizeof(kctl->id.name));
+}
+
+/*
+ * A lot of headsets/headphones have a "Speaker" mixer. Make sure we
+ * rename it to "Headphone". We determine if something is a headphone
+ * similar to how udev determines form factor.
+ */
+static void check_no_speaker_on_headset(struct snd_kcontrol *kctl,
+ struct snd_card *card)
+{
+ const char *names_to_check[] = {
+ "Headset", "headset", "Headphone", "headphone", NULL};
+ const char **s;
+ bool found = false;
+
+ if (strcmp("Speaker", kctl->id.name))
+ return;
+
+ for (s = names_to_check; *s; s++)
+ if (strstr(card->shortname, *s)) {
+ found = true;
+ break;
+ }
+
+ if (!found)
+ return;
+
+ strlcpy(kctl->id.name, "Headphone", sizeof(kctl->id.name));
+}
+
+static void build_feature_ctl(struct mixer_build *state, void *raw_desc,
+ unsigned int ctl_mask, int control,
+ struct usb_audio_term *iterm, int unitid,
+ int readonly_mask)
+{
+ struct uac_feature_unit_descriptor *desc = raw_desc;
+ unsigned int len = 0;
+ int mapped_name = 0;
+ int nameid = uac_feature_unit_iFeature(desc);
+ struct snd_kcontrol *kctl;
+ struct usb_mixer_elem_info *cval;
+ const struct usbmix_name_map *map;
+ unsigned int range;
+
+ control++; /* change from zero-based to 1-based value */
+
+ if (control == UAC_FU_GRAPHIC_EQUALIZER) {
+ /* FIXME: not supported yet */
+ return;
+ }
+
+ map = find_map(state, unitid, control);
+ if (check_ignored_ctl(map))
+ return;
+
+ cval = kzalloc(sizeof(*cval), GFP_KERNEL);
+ if (!cval)
+ return;
+ snd_usb_mixer_elem_init_std(&cval->head, state->mixer, 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 */
+ cval->master_readonly = readonly_mask;
+ } else {
+ int i, c = 0;
+ for (i = 0; i < 16; i++)
+ if (ctl_mask & (1 << i))
+ c++;
+ cval->channels = c;
+ cval->ch_readonly = readonly_mask;
+ }
+
+ /*
+ * If all channels in the mask are marked read-only, make the control
+ * read-only. snd_usb_set_cur_mix_value() will check the mask again and won't
+ * issue write commands to read-only channels.
+ */
+ if (cval->channels == readonly_mask)
+ kctl = snd_ctl_new1(&usb_feature_unit_ctl_ro, cval);
+ else
+ kctl = snd_ctl_new1(&usb_feature_unit_ctl, cval);
+
+ if (!kctl) {
+ usb_audio_err(state->chip, "cannot malloc kcontrol\n");
+ kfree(cval);
+ return;
+ }
+ kctl->private_free = snd_usb_mixer_elem_free;
+
+ len = check_mapped_name(map, kctl->id.name, sizeof(kctl->id.name));
+ 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 UAC_FU_MUTE:
+ case UAC_FU_VOLUME:
+ /*
+ * determine the control name. the rule is:
+ * - if a name id is given in descriptor, use it.
+ * - if the connected input can be determined, then use the name
+ * of terminal type.
+ * - if the connected output can be determined, use it.
+ * - otherwise, anonymous name.
+ */
+ if (!len) {
+ 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)
+ snprintf(kctl->id.name, sizeof(kctl->id.name),
+ "Feature %d", unitid);
+ }
+
+ if (!mapped_name)
+ check_no_speaker_on_headset(kctl, state->mixer->chip->card);
+
+ /*
+ * determine the stream direction:
+ * if the connected output is USB stream, then it's likely a
+ * capture stream. otherwise it should be playback (hopefully :)
+ */
+ if (!mapped_name && !(state->oterm.type >> 16)) {
+ if ((state->oterm.type & 0xff00) == 0x0100)
+ append_ctl_name(kctl, " Capture");
+ else
+ append_ctl_name(kctl, " Playback");
+ }
+ append_ctl_name(kctl, control == UAC_FU_MUTE ?
+ " Switch" : " Volume");
+ break;
+ default:
+ if (!len)
+ strlcpy(kctl->id.name, audio_feature_info[control-1].name,
+ sizeof(kctl->id.name));
+ break;
+ }
+
+ /* get min/max values */
+ get_min_max_with_quirks(cval, 0, kctl);
+
+ if (control == UAC_FU_VOLUME) {
+ check_mapped_dB(map, cval);
+ if (cval->dBmin < cval->dBmax || !cval->initialized) {
+ kctl->tlv.c = snd_usb_mixer_vol_tlv;
+ kctl->vd[0].access |=
+ SNDRV_CTL_ELEM_ACCESS_TLV_READ |
+ SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK;
+ }
+ }
+
+ range = (cval->max - cval->min) / cval->res;
+ /*
+ * Are there devices with volume range more than 255? I use a bit more
+ * to be sure. 384 is a resolution magic number found on Logitech
+ * devices. It will definitively catch all buggy Logitech devices.
+ */
+ if (range > 384) {
+ usb_audio_warn(state->chip,
+ "Warning! Unlikely big volume range (=%u), cval->res is probably wrong.",
+ range);
+ usb_audio_warn(state->chip,
+ "[%d] FU [%s] ch = %d, val = %d/%d/%d",
+ cval->head.id, kctl->id.name, cval->channels,
+ cval->min, cval->max, cval->res);
+ }
+
+ usb_audio_dbg(state->chip, "[%d] FU [%s] ch = %d, val = %d/%d/%d\n",
+ cval->head.id, kctl->id.name, cval->channels,
+ cval->min, cval->max, cval->res);
+ snd_usb_mixer_add_control(&cval->head, kctl);
+}
+
+/*
+ * parse a feature unit
+ *
+ * most of controls are defined here.
+ */
+static int parse_audio_feature_unit(struct mixer_build *state, int unitid,
+ void *_ftr)
+{
+ int channels, i, j;
+ struct usb_audio_term iterm;
+ unsigned int master_bits, first_ch_bits;
+ int err, csize;
+ struct uac_feature_unit_descriptor *hdr = _ftr;
+ __u8 *bmaControls;
+
+ if (state->mixer->protocol == UAC_VERSION_1) {
+ csize = hdr->bControlSize;
+ if (!csize) {
+ usb_audio_dbg(state->chip,
+ "unit %u: invalid bControlSize == 0\n",
+ unitid);
+ return -EINVAL;
+ }
+ channels = (hdr->bLength - 7) / csize - 1;
+ bmaControls = hdr->bmaControls;
+ if (hdr->bLength < 7 + csize) {
+ usb_audio_err(state->chip,
+ "unit %u: invalid UAC_FEATURE_UNIT descriptor\n",
+ unitid);
+ return -EINVAL;
+ }
+ } else {
+ struct uac2_feature_unit_descriptor *ftr = _ftr;
+ csize = 4;
+ channels = (hdr->bLength - 6) / 4 - 1;
+ bmaControls = ftr->bmaControls;
+ if (hdr->bLength < 6 + csize) {
+ usb_audio_err(state->chip,
+ "unit %u: invalid UAC_FEATURE_UNIT descriptor\n",
+ unitid);
+ return -EINVAL;
+ }
+ }
+
+ /* parse the source unit */
+ if ((err = parse_audio_unit(state, hdr->bSourceID)) < 0)
+ return err;
+
+ /* determine the input source type and name */
+ err = check_input_term(state, hdr->bSourceID, &iterm);
+ if (err < 0)
+ return err;
+
+ master_bits = snd_usb_combine_bytes(bmaControls, csize);
+ /* master configuration quirks */
+ switch (state->chip->usb_id) {
+ case USB_ID(0x08bb, 0x2702):
+ usb_audio_info(state->chip,
+ "usbmixer: master volume quirk for PCM2702 chip\n");
+ /* disable non-functional volume control */
+ master_bits &= ~UAC_CONTROL_BIT(UAC_FU_VOLUME);
+ break;
+ case USB_ID(0x1130, 0xf211):
+ usb_audio_info(state->chip,
+ "usbmixer: volume control quirk for Tenx TP6911 Audio Headset\n");
+ /* disable non-functional volume control */
+ channels = 0;
+ break;
+
+ }
+ if (channels > 0)
+ first_ch_bits = snd_usb_combine_bytes(bmaControls + csize, csize);
+ else
+ first_ch_bits = 0;
+
+ if (state->mixer->protocol == UAC_VERSION_1) {
+ /* check all control types */
+ for (i = 0; i < 10; i++) {
+ unsigned int ch_bits = 0;
+ for (j = 0; j < channels; j++) {
+ unsigned int mask;
+
+ mask = snd_usb_combine_bytes(bmaControls +
+ csize * (j+1), csize);
+ if (mask & (1 << i))
+ ch_bits |= (1 << j);
+ }
+ /* audio class v1 controls are never read-only */
+
+ /*
+ * The first channel must be set
+ * (for ease of programming).
+ */
+ if (ch_bits & 1)
+ build_feature_ctl(state, _ftr, ch_bits, i,
+ &iterm, unitid, 0);
+ if (master_bits & (1 << i))
+ build_feature_ctl(state, _ftr, 0, i, &iterm,
+ unitid, 0);
+ }
+ } else { /* UAC_VERSION_2 */
+ for (i = 0; i < ARRAY_SIZE(audio_feature_info); i++) {
+ unsigned int ch_bits = 0;
+ unsigned int ch_read_only = 0;
+
+ for (j = 0; j < channels; j++) {
+ unsigned int mask;
+
+ mask = snd_usb_combine_bytes(bmaControls +
+ csize * (j+1), csize);
+ if (uac2_control_is_readable(mask, i)) {
+ ch_bits |= (1 << j);
+ if (!uac2_control_is_writeable(mask, i))
+ ch_read_only |= (1 << j);
+ }
+ }
+
+ /*
+ * NOTE: build_feature_ctl() will mark the control
+ * read-only if all channels are marked read-only in
+ * the descriptors. Otherwise, the control will be
+ * reported as writeable, but the driver will not
+ * actually issue a write command for read-only
+ * channels.
+ */
+
+ /*
+ * The first channel must be set
+ * (for ease of programming).
+ */
+ if (ch_bits & 1)
+ build_feature_ctl(state, _ftr, ch_bits, i,
+ &iterm, unitid, ch_read_only);
+ if (uac2_control_is_readable(master_bits, i))
+ build_feature_ctl(state, _ftr, 0, i, &iterm, unitid,
+ !uac2_control_is_writeable(master_bits, i));
+ }
+ }
+
+ return 0;
+}
+
+/*
+ * Mixer Unit
+ */
+
+/*
+ * build a mixer unit control
+ *
+ * the callbacks are identical with feature unit.
+ * input channel number (zero based) is given in control field instead.
+ */
+static void build_mixer_unit_ctl(struct mixer_build *state,
+ struct uac_mixer_unit_descriptor *desc,
+ int in_pin, int in_ch, int unitid,
+ struct usb_audio_term *iterm)
+{
+ struct usb_mixer_elem_info *cval;
+ unsigned int num_outs = uac_mixer_unit_bNrChannels(desc);
+ unsigned int i, len;
+ struct snd_kcontrol *kctl;
+ const struct usbmix_name_map *map;
+
+ map = find_map(state, unitid, 0);
+ if (check_ignored_ctl(map))
+ return;
+
+ cval = kzalloc(sizeof(*cval), GFP_KERNEL);
+ if (!cval)
+ return;
+
+ snd_usb_mixer_elem_init_std(&cval->head, state->mixer, unitid);
+ cval->control = in_ch + 1; /* based on 1 */
+ cval->val_type = USB_MIXER_S16;
+ for (i = 0; i < num_outs; i++) {
+ __u8 *c = uac_mixer_unit_bmControls(desc, state->mixer->protocol);
+
+ if (check_matrix_bitmap(c, in_ch, i, num_outs)) {
+ cval->cmask |= (1 << i);
+ cval->channels++;
+ }
+ }
+
+ /* get min/max values */
+ get_min_max(cval, 0);
+
+ kctl = snd_ctl_new1(&usb_feature_unit_ctl, cval);
+ if (!kctl) {
+ usb_audio_err(state->chip, "cannot malloc kcontrol\n");
+ kfree(cval);
+ return;
+ }
+ kctl->private_free = snd_usb_mixer_elem_free;
+
+ len = check_mapped_name(map, kctl->id.name, sizeof(kctl->id.name));
+ if (!len)
+ len = get_term_name(state, iterm, kctl->id.name,
+ sizeof(kctl->id.name), 0);
+ if (!len)
+ len = sprintf(kctl->id.name, "Mixer Source %d", in_ch + 1);
+ append_ctl_name(kctl, " Volume");
+
+ usb_audio_dbg(state->chip, "[%d] MU [%s] ch = %d, val = %d/%d\n",
+ cval->head.id, kctl->id.name, cval->channels, cval->min, cval->max);
+ snd_usb_mixer_add_control(&cval->head, kctl);
+}
+
+/*
+ * parse a mixer unit
+ */
+static int parse_audio_mixer_unit(struct mixer_build *state, int unitid,
+ void *raw_desc)
+{
+ struct uac_mixer_unit_descriptor *desc = raw_desc;
+ struct usb_audio_term iterm;
+ int input_pins, num_ins, num_outs;
+ int pin, ich, err;
+
+ if (desc->bLength < 11 || !(input_pins = desc->bNrInPins) ||
+ !(num_outs = uac_mixer_unit_bNrChannels(desc))) {
+ usb_audio_err(state->chip,
+ "invalid MIXER UNIT descriptor %d\n",
+ unitid);
+ return -EINVAL;
+ }
+
+ num_ins = 0;
+ ich = 0;
+ for (pin = 0; pin < input_pins; pin++) {
+ err = parse_audio_unit(state, desc->baSourceID[pin]);
+ if (err < 0)
+ continue;
+ /* no bmControls field (e.g. Maya44) -> ignore */
+ if (desc->bLength <= 10 + input_pins)
+ continue;
+ err = check_input_term(state, desc->baSourceID[pin], &iterm);
+ if (err < 0)
+ return err;
+ num_ins += iterm.channels;
+ for (; ich < num_ins; ich++) {
+ int och, ich_has_controls = 0;
+
+ for (och = 0; och < num_outs; och++) {
+ __u8 *c = uac_mixer_unit_bmControls(desc,
+ state->mixer->protocol);
+
+ if (check_matrix_bitmap(c, ich, och, num_outs)) {
+ ich_has_controls = 1;
+ break;
+ }
+ }
+ if (ich_has_controls)
+ build_mixer_unit_ctl(state, desc, pin, ich,
+ unitid, &iterm);
+ }
+ }
+ return 0;
+}
+
+/*
+ * Processing Unit / Extension Unit
+ */
+
+/* get callback for processing/extension unit */
+static int mixer_ctl_procunit_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct usb_mixer_elem_info *cval = kcontrol->private_data;
+ int err, val;
+
+ err = get_cur_ctl_value(cval, cval->control << 8, &val);
+ if (err < 0) {
+ ucontrol->value.integer.value[0] = cval->min;
+ return filter_error(cval, err);
+ }
+ val = get_relative_value(cval, val);
+ ucontrol->value.integer.value[0] = val;
+ return 0;
+}
+
+/* put callback for processing/extension unit */
+static int mixer_ctl_procunit_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct usb_mixer_elem_info *cval = kcontrol->private_data;
+ int val, oval, err;
+
+ err = get_cur_ctl_value(cval, cval->control << 8, &oval);
+ if (err < 0)
+ return filter_error(cval, err);
+ val = ucontrol->value.integer.value[0];
+ val = get_abs_value(cval, val);
+ if (val != oval) {
+ set_cur_ctl_value(cval, cval->control << 8, val);
+ return 1;
+ }
+ return 0;
+}
+
+/* alsa control interface for processing/extension unit */
+static struct snd_kcontrol_new mixer_procunit_ctl = {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "", /* will be filled later */
+ .info = mixer_ctl_feature_info,
+ .get = mixer_ctl_procunit_get,
+ .put = mixer_ctl_procunit_put,
+};
+
+/*
+ * predefined data for processing units
+ */
+struct procunit_value_info {
+ int control;
+ char *suffix;
+ int val_type;
+ int min_value;
+};
+
+struct procunit_info {
+ int type;
+ char *name;
+ struct procunit_value_info *values;
+};
+
+static struct procunit_value_info updown_proc_info[] = {
+ { UAC_UD_ENABLE, "Switch", USB_MIXER_BOOLEAN },
+ { UAC_UD_MODE_SELECT, "Mode Select", USB_MIXER_U8, 1 },
+ { 0 }
+};
+static struct procunit_value_info prologic_proc_info[] = {
+ { UAC_DP_ENABLE, "Switch", USB_MIXER_BOOLEAN },
+ { UAC_DP_MODE_SELECT, "Mode Select", USB_MIXER_U8, 1 },
+ { 0 }
+};
+static struct procunit_value_info threed_enh_proc_info[] = {
+ { UAC_3D_ENABLE, "Switch", USB_MIXER_BOOLEAN },
+ { UAC_3D_SPACE, "Spaciousness", USB_MIXER_U8 },
+ { 0 }
+};
+static struct procunit_value_info reverb_proc_info[] = {
+ { UAC_REVERB_ENABLE, "Switch", USB_MIXER_BOOLEAN },
+ { UAC_REVERB_LEVEL, "Level", USB_MIXER_U8 },
+ { UAC_REVERB_TIME, "Time", USB_MIXER_U16 },
+ { UAC_REVERB_FEEDBACK, "Feedback", USB_MIXER_U8 },
+ { 0 }
+};
+static struct procunit_value_info chorus_proc_info[] = {
+ { UAC_CHORUS_ENABLE, "Switch", USB_MIXER_BOOLEAN },
+ { UAC_CHORUS_LEVEL, "Level", USB_MIXER_U8 },
+ { UAC_CHORUS_RATE, "Rate", USB_MIXER_U16 },
+ { UAC_CHORUS_DEPTH, "Depth", USB_MIXER_U16 },
+ { 0 }
+};
+static struct procunit_value_info dcr_proc_info[] = {
+ { UAC_DCR_ENABLE, "Switch", USB_MIXER_BOOLEAN },
+ { UAC_DCR_RATE, "Ratio", USB_MIXER_U16 },
+ { UAC_DCR_MAXAMPL, "Max Amp", USB_MIXER_S16 },
+ { UAC_DCR_THRESHOLD, "Threshold", USB_MIXER_S16 },
+ { UAC_DCR_ATTACK_TIME, "Attack Time", USB_MIXER_U16 },
+ { UAC_DCR_RELEASE_TIME, "Release Time", USB_MIXER_U16 },
+ { 0 }
+};
+
+static struct procunit_info procunits[] = {
+ { UAC_PROCESS_UP_DOWNMIX, "Up Down", updown_proc_info },
+ { UAC_PROCESS_DOLBY_PROLOGIC, "Dolby Prologic", prologic_proc_info },
+ { UAC_PROCESS_STEREO_EXTENDER, "3D Stereo Extender", threed_enh_proc_info },
+ { UAC_PROCESS_REVERB, "Reverb", reverb_proc_info },
+ { UAC_PROCESS_CHORUS, "Chorus", chorus_proc_info },
+ { UAC_PROCESS_DYN_RANGE_COMP, "DCR", dcr_proc_info },
+ { 0 },
+};
+/*
+ * predefined data for extension units
+ */
+static struct procunit_value_info clock_rate_xu_info[] = {
+ { USB_XU_CLOCK_RATE_SELECTOR, "Selector", USB_MIXER_U8, 0 },
+ { 0 }
+};
+static struct procunit_value_info clock_source_xu_info[] = {
+ { USB_XU_CLOCK_SOURCE_SELECTOR, "External", USB_MIXER_BOOLEAN },
+ { 0 }
+};
+static struct procunit_value_info spdif_format_xu_info[] = {
+ { USB_XU_DIGITAL_FORMAT_SELECTOR, "SPDIF/AC3", USB_MIXER_BOOLEAN },
+ { 0 }
+};
+static struct procunit_value_info soft_limit_xu_info[] = {
+ { USB_XU_SOFT_LIMIT_SELECTOR, " ", USB_MIXER_BOOLEAN },
+ { 0 }
+};
+static struct procunit_info extunits[] = {
+ { USB_XU_CLOCK_RATE, "Clock rate", clock_rate_xu_info },
+ { USB_XU_CLOCK_SOURCE, "DigitalIn CLK source", clock_source_xu_info },
+ { USB_XU_DIGITAL_IO_STATUS, "DigitalOut format:", spdif_format_xu_info },
+ { USB_XU_DEVICE_OPTIONS, "AnalogueIn Soft Limit", soft_limit_xu_info },
+ { 0 }
+};
+
+/*
+ * build a processing/extension unit
+ */
+static int build_audio_procunit(struct mixer_build *state, int unitid,
+ void *raw_desc, struct procunit_info *list,
+ char *name)
+{
+ struct uac_processing_unit_descriptor *desc = raw_desc;
+ int num_ins = desc->bNrInPins;
+ struct usb_mixer_elem_info *cval;
+ struct snd_kcontrol *kctl;
+ int i, err, nameid, type, len;
+ struct procunit_info *info;
+ struct procunit_value_info *valinfo;
+ const struct usbmix_name_map *map;
+ static struct procunit_value_info default_value_info[] = {
+ { 0x01, "Switch", USB_MIXER_BOOLEAN },
+ { 0 }
+ };
+ static struct procunit_info default_info = {
+ 0, NULL, default_value_info
+ };
+
+ if (desc->bLength < 13 || desc->bLength < 13 + num_ins ||
+ desc->bLength < num_ins + uac_processing_unit_bControlSize(desc, state->mixer->protocol)) {
+ usb_audio_err(state->chip, "invalid %s descriptor (id %d)\n", name, unitid);
+ return -EINVAL;
+ }
+
+ for (i = 0; i < num_ins; i++) {
+ if ((err = parse_audio_unit(state, desc->baSourceID[i])) < 0)
+ return err;
+ }
+
+ type = le16_to_cpu(desc->wProcessType);
+ for (info = list; info && info->type; info++)
+ if (info->type == type)
+ break;
+ if (!info || !info->type)
+ info = &default_info;
+
+ for (valinfo = info->values; valinfo->control; valinfo++) {
+ __u8 *controls = uac_processing_unit_bmControls(desc, state->mixer->protocol);
+
+ if (!(controls[valinfo->control / 8] & (1 << ((valinfo->control % 8) - 1))))
+ continue;
+ map = find_map(state, unitid, valinfo->control);
+ if (check_ignored_ctl(map))
+ continue;
+ cval = kzalloc(sizeof(*cval), GFP_KERNEL);
+ if (!cval)
+ return -ENOMEM;
+ snd_usb_mixer_elem_init_std(&cval->head, state->mixer, unitid);
+ cval->control = valinfo->control;
+ cval->val_type = valinfo->val_type;
+ cval->channels = 1;
+
+ /* get min/max values */
+ if (type == UAC_PROCESS_UP_DOWNMIX && cval->control == UAC_UD_MODE_SELECT) {
+ __u8 *control_spec = uac_processing_unit_specific(desc, state->mixer->protocol);
+ /* FIXME: hard-coded */
+ cval->min = 1;
+ cval->max = control_spec[0];
+ cval->res = 1;
+ cval->initialized = 1;
+ } else {
+ if (type == USB_XU_CLOCK_RATE) {
+ /*
+ * E-Mu USB 0404/0202/TrackerPre/0204
+ * samplerate control quirk
+ */
+ cval->min = 0;
+ cval->max = 5;
+ cval->res = 1;
+ cval->initialized = 1;
+ } else
+ get_min_max(cval, valinfo->min_value);
+ }
+
+ kctl = snd_ctl_new1(&mixer_procunit_ctl, cval);
+ if (!kctl) {
+ kfree(cval);
+ return -ENOMEM;
+ }
+ kctl->private_free = snd_usb_mixer_elem_free;
+
+ if (check_mapped_name(map, kctl->id.name, sizeof(kctl->id.name))) {
+ /* nothing */ ;
+ } else if (info->name) {
+ strlcpy(kctl->id.name, info->name, sizeof(kctl->id.name));
+ } else {
+ nameid = uac_processing_unit_iProcessing(desc, state->mixer->protocol);
+ len = 0;
+ if (nameid)
+ len = snd_usb_copy_string_desc(state, nameid,
+ kctl->id.name,
+ sizeof(kctl->id.name));
+ if (!len)
+ strlcpy(kctl->id.name, name, sizeof(kctl->id.name));
+ }
+ append_ctl_name(kctl, " ");
+ append_ctl_name(kctl, valinfo->suffix);
+
+ usb_audio_dbg(state->chip,
+ "[%d] PU [%s] ch = %d, val = %d/%d\n",
+ cval->head.id, kctl->id.name, cval->channels,
+ cval->min, cval->max);
+
+ err = snd_usb_mixer_add_control(&cval->head, kctl);
+ if (err < 0)
+ return err;
+ }
+ return 0;
+}
+
+static int parse_audio_processing_unit(struct mixer_build *state, int unitid,
+ void *raw_desc)
+{
+ return build_audio_procunit(state, unitid, raw_desc,
+ procunits, "Processing Unit");
+}
+
+static int parse_audio_extension_unit(struct mixer_build *state, int unitid,
+ void *raw_desc)
+{
+ /*
+ * Note that we parse extension units with processing unit descriptors.
+ * That's ok as the layout is the same.
+ */
+ return build_audio_procunit(state, unitid, raw_desc,
+ extunits, "Extension Unit");
+}
+
+/*
+ * Selector Unit
+ */
+
+/*
+ * info callback for selector unit
+ * use an enumerator type for routing
+ */
+static int mixer_ctl_selector_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ struct usb_mixer_elem_info *cval = kcontrol->private_data;
+ const char **itemlist = (const char **)kcontrol->private_value;
+
+ if (snd_BUG_ON(!itemlist))
+ return -EINVAL;
+ return snd_ctl_enum_info(uinfo, 1, cval->max, itemlist);
+}
+
+/* get callback for selector unit */
+static int mixer_ctl_selector_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct usb_mixer_elem_info *cval = kcontrol->private_data;
+ int val, err;
+
+ err = get_cur_ctl_value(cval, cval->control << 8, &val);
+ if (err < 0) {
+ ucontrol->value.enumerated.item[0] = 0;
+ return filter_error(cval, err);
+ }
+ val = get_relative_value(cval, val);
+ ucontrol->value.enumerated.item[0] = val;
+ return 0;
+}
+
+/* put callback for selector unit */
+static int mixer_ctl_selector_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct usb_mixer_elem_info *cval = kcontrol->private_data;
+ int val, oval, err;
+
+ err = get_cur_ctl_value(cval, cval->control << 8, &oval);
+ if (err < 0)
+ return filter_error(cval, err);
+ val = ucontrol->value.enumerated.item[0];
+ val = get_abs_value(cval, val);
+ if (val != oval) {
+ set_cur_ctl_value(cval, cval->control << 8, val);
+ return 1;
+ }
+ return 0;
+}
+
+/* alsa control interface for selector unit */
+static struct snd_kcontrol_new mixer_selectunit_ctl = {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "", /* will be filled later */
+ .info = mixer_ctl_selector_info,
+ .get = mixer_ctl_selector_get,
+ .put = mixer_ctl_selector_put,
+};
+
+/*
+ * private free callback.
+ * free both private_data and private_value
+ */
+static void usb_mixer_selector_elem_free(struct snd_kcontrol *kctl)
+{
+ int i, num_ins = 0;
+
+ if (kctl->private_data) {
+ struct usb_mixer_elem_info *cval = kctl->private_data;
+ num_ins = cval->max;
+ kfree(cval);
+ kctl->private_data = NULL;
+ }
+ if (kctl->private_value) {
+ char **itemlist = (char **)kctl->private_value;
+ for (i = 0; i < num_ins; i++)
+ kfree(itemlist[i]);
+ kfree(itemlist);
+ kctl->private_value = 0;
+ }
+}
+
+/*
+ * parse a selector unit
+ */
+static int parse_audio_selector_unit(struct mixer_build *state, int unitid,
+ void *raw_desc)
+{
+ struct uac_selector_unit_descriptor *desc = raw_desc;
+ unsigned int i, nameid, len;
+ int err;
+ struct usb_mixer_elem_info *cval;
+ struct snd_kcontrol *kctl;
+ const struct usbmix_name_map *map;
+ char **namelist;
+
+ if (!desc->bNrInPins || desc->bLength < 5 + desc->bNrInPins) {
+ usb_audio_err(state->chip,
+ "invalid SELECTOR UNIT descriptor %d\n", unitid);
+ return -EINVAL;
+ }
+
+ for (i = 0; i < desc->bNrInPins; i++) {
+ if ((err = parse_audio_unit(state, desc->baSourceID[i])) < 0)
+ return err;
+ }
+
+ if (desc->bNrInPins == 1) /* only one ? nonsense! */
+ return 0;
+
+ map = find_map(state, unitid, 0);
+ if (check_ignored_ctl(map))
+ return 0;
+
+ cval = kzalloc(sizeof(*cval), GFP_KERNEL);
+ if (!cval)
+ return -ENOMEM;
+ snd_usb_mixer_elem_init_std(&cval->head, state->mixer, unitid);
+ cval->val_type = USB_MIXER_U8;
+ cval->channels = 1;
+ cval->min = 1;
+ cval->max = desc->bNrInPins;
+ cval->res = 1;
+ cval->initialized = 1;
+
+ if (state->mixer->protocol == UAC_VERSION_1)
+ cval->control = 0;
+ else /* UAC_VERSION_2 */
+ cval->control = (desc->bDescriptorSubtype == UAC2_CLOCK_SELECTOR) ?
+ UAC2_CX_CLOCK_SELECTOR : UAC2_SU_SELECTOR;
+
+ namelist = kmalloc(sizeof(char *) * desc->bNrInPins, GFP_KERNEL);
+ if (!namelist) {
+ kfree(cval);
+ return -ENOMEM;
+ }
+#define MAX_ITEM_NAME_LEN 64
+ for (i = 0; i < desc->bNrInPins; i++) {
+ struct usb_audio_term iterm;
+ len = 0;
+ namelist[i] = kmalloc(MAX_ITEM_NAME_LEN, GFP_KERNEL);
+ if (!namelist[i]) {
+ while (i--)
+ kfree(namelist[i]);
+ kfree(namelist);
+ kfree(cval);
+ return -ENOMEM;
+ }
+ len = check_mapped_selector_name(state, unitid, i, namelist[i],
+ MAX_ITEM_NAME_LEN);
+ if (! len && check_input_term(state, desc->baSourceID[i], &iterm) >= 0)
+ len = get_term_name(state, &iterm, namelist[i], MAX_ITEM_NAME_LEN, 0);
+ if (! len)
+ sprintf(namelist[i], "Input %u", i);
+ }
+
+ kctl = snd_ctl_new1(&mixer_selectunit_ctl, cval);
+ if (! kctl) {
+ usb_audio_err(state->chip, "cannot malloc kcontrol\n");
+ kfree(namelist);
+ kfree(cval);
+ return -ENOMEM;
+ }
+ kctl->private_value = (unsigned long)namelist;
+ kctl->private_free = usb_mixer_selector_elem_free;
+
+ nameid = uac_selector_unit_iSelector(desc);
+ len = check_mapped_name(map, 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 (desc->bDescriptorSubtype == UAC2_CLOCK_SELECTOR)
+ append_ctl_name(kctl, " Clock Source");
+ else if ((state->oterm.type & 0xff00) == 0x0100)
+ append_ctl_name(kctl, " Capture Source");
+ else
+ append_ctl_name(kctl, " Playback Source");
+ }
+
+ usb_audio_dbg(state->chip, "[%d] SU [%s] items = %d\n",
+ cval->head.id, kctl->id.name, desc->bNrInPins);
+ return snd_usb_mixer_add_control(&cval->head, kctl);
+}
+
+/*
+ * parse an audio unit recursively
+ */
+
+static int parse_audio_unit(struct mixer_build *state, int unitid)
+{
+ unsigned char *p1;
+
+ if (test_and_set_bit(unitid, state->unitbitmap))
+ return 0; /* the unit already visited */
+
+ p1 = find_audio_control_unit(state, unitid);
+ if (!p1) {
+ usb_audio_err(state->chip, "unit %d not found!\n", unitid);
+ return -EINVAL;
+ }
+
+ switch (p1[2]) {
+ case UAC_INPUT_TERMINAL:
+ case UAC2_CLOCK_SOURCE:
+ return 0; /* NOP */
+ case UAC_MIXER_UNIT:
+ return parse_audio_mixer_unit(state, unitid, p1);
+ case UAC_SELECTOR_UNIT:
+ case UAC2_CLOCK_SELECTOR:
+ return parse_audio_selector_unit(state, unitid, p1);
+ case UAC_FEATURE_UNIT:
+ return parse_audio_feature_unit(state, unitid, p1);
+ case UAC1_PROCESSING_UNIT:
+ /* UAC2_EFFECT_UNIT has the same value */
+ if (state->mixer->protocol == UAC_VERSION_1)
+ return parse_audio_processing_unit(state, unitid, p1);
+ else
+ return 0; /* FIXME - effect units not implemented yet */
+ case UAC1_EXTENSION_UNIT:
+ /* UAC2_PROCESSING_UNIT_V2 has the same value */
+ if (state->mixer->protocol == UAC_VERSION_1)
+ return parse_audio_extension_unit(state, unitid, p1);
+ else /* UAC_VERSION_2 */
+ return parse_audio_processing_unit(state, unitid, p1);
+ case UAC2_EXTENSION_UNIT_V2:
+ return parse_audio_extension_unit(state, unitid, p1);
+ default:
+ usb_audio_err(state->chip,
+ "unit %u: unexpected type 0x%02x\n", unitid, p1[2]);
+ return -EINVAL;
+ }
+}
+
+static void snd_usb_mixer_free(struct usb_mixer_interface *mixer)
+{
+ kfree(mixer->id_elems);
+ if (mixer->urb) {
+ kfree(mixer->urb->transfer_buffer);
+ usb_free_urb(mixer->urb);
+ }
+ usb_free_urb(mixer->rc_urb);
+ kfree(mixer->rc_setup_packet);
+ kfree(mixer);
+}
+
+static int snd_usb_mixer_dev_free(struct snd_device *device)
+{
+ struct usb_mixer_interface *mixer = device->device_data;
+ snd_usb_mixer_free(mixer);
+ return 0;
+}
+
+/*
+ * create mixer controls
+ *
+ * walk through all UAC_OUTPUT_TERMINAL descriptors to search for mixers
+ */
+static int snd_usb_mixer_controls(struct usb_mixer_interface *mixer)
+{
+ struct mixer_build state;
+ int err;
+ const struct usbmix_ctl_map *map;
+ void *p;
+
+ memset(&state, 0, sizeof(state));
+ state.chip = mixer->chip;
+ state.mixer = mixer;
+ state.buffer = mixer->hostif->extra;
+ state.buflen = mixer->hostif->extralen;
+
+ /* check the mapping table */
+ for (map = usbmix_ctl_maps; map->id; map++) {
+ if (map->id == state.chip->usb_id) {
+ state.map = map->map;
+ state.selector_map = map->selector_map;
+ mixer->ignore_ctl_error = map->ignore_ctl_error;
+ break;
+ }
+ }
+
+ p = NULL;
+ while ((p = snd_usb_find_csint_desc(mixer->hostif->extra,
+ mixer->hostif->extralen,
+ p, UAC_OUTPUT_TERMINAL)) != NULL) {
+ if (mixer->protocol == UAC_VERSION_1) {
+ struct uac1_output_terminal_descriptor *desc = p;
+
+ if (desc->bLength < sizeof(*desc))
+ continue; /* invalid descriptor? */
+ /* mark terminal ID as visited */
+ set_bit(desc->bTerminalID, state.unitbitmap);
+ state.oterm.id = desc->bTerminalID;
+ state.oterm.type = le16_to_cpu(desc->wTerminalType);
+ state.oterm.name = desc->iTerminal;
+ err = parse_audio_unit(&state, desc->bSourceID);
+ if (err < 0 && err != -EINVAL)
+ return err;
+ } else { /* UAC_VERSION_2 */
+ struct uac2_output_terminal_descriptor *desc = p;
+
+ if (desc->bLength < sizeof(*desc))
+ continue; /* invalid descriptor? */
+ /* mark terminal ID as visited */
+ set_bit(desc->bTerminalID, state.unitbitmap);
+ state.oterm.id = desc->bTerminalID;
+ state.oterm.type = le16_to_cpu(desc->wTerminalType);
+ state.oterm.name = desc->iTerminal;
+ err = parse_audio_unit(&state, desc->bSourceID);
+ if (err < 0 && err != -EINVAL)
+ return err;
+
+ /*
+ * For UAC2, use the same approach to also add the
+ * clock selectors
+ */
+ err = parse_audio_unit(&state, desc->bCSourceID);
+ if (err < 0 && err != -EINVAL)
+ return err;
+ }
+ }
+
+ return 0;
+}
+
+void snd_usb_mixer_notify_id(struct usb_mixer_interface *mixer, int unitid)
+{
+ struct usb_mixer_elem_list *list;
+
+ for (list = mixer->id_elems[unitid]; list; list = list->next_id_elem)
+ snd_ctl_notify(mixer->chip->card, SNDRV_CTL_EVENT_MASK_VALUE,
+ &list->kctl->id);
+}
+
+static void snd_usb_mixer_dump_cval(struct snd_info_buffer *buffer,
+ struct usb_mixer_elem_list *list)
+{
+ struct usb_mixer_elem_info *cval = (struct usb_mixer_elem_info *)list;
+ static char *val_types[] = {"BOOLEAN", "INV_BOOLEAN",
+ "S8", "U8", "S16", "U16"};
+ snd_iprintf(buffer, " Info: id=%i, control=%i, cmask=0x%x, "
+ "channels=%i, type=\"%s\"\n", cval->head.id,
+ cval->control, cval->cmask, cval->channels,
+ val_types[cval->val_type]);
+ snd_iprintf(buffer, " Volume: min=%i, max=%i, dBmin=%i, dBmax=%i\n",
+ cval->min, cval->max, cval->dBmin, cval->dBmax);
+}
+
+static void snd_usb_mixer_proc_read(struct snd_info_entry *entry,
+ struct snd_info_buffer *buffer)
+{
+ struct snd_usb_audio *chip = entry->private_data;
+ struct usb_mixer_interface *mixer;
+ struct usb_mixer_elem_list *list;
+ int unitid;
+
+ list_for_each_entry(mixer, &chip->mixer_list, list) {
+ snd_iprintf(buffer,
+ "USB Mixer: usb_id=0x%08x, ctrlif=%i, ctlerr=%i\n",
+ chip->usb_id, snd_usb_ctrl_intf(chip),
+ mixer->ignore_ctl_error);
+ snd_iprintf(buffer, "Card: %s\n", chip->card->longname);
+ for (unitid = 0; unitid < MAX_ID_ELEMS; unitid++) {
+ for (list = mixer->id_elems[unitid]; list;
+ list = list->next_id_elem) {
+ snd_iprintf(buffer, " Unit: %i\n", list->id);
+ if (list->kctl)
+ snd_iprintf(buffer,
+ " Control: name=\"%s\", index=%i\n",
+ list->kctl->id.name,
+ list->kctl->id.index);
+ if (list->dump)
+ list->dump(buffer, list);
+ }
+ }
+ }
+}
+
+static void snd_usb_mixer_interrupt_v2(struct usb_mixer_interface *mixer,
+ int attribute, int value, int index)
+{
+ struct usb_mixer_elem_list *list;
+ __u8 unitid = (index >> 8) & 0xff;
+ __u8 control = (value >> 8) & 0xff;
+ __u8 channel = value & 0xff;
+
+ if (channel >= MAX_CHANNELS) {
+ usb_audio_dbg(mixer->chip,
+ "%s(): bogus channel number %d\n",
+ __func__, channel);
+ return;
+ }
+
+ for (list = mixer->id_elems[unitid]; list; list = list->next_id_elem) {
+ struct usb_mixer_elem_info *info;
+
+ if (!list->kctl)
+ continue;
+
+ info = (struct usb_mixer_elem_info *)list;
+ if (info->control != control)
+ continue;
+
+ switch (attribute) {
+ case UAC2_CS_CUR:
+ /* invalidate cache, so the value is read from the device */
+ if (channel)
+ info->cached &= ~(1 << channel);
+ else /* master channel */
+ info->cached = 0;
+
+ snd_ctl_notify(mixer->chip->card, SNDRV_CTL_EVENT_MASK_VALUE,
+ &info->head.kctl->id);
+ break;
+
+ case UAC2_CS_RANGE:
+ /* TODO */
+ break;
+
+ case UAC2_CS_MEM:
+ /* TODO */
+ break;
+
+ default:
+ usb_audio_dbg(mixer->chip,
+ "unknown attribute %d in interrupt\n",
+ attribute);
+ break;
+ } /* switch */
+ }
+}
+
+static void snd_usb_mixer_interrupt(struct urb *urb)
+{
+ struct usb_mixer_interface *mixer = urb->context;
+ int len = urb->actual_length;
+ int ustatus = urb->status;
+
+ if (ustatus != 0)
+ goto requeue;
+
+ if (mixer->protocol == UAC_VERSION_1) {
+ struct uac1_status_word *status;
+
+ for (status = urb->transfer_buffer;
+ len >= sizeof(*status);
+ len -= sizeof(*status), status++) {
+ dev_dbg(&urb->dev->dev, "status interrupt: %02x %02x\n",
+ status->bStatusType,
+ status->bOriginator);
+
+ /* ignore any notifications not from the control interface */
+ if ((status->bStatusType & UAC1_STATUS_TYPE_ORIG_MASK) !=
+ UAC1_STATUS_TYPE_ORIG_AUDIO_CONTROL_IF)
+ continue;
+
+ if (status->bStatusType & UAC1_STATUS_TYPE_MEM_CHANGED)
+ snd_usb_mixer_rc_memory_change(mixer, status->bOriginator);
+ else
+ snd_usb_mixer_notify_id(mixer, status->bOriginator);
+ }
+ } else { /* UAC_VERSION_2 */
+ struct uac2_interrupt_data_msg *msg;
+
+ for (msg = urb->transfer_buffer;
+ len >= sizeof(*msg);
+ len -= sizeof(*msg), msg++) {
+ /* drop vendor specific and endpoint requests */
+ if ((msg->bInfo & UAC2_INTERRUPT_DATA_MSG_VENDOR) ||
+ (msg->bInfo & UAC2_INTERRUPT_DATA_MSG_EP))
+ continue;
+
+ snd_usb_mixer_interrupt_v2(mixer, msg->bAttribute,
+ le16_to_cpu(msg->wValue),
+ le16_to_cpu(msg->wIndex));
+ }
+ }
+
+requeue:
+ if (ustatus != -ENOENT &&
+ ustatus != -ECONNRESET &&
+ ustatus != -ESHUTDOWN) {
+ urb->dev = mixer->chip->dev;
+ usb_submit_urb(urb, GFP_ATOMIC);
+ }
+}
+
+/* create the handler for the optional status interrupt endpoint */
+static int snd_usb_mixer_status_create(struct usb_mixer_interface *mixer)
+{
+ struct usb_endpoint_descriptor *ep;
+ void *transfer_buffer;
+ int buffer_length;
+ unsigned int epnum;
+
+ /* we need one interrupt input endpoint */
+ if (get_iface_desc(mixer->hostif)->bNumEndpoints < 1)
+ return 0;
+ ep = get_endpoint(mixer->hostif, 0);
+ if (!usb_endpoint_dir_in(ep) || !usb_endpoint_xfer_int(ep))
+ return 0;
+
+ epnum = usb_endpoint_num(ep);
+ buffer_length = le16_to_cpu(ep->wMaxPacketSize);
+ transfer_buffer = kmalloc(buffer_length, GFP_KERNEL);
+ if (!transfer_buffer)
+ return -ENOMEM;
+ mixer->urb = usb_alloc_urb(0, GFP_KERNEL);
+ if (!mixer->urb) {
+ kfree(transfer_buffer);
+ return -ENOMEM;
+ }
+ usb_fill_int_urb(mixer->urb, mixer->chip->dev,
+ usb_rcvintpipe(mixer->chip->dev, epnum),
+ transfer_buffer, buffer_length,
+ snd_usb_mixer_interrupt, mixer, ep->bInterval);
+ usb_submit_urb(mixer->urb, GFP_KERNEL);
+ return 0;
+}
+
+int snd_usb_create_mixer(struct snd_usb_audio *chip, int ctrlif,
+ int ignore_error)
+{
+ static struct snd_device_ops dev_ops = {
+ .dev_free = snd_usb_mixer_dev_free
+ };
+ struct usb_mixer_interface *mixer;
+ struct snd_info_entry *entry;
+ int err;
+
+ strcpy(chip->card->mixername, "USB Mixer");
+
+ mixer = kzalloc(sizeof(*mixer), GFP_KERNEL);
+ if (!mixer)
+ return -ENOMEM;
+ mixer->chip = chip;
+ mixer->ignore_ctl_error = ignore_error;
+ mixer->id_elems = kcalloc(MAX_ID_ELEMS, sizeof(*mixer->id_elems),
+ GFP_KERNEL);
+ if (!mixer->id_elems) {
+ kfree(mixer);
+ return -ENOMEM;
+ }
+
+ mixer->hostif = &usb_ifnum_to_if(chip->dev, ctrlif)->altsetting[0];
+ switch (get_iface_desc(mixer->hostif)->bInterfaceProtocol) {
+ case UAC_VERSION_1:
+ default:
+ mixer->protocol = UAC_VERSION_1;
+ break;
+ case UAC_VERSION_2:
+ mixer->protocol = UAC_VERSION_2;
+ break;
+ }
+
+ if ((err = snd_usb_mixer_controls(mixer)) < 0 ||
+ (err = snd_usb_mixer_status_create(mixer)) < 0)
+ goto _error;
+
+ snd_usb_mixer_apply_create_quirk(mixer);
+
+ err = snd_device_new(chip->card, SNDRV_DEV_CODEC, mixer, &dev_ops);
+ if (err < 0)
+ goto _error;
+
+ if (list_empty(&chip->mixer_list) &&
+ !snd_card_proc_new(chip->card, "usbmixer", &entry))
+ snd_info_set_text_ops(entry, chip, snd_usb_mixer_proc_read);
+
+ list_add(&mixer->list, &chip->mixer_list);
+ return 0;
+
+_error:
+ snd_usb_mixer_free(mixer);
+ return err;
+}
+
+void snd_usb_mixer_disconnect(struct usb_mixer_interface *mixer)
+{
+ usb_kill_urb(mixer->urb);
+ usb_kill_urb(mixer->rc_urb);
+}
+
+#ifdef CONFIG_PM
+/* stop any bus activity of a mixer */
+static void snd_usb_mixer_inactivate(struct usb_mixer_interface *mixer)
+{
+ usb_kill_urb(mixer->urb);
+ usb_kill_urb(mixer->rc_urb);
+}
+
+static int snd_usb_mixer_activate(struct usb_mixer_interface *mixer)
+{
+ int err;
+
+ if (mixer->urb) {
+ err = usb_submit_urb(mixer->urb, GFP_NOIO);
+ if (err < 0)
+ return err;
+ }
+
+ return 0;
+}
+
+int snd_usb_mixer_suspend(struct usb_mixer_interface *mixer)
+{
+ snd_usb_mixer_inactivate(mixer);
+ return 0;
+}
+
+static int restore_mixer_value(struct usb_mixer_elem_list *list)
+{
+ struct usb_mixer_elem_info *cval = (struct usb_mixer_elem_info *)list;
+ int c, err, idx;
+
+ if (cval->cmask) {
+ idx = 0;
+ for (c = 0; c < MAX_CHANNELS; c++) {
+ if (!(cval->cmask & (1 << c)))
+ continue;
+ if (cval->cached & (1 << c)) {
+ err = snd_usb_set_cur_mix_value(cval, c + 1, idx,
+ cval->cache_val[idx]);
+ if (err < 0)
+ return err;
+ }
+ idx++;
+ }
+ } else {
+ /* master */
+ if (cval->cached) {
+ err = snd_usb_set_cur_mix_value(cval, 0, 0, *cval->cache_val);
+ if (err < 0)
+ return err;
+ }
+ }
+
+ return 0;
+}
+
+int snd_usb_mixer_resume(struct usb_mixer_interface *mixer, bool reset_resume)
+{
+ struct usb_mixer_elem_list *list;
+ int id, err;
+
+ if (reset_resume) {
+ /* restore cached mixer values */
+ for (id = 0; id < MAX_ID_ELEMS; id++) {
+ for (list = mixer->id_elems[id]; list;
+ list = list->next_id_elem) {
+ if (list->resume) {
+ err = list->resume(list);
+ if (err < 0)
+ return err;
+ }
+ }
+ }
+ }
+
+ return snd_usb_mixer_activate(mixer);
+}
+#endif
+
+void snd_usb_mixer_elem_init_std(struct usb_mixer_elem_list *list,
+ struct usb_mixer_interface *mixer,
+ int unitid)
+{
+ list->mixer = mixer;
+ list->id = unitid;
+ list->dump = snd_usb_mixer_dump_cval;
+#ifdef CONFIG_PM
+ list->resume = restore_mixer_value;
+#endif
+}
diff --git a/sound/usb/mixer.h b/sound/usb/mixer.h
new file mode 100644
index 000000000..d3268f0ee
--- /dev/null
+++ b/sound/usb/mixer.h
@@ -0,0 +1,100 @@
+#ifndef __USBMIXER_H
+#define __USBMIXER_H
+
+#include <sound/info.h>
+
+struct usb_mixer_interface {
+ struct snd_usb_audio *chip;
+ struct usb_host_interface *hostif;
+ struct list_head list;
+ unsigned int ignore_ctl_error;
+ struct urb *urb;
+ /* array[MAX_ID_ELEMS], indexed by unit id */
+ struct usb_mixer_elem_list **id_elems;
+
+ /* the usb audio specification version this interface complies to */
+ int protocol;
+
+ /* Sound Blaster remote control stuff */
+ const struct rc_config *rc_cfg;
+ u32 rc_code;
+ wait_queue_head_t rc_waitq;
+ struct urb *rc_urb;
+ struct usb_ctrlrequest *rc_setup_packet;
+ u8 rc_buffer[6];
+};
+
+#define MAX_CHANNELS 16 /* max logical channels */
+
+enum {
+ USB_MIXER_BOOLEAN,
+ USB_MIXER_INV_BOOLEAN,
+ USB_MIXER_S8,
+ USB_MIXER_U8,
+ USB_MIXER_S16,
+ USB_MIXER_U16,
+};
+
+typedef void (*usb_mixer_elem_dump_func_t)(struct snd_info_buffer *buffer,
+ struct usb_mixer_elem_list *list);
+typedef int (*usb_mixer_elem_resume_func_t)(struct usb_mixer_elem_list *elem);
+
+struct usb_mixer_elem_list {
+ struct usb_mixer_interface *mixer;
+ struct usb_mixer_elem_list *next_id_elem; /* list of controls with same id */
+ struct snd_kcontrol *kctl;
+ unsigned int id;
+ usb_mixer_elem_dump_func_t dump;
+ usb_mixer_elem_resume_func_t resume;
+};
+
+struct usb_mixer_elem_info {
+ struct usb_mixer_elem_list head;
+ unsigned int control; /* CS or ICN (high byte) */
+ unsigned int cmask; /* channel mask bitmap: 0 = master */
+ unsigned int idx_off; /* Control index offset */
+ unsigned int ch_readonly;
+ unsigned int master_readonly;
+ int channels;
+ int val_type;
+ int min, max, res;
+ int dBmin, dBmax;
+ int cached;
+ int cache_val[MAX_CHANNELS];
+ u8 initialized;
+ void *private_data;
+};
+
+int snd_usb_create_mixer(struct snd_usb_audio *chip, int ctrlif,
+ int ignore_error);
+void snd_usb_mixer_disconnect(struct usb_mixer_interface *mixer);
+
+void snd_usb_mixer_notify_id(struct usb_mixer_interface *mixer, int unitid);
+
+int snd_usb_mixer_set_ctl_value(struct usb_mixer_elem_info *cval,
+ int request, int validx, int value_set);
+
+int snd_usb_mixer_add_control(struct usb_mixer_elem_list *list,
+ struct snd_kcontrol *kctl);
+
+void snd_usb_mixer_elem_init_std(struct usb_mixer_elem_list *list,
+ struct usb_mixer_interface *mixer,
+ int unitid);
+
+int snd_usb_mixer_vol_tlv(struct snd_kcontrol *kcontrol, int op_flag,
+ unsigned int size, unsigned int __user *_tlv);
+
+#ifdef CONFIG_PM
+int snd_usb_mixer_suspend(struct usb_mixer_interface *mixer);
+int snd_usb_mixer_resume(struct usb_mixer_interface *mixer, bool reset_resume);
+#endif
+
+int snd_usb_set_cur_mix_value(struct usb_mixer_elem_info *cval, int channel,
+ int index, int value);
+
+int snd_usb_get_cur_mix_value(struct usb_mixer_elem_info *cval,
+ int channel, int index, int *value);
+
+extern void snd_usb_mixer_elem_free(struct snd_kcontrol *kctl);
+
+#endif /* __USBMIXER_H */
diff --git a/sound/usb/mixer_maps.c b/sound/usb/mixer_maps.c
new file mode 100644
index 000000000..e5000da9e
--- /dev/null
+++ b/sound/usb/mixer_maps.c
@@ -0,0 +1,456 @@
+/*
+ * Additional mixer mapping
+ *
+ * Copyright (c) 2002 by Takashi Iwai <tiwai@suse.de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+
+struct usbmix_dB_map {
+ u32 min;
+ u32 max;
+};
+
+struct usbmix_name_map {
+ int id;
+ const char *name;
+ int control;
+ struct usbmix_dB_map *dB;
+};
+
+struct usbmix_selector_map {
+ int id;
+ int count;
+ const char **names;
+};
+
+struct usbmix_ctl_map {
+ u32 id;
+ const struct usbmix_name_map *map;
+ const struct usbmix_selector_map *selector_map;
+ int ignore_ctl_error;
+};
+
+/*
+ * USB control mappers for SB Exitigy
+ */
+
+/*
+ * Topology of SB Extigy (see on the wide screen :)
+
+USB_IN[1] --->FU[2]------------------------------+->MU[16]-->PU[17]-+->FU[18]--+->EU[27]--+->EU[21]-->FU[22]--+->FU[23] > Dig_OUT[24]
+ ^ | | | |
+USB_IN[3] -+->SU[5]-->FU[6]--+->MU[14] ->PU[15]->+ | | | +->FU[25] > Dig_OUT[26]
+ ^ ^ | | | |
+Dig_IN[4] -+ | | | | +->FU[28]---------------------> Spk_OUT[19]
+ | | | |
+Lin-IN[7] -+-->FU[8]---------+ | | +----------------------------------------> Hph_OUT[20]
+ | | |
+Mic-IN[9] --+->FU[10]----------------------------+ |
+ || |
+ || +----------------------------------------------------+
+ VV V
+ ++--+->SU[11]-->FU[12] --------------------------------------------------------------------------------------> USB_OUT[13]
+*/
+
+static struct usbmix_name_map extigy_map[] = {
+ /* 1: IT pcm */
+ { 2, "PCM Playback" }, /* FU */
+ /* 3: IT pcm */
+ /* 4: IT digital in */
+ { 5, NULL }, /* DISABLED: this seems to be bogus on some firmware */
+ { 6, "Digital In" }, /* FU */
+ /* 7: IT line */
+ { 8, "Line Playback" }, /* FU */
+ /* 9: IT mic */
+ { 10, "Mic Playback" }, /* FU */
+ { 11, "Capture Source" }, /* SU */
+ { 12, "Capture" }, /* FU */
+ /* 13: OT pcm capture */
+ /* 14: MU (w/o controls) */
+ /* 15: PU (3D enh) */
+ /* 16: MU (w/o controls) */
+ { 17, NULL, 1 }, /* DISABLED: PU-switch (any effect?) */
+ { 17, "Channel Routing", 2 }, /* PU: mode select */
+ { 18, "Tone Control - Bass", UAC_FU_BASS }, /* FU */
+ { 18, "Tone Control - Treble", UAC_FU_TREBLE }, /* FU */
+ { 18, "Master Playback" }, /* FU; others */
+ /* 19: OT speaker */
+ /* 20: OT headphone */
+ { 21, NULL }, /* DISABLED: EU (for what?) */
+ { 22, "Digital Out Playback" }, /* FU */
+ { 23, "Digital Out1 Playback" }, /* FU */ /* FIXME: corresponds to 24 */
+ /* 24: OT digital out */
+ { 25, "IEC958 Optical Playback" }, /* FU */
+ { 26, "IEC958 Optical Playback" }, /* OT */
+ { 27, NULL }, /* DISABLED: EU (for what?) */
+ /* 28: FU speaker (mute) */
+ { 29, NULL }, /* Digital Input Playback Source? */
+ { 0 } /* terminator */
+};
+
+/* Sound Blaster MP3+ controls mapping
+ * The default mixer channels have totally misleading names,
+ * e.g. no Master and fake PCM volume
+ * Pavel Mihaylov <bin@bash.info>
+ */
+static struct usbmix_dB_map mp3plus_dB_1 = {-4781, 0}; /* just guess */
+static struct usbmix_dB_map mp3plus_dB_2 = {-1781, 618}; /* just guess */
+
+static struct usbmix_name_map mp3plus_map[] = {
+ /* 1: IT pcm */
+ /* 2: IT mic */
+ /* 3: IT line */
+ /* 4: IT digital in */
+ /* 5: OT digital out */
+ /* 6: OT speaker */
+ /* 7: OT pcm capture */
+ { 8, "Capture Source" }, /* FU, default PCM Capture Source */
+ /* (Mic, Input 1 = Line input, Input 2 = Optical input) */
+ { 9, "Master Playback" }, /* FU, default Speaker 1 */
+ /* { 10, "Mic Capture", 1 }, */ /* FU, Mic Capture */
+ { 10, /* "Mic Capture", */ NULL, 2, .dB = &mp3plus_dB_2 },
+ /* FU, Mic Capture */
+ { 10, "Mic Boost", 7 }, /* FU, default Auto Gain Input */
+ { 11, "Line Capture", .dB = &mp3plus_dB_2 },
+ /* FU, default PCM Capture */
+ { 12, "Digital In Playback" }, /* FU, default PCM 1 */
+ { 13, /* "Mic Playback", */ .dB = &mp3plus_dB_1 },
+ /* FU, default Mic Playback */
+ { 14, "Line Playback", .dB = &mp3plus_dB_1 }, /* FU, default Speaker */
+ /* 15: MU */
+ { 0 } /* terminator */
+};
+
+/* Topology of SB Audigy 2 NX
+
+ +----------------------------->EU[27]--+
+ | v
+ | +----------------------------------->SU[29]---->FU[22]-->Dig_OUT[24]
+ | | ^
+USB_IN[1]-+------------+ +->EU[17]->+->FU[11]-+
+ | v | v |
+Dig_IN[4]---+->FU[6]-->MU[16]->FU[18]-+->EU[21]->SU[31]----->FU[30]->Hph_OUT[20]
+ | ^ | |
+Lin_IN[7]-+--->FU[8]---+ +->EU[23]->FU[28]------------->Spk_OUT[19]
+ | | v
+ +--->FU[12]------------------------------------->SU[14]--->USB_OUT[15]
+ | ^
+ +->FU[13]--------------------------------------+
+*/
+static struct usbmix_name_map audigy2nx_map[] = {
+ /* 1: IT pcm playback */
+ /* 4: IT digital in */
+ { 6, "Digital In Playback" }, /* FU */
+ /* 7: IT line in */
+ { 8, "Line Playback" }, /* FU */
+ { 11, "What-U-Hear Capture" }, /* FU */
+ { 12, "Line Capture" }, /* FU */
+ { 13, "Digital In Capture" }, /* FU */
+ { 14, "Capture Source" }, /* SU */
+ /* 15: OT pcm capture */
+ /* 16: MU w/o controls */
+ { 17, NULL }, /* DISABLED: EU (for what?) */
+ { 18, "Master Playback" }, /* FU */
+ /* 19: OT speaker */
+ /* 20: OT headphone */
+ { 21, NULL }, /* DISABLED: EU (for what?) */
+ { 22, "Digital Out Playback" }, /* FU */
+ { 23, NULL }, /* DISABLED: EU (for what?) */
+ /* 24: OT digital out */
+ { 27, NULL }, /* DISABLED: EU (for what?) */
+ { 28, "Speaker Playback" }, /* FU */
+ { 29, "Digital Out Source" }, /* SU */
+ { 30, "Headphone Playback" }, /* FU */
+ { 31, "Headphone Source" }, /* SU */
+ { 0 } /* terminator */
+};
+
+static struct usbmix_name_map mbox1_map[] = {
+ { 1, "Clock" },
+ { 0 } /* terminator */
+};
+
+static struct usbmix_selector_map c400_selectors[] = {
+ {
+ .id = 0x80,
+ .count = 2,
+ .names = (const char*[]) {"Internal", "SPDIF"}
+ },
+ { 0 } /* terminator */
+};
+
+static struct usbmix_selector_map audigy2nx_selectors[] = {
+ {
+ .id = 14, /* Capture Source */
+ .count = 3,
+ .names = (const char*[]) {"Line", "Digital In", "What-U-Hear"}
+ },
+ {
+ .id = 29, /* Digital Out Source */
+ .count = 3,
+ .names = (const char*[]) {"Front", "PCM", "Digital In"}
+ },
+ {
+ .id = 31, /* Headphone Source */
+ .count = 2,
+ .names = (const char*[]) {"Front", "Side"}
+ },
+ { 0 } /* terminator */
+};
+
+/* Creative SoundBlaster Live! 24-bit External */
+static struct usbmix_name_map live24ext_map[] = {
+ /* 2: PCM Playback Volume */
+ { 5, "Mic Capture" }, /* FU, default PCM Capture Volume */
+ { 0 } /* terminator */
+};
+
+/* LineX FM Transmitter entry - needed to bypass controls bug */
+static struct usbmix_name_map linex_map[] = {
+ /* 1: IT pcm */
+ /* 2: OT Speaker */
+ { 3, "Master" }, /* FU: master volume - left / right / mute */
+ { 0 } /* terminator */
+};
+
+static struct usbmix_name_map maya44_map[] = {
+ /* 1: IT line */
+ { 2, "Line Playback" }, /* FU */
+ /* 3: IT line */
+ { 4, "Line Playback" }, /* FU */
+ /* 5: IT pcm playback */
+ /* 6: MU */
+ { 7, "Master Playback" }, /* FU */
+ /* 8: OT speaker */
+ /* 9: IT line */
+ { 10, "Line Capture" }, /* FU */
+ /* 11: MU */
+ /* 12: OT pcm capture */
+ { }
+};
+
+/* Section "justlink_map" below added by James Courtier-Dutton <James@superbug.demon.co.uk>
+ * sourced from Maplin Electronics (http://www.maplin.co.uk), part number A56AK
+ * Part has 2 connectors that act as a single output. (TOSLINK Optical for digital out, and 3.5mm Jack for Analogue out.)
+ * The USB Mixer publishes a Microphone and extra Volume controls for it, but none exist on the device,
+ * so this map removes all unwanted sliders from alsamixer
+ */
+
+static struct usbmix_name_map justlink_map[] = {
+ /* 1: IT pcm playback */
+ /* 2: Not present */
+ { 3, NULL}, /* IT mic (No mic input on device) */
+ /* 4: Not present */
+ /* 5: OT speacker */
+ /* 6: OT pcm capture */
+ { 7, "Master Playback" }, /* Mute/volume for speaker */
+ { 8, NULL }, /* Capture Switch (No capture inputs on device) */
+ { 9, NULL }, /* Capture Mute/volume (No capture inputs on device */
+ /* 0xa: Not present */
+ /* 0xb: MU (w/o controls) */
+ { 0xc, NULL }, /* Mic feedback Mute/volume (No capture inputs on device) */
+ { 0 } /* terminator */
+};
+
+/* TerraTec Aureon 5.1 MkII USB */
+static struct usbmix_name_map aureon_51_2_map[] = {
+ /* 1: IT USB */
+ /* 2: IT Mic */
+ /* 3: IT Line */
+ /* 4: IT SPDIF */
+ /* 5: OT SPDIF */
+ /* 6: OT Speaker */
+ /* 7: OT USB */
+ { 8, "Capture Source" }, /* SU */
+ { 9, "Master Playback" }, /* FU */
+ { 10, "Mic Capture" }, /* FU */
+ { 11, "Line Capture" }, /* FU */
+ { 12, "IEC958 In Capture" }, /* FU */
+ { 13, "Mic Playback" }, /* FU */
+ { 14, "Line Playback" }, /* FU */
+ /* 15: MU */
+ {} /* terminator */
+};
+
+static struct usbmix_name_map scratch_live_map[] = {
+ /* 1: IT Line 1 (USB streaming) */
+ /* 2: OT Line 1 (Speaker) */
+ /* 3: IT Line 1 (Line connector) */
+ { 4, "Line 1 In" }, /* FU */
+ /* 5: OT Line 1 (USB streaming) */
+ /* 6: IT Line 2 (USB streaming) */
+ /* 7: OT Line 2 (Speaker) */
+ /* 8: IT Line 2 (Line connector) */
+ { 9, "Line 2 In" }, /* FU */
+ /* 10: OT Line 2 (USB streaming) */
+ /* 11: IT Mic (Line connector) */
+ /* 12: OT Mic (USB streaming) */
+ { 0 } /* terminator */
+};
+
+static struct usbmix_name_map ebox44_map[] = {
+ { 4, NULL }, /* FU */
+ { 6, NULL }, /* MU */
+ { 7, NULL }, /* FU */
+ { 10, NULL }, /* FU */
+ { 11, NULL }, /* MU */
+ { 0 }
+};
+
+/* "Gamesurround Muse Pocket LT" looks same like "Sound Blaster MP3+"
+ * most importand difference is SU[8], it should be set to "Capture Source"
+ * to make alsamixer and PA working properly.
+ * FIXME: or mp3plus_map should use "Capture Source" too,
+ * so this maps can be merget
+ */
+static struct usbmix_name_map hercules_usb51_map[] = {
+ { 8, "Capture Source" }, /* SU, default "PCM Capture Source" */
+ { 9, "Master Playback" }, /* FU, default "Speaker Playback" */
+ { 10, "Mic Boost", 7 }, /* FU, default "Auto Gain Input" */
+ { 11, "Line Capture" }, /* FU, default "PCM Capture" */
+ { 13, "Mic Bypass Playback" }, /* FU, default "Mic Playback" */
+ { 14, "Line Bypass Playback" }, /* FU, default "Line Playback" */
+ { 0 } /* terminator */
+};
+
+/* Plantronics Gamecom 780 has a broken volume control, better to disable it */
+static struct usbmix_name_map gamecom780_map[] = {
+ { 9, NULL }, /* FU, speaker out */
+ {}
+};
+
+/* some (all?) SCMS USB3318 devices are affected by a firmware lock up
+ * when anything attempts to access FU 10 (control)
+ */
+static const struct usbmix_name_map scms_usb3318_map[] = {
+ { 10, NULL },
+ { 0 }
+};
+
+/*
+ * Control map entries
+ */
+
+static struct usbmix_ctl_map usbmix_ctl_maps[] = {
+ {
+ .id = USB_ID(0x041e, 0x3000),
+ .map = extigy_map,
+ .ignore_ctl_error = 1,
+ },
+ {
+ .id = USB_ID(0x041e, 0x3010),
+ .map = mp3plus_map,
+ },
+ {
+ .id = USB_ID(0x041e, 0x3020),
+ .map = audigy2nx_map,
+ .selector_map = audigy2nx_selectors,
+ },
+ {
+ .id = USB_ID(0x041e, 0x3040),
+ .map = live24ext_map,
+ },
+ {
+ .id = USB_ID(0x041e, 0x3048),
+ .map = audigy2nx_map,
+ .selector_map = audigy2nx_selectors,
+ },
+ { /* Logitech, Inc. QuickCam Pro for Notebooks */
+ .id = USB_ID(0x046d, 0x0991),
+ .ignore_ctl_error = 1,
+ },
+ { /* Logitech, Inc. QuickCam E 3500 */
+ .id = USB_ID(0x046d, 0x09a4),
+ .ignore_ctl_error = 1,
+ },
+ { /* Plantronics GameCom 780 */
+ .id = USB_ID(0x047f, 0xc010),
+ .map = gamecom780_map,
+ },
+ {
+ /* Hercules DJ Console (Windows Edition) */
+ .id = USB_ID(0x06f8, 0xb000),
+ .ignore_ctl_error = 1,
+ },
+ {
+ /* Hercules DJ Console (Macintosh Edition) */
+ .id = USB_ID(0x06f8, 0xd002),
+ .ignore_ctl_error = 1,
+ },
+ {
+ /* Hercules Gamesurround Muse Pocket LT
+ * (USB 5.1 Channel Audio Adapter)
+ */
+ .id = USB_ID(0x06f8, 0xc000),
+ .map = hercules_usb51_map,
+ },
+ {
+ .id = USB_ID(0x0763, 0x2030),
+ .selector_map = c400_selectors,
+ },
+ {
+ .id = USB_ID(0x0763, 0x2031),
+ .selector_map = c400_selectors,
+ },
+ {
+ .id = USB_ID(0x08bb, 0x2702),
+ .map = linex_map,
+ .ignore_ctl_error = 1,
+ },
+ {
+ .id = USB_ID(0x0a92, 0x0091),
+ .map = maya44_map,
+ },
+ {
+ .id = USB_ID(0x0c45, 0x1158),
+ .map = justlink_map,
+ },
+ {
+ .id = USB_ID(0x0ccd, 0x0028),
+ .map = aureon_51_2_map,
+ },
+ {
+ .id = USB_ID(0x0dba, 0x1000),
+ .map = mbox1_map,
+ },
+ {
+ .id = USB_ID(0x13e5, 0x0001),
+ .map = scratch_live_map,
+ .ignore_ctl_error = 1,
+ },
+ {
+ .id = USB_ID(0x200c, 0x1018),
+ .map = ebox44_map,
+ },
+ {
+ /* MAYA44 USB+ */
+ .id = USB_ID(0x2573, 0x0008),
+ .map = maya44_map,
+ },
+ {
+ /* KEF X300A */
+ .id = USB_ID(0x27ac, 0x1000),
+ .map = scms_usb3318_map,
+ },
+ {
+ /* Arcam rPAC */
+ .id = USB_ID(0x25c4, 0x0003),
+ .map = scms_usb3318_map,
+ },
+ { 0 } /* terminator */
+};
+
diff --git a/sound/usb/mixer_quirks.c b/sound/usb/mixer_quirks.c
new file mode 100644
index 000000000..337c317ea
--- /dev/null
+++ b/sound/usb/mixer_quirks.c
@@ -0,0 +1,1845 @@
+/*
+ * USB Audio Driver for ALSA
+ *
+ * Quirks and vendor-specific extensions for mixer interfaces
+ *
+ * Copyright (c) 2002 by Takashi Iwai <tiwai@suse.de>
+ *
+ * Many codes borrowed from audio.c by
+ * Alan Cox (alan@lxorguk.ukuu.org.uk)
+ * Thomas Sailer (sailer@ife.ee.ethz.ch)
+ *
+ * Audio Advantage Micro II support added by:
+ * Przemek Rudy (prudy1@o2.pl)
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/usb.h>
+#include <linux/usb/audio.h>
+
+#include <sound/asoundef.h>
+#include <sound/core.h>
+#include <sound/control.h>
+#include <sound/hwdep.h>
+#include <sound/info.h>
+
+#include "usbaudio.h"
+#include "mixer.h"
+#include "mixer_quirks.h"
+#include "mixer_scarlett.h"
+#include "helper.h"
+
+extern struct snd_kcontrol_new *snd_usb_feature_unit_ctl;
+
+struct std_mono_table {
+ unsigned int unitid, control, cmask;
+ int val_type;
+ const char *name;
+ snd_kcontrol_tlv_rw_t *tlv_callback;
+};
+
+/* This function allows for the creation of standard UAC controls.
+ * See the quirks for M-Audio FTUs or Ebox-44.
+ * If you don't want to set a TLV callback pass NULL.
+ *
+ * Since there doesn't seem to be a devices that needs a multichannel
+ * version, we keep it mono for simplicity.
+ */
+static int snd_create_std_mono_ctl_offset(struct usb_mixer_interface *mixer,
+ unsigned int unitid,
+ unsigned int control,
+ unsigned int cmask,
+ int val_type,
+ unsigned int idx_off,
+ const char *name,
+ snd_kcontrol_tlv_rw_t *tlv_callback)
+{
+ struct usb_mixer_elem_info *cval;
+ struct snd_kcontrol *kctl;
+
+ cval = kzalloc(sizeof(*cval), GFP_KERNEL);
+ if (!cval)
+ return -ENOMEM;
+
+ snd_usb_mixer_elem_init_std(&cval->head, mixer, unitid);
+ cval->val_type = val_type;
+ cval->channels = 1;
+ cval->control = control;
+ cval->cmask = cmask;
+ cval->idx_off = idx_off;
+
+ /* get_min_max() is called only for integer volumes later,
+ * so provide a short-cut for booleans */
+ cval->min = 0;
+ cval->max = 1;
+ cval->res = 0;
+ cval->dBmin = 0;
+ cval->dBmax = 0;
+
+ /* Create control */
+ kctl = snd_ctl_new1(snd_usb_feature_unit_ctl, cval);
+ if (!kctl) {
+ kfree(cval);
+ return -ENOMEM;
+ }
+
+ /* Set name */
+ snprintf(kctl->id.name, sizeof(kctl->id.name), name);
+ kctl->private_free = snd_usb_mixer_elem_free;
+
+ /* set TLV */
+ if (tlv_callback) {
+ kctl->tlv.c = tlv_callback;
+ kctl->vd[0].access |=
+ SNDRV_CTL_ELEM_ACCESS_TLV_READ |
+ SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK;
+ }
+ /* Add control to mixer */
+ return snd_usb_mixer_add_control(&cval->head, kctl);
+}
+
+static int snd_create_std_mono_ctl(struct usb_mixer_interface *mixer,
+ unsigned int unitid,
+ unsigned int control,
+ unsigned int cmask,
+ int val_type,
+ const char *name,
+ snd_kcontrol_tlv_rw_t *tlv_callback)
+{
+ return snd_create_std_mono_ctl_offset(mixer, unitid, control, cmask,
+ val_type, 0 /* Offset */, name, tlv_callback);
+}
+
+/*
+ * Create a set of standard UAC controls from a table
+ */
+static int snd_create_std_mono_table(struct usb_mixer_interface *mixer,
+ struct std_mono_table *t)
+{
+ int err;
+
+ while (t->name != NULL) {
+ err = snd_create_std_mono_ctl(mixer, t->unitid, t->control,
+ t->cmask, t->val_type, t->name, t->tlv_callback);
+ if (err < 0)
+ return err;
+ t++;
+ }
+
+ return 0;
+}
+
+static int add_single_ctl_with_resume(struct usb_mixer_interface *mixer,
+ int id,
+ usb_mixer_elem_resume_func_t resume,
+ const struct snd_kcontrol_new *knew,
+ struct usb_mixer_elem_list **listp)
+{
+ struct usb_mixer_elem_list *list;
+ struct snd_kcontrol *kctl;
+
+ list = kzalloc(sizeof(*list), GFP_KERNEL);
+ if (!list)
+ return -ENOMEM;
+ if (listp)
+ *listp = list;
+ list->mixer = mixer;
+ list->id = id;
+ list->resume = resume;
+ kctl = snd_ctl_new1(knew, list);
+ if (!kctl) {
+ kfree(list);
+ return -ENOMEM;
+ }
+ kctl->private_free = snd_usb_mixer_elem_free;
+ return snd_usb_mixer_add_control(list, kctl);
+}
+
+/*
+ * Sound Blaster remote control configuration
+ *
+ * format of remote control data:
+ * Extigy: xx 00
+ * Audigy 2 NX: 06 80 xx 00 00 00
+ * Live! 24-bit: 06 80 xx yy 22 83
+ */
+static const struct rc_config {
+ u32 usb_id;
+ u8 offset;
+ u8 length;
+ u8 packet_length;
+ u8 min_packet_length; /* minimum accepted length of the URB result */
+ u8 mute_mixer_id;
+ u32 mute_code;
+} rc_configs[] = {
+ { USB_ID(0x041e, 0x3000), 0, 1, 2, 1, 18, 0x0013 }, /* Extigy */
+ { USB_ID(0x041e, 0x3020), 2, 1, 6, 6, 18, 0x0013 }, /* Audigy 2 NX */
+ { USB_ID(0x041e, 0x3040), 2, 2, 6, 6, 2, 0x6e91 }, /* Live! 24-bit */
+ { USB_ID(0x041e, 0x3042), 0, 1, 1, 1, 1, 0x000d }, /* Usb X-Fi S51 */
+ { USB_ID(0x041e, 0x30df), 0, 1, 1, 1, 1, 0x000d }, /* Usb X-Fi S51 Pro */
+ { USB_ID(0x041e, 0x3237), 0, 1, 1, 1, 1, 0x000d }, /* Usb X-Fi S51 Pro */
+ { USB_ID(0x041e, 0x3048), 2, 2, 6, 6, 2, 0x6e91 }, /* Toshiba SB0500 */
+};
+
+static void snd_usb_soundblaster_remote_complete(struct urb *urb)
+{
+ struct usb_mixer_interface *mixer = urb->context;
+ const struct rc_config *rc = mixer->rc_cfg;
+ u32 code;
+
+ if (urb->status < 0 || urb->actual_length < rc->min_packet_length)
+ return;
+
+ code = mixer->rc_buffer[rc->offset];
+ if (rc->length == 2)
+ code |= mixer->rc_buffer[rc->offset + 1] << 8;
+
+ /* the Mute button actually changes the mixer control */
+ if (code == rc->mute_code)
+ snd_usb_mixer_notify_id(mixer, rc->mute_mixer_id);
+ mixer->rc_code = code;
+ wmb();
+ wake_up(&mixer->rc_waitq);
+}
+
+static long snd_usb_sbrc_hwdep_read(struct snd_hwdep *hw, char __user *buf,
+ long count, loff_t *offset)
+{
+ struct usb_mixer_interface *mixer = hw->private_data;
+ int err;
+ u32 rc_code;
+
+ if (count != 1 && count != 4)
+ return -EINVAL;
+ err = wait_event_interruptible(mixer->rc_waitq,
+ (rc_code = xchg(&mixer->rc_code, 0)) != 0);
+ if (err == 0) {
+ if (count == 1)
+ err = put_user(rc_code, buf);
+ else
+ err = put_user(rc_code, (u32 __user *)buf);
+ }
+ return err < 0 ? err : count;
+}
+
+static unsigned int snd_usb_sbrc_hwdep_poll(struct snd_hwdep *hw, struct file *file,
+ poll_table *wait)
+{
+ struct usb_mixer_interface *mixer = hw->private_data;
+
+ poll_wait(file, &mixer->rc_waitq, wait);
+ return mixer->rc_code ? POLLIN | POLLRDNORM : 0;
+}
+
+static int snd_usb_soundblaster_remote_init(struct usb_mixer_interface *mixer)
+{
+ struct snd_hwdep *hwdep;
+ int err, len, i;
+
+ for (i = 0; i < ARRAY_SIZE(rc_configs); ++i)
+ if (rc_configs[i].usb_id == mixer->chip->usb_id)
+ break;
+ if (i >= ARRAY_SIZE(rc_configs))
+ return 0;
+ mixer->rc_cfg = &rc_configs[i];
+
+ len = mixer->rc_cfg->packet_length;
+
+ init_waitqueue_head(&mixer->rc_waitq);
+ err = snd_hwdep_new(mixer->chip->card, "SB remote control", 0, &hwdep);
+ if (err < 0)
+ return err;
+ snprintf(hwdep->name, sizeof(hwdep->name),
+ "%s remote control", mixer->chip->card->shortname);
+ hwdep->iface = SNDRV_HWDEP_IFACE_SB_RC;
+ hwdep->private_data = mixer;
+ hwdep->ops.read = snd_usb_sbrc_hwdep_read;
+ hwdep->ops.poll = snd_usb_sbrc_hwdep_poll;
+ hwdep->exclusive = 1;
+
+ mixer->rc_urb = usb_alloc_urb(0, GFP_KERNEL);
+ if (!mixer->rc_urb)
+ return -ENOMEM;
+ mixer->rc_setup_packet = kmalloc(sizeof(*mixer->rc_setup_packet), GFP_KERNEL);
+ if (!mixer->rc_setup_packet) {
+ usb_free_urb(mixer->rc_urb);
+ mixer->rc_urb = NULL;
+ return -ENOMEM;
+ }
+ mixer->rc_setup_packet->bRequestType =
+ USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE;
+ mixer->rc_setup_packet->bRequest = UAC_GET_MEM;
+ mixer->rc_setup_packet->wValue = cpu_to_le16(0);
+ mixer->rc_setup_packet->wIndex = cpu_to_le16(0);
+ mixer->rc_setup_packet->wLength = cpu_to_le16(len);
+ usb_fill_control_urb(mixer->rc_urb, mixer->chip->dev,
+ usb_rcvctrlpipe(mixer->chip->dev, 0),
+ (u8*)mixer->rc_setup_packet, mixer->rc_buffer, len,
+ snd_usb_soundblaster_remote_complete, mixer);
+ return 0;
+}
+
+#define snd_audigy2nx_led_info snd_ctl_boolean_mono_info
+
+static int snd_audigy2nx_led_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+ ucontrol->value.integer.value[0] = kcontrol->private_value >> 8;
+ return 0;
+}
+
+static int snd_audigy2nx_led_update(struct usb_mixer_interface *mixer,
+ int value, int index)
+{
+ struct snd_usb_audio *chip = mixer->chip;
+ int err;
+
+ down_read(&chip->shutdown_rwsem);
+ if (chip->shutdown) {
+ err = -ENODEV;
+ goto out;
+ }
+ if (chip->usb_id == USB_ID(0x041e, 0x3042))
+ err = snd_usb_ctl_msg(chip->dev,
+ usb_sndctrlpipe(chip->dev, 0), 0x24,
+ USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_OTHER,
+ !value, 0, NULL, 0);
+ /* USB X-Fi S51 Pro */
+ if (chip->usb_id == USB_ID(0x041e, 0x30df))
+ err = snd_usb_ctl_msg(chip->dev,
+ usb_sndctrlpipe(chip->dev, 0), 0x24,
+ USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_OTHER,
+ !value, 0, NULL, 0);
+ else
+ err = snd_usb_ctl_msg(chip->dev,
+ usb_sndctrlpipe(chip->dev, 0), 0x24,
+ USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_OTHER,
+ value, index + 2, NULL, 0);
+ out:
+ up_read(&chip->shutdown_rwsem);
+ return err;
+}
+
+static int snd_audigy2nx_led_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct usb_mixer_elem_list *list = snd_kcontrol_chip(kcontrol);
+ struct usb_mixer_interface *mixer = list->mixer;
+ int index = kcontrol->private_value & 0xff;
+ int value = ucontrol->value.integer.value[0];
+ int old_value = kcontrol->private_value >> 8;
+ int err;
+
+ if (value > 1)
+ return -EINVAL;
+ if (value == old_value)
+ return 0;
+ kcontrol->private_value = (value << 8) | index;
+ err = snd_audigy2nx_led_update(mixer, value, index);
+ return err < 0 ? err : 1;
+}
+
+static int snd_audigy2nx_led_resume(struct usb_mixer_elem_list *list)
+{
+ int priv_value = list->kctl->private_value;
+
+ return snd_audigy2nx_led_update(list->mixer, priv_value >> 8,
+ priv_value & 0xff);
+}
+
+/* name and private_value are set dynamically */
+static struct snd_kcontrol_new snd_audigy2nx_control = {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .info = snd_audigy2nx_led_info,
+ .get = snd_audigy2nx_led_get,
+ .put = snd_audigy2nx_led_put,
+};
+
+static const char * const snd_audigy2nx_led_names[] = {
+ "CMSS LED Switch",
+ "Power LED Switch",
+ "Dolby Digital LED Switch",
+};
+
+static int snd_audigy2nx_controls_create(struct usb_mixer_interface *mixer)
+{
+ int i, err;
+
+ for (i = 0; i < ARRAY_SIZE(snd_audigy2nx_led_names); ++i) {
+ struct snd_kcontrol_new knew;
+
+ /* USB X-Fi S51 doesn't have a CMSS LED */
+ if ((mixer->chip->usb_id == USB_ID(0x041e, 0x3042)) && i == 0)
+ continue;
+ /* USB X-Fi S51 Pro doesn't have one either */
+ if ((mixer->chip->usb_id == USB_ID(0x041e, 0x30df)) && i == 0)
+ continue;
+ if (i > 1 && /* Live24ext has 2 LEDs only */
+ (mixer->chip->usb_id == USB_ID(0x041e, 0x3040) ||
+ mixer->chip->usb_id == USB_ID(0x041e, 0x3042) ||
+ mixer->chip->usb_id == USB_ID(0x041e, 0x30df) ||
+ mixer->chip->usb_id == USB_ID(0x041e, 0x3048)))
+ break;
+
+ knew = snd_audigy2nx_control;
+ knew.name = snd_audigy2nx_led_names[i];
+ knew.private_value = (1 << 8) | i; /* LED on as default */
+ err = add_single_ctl_with_resume(mixer, 0,
+ snd_audigy2nx_led_resume,
+ &knew, NULL);
+ if (err < 0)
+ return err;
+ }
+ return 0;
+}
+
+static void snd_audigy2nx_proc_read(struct snd_info_entry *entry,
+ struct snd_info_buffer *buffer)
+{
+ static const struct sb_jack {
+ int unitid;
+ const char *name;
+ } jacks_audigy2nx[] = {
+ {4, "dig in "},
+ {7, "line in"},
+ {19, "spk out"},
+ {20, "hph out"},
+ {-1, NULL}
+ }, jacks_live24ext[] = {
+ {4, "line in"}, /* &1=Line, &2=Mic*/
+ {3, "hph out"}, /* headphones */
+ {0, "RC "}, /* last command, 6 bytes see rc_config above */
+ {-1, NULL}
+ };
+ const struct sb_jack *jacks;
+ struct usb_mixer_interface *mixer = entry->private_data;
+ int i, err;
+ u8 buf[3];
+
+ snd_iprintf(buffer, "%s jacks\n\n", mixer->chip->card->shortname);
+ if (mixer->chip->usb_id == USB_ID(0x041e, 0x3020))
+ jacks = jacks_audigy2nx;
+ else if (mixer->chip->usb_id == USB_ID(0x041e, 0x3040) ||
+ mixer->chip->usb_id == USB_ID(0x041e, 0x3048))
+ jacks = jacks_live24ext;
+ else
+ return;
+
+ for (i = 0; jacks[i].name; ++i) {
+ snd_iprintf(buffer, "%s: ", jacks[i].name);
+ down_read(&mixer->chip->shutdown_rwsem);
+ if (mixer->chip->shutdown)
+ err = 0;
+ else
+ err = snd_usb_ctl_msg(mixer->chip->dev,
+ usb_rcvctrlpipe(mixer->chip->dev, 0),
+ UAC_GET_MEM, USB_DIR_IN | USB_TYPE_CLASS |
+ USB_RECIP_INTERFACE, 0,
+ jacks[i].unitid << 8, buf, 3);
+ up_read(&mixer->chip->shutdown_rwsem);
+ if (err == 3 && (buf[0] == 3 || buf[0] == 6))
+ snd_iprintf(buffer, "%02x %02x\n", buf[1], buf[2]);
+ else
+ snd_iprintf(buffer, "?\n");
+ }
+}
+
+/* EMU0204 */
+static int snd_emu0204_ch_switch_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ static const char * const texts[2] = {"1/2", "3/4"};
+
+ return snd_ctl_enum_info(uinfo, 1, ARRAY_SIZE(texts), texts);
+}
+
+static int snd_emu0204_ch_switch_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ ucontrol->value.enumerated.item[0] = kcontrol->private_value;
+ return 0;
+}
+
+static int snd_emu0204_ch_switch_update(struct usb_mixer_interface *mixer,
+ int value)
+{
+ struct snd_usb_audio *chip = mixer->chip;
+ int err;
+ unsigned char buf[2];
+
+ down_read(&chip->shutdown_rwsem);
+ if (mixer->chip->shutdown) {
+ err = -ENODEV;
+ goto out;
+ }
+
+ buf[0] = 0x01;
+ buf[1] = value ? 0x02 : 0x01;
+ err = snd_usb_ctl_msg(chip->dev,
+ usb_sndctrlpipe(chip->dev, 0), UAC_SET_CUR,
+ USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_OUT,
+ 0x0400, 0x0e00, buf, 2);
+ out:
+ up_read(&chip->shutdown_rwsem);
+ return err;
+}
+
+static int snd_emu0204_ch_switch_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct usb_mixer_elem_list *list = snd_kcontrol_chip(kcontrol);
+ struct usb_mixer_interface *mixer = list->mixer;
+ unsigned int value = ucontrol->value.enumerated.item[0];
+ int err;
+
+ if (value > 1)
+ return -EINVAL;
+
+ if (value == kcontrol->private_value)
+ return 0;
+
+ kcontrol->private_value = value;
+ err = snd_emu0204_ch_switch_update(mixer, value);
+ return err < 0 ? err : 1;
+}
+
+static int snd_emu0204_ch_switch_resume(struct usb_mixer_elem_list *list)
+{
+ return snd_emu0204_ch_switch_update(list->mixer,
+ list->kctl->private_value);
+}
+
+static struct snd_kcontrol_new snd_emu0204_control = {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Front Jack Channels",
+ .info = snd_emu0204_ch_switch_info,
+ .get = snd_emu0204_ch_switch_get,
+ .put = snd_emu0204_ch_switch_put,
+ .private_value = 0,
+};
+
+static int snd_emu0204_controls_create(struct usb_mixer_interface *mixer)
+{
+ return add_single_ctl_with_resume(mixer, 0,
+ snd_emu0204_ch_switch_resume,
+ &snd_emu0204_control, NULL);
+}
+
+/* ASUS Xonar U1 / U3 controls */
+
+static int snd_xonar_u1_switch_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ ucontrol->value.integer.value[0] = !!(kcontrol->private_value & 0x02);
+ return 0;
+}
+
+static int snd_xonar_u1_switch_update(struct usb_mixer_interface *mixer,
+ unsigned char status)
+{
+ struct snd_usb_audio *chip = mixer->chip;
+ int err;
+
+ down_read(&chip->shutdown_rwsem);
+ if (chip->shutdown)
+ err = -ENODEV;
+ else
+ err = snd_usb_ctl_msg(chip->dev,
+ usb_sndctrlpipe(chip->dev, 0), 0x08,
+ USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_OTHER,
+ 50, 0, &status, 1);
+ up_read(&chip->shutdown_rwsem);
+ return err;
+}
+
+static int snd_xonar_u1_switch_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct usb_mixer_elem_list *list = snd_kcontrol_chip(kcontrol);
+ u8 old_status, new_status;
+ int err;
+
+ old_status = kcontrol->private_value;
+ if (ucontrol->value.integer.value[0])
+ new_status = old_status | 0x02;
+ else
+ new_status = old_status & ~0x02;
+ if (new_status == old_status)
+ return 0;
+
+ kcontrol->private_value = new_status;
+ err = snd_xonar_u1_switch_update(list->mixer, new_status);
+ return err < 0 ? err : 1;
+}
+
+static int snd_xonar_u1_switch_resume(struct usb_mixer_elem_list *list)
+{
+ return snd_xonar_u1_switch_update(list->mixer,
+ list->kctl->private_value);
+}
+
+static struct snd_kcontrol_new snd_xonar_u1_output_switch = {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Digital Playback Switch",
+ .info = snd_ctl_boolean_mono_info,
+ .get = snd_xonar_u1_switch_get,
+ .put = snd_xonar_u1_switch_put,
+ .private_value = 0x05,
+};
+
+static int snd_xonar_u1_controls_create(struct usb_mixer_interface *mixer)
+{
+ return add_single_ctl_with_resume(mixer, 0,
+ snd_xonar_u1_switch_resume,
+ &snd_xonar_u1_output_switch, NULL);
+}
+
+/* Digidesign Mbox 1 clock source switch (internal/spdif) */
+
+static int snd_mbox1_switch_get(struct snd_kcontrol *kctl,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ ucontrol->value.enumerated.item[0] = kctl->private_value;
+ return 0;
+}
+
+static int snd_mbox1_switch_update(struct usb_mixer_interface *mixer, int val)
+{
+ struct snd_usb_audio *chip = mixer->chip;
+ int err;
+ unsigned char buff[3];
+
+ down_read(&chip->shutdown_rwsem);
+ if (chip->shutdown) {
+ err = -ENODEV;
+ goto err;
+ }
+
+ /* Prepare for magic command to toggle clock source */
+ err = snd_usb_ctl_msg(chip->dev,
+ usb_rcvctrlpipe(chip->dev, 0), 0x81,
+ USB_DIR_IN |
+ USB_TYPE_CLASS |
+ USB_RECIP_INTERFACE, 0x00, 0x500, buff, 1);
+ if (err < 0)
+ goto err;
+ err = snd_usb_ctl_msg(chip->dev,
+ usb_rcvctrlpipe(chip->dev, 0), 0x81,
+ USB_DIR_IN |
+ USB_TYPE_CLASS |
+ USB_RECIP_ENDPOINT, 0x100, 0x81, buff, 3);
+ if (err < 0)
+ goto err;
+
+ /* 2 possibilities: Internal -> send sample rate
+ * S/PDIF sync -> send zeroes
+ * NB: Sample rate locked to 48kHz on purpose to
+ * prevent user from resetting the sample rate
+ * while S/PDIF sync is enabled and confusing
+ * this configuration.
+ */
+ if (val == 0) {
+ buff[0] = 0x80;
+ buff[1] = 0xbb;
+ buff[2] = 0x00;
+ } else {
+ buff[0] = buff[1] = buff[2] = 0x00;
+ }
+
+ /* Send the magic command to toggle the clock source */
+ err = snd_usb_ctl_msg(chip->dev,
+ usb_sndctrlpipe(chip->dev, 0), 0x1,
+ USB_TYPE_CLASS |
+ USB_RECIP_ENDPOINT, 0x100, 0x81, buff, 3);
+ if (err < 0)
+ goto err;
+ err = snd_usb_ctl_msg(chip->dev,
+ usb_rcvctrlpipe(chip->dev, 0), 0x81,
+ USB_DIR_IN |
+ USB_TYPE_CLASS |
+ USB_RECIP_ENDPOINT, 0x100, 0x81, buff, 3);
+ if (err < 0)
+ goto err;
+ err = snd_usb_ctl_msg(chip->dev,
+ usb_rcvctrlpipe(chip->dev, 0), 0x81,
+ USB_DIR_IN |
+ USB_TYPE_CLASS |
+ USB_RECIP_ENDPOINT, 0x100, 0x2, buff, 3);
+ if (err < 0)
+ goto err;
+
+err:
+ up_read(&chip->shutdown_rwsem);
+ return err;
+}
+
+static int snd_mbox1_switch_put(struct snd_kcontrol *kctl,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct usb_mixer_elem_list *list = snd_kcontrol_chip(kctl);
+ struct usb_mixer_interface *mixer = list->mixer;
+ int err;
+ bool cur_val, new_val;
+
+ cur_val = kctl->private_value;
+ new_val = ucontrol->value.enumerated.item[0];
+ if (cur_val == new_val)
+ return 0;
+
+ kctl->private_value = new_val;
+ err = snd_mbox1_switch_update(mixer, new_val);
+ return err < 0 ? err : 1;
+}
+
+static int snd_mbox1_switch_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ static const char *const texts[2] = {
+ "Internal",
+ "S/PDIF"
+ };
+
+ return snd_ctl_enum_info(uinfo, 1, ARRAY_SIZE(texts), texts);
+}
+
+static int snd_mbox1_switch_resume(struct usb_mixer_elem_list *list)
+{
+ return snd_mbox1_switch_update(list->mixer, list->kctl->private_value);
+}
+
+static struct snd_kcontrol_new snd_mbox1_switch = {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Clock Source",
+ .index = 0,
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+ .info = snd_mbox1_switch_info,
+ .get = snd_mbox1_switch_get,
+ .put = snd_mbox1_switch_put,
+ .private_value = 0
+};
+
+static int snd_mbox1_create_sync_switch(struct usb_mixer_interface *mixer)
+{
+ return add_single_ctl_with_resume(mixer, 0,
+ snd_mbox1_switch_resume,
+ &snd_mbox1_switch, NULL);
+}
+
+/* Native Instruments device quirks */
+
+#define _MAKE_NI_CONTROL(bRequest,wIndex) ((bRequest) << 16 | (wIndex))
+
+static int snd_ni_control_init_val(struct usb_mixer_interface *mixer,
+ struct snd_kcontrol *kctl)
+{
+ struct usb_device *dev = mixer->chip->dev;
+ unsigned int pval = kctl->private_value;
+ u8 value;
+ int err;
+
+ err = snd_usb_ctl_msg(dev, usb_rcvctrlpipe(dev, 0),
+ (pval >> 16) & 0xff,
+ USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_IN,
+ 0, pval & 0xffff, &value, 1);
+ if (err < 0) {
+ dev_err(&dev->dev,
+ "unable to issue vendor read request (ret = %d)", err);
+ return err;
+ }
+
+ kctl->private_value |= (value << 24);
+ return 0;
+}
+
+static int snd_nativeinstruments_control_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ ucontrol->value.integer.value[0] = kcontrol->private_value >> 24;
+ return 0;
+}
+
+static int snd_ni_update_cur_val(struct usb_mixer_elem_list *list)
+{
+ struct snd_usb_audio *chip = list->mixer->chip;
+ unsigned int pval = list->kctl->private_value;
+ int err;
+
+ down_read(&chip->shutdown_rwsem);
+ if (chip->shutdown)
+ err = -ENODEV;
+ else
+ err = usb_control_msg(chip->dev, usb_sndctrlpipe(chip->dev, 0),
+ (pval >> 16) & 0xff,
+ USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_OUT,
+ pval >> 24, pval & 0xffff, NULL, 0, 1000);
+ up_read(&chip->shutdown_rwsem);
+ return err;
+}
+
+static int snd_nativeinstruments_control_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct usb_mixer_elem_list *list = snd_kcontrol_chip(kcontrol);
+ u8 oldval = (kcontrol->private_value >> 24) & 0xff;
+ u8 newval = ucontrol->value.integer.value[0];
+ int err;
+
+ if (oldval == newval)
+ return 0;
+
+ kcontrol->private_value &= ~(0xff << 24);
+ kcontrol->private_value |= newval;
+ err = snd_ni_update_cur_val(list);
+ return err < 0 ? err : 1;
+}
+
+static struct snd_kcontrol_new snd_nativeinstruments_ta6_mixers[] = {
+ {
+ .name = "Direct Thru Channel A",
+ .private_value = _MAKE_NI_CONTROL(0x01, 0x03),
+ },
+ {
+ .name = "Direct Thru Channel B",
+ .private_value = _MAKE_NI_CONTROL(0x01, 0x05),
+ },
+ {
+ .name = "Phono Input Channel A",
+ .private_value = _MAKE_NI_CONTROL(0x02, 0x03),
+ },
+ {
+ .name = "Phono Input Channel B",
+ .private_value = _MAKE_NI_CONTROL(0x02, 0x05),
+ },
+};
+
+static struct snd_kcontrol_new snd_nativeinstruments_ta10_mixers[] = {
+ {
+ .name = "Direct Thru Channel A",
+ .private_value = _MAKE_NI_CONTROL(0x01, 0x03),
+ },
+ {
+ .name = "Direct Thru Channel B",
+ .private_value = _MAKE_NI_CONTROL(0x01, 0x05),
+ },
+ {
+ .name = "Direct Thru Channel C",
+ .private_value = _MAKE_NI_CONTROL(0x01, 0x07),
+ },
+ {
+ .name = "Direct Thru Channel D",
+ .private_value = _MAKE_NI_CONTROL(0x01, 0x09),
+ },
+ {
+ .name = "Phono Input Channel A",
+ .private_value = _MAKE_NI_CONTROL(0x02, 0x03),
+ },
+ {
+ .name = "Phono Input Channel B",
+ .private_value = _MAKE_NI_CONTROL(0x02, 0x05),
+ },
+ {
+ .name = "Phono Input Channel C",
+ .private_value = _MAKE_NI_CONTROL(0x02, 0x07),
+ },
+ {
+ .name = "Phono Input Channel D",
+ .private_value = _MAKE_NI_CONTROL(0x02, 0x09),
+ },
+};
+
+static int snd_nativeinstruments_create_mixer(struct usb_mixer_interface *mixer,
+ const struct snd_kcontrol_new *kc,
+ unsigned int count)
+{
+ int i, err = 0;
+ struct snd_kcontrol_new template = {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+ .get = snd_nativeinstruments_control_get,
+ .put = snd_nativeinstruments_control_put,
+ .info = snd_ctl_boolean_mono_info,
+ };
+
+ for (i = 0; i < count; i++) {
+ struct usb_mixer_elem_list *list;
+
+ template.name = kc[i].name;
+ template.private_value = kc[i].private_value;
+
+ err = add_single_ctl_with_resume(mixer, 0,
+ snd_ni_update_cur_val,
+ &template, &list);
+ if (err < 0)
+ break;
+ snd_ni_control_init_val(mixer, list->kctl);
+ }
+
+ return err;
+}
+
+/* M-Audio FastTrack Ultra quirks */
+/* FTU Effect switch (also used by C400/C600) */
+static int snd_ftu_eff_switch_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ static const char *const texts[8] = {
+ "Room 1", "Room 2", "Room 3", "Hall 1",
+ "Hall 2", "Plate", "Delay", "Echo"
+ };
+
+ return snd_ctl_enum_info(uinfo, 1, ARRAY_SIZE(texts), texts);
+}
+
+static int snd_ftu_eff_switch_init(struct usb_mixer_interface *mixer,
+ struct snd_kcontrol *kctl)
+{
+ struct usb_device *dev = mixer->chip->dev;
+ unsigned int pval = kctl->private_value;
+ int err;
+ unsigned char value[2];
+
+ value[0] = 0x00;
+ value[1] = 0x00;
+
+ err = snd_usb_ctl_msg(dev, usb_rcvctrlpipe(dev, 0), UAC_GET_CUR,
+ USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_IN,
+ pval & 0xff00,
+ snd_usb_ctrl_intf(mixer->chip) | ((pval & 0xff) << 8),
+ value, 2);
+ if (err < 0)
+ return err;
+
+ kctl->private_value |= value[0] << 24;
+ return 0;
+}
+
+static int snd_ftu_eff_switch_get(struct snd_kcontrol *kctl,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ ucontrol->value.enumerated.item[0] = kctl->private_value >> 24;
+ return 0;
+}
+
+static int snd_ftu_eff_switch_update(struct usb_mixer_elem_list *list)
+{
+ struct snd_usb_audio *chip = list->mixer->chip;
+ unsigned int pval = list->kctl->private_value;
+ unsigned char value[2];
+ int err;
+
+ value[0] = pval >> 24;
+ value[1] = 0;
+
+ down_read(&chip->shutdown_rwsem);
+ if (chip->shutdown)
+ err = -ENODEV;
+ else
+ err = snd_usb_ctl_msg(chip->dev,
+ usb_sndctrlpipe(chip->dev, 0),
+ UAC_SET_CUR,
+ USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_OUT,
+ pval & 0xff00,
+ snd_usb_ctrl_intf(chip) | ((pval & 0xff) << 8),
+ value, 2);
+ up_read(&chip->shutdown_rwsem);
+ return err;
+}
+
+static int snd_ftu_eff_switch_put(struct snd_kcontrol *kctl,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct usb_mixer_elem_list *list = snd_kcontrol_chip(kctl);
+ unsigned int pval = list->kctl->private_value;
+ int cur_val, err, new_val;
+
+ cur_val = pval >> 24;
+ new_val = ucontrol->value.enumerated.item[0];
+ if (cur_val == new_val)
+ return 0;
+
+ kctl->private_value &= ~(0xff << 24);
+ kctl->private_value |= new_val << 24;
+ err = snd_ftu_eff_switch_update(list);
+ return err < 0 ? err : 1;
+}
+
+static int snd_ftu_create_effect_switch(struct usb_mixer_interface *mixer,
+ int validx, int bUnitID)
+{
+ static struct snd_kcontrol_new template = {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Effect Program Switch",
+ .index = 0,
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+ .info = snd_ftu_eff_switch_info,
+ .get = snd_ftu_eff_switch_get,
+ .put = snd_ftu_eff_switch_put
+ };
+ struct usb_mixer_elem_list *list;
+ int err;
+
+ err = add_single_ctl_with_resume(mixer, bUnitID,
+ snd_ftu_eff_switch_update,
+ &template, &list);
+ if (err < 0)
+ return err;
+ list->kctl->private_value = (validx << 8) | bUnitID;
+ snd_ftu_eff_switch_init(mixer, list->kctl);
+ return 0;
+}
+
+/* Create volume controls for FTU devices*/
+static int snd_ftu_create_volume_ctls(struct usb_mixer_interface *mixer)
+{
+ char name[64];
+ unsigned int control, cmask;
+ int in, out, err;
+
+ const unsigned int id = 5;
+ const int val_type = USB_MIXER_S16;
+
+ for (out = 0; out < 8; out++) {
+ control = out + 1;
+ for (in = 0; in < 8; in++) {
+ cmask = 1 << in;
+ snprintf(name, sizeof(name),
+ "AIn%d - Out%d Capture Volume",
+ in + 1, out + 1);
+ err = snd_create_std_mono_ctl(mixer, id, control,
+ cmask, val_type, name,
+ &snd_usb_mixer_vol_tlv);
+ if (err < 0)
+ return err;
+ }
+ for (in = 8; in < 16; in++) {
+ cmask = 1 << in;
+ snprintf(name, sizeof(name),
+ "DIn%d - Out%d Playback Volume",
+ in - 7, out + 1);
+ err = snd_create_std_mono_ctl(mixer, id, control,
+ cmask, val_type, name,
+ &snd_usb_mixer_vol_tlv);
+ if (err < 0)
+ return err;
+ }
+ }
+
+ return 0;
+}
+
+/* This control needs a volume quirk, see mixer.c */
+static int snd_ftu_create_effect_volume_ctl(struct usb_mixer_interface *mixer)
+{
+ static const char name[] = "Effect Volume";
+ const unsigned int id = 6;
+ const int val_type = USB_MIXER_U8;
+ const unsigned int control = 2;
+ const unsigned int cmask = 0;
+
+ return snd_create_std_mono_ctl(mixer, id, control, cmask, val_type,
+ name, snd_usb_mixer_vol_tlv);
+}
+
+/* This control needs a volume quirk, see mixer.c */
+static int snd_ftu_create_effect_duration_ctl(struct usb_mixer_interface *mixer)
+{
+ static const char name[] = "Effect Duration";
+ const unsigned int id = 6;
+ const int val_type = USB_MIXER_S16;
+ const unsigned int control = 3;
+ const unsigned int cmask = 0;
+
+ return snd_create_std_mono_ctl(mixer, id, control, cmask, val_type,
+ name, snd_usb_mixer_vol_tlv);
+}
+
+/* This control needs a volume quirk, see mixer.c */
+static int snd_ftu_create_effect_feedback_ctl(struct usb_mixer_interface *mixer)
+{
+ static const char name[] = "Effect Feedback Volume";
+ const unsigned int id = 6;
+ const int val_type = USB_MIXER_U8;
+ const unsigned int control = 4;
+ const unsigned int cmask = 0;
+
+ return snd_create_std_mono_ctl(mixer, id, control, cmask, val_type,
+ name, NULL);
+}
+
+static int snd_ftu_create_effect_return_ctls(struct usb_mixer_interface *mixer)
+{
+ unsigned int cmask;
+ int err, ch;
+ char name[48];
+
+ const unsigned int id = 7;
+ const int val_type = USB_MIXER_S16;
+ const unsigned int control = 7;
+
+ for (ch = 0; ch < 4; ++ch) {
+ cmask = 1 << ch;
+ snprintf(name, sizeof(name),
+ "Effect Return %d Volume", ch + 1);
+ err = snd_create_std_mono_ctl(mixer, id, control,
+ cmask, val_type, name,
+ snd_usb_mixer_vol_tlv);
+ if (err < 0)
+ return err;
+ }
+
+ return 0;
+}
+
+static int snd_ftu_create_effect_send_ctls(struct usb_mixer_interface *mixer)
+{
+ unsigned int cmask;
+ int err, ch;
+ char name[48];
+
+ const unsigned int id = 5;
+ const int val_type = USB_MIXER_S16;
+ const unsigned int control = 9;
+
+ for (ch = 0; ch < 8; ++ch) {
+ cmask = 1 << ch;
+ snprintf(name, sizeof(name),
+ "Effect Send AIn%d Volume", ch + 1);
+ err = snd_create_std_mono_ctl(mixer, id, control, cmask,
+ val_type, name,
+ snd_usb_mixer_vol_tlv);
+ if (err < 0)
+ return err;
+ }
+ for (ch = 8; ch < 16; ++ch) {
+ cmask = 1 << ch;
+ snprintf(name, sizeof(name),
+ "Effect Send DIn%d Volume", ch - 7);
+ err = snd_create_std_mono_ctl(mixer, id, control, cmask,
+ val_type, name,
+ snd_usb_mixer_vol_tlv);
+ if (err < 0)
+ return err;
+ }
+ return 0;
+}
+
+static int snd_ftu_create_mixer(struct usb_mixer_interface *mixer)
+{
+ int err;
+
+ err = snd_ftu_create_volume_ctls(mixer);
+ if (err < 0)
+ return err;
+
+ err = snd_ftu_create_effect_switch(mixer, 1, 6);
+ if (err < 0)
+ return err;
+
+ err = snd_ftu_create_effect_volume_ctl(mixer);
+ if (err < 0)
+ return err;
+
+ err = snd_ftu_create_effect_duration_ctl(mixer);
+ if (err < 0)
+ return err;
+
+ err = snd_ftu_create_effect_feedback_ctl(mixer);
+ if (err < 0)
+ return err;
+
+ err = snd_ftu_create_effect_return_ctls(mixer);
+ if (err < 0)
+ return err;
+
+ err = snd_ftu_create_effect_send_ctls(mixer);
+ if (err < 0)
+ return err;
+
+ return 0;
+}
+
+void snd_emuusb_set_samplerate(struct snd_usb_audio *chip,
+ unsigned char samplerate_id)
+{
+ struct usb_mixer_interface *mixer;
+ struct usb_mixer_elem_info *cval;
+ int unitid = 12; /* SamleRate ExtensionUnit ID */
+
+ list_for_each_entry(mixer, &chip->mixer_list, list) {
+ cval = (struct usb_mixer_elem_info *)mixer->id_elems[unitid];
+ if (cval) {
+ snd_usb_mixer_set_ctl_value(cval, UAC_SET_CUR,
+ cval->control << 8,
+ samplerate_id);
+ snd_usb_mixer_notify_id(mixer, unitid);
+ }
+ break;
+ }
+}
+
+/* M-Audio Fast Track C400/C600 */
+/* C400/C600 volume controls, this control needs a volume quirk, see mixer.c */
+static int snd_c400_create_vol_ctls(struct usb_mixer_interface *mixer)
+{
+ char name[64];
+ unsigned int cmask, offset;
+ int out, chan, err;
+ int num_outs = 0;
+ int num_ins = 0;
+
+ const unsigned int id = 0x40;
+ const int val_type = USB_MIXER_S16;
+ const int control = 1;
+
+ switch (mixer->chip->usb_id) {
+ case USB_ID(0x0763, 0x2030):
+ num_outs = 6;
+ num_ins = 4;
+ break;
+ case USB_ID(0x0763, 0x2031):
+ num_outs = 8;
+ num_ins = 6;
+ break;
+ }
+
+ for (chan = 0; chan < num_outs + num_ins; chan++) {
+ for (out = 0; out < num_outs; out++) {
+ if (chan < num_outs) {
+ snprintf(name, sizeof(name),
+ "PCM%d-Out%d Playback Volume",
+ chan + 1, out + 1);
+ } else {
+ snprintf(name, sizeof(name),
+ "In%d-Out%d Playback Volume",
+ chan - num_outs + 1, out + 1);
+ }
+
+ cmask = (out == 0) ? 0 : 1 << (out - 1);
+ offset = chan * num_outs;
+ err = snd_create_std_mono_ctl_offset(mixer, id, control,
+ cmask, val_type, offset, name,
+ &snd_usb_mixer_vol_tlv);
+ if (err < 0)
+ return err;
+ }
+ }
+
+ return 0;
+}
+
+/* This control needs a volume quirk, see mixer.c */
+static int snd_c400_create_effect_volume_ctl(struct usb_mixer_interface *mixer)
+{
+ static const char name[] = "Effect Volume";
+ const unsigned int id = 0x43;
+ const int val_type = USB_MIXER_U8;
+ const unsigned int control = 3;
+ const unsigned int cmask = 0;
+
+ return snd_create_std_mono_ctl(mixer, id, control, cmask, val_type,
+ name, snd_usb_mixer_vol_tlv);
+}
+
+/* This control needs a volume quirk, see mixer.c */
+static int snd_c400_create_effect_duration_ctl(struct usb_mixer_interface *mixer)
+{
+ static const char name[] = "Effect Duration";
+ const unsigned int id = 0x43;
+ const int val_type = USB_MIXER_S16;
+ const unsigned int control = 4;
+ const unsigned int cmask = 0;
+
+ return snd_create_std_mono_ctl(mixer, id, control, cmask, val_type,
+ name, snd_usb_mixer_vol_tlv);
+}
+
+/* This control needs a volume quirk, see mixer.c */
+static int snd_c400_create_effect_feedback_ctl(struct usb_mixer_interface *mixer)
+{
+ static const char name[] = "Effect Feedback Volume";
+ const unsigned int id = 0x43;
+ const int val_type = USB_MIXER_U8;
+ const unsigned int control = 5;
+ const unsigned int cmask = 0;
+
+ return snd_create_std_mono_ctl(mixer, id, control, cmask, val_type,
+ name, NULL);
+}
+
+static int snd_c400_create_effect_vol_ctls(struct usb_mixer_interface *mixer)
+{
+ char name[64];
+ unsigned int cmask;
+ int chan, err;
+ int num_outs = 0;
+ int num_ins = 0;
+
+ const unsigned int id = 0x42;
+ const int val_type = USB_MIXER_S16;
+ const int control = 1;
+
+ switch (mixer->chip->usb_id) {
+ case USB_ID(0x0763, 0x2030):
+ num_outs = 6;
+ num_ins = 4;
+ break;
+ case USB_ID(0x0763, 0x2031):
+ num_outs = 8;
+ num_ins = 6;
+ break;
+ }
+
+ for (chan = 0; chan < num_outs + num_ins; chan++) {
+ if (chan < num_outs) {
+ snprintf(name, sizeof(name),
+ "Effect Send DOut%d",
+ chan + 1);
+ } else {
+ snprintf(name, sizeof(name),
+ "Effect Send AIn%d",
+ chan - num_outs + 1);
+ }
+
+ cmask = (chan == 0) ? 0 : 1 << (chan - 1);
+ err = snd_create_std_mono_ctl(mixer, id, control,
+ cmask, val_type, name,
+ &snd_usb_mixer_vol_tlv);
+ if (err < 0)
+ return err;
+ }
+
+ return 0;
+}
+
+static int snd_c400_create_effect_ret_vol_ctls(struct usb_mixer_interface *mixer)
+{
+ char name[64];
+ unsigned int cmask;
+ int chan, err;
+ int num_outs = 0;
+ int offset = 0;
+
+ const unsigned int id = 0x40;
+ const int val_type = USB_MIXER_S16;
+ const int control = 1;
+
+ switch (mixer->chip->usb_id) {
+ case USB_ID(0x0763, 0x2030):
+ num_outs = 6;
+ offset = 0x3c;
+ /* { 0x3c, 0x43, 0x3e, 0x45, 0x40, 0x47 } */
+ break;
+ case USB_ID(0x0763, 0x2031):
+ num_outs = 8;
+ offset = 0x70;
+ /* { 0x70, 0x79, 0x72, 0x7b, 0x74, 0x7d, 0x76, 0x7f } */
+ break;
+ }
+
+ for (chan = 0; chan < num_outs; chan++) {
+ snprintf(name, sizeof(name),
+ "Effect Return %d",
+ chan + 1);
+
+ cmask = (chan == 0) ? 0 :
+ 1 << (chan + (chan % 2) * num_outs - 1);
+ err = snd_create_std_mono_ctl_offset(mixer, id, control,
+ cmask, val_type, offset, name,
+ &snd_usb_mixer_vol_tlv);
+ if (err < 0)
+ return err;
+ }
+
+ return 0;
+}
+
+static int snd_c400_create_mixer(struct usb_mixer_interface *mixer)
+{
+ int err;
+
+ err = snd_c400_create_vol_ctls(mixer);
+ if (err < 0)
+ return err;
+
+ err = snd_c400_create_effect_vol_ctls(mixer);
+ if (err < 0)
+ return err;
+
+ err = snd_c400_create_effect_ret_vol_ctls(mixer);
+ if (err < 0)
+ return err;
+
+ err = snd_ftu_create_effect_switch(mixer, 2, 0x43);
+ if (err < 0)
+ return err;
+
+ err = snd_c400_create_effect_volume_ctl(mixer);
+ if (err < 0)
+ return err;
+
+ err = snd_c400_create_effect_duration_ctl(mixer);
+ if (err < 0)
+ return err;
+
+ err = snd_c400_create_effect_feedback_ctl(mixer);
+ if (err < 0)
+ return err;
+
+ return 0;
+}
+
+/*
+ * The mixer units for Ebox-44 are corrupt, and even where they
+ * are valid they presents mono controls as L and R channels of
+ * stereo. So we provide a good mixer here.
+ */
+static struct std_mono_table ebox44_table[] = {
+ {
+ .unitid = 4,
+ .control = 1,
+ .cmask = 0x0,
+ .val_type = USB_MIXER_INV_BOOLEAN,
+ .name = "Headphone Playback Switch"
+ },
+ {
+ .unitid = 4,
+ .control = 2,
+ .cmask = 0x1,
+ .val_type = USB_MIXER_S16,
+ .name = "Headphone A Mix Playback Volume"
+ },
+ {
+ .unitid = 4,
+ .control = 2,
+ .cmask = 0x2,
+ .val_type = USB_MIXER_S16,
+ .name = "Headphone B Mix Playback Volume"
+ },
+
+ {
+ .unitid = 7,
+ .control = 1,
+ .cmask = 0x0,
+ .val_type = USB_MIXER_INV_BOOLEAN,
+ .name = "Output Playback Switch"
+ },
+ {
+ .unitid = 7,
+ .control = 2,
+ .cmask = 0x1,
+ .val_type = USB_MIXER_S16,
+ .name = "Output A Playback Volume"
+ },
+ {
+ .unitid = 7,
+ .control = 2,
+ .cmask = 0x2,
+ .val_type = USB_MIXER_S16,
+ .name = "Output B Playback Volume"
+ },
+
+ {
+ .unitid = 10,
+ .control = 1,
+ .cmask = 0x0,
+ .val_type = USB_MIXER_INV_BOOLEAN,
+ .name = "Input Capture Switch"
+ },
+ {
+ .unitid = 10,
+ .control = 2,
+ .cmask = 0x1,
+ .val_type = USB_MIXER_S16,
+ .name = "Input A Capture Volume"
+ },
+ {
+ .unitid = 10,
+ .control = 2,
+ .cmask = 0x2,
+ .val_type = USB_MIXER_S16,
+ .name = "Input B Capture Volume"
+ },
+
+ {}
+};
+
+/* Audio Advantage Micro II findings:
+ *
+ * Mapping spdif AES bits to vendor register.bit:
+ * AES0: [0 0 0 0 2.3 2.2 2.1 2.0] - default 0x00
+ * AES1: [3.3 3.2.3.1.3.0 2.7 2.6 2.5 2.4] - default: 0x01
+ * AES2: [0 0 0 0 0 0 0 0]
+ * AES3: [0 0 0 0 0 0 x 0] - 'x' bit is set basing on standard usb request
+ * (UAC_EP_CS_ATTR_SAMPLE_RATE) for Audio Devices
+ *
+ * power on values:
+ * r2: 0x10
+ * r3: 0x20 (b7 is zeroed just before playback (except IEC61937) and set
+ * just after it to 0xa0, presumably it disables/mutes some analog
+ * parts when there is no audio.)
+ * r9: 0x28
+ *
+ * Optical transmitter on/off:
+ * vendor register.bit: 9.1
+ * 0 - on (0x28 register value)
+ * 1 - off (0x2a register value)
+ *
+ */
+static int snd_microii_spdif_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958;
+ uinfo->count = 1;
+ return 0;
+}
+
+static int snd_microii_spdif_default_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct usb_mixer_elem_list *list = snd_kcontrol_chip(kcontrol);
+ struct snd_usb_audio *chip = list->mixer->chip;
+ int err;
+ struct usb_interface *iface;
+ struct usb_host_interface *alts;
+ unsigned int ep;
+ unsigned char data[3];
+ int rate;
+
+ down_read(&chip->shutdown_rwsem);
+ if (chip->shutdown) {
+ err = -ENODEV;
+ goto end;
+ }
+
+ ucontrol->value.iec958.status[0] = kcontrol->private_value & 0xff;
+ ucontrol->value.iec958.status[1] = (kcontrol->private_value >> 8) & 0xff;
+ ucontrol->value.iec958.status[2] = 0x00;
+
+ /* use known values for that card: interface#1 altsetting#1 */
+ iface = usb_ifnum_to_if(chip->dev, 1);
+ alts = &iface->altsetting[1];
+ ep = get_endpoint(alts, 0)->bEndpointAddress;
+
+ err = snd_usb_ctl_msg(chip->dev,
+ usb_rcvctrlpipe(chip->dev, 0),
+ UAC_GET_CUR,
+ USB_TYPE_CLASS | USB_RECIP_ENDPOINT | USB_DIR_IN,
+ UAC_EP_CS_ATTR_SAMPLE_RATE << 8,
+ ep,
+ data,
+ sizeof(data));
+ if (err < 0)
+ goto end;
+
+ rate = data[0] | (data[1] << 8) | (data[2] << 16);
+ ucontrol->value.iec958.status[3] = (rate == 48000) ?
+ IEC958_AES3_CON_FS_48000 : IEC958_AES3_CON_FS_44100;
+
+ err = 0;
+ end:
+ up_read(&chip->shutdown_rwsem);
+ return err;
+}
+
+static int snd_microii_spdif_default_update(struct usb_mixer_elem_list *list)
+{
+ struct snd_usb_audio *chip = list->mixer->chip;
+ unsigned int pval = list->kctl->private_value;
+ u8 reg;
+ int err;
+
+ down_read(&chip->shutdown_rwsem);
+ if (chip->shutdown) {
+ err = -ENODEV;
+ goto end;
+ }
+
+ reg = ((pval >> 4) & 0xf0) | (pval & 0x0f);
+ err = snd_usb_ctl_msg(chip->dev,
+ usb_sndctrlpipe(chip->dev, 0),
+ UAC_SET_CUR,
+ USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_OTHER,
+ reg,
+ 2,
+ NULL,
+ 0);
+ if (err < 0)
+ goto end;
+
+ reg = (pval & IEC958_AES0_NONAUDIO) ? 0xa0 : 0x20;
+ reg |= (pval >> 12) & 0x0f;
+ err = snd_usb_ctl_msg(chip->dev,
+ usb_sndctrlpipe(chip->dev, 0),
+ UAC_SET_CUR,
+ USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_OTHER,
+ reg,
+ 3,
+ NULL,
+ 0);
+ if (err < 0)
+ goto end;
+
+ end:
+ up_read(&chip->shutdown_rwsem);
+ return err;
+}
+
+static int snd_microii_spdif_default_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct usb_mixer_elem_list *list = snd_kcontrol_chip(kcontrol);
+ unsigned int pval, pval_old;
+ int err;
+
+ pval = pval_old = kcontrol->private_value;
+ pval &= 0xfffff0f0;
+ pval |= (ucontrol->value.iec958.status[1] & 0x0f) << 8;
+ pval |= (ucontrol->value.iec958.status[0] & 0x0f);
+
+ pval &= 0xffff0fff;
+ pval |= (ucontrol->value.iec958.status[1] & 0xf0) << 8;
+
+ /* The frequency bits in AES3 cannot be set via register access. */
+
+ /* Silently ignore any bits from the request that cannot be set. */
+
+ if (pval == pval_old)
+ return 0;
+
+ kcontrol->private_value = pval;
+ err = snd_microii_spdif_default_update(list);
+ return err < 0 ? err : 1;
+}
+
+static int snd_microii_spdif_mask_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ ucontrol->value.iec958.status[0] = 0x0f;
+ ucontrol->value.iec958.status[1] = 0xff;
+ ucontrol->value.iec958.status[2] = 0x00;
+ ucontrol->value.iec958.status[3] = 0x00;
+
+ return 0;
+}
+
+static int snd_microii_spdif_switch_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ ucontrol->value.integer.value[0] = !(kcontrol->private_value & 0x02);
+
+ return 0;
+}
+
+static int snd_microii_spdif_switch_update(struct usb_mixer_elem_list *list)
+{
+ struct snd_usb_audio *chip = list->mixer->chip;
+ u8 reg = list->kctl->private_value;
+ int err;
+
+ down_read(&chip->shutdown_rwsem);
+ if (chip->shutdown) {
+ err = -ENODEV;
+ goto end;
+ }
+
+ err = snd_usb_ctl_msg(chip->dev,
+ usb_sndctrlpipe(chip->dev, 0),
+ UAC_SET_CUR,
+ USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_OTHER,
+ reg,
+ 9,
+ NULL,
+ 0);
+
+ end:
+ up_read(&chip->shutdown_rwsem);
+ return err;
+}
+
+static int snd_microii_spdif_switch_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct usb_mixer_elem_list *list = snd_kcontrol_chip(kcontrol);
+ u8 reg;
+ int err;
+
+ reg = ucontrol->value.integer.value[0] ? 0x28 : 0x2a;
+ if (reg != list->kctl->private_value)
+ return 0;
+
+ kcontrol->private_value = reg;
+ err = snd_microii_spdif_switch_update(list);
+ return err < 0 ? err : 1;
+}
+
+static struct snd_kcontrol_new snd_microii_mixer_spdif[] = {
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_PCM,
+ .name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, DEFAULT),
+ .info = snd_microii_spdif_info,
+ .get = snd_microii_spdif_default_get,
+ .put = snd_microii_spdif_default_put,
+ .private_value = 0x00000100UL,/* reset value */
+ },
+ {
+ .access = SNDRV_CTL_ELEM_ACCESS_READ,
+ .iface = SNDRV_CTL_ELEM_IFACE_PCM,
+ .name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, MASK),
+ .info = snd_microii_spdif_info,
+ .get = snd_microii_spdif_mask_get,
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, SWITCH),
+ .info = snd_ctl_boolean_mono_info,
+ .get = snd_microii_spdif_switch_get,
+ .put = snd_microii_spdif_switch_put,
+ .private_value = 0x00000028UL,/* reset value */
+ }
+};
+
+static int snd_microii_controls_create(struct usb_mixer_interface *mixer)
+{
+ int err, i;
+ static usb_mixer_elem_resume_func_t resume_funcs[] = {
+ snd_microii_spdif_default_update,
+ NULL,
+ snd_microii_spdif_switch_update
+ };
+
+ for (i = 0; i < ARRAY_SIZE(snd_microii_mixer_spdif); ++i) {
+ err = add_single_ctl_with_resume(mixer, 0,
+ resume_funcs[i],
+ &snd_microii_mixer_spdif[i],
+ NULL);
+ if (err < 0)
+ return err;
+ }
+
+ return 0;
+}
+
+int snd_usb_mixer_apply_create_quirk(struct usb_mixer_interface *mixer)
+{
+ int err = 0;
+ struct snd_info_entry *entry;
+
+ if ((err = snd_usb_soundblaster_remote_init(mixer)) < 0)
+ return err;
+
+ switch (mixer->chip->usb_id) {
+ case USB_ID(0x041e, 0x3020):
+ case USB_ID(0x041e, 0x3040):
+ case USB_ID(0x041e, 0x3042):
+ case USB_ID(0x041e, 0x30df):
+ case USB_ID(0x041e, 0x3048):
+ err = snd_audigy2nx_controls_create(mixer);
+ if (err < 0)
+ break;
+ if (!snd_card_proc_new(mixer->chip->card, "audigy2nx", &entry))
+ snd_info_set_text_ops(entry, mixer,
+ snd_audigy2nx_proc_read);
+ break;
+
+ /* EMU0204 */
+ case USB_ID(0x041e, 0x3f19):
+ err = snd_emu0204_controls_create(mixer);
+ if (err < 0)
+ break;
+ break;
+
+ case USB_ID(0x0763, 0x2030): /* M-Audio Fast Track C400 */
+ case USB_ID(0x0763, 0x2031): /* M-Audio Fast Track C400 */
+ err = snd_c400_create_mixer(mixer);
+ break;
+
+ case USB_ID(0x0763, 0x2080): /* M-Audio Fast Track Ultra */
+ case USB_ID(0x0763, 0x2081): /* M-Audio Fast Track Ultra 8R */
+ err = snd_ftu_create_mixer(mixer);
+ break;
+
+ case USB_ID(0x0b05, 0x1739): /* ASUS Xonar U1 */
+ case USB_ID(0x0b05, 0x1743): /* ASUS Xonar U1 (2) */
+ case USB_ID(0x0b05, 0x17a0): /* ASUS Xonar U3 */
+ err = snd_xonar_u1_controls_create(mixer);
+ break;
+
+ case USB_ID(0x0d8c, 0x0103): /* Audio Advantage Micro II */
+ err = snd_microii_controls_create(mixer);
+ break;
+
+ case USB_ID(0x0dba, 0x1000): /* Digidesign Mbox 1 */
+ err = snd_mbox1_create_sync_switch(mixer);
+ break;
+
+ case USB_ID(0x17cc, 0x1011): /* Traktor Audio 6 */
+ err = snd_nativeinstruments_create_mixer(mixer,
+ snd_nativeinstruments_ta6_mixers,
+ ARRAY_SIZE(snd_nativeinstruments_ta6_mixers));
+ break;
+
+ case USB_ID(0x17cc, 0x1021): /* Traktor Audio 10 */
+ err = snd_nativeinstruments_create_mixer(mixer,
+ snd_nativeinstruments_ta10_mixers,
+ ARRAY_SIZE(snd_nativeinstruments_ta10_mixers));
+ break;
+
+ case USB_ID(0x200c, 0x1018): /* Electrix Ebox-44 */
+ /* detection is disabled in mixer_maps.c */
+ err = snd_create_std_mono_table(mixer, ebox44_table);
+ break;
+
+ case USB_ID(0x1235, 0x8012): /* Focusrite Scarlett 6i6 */
+ case USB_ID(0x1235, 0x8002): /* Focusrite Scarlett 8i6 */
+ case USB_ID(0x1235, 0x8004): /* Focusrite Scarlett 18i6 */
+ case USB_ID(0x1235, 0x8014): /* Focusrite Scarlett 18i8 */
+ case USB_ID(0x1235, 0x800c): /* Focusrite Scarlett 18i20 */
+ err = snd_scarlett_controls_create(mixer);
+ break;
+ }
+
+ return err;
+}
+
+void snd_usb_mixer_rc_memory_change(struct usb_mixer_interface *mixer,
+ int unitid)
+{
+ if (!mixer->rc_cfg)
+ return;
+ /* unit ids specific to Extigy/Audigy 2 NX: */
+ switch (unitid) {
+ case 0: /* remote control */
+ mixer->rc_urb->dev = mixer->chip->dev;
+ usb_submit_urb(mixer->rc_urb, GFP_ATOMIC);
+ break;
+ case 4: /* digital in jack */
+ case 7: /* line in jacks */
+ case 19: /* speaker out jacks */
+ case 20: /* headphones out jack */
+ break;
+ /* live24ext: 4 = line-in jack */
+ case 3: /* hp-out jack (may actuate Mute) */
+ if (mixer->chip->usb_id == USB_ID(0x041e, 0x3040) ||
+ mixer->chip->usb_id == USB_ID(0x041e, 0x3048))
+ snd_usb_mixer_notify_id(mixer, mixer->rc_cfg->mute_mixer_id);
+ break;
+ default:
+ usb_audio_dbg(mixer->chip, "memory change in unknown unit %d\n", unitid);
+ break;
+ }
+}
+
diff --git a/sound/usb/mixer_quirks.h b/sound/usb/mixer_quirks.h
new file mode 100644
index 000000000..bdbfab093
--- /dev/null
+++ b/sound/usb/mixer_quirks.h
@@ -0,0 +1,13 @@
+#ifndef SND_USB_MIXER_QUIRKS_H
+#define SND_USB_MIXER_QUIRKS_H
+
+int snd_usb_mixer_apply_create_quirk(struct usb_mixer_interface *mixer);
+
+void snd_emuusb_set_samplerate(struct snd_usb_audio *chip,
+ unsigned char samplerate_id);
+
+void snd_usb_mixer_rc_memory_change(struct usb_mixer_interface *mixer,
+ int unitid);
+
+#endif /* SND_USB_MIXER_QUIRKS_H */
+
diff --git a/sound/usb/mixer_scarlett.c b/sound/usb/mixer_scarlett.c
new file mode 100644
index 000000000..7438e7c4a
--- /dev/null
+++ b/sound/usb/mixer_scarlett.c
@@ -0,0 +1,1004 @@
+/*
+ * Scarlett Driver for ALSA
+ *
+ * Copyright (c) 2013 by Tobias Hoffmann
+ * Copyright (c) 2013 by Robin Gareus <robin at gareus.org>
+ * Copyright (c) 2002 by Takashi Iwai <tiwai at suse.de>
+ * Copyright (c) 2014 by Chris J Arges <chris.j.arges at canonical.com>
+ *
+ * Many codes borrowed from audio.c by
+ * Alan Cox (alan at lxorguk.ukuu.org.uk)
+ * Thomas Sailer (sailer at ife.ee.ethz.ch)
+ *
+ * Code cleanup:
+ * David Henningsson <david.henningsson at canonical.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+
+/*
+ * Rewritten and extended to support more models, e.g. Scarlett 18i8.
+ *
+ * Auto-detection via UAC2 is not feasible to properly discover the vast
+ * majority of features. It's related to both Linux/ALSA's UAC2 as well as
+ * Focusrite's implementation of it. Eventually quirks may be sufficient but
+ * right now it's a major headache to work arount these things.
+ *
+ * NB. Neither the OSX nor the win driver provided by Focusrite performs
+ * discovery, they seem to operate the same as this driver.
+ */
+
+/* Mixer Interface for the Focusrite Scarlett 18i6 audio interface.
+ *
+ * The protocol was reverse engineered by looking at communication between
+ * Scarlett MixControl (v 1.2.128.0) and the Focusrite(R) Scarlett 18i6
+ * (firmware v305) using wireshark and usbmon in January 2013.
+ * Extended in July 2013.
+ *
+ * this mixer gives complete access to all features of the device:
+ * - change Impedance of inputs (Line-in, Mic / Instrument, Hi-Z)
+ * - select clock source
+ * - dynamic input to mixer-matrix assignment
+ * - 18 x 6 mixer-matrix gain stages
+ * - bus routing & volume control
+ * - automatic re-initialization on connect if device was power-cycled
+ *
+ * USB URB commands overview (bRequest = 0x01 = UAC2_CS_CUR)
+ * wIndex
+ * 0x01 Analog Input line/instrument impedance switch, wValue=0x0901 +
+ * channel, data=Line/Inst (2bytes)
+ * pad (-10dB) switch, wValue=0x0b01 + channel, data=Off/On (2bytes)
+ * ?? wValue=0x0803/04, ?? (2bytes)
+ * 0x0a Master Volume, wValue=0x0200+bus[0:all + only 1..4?] data(2bytes)
+ * Bus Mute/Unmute wValue=0x0100+bus[0:all + only 1..4?], data(2bytes)
+ * 0x28 Clock source, wValue=0x0100, data={1:int,2:spdif,3:adat} (1byte)
+ * 0x29 Set Sample-rate, wValue=0x0100, data=sample-rate(4bytes)
+ * 0x32 Mixer mux, wValue=0x0600 + mixer-channel, data=input-to-connect(2bytes)
+ * 0x33 Output mux, wValue=bus, data=input-to-connect(2bytes)
+ * 0x34 Capture mux, wValue=0...18, data=input-to-connect(2bytes)
+ * 0x3c Matrix Mixer gains, wValue=mixer-node data=gain(2bytes)
+ * ?? [sometimes](4bytes, e.g 0x000003be 0x000003bf ...03ff)
+ *
+ * USB reads: (i.e. actually issued by original software)
+ * 0x01 wValue=0x0901+channel (1byte!!), wValue=0x0b01+channed (1byte!!)
+ * 0x29 wValue=0x0100 sample-rate(4bytes)
+ * wValue=0x0200 ?? 1byte (only once)
+ * 0x2a wValue=0x0100 ?? 4bytes, sample-rate2 ??
+ *
+ * USB reads with bRequest = 0x03 = UAC2_CS_MEM
+ * 0x3c wValue=0x0002 1byte: sync status (locked=1)
+ * wValue=0x0000 18*2byte: peak meter (inputs)
+ * wValue=0x0001 8(?)*2byte: peak meter (mix)
+ * wValue=0x0003 6*2byte: peak meter (pcm/daw)
+ *
+ * USB write with bRequest = 0x03
+ * 0x3c Save settings to hardware: wValue=0x005a, data=0xa5
+ *
+ *
+ * <ditaa>
+ * /--------------\ 18chn 6chn /--------------\
+ * | Hardware in +--+-------\ /------+--+ ALSA PCM out |
+ * \--------------/ | | | | \--------------/
+ * | | | |
+ * | v v |
+ * | +---------------+ |
+ * | \ Matrix Mux / |
+ * | +-----+-----+ |
+ * | | |
+ * | | 18chn |
+ * | v |
+ * | +-----------+ |
+ * | | Mixer | |
+ * | | Matrix | |
+ * | | | |
+ * | | 18x6 Gain | |
+ * | | stages | |
+ * | +-----+-----+ |
+ * | | |
+ * | | |
+ * | 18chn | 6chn | 6chn
+ * v v v
+ * =========================
+ * +---------------+ +--—------------+
+ * \ Output Mux / \ Capture Mux /
+ * +-----+-----+ +-----+-----+
+ * | |
+ * | 6chn |
+ * v |
+ * +-------------+ |
+ * | Master Gain | |
+ * +------+------+ |
+ * | |
+ * | 6chn | 18chn
+ * | (3 stereo pairs) |
+ * /--------------\ | | /--------------\
+ * | Hardware out |<--/ \-->| ALSA PCM in |
+ * \--------------/ \--------------/
+ * </ditaa>
+ *
+ */
+
+#include <linux/slab.h>
+#include <linux/usb.h>
+#include <linux/usb/audio-v2.h>
+
+#include <sound/core.h>
+#include <sound/control.h>
+#include <sound/tlv.h>
+
+#include "usbaudio.h"
+#include "mixer.h"
+#include "helper.h"
+#include "power.h"
+
+#include "mixer_scarlett.h"
+
+/* some gui mixers can't handle negative ctl values */
+#define SND_SCARLETT_LEVEL_BIAS 128
+#define SND_SCARLETT_MATRIX_IN_MAX 18
+#define SND_SCARLETT_CONTROLS_MAX 10
+#define SND_SCARLETT_OFFSETS_MAX 5
+
+enum {
+ SCARLETT_OUTPUTS,
+ SCARLETT_SWITCH_IMPEDANCE,
+ SCARLETT_SWITCH_PAD,
+};
+
+enum {
+ SCARLETT_OFFSET_PCM = 0,
+ SCARLETT_OFFSET_ANALOG = 1,
+ SCARLETT_OFFSET_SPDIF = 2,
+ SCARLETT_OFFSET_ADAT = 3,
+ SCARLETT_OFFSET_MIX = 4,
+};
+
+struct scarlett_mixer_elem_enum_info {
+ int start;
+ int len;
+ int offsets[SND_SCARLETT_OFFSETS_MAX];
+ char const * const *names;
+};
+
+struct scarlett_mixer_control {
+ unsigned char num;
+ unsigned char type;
+ const char *name;
+};
+
+struct scarlett_device_info {
+ int matrix_in;
+ int matrix_out;
+ int input_len;
+ int output_len;
+
+ struct scarlett_mixer_elem_enum_info opt_master;
+ struct scarlett_mixer_elem_enum_info opt_matrix;
+
+ /* initial values for matrix mux */
+ int matrix_mux_init[SND_SCARLETT_MATRIX_IN_MAX];
+
+ int num_controls; /* number of items in controls */
+ const struct scarlett_mixer_control controls[SND_SCARLETT_CONTROLS_MAX];
+};
+
+/********************** Enum Strings *************************/
+
+static const struct scarlett_mixer_elem_enum_info opt_pad = {
+ .start = 0,
+ .len = 2,
+ .offsets = {},
+ .names = (char const * const []){
+ "0dB", "-10dB"
+ }
+};
+
+static const struct scarlett_mixer_elem_enum_info opt_impedance = {
+ .start = 0,
+ .len = 2,
+ .offsets = {},
+ .names = (char const * const []){
+ "Line", "Hi-Z"
+ }
+};
+
+static const struct scarlett_mixer_elem_enum_info opt_clock = {
+ .start = 1,
+ .len = 3,
+ .offsets = {},
+ .names = (char const * const []){
+ "Internal", "SPDIF", "ADAT"
+ }
+};
+
+static const struct scarlett_mixer_elem_enum_info opt_sync = {
+ .start = 0,
+ .len = 2,
+ .offsets = {},
+ .names = (char const * const []){
+ "No Lock", "Locked"
+ }
+};
+
+static int scarlett_ctl_switch_info(struct snd_kcontrol *kctl,
+ struct snd_ctl_elem_info *uinfo)
+{
+ struct usb_mixer_elem_info *elem = kctl->private_data;
+
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
+ uinfo->count = elem->channels;
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = 1;
+ return 0;
+}
+
+static int scarlett_ctl_switch_get(struct snd_kcontrol *kctl,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct usb_mixer_elem_info *elem = kctl->private_data;
+ int i, err, val;
+
+ for (i = 0; i < elem->channels; i++) {
+ err = snd_usb_get_cur_mix_value(elem, i, i, &val);
+ if (err < 0)
+ return err;
+
+ val = !val; /* invert mute logic for mixer */
+ ucontrol->value.integer.value[i] = val;
+ }
+
+ return 0;
+}
+
+static int scarlett_ctl_switch_put(struct snd_kcontrol *kctl,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct usb_mixer_elem_info *elem = kctl->private_data;
+ int i, changed = 0;
+ int err, oval, val;
+
+ for (i = 0; i < elem->channels; i++) {
+ err = snd_usb_get_cur_mix_value(elem, i, i, &oval);
+ if (err < 0)
+ return err;
+
+ val = ucontrol->value.integer.value[i];
+ val = !val;
+ if (oval != val) {
+ err = snd_usb_set_cur_mix_value(elem, i, i, val);
+ if (err < 0)
+ return err;
+
+ changed = 1;
+ }
+ }
+
+ return changed;
+}
+
+static int scarlett_ctl_resume(struct usb_mixer_elem_list *list)
+{
+ struct usb_mixer_elem_info *elem =
+ container_of(list, struct usb_mixer_elem_info, head);
+ int i;
+
+ for (i = 0; i < elem->channels; i++)
+ if (elem->cached & (1 << i))
+ snd_usb_set_cur_mix_value(elem, i, i,
+ elem->cache_val[i]);
+ return 0;
+}
+
+static int scarlett_ctl_info(struct snd_kcontrol *kctl,
+ struct snd_ctl_elem_info *uinfo)
+{
+ struct usb_mixer_elem_info *elem = kctl->private_data;
+
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = elem->channels;
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = (int)kctl->private_value +
+ SND_SCARLETT_LEVEL_BIAS;
+ uinfo->value.integer.step = 1;
+ return 0;
+}
+
+static int scarlett_ctl_get(struct snd_kcontrol *kctl,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct usb_mixer_elem_info *elem = kctl->private_data;
+ int i, err, val;
+
+ for (i = 0; i < elem->channels; i++) {
+ err = snd_usb_get_cur_mix_value(elem, i, i, &val);
+ if (err < 0)
+ return err;
+
+ val = clamp(val / 256, -128, (int)kctl->private_value) +
+ SND_SCARLETT_LEVEL_BIAS;
+ ucontrol->value.integer.value[i] = val;
+ }
+
+ return 0;
+}
+
+static int scarlett_ctl_put(struct snd_kcontrol *kctl,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct usb_mixer_elem_info *elem = kctl->private_data;
+ int i, changed = 0;
+ int err, oval, val;
+
+ for (i = 0; i < elem->channels; i++) {
+ err = snd_usb_get_cur_mix_value(elem, i, i, &oval);
+ if (err < 0)
+ return err;
+
+ val = ucontrol->value.integer.value[i] -
+ SND_SCARLETT_LEVEL_BIAS;
+ val = val * 256;
+ if (oval != val) {
+ err = snd_usb_set_cur_mix_value(elem, i, i, val);
+ if (err < 0)
+ return err;
+
+ changed = 1;
+ }
+ }
+
+ return changed;
+}
+
+static void scarlett_generate_name(int i, char *dst, int offsets[])
+{
+ if (i > offsets[SCARLETT_OFFSET_MIX])
+ sprintf(dst, "Mix %c",
+ 'A'+(i - offsets[SCARLETT_OFFSET_MIX] - 1));
+ else if (i > offsets[SCARLETT_OFFSET_ADAT])
+ sprintf(dst, "ADAT %d", i - offsets[SCARLETT_OFFSET_ADAT]);
+ else if (i > offsets[SCARLETT_OFFSET_SPDIF])
+ sprintf(dst, "SPDIF %d", i - offsets[SCARLETT_OFFSET_SPDIF]);
+ else if (i > offsets[SCARLETT_OFFSET_ANALOG])
+ sprintf(dst, "Analog %d", i - offsets[SCARLETT_OFFSET_ANALOG]);
+ else if (i > offsets[SCARLETT_OFFSET_PCM])
+ sprintf(dst, "PCM %d", i - offsets[SCARLETT_OFFSET_PCM]);
+ else
+ sprintf(dst, "Off");
+}
+
+static int scarlett_ctl_enum_dynamic_info(struct snd_kcontrol *kctl,
+ struct snd_ctl_elem_info *uinfo)
+{
+ struct usb_mixer_elem_info *elem = kctl->private_data;
+ struct scarlett_mixer_elem_enum_info *opt = elem->private_data;
+ unsigned int items = opt->len;
+
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+ uinfo->count = elem->channels;
+ uinfo->value.enumerated.items = items;
+
+ if (uinfo->value.enumerated.item >= items)
+ uinfo->value.enumerated.item = items - 1;
+
+ /* generate name dynamically based on item number and offset info */
+ scarlett_generate_name(uinfo->value.enumerated.item,
+ uinfo->value.enumerated.name,
+ opt->offsets);
+
+ return 0;
+}
+
+static int scarlett_ctl_enum_info(struct snd_kcontrol *kctl,
+ struct snd_ctl_elem_info *uinfo)
+{
+ struct usb_mixer_elem_info *elem = kctl->private_data;
+ struct scarlett_mixer_elem_enum_info *opt = elem->private_data;
+
+ return snd_ctl_enum_info(uinfo, elem->channels, opt->len,
+ (const char * const *)opt->names);
+}
+
+static int scarlett_ctl_enum_get(struct snd_kcontrol *kctl,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct usb_mixer_elem_info *elem = kctl->private_data;
+ struct scarlett_mixer_elem_enum_info *opt = elem->private_data;
+ int err, val;
+
+ err = snd_usb_get_cur_mix_value(elem, 0, 0, &val);
+ if (err < 0)
+ return err;
+
+ val = clamp(val - opt->start, 0, opt->len-1);
+
+ ucontrol->value.enumerated.item[0] = val;
+
+ return 0;
+}
+
+static int scarlett_ctl_enum_put(struct snd_kcontrol *kctl,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct usb_mixer_elem_info *elem = kctl->private_data;
+ struct scarlett_mixer_elem_enum_info *opt = elem->private_data;
+ int err, oval, val;
+
+ err = snd_usb_get_cur_mix_value(elem, 0, 0, &oval);
+ if (err < 0)
+ return err;
+
+ val = ucontrol->value.integer.value[0];
+ val = val + opt->start;
+ if (val != oval) {
+ snd_usb_set_cur_mix_value(elem, 0, 0, val);
+ return 1;
+ }
+ return 0;
+}
+
+static int scarlett_ctl_enum_resume(struct usb_mixer_elem_list *list)
+{
+ struct usb_mixer_elem_info *elem =
+ container_of(list, struct usb_mixer_elem_info, head);
+
+ if (elem->cached)
+ snd_usb_set_cur_mix_value(elem, 0, 0, *elem->cache_val);
+ return 0;
+}
+
+static int scarlett_ctl_meter_get(struct snd_kcontrol *kctl,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct usb_mixer_elem_info *elem = kctl->private_data;
+ struct snd_usb_audio *chip = elem->head.mixer->chip;
+ unsigned char buf[2 * MAX_CHANNELS] = {0, };
+ int wValue = (elem->control << 8) | elem->idx_off;
+ int idx = snd_usb_ctrl_intf(chip) | (elem->head.id << 8);
+ int err;
+
+ err = snd_usb_ctl_msg(chip->dev,
+ usb_rcvctrlpipe(chip->dev, 0),
+ UAC2_CS_MEM,
+ USB_RECIP_INTERFACE | USB_TYPE_CLASS |
+ USB_DIR_IN, wValue, idx, buf, elem->channels);
+ if (err < 0)
+ return err;
+
+ ucontrol->value.enumerated.item[0] = clamp((int)buf[0], 0, 1);
+ return 0;
+}
+
+static struct snd_kcontrol_new usb_scarlett_ctl_switch = {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "",
+ .info = scarlett_ctl_switch_info,
+ .get = scarlett_ctl_switch_get,
+ .put = scarlett_ctl_switch_put,
+};
+
+static const DECLARE_TLV_DB_SCALE(db_scale_scarlett_gain, -12800, 100, 0);
+
+static struct snd_kcontrol_new usb_scarlett_ctl = {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE |
+ SNDRV_CTL_ELEM_ACCESS_TLV_READ,
+ .name = "",
+ .info = scarlett_ctl_info,
+ .get = scarlett_ctl_get,
+ .put = scarlett_ctl_put,
+ .private_value = 6, /* max value */
+ .tlv = { .p = db_scale_scarlett_gain }
+};
+
+static struct snd_kcontrol_new usb_scarlett_ctl_master = {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE |
+ SNDRV_CTL_ELEM_ACCESS_TLV_READ,
+ .name = "",
+ .info = scarlett_ctl_info,
+ .get = scarlett_ctl_get,
+ .put = scarlett_ctl_put,
+ .private_value = 6, /* max value */
+ .tlv = { .p = db_scale_scarlett_gain }
+};
+
+static struct snd_kcontrol_new usb_scarlett_ctl_enum = {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "",
+ .info = scarlett_ctl_enum_info,
+ .get = scarlett_ctl_enum_get,
+ .put = scarlett_ctl_enum_put,
+};
+
+static struct snd_kcontrol_new usb_scarlett_ctl_dynamic_enum = {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "",
+ .info = scarlett_ctl_enum_dynamic_info,
+ .get = scarlett_ctl_enum_get,
+ .put = scarlett_ctl_enum_put,
+};
+
+static struct snd_kcontrol_new usb_scarlett_ctl_sync = {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE,
+ .name = "",
+ .info = scarlett_ctl_enum_info,
+ .get = scarlett_ctl_meter_get,
+};
+
+static int add_new_ctl(struct usb_mixer_interface *mixer,
+ const struct snd_kcontrol_new *ncontrol,
+ usb_mixer_elem_resume_func_t resume,
+ int index, int offset, int num,
+ int val_type, int channels, const char *name,
+ const struct scarlett_mixer_elem_enum_info *opt,
+ struct usb_mixer_elem_info **elem_ret
+)
+{
+ struct snd_kcontrol *kctl;
+ struct usb_mixer_elem_info *elem;
+ int err;
+
+ elem = kzalloc(sizeof(*elem), GFP_KERNEL);
+ if (!elem)
+ return -ENOMEM;
+
+ elem->head.mixer = mixer;
+ elem->head.resume = resume;
+ elem->control = offset;
+ elem->idx_off = num;
+ elem->head.id = index;
+ elem->val_type = val_type;
+
+ elem->channels = channels;
+
+ /* add scarlett_mixer_elem_enum_info struct */
+ elem->private_data = (void *)opt;
+
+ kctl = snd_ctl_new1(ncontrol, elem);
+ if (!kctl) {
+ kfree(elem);
+ return -ENOMEM;
+ }
+ kctl->private_free = snd_usb_mixer_elem_free;
+
+ strlcpy(kctl->id.name, name, sizeof(kctl->id.name));
+
+ err = snd_usb_mixer_add_control(&elem->head, kctl);
+ if (err < 0)
+ return err;
+
+ if (elem_ret)
+ *elem_ret = elem;
+
+ return 0;
+}
+
+static int add_output_ctls(struct usb_mixer_interface *mixer,
+ int index, const char *name,
+ const struct scarlett_device_info *info)
+{
+ int err;
+ char mx[SNDRV_CTL_ELEM_ID_NAME_MAXLEN];
+ struct usb_mixer_elem_info *elem;
+
+ /* Add mute switch */
+ snprintf(mx, sizeof(mx), "Master %d (%s) Playback Switch",
+ index + 1, name);
+ err = add_new_ctl(mixer, &usb_scarlett_ctl_switch,
+ scarlett_ctl_resume, 0x0a, 0x01,
+ 2*index+1, USB_MIXER_S16, 2, mx, NULL, &elem);
+ if (err < 0)
+ return err;
+
+ /* Add volume control and initialize to 0 */
+ snprintf(mx, sizeof(mx), "Master %d (%s) Playback Volume",
+ index + 1, name);
+ err = add_new_ctl(mixer, &usb_scarlett_ctl_master,
+ scarlett_ctl_resume, 0x0a, 0x02,
+ 2*index+1, USB_MIXER_S16, 2, mx, NULL, &elem);
+ if (err < 0)
+ return err;
+
+ /* Add L channel source playback enumeration */
+ snprintf(mx, sizeof(mx), "Master %dL (%s) Source Playback Enum",
+ index + 1, name);
+ err = add_new_ctl(mixer, &usb_scarlett_ctl_dynamic_enum,
+ scarlett_ctl_enum_resume, 0x33, 0x00,
+ 2*index, USB_MIXER_S16, 1, mx, &info->opt_master,
+ &elem);
+ if (err < 0)
+ return err;
+
+ /* Add R channel source playback enumeration */
+ snprintf(mx, sizeof(mx), "Master %dR (%s) Source Playback Enum",
+ index + 1, name);
+ err = add_new_ctl(mixer, &usb_scarlett_ctl_dynamic_enum,
+ scarlett_ctl_enum_resume, 0x33, 0x00,
+ 2*index+1, USB_MIXER_S16, 1, mx, &info->opt_master,
+ &elem);
+ if (err < 0)
+ return err;
+
+ return 0;
+}
+
+/********************** device-specific config *************************/
+
+/* untested... */
+static struct scarlett_device_info s6i6_info = {
+ .matrix_in = 18,
+ .matrix_out = 8,
+ .input_len = 6,
+ .output_len = 6,
+
+ .opt_master = {
+ .start = -1,
+ .len = 27,
+ .offsets = {0, 12, 16, 18, 18},
+ .names = NULL
+ },
+
+ .opt_matrix = {
+ .start = -1,
+ .len = 19,
+ .offsets = {0, 12, 16, 18, 18},
+ .names = NULL
+ },
+
+ .num_controls = 9,
+ .controls = {
+ { .num = 0, .type = SCARLETT_OUTPUTS, .name = "Monitor" },
+ { .num = 1, .type = SCARLETT_OUTPUTS, .name = "Headphone" },
+ { .num = 2, .type = SCARLETT_OUTPUTS, .name = "SPDIF" },
+ { .num = 1, .type = SCARLETT_SWITCH_IMPEDANCE, .name = NULL},
+ { .num = 1, .type = SCARLETT_SWITCH_PAD, .name = NULL},
+ { .num = 2, .type = SCARLETT_SWITCH_IMPEDANCE, .name = NULL},
+ { .num = 2, .type = SCARLETT_SWITCH_PAD, .name = NULL},
+ { .num = 3, .type = SCARLETT_SWITCH_PAD, .name = NULL},
+ { .num = 4, .type = SCARLETT_SWITCH_PAD, .name = NULL},
+ },
+
+ .matrix_mux_init = {
+ 12, 13, 14, 15, /* Analog -> 1..4 */
+ 16, 17, /* SPDIF -> 5,6 */
+ 0, 1, 2, 3, 4, 5, 6, 7, /* PCM[1..12] -> 7..18 */
+ 8, 9, 10, 11
+ }
+};
+
+/* untested... */
+static struct scarlett_device_info s8i6_info = {
+ .matrix_in = 18,
+ .matrix_out = 6,
+ .input_len = 8,
+ .output_len = 6,
+
+ .opt_master = {
+ .start = -1,
+ .len = 25,
+ .offsets = {0, 12, 16, 18, 18},
+ .names = NULL
+ },
+
+ .opt_matrix = {
+ .start = -1,
+ .len = 19,
+ .offsets = {0, 12, 16, 18, 18},
+ .names = NULL
+ },
+
+ .num_controls = 7,
+ .controls = {
+ { .num = 0, .type = SCARLETT_OUTPUTS, .name = "Monitor" },
+ { .num = 1, .type = SCARLETT_OUTPUTS, .name = "Headphone" },
+ { .num = 2, .type = SCARLETT_OUTPUTS, .name = "SPDIF" },
+ { .num = 1, .type = SCARLETT_SWITCH_IMPEDANCE, .name = NULL},
+ { .num = 2, .type = SCARLETT_SWITCH_IMPEDANCE, .name = NULL},
+ { .num = 3, .type = SCARLETT_SWITCH_PAD, .name = NULL},
+ { .num = 4, .type = SCARLETT_SWITCH_PAD, .name = NULL},
+ },
+
+ .matrix_mux_init = {
+ 12, 13, 14, 15, /* Analog -> 1..4 */
+ 16, 17, /* SPDIF -> 5,6 */
+ 0, 1, 2, 3, 4, 5, 6, 7, /* PCM[1..12] -> 7..18 */
+ 8, 9, 10, 11
+ }
+};
+
+static struct scarlett_device_info s18i6_info = {
+ .matrix_in = 18,
+ .matrix_out = 6,
+ .input_len = 18,
+ .output_len = 6,
+
+ .opt_master = {
+ .start = -1,
+ .len = 31,
+ .offsets = {0, 6, 14, 16, 24},
+ .names = NULL,
+ },
+
+ .opt_matrix = {
+ .start = -1,
+ .len = 25,
+ .offsets = {0, 6, 14, 16, 24},
+ .names = NULL,
+ },
+
+ .num_controls = 5,
+ .controls = {
+ { .num = 0, .type = SCARLETT_OUTPUTS, .name = "Monitor" },
+ { .num = 1, .type = SCARLETT_OUTPUTS, .name = "Headphone" },
+ { .num = 2, .type = SCARLETT_OUTPUTS, .name = "SPDIF" },
+ { .num = 1, .type = SCARLETT_SWITCH_IMPEDANCE, .name = NULL},
+ { .num = 2, .type = SCARLETT_SWITCH_IMPEDANCE, .name = NULL},
+ },
+
+ .matrix_mux_init = {
+ 6, 7, 8, 9, 10, 11, 12, 13, /* Analog -> 1..8 */
+ 16, 17, 18, 19, 20, 21, /* ADAT[1..6] -> 9..14 */
+ 14, 15, /* SPDIF -> 15,16 */
+ 0, 1 /* PCM[1,2] -> 17,18 */
+ }
+};
+
+static struct scarlett_device_info s18i8_info = {
+ .matrix_in = 18,
+ .matrix_out = 8,
+ .input_len = 18,
+ .output_len = 8,
+
+ .opt_master = {
+ .start = -1,
+ .len = 35,
+ .offsets = {0, 8, 16, 18, 26},
+ .names = NULL
+ },
+
+ .opt_matrix = {
+ .start = -1,
+ .len = 27,
+ .offsets = {0, 8, 16, 18, 26},
+ .names = NULL
+ },
+
+ .num_controls = 10,
+ .controls = {
+ { .num = 0, .type = SCARLETT_OUTPUTS, .name = "Monitor" },
+ { .num = 1, .type = SCARLETT_OUTPUTS, .name = "Headphone 1" },
+ { .num = 2, .type = SCARLETT_OUTPUTS, .name = "Headphone 2" },
+ { .num = 3, .type = SCARLETT_OUTPUTS, .name = "SPDIF" },
+ { .num = 1, .type = SCARLETT_SWITCH_IMPEDANCE, .name = NULL},
+ { .num = 1, .type = SCARLETT_SWITCH_PAD, .name = NULL},
+ { .num = 2, .type = SCARLETT_SWITCH_IMPEDANCE, .name = NULL},
+ { .num = 2, .type = SCARLETT_SWITCH_PAD, .name = NULL},
+ { .num = 3, .type = SCARLETT_SWITCH_PAD, .name = NULL},
+ { .num = 4, .type = SCARLETT_SWITCH_PAD, .name = NULL},
+ },
+
+ .matrix_mux_init = {
+ 8, 9, 10, 11, 12, 13, 14, 15, /* Analog -> 1..8 */
+ 18, 19, 20, 21, 22, 23, /* ADAT[1..6] -> 9..14 */
+ 16, 17, /* SPDIF -> 15,16 */
+ 0, 1 /* PCM[1,2] -> 17,18 */
+ }
+};
+
+static struct scarlett_device_info s18i20_info = {
+ .matrix_in = 18,
+ .matrix_out = 8,
+ .input_len = 18,
+ .output_len = 20,
+
+ .opt_master = {
+ .start = -1,
+ .len = 47,
+ .offsets = {0, 20, 28, 30, 38},
+ .names = NULL
+ },
+
+ .opt_matrix = {
+ .start = -1,
+ .len = 39,
+ .offsets = {0, 20, 28, 30, 38},
+ .names = NULL
+ },
+
+ .num_controls = 10,
+ .controls = {
+ { .num = 0, .type = SCARLETT_OUTPUTS, .name = "Monitor" },
+ { .num = 1, .type = SCARLETT_OUTPUTS, .name = "Line 3/4" },
+ { .num = 2, .type = SCARLETT_OUTPUTS, .name = "Line 5/6" },
+ { .num = 3, .type = SCARLETT_OUTPUTS, .name = "Line 7/8" },
+ { .num = 4, .type = SCARLETT_OUTPUTS, .name = "Line 9/10" },
+ { .num = 5, .type = SCARLETT_OUTPUTS, .name = "SPDIF" },
+ { .num = 6, .type = SCARLETT_OUTPUTS, .name = "ADAT 1/2" },
+ { .num = 7, .type = SCARLETT_OUTPUTS, .name = "ADAT 3/4" },
+ { .num = 8, .type = SCARLETT_OUTPUTS, .name = "ADAT 5/6" },
+ { .num = 9, .type = SCARLETT_OUTPUTS, .name = "ADAT 7/8" },
+ /*{ .num = 1, .type = SCARLETT_SWITCH_IMPEDANCE, .name = NULL},
+ { .num = 1, .type = SCARLETT_SWITCH_PAD, .name = NULL},
+ { .num = 2, .type = SCARLETT_SWITCH_IMPEDANCE, .name = NULL},
+ { .num = 2, .type = SCARLETT_SWITCH_PAD, .name = NULL},
+ { .num = 3, .type = SCARLETT_SWITCH_PAD, .name = NULL},
+ { .num = 4, .type = SCARLETT_SWITCH_PAD, .name = NULL},*/
+ },
+
+ .matrix_mux_init = {
+ 20, 21, 22, 23, 24, 25, 26, 27, /* Analog -> 1..8 */
+ 30, 31, 32, 33, 34, 35, /* ADAT[1..6] -> 9..14 */
+ 28, 29, /* SPDIF -> 15,16 */
+ 0, 1 /* PCM[1,2] -> 17,18 */
+ }
+};
+
+
+static int scarlett_controls_create_generic(struct usb_mixer_interface *mixer,
+ struct scarlett_device_info *info)
+{
+ int i, err;
+ char mx[SNDRV_CTL_ELEM_ID_NAME_MAXLEN];
+ const struct scarlett_mixer_control *ctl;
+ struct usb_mixer_elem_info *elem;
+
+ /* create master switch and playback volume */
+ err = add_new_ctl(mixer, &usb_scarlett_ctl_switch,
+ scarlett_ctl_resume, 0x0a, 0x01, 0,
+ USB_MIXER_S16, 1, "Master Playback Switch", NULL,
+ &elem);
+ if (err < 0)
+ return err;
+
+ err = add_new_ctl(mixer, &usb_scarlett_ctl_master,
+ scarlett_ctl_resume, 0x0a, 0x02, 0,
+ USB_MIXER_S16, 1, "Master Playback Volume", NULL,
+ &elem);
+ if (err < 0)
+ return err;
+
+ /* iterate through controls in info struct and create each one */
+ for (i = 0; i < info->num_controls; i++) {
+ ctl = &info->controls[i];
+
+ switch (ctl->type) {
+ case SCARLETT_OUTPUTS:
+ err = add_output_ctls(mixer, ctl->num, ctl->name, info);
+ if (err < 0)
+ return err;
+ break;
+ case SCARLETT_SWITCH_IMPEDANCE:
+ sprintf(mx, "Input %d Impedance Switch", ctl->num);
+ err = add_new_ctl(mixer, &usb_scarlett_ctl_enum,
+ scarlett_ctl_enum_resume, 0x01,
+ 0x09, ctl->num, USB_MIXER_S16, 1, mx,
+ &opt_impedance, &elem);
+ if (err < 0)
+ return err;
+ break;
+ case SCARLETT_SWITCH_PAD:
+ sprintf(mx, "Input %d Pad Switch", ctl->num);
+ err = add_new_ctl(mixer, &usb_scarlett_ctl_enum,
+ scarlett_ctl_enum_resume, 0x01,
+ 0x0b, ctl->num, USB_MIXER_S16, 1, mx,
+ &opt_pad, &elem);
+ if (err < 0)
+ return err;
+ break;
+ }
+ }
+
+ return 0;
+}
+
+/*
+ * Create and initialize a mixer for the Focusrite(R) Scarlett
+ */
+int snd_scarlett_controls_create(struct usb_mixer_interface *mixer)
+{
+ int err, i, o;
+ char mx[SNDRV_CTL_ELEM_ID_NAME_MAXLEN];
+ struct scarlett_device_info *info;
+ struct usb_mixer_elem_info *elem;
+ static char sample_rate_buffer[4] = { '\x80', '\xbb', '\x00', '\x00' };
+
+ /* only use UAC_VERSION_2 */
+ if (!mixer->protocol)
+ return 0;
+
+ switch (mixer->chip->usb_id) {
+ case USB_ID(0x1235, 0x8012):
+ info = &s6i6_info;
+ break;
+ case USB_ID(0x1235, 0x8002):
+ info = &s8i6_info;
+ break;
+ case USB_ID(0x1235, 0x8004):
+ info = &s18i6_info;
+ break;
+ case USB_ID(0x1235, 0x8014):
+ info = &s18i8_info;
+ break;
+ case USB_ID(0x1235, 0x800c):
+ info = &s18i20_info;
+ break;
+ default: /* device not (yet) supported */
+ return -EINVAL;
+ }
+
+ /* generic function to create controls */
+ err = scarlett_controls_create_generic(mixer, info);
+ if (err < 0)
+ return err;
+
+ /* setup matrix controls */
+ for (i = 0; i < info->matrix_in; i++) {
+ snprintf(mx, sizeof(mx), "Matrix %02d Input Playback Route",
+ i+1);
+ err = add_new_ctl(mixer, &usb_scarlett_ctl_dynamic_enum,
+ scarlett_ctl_enum_resume, 0x32,
+ 0x06, i, USB_MIXER_S16, 1, mx,
+ &info->opt_matrix, &elem);
+ if (err < 0)
+ return err;
+
+ for (o = 0; o < info->matrix_out; o++) {
+ sprintf(mx, "Matrix %02d Mix %c Playback Volume", i+1,
+ o+'A');
+ err = add_new_ctl(mixer, &usb_scarlett_ctl,
+ scarlett_ctl_resume, 0x3c, 0x00,
+ (i << 3) + (o & 0x07), USB_MIXER_S16,
+ 1, mx, NULL, &elem);
+ if (err < 0)
+ return err;
+
+ }
+ }
+
+ for (i = 0; i < info->input_len; i++) {
+ snprintf(mx, sizeof(mx), "Input Source %02d Capture Route",
+ i+1);
+ err = add_new_ctl(mixer, &usb_scarlett_ctl_dynamic_enum,
+ scarlett_ctl_enum_resume, 0x34,
+ 0x00, i, USB_MIXER_S16, 1, mx,
+ &info->opt_master, &elem);
+ if (err < 0)
+ return err;
+ }
+
+ /* val_len == 1 needed here */
+ err = add_new_ctl(mixer, &usb_scarlett_ctl_enum,
+ scarlett_ctl_enum_resume, 0x28, 0x01, 0,
+ USB_MIXER_U8, 1, "Sample Clock Source",
+ &opt_clock, &elem);
+ if (err < 0)
+ return err;
+
+ /* val_len == 1 and UAC2_CS_MEM */
+ err = add_new_ctl(mixer, &usb_scarlett_ctl_sync, NULL, 0x3c, 0x00, 2,
+ USB_MIXER_U8, 1, "Sample Clock Sync Status",
+ &opt_sync, &elem);
+ if (err < 0)
+ return err;
+
+ /* initialize sampling rate to 48000 */
+ err = snd_usb_ctl_msg(mixer->chip->dev,
+ usb_sndctrlpipe(mixer->chip->dev, 0), UAC2_CS_CUR,
+ USB_RECIP_INTERFACE | USB_TYPE_CLASS |
+ USB_DIR_OUT, 0x0100, snd_usb_ctrl_intf(mixer->chip) |
+ (0x29 << 8), sample_rate_buffer, 4);
+ if (err < 0)
+ return err;
+
+ return err;
+}
diff --git a/sound/usb/mixer_scarlett.h b/sound/usb/mixer_scarlett.h
new file mode 100644
index 000000000..19c592ab0
--- /dev/null
+++ b/sound/usb/mixer_scarlett.h
@@ -0,0 +1,6 @@
+#ifndef __USB_MIXER_SCARLETT_H
+#define __USB_MIXER_SCARLETT_H
+
+int snd_scarlett_controls_create(struct usb_mixer_interface *mixer);
+
+#endif /* __USB_MIXER_SCARLETT_H */
diff --git a/sound/usb/pcm.c b/sound/usb/pcm.c
new file mode 100644
index 000000000..b4ef410e5
--- /dev/null
+++ b/sound/usb/pcm.c
@@ -0,0 +1,1645 @@
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/bitrev.h>
+#include <linux/ratelimit.h>
+#include <linux/usb.h>
+#include <linux/usb/audio.h>
+#include <linux/usb/audio-v2.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+
+#include "usbaudio.h"
+#include "card.h"
+#include "quirks.h"
+#include "debug.h"
+#include "endpoint.h"
+#include "helper.h"
+#include "pcm.h"
+#include "clock.h"
+#include "power.h"
+
+#define SUBSTREAM_FLAG_DATA_EP_STARTED 0
+#define SUBSTREAM_FLAG_SYNC_EP_STARTED 1
+
+/* return the estimated delay based on USB frame counters */
+snd_pcm_uframes_t snd_usb_pcm_delay(struct snd_usb_substream *subs,
+ unsigned int rate)
+{
+ int current_frame_number;
+ int frame_diff;
+ int est_delay;
+
+ if (!subs->last_delay)
+ return 0; /* short path */
+
+ current_frame_number = usb_get_current_frame_number(subs->dev);
+ /*
+ * HCD implementations use different widths, use lower 8 bits.
+ * The delay will be managed up to 256ms, which is more than
+ * enough
+ */
+ frame_diff = (current_frame_number - subs->last_frame_number) & 0xff;
+
+ /* Approximation based on number of samples per USB frame (ms),
+ some truncation for 44.1 but the estimate is good enough */
+ est_delay = frame_diff * rate / 1000;
+ if (subs->direction == SNDRV_PCM_STREAM_PLAYBACK)
+ est_delay = subs->last_delay - est_delay;
+ else
+ est_delay = subs->last_delay + est_delay;
+
+ if (est_delay < 0)
+ est_delay = 0;
+ return est_delay;
+}
+
+/*
+ * return the current pcm pointer. just based on the hwptr_done value.
+ */
+static snd_pcm_uframes_t snd_usb_pcm_pointer(struct snd_pcm_substream *substream)
+{
+ struct snd_usb_substream *subs;
+ unsigned int hwptr_done;
+
+ subs = (struct snd_usb_substream *)substream->runtime->private_data;
+ if (subs->stream->chip->shutdown)
+ return SNDRV_PCM_POS_XRUN;
+ spin_lock(&subs->lock);
+ hwptr_done = subs->hwptr_done;
+ substream->runtime->delay = snd_usb_pcm_delay(subs,
+ substream->runtime->rate);
+ spin_unlock(&subs->lock);
+ return hwptr_done / (substream->runtime->frame_bits >> 3);
+}
+
+/*
+ * find a matching audio format
+ */
+static struct audioformat *find_format(struct snd_usb_substream *subs)
+{
+ struct audioformat *fp;
+ struct audioformat *found = NULL;
+ int cur_attr = 0, attr;
+
+ list_for_each_entry(fp, &subs->fmt_list, list) {
+ if (!(fp->formats & pcm_format_to_bits(subs->pcm_format)))
+ continue;
+ if (fp->channels != subs->channels)
+ continue;
+ if (subs->cur_rate < fp->rate_min ||
+ subs->cur_rate > fp->rate_max)
+ continue;
+ if (! (fp->rates & SNDRV_PCM_RATE_CONTINUOUS)) {
+ unsigned int i;
+ for (i = 0; i < fp->nr_rates; i++)
+ if (fp->rate_table[i] == subs->cur_rate)
+ break;
+ if (i >= fp->nr_rates)
+ continue;
+ }
+ attr = fp->ep_attr & USB_ENDPOINT_SYNCTYPE;
+ if (! found) {
+ found = fp;
+ cur_attr = attr;
+ continue;
+ }
+ /* avoid async out and adaptive in if the other method
+ * supports the same format.
+ * this is a workaround for the case like
+ * M-audio audiophile USB.
+ */
+ if (attr != cur_attr) {
+ if ((attr == USB_ENDPOINT_SYNC_ASYNC &&
+ subs->direction == SNDRV_PCM_STREAM_PLAYBACK) ||
+ (attr == USB_ENDPOINT_SYNC_ADAPTIVE &&
+ subs->direction == SNDRV_PCM_STREAM_CAPTURE))
+ continue;
+ if ((cur_attr == USB_ENDPOINT_SYNC_ASYNC &&
+ subs->direction == SNDRV_PCM_STREAM_PLAYBACK) ||
+ (cur_attr == USB_ENDPOINT_SYNC_ADAPTIVE &&
+ subs->direction == SNDRV_PCM_STREAM_CAPTURE)) {
+ found = fp;
+ cur_attr = attr;
+ continue;
+ }
+ }
+ /* find the format with the largest max. packet size */
+ if (fp->maxpacksize > found->maxpacksize) {
+ found = fp;
+ cur_attr = attr;
+ }
+ }
+ return found;
+}
+
+static int init_pitch_v1(struct snd_usb_audio *chip, int iface,
+ struct usb_host_interface *alts,
+ struct audioformat *fmt)
+{
+ struct usb_device *dev = chip->dev;
+ unsigned int ep;
+ unsigned char data[1];
+ int err;
+
+ ep = get_endpoint(alts, 0)->bEndpointAddress;
+
+ data[0] = 1;
+ if ((err = snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), UAC_SET_CUR,
+ USB_TYPE_CLASS|USB_RECIP_ENDPOINT|USB_DIR_OUT,
+ UAC_EP_CS_ATTR_PITCH_CONTROL << 8, ep,
+ data, sizeof(data))) < 0) {
+ usb_audio_err(chip, "%d:%d: cannot set enable PITCH\n",
+ iface, ep);
+ return err;
+ }
+
+ return 0;
+}
+
+static int init_pitch_v2(struct snd_usb_audio *chip, int iface,
+ struct usb_host_interface *alts,
+ struct audioformat *fmt)
+{
+ struct usb_device *dev = chip->dev;
+ unsigned char data[1];
+ int err;
+
+ data[0] = 1;
+ if ((err = snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), UAC2_CS_CUR,
+ USB_TYPE_CLASS | USB_RECIP_ENDPOINT | USB_DIR_OUT,
+ UAC2_EP_CS_PITCH << 8, 0,
+ data, sizeof(data))) < 0) {
+ usb_audio_err(chip, "%d:%d: cannot set enable PITCH (v2)\n",
+ iface, fmt->altsetting);
+ return err;
+ }
+
+ return 0;
+}
+
+/*
+ * initialize the pitch control and sample rate
+ */
+int snd_usb_init_pitch(struct snd_usb_audio *chip, int iface,
+ struct usb_host_interface *alts,
+ struct audioformat *fmt)
+{
+ /* if endpoint doesn't have pitch control, bail out */
+ if (!(fmt->attributes & UAC_EP_CS_ATTR_PITCH_CONTROL))
+ return 0;
+
+ switch (fmt->protocol) {
+ case UAC_VERSION_1:
+ default:
+ return init_pitch_v1(chip, iface, alts, fmt);
+
+ case UAC_VERSION_2:
+ return init_pitch_v2(chip, iface, alts, fmt);
+ }
+}
+
+static int start_endpoints(struct snd_usb_substream *subs, bool can_sleep)
+{
+ int err;
+
+ if (!subs->data_endpoint)
+ return -EINVAL;
+
+ if (!test_and_set_bit(SUBSTREAM_FLAG_DATA_EP_STARTED, &subs->flags)) {
+ struct snd_usb_endpoint *ep = subs->data_endpoint;
+
+ dev_dbg(&subs->dev->dev, "Starting data EP @%p\n", ep);
+
+ ep->data_subs = subs;
+ err = snd_usb_endpoint_start(ep, can_sleep);
+ if (err < 0) {
+ clear_bit(SUBSTREAM_FLAG_DATA_EP_STARTED, &subs->flags);
+ return err;
+ }
+ }
+
+ if (subs->sync_endpoint &&
+ !test_and_set_bit(SUBSTREAM_FLAG_SYNC_EP_STARTED, &subs->flags)) {
+ struct snd_usb_endpoint *ep = subs->sync_endpoint;
+
+ if (subs->data_endpoint->iface != subs->sync_endpoint->iface ||
+ subs->data_endpoint->altsetting != subs->sync_endpoint->altsetting) {
+ err = usb_set_interface(subs->dev,
+ subs->sync_endpoint->iface,
+ subs->sync_endpoint->altsetting);
+ if (err < 0) {
+ clear_bit(SUBSTREAM_FLAG_SYNC_EP_STARTED, &subs->flags);
+ dev_err(&subs->dev->dev,
+ "%d:%d: cannot set interface (%d)\n",
+ subs->sync_endpoint->iface,
+ subs->sync_endpoint->altsetting, err);
+ return -EIO;
+ }
+ }
+
+ dev_dbg(&subs->dev->dev, "Starting sync EP @%p\n", ep);
+
+ ep->sync_slave = subs->data_endpoint;
+ err = snd_usb_endpoint_start(ep, can_sleep);
+ if (err < 0) {
+ clear_bit(SUBSTREAM_FLAG_SYNC_EP_STARTED, &subs->flags);
+ return err;
+ }
+ }
+
+ return 0;
+}
+
+static void stop_endpoints(struct snd_usb_substream *subs, bool wait)
+{
+ if (test_and_clear_bit(SUBSTREAM_FLAG_SYNC_EP_STARTED, &subs->flags))
+ snd_usb_endpoint_stop(subs->sync_endpoint);
+
+ if (test_and_clear_bit(SUBSTREAM_FLAG_DATA_EP_STARTED, &subs->flags))
+ snd_usb_endpoint_stop(subs->data_endpoint);
+
+ if (wait) {
+ snd_usb_endpoint_sync_pending_stop(subs->sync_endpoint);
+ snd_usb_endpoint_sync_pending_stop(subs->data_endpoint);
+ }
+}
+
+static int search_roland_implicit_fb(struct usb_device *dev, int ifnum,
+ unsigned int altsetting,
+ struct usb_host_interface **alts,
+ unsigned int *ep)
+{
+ struct usb_interface *iface;
+ struct usb_interface_descriptor *altsd;
+ struct usb_endpoint_descriptor *epd;
+
+ iface = usb_ifnum_to_if(dev, ifnum);
+ if (!iface || iface->num_altsetting < altsetting + 1)
+ return -ENOENT;
+ *alts = &iface->altsetting[altsetting];
+ altsd = get_iface_desc(*alts);
+ if (altsd->bAlternateSetting != altsetting ||
+ altsd->bInterfaceClass != USB_CLASS_VENDOR_SPEC ||
+ (altsd->bInterfaceSubClass != 2 &&
+ altsd->bInterfaceProtocol != 2 ) ||
+ altsd->bNumEndpoints < 1)
+ return -ENOENT;
+ epd = get_endpoint(*alts, 0);
+ if (!usb_endpoint_is_isoc_in(epd) ||
+ (epd->bmAttributes & USB_ENDPOINT_USAGE_MASK) !=
+ USB_ENDPOINT_USAGE_IMPLICIT_FB)
+ return -ENOENT;
+ *ep = epd->bEndpointAddress;
+ return 0;
+}
+
+static int set_sync_ep_implicit_fb_quirk(struct snd_usb_substream *subs,
+ struct usb_device *dev,
+ struct usb_interface_descriptor *altsd,
+ unsigned int attr)
+{
+ struct usb_host_interface *alts;
+ struct usb_interface *iface;
+ unsigned int ep;
+
+ /* Implicit feedback sync EPs consumers are always playback EPs */
+ if (subs->direction != SNDRV_PCM_STREAM_PLAYBACK)
+ return 0;
+
+ switch (subs->stream->chip->usb_id) {
+ case USB_ID(0x0763, 0x2030): /* M-Audio Fast Track C400 */
+ case USB_ID(0x0763, 0x2031): /* M-Audio Fast Track C600 */
+ ep = 0x81;
+ iface = usb_ifnum_to_if(dev, 3);
+
+ if (!iface || iface->num_altsetting == 0)
+ return -EINVAL;
+
+ alts = &iface->altsetting[1];
+ goto add_sync_ep;
+ break;
+ case USB_ID(0x0763, 0x2080): /* M-Audio FastTrack Ultra */
+ case USB_ID(0x0763, 0x2081):
+ ep = 0x81;
+ iface = usb_ifnum_to_if(dev, 2);
+
+ if (!iface || iface->num_altsetting == 0)
+ return -EINVAL;
+
+ alts = &iface->altsetting[1];
+ goto add_sync_ep;
+ }
+ if (attr == USB_ENDPOINT_SYNC_ASYNC &&
+ altsd->bInterfaceClass == USB_CLASS_VENDOR_SPEC &&
+ altsd->bInterfaceProtocol == 2 &&
+ altsd->bNumEndpoints == 1 &&
+ USB_ID_VENDOR(subs->stream->chip->usb_id) == 0x0582 /* Roland */ &&
+ search_roland_implicit_fb(dev, altsd->bInterfaceNumber + 1,
+ altsd->bAlternateSetting,
+ &alts, &ep) >= 0) {
+ goto add_sync_ep;
+ }
+
+ /* No quirk */
+ return 0;
+
+add_sync_ep:
+ subs->sync_endpoint = snd_usb_add_endpoint(subs->stream->chip,
+ alts, ep, !subs->direction,
+ SND_USB_ENDPOINT_TYPE_DATA);
+ if (!subs->sync_endpoint)
+ return -EINVAL;
+
+ subs->data_endpoint->sync_master = subs->sync_endpoint;
+
+ return 0;
+}
+
+static int set_sync_endpoint(struct snd_usb_substream *subs,
+ struct audioformat *fmt,
+ struct usb_device *dev,
+ struct usb_host_interface *alts,
+ struct usb_interface_descriptor *altsd)
+{
+ int is_playback = subs->direction == SNDRV_PCM_STREAM_PLAYBACK;
+ unsigned int ep, attr;
+ bool implicit_fb;
+ int err;
+
+ /* we need a sync pipe in async OUT or adaptive IN mode */
+ /* check the number of EP, since some devices have broken
+ * descriptors which fool us. if it has only one EP,
+ * assume it as adaptive-out or sync-in.
+ */
+ attr = fmt->ep_attr & USB_ENDPOINT_SYNCTYPE;
+
+ err = set_sync_ep_implicit_fb_quirk(subs, dev, altsd, attr);
+ if (err < 0)
+ return err;
+
+ if (altsd->bNumEndpoints < 2)
+ return 0;
+
+ if ((is_playback && attr != USB_ENDPOINT_SYNC_ASYNC) ||
+ (!is_playback && attr != USB_ENDPOINT_SYNC_ADAPTIVE))
+ return 0;
+
+ /* check sync-pipe endpoint */
+ /* ... and check descriptor size before accessing bSynchAddress
+ because there is a version of the SB Audigy 2 NX firmware lacking
+ the audio fields in the endpoint descriptors */
+ if ((get_endpoint(alts, 1)->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) != USB_ENDPOINT_XFER_ISOC ||
+ (get_endpoint(alts, 1)->bLength >= USB_DT_ENDPOINT_AUDIO_SIZE &&
+ get_endpoint(alts, 1)->bSynchAddress != 0)) {
+ dev_err(&dev->dev,
+ "%d:%d : invalid sync pipe. bmAttributes %02x, bLength %d, bSynchAddress %02x\n",
+ fmt->iface, fmt->altsetting,
+ get_endpoint(alts, 1)->bmAttributes,
+ get_endpoint(alts, 1)->bLength,
+ get_endpoint(alts, 1)->bSynchAddress);
+ return -EINVAL;
+ }
+ ep = get_endpoint(alts, 1)->bEndpointAddress;
+ if (get_endpoint(alts, 0)->bLength >= USB_DT_ENDPOINT_AUDIO_SIZE &&
+ ((is_playback && ep != (unsigned int)(get_endpoint(alts, 0)->bSynchAddress | USB_DIR_IN)) ||
+ (!is_playback && ep != (unsigned int)(get_endpoint(alts, 0)->bSynchAddress & ~USB_DIR_IN)))) {
+ dev_err(&dev->dev,
+ "%d:%d : invalid sync pipe. is_playback %d, ep %02x, bSynchAddress %02x\n",
+ fmt->iface, fmt->altsetting,
+ is_playback, ep, get_endpoint(alts, 0)->bSynchAddress);
+ return -EINVAL;
+ }
+
+ implicit_fb = (get_endpoint(alts, 1)->bmAttributes & USB_ENDPOINT_USAGE_MASK)
+ == USB_ENDPOINT_USAGE_IMPLICIT_FB;
+
+ subs->sync_endpoint = snd_usb_add_endpoint(subs->stream->chip,
+ alts, ep, !subs->direction,
+ implicit_fb ?
+ SND_USB_ENDPOINT_TYPE_DATA :
+ SND_USB_ENDPOINT_TYPE_SYNC);
+ if (!subs->sync_endpoint)
+ return -EINVAL;
+
+ subs->data_endpoint->sync_master = subs->sync_endpoint;
+
+ return 0;
+}
+
+/*
+ * find a matching format and set up the interface
+ */
+static int set_format(struct snd_usb_substream *subs, struct audioformat *fmt)
+{
+ struct usb_device *dev = subs->dev;
+ struct usb_host_interface *alts;
+ struct usb_interface_descriptor *altsd;
+ struct usb_interface *iface;
+ int err;
+
+ iface = usb_ifnum_to_if(dev, fmt->iface);
+ if (WARN_ON(!iface))
+ return -EINVAL;
+ alts = &iface->altsetting[fmt->altset_idx];
+ altsd = get_iface_desc(alts);
+ if (WARN_ON(altsd->bAlternateSetting != fmt->altsetting))
+ return -EINVAL;
+
+ if (fmt == subs->cur_audiofmt)
+ return 0;
+
+ /* close the old interface */
+ if (subs->interface >= 0 && subs->interface != fmt->iface) {
+ err = usb_set_interface(subs->dev, subs->interface, 0);
+ if (err < 0) {
+ dev_err(&dev->dev,
+ "%d:%d: return to setting 0 failed (%d)\n",
+ fmt->iface, fmt->altsetting, err);
+ return -EIO;
+ }
+ subs->interface = -1;
+ subs->altset_idx = 0;
+ }
+
+ /* set interface */
+ if (subs->interface != fmt->iface ||
+ subs->altset_idx != fmt->altset_idx) {
+
+ err = snd_usb_select_mode_quirk(subs, fmt);
+ if (err < 0)
+ return -EIO;
+
+ err = usb_set_interface(dev, fmt->iface, fmt->altsetting);
+ if (err < 0) {
+ dev_err(&dev->dev,
+ "%d:%d: usb_set_interface failed (%d)\n",
+ fmt->iface, fmt->altsetting, err);
+ return -EIO;
+ }
+ dev_dbg(&dev->dev, "setting usb interface %d:%d\n",
+ fmt->iface, fmt->altsetting);
+ subs->interface = fmt->iface;
+ subs->altset_idx = fmt->altset_idx;
+
+ snd_usb_set_interface_quirk(dev);
+ }
+
+ subs->data_endpoint = snd_usb_add_endpoint(subs->stream->chip,
+ alts, fmt->endpoint, subs->direction,
+ SND_USB_ENDPOINT_TYPE_DATA);
+
+ if (!subs->data_endpoint)
+ return -EINVAL;
+
+ err = set_sync_endpoint(subs, fmt, dev, alts, altsd);
+ if (err < 0)
+ return err;
+
+ err = snd_usb_init_pitch(subs->stream->chip, fmt->iface, alts, fmt);
+ if (err < 0)
+ return err;
+
+ subs->cur_audiofmt = fmt;
+
+ snd_usb_set_format_quirk(subs, fmt);
+
+ return 0;
+}
+
+/*
+ * Return the score of matching two audioformats.
+ * Veto the audioformat if:
+ * - It has no channels for some reason.
+ * - Requested PCM format is not supported.
+ * - Requested sample rate is not supported.
+ */
+static int match_endpoint_audioformats(struct snd_usb_substream *subs,
+ struct audioformat *fp,
+ struct audioformat *match, int rate,
+ snd_pcm_format_t pcm_format)
+{
+ int i;
+ int score = 0;
+
+ if (fp->channels < 1) {
+ dev_dbg(&subs->dev->dev,
+ "%s: (fmt @%p) no channels\n", __func__, fp);
+ return 0;
+ }
+
+ if (!(fp->formats & pcm_format_to_bits(pcm_format))) {
+ dev_dbg(&subs->dev->dev,
+ "%s: (fmt @%p) no match for format %d\n", __func__,
+ fp, pcm_format);
+ return 0;
+ }
+
+ for (i = 0; i < fp->nr_rates; i++) {
+ if (fp->rate_table[i] == rate) {
+ score++;
+ break;
+ }
+ }
+ if (!score) {
+ dev_dbg(&subs->dev->dev,
+ "%s: (fmt @%p) no match for rate %d\n", __func__,
+ fp, rate);
+ return 0;
+ }
+
+ if (fp->channels == match->channels)
+ score++;
+
+ dev_dbg(&subs->dev->dev,
+ "%s: (fmt @%p) score %d\n", __func__, fp, score);
+
+ return score;
+}
+
+/*
+ * Configure the sync ep using the rate and pcm format of the data ep.
+ */
+static int configure_sync_endpoint(struct snd_usb_substream *subs)
+{
+ int ret;
+ struct audioformat *fp;
+ struct audioformat *sync_fp = NULL;
+ int cur_score = 0;
+ int sync_period_bytes = subs->period_bytes;
+ struct snd_usb_substream *sync_subs =
+ &subs->stream->substream[subs->direction ^ 1];
+
+ if (subs->sync_endpoint->type != SND_USB_ENDPOINT_TYPE_DATA ||
+ !subs->stream)
+ return snd_usb_endpoint_set_params(subs->sync_endpoint,
+ subs->pcm_format,
+ subs->channels,
+ subs->period_bytes,
+ 0, 0,
+ subs->cur_rate,
+ subs->cur_audiofmt,
+ NULL);
+
+ /* Try to find the best matching audioformat. */
+ list_for_each_entry(fp, &sync_subs->fmt_list, list) {
+ int score = match_endpoint_audioformats(subs,
+ fp, subs->cur_audiofmt,
+ subs->cur_rate, subs->pcm_format);
+
+ if (score > cur_score) {
+ sync_fp = fp;
+ cur_score = score;
+ }
+ }
+
+ if (unlikely(sync_fp == NULL)) {
+ dev_err(&subs->dev->dev,
+ "%s: no valid audioformat for sync ep %x found\n",
+ __func__, sync_subs->ep_num);
+ return -EINVAL;
+ }
+
+ /*
+ * Recalculate the period bytes if channel number differ between
+ * data and sync ep audioformat.
+ */
+ if (sync_fp->channels != subs->channels) {
+ sync_period_bytes = (subs->period_bytes / subs->channels) *
+ sync_fp->channels;
+ dev_dbg(&subs->dev->dev,
+ "%s: adjusted sync ep period bytes (%d -> %d)\n",
+ __func__, subs->period_bytes, sync_period_bytes);
+ }
+
+ ret = snd_usb_endpoint_set_params(subs->sync_endpoint,
+ subs->pcm_format,
+ sync_fp->channels,
+ sync_period_bytes,
+ 0, 0,
+ subs->cur_rate,
+ sync_fp,
+ NULL);
+
+ return ret;
+}
+
+/*
+ * configure endpoint params
+ *
+ * called during initial setup and upon resume
+ */
+static int configure_endpoint(struct snd_usb_substream *subs)
+{
+ int ret;
+
+ /* format changed */
+ stop_endpoints(subs, true);
+ ret = snd_usb_endpoint_set_params(subs->data_endpoint,
+ subs->pcm_format,
+ subs->channels,
+ subs->period_bytes,
+ subs->period_frames,
+ subs->buffer_periods,
+ subs->cur_rate,
+ subs->cur_audiofmt,
+ subs->sync_endpoint);
+ if (ret < 0)
+ return ret;
+
+ if (subs->sync_endpoint)
+ ret = configure_sync_endpoint(subs);
+
+ return ret;
+}
+
+/*
+ * hw_params callback
+ *
+ * allocate a buffer and set the given audio format.
+ *
+ * so far we use a physically linear buffer although packetize transfer
+ * doesn't need a continuous area.
+ * if sg buffer is supported on the later version of alsa, we'll follow
+ * that.
+ */
+static int snd_usb_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *hw_params)
+{
+ struct snd_usb_substream *subs = substream->runtime->private_data;
+ struct audioformat *fmt;
+ int ret;
+
+ ret = snd_pcm_lib_alloc_vmalloc_buffer(substream,
+ params_buffer_bytes(hw_params));
+ if (ret < 0)
+ return ret;
+
+ subs->pcm_format = params_format(hw_params);
+ subs->period_bytes = params_period_bytes(hw_params);
+ subs->period_frames = params_period_size(hw_params);
+ subs->buffer_periods = params_periods(hw_params);
+ subs->channels = params_channels(hw_params);
+ subs->cur_rate = params_rate(hw_params);
+
+ fmt = find_format(subs);
+ if (!fmt) {
+ dev_dbg(&subs->dev->dev,
+ "cannot set format: format = %#x, rate = %d, channels = %d\n",
+ subs->pcm_format, subs->cur_rate, subs->channels);
+ return -EINVAL;
+ }
+
+ down_read(&subs->stream->chip->shutdown_rwsem);
+ if (subs->stream->chip->shutdown)
+ ret = -ENODEV;
+ else
+ ret = set_format(subs, fmt);
+ up_read(&subs->stream->chip->shutdown_rwsem);
+ if (ret < 0)
+ return ret;
+
+ subs->interface = fmt->iface;
+ subs->altset_idx = fmt->altset_idx;
+ subs->need_setup_ep = true;
+
+ return 0;
+}
+
+/*
+ * hw_free callback
+ *
+ * reset the audio format and release the buffer
+ */
+static int snd_usb_hw_free(struct snd_pcm_substream *substream)
+{
+ struct snd_usb_substream *subs = substream->runtime->private_data;
+
+ subs->cur_audiofmt = NULL;
+ subs->cur_rate = 0;
+ subs->period_bytes = 0;
+ down_read(&subs->stream->chip->shutdown_rwsem);
+ if (!subs->stream->chip->shutdown) {
+ stop_endpoints(subs, true);
+ snd_usb_endpoint_deactivate(subs->sync_endpoint);
+ snd_usb_endpoint_deactivate(subs->data_endpoint);
+ }
+ up_read(&subs->stream->chip->shutdown_rwsem);
+ return snd_pcm_lib_free_vmalloc_buffer(substream);
+}
+
+/*
+ * prepare callback
+ *
+ * only a few subtle things...
+ */
+static int snd_usb_pcm_prepare(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct snd_usb_substream *subs = runtime->private_data;
+ struct usb_host_interface *alts;
+ struct usb_interface *iface;
+ int ret;
+
+ if (! subs->cur_audiofmt) {
+ dev_err(&subs->dev->dev, "no format is specified!\n");
+ return -ENXIO;
+ }
+
+ down_read(&subs->stream->chip->shutdown_rwsem);
+ if (subs->stream->chip->shutdown) {
+ ret = -ENODEV;
+ goto unlock;
+ }
+ if (snd_BUG_ON(!subs->data_endpoint)) {
+ ret = -EIO;
+ goto unlock;
+ }
+
+ snd_usb_endpoint_sync_pending_stop(subs->sync_endpoint);
+ snd_usb_endpoint_sync_pending_stop(subs->data_endpoint);
+
+ ret = set_format(subs, subs->cur_audiofmt);
+ if (ret < 0)
+ goto unlock;
+
+ iface = usb_ifnum_to_if(subs->dev, subs->cur_audiofmt->iface);
+ alts = &iface->altsetting[subs->cur_audiofmt->altset_idx];
+ ret = snd_usb_init_sample_rate(subs->stream->chip,
+ subs->cur_audiofmt->iface,
+ alts,
+ subs->cur_audiofmt,
+ subs->cur_rate);
+ if (ret < 0)
+ goto unlock;
+
+ if (subs->need_setup_ep) {
+ ret = configure_endpoint(subs);
+ if (ret < 0)
+ goto unlock;
+ subs->need_setup_ep = false;
+ }
+
+ /* some unit conversions in runtime */
+ subs->data_endpoint->maxframesize =
+ bytes_to_frames(runtime, subs->data_endpoint->maxpacksize);
+ subs->data_endpoint->curframesize =
+ bytes_to_frames(runtime, subs->data_endpoint->curpacksize);
+
+ /* reset the pointer */
+ subs->hwptr_done = 0;
+ subs->transfer_done = 0;
+ subs->last_delay = 0;
+ subs->last_frame_number = 0;
+ runtime->delay = 0;
+
+ /* for playback, submit the URBs now; otherwise, the first hwptr_done
+ * updates for all URBs would happen at the same time when starting */
+ if (subs->direction == SNDRV_PCM_STREAM_PLAYBACK)
+ ret = start_endpoints(subs, true);
+
+ unlock:
+ up_read(&subs->stream->chip->shutdown_rwsem);
+ return ret;
+}
+
+static struct snd_pcm_hardware snd_usb_hardware =
+{
+ .info = SNDRV_PCM_INFO_MMAP |
+ SNDRV_PCM_INFO_MMAP_VALID |
+ SNDRV_PCM_INFO_BATCH |
+ SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_BLOCK_TRANSFER |
+ SNDRV_PCM_INFO_PAUSE,
+ .buffer_bytes_max = 1024 * 1024,
+ .period_bytes_min = 64,
+ .period_bytes_max = 512 * 1024,
+ .periods_min = 2,
+ .periods_max = 1024,
+};
+
+static int hw_check_valid_format(struct snd_usb_substream *subs,
+ struct snd_pcm_hw_params *params,
+ struct audioformat *fp)
+{
+ struct snd_interval *it = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE);
+ struct snd_interval *ct = hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS);
+ struct snd_mask *fmts = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT);
+ struct snd_interval *pt = hw_param_interval(params, SNDRV_PCM_HW_PARAM_PERIOD_TIME);
+ struct snd_mask check_fmts;
+ unsigned int ptime;
+
+ /* check the format */
+ snd_mask_none(&check_fmts);
+ check_fmts.bits[0] = (u32)fp->formats;
+ check_fmts.bits[1] = (u32)(fp->formats >> 32);
+ snd_mask_intersect(&check_fmts, fmts);
+ if (snd_mask_empty(&check_fmts)) {
+ hwc_debug(" > check: no supported format %d\n", fp->format);
+ return 0;
+ }
+ /* check the channels */
+ if (fp->channels < ct->min || fp->channels > ct->max) {
+ hwc_debug(" > check: no valid channels %d (%d/%d)\n", fp->channels, ct->min, ct->max);
+ return 0;
+ }
+ /* check the rate is within the range */
+ if (fp->rate_min > it->max || (fp->rate_min == it->max && it->openmax)) {
+ hwc_debug(" > check: rate_min %d > max %d\n", fp->rate_min, it->max);
+ return 0;
+ }
+ if (fp->rate_max < it->min || (fp->rate_max == it->min && it->openmin)) {
+ hwc_debug(" > check: rate_max %d < min %d\n", fp->rate_max, it->min);
+ return 0;
+ }
+ /* check whether the period time is >= the data packet interval */
+ if (subs->speed != USB_SPEED_FULL) {
+ ptime = 125 * (1 << fp->datainterval);
+ if (ptime > pt->max || (ptime == pt->max && pt->openmax)) {
+ hwc_debug(" > check: ptime %u > max %u\n", ptime, pt->max);
+ return 0;
+ }
+ }
+ return 1;
+}
+
+static int hw_rule_rate(struct snd_pcm_hw_params *params,
+ struct snd_pcm_hw_rule *rule)
+{
+ struct snd_usb_substream *subs = rule->private;
+ struct audioformat *fp;
+ struct snd_interval *it = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE);
+ unsigned int rmin, rmax;
+ int changed;
+
+ hwc_debug("hw_rule_rate: (%d,%d)\n", it->min, it->max);
+ changed = 0;
+ rmin = rmax = 0;
+ list_for_each_entry(fp, &subs->fmt_list, list) {
+ if (!hw_check_valid_format(subs, params, fp))
+ continue;
+ if (changed++) {
+ if (rmin > fp->rate_min)
+ rmin = fp->rate_min;
+ if (rmax < fp->rate_max)
+ rmax = fp->rate_max;
+ } else {
+ rmin = fp->rate_min;
+ rmax = fp->rate_max;
+ }
+ }
+
+ if (!changed) {
+ hwc_debug(" --> get empty\n");
+ it->empty = 1;
+ return -EINVAL;
+ }
+
+ changed = 0;
+ if (it->min < rmin) {
+ it->min = rmin;
+ it->openmin = 0;
+ changed = 1;
+ }
+ if (it->max > rmax) {
+ it->max = rmax;
+ it->openmax = 0;
+ changed = 1;
+ }
+ if (snd_interval_checkempty(it)) {
+ it->empty = 1;
+ return -EINVAL;
+ }
+ hwc_debug(" --> (%d, %d) (changed = %d)\n", it->min, it->max, changed);
+ return changed;
+}
+
+
+static int hw_rule_channels(struct snd_pcm_hw_params *params,
+ struct snd_pcm_hw_rule *rule)
+{
+ struct snd_usb_substream *subs = rule->private;
+ struct audioformat *fp;
+ struct snd_interval *it = hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS);
+ unsigned int rmin, rmax;
+ int changed;
+
+ hwc_debug("hw_rule_channels: (%d,%d)\n", it->min, it->max);
+ changed = 0;
+ rmin = rmax = 0;
+ list_for_each_entry(fp, &subs->fmt_list, list) {
+ if (!hw_check_valid_format(subs, params, fp))
+ continue;
+ if (changed++) {
+ if (rmin > fp->channels)
+ rmin = fp->channels;
+ if (rmax < fp->channels)
+ rmax = fp->channels;
+ } else {
+ rmin = fp->channels;
+ rmax = fp->channels;
+ }
+ }
+
+ if (!changed) {
+ hwc_debug(" --> get empty\n");
+ it->empty = 1;
+ return -EINVAL;
+ }
+
+ changed = 0;
+ if (it->min < rmin) {
+ it->min = rmin;
+ it->openmin = 0;
+ changed = 1;
+ }
+ if (it->max > rmax) {
+ it->max = rmax;
+ it->openmax = 0;
+ changed = 1;
+ }
+ if (snd_interval_checkempty(it)) {
+ it->empty = 1;
+ return -EINVAL;
+ }
+ hwc_debug(" --> (%d, %d) (changed = %d)\n", it->min, it->max, changed);
+ return changed;
+}
+
+static int hw_rule_format(struct snd_pcm_hw_params *params,
+ struct snd_pcm_hw_rule *rule)
+{
+ struct snd_usb_substream *subs = rule->private;
+ struct audioformat *fp;
+ struct snd_mask *fmt = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT);
+ u64 fbits;
+ u32 oldbits[2];
+ int changed;
+
+ hwc_debug("hw_rule_format: %x:%x\n", fmt->bits[0], fmt->bits[1]);
+ fbits = 0;
+ list_for_each_entry(fp, &subs->fmt_list, list) {
+ if (!hw_check_valid_format(subs, params, fp))
+ continue;
+ fbits |= fp->formats;
+ }
+
+ oldbits[0] = fmt->bits[0];
+ oldbits[1] = fmt->bits[1];
+ fmt->bits[0] &= (u32)fbits;
+ fmt->bits[1] &= (u32)(fbits >> 32);
+ if (!fmt->bits[0] && !fmt->bits[1]) {
+ hwc_debug(" --> get empty\n");
+ return -EINVAL;
+ }
+ changed = (oldbits[0] != fmt->bits[0] || oldbits[1] != fmt->bits[1]);
+ hwc_debug(" --> %x:%x (changed = %d)\n", fmt->bits[0], fmt->bits[1], changed);
+ return changed;
+}
+
+static int hw_rule_period_time(struct snd_pcm_hw_params *params,
+ struct snd_pcm_hw_rule *rule)
+{
+ struct snd_usb_substream *subs = rule->private;
+ struct audioformat *fp;
+ struct snd_interval *it;
+ unsigned char min_datainterval;
+ unsigned int pmin;
+ int changed;
+
+ it = hw_param_interval(params, SNDRV_PCM_HW_PARAM_PERIOD_TIME);
+ hwc_debug("hw_rule_period_time: (%u,%u)\n", it->min, it->max);
+ min_datainterval = 0xff;
+ list_for_each_entry(fp, &subs->fmt_list, list) {
+ if (!hw_check_valid_format(subs, params, fp))
+ continue;
+ min_datainterval = min(min_datainterval, fp->datainterval);
+ }
+ if (min_datainterval == 0xff) {
+ hwc_debug(" --> get empty\n");
+ it->empty = 1;
+ return -EINVAL;
+ }
+ pmin = 125 * (1 << min_datainterval);
+ changed = 0;
+ if (it->min < pmin) {
+ it->min = pmin;
+ it->openmin = 0;
+ changed = 1;
+ }
+ if (snd_interval_checkempty(it)) {
+ it->empty = 1;
+ return -EINVAL;
+ }
+ hwc_debug(" --> (%u,%u) (changed = %d)\n", it->min, it->max, changed);
+ return changed;
+}
+
+/*
+ * If the device supports unusual bit rates, does the request meet these?
+ */
+static int snd_usb_pcm_check_knot(struct snd_pcm_runtime *runtime,
+ struct snd_usb_substream *subs)
+{
+ struct audioformat *fp;
+ int *rate_list;
+ int count = 0, needs_knot = 0;
+ int err;
+
+ kfree(subs->rate_list.list);
+ subs->rate_list.list = NULL;
+
+ list_for_each_entry(fp, &subs->fmt_list, list) {
+ if (fp->rates & SNDRV_PCM_RATE_CONTINUOUS)
+ return 0;
+ count += fp->nr_rates;
+ if (fp->rates & SNDRV_PCM_RATE_KNOT)
+ needs_knot = 1;
+ }
+ if (!needs_knot)
+ return 0;
+
+ subs->rate_list.list = rate_list =
+ kmalloc(sizeof(int) * count, GFP_KERNEL);
+ if (!subs->rate_list.list)
+ return -ENOMEM;
+ subs->rate_list.count = count;
+ subs->rate_list.mask = 0;
+ count = 0;
+ list_for_each_entry(fp, &subs->fmt_list, list) {
+ int i;
+ for (i = 0; i < fp->nr_rates; i++)
+ rate_list[count++] = fp->rate_table[i];
+ }
+ err = snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
+ &subs->rate_list);
+ if (err < 0)
+ return err;
+
+ return 0;
+}
+
+
+/*
+ * set up the runtime hardware information.
+ */
+
+static int setup_hw_info(struct snd_pcm_runtime *runtime, struct snd_usb_substream *subs)
+{
+ struct audioformat *fp;
+ unsigned int pt, ptmin;
+ int param_period_time_if_needed;
+ int err;
+
+ runtime->hw.formats = subs->formats;
+
+ runtime->hw.rate_min = 0x7fffffff;
+ runtime->hw.rate_max = 0;
+ runtime->hw.channels_min = 256;
+ runtime->hw.channels_max = 0;
+ runtime->hw.rates = 0;
+ ptmin = UINT_MAX;
+ /* check min/max rates and channels */
+ list_for_each_entry(fp, &subs->fmt_list, list) {
+ runtime->hw.rates |= fp->rates;
+ if (runtime->hw.rate_min > fp->rate_min)
+ runtime->hw.rate_min = fp->rate_min;
+ if (runtime->hw.rate_max < fp->rate_max)
+ runtime->hw.rate_max = fp->rate_max;
+ if (runtime->hw.channels_min > fp->channels)
+ runtime->hw.channels_min = fp->channels;
+ if (runtime->hw.channels_max < fp->channels)
+ runtime->hw.channels_max = fp->channels;
+ if (fp->fmt_type == UAC_FORMAT_TYPE_II && fp->frame_size > 0) {
+ /* FIXME: there might be more than one audio formats... */
+ runtime->hw.period_bytes_min = runtime->hw.period_bytes_max =
+ fp->frame_size;
+ }
+ pt = 125 * (1 << fp->datainterval);
+ ptmin = min(ptmin, pt);
+ }
+ err = snd_usb_autoresume(subs->stream->chip);
+ if (err < 0)
+ return err;
+
+ param_period_time_if_needed = SNDRV_PCM_HW_PARAM_PERIOD_TIME;
+ if (subs->speed == USB_SPEED_FULL)
+ /* full speed devices have fixed data packet interval */
+ ptmin = 1000;
+ if (ptmin == 1000)
+ /* if period time doesn't go below 1 ms, no rules needed */
+ param_period_time_if_needed = -1;
+ snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_PERIOD_TIME,
+ ptmin, UINT_MAX);
+
+ 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,
+ param_period_time_if_needed,
+ -1)) < 0)
+ goto rep_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,
+ param_period_time_if_needed,
+ -1)) < 0)
+ goto rep_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,
+ param_period_time_if_needed,
+ -1)) < 0)
+ goto rep_err;
+ if (param_period_time_if_needed >= 0) {
+ err = snd_pcm_hw_rule_add(runtime, 0,
+ SNDRV_PCM_HW_PARAM_PERIOD_TIME,
+ hw_rule_period_time, subs,
+ SNDRV_PCM_HW_PARAM_FORMAT,
+ SNDRV_PCM_HW_PARAM_CHANNELS,
+ SNDRV_PCM_HW_PARAM_RATE,
+ -1);
+ if (err < 0)
+ goto rep_err;
+ }
+ if ((err = snd_usb_pcm_check_knot(runtime, subs)) < 0)
+ goto rep_err;
+ return 0;
+
+rep_err:
+ snd_usb_autosuspend(subs->stream->chip);
+ return err;
+}
+
+static int snd_usb_pcm_open(struct snd_pcm_substream *substream, int direction)
+{
+ struct snd_usb_stream *as = snd_pcm_substream_chip(substream);
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct snd_usb_substream *subs = &as->substream[direction];
+
+ subs->interface = -1;
+ subs->altset_idx = 0;
+ runtime->hw = snd_usb_hardware;
+ runtime->private_data = subs;
+ subs->pcm_substream = substream;
+ /* runtime PM is also done there */
+
+ /* initialize DSD/DOP context */
+ subs->dsd_dop.byte_idx = 0;
+ subs->dsd_dop.channel = 0;
+ subs->dsd_dop.marker = 1;
+
+ return setup_hw_info(runtime, subs);
+}
+
+static int snd_usb_pcm_close(struct snd_pcm_substream *substream, int direction)
+{
+ struct snd_usb_stream *as = snd_pcm_substream_chip(substream);
+ struct snd_usb_substream *subs = &as->substream[direction];
+
+ stop_endpoints(subs, true);
+
+ if (!as->chip->shutdown && subs->interface >= 0) {
+ usb_set_interface(subs->dev, subs->interface, 0);
+ subs->interface = -1;
+ }
+
+ subs->pcm_substream = NULL;
+ snd_usb_autosuspend(subs->stream->chip);
+
+ return 0;
+}
+
+/* Since a URB can handle only a single linear buffer, we must use double
+ * buffering when the data to be transferred overflows the buffer boundary.
+ * To avoid inconsistencies when updating hwptr_done, we use double buffering
+ * for all URBs.
+ */
+static void retire_capture_urb(struct snd_usb_substream *subs,
+ struct urb *urb)
+{
+ struct snd_pcm_runtime *runtime = subs->pcm_substream->runtime;
+ unsigned int stride, frames, bytes, oldptr;
+ int i, period_elapsed = 0;
+ unsigned long flags;
+ unsigned char *cp;
+ int current_frame_number;
+
+ /* read frame number here, update pointer in critical section */
+ current_frame_number = usb_get_current_frame_number(subs->dev);
+
+ stride = runtime->frame_bits >> 3;
+
+ for (i = 0; i < urb->number_of_packets; i++) {
+ cp = (unsigned char *)urb->transfer_buffer + urb->iso_frame_desc[i].offset + subs->pkt_offset_adj;
+ if (urb->iso_frame_desc[i].status && printk_ratelimit()) {
+ dev_dbg(&subs->dev->dev, "frame %d active: %d\n",
+ i, urb->iso_frame_desc[i].status);
+ // continue;
+ }
+ bytes = urb->iso_frame_desc[i].actual_length;
+ frames = bytes / stride;
+ if (!subs->txfr_quirk)
+ bytes = frames * stride;
+ if (bytes % (runtime->sample_bits >> 3) != 0) {
+ int oldbytes = bytes;
+ bytes = frames * stride;
+ dev_warn(&subs->dev->dev,
+ "Corrected urb data len. %d->%d\n",
+ oldbytes, bytes);
+ }
+ /* update the current pointer */
+ spin_lock_irqsave(&subs->lock, flags);
+ oldptr = subs->hwptr_done;
+ subs->hwptr_done += bytes;
+ if (subs->hwptr_done >= runtime->buffer_size * stride)
+ subs->hwptr_done -= runtime->buffer_size * stride;
+ frames = (bytes + (oldptr % stride)) / stride;
+ subs->transfer_done += frames;
+ if (subs->transfer_done >= runtime->period_size) {
+ subs->transfer_done -= runtime->period_size;
+ period_elapsed = 1;
+ }
+ /* capture delay is by construction limited to one URB,
+ * reset delays here
+ */
+ runtime->delay = subs->last_delay = 0;
+
+ /* realign last_frame_number */
+ subs->last_frame_number = current_frame_number;
+ subs->last_frame_number &= 0xFF; /* keep 8 LSBs */
+
+ spin_unlock_irqrestore(&subs->lock, flags);
+ /* copy a data chunk */
+ if (oldptr + bytes > runtime->buffer_size * stride) {
+ unsigned int bytes1 =
+ runtime->buffer_size * stride - oldptr;
+ memcpy(runtime->dma_area + oldptr, cp, bytes1);
+ memcpy(runtime->dma_area, cp + bytes1, bytes - bytes1);
+ } else {
+ memcpy(runtime->dma_area + oldptr, cp, bytes);
+ }
+ }
+
+ if (period_elapsed)
+ snd_pcm_period_elapsed(subs->pcm_substream);
+}
+
+static inline void fill_playback_urb_dsd_dop(struct snd_usb_substream *subs,
+ struct urb *urb, unsigned int bytes)
+{
+ struct snd_pcm_runtime *runtime = subs->pcm_substream->runtime;
+ unsigned int stride = runtime->frame_bits >> 3;
+ unsigned int dst_idx = 0;
+ unsigned int src_idx = subs->hwptr_done;
+ unsigned int wrap = runtime->buffer_size * stride;
+ u8 *dst = urb->transfer_buffer;
+ u8 *src = runtime->dma_area;
+ u8 marker[] = { 0x05, 0xfa };
+
+ /*
+ * The DSP DOP format defines a way to transport DSD samples over
+ * normal PCM data endpoints. It requires stuffing of marker bytes
+ * (0x05 and 0xfa, alternating per sample frame), and then expects
+ * 2 additional bytes of actual payload. The whole frame is stored
+ * LSB.
+ *
+ * Hence, for a stereo transport, the buffer layout looks like this,
+ * where L refers to left channel samples and R to right.
+ *
+ * L1 L2 0x05 R1 R2 0x05 L3 L4 0xfa R3 R4 0xfa
+ * L5 L6 0x05 R5 R6 0x05 L7 L8 0xfa R7 R8 0xfa
+ * .....
+ *
+ */
+
+ while (bytes--) {
+ if (++subs->dsd_dop.byte_idx == 3) {
+ /* frame boundary? */
+ dst[dst_idx++] = marker[subs->dsd_dop.marker];
+ src_idx += 2;
+ subs->dsd_dop.byte_idx = 0;
+
+ if (++subs->dsd_dop.channel % runtime->channels == 0) {
+ /* alternate the marker */
+ subs->dsd_dop.marker++;
+ subs->dsd_dop.marker %= ARRAY_SIZE(marker);
+ subs->dsd_dop.channel = 0;
+ }
+ } else {
+ /* stuff the DSD payload */
+ int idx = (src_idx + subs->dsd_dop.byte_idx - 1) % wrap;
+
+ if (subs->cur_audiofmt->dsd_bitrev)
+ dst[dst_idx++] = bitrev8(src[idx]);
+ else
+ dst[dst_idx++] = src[idx];
+
+ subs->hwptr_done++;
+ }
+ }
+}
+
+static void prepare_playback_urb(struct snd_usb_substream *subs,
+ struct urb *urb)
+{
+ struct snd_pcm_runtime *runtime = subs->pcm_substream->runtime;
+ struct snd_usb_endpoint *ep = subs->data_endpoint;
+ struct snd_urb_ctx *ctx = urb->context;
+ unsigned int counts, frames, bytes;
+ int i, stride, period_elapsed = 0;
+ unsigned long flags;
+
+ stride = runtime->frame_bits >> 3;
+
+ frames = 0;
+ urb->number_of_packets = 0;
+ spin_lock_irqsave(&subs->lock, flags);
+ subs->frame_limit += ep->max_urb_frames;
+ for (i = 0; i < ctx->packets; i++) {
+ if (ctx->packet_size[i])
+ counts = ctx->packet_size[i];
+ else
+ counts = snd_usb_endpoint_next_packet_size(ep);
+
+ /* set up descriptor */
+ urb->iso_frame_desc[i].offset = frames * ep->stride;
+ urb->iso_frame_desc[i].length = counts * ep->stride;
+ frames += counts;
+ urb->number_of_packets++;
+ subs->transfer_done += counts;
+ if (subs->transfer_done >= runtime->period_size) {
+ subs->transfer_done -= runtime->period_size;
+ subs->frame_limit = 0;
+ period_elapsed = 1;
+ if (subs->fmt_type == UAC_FORMAT_TYPE_II) {
+ if (subs->transfer_done > 0) {
+ /* FIXME: fill-max mode is not
+ * supported yet */
+ frames -= subs->transfer_done;
+ counts -= subs->transfer_done;
+ urb->iso_frame_desc[i].length =
+ counts * ep->stride;
+ subs->transfer_done = 0;
+ }
+ i++;
+ if (i < ctx->packets) {
+ /* add a transfer delimiter */
+ urb->iso_frame_desc[i].offset =
+ frames * ep->stride;
+ urb->iso_frame_desc[i].length = 0;
+ urb->number_of_packets++;
+ }
+ break;
+ }
+ }
+ /* finish at the period boundary or after enough frames */
+ if ((period_elapsed ||
+ subs->transfer_done >= subs->frame_limit) &&
+ !snd_usb_endpoint_implicit_feedback_sink(ep))
+ break;
+ }
+ bytes = frames * ep->stride;
+
+ if (unlikely(subs->pcm_format == SNDRV_PCM_FORMAT_DSD_U16_LE &&
+ subs->cur_audiofmt->dsd_dop)) {
+ fill_playback_urb_dsd_dop(subs, urb, bytes);
+ } else if (unlikely(subs->pcm_format == SNDRV_PCM_FORMAT_DSD_U8 &&
+ subs->cur_audiofmt->dsd_bitrev)) {
+ /* bit-reverse the bytes */
+ u8 *buf = urb->transfer_buffer;
+ for (i = 0; i < bytes; i++) {
+ int idx = (subs->hwptr_done + i)
+ % (runtime->buffer_size * stride);
+ buf[i] = bitrev8(runtime->dma_area[idx]);
+ }
+
+ subs->hwptr_done += bytes;
+ } else {
+ /* usual PCM */
+ if (subs->hwptr_done + bytes > runtime->buffer_size * stride) {
+ /* err, the transferred area goes over buffer boundary. */
+ unsigned int bytes1 =
+ runtime->buffer_size * stride - subs->hwptr_done;
+ memcpy(urb->transfer_buffer,
+ runtime->dma_area + subs->hwptr_done, bytes1);
+ memcpy(urb->transfer_buffer + bytes1,
+ runtime->dma_area, bytes - bytes1);
+ } else {
+ memcpy(urb->transfer_buffer,
+ runtime->dma_area + subs->hwptr_done, bytes);
+ }
+
+ subs->hwptr_done += bytes;
+ }
+
+ if (subs->hwptr_done >= runtime->buffer_size * stride)
+ subs->hwptr_done -= runtime->buffer_size * stride;
+
+ /* update delay with exact number of samples queued */
+ runtime->delay = subs->last_delay;
+ runtime->delay += frames;
+ subs->last_delay = runtime->delay;
+
+ /* realign last_frame_number */
+ subs->last_frame_number = usb_get_current_frame_number(subs->dev);
+ subs->last_frame_number &= 0xFF; /* keep 8 LSBs */
+
+ if (subs->trigger_tstamp_pending_update) {
+ /* this is the first actual URB submitted,
+ * update trigger timestamp to reflect actual start time
+ */
+ snd_pcm_gettime(runtime, &runtime->trigger_tstamp);
+ subs->trigger_tstamp_pending_update = false;
+ }
+
+ spin_unlock_irqrestore(&subs->lock, flags);
+ urb->transfer_buffer_length = bytes;
+ if (period_elapsed)
+ snd_pcm_period_elapsed(subs->pcm_substream);
+}
+
+/*
+ * process after playback data complete
+ * - decrease the delay count again
+ */
+static void retire_playback_urb(struct snd_usb_substream *subs,
+ struct urb *urb)
+{
+ unsigned long flags;
+ struct snd_pcm_runtime *runtime = subs->pcm_substream->runtime;
+ struct snd_usb_endpoint *ep = subs->data_endpoint;
+ int processed = urb->transfer_buffer_length / ep->stride;
+ int est_delay;
+
+ /* ignore the delay accounting when procssed=0 is given, i.e.
+ * silent payloads are procssed before handling the actual data
+ */
+ if (!processed)
+ return;
+
+ spin_lock_irqsave(&subs->lock, flags);
+ if (!subs->last_delay)
+ goto out; /* short path */
+
+ est_delay = snd_usb_pcm_delay(subs, runtime->rate);
+ /* update delay with exact number of samples played */
+ if (processed > subs->last_delay)
+ subs->last_delay = 0;
+ else
+ subs->last_delay -= processed;
+ runtime->delay = subs->last_delay;
+
+ /*
+ * Report when delay estimate is off by more than 2ms.
+ * The error should be lower than 2ms since the estimate relies
+ * on two reads of a counter updated every ms.
+ */
+ if (abs(est_delay - subs->last_delay) * 1000 > runtime->rate * 2)
+ dev_dbg_ratelimited(&subs->dev->dev,
+ "delay: estimated %d, actual %d\n",
+ est_delay, subs->last_delay);
+
+ if (!subs->running) {
+ /* update last_frame_number for delay counting here since
+ * prepare_playback_urb won't be called during pause
+ */
+ subs->last_frame_number =
+ usb_get_current_frame_number(subs->dev) & 0xff;
+ }
+
+ out:
+ spin_unlock_irqrestore(&subs->lock, flags);
+}
+
+static int snd_usb_playback_open(struct snd_pcm_substream *substream)
+{
+ return snd_usb_pcm_open(substream, SNDRV_PCM_STREAM_PLAYBACK);
+}
+
+static int snd_usb_playback_close(struct snd_pcm_substream *substream)
+{
+ return snd_usb_pcm_close(substream, SNDRV_PCM_STREAM_PLAYBACK);
+}
+
+static int snd_usb_capture_open(struct snd_pcm_substream *substream)
+{
+ return snd_usb_pcm_open(substream, SNDRV_PCM_STREAM_CAPTURE);
+}
+
+static int snd_usb_capture_close(struct snd_pcm_substream *substream)
+{
+ return snd_usb_pcm_close(substream, SNDRV_PCM_STREAM_CAPTURE);
+}
+
+static int snd_usb_substream_playback_trigger(struct snd_pcm_substream *substream,
+ int cmd)
+{
+ struct snd_usb_substream *subs = substream->runtime->private_data;
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ subs->trigger_tstamp_pending_update = true;
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ subs->data_endpoint->prepare_data_urb = prepare_playback_urb;
+ subs->data_endpoint->retire_data_urb = retire_playback_urb;
+ subs->running = 1;
+ return 0;
+ case SNDRV_PCM_TRIGGER_STOP:
+ stop_endpoints(subs, false);
+ subs->running = 0;
+ return 0;
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ subs->data_endpoint->prepare_data_urb = NULL;
+ /* keep retire_data_urb for delay calculation */
+ subs->data_endpoint->retire_data_urb = retire_playback_urb;
+ subs->running = 0;
+ return 0;
+ }
+
+ return -EINVAL;
+}
+
+static int snd_usb_substream_capture_trigger(struct snd_pcm_substream *substream,
+ int cmd)
+{
+ int err;
+ struct snd_usb_substream *subs = substream->runtime->private_data;
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ err = start_endpoints(subs, false);
+ if (err < 0)
+ return err;
+
+ subs->data_endpoint->retire_data_urb = retire_capture_urb;
+ subs->running = 1;
+ return 0;
+ case SNDRV_PCM_TRIGGER_STOP:
+ stop_endpoints(subs, false);
+ subs->running = 0;
+ return 0;
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ subs->data_endpoint->retire_data_urb = NULL;
+ subs->running = 0;
+ return 0;
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ subs->data_endpoint->retire_data_urb = retire_capture_urb;
+ subs->running = 1;
+ return 0;
+ }
+
+ return -EINVAL;
+}
+
+static struct snd_pcm_ops 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_substream_playback_trigger,
+ .pointer = snd_usb_pcm_pointer,
+ .page = snd_pcm_lib_get_vmalloc_page,
+ .mmap = snd_pcm_lib_mmap_vmalloc,
+};
+
+static struct snd_pcm_ops 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_substream_capture_trigger,
+ .pointer = snd_usb_pcm_pointer,
+ .page = snd_pcm_lib_get_vmalloc_page,
+ .mmap = snd_pcm_lib_mmap_vmalloc,
+};
+
+void snd_usb_set_pcm_ops(struct snd_pcm *pcm, int stream)
+{
+ snd_pcm_set_ops(pcm, stream,
+ stream == SNDRV_PCM_STREAM_PLAYBACK ?
+ &snd_usb_playback_ops : &snd_usb_capture_ops);
+}
diff --git a/sound/usb/pcm.h b/sound/usb/pcm.h
new file mode 100644
index 000000000..df7a00368
--- /dev/null
+++ b/sound/usb/pcm.h
@@ -0,0 +1,14 @@
+#ifndef __USBAUDIO_PCM_H
+#define __USBAUDIO_PCM_H
+
+snd_pcm_uframes_t snd_usb_pcm_delay(struct snd_usb_substream *subs,
+ unsigned int rate);
+
+void snd_usb_set_pcm_ops(struct snd_pcm *pcm, int stream);
+
+int snd_usb_init_pitch(struct snd_usb_audio *chip, int iface,
+ struct usb_host_interface *alts,
+ struct audioformat *fmt);
+
+
+#endif /* __USBAUDIO_PCM_H */
diff --git a/sound/usb/power.h b/sound/usb/power.h
new file mode 100644
index 000000000..48ee51dcb
--- /dev/null
+++ b/sound/usb/power.h
@@ -0,0 +1,17 @@
+#ifndef __USBAUDIO_POWER_H
+#define __USBAUDIO_POWER_H
+
+#ifdef CONFIG_PM
+int snd_usb_autoresume(struct snd_usb_audio *chip);
+void snd_usb_autosuspend(struct snd_usb_audio *chip);
+#else
+static inline int snd_usb_autoresume(struct snd_usb_audio *chip)
+{
+ return 0;
+}
+static inline void snd_usb_autosuspend(struct snd_usb_audio *chip)
+{
+}
+#endif
+
+#endif /* __USBAUDIO_POWER_H */
diff --git a/sound/usb/proc.c b/sound/usb/proc.c
new file mode 100644
index 000000000..5f761ab34
--- /dev/null
+++ b/sound/usb/proc.c
@@ -0,0 +1,178 @@
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+
+#include <linux/init.h>
+#include <linux/usb.h>
+
+#include <sound/core.h>
+#include <sound/info.h>
+#include <sound/pcm.h>
+
+#include "usbaudio.h"
+#include "helper.h"
+#include "card.h"
+#include "endpoint.h"
+#include "proc.h"
+
+/* convert our full speed USB rate into sampling rate in Hz */
+static inline unsigned get_full_speed_hz(unsigned int usb_rate)
+{
+ return (usb_rate * 125 + (1 << 12)) >> 13;
+}
+
+/* convert our high speed USB rate into sampling rate in Hz */
+static inline unsigned get_high_speed_hz(unsigned int usb_rate)
+{
+ return (usb_rate * 125 + (1 << 9)) >> 10;
+}
+
+/*
+ * common proc files to show the usb device info
+ */
+static void proc_audio_usbbus_read(struct snd_info_entry *entry, struct snd_info_buffer *buffer)
+{
+ struct snd_usb_audio *chip = entry->private_data;
+ if (!chip->shutdown)
+ snd_iprintf(buffer, "%03d/%03d\n", chip->dev->bus->busnum, chip->dev->devnum);
+}
+
+static void proc_audio_usbid_read(struct snd_info_entry *entry, struct snd_info_buffer *buffer)
+{
+ struct snd_usb_audio *chip = entry->private_data;
+ if (!chip->shutdown)
+ snd_iprintf(buffer, "%04x:%04x\n",
+ USB_ID_VENDOR(chip->usb_id),
+ USB_ID_PRODUCT(chip->usb_id));
+}
+
+void snd_usb_audio_create_proc(struct snd_usb_audio *chip)
+{
+ struct snd_info_entry *entry;
+ if (!snd_card_proc_new(chip->card, "usbbus", &entry))
+ snd_info_set_text_ops(entry, chip, proc_audio_usbbus_read);
+ if (!snd_card_proc_new(chip->card, "usbid", &entry))
+ snd_info_set_text_ops(entry, chip, proc_audio_usbid_read);
+}
+
+/*
+ * proc interface for list the supported pcm formats
+ */
+static void proc_dump_substream_formats(struct snd_usb_substream *subs, struct snd_info_buffer *buffer)
+{
+ struct audioformat *fp;
+ static char *sync_types[4] = {
+ "NONE", "ASYNC", "ADAPTIVE", "SYNC"
+ };
+
+ list_for_each_entry(fp, &subs->fmt_list, list) {
+ snd_pcm_format_t fmt;
+
+ snd_iprintf(buffer, " Interface %d\n", fp->iface);
+ snd_iprintf(buffer, " Altset %d\n", fp->altsetting);
+ snd_iprintf(buffer, " Format:");
+ for (fmt = 0; fmt <= SNDRV_PCM_FORMAT_LAST; ++fmt)
+ if (fp->formats & pcm_format_to_bits(fmt))
+ snd_iprintf(buffer, " %s",
+ snd_pcm_format_name(fmt));
+ snd_iprintf(buffer, "\n");
+ snd_iprintf(buffer, " Channels: %d\n", fp->channels);
+ snd_iprintf(buffer, " Endpoint: %d %s (%s)\n",
+ fp->endpoint & USB_ENDPOINT_NUMBER_MASK,
+ fp->endpoint & USB_DIR_IN ? "IN" : "OUT",
+ sync_types[(fp->ep_attr & USB_ENDPOINT_SYNCTYPE) >> 2]);
+ if (fp->rates & SNDRV_PCM_RATE_CONTINUOUS) {
+ snd_iprintf(buffer, " Rates: %d - %d (continuous)\n",
+ fp->rate_min, fp->rate_max);
+ } else {
+ unsigned int i;
+ snd_iprintf(buffer, " Rates: ");
+ for (i = 0; i < fp->nr_rates; i++) {
+ if (i > 0)
+ snd_iprintf(buffer, ", ");
+ snd_iprintf(buffer, "%d", fp->rate_table[i]);
+ }
+ snd_iprintf(buffer, "\n");
+ }
+ if (subs->speed != USB_SPEED_FULL)
+ snd_iprintf(buffer, " Data packet interval: %d us\n",
+ 125 * (1 << fp->datainterval));
+ // snd_iprintf(buffer, " Max Packet Size = %d\n", fp->maxpacksize);
+ // snd_iprintf(buffer, " EP Attribute = %#x\n", fp->attributes);
+ }
+}
+
+static void proc_dump_ep_status(struct snd_usb_substream *subs,
+ struct snd_usb_endpoint *data_ep,
+ struct snd_usb_endpoint *sync_ep,
+ struct snd_info_buffer *buffer)
+{
+ if (!data_ep)
+ return;
+ snd_iprintf(buffer, " Packet Size = %d\n", data_ep->curpacksize);
+ snd_iprintf(buffer, " Momentary freq = %u Hz (%#x.%04x)\n",
+ subs->speed == USB_SPEED_FULL
+ ? get_full_speed_hz(data_ep->freqm)
+ : get_high_speed_hz(data_ep->freqm),
+ data_ep->freqm >> 16, data_ep->freqm & 0xffff);
+ if (sync_ep && data_ep->freqshift != INT_MIN) {
+ int res = 16 - data_ep->freqshift;
+ snd_iprintf(buffer, " Feedback Format = %d.%d\n",
+ (sync_ep->syncmaxsize > 3 ? 32 : 24) - res, res);
+ }
+}
+
+static void proc_dump_substream_status(struct snd_usb_substream *subs, struct snd_info_buffer *buffer)
+{
+ if (subs->running) {
+ snd_iprintf(buffer, " Status: Running\n");
+ snd_iprintf(buffer, " Interface = %d\n", subs->interface);
+ snd_iprintf(buffer, " Altset = %d\n", subs->altset_idx);
+ proc_dump_ep_status(subs, subs->data_endpoint, subs->sync_endpoint, buffer);
+ } else {
+ snd_iprintf(buffer, " Status: Stop\n");
+ }
+}
+
+static void proc_pcm_format_read(struct snd_info_entry *entry, struct snd_info_buffer *buffer)
+{
+ struct snd_usb_stream *stream = entry->private_data;
+
+ snd_iprintf(buffer, "%s : %s\n", stream->chip->card->longname, stream->pcm->name);
+
+ if (stream->substream[SNDRV_PCM_STREAM_PLAYBACK].num_formats) {
+ snd_iprintf(buffer, "\nPlayback:\n");
+ proc_dump_substream_status(&stream->substream[SNDRV_PCM_STREAM_PLAYBACK], buffer);
+ proc_dump_substream_formats(&stream->substream[SNDRV_PCM_STREAM_PLAYBACK], buffer);
+ }
+ if (stream->substream[SNDRV_PCM_STREAM_CAPTURE].num_formats) {
+ snd_iprintf(buffer, "\nCapture:\n");
+ proc_dump_substream_status(&stream->substream[SNDRV_PCM_STREAM_CAPTURE], buffer);
+ proc_dump_substream_formats(&stream->substream[SNDRV_PCM_STREAM_CAPTURE], buffer);
+ }
+}
+
+void snd_usb_proc_pcm_format_add(struct snd_usb_stream *stream)
+{
+ struct snd_info_entry *entry;
+ char name[32];
+ struct snd_card *card = stream->chip->card;
+
+ sprintf(name, "stream%d", stream->pcm_index);
+ if (!snd_card_proc_new(card, name, &entry))
+ snd_info_set_text_ops(entry, stream, proc_pcm_format_read);
+}
+
diff --git a/sound/usb/proc.h b/sound/usb/proc.h
new file mode 100644
index 000000000..a45b765e4
--- /dev/null
+++ b/sound/usb/proc.h
@@ -0,0 +1,8 @@
+#ifndef __USBAUDIO_PROC_H
+#define __USBAUDIO_PROC_H
+
+void snd_usb_audio_create_proc(struct snd_usb_audio *chip);
+void snd_usb_proc_pcm_format_add(struct snd_usb_stream *stream);
+
+#endif /* __USBAUDIO_PROC_H */
+
diff --git a/sound/usb/quirks-table.h b/sound/usb/quirks-table.h
new file mode 100644
index 000000000..2f6d3e9a1
--- /dev/null
+++ b/sound/usb/quirks-table.h
@@ -0,0 +1,3181 @@
+/*
+ * ALSA USB Audio Driver
+ *
+ * Copyright (c) 2002 by Takashi Iwai <tiwai@suse.de>,
+ * Clemens Ladisch <clemens@ladisch.de>
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+/*
+ * The contents of this file are part of the driver's id_table.
+ *
+ * In a perfect world, this file would be empty.
+ */
+
+/*
+ * Use this for devices where other interfaces are standard compliant,
+ * to prevent the quirk being applied to those interfaces. (To work with
+ * hotplugging, bDeviceClass must be set to USB_CLASS_PER_INTERFACE.)
+ */
+#define USB_DEVICE_VENDOR_SPEC(vend, prod) \
+ .match_flags = USB_DEVICE_ID_MATCH_VENDOR | \
+ USB_DEVICE_ID_MATCH_PRODUCT | \
+ USB_DEVICE_ID_MATCH_INT_CLASS, \
+ .idVendor = vend, \
+ .idProduct = prod, \
+ .bInterfaceClass = USB_CLASS_VENDOR_SPEC
+
+/* FTDI devices */
+{
+ USB_DEVICE(0x0403, 0xb8d8),
+ .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+ /* .vendor_name = "STARR LABS", */
+ /* .product_name = "Starr Labs MIDI USB device", */
+ .ifnum = 0,
+ .type = QUIRK_MIDI_FTDI
+ }
+},
+
+{
+ /* Creative BT-D1 */
+ USB_DEVICE(0x041e, 0x0005),
+ .driver_info = (unsigned long) &(const struct snd_usb_audio_quirk) {
+ .ifnum = 1,
+ .type = QUIRK_AUDIO_FIXED_ENDPOINT,
+ .data = &(const struct audioformat) {
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ .channels = 2,
+ .iface = 1,
+ .altsetting = 1,
+ .altset_idx = 1,
+ .endpoint = 0x03,
+ .ep_attr = USB_ENDPOINT_XFER_ISOC,
+ .attributes = 0,
+ .rates = SNDRV_PCM_RATE_CONTINUOUS,
+ .rate_min = 48000,
+ .rate_max = 48000,
+ }
+ }
+},
+
+/* Creative/E-Mu devices */
+{
+ USB_DEVICE(0x041e, 0x3010),
+ .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+ .vendor_name = "Creative Labs",
+ .product_name = "Sound Blaster MP3+",
+ .ifnum = QUIRK_NO_INTERFACE
+ }
+},
+/* Creative/Toshiba Multimedia Center SB-0500 */
+{
+ USB_DEVICE(0x041e, 0x3048),
+ .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+ .vendor_name = "Toshiba",
+ .product_name = "SB-0500",
+ .ifnum = QUIRK_NO_INTERFACE
+ }
+},
+{
+ /* E-Mu 0202 USB */
+ .match_flags = USB_DEVICE_ID_MATCH_DEVICE,
+ .idVendor = 0x041e,
+ .idProduct = 0x3f02,
+ .bInterfaceClass = USB_CLASS_AUDIO,
+},
+{
+ /* E-Mu 0404 USB */
+ .match_flags = USB_DEVICE_ID_MATCH_DEVICE,
+ .idVendor = 0x041e,
+ .idProduct = 0x3f04,
+ .bInterfaceClass = USB_CLASS_AUDIO,
+},
+{
+ /* E-Mu Tracker Pre */
+ .match_flags = USB_DEVICE_ID_MATCH_DEVICE,
+ .idVendor = 0x041e,
+ .idProduct = 0x3f0a,
+ .bInterfaceClass = USB_CLASS_AUDIO,
+},
+{
+ /* E-Mu 0204 USB */
+ .match_flags = USB_DEVICE_ID_MATCH_DEVICE,
+ .idVendor = 0x041e,
+ .idProduct = 0x3f19,
+ .bInterfaceClass = USB_CLASS_AUDIO,
+},
+
+/*
+ * HP Wireless Audio
+ * When not ignored, causes instability issues for some users, forcing them to
+ * blacklist the entire module.
+ */
+{
+ USB_DEVICE(0x0424, 0xb832),
+ .driver_info = (unsigned long) &(const struct snd_usb_audio_quirk) {
+ .vendor_name = "Standard Microsystems Corp.",
+ .product_name = "HP Wireless Audio",
+ .ifnum = QUIRK_ANY_INTERFACE,
+ .type = QUIRK_COMPOSITE,
+ .data = (const struct snd_usb_audio_quirk[]) {
+ /* Mixer */
+ {
+ .ifnum = 0,
+ .type = QUIRK_IGNORE_INTERFACE,
+ },
+ /* Playback */
+ {
+ .ifnum = 1,
+ .type = QUIRK_IGNORE_INTERFACE,
+ },
+ /* Capture */
+ {
+ .ifnum = 2,
+ .type = QUIRK_IGNORE_INTERFACE,
+ },
+ /* HID Device, .ifnum = 3 */
+ {
+ .ifnum = -1,
+ }
+ }
+ }
+},
+
+/*
+ * Logitech QuickCam: bDeviceClass is vendor-specific, so generic interface
+ * class matches do not take effect without an explicit ID match.
+ */
+{
+ .match_flags = USB_DEVICE_ID_MATCH_DEVICE |
+ USB_DEVICE_ID_MATCH_INT_CLASS |
+ USB_DEVICE_ID_MATCH_INT_SUBCLASS,
+ .idVendor = 0x046d,
+ .idProduct = 0x0850,
+ .bInterfaceClass = USB_CLASS_AUDIO,
+ .bInterfaceSubClass = USB_SUBCLASS_AUDIOCONTROL
+},
+{
+ .match_flags = USB_DEVICE_ID_MATCH_DEVICE |
+ USB_DEVICE_ID_MATCH_INT_CLASS |
+ USB_DEVICE_ID_MATCH_INT_SUBCLASS,
+ .idVendor = 0x046d,
+ .idProduct = 0x08ae,
+ .bInterfaceClass = USB_CLASS_AUDIO,
+ .bInterfaceSubClass = USB_SUBCLASS_AUDIOCONTROL
+},
+{
+ .match_flags = USB_DEVICE_ID_MATCH_DEVICE |
+ USB_DEVICE_ID_MATCH_INT_CLASS |
+ USB_DEVICE_ID_MATCH_INT_SUBCLASS,
+ .idVendor = 0x046d,
+ .idProduct = 0x08c6,
+ .bInterfaceClass = USB_CLASS_AUDIO,
+ .bInterfaceSubClass = USB_SUBCLASS_AUDIOCONTROL
+},
+{
+ .match_flags = USB_DEVICE_ID_MATCH_DEVICE |
+ USB_DEVICE_ID_MATCH_INT_CLASS |
+ USB_DEVICE_ID_MATCH_INT_SUBCLASS,
+ .idVendor = 0x046d,
+ .idProduct = 0x08f0,
+ .bInterfaceClass = USB_CLASS_AUDIO,
+ .bInterfaceSubClass = USB_SUBCLASS_AUDIOCONTROL
+},
+{
+ .match_flags = USB_DEVICE_ID_MATCH_DEVICE |
+ USB_DEVICE_ID_MATCH_INT_CLASS |
+ USB_DEVICE_ID_MATCH_INT_SUBCLASS,
+ .idVendor = 0x046d,
+ .idProduct = 0x08f5,
+ .bInterfaceClass = USB_CLASS_AUDIO,
+ .bInterfaceSubClass = USB_SUBCLASS_AUDIOCONTROL
+},
+{
+ .match_flags = USB_DEVICE_ID_MATCH_DEVICE |
+ USB_DEVICE_ID_MATCH_INT_CLASS |
+ USB_DEVICE_ID_MATCH_INT_SUBCLASS,
+ .idVendor = 0x046d,
+ .idProduct = 0x08f6,
+ .bInterfaceClass = USB_CLASS_AUDIO,
+ .bInterfaceSubClass = USB_SUBCLASS_AUDIOCONTROL
+},
+{
+ .match_flags = USB_DEVICE_ID_MATCH_DEVICE |
+ USB_DEVICE_ID_MATCH_INT_CLASS |
+ USB_DEVICE_ID_MATCH_INT_SUBCLASS,
+ .idVendor = 0x046d,
+ .idProduct = 0x0990,
+ .bInterfaceClass = USB_CLASS_AUDIO,
+ .bInterfaceSubClass = USB_SUBCLASS_AUDIOCONTROL,
+ .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+ .vendor_name = "Logitech, Inc.",
+ .product_name = "QuickCam Pro 9000",
+ .ifnum = QUIRK_NO_INTERFACE
+ }
+},
+
+/*
+ * Yamaha devices
+ */
+
+#define YAMAHA_DEVICE(id, name) { \
+ USB_DEVICE(0x0499, id), \
+ .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { \
+ .vendor_name = "Yamaha", \
+ .product_name = name, \
+ .ifnum = QUIRK_ANY_INTERFACE, \
+ .type = QUIRK_MIDI_YAMAHA \
+ } \
+}
+#define YAMAHA_INTERFACE(id, intf, name) { \
+ USB_DEVICE_VENDOR_SPEC(0x0499, id), \
+ .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { \
+ .vendor_name = "Yamaha", \
+ .product_name = name, \
+ .ifnum = intf, \
+ .type = QUIRK_MIDI_YAMAHA \
+ } \
+}
+YAMAHA_DEVICE(0x1000, "UX256"),
+YAMAHA_DEVICE(0x1001, "MU1000"),
+YAMAHA_DEVICE(0x1002, "MU2000"),
+YAMAHA_DEVICE(0x1003, "MU500"),
+YAMAHA_INTERFACE(0x1004, 3, "UW500"),
+YAMAHA_DEVICE(0x1005, "MOTIF6"),
+YAMAHA_DEVICE(0x1006, "MOTIF7"),
+YAMAHA_DEVICE(0x1007, "MOTIF8"),
+YAMAHA_DEVICE(0x1008, "UX96"),
+YAMAHA_DEVICE(0x1009, "UX16"),
+YAMAHA_INTERFACE(0x100a, 3, "EOS BX"),
+YAMAHA_DEVICE(0x100c, "UC-MX"),
+YAMAHA_DEVICE(0x100d, "UC-KX"),
+YAMAHA_DEVICE(0x100e, "S08"),
+YAMAHA_DEVICE(0x100f, "CLP-150"),
+YAMAHA_DEVICE(0x1010, "CLP-170"),
+YAMAHA_DEVICE(0x1011, "P-250"),
+YAMAHA_DEVICE(0x1012, "TYROS"),
+YAMAHA_DEVICE(0x1013, "PF-500"),
+YAMAHA_DEVICE(0x1014, "S90"),
+YAMAHA_DEVICE(0x1015, "MOTIF-R"),
+YAMAHA_DEVICE(0x1016, "MDP-5"),
+YAMAHA_DEVICE(0x1017, "CVP-204"),
+YAMAHA_DEVICE(0x1018, "CVP-206"),
+YAMAHA_DEVICE(0x1019, "CVP-208"),
+YAMAHA_DEVICE(0x101a, "CVP-210"),
+YAMAHA_DEVICE(0x101b, "PSR-1100"),
+YAMAHA_DEVICE(0x101c, "PSR-2100"),
+YAMAHA_DEVICE(0x101d, "CLP-175"),
+YAMAHA_DEVICE(0x101e, "PSR-K1"),
+YAMAHA_DEVICE(0x101f, "EZ-J24"),
+YAMAHA_DEVICE(0x1020, "EZ-250i"),
+YAMAHA_DEVICE(0x1021, "MOTIF ES 6"),
+YAMAHA_DEVICE(0x1022, "MOTIF ES 7"),
+YAMAHA_DEVICE(0x1023, "MOTIF ES 8"),
+YAMAHA_DEVICE(0x1024, "CVP-301"),
+YAMAHA_DEVICE(0x1025, "CVP-303"),
+YAMAHA_DEVICE(0x1026, "CVP-305"),
+YAMAHA_DEVICE(0x1027, "CVP-307"),
+YAMAHA_DEVICE(0x1028, "CVP-309"),
+YAMAHA_DEVICE(0x1029, "CVP-309GP"),
+YAMAHA_DEVICE(0x102a, "PSR-1500"),
+YAMAHA_DEVICE(0x102b, "PSR-3000"),
+YAMAHA_DEVICE(0x102e, "ELS-01/01C"),
+YAMAHA_DEVICE(0x1030, "PSR-295/293"),
+YAMAHA_DEVICE(0x1031, "DGX-205/203"),
+YAMAHA_DEVICE(0x1032, "DGX-305"),
+YAMAHA_DEVICE(0x1033, "DGX-505"),
+YAMAHA_DEVICE(0x1034, NULL),
+YAMAHA_DEVICE(0x1035, NULL),
+YAMAHA_DEVICE(0x1036, NULL),
+YAMAHA_DEVICE(0x1037, NULL),
+YAMAHA_DEVICE(0x1038, NULL),
+YAMAHA_DEVICE(0x1039, NULL),
+YAMAHA_DEVICE(0x103a, NULL),
+YAMAHA_DEVICE(0x103b, NULL),
+YAMAHA_DEVICE(0x103c, NULL),
+YAMAHA_DEVICE(0x103d, NULL),
+YAMAHA_DEVICE(0x103e, NULL),
+YAMAHA_DEVICE(0x103f, NULL),
+YAMAHA_DEVICE(0x1040, NULL),
+YAMAHA_DEVICE(0x1041, NULL),
+YAMAHA_DEVICE(0x1042, NULL),
+YAMAHA_DEVICE(0x1043, NULL),
+YAMAHA_DEVICE(0x1044, NULL),
+YAMAHA_DEVICE(0x1045, NULL),
+YAMAHA_INTERFACE(0x104e, 0, NULL),
+YAMAHA_DEVICE(0x104f, NULL),
+YAMAHA_DEVICE(0x1050, NULL),
+YAMAHA_DEVICE(0x1051, NULL),
+YAMAHA_DEVICE(0x1052, NULL),
+YAMAHA_INTERFACE(0x1053, 0, NULL),
+YAMAHA_INTERFACE(0x1054, 0, NULL),
+YAMAHA_DEVICE(0x1055, NULL),
+YAMAHA_DEVICE(0x1056, NULL),
+YAMAHA_DEVICE(0x1057, NULL),
+YAMAHA_DEVICE(0x1058, NULL),
+YAMAHA_DEVICE(0x1059, NULL),
+YAMAHA_DEVICE(0x105a, NULL),
+YAMAHA_DEVICE(0x105b, NULL),
+YAMAHA_DEVICE(0x105c, NULL),
+YAMAHA_DEVICE(0x105d, NULL),
+{
+ USB_DEVICE(0x0499, 0x1503),
+ .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+ /* .vendor_name = "Yamaha", */
+ /* .product_name = "MOX6/MOX8", */
+ .ifnum = QUIRK_ANY_INTERFACE,
+ .type = QUIRK_COMPOSITE,
+ .data = (const struct snd_usb_audio_quirk[]) {
+ {
+ .ifnum = 1,
+ .type = QUIRK_AUDIO_STANDARD_INTERFACE
+ },
+ {
+ .ifnum = 2,
+ .type = QUIRK_AUDIO_STANDARD_INTERFACE
+ },
+ {
+ .ifnum = 3,
+ .type = QUIRK_MIDI_YAMAHA
+ },
+ {
+ .ifnum = -1
+ }
+ }
+ }
+},
+{
+ USB_DEVICE(0x0499, 0x1507),
+ .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+ /* .vendor_name = "Yamaha", */
+ /* .product_name = "THR10", */
+ .ifnum = QUIRK_ANY_INTERFACE,
+ .type = QUIRK_COMPOSITE,
+ .data = (const struct snd_usb_audio_quirk[]) {
+ {
+ .ifnum = 1,
+ .type = QUIRK_AUDIO_STANDARD_INTERFACE
+ },
+ {
+ .ifnum = 2,
+ .type = QUIRK_AUDIO_STANDARD_INTERFACE
+ },
+ {
+ .ifnum = 3,
+ .type = QUIRK_MIDI_YAMAHA
+ },
+ {
+ .ifnum = -1
+ }
+ }
+ }
+},
+{
+ USB_DEVICE(0x0499, 0x1509),
+ .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+ /* .vendor_name = "Yamaha", */
+ /* .product_name = "Steinberg UR22", */
+ .ifnum = QUIRK_ANY_INTERFACE,
+ .type = QUIRK_COMPOSITE,
+ .data = (const struct snd_usb_audio_quirk[]) {
+ {
+ .ifnum = 1,
+ .type = QUIRK_AUDIO_STANDARD_INTERFACE
+ },
+ {
+ .ifnum = 2,
+ .type = QUIRK_AUDIO_STANDARD_INTERFACE
+ },
+ {
+ .ifnum = 3,
+ .type = QUIRK_MIDI_YAMAHA
+ },
+ {
+ .ifnum = 4,
+ .type = QUIRK_IGNORE_INTERFACE
+ },
+ {
+ .ifnum = -1
+ }
+ }
+ }
+},
+{
+ USB_DEVICE(0x0499, 0x150a),
+ .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+ /* .vendor_name = "Yamaha", */
+ /* .product_name = "THR5A", */
+ .ifnum = QUIRK_ANY_INTERFACE,
+ .type = QUIRK_COMPOSITE,
+ .data = (const struct snd_usb_audio_quirk[]) {
+ {
+ .ifnum = 1,
+ .type = QUIRK_AUDIO_STANDARD_INTERFACE
+ },
+ {
+ .ifnum = 2,
+ .type = QUIRK_AUDIO_STANDARD_INTERFACE
+ },
+ {
+ .ifnum = 3,
+ .type = QUIRK_MIDI_YAMAHA
+ },
+ {
+ .ifnum = -1
+ }
+ }
+ }
+},
+{
+ USB_DEVICE(0x0499, 0x150c),
+ .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+ /* .vendor_name = "Yamaha", */
+ /* .product_name = "THR10C", */
+ .ifnum = QUIRK_ANY_INTERFACE,
+ .type = QUIRK_COMPOSITE,
+ .data = (const struct snd_usb_audio_quirk[]) {
+ {
+ .ifnum = 1,
+ .type = QUIRK_AUDIO_STANDARD_INTERFACE
+ },
+ {
+ .ifnum = 2,
+ .type = QUIRK_AUDIO_STANDARD_INTERFACE
+ },
+ {
+ .ifnum = 3,
+ .type = QUIRK_MIDI_YAMAHA
+ },
+ {
+ .ifnum = -1
+ }
+ }
+ }
+},
+YAMAHA_DEVICE(0x2000, "DGP-7"),
+YAMAHA_DEVICE(0x2001, "DGP-5"),
+YAMAHA_DEVICE(0x2002, NULL),
+YAMAHA_DEVICE(0x2003, NULL),
+YAMAHA_DEVICE(0x5000, "CS1D"),
+YAMAHA_DEVICE(0x5001, "DSP1D"),
+YAMAHA_DEVICE(0x5002, "DME32"),
+YAMAHA_DEVICE(0x5003, "DM2000"),
+YAMAHA_DEVICE(0x5004, "02R96"),
+YAMAHA_DEVICE(0x5005, "ACU16-C"),
+YAMAHA_DEVICE(0x5006, "NHB32-C"),
+YAMAHA_DEVICE(0x5007, "DM1000"),
+YAMAHA_DEVICE(0x5008, "01V96"),
+YAMAHA_DEVICE(0x5009, "SPX2000"),
+YAMAHA_DEVICE(0x500a, "PM5D"),
+YAMAHA_DEVICE(0x500b, "DME64N"),
+YAMAHA_DEVICE(0x500c, "DME24N"),
+YAMAHA_DEVICE(0x500d, NULL),
+YAMAHA_DEVICE(0x500e, NULL),
+YAMAHA_DEVICE(0x500f, NULL),
+YAMAHA_DEVICE(0x7000, "DTX"),
+YAMAHA_DEVICE(0x7010, "UB99"),
+#undef YAMAHA_DEVICE
+#undef YAMAHA_INTERFACE
+/* this catches most recent vendor-specific Yamaha devices */
+{
+ .match_flags = USB_DEVICE_ID_MATCH_VENDOR |
+ USB_DEVICE_ID_MATCH_INT_CLASS,
+ .idVendor = 0x0499,
+ .bInterfaceClass = USB_CLASS_VENDOR_SPEC,
+ .driver_info = (unsigned long) &(const struct snd_usb_audio_quirk) {
+ .ifnum = QUIRK_ANY_INTERFACE,
+ .type = QUIRK_AUTODETECT
+ }
+},
+
+/*
+ * Roland/RolandED/Edirol/BOSS devices
+ */
+{
+ USB_DEVICE(0x0582, 0x0000),
+ .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+ .vendor_name = "Roland",
+ .product_name = "UA-100",
+ .ifnum = QUIRK_ANY_INTERFACE,
+ .type = QUIRK_COMPOSITE,
+ .data = (const struct snd_usb_audio_quirk[]) {
+ {
+ .ifnum = 0,
+ .type = QUIRK_AUDIO_FIXED_ENDPOINT,
+ .data = & (const struct audioformat) {
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ .channels = 4,
+ .iface = 0,
+ .altsetting = 1,
+ .altset_idx = 1,
+ .attributes = 0,
+ .endpoint = 0x01,
+ .ep_attr = 0x09,
+ .rates = SNDRV_PCM_RATE_CONTINUOUS,
+ .rate_min = 44100,
+ .rate_max = 44100,
+ }
+ },
+ {
+ .ifnum = 1,
+ .type = QUIRK_AUDIO_FIXED_ENDPOINT,
+ .data = & (const struct audioformat) {
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ .channels = 2,
+ .iface = 1,
+ .altsetting = 1,
+ .altset_idx = 1,
+ .attributes = UAC_EP_CS_ATTR_FILL_MAX,
+ .endpoint = 0x81,
+ .ep_attr = 0x05,
+ .rates = SNDRV_PCM_RATE_CONTINUOUS,
+ .rate_min = 44100,
+ .rate_max = 44100,
+ }
+ },
+ {
+ .ifnum = 2,
+ .type = QUIRK_MIDI_FIXED_ENDPOINT,
+ .data = & (const struct snd_usb_midi_endpoint_info) {
+ .out_cables = 0x0007,
+ .in_cables = 0x0007
+ }
+ },
+ {
+ .ifnum = -1
+ }
+ }
+ }
+},
+{
+ USB_DEVICE(0x0582, 0x0002),
+ .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+ .vendor_name = "EDIROL",
+ .product_name = "UM-4",
+ .ifnum = QUIRK_ANY_INTERFACE,
+ .type = QUIRK_COMPOSITE,
+ .data = (const struct snd_usb_audio_quirk[]) {
+ {
+ .ifnum = 0,
+ .type = QUIRK_IGNORE_INTERFACE
+ },
+ {
+ .ifnum = 1,
+ .type = QUIRK_IGNORE_INTERFACE
+ },
+ {
+ .ifnum = 2,
+ .type = QUIRK_MIDI_FIXED_ENDPOINT,
+ .data = & (const struct snd_usb_midi_endpoint_info) {
+ .out_cables = 0x000f,
+ .in_cables = 0x000f
+ }
+ },
+ {
+ .ifnum = -1
+ }
+ }
+ }
+},
+{
+ USB_DEVICE(0x0582, 0x0003),
+ .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+ .vendor_name = "Roland",
+ .product_name = "SC-8850",
+ .ifnum = QUIRK_ANY_INTERFACE,
+ .type = QUIRK_COMPOSITE,
+ .data = (const struct snd_usb_audio_quirk[]) {
+ {
+ .ifnum = 0,
+ .type = QUIRK_IGNORE_INTERFACE
+ },
+ {
+ .ifnum = 1,
+ .type = QUIRK_IGNORE_INTERFACE
+ },
+ {
+ .ifnum = 2,
+ .type = QUIRK_MIDI_FIXED_ENDPOINT,
+ .data = & (const struct snd_usb_midi_endpoint_info) {
+ .out_cables = 0x003f,
+ .in_cables = 0x003f
+ }
+ },
+ {
+ .ifnum = -1
+ }
+ }
+ }
+},
+{
+ USB_DEVICE(0x0582, 0x0004),
+ .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+ .vendor_name = "Roland",
+ .product_name = "U-8",
+ .ifnum = QUIRK_ANY_INTERFACE,
+ .type = QUIRK_COMPOSITE,
+ .data = (const struct snd_usb_audio_quirk[]) {
+ {
+ .ifnum = 0,
+ .type = QUIRK_IGNORE_INTERFACE
+ },
+ {
+ .ifnum = 1,
+ .type = QUIRK_IGNORE_INTERFACE
+ },
+ {
+ .ifnum = 2,
+ .type = QUIRK_MIDI_FIXED_ENDPOINT,
+ .data = & (const struct snd_usb_midi_endpoint_info) {
+ .out_cables = 0x0005,
+ .in_cables = 0x0005
+ }
+ },
+ {
+ .ifnum = -1
+ }
+ }
+ }
+},
+{
+ /* Has ID 0x0099 when not in "Advanced Driver" mode.
+ * The UM-2EX has only one input, but we cannot detect this. */
+ USB_DEVICE(0x0582, 0x0005),
+ .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+ .vendor_name = "EDIROL",
+ .product_name = "UM-2",
+ .ifnum = QUIRK_ANY_INTERFACE,
+ .type = QUIRK_COMPOSITE,
+ .data = (const struct snd_usb_audio_quirk[]) {
+ {
+ .ifnum = 0,
+ .type = QUIRK_IGNORE_INTERFACE
+ },
+ {
+ .ifnum = 1,
+ .type = QUIRK_IGNORE_INTERFACE
+ },
+ {
+ .ifnum = 2,
+ .type = QUIRK_MIDI_FIXED_ENDPOINT,
+ .data = & (const struct snd_usb_midi_endpoint_info) {
+ .out_cables = 0x0003,
+ .in_cables = 0x0003
+ }
+ },
+ {
+ .ifnum = -1
+ }
+ }
+ }
+},
+{
+ USB_DEVICE(0x0582, 0x0007),
+ .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+ .vendor_name = "Roland",
+ .product_name = "SC-8820",
+ .ifnum = QUIRK_ANY_INTERFACE,
+ .type = QUIRK_COMPOSITE,
+ .data = (const struct snd_usb_audio_quirk[]) {
+ {
+ .ifnum = 0,
+ .type = QUIRK_IGNORE_INTERFACE
+ },
+ {
+ .ifnum = 1,
+ .type = QUIRK_IGNORE_INTERFACE
+ },
+ {
+ .ifnum = 2,
+ .type = QUIRK_MIDI_FIXED_ENDPOINT,
+ .data = & (const struct snd_usb_midi_endpoint_info) {
+ .out_cables = 0x0013,
+ .in_cables = 0x0013
+ }
+ },
+ {
+ .ifnum = -1
+ }
+ }
+ }
+},
+{
+ USB_DEVICE(0x0582, 0x0008),
+ .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+ .vendor_name = "Roland",
+ .product_name = "PC-300",
+ .ifnum = QUIRK_ANY_INTERFACE,
+ .type = QUIRK_COMPOSITE,
+ .data = (const struct snd_usb_audio_quirk[]) {
+ {
+ .ifnum = 0,
+ .type = QUIRK_IGNORE_INTERFACE
+ },
+ {
+ .ifnum = 1,
+ .type = QUIRK_IGNORE_INTERFACE
+ },
+ {
+ .ifnum = 2,
+ .type = QUIRK_MIDI_FIXED_ENDPOINT,
+ .data = & (const struct snd_usb_midi_endpoint_info) {
+ .out_cables = 0x0001,
+ .in_cables = 0x0001
+ }
+ },
+ {
+ .ifnum = -1
+ }
+ }
+ }
+},
+{
+ /* has ID 0x009d when not in "Advanced Driver" mode */
+ USB_DEVICE(0x0582, 0x0009),
+ .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+ .vendor_name = "EDIROL",
+ .product_name = "UM-1",
+ .ifnum = QUIRK_ANY_INTERFACE,
+ .type = QUIRK_COMPOSITE,
+ .data = (const struct snd_usb_audio_quirk[]) {
+ {
+ .ifnum = 0,
+ .type = QUIRK_IGNORE_INTERFACE
+ },
+ {
+ .ifnum = 1,
+ .type = QUIRK_IGNORE_INTERFACE
+ },
+ {
+ .ifnum = 2,
+ .type = QUIRK_MIDI_FIXED_ENDPOINT,
+ .data = & (const struct snd_usb_midi_endpoint_info) {
+ .out_cables = 0x0001,
+ .in_cables = 0x0001
+ }
+ },
+ {
+ .ifnum = -1
+ }
+ }
+ }
+},
+{
+ USB_DEVICE(0x0582, 0x000b),
+ .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+ .vendor_name = "Roland",
+ .product_name = "SK-500",
+ .ifnum = QUIRK_ANY_INTERFACE,
+ .type = QUIRK_COMPOSITE,
+ .data = (const struct snd_usb_audio_quirk[]) {
+ {
+ .ifnum = 0,
+ .type = QUIRK_IGNORE_INTERFACE
+ },
+ {
+ .ifnum = 1,
+ .type = QUIRK_IGNORE_INTERFACE
+ },
+ {
+ .ifnum = 2,
+ .type = QUIRK_MIDI_FIXED_ENDPOINT,
+ .data = & (const struct snd_usb_midi_endpoint_info) {
+ .out_cables = 0x0013,
+ .in_cables = 0x0013
+ }
+ },
+ {
+ .ifnum = -1
+ }
+ }
+ }
+},
+{
+ /* thanks to Emiliano Grilli <emillo@libero.it>
+ * for helping researching this data */
+ USB_DEVICE(0x0582, 0x000c),
+ .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+ .vendor_name = "Roland",
+ .product_name = "SC-D70",
+ .ifnum = QUIRK_ANY_INTERFACE,
+ .type = QUIRK_COMPOSITE,
+ .data = (const struct snd_usb_audio_quirk[]) {
+ {
+ .ifnum = 0,
+ .type = QUIRK_AUDIO_STANDARD_INTERFACE
+ },
+ {
+ .ifnum = 1,
+ .type = QUIRK_AUDIO_STANDARD_INTERFACE
+ },
+ {
+ .ifnum = 2,
+ .type = QUIRK_MIDI_FIXED_ENDPOINT,
+ .data = & (const struct snd_usb_midi_endpoint_info) {
+ .out_cables = 0x0007,
+ .in_cables = 0x0007
+ }
+ },
+ {
+ .ifnum = -1
+ }
+ }
+ }
+},
+{ /*
+ * This quirk is for the "Advanced Driver" mode of the Edirol UA-5.
+ * If the advanced mode switch at the back of the unit is off, the
+ * UA-5 has ID 0x0582/0x0011 and is standard compliant (no quirks),
+ * but offers only 16-bit PCM.
+ * In advanced mode, the UA-5 will output S24_3LE samples (two
+ * channels) at the rate indicated on the front switch, including
+ * the 96kHz sample rate.
+ */
+ USB_DEVICE(0x0582, 0x0010),
+ .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+ .vendor_name = "EDIROL",
+ .product_name = "UA-5",
+ .ifnum = QUIRK_ANY_INTERFACE,
+ .type = QUIRK_COMPOSITE,
+ .data = (const struct snd_usb_audio_quirk[]) {
+ {
+ .ifnum = 1,
+ .type = QUIRK_AUDIO_STANDARD_INTERFACE
+ },
+ {
+ .ifnum = 2,
+ .type = QUIRK_AUDIO_STANDARD_INTERFACE
+ },
+ {
+ .ifnum = -1
+ }
+ }
+ }
+},
+{
+ /* has ID 0x0013 when not in "Advanced Driver" mode */
+ USB_DEVICE(0x0582, 0x0012),
+ .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+ .vendor_name = "Roland",
+ .product_name = "XV-5050",
+ .ifnum = 0,
+ .type = QUIRK_MIDI_FIXED_ENDPOINT,
+ .data = & (const struct snd_usb_midi_endpoint_info) {
+ .out_cables = 0x0001,
+ .in_cables = 0x0001
+ }
+ }
+},
+{
+ /* has ID 0x0015 when not in "Advanced Driver" mode */
+ USB_DEVICE(0x0582, 0x0014),
+ .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+ .vendor_name = "EDIROL",
+ .product_name = "UM-880",
+ .ifnum = 0,
+ .type = QUIRK_MIDI_FIXED_ENDPOINT,
+ .data = & (const struct snd_usb_midi_endpoint_info) {
+ .out_cables = 0x01ff,
+ .in_cables = 0x01ff
+ }
+ }
+},
+{
+ /* has ID 0x0017 when not in "Advanced Driver" mode */
+ USB_DEVICE(0x0582, 0x0016),
+ .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+ .vendor_name = "EDIROL",
+ .product_name = "SD-90",
+ .ifnum = QUIRK_ANY_INTERFACE,
+ .type = QUIRK_COMPOSITE,
+ .data = (const struct snd_usb_audio_quirk[]) {
+ {
+ .ifnum = 0,
+ .type = QUIRK_AUDIO_STANDARD_INTERFACE
+ },
+ {
+ .ifnum = 1,
+ .type = QUIRK_AUDIO_STANDARD_INTERFACE
+ },
+ {
+ .ifnum = 2,
+ .type = QUIRK_MIDI_FIXED_ENDPOINT,
+ .data = & (const struct snd_usb_midi_endpoint_info) {
+ .out_cables = 0x000f,
+ .in_cables = 0x000f
+ }
+ },
+ {
+ .ifnum = -1
+ }
+ }
+ }
+},
+{
+ /* has ID 0x001c when not in "Advanced Driver" mode */
+ USB_DEVICE(0x0582, 0x001b),
+ .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+ .vendor_name = "Roland",
+ .product_name = "MMP-2",
+ .ifnum = QUIRK_ANY_INTERFACE,
+ .type = QUIRK_COMPOSITE,
+ .data = (const struct snd_usb_audio_quirk[]) {
+ {
+ .ifnum = 0,
+ .type = QUIRK_IGNORE_INTERFACE
+ },
+ {
+ .ifnum = 1,
+ .type = QUIRK_IGNORE_INTERFACE
+ },
+ {
+ .ifnum = 2,
+ .type = QUIRK_MIDI_FIXED_ENDPOINT,
+ .data = & (const struct snd_usb_midi_endpoint_info) {
+ .out_cables = 0x0001,
+ .in_cables = 0x0001
+ }
+ },
+ {
+ .ifnum = -1
+ }
+ }
+ }
+},
+{
+ /* has ID 0x001e when not in "Advanced Driver" mode */
+ USB_DEVICE(0x0582, 0x001d),
+ .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+ .vendor_name = "Roland",
+ .product_name = "V-SYNTH",
+ .ifnum = 0,
+ .type = QUIRK_MIDI_FIXED_ENDPOINT,
+ .data = & (const struct snd_usb_midi_endpoint_info) {
+ .out_cables = 0x0001,
+ .in_cables = 0x0001
+ }
+ }
+},
+{
+ /* has ID 0x0024 when not in "Advanced Driver" mode */
+ USB_DEVICE(0x0582, 0x0023),
+ .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+ .vendor_name = "EDIROL",
+ .product_name = "UM-550",
+ .ifnum = 0,
+ .type = QUIRK_MIDI_FIXED_ENDPOINT,
+ .data = & (const struct snd_usb_midi_endpoint_info) {
+ .out_cables = 0x003f,
+ .in_cables = 0x003f
+ }
+ }
+},
+{
+ /*
+ * This quirk is for the "Advanced Driver" mode. If off, the UA-20
+ * has ID 0x0026 and is standard compliant, but has only 16-bit PCM
+ * and no MIDI.
+ */
+ USB_DEVICE(0x0582, 0x0025),
+ .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+ .vendor_name = "EDIROL",
+ .product_name = "UA-20",
+ .ifnum = QUIRK_ANY_INTERFACE,
+ .type = QUIRK_COMPOSITE,
+ .data = (const struct snd_usb_audio_quirk[]) {
+ {
+ .ifnum = 0,
+ .type = QUIRK_IGNORE_INTERFACE
+ },
+ {
+ .ifnum = 1,
+ .type = QUIRK_AUDIO_FIXED_ENDPOINT,
+ .data = & (const struct audioformat) {
+ .formats = SNDRV_PCM_FMTBIT_S24_3LE,
+ .channels = 2,
+ .iface = 1,
+ .altsetting = 1,
+ .altset_idx = 1,
+ .attributes = 0,
+ .endpoint = 0x01,
+ .ep_attr = 0x01,
+ .rates = SNDRV_PCM_RATE_CONTINUOUS,
+ .rate_min = 44100,
+ .rate_max = 44100,
+ }
+ },
+ {
+ .ifnum = 2,
+ .type = QUIRK_AUDIO_FIXED_ENDPOINT,
+ .data = & (const struct audioformat) {
+ .formats = SNDRV_PCM_FMTBIT_S24_3LE,
+ .channels = 2,
+ .iface = 2,
+ .altsetting = 1,
+ .altset_idx = 1,
+ .attributes = 0,
+ .endpoint = 0x82,
+ .ep_attr = 0x01,
+ .rates = SNDRV_PCM_RATE_CONTINUOUS,
+ .rate_min = 44100,
+ .rate_max = 44100,
+ }
+ },
+ {
+ .ifnum = 3,
+ .type = QUIRK_MIDI_FIXED_ENDPOINT,
+ .data = & (const struct snd_usb_midi_endpoint_info) {
+ .out_cables = 0x0001,
+ .in_cables = 0x0001
+ }
+ },
+ {
+ .ifnum = -1
+ }
+ }
+ }
+},
+{
+ /* has ID 0x0028 when not in "Advanced Driver" mode */
+ USB_DEVICE(0x0582, 0x0027),
+ .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+ .vendor_name = "EDIROL",
+ .product_name = "SD-20",
+ .ifnum = 0,
+ .type = QUIRK_MIDI_FIXED_ENDPOINT,
+ .data = & (const struct snd_usb_midi_endpoint_info) {
+ .out_cables = 0x0003,
+ .in_cables = 0x0007
+ }
+ }
+},
+{
+ /* has ID 0x002a when not in "Advanced Driver" mode */
+ USB_DEVICE(0x0582, 0x0029),
+ .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+ .vendor_name = "EDIROL",
+ .product_name = "SD-80",
+ .ifnum = 0,
+ .type = QUIRK_MIDI_FIXED_ENDPOINT,
+ .data = & (const struct snd_usb_midi_endpoint_info) {
+ .out_cables = 0x000f,
+ .in_cables = 0x000f
+ }
+ }
+},
+{ /*
+ * This quirk is for the "Advanced" modes of the Edirol UA-700.
+ * If the sample format switch is not in an advanced setting, the
+ * UA-700 has ID 0x0582/0x002c and is standard compliant (no quirks),
+ * but offers only 16-bit PCM and no MIDI.
+ */
+ USB_DEVICE_VENDOR_SPEC(0x0582, 0x002b),
+ .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+ .vendor_name = "EDIROL",
+ .product_name = "UA-700",
+ .ifnum = QUIRK_ANY_INTERFACE,
+ .type = QUIRK_COMPOSITE,
+ .data = (const struct snd_usb_audio_quirk[]) {
+ {
+ .ifnum = 1,
+ .type = QUIRK_AUDIO_EDIROL_UAXX
+ },
+ {
+ .ifnum = 2,
+ .type = QUIRK_AUDIO_EDIROL_UAXX
+ },
+ {
+ .ifnum = 3,
+ .type = QUIRK_AUDIO_EDIROL_UAXX
+ },
+ {
+ .ifnum = -1
+ }
+ }
+ }
+},
+{
+ /* has ID 0x002e when not in "Advanced Driver" mode */
+ USB_DEVICE(0x0582, 0x002d),
+ .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+ .vendor_name = "Roland",
+ .product_name = "XV-2020",
+ .ifnum = 0,
+ .type = QUIRK_MIDI_FIXED_ENDPOINT,
+ .data = & (const struct snd_usb_midi_endpoint_info) {
+ .out_cables = 0x0001,
+ .in_cables = 0x0001
+ }
+ }
+},
+{
+ /* has ID 0x0030 when not in "Advanced Driver" mode */
+ USB_DEVICE(0x0582, 0x002f),
+ .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+ .vendor_name = "Roland",
+ .product_name = "VariOS",
+ .ifnum = 0,
+ .type = QUIRK_MIDI_FIXED_ENDPOINT,
+ .data = & (const struct snd_usb_midi_endpoint_info) {
+ .out_cables = 0x0007,
+ .in_cables = 0x0007
+ }
+ }
+},
+{
+ /* has ID 0x0034 when not in "Advanced Driver" mode */
+ USB_DEVICE(0x0582, 0x0033),
+ .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+ .vendor_name = "EDIROL",
+ .product_name = "PCR",
+ .ifnum = 0,
+ .type = QUIRK_MIDI_FIXED_ENDPOINT,
+ .data = & (const struct snd_usb_midi_endpoint_info) {
+ .out_cables = 0x0003,
+ .in_cables = 0x0007
+ }
+ }
+},
+{
+ /*
+ * Has ID 0x0038 when not in "Advanced Driver" mode;
+ * later revisions use IDs 0x0054 and 0x00a2.
+ */
+ USB_DEVICE(0x0582, 0x0037),
+ .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+ .vendor_name = "Roland",
+ .product_name = "Digital Piano",
+ .ifnum = 0,
+ .type = QUIRK_MIDI_FIXED_ENDPOINT,
+ .data = & (const struct snd_usb_midi_endpoint_info) {
+ .out_cables = 0x0001,
+ .in_cables = 0x0001
+ }
+ }
+},
+{
+ /*
+ * This quirk is for the "Advanced Driver" mode. If off, the GS-10
+ * has ID 0x003c and is standard compliant, but has only 16-bit PCM
+ * and no MIDI.
+ */
+ USB_DEVICE_VENDOR_SPEC(0x0582, 0x003b),
+ .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+ .vendor_name = "BOSS",
+ .product_name = "GS-10",
+ .ifnum = QUIRK_ANY_INTERFACE,
+ .type = QUIRK_COMPOSITE,
+ .data = & (const struct snd_usb_audio_quirk[]) {
+ {
+ .ifnum = 1,
+ .type = QUIRK_AUDIO_STANDARD_INTERFACE
+ },
+ {
+ .ifnum = 2,
+ .type = QUIRK_AUDIO_STANDARD_INTERFACE
+ },
+ {
+ .ifnum = 3,
+ .type = QUIRK_MIDI_STANDARD_INTERFACE
+ },
+ {
+ .ifnum = -1
+ }
+ }
+ }
+},
+{
+ /* has ID 0x0041 when not in "Advanced Driver" mode */
+ USB_DEVICE(0x0582, 0x0040),
+ .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+ .vendor_name = "Roland",
+ .product_name = "GI-20",
+ .ifnum = 0,
+ .type = QUIRK_MIDI_FIXED_ENDPOINT,
+ .data = & (const struct snd_usb_midi_endpoint_info) {
+ .out_cables = 0x0001,
+ .in_cables = 0x0001
+ }
+ }
+},
+{
+ /* has ID 0x0043 when not in "Advanced Driver" mode */
+ USB_DEVICE(0x0582, 0x0042),
+ .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+ .vendor_name = "Roland",
+ .product_name = "RS-70",
+ .ifnum = 0,
+ .type = QUIRK_MIDI_FIXED_ENDPOINT,
+ .data = & (const struct snd_usb_midi_endpoint_info) {
+ .out_cables = 0x0001,
+ .in_cables = 0x0001
+ }
+ }
+},
+{
+ /* has ID 0x0049 when not in "Advanced Driver" mode */
+ USB_DEVICE(0x0582, 0x0047),
+ .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+ /* .vendor_name = "EDIROL", */
+ /* .product_name = "UR-80", */
+ .ifnum = QUIRK_ANY_INTERFACE,
+ .type = QUIRK_COMPOSITE,
+ .data = (const struct snd_usb_audio_quirk[]) {
+ /* in the 96 kHz modes, only interface 1 is there */
+ {
+ .ifnum = 1,
+ .type = QUIRK_AUDIO_STANDARD_INTERFACE
+ },
+ {
+ .ifnum = 2,
+ .type = QUIRK_AUDIO_STANDARD_INTERFACE
+ },
+ {
+ .ifnum = -1
+ }
+ }
+ }
+},
+{
+ /* has ID 0x004a when not in "Advanced Driver" mode */
+ USB_DEVICE(0x0582, 0x0048),
+ .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+ /* .vendor_name = "EDIROL", */
+ /* .product_name = "UR-80", */
+ .ifnum = 0,
+ .type = QUIRK_MIDI_FIXED_ENDPOINT,
+ .data = & (const struct snd_usb_midi_endpoint_info) {
+ .out_cables = 0x0003,
+ .in_cables = 0x0007
+ }
+ }
+},
+{
+ /* has ID 0x004e when not in "Advanced Driver" mode */
+ USB_DEVICE(0x0582, 0x004c),
+ .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+ .vendor_name = "EDIROL",
+ .product_name = "PCR-A",
+ .ifnum = QUIRK_ANY_INTERFACE,
+ .type = QUIRK_COMPOSITE,
+ .data = (const struct snd_usb_audio_quirk[]) {
+ {
+ .ifnum = 1,
+ .type = QUIRK_AUDIO_STANDARD_INTERFACE
+ },
+ {
+ .ifnum = 2,
+ .type = QUIRK_AUDIO_STANDARD_INTERFACE
+ },
+ {
+ .ifnum = -1
+ }
+ }
+ }
+},
+{
+ /* has ID 0x004f when not in "Advanced Driver" mode */
+ USB_DEVICE(0x0582, 0x004d),
+ .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+ .vendor_name = "EDIROL",
+ .product_name = "PCR-A",
+ .ifnum = 0,
+ .type = QUIRK_MIDI_FIXED_ENDPOINT,
+ .data = & (const struct snd_usb_midi_endpoint_info) {
+ .out_cables = 0x0003,
+ .in_cables = 0x0007
+ }
+ }
+},
+{
+ /*
+ * This quirk is for the "Advanced Driver" mode. If off, the UA-3FX
+ * is standard compliant, but has only 16-bit PCM.
+ */
+ USB_DEVICE(0x0582, 0x0050),
+ .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+ .vendor_name = "EDIROL",
+ .product_name = "UA-3FX",
+ .ifnum = QUIRK_ANY_INTERFACE,
+ .type = QUIRK_COMPOSITE,
+ .data = (const struct snd_usb_audio_quirk[]) {
+ {
+ .ifnum = 1,
+ .type = QUIRK_AUDIO_STANDARD_INTERFACE
+ },
+ {
+ .ifnum = 2,
+ .type = QUIRK_AUDIO_STANDARD_INTERFACE
+ },
+ {
+ .ifnum = -1
+ }
+ }
+ }
+},
+{
+ USB_DEVICE(0x0582, 0x0052),
+ .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+ .vendor_name = "EDIROL",
+ .product_name = "UM-1SX",
+ .ifnum = 0,
+ .type = QUIRK_MIDI_STANDARD_INTERFACE
+ }
+},
+{
+ USB_DEVICE(0x0582, 0x0060),
+ .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+ .vendor_name = "Roland",
+ .product_name = "EXR Series",
+ .ifnum = 0,
+ .type = QUIRK_MIDI_STANDARD_INTERFACE
+ }
+},
+{
+ /* has ID 0x0066 when not in "Advanced Driver" mode */
+ USB_DEVICE(0x0582, 0x0064),
+ .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+ /* .vendor_name = "EDIROL", */
+ /* .product_name = "PCR-1", */
+ .ifnum = QUIRK_ANY_INTERFACE,
+ .type = QUIRK_COMPOSITE,
+ .data = (const struct snd_usb_audio_quirk[]) {
+ {
+ .ifnum = 1,
+ .type = QUIRK_AUDIO_STANDARD_INTERFACE
+ },
+ {
+ .ifnum = 2,
+ .type = QUIRK_AUDIO_STANDARD_INTERFACE
+ },
+ {
+ .ifnum = -1
+ }
+ }
+ }
+},
+{
+ /* has ID 0x0067 when not in "Advanced Driver" mode */
+ USB_DEVICE(0x0582, 0x0065),
+ .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+ /* .vendor_name = "EDIROL", */
+ /* .product_name = "PCR-1", */
+ .ifnum = 0,
+ .type = QUIRK_MIDI_FIXED_ENDPOINT,
+ .data = & (const struct snd_usb_midi_endpoint_info) {
+ .out_cables = 0x0001,
+ .in_cables = 0x0003
+ }
+ }
+},
+{
+ /* has ID 0x006e when not in "Advanced Driver" mode */
+ USB_DEVICE(0x0582, 0x006d),
+ .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+ .vendor_name = "Roland",
+ .product_name = "FANTOM-X",
+ .ifnum = 0,
+ .type = QUIRK_MIDI_FIXED_ENDPOINT,
+ .data = & (const struct snd_usb_midi_endpoint_info) {
+ .out_cables = 0x0001,
+ .in_cables = 0x0001
+ }
+ }
+},
+{ /*
+ * This quirk is for the "Advanced" modes of the Edirol UA-25.
+ * If the switch is not in an advanced setting, the UA-25 has
+ * ID 0x0582/0x0073 and is standard compliant (no quirks), but
+ * offers only 16-bit PCM at 44.1 kHz and no MIDI.
+ */
+ USB_DEVICE_VENDOR_SPEC(0x0582, 0x0074),
+ .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+ .vendor_name = "EDIROL",
+ .product_name = "UA-25",
+ .ifnum = QUIRK_ANY_INTERFACE,
+ .type = QUIRK_COMPOSITE,
+ .data = (const struct snd_usb_audio_quirk[]) {
+ {
+ .ifnum = 0,
+ .type = QUIRK_AUDIO_EDIROL_UAXX
+ },
+ {
+ .ifnum = 1,
+ .type = QUIRK_AUDIO_EDIROL_UAXX
+ },
+ {
+ .ifnum = 2,
+ .type = QUIRK_AUDIO_EDIROL_UAXX
+ },
+ {
+ .ifnum = -1
+ }
+ }
+ }
+},
+{
+ /* has ID 0x0076 when not in "Advanced Driver" mode */
+ USB_DEVICE(0x0582, 0x0075),
+ .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+ .vendor_name = "BOSS",
+ .product_name = "DR-880",
+ .ifnum = 0,
+ .type = QUIRK_MIDI_FIXED_ENDPOINT,
+ .data = & (const struct snd_usb_midi_endpoint_info) {
+ .out_cables = 0x0001,
+ .in_cables = 0x0001
+ }
+ }
+},
+{
+ /* has ID 0x007b when not in "Advanced Driver" mode */
+ USB_DEVICE_VENDOR_SPEC(0x0582, 0x007a),
+ .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+ .vendor_name = "Roland",
+ /* "RD" or "RD-700SX"? */
+ .ifnum = 0,
+ .type = QUIRK_MIDI_FIXED_ENDPOINT,
+ .data = & (const struct snd_usb_midi_endpoint_info) {
+ .out_cables = 0x0003,
+ .in_cables = 0x0003
+ }
+ }
+},
+{
+ /* has ID 0x0081 when not in "Advanced Driver" mode */
+ USB_DEVICE(0x0582, 0x0080),
+ .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+ .vendor_name = "Roland",
+ .product_name = "G-70",
+ .ifnum = 0,
+ .type = QUIRK_MIDI_FIXED_ENDPOINT,
+ .data = & (const struct snd_usb_midi_endpoint_info) {
+ .out_cables = 0x0001,
+ .in_cables = 0x0001
+ }
+ }
+},
+{
+ /* has ID 0x008c when not in "Advanced Driver" mode */
+ USB_DEVICE(0x0582, 0x008b),
+ .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+ .vendor_name = "EDIROL",
+ .product_name = "PC-50",
+ .ifnum = 0,
+ .type = QUIRK_MIDI_FIXED_ENDPOINT,
+ .data = & (const struct snd_usb_midi_endpoint_info) {
+ .out_cables = 0x0001,
+ .in_cables = 0x0001
+ }
+ }
+},
+{
+ /*
+ * This quirk is for the "Advanced Driver" mode. If off, the UA-4FX
+ * is standard compliant, but has only 16-bit PCM and no MIDI.
+ */
+ USB_DEVICE(0x0582, 0x00a3),
+ .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+ .vendor_name = "EDIROL",
+ .product_name = "UA-4FX",
+ .ifnum = QUIRK_ANY_INTERFACE,
+ .type = QUIRK_COMPOSITE,
+ .data = (const struct snd_usb_audio_quirk[]) {
+ {
+ .ifnum = 0,
+ .type = QUIRK_AUDIO_EDIROL_UAXX
+ },
+ {
+ .ifnum = 1,
+ .type = QUIRK_AUDIO_EDIROL_UAXX
+ },
+ {
+ .ifnum = 2,
+ .type = QUIRK_AUDIO_EDIROL_UAXX
+ },
+ {
+ .ifnum = -1
+ }
+ }
+ }
+},
+{
+ /* Edirol M-16DX */
+ USB_DEVICE(0x0582, 0x00c4),
+ .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+ .ifnum = QUIRK_ANY_INTERFACE,
+ .type = QUIRK_COMPOSITE,
+ .data = (const struct snd_usb_audio_quirk[]) {
+ {
+ .ifnum = 0,
+ .type = QUIRK_AUDIO_STANDARD_INTERFACE
+ },
+ {
+ .ifnum = 1,
+ .type = QUIRK_AUDIO_STANDARD_INTERFACE
+ },
+ {
+ .ifnum = 2,
+ .type = QUIRK_MIDI_FIXED_ENDPOINT,
+ .data = & (const struct snd_usb_midi_endpoint_info) {
+ .out_cables = 0x0001,
+ .in_cables = 0x0001
+ }
+ },
+ {
+ .ifnum = -1
+ }
+ }
+ }
+},
+{
+ /* Advanced modes of the Edirol UA-25EX.
+ * For the standard mode, UA-25EX has ID 0582:00e7, which
+ * offers only 16-bit PCM at 44.1 kHz and no MIDI.
+ */
+ USB_DEVICE_VENDOR_SPEC(0x0582, 0x00e6),
+ .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+ .vendor_name = "EDIROL",
+ .product_name = "UA-25EX",
+ .ifnum = QUIRK_ANY_INTERFACE,
+ .type = QUIRK_COMPOSITE,
+ .data = (const struct snd_usb_audio_quirk[]) {
+ {
+ .ifnum = 0,
+ .type = QUIRK_AUDIO_EDIROL_UAXX
+ },
+ {
+ .ifnum = 1,
+ .type = QUIRK_AUDIO_EDIROL_UAXX
+ },
+ {
+ .ifnum = 2,
+ .type = QUIRK_AUDIO_EDIROL_UAXX
+ },
+ {
+ .ifnum = -1
+ }
+ }
+ }
+},
+{
+ /* Edirol UM-3G */
+ USB_DEVICE_VENDOR_SPEC(0x0582, 0x0108),
+ .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+ .ifnum = 0,
+ .type = QUIRK_MIDI_FIXED_ENDPOINT,
+ .data = & (const struct snd_usb_midi_endpoint_info) {
+ .out_cables = 0x0007,
+ .in_cables = 0x0007
+ }
+ }
+},
+{
+ /* BOSS ME-25 */
+ USB_DEVICE(0x0582, 0x0113),
+ .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+ .ifnum = QUIRK_ANY_INTERFACE,
+ .type = QUIRK_COMPOSITE,
+ .data = (const struct snd_usb_audio_quirk[]) {
+ {
+ .ifnum = 0,
+ .type = QUIRK_AUDIO_STANDARD_INTERFACE
+ },
+ {
+ .ifnum = 1,
+ .type = QUIRK_AUDIO_STANDARD_INTERFACE
+ },
+ {
+ .ifnum = 2,
+ .type = QUIRK_MIDI_FIXED_ENDPOINT,
+ .data = & (const struct snd_usb_midi_endpoint_info) {
+ .out_cables = 0x0001,
+ .in_cables = 0x0001
+ }
+ },
+ {
+ .ifnum = -1
+ }
+ }
+ }
+},
+{
+ /* only 44.1 kHz works at the moment */
+ USB_DEVICE(0x0582, 0x0120),
+ .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+ /* .vendor_name = "Roland", */
+ /* .product_name = "OCTO-CAPTURE", */
+ .ifnum = QUIRK_ANY_INTERFACE,
+ .type = QUIRK_COMPOSITE,
+ .data = (const struct snd_usb_audio_quirk[]) {
+ {
+ .ifnum = 0,
+ .type = QUIRK_AUDIO_FIXED_ENDPOINT,
+ .data = & (const struct audioformat) {
+ .formats = SNDRV_PCM_FMTBIT_S32_LE,
+ .channels = 10,
+ .iface = 0,
+ .altsetting = 1,
+ .altset_idx = 1,
+ .endpoint = 0x05,
+ .ep_attr = 0x05,
+ .rates = SNDRV_PCM_RATE_44100,
+ .rate_min = 44100,
+ .rate_max = 44100,
+ .nr_rates = 1,
+ .rate_table = (unsigned int[]) { 44100 }
+ }
+ },
+ {
+ .ifnum = 1,
+ .type = QUIRK_AUDIO_FIXED_ENDPOINT,
+ .data = & (const struct audioformat) {
+ .formats = SNDRV_PCM_FMTBIT_S32_LE,
+ .channels = 12,
+ .iface = 1,
+ .altsetting = 1,
+ .altset_idx = 1,
+ .endpoint = 0x85,
+ .ep_attr = 0x25,
+ .rates = SNDRV_PCM_RATE_44100,
+ .rate_min = 44100,
+ .rate_max = 44100,
+ .nr_rates = 1,
+ .rate_table = (unsigned int[]) { 44100 }
+ }
+ },
+ {
+ .ifnum = 2,
+ .type = QUIRK_MIDI_FIXED_ENDPOINT,
+ .data = & (const struct snd_usb_midi_endpoint_info) {
+ .out_cables = 0x0001,
+ .in_cables = 0x0001
+ }
+ },
+ {
+ .ifnum = 3,
+ .type = QUIRK_IGNORE_INTERFACE
+ },
+ {
+ .ifnum = 4,
+ .type = QUIRK_IGNORE_INTERFACE
+ },
+ {
+ .ifnum = -1
+ }
+ }
+ }
+},
+{
+ /* only 44.1 kHz works at the moment */
+ USB_DEVICE(0x0582, 0x012f),
+ .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+ /* .vendor_name = "Roland", */
+ /* .product_name = "QUAD-CAPTURE", */
+ .ifnum = QUIRK_ANY_INTERFACE,
+ .type = QUIRK_COMPOSITE,
+ .data = (const struct snd_usb_audio_quirk[]) {
+ {
+ .ifnum = 0,
+ .type = QUIRK_AUDIO_FIXED_ENDPOINT,
+ .data = & (const struct audioformat) {
+ .formats = SNDRV_PCM_FMTBIT_S32_LE,
+ .channels = 4,
+ .iface = 0,
+ .altsetting = 1,
+ .altset_idx = 1,
+ .endpoint = 0x05,
+ .ep_attr = 0x05,
+ .rates = SNDRV_PCM_RATE_44100,
+ .rate_min = 44100,
+ .rate_max = 44100,
+ .nr_rates = 1,
+ .rate_table = (unsigned int[]) { 44100 }
+ }
+ },
+ {
+ .ifnum = 1,
+ .type = QUIRK_AUDIO_FIXED_ENDPOINT,
+ .data = & (const struct audioformat) {
+ .formats = SNDRV_PCM_FMTBIT_S32_LE,
+ .channels = 6,
+ .iface = 1,
+ .altsetting = 1,
+ .altset_idx = 1,
+ .endpoint = 0x85,
+ .ep_attr = 0x25,
+ .rates = SNDRV_PCM_RATE_44100,
+ .rate_min = 44100,
+ .rate_max = 44100,
+ .nr_rates = 1,
+ .rate_table = (unsigned int[]) { 44100 }
+ }
+ },
+ {
+ .ifnum = 2,
+ .type = QUIRK_MIDI_FIXED_ENDPOINT,
+ .data = & (const struct snd_usb_midi_endpoint_info) {
+ .out_cables = 0x0001,
+ .in_cables = 0x0001
+ }
+ },
+ {
+ .ifnum = 3,
+ .type = QUIRK_IGNORE_INTERFACE
+ },
+ {
+ .ifnum = 4,
+ .type = QUIRK_IGNORE_INTERFACE
+ },
+ {
+ .ifnum = -1
+ }
+ }
+ }
+},
+{
+ USB_DEVICE(0x0582, 0x0159),
+ .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+ /* .vendor_name = "Roland", */
+ /* .product_name = "UA-22", */
+ .ifnum = QUIRK_ANY_INTERFACE,
+ .type = QUIRK_COMPOSITE,
+ .data = (const struct snd_usb_audio_quirk[]) {
+ {
+ .ifnum = 0,
+ .type = QUIRK_AUDIO_STANDARD_INTERFACE
+ },
+ {
+ .ifnum = 1,
+ .type = QUIRK_AUDIO_STANDARD_INTERFACE
+ },
+ {
+ .ifnum = 2,
+ .type = QUIRK_MIDI_FIXED_ENDPOINT,
+ .data = & (const struct snd_usb_midi_endpoint_info) {
+ .out_cables = 0x0001,
+ .in_cables = 0x0001
+ }
+ },
+ {
+ .ifnum = -1
+ }
+ }
+ }
+},
+/* this catches most recent vendor-specific Roland devices */
+{
+ .match_flags = USB_DEVICE_ID_MATCH_VENDOR |
+ USB_DEVICE_ID_MATCH_INT_CLASS,
+ .idVendor = 0x0582,
+ .bInterfaceClass = USB_CLASS_VENDOR_SPEC,
+ .driver_info = (unsigned long) &(const struct snd_usb_audio_quirk) {
+ .ifnum = QUIRK_ANY_INTERFACE,
+ .type = QUIRK_AUTODETECT
+ }
+},
+
+/* Guillemot devices */
+{
+ /*
+ * This is for the "Windows Edition" where the external MIDI ports are
+ * the only MIDI ports; the control data is reported through HID
+ * interfaces. The "Macintosh Edition" has ID 0xd002 and uses standard
+ * compliant USB MIDI ports for external MIDI and controls.
+ */
+ USB_DEVICE_VENDOR_SPEC(0x06f8, 0xb000),
+ .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+ .vendor_name = "Hercules",
+ .product_name = "DJ Console (WE)",
+ .ifnum = 4,
+ .type = QUIRK_MIDI_FIXED_ENDPOINT,
+ .data = & (const struct snd_usb_midi_endpoint_info) {
+ .out_cables = 0x0001,
+ .in_cables = 0x0001
+ }
+ }
+},
+
+/* Midiman/M-Audio devices */
+{
+ USB_DEVICE_VENDOR_SPEC(0x0763, 0x1002),
+ .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+ .vendor_name = "M-Audio",
+ .product_name = "MidiSport 2x2",
+ .ifnum = QUIRK_ANY_INTERFACE,
+ .type = QUIRK_MIDI_MIDIMAN,
+ .data = & (const struct snd_usb_midi_endpoint_info) {
+ .out_cables = 0x0003,
+ .in_cables = 0x0003
+ }
+ }
+},
+{
+ USB_DEVICE_VENDOR_SPEC(0x0763, 0x1011),
+ .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+ .vendor_name = "M-Audio",
+ .product_name = "MidiSport 1x1",
+ .ifnum = QUIRK_ANY_INTERFACE,
+ .type = QUIRK_MIDI_MIDIMAN,
+ .data = & (const struct snd_usb_midi_endpoint_info) {
+ .out_cables = 0x0001,
+ .in_cables = 0x0001
+ }
+ }
+},
+{
+ USB_DEVICE_VENDOR_SPEC(0x0763, 0x1015),
+ .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+ .vendor_name = "M-Audio",
+ .product_name = "Keystation",
+ .ifnum = QUIRK_ANY_INTERFACE,
+ .type = QUIRK_MIDI_MIDIMAN,
+ .data = & (const struct snd_usb_midi_endpoint_info) {
+ .out_cables = 0x0001,
+ .in_cables = 0x0001
+ }
+ }
+},
+{
+ USB_DEVICE_VENDOR_SPEC(0x0763, 0x1021),
+ .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+ .vendor_name = "M-Audio",
+ .product_name = "MidiSport 4x4",
+ .ifnum = QUIRK_ANY_INTERFACE,
+ .type = QUIRK_MIDI_MIDIMAN,
+ .data = & (const struct snd_usb_midi_endpoint_info) {
+ .out_cables = 0x000f,
+ .in_cables = 0x000f
+ }
+ }
+},
+{
+ /*
+ * For hardware revision 1.05; in the later revisions (1.10 and
+ * 1.21), 0x1031 is the ID for the device without firmware.
+ * Thanks to Olaf Giesbrecht <Olaf_Giesbrecht@yahoo.de>
+ */
+ USB_DEVICE_VER(0x0763, 0x1031, 0x0100, 0x0109),
+ .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+ .vendor_name = "M-Audio",
+ .product_name = "MidiSport 8x8",
+ .ifnum = QUIRK_ANY_INTERFACE,
+ .type = QUIRK_MIDI_MIDIMAN,
+ .data = & (const struct snd_usb_midi_endpoint_info) {
+ .out_cables = 0x01ff,
+ .in_cables = 0x01ff
+ }
+ }
+},
+{
+ USB_DEVICE_VENDOR_SPEC(0x0763, 0x1033),
+ .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+ .vendor_name = "M-Audio",
+ .product_name = "MidiSport 8x8",
+ .ifnum = QUIRK_ANY_INTERFACE,
+ .type = QUIRK_MIDI_MIDIMAN,
+ .data = & (const struct snd_usb_midi_endpoint_info) {
+ .out_cables = 0x01ff,
+ .in_cables = 0x01ff
+ }
+ }
+},
+{
+ USB_DEVICE_VENDOR_SPEC(0x0763, 0x1041),
+ .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+ .vendor_name = "M-Audio",
+ .product_name = "MidiSport 2x4",
+ .ifnum = QUIRK_ANY_INTERFACE,
+ .type = QUIRK_MIDI_MIDIMAN,
+ .data = & (const struct snd_usb_midi_endpoint_info) {
+ .out_cables = 0x000f,
+ .in_cables = 0x0003
+ }
+ }
+},
+{
+ USB_DEVICE_VENDOR_SPEC(0x0763, 0x2001),
+ .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+ .vendor_name = "M-Audio",
+ .product_name = "Quattro",
+ .ifnum = QUIRK_ANY_INTERFACE,
+ .type = QUIRK_COMPOSITE,
+ .data = & (const struct snd_usb_audio_quirk[]) {
+ /*
+ * Interfaces 0-2 are "Windows-compatible", 16-bit only,
+ * and share endpoints with the other interfaces.
+ * Ignore them. The other interfaces can do 24 bits,
+ * but captured samples are big-endian (see usbaudio.c).
+ */
+ {
+ .ifnum = 0,
+ .type = QUIRK_IGNORE_INTERFACE
+ },
+ {
+ .ifnum = 1,
+ .type = QUIRK_IGNORE_INTERFACE
+ },
+ {
+ .ifnum = 2,
+ .type = QUIRK_IGNORE_INTERFACE
+ },
+ {
+ .ifnum = 3,
+ .type = QUIRK_IGNORE_INTERFACE
+ },
+ {
+ .ifnum = 4,
+ .type = QUIRK_AUDIO_STANDARD_INTERFACE
+ },
+ {
+ .ifnum = 5,
+ .type = QUIRK_AUDIO_STANDARD_INTERFACE
+ },
+ {
+ .ifnum = 6,
+ .type = QUIRK_IGNORE_INTERFACE
+ },
+ {
+ .ifnum = 7,
+ .type = QUIRK_AUDIO_STANDARD_INTERFACE
+ },
+ {
+ .ifnum = 8,
+ .type = QUIRK_AUDIO_STANDARD_INTERFACE
+ },
+ {
+ .ifnum = 9,
+ .type = QUIRK_MIDI_MIDIMAN,
+ .data = & (const struct snd_usb_midi_endpoint_info) {
+ .out_cables = 0x0001,
+ .in_cables = 0x0001
+ }
+ },
+ {
+ .ifnum = -1
+ }
+ }
+ }
+},
+{
+ USB_DEVICE_VENDOR_SPEC(0x0763, 0x2003),
+ .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+ .vendor_name = "M-Audio",
+ .product_name = "AudioPhile",
+ .ifnum = 6,
+ .type = QUIRK_MIDI_MIDIMAN,
+ .data = & (const struct snd_usb_midi_endpoint_info) {
+ .out_cables = 0x0001,
+ .in_cables = 0x0001
+ }
+ }
+},
+{
+ USB_DEVICE_VENDOR_SPEC(0x0763, 0x2008),
+ .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+ .vendor_name = "M-Audio",
+ .product_name = "Ozone",
+ .ifnum = 3,
+ .type = QUIRK_MIDI_MIDIMAN,
+ .data = & (const struct snd_usb_midi_endpoint_info) {
+ .out_cables = 0x0001,
+ .in_cables = 0x0001
+ }
+ }
+},
+{
+ USB_DEVICE_VENDOR_SPEC(0x0763, 0x200d),
+ .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+ .vendor_name = "M-Audio",
+ .product_name = "OmniStudio",
+ .ifnum = QUIRK_ANY_INTERFACE,
+ .type = QUIRK_COMPOSITE,
+ .data = & (const struct snd_usb_audio_quirk[]) {
+ {
+ .ifnum = 0,
+ .type = QUIRK_IGNORE_INTERFACE
+ },
+ {
+ .ifnum = 1,
+ .type = QUIRK_IGNORE_INTERFACE
+ },
+ {
+ .ifnum = 2,
+ .type = QUIRK_IGNORE_INTERFACE
+ },
+ {
+ .ifnum = 3,
+ .type = QUIRK_IGNORE_INTERFACE
+ },
+ {
+ .ifnum = 4,
+ .type = QUIRK_AUDIO_STANDARD_INTERFACE
+ },
+ {
+ .ifnum = 5,
+ .type = QUIRK_AUDIO_STANDARD_INTERFACE
+ },
+ {
+ .ifnum = 6,
+ .type = QUIRK_IGNORE_INTERFACE
+ },
+ {
+ .ifnum = 7,
+ .type = QUIRK_AUDIO_STANDARD_INTERFACE
+ },
+ {
+ .ifnum = 8,
+ .type = QUIRK_AUDIO_STANDARD_INTERFACE
+ },
+ {
+ .ifnum = 9,
+ .type = QUIRK_MIDI_MIDIMAN,
+ .data = & (const struct snd_usb_midi_endpoint_info) {
+ .out_cables = 0x0001,
+ .in_cables = 0x0001
+ }
+ },
+ {
+ .ifnum = -1
+ }
+ }
+ }
+},
+{
+ USB_DEVICE(0x0763, 0x2019),
+ .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+ /* .vendor_name = "M-Audio", */
+ /* .product_name = "Ozone Academic", */
+ .ifnum = QUIRK_ANY_INTERFACE,
+ .type = QUIRK_COMPOSITE,
+ .data = & (const struct snd_usb_audio_quirk[]) {
+ {
+ .ifnum = 0,
+ .type = QUIRK_AUDIO_STANDARD_INTERFACE
+ },
+ {
+ .ifnum = 1,
+ .type = QUIRK_AUDIO_STANDARD_INTERFACE
+ },
+ {
+ .ifnum = 2,
+ .type = QUIRK_AUDIO_STANDARD_INTERFACE
+ },
+ {
+ .ifnum = 3,
+ .type = QUIRK_MIDI_MIDIMAN,
+ .data = & (const struct snd_usb_midi_endpoint_info) {
+ .out_cables = 0x0001,
+ .in_cables = 0x0001
+ }
+ },
+ {
+ .ifnum = -1
+ }
+ }
+ }
+},
+{
+ USB_DEVICE_VENDOR_SPEC(0x0763, 0x2030),
+ .driver_info = (unsigned long) &(const struct snd_usb_audio_quirk) {
+ /* .vendor_name = "M-Audio", */
+ /* .product_name = "Fast Track C400", */
+ .ifnum = QUIRK_ANY_INTERFACE,
+ .type = QUIRK_COMPOSITE,
+ .data = &(const struct snd_usb_audio_quirk[]) {
+ {
+ .ifnum = 1,
+ .type = QUIRK_AUDIO_STANDARD_MIXER,
+ },
+ /* Playback */
+ {
+ .ifnum = 2,
+ .type = QUIRK_AUDIO_FIXED_ENDPOINT,
+ .data = &(const struct audioformat) {
+ .formats = SNDRV_PCM_FMTBIT_S24_3LE,
+ .channels = 6,
+ .iface = 2,
+ .altsetting = 1,
+ .altset_idx = 1,
+ .attributes = UAC_EP_CS_ATTR_SAMPLE_RATE,
+ .endpoint = 0x01,
+ .ep_attr = 0x09,
+ .rates = SNDRV_PCM_RATE_44100 |
+ SNDRV_PCM_RATE_48000 |
+ SNDRV_PCM_RATE_88200 |
+ SNDRV_PCM_RATE_96000,
+ .rate_min = 44100,
+ .rate_max = 96000,
+ .nr_rates = 4,
+ .rate_table = (unsigned int[]) {
+ 44100, 48000, 88200, 96000
+ },
+ .clock = 0x80,
+ }
+ },
+ /* Capture */
+ {
+ .ifnum = 3,
+ .type = QUIRK_AUDIO_FIXED_ENDPOINT,
+ .data = &(const struct audioformat) {
+ .formats = SNDRV_PCM_FMTBIT_S24_3LE,
+ .channels = 4,
+ .iface = 3,
+ .altsetting = 1,
+ .altset_idx = 1,
+ .attributes = UAC_EP_CS_ATTR_SAMPLE_RATE,
+ .endpoint = 0x81,
+ .ep_attr = 0x05,
+ .rates = SNDRV_PCM_RATE_44100 |
+ SNDRV_PCM_RATE_48000 |
+ SNDRV_PCM_RATE_88200 |
+ SNDRV_PCM_RATE_96000,
+ .rate_min = 44100,
+ .rate_max = 96000,
+ .nr_rates = 4,
+ .rate_table = (unsigned int[]) {
+ 44100, 48000, 88200, 96000
+ },
+ .clock = 0x80,
+ }
+ },
+ /* MIDI */
+ {
+ .ifnum = -1 /* Interface = 4 */
+ }
+ }
+ }
+},
+{
+ USB_DEVICE_VENDOR_SPEC(0x0763, 0x2031),
+ .driver_info = (unsigned long) &(const struct snd_usb_audio_quirk) {
+ /* .vendor_name = "M-Audio", */
+ /* .product_name = "Fast Track C600", */
+ .ifnum = QUIRK_ANY_INTERFACE,
+ .type = QUIRK_COMPOSITE,
+ .data = &(const struct snd_usb_audio_quirk[]) {
+ {
+ .ifnum = 1,
+ .type = QUIRK_AUDIO_STANDARD_MIXER,
+ },
+ /* Playback */
+ {
+ .ifnum = 2,
+ .type = QUIRK_AUDIO_FIXED_ENDPOINT,
+ .data = &(const struct audioformat) {
+ .formats = SNDRV_PCM_FMTBIT_S24_3LE,
+ .channels = 8,
+ .iface = 2,
+ .altsetting = 1,
+ .altset_idx = 1,
+ .attributes = UAC_EP_CS_ATTR_SAMPLE_RATE,
+ .endpoint = 0x01,
+ .ep_attr = 0x09,
+ .rates = SNDRV_PCM_RATE_44100 |
+ SNDRV_PCM_RATE_48000 |
+ SNDRV_PCM_RATE_88200 |
+ SNDRV_PCM_RATE_96000,
+ .rate_min = 44100,
+ .rate_max = 96000,
+ .nr_rates = 4,
+ .rate_table = (unsigned int[]) {
+ 44100, 48000, 88200, 96000
+ },
+ .clock = 0x80,
+ }
+ },
+ /* Capture */
+ {
+ .ifnum = 3,
+ .type = QUIRK_AUDIO_FIXED_ENDPOINT,
+ .data = &(const struct audioformat) {
+ .formats = SNDRV_PCM_FMTBIT_S24_3LE,
+ .channels = 6,
+ .iface = 3,
+ .altsetting = 1,
+ .altset_idx = 1,
+ .attributes = UAC_EP_CS_ATTR_SAMPLE_RATE,
+ .endpoint = 0x81,
+ .ep_attr = 0x05,
+ .rates = SNDRV_PCM_RATE_44100 |
+ SNDRV_PCM_RATE_48000 |
+ SNDRV_PCM_RATE_88200 |
+ SNDRV_PCM_RATE_96000,
+ .rate_min = 44100,
+ .rate_max = 96000,
+ .nr_rates = 4,
+ .rate_table = (unsigned int[]) {
+ 44100, 48000, 88200, 96000
+ },
+ .clock = 0x80,
+ }
+ },
+ /* MIDI */
+ {
+ .ifnum = -1 /* Interface = 4 */
+ }
+ }
+ }
+},
+{
+ USB_DEVICE_VENDOR_SPEC(0x0763, 0x2080),
+ .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+ /* .vendor_name = "M-Audio", */
+ /* .product_name = "Fast Track Ultra", */
+ .ifnum = QUIRK_ANY_INTERFACE,
+ .type = QUIRK_COMPOSITE,
+ .data = & (const struct snd_usb_audio_quirk[]) {
+ {
+ .ifnum = 0,
+ .type = QUIRK_AUDIO_STANDARD_MIXER,
+ },
+ {
+ .ifnum = 1,
+ .type = QUIRK_AUDIO_FIXED_ENDPOINT,
+ .data = & (const struct audioformat) {
+ .formats = SNDRV_PCM_FMTBIT_S24_3LE,
+ .channels = 8,
+ .iface = 1,
+ .altsetting = 1,
+ .altset_idx = 1,
+ .attributes = UAC_EP_CS_ATTR_SAMPLE_RATE,
+ .endpoint = 0x01,
+ .ep_attr = 0x09,
+ .rates = SNDRV_PCM_RATE_44100 |
+ SNDRV_PCM_RATE_48000 |
+ SNDRV_PCM_RATE_88200 |
+ SNDRV_PCM_RATE_96000,
+ .rate_min = 44100,
+ .rate_max = 96000,
+ .nr_rates = 4,
+ .rate_table = (unsigned int[]) {
+ 44100, 48000, 88200, 96000
+ }
+ }
+ },
+ {
+ .ifnum = 2,
+ .type = QUIRK_AUDIO_FIXED_ENDPOINT,
+ .data = & (const struct audioformat) {
+ .formats = SNDRV_PCM_FMTBIT_S24_3LE,
+ .channels = 8,
+ .iface = 2,
+ .altsetting = 1,
+ .altset_idx = 1,
+ .attributes = UAC_EP_CS_ATTR_SAMPLE_RATE,
+ .endpoint = 0x81,
+ .ep_attr = 0x05,
+ .rates = SNDRV_PCM_RATE_44100 |
+ SNDRV_PCM_RATE_48000 |
+ SNDRV_PCM_RATE_88200 |
+ SNDRV_PCM_RATE_96000,
+ .rate_min = 44100,
+ .rate_max = 96000,
+ .nr_rates = 4,
+ .rate_table = (unsigned int[]) {
+ 44100, 48000, 88200, 96000
+ }
+ }
+ },
+ /* interface 3 (MIDI) is standard compliant */
+ {
+ .ifnum = -1
+ }
+ }
+ }
+},
+{
+ USB_DEVICE_VENDOR_SPEC(0x0763, 0x2081),
+ .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+ /* .vendor_name = "M-Audio", */
+ /* .product_name = "Fast Track Ultra 8R", */
+ .ifnum = QUIRK_ANY_INTERFACE,
+ .type = QUIRK_COMPOSITE,
+ .data = & (const struct snd_usb_audio_quirk[]) {
+ {
+ .ifnum = 0,
+ .type = QUIRK_AUDIO_STANDARD_MIXER,
+ },
+ {
+ .ifnum = 1,
+ .type = QUIRK_AUDIO_FIXED_ENDPOINT,
+ .data = & (const struct audioformat) {
+ .formats = SNDRV_PCM_FMTBIT_S24_3LE,
+ .channels = 8,
+ .iface = 1,
+ .altsetting = 1,
+ .altset_idx = 1,
+ .attributes = UAC_EP_CS_ATTR_SAMPLE_RATE,
+ .endpoint = 0x01,
+ .ep_attr = 0x09,
+ .rates = SNDRV_PCM_RATE_44100 |
+ SNDRV_PCM_RATE_48000 |
+ SNDRV_PCM_RATE_88200 |
+ SNDRV_PCM_RATE_96000,
+ .rate_min = 44100,
+ .rate_max = 96000,
+ .nr_rates = 4,
+ .rate_table = (unsigned int[]) {
+ 44100, 48000, 88200, 96000
+ }
+ }
+ },
+ {
+ .ifnum = 2,
+ .type = QUIRK_AUDIO_FIXED_ENDPOINT,
+ .data = & (const struct audioformat) {
+ .formats = SNDRV_PCM_FMTBIT_S24_3LE,
+ .channels = 8,
+ .iface = 2,
+ .altsetting = 1,
+ .altset_idx = 1,
+ .attributes = UAC_EP_CS_ATTR_SAMPLE_RATE,
+ .endpoint = 0x81,
+ .ep_attr = 0x05,
+ .rates = SNDRV_PCM_RATE_44100 |
+ SNDRV_PCM_RATE_48000 |
+ SNDRV_PCM_RATE_88200 |
+ SNDRV_PCM_RATE_96000,
+ .rate_min = 44100,
+ .rate_max = 96000,
+ .nr_rates = 4,
+ .rate_table = (unsigned int[]) {
+ 44100, 48000, 88200, 96000
+ }
+ }
+ },
+ /* interface 3 (MIDI) is standard compliant */
+ {
+ .ifnum = -1
+ }
+ }
+ }
+},
+
+/* Casio devices */
+{
+ USB_DEVICE(0x07cf, 0x6801),
+ .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+ .vendor_name = "Casio",
+ .product_name = "PL-40R",
+ .ifnum = 0,
+ .type = QUIRK_MIDI_YAMAHA
+ }
+},
+{
+ /* this ID is used by several devices without a product ID */
+ USB_DEVICE(0x07cf, 0x6802),
+ .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+ .vendor_name = "Casio",
+ .product_name = "Keyboard",
+ .ifnum = 0,
+ .type = QUIRK_MIDI_YAMAHA
+ }
+},
+
+/* Mark of the Unicorn devices */
+{
+ /* thanks to Robert A. Lerche <ral 'at' msbit.com> */
+ .match_flags = USB_DEVICE_ID_MATCH_VENDOR |
+ USB_DEVICE_ID_MATCH_PRODUCT |
+ USB_DEVICE_ID_MATCH_DEV_SUBCLASS,
+ .idVendor = 0x07fd,
+ .idProduct = 0x0001,
+ .bDeviceSubClass = 2,
+ .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+ .vendor_name = "MOTU",
+ .product_name = "Fastlane",
+ .ifnum = QUIRK_ANY_INTERFACE,
+ .type = QUIRK_COMPOSITE,
+ .data = & (const struct snd_usb_audio_quirk[]) {
+ {
+ .ifnum = 0,
+ .type = QUIRK_MIDI_RAW_BYTES
+ },
+ {
+ .ifnum = 1,
+ .type = QUIRK_IGNORE_INTERFACE
+ },
+ {
+ .ifnum = -1
+ }
+ }
+ }
+},
+
+/* Emagic devices */
+{
+ USB_DEVICE(0x086a, 0x0001),
+ .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+ .vendor_name = "Emagic",
+ /* .product_name = "Unitor8", */
+ .ifnum = 2,
+ .type = QUIRK_MIDI_EMAGIC,
+ .data = & (const struct snd_usb_midi_endpoint_info) {
+ .out_cables = 0x80ff,
+ .in_cables = 0x80ff
+ }
+ }
+},
+{
+ USB_DEVICE(0x086a, 0x0002),
+ .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+ .vendor_name = "Emagic",
+ /* .product_name = "AMT8", */
+ .ifnum = 2,
+ .type = QUIRK_MIDI_EMAGIC,
+ .data = & (const struct snd_usb_midi_endpoint_info) {
+ .out_cables = 0x80ff,
+ .in_cables = 0x80ff
+ }
+ }
+},
+{
+ USB_DEVICE(0x086a, 0x0003),
+ .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+ .vendor_name = "Emagic",
+ /* .product_name = "MT4", */
+ .ifnum = 2,
+ .type = QUIRK_MIDI_EMAGIC,
+ .data = & (const struct snd_usb_midi_endpoint_info) {
+ .out_cables = 0x800f,
+ .in_cables = 0x8003
+ }
+ }
+},
+
+/* KORG devices */
+{
+ USB_DEVICE_VENDOR_SPEC(0x0944, 0x0200),
+ .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+ .vendor_name = "KORG, Inc.",
+ /* .product_name = "PANDORA PX5D", */
+ .ifnum = 3,
+ .type = QUIRK_MIDI_STANDARD_INTERFACE,
+ }
+},
+
+{
+ USB_DEVICE_VENDOR_SPEC(0x0944, 0x0201),
+ .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+ .vendor_name = "KORG, Inc.",
+ /* .product_name = "ToneLab ST", */
+ .ifnum = 3,
+ .type = QUIRK_MIDI_STANDARD_INTERFACE,
+ }
+},
+
+/* AKAI devices */
+{
+ USB_DEVICE(0x09e8, 0x0062),
+ .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+ .vendor_name = "AKAI",
+ .product_name = "MPD16",
+ .ifnum = 0,
+ .type = QUIRK_MIDI_AKAI,
+ }
+},
+
+{
+ /* Akai MPC Element */
+ USB_DEVICE(0x09e8, 0x0021),
+ .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+ .ifnum = QUIRK_ANY_INTERFACE,
+ .type = QUIRK_COMPOSITE,
+ .data = & (const struct snd_usb_audio_quirk[]) {
+ {
+ .ifnum = 0,
+ .type = QUIRK_IGNORE_INTERFACE
+ },
+ {
+ .ifnum = 1,
+ .type = QUIRK_MIDI_STANDARD_INTERFACE
+ },
+ {
+ .ifnum = -1
+ }
+ }
+ }
+},
+
+/* TerraTec devices */
+{
+ USB_DEVICE_VENDOR_SPEC(0x0ccd, 0x0012),
+ .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+ .vendor_name = "TerraTec",
+ .product_name = "PHASE 26",
+ .ifnum = 3,
+ .type = QUIRK_MIDI_STANDARD_INTERFACE
+ }
+},
+{
+ USB_DEVICE_VENDOR_SPEC(0x0ccd, 0x0013),
+ .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+ .vendor_name = "TerraTec",
+ .product_name = "PHASE 26",
+ .ifnum = 3,
+ .type = QUIRK_MIDI_STANDARD_INTERFACE
+ }
+},
+{
+ USB_DEVICE_VENDOR_SPEC(0x0ccd, 0x0014),
+ .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+ .vendor_name = "TerraTec",
+ .product_name = "PHASE 26",
+ .ifnum = 3,
+ .type = QUIRK_MIDI_STANDARD_INTERFACE
+ }
+},
+{
+ USB_DEVICE(0x0ccd, 0x0028),
+ .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+ .vendor_name = "TerraTec",
+ .product_name = "Aureon5.1MkII",
+ .ifnum = QUIRK_NO_INTERFACE
+ }
+},
+{
+ USB_DEVICE(0x0ccd, 0x0035),
+ .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+ .vendor_name = "Miditech",
+ .product_name = "Play'n Roll",
+ .ifnum = 0,
+ .type = QUIRK_MIDI_CME
+ }
+},
+
+/* Stanton/N2IT Final Scratch v1 device ('Scratchamp') */
+{
+ USB_DEVICE(0x103d, 0x0100),
+ .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+ .vendor_name = "Stanton",
+ .product_name = "ScratchAmp",
+ .ifnum = QUIRK_NO_INTERFACE
+ }
+},
+{
+ USB_DEVICE(0x103d, 0x0101),
+ .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+ .vendor_name = "Stanton",
+ .product_name = "ScratchAmp",
+ .ifnum = QUIRK_NO_INTERFACE
+ }
+},
+
+/* Novation EMS devices */
+{
+ USB_DEVICE_VENDOR_SPEC(0x1235, 0x0001),
+ .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+ .vendor_name = "Novation",
+ .product_name = "ReMOTE Audio/XStation",
+ .ifnum = 4,
+ .type = QUIRK_MIDI_NOVATION
+ }
+},
+{
+ USB_DEVICE_VENDOR_SPEC(0x1235, 0x0002),
+ .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+ .vendor_name = "Novation",
+ .product_name = "Speedio",
+ .ifnum = 3,
+ .type = QUIRK_MIDI_NOVATION
+ }
+},
+{
+ USB_DEVICE(0x1235, 0x000e),
+ .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+ /* .vendor_name = "Novation", */
+ /* .product_name = "Launchpad", */
+ .ifnum = 0,
+ .type = QUIRK_MIDI_RAW_BYTES
+ }
+},
+{
+ USB_DEVICE(0x1235, 0x0010),
+ .driver_info = (unsigned long) &(const struct snd_usb_audio_quirk) {
+ .vendor_name = "Focusrite",
+ .product_name = "Saffire 6 USB",
+ .ifnum = QUIRK_ANY_INTERFACE,
+ .type = QUIRK_COMPOSITE,
+ .data = (const struct snd_usb_audio_quirk[]) {
+ {
+ .ifnum = 0,
+ .type = QUIRK_AUDIO_FIXED_ENDPOINT,
+ .data = &(const struct audioformat) {
+ .formats = SNDRV_PCM_FMTBIT_S24_3LE,
+ .channels = 4,
+ .iface = 0,
+ .altsetting = 1,
+ .altset_idx = 1,
+ .attributes = UAC_EP_CS_ATTR_SAMPLE_RATE,
+ .endpoint = 0x01,
+ .ep_attr = USB_ENDPOINT_XFER_ISOC,
+ .rates = SNDRV_PCM_RATE_44100 |
+ SNDRV_PCM_RATE_48000,
+ .rate_min = 44100,
+ .rate_max = 48000,
+ .nr_rates = 2,
+ .rate_table = (unsigned int[]) {
+ 44100, 48000
+ }
+ }
+ },
+ {
+ .ifnum = 1,
+ .type = QUIRK_MIDI_RAW_BYTES
+ },
+ {
+ .ifnum = -1
+ }
+ }
+ }
+},
+{
+ USB_DEVICE(0x1235, 0x0018),
+ .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+ .vendor_name = "Novation",
+ .product_name = "Twitch",
+ .ifnum = QUIRK_ANY_INTERFACE,
+ .type = QUIRK_COMPOSITE,
+ .data = (const struct snd_usb_audio_quirk[]) {
+ {
+ .ifnum = 0,
+ .type = QUIRK_AUDIO_FIXED_ENDPOINT,
+ .data = & (const struct audioformat) {
+ .formats = SNDRV_PCM_FMTBIT_S24_3LE,
+ .channels = 4,
+ .iface = 0,
+ .altsetting = 1,
+ .altset_idx = 1,
+ .attributes = UAC_EP_CS_ATTR_SAMPLE_RATE,
+ .endpoint = 0x01,
+ .ep_attr = USB_ENDPOINT_XFER_ISOC,
+ .rates = SNDRV_PCM_RATE_44100 |
+ SNDRV_PCM_RATE_48000,
+ .rate_min = 44100,
+ .rate_max = 48000,
+ .nr_rates = 2,
+ .rate_table = (unsigned int[]) {
+ 44100, 48000
+ }
+ }
+ },
+ {
+ .ifnum = 1,
+ .type = QUIRK_MIDI_RAW_BYTES
+ },
+ {
+ .ifnum = -1
+ }
+ }
+ }
+},
+{
+ USB_DEVICE_VENDOR_SPEC(0x1235, 0x4661),
+ .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+ .vendor_name = "Novation",
+ .product_name = "ReMOTE25",
+ .ifnum = 0,
+ .type = QUIRK_MIDI_NOVATION
+ }
+},
+
+/* Access Music devices */
+{
+ /* VirusTI Desktop */
+ USB_DEVICE_VENDOR_SPEC(0x133e, 0x0815),
+ .driver_info = (unsigned long) &(const struct snd_usb_audio_quirk) {
+ .ifnum = QUIRK_ANY_INTERFACE,
+ .type = QUIRK_COMPOSITE,
+ .data = &(const struct snd_usb_audio_quirk[]) {
+ {
+ .ifnum = 3,
+ .type = QUIRK_MIDI_FIXED_ENDPOINT,
+ .data = &(const struct snd_usb_midi_endpoint_info) {
+ .out_cables = 0x0003,
+ .in_cables = 0x0003
+ }
+ },
+ {
+ .ifnum = 4,
+ .type = QUIRK_IGNORE_INTERFACE
+ },
+ {
+ .ifnum = -1
+ }
+ }
+ }
+},
+
+/* */
+{
+ /* aka. Serato Scratch Live DJ Box */
+ USB_DEVICE(0x13e5, 0x0001),
+ .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+ .vendor_name = "Rane",
+ .product_name = "SL-1",
+ .ifnum = QUIRK_NO_INTERFACE
+ }
+},
+
+/* Native Instruments MK2 series */
+{
+ /* Komplete Audio 6 */
+ .match_flags = USB_DEVICE_ID_MATCH_DEVICE,
+ .idVendor = 0x17cc,
+ .idProduct = 0x1000,
+},
+{
+ /* Traktor Audio 6 */
+ .match_flags = USB_DEVICE_ID_MATCH_DEVICE,
+ .idVendor = 0x17cc,
+ .idProduct = 0x1010,
+},
+{
+ /* Traktor Audio 10 */
+ .match_flags = USB_DEVICE_ID_MATCH_DEVICE,
+ .idVendor = 0x17cc,
+ .idProduct = 0x1020,
+},
+
+/* KeithMcMillen Stringport */
+{
+ USB_DEVICE(0x1f38, 0x0001),
+ .bInterfaceClass = USB_CLASS_AUDIO,
+},
+
+/* Miditech devices */
+{
+ USB_DEVICE(0x4752, 0x0011),
+ .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+ .vendor_name = "Miditech",
+ .product_name = "Midistart-2",
+ .ifnum = 0,
+ .type = QUIRK_MIDI_CME
+ }
+},
+
+/* Central Music devices */
+{
+ /* this ID used by both Miditech MidiStudio-2 and CME UF-x */
+ USB_DEVICE(0x7104, 0x2202),
+ .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+ .ifnum = 0,
+ .type = QUIRK_MIDI_CME
+ }
+},
+
+/*
+ * Auvitek au0828 devices with audio interface.
+ * This should be kept in sync with drivers/media/usb/au0828/au0828-cards.c
+ * Please notice that some drivers are DVB only, and don't need to be
+ * here. That's the case, for example, of DVICO_FUSIONHDTV7.
+ */
+
+#define AU0828_DEVICE(vid, pid, vname, pname) { \
+ USB_DEVICE_VENDOR_SPEC(vid, pid), \
+ .match_flags = USB_DEVICE_ID_MATCH_DEVICE | \
+ USB_DEVICE_ID_MATCH_INT_CLASS | \
+ USB_DEVICE_ID_MATCH_INT_SUBCLASS, \
+ .bInterfaceClass = USB_CLASS_AUDIO, \
+ .bInterfaceSubClass = USB_SUBCLASS_AUDIOCONTROL, \
+ .driver_info = (unsigned long) &(const struct snd_usb_audio_quirk) { \
+ .vendor_name = vname, \
+ .product_name = pname, \
+ .ifnum = QUIRK_ANY_INTERFACE, \
+ .type = QUIRK_AUDIO_ALIGN_TRANSFER, \
+ } \
+}
+
+AU0828_DEVICE(0x2040, 0x7200, "Hauppauge", "HVR-950Q"),
+AU0828_DEVICE(0x2040, 0x7240, "Hauppauge", "HVR-850"),
+AU0828_DEVICE(0x2040, 0x7210, "Hauppauge", "HVR-950Q"),
+AU0828_DEVICE(0x2040, 0x7217, "Hauppauge", "HVR-950Q"),
+AU0828_DEVICE(0x2040, 0x721b, "Hauppauge", "HVR-950Q"),
+AU0828_DEVICE(0x2040, 0x721e, "Hauppauge", "HVR-950Q"),
+AU0828_DEVICE(0x2040, 0x721f, "Hauppauge", "HVR-950Q"),
+AU0828_DEVICE(0x2040, 0x7280, "Hauppauge", "HVR-950Q"),
+AU0828_DEVICE(0x0fd9, 0x0008, "Hauppauge", "HVR-950Q"),
+AU0828_DEVICE(0x2040, 0x7201, "Hauppauge", "HVR-950Q-MXL"),
+AU0828_DEVICE(0x2040, 0x7211, "Hauppauge", "HVR-950Q-MXL"),
+AU0828_DEVICE(0x2040, 0x7281, "Hauppauge", "HVR-950Q-MXL"),
+AU0828_DEVICE(0x05e1, 0x0480, "Hauppauge", "Woodbury"),
+AU0828_DEVICE(0x2040, 0x8200, "Hauppauge", "Woodbury"),
+AU0828_DEVICE(0x2040, 0x7260, "Hauppauge", "HVR-950Q"),
+AU0828_DEVICE(0x2040, 0x7213, "Hauppauge", "HVR-950Q"),
+AU0828_DEVICE(0x2040, 0x7270, "Hauppauge", "HVR-950Q"),
+
+/* Digidesign Mbox */
+{
+ /* Thanks to Clemens Ladisch <clemens@ladisch.de> */
+ USB_DEVICE(0x0dba, 0x1000),
+ .driver_info = (unsigned long) &(const struct snd_usb_audio_quirk) {
+ .vendor_name = "Digidesign",
+ .product_name = "MBox",
+ .ifnum = QUIRK_ANY_INTERFACE,
+ .type = QUIRK_COMPOSITE,
+ .data = (const struct snd_usb_audio_quirk[]){
+ {
+ .ifnum = 0,
+ .type = QUIRK_AUDIO_STANDARD_MIXER,
+ },
+ {
+ .ifnum = 1,
+ .type = QUIRK_AUDIO_FIXED_ENDPOINT,
+ .data = &(const struct audioformat) {
+ .formats = SNDRV_PCM_FMTBIT_S24_3BE,
+ .channels = 2,
+ .iface = 1,
+ .altsetting = 1,
+ .altset_idx = 1,
+ .attributes = 0x4,
+ .endpoint = 0x02,
+ .ep_attr = USB_ENDPOINT_XFER_ISOC |
+ USB_ENDPOINT_SYNC_SYNC,
+ .maxpacksize = 0x130,
+ .rates = SNDRV_PCM_RATE_48000,
+ .rate_min = 48000,
+ .rate_max = 48000,
+ .nr_rates = 1,
+ .rate_table = (unsigned int[]) {
+ 48000
+ }
+ }
+ },
+ {
+ .ifnum = 1,
+ .type = QUIRK_AUDIO_FIXED_ENDPOINT,
+ .data = &(const struct audioformat) {
+ .formats = SNDRV_PCM_FMTBIT_S24_3BE,
+ .channels = 2,
+ .iface = 1,
+ .altsetting = 1,
+ .altset_idx = 1,
+ .attributes = 0x4,
+ .endpoint = 0x81,
+ .ep_attr = USB_ENDPOINT_XFER_ISOC |
+ USB_ENDPOINT_SYNC_ASYNC,
+ .maxpacksize = 0x130,
+ .rates = SNDRV_PCM_RATE_48000,
+ .rate_min = 48000,
+ .rate_max = 48000,
+ .nr_rates = 1,
+ .rate_table = (unsigned int[]) {
+ 48000
+ }
+ }
+ },
+ {
+ .ifnum = -1
+ }
+ }
+ }
+},
+
+/* DIGIDESIGN MBOX 2 */
+{
+ USB_DEVICE(0x0dba, 0x3000),
+ .driver_info = (unsigned long) &(const struct snd_usb_audio_quirk) {
+ .vendor_name = "Digidesign",
+ .product_name = "Mbox 2",
+ .ifnum = QUIRK_ANY_INTERFACE,
+ .type = QUIRK_COMPOSITE,
+ .data = (const struct snd_usb_audio_quirk[]) {
+ {
+ .ifnum = 0,
+ .type = QUIRK_IGNORE_INTERFACE
+ },
+ {
+ .ifnum = 1,
+ .type = QUIRK_IGNORE_INTERFACE
+ },
+ {
+ .ifnum = 2,
+ .type = QUIRK_AUDIO_FIXED_ENDPOINT,
+ .data = &(const struct audioformat) {
+ .formats = SNDRV_PCM_FMTBIT_S24_3BE,
+ .channels = 2,
+ .iface = 2,
+ .altsetting = 2,
+ .altset_idx = 1,
+ .attributes = 0x00,
+ .endpoint = 0x03,
+ .ep_attr = USB_ENDPOINT_SYNC_ASYNC,
+ .rates = SNDRV_PCM_RATE_48000,
+ .rate_min = 48000,
+ .rate_max = 48000,
+ .nr_rates = 1,
+ .rate_table = (unsigned int[]) {
+ 48000
+ }
+ }
+ },
+ {
+ .ifnum = 3,
+ .type = QUIRK_IGNORE_INTERFACE
+ },
+ {
+ .ifnum = 4,
+ .type = QUIRK_AUDIO_FIXED_ENDPOINT,
+ .data = &(const struct audioformat) {
+ .formats = SNDRV_PCM_FMTBIT_S24_3BE,
+ .channels = 2,
+ .iface = 4,
+ .altsetting = 2,
+ .altset_idx = 1,
+ .attributes = UAC_EP_CS_ATTR_SAMPLE_RATE,
+ .endpoint = 0x85,
+ .ep_attr = USB_ENDPOINT_SYNC_SYNC,
+ .rates = SNDRV_PCM_RATE_48000,
+ .rate_min = 48000,
+ .rate_max = 48000,
+ .nr_rates = 1,
+ .rate_table = (unsigned int[]) {
+ 48000
+ }
+ }
+ },
+ {
+ .ifnum = 5,
+ .type = QUIRK_IGNORE_INTERFACE
+ },
+ {
+ .ifnum = 6,
+ .type = QUIRK_MIDI_MIDIMAN,
+ .data = &(const struct snd_usb_midi_endpoint_info) {
+ .out_ep = 0x02,
+ .out_cables = 0x0001,
+ .in_ep = 0x81,
+ .in_interval = 0x01,
+ .in_cables = 0x0001
+ }
+ },
+ {
+ .ifnum = -1
+ }
+ }
+ }
+},
+{
+ /* Tascam US122 MKII - playback-only support */
+ .match_flags = USB_DEVICE_ID_MATCH_DEVICE,
+ .idVendor = 0x0644,
+ .idProduct = 0x8021,
+ .bInterfaceClass = USB_CLASS_AUDIO,
+ .driver_info = (unsigned long) &(const struct snd_usb_audio_quirk) {
+ .vendor_name = "TASCAM",
+ .product_name = "US122 MKII",
+ .ifnum = QUIRK_ANY_INTERFACE,
+ .type = QUIRK_COMPOSITE,
+ .data = (const struct snd_usb_audio_quirk[]) {
+ {
+ .ifnum = 0,
+ .type = QUIRK_IGNORE_INTERFACE
+ },
+ {
+ .ifnum = 1,
+ .type = QUIRK_AUDIO_FIXED_ENDPOINT,
+ .data = &(const struct audioformat) {
+ .formats = SNDRV_PCM_FMTBIT_S24_3LE,
+ .channels = 2,
+ .iface = 1,
+ .altsetting = 1,
+ .altset_idx = 1,
+ .attributes = UAC_EP_CS_ATTR_SAMPLE_RATE,
+ .endpoint = 0x02,
+ .ep_attr = USB_ENDPOINT_XFER_ISOC,
+ .rates = SNDRV_PCM_RATE_44100 |
+ SNDRV_PCM_RATE_48000 |
+ SNDRV_PCM_RATE_88200 |
+ SNDRV_PCM_RATE_96000,
+ .rate_min = 44100,
+ .rate_max = 96000,
+ .nr_rates = 4,
+ .rate_table = (unsigned int[]) {
+ 44100, 48000, 88200, 96000
+ }
+ }
+ },
+ {
+ .ifnum = -1
+ }
+ }
+ }
+},
+
+/* Microsoft XboxLive Headset/Xbox Communicator */
+{
+ USB_DEVICE(0x045e, 0x0283),
+ .bInterfaceClass = USB_CLASS_PER_INTERFACE,
+ .driver_info = (unsigned long) &(const struct snd_usb_audio_quirk) {
+ .vendor_name = "Microsoft",
+ .product_name = "XboxLive Headset/Xbox Communicator",
+ .ifnum = QUIRK_ANY_INTERFACE,
+ .type = QUIRK_COMPOSITE,
+ .data = &(const struct snd_usb_audio_quirk[]) {
+ {
+ /* playback */
+ .ifnum = 0,
+ .type = QUIRK_AUDIO_FIXED_ENDPOINT,
+ .data = &(const struct audioformat) {
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ .channels = 1,
+ .iface = 0,
+ .altsetting = 0,
+ .altset_idx = 0,
+ .attributes = 0,
+ .endpoint = 0x04,
+ .ep_attr = 0x05,
+ .rates = SNDRV_PCM_RATE_CONTINUOUS,
+ .rate_min = 22050,
+ .rate_max = 22050
+ }
+ },
+ {
+ /* capture */
+ .ifnum = 1,
+ .type = QUIRK_AUDIO_FIXED_ENDPOINT,
+ .data = &(const struct audioformat) {
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ .channels = 1,
+ .iface = 1,
+ .altsetting = 0,
+ .altset_idx = 0,
+ .attributes = 0,
+ .endpoint = 0x85,
+ .ep_attr = 0x05,
+ .rates = SNDRV_PCM_RATE_CONTINUOUS,
+ .rate_min = 16000,
+ .rate_max = 16000
+ }
+ },
+ {
+ .ifnum = -1
+ }
+ }
+ }
+},
+
+/* Reloop Play */
+{
+ USB_DEVICE(0x200c, 0x100b),
+ .bInterfaceClass = USB_CLASS_PER_INTERFACE,
+ .driver_info = (unsigned long) &(const struct snd_usb_audio_quirk) {
+ .ifnum = QUIRK_ANY_INTERFACE,
+ .type = QUIRK_COMPOSITE,
+ .data = &(const struct snd_usb_audio_quirk[]) {
+ {
+ .ifnum = 0,
+ .type = QUIRK_AUDIO_STANDARD_MIXER,
+ },
+ {
+ .ifnum = 1,
+ .type = QUIRK_AUDIO_FIXED_ENDPOINT,
+ .data = &(const struct audioformat) {
+ .formats = SNDRV_PCM_FMTBIT_S24_3LE,
+ .channels = 4,
+ .iface = 1,
+ .altsetting = 1,
+ .altset_idx = 1,
+ .attributes = UAC_EP_CS_ATTR_SAMPLE_RATE,
+ .endpoint = 0x01,
+ .ep_attr = USB_ENDPOINT_SYNC_ADAPTIVE,
+ .rates = SNDRV_PCM_RATE_44100 |
+ SNDRV_PCM_RATE_48000,
+ .rate_min = 44100,
+ .rate_max = 48000,
+ .nr_rates = 2,
+ .rate_table = (unsigned int[]) {
+ 44100, 48000
+ }
+ }
+ },
+ {
+ .ifnum = -1
+ }
+ }
+ }
+},
+
+{
+ /*
+ * ZOOM R16/24 in audio interface mode.
+ * Mixer descriptors are garbage, further quirks will be needed
+ * to make any of it functional, thus disabled for now.
+ * Playback stream appears to start and run fine but no sound
+ * is produced, so also disabled for now.
+ */
+ USB_DEVICE(0x1686, 0x00dd),
+ .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+ .ifnum = QUIRK_ANY_INTERFACE,
+ .type = QUIRK_COMPOSITE,
+ .data = (const struct snd_usb_audio_quirk[]) {
+ {
+ /* Mixer */
+ .ifnum = 0,
+ .type = QUIRK_IGNORE_INTERFACE,
+ },
+ {
+ /* Playback */
+ .ifnum = 1,
+ .type = QUIRK_IGNORE_INTERFACE,
+ },
+ {
+ /* Capture */
+ .ifnum = 2,
+ .type = QUIRK_AUDIO_STANDARD_INTERFACE,
+ },
+ {
+ /* Midi */
+ .ifnum = 3,
+ .type = QUIRK_MIDI_STANDARD_INTERFACE
+ },
+ {
+ .ifnum = -1
+ },
+ }
+ }
+},
+
+{
+ /*
+ * Some USB MIDI devices don't have an audio control interface,
+ * so we have to grab MIDI streaming interfaces here.
+ */
+ .match_flags = USB_DEVICE_ID_MATCH_INT_CLASS |
+ USB_DEVICE_ID_MATCH_INT_SUBCLASS,
+ .bInterfaceClass = USB_CLASS_AUDIO,
+ .bInterfaceSubClass = USB_SUBCLASS_MIDISTREAMING,
+ .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+ .ifnum = QUIRK_ANY_INTERFACE,
+ .type = QUIRK_MIDI_STANDARD_INTERFACE
+ }
+},
+
+{
+ /*
+ * The original product_name is "USB Sound Device", however this name
+ * is also used by the CM106 based cards, so make it unique.
+ */
+ USB_DEVICE(0x0d8c, 0x0103),
+ .driver_info = (unsigned long) &(const struct snd_usb_audio_quirk) {
+ .product_name = "Audio Advantage MicroII",
+ .ifnum = QUIRK_NO_INTERFACE
+ }
+},
+
+#undef USB_DEVICE_VENDOR_SPEC
diff --git a/sound/usb/quirks.c b/sound/usb/quirks.c
new file mode 100644
index 000000000..754e68959
--- /dev/null
+++ b/sound/usb/quirks.c
@@ -0,0 +1,1287 @@
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/usb.h>
+#include <linux/usb/audio.h>
+#include <linux/usb/midi.h>
+
+#include <sound/control.h>
+#include <sound/core.h>
+#include <sound/info.h>
+#include <sound/pcm.h>
+
+#include "usbaudio.h"
+#include "card.h"
+#include "mixer.h"
+#include "mixer_quirks.h"
+#include "midi.h"
+#include "quirks.h"
+#include "helper.h"
+#include "endpoint.h"
+#include "pcm.h"
+#include "clock.h"
+#include "stream.h"
+
+/*
+ * handle the quirks for the contained interfaces
+ */
+static int create_composite_quirk(struct snd_usb_audio *chip,
+ struct usb_interface *iface,
+ struct usb_driver *driver,
+ const struct snd_usb_audio_quirk *quirk_comp)
+{
+ int probed_ifnum = get_iface_desc(iface->altsetting)->bInterfaceNumber;
+ const struct snd_usb_audio_quirk *quirk;
+ int err;
+
+ for (quirk = quirk_comp->data; quirk->ifnum >= 0; ++quirk) {
+ iface = usb_ifnum_to_if(chip->dev, quirk->ifnum);
+ if (!iface)
+ continue;
+ if (quirk->ifnum != probed_ifnum &&
+ usb_interface_claimed(iface))
+ continue;
+ err = snd_usb_create_quirk(chip, iface, driver, quirk);
+ if (err < 0)
+ return err;
+ }
+
+ for (quirk = quirk_comp->data; quirk->ifnum >= 0; ++quirk) {
+ iface = usb_ifnum_to_if(chip->dev, quirk->ifnum);
+ if (!iface)
+ continue;
+ if (quirk->ifnum != probed_ifnum &&
+ !usb_interface_claimed(iface))
+ usb_driver_claim_interface(driver, iface, (void *)-1L);
+ }
+
+ return 0;
+}
+
+static int ignore_interface_quirk(struct snd_usb_audio *chip,
+ struct usb_interface *iface,
+ struct usb_driver *driver,
+ const struct snd_usb_audio_quirk *quirk)
+{
+ return 0;
+}
+
+
+/*
+ * Allow alignment on audio sub-slot (channel samples) rather than
+ * on audio slots (audio frames)
+ */
+static int create_align_transfer_quirk(struct snd_usb_audio *chip,
+ struct usb_interface *iface,
+ struct usb_driver *driver,
+ const struct snd_usb_audio_quirk *quirk)
+{
+ chip->txfr_quirk = 1;
+ return 1; /* Continue with creating streams and mixer */
+}
+
+static int create_any_midi_quirk(struct snd_usb_audio *chip,
+ struct usb_interface *intf,
+ struct usb_driver *driver,
+ const struct snd_usb_audio_quirk *quirk)
+{
+ return snd_usbmidi_create(chip->card, intf, &chip->midi_list, quirk);
+}
+
+/*
+ * create a stream for an interface with proper descriptors
+ */
+static int create_standard_audio_quirk(struct snd_usb_audio *chip,
+ struct usb_interface *iface,
+ struct usb_driver *driver,
+ const struct snd_usb_audio_quirk *quirk)
+{
+ struct usb_host_interface *alts;
+ struct usb_interface_descriptor *altsd;
+ int err;
+
+ alts = &iface->altsetting[0];
+ altsd = get_iface_desc(alts);
+ err = snd_usb_parse_audio_interface(chip, altsd->bInterfaceNumber);
+ if (err < 0) {
+ usb_audio_err(chip, "cannot setup if %d: error %d\n",
+ altsd->bInterfaceNumber, err);
+ return err;
+ }
+ /* reset the current interface */
+ usb_set_interface(chip->dev, altsd->bInterfaceNumber, 0);
+ return 0;
+}
+
+/*
+ * create a stream for an endpoint/altsetting without proper descriptors
+ */
+static int create_fixed_stream_quirk(struct snd_usb_audio *chip,
+ struct usb_interface *iface,
+ struct usb_driver *driver,
+ const struct snd_usb_audio_quirk *quirk)
+{
+ struct audioformat *fp;
+ struct usb_host_interface *alts;
+ struct usb_interface_descriptor *altsd;
+ int stream, err;
+ unsigned *rate_table = NULL;
+
+ fp = kmemdup(quirk->data, sizeof(*fp), GFP_KERNEL);
+ if (!fp) {
+ usb_audio_err(chip, "cannot memdup\n");
+ return -ENOMEM;
+ }
+ if (fp->nr_rates > MAX_NR_RATES) {
+ kfree(fp);
+ return -EINVAL;
+ }
+ if (fp->nr_rates > 0) {
+ rate_table = kmemdup(fp->rate_table,
+ sizeof(int) * fp->nr_rates, GFP_KERNEL);
+ if (!rate_table) {
+ kfree(fp);
+ return -ENOMEM;
+ }
+ fp->rate_table = rate_table;
+ }
+
+ stream = (fp->endpoint & USB_DIR_IN)
+ ? SNDRV_PCM_STREAM_CAPTURE : SNDRV_PCM_STREAM_PLAYBACK;
+ err = snd_usb_add_audio_stream(chip, stream, fp);
+ if (err < 0) {
+ 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];
+ altsd = get_iface_desc(alts);
+ fp->protocol = altsd->bInterfaceProtocol;
+
+ if (fp->datainterval == 0)
+ fp->datainterval = snd_usb_parse_datainterval(chip, alts);
+ if (fp->maxpacksize == 0)
+ fp->maxpacksize = le16_to_cpu(get_endpoint(alts, 0)->wMaxPacketSize);
+ usb_set_interface(chip->dev, fp->iface, 0);
+ snd_usb_init_pitch(chip, fp->iface, alts, fp);
+ snd_usb_init_sample_rate(chip, fp->iface, alts, fp, fp->rate_max);
+ return 0;
+}
+
+static int create_auto_pcm_quirk(struct snd_usb_audio *chip,
+ struct usb_interface *iface,
+ struct usb_driver *driver)
+{
+ struct usb_host_interface *alts;
+ struct usb_interface_descriptor *altsd;
+ struct usb_endpoint_descriptor *epd;
+ struct uac1_as_header_descriptor *ashd;
+ struct uac_format_type_i_discrete_descriptor *fmtd;
+
+ /*
+ * Most Roland/Yamaha audio streaming interfaces have more or less
+ * standard descriptors, but older devices might lack descriptors, and
+ * future ones might change, so ensure that we fail silently if the
+ * interface doesn't look exactly right.
+ */
+
+ /* must have a non-zero altsetting for streaming */
+ if (iface->num_altsetting < 2)
+ return -ENODEV;
+ alts = &iface->altsetting[1];
+ altsd = get_iface_desc(alts);
+
+ /* must have an isochronous endpoint for streaming */
+ if (altsd->bNumEndpoints < 1)
+ return -ENODEV;
+ epd = get_endpoint(alts, 0);
+ if (!usb_endpoint_xfer_isoc(epd))
+ return -ENODEV;
+
+ /* must have format descriptors */
+ ashd = snd_usb_find_csint_desc(alts->extra, alts->extralen, NULL,
+ UAC_AS_GENERAL);
+ fmtd = snd_usb_find_csint_desc(alts->extra, alts->extralen, NULL,
+ UAC_FORMAT_TYPE);
+ if (!ashd || ashd->bLength < 7 ||
+ !fmtd || fmtd->bLength < 8)
+ return -ENODEV;
+
+ return create_standard_audio_quirk(chip, iface, driver, NULL);
+}
+
+static int create_yamaha_midi_quirk(struct snd_usb_audio *chip,
+ struct usb_interface *iface,
+ struct usb_driver *driver,
+ struct usb_host_interface *alts)
+{
+ static const struct snd_usb_audio_quirk yamaha_midi_quirk = {
+ .type = QUIRK_MIDI_YAMAHA
+ };
+ struct usb_midi_in_jack_descriptor *injd;
+ struct usb_midi_out_jack_descriptor *outjd;
+
+ /* must have some valid jack descriptors */
+ injd = snd_usb_find_csint_desc(alts->extra, alts->extralen,
+ NULL, USB_MS_MIDI_IN_JACK);
+ outjd = snd_usb_find_csint_desc(alts->extra, alts->extralen,
+ NULL, USB_MS_MIDI_OUT_JACK);
+ if (!injd && !outjd)
+ return -ENODEV;
+ if (injd && (injd->bLength < 5 ||
+ (injd->bJackType != USB_MS_EMBEDDED &&
+ injd->bJackType != USB_MS_EXTERNAL)))
+ return -ENODEV;
+ if (outjd && (outjd->bLength < 6 ||
+ (outjd->bJackType != USB_MS_EMBEDDED &&
+ outjd->bJackType != USB_MS_EXTERNAL)))
+ return -ENODEV;
+ return create_any_midi_quirk(chip, iface, driver, &yamaha_midi_quirk);
+}
+
+static int create_roland_midi_quirk(struct snd_usb_audio *chip,
+ struct usb_interface *iface,
+ struct usb_driver *driver,
+ struct usb_host_interface *alts)
+{
+ static const struct snd_usb_audio_quirk roland_midi_quirk = {
+ .type = QUIRK_MIDI_ROLAND
+ };
+ u8 *roland_desc = NULL;
+
+ /* might have a vendor-specific descriptor <06 24 F1 02 ...> */
+ for (;;) {
+ roland_desc = snd_usb_find_csint_desc(alts->extra,
+ alts->extralen,
+ roland_desc, 0xf1);
+ if (!roland_desc)
+ return -ENODEV;
+ if (roland_desc[0] < 6 || roland_desc[3] != 2)
+ continue;
+ return create_any_midi_quirk(chip, iface, driver,
+ &roland_midi_quirk);
+ }
+}
+
+static int create_std_midi_quirk(struct snd_usb_audio *chip,
+ struct usb_interface *iface,
+ struct usb_driver *driver,
+ struct usb_host_interface *alts)
+{
+ struct usb_ms_header_descriptor *mshd;
+ struct usb_ms_endpoint_descriptor *msepd;
+
+ /* must have the MIDIStreaming interface header descriptor*/
+ mshd = (struct usb_ms_header_descriptor *)alts->extra;
+ if (alts->extralen < 7 ||
+ mshd->bLength < 7 ||
+ mshd->bDescriptorType != USB_DT_CS_INTERFACE ||
+ mshd->bDescriptorSubtype != USB_MS_HEADER)
+ return -ENODEV;
+ /* must have the MIDIStreaming endpoint descriptor*/
+ msepd = (struct usb_ms_endpoint_descriptor *)alts->endpoint[0].extra;
+ if (alts->endpoint[0].extralen < 4 ||
+ msepd->bLength < 4 ||
+ msepd->bDescriptorType != USB_DT_CS_ENDPOINT ||
+ msepd->bDescriptorSubtype != UAC_MS_GENERAL ||
+ msepd->bNumEmbMIDIJack < 1 ||
+ msepd->bNumEmbMIDIJack > 16)
+ return -ENODEV;
+
+ return create_any_midi_quirk(chip, iface, driver, NULL);
+}
+
+static int create_auto_midi_quirk(struct snd_usb_audio *chip,
+ struct usb_interface *iface,
+ struct usb_driver *driver)
+{
+ struct usb_host_interface *alts;
+ struct usb_interface_descriptor *altsd;
+ struct usb_endpoint_descriptor *epd;
+ int err;
+
+ alts = &iface->altsetting[0];
+ altsd = get_iface_desc(alts);
+
+ /* must have at least one bulk/interrupt endpoint for streaming */
+ if (altsd->bNumEndpoints < 1)
+ return -ENODEV;
+ epd = get_endpoint(alts, 0);
+ if (!usb_endpoint_xfer_bulk(epd) &&
+ !usb_endpoint_xfer_int(epd))
+ return -ENODEV;
+
+ switch (USB_ID_VENDOR(chip->usb_id)) {
+ case 0x0499: /* Yamaha */
+ err = create_yamaha_midi_quirk(chip, iface, driver, alts);
+ if (err != -ENODEV)
+ return err;
+ break;
+ case 0x0582: /* Roland */
+ err = create_roland_midi_quirk(chip, iface, driver, alts);
+ if (err != -ENODEV)
+ return err;
+ break;
+ }
+
+ return create_std_midi_quirk(chip, iface, driver, alts);
+}
+
+static int create_autodetect_quirk(struct snd_usb_audio *chip,
+ struct usb_interface *iface,
+ struct usb_driver *driver)
+{
+ int err;
+
+ err = create_auto_pcm_quirk(chip, iface, driver);
+ if (err == -ENODEV)
+ err = create_auto_midi_quirk(chip, iface, driver);
+ return err;
+}
+
+static int create_autodetect_quirks(struct snd_usb_audio *chip,
+ struct usb_interface *iface,
+ struct usb_driver *driver,
+ const struct snd_usb_audio_quirk *quirk)
+{
+ int probed_ifnum = get_iface_desc(iface->altsetting)->bInterfaceNumber;
+ int ifcount, ifnum, err;
+
+ err = create_autodetect_quirk(chip, iface, driver);
+ if (err < 0)
+ return err;
+
+ /*
+ * ALSA PCM playback/capture devices cannot be registered in two steps,
+ * so we have to claim the other corresponding interface here.
+ */
+ ifcount = chip->dev->actconfig->desc.bNumInterfaces;
+ for (ifnum = 0; ifnum < ifcount; ifnum++) {
+ if (ifnum == probed_ifnum || quirk->ifnum >= 0)
+ continue;
+ iface = usb_ifnum_to_if(chip->dev, ifnum);
+ if (!iface ||
+ usb_interface_claimed(iface) ||
+ get_iface_desc(iface->altsetting)->bInterfaceClass !=
+ USB_CLASS_VENDOR_SPEC)
+ continue;
+
+ err = create_autodetect_quirk(chip, iface, driver);
+ if (err >= 0)
+ usb_driver_claim_interface(driver, iface, (void *)-1L);
+ }
+
+ return 0;
+}
+
+/*
+ * Create a stream for an Edirol UA-700/UA-25/UA-4FX interface.
+ * The only way to detect the sample rate is by looking at wMaxPacketSize.
+ */
+static int create_uaxx_quirk(struct snd_usb_audio *chip,
+ struct usb_interface *iface,
+ struct usb_driver *driver,
+ const struct snd_usb_audio_quirk *quirk)
+{
+ static const struct audioformat ua_format = {
+ .formats = SNDRV_PCM_FMTBIT_S24_3LE,
+ .channels = 2,
+ .fmt_type = UAC_FORMAT_TYPE_I,
+ .altsetting = 1,
+ .altset_idx = 1,
+ .rates = SNDRV_PCM_RATE_CONTINUOUS,
+ };
+ struct usb_host_interface *alts;
+ struct usb_interface_descriptor *altsd;
+ struct audioformat *fp;
+ int stream, err;
+
+ /* both PCM and MIDI interfaces have 2 or more altsettings */
+ if (iface->num_altsetting < 2)
+ return -ENXIO;
+ alts = &iface->altsetting[1];
+ altsd = get_iface_desc(alts);
+
+ if (altsd->bNumEndpoints == 2) {
+ static const struct snd_usb_midi_endpoint_info ua700_ep = {
+ .out_cables = 0x0003,
+ .in_cables = 0x0003
+ };
+ static const struct snd_usb_audio_quirk ua700_quirk = {
+ .type = QUIRK_MIDI_FIXED_ENDPOINT,
+ .data = &ua700_ep
+ };
+ static const struct snd_usb_midi_endpoint_info uaxx_ep = {
+ .out_cables = 0x0001,
+ .in_cables = 0x0001
+ };
+ static const struct snd_usb_audio_quirk uaxx_quirk = {
+ .type = QUIRK_MIDI_FIXED_ENDPOINT,
+ .data = &uaxx_ep
+ };
+ const struct snd_usb_audio_quirk *quirk =
+ chip->usb_id == USB_ID(0x0582, 0x002b)
+ ? &ua700_quirk : &uaxx_quirk;
+ return snd_usbmidi_create(chip->card, iface,
+ &chip->midi_list, quirk);
+ }
+
+ if (altsd->bNumEndpoints != 1)
+ return -ENXIO;
+
+ fp = kmemdup(&ua_format, sizeof(*fp), GFP_KERNEL);
+ if (!fp)
+ return -ENOMEM;
+
+ fp->iface = altsd->bInterfaceNumber;
+ fp->endpoint = get_endpoint(alts, 0)->bEndpointAddress;
+ fp->ep_attr = get_endpoint(alts, 0)->bmAttributes;
+ fp->datainterval = 0;
+ fp->maxpacksize = le16_to_cpu(get_endpoint(alts, 0)->wMaxPacketSize);
+
+ switch (fp->maxpacksize) {
+ case 0x120:
+ fp->rate_max = fp->rate_min = 44100;
+ break;
+ case 0x138:
+ case 0x140:
+ fp->rate_max = fp->rate_min = 48000;
+ break;
+ case 0x258:
+ case 0x260:
+ fp->rate_max = fp->rate_min = 96000;
+ break;
+ default:
+ usb_audio_err(chip, "unknown sample rate\n");
+ kfree(fp);
+ return -ENXIO;
+ }
+
+ stream = (fp->endpoint & USB_DIR_IN)
+ ? SNDRV_PCM_STREAM_CAPTURE : SNDRV_PCM_STREAM_PLAYBACK;
+ err = snd_usb_add_audio_stream(chip, stream, fp);
+ if (err < 0) {
+ kfree(fp);
+ return err;
+ }
+ usb_set_interface(chip->dev, fp->iface, 0);
+ return 0;
+}
+
+/*
+ * Create a standard mixer for the specified interface.
+ */
+static int create_standard_mixer_quirk(struct snd_usb_audio *chip,
+ struct usb_interface *iface,
+ struct usb_driver *driver,
+ const struct snd_usb_audio_quirk *quirk)
+{
+ if (quirk->ifnum < 0)
+ return 0;
+
+ return snd_usb_create_mixer(chip, quirk->ifnum, 0);
+}
+
+/*
+ * audio-interface quirks
+ *
+ * returns zero if no standard audio/MIDI parsing is needed.
+ * returns a positive value if standard audio/midi interfaces are parsed
+ * after this.
+ * returns a negative value at error.
+ */
+int snd_usb_create_quirk(struct snd_usb_audio *chip,
+ struct usb_interface *iface,
+ struct usb_driver *driver,
+ const struct snd_usb_audio_quirk *quirk)
+{
+ typedef int (*quirk_func_t)(struct snd_usb_audio *,
+ struct usb_interface *,
+ struct usb_driver *,
+ const struct snd_usb_audio_quirk *);
+ static const quirk_func_t quirk_funcs[] = {
+ [QUIRK_IGNORE_INTERFACE] = ignore_interface_quirk,
+ [QUIRK_COMPOSITE] = create_composite_quirk,
+ [QUIRK_AUTODETECT] = create_autodetect_quirks,
+ [QUIRK_MIDI_STANDARD_INTERFACE] = create_any_midi_quirk,
+ [QUIRK_MIDI_FIXED_ENDPOINT] = create_any_midi_quirk,
+ [QUIRK_MIDI_YAMAHA] = create_any_midi_quirk,
+ [QUIRK_MIDI_ROLAND] = create_any_midi_quirk,
+ [QUIRK_MIDI_MIDIMAN] = create_any_midi_quirk,
+ [QUIRK_MIDI_NOVATION] = create_any_midi_quirk,
+ [QUIRK_MIDI_RAW_BYTES] = create_any_midi_quirk,
+ [QUIRK_MIDI_EMAGIC] = create_any_midi_quirk,
+ [QUIRK_MIDI_CME] = create_any_midi_quirk,
+ [QUIRK_MIDI_AKAI] = create_any_midi_quirk,
+ [QUIRK_MIDI_FTDI] = create_any_midi_quirk,
+ [QUIRK_AUDIO_STANDARD_INTERFACE] = create_standard_audio_quirk,
+ [QUIRK_AUDIO_FIXED_ENDPOINT] = create_fixed_stream_quirk,
+ [QUIRK_AUDIO_EDIROL_UAXX] = create_uaxx_quirk,
+ [QUIRK_AUDIO_ALIGN_TRANSFER] = create_align_transfer_quirk,
+ [QUIRK_AUDIO_STANDARD_MIXER] = create_standard_mixer_quirk,
+ };
+
+ if (quirk->type < QUIRK_TYPE_COUNT) {
+ return quirk_funcs[quirk->type](chip, iface, driver, quirk);
+ } else {
+ usb_audio_err(chip, "invalid quirk type %d\n", quirk->type);
+ return -ENXIO;
+ }
+}
+
+/*
+ * boot quirks
+ */
+
+#define EXTIGY_FIRMWARE_SIZE_OLD 794
+#define EXTIGY_FIRMWARE_SIZE_NEW 483
+
+static int snd_usb_extigy_boot_quirk(struct usb_device *dev, struct usb_interface *intf)
+{
+ struct usb_host_config *config = dev->actconfig;
+ int err;
+
+ if (le16_to_cpu(get_cfg_desc(config)->wTotalLength) == EXTIGY_FIRMWARE_SIZE_OLD ||
+ le16_to_cpu(get_cfg_desc(config)->wTotalLength) == EXTIGY_FIRMWARE_SIZE_NEW) {
+ dev_dbg(&dev->dev, "sending Extigy boot sequence...\n");
+ /* Send message to force it to reconnect with full interface. */
+ err = snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev,0),
+ 0x10, 0x43, 0x0001, 0x000a, NULL, 0);
+ if (err < 0)
+ dev_dbg(&dev->dev, "error sending boot message: %d\n", err);
+ err = usb_get_descriptor(dev, USB_DT_DEVICE, 0,
+ &dev->descriptor, sizeof(dev->descriptor));
+ config = dev->actconfig;
+ if (err < 0)
+ dev_dbg(&dev->dev, "error usb_get_descriptor: %d\n", err);
+ err = usb_reset_configuration(dev);
+ if (err < 0)
+ dev_dbg(&dev->dev, "error usb_reset_configuration: %d\n", err);
+ dev_dbg(&dev->dev, "extigy_boot: new boot length = %d\n",
+ le16_to_cpu(get_cfg_desc(config)->wTotalLength));
+ return -ENODEV; /* quit this anyway */
+ }
+ return 0;
+}
+
+static int snd_usb_audigy2nx_boot_quirk(struct usb_device *dev)
+{
+ u8 buf = 1;
+
+ snd_usb_ctl_msg(dev, usb_rcvctrlpipe(dev, 0), 0x2a,
+ USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_OTHER,
+ 0, 0, &buf, 1);
+ if (buf == 0) {
+ snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), 0x29,
+ USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_OTHER,
+ 1, 2000, NULL, 0);
+ return -ENODEV;
+ }
+ return 0;
+}
+
+static int snd_usb_fasttrackpro_boot_quirk(struct usb_device *dev)
+{
+ int err;
+
+ if (dev->actconfig->desc.bConfigurationValue == 1) {
+ dev_info(&dev->dev,
+ "Fast Track Pro switching to config #2\n");
+ /* This function has to be available by the usb core module.
+ * if it is not avialable the boot quirk has to be left out
+ * and the configuration has to be set by udev or hotplug
+ * rules
+ */
+ err = usb_driver_set_configuration(dev, 2);
+ if (err < 0)
+ dev_dbg(&dev->dev,
+ "error usb_driver_set_configuration: %d\n",
+ err);
+ /* Always return an error, so that we stop creating a device
+ that will just be destroyed and recreated with a new
+ configuration */
+ return -ENODEV;
+ } else
+ dev_info(&dev->dev, "Fast Track Pro config OK\n");
+
+ return 0;
+}
+
+/*
+ * C-Media CM106/CM106+ have four 16-bit internal registers that are nicely
+ * documented in the device's data sheet.
+ */
+static int snd_usb_cm106_write_int_reg(struct usb_device *dev, int reg, u16 value)
+{
+ u8 buf[4];
+ buf[0] = 0x20;
+ buf[1] = value & 0xff;
+ buf[2] = (value >> 8) & 0xff;
+ buf[3] = reg;
+ return snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), USB_REQ_SET_CONFIGURATION,
+ USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_ENDPOINT,
+ 0, 0, &buf, 4);
+}
+
+static int snd_usb_cm106_boot_quirk(struct usb_device *dev)
+{
+ /*
+ * Enable line-out driver mode, set headphone source to front
+ * channels, enable stereo mic.
+ */
+ return snd_usb_cm106_write_int_reg(dev, 2, 0x8004);
+}
+
+/*
+ * C-Media CM6206 is based on CM106 with two additional
+ * registers that are not documented in the data sheet.
+ * Values here are chosen based on sniffing USB traffic
+ * under Windows.
+ */
+static int snd_usb_cm6206_boot_quirk(struct usb_device *dev)
+{
+ int err = 0, reg;
+ int val[] = {0x2004, 0x3000, 0xf800, 0x143f, 0x0000, 0x3000};
+
+ for (reg = 0; reg < ARRAY_SIZE(val); reg++) {
+ err = snd_usb_cm106_write_int_reg(dev, reg, val[reg]);
+ if (err < 0)
+ return err;
+ }
+
+ return err;
+}
+
+/* quirk for Plantronics GameCom 780 with CM6302 chip */
+static int snd_usb_gamecon780_boot_quirk(struct usb_device *dev)
+{
+ /* set the initial volume and don't change; other values are either
+ * too loud or silent due to firmware bug (bko#65251)
+ */
+ u8 buf[2] = { 0x74, 0xe3 };
+ return snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), UAC_SET_CUR,
+ USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_OUT,
+ UAC_FU_VOLUME << 8, 9 << 8, buf, 2);
+}
+
+/*
+ * Novation Twitch DJ controller
+ * Focusrite Novation Saffire 6 USB audio card
+ */
+static int snd_usb_novation_boot_quirk(struct usb_device *dev)
+{
+ /* preemptively set up the device because otherwise the
+ * raw MIDI endpoints are not active */
+ usb_set_interface(dev, 0, 1);
+ return 0;
+}
+
+/*
+ * This call will put the synth in "USB send" mode, i.e it will send MIDI
+ * messages through USB (this is disabled at startup). The synth will
+ * acknowledge by sending a sysex on endpoint 0x85 and by displaying a USB
+ * sign on its LCD. Values here are chosen based on sniffing USB traffic
+ * under Windows.
+ */
+static int snd_usb_accessmusic_boot_quirk(struct usb_device *dev)
+{
+ int err, actual_length;
+
+ /* "midi send" enable */
+ static const u8 seq[] = { 0x4e, 0x73, 0x52, 0x01 };
+
+ void *buf = kmemdup(seq, ARRAY_SIZE(seq), GFP_KERNEL);
+ if (!buf)
+ return -ENOMEM;
+ err = usb_interrupt_msg(dev, usb_sndintpipe(dev, 0x05), buf,
+ ARRAY_SIZE(seq), &actual_length, 1000);
+ kfree(buf);
+ if (err < 0)
+ return err;
+
+ return 0;
+}
+
+/*
+ * Some sound cards from Native Instruments are in fact compliant to the USB
+ * audio standard of version 2 and other approved USB standards, even though
+ * they come up as vendor-specific device when first connected.
+ *
+ * However, they can be told to come up with a new set of descriptors
+ * upon their next enumeration, and the interfaces announced by the new
+ * descriptors will then be handled by the kernel's class drivers. As the
+ * product ID will also change, no further checks are required.
+ */
+
+static int snd_usb_nativeinstruments_boot_quirk(struct usb_device *dev)
+{
+ int ret = usb_control_msg(dev, usb_sndctrlpipe(dev, 0),
+ 0xaf, USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+ 1, 0, NULL, 0, 1000);
+
+ if (ret < 0)
+ return ret;
+
+ usb_reset_device(dev);
+
+ /* return -EAGAIN, so the creation of an audio interface for this
+ * temporary device is aborted. The device will reconnect with a
+ * new product ID */
+ return -EAGAIN;
+}
+
+static void mbox2_setup_48_24_magic(struct usb_device *dev)
+{
+ u8 srate[3];
+ u8 temp[12];
+
+ /* Choose 48000Hz permanently */
+ srate[0] = 0x80;
+ srate[1] = 0xbb;
+ srate[2] = 0x00;
+
+ /* Send the magic! */
+ snd_usb_ctl_msg(dev, usb_rcvctrlpipe(dev, 0),
+ 0x01, 0x22, 0x0100, 0x0085, &temp, 0x0003);
+ snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0),
+ 0x81, 0xa2, 0x0100, 0x0085, &srate, 0x0003);
+ snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0),
+ 0x81, 0xa2, 0x0100, 0x0086, &srate, 0x0003);
+ snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0),
+ 0x81, 0xa2, 0x0100, 0x0003, &srate, 0x0003);
+ return;
+}
+
+/* Digidesign Mbox 2 needs to load firmware onboard
+ * and driver must wait a few seconds for initialisation.
+ */
+
+#define MBOX2_FIRMWARE_SIZE 646
+#define MBOX2_BOOT_LOADING 0x01 /* Hard coded into the device */
+#define MBOX2_BOOT_READY 0x02 /* Hard coded into the device */
+
+static int snd_usb_mbox2_boot_quirk(struct usb_device *dev)
+{
+ struct usb_host_config *config = dev->actconfig;
+ int err;
+ u8 bootresponse[0x12];
+ int fwsize;
+ int count;
+
+ fwsize = le16_to_cpu(get_cfg_desc(config)->wTotalLength);
+
+ if (fwsize != MBOX2_FIRMWARE_SIZE) {
+ dev_err(&dev->dev, "Invalid firmware size=%d.\n", fwsize);
+ return -ENODEV;
+ }
+
+ dev_dbg(&dev->dev, "Sending Digidesign Mbox 2 boot sequence...\n");
+
+ count = 0;
+ bootresponse[0] = MBOX2_BOOT_LOADING;
+ while ((bootresponse[0] == MBOX2_BOOT_LOADING) && (count < 10)) {
+ msleep(500); /* 0.5 second delay */
+ snd_usb_ctl_msg(dev, usb_rcvctrlpipe(dev, 0),
+ /* Control magic - load onboard firmware */
+ 0x85, 0xc0, 0x0001, 0x0000, &bootresponse, 0x0012);
+ if (bootresponse[0] == MBOX2_BOOT_READY)
+ break;
+ dev_dbg(&dev->dev, "device not ready, resending boot sequence...\n");
+ count++;
+ }
+
+ if (bootresponse[0] != MBOX2_BOOT_READY) {
+ dev_err(&dev->dev, "Unknown bootresponse=%d, or timed out, ignoring device.\n", bootresponse[0]);
+ return -ENODEV;
+ }
+
+ dev_dbg(&dev->dev, "device initialised!\n");
+
+ err = usb_get_descriptor(dev, USB_DT_DEVICE, 0,
+ &dev->descriptor, sizeof(dev->descriptor));
+ config = dev->actconfig;
+ if (err < 0)
+ dev_dbg(&dev->dev, "error usb_get_descriptor: %d\n", err);
+
+ err = usb_reset_configuration(dev);
+ if (err < 0)
+ dev_dbg(&dev->dev, "error usb_reset_configuration: %d\n", err);
+ dev_dbg(&dev->dev, "mbox2_boot: new boot length = %d\n",
+ le16_to_cpu(get_cfg_desc(config)->wTotalLength));
+
+ mbox2_setup_48_24_magic(dev);
+
+ dev_info(&dev->dev, "Digidesign Mbox 2: 24bit 48kHz");
+
+ return 0; /* Successful boot */
+}
+
+/*
+ * Setup quirks
+ */
+#define MAUDIO_SET 0x01 /* parse device_setup */
+#define MAUDIO_SET_COMPATIBLE 0x80 /* use only "win-compatible" interfaces */
+#define MAUDIO_SET_DTS 0x02 /* enable DTS Digital Output */
+#define MAUDIO_SET_96K 0x04 /* 48-96KHz rate if set, 8-48KHz otherwise */
+#define MAUDIO_SET_24B 0x08 /* 24bits sample if set, 16bits otherwise */
+#define MAUDIO_SET_DI 0x10 /* enable Digital Input */
+#define MAUDIO_SET_MASK 0x1f /* bit mask for setup value */
+#define MAUDIO_SET_24B_48K_DI 0x19 /* 24bits+48KHz+Digital Input */
+#define MAUDIO_SET_24B_48K_NOTDI 0x09 /* 24bits+48KHz+No Digital Input */
+#define MAUDIO_SET_16B_48K_DI 0x11 /* 16bits+48KHz+Digital Input */
+#define MAUDIO_SET_16B_48K_NOTDI 0x01 /* 16bits+48KHz+No Digital Input */
+
+static int quattro_skip_setting_quirk(struct snd_usb_audio *chip,
+ int iface, int altno)
+{
+ /* Reset ALL ifaces to 0 altsetting.
+ * Call it for every possible altsetting of every interface.
+ */
+ usb_set_interface(chip->dev, iface, 0);
+ if (chip->setup & MAUDIO_SET) {
+ if (chip->setup & MAUDIO_SET_COMPATIBLE) {
+ if (iface != 1 && iface != 2)
+ return 1; /* skip all interfaces but 1 and 2 */
+ } else {
+ unsigned int mask;
+ if (iface == 1 || iface == 2)
+ return 1; /* skip interfaces 1 and 2 */
+ if ((chip->setup & MAUDIO_SET_96K) && altno != 1)
+ return 1; /* skip this altsetting */
+ mask = chip->setup & MAUDIO_SET_MASK;
+ if (mask == MAUDIO_SET_24B_48K_DI && altno != 2)
+ return 1; /* skip this altsetting */
+ if (mask == MAUDIO_SET_24B_48K_NOTDI && altno != 3)
+ return 1; /* skip this altsetting */
+ if (mask == MAUDIO_SET_16B_48K_NOTDI && altno != 4)
+ return 1; /* skip this altsetting */
+ }
+ }
+ usb_audio_dbg(chip,
+ "using altsetting %d for interface %d config %d\n",
+ altno, iface, chip->setup);
+ return 0; /* keep this altsetting */
+}
+
+static int audiophile_skip_setting_quirk(struct snd_usb_audio *chip,
+ int iface,
+ int altno)
+{
+ /* Reset ALL ifaces to 0 altsetting.
+ * Call it for every possible altsetting of every interface.
+ */
+ usb_set_interface(chip->dev, iface, 0);
+
+ if (chip->setup & MAUDIO_SET) {
+ unsigned int mask;
+ if ((chip->setup & MAUDIO_SET_DTS) && altno != 6)
+ return 1; /* skip this altsetting */
+ if ((chip->setup & MAUDIO_SET_96K) && altno != 1)
+ return 1; /* skip this altsetting */
+ mask = chip->setup & MAUDIO_SET_MASK;
+ if (mask == MAUDIO_SET_24B_48K_DI && altno != 2)
+ return 1; /* skip this altsetting */
+ if (mask == MAUDIO_SET_24B_48K_NOTDI && altno != 3)
+ return 1; /* skip this altsetting */
+ if (mask == MAUDIO_SET_16B_48K_DI && altno != 4)
+ return 1; /* skip this altsetting */
+ if (mask == MAUDIO_SET_16B_48K_NOTDI && altno != 5)
+ return 1; /* skip this altsetting */
+ }
+
+ return 0; /* keep this altsetting */
+}
+
+static int fasttrackpro_skip_setting_quirk(struct snd_usb_audio *chip,
+ int iface, int altno)
+{
+ /* Reset ALL ifaces to 0 altsetting.
+ * Call it for every possible altsetting of every interface.
+ */
+ usb_set_interface(chip->dev, iface, 0);
+
+ /* possible configuration where both inputs and only one output is
+ *used is not supported by the current setup
+ */
+ if (chip->setup & (MAUDIO_SET | MAUDIO_SET_24B)) {
+ if (chip->setup & MAUDIO_SET_96K) {
+ if (altno != 3 && altno != 6)
+ return 1;
+ } else if (chip->setup & MAUDIO_SET_DI) {
+ if (iface == 4)
+ return 1; /* no analog input */
+ if (altno != 2 && altno != 5)
+ return 1; /* enable only altsets 2 and 5 */
+ } else {
+ if (iface == 5)
+ return 1; /* disable digialt input */
+ if (altno != 2 && altno != 5)
+ return 1; /* enalbe only altsets 2 and 5 */
+ }
+ } else {
+ /* keep only 16-Bit mode */
+ if (altno != 1)
+ return 1;
+ }
+
+ usb_audio_dbg(chip,
+ "using altsetting %d for interface %d config %d\n",
+ altno, iface, chip->setup);
+ return 0; /* keep this altsetting */
+}
+
+int snd_usb_apply_interface_quirk(struct snd_usb_audio *chip,
+ int iface,
+ int altno)
+{
+ /* audiophile usb: skip altsets incompatible with device_setup */
+ if (chip->usb_id == USB_ID(0x0763, 0x2003))
+ return audiophile_skip_setting_quirk(chip, iface, altno);
+ /* quattro usb: skip altsets incompatible with device_setup */
+ if (chip->usb_id == USB_ID(0x0763, 0x2001))
+ return quattro_skip_setting_quirk(chip, iface, altno);
+ /* fasttrackpro usb: skip altsets incompatible with device_setup */
+ if (chip->usb_id == USB_ID(0x0763, 0x2012))
+ return fasttrackpro_skip_setting_quirk(chip, iface, altno);
+
+ return 0;
+}
+
+int snd_usb_apply_boot_quirk(struct usb_device *dev,
+ struct usb_interface *intf,
+ const struct snd_usb_audio_quirk *quirk)
+{
+ u32 id = USB_ID(le16_to_cpu(dev->descriptor.idVendor),
+ le16_to_cpu(dev->descriptor.idProduct));
+
+ switch (id) {
+ case USB_ID(0x041e, 0x3000):
+ /* SB Extigy needs special boot-up sequence */
+ /* if more models come, this will go to the quirk list. */
+ return snd_usb_extigy_boot_quirk(dev, intf);
+
+ case USB_ID(0x041e, 0x3020):
+ /* SB Audigy 2 NX needs its own boot-up magic, too */
+ return snd_usb_audigy2nx_boot_quirk(dev);
+
+ case USB_ID(0x10f5, 0x0200):
+ /* C-Media CM106 / Turtle Beach Audio Advantage Roadie */
+ return snd_usb_cm106_boot_quirk(dev);
+
+ case USB_ID(0x0d8c, 0x0102):
+ /* C-Media CM6206 / CM106-Like Sound Device */
+ case USB_ID(0x0ccd, 0x00b1): /* Terratec Aureon 7.1 USB */
+ return snd_usb_cm6206_boot_quirk(dev);
+
+ case USB_ID(0x0dba, 0x3000):
+ /* Digidesign Mbox 2 */
+ return snd_usb_mbox2_boot_quirk(dev);
+
+ case USB_ID(0x1235, 0x0010): /* Focusrite Novation Saffire 6 USB */
+ case USB_ID(0x1235, 0x0018): /* Focusrite Novation Twitch */
+ return snd_usb_novation_boot_quirk(dev);
+
+ case USB_ID(0x133e, 0x0815):
+ /* Access Music VirusTI Desktop */
+ return snd_usb_accessmusic_boot_quirk(dev);
+
+ case USB_ID(0x17cc, 0x1000): /* Komplete Audio 6 */
+ case USB_ID(0x17cc, 0x1010): /* Traktor Audio 6 */
+ case USB_ID(0x17cc, 0x1020): /* Traktor Audio 10 */
+ return snd_usb_nativeinstruments_boot_quirk(dev);
+ case USB_ID(0x0763, 0x2012): /* M-Audio Fast Track Pro USB */
+ return snd_usb_fasttrackpro_boot_quirk(dev);
+ case USB_ID(0x047f, 0xc010): /* Plantronics Gamecom 780 */
+ return snd_usb_gamecon780_boot_quirk(dev);
+ }
+
+ return 0;
+}
+
+/*
+ * check if the device uses big-endian samples
+ */
+int snd_usb_is_big_endian_format(struct snd_usb_audio *chip, struct audioformat *fp)
+{
+ /* it depends on altsetting whether the device is big-endian or not */
+ switch (chip->usb_id) {
+ case USB_ID(0x0763, 0x2001): /* M-Audio Quattro: captured data only */
+ if (fp->altsetting == 2 || fp->altsetting == 3 ||
+ fp->altsetting == 5 || fp->altsetting == 6)
+ return 1;
+ break;
+ case USB_ID(0x0763, 0x2003): /* M-Audio Audiophile USB */
+ if (chip->setup == 0x00 ||
+ fp->altsetting == 1 || fp->altsetting == 2 ||
+ fp->altsetting == 3)
+ return 1;
+ break;
+ case USB_ID(0x0763, 0x2012): /* M-Audio Fast Track Pro */
+ if (fp->altsetting == 2 || fp->altsetting == 3 ||
+ fp->altsetting == 5 || fp->altsetting == 6)
+ return 1;
+ break;
+ }
+ return 0;
+}
+
+/*
+ * For E-Mu 0404USB/0202USB/TrackerPre/0204 sample rate should be set for device,
+ * not for interface.
+ */
+
+enum {
+ EMU_QUIRK_SR_44100HZ = 0,
+ EMU_QUIRK_SR_48000HZ,
+ EMU_QUIRK_SR_88200HZ,
+ EMU_QUIRK_SR_96000HZ,
+ EMU_QUIRK_SR_176400HZ,
+ EMU_QUIRK_SR_192000HZ
+};
+
+static void set_format_emu_quirk(struct snd_usb_substream *subs,
+ struct audioformat *fmt)
+{
+ unsigned char emu_samplerate_id = 0;
+
+ /* When capture is active
+ * sample rate shouldn't be changed
+ * by playback substream
+ */
+ if (subs->direction == SNDRV_PCM_STREAM_PLAYBACK) {
+ if (subs->stream->substream[SNDRV_PCM_STREAM_CAPTURE].interface != -1)
+ return;
+ }
+
+ switch (fmt->rate_min) {
+ case 48000:
+ emu_samplerate_id = EMU_QUIRK_SR_48000HZ;
+ break;
+ case 88200:
+ emu_samplerate_id = EMU_QUIRK_SR_88200HZ;
+ break;
+ case 96000:
+ emu_samplerate_id = EMU_QUIRK_SR_96000HZ;
+ break;
+ case 176400:
+ emu_samplerate_id = EMU_QUIRK_SR_176400HZ;
+ break;
+ case 192000:
+ emu_samplerate_id = EMU_QUIRK_SR_192000HZ;
+ break;
+ default:
+ emu_samplerate_id = EMU_QUIRK_SR_44100HZ;
+ break;
+ }
+ snd_emuusb_set_samplerate(subs->stream->chip, emu_samplerate_id);
+ subs->pkt_offset_adj = (emu_samplerate_id >= EMU_QUIRK_SR_176400HZ) ? 4 : 0;
+}
+
+void snd_usb_set_format_quirk(struct snd_usb_substream *subs,
+ struct audioformat *fmt)
+{
+ switch (subs->stream->chip->usb_id) {
+ case USB_ID(0x041e, 0x3f02): /* E-Mu 0202 USB */
+ case USB_ID(0x041e, 0x3f04): /* E-Mu 0404 USB */
+ case USB_ID(0x041e, 0x3f0a): /* E-Mu Tracker Pre */
+ case USB_ID(0x041e, 0x3f19): /* E-Mu 0204 USB */
+ set_format_emu_quirk(subs, fmt);
+ break;
+ }
+}
+
+bool snd_usb_get_sample_rate_quirk(struct snd_usb_audio *chip)
+{
+ /* devices which do not support reading the sample rate. */
+ switch (chip->usb_id) {
+ case USB_ID(0x045E, 0x075D): /* MS Lifecam Cinema */
+ case USB_ID(0x045E, 0x076D): /* MS Lifecam HD-5000 */
+ case USB_ID(0x045E, 0x0772): /* MS Lifecam Studio */
+ case USB_ID(0x045E, 0x0779): /* MS Lifecam HD-3000 */
+ case USB_ID(0x04D8, 0xFEEA): /* Benchmark DAC1 Pre */
+ case USB_ID(0x074D, 0x3553): /* Outlaw RR2150 (Micronas UAC3553B) */
+ return true;
+ }
+ return false;
+}
+
+/* Marantz/Denon USB DACs need a vendor cmd to switch
+ * between PCM and native DSD mode
+ */
+static bool is_marantz_denon_dac(unsigned int id)
+{
+ switch (id) {
+ case USB_ID(0x154e, 0x1003): /* Denon DA-300USB */
+ case USB_ID(0x154e, 0x3005): /* Marantz HD-DAC1 */
+ case USB_ID(0x154e, 0x3006): /* Marantz SA-14S1 */
+ return true;
+ }
+ return false;
+}
+
+int snd_usb_select_mode_quirk(struct snd_usb_substream *subs,
+ struct audioformat *fmt)
+{
+ struct usb_device *dev = subs->dev;
+ int err;
+
+ if (is_marantz_denon_dac(subs->stream->chip->usb_id)) {
+ /* First switch to alt set 0, otherwise the mode switch cmd
+ * will not be accepted by the DAC
+ */
+ err = usb_set_interface(dev, fmt->iface, 0);
+ if (err < 0)
+ return err;
+
+ mdelay(20); /* Delay needed after setting the interface */
+
+ switch (fmt->altsetting) {
+ case 2: /* DSD mode requested */
+ case 1: /* PCM mode requested */
+ err = snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), 0,
+ USB_DIR_OUT|USB_TYPE_VENDOR|USB_RECIP_INTERFACE,
+ fmt->altsetting - 1, 1, NULL, 0);
+ if (err < 0)
+ return err;
+ break;
+ }
+ mdelay(20);
+ }
+ return 0;
+}
+
+void snd_usb_endpoint_start_quirk(struct snd_usb_endpoint *ep)
+{
+ /*
+ * "Playback Design" products send bogus feedback data at the start
+ * of the stream. Ignore them.
+ */
+ if ((le16_to_cpu(ep->chip->dev->descriptor.idVendor) == 0x23ba) &&
+ ep->type == SND_USB_ENDPOINT_TYPE_SYNC)
+ ep->skip_packets = 4;
+
+ /*
+ * M-Audio Fast Track C400/C600 - when packets are not skipped, real
+ * world latency varies by approx. +/- 50 frames (at 96KHz) each time
+ * the stream is (re)started. When skipping packets 16 at endpoint
+ * start up, the real world latency is stable within +/- 1 frame (also
+ * across power cycles).
+ */
+ if ((ep->chip->usb_id == USB_ID(0x0763, 0x2030) ||
+ ep->chip->usb_id == USB_ID(0x0763, 0x2031)) &&
+ ep->type == SND_USB_ENDPOINT_TYPE_DATA)
+ ep->skip_packets = 16;
+}
+
+void snd_usb_set_interface_quirk(struct usb_device *dev)
+{
+ /*
+ * "Playback Design" products need a 50ms delay after setting the
+ * USB interface.
+ */
+ if (le16_to_cpu(dev->descriptor.idVendor) == 0x23ba)
+ mdelay(50);
+}
+
+void snd_usb_ctl_msg_quirk(struct usb_device *dev, unsigned int pipe,
+ __u8 request, __u8 requesttype, __u16 value,
+ __u16 index, void *data, __u16 size)
+{
+ /*
+ * "Playback Design" products need a 20ms delay after each
+ * class compliant request
+ */
+ if ((le16_to_cpu(dev->descriptor.idVendor) == 0x23ba) &&
+ (requesttype & USB_TYPE_MASK) == USB_TYPE_CLASS)
+ mdelay(20);
+
+ /* Marantz/Denon devices with USB DAC functionality need a delay
+ * after each class compliant request
+ */
+ if (is_marantz_denon_dac(USB_ID(le16_to_cpu(dev->descriptor.idVendor),
+ le16_to_cpu(dev->descriptor.idProduct)))
+ && (requesttype & USB_TYPE_MASK) == USB_TYPE_CLASS)
+ mdelay(20);
+
+ /* Zoom R16/24 needs a tiny delay here, otherwise requests like
+ * get/set frequency return as failed despite actually succeeding.
+ */
+ if ((le16_to_cpu(dev->descriptor.idVendor) == 0x1686) &&
+ (le16_to_cpu(dev->descriptor.idProduct) == 0x00dd) &&
+ (requesttype & USB_TYPE_MASK) == USB_TYPE_CLASS)
+ mdelay(1);
+}
+
+/*
+ * snd_usb_interface_dsd_format_quirks() is called from format.c to
+ * augment the PCM format bit-field for DSD types. The UAC standards
+ * don't have a designated bit field to denote DSD-capable interfaces,
+ * hence all hardware that is known to support this format has to be
+ * listed here.
+ */
+u64 snd_usb_interface_dsd_format_quirks(struct snd_usb_audio *chip,
+ struct audioformat *fp,
+ unsigned int sample_bytes)
+{
+ /* Playback Designs */
+ if (le16_to_cpu(chip->dev->descriptor.idVendor) == 0x23ba) {
+ switch (fp->altsetting) {
+ case 1:
+ fp->dsd_dop = true;
+ return SNDRV_PCM_FMTBIT_DSD_U16_LE;
+ case 2:
+ fp->dsd_bitrev = true;
+ return SNDRV_PCM_FMTBIT_DSD_U8;
+ case 3:
+ fp->dsd_bitrev = true;
+ return SNDRV_PCM_FMTBIT_DSD_U16_LE;
+ }
+ }
+
+ /* XMOS based USB DACs */
+ switch (chip->usb_id) {
+ case USB_ID(0x20b1, 0x3008): /* iFi Audio micro/nano iDSD */
+ case USB_ID(0x20b1, 0x2008): /* Matrix Audio X-Sabre */
+ case USB_ID(0x20b1, 0x300a): /* Matrix Audio Mini-i Pro */
+ if (fp->altsetting == 2)
+ return SNDRV_PCM_FMTBIT_DSD_U32_BE;
+ break;
+
+ case USB_ID(0x20b1, 0x2009): /* DIYINHK DSD DXD 384kHz USB to I2S/DSD */
+ case USB_ID(0x20b1, 0x2023): /* JLsounds I2SoverUSB */
+ if (fp->altsetting == 3)
+ return SNDRV_PCM_FMTBIT_DSD_U32_BE;
+ break;
+ default:
+ break;
+ }
+
+ /* Denon/Marantz devices with USB DAC functionality */
+ if (is_marantz_denon_dac(chip->usb_id)) {
+ if (fp->altsetting == 2)
+ return SNDRV_PCM_FMTBIT_DSD_U32_BE;
+ }
+
+ return 0;
+}
diff --git a/sound/usb/quirks.h b/sound/usb/quirks.h
new file mode 100644
index 000000000..2cd71ed12
--- /dev/null
+++ b/sound/usb/quirks.h
@@ -0,0 +1,43 @@
+#ifndef __USBAUDIO_QUIRKS_H
+#define __USBAUDIO_QUIRKS_H
+
+struct audioformat;
+struct snd_usb_endpoint;
+struct snd_usb_substream;
+
+int snd_usb_create_quirk(struct snd_usb_audio *chip,
+ struct usb_interface *iface,
+ struct usb_driver *driver,
+ const struct snd_usb_audio_quirk *quirk);
+
+int snd_usb_apply_interface_quirk(struct snd_usb_audio *chip,
+ int iface,
+ int altno);
+
+int snd_usb_apply_boot_quirk(struct usb_device *dev,
+ struct usb_interface *intf,
+ const struct snd_usb_audio_quirk *quirk);
+
+void snd_usb_set_format_quirk(struct snd_usb_substream *subs,
+ struct audioformat *fmt);
+
+bool snd_usb_get_sample_rate_quirk(struct snd_usb_audio *chip);
+
+int snd_usb_is_big_endian_format(struct snd_usb_audio *chip,
+ struct audioformat *fp);
+
+void snd_usb_endpoint_start_quirk(struct snd_usb_endpoint *ep);
+
+void snd_usb_set_interface_quirk(struct usb_device *dev);
+void snd_usb_ctl_msg_quirk(struct usb_device *dev, unsigned int pipe,
+ __u8 request, __u8 requesttype, __u16 value,
+ __u16 index, void *data, __u16 size);
+
+int snd_usb_select_mode_quirk(struct snd_usb_substream *subs,
+ struct audioformat *fmt);
+
+u64 snd_usb_interface_dsd_format_quirks(struct snd_usb_audio *chip,
+ struct audioformat *fp,
+ unsigned int sample_bytes);
+
+#endif /* __USBAUDIO_QUIRKS_H */
diff --git a/sound/usb/stream.c b/sound/usb/stream.c
new file mode 100644
index 000000000..310a3822d
--- /dev/null
+++ b/sound/usb/stream.c
@@ -0,0 +1,731 @@
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/usb.h>
+#include <linux/usb/audio.h>
+#include <linux/usb/audio-v2.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/control.h>
+#include <sound/tlv.h>
+
+#include "usbaudio.h"
+#include "card.h"
+#include "proc.h"
+#include "quirks.h"
+#include "endpoint.h"
+#include "pcm.h"
+#include "helper.h"
+#include "format.h"
+#include "clock.h"
+#include "stream.h"
+
+/*
+ * free a substream
+ */
+static void free_substream(struct snd_usb_substream *subs)
+{
+ struct audioformat *fp, *n;
+
+ if (!subs->num_formats)
+ return; /* not initialized */
+ list_for_each_entry_safe(fp, n, &subs->fmt_list, list) {
+ kfree(fp->rate_table);
+ kfree(fp->chmap);
+ kfree(fp);
+ }
+ kfree(subs->rate_list.list);
+}
+
+
+/*
+ * free a usb stream instance
+ */
+static void snd_usb_audio_stream_free(struct snd_usb_stream *stream)
+{
+ free_substream(&stream->substream[0]);
+ free_substream(&stream->substream[1]);
+ list_del(&stream->list);
+ kfree(stream);
+}
+
+static void snd_usb_audio_pcm_free(struct snd_pcm *pcm)
+{
+ struct snd_usb_stream *stream = pcm->private_data;
+ if (stream) {
+ stream->pcm = NULL;
+ snd_usb_audio_stream_free(stream);
+ }
+}
+
+/*
+ * initialize the substream instance.
+ */
+
+static void snd_usb_init_substream(struct snd_usb_stream *as,
+ int stream,
+ struct audioformat *fp)
+{
+ struct snd_usb_substream *subs = &as->substream[stream];
+
+ INIT_LIST_HEAD(&subs->fmt_list);
+ spin_lock_init(&subs->lock);
+
+ subs->stream = as;
+ subs->direction = stream;
+ subs->dev = as->chip->dev;
+ subs->txfr_quirk = as->chip->txfr_quirk;
+ subs->speed = snd_usb_get_speed(subs->dev);
+ subs->pkt_offset_adj = 0;
+
+ snd_usb_set_pcm_ops(as->pcm, stream);
+
+ list_add_tail(&fp->list, &subs->fmt_list);
+ subs->formats |= fp->formats;
+ subs->num_formats++;
+ subs->fmt_type = fp->fmt_type;
+ subs->ep_num = fp->endpoint;
+ if (fp->channels > subs->channels_max)
+ subs->channels_max = fp->channels;
+}
+
+/* kctl callbacks for usb-audio channel maps */
+static int usb_chmap_ctl_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ struct snd_pcm_chmap *info = snd_kcontrol_chip(kcontrol);
+ struct snd_usb_substream *subs = info->private_data;
+
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = subs->channels_max;
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = SNDRV_CHMAP_LAST;
+ return 0;
+}
+
+/* check whether a duplicated entry exists in the audiofmt list */
+static bool have_dup_chmap(struct snd_usb_substream *subs,
+ struct audioformat *fp)
+{
+ struct list_head *p;
+
+ for (p = fp->list.prev; p != &subs->fmt_list; p = p->prev) {
+ struct audioformat *prev;
+ prev = list_entry(p, struct audioformat, list);
+ if (prev->chmap &&
+ !memcmp(prev->chmap, fp->chmap, sizeof(*fp->chmap)))
+ return true;
+ }
+ return false;
+}
+
+static int usb_chmap_ctl_tlv(struct snd_kcontrol *kcontrol, int op_flag,
+ unsigned int size, unsigned int __user *tlv)
+{
+ struct snd_pcm_chmap *info = snd_kcontrol_chip(kcontrol);
+ struct snd_usb_substream *subs = info->private_data;
+ struct audioformat *fp;
+ unsigned int __user *dst;
+ int count = 0;
+
+ if (size < 8)
+ return -ENOMEM;
+ if (put_user(SNDRV_CTL_TLVT_CONTAINER, tlv))
+ return -EFAULT;
+ size -= 8;
+ dst = tlv + 2;
+ list_for_each_entry(fp, &subs->fmt_list, list) {
+ int i, ch_bytes;
+
+ if (!fp->chmap)
+ continue;
+ if (have_dup_chmap(subs, fp))
+ continue;
+ /* copy the entry */
+ ch_bytes = fp->chmap->channels * 4;
+ if (size < 8 + ch_bytes)
+ return -ENOMEM;
+ if (put_user(SNDRV_CTL_TLVT_CHMAP_FIXED, dst) ||
+ put_user(ch_bytes, dst + 1))
+ return -EFAULT;
+ dst += 2;
+ for (i = 0; i < fp->chmap->channels; i++, dst++) {
+ if (put_user(fp->chmap->map[i], dst))
+ return -EFAULT;
+ }
+
+ count += 8 + ch_bytes;
+ size -= 8 + ch_bytes;
+ }
+ if (put_user(count, tlv + 1))
+ return -EFAULT;
+ return 0;
+}
+
+static int usb_chmap_ctl_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_pcm_chmap *info = snd_kcontrol_chip(kcontrol);
+ struct snd_usb_substream *subs = info->private_data;
+ struct snd_pcm_chmap_elem *chmap = NULL;
+ int i;
+
+ memset(ucontrol->value.integer.value, 0,
+ sizeof(ucontrol->value.integer.value));
+ if (subs->cur_audiofmt)
+ chmap = subs->cur_audiofmt->chmap;
+ if (chmap) {
+ for (i = 0; i < chmap->channels; i++)
+ ucontrol->value.integer.value[i] = chmap->map[i];
+ }
+ return 0;
+}
+
+/* create a chmap kctl assigned to the given USB substream */
+static int add_chmap(struct snd_pcm *pcm, int stream,
+ struct snd_usb_substream *subs)
+{
+ struct audioformat *fp;
+ struct snd_pcm_chmap *chmap;
+ struct snd_kcontrol *kctl;
+ int err;
+
+ list_for_each_entry(fp, &subs->fmt_list, list)
+ if (fp->chmap)
+ goto ok;
+ /* no chmap is found */
+ return 0;
+
+ ok:
+ err = snd_pcm_add_chmap_ctls(pcm, stream, NULL, 0, 0, &chmap);
+ if (err < 0)
+ return err;
+
+ /* override handlers */
+ chmap->private_data = subs;
+ kctl = chmap->kctl;
+ kctl->info = usb_chmap_ctl_info;
+ kctl->get = usb_chmap_ctl_get;
+ kctl->tlv.c = usb_chmap_ctl_tlv;
+
+ return 0;
+}
+
+/* convert from USB ChannelConfig bits to ALSA chmap element */
+static struct snd_pcm_chmap_elem *convert_chmap(int channels, unsigned int bits,
+ int protocol)
+{
+ static unsigned int uac1_maps[] = {
+ SNDRV_CHMAP_FL, /* left front */
+ SNDRV_CHMAP_FR, /* right front */
+ SNDRV_CHMAP_FC, /* center front */
+ SNDRV_CHMAP_LFE, /* LFE */
+ SNDRV_CHMAP_SL, /* left surround */
+ SNDRV_CHMAP_SR, /* right surround */
+ SNDRV_CHMAP_FLC, /* left of center */
+ SNDRV_CHMAP_FRC, /* right of center */
+ SNDRV_CHMAP_RC, /* surround */
+ SNDRV_CHMAP_SL, /* side left */
+ SNDRV_CHMAP_SR, /* side right */
+ SNDRV_CHMAP_TC, /* top */
+ 0 /* terminator */
+ };
+ static unsigned int uac2_maps[] = {
+ SNDRV_CHMAP_FL, /* front left */
+ SNDRV_CHMAP_FR, /* front right */
+ SNDRV_CHMAP_FC, /* front center */
+ SNDRV_CHMAP_LFE, /* LFE */
+ SNDRV_CHMAP_RL, /* back left */
+ SNDRV_CHMAP_RR, /* back right */
+ SNDRV_CHMAP_FLC, /* front left of center */
+ SNDRV_CHMAP_FRC, /* front right of center */
+ SNDRV_CHMAP_RC, /* back center */
+ SNDRV_CHMAP_SL, /* side left */
+ SNDRV_CHMAP_SR, /* side right */
+ SNDRV_CHMAP_TC, /* top center */
+ SNDRV_CHMAP_TFL, /* top front left */
+ SNDRV_CHMAP_TFC, /* top front center */
+ SNDRV_CHMAP_TFR, /* top front right */
+ SNDRV_CHMAP_TRL, /* top back left */
+ SNDRV_CHMAP_TRC, /* top back center */
+ SNDRV_CHMAP_TRR, /* top back right */
+ SNDRV_CHMAP_TFLC, /* top front left of center */
+ SNDRV_CHMAP_TFRC, /* top front right of center */
+ SNDRV_CHMAP_LLFE, /* left LFE */
+ SNDRV_CHMAP_RLFE, /* right LFE */
+ SNDRV_CHMAP_TSL, /* top side left */
+ SNDRV_CHMAP_TSR, /* top side right */
+ SNDRV_CHMAP_BC, /* bottom center */
+ SNDRV_CHMAP_RLC, /* back left of center */
+ SNDRV_CHMAP_RRC, /* back right of center */
+ 0 /* terminator */
+ };
+ struct snd_pcm_chmap_elem *chmap;
+ const unsigned int *maps;
+ int c;
+
+ if (channels > ARRAY_SIZE(chmap->map))
+ return NULL;
+
+ chmap = kzalloc(sizeof(*chmap), GFP_KERNEL);
+ if (!chmap)
+ return NULL;
+
+ maps = protocol == UAC_VERSION_2 ? uac2_maps : uac1_maps;
+ chmap->channels = channels;
+ c = 0;
+
+ if (bits) {
+ for (; bits && *maps; maps++, bits >>= 1)
+ if (bits & 1)
+ chmap->map[c++] = *maps;
+ } else {
+ /* If we're missing wChannelConfig, then guess something
+ to make sure the channel map is not skipped entirely */
+ if (channels == 1)
+ chmap->map[c++] = SNDRV_CHMAP_MONO;
+ else
+ for (; c < channels && *maps; maps++)
+ chmap->map[c++] = *maps;
+ }
+
+ for (; c < channels; c++)
+ chmap->map[c] = SNDRV_CHMAP_UNKNOWN;
+
+ return chmap;
+}
+
+/*
+ * 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.
+ */
+int snd_usb_add_audio_stream(struct snd_usb_audio *chip,
+ int stream,
+ struct audioformat *fp)
+{
+ struct snd_usb_stream *as;
+ struct snd_usb_substream *subs;
+ struct snd_pcm *pcm;
+ int err;
+
+ list_for_each_entry(as, &chip->pcm_list, list) {
+ if (as->fmt_type != fp->fmt_type)
+ continue;
+ subs = &as->substream[stream];
+ if (subs->ep_num == fp->endpoint) {
+ list_add_tail(&fp->list, &subs->fmt_list);
+ subs->num_formats++;
+ subs->formats |= fp->formats;
+ return 0;
+ }
+ }
+ /* look for an empty stream */
+ list_for_each_entry(as, &chip->pcm_list, list) {
+ if (as->fmt_type != fp->fmt_type)
+ continue;
+ subs = &as->substream[stream];
+ if (subs->ep_num)
+ continue;
+ err = snd_pcm_new_stream(as->pcm, stream, 1);
+ if (err < 0)
+ return err;
+ snd_usb_init_substream(as, stream, fp);
+ return add_chmap(as->pcm, stream, subs);
+ }
+
+ /* create a new pcm */
+ as = kzalloc(sizeof(*as), GFP_KERNEL);
+ if (!as)
+ return -ENOMEM;
+ as->pcm_index = chip->pcm_devs;
+ as->chip = chip;
+ as->fmt_type = fp->fmt_type;
+ err = snd_pcm_new(chip->card, "USB Audio", chip->pcm_devs,
+ stream == SNDRV_PCM_STREAM_PLAYBACK ? 1 : 0,
+ stream == SNDRV_PCM_STREAM_PLAYBACK ? 0 : 1,
+ &pcm);
+ if (err < 0) {
+ kfree(as);
+ return err;
+ }
+ as->pcm = pcm;
+ pcm->private_data = as;
+ pcm->private_free = snd_usb_audio_pcm_free;
+ pcm->info_flags = 0;
+ if (chip->pcm_devs > 0)
+ sprintf(pcm->name, "USB Audio #%d", chip->pcm_devs);
+ else
+ strcpy(pcm->name, "USB Audio");
+
+ snd_usb_init_substream(as, stream, fp);
+
+ list_add(&as->list, &chip->pcm_list);
+ chip->pcm_devs++;
+
+ snd_usb_proc_pcm_format_add(as);
+
+ return add_chmap(pcm, stream, &as->substream[stream]);
+}
+
+static int parse_uac_endpoint_attributes(struct snd_usb_audio *chip,
+ struct usb_host_interface *alts,
+ int protocol, int iface_no)
+{
+ /* parsed with a v1 header here. that's ok as we only look at the
+ * header first which is the same for both versions */
+ struct uac_iso_endpoint_descriptor *csep;
+ struct usb_interface_descriptor *altsd = get_iface_desc(alts);
+ int attributes = 0;
+
+ csep = snd_usb_find_desc(alts->endpoint[0].extra, alts->endpoint[0].extralen, NULL, USB_DT_CS_ENDPOINT);
+
+ /* Creamware Noah has this descriptor after the 2nd endpoint */
+ if (!csep && altsd->bNumEndpoints >= 2)
+ csep = snd_usb_find_desc(alts->endpoint[1].extra, alts->endpoint[1].extralen, NULL, USB_DT_CS_ENDPOINT);
+
+ /*
+ * If we can't locate the USB_DT_CS_ENDPOINT descriptor in the extra
+ * bytes after the first endpoint, go search the entire interface.
+ * Some devices have it directly *before* the standard endpoint.
+ */
+ if (!csep)
+ csep = snd_usb_find_desc(alts->extra, alts->extralen, NULL, USB_DT_CS_ENDPOINT);
+
+ if (!csep || csep->bLength < 7 ||
+ csep->bDescriptorSubtype != UAC_EP_GENERAL) {
+ usb_audio_warn(chip,
+ "%u:%d : no or invalid class specific endpoint descriptor\n",
+ iface_no, altsd->bAlternateSetting);
+ return 0;
+ }
+
+ if (protocol == UAC_VERSION_1) {
+ attributes = csep->bmAttributes;
+ } else {
+ struct uac2_iso_endpoint_descriptor *csep2 =
+ (struct uac2_iso_endpoint_descriptor *) csep;
+
+ attributes = csep->bmAttributes & UAC_EP_CS_ATTR_FILL_MAX;
+
+ /* emulate the endpoint attributes of a v1 device */
+ if (csep2->bmControls & UAC2_CONTROL_PITCH)
+ attributes |= UAC_EP_CS_ATTR_PITCH_CONTROL;
+ }
+
+ return attributes;
+}
+
+/* find an input terminal descriptor (either UAC1 or UAC2) with the given
+ * terminal id
+ */
+static void *
+snd_usb_find_input_terminal_descriptor(struct usb_host_interface *ctrl_iface,
+ int terminal_id)
+{
+ struct uac2_input_terminal_descriptor *term = NULL;
+
+ while ((term = snd_usb_find_csint_desc(ctrl_iface->extra,
+ ctrl_iface->extralen,
+ term, UAC_INPUT_TERMINAL))) {
+ if (term->bTerminalID == terminal_id)
+ return term;
+ }
+
+ return NULL;
+}
+
+static struct uac2_output_terminal_descriptor *
+ snd_usb_find_output_terminal_descriptor(struct usb_host_interface *ctrl_iface,
+ int terminal_id)
+{
+ struct uac2_output_terminal_descriptor *term = NULL;
+
+ while ((term = snd_usb_find_csint_desc(ctrl_iface->extra,
+ ctrl_iface->extralen,
+ term, UAC_OUTPUT_TERMINAL))) {
+ if (term->bTerminalID == terminal_id)
+ return term;
+ }
+
+ return NULL;
+}
+
+int snd_usb_parse_audio_interface(struct snd_usb_audio *chip, int iface_no)
+{
+ struct usb_device *dev;
+ struct usb_interface *iface;
+ struct usb_host_interface *alts;
+ struct usb_interface_descriptor *altsd;
+ int i, altno, err, stream;
+ unsigned int format = 0, num_channels = 0;
+ struct audioformat *fp = NULL;
+ int num, protocol, clock = 0;
+ struct uac_format_type_i_continuous_descriptor *fmt;
+ unsigned int chconfig;
+
+ dev = chip->dev;
+
+ /* parse the interface's altsettings */
+ iface = usb_ifnum_to_if(dev, iface_no);
+
+ num = iface->num_altsetting;
+
+ /*
+ * Dallas DS4201 workaround: It presents 5 altsettings, but the last
+ * one misses syncpipe, and does not produce any sound.
+ */
+ if (chip->usb_id == USB_ID(0x04fa, 0x4201))
+ num = 4;
+
+ for (i = 0; i < num; i++) {
+ alts = &iface->altsetting[i];
+ altsd = get_iface_desc(alts);
+ protocol = altsd->bInterfaceProtocol;
+ /* skip invalid one */
+ if (((altsd->bInterfaceClass != USB_CLASS_AUDIO ||
+ (altsd->bInterfaceSubClass != USB_SUBCLASS_AUDIOSTREAMING &&
+ altsd->bInterfaceSubClass != USB_SUBCLASS_VENDOR_SPEC)) &&
+ altsd->bInterfaceClass != USB_CLASS_VENDOR_SPEC) ||
+ altsd->bNumEndpoints < 1 ||
+ le16_to_cpu(get_endpoint(alts, 0)->wMaxPacketSize) == 0)
+ continue;
+ /* must be isochronous */
+ if ((get_endpoint(alts, 0)->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) !=
+ USB_ENDPOINT_XFER_ISOC)
+ continue;
+ /* check direction */
+ stream = (get_endpoint(alts, 0)->bEndpointAddress & USB_DIR_IN) ?
+ SNDRV_PCM_STREAM_CAPTURE : SNDRV_PCM_STREAM_PLAYBACK;
+ altno = altsd->bAlternateSetting;
+
+ if (snd_usb_apply_interface_quirk(chip, iface_no, altno))
+ continue;
+
+ /*
+ * Roland audio streaming interfaces are marked with protocols
+ * 0/1/2, but are UAC 1 compatible.
+ */
+ if (USB_ID_VENDOR(chip->usb_id) == 0x0582 &&
+ altsd->bInterfaceClass == USB_CLASS_VENDOR_SPEC &&
+ protocol <= 2)
+ protocol = UAC_VERSION_1;
+
+ chconfig = 0;
+ /* get audio formats */
+ switch (protocol) {
+ default:
+ dev_dbg(&dev->dev, "%u:%d: unknown interface protocol %#02x, assuming v1\n",
+ iface_no, altno, protocol);
+ protocol = UAC_VERSION_1;
+ /* fall through */
+
+ case UAC_VERSION_1: {
+ struct uac1_as_header_descriptor *as =
+ snd_usb_find_csint_desc(alts->extra, alts->extralen, NULL, UAC_AS_GENERAL);
+ struct uac_input_terminal_descriptor *iterm;
+
+ if (!as) {
+ dev_err(&dev->dev,
+ "%u:%d : UAC_AS_GENERAL descriptor not found\n",
+ iface_no, altno);
+ continue;
+ }
+
+ if (as->bLength < sizeof(*as)) {
+ dev_err(&dev->dev,
+ "%u:%d : invalid UAC_AS_GENERAL desc\n",
+ iface_no, altno);
+ continue;
+ }
+
+ format = le16_to_cpu(as->wFormatTag); /* remember the format value */
+
+ iterm = snd_usb_find_input_terminal_descriptor(chip->ctrl_intf,
+ as->bTerminalLink);
+ if (iterm) {
+ num_channels = iterm->bNrChannels;
+ chconfig = le16_to_cpu(iterm->wChannelConfig);
+ }
+
+ break;
+ }
+
+ case UAC_VERSION_2: {
+ struct uac2_input_terminal_descriptor *input_term;
+ struct uac2_output_terminal_descriptor *output_term;
+ struct uac2_as_header_descriptor *as =
+ snd_usb_find_csint_desc(alts->extra, alts->extralen, NULL, UAC_AS_GENERAL);
+
+ if (!as) {
+ dev_err(&dev->dev,
+ "%u:%d : UAC_AS_GENERAL descriptor not found\n",
+ iface_no, altno);
+ continue;
+ }
+
+ if (as->bLength < sizeof(*as)) {
+ dev_err(&dev->dev,
+ "%u:%d : invalid UAC_AS_GENERAL desc\n",
+ iface_no, altno);
+ continue;
+ }
+
+ num_channels = as->bNrChannels;
+ format = le32_to_cpu(as->bmFormats);
+ chconfig = le32_to_cpu(as->bmChannelConfig);
+
+ /* lookup the terminal associated to this interface
+ * to extract the clock */
+ input_term = snd_usb_find_input_terminal_descriptor(chip->ctrl_intf,
+ as->bTerminalLink);
+ if (input_term) {
+ clock = input_term->bCSourceID;
+ if (!chconfig && (num_channels == input_term->bNrChannels))
+ chconfig = le32_to_cpu(input_term->bmChannelConfig);
+ break;
+ }
+
+ output_term = snd_usb_find_output_terminal_descriptor(chip->ctrl_intf,
+ as->bTerminalLink);
+ if (output_term) {
+ clock = output_term->bCSourceID;
+ break;
+ }
+
+ dev_err(&dev->dev,
+ "%u:%d : bogus bTerminalLink %d\n",
+ iface_no, altno, as->bTerminalLink);
+ continue;
+ }
+ }
+
+ /* get format type */
+ fmt = snd_usb_find_csint_desc(alts->extra, alts->extralen, NULL, UAC_FORMAT_TYPE);
+ if (!fmt) {
+ dev_err(&dev->dev,
+ "%u:%d : no UAC_FORMAT_TYPE desc\n",
+ iface_no, altno);
+ continue;
+ }
+ if (((protocol == UAC_VERSION_1) && (fmt->bLength < 8)) ||
+ ((protocol == UAC_VERSION_2) && (fmt->bLength < 6))) {
+ dev_err(&dev->dev,
+ "%u:%d : invalid UAC_FORMAT_TYPE desc\n",
+ iface_no, altno);
+ continue;
+ }
+
+ /*
+ * Blue Microphones workaround: The last altsetting is identical
+ * with the previous one, except for a larger packet size, but
+ * is actually a mislabeled two-channel setting; ignore it.
+ */
+ if (fmt->bNrChannels == 1 &&
+ fmt->bSubframeSize == 2 &&
+ altno == 2 && num == 3 &&
+ fp && fp->altsetting == 1 && fp->channels == 1 &&
+ fp->formats == SNDRV_PCM_FMTBIT_S16_LE &&
+ protocol == UAC_VERSION_1 &&
+ le16_to_cpu(get_endpoint(alts, 0)->wMaxPacketSize) ==
+ fp->maxpacksize * 2)
+ continue;
+
+ fp = kzalloc(sizeof(*fp), GFP_KERNEL);
+ if (! fp) {
+ dev_err(&dev->dev, "cannot malloc\n");
+ return -ENOMEM;
+ }
+
+ 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;
+ fp->datainterval = snd_usb_parse_datainterval(chip, alts);
+ fp->protocol = protocol;
+ fp->maxpacksize = le16_to_cpu(get_endpoint(alts, 0)->wMaxPacketSize);
+ fp->channels = num_channels;
+ if (snd_usb_get_speed(dev) == USB_SPEED_HIGH)
+ fp->maxpacksize = (((fp->maxpacksize >> 11) & 3) + 1)
+ * (fp->maxpacksize & 0x7ff);
+ fp->attributes = parse_uac_endpoint_attributes(chip, alts, protocol, iface_no);
+ fp->clock = clock;
+
+ /* some quirks for attributes here */
+
+ switch (chip->usb_id) {
+ case USB_ID(0x0a92, 0x0053): /* AudioTrak Optoplay */
+ /* Optoplay sets the sample rate attribute although
+ * it seems not supporting it in fact.
+ */
+ fp->attributes &= ~UAC_EP_CS_ATTR_SAMPLE_RATE;
+ break;
+ case USB_ID(0x041e, 0x3020): /* Creative SB Audigy 2 NX */
+ case USB_ID(0x0763, 0x2003): /* M-Audio Audiophile USB */
+ /* doesn't set the sample rate attribute, but supports it */
+ fp->attributes |= UAC_EP_CS_ATTR_SAMPLE_RATE;
+ break;
+ case USB_ID(0x0763, 0x2001): /* M-Audio Quattro USB */
+ case USB_ID(0x0763, 0x2012): /* M-Audio Fast Track Pro USB */
+ case USB_ID(0x047f, 0x0ca1): /* plantronics headset */
+ case USB_ID(0x077d, 0x07af): /* Griffin iMic (note that there is
+ an older model 77d:223) */
+ /*
+ * plantronics headset and Griffin iMic have set adaptive-in
+ * although it's really not...
+ */
+ fp->ep_attr &= ~USB_ENDPOINT_SYNCTYPE;
+ if (stream == SNDRV_PCM_STREAM_PLAYBACK)
+ fp->ep_attr |= USB_ENDPOINT_SYNC_ADAPTIVE;
+ else
+ fp->ep_attr |= USB_ENDPOINT_SYNC_SYNC;
+ break;
+ }
+
+ /* ok, let's parse further... */
+ if (snd_usb_parse_audio_format(chip, fp, format, fmt, stream) < 0) {
+ kfree(fp->rate_table);
+ kfree(fp);
+ fp = NULL;
+ continue;
+ }
+
+ /* Create chmap */
+ if (fp->channels != num_channels)
+ chconfig = 0;
+ fp->chmap = convert_chmap(fp->channels, chconfig, protocol);
+
+ dev_dbg(&dev->dev, "%u:%d: add audio endpoint %#x\n", iface_no, altno, fp->endpoint);
+ err = snd_usb_add_audio_stream(chip, stream, fp);
+ if (err < 0) {
+ kfree(fp->rate_table);
+ kfree(fp->chmap);
+ kfree(fp);
+ return err;
+ }
+ /* try to set the interface... */
+ usb_set_interface(chip->dev, iface_no, altno);
+ snd_usb_init_pitch(chip, iface_no, alts, fp);
+ snd_usb_init_sample_rate(chip, iface_no, alts, fp, fp->rate_max);
+ }
+ return 0;
+}
+
diff --git a/sound/usb/stream.h b/sound/usb/stream.h
new file mode 100644
index 000000000..c97f679fc
--- /dev/null
+++ b/sound/usb/stream.h
@@ -0,0 +1,12 @@
+#ifndef __USBAUDIO_STREAM_H
+#define __USBAUDIO_STREAM_H
+
+int snd_usb_parse_audio_interface(struct snd_usb_audio *chip,
+ int iface_no);
+
+int snd_usb_add_audio_stream(struct snd_usb_audio *chip,
+ int stream,
+ struct audioformat *fp);
+
+#endif /* __USBAUDIO_STREAM_H */
+
diff --git a/sound/usb/usbaudio.h b/sound/usb/usbaudio.h
new file mode 100644
index 000000000..91d038043
--- /dev/null
+++ b/sound/usb/usbaudio.h
@@ -0,0 +1,118 @@
+#ifndef __USBAUDIO_H
+#define __USBAUDIO_H
+/*
+ * (Tentative) USB Audio Driver for ALSA
+ *
+ * Copyright (c) 2002 by Takashi Iwai <tiwai@suse.de>
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+/* handling of USB vendor/product ID pairs as 32-bit numbers */
+#define USB_ID(vendor, product) (((vendor) << 16) | (product))
+#define USB_ID_VENDOR(id) ((id) >> 16)
+#define USB_ID_PRODUCT(id) ((u16)(id))
+
+/*
+ *
+ */
+
+struct snd_usb_audio {
+ int index;
+ struct usb_device *dev;
+ struct snd_card *card;
+ struct usb_interface *pm_intf;
+ u32 usb_id;
+ struct mutex mutex;
+ struct rw_semaphore shutdown_rwsem;
+ unsigned int shutdown:1;
+ unsigned int probing:1;
+ unsigned int in_pm:1;
+ unsigned int autosuspended:1;
+ unsigned int txfr_quirk:1; /* Subframe boundaries on transfers */
+
+ int num_interfaces;
+ int num_suspended_intf;
+
+ struct list_head pcm_list; /* list of pcm streams */
+ struct list_head ep_list; /* list of audio-related endpoints */
+ int pcm_devs;
+
+ struct list_head midi_list; /* list of midi interfaces */
+
+ struct list_head mixer_list; /* list of mixer interfaces */
+
+ int setup; /* from the 'device_setup' module param */
+ bool autoclock; /* from the 'autoclock' module param */
+
+ struct usb_host_interface *ctrl_intf; /* the audio control interface */
+};
+
+#define usb_audio_err(chip, fmt, args...) \
+ dev_err(&(chip)->dev->dev, fmt, ##args)
+#define usb_audio_warn(chip, fmt, args...) \
+ dev_warn(&(chip)->dev->dev, fmt, ##args)
+#define usb_audio_info(chip, fmt, args...) \
+ dev_info(&(chip)->dev->dev, fmt, ##args)
+#define usb_audio_dbg(chip, fmt, args...) \
+ dev_dbg(&(chip)->dev->dev, fmt, ##args)
+
+/*
+ * Information about devices with broken descriptors
+ */
+
+/* special values for .ifnum */
+#define QUIRK_NO_INTERFACE -2
+#define QUIRK_ANY_INTERFACE -1
+
+enum quirk_type {
+ QUIRK_IGNORE_INTERFACE,
+ QUIRK_COMPOSITE,
+ QUIRK_AUTODETECT,
+ QUIRK_MIDI_STANDARD_INTERFACE,
+ QUIRK_MIDI_FIXED_ENDPOINT,
+ QUIRK_MIDI_YAMAHA,
+ QUIRK_MIDI_ROLAND,
+ QUIRK_MIDI_MIDIMAN,
+ QUIRK_MIDI_NOVATION,
+ QUIRK_MIDI_RAW_BYTES,
+ QUIRK_MIDI_EMAGIC,
+ QUIRK_MIDI_CME,
+ QUIRK_MIDI_AKAI,
+ QUIRK_MIDI_US122L,
+ QUIRK_MIDI_FTDI,
+ QUIRK_AUDIO_STANDARD_INTERFACE,
+ QUIRK_AUDIO_FIXED_ENDPOINT,
+ QUIRK_AUDIO_EDIROL_UAXX,
+ QUIRK_AUDIO_ALIGN_TRANSFER,
+ QUIRK_AUDIO_STANDARD_MIXER,
+
+ QUIRK_TYPE_COUNT
+};
+
+struct snd_usb_audio_quirk {
+ const char *vendor_name;
+ const char *product_name;
+ int16_t ifnum;
+ uint16_t type;
+ const void *data;
+};
+
+#define combine_word(s) ((*(s)) | ((unsigned int)(s)[1] << 8))
+#define combine_triple(s) (combine_word(s) | ((unsigned int)(s)[2] << 16))
+#define combine_quad(s) (combine_triple(s) | ((unsigned int)(s)[3] << 24))
+
+#endif /* __USBAUDIO_H */
diff --git a/sound/usb/usx2y/Makefile b/sound/usb/usx2y/Makefile
new file mode 100644
index 000000000..748933054
--- /dev/null
+++ b/sound/usb/usx2y/Makefile
@@ -0,0 +1,5 @@
+snd-usb-usx2y-objs := usbusx2y.o usX2Yhwdep.o usx2yhwdeppcm.o
+snd-usb-us122l-objs := us122l.o
+
+obj-$(CONFIG_SND_USB_USX2Y) += snd-usb-usx2y.o
+obj-$(CONFIG_SND_USB_US122L) += snd-usb-us122l.o
diff --git a/sound/usb/usx2y/us122l.c b/sound/usb/usx2y/us122l.c
new file mode 100644
index 000000000..cf5dc33f4
--- /dev/null
+++ b/sound/usb/usx2y/us122l.c
@@ -0,0 +1,778 @@
+/*
+ * Copyright (C) 2007, 2008 Karsten Wiese <fzu@wemgehoertderstaat.de>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <linux/slab.h>
+#include <linux/usb.h>
+#include <linux/usb/audio.h>
+#include <linux/module.h>
+#include <sound/core.h>
+#include <sound/hwdep.h>
+#include <sound/pcm.h>
+#include <sound/initval.h>
+#define MODNAME "US122L"
+#include "usb_stream.c"
+#include "../usbaudio.h"
+#include "../midi.h"
+#include "us122l.h"
+
+MODULE_AUTHOR("Karsten Wiese <fzu@wemgehoertderstaat.de>");
+MODULE_DESCRIPTION("TASCAM "NAME_ALLCAPS" Version 0.5");
+MODULE_LICENSE("GPL");
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 0-max */
+static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* Id for this card */
+ /* Enable this card */
+static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP;
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for "NAME_ALLCAPS".");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for "NAME_ALLCAPS".");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "Enable "NAME_ALLCAPS".");
+
+static int snd_us122l_card_used[SNDRV_CARDS];
+
+
+static int us122l_create_usbmidi(struct snd_card *card)
+{
+ static struct snd_usb_midi_endpoint_info quirk_data = {
+ .out_ep = 4,
+ .in_ep = 3,
+ .out_cables = 0x001,
+ .in_cables = 0x001
+ };
+ static struct snd_usb_audio_quirk quirk = {
+ .vendor_name = "US122L",
+ .product_name = NAME_ALLCAPS,
+ .ifnum = 1,
+ .type = QUIRK_MIDI_US122L,
+ .data = &quirk_data
+ };
+ struct usb_device *dev = US122L(card)->dev;
+ struct usb_interface *iface = usb_ifnum_to_if(dev, 1);
+
+ return snd_usbmidi_create(card, iface,
+ &US122L(card)->midi_list, &quirk);
+}
+
+static int us144_create_usbmidi(struct snd_card *card)
+{
+ static struct snd_usb_midi_endpoint_info quirk_data = {
+ .out_ep = 4,
+ .in_ep = 3,
+ .out_cables = 0x001,
+ .in_cables = 0x001
+ };
+ static struct snd_usb_audio_quirk quirk = {
+ .vendor_name = "US144",
+ .product_name = NAME_ALLCAPS,
+ .ifnum = 0,
+ .type = QUIRK_MIDI_US122L,
+ .data = &quirk_data
+ };
+ struct usb_device *dev = US122L(card)->dev;
+ struct usb_interface *iface = usb_ifnum_to_if(dev, 0);
+
+ return snd_usbmidi_create(card, iface,
+ &US122L(card)->midi_list, &quirk);
+}
+
+/*
+ * Wrapper for usb_control_msg().
+ * Allocates a temp buffer to prevent dmaing from/to the stack.
+ */
+static int us122l_ctl_msg(struct usb_device *dev, unsigned int pipe,
+ __u8 request, __u8 requesttype,
+ __u16 value, __u16 index, void *data,
+ __u16 size, int timeout)
+{
+ int err;
+ void *buf = NULL;
+
+ if (size > 0) {
+ buf = kmemdup(data, size, GFP_KERNEL);
+ if (!buf)
+ return -ENOMEM;
+ }
+ err = usb_control_msg(dev, pipe, request, requesttype,
+ value, index, buf, size, timeout);
+ if (size > 0) {
+ memcpy(data, buf, size);
+ kfree(buf);
+ }
+ return err;
+}
+
+static void pt_info_set(struct usb_device *dev, u8 v)
+{
+ int ret;
+
+ ret = usb_control_msg(dev, usb_sndctrlpipe(dev, 0),
+ 'I',
+ USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+ v, 0, NULL, 0, 1000);
+ snd_printdd(KERN_DEBUG "%i\n", ret);
+}
+
+static void usb_stream_hwdep_vm_open(struct vm_area_struct *area)
+{
+ struct us122l *us122l = area->vm_private_data;
+ atomic_inc(&us122l->mmap_count);
+ snd_printdd(KERN_DEBUG "%i\n", atomic_read(&us122l->mmap_count));
+}
+
+static int usb_stream_hwdep_vm_fault(struct vm_area_struct *area,
+ struct vm_fault *vmf)
+{
+ unsigned long offset;
+ struct page *page;
+ void *vaddr;
+ struct us122l *us122l = area->vm_private_data;
+ struct usb_stream *s;
+
+ mutex_lock(&us122l->mutex);
+ s = us122l->sk.s;
+ if (!s)
+ goto unlock;
+
+ offset = vmf->pgoff << PAGE_SHIFT;
+ if (offset < PAGE_ALIGN(s->read_size))
+ vaddr = (char *)s + offset;
+ else {
+ offset -= PAGE_ALIGN(s->read_size);
+ if (offset >= PAGE_ALIGN(s->write_size))
+ goto unlock;
+
+ vaddr = us122l->sk.write_page + offset;
+ }
+ page = virt_to_page(vaddr);
+
+ get_page(page);
+ mutex_unlock(&us122l->mutex);
+
+ vmf->page = page;
+
+ return 0;
+unlock:
+ mutex_unlock(&us122l->mutex);
+ return VM_FAULT_SIGBUS;
+}
+
+static void usb_stream_hwdep_vm_close(struct vm_area_struct *area)
+{
+ struct us122l *us122l = area->vm_private_data;
+ atomic_dec(&us122l->mmap_count);
+ snd_printdd(KERN_DEBUG "%i\n", atomic_read(&us122l->mmap_count));
+}
+
+static const struct vm_operations_struct usb_stream_hwdep_vm_ops = {
+ .open = usb_stream_hwdep_vm_open,
+ .fault = usb_stream_hwdep_vm_fault,
+ .close = usb_stream_hwdep_vm_close,
+};
+
+
+static int usb_stream_hwdep_open(struct snd_hwdep *hw, struct file *file)
+{
+ struct us122l *us122l = hw->private_data;
+ struct usb_interface *iface;
+ snd_printdd(KERN_DEBUG "%p %p\n", hw, file);
+ if (hw->used >= 2)
+ return -EBUSY;
+
+ if (!us122l->first)
+ us122l->first = file;
+
+ if (us122l->dev->descriptor.idProduct == USB_ID_US144 ||
+ us122l->dev->descriptor.idProduct == USB_ID_US144MKII) {
+ iface = usb_ifnum_to_if(us122l->dev, 0);
+ usb_autopm_get_interface(iface);
+ }
+ iface = usb_ifnum_to_if(us122l->dev, 1);
+ usb_autopm_get_interface(iface);
+ return 0;
+}
+
+static int usb_stream_hwdep_release(struct snd_hwdep *hw, struct file *file)
+{
+ struct us122l *us122l = hw->private_data;
+ struct usb_interface *iface;
+ snd_printdd(KERN_DEBUG "%p %p\n", hw, file);
+
+ if (us122l->dev->descriptor.idProduct == USB_ID_US144 ||
+ us122l->dev->descriptor.idProduct == USB_ID_US144MKII) {
+ iface = usb_ifnum_to_if(us122l->dev, 0);
+ usb_autopm_put_interface(iface);
+ }
+ iface = usb_ifnum_to_if(us122l->dev, 1);
+ usb_autopm_put_interface(iface);
+ if (us122l->first == file)
+ us122l->first = NULL;
+ mutex_lock(&us122l->mutex);
+ if (us122l->master == file)
+ us122l->master = us122l->slave;
+
+ us122l->slave = NULL;
+ mutex_unlock(&us122l->mutex);
+ return 0;
+}
+
+static int usb_stream_hwdep_mmap(struct snd_hwdep *hw,
+ struct file *filp, struct vm_area_struct *area)
+{
+ unsigned long size = area->vm_end - area->vm_start;
+ struct us122l *us122l = hw->private_data;
+ unsigned long offset;
+ struct usb_stream *s;
+ int err = 0;
+ bool read;
+
+ offset = area->vm_pgoff << PAGE_SHIFT;
+ mutex_lock(&us122l->mutex);
+ s = us122l->sk.s;
+ read = offset < s->read_size;
+ if (read && area->vm_flags & VM_WRITE) {
+ err = -EPERM;
+ goto out;
+ }
+ snd_printdd(KERN_DEBUG "%lu %u\n", size,
+ read ? s->read_size : s->write_size);
+ /* if userspace tries to mmap beyond end of our buffer, fail */
+ if (size > PAGE_ALIGN(read ? s->read_size : s->write_size)) {
+ snd_printk(KERN_WARNING "%lu > %u\n", size,
+ read ? s->read_size : s->write_size);
+ err = -EINVAL;
+ goto out;
+ }
+
+ area->vm_ops = &usb_stream_hwdep_vm_ops;
+ area->vm_flags |= VM_DONTDUMP;
+ if (!read)
+ area->vm_flags |= VM_DONTEXPAND;
+ area->vm_private_data = us122l;
+ atomic_inc(&us122l->mmap_count);
+out:
+ mutex_unlock(&us122l->mutex);
+ return err;
+}
+
+static unsigned int usb_stream_hwdep_poll(struct snd_hwdep *hw,
+ struct file *file, poll_table *wait)
+{
+ struct us122l *us122l = hw->private_data;
+ unsigned *polled;
+ unsigned int mask;
+
+ poll_wait(file, &us122l->sk.sleep, wait);
+
+ mask = POLLIN | POLLOUT | POLLWRNORM | POLLERR;
+ if (mutex_trylock(&us122l->mutex)) {
+ struct usb_stream *s = us122l->sk.s;
+ if (s && s->state == usb_stream_ready) {
+ if (us122l->first == file)
+ polled = &s->periods_polled;
+ else
+ polled = &us122l->second_periods_polled;
+ if (*polled != s->periods_done) {
+ *polled = s->periods_done;
+ mask = POLLIN | POLLOUT | POLLWRNORM;
+ } else
+ mask = 0;
+ }
+ mutex_unlock(&us122l->mutex);
+ }
+ return mask;
+}
+
+static void us122l_stop(struct us122l *us122l)
+{
+ struct list_head *p;
+ list_for_each(p, &us122l->midi_list)
+ snd_usbmidi_input_stop(p);
+
+ usb_stream_stop(&us122l->sk);
+ usb_stream_free(&us122l->sk);
+}
+
+static int us122l_set_sample_rate(struct usb_device *dev, int rate)
+{
+ unsigned int ep = 0x81;
+ unsigned char data[3];
+ int err;
+
+ data[0] = rate;
+ data[1] = rate >> 8;
+ data[2] = rate >> 16;
+ err = us122l_ctl_msg(dev, usb_sndctrlpipe(dev, 0), UAC_SET_CUR,
+ USB_TYPE_CLASS|USB_RECIP_ENDPOINT|USB_DIR_OUT,
+ UAC_EP_CS_ATTR_SAMPLE_RATE << 8, ep, data, 3, 1000);
+ if (err < 0)
+ snd_printk(KERN_ERR "%d: cannot set freq %d to ep 0x%x\n",
+ dev->devnum, rate, ep);
+ return err;
+}
+
+static bool us122l_start(struct us122l *us122l,
+ unsigned rate, unsigned period_frames)
+{
+ struct list_head *p;
+ int err;
+ unsigned use_packsize = 0;
+ bool success = false;
+
+ if (us122l->dev->speed == USB_SPEED_HIGH) {
+ /* The us-122l's descriptor defaults to iso max_packsize 78,
+ which isn't needed for samplerates <= 48000.
+ Lets save some memory:
+ */
+ switch (rate) {
+ case 44100:
+ use_packsize = 36;
+ break;
+ case 48000:
+ use_packsize = 42;
+ break;
+ case 88200:
+ use_packsize = 72;
+ break;
+ }
+ }
+ if (!usb_stream_new(&us122l->sk, us122l->dev, 1, 2,
+ rate, use_packsize, period_frames, 6))
+ goto out;
+
+ err = us122l_set_sample_rate(us122l->dev, rate);
+ if (err < 0) {
+ us122l_stop(us122l);
+ snd_printk(KERN_ERR "us122l_set_sample_rate error \n");
+ goto out;
+ }
+ err = usb_stream_start(&us122l->sk);
+ if (err < 0) {
+ us122l_stop(us122l);
+ snd_printk(KERN_ERR "us122l_start error %i \n", err);
+ goto out;
+ }
+ list_for_each(p, &us122l->midi_list)
+ snd_usbmidi_input_start(p);
+ success = true;
+out:
+ return success;
+}
+
+static int usb_stream_hwdep_ioctl(struct snd_hwdep *hw, struct file *file,
+ unsigned cmd, unsigned long arg)
+{
+ struct usb_stream_config *cfg;
+ struct us122l *us122l = hw->private_data;
+ struct usb_stream *s;
+ unsigned min_period_frames;
+ int err = 0;
+ bool high_speed;
+
+ if (cmd != SNDRV_USB_STREAM_IOCTL_SET_PARAMS)
+ return -ENOTTY;
+
+ cfg = memdup_user((void *)arg, sizeof(*cfg));
+ if (IS_ERR(cfg))
+ return PTR_ERR(cfg);
+
+ if (cfg->version != USB_STREAM_INTERFACE_VERSION) {
+ err = -ENXIO;
+ goto free;
+ }
+ high_speed = us122l->dev->speed == USB_SPEED_HIGH;
+ if ((cfg->sample_rate != 44100 && cfg->sample_rate != 48000 &&
+ (!high_speed ||
+ (cfg->sample_rate != 88200 && cfg->sample_rate != 96000))) ||
+ cfg->frame_size != 6 ||
+ cfg->period_frames > 0x3000) {
+ err = -EINVAL;
+ goto free;
+ }
+ switch (cfg->sample_rate) {
+ case 44100:
+ min_period_frames = 48;
+ break;
+ case 48000:
+ min_period_frames = 52;
+ break;
+ default:
+ min_period_frames = 104;
+ break;
+ }
+ if (!high_speed)
+ min_period_frames <<= 1;
+ if (cfg->period_frames < min_period_frames) {
+ err = -EINVAL;
+ goto free;
+ }
+
+ snd_power_wait(hw->card, SNDRV_CTL_POWER_D0);
+
+ mutex_lock(&us122l->mutex);
+ s = us122l->sk.s;
+ if (!us122l->master)
+ us122l->master = file;
+ else if (us122l->master != file) {
+ if (!s || memcmp(cfg, &s->cfg, sizeof(*cfg))) {
+ err = -EIO;
+ goto unlock;
+ }
+ us122l->slave = file;
+ }
+ if (!s || memcmp(cfg, &s->cfg, sizeof(*cfg)) ||
+ s->state == usb_stream_xrun) {
+ us122l_stop(us122l);
+ if (!us122l_start(us122l, cfg->sample_rate, cfg->period_frames))
+ err = -EIO;
+ else
+ err = 1;
+ }
+unlock:
+ mutex_unlock(&us122l->mutex);
+free:
+ kfree(cfg);
+ wake_up_all(&us122l->sk.sleep);
+ return err;
+}
+
+#define SND_USB_STREAM_ID "USB STREAM"
+static int usb_stream_hwdep_new(struct snd_card *card)
+{
+ int err;
+ struct snd_hwdep *hw;
+ struct usb_device *dev = US122L(card)->dev;
+
+ err = snd_hwdep_new(card, SND_USB_STREAM_ID, 0, &hw);
+ if (err < 0)
+ return err;
+
+ hw->iface = SNDRV_HWDEP_IFACE_USB_STREAM;
+ hw->private_data = US122L(card);
+ hw->ops.open = usb_stream_hwdep_open;
+ hw->ops.release = usb_stream_hwdep_release;
+ hw->ops.ioctl = usb_stream_hwdep_ioctl;
+ hw->ops.ioctl_compat = usb_stream_hwdep_ioctl;
+ hw->ops.mmap = usb_stream_hwdep_mmap;
+ hw->ops.poll = usb_stream_hwdep_poll;
+
+ sprintf(hw->name, "/proc/bus/usb/%03d/%03d/hwdeppcm",
+ dev->bus->busnum, dev->devnum);
+ return 0;
+}
+
+
+static bool us122l_create_card(struct snd_card *card)
+{
+ int err;
+ struct us122l *us122l = US122L(card);
+
+ if (us122l->dev->descriptor.idProduct == USB_ID_US144 ||
+ us122l->dev->descriptor.idProduct == USB_ID_US144MKII) {
+ err = usb_set_interface(us122l->dev, 0, 1);
+ if (err) {
+ snd_printk(KERN_ERR "usb_set_interface error \n");
+ return false;
+ }
+ }
+ err = usb_set_interface(us122l->dev, 1, 1);
+ if (err) {
+ snd_printk(KERN_ERR "usb_set_interface error \n");
+ return false;
+ }
+
+ pt_info_set(us122l->dev, 0x11);
+ pt_info_set(us122l->dev, 0x10);
+
+ if (!us122l_start(us122l, 44100, 256))
+ return false;
+
+ if (us122l->dev->descriptor.idProduct == USB_ID_US144 ||
+ us122l->dev->descriptor.idProduct == USB_ID_US144MKII)
+ err = us144_create_usbmidi(card);
+ else
+ err = us122l_create_usbmidi(card);
+ if (err < 0) {
+ snd_printk(KERN_ERR "us122l_create_usbmidi error %i \n", err);
+ us122l_stop(us122l);
+ return false;
+ }
+ err = usb_stream_hwdep_new(card);
+ if (err < 0) {
+/* release the midi resources */
+ struct list_head *p;
+ list_for_each(p, &us122l->midi_list)
+ snd_usbmidi_disconnect(p);
+
+ us122l_stop(us122l);
+ return false;
+ }
+ return true;
+}
+
+static void snd_us122l_free(struct snd_card *card)
+{
+ struct us122l *us122l = US122L(card);
+ int index = us122l->card_index;
+ if (index >= 0 && index < SNDRV_CARDS)
+ snd_us122l_card_used[index] = 0;
+}
+
+static int usx2y_create_card(struct usb_device *device,
+ struct usb_interface *intf,
+ struct snd_card **cardp)
+{
+ int dev;
+ struct snd_card *card;
+ int err;
+
+ for (dev = 0; dev < SNDRV_CARDS; ++dev)
+ if (enable[dev] && !snd_us122l_card_used[dev])
+ break;
+ if (dev >= SNDRV_CARDS)
+ return -ENODEV;
+ err = snd_card_new(&intf->dev, index[dev], id[dev], THIS_MODULE,
+ sizeof(struct us122l), &card);
+ if (err < 0)
+ return err;
+ snd_us122l_card_used[US122L(card)->card_index = dev] = 1;
+ card->private_free = snd_us122l_free;
+ US122L(card)->dev = device;
+ mutex_init(&US122L(card)->mutex);
+ init_waitqueue_head(&US122L(card)->sk.sleep);
+ INIT_LIST_HEAD(&US122L(card)->midi_list);
+ strcpy(card->driver, "USB "NAME_ALLCAPS"");
+ sprintf(card->shortname, "TASCAM "NAME_ALLCAPS"");
+ sprintf(card->longname, "%s (%x:%x if %d at %03d/%03d)",
+ card->shortname,
+ le16_to_cpu(device->descriptor.idVendor),
+ le16_to_cpu(device->descriptor.idProduct),
+ 0,
+ US122L(card)->dev->bus->busnum,
+ US122L(card)->dev->devnum
+ );
+ *cardp = card;
+ return 0;
+}
+
+static int us122l_usb_probe(struct usb_interface *intf,
+ const struct usb_device_id *device_id,
+ struct snd_card **cardp)
+{
+ struct usb_device *device = interface_to_usbdev(intf);
+ struct snd_card *card;
+ int err;
+
+ err = usx2y_create_card(device, intf, &card);
+ if (err < 0)
+ return err;
+
+ if (!us122l_create_card(card)) {
+ snd_card_free(card);
+ return -EINVAL;
+ }
+
+ err = snd_card_register(card);
+ if (err < 0) {
+ snd_card_free(card);
+ return err;
+ }
+
+ usb_get_intf(usb_ifnum_to_if(device, 0));
+ usb_get_dev(device);
+ *cardp = card;
+ return 0;
+}
+
+static int snd_us122l_probe(struct usb_interface *intf,
+ const struct usb_device_id *id)
+{
+ struct usb_device *device = interface_to_usbdev(intf);
+ struct snd_card *card;
+ int err;
+
+ if ((device->descriptor.idProduct == USB_ID_US144 ||
+ device->descriptor.idProduct == USB_ID_US144MKII)
+ && device->speed == USB_SPEED_HIGH) {
+ snd_printk(KERN_ERR "disable ehci-hcd to run US-144 \n");
+ return -ENODEV;
+ }
+
+ snd_printdd(KERN_DEBUG"%p:%i\n",
+ intf, intf->cur_altsetting->desc.bInterfaceNumber);
+ if (intf->cur_altsetting->desc.bInterfaceNumber != 1)
+ return 0;
+
+ err = us122l_usb_probe(usb_get_intf(intf), id, &card);
+ if (err < 0) {
+ usb_put_intf(intf);
+ return err;
+ }
+
+ usb_set_intfdata(intf, card);
+ return 0;
+}
+
+static void snd_us122l_disconnect(struct usb_interface *intf)
+{
+ struct snd_card *card;
+ struct us122l *us122l;
+ struct list_head *p;
+
+ card = usb_get_intfdata(intf);
+ if (!card)
+ return;
+
+ snd_card_disconnect(card);
+
+ us122l = US122L(card);
+ mutex_lock(&us122l->mutex);
+ us122l_stop(us122l);
+ mutex_unlock(&us122l->mutex);
+
+/* release the midi resources */
+ list_for_each(p, &us122l->midi_list) {
+ snd_usbmidi_disconnect(p);
+ }
+
+ usb_put_intf(usb_ifnum_to_if(us122l->dev, 0));
+ usb_put_intf(usb_ifnum_to_if(us122l->dev, 1));
+ usb_put_dev(us122l->dev);
+
+ while (atomic_read(&us122l->mmap_count))
+ msleep(500);
+
+ snd_card_free(card);
+}
+
+static int snd_us122l_suspend(struct usb_interface *intf, pm_message_t message)
+{
+ struct snd_card *card;
+ struct us122l *us122l;
+ struct list_head *p;
+
+ card = usb_get_intfdata(intf);
+ if (!card)
+ return 0;
+ snd_power_change_state(card, SNDRV_CTL_POWER_D3hot);
+
+ us122l = US122L(card);
+ if (!us122l)
+ return 0;
+
+ list_for_each(p, &us122l->midi_list)
+ snd_usbmidi_input_stop(p);
+
+ mutex_lock(&us122l->mutex);
+ usb_stream_stop(&us122l->sk);
+ mutex_unlock(&us122l->mutex);
+
+ return 0;
+}
+
+static int snd_us122l_resume(struct usb_interface *intf)
+{
+ struct snd_card *card;
+ struct us122l *us122l;
+ struct list_head *p;
+ int err;
+
+ card = usb_get_intfdata(intf);
+ if (!card)
+ return 0;
+
+ us122l = US122L(card);
+ if (!us122l)
+ return 0;
+
+ mutex_lock(&us122l->mutex);
+ /* needed, doesn't restart without: */
+ if (us122l->dev->descriptor.idProduct == USB_ID_US144 ||
+ us122l->dev->descriptor.idProduct == USB_ID_US144MKII) {
+ err = usb_set_interface(us122l->dev, 0, 1);
+ if (err) {
+ snd_printk(KERN_ERR "usb_set_interface error \n");
+ goto unlock;
+ }
+ }
+ err = usb_set_interface(us122l->dev, 1, 1);
+ if (err) {
+ snd_printk(KERN_ERR "usb_set_interface error \n");
+ goto unlock;
+ }
+
+ pt_info_set(us122l->dev, 0x11);
+ pt_info_set(us122l->dev, 0x10);
+
+ err = us122l_set_sample_rate(us122l->dev,
+ us122l->sk.s->cfg.sample_rate);
+ if (err < 0) {
+ snd_printk(KERN_ERR "us122l_set_sample_rate error \n");
+ goto unlock;
+ }
+ err = usb_stream_start(&us122l->sk);
+ if (err)
+ goto unlock;
+
+ list_for_each(p, &us122l->midi_list)
+ snd_usbmidi_input_start(p);
+unlock:
+ mutex_unlock(&us122l->mutex);
+ snd_power_change_state(card, SNDRV_CTL_POWER_D0);
+ return err;
+}
+
+static struct usb_device_id snd_us122l_usb_id_table[] = {
+ {
+ .match_flags = USB_DEVICE_ID_MATCH_DEVICE,
+ .idVendor = 0x0644,
+ .idProduct = USB_ID_US122L
+ },
+ { /* US-144 only works at USB1.1! Disable module ehci-hcd. */
+ .match_flags = USB_DEVICE_ID_MATCH_DEVICE,
+ .idVendor = 0x0644,
+ .idProduct = USB_ID_US144
+ },
+ {
+ .match_flags = USB_DEVICE_ID_MATCH_DEVICE,
+ .idVendor = 0x0644,
+ .idProduct = USB_ID_US122MKII
+ },
+ {
+ .match_flags = USB_DEVICE_ID_MATCH_DEVICE,
+ .idVendor = 0x0644,
+ .idProduct = USB_ID_US144MKII
+ },
+ { /* terminator */ }
+};
+
+MODULE_DEVICE_TABLE(usb, snd_us122l_usb_id_table);
+static struct usb_driver snd_us122l_usb_driver = {
+ .name = "snd-usb-us122l",
+ .probe = snd_us122l_probe,
+ .disconnect = snd_us122l_disconnect,
+ .suspend = snd_us122l_suspend,
+ .resume = snd_us122l_resume,
+ .reset_resume = snd_us122l_resume,
+ .id_table = snd_us122l_usb_id_table,
+ .supports_autosuspend = 1
+};
+
+module_usb_driver(snd_us122l_usb_driver);
diff --git a/sound/usb/usx2y/us122l.h b/sound/usb/usx2y/us122l.h
new file mode 100644
index 000000000..f263b3f96
--- /dev/null
+++ b/sound/usb/usx2y/us122l.h
@@ -0,0 +1,31 @@
+#ifndef US122L_H
+#define US122L_H
+
+
+struct us122l {
+ struct usb_device *dev;
+ int card_index;
+ int stride;
+ struct usb_stream_kernel sk;
+
+ struct mutex mutex;
+ struct file *first;
+ unsigned second_periods_polled;
+ struct file *master;
+ struct file *slave;
+ struct list_head midi_list;
+
+ atomic_t mmap_count;
+};
+
+
+#define US122L(c) ((struct us122l *)(c)->private_data)
+
+#define NAME_ALLCAPS "US-122L"
+
+#define USB_ID_US122L 0x800E
+#define USB_ID_US144 0x800F
+#define USB_ID_US122MKII 0x8021
+#define USB_ID_US144MKII 0x8020
+
+#endif
diff --git a/sound/usb/usx2y/usX2Yhwdep.c b/sound/usb/usx2y/usX2Yhwdep.c
new file mode 100644
index 000000000..0b34dbc8f
--- /dev/null
+++ b/sound/usb/usx2y/usX2Yhwdep.c
@@ -0,0 +1,265 @@
+/*
+ * Driver for Tascam US-X2Y USB soundcards
+ *
+ * FPGA Loader + ALSA Startup
+ *
+ * Copyright (c) 2003 by Karsten Wiese <annabellesgarden@yahoo.de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include <linux/interrupt.h>
+#include <linux/slab.h>
+#include <linux/usb.h>
+#include <sound/core.h>
+#include <sound/memalloc.h>
+#include <sound/pcm.h>
+#include <sound/hwdep.h>
+#include "usx2y.h"
+#include "usbusx2y.h"
+#include "usX2Yhwdep.h"
+
+static int snd_us428ctls_vm_fault(struct vm_area_struct *area,
+ struct vm_fault *vmf)
+{
+ unsigned long offset;
+ struct page * page;
+ void *vaddr;
+
+ snd_printdd("ENTER, start %lXh, pgoff %ld\n",
+ area->vm_start,
+ vmf->pgoff);
+
+ offset = vmf->pgoff << PAGE_SHIFT;
+ vaddr = (char*)((struct usX2Ydev *)area->vm_private_data)->us428ctls_sharedmem + offset;
+ page = virt_to_page(vaddr);
+ get_page(page);
+ vmf->page = page;
+
+ snd_printdd("vaddr=%p made us428ctls_vm_fault() page %p\n",
+ vaddr, page);
+
+ return 0;
+}
+
+static const struct vm_operations_struct us428ctls_vm_ops = {
+ .fault = snd_us428ctls_vm_fault,
+};
+
+static int snd_us428ctls_mmap(struct snd_hwdep * hw, struct file *filp, struct vm_area_struct *area)
+{
+ unsigned long size = (unsigned long)(area->vm_end - area->vm_start);
+ struct usX2Ydev *us428 = hw->private_data;
+
+ // FIXME this hwdep interface is used twice: fpga download and mmap for controlling Lights etc. Maybe better using 2 hwdep devs?
+ // so as long as the device isn't fully initialised yet we return -EBUSY here.
+ if (!(us428->chip_status & USX2Y_STAT_CHIP_INIT))
+ return -EBUSY;
+
+ /* if userspace tries to mmap beyond end of our buffer, fail */
+ if (size > PAGE_ALIGN(sizeof(struct us428ctls_sharedmem))) {
+ snd_printd( "%lu > %lu\n", size, (unsigned long)sizeof(struct us428ctls_sharedmem));
+ return -EINVAL;
+ }
+
+ if (!us428->us428ctls_sharedmem) {
+ init_waitqueue_head(&us428->us428ctls_wait_queue_head);
+ if(!(us428->us428ctls_sharedmem = snd_malloc_pages(sizeof(struct us428ctls_sharedmem), GFP_KERNEL)))
+ return -ENOMEM;
+ memset(us428->us428ctls_sharedmem, -1, sizeof(struct us428ctls_sharedmem));
+ us428->us428ctls_sharedmem->CtlSnapShotLast = -2;
+ }
+ area->vm_ops = &us428ctls_vm_ops;
+ area->vm_flags |= VM_DONTEXPAND | VM_DONTDUMP;
+ area->vm_private_data = hw->private_data;
+ return 0;
+}
+
+static unsigned int snd_us428ctls_poll(struct snd_hwdep *hw, struct file *file, poll_table *wait)
+{
+ unsigned int mask = 0;
+ struct usX2Ydev *us428 = hw->private_data;
+ struct us428ctls_sharedmem *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_dsp_status(struct snd_hwdep *hw,
+ struct snd_hwdep_dsp_status *info)
+{
+ static char *type_ids[USX2Y_TYPE_NUMS] = {
+ [USX2Y_TYPE_122] = "us122",
+ [USX2Y_TYPE_224] = "us224",
+ [USX2Y_TYPE_428] = "us428",
+ };
+ struct usX2Ydev *us428 = hw->private_data;
+ int id = -1;
+
+ switch (le16_to_cpu(us428->dev->descriptor.idProduct)) {
+ case USB_ID_US122:
+ id = USX2Y_TYPE_122;
+ break;
+ case USB_ID_US224:
+ id = USX2Y_TYPE_224;
+ break;
+ case USB_ID_US428:
+ id = USX2Y_TYPE_428;
+ break;
+ }
+ if (0 > id)
+ return -ENODEV;
+ strcpy(info->id, type_ids[id]);
+ info->num_dsps = 2; // 0: Prepad Data, 1: FPGA Code
+ if (us428->chip_status & USX2Y_STAT_CHIP_INIT)
+ info->chip_ready = 1;
+ info->version = USX2Y_DRIVER_VERSION;
+ return 0;
+}
+
+
+static int usX2Y_create_usbmidi(struct snd_card *card)
+{
+ static struct snd_usb_midi_endpoint_info quirk_data_1 = {
+ .out_ep = 0x06,
+ .in_ep = 0x06,
+ .out_cables = 0x001,
+ .in_cables = 0x001
+ };
+ static struct snd_usb_audio_quirk quirk_1 = {
+ .vendor_name = "TASCAM",
+ .product_name = NAME_ALLCAPS,
+ .ifnum = 0,
+ .type = QUIRK_MIDI_FIXED_ENDPOINT,
+ .data = &quirk_data_1
+ };
+ static struct snd_usb_midi_endpoint_info quirk_data_2 = {
+ .out_ep = 0x06,
+ .in_ep = 0x06,
+ .out_cables = 0x003,
+ .in_cables = 0x003
+ };
+ static struct snd_usb_audio_quirk quirk_2 = {
+ .vendor_name = "TASCAM",
+ .product_name = "US428",
+ .ifnum = 0,
+ .type = QUIRK_MIDI_FIXED_ENDPOINT,
+ .data = &quirk_data_2
+ };
+ struct usb_device *dev = usX2Y(card)->dev;
+ struct usb_interface *iface = usb_ifnum_to_if(dev, 0);
+ struct snd_usb_audio_quirk *quirk =
+ le16_to_cpu(dev->descriptor.idProduct) == USB_ID_US428 ?
+ &quirk_2 : &quirk_1;
+
+ snd_printdd("usX2Y_create_usbmidi \n");
+ return snd_usbmidi_create(card, iface, &usX2Y(card)->midi_list, quirk);
+}
+
+static int usX2Y_create_alsa_devices(struct snd_card *card)
+{
+ int err;
+
+ do {
+ if ((err = usX2Y_create_usbmidi(card)) < 0) {
+ snd_printk(KERN_ERR "usX2Y_create_alsa_devices: usX2Y_create_usbmidi error %i \n", err);
+ break;
+ }
+ if ((err = usX2Y_audio_create(card)) < 0)
+ break;
+ if ((err = usX2Y_hwdep_pcm_new(card)) < 0)
+ break;
+ if ((err = snd_card_register(card)) < 0)
+ break;
+ } while (0);
+
+ return err;
+}
+
+static int snd_usX2Y_hwdep_dsp_load(struct snd_hwdep *hw,
+ struct snd_hwdep_dsp_image *dsp)
+{
+ struct usX2Ydev *priv = hw->private_data;
+ 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->dev;
+ char *buf;
+
+ buf = memdup_user(dsp->image, dsp->length);
+ if (IS_ERR(buf))
+ return PTR_ERR(buf);
+
+ err = usb_set_interface(dev, 0, 1);
+ if (err)
+ snd_printk(KERN_ERR "usb_set_interface error \n");
+ else
+ err = usb_bulk_msg(dev, usb_sndbulkpipe(dev, 2), buf, dsp->length, &lret, 6000);
+ kfree(buf);
+ }
+ if (err)
+ return err;
+ if (dsp->index == 1) {
+ msleep(250); // give the device some time
+ err = usX2Y_AsyncSeq04_init(priv);
+ if (err) {
+ snd_printk(KERN_ERR "usX2Y_AsyncSeq04_init error \n");
+ return err;
+ }
+ err = usX2Y_In04_init(priv);
+ if (err) {
+ snd_printk(KERN_ERR "usX2Y_In04_init error \n");
+ return err;
+ }
+ err = usX2Y_create_alsa_devices(hw->card);
+ if (err) {
+ snd_printk(KERN_ERR "usX2Y_create_alsa_devices error %i \n", err);
+ snd_card_free(hw->card);
+ return err;
+ }
+ priv->chip_status |= USX2Y_STAT_CHIP_INIT;
+ snd_printdd("%s: alsa all started\n", hw->name);
+ }
+ return err;
+}
+
+
+int usX2Y_hwdep_new(struct snd_card *card, struct usb_device* device)
+{
+ int err;
+ struct snd_hwdep *hw;
+
+ if ((err = snd_hwdep_new(card, SND_USX2Y_LOADER_ID, 0, &hw)) < 0)
+ return err;
+
+ hw->iface = SNDRV_HWDEP_IFACE_USX2Y;
+ hw->private_data = usX2Y(card);
+ hw->ops.dsp_status = snd_usX2Y_hwdep_dsp_status;
+ hw->ops.dsp_load = snd_usX2Y_hwdep_dsp_load;
+ hw->ops.mmap = snd_us428ctls_mmap;
+ hw->ops.poll = snd_us428ctls_poll;
+ hw->exclusive = 1;
+ sprintf(hw->name, "/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 000000000..c095d5bf1
--- /dev/null
+++ b/sound/usb/usx2y/usX2Yhwdep.h
@@ -0,0 +1,6 @@
+#ifndef USX2YHWDEP_H
+#define USX2YHWDEP_H
+
+int usX2Y_hwdep_new(struct snd_card *card, struct usb_device* device);
+
+#endif
diff --git a/sound/usb/usx2y/usb_stream.c b/sound/usb/usx2y/usb_stream.c
new file mode 100644
index 000000000..bf618e150
--- /dev/null
+++ b/sound/usb/usx2y/usb_stream.c
@@ -0,0 +1,753 @@
+/*
+ * Copyright (C) 2007, 2008 Karsten Wiese <fzu@wemgehoertderstaat.de>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <linux/usb.h>
+#include <linux/gfp.h>
+
+#include "usb_stream.h"
+
+
+/* setup */
+
+static unsigned usb_stream_next_packet_size(struct usb_stream_kernel *sk)
+{
+ struct usb_stream *s = sk->s;
+ sk->out_phase_peeked = (sk->out_phase & 0xffff) + sk->freqn;
+ return (sk->out_phase_peeked >> 16) * s->cfg.frame_size;
+}
+
+static void playback_prep_freqn(struct usb_stream_kernel *sk, struct urb *urb)
+{
+ struct usb_stream *s = sk->s;
+ int pack, lb = 0;
+
+ for (pack = 0; pack < sk->n_o_ps; pack++) {
+ int l = usb_stream_next_packet_size(sk);
+ if (s->idle_outsize + lb + l > s->period_size)
+ goto check;
+
+ sk->out_phase = sk->out_phase_peeked;
+ urb->iso_frame_desc[pack].offset = lb;
+ urb->iso_frame_desc[pack].length = l;
+ lb += l;
+ }
+ snd_printdd(KERN_DEBUG "%i\n", lb);
+
+check:
+ urb->number_of_packets = pack;
+ urb->transfer_buffer_length = lb;
+ s->idle_outsize += lb - s->period_size;
+ snd_printdd(KERN_DEBUG "idle=%i ul=%i ps=%i\n", s->idle_outsize,
+ lb, s->period_size);
+}
+
+static void init_pipe_urbs(struct usb_stream_kernel *sk, unsigned use_packsize,
+ struct urb **urbs, char *transfer,
+ struct usb_device *dev, int pipe)
+{
+ int u, p;
+ int maxpacket = use_packsize ?
+ use_packsize : usb_maxpacket(dev, pipe, usb_pipeout(pipe));
+ int transfer_length = maxpacket * sk->n_o_ps;
+
+ for (u = 0; u < USB_STREAM_NURBS;
+ ++u, transfer += transfer_length) {
+ struct urb *urb = urbs[u];
+ struct usb_iso_packet_descriptor *desc;
+ urb->transfer_buffer = transfer;
+ urb->dev = dev;
+ urb->pipe = pipe;
+ urb->number_of_packets = sk->n_o_ps;
+ urb->context = sk;
+ urb->interval = 1;
+ if (usb_pipeout(pipe))
+ continue;
+
+ urb->transfer_buffer_length = transfer_length;
+ desc = urb->iso_frame_desc;
+ desc->offset = 0;
+ desc->length = maxpacket;
+ for (p = 1; p < sk->n_o_ps; ++p) {
+ desc[p].offset = desc[p - 1].offset + maxpacket;
+ desc[p].length = maxpacket;
+ }
+ }
+}
+
+static void init_urbs(struct usb_stream_kernel *sk, unsigned use_packsize,
+ struct usb_device *dev, int in_pipe, int out_pipe)
+{
+ struct usb_stream *s = sk->s;
+ char *indata = (char *)s + sizeof(*s) +
+ sizeof(struct usb_stream_packet) *
+ s->inpackets;
+ int u;
+
+ for (u = 0; u < USB_STREAM_NURBS; ++u) {
+ sk->inurb[u] = usb_alloc_urb(sk->n_o_ps, GFP_KERNEL);
+ sk->outurb[u] = usb_alloc_urb(sk->n_o_ps, GFP_KERNEL);
+ }
+
+ init_pipe_urbs(sk, use_packsize, sk->inurb, indata, dev, in_pipe);
+ init_pipe_urbs(sk, use_packsize, sk->outurb, sk->write_page, dev,
+ out_pipe);
+}
+
+
+/*
+ * convert a sampling rate into our full speed format (fs/1000 in Q16.16)
+ * this will overflow at approx 524 kHz
+ */
+static inline unsigned get_usb_full_speed_rate(unsigned rate)
+{
+ return ((rate << 13) + 62) / 125;
+}
+
+/*
+ * convert a sampling rate into USB high speed format (fs/8000 in Q16.16)
+ * this will overflow at approx 4 MHz
+ */
+static inline unsigned get_usb_high_speed_rate(unsigned rate)
+{
+ return ((rate << 10) + 62) / 125;
+}
+
+void usb_stream_free(struct usb_stream_kernel *sk)
+{
+ struct usb_stream *s;
+ unsigned u;
+
+ for (u = 0; u < USB_STREAM_NURBS; ++u) {
+ usb_free_urb(sk->inurb[u]);
+ sk->inurb[u] = NULL;
+ usb_free_urb(sk->outurb[u]);
+ sk->outurb[u] = NULL;
+ }
+
+ s = sk->s;
+ if (!s)
+ return;
+
+ free_pages((unsigned long)sk->write_page, get_order(s->write_size));
+ sk->write_page = NULL;
+ free_pages((unsigned long)s, get_order(s->read_size));
+ sk->s = NULL;
+}
+
+struct usb_stream *usb_stream_new(struct usb_stream_kernel *sk,
+ struct usb_device *dev,
+ unsigned in_endpoint, unsigned out_endpoint,
+ unsigned sample_rate, unsigned use_packsize,
+ unsigned period_frames, unsigned frame_size)
+{
+ int packets, max_packsize;
+ int in_pipe, out_pipe;
+ int read_size = sizeof(struct usb_stream);
+ int write_size;
+ int usb_frames = dev->speed == USB_SPEED_HIGH ? 8000 : 1000;
+ int pg;
+
+ in_pipe = usb_rcvisocpipe(dev, in_endpoint);
+ out_pipe = usb_sndisocpipe(dev, out_endpoint);
+
+ max_packsize = use_packsize ?
+ use_packsize : usb_maxpacket(dev, in_pipe, 0);
+
+ /*
+ t_period = period_frames / sample_rate
+ iso_packs = t_period / t_iso_frame
+ = (period_frames / sample_rate) * (1 / t_iso_frame)
+ */
+
+ packets = period_frames * usb_frames / sample_rate + 1;
+
+ if (dev->speed == USB_SPEED_HIGH)
+ packets = (packets + 7) & ~7;
+
+ read_size += packets * USB_STREAM_URBDEPTH *
+ (max_packsize + sizeof(struct usb_stream_packet));
+
+ max_packsize = usb_maxpacket(dev, out_pipe, 1);
+ write_size = max_packsize * packets * USB_STREAM_URBDEPTH;
+
+ if (read_size >= 256*PAGE_SIZE || write_size >= 256*PAGE_SIZE) {
+ snd_printk(KERN_WARNING "a size exceeds 128*PAGE_SIZE\n");
+ goto out;
+ }
+
+ pg = get_order(read_size);
+ sk->s = (void *) __get_free_pages(GFP_KERNEL|__GFP_COMP|__GFP_ZERO, pg);
+ if (!sk->s) {
+ snd_printk(KERN_WARNING "couldn't __get_free_pages()\n");
+ goto out;
+ }
+ sk->s->cfg.version = USB_STREAM_INTERFACE_VERSION;
+
+ sk->s->read_size = read_size;
+
+ sk->s->cfg.sample_rate = sample_rate;
+ sk->s->cfg.frame_size = frame_size;
+ sk->n_o_ps = packets;
+ sk->s->inpackets = packets * USB_STREAM_URBDEPTH;
+ sk->s->cfg.period_frames = period_frames;
+ sk->s->period_size = frame_size * period_frames;
+
+ sk->s->write_size = write_size;
+ pg = get_order(write_size);
+
+ sk->write_page =
+ (void *)__get_free_pages(GFP_KERNEL|__GFP_COMP|__GFP_ZERO, pg);
+ if (!sk->write_page) {
+ snd_printk(KERN_WARNING "couldn't __get_free_pages()\n");
+ usb_stream_free(sk);
+ return NULL;
+ }
+
+ /* calculate the frequency in 16.16 format */
+ if (dev->speed == USB_SPEED_FULL)
+ sk->freqn = get_usb_full_speed_rate(sample_rate);
+ else
+ sk->freqn = get_usb_high_speed_rate(sample_rate);
+
+ init_urbs(sk, use_packsize, dev, in_pipe, out_pipe);
+ sk->s->state = usb_stream_stopped;
+out:
+ return sk->s;
+}
+
+
+/* start */
+
+static bool balance_check(struct usb_stream_kernel *sk, struct urb *urb)
+{
+ bool r;
+ if (unlikely(urb->status)) {
+ if (urb->status != -ESHUTDOWN && urb->status != -ENOENT)
+ snd_printk(KERN_WARNING "status=%i\n", urb->status);
+ sk->iso_frame_balance = 0x7FFFFFFF;
+ return false;
+ }
+ r = sk->iso_frame_balance == 0;
+ if (!r)
+ sk->i_urb = urb;
+ return r;
+}
+
+static bool balance_playback(struct usb_stream_kernel *sk, struct urb *urb)
+{
+ sk->iso_frame_balance += urb->number_of_packets;
+ return balance_check(sk, urb);
+}
+
+static bool balance_capture(struct usb_stream_kernel *sk, struct urb *urb)
+{
+ sk->iso_frame_balance -= urb->number_of_packets;
+ return balance_check(sk, urb);
+}
+
+static void subs_set_complete(struct urb **urbs, void (*complete)(struct urb *))
+{
+ int u;
+
+ for (u = 0; u < USB_STREAM_NURBS; u++) {
+ struct urb *urb = urbs[u];
+ urb->complete = complete;
+ }
+}
+
+static int usb_stream_prepare_playback(struct usb_stream_kernel *sk,
+ struct urb *inurb)
+{
+ struct usb_stream *s = sk->s;
+ struct urb *io;
+ struct usb_iso_packet_descriptor *id, *od;
+ int p = 0, lb = 0, l = 0;
+
+ io = sk->idle_outurb;
+ od = io->iso_frame_desc;
+
+ for (; s->sync_packet < 0; ++p, ++s->sync_packet) {
+ struct urb *ii = sk->completed_inurb;
+ id = ii->iso_frame_desc +
+ ii->number_of_packets + s->sync_packet;
+ l = id->actual_length;
+
+ od[p].length = l;
+ od[p].offset = lb;
+ lb += l;
+ }
+
+ for (;
+ s->sync_packet < inurb->number_of_packets && p < sk->n_o_ps;
+ ++p, ++s->sync_packet) {
+ l = inurb->iso_frame_desc[s->sync_packet].actual_length;
+
+ if (s->idle_outsize + lb + l > s->period_size)
+ goto check_ok;
+
+ od[p].length = l;
+ od[p].offset = lb;
+ lb += l;
+ }
+
+check_ok:
+ s->sync_packet -= inurb->number_of_packets;
+ if (unlikely(s->sync_packet < -2 || s->sync_packet > 0)) {
+ snd_printk(KERN_WARNING "invalid sync_packet = %i;"
+ " p=%i nop=%i %i %x %x %x > %x\n",
+ s->sync_packet, p, inurb->number_of_packets,
+ s->idle_outsize + lb + l,
+ s->idle_outsize, lb, l,
+ s->period_size);
+ return -1;
+ }
+ if (unlikely(lb % s->cfg.frame_size)) {
+ snd_printk(KERN_WARNING"invalid outsize = %i\n",
+ lb);
+ return -1;
+ }
+ s->idle_outsize += lb - s->period_size;
+ io->number_of_packets = p;
+ io->transfer_buffer_length = lb;
+ if (s->idle_outsize <= 0)
+ return 0;
+
+ snd_printk(KERN_WARNING "idle=%i\n", s->idle_outsize);
+ return -1;
+}
+
+static void prepare_inurb(int number_of_packets, struct urb *iu)
+{
+ struct usb_iso_packet_descriptor *id;
+ int p;
+
+ iu->number_of_packets = number_of_packets;
+ id = iu->iso_frame_desc;
+ id->offset = 0;
+ for (p = 0; p < iu->number_of_packets - 1; ++p)
+ id[p + 1].offset = id[p].offset + id[p].length;
+
+ iu->transfer_buffer_length =
+ id[0].length * iu->number_of_packets;
+}
+
+static int submit_urbs(struct usb_stream_kernel *sk,
+ struct urb *inurb, struct urb *outurb)
+{
+ int err;
+ prepare_inurb(sk->idle_outurb->number_of_packets, sk->idle_inurb);
+ err = usb_submit_urb(sk->idle_inurb, GFP_ATOMIC);
+ if (err < 0) {
+ snd_printk(KERN_ERR "%i\n", err);
+ return err;
+ }
+ sk->idle_inurb = sk->completed_inurb;
+ sk->completed_inurb = inurb;
+ err = usb_submit_urb(sk->idle_outurb, GFP_ATOMIC);
+ if (err < 0) {
+ snd_printk(KERN_ERR "%i\n", err);
+ return err;
+ }
+ sk->idle_outurb = sk->completed_outurb;
+ sk->completed_outurb = outurb;
+ return 0;
+}
+
+#ifdef DEBUG_LOOP_BACK
+/*
+ This loop_back() shows how to read/write the period data.
+ */
+static void loop_back(struct usb_stream *s)
+{
+ char *i, *o;
+ int il, ol, l, p;
+ struct urb *iu;
+ struct usb_iso_packet_descriptor *id;
+
+ o = s->playback1st_to;
+ ol = s->playback1st_size;
+ l = 0;
+
+ if (s->insplit_pack >= 0) {
+ iu = sk->idle_inurb;
+ id = iu->iso_frame_desc;
+ p = s->insplit_pack;
+ } else
+ goto second;
+loop:
+ for (; p < iu->number_of_packets && l < s->period_size; ++p) {
+ i = iu->transfer_buffer + id[p].offset;
+ il = id[p].actual_length;
+ if (l + il > s->period_size)
+ il = s->period_size - l;
+ if (il <= ol) {
+ memcpy(o, i, il);
+ o += il;
+ ol -= il;
+ } else {
+ memcpy(o, i, ol);
+ singen_6pack(o, ol);
+ o = s->playback_to;
+ memcpy(o, i + ol, il - ol);
+ o += il - ol;
+ ol = s->period_size - s->playback1st_size;
+ }
+ l += il;
+ }
+ if (iu == sk->completed_inurb) {
+ if (l != s->period_size)
+ printk(KERN_DEBUG"%s:%i %i\n", __func__, __LINE__,
+ l/(int)s->cfg.frame_size);
+
+ return;
+ }
+second:
+ iu = sk->completed_inurb;
+ id = iu->iso_frame_desc;
+ p = 0;
+ goto loop;
+
+}
+#else
+static void loop_back(struct usb_stream *s)
+{
+}
+#endif
+
+static void stream_idle(struct usb_stream_kernel *sk,
+ struct urb *inurb, struct urb *outurb)
+{
+ struct usb_stream *s = sk->s;
+ int l, p;
+ int insize = s->idle_insize;
+ int urb_size = 0;
+
+ s->inpacket_split = s->next_inpacket_split;
+ s->inpacket_split_at = s->next_inpacket_split_at;
+ s->next_inpacket_split = -1;
+ s->next_inpacket_split_at = 0;
+
+ for (p = 0; p < inurb->number_of_packets; ++p) {
+ struct usb_iso_packet_descriptor *id = inurb->iso_frame_desc;
+ l = id[p].actual_length;
+ if (unlikely(l == 0 || id[p].status)) {
+ snd_printk(KERN_WARNING "underrun, status=%u\n",
+ id[p].status);
+ goto err_out;
+ }
+ s->inpacket_head++;
+ s->inpacket_head %= s->inpackets;
+ if (s->inpacket_split == -1)
+ s->inpacket_split = s->inpacket_head;
+
+ s->inpacket[s->inpacket_head].offset =
+ id[p].offset + (inurb->transfer_buffer - (void *)s);
+ s->inpacket[s->inpacket_head].length = l;
+ if (insize + l > s->period_size &&
+ s->next_inpacket_split == -1) {
+ s->next_inpacket_split = s->inpacket_head;
+ s->next_inpacket_split_at = s->period_size - insize;
+ }
+ insize += l;
+ urb_size += l;
+ }
+ s->idle_insize += urb_size - s->period_size;
+ if (s->idle_insize < 0) {
+ snd_printk(KERN_WARNING "%i\n",
+ (s->idle_insize)/(int)s->cfg.frame_size);
+ goto err_out;
+ }
+ s->insize_done += urb_size;
+
+ l = s->idle_outsize;
+ s->outpacket[0].offset = (sk->idle_outurb->transfer_buffer -
+ sk->write_page) - l;
+
+ if (usb_stream_prepare_playback(sk, inurb) < 0)
+ goto err_out;
+
+ s->outpacket[0].length = sk->idle_outurb->transfer_buffer_length + l;
+ s->outpacket[1].offset = sk->completed_outurb->transfer_buffer -
+ sk->write_page;
+
+ if (submit_urbs(sk, inurb, outurb) < 0)
+ goto err_out;
+
+ loop_back(s);
+ s->periods_done++;
+ wake_up_all(&sk->sleep);
+ return;
+err_out:
+ s->state = usb_stream_xrun;
+ wake_up_all(&sk->sleep);
+}
+
+static void i_capture_idle(struct urb *urb)
+{
+ struct usb_stream_kernel *sk = urb->context;
+ if (balance_capture(sk, urb))
+ stream_idle(sk, urb, sk->i_urb);
+}
+
+static void i_playback_idle(struct urb *urb)
+{
+ struct usb_stream_kernel *sk = urb->context;
+ if (balance_playback(sk, urb))
+ stream_idle(sk, sk->i_urb, urb);
+}
+
+static void stream_start(struct usb_stream_kernel *sk,
+ struct urb *inurb, struct urb *outurb)
+{
+ struct usb_stream *s = sk->s;
+ if (s->state >= usb_stream_sync1) {
+ int l, p, max_diff, max_diff_0;
+ int urb_size = 0;
+ unsigned frames_per_packet, min_frames = 0;
+ frames_per_packet = (s->period_size - s->idle_insize);
+ frames_per_packet <<= 8;
+ frames_per_packet /=
+ s->cfg.frame_size * inurb->number_of_packets;
+ frames_per_packet++;
+
+ max_diff_0 = s->cfg.frame_size;
+ if (s->cfg.period_frames >= 256)
+ max_diff_0 <<= 1;
+ if (s->cfg.period_frames >= 1024)
+ max_diff_0 <<= 1;
+ max_diff = max_diff_0;
+ for (p = 0; p < inurb->number_of_packets; ++p) {
+ int diff;
+ l = inurb->iso_frame_desc[p].actual_length;
+ urb_size += l;
+
+ min_frames += frames_per_packet;
+ diff = urb_size -
+ (min_frames >> 8) * s->cfg.frame_size;
+ if (diff < max_diff) {
+ snd_printdd(KERN_DEBUG "%i %i %i %i\n",
+ s->insize_done,
+ urb_size / (int)s->cfg.frame_size,
+ inurb->number_of_packets, diff);
+ max_diff = diff;
+ }
+ }
+ s->idle_insize -= max_diff - max_diff_0;
+ s->idle_insize += urb_size - s->period_size;
+ if (s->idle_insize < 0) {
+ snd_printk(KERN_WARNING "%i %i %i\n",
+ s->idle_insize, urb_size, s->period_size);
+ return;
+ } else if (s->idle_insize == 0) {
+ s->next_inpacket_split =
+ (s->inpacket_head + 1) % s->inpackets;
+ s->next_inpacket_split_at = 0;
+ } else {
+ unsigned split = s->inpacket_head;
+ l = s->idle_insize;
+ while (l > s->inpacket[split].length) {
+ l -= s->inpacket[split].length;
+ if (split == 0)
+ split = s->inpackets - 1;
+ else
+ split--;
+ }
+ s->next_inpacket_split = split;
+ s->next_inpacket_split_at =
+ s->inpacket[split].length - l;
+ }
+
+ s->insize_done += urb_size;
+
+ if (usb_stream_prepare_playback(sk, inurb) < 0)
+ return;
+
+ } else
+ playback_prep_freqn(sk, sk->idle_outurb);
+
+ if (submit_urbs(sk, inurb, outurb) < 0)
+ return;
+
+ if (s->state == usb_stream_sync1 && s->insize_done > 360000) {
+ /* just guesswork ^^^^^^ */
+ s->state = usb_stream_ready;
+ subs_set_complete(sk->inurb, i_capture_idle);
+ subs_set_complete(sk->outurb, i_playback_idle);
+ }
+}
+
+static void i_capture_start(struct urb *urb)
+{
+ struct usb_iso_packet_descriptor *id = urb->iso_frame_desc;
+ struct usb_stream_kernel *sk = urb->context;
+ struct usb_stream *s = sk->s;
+ int p;
+ int empty = 0;
+
+ if (urb->status) {
+ snd_printk(KERN_WARNING "status=%i\n", urb->status);
+ return;
+ }
+
+ for (p = 0; p < urb->number_of_packets; ++p) {
+ int l = id[p].actual_length;
+ if (l < s->cfg.frame_size) {
+ ++empty;
+ if (s->state >= usb_stream_sync0) {
+ snd_printk(KERN_WARNING "%i\n", l);
+ return;
+ }
+ }
+ s->inpacket_head++;
+ s->inpacket_head %= s->inpackets;
+ s->inpacket[s->inpacket_head].offset =
+ id[p].offset + (urb->transfer_buffer - (void *)s);
+ s->inpacket[s->inpacket_head].length = l;
+ }
+#ifdef SHOW_EMPTY
+ if (empty) {
+ printk(KERN_DEBUG"%s:%i: %i", __func__, __LINE__,
+ urb->iso_frame_desc[0].actual_length);
+ for (pack = 1; pack < urb->number_of_packets; ++pack) {
+ int l = urb->iso_frame_desc[pack].actual_length;
+ printk(" %i", l);
+ }
+ printk("\n");
+ }
+#endif
+ if (!empty && s->state < usb_stream_sync1)
+ ++s->state;
+
+ if (balance_capture(sk, urb))
+ stream_start(sk, urb, sk->i_urb);
+}
+
+static void i_playback_start(struct urb *urb)
+{
+ struct usb_stream_kernel *sk = urb->context;
+ if (balance_playback(sk, urb))
+ stream_start(sk, sk->i_urb, urb);
+}
+
+int usb_stream_start(struct usb_stream_kernel *sk)
+{
+ struct usb_stream *s = sk->s;
+ int frame = 0, iters = 0;
+ int u, err;
+ int try = 0;
+
+ if (s->state != usb_stream_stopped)
+ return -EAGAIN;
+
+ subs_set_complete(sk->inurb, i_capture_start);
+ subs_set_complete(sk->outurb, i_playback_start);
+ memset(sk->write_page, 0, s->write_size);
+dotry:
+ s->insize_done = 0;
+ s->idle_insize = 0;
+ s->idle_outsize = 0;
+ s->sync_packet = -1;
+ s->inpacket_head = -1;
+ sk->iso_frame_balance = 0;
+ ++try;
+ for (u = 0; u < 2; u++) {
+ struct urb *inurb = sk->inurb[u];
+ struct urb *outurb = sk->outurb[u];
+ playback_prep_freqn(sk, outurb);
+ inurb->number_of_packets = outurb->number_of_packets;
+ inurb->transfer_buffer_length =
+ inurb->number_of_packets *
+ inurb->iso_frame_desc[0].length;
+
+ if (u == 0) {
+ int now;
+ struct usb_device *dev = inurb->dev;
+ frame = usb_get_current_frame_number(dev);
+ do {
+ now = usb_get_current_frame_number(dev);
+ ++iters;
+ } while (now > -1 && now == frame);
+ }
+ err = usb_submit_urb(inurb, GFP_ATOMIC);
+ if (err < 0) {
+ snd_printk(KERN_ERR"usb_submit_urb(sk->inurb[%i])"
+ " returned %i\n", u, err);
+ return err;
+ }
+ err = usb_submit_urb(outurb, GFP_ATOMIC);
+ if (err < 0) {
+ snd_printk(KERN_ERR"usb_submit_urb(sk->outurb[%i])"
+ " returned %i\n", u, err);
+ return err;
+ }
+
+ if (inurb->start_frame != outurb->start_frame) {
+ snd_printd(KERN_DEBUG
+ "u[%i] start_frames differ in:%u out:%u\n",
+ u, inurb->start_frame, outurb->start_frame);
+ goto check_retry;
+ }
+ }
+ snd_printdd(KERN_DEBUG "%i %i\n", frame, iters);
+ try = 0;
+check_retry:
+ if (try) {
+ usb_stream_stop(sk);
+ if (try < 5) {
+ msleep(1500);
+ snd_printd(KERN_DEBUG "goto dotry;\n");
+ goto dotry;
+ }
+ snd_printk(KERN_WARNING"couldn't start"
+ " all urbs on the same start_frame.\n");
+ return -EFAULT;
+ }
+
+ sk->idle_inurb = sk->inurb[USB_STREAM_NURBS - 2];
+ sk->idle_outurb = sk->outurb[USB_STREAM_NURBS - 2];
+ sk->completed_inurb = sk->inurb[USB_STREAM_NURBS - 1];
+ sk->completed_outurb = sk->outurb[USB_STREAM_NURBS - 1];
+
+/* wait, check */
+ {
+ int wait_ms = 3000;
+ while (s->state != usb_stream_ready && wait_ms > 0) {
+ snd_printdd(KERN_DEBUG "%i\n", s->state);
+ msleep(200);
+ wait_ms -= 200;
+ }
+ }
+
+ return s->state == usb_stream_ready ? 0 : -EFAULT;
+}
+
+
+/* stop */
+
+void usb_stream_stop(struct usb_stream_kernel *sk)
+{
+ int u;
+ if (!sk->s)
+ return;
+ for (u = 0; u < USB_STREAM_NURBS; ++u) {
+ usb_kill_urb(sk->inurb[u]);
+ usb_kill_urb(sk->outurb[u]);
+ }
+ sk->s->state = usb_stream_stopped;
+ msleep(400);
+}
diff --git a/sound/usb/usx2y/usb_stream.h b/sound/usb/usx2y/usb_stream.h
new file mode 100644
index 000000000..90369001e
--- /dev/null
+++ b/sound/usb/usx2y/usb_stream.h
@@ -0,0 +1,42 @@
+#ifndef __USB_STREAM_H
+#define __USB_STREAM_H
+
+#include <uapi/sound/usb_stream.h>
+
+#define USB_STREAM_NURBS 4
+#define USB_STREAM_URBDEPTH 4
+
+struct usb_stream_kernel {
+ struct usb_stream *s;
+
+ void *write_page;
+
+ unsigned n_o_ps;
+
+ struct urb *inurb[USB_STREAM_NURBS];
+ struct urb *idle_inurb;
+ struct urb *completed_inurb;
+ struct urb *outurb[USB_STREAM_NURBS];
+ struct urb *idle_outurb;
+ struct urb *completed_outurb;
+ struct urb *i_urb;
+
+ int iso_frame_balance;
+
+ wait_queue_head_t sleep;
+
+ unsigned out_phase;
+ unsigned out_phase_peeked;
+ unsigned freqn;
+};
+
+struct usb_stream *usb_stream_new(struct usb_stream_kernel *sk,
+ struct usb_device *dev,
+ unsigned in_endpoint, unsigned out_endpoint,
+ unsigned sample_rate, unsigned use_packsize,
+ unsigned period_frames, unsigned frame_size);
+void usb_stream_free(struct usb_stream_kernel *);
+int usb_stream_start(struct usb_stream_kernel *);
+void usb_stream_stop(struct usb_stream_kernel *);
+
+#endif /* __USB_STREAM_H */
diff --git a/sound/usb/usx2y/usbus428ctldefs.h b/sound/usb/usx2y/usbus428ctldefs.h
new file mode 100644
index 000000000..b864e7e26
--- /dev/null
+++ b/sound/usb/usx2y/usbus428ctldefs.h
@@ -0,0 +1,104 @@
+/*
+ *
+ * Copyright (c) 2003 by Karsten Wiese <annabellesgarden@yahoo.de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+enum E_In84{
+ eFader0 = 0,
+ eFader1,
+ eFader2,
+ eFader3,
+ eFader4,
+ eFader5,
+ eFader6,
+ eFader7,
+ eFaderM,
+ eTransport,
+ eModifier = 10,
+ eFilterSelect,
+ eSelect,
+ eMute,
+
+ eSwitch = 15,
+ eWheelGain,
+ eWheelFreq,
+ eWheelQ,
+ eWheelPan,
+ eWheel = 20
+};
+
+#define T_RECORD 1
+#define T_PLAY 2
+#define T_STOP 4
+#define T_F_FWD 8
+#define T_REW 0x10
+#define T_SOLO 0x20
+#define T_REC 0x40
+#define T_NULL 0x80
+
+
+struct us428_ctls {
+ unsigned char Fader[9];
+ unsigned char Transport;
+ unsigned char Modifier;
+ unsigned char FilterSelect;
+ unsigned char Select;
+ unsigned char Mute;
+ unsigned char UNKNOWN;
+ unsigned char Switch;
+ unsigned char Wheel[5];
+};
+
+struct us428_setByte {
+ unsigned char Offset,
+ Value;
+};
+
+enum {
+ eLT_Volume = 0,
+ eLT_Light
+};
+
+struct usX2Y_volume {
+ unsigned char Channel,
+ LH,
+ LL,
+ RH,
+ RL;
+};
+
+struct us428_lights {
+ struct us428_setByte Light[7];
+};
+
+struct us428_p4out {
+ char type;
+ union {
+ struct usX2Y_volume vol;
+ struct us428_lights lights;
+ } val;
+};
+
+#define N_us428_ctl_BUFS 16
+#define N_us428_p4out_BUFS 16
+struct us428ctls_sharedmem{
+ struct us428_ctls CtlSnapShot[N_us428_ctl_BUFS];
+ int CtlSnapShotDiffersAt[N_us428_ctl_BUFS];
+ int CtlSnapShotLast, CtlSnapShotRed;
+ struct us428_p4out p4out[N_us428_p4out_BUFS];
+ int p4outLast, p4outSent;
+};
diff --git a/sound/usb/usx2y/usbusx2y.c b/sound/usb/usx2y/usbusx2y.c
new file mode 100644
index 000000000..91e0e2a48
--- /dev/null
+++ b/sound/usb/usx2y/usbusx2y.c
@@ -0,0 +1,461 @@
+/*
+ * usbusy2y.c - ALSA USB US-428 Driver
+ *
+2005-04-14 Karsten Wiese
+ Version 0.8.7.2:
+ Call snd_card_free() instead of snd_card_free_in_thread() to prevent oops with dead keyboard symptom.
+ Tested ok with kernel 2.6.12-rc2.
+
+2004-12-14 Karsten Wiese
+ Version 0.8.7.1:
+ snd_pcm_open for rawusb pcm-devices now returns -EBUSY if called without rawusb's hwdep device being open.
+
+2004-12-02 Karsten Wiese
+ Version 0.8.7:
+ Use macro usb_maxpacket() for portability.
+
+2004-10-26 Karsten Wiese
+ Version 0.8.6:
+ wake_up() process waiting in usX2Y_urbs_start() on error.
+
+2004-10-21 Karsten Wiese
+ Version 0.8.5:
+ nrpacks is runtime or compiletime configurable now with tested values from 1 to 4.
+
+2004-10-03 Karsten Wiese
+ Version 0.8.2:
+ Avoid any possible racing while in prepare callback.
+
+2004-09-30 Karsten Wiese
+ Version 0.8.0:
+ Simplified things and made ohci work again.
+
+2004-09-20 Karsten Wiese
+ Version 0.7.3:
+ Use usb_kill_urb() instead of deprecated (kernel 2.6.9) usb_unlink_urb().
+
+2004-07-13 Karsten Wiese
+ Version 0.7.1:
+ Don't sleep in START/STOP callbacks anymore.
+ us428 channels C/D not handled just for this version, sorry.
+
+2004-06-21 Karsten Wiese
+ Version 0.6.4:
+ Temporarely suspend midi input
+ to sanely call usb_set_interface() when setting format.
+
+2004-06-12 Karsten Wiese
+ Version 0.6.3:
+ Made it thus the following rule is enforced:
+ "All pcm substreams of one usX2Y have to operate at the same rate & format."
+
+2004-04-06 Karsten Wiese
+ Version 0.6.0:
+ Runs on 2.6.5 kernel without any "--with-debug=" things.
+ us224 reported running.
+
+2004-01-14 Karsten Wiese
+ Version 0.5.1:
+ Runs with 2.6.1 kernel.
+
+2003-12-30 Karsten Wiese
+ Version 0.4.1:
+ Fix 24Bit 4Channel capturing for the us428.
+
+2003-11-27 Karsten Wiese, Martin Langer
+ Version 0.4:
+ us122 support.
+ us224 could be tested by uncommenting the sections containing USB_ID_US224
+
+2003-11-03 Karsten Wiese
+ Version 0.3:
+ 24Bit support.
+ "arecord -D hw:1 -c 2 -r 48000 -M -f S24_3LE|aplay -D hw:1 -c 2 -r 48000 -M -f S24_3LE" works.
+
+2003-08-22 Karsten Wiese
+ Version 0.0.8:
+ Removed EZUSB Firmware. First Stage Firmwaredownload is now done by tascam-firmware downloader.
+ See:
+ http://usb-midi-fw.sourceforge.net/tascam-firmware.tar.gz
+
+2003-06-18 Karsten Wiese
+ Version 0.0.5:
+ changed to compile with kernel 2.4.21 and alsa 0.9.4
+
+2002-10-16 Karsten Wiese
+ Version 0.0.4:
+ compiles again with alsa-current.
+ USB_ISO_ASAP not used anymore (most of the time), instead
+ urb->start_frame is calculated here now, some calls inside usb-driver don't need to happen anymore.
+
+ To get the best out of this:
+ Disable APM-support in the kernel as APM-BIOS calls (once each second) hard disable interrupt for many precious milliseconds.
+ This helped me much on my slowish PII 400 & PIII 500.
+ ACPI yet untested but might cause the same bad behaviour.
+ Use a kernel with lowlatency and preemptiv patches applied.
+ To autoload snd-usb-midi append a line
+ post-install snd-usb-us428 modprobe snd-usb-midi
+ to /etc/modules.conf.
+
+ known problems:
+ sliders, knobs, lights not yet handled except MASTER Volume slider.
+ "pcm -c 2" doesn't work. "pcm -c 2 -m direct_interleaved" does.
+ KDE3: "Enable full duplex operation" deadlocks.
+
+
+2002-08-31 Karsten Wiese
+ Version 0.0.3: audio also simplex;
+ simplifying: iso urbs only 1 packet, melted structs.
+ ASYNC_UNLINK not used anymore: no more crashes so far.....
+ for alsa 0.9 rc3.
+
+2002-08-09 Karsten Wiese
+ Version 0.0.2: midi works with snd-usb-midi, audio (only fullduplex now) with i.e. bristol.
+ The firmware has been sniffed from win2k us-428 driver 3.09.
+
+ * Copyright (c) 2002 - 2004 Karsten Wiese
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*/
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/slab.h>
+#include <linux/interrupt.h>
+#include <linux/usb.h>
+#include <sound/core.h>
+#include <sound/initval.h>
+#include <sound/pcm.h>
+
+#include <sound/rawmidi.h>
+#include "usx2y.h"
+#include "usbusx2y.h"
+#include "usX2Yhwdep.h"
+
+
+
+MODULE_AUTHOR("Karsten Wiese <annabellesgarden@yahoo.de>");
+MODULE_DESCRIPTION("TASCAM "NAME_ALLCAPS" Version 0.8.7.2");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("{{TASCAM(0x1604),"NAME_ALLCAPS"(0x8001)(0x8005)(0x8007)}}");
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 0-max */
+static char* id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* Id for this card */
+static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP; /* Enable this card */
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for "NAME_ALLCAPS".");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for "NAME_ALLCAPS".");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "Enable "NAME_ALLCAPS".");
+
+
+static int snd_usX2Y_card_used[SNDRV_CARDS];
+
+static void usX2Y_usb_disconnect(struct usb_device* usb_device, void* ptr);
+static void snd_usX2Y_card_private_free(struct snd_card *card);
+
+/*
+ * pipe 4 is used for switching the lamps, setting samplerate, volumes ....
+ */
+static void i_usX2Y_Out04Int(struct urb *urb)
+{
+#ifdef CONFIG_SND_DEBUG
+ if (urb->status) {
+ int i;
+ struct usX2Ydev *usX2Y = urb->context;
+ for (i = 0; i < 10 && usX2Y->AS04.urb[i] != urb; i++);
+ snd_printdd("i_usX2Y_Out04Int() urb %i status=%i\n", i, urb->status);
+ }
+#endif
+}
+
+static void i_usX2Y_In04Int(struct urb *urb)
+{
+ int err = 0;
+ struct usX2Ydev *usX2Y = urb->context;
+ struct us428ctls_sharedmem *us428ctls = usX2Y->us428ctls_sharedmem;
+
+ usX2Y->In04IntCalls++;
+
+ if (urb->status) {
+ snd_printdd("Interrupt Pipe 4 came back with status=%i\n", urb->status);
+ return;
+ }
+
+ // printk("%i:0x%02X ", 8, (int)((unsigned char*)usX2Y->In04Buf)[8]); Master volume shows 0 here if fader is at max during boot ?!?
+ if (us428ctls) {
+ int diff = -1;
+ if (-2 == us428ctls->CtlSnapShotLast) {
+ diff = 0;
+ memcpy(usX2Y->In04Last, usX2Y->In04Buf, sizeof(usX2Y->In04Last));
+ us428ctls->CtlSnapShotLast = -1;
+ } else {
+ int i;
+ for (i = 0; i < 21; i++) {
+ if (usX2Y->In04Last[i] != ((char*)usX2Y->In04Buf)[i]) {
+ if (diff < 0)
+ diff = i;
+ usX2Y->In04Last[i] = ((char*)usX2Y->In04Buf)[i];
+ }
+ }
+ }
+ if (0 <= diff) {
+ int n = us428ctls->CtlSnapShotLast + 1;
+ if (n >= N_us428_ctl_BUFS || n < 0)
+ n = 0;
+ memcpy(us428ctls->CtlSnapShot + n, usX2Y->In04Buf, sizeof(us428ctls->CtlSnapShot[0]));
+ us428ctls->CtlSnapShotDiffersAt[n] = diff;
+ us428ctls->CtlSnapShotLast = n;
+ wake_up(&usX2Y->us428ctls_wait_queue_head);
+ }
+ }
+
+
+ if (usX2Y->US04) {
+ if (0 == usX2Y->US04->submitted)
+ do {
+ err = usb_submit_urb(usX2Y->US04->urb[usX2Y->US04->submitted++], GFP_ATOMIC);
+ } while (!err && usX2Y->US04->submitted < usX2Y->US04->len);
+ } else
+ if (us428ctls && us428ctls->p4outLast >= 0 && us428ctls->p4outLast < N_us428_p4out_BUFS) {
+ if (us428ctls->p4outLast != us428ctls->p4outSent) {
+ int j, send = us428ctls->p4outSent + 1;
+ if (send >= N_us428_p4out_BUFS)
+ send = 0;
+ for (j = 0; j < URBS_AsyncSeq && !err; ++j)
+ if (0 == usX2Y->AS04.urb[j]->status) {
+ struct us428_p4out *p4out = us428ctls->p4out + send; // FIXME if more than 1 p4out is new, 1 gets lost.
+ usb_fill_bulk_urb(usX2Y->AS04.urb[j], usX2Y->dev,
+ usb_sndbulkpipe(usX2Y->dev, 0x04), &p4out->val.vol,
+ p4out->type == eLT_Light ? sizeof(struct us428_lights) : 5,
+ i_usX2Y_Out04Int, usX2Y);
+ err = usb_submit_urb(usX2Y->AS04.urb[j], GFP_ATOMIC);
+ us428ctls->p4outSent = send;
+ break;
+ }
+ }
+ }
+
+ if (err)
+ snd_printk(KERN_ERR "In04Int() usb_submit_urb err=%i\n", err);
+
+ urb->dev = usX2Y->dev;
+ usb_submit_urb(urb, GFP_ATOMIC);
+}
+
+/*
+ * Prepare some urbs
+ */
+int usX2Y_AsyncSeq04_init(struct usX2Ydev *usX2Y)
+{
+ int err = 0,
+ i;
+
+ 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->dev,
+ usb_sndbulkpipe(usX2Y->dev, 0x04),
+ usX2Y->AS04.buffer + URB_DataLen_AsyncSeq*i, 0,
+ i_usX2Y_Out04Int, usX2Y
+ );
+ }
+ return err;
+}
+
+int usX2Y_In04_init(struct usX2Ydev *usX2Y)
+{
+ if (! (usX2Y->In04urb = usb_alloc_urb(0, GFP_KERNEL)))
+ return -ENOMEM;
+
+ if (! (usX2Y->In04Buf = kmalloc(21, GFP_KERNEL))) {
+ usb_free_urb(usX2Y->In04urb);
+ return -ENOMEM;
+ }
+
+ init_waitqueue_head(&usX2Y->In04WaitQueue);
+ usb_fill_int_urb(usX2Y->In04urb, usX2Y->dev, usb_rcvintpipe(usX2Y->dev, 0x4),
+ usX2Y->In04Buf, 21,
+ i_usX2Y_In04Int, usX2Y,
+ 10);
+ return usb_submit_urb(usX2Y->In04urb, GFP_KERNEL);
+}
+
+static void usX2Y_unlinkSeq(struct snd_usX2Y_AsyncSeq *S)
+{
+ int i;
+ for (i = 0; i < URBS_AsyncSeq; ++i) {
+ usb_kill_urb(S->urb[i]);
+ usb_free_urb(S->urb[i]);
+ S->urb[i] = NULL;
+ }
+ kfree(S->buffer);
+}
+
+
+static struct usb_device_id snd_usX2Y_usb_id_table[] = {
+ {
+ .match_flags = USB_DEVICE_ID_MATCH_DEVICE,
+ .idVendor = 0x1604,
+ .idProduct = USB_ID_US428
+ },
+ {
+ .match_flags = USB_DEVICE_ID_MATCH_DEVICE,
+ .idVendor = 0x1604,
+ .idProduct = USB_ID_US122
+ },
+ {
+ .match_flags = USB_DEVICE_ID_MATCH_DEVICE,
+ .idVendor = 0x1604,
+ .idProduct = USB_ID_US224
+ },
+ { /* terminator */ }
+};
+
+static int usX2Y_create_card(struct usb_device *device,
+ struct usb_interface *intf,
+ struct snd_card **cardp)
+{
+ int dev;
+ struct snd_card * card;
+ int err;
+
+ for (dev = 0; dev < SNDRV_CARDS; ++dev)
+ if (enable[dev] && !snd_usX2Y_card_used[dev])
+ break;
+ if (dev >= SNDRV_CARDS)
+ return -ENODEV;
+ err = snd_card_new(&intf->dev, index[dev], id[dev], THIS_MODULE,
+ sizeof(struct usX2Ydev), &card);
+ if (err < 0)
+ return err;
+ snd_usX2Y_card_used[usX2Y(card)->card_index = dev] = 1;
+ card->private_free = snd_usX2Y_card_private_free;
+ usX2Y(card)->dev = device;
+ init_waitqueue_head(&usX2Y(card)->prepare_wait_queue);
+ mutex_init(&usX2Y(card)->pcm_mutex);
+ INIT_LIST_HEAD(&usX2Y(card)->midi_list);
+ strcpy(card->driver, "USB "NAME_ALLCAPS"");
+ sprintf(card->shortname, "TASCAM "NAME_ALLCAPS"");
+ sprintf(card->longname, "%s (%x:%x if %d at %03d/%03d)",
+ card->shortname,
+ le16_to_cpu(device->descriptor.idVendor),
+ le16_to_cpu(device->descriptor.idProduct),
+ 0,//us428(card)->usbmidi.ifnum,
+ usX2Y(card)->dev->bus->busnum, usX2Y(card)->dev->devnum
+ );
+ *cardp = card;
+ return 0;
+}
+
+
+static int usX2Y_usb_probe(struct usb_device *device,
+ struct usb_interface *intf,
+ const struct usb_device_id *device_id,
+ struct snd_card **cardp)
+{
+ int err;
+ struct snd_card * card;
+
+ *cardp = NULL;
+ if (le16_to_cpu(device->descriptor.idVendor) != 0x1604 ||
+ (le16_to_cpu(device->descriptor.idProduct) != USB_ID_US122 &&
+ le16_to_cpu(device->descriptor.idProduct) != USB_ID_US224 &&
+ le16_to_cpu(device->descriptor.idProduct) != USB_ID_US428))
+ return -EINVAL;
+
+ err = usX2Y_create_card(device, intf, &card);
+ if (err < 0)
+ return err;
+ if ((err = usX2Y_hwdep_new(card, device)) < 0 ||
+ (err = snd_card_register(card)) < 0) {
+ snd_card_free(card);
+ return err;
+ }
+ *cardp = card;
+ return 0;
+}
+
+/*
+ * new 2.5 USB kernel API
+ */
+static int snd_usX2Y_probe(struct usb_interface *intf, const struct usb_device_id *id)
+{
+ struct snd_card *card;
+ int err;
+
+ err = usX2Y_usb_probe(interface_to_usbdev(intf), intf, id, &card);
+ if (err < 0)
+ return err;
+ dev_set_drvdata(&intf->dev, card);
+ return 0;
+}
+
+static void snd_usX2Y_disconnect(struct usb_interface *intf)
+{
+ usX2Y_usb_disconnect(interface_to_usbdev(intf),
+ usb_get_intfdata(intf));
+}
+
+MODULE_DEVICE_TABLE(usb, snd_usX2Y_usb_id_table);
+static struct usb_driver snd_usX2Y_usb_driver = {
+ .name = "snd-usb-usx2y",
+ .probe = snd_usX2Y_probe,
+ .disconnect = snd_usX2Y_disconnect,
+ .id_table = snd_usX2Y_usb_id_table,
+};
+
+static void snd_usX2Y_card_private_free(struct snd_card *card)
+{
+ kfree(usX2Y(card)->In04Buf);
+ usb_free_urb(usX2Y(card)->In04urb);
+ if (usX2Y(card)->us428ctls_sharedmem)
+ snd_free_pages(usX2Y(card)->us428ctls_sharedmem, sizeof(*usX2Y(card)->us428ctls_sharedmem));
+ if (usX2Y(card)->card_index >= 0 && usX2Y(card)->card_index < SNDRV_CARDS)
+ snd_usX2Y_card_used[usX2Y(card)->card_index] = 0;
+}
+
+/*
+ * Frees the device.
+ */
+static void usX2Y_usb_disconnect(struct usb_device *device, void* ptr)
+{
+ if (ptr) {
+ struct snd_card *card = ptr;
+ struct usX2Ydev *usX2Y = usX2Y(card);
+ struct list_head *p;
+ usX2Y->chip_status = USX2Y_STAT_CHIP_HUP;
+ usX2Y_unlinkSeq(&usX2Y->AS04);
+ usb_kill_urb(usX2Y->In04urb);
+ snd_card_disconnect(card);
+ /* release the midi resources */
+ list_for_each(p, &usX2Y->midi_list) {
+ snd_usbmidi_disconnect(p);
+ }
+ if (usX2Y->us428ctls_sharedmem)
+ wake_up(&usX2Y->us428ctls_wait_queue_head);
+ snd_card_free(card);
+ }
+}
+
+module_usb_driver(snd_usX2Y_usb_driver);
diff --git a/sound/usb/usx2y/usbusx2y.h b/sound/usb/usx2y/usbusx2y.h
new file mode 100644
index 000000000..6ae6b0806
--- /dev/null
+++ b/sound/usb/usx2y/usbusx2y.h
@@ -0,0 +1,88 @@
+#ifndef USBUSX2Y_H
+#define USBUSX2Y_H
+#include "../usbaudio.h"
+#include "../midi.h"
+#include "usbus428ctldefs.h"
+
+#define NRURBS 2
+
+
+#define URBS_AsyncSeq 10
+#define URB_DataLen_AsyncSeq 32
+struct snd_usX2Y_AsyncSeq {
+ struct urb *urb[URBS_AsyncSeq];
+ char *buffer;
+};
+
+struct snd_usX2Y_urbSeq {
+ int submitted;
+ int len;
+ struct urb *urb[0];
+};
+
+#include "usx2yhwdeppcm.h"
+
+struct usX2Ydev {
+ struct usb_device *dev;
+ int card_index;
+ int stride;
+ struct urb *In04urb;
+ void *In04Buf;
+ char In04Last[24];
+ unsigned In04IntCalls;
+ struct snd_usX2Y_urbSeq *US04;
+ wait_queue_head_t In04WaitQueue;
+ struct snd_usX2Y_AsyncSeq AS04;
+ unsigned int rate,
+ format;
+ int chip_status;
+ struct mutex pcm_mutex;
+ struct us428ctls_sharedmem *us428ctls_sharedmem;
+ int wait_iso_frame;
+ wait_queue_head_t us428ctls_wait_queue_head;
+ struct snd_usX2Y_hwdep_pcm_shm *hwdep_pcm_shm;
+ struct snd_usX2Y_substream *subs[4];
+ struct snd_usX2Y_substream * volatile prepare_subs;
+ wait_queue_head_t prepare_wait_queue;
+ struct list_head midi_list;
+ struct list_head pcm_list;
+ int pcm_devs;
+};
+
+
+struct snd_usX2Y_substream {
+ struct usX2Ydev *usX2Y;
+ struct snd_pcm_substream *pcm_substream;
+
+ int endpoint;
+ unsigned int maxpacksize; /* max packet size in bytes */
+
+ atomic_t state;
+#define state_STOPPED 0
+#define state_STARTING1 1
+#define state_STARTING2 2
+#define state_STARTING3 3
+#define state_PREPARED 4
+#define state_PRERUNNING 6
+#define state_RUNNING 8
+
+ int hwptr; /* free frame position in the buffer (only for playback) */
+ int hwptr_done; /* processed frame position in the buffer */
+ int transfer_done; /* processed frames since last period update */
+
+ struct urb *urb[NRURBS]; /* data urb table */
+ struct urb *completed_urb;
+ char *tmpbuf; /* temporary buffer for playback */
+};
+
+
+#define usX2Y(c) ((struct usX2Ydev *)(c)->private_data)
+
+int usX2Y_audio_create(struct snd_card *card);
+
+int usX2Y_AsyncSeq04_init(struct usX2Ydev *usX2Y);
+int usX2Y_In04_init(struct usX2Ydev *usX2Y);
+
+#define NAME_ALLCAPS "US-X2Y"
+
+#endif
diff --git a/sound/usb/usx2y/usbusx2yaudio.c b/sound/usb/usx2y/usbusx2yaudio.c
new file mode 100644
index 000000000..61d5dc2a3
--- /dev/null
+++ b/sound/usb/usx2y/usbusx2yaudio.c
@@ -0,0 +1,1015 @@
+/*
+ * US-X2Y AUDIO
+ * Copyright (c) 2002-2004 by Karsten Wiese
+ *
+ * based on
+ *
+ * (Tentative) USB Audio Driver for ALSA
+ *
+ * Main and PCM part
+ *
+ * Copyright (c) 2002 by Takashi Iwai <tiwai@suse.de>
+ *
+ * Many codes borrowed from audio.c by
+ * Alan Cox (alan@lxorguk.ukuu.org.uk)
+ * Thomas Sailer (sailer@ife.ee.ethz.ch)
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+
+#include <linux/interrupt.h>
+#include <linux/slab.h>
+#include <linux/usb.h>
+#include <linux/moduleparam.h>
+#include <sound/core.h>
+#include <sound/info.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include "usx2y.h"
+#include "usbusx2y.h"
+
+#define USX2Y_NRPACKS 4 /* Default value used for nr of packs per urb.
+ 1 to 4 have been tested ok on uhci.
+ To use 3 on ohci, you'd need a patch:
+ look for "0000425-linux-2.6.9-rc4-mm1_ohci-hcd.patch.gz" on
+ "https://bugtrack.alsa-project.org/alsa-bug/bug_view_page.php?bug_id=0000425"
+ .
+ 1, 2 and 4 work out of the box on ohci, if I recall correctly.
+ Bigger is safer operation,
+ smaller gives lower latencies.
+ */
+#define USX2Y_NRPACKS_VARIABLE y /* If your system works ok with this module's parameter
+ nrpacks set to 1, you might as well comment
+ this #define out, and thereby produce smaller, faster code.
+ You'd also set USX2Y_NRPACKS to 1 then.
+ */
+
+#ifdef USX2Y_NRPACKS_VARIABLE
+ static int nrpacks = USX2Y_NRPACKS; /* number of packets per urb */
+ #define nr_of_packs() nrpacks
+ module_param(nrpacks, int, 0444);
+ MODULE_PARM_DESC(nrpacks, "Number of packets per URB.");
+#else
+ #define nr_of_packs() USX2Y_NRPACKS
+#endif
+
+
+static int usX2Y_urb_capt_retire(struct snd_usX2Y_substream *subs)
+{
+ struct urb *urb = subs->completed_urb;
+ struct snd_pcm_runtime *runtime = subs->pcm_substream->runtime;
+ unsigned char *cp;
+ int i, len, lens = 0, hwptr_done = subs->hwptr_done;
+ struct usX2Ydev *usX2Y = subs->usX2Y;
+
+ for (i = 0; i < nr_of_packs(); i++) {
+ cp = (unsigned char*)urb->transfer_buffer + urb->iso_frame_desc[i].offset;
+ if (urb->iso_frame_desc[i].status) { /* active? hmm, skip this */
+ snd_printk(KERN_ERR "active frame status %i. "
+ "Most probably some hardware problem.\n",
+ urb->iso_frame_desc[i].status);
+ return urb->iso_frame_desc[i].status;
+ }
+ len = urb->iso_frame_desc[i].actual_length / usX2Y->stride;
+ if (! len) {
+ snd_printd("0 == len ERROR!\n");
+ continue;
+ }
+
+ /* copy a data chunk */
+ if ((hwptr_done + len) > runtime->buffer_size) {
+ int cnt = runtime->buffer_size - hwptr_done;
+ int blen = cnt * usX2Y->stride;
+ memcpy(runtime->dma_area + hwptr_done * usX2Y->stride, cp, blen);
+ memcpy(runtime->dma_area, cp + blen, len * usX2Y->stride - blen);
+ } else {
+ memcpy(runtime->dma_area + hwptr_done * usX2Y->stride, cp,
+ len * usX2Y->stride);
+ }
+ lens += len;
+ if ((hwptr_done += len) >= runtime->buffer_size)
+ hwptr_done -= runtime->buffer_size;
+ }
+
+ subs->hwptr_done = hwptr_done;
+ subs->transfer_done += lens;
+ /* update the pointer, call callback if necessary */
+ if (subs->transfer_done >= runtime->period_size) {
+ subs->transfer_done -= runtime->period_size;
+ snd_pcm_period_elapsed(subs->pcm_substream);
+ }
+ return 0;
+}
+/*
+ * prepare urb for playback data pipe
+ *
+ * we copy the data directly from the pcm buffer.
+ * the current position to be copied is held in hwptr field.
+ * since a urb can handle only a single linear buffer, if the total
+ * transferred area overflows the buffer boundary, we cannot send
+ * it directly from the buffer. thus the data is once copied to
+ * a temporary buffer and urb points to that.
+ */
+static int usX2Y_urb_play_prepare(struct snd_usX2Y_substream *subs,
+ struct urb *cap_urb,
+ struct urb *urb)
+{
+ int count, counts, pack;
+ struct usX2Ydev *usX2Y = subs->usX2Y;
+ struct snd_pcm_runtime *runtime = subs->pcm_substream->runtime;
+
+ count = 0;
+ for (pack = 0; pack < nr_of_packs(); pack++) {
+ /* calculate the size of a packet */
+ counts = cap_urb->iso_frame_desc[pack].actual_length / usX2Y->stride;
+ count += counts;
+ if (counts < 43 || counts > 50) {
+ snd_printk(KERN_ERR "should not be here with counts=%i\n", counts);
+ return -EPIPE;
+ }
+ /* set up descriptor */
+ urb->iso_frame_desc[pack].offset = pack ?
+ urb->iso_frame_desc[pack - 1].offset +
+ urb->iso_frame_desc[pack - 1].length :
+ 0;
+ urb->iso_frame_desc[pack].length = cap_urb->iso_frame_desc[pack].actual_length;
+ }
+ if (atomic_read(&subs->state) >= state_PRERUNNING)
+ if (subs->hwptr + count > runtime->buffer_size) {
+ /* err, the transferred area goes over buffer boundary.
+ * copy the data to the temp buffer.
+ */
+ int len;
+ len = runtime->buffer_size - subs->hwptr;
+ urb->transfer_buffer = subs->tmpbuf;
+ memcpy(subs->tmpbuf, runtime->dma_area +
+ subs->hwptr * usX2Y->stride, len * usX2Y->stride);
+ memcpy(subs->tmpbuf + len * usX2Y->stride,
+ runtime->dma_area, (count - len) * usX2Y->stride);
+ subs->hwptr += count;
+ subs->hwptr -= runtime->buffer_size;
+ } else {
+ /* set the buffer pointer */
+ urb->transfer_buffer = runtime->dma_area + subs->hwptr * usX2Y->stride;
+ if ((subs->hwptr += count) >= runtime->buffer_size)
+ subs->hwptr -= runtime->buffer_size;
+ }
+ else
+ urb->transfer_buffer = subs->tmpbuf;
+ urb->transfer_buffer_length = count * usX2Y->stride;
+ return 0;
+}
+
+/*
+ * process after playback data complete
+ *
+ * update the current position and call callback if a period is processed.
+ */
+static void usX2Y_urb_play_retire(struct snd_usX2Y_substream *subs, struct urb *urb)
+{
+ struct snd_pcm_runtime *runtime = subs->pcm_substream->runtime;
+ int len = urb->actual_length / subs->usX2Y->stride;
+
+ subs->transfer_done += len;
+ subs->hwptr_done += len;
+ if (subs->hwptr_done >= runtime->buffer_size)
+ subs->hwptr_done -= runtime->buffer_size;
+ if (subs->transfer_done >= runtime->period_size) {
+ subs->transfer_done -= runtime->period_size;
+ snd_pcm_period_elapsed(subs->pcm_substream);
+ }
+}
+
+static int usX2Y_urb_submit(struct snd_usX2Y_substream *subs, struct urb *urb, int frame)
+{
+ int err;
+ if (!urb)
+ return -ENODEV;
+ urb->start_frame = (frame + NRURBS * nr_of_packs()); // let hcd do rollover sanity checks
+ urb->hcpriv = NULL;
+ urb->dev = subs->usX2Y->dev; /* we need to set this at each time */
+ if ((err = usb_submit_urb(urb, GFP_ATOMIC)) < 0) {
+ snd_printk(KERN_ERR "usb_submit_urb() returned %i\n", err);
+ return err;
+ }
+ return 0;
+}
+
+static inline int usX2Y_usbframe_complete(struct snd_usX2Y_substream *capsubs,
+ struct snd_usX2Y_substream *playbacksubs,
+ int frame)
+{
+ int err, state;
+ struct urb *urb = playbacksubs->completed_urb;
+
+ state = atomic_read(&playbacksubs->state);
+ if (NULL != urb) {
+ if (state == state_RUNNING)
+ usX2Y_urb_play_retire(playbacksubs, urb);
+ else if (state >= state_PRERUNNING)
+ atomic_inc(&playbacksubs->state);
+ } else {
+ switch (state) {
+ case state_STARTING1:
+ urb = playbacksubs->urb[0];
+ atomic_inc(&playbacksubs->state);
+ break;
+ case state_STARTING2:
+ urb = playbacksubs->urb[1];
+ atomic_inc(&playbacksubs->state);
+ break;
+ }
+ }
+ if (urb) {
+ if ((err = usX2Y_urb_play_prepare(playbacksubs, capsubs->completed_urb, urb)) ||
+ (err = usX2Y_urb_submit(playbacksubs, urb, frame))) {
+ return err;
+ }
+ }
+
+ playbacksubs->completed_urb = NULL;
+
+ state = atomic_read(&capsubs->state);
+ if (state >= state_PREPARED) {
+ if (state == state_RUNNING) {
+ if ((err = usX2Y_urb_capt_retire(capsubs)))
+ return err;
+ } else if (state >= state_PRERUNNING)
+ atomic_inc(&capsubs->state);
+ if ((err = usX2Y_urb_submit(capsubs, capsubs->completed_urb, frame)))
+ return err;
+ }
+ capsubs->completed_urb = NULL;
+ return 0;
+}
+
+
+static void usX2Y_clients_stop(struct usX2Ydev *usX2Y)
+{
+ int s, u;
+
+ for (s = 0; s < 4; s++) {
+ struct snd_usX2Y_substream *subs = usX2Y->subs[s];
+ if (subs) {
+ snd_printdd("%i %p state=%i\n", s, subs, atomic_read(&subs->state));
+ atomic_set(&subs->state, state_STOPPED);
+ }
+ }
+ for (s = 0; s < 4; s++) {
+ struct snd_usX2Y_substream *subs = usX2Y->subs[s];
+ if (subs) {
+ if (atomic_read(&subs->state) >= state_PRERUNNING)
+ snd_pcm_stop_xrun(subs->pcm_substream);
+ for (u = 0; u < NRURBS; u++) {
+ struct urb *urb = subs->urb[u];
+ if (NULL != urb)
+ snd_printdd("%i status=%i start_frame=%i\n",
+ u, urb->status, urb->start_frame);
+ }
+ }
+ }
+ usX2Y->prepare_subs = NULL;
+ wake_up(&usX2Y->prepare_wait_queue);
+}
+
+static void usX2Y_error_urb_status(struct usX2Ydev *usX2Y,
+ struct snd_usX2Y_substream *subs, struct urb *urb)
+{
+ snd_printk(KERN_ERR "ep=%i stalled with status=%i\n", subs->endpoint, urb->status);
+ urb->status = 0;
+ usX2Y_clients_stop(usX2Y);
+}
+
+static void i_usX2Y_urb_complete(struct urb *urb)
+{
+ struct snd_usX2Y_substream *subs = urb->context;
+ struct usX2Ydev *usX2Y = subs->usX2Y;
+
+ if (unlikely(atomic_read(&subs->state) < state_PREPARED)) {
+ snd_printdd("hcd_frame=%i ep=%i%s status=%i start_frame=%i\n",
+ usb_get_current_frame_number(usX2Y->dev),
+ subs->endpoint, usb_pipein(urb->pipe) ? "in" : "out",
+ urb->status, urb->start_frame);
+ return;
+ }
+ if (unlikely(urb->status)) {
+ usX2Y_error_urb_status(usX2Y, subs, urb);
+ return;
+ }
+
+ subs->completed_urb = urb;
+
+ {
+ struct snd_usX2Y_substream *capsubs = usX2Y->subs[SNDRV_PCM_STREAM_CAPTURE],
+ *playbacksubs = usX2Y->subs[SNDRV_PCM_STREAM_PLAYBACK];
+ if (capsubs->completed_urb &&
+ atomic_read(&capsubs->state) >= state_PREPARED &&
+ (playbacksubs->completed_urb ||
+ atomic_read(&playbacksubs->state) < state_PREPARED)) {
+ if (!usX2Y_usbframe_complete(capsubs, playbacksubs, urb->start_frame))
+ usX2Y->wait_iso_frame += nr_of_packs();
+ else {
+ snd_printdd("\n");
+ usX2Y_clients_stop(usX2Y);
+ }
+ }
+ }
+}
+
+static void usX2Y_urbs_set_complete(struct usX2Ydev * usX2Y,
+ void (*complete)(struct urb *))
+{
+ int s, u;
+ for (s = 0; s < 4; s++) {
+ struct snd_usX2Y_substream *subs = usX2Y->subs[s];
+ if (NULL != subs)
+ for (u = 0; u < NRURBS; u++) {
+ struct urb * urb = subs->urb[u];
+ if (NULL != urb)
+ urb->complete = complete;
+ }
+ }
+}
+
+static void usX2Y_subs_startup_finish(struct usX2Ydev * usX2Y)
+{
+ usX2Y_urbs_set_complete(usX2Y, i_usX2Y_urb_complete);
+ usX2Y->prepare_subs = NULL;
+}
+
+static void i_usX2Y_subs_startup(struct urb *urb)
+{
+ struct snd_usX2Y_substream *subs = urb->context;
+ struct usX2Ydev *usX2Y = subs->usX2Y;
+ struct snd_usX2Y_substream *prepare_subs = usX2Y->prepare_subs;
+ if (NULL != prepare_subs)
+ if (urb->start_frame == prepare_subs->urb[0]->start_frame) {
+ usX2Y_subs_startup_finish(usX2Y);
+ atomic_inc(&prepare_subs->state);
+ wake_up(&usX2Y->prepare_wait_queue);
+ }
+
+ i_usX2Y_urb_complete(urb);
+}
+
+static void usX2Y_subs_prepare(struct snd_usX2Y_substream *subs)
+{
+ snd_printdd("usX2Y_substream_prepare(%p) ep=%i urb0=%p urb1=%p\n",
+ subs, subs->endpoint, subs->urb[0], subs->urb[1]);
+ /* reset the pointer */
+ subs->hwptr = 0;
+ subs->hwptr_done = 0;
+ subs->transfer_done = 0;
+}
+
+
+static void usX2Y_urb_release(struct urb **urb, int free_tb)
+{
+ if (*urb) {
+ usb_kill_urb(*urb);
+ if (free_tb)
+ kfree((*urb)->transfer_buffer);
+ usb_free_urb(*urb);
+ *urb = NULL;
+ }
+}
+/*
+ * release a substreams urbs
+ */
+static void usX2Y_urbs_release(struct snd_usX2Y_substream *subs)
+{
+ int i;
+ snd_printdd("usX2Y_urbs_release() %i\n", subs->endpoint);
+ for (i = 0; i < NRURBS; i++)
+ usX2Y_urb_release(subs->urb + i,
+ subs != subs->usX2Y->subs[SNDRV_PCM_STREAM_PLAYBACK]);
+
+ kfree(subs->tmpbuf);
+ subs->tmpbuf = NULL;
+}
+/*
+ * initialize a substream's urbs
+ */
+static int usX2Y_urbs_allocate(struct snd_usX2Y_substream *subs)
+{
+ int i;
+ unsigned int pipe;
+ int is_playback = subs == subs->usX2Y->subs[SNDRV_PCM_STREAM_PLAYBACK];
+ struct usb_device *dev = subs->usX2Y->dev;
+
+ pipe = is_playback ? usb_sndisocpipe(dev, subs->endpoint) :
+ usb_rcvisocpipe(dev, subs->endpoint);
+ subs->maxpacksize = usb_maxpacket(dev, pipe, is_playback);
+ if (!subs->maxpacksize)
+ return -EINVAL;
+
+ if (is_playback && NULL == subs->tmpbuf) { /* allocate a temporary buffer for playback */
+ subs->tmpbuf = kcalloc(nr_of_packs(), subs->maxpacksize, GFP_KERNEL);
+ if (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(struct snd_usX2Y_substream *subs)
+{
+ struct usX2Ydev *usX2Y = subs->usX2Y;
+ usX2Y->prepare_subs = subs;
+ subs->urb[0]->start_frame = -1;
+ wmb();
+ usX2Y_urbs_set_complete(usX2Y, i_usX2Y_subs_startup);
+}
+
+static int usX2Y_urbs_start(struct snd_usX2Y_substream *subs)
+{
+ int i, err;
+ struct usX2Ydev *usX2Y = subs->usX2Y;
+
+ if ((err = usX2Y_urbs_allocate(subs)) < 0)
+ return err;
+ subs->completed_urb = NULL;
+ for (i = 0; i < 4; i++) {
+ struct snd_usX2Y_substream *subs = usX2Y->subs[i];
+ if (subs != NULL && atomic_read(&subs->state) >= state_PREPARED)
+ goto start;
+ }
+
+ start:
+ usX2Y_subs_startup(subs);
+ for (i = 0; i < NRURBS; i++) {
+ struct urb *urb = subs->urb[i];
+ if (usb_pipein(urb->pipe)) {
+ unsigned long pack;
+ if (0 == i)
+ atomic_set(&subs->state, state_STARTING3);
+ urb->dev = usX2Y->dev;
+ for (pack = 0; pack < nr_of_packs(); pack++) {
+ urb->iso_frame_desc[pack].offset = subs->maxpacksize * pack;
+ urb->iso_frame_desc[pack].length = subs->maxpacksize;
+ }
+ urb->transfer_buffer_length = subs->maxpacksize * nr_of_packs();
+ if ((err = usb_submit_urb(urb, GFP_ATOMIC)) < 0) {
+ snd_printk (KERN_ERR "cannot submit datapipe for urb %d, err = %d\n", i, err);
+ err = -EPIPE;
+ goto cleanup;
+ } else
+ if (i == 0)
+ usX2Y->wait_iso_frame = urb->start_frame;
+ urb->transfer_flags = 0;
+ } else {
+ atomic_set(&subs->state, state_STARTING1);
+ break;
+ }
+ }
+ err = 0;
+ wait_event(usX2Y->prepare_wait_queue, NULL == usX2Y->prepare_subs);
+ if (atomic_read(&subs->state) != state_PREPARED)
+ err = -EPIPE;
+
+ cleanup:
+ if (err) {
+ usX2Y_subs_startup_finish(usX2Y);
+ usX2Y_clients_stop(usX2Y); // something is completely wroong > stop evrything
+ }
+ return err;
+}
+
+/*
+ * return the current pcm pointer. just return the hwptr_done value.
+ */
+static snd_pcm_uframes_t snd_usX2Y_pcm_pointer(struct snd_pcm_substream *substream)
+{
+ struct snd_usX2Y_substream *subs = substream->runtime->private_data;
+ return subs->hwptr_done;
+}
+/*
+ * start/stop substream
+ */
+static int snd_usX2Y_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+ struct snd_usX2Y_substream *subs = substream->runtime->private_data;
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ snd_printdd("snd_usX2Y_pcm_trigger(START)\n");
+ if (atomic_read(&subs->state) == state_PREPARED &&
+ atomic_read(&subs->usX2Y->subs[SNDRV_PCM_STREAM_CAPTURE]->state) >= state_PREPARED) {
+ atomic_set(&subs->state, state_PRERUNNING);
+ } else {
+ snd_printdd("\n");
+ return -EPIPE;
+ }
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ snd_printdd("snd_usX2Y_pcm_trigger(STOP)\n");
+ if (atomic_read(&subs->state) >= state_PRERUNNING)
+ atomic_set(&subs->state, state_PREPARED);
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+
+/*
+ * allocate a buffer, setup samplerate
+ *
+ * so far we use a physically linear buffer although packetize transfer
+ * doesn't need a continuous area.
+ * if sg buffer is supported on the later version of alsa, we'll follow
+ * that.
+ */
+static struct s_c2
+{
+ char c1, c2;
+}
+ SetRate44100[] =
+{
+ { 0x14, 0x08}, // this line sets 44100, well actually a little less
+ { 0x18, 0x40}, // only tascam / frontier design knows the further lines .......
+ { 0x18, 0x42},
+ { 0x18, 0x45},
+ { 0x18, 0x46},
+ { 0x18, 0x48},
+ { 0x18, 0x4A},
+ { 0x18, 0x4C},
+ { 0x18, 0x4E},
+ { 0x18, 0x50},
+ { 0x18, 0x52},
+ { 0x18, 0x54},
+ { 0x18, 0x56},
+ { 0x18, 0x58},
+ { 0x18, 0x5A},
+ { 0x18, 0x5C},
+ { 0x18, 0x5E},
+ { 0x18, 0x60},
+ { 0x18, 0x62},
+ { 0x18, 0x64},
+ { 0x18, 0x66},
+ { 0x18, 0x68},
+ { 0x18, 0x6A},
+ { 0x18, 0x6C},
+ { 0x18, 0x6E},
+ { 0x18, 0x70},
+ { 0x18, 0x72},
+ { 0x18, 0x74},
+ { 0x18, 0x76},
+ { 0x18, 0x78},
+ { 0x18, 0x7A},
+ { 0x18, 0x7C},
+ { 0x18, 0x7E}
+};
+static struct s_c2 SetRate48000[] =
+{
+ { 0x14, 0x09}, // this line sets 48000, well actually a little less
+ { 0x18, 0x40}, // only tascam / frontier design knows the further lines .......
+ { 0x18, 0x42},
+ { 0x18, 0x45},
+ { 0x18, 0x46},
+ { 0x18, 0x48},
+ { 0x18, 0x4A},
+ { 0x18, 0x4C},
+ { 0x18, 0x4E},
+ { 0x18, 0x50},
+ { 0x18, 0x52},
+ { 0x18, 0x54},
+ { 0x18, 0x56},
+ { 0x18, 0x58},
+ { 0x18, 0x5A},
+ { 0x18, 0x5C},
+ { 0x18, 0x5E},
+ { 0x18, 0x60},
+ { 0x18, 0x62},
+ { 0x18, 0x64},
+ { 0x18, 0x66},
+ { 0x18, 0x68},
+ { 0x18, 0x6A},
+ { 0x18, 0x6C},
+ { 0x18, 0x6E},
+ { 0x18, 0x70},
+ { 0x18, 0x73},
+ { 0x18, 0x74},
+ { 0x18, 0x76},
+ { 0x18, 0x78},
+ { 0x18, 0x7A},
+ { 0x18, 0x7C},
+ { 0x18, 0x7E}
+};
+#define NOOF_SETRATE_URBS ARRAY_SIZE(SetRate48000)
+
+static void i_usX2Y_04Int(struct urb *urb)
+{
+ struct usX2Ydev *usX2Y = urb->context;
+
+ if (urb->status)
+ snd_printk(KERN_ERR "snd_usX2Y_04Int() urb->status=%i\n", urb->status);
+ if (0 == --usX2Y->US04->len)
+ wake_up(&usX2Y->In04WaitQueue);
+}
+
+static int usX2Y_rate_set(struct usX2Ydev *usX2Y, int rate)
+{
+ int err = 0, i;
+ struct snd_usX2Y_urbSeq *us = NULL;
+ int *usbdata = NULL;
+ struct s_c2 *ra = rate == 48000 ? SetRate48000 : SetRate44100;
+
+ if (usX2Y->rate != rate) {
+ us = kzalloc(sizeof(*us) + sizeof(struct urb*) * NOOF_SETRATE_URBS, GFP_KERNEL);
+ if (NULL == us) {
+ err = -ENOMEM;
+ goto cleanup;
+ }
+ usbdata = kmalloc(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->dev, usb_sndbulkpipe(usX2Y->dev, 4),
+ usbdata + i, 2, i_usX2Y_04Int, usX2Y);
+ }
+ us->submitted = 0;
+ us->len = NOOF_SETRATE_URBS;
+ usX2Y->US04 = us;
+ wait_event_timeout(usX2Y->In04WaitQueue, 0 == us->len, HZ);
+ usX2Y->US04 = NULL;
+ if (us->len)
+ err = -ENODEV;
+ cleanup:
+ if (us) {
+ us->submitted = 2*NOOF_SETRATE_URBS;
+ for (i = 0; i < NOOF_SETRATE_URBS; ++i) {
+ struct urb *urb = us->urb[i];
+ if (urb->status) {
+ if (!err)
+ err = -ENODEV;
+ usb_kill_urb(urb);
+ }
+ usb_free_urb(urb);
+ }
+ usX2Y->US04 = NULL;
+ kfree(usbdata);
+ kfree(us);
+ if (!err)
+ usX2Y->rate = rate;
+ }
+ }
+
+ return err;
+}
+
+
+static int usX2Y_format_set(struct usX2Ydev *usX2Y, snd_pcm_format_t format)
+{
+ int alternate, err;
+ struct list_head* p;
+ if (format == SNDRV_PCM_FORMAT_S24_3LE) {
+ alternate = 2;
+ usX2Y->stride = 6;
+ } else {
+ alternate = 1;
+ usX2Y->stride = 4;
+ }
+ list_for_each(p, &usX2Y->midi_list) {
+ snd_usbmidi_input_stop(p);
+ }
+ usb_kill_urb(usX2Y->In04urb);
+ if ((err = usb_set_interface(usX2Y->dev, 0, alternate))) {
+ snd_printk(KERN_ERR "usb_set_interface error \n");
+ return err;
+ }
+ usX2Y->In04urb->dev = usX2Y->dev;
+ err = usb_submit_urb(usX2Y->In04urb, GFP_KERNEL);
+ list_for_each(p, &usX2Y->midi_list) {
+ snd_usbmidi_input_start(p);
+ }
+ usX2Y->format = format;
+ usX2Y->rate = 0;
+ return err;
+}
+
+
+static int snd_usX2Y_pcm_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *hw_params)
+{
+ int err = 0;
+ unsigned int rate = params_rate(hw_params);
+ snd_pcm_format_t format = params_format(hw_params);
+ struct snd_card *card = substream->pstr->pcm->card;
+ struct usX2Ydev *dev = usX2Y(card);
+ int i;
+
+ mutex_lock(&usX2Y(card)->pcm_mutex);
+ snd_printdd("snd_usX2Y_hw_params(%p, %p)\n", substream, hw_params);
+ /* all pcm substreams off one usX2Y have to operate at the same
+ * rate & format
+ */
+ for (i = 0; i < dev->pcm_devs * 2; i++) {
+ struct snd_usX2Y_substream *subs = dev->subs[i];
+ struct snd_pcm_substream *test_substream;
+
+ if (!subs)
+ continue;
+ test_substream = subs->pcm_substream;
+ if (!test_substream || test_substream == substream ||
+ !test_substream->runtime)
+ continue;
+ if ((test_substream->runtime->format &&
+ test_substream->runtime->format != format) ||
+ (test_substream->runtime->rate &&
+ test_substream->runtime->rate != rate)) {
+ err = -EINVAL;
+ goto error;
+ }
+ }
+
+ err = snd_pcm_lib_malloc_pages(substream,
+ params_buffer_bytes(hw_params));
+ if (err < 0) {
+ snd_printk(KERN_ERR "snd_pcm_lib_malloc_pages(%p, %i) returned %i\n",
+ substream, params_buffer_bytes(hw_params), err);
+ goto error;
+ }
+
+ error:
+ mutex_unlock(&usX2Y(card)->pcm_mutex);
+ return err;
+}
+
+/*
+ * free the buffer
+ */
+static int snd_usX2Y_pcm_hw_free(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct snd_usX2Y_substream *subs = runtime->private_data;
+ mutex_lock(&subs->usX2Y->pcm_mutex);
+ snd_printdd("snd_usX2Y_hw_free(%p)\n", substream);
+
+ if (SNDRV_PCM_STREAM_PLAYBACK == substream->stream) {
+ struct snd_usX2Y_substream *cap_subs = subs->usX2Y->subs[SNDRV_PCM_STREAM_CAPTURE];
+ atomic_set(&subs->state, state_STOPPED);
+ usX2Y_urbs_release(subs);
+ if (!cap_subs->pcm_substream ||
+ !cap_subs->pcm_substream->runtime ||
+ !cap_subs->pcm_substream->runtime->status ||
+ cap_subs->pcm_substream->runtime->status->state < SNDRV_PCM_STATE_PREPARED) {
+ atomic_set(&cap_subs->state, state_STOPPED);
+ usX2Y_urbs_release(cap_subs);
+ }
+ } else {
+ struct snd_usX2Y_substream *playback_subs = subs->usX2Y->subs[SNDRV_PCM_STREAM_PLAYBACK];
+ if (atomic_read(&playback_subs->state) < state_PREPARED) {
+ atomic_set(&subs->state, state_STOPPED);
+ usX2Y_urbs_release(subs);
+ }
+ }
+ mutex_unlock(&subs->usX2Y->pcm_mutex);
+ return snd_pcm_lib_free_pages(substream);
+}
+/*
+ * prepare callback
+ *
+ * set format and initialize urbs
+ */
+static int snd_usX2Y_pcm_prepare(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct snd_usX2Y_substream *subs = runtime->private_data;
+ struct usX2Ydev *usX2Y = subs->usX2Y;
+ struct snd_usX2Y_substream *capsubs = subs->usX2Y->subs[SNDRV_PCM_STREAM_CAPTURE];
+ int err = 0;
+ snd_printdd("snd_usX2Y_pcm_prepare(%p)\n", substream);
+
+ mutex_lock(&usX2Y->pcm_mutex);
+ usX2Y_subs_prepare(subs);
+// Start hardware streams
+// SyncStream first....
+ if (atomic_read(&capsubs->state) < state_PREPARED) {
+ if (usX2Y->format != runtime->format)
+ if ((err = usX2Y_format_set(usX2Y, runtime->format)) < 0)
+ goto up_prepare_mutex;
+ if (usX2Y->rate != runtime->rate)
+ if ((err = usX2Y_rate_set(usX2Y, runtime->rate)) < 0)
+ goto up_prepare_mutex;
+ snd_printdd("starting capture pipe for %s\n", subs == capsubs ? "self" : "playpipe");
+ if (0 > (err = usX2Y_urbs_start(capsubs)))
+ goto up_prepare_mutex;
+ }
+
+ if (subs != capsubs && atomic_read(&subs->state) < state_PREPARED)
+ err = usX2Y_urbs_start(subs);
+
+ up_prepare_mutex:
+ mutex_unlock(&usX2Y->pcm_mutex);
+ return err;
+}
+
+static struct snd_pcm_hardware snd_usX2Y_2c =
+{
+ .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_BLOCK_TRANSFER |
+ SNDRV_PCM_INFO_MMAP_VALID |
+ SNDRV_PCM_INFO_BATCH),
+ .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_3LE,
+ .rates = SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000,
+ .rate_min = 44100,
+ .rate_max = 48000,
+ .channels_min = 2,
+ .channels_max = 2,
+ .buffer_bytes_max = (2*128*1024),
+ .period_bytes_min = 64,
+ .period_bytes_max = (128*1024),
+ .periods_min = 2,
+ .periods_max = 1024,
+ .fifo_size = 0
+};
+
+
+
+static int snd_usX2Y_pcm_open(struct snd_pcm_substream *substream)
+{
+ struct snd_usX2Y_substream *subs = ((struct snd_usX2Y_substream **)
+ snd_pcm_substream_chip(substream))[substream->stream];
+ struct snd_pcm_runtime *runtime = substream->runtime;
+
+ if (subs->usX2Y->chip_status & USX2Y_STAT_CHIP_MMAP_PCM_URBS)
+ return -EBUSY;
+
+ runtime->hw = snd_usX2Y_2c;
+ runtime->private_data = subs;
+ subs->pcm_substream = substream;
+ snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_PERIOD_TIME, 1000, 200000);
+ return 0;
+}
+
+
+
+static int snd_usX2Y_pcm_close(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct snd_usX2Y_substream *subs = runtime->private_data;
+
+ subs->pcm_substream = NULL;
+
+ return 0;
+}
+
+
+static struct snd_pcm_ops snd_usX2Y_pcm_ops =
+{
+ .open = snd_usX2Y_pcm_open,
+ .close = snd_usX2Y_pcm_close,
+ .ioctl = snd_pcm_lib_ioctl,
+ .hw_params = snd_usX2Y_pcm_hw_params,
+ .hw_free = snd_usX2Y_pcm_hw_free,
+ .prepare = snd_usX2Y_pcm_prepare,
+ .trigger = snd_usX2Y_pcm_trigger,
+ .pointer = snd_usX2Y_pcm_pointer,
+};
+
+
+/*
+ * free a usb stream instance
+ */
+static void usX2Y_audio_stream_free(struct snd_usX2Y_substream **usX2Y_substream)
+{
+ kfree(usX2Y_substream[SNDRV_PCM_STREAM_PLAYBACK]);
+ usX2Y_substream[SNDRV_PCM_STREAM_PLAYBACK] = NULL;
+
+ kfree(usX2Y_substream[SNDRV_PCM_STREAM_CAPTURE]);
+ usX2Y_substream[SNDRV_PCM_STREAM_CAPTURE] = NULL;
+}
+
+static void snd_usX2Y_pcm_private_free(struct snd_pcm *pcm)
+{
+ struct snd_usX2Y_substream **usX2Y_stream = pcm->private_data;
+ if (usX2Y_stream)
+ usX2Y_audio_stream_free(usX2Y_stream);
+}
+
+static int usX2Y_audio_stream_new(struct snd_card *card, int playback_endpoint, int capture_endpoint)
+{
+ struct snd_pcm *pcm;
+ int err, i;
+ struct snd_usX2Y_substream **usX2Y_substream =
+ usX2Y(card)->subs + 2 * usX2Y(card)->pcm_devs;
+
+ for (i = playback_endpoint ? SNDRV_PCM_STREAM_PLAYBACK : SNDRV_PCM_STREAM_CAPTURE;
+ i <= SNDRV_PCM_STREAM_CAPTURE; ++i) {
+ usX2Y_substream[i] = kzalloc(sizeof(struct snd_usX2Y_substream), GFP_KERNEL);
+ if (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)->pcm_devs,
+ playback_endpoint ? 1 : 0, 1,
+ &pcm);
+ if (err < 0) {
+ usX2Y_audio_stream_free(usX2Y_substream);
+ return err;
+ }
+
+ if (playback_endpoint)
+ snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_usX2Y_pcm_ops);
+ snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_usX2Y_pcm_ops);
+
+ pcm->private_data = usX2Y_substream;
+ pcm->private_free = snd_usX2Y_pcm_private_free;
+ pcm->info_flags = 0;
+
+ sprintf(pcm->name, NAME_ALLCAPS" Audio #%d", usX2Y(card)->pcm_devs);
+
+ if ((playback_endpoint &&
+ 0 > (err = snd_pcm_lib_preallocate_pages(pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream,
+ SNDRV_DMA_TYPE_CONTINUOUS,
+ snd_dma_continuous_data(GFP_KERNEL),
+ 64*1024, 128*1024))) ||
+ 0 > (err = snd_pcm_lib_preallocate_pages(pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream,
+ SNDRV_DMA_TYPE_CONTINUOUS,
+ snd_dma_continuous_data(GFP_KERNEL),
+ 64*1024, 128*1024))) {
+ snd_usX2Y_pcm_private_free(pcm);
+ return err;
+ }
+ usX2Y(card)->pcm_devs++;
+
+ return 0;
+}
+
+/*
+ * create a chip instance and set its names.
+ */
+int usX2Y_audio_create(struct snd_card *card)
+{
+ int err = 0;
+
+ INIT_LIST_HEAD(&usX2Y(card)->pcm_list);
+
+ if (0 > (err = usX2Y_audio_stream_new(card, 0xA, 0x8)))
+ return err;
+ if (le16_to_cpu(usX2Y(card)->dev->descriptor.idProduct) == USB_ID_US428)
+ if (0 > (err = usX2Y_audio_stream_new(card, 0, 0xA)))
+ return err;
+ if (le16_to_cpu(usX2Y(card)->dev->descriptor.idProduct) != USB_ID_US122)
+ err = usX2Y_rate_set(usX2Y(card), 44100); // Lets us428 recognize output-volume settings, disturbs us122.
+ return err;
+}
diff --git a/sound/usb/usx2y/usx2y.h b/sound/usb/usx2y/usx2y.h
new file mode 100644
index 000000000..7e59263dd
--- /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 000000000..90766a92e
--- /dev/null
+++ b/sound/usb/usx2y/usx2yhwdeppcm.c
@@ -0,0 +1,762 @@
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+/* USX2Y "rawusb" aka hwdep_pcm implementation
+
+ Its usb's unableness to atomically handle power of 2 period sized data chuncs
+ at standard samplerates,
+ what led to this part of the usx2y module:
+ It provides the alsa kernel half of the usx2y-alsa-jack driver pair.
+ The pair uses a hardware dependent alsa-device for mmaped pcm transport.
+ Advantage achieved:
+ The usb_hc moves pcm data from/into memory via DMA.
+ That memory is mmaped by jack's usx2y driver.
+ Jack's usx2y driver is the first/last to read/write pcm data.
+ Read/write is a combination of power of 2 period shaping and
+ float/int conversation.
+ Compared to mainline alsa/jack we leave out power of 2 period shaping inside
+ snd-usb-usx2y which needs memcpy() and additional buffers.
+ As a side effect possible unwanted pcm-data coruption resulting of
+ standard alsa's snd-usb-usx2y period shaping scheme falls away.
+ Result is sane jack operation at buffering schemes down to 128frames,
+ 2 periods.
+ plain usx2y alsa mode is able to achieve 64frames, 4periods, but only at the
+ cost of easier triggered i.e. aeolus xruns (128 or 256frames,
+ 2periods works but is useless cause of crackling).
+
+ This is a first "proof of concept" implementation.
+ Later, functionalities should migrate to more appropriate places:
+ Userland:
+ - The jackd could mmap its float-pcm buffers directly from alsa-lib.
+ - alsa-lib could provide power of 2 period sized shaping combined with int/float
+ conversation.
+ Currently the usx2y jack driver provides above 2 services.
+ Kernel:
+ - rawusb dma pcm buffer transport should go to snd-usb-lib, so also snd-usb-audio
+ devices can use it.
+ Currently rawusb dma pcm buffer transport (this file) is only available to snd-usb-usx2y.
+*/
+
+#include <linux/delay.h>
+#include <linux/gfp.h>
+#include "usbusx2yaudio.c"
+
+#if defined(USX2Y_NRPACKS_VARIABLE) || USX2Y_NRPACKS == 1
+
+#include <sound/hwdep.h>
+
+
+static int usX2Y_usbpcm_urb_capt_retire(struct snd_usX2Y_substream *subs)
+{
+ struct urb *urb = subs->completed_urb;
+ struct snd_pcm_runtime *runtime = subs->pcm_substream->runtime;
+ int i, lens = 0, hwptr_done = subs->hwptr_done;
+ struct usX2Ydev *usX2Y = subs->usX2Y;
+ if (0 > usX2Y->hwdep_pcm_shm->capture_iso_start) { //FIXME
+ int head = usX2Y->hwdep_pcm_shm->captured_iso_head + 1;
+ if (head >= ARRAY_SIZE(usX2Y->hwdep_pcm_shm->captured_iso))
+ head = 0;
+ usX2Y->hwdep_pcm_shm->capture_iso_start = head;
+ snd_printdd("cap start %i\n", head);
+ }
+ for (i = 0; i < nr_of_packs(); i++) {
+ if (urb->iso_frame_desc[i].status) { /* active? hmm, skip this */
+ snd_printk(KERN_ERR "active frame status %i. Most probably some hardware problem.\n", urb->iso_frame_desc[i].status);
+ return urb->iso_frame_desc[i].status;
+ }
+ lens += urb->iso_frame_desc[i].actual_length / usX2Y->stride;
+ }
+ if ((hwptr_done += lens) >= runtime->buffer_size)
+ hwptr_done -= runtime->buffer_size;
+ subs->hwptr_done = hwptr_done;
+ subs->transfer_done += lens;
+ /* update the pointer, call callback if necessary */
+ if (subs->transfer_done >= runtime->period_size) {
+ subs->transfer_done -= runtime->period_size;
+ snd_pcm_period_elapsed(subs->pcm_substream);
+ }
+ return 0;
+}
+
+static inline int usX2Y_iso_frames_per_buffer(struct snd_pcm_runtime *runtime,
+ struct usX2Ydev * usX2Y)
+{
+ return (runtime->buffer_size * 1000) / usX2Y->rate + 1; //FIXME: so far only correct period_size == 2^x ?
+}
+
+/*
+ * prepare urb for playback data pipe
+ *
+ * we copy the data directly from the pcm buffer.
+ * the current position to be copied is held in hwptr field.
+ * since a urb can handle only a single linear buffer, if the total
+ * transferred area overflows the buffer boundary, we cannot send
+ * it directly from the buffer. thus the data is once copied to
+ * a temporary buffer and urb points to that.
+ */
+static int usX2Y_hwdep_urb_play_prepare(struct snd_usX2Y_substream *subs,
+ struct urb *urb)
+{
+ int count, counts, pack;
+ struct usX2Ydev *usX2Y = subs->usX2Y;
+ struct snd_usX2Y_hwdep_pcm_shm *shm = usX2Y->hwdep_pcm_shm;
+ struct snd_pcm_runtime *runtime = subs->pcm_substream->runtime;
+
+ if (0 > shm->playback_iso_start) {
+ shm->playback_iso_start = shm->captured_iso_head -
+ usX2Y_iso_frames_per_buffer(runtime, usX2Y);
+ if (0 > shm->playback_iso_start)
+ shm->playback_iso_start += ARRAY_SIZE(shm->captured_iso);
+ shm->playback_iso_head = shm->playback_iso_start;
+ }
+
+ count = 0;
+ for (pack = 0; pack < nr_of_packs(); pack++) {
+ /* calculate the size of a packet */
+ counts = shm->captured_iso[shm->playback_iso_head].length / usX2Y->stride;
+ if (counts < 43 || counts > 50) {
+ snd_printk(KERN_ERR "should not be here with counts=%i\n", counts);
+ return -EPIPE;
+ }
+ /* set up descriptor */
+ urb->iso_frame_desc[pack].offset = shm->captured_iso[shm->playback_iso_head].offset;
+ urb->iso_frame_desc[pack].length = shm->captured_iso[shm->playback_iso_head].length;
+ if (atomic_read(&subs->state) != state_RUNNING)
+ memset((char *)urb->transfer_buffer + urb->iso_frame_desc[pack].offset, 0,
+ urb->iso_frame_desc[pack].length);
+ if (++shm->playback_iso_head >= ARRAY_SIZE(shm->captured_iso))
+ shm->playback_iso_head = 0;
+ count += counts;
+ }
+ urb->transfer_buffer_length = count * usX2Y->stride;
+ return 0;
+}
+
+
+static inline void usX2Y_usbpcm_urb_capt_iso_advance(struct snd_usX2Y_substream *subs,
+ struct urb *urb)
+{
+ int pack;
+ for (pack = 0; pack < nr_of_packs(); ++pack) {
+ struct usb_iso_packet_descriptor *desc = urb->iso_frame_desc + pack;
+ if (NULL != subs) {
+ struct snd_usX2Y_hwdep_pcm_shm *shm = subs->usX2Y->hwdep_pcm_shm;
+ int head = shm->captured_iso_head + 1;
+ if (head >= ARRAY_SIZE(shm->captured_iso))
+ head = 0;
+ shm->captured_iso[head].frame = urb->start_frame + pack;
+ shm->captured_iso[head].offset = desc->offset;
+ shm->captured_iso[head].length = desc->actual_length;
+ shm->captured_iso_head = head;
+ shm->captured_iso_frames++;
+ }
+ if ((desc->offset += desc->length * NRURBS*nr_of_packs()) +
+ desc->length >= SSS)
+ desc->offset -= (SSS - desc->length);
+ }
+}
+
+static inline int usX2Y_usbpcm_usbframe_complete(struct snd_usX2Y_substream *capsubs,
+ struct snd_usX2Y_substream *capsubs2,
+ struct snd_usX2Y_substream *playbacksubs,
+ int frame)
+{
+ int err, state;
+ struct urb *urb = playbacksubs->completed_urb;
+
+ state = atomic_read(&playbacksubs->state);
+ if (NULL != urb) {
+ if (state == state_RUNNING)
+ usX2Y_urb_play_retire(playbacksubs, urb);
+ else if (state >= state_PRERUNNING)
+ atomic_inc(&playbacksubs->state);
+ } else {
+ switch (state) {
+ case state_STARTING1:
+ urb = playbacksubs->urb[0];
+ atomic_inc(&playbacksubs->state);
+ break;
+ case state_STARTING2:
+ urb = playbacksubs->urb[1];
+ atomic_inc(&playbacksubs->state);
+ break;
+ }
+ }
+ if (urb) {
+ if ((err = usX2Y_hwdep_urb_play_prepare(playbacksubs, urb)) ||
+ (err = usX2Y_urb_submit(playbacksubs, urb, frame))) {
+ return err;
+ }
+ }
+
+ playbacksubs->completed_urb = NULL;
+
+ state = atomic_read(&capsubs->state);
+ if (state >= state_PREPARED) {
+ if (state == state_RUNNING) {
+ if ((err = usX2Y_usbpcm_urb_capt_retire(capsubs)))
+ return err;
+ } else if (state >= state_PRERUNNING)
+ atomic_inc(&capsubs->state);
+ usX2Y_usbpcm_urb_capt_iso_advance(capsubs, capsubs->completed_urb);
+ if (NULL != capsubs2)
+ usX2Y_usbpcm_urb_capt_iso_advance(NULL, capsubs2->completed_urb);
+ if ((err = usX2Y_urb_submit(capsubs, capsubs->completed_urb, frame)))
+ return err;
+ if (NULL != capsubs2)
+ if ((err = usX2Y_urb_submit(capsubs2, capsubs2->completed_urb, frame)))
+ return err;
+ }
+ capsubs->completed_urb = NULL;
+ if (NULL != capsubs2)
+ capsubs2->completed_urb = NULL;
+ return 0;
+}
+
+
+static void i_usX2Y_usbpcm_urb_complete(struct urb *urb)
+{
+ struct snd_usX2Y_substream *subs = urb->context;
+ struct usX2Ydev *usX2Y = subs->usX2Y;
+ struct snd_usX2Y_substream *capsubs, *capsubs2, *playbacksubs;
+
+ if (unlikely(atomic_read(&subs->state) < state_PREPARED)) {
+ snd_printdd("hcd_frame=%i ep=%i%s status=%i start_frame=%i\n",
+ usb_get_current_frame_number(usX2Y->dev),
+ subs->endpoint, usb_pipein(urb->pipe) ? "in" : "out",
+ urb->status, urb->start_frame);
+ return;
+ }
+ if (unlikely(urb->status)) {
+ usX2Y_error_urb_status(usX2Y, subs, urb);
+ return;
+ }
+
+ subs->completed_urb = urb;
+ capsubs = usX2Y->subs[SNDRV_PCM_STREAM_CAPTURE];
+ capsubs2 = usX2Y->subs[SNDRV_PCM_STREAM_CAPTURE + 2];
+ playbacksubs = usX2Y->subs[SNDRV_PCM_STREAM_PLAYBACK];
+ if (capsubs->completed_urb && atomic_read(&capsubs->state) >= state_PREPARED &&
+ (NULL == capsubs2 || capsubs2->completed_urb) &&
+ (playbacksubs->completed_urb || atomic_read(&playbacksubs->state) < state_PREPARED)) {
+ if (!usX2Y_usbpcm_usbframe_complete(capsubs, capsubs2, playbacksubs, urb->start_frame))
+ usX2Y->wait_iso_frame += nr_of_packs();
+ else {
+ snd_printdd("\n");
+ usX2Y_clients_stop(usX2Y);
+ }
+ }
+}
+
+
+static void usX2Y_hwdep_urb_release(struct urb **urb)
+{
+ usb_kill_urb(*urb);
+ usb_free_urb(*urb);
+ *urb = NULL;
+}
+
+/*
+ * release a substream
+ */
+static void usX2Y_usbpcm_urbs_release(struct snd_usX2Y_substream *subs)
+{
+ int i;
+ snd_printdd("snd_usX2Y_urbs_release() %i\n", subs->endpoint);
+ for (i = 0; i < NRURBS; i++)
+ usX2Y_hwdep_urb_release(subs->urb + i);
+}
+
+static void usX2Y_usbpcm_subs_startup_finish(struct usX2Ydev * usX2Y)
+{
+ usX2Y_urbs_set_complete(usX2Y, i_usX2Y_usbpcm_urb_complete);
+ usX2Y->prepare_subs = NULL;
+}
+
+static void i_usX2Y_usbpcm_subs_startup(struct urb *urb)
+{
+ struct snd_usX2Y_substream *subs = urb->context;
+ struct usX2Ydev *usX2Y = subs->usX2Y;
+ struct snd_usX2Y_substream *prepare_subs = usX2Y->prepare_subs;
+ if (NULL != prepare_subs &&
+ urb->start_frame == prepare_subs->urb[0]->start_frame) {
+ atomic_inc(&prepare_subs->state);
+ if (prepare_subs == usX2Y->subs[SNDRV_PCM_STREAM_CAPTURE]) {
+ struct snd_usX2Y_substream *cap_subs2 = usX2Y->subs[SNDRV_PCM_STREAM_CAPTURE + 2];
+ if (cap_subs2 != NULL)
+ atomic_inc(&cap_subs2->state);
+ }
+ usX2Y_usbpcm_subs_startup_finish(usX2Y);
+ wake_up(&usX2Y->prepare_wait_queue);
+ }
+
+ i_usX2Y_usbpcm_urb_complete(urb);
+}
+
+/*
+ * initialize a substream's urbs
+ */
+static int usX2Y_usbpcm_urbs_allocate(struct snd_usX2Y_substream *subs)
+{
+ int i;
+ unsigned int pipe;
+ int is_playback = subs == subs->usX2Y->subs[SNDRV_PCM_STREAM_PLAYBACK];
+ struct usb_device *dev = subs->usX2Y->dev;
+
+ pipe = is_playback ? usb_sndisocpipe(dev, subs->endpoint) :
+ usb_rcvisocpipe(dev, subs->endpoint);
+ subs->maxpacksize = usb_maxpacket(dev, pipe, is_playback);
+ if (!subs->maxpacksize)
+ return -EINVAL;
+
+ /* allocate and initialize data urbs */
+ for (i = 0; i < NRURBS; i++) {
+ struct urb **purb = subs->urb + i;
+ if (*purb) {
+ usb_kill_urb(*purb);
+ continue;
+ }
+ *purb = usb_alloc_urb(nr_of_packs(), GFP_KERNEL);
+ if (NULL == *purb) {
+ usX2Y_usbpcm_urbs_release(subs);
+ return -ENOMEM;
+ }
+ (*purb)->transfer_buffer = is_playback ?
+ subs->usX2Y->hwdep_pcm_shm->playback : (
+ subs->endpoint == 0x8 ?
+ subs->usX2Y->hwdep_pcm_shm->capture0x8 :
+ subs->usX2Y->hwdep_pcm_shm->capture0xA);
+
+ (*purb)->dev = dev;
+ (*purb)->pipe = pipe;
+ (*purb)->number_of_packets = nr_of_packs();
+ (*purb)->context = subs;
+ (*purb)->interval = 1;
+ (*purb)->complete = i_usX2Y_usbpcm_subs_startup;
+ }
+ return 0;
+}
+
+/*
+ * free the buffer
+ */
+static int snd_usX2Y_usbpcm_hw_free(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct snd_usX2Y_substream *subs = runtime->private_data,
+ *cap_subs2 = subs->usX2Y->subs[SNDRV_PCM_STREAM_CAPTURE + 2];
+ mutex_lock(&subs->usX2Y->pcm_mutex);
+ snd_printdd("snd_usX2Y_usbpcm_hw_free(%p)\n", substream);
+
+ if (SNDRV_PCM_STREAM_PLAYBACK == substream->stream) {
+ struct snd_usX2Y_substream *cap_subs = subs->usX2Y->subs[SNDRV_PCM_STREAM_CAPTURE];
+ atomic_set(&subs->state, state_STOPPED);
+ usX2Y_usbpcm_urbs_release(subs);
+ if (!cap_subs->pcm_substream ||
+ !cap_subs->pcm_substream->runtime ||
+ !cap_subs->pcm_substream->runtime->status ||
+ cap_subs->pcm_substream->runtime->status->state < SNDRV_PCM_STATE_PREPARED) {
+ atomic_set(&cap_subs->state, state_STOPPED);
+ if (NULL != cap_subs2)
+ atomic_set(&cap_subs2->state, state_STOPPED);
+ usX2Y_usbpcm_urbs_release(cap_subs);
+ if (NULL != cap_subs2)
+ usX2Y_usbpcm_urbs_release(cap_subs2);
+ }
+ } else {
+ struct snd_usX2Y_substream *playback_subs = subs->usX2Y->subs[SNDRV_PCM_STREAM_PLAYBACK];
+ if (atomic_read(&playback_subs->state) < state_PREPARED) {
+ atomic_set(&subs->state, state_STOPPED);
+ if (NULL != cap_subs2)
+ atomic_set(&cap_subs2->state, state_STOPPED);
+ usX2Y_usbpcm_urbs_release(subs);
+ if (NULL != cap_subs2)
+ usX2Y_usbpcm_urbs_release(cap_subs2);
+ }
+ }
+ mutex_unlock(&subs->usX2Y->pcm_mutex);
+ return snd_pcm_lib_free_pages(substream);
+}
+
+static void usX2Y_usbpcm_subs_startup(struct snd_usX2Y_substream *subs)
+{
+ struct usX2Ydev * usX2Y = subs->usX2Y;
+ usX2Y->prepare_subs = subs;
+ subs->urb[0]->start_frame = -1;
+ smp_wmb(); // Make sure above modifications are seen by i_usX2Y_subs_startup()
+ usX2Y_urbs_set_complete(usX2Y, i_usX2Y_usbpcm_subs_startup);
+}
+
+static int usX2Y_usbpcm_urbs_start(struct snd_usX2Y_substream *subs)
+{
+ int p, u, err,
+ stream = subs->pcm_substream->stream;
+ struct usX2Ydev *usX2Y = subs->usX2Y;
+
+ if (SNDRV_PCM_STREAM_CAPTURE == stream) {
+ usX2Y->hwdep_pcm_shm->captured_iso_head = -1;
+ usX2Y->hwdep_pcm_shm->captured_iso_frames = 0;
+ }
+
+ for (p = 0; 3 >= (stream + p); p += 2) {
+ struct snd_usX2Y_substream *subs = usX2Y->subs[stream + p];
+ if (subs != NULL) {
+ if ((err = usX2Y_usbpcm_urbs_allocate(subs)) < 0)
+ return err;
+ subs->completed_urb = NULL;
+ }
+ }
+
+ for (p = 0; p < 4; p++) {
+ struct snd_usX2Y_substream *subs = usX2Y->subs[p];
+ if (subs != NULL && atomic_read(&subs->state) >= state_PREPARED)
+ goto start;
+ }
+
+ start:
+ usX2Y_usbpcm_subs_startup(subs);
+ for (u = 0; u < NRURBS; u++) {
+ for (p = 0; 3 >= (stream + p); p += 2) {
+ struct snd_usX2Y_substream *subs = usX2Y->subs[stream + p];
+ if (subs != NULL) {
+ struct urb *urb = subs->urb[u];
+ if (usb_pipein(urb->pipe)) {
+ unsigned long pack;
+ if (0 == u)
+ atomic_set(&subs->state, state_STARTING3);
+ urb->dev = usX2Y->dev;
+ for (pack = 0; pack < nr_of_packs(); pack++) {
+ urb->iso_frame_desc[pack].offset = subs->maxpacksize * (pack + u * nr_of_packs());
+ urb->iso_frame_desc[pack].length = subs->maxpacksize;
+ }
+ urb->transfer_buffer_length = subs->maxpacksize * nr_of_packs();
+ if ((err = usb_submit_urb(urb, GFP_KERNEL)) < 0) {
+ snd_printk (KERN_ERR "cannot usb_submit_urb() for urb %d, err = %d\n", u, err);
+ err = -EPIPE;
+ goto cleanup;
+ } else {
+ snd_printdd("%i\n", urb->start_frame);
+ if (u == 0)
+ usX2Y->wait_iso_frame = urb->start_frame;
+ }
+ urb->transfer_flags = 0;
+ } else {
+ atomic_set(&subs->state, state_STARTING1);
+ break;
+ }
+ }
+ }
+ }
+ err = 0;
+ wait_event(usX2Y->prepare_wait_queue, NULL == usX2Y->prepare_subs);
+ if (atomic_read(&subs->state) != state_PREPARED)
+ err = -EPIPE;
+
+ cleanup:
+ if (err) {
+ usX2Y_subs_startup_finish(usX2Y); // Call it now
+ usX2Y_clients_stop(usX2Y); // something is completely wroong > stop evrything
+ }
+ return err;
+}
+
+/*
+ * prepare callback
+ *
+ * set format and initialize urbs
+ */
+static int snd_usX2Y_usbpcm_prepare(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct snd_usX2Y_substream *subs = runtime->private_data;
+ struct usX2Ydev *usX2Y = subs->usX2Y;
+ struct snd_usX2Y_substream *capsubs = subs->usX2Y->subs[SNDRV_PCM_STREAM_CAPTURE];
+ int err = 0;
+ snd_printdd("snd_usX2Y_pcm_prepare(%p)\n", substream);
+
+ if (NULL == usX2Y->hwdep_pcm_shm) {
+ if (NULL == (usX2Y->hwdep_pcm_shm = snd_malloc_pages(sizeof(struct snd_usX2Y_hwdep_pcm_shm), GFP_KERNEL)))
+ return -ENOMEM;
+ memset(usX2Y->hwdep_pcm_shm, 0, sizeof(struct snd_usX2Y_hwdep_pcm_shm));
+ }
+
+ mutex_lock(&usX2Y->pcm_mutex);
+ usX2Y_subs_prepare(subs);
+// Start hardware streams
+// SyncStream first....
+ if (atomic_read(&capsubs->state) < state_PREPARED) {
+ if (usX2Y->format != runtime->format)
+ if ((err = usX2Y_format_set(usX2Y, runtime->format)) < 0)
+ goto up_prepare_mutex;
+ if (usX2Y->rate != runtime->rate)
+ if ((err = usX2Y_rate_set(usX2Y, runtime->rate)) < 0)
+ goto up_prepare_mutex;
+ snd_printdd("starting capture pipe for %s\n", subs == capsubs ?
+ "self" : "playpipe");
+ if (0 > (err = usX2Y_usbpcm_urbs_start(capsubs)))
+ goto up_prepare_mutex;
+ }
+
+ if (subs != capsubs) {
+ usX2Y->hwdep_pcm_shm->playback_iso_start = -1;
+ if (atomic_read(&subs->state) < state_PREPARED) {
+ while (usX2Y_iso_frames_per_buffer(runtime, usX2Y) >
+ usX2Y->hwdep_pcm_shm->captured_iso_frames) {
+ snd_printdd("Wait: iso_frames_per_buffer=%i,"
+ "captured_iso_frames=%i\n",
+ usX2Y_iso_frames_per_buffer(runtime, usX2Y),
+ usX2Y->hwdep_pcm_shm->captured_iso_frames);
+ if (msleep_interruptible(10)) {
+ err = -ERESTARTSYS;
+ goto up_prepare_mutex;
+ }
+ }
+ if (0 > (err = usX2Y_usbpcm_urbs_start(subs)))
+ goto up_prepare_mutex;
+ }
+ snd_printdd("Ready: iso_frames_per_buffer=%i,captured_iso_frames=%i\n",
+ usX2Y_iso_frames_per_buffer(runtime, usX2Y),
+ usX2Y->hwdep_pcm_shm->captured_iso_frames);
+ } else
+ usX2Y->hwdep_pcm_shm->capture_iso_start = -1;
+
+ up_prepare_mutex:
+ mutex_unlock(&usX2Y->pcm_mutex);
+ return err;
+}
+
+static struct snd_pcm_hardware snd_usX2Y_4c =
+{
+ .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_BLOCK_TRANSFER |
+ SNDRV_PCM_INFO_MMAP_VALID),
+ .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_3LE,
+ .rates = SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000,
+ .rate_min = 44100,
+ .rate_max = 48000,
+ .channels_min = 2,
+ .channels_max = 4,
+ .buffer_bytes_max = (2*128*1024),
+ .period_bytes_min = 64,
+ .period_bytes_max = (128*1024),
+ .periods_min = 2,
+ .periods_max = 1024,
+ .fifo_size = 0
+};
+
+
+
+static int snd_usX2Y_usbpcm_open(struct snd_pcm_substream *substream)
+{
+ struct snd_usX2Y_substream *subs = ((struct snd_usX2Y_substream **)
+ snd_pcm_substream_chip(substream))[substream->stream];
+ struct snd_pcm_runtime *runtime = substream->runtime;
+
+ if (!(subs->usX2Y->chip_status & USX2Y_STAT_CHIP_MMAP_PCM_URBS))
+ return -EBUSY;
+
+ runtime->hw = SNDRV_PCM_STREAM_PLAYBACK == substream->stream ? snd_usX2Y_2c :
+ (subs->usX2Y->subs[3] ? snd_usX2Y_4c : snd_usX2Y_2c);
+ runtime->private_data = subs;
+ subs->pcm_substream = substream;
+ snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_PERIOD_TIME, 1000, 200000);
+ return 0;
+}
+
+
+static int snd_usX2Y_usbpcm_close(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct snd_usX2Y_substream *subs = runtime->private_data;
+
+ subs->pcm_substream = NULL;
+ return 0;
+}
+
+
+static struct snd_pcm_ops snd_usX2Y_usbpcm_ops =
+{
+ .open = snd_usX2Y_usbpcm_open,
+ .close = snd_usX2Y_usbpcm_close,
+ .ioctl = snd_pcm_lib_ioctl,
+ .hw_params = snd_usX2Y_pcm_hw_params,
+ .hw_free = snd_usX2Y_usbpcm_hw_free,
+ .prepare = snd_usX2Y_usbpcm_prepare,
+ .trigger = snd_usX2Y_pcm_trigger,
+ .pointer = snd_usX2Y_pcm_pointer,
+};
+
+
+static int usX2Y_pcms_busy_check(struct snd_card *card)
+{
+ struct usX2Ydev *dev = usX2Y(card);
+ int i;
+
+ for (i = 0; i < dev->pcm_devs * 2; i++) {
+ struct snd_usX2Y_substream *subs = dev->subs[i];
+ if (subs && subs->pcm_substream &&
+ SUBSTREAM_BUSY(subs->pcm_substream))
+ return -EBUSY;
+ }
+ return 0;
+}
+
+static int snd_usX2Y_hwdep_pcm_open(struct snd_hwdep *hw, struct file *file)
+{
+ struct snd_card *card = hw->card;
+ int err;
+
+ mutex_lock(&usX2Y(card)->pcm_mutex);
+ err = usX2Y_pcms_busy_check(card);
+ if (!err)
+ usX2Y(card)->chip_status |= USX2Y_STAT_CHIP_MMAP_PCM_URBS;
+ mutex_unlock(&usX2Y(card)->pcm_mutex);
+ return err;
+}
+
+
+static int snd_usX2Y_hwdep_pcm_release(struct snd_hwdep *hw, struct file *file)
+{
+ struct snd_card *card = hw->card;
+ int err;
+
+ mutex_lock(&usX2Y(card)->pcm_mutex);
+ err = usX2Y_pcms_busy_check(card);
+ if (!err)
+ usX2Y(hw->card)->chip_status &= ~USX2Y_STAT_CHIP_MMAP_PCM_URBS;
+ mutex_unlock(&usX2Y(card)->pcm_mutex);
+ return err;
+}
+
+
+static void snd_usX2Y_hwdep_pcm_vm_open(struct vm_area_struct *area)
+{
+}
+
+
+static void snd_usX2Y_hwdep_pcm_vm_close(struct vm_area_struct *area)
+{
+}
+
+
+static int snd_usX2Y_hwdep_pcm_vm_fault(struct vm_area_struct *area,
+ struct vm_fault *vmf)
+{
+ unsigned long offset;
+ void *vaddr;
+
+ offset = vmf->pgoff << PAGE_SHIFT;
+ vaddr = (char*)((struct usX2Ydev *)area->vm_private_data)->hwdep_pcm_shm + offset;
+ vmf->page = virt_to_page(vaddr);
+ get_page(vmf->page);
+ return 0;
+}
+
+
+static const struct vm_operations_struct snd_usX2Y_hwdep_pcm_vm_ops = {
+ .open = snd_usX2Y_hwdep_pcm_vm_open,
+ .close = snd_usX2Y_hwdep_pcm_vm_close,
+ .fault = snd_usX2Y_hwdep_pcm_vm_fault,
+};
+
+
+static int snd_usX2Y_hwdep_pcm_mmap(struct snd_hwdep * hw, struct file *filp, struct vm_area_struct *area)
+{
+ unsigned long size = (unsigned long)(area->vm_end - area->vm_start);
+ struct usX2Ydev *usX2Y = hw->private_data;
+
+ if (!(usX2Y->chip_status & USX2Y_STAT_CHIP_INIT))
+ return -EBUSY;
+
+ /* if userspace tries to mmap beyond end of our buffer, fail */
+ if (size > PAGE_ALIGN(sizeof(struct snd_usX2Y_hwdep_pcm_shm))) {
+ snd_printd("%lu > %lu\n", size, (unsigned long)sizeof(struct snd_usX2Y_hwdep_pcm_shm));
+ return -EINVAL;
+ }
+
+ if (!usX2Y->hwdep_pcm_shm) {
+ return -ENODEV;
+ }
+ area->vm_ops = &snd_usX2Y_hwdep_pcm_vm_ops;
+ area->vm_flags |= VM_DONTEXPAND | VM_DONTDUMP;
+ area->vm_private_data = hw->private_data;
+ return 0;
+}
+
+
+static void snd_usX2Y_hwdep_pcm_private_free(struct snd_hwdep *hwdep)
+{
+ struct usX2Ydev *usX2Y = hwdep->private_data;
+ if (NULL != usX2Y->hwdep_pcm_shm)
+ snd_free_pages(usX2Y->hwdep_pcm_shm, sizeof(struct snd_usX2Y_hwdep_pcm_shm));
+}
+
+
+int usX2Y_hwdep_pcm_new(struct snd_card *card)
+{
+ int err;
+ struct snd_hwdep *hw;
+ struct snd_pcm *pcm;
+ struct usb_device *dev = usX2Y(card)->dev;
+ if (1 != nr_of_packs())
+ return 0;
+
+ if ((err = snd_hwdep_new(card, SND_USX2Y_USBPCM_ID, 1, &hw)) < 0)
+ return err;
+
+ hw->iface = SNDRV_HWDEP_IFACE_USX2Y_PCM;
+ hw->private_data = usX2Y(card);
+ hw->private_free = snd_usX2Y_hwdep_pcm_private_free;
+ hw->ops.open = snd_usX2Y_hwdep_pcm_open;
+ hw->ops.release = snd_usX2Y_hwdep_pcm_release;
+ hw->ops.mmap = snd_usX2Y_hwdep_pcm_mmap;
+ hw->exclusive = 1;
+ sprintf(hw->name, "/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->info_flags = 0;
+
+ sprintf(pcm->name, NAME_ALLCAPS" hwdep Audio");
+ if (0 > (err = snd_pcm_lib_preallocate_pages(pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream,
+ SNDRV_DMA_TYPE_CONTINUOUS,
+ snd_dma_continuous_data(GFP_KERNEL),
+ 64*1024, 128*1024)) ||
+ 0 > (err = snd_pcm_lib_preallocate_pages(pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream,
+ SNDRV_DMA_TYPE_CONTINUOUS,
+ snd_dma_continuous_data(GFP_KERNEL),
+ 64*1024, 128*1024))) {
+ return err;
+ }
+
+
+ return 0;
+}
+
+#else
+
+int usX2Y_hwdep_pcm_new(struct snd_card *card)
+{
+ return 0;
+}
+
+#endif
diff --git a/sound/usb/usx2y/usx2yhwdeppcm.h b/sound/usb/usx2y/usx2yhwdeppcm.h
new file mode 100644
index 000000000..9c4fb84b2
--- /dev/null
+++ b/sound/usb/usx2y/usx2yhwdeppcm.h
@@ -0,0 +1,22 @@
+#define MAXPACK 50
+#define MAXBUFFERMS 100
+#define MAXSTRIDE 3
+
+#define SSS (((MAXPACK*MAXBUFFERMS*MAXSTRIDE + 4096) / 4096) * 4096)
+struct snd_usX2Y_hwdep_pcm_shm {
+ char playback[SSS];
+ char capture0x8[SSS];
+ char capture0xA[SSS];
+ volatile int playback_iso_head;
+ int playback_iso_start;
+ struct {
+ int frame,
+ offset,
+ length;
+ } captured_iso[128];
+ volatile int captured_iso_head;
+ volatile unsigned captured_iso_frames;
+ int capture_iso_start;
+};
+
+int usX2Y_hwdep_pcm_new(struct snd_card *card);