diff options
Diffstat (limited to 'sound/usb')
85 files changed, 24469 insertions, 8582 deletions
diff --git a/sound/usb/6fire/Makefile b/sound/usb/6fire/Makefile new file mode 100644 index 00000000000..dfce6ec5351 --- /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 00000000000..dcddfc354ba --- /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(®ister_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(®ister_mutex); + return 0; + } else if (!devices[i] && regidx < 0) + regidx = i; + } + if (regidx < 0) { + mutex_unlock(®ister_mutex); + dev_err(&intf->dev, "too many cards registered.\n"); + return -ENODEV; + } + devices[regidx] = device; + mutex_unlock(®ister_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(®ister_mutex); + devices[chip->regidx] = NULL; + chips[chip->regidx] = NULL; + mutex_unlock(®ister_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 00000000000..bde02d105a5 --- /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 00000000000..161215d78d9 --- /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 00000000000..780d5ed8e5d --- /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 00000000000..b6eb03ed1c2 --- /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 00000000000..184e3987ac2 --- /dev/null +++ b/sound/usb/6fire/control.c @@ -0,0 +1,635 @@ +/* + * 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 char *opt_coax_texts[2] = { "Optical", "Coax" }; +static char *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) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; + uinfo->count = 1; + uinfo->value.enumerated.items = 2; + if (uinfo->value.enumerated.item > 1) + uinfo->value.enumerated.item = 1; + strcpy(uinfo->value.enumerated.name, + line_phono_texts[uinfo->value.enumerated.item]); + return 0; +} + +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) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; + uinfo->count = 1; + uinfo->value.enumerated.items = 2; + if (uinfo->value.enumerated.item > 1) + uinfo->value.enumerated.item = 1; + strcpy(uinfo->value.enumerated.name, + opt_coax_texts[uinfo->value.enumerated.item]); + return 0; +} + +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 00000000000..5a40ba14348 --- /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 00000000000..3b02e54b8f6 --- /dev/null +++ b/sound/usb/6fire/firmware.c @@ -0,0 +1,421 @@ +/* + * Linux driver for TerraTec DMX 6Fire USB + * + * Firmware loader + * + * Author: Torsten Schenk <torsten.schenk@zoho.com> + * Created: Jan 01, 2011 + * Copyright: (C) Torsten Schenk + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#include <linux/firmware.h> +#include <linux/module.h> +#include <linux/bitrev.h> +#include <linux/kernel.h> + +#include "firmware.h" +#include "chip.h" + +MODULE_FIRMWARE("6fire/dmx6firel2.ihx"); +MODULE_FIRMWARE("6fire/dmx6fireap.ihx"); +MODULE_FIRMWARE("6fire/dmx6firecf.bin"); + +enum { + FPGA_BUFSIZE = 512, FPGA_EP = 2 +}; + +/* + * wMaxPacketSize of pcm endpoints. + * keep synced with rates_in_packet_size and rates_out_packet_size in pcm.c + * fpp: frames per isopacket + * + * CAUTION: keep sizeof <= buffer[] in usb6fire_fw_init + */ +static const u8 ep_w_max_packet_size[] = { + 0xe4, 0x00, 0xe4, 0x00, /* alt 1: 228 EP2 and EP6 (7 fpp) */ + 0xa4, 0x01, 0xa4, 0x01, /* alt 2: 420 EP2 and EP6 (13 fpp)*/ + 0x94, 0x01, 0x5c, 0x02 /* alt 3: 404 EP2 and 604 EP6 (25 fpp) */ +}; + +static const u8 known_fw_versions[][2] = { + { 0x03, 0x01 } +}; + +struct ihex_record { + u16 address; + u8 len; + u8 data[256]; + char error; /* true if an error occurred parsing this record */ + + u8 max_len; /* maximum record length in whole ihex */ + + /* private */ + const char *txt_data; + unsigned int txt_length; + unsigned int txt_offset; /* current position in txt_data */ +}; + +static u8 usb6fire_fw_ihex_hex(const u8 *data, u8 *crc) +{ + u8 val = 0; + int hval; + + hval = hex_to_bin(data[0]); + if (hval >= 0) + val |= (hval << 4); + + hval = hex_to_bin(data[1]); + if (hval >= 0) + val |= hval; + + *crc += val; + return val; +} + +/* + * returns true if record is available, false otherwise. + * iff an error occurred, false will be returned and record->error will be true. + */ +static bool usb6fire_fw_ihex_next_record(struct ihex_record *record) +{ + u8 crc = 0; + u8 type; + int i; + + record->error = false; + + /* find begin of record (marked by a colon) */ + while (record->txt_offset < record->txt_length + && record->txt_data[record->txt_offset] != ':') + record->txt_offset++; + if (record->txt_offset == record->txt_length) + return false; + + /* number of characters needed for len, addr and type entries */ + record->txt_offset++; + if (record->txt_offset + 8 > record->txt_length) { + record->error = true; + return false; + } + + record->len = usb6fire_fw_ihex_hex(record->txt_data + + record->txt_offset, &crc); + record->txt_offset += 2; + record->address = usb6fire_fw_ihex_hex(record->txt_data + + record->txt_offset, &crc) << 8; + record->txt_offset += 2; + record->address |= usb6fire_fw_ihex_hex(record->txt_data + + record->txt_offset, &crc); + record->txt_offset += 2; + type = usb6fire_fw_ihex_hex(record->txt_data + + record->txt_offset, &crc); + record->txt_offset += 2; + + /* number of characters needed for data and crc entries */ + if (record->txt_offset + 2 * (record->len + 1) > record->txt_length) { + record->error = true; + return false; + } + for (i = 0; i < record->len; i++) { + record->data[i] = usb6fire_fw_ihex_hex(record->txt_data + + record->txt_offset, &crc); + record->txt_offset += 2; + } + usb6fire_fw_ihex_hex(record->txt_data + record->txt_offset, &crc); + if (crc) { + record->error = true; + return false; + } + + if (type == 1 || !record->len) /* eof */ + return false; + else if (type == 0) + return true; + else { + record->error = true; + return false; + } +} + +static int usb6fire_fw_ihex_init(const struct firmware *fw, + struct ihex_record *record) +{ + record->txt_data = fw->data; + record->txt_length = fw->size; + record->txt_offset = 0; + record->max_len = 0; + /* read all records, if loop ends, record->error indicates, + * whether ihex is valid. */ + while (usb6fire_fw_ihex_next_record(record)) + record->max_len = max(record->len, record->max_len); + if (record->error) + return -EINVAL; + record->txt_offset = 0; + return 0; +} + +static int usb6fire_fw_ezusb_write(struct usb_device *device, + int type, int value, char *data, int len) +{ + int ret; + + ret = usb_control_msg(device, usb_sndctrlpipe(device, 0), type, + USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + value, 0, data, len, HZ); + if (ret < 0) + return ret; + else if (ret != len) + return -EIO; + return 0; +} + +static int usb6fire_fw_ezusb_read(struct usb_device *device, + int type, int value, char *data, int len) +{ + int ret = usb_control_msg(device, usb_rcvctrlpipe(device, 0), type, + USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE, value, + 0, data, len, HZ); + if (ret < 0) + return ret; + else if (ret != len) + return -EIO; + return 0; +} + +static int usb6fire_fw_fpga_write(struct usb_device *device, + char *data, int len) +{ + int actual_len; + int ret; + + ret = usb_bulk_msg(device, usb_sndbulkpipe(device, FPGA_EP), data, len, + &actual_len, HZ); + if (ret < 0) + return ret; + else if (actual_len != len) + return -EIO; + return 0; +} + +static int usb6fire_fw_ezusb_upload( + struct usb_interface *intf, const char *fwname, + unsigned int postaddr, u8 *postdata, unsigned int postlen) +{ + int ret; + u8 data; + struct usb_device *device = interface_to_usbdev(intf); + const struct firmware *fw = NULL; + struct ihex_record *rec = kmalloc(sizeof(struct ihex_record), + GFP_KERNEL); + + if (!rec) + return -ENOMEM; + + ret = request_firmware(&fw, fwname, &device->dev); + if (ret < 0) { + kfree(rec); + dev_err(&intf->dev, + "error requesting ezusb firmware %s.\n", fwname); + return ret; + } + ret = usb6fire_fw_ihex_init(fw, rec); + if (ret < 0) { + kfree(rec); + release_firmware(fw); + dev_err(&intf->dev, + "error validating ezusb firmware %s.\n", fwname); + return ret; + } + /* upload firmware image */ + data = 0x01; /* stop ezusb cpu */ + ret = usb6fire_fw_ezusb_write(device, 0xa0, 0xe600, &data, 1); + if (ret < 0) { + kfree(rec); + release_firmware(fw); + dev_err(&intf->dev, + "unable to upload ezusb firmware %s: begin message.\n", + fwname); + return ret; + } + + while (usb6fire_fw_ihex_next_record(rec)) { /* write firmware */ + ret = usb6fire_fw_ezusb_write(device, 0xa0, rec->address, + rec->data, rec->len); + if (ret < 0) { + kfree(rec); + release_firmware(fw); + dev_err(&intf->dev, + "unable to upload ezusb firmware %s: data urb.\n", + fwname); + return ret; + } + } + + release_firmware(fw); + kfree(rec); + if (postdata) { /* write data after firmware has been uploaded */ + ret = usb6fire_fw_ezusb_write(device, 0xa0, postaddr, + postdata, postlen); + if (ret < 0) { + dev_err(&intf->dev, + "unable to upload ezusb firmware %s: post urb.\n", + fwname); + return ret; + } + } + + data = 0x00; /* resume ezusb cpu */ + ret = usb6fire_fw_ezusb_write(device, 0xa0, 0xe600, &data, 1); + if (ret < 0) { + dev_err(&intf->dev, + "unable to upload ezusb firmware %s: end message.\n", + fwname); + return ret; + } + return 0; +} + +static int usb6fire_fw_fpga_upload( + struct usb_interface *intf, const char *fwname) +{ + int ret; + int i; + struct usb_device *device = interface_to_usbdev(intf); + u8 *buffer = kmalloc(FPGA_BUFSIZE, GFP_KERNEL); + const char *c; + const char *end; + const struct firmware *fw; + + if (!buffer) + return -ENOMEM; + + ret = request_firmware(&fw, fwname, &device->dev); + if (ret < 0) { + dev_err(&intf->dev, "unable to get fpga firmware %s.\n", + fwname); + kfree(buffer); + return -EIO; + } + + c = fw->data; + end = fw->data + fw->size; + + ret = usb6fire_fw_ezusb_write(device, 8, 0, NULL, 0); + if (ret < 0) { + kfree(buffer); + release_firmware(fw); + dev_err(&intf->dev, + "unable to upload fpga firmware: begin urb.\n"); + return ret; + } + + while (c != end) { + for (i = 0; c != end && i < FPGA_BUFSIZE; i++, c++) + buffer[i] = byte_rev_table[(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, + "6fire/dmx6firel2.ihx", 0, NULL, 0); + if (ret < 0) + return ret; + return FW_NOT_READY; + } + /* do we need fpga firmware and application ezusb firmware? */ + else if (buffer[3] == 0x02) { + ret = usb6fire_fw_check(intf, buffer + 4); + if (ret < 0) + return ret; + ret = usb6fire_fw_fpga_upload(intf, "6fire/dmx6firecf.bin"); + if (ret < 0) + return ret; + memcpy(buffer, ep_w_max_packet_size, + sizeof(ep_w_max_packet_size)); + ret = usb6fire_fw_ezusb_upload(intf, "6fire/dmx6fireap.ihx", + 0x0003, buffer, sizeof(ep_w_max_packet_size)); + if (ret < 0) + return ret; + return FW_NOT_READY; + } + /* all fw loaded? */ + else if (buffer[3] == 0x03) + return usb6fire_fw_check(intf, buffer + 4); + /* unknown data? */ + else { + dev_err(&intf->dev, + "unknown device firmware state received from device: "); + for (i = 0; i < 8; i++) + printk(KERN_CONT "%02x ", buffer[i]); + printk(KERN_CONT "\n"); + return -EIO; + } + return 0; +} + diff --git a/sound/usb/6fire/firmware.h b/sound/usb/6fire/firmware.h new file mode 100644 index 00000000000..c109c4f75ab --- /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 00000000000..3d410969553 --- /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 00000000000..84851b9f555 --- /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 00000000000..ba40489b2de --- /dev/null +++ b/sound/usb/6fire/pcm.c @@ -0,0 +1,717 @@ +/* + * 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; + unsigned long flags; + int i; + + if (rt) { + rt->panic = true; + + if (rt->playback.instance) { + snd_pcm_stream_lock_irqsave(rt->playback.instance, flags); + snd_pcm_stop(rt->playback.instance, + SNDRV_PCM_STATE_XRUN); + snd_pcm_stream_unlock_irqrestore(rt->playback.instance, flags); + } + + if (rt->capture.instance) { + snd_pcm_stream_lock_irqsave(rt->capture.instance, flags); + snd_pcm_stop(rt->capture.instance, + SNDRV_PCM_STATE_XRUN); + snd_pcm_stream_unlock_irqrestore(rt->capture.instance, flags); + } + + 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 00000000000..f5779d6182c --- /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 index 9351b8a765b..d393153c474 100644 --- a/sound/usb/Kconfig +++ b/sound/usb/Kconfig @@ -1,14 +1,20 @@ # ALSA USB drivers -menu "USB devices" - depends on SND!=n && USB!=n +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" - depends on SND && USB 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. @@ -16,9 +22,20 @@ config SND_USB_AUDIO 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 SND && USB && (X86 || PPC || ALPHA) + depends on X86 || PPC || ALPHA select SND_HWDEP select SND_RAWMIDI select SND_PCM @@ -31,7 +48,6 @@ config SND_USB_USX2Y config SND_USB_CAIAQ tristate "Native Instruments USB audio devices" - depends on SND && USB select SND_HWDEP select SND_RAWMIDI select SND_PCM @@ -44,7 +60,15 @@ config SND_USB_CAIAQ * 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. @@ -62,6 +86,79 @@ config SND_USB_CAIAQ_INPUT * 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. -endmenu +endif # SND_USB diff --git a/sound/usb/Makefile b/sound/usb/Makefile index aa252ef2ebf..2b92f0dcbc4 100644 --- a/sound/usb/Makefile +++ b/sound/usb/Makefile @@ -2,11 +2,25 @@ # Makefile for ALSA # -snd-usb-audio-objs := usbaudio.o usbmixer.o -snd-usb-lib-objs := usbmidi.o +snd-usb-audio-objs := card.o \ + clock.o \ + endpoint.o \ + format.o \ + helper.o \ + mixer.o \ + mixer_quirks.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-usb-lib.o -obj-$(CONFIG_SND_USB_USX2Y) += snd-usb-lib.o +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) += usx2y/ caiaq/ +obj-$(CONFIG_SND) += misc/ usx2y/ caiaq/ 6fire/ hiface/ bcd2000/ diff --git a/sound/usb/bcd2000/Makefile b/sound/usb/bcd2000/Makefile new file mode 100644 index 00000000000..f09ccc0af6f --- /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 00000000000..820d6ca8c45 --- /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 index 23dadd5a11c..388999653aa 100644 --- a/sound/usb/caiaq/Makefile +++ b/sound/usb/caiaq/Makefile @@ -1,4 +1,4 @@ -snd-usb-caiaq-y := caiaq-device.o caiaq-audio.o caiaq-midi.o caiaq-control.o -snd-usb-caiaq-$(CONFIG_SND_USB_CAIAQ_INPUT) += caiaq-input.o +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 00000000000..7103b0908d1 --- /dev/null +++ b/sound/usb/caiaq/audio.c @@ -0,0 +1,901 @@ +/* + * 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; + } + + 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 00000000000..bdf155300a8 --- /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/caiaq-audio.c b/sound/usb/caiaq/caiaq-audio.c deleted file mode 100644 index 9cc4cd8283f..00000000000 --- a/sound/usb/caiaq/caiaq-audio.c +++ /dev/null @@ -1,705 +0,0 @@ -/* - * Copyright (c) 2006,2007 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/init.h> -#include <linux/module.h> -#include <linux/moduleparam.h> -#include <linux/interrupt.h> -#include <linux/usb.h> -#include <linux/spinlock.h> -#include <sound/core.h> -#include <sound/initval.h> -#include <sound/pcm.h> -#include <sound/rawmidi.h> -#include <linux/input.h> - -#include "caiaq-device.h" -#include "caiaq-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 ENDPOINT_CAPTURE 2 -#define ENDPOINT_PLAYBACK 6 - -#define MAKE_CHECKBYTE(dev,stream,i) \ - (stream << 1) | (~(i / (dev->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 *dev, - struct snd_pcm_substream *sub) -{ - if (sub->stream == SNDRV_PCM_STREAM_PLAYBACK) - dev->sub_playback[sub->number] = sub; - else - dev->sub_capture[sub->number] = sub; -} - -static void -deactivate_substream(struct snd_usb_caiaqdev *dev, - struct snd_pcm_substream *sub) -{ - if (sub->stream == SNDRV_PCM_STREAM_PLAYBACK) - dev->sub_playback[sub->number] = NULL; - else - dev->sub_capture[sub->number] = NULL; -} - -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 *dev) -{ - int i, ret; - - debug("stream_start(%p)\n", dev); - spin_lock_irq(&dev->spinlock); - if (dev->streaming) { - spin_unlock_irq(&dev->spinlock); - return -EINVAL; - } - - dev->input_panic = 0; - dev->output_panic = 0; - dev->first_packet = 1; - dev->streaming = 1; - - for (i = 0; i < N_URBS; i++) { - ret = usb_submit_urb(dev->data_urbs_in[i], GFP_ATOMIC); - if (ret) { - log("unable to trigger initial read #%d! (ret = %d)\n", - i, ret); - dev->streaming = 0; - spin_unlock_irq(&dev->spinlock); - return -EPIPE; - } - } - - spin_unlock_irq(&dev->spinlock); - return 0; -} - -static void stream_stop(struct snd_usb_caiaqdev *dev) -{ - int i; - - debug("stream_stop(%p)\n", dev); - if (!dev->streaming) - return; - - dev->streaming = 0; - for (i = 0; i < N_URBS; i++) { - usb_unlink_urb(dev->data_urbs_in[i]); - usb_unlink_urb(dev->data_urbs_out[i]); - } -} - -static int snd_usb_caiaq_substream_open(struct snd_pcm_substream *substream) -{ - struct snd_usb_caiaqdev *dev = snd_pcm_substream_chip(substream); - debug("snd_usb_caiaq_substream_open(%p)\n", substream); - substream->runtime->hw = dev->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 *dev = snd_pcm_substream_chip(substream); - - debug("snd_usb_caiaq_substream_close(%p)\n", substream); - if (all_substreams_zero(dev->sub_playback) && - all_substreams_zero(dev->sub_capture)) { - /* when the last client has stopped streaming, - * all sample rates are allowed again */ - stream_stop(dev); - dev->pcm_info.rates = dev->samplerates; - } - - return 0; -} - -static int snd_usb_caiaq_pcm_hw_params(struct snd_pcm_substream *sub, - struct snd_pcm_hw_params *hw_params) -{ - debug("snd_usb_caiaq_pcm_hw_params(%p)\n", sub); - return snd_pcm_lib_malloc_pages(sub, params_buffer_bytes(hw_params)); -} - -static int snd_usb_caiaq_pcm_hw_free(struct snd_pcm_substream *sub) -{ - struct snd_usb_caiaqdev *dev = snd_pcm_substream_chip(sub); - debug("snd_usb_caiaq_pcm_hw_free(%p)\n", sub); - spin_lock_irq(&dev->spinlock); - deactivate_substream(dev, sub); - spin_unlock_irq(&dev->spinlock); - return snd_pcm_lib_free_pages(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 *dev = snd_pcm_substream_chip(substream); - struct snd_pcm_runtime *runtime = substream->runtime; - - debug("snd_usb_caiaq_pcm_prepare(%p)\n", substream); - - if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) - dev->audio_out_buf_pos[index] = BYTES_PER_SAMPLE + 1; - else - dev->audio_in_buf_pos[index] = 0; - - if (dev->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]) - dev->pcm_info.rates = 1 << i; - - snd_pcm_limit_hw_rates(runtime); - - bytes_per_sample = BYTES_PER_SAMPLE; - if (dev->spec.data_alignment == 2) - bytes_per_sample++; - - bpp = ((runtime->rate / 8000) + CLOCK_DRIFT_TOLERANCE) - * bytes_per_sample * CHANNELS_PER_STREAM * dev->n_streams; - - ret = snd_usb_caiaq_set_audio_params(dev, runtime->rate, - runtime->sample_bits, bpp); - if (ret) - return ret; - - ret = stream_start(dev); - if (ret) - return ret; - - dev->output_running = 0; - wait_event_timeout(dev->prepare_wait_queue, dev->output_running, HZ); - if (!dev->output_running) { - stream_stop(dev); - return -EPIPE; - } - - return 0; -} - -static int snd_usb_caiaq_pcm_trigger(struct snd_pcm_substream *sub, int cmd) -{ - struct snd_usb_caiaqdev *dev = snd_pcm_substream_chip(sub); - - switch (cmd) { - case SNDRV_PCM_TRIGGER_START: - case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: - spin_lock(&dev->spinlock); - activate_substream(dev, sub); - spin_unlock(&dev->spinlock); - break; - case SNDRV_PCM_TRIGGER_STOP: - case SNDRV_PCM_TRIGGER_PAUSE_PUSH: - spin_lock(&dev->spinlock); - deactivate_substream(dev, sub); - spin_unlock(&dev->spinlock); - 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 *dev = snd_pcm_substream_chip(sub); - - if (dev->input_panic || dev->output_panic) - return SNDRV_PCM_POS_XRUN; - - if (sub->stream == SNDRV_PCM_STREAM_PLAYBACK) - return bytes_to_frames(sub->runtime, - dev->audio_out_buf_pos[index]); - else - return bytes_to_frames(sub->runtime, - dev->audio_in_buf_pos[index]); -} - -/* 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 -}; - -static void check_for_elapsed_periods(struct snd_usb_caiaqdev *dev, - struct snd_pcm_substream **subs) -{ - int stream, pb, *cnt; - struct snd_pcm_substream *sub; - - for (stream = 0; stream < dev->n_streams; stream++) { - sub = subs[stream]; - if (!sub) - continue; - - pb = frames_to_bytes(sub->runtime, - sub->runtime->period_size); - cnt = (sub->stream == SNDRV_PCM_STREAM_PLAYBACK) ? - &dev->period_out_count[stream] : - &dev->period_in_count[stream]; - - if (*cnt >= pb) { - snd_pcm_period_elapsed(sub); - *cnt %= pb; - } - } -} - -static void read_in_urb_mode0(struct snd_usb_caiaqdev *dev, - 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(dev->sub_capture)) - return; - - spin_lock(&dev->spinlock); - - for (i = 0; i < iso->actual_length;) { - for (stream = 0; stream < dev->n_streams; stream++, i++) { - sub = dev->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[dev->audio_in_buf_pos[stream]++] - = usb_buf[i]; - dev->period_in_count[stream]++; - if (dev->audio_in_buf_pos[stream] == sz) - dev->audio_in_buf_pos[stream] = 0; - } - } - } - - spin_unlock(&dev->spinlock); -} - -static void read_in_urb_mode2(struct snd_usb_caiaqdev *dev, - 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; - - spin_lock(&dev->spinlock); - - for (i = 0; i < iso->actual_length;) { - if (i % (dev->n_streams * BYTES_PER_SAMPLE_USB) == 0) { - for (stream = 0; - stream < dev->n_streams; - stream++, i++) { - if (dev->first_packet) - continue; - - check_byte = MAKE_CHECKBYTE(dev, stream, i); - - if ((usb_buf[i] & 0x3f) != check_byte) - dev->input_panic = 1; - - if (usb_buf[i] & 0x80) - dev->output_panic = 1; - } - } - dev->first_packet = 0; - - for (stream = 0; stream < dev->n_streams; stream++, i++) { - sub = dev->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[dev->audio_in_buf_pos[stream]++] = - usb_buf[i]; - dev->period_in_count[stream]++; - if (dev->audio_in_buf_pos[stream] == sz) - dev->audio_in_buf_pos[stream] = 0; - } - } - } - - spin_unlock(&dev->spinlock); -} - -static void read_in_urb(struct snd_usb_caiaqdev *dev, - const struct urb *urb, - const struct usb_iso_packet_descriptor *iso) -{ - if (!dev->streaming) - return; - - switch (dev->spec.data_alignment) { - case 0: - read_in_urb_mode0(dev, urb, iso); - break; - case 2: - read_in_urb_mode2(dev, urb, iso); - break; - } - - if (dev->input_panic || dev->output_panic) { - debug("streaming error detected %s %s\n", - dev->input_panic ? "(input)" : "", - dev->output_panic ? "(output)" : ""); - } - - check_for_elapsed_periods(dev, dev->sub_capture); -} - -static void fill_out_urb(struct snd_usb_caiaqdev *dev, - 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; - - spin_lock(&dev->spinlock); - - for (i = 0; i < iso->length;) { - for (stream = 0; stream < dev->n_streams; stream++, i++) { - sub = dev->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[dev->audio_out_buf_pos[stream]]; - dev->period_out_count[stream]++; - dev->audio_out_buf_pos[stream]++; - if (dev->audio_out_buf_pos[stream] == sz) - dev->audio_out_buf_pos[stream] = 0; - } else - usb_buf[i] = 0; - } - - /* fill in the check bytes */ - if (dev->spec.data_alignment == 2 && - i % (dev->n_streams * BYTES_PER_SAMPLE_USB) == - (dev->n_streams * CHANNELS_PER_STREAM)) - for (stream = 0; stream < dev->n_streams; stream++, i++) - usb_buf[i] = MAKE_CHECKBYTE(dev, stream, i); - } - - spin_unlock(&dev->spinlock); - check_for_elapsed_periods(dev, dev->sub_playback); -} - -static void read_completed(struct urb *urb) -{ - struct snd_usb_caiaq_cb_info *info = urb->context; - struct snd_usb_caiaqdev *dev; - struct urb *out; - int frame, len, send_it = 0, outframe = 0; - - if (urb->status || !info) - return; - - dev = info->dev; - if (!dev->streaming) - return; - - out = dev->data_urbs_out[info->index]; - - /* 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 = BYTES_PER_FRAME * frame; - - if (len > 0) { - fill_out_urb(dev, out, &out->iso_frame_desc[outframe]); - read_in_urb(dev, urb, &urb->iso_frame_desc[frame]); - send_it = 1; - } - - outframe++; - } - - if (send_it) { - out->number_of_packets = FRAMES_PER_URB; - out->transfer_flags = URB_ISO_ASAP; - usb_submit_urb(out, GFP_ATOMIC); - } - - /* 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; - urb->transfer_flags = URB_ISO_ASAP; - 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 *dev = info->dev; - - if (!dev->output_running) { - dev->output_running = 1; - wake_up(&dev->prepare_wait_queue); - } -} - -static struct urb **alloc_urbs(struct snd_usb_caiaqdev *dev, int dir, int *ret) -{ - int i, frame; - struct urb **urbs; - struct usb_device *usb_dev = dev->chip.dev; - unsigned int pipe; - - pipe = (dir == SNDRV_PCM_STREAM_PLAYBACK) ? - usb_sndisocpipe(usb_dev, ENDPOINT_PLAYBACK) : - usb_rcvisocpipe(usb_dev, ENDPOINT_CAPTURE); - - urbs = kmalloc(N_URBS * sizeof(*urbs), GFP_KERNEL); - if (!urbs) { - log("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]) { - log("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) { - log("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 = &dev->data_cb_info[i]; - urbs[i]->interval = 1; - urbs[i]->transfer_flags = URB_ISO_ASAP; - 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 *dev) -{ - int i, ret; - - dev->n_audio_in = max(dev->spec.num_analog_audio_in, - dev->spec.num_digital_audio_in) / - CHANNELS_PER_STREAM; - dev->n_audio_out = max(dev->spec.num_analog_audio_out, - dev->spec.num_digital_audio_out) / - CHANNELS_PER_STREAM; - dev->n_streams = max(dev->n_audio_in, dev->n_audio_out); - - debug("dev->n_audio_in = %d\n", dev->n_audio_in); - debug("dev->n_audio_out = %d\n", dev->n_audio_out); - debug("dev->n_streams = %d\n", dev->n_streams); - - if (dev->n_streams > MAX_STREAMS) { - log("unable to initialize device, too many streams.\n"); - return -EINVAL; - } - - ret = snd_pcm_new(dev->chip.card, dev->product_name, 0, - dev->n_audio_out, dev->n_audio_in, &dev->pcm); - - if (ret < 0) { - log("snd_pcm_new() returned %d\n", ret); - return ret; - } - - dev->pcm->private_data = dev; - strcpy(dev->pcm->name, dev->product_name); - - memset(dev->sub_playback, 0, sizeof(dev->sub_playback)); - memset(dev->sub_capture, 0, sizeof(dev->sub_capture)); - - memcpy(&dev->pcm_info, &snd_usb_caiaq_pcm_hardware, - sizeof(snd_usb_caiaq_pcm_hardware)); - - /* setup samplerates */ - dev->samplerates = dev->pcm_info.rates; - switch (dev->chip.usb_id) { - case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_AK1): - case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_RIGKONTROL3): - dev->samplerates |= SNDRV_PCM_RATE_88200; - dev->samplerates |= SNDRV_PCM_RATE_192000; - break; - case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_AUDIO8DJ): - dev->samplerates |= SNDRV_PCM_RATE_88200; - break; - } - - snd_pcm_set_ops(dev->pcm, SNDRV_PCM_STREAM_PLAYBACK, - &snd_usb_caiaq_ops); - snd_pcm_set_ops(dev->pcm, SNDRV_PCM_STREAM_CAPTURE, - &snd_usb_caiaq_ops); - - snd_pcm_lib_preallocate_pages_for_all(dev->pcm, - SNDRV_DMA_TYPE_CONTINUOUS, - snd_dma_continuous_data(GFP_KERNEL), - MAX_BUFFER_SIZE, MAX_BUFFER_SIZE); - - dev->data_cb_info = - kmalloc(sizeof(struct snd_usb_caiaq_cb_info) * N_URBS, - GFP_KERNEL); - - if (!dev->data_cb_info) - return -ENOMEM; - - for (i = 0; i < N_URBS; i++) { - dev->data_cb_info[i].dev = dev; - dev->data_cb_info[i].index = i; - } - - dev->data_urbs_in = alloc_urbs(dev, SNDRV_PCM_STREAM_CAPTURE, &ret); - if (ret < 0) { - kfree(dev->data_cb_info); - free_urbs(dev->data_urbs_in); - return ret; - } - - dev->data_urbs_out = alloc_urbs(dev, SNDRV_PCM_STREAM_PLAYBACK, &ret); - if (ret < 0) { - kfree(dev->data_cb_info); - free_urbs(dev->data_urbs_in); - free_urbs(dev->data_urbs_out); - return ret; - } - - return 0; -} - -void snd_usb_caiaq_audio_free(struct snd_usb_caiaqdev *dev) -{ - debug("snd_usb_caiaq_audio_free (%p)\n", dev); - stream_stop(dev); - free_urbs(dev->data_urbs_in); - free_urbs(dev->data_urbs_out); - kfree(dev->data_cb_info); -} - diff --git a/sound/usb/caiaq/caiaq-audio.h b/sound/usb/caiaq/caiaq-audio.h deleted file mode 100644 index 8ab1f8d9529..00000000000 --- a/sound/usb/caiaq/caiaq-audio.h +++ /dev/null @@ -1,7 +0,0 @@ -#ifndef CAIAQ_AUDIO_H -#define CAIAQ_AUDIO_H - -int snd_usb_caiaq_audio_init(struct snd_usb_caiaqdev *dev); -void snd_usb_caiaq_audio_free(struct snd_usb_caiaqdev *dev); - -#endif /* CAIAQ_AUDIO_H */ diff --git a/sound/usb/caiaq/caiaq-control.c b/sound/usb/caiaq/caiaq-control.c deleted file mode 100644 index 798ca124da5..00000000000 --- a/sound/usb/caiaq/caiaq-control.c +++ /dev/null @@ -1,315 +0,0 @@ -/* - * 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/init.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 <sound/control.h> -#include <linux/input.h> - -#include "caiaq-device.h" -#include "caiaq-control.h" - -#define CNT_INTVAL 0x10000 - -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 *dev = caiaqdev(chip->card); - int pos = kcontrol->private_value; - int is_intval = pos & CNT_INTVAL; - - uinfo->count = 1; - pos &= ~CNT_INTVAL; - - if (dev->chip.usb_id == - USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_AUDIO8DJ) - && (pos == 0)) { - /* current input mode of A8DJ */ - uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; - uinfo->value.integer.min = 0; - uinfo->value.integer.max = 2; - return 0; - } - - if (is_intval) { - uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; - uinfo->value.integer.min = 0; - uinfo->value.integer.max = 64; - } 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 *dev = caiaqdev(chip->card); - int pos = kcontrol->private_value; - - if (pos & CNT_INTVAL) - ucontrol->value.integer.value[0] - = dev->control_state[pos & ~CNT_INTVAL]; - else - ucontrol->value.integer.value[0] - = !!(dev->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 *dev = caiaqdev(chip->card); - int pos = kcontrol->private_value; - - if (pos & CNT_INTVAL) { - dev->control_state[pos & ~CNT_INTVAL] - = ucontrol->value.integer.value[0]; - snd_usb_caiaq_send_command(dev, EP1_CMD_DIMM_LEDS, - dev->control_state, sizeof(dev->control_state)); - } else { - if (ucontrol->value.integer.value[0]) - dev->control_state[pos / 8] |= 1 << (pos % 8); - else - dev->control_state[pos / 8] &= ~(1 << (pos % 8)); - - snd_usb_caiaq_send_command(dev, EP1_CMD_WRITE_IO, - dev->control_state, sizeof(dev->control_state)); - } - - return 1; -} - -static struct snd_kcontrol_new kcontrol_template __devinitdata = { - .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 }, - { "GND lift for TC Vinyl mode", 24 + 3 }, - { "Software lock", 40 } -}; - -int __devinit snd_usb_caiaq_control_init(struct snd_usb_caiaqdev *dev) -{ - int i; - struct snd_kcontrol *kc; - - switch (dev->chip.usb_id) { - case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_AK1): - for (i = 0; i < ARRAY_SIZE(ak1_controller); i++) { - struct caiaq_controller *c = ak1_controller + i; - kcontrol_template.name = c->name; - kcontrol_template.private_value = c->index; - kc = snd_ctl_new1(&kcontrol_template, dev); - snd_ctl_add(dev->chip.card, kc); - } - - break; - - case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_RIGKONTROL2): - for (i = 0; i < ARRAY_SIZE(rk2_controller); i++) { - struct caiaq_controller *c = rk2_controller + i; - kcontrol_template.name = c->name; - kcontrol_template.private_value = c->index; - kc = snd_ctl_new1(&kcontrol_template, dev); - snd_ctl_add(dev->chip.card, kc); - } - - break; - - case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_RIGKONTROL3): - for (i = 0; i < ARRAY_SIZE(rk3_controller); i++) { - struct caiaq_controller *c = rk3_controller + i; - kcontrol_template.name = c->name; - kcontrol_template.private_value = c->index; - kc = snd_ctl_new1(&kcontrol_template, dev); - snd_ctl_add(dev->chip.card, kc); - } - - break; - - case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_KORECONTROLLER): - case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_KORECONTROLLER2): - for (i = 0; i < ARRAY_SIZE(kore_controller); i++) { - struct caiaq_controller *c = kore_controller + i; - kcontrol_template.name = c->name; - kcontrol_template.private_value = c->index; - kc = snd_ctl_new1(&kcontrol_template, dev); - snd_ctl_add(dev->chip.card, kc); - } - - break; - - case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_AUDIO8DJ): - for (i = 0; i < ARRAY_SIZE(a8dj_controller); i++) { - struct caiaq_controller *c = a8dj_controller + i; - kcontrol_template.name = c->name; - kcontrol_template.private_value = c->index; - kc = snd_ctl_new1(&kcontrol_template, dev); - snd_ctl_add(dev->chip.card, kc); - } - - break; - } - - return 0; -} - diff --git a/sound/usb/caiaq/caiaq-device.c b/sound/usb/caiaq/caiaq-device.c deleted file mode 100644 index 7c44a2c7f96..00000000000 --- a/sound/usb/caiaq/caiaq-device.c +++ /dev/null @@ -1,500 +0,0 @@ -/* - * 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/init.h> -#include <linux/module.h> -#include <linux/moduleparam.h> -#include <linux/interrupt.h> -#include <linux/usb.h> -#include <linux/input.h> -#include <linux/spinlock.h> -#include <sound/core.h> -#include <sound/initval.h> -#include <sound/pcm.h> -#include <sound/rawmidi.h> -#include <sound/control.h> - -#include "caiaq-device.h" -#include "caiaq-audio.h" -#include "caiaq-midi.h" -#include "caiaq-control.h" - -#ifdef CONFIG_SND_USB_CAIAQ_INPUT -#include "caiaq-input.h" -#endif - -MODULE_AUTHOR("Daniel Mack <daniel@caiaq.de>"); -MODULE_DESCRIPTION("caiaq USB audio, version 1.3.2"); -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 8 DJ}}"); - -static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 0-max */ -static char* id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* Id for this card */ -static int enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP; /* Enable this card */ -static int snd_card_used[SNDRV_CARDS]; - -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 - }, - { /* terminator */ } -}; - -static void usb_ep1_command_reply_dispatch (struct urb* urb) -{ - int ret; - struct snd_usb_caiaqdev *dev = urb->context; - unsigned char *buf = urb->transfer_buffer; - - if (urb->status || !dev) { - log("received EP1 urb->status = %i\n", urb->status); - return; - } - - switch(buf[0]) { - case EP1_CMD_GET_DEVICE_INFO: - memcpy(&dev->spec, buf+1, sizeof(struct caiaq_device_spec)); - dev->spec.fw_version = le16_to_cpu(dev->spec.fw_version); - debug("device spec (firmware %d): audio: %d in, %d out, " - "MIDI: %d in, %d out, data alignment %d\n", - dev->spec.fw_version, - dev->spec.num_analog_audio_in, - dev->spec.num_analog_audio_out, - dev->spec.num_midi_in, - dev->spec.num_midi_out, - dev->spec.data_alignment); - - dev->spec_received++; - wake_up(&dev->ep1_wait_queue); - break; - case EP1_CMD_AUDIO_PARAMS: - dev->audio_parm_answer = buf[1]; - wake_up(&dev->ep1_wait_queue); - break; - case EP1_CMD_MIDI_READ: - snd_usb_caiaq_midi_handle_input(dev, buf[1], buf + 3, buf[2]); - break; - case EP1_CMD_READ_IO: - if (dev->chip.usb_id == - USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_AUDIO8DJ)) { - if (urb->actual_length > sizeof(dev->control_state)) - urb->actual_length = sizeof(dev->control_state); - memcpy(dev->control_state, buf + 1, urb->actual_length); - wake_up(&dev->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(dev, buf, urb->actual_length); -#endif - break; - } - - dev->ep1_in_urb.actual_length = 0; - ret = usb_submit_urb(&dev->ep1_in_urb, GFP_ATOMIC); - if (ret < 0) - log("unable to submit urb. OOM!?\n"); -} - -int snd_usb_caiaq_send_command(struct snd_usb_caiaqdev *dev, - unsigned char command, - const unsigned char *buffer, - int len) -{ - int actual_len; - struct usb_device *usb_dev = dev->chip.dev; - - if (!usb_dev) - return -EIO; - - if (len > EP1_BUFSIZE - 1) - len = EP1_BUFSIZE - 1; - - if (buffer && len > 0) - memcpy(dev->ep1_out_buf+1, buffer, len); - - dev->ep1_out_buf[0] = command; - return usb_bulk_msg(usb_dev, usb_sndbulkpipe(usb_dev, 1), - dev->ep1_out_buf, len+1, &actual_len, 200); -} - -int snd_usb_caiaq_set_audio_params (struct snd_usb_caiaqdev *dev, - int rate, int depth, int bpp) -{ - int ret; - char tmp[5]; - - 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 */ - - debug("setting audio params: %d Hz, %d bits, %d bpp\n", - rate, depth, bpp); - - dev->audio_parm_answer = -1; - ret = snd_usb_caiaq_send_command(dev, EP1_CMD_AUDIO_PARAMS, - tmp, sizeof(tmp)); - - if (ret) - return ret; - - if (!wait_event_timeout(dev->ep1_wait_queue, - dev->audio_parm_answer >= 0, HZ)) - return -EPIPE; - - if (dev->audio_parm_answer != 1) - debug("unable to set the device's audio params\n"); - - return dev->audio_parm_answer == 1 ? 0 : -EINVAL; -} - -int snd_usb_caiaq_set_auto_msg (struct snd_usb_caiaqdev *dev, - int digital, int analog, int erp) -{ - char tmp[3] = { digital, analog, erp }; - return snd_usb_caiaq_send_command(dev, EP1_CMD_AUTO_MSG, - tmp, sizeof(tmp)); -} - -static void __devinit setup_card(struct snd_usb_caiaqdev *dev) -{ - int ret; - char val[4]; - - /* device-specific startup specials */ - switch (dev->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(dev, 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(dev, 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(dev, EP1_CMD_WRITE_IO, val, 1); - break; - case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_AUDIO8DJ): - /* Audio 8 DJ - trigger read of current settings */ - dev->control_state[0] = 0xff; - snd_usb_caiaq_set_auto_msg(dev, 1, 0, 0); - snd_usb_caiaq_send_command(dev, EP1_CMD_READ_IO, NULL, 0); - - if (!wait_event_timeout(dev->ep1_wait_queue, - dev->control_state[0] != 0xff, HZ)) - return; - - /* fix up some defaults */ - if ((dev->control_state[1] != 2) || - (dev->control_state[2] != 3) || - (dev->control_state[4] != 2)) { - dev->control_state[1] = 2; - dev->control_state[2] = 3; - dev->control_state[4] = 2; - snd_usb_caiaq_send_command(dev, - EP1_CMD_WRITE_IO, dev->control_state, 6); - } - - break; - } - - if (dev->spec.num_analog_audio_out + - dev->spec.num_analog_audio_in + - dev->spec.num_digital_audio_out + - dev->spec.num_digital_audio_in > 0) { - ret = snd_usb_caiaq_audio_init(dev); - if (ret < 0) - log("Unable to set up audio system (ret=%d)\n", ret); - } - - if (dev->spec.num_midi_in + - dev->spec.num_midi_out > 0) { - ret = snd_usb_caiaq_midi_init(dev); - if (ret < 0) - log("Unable to set up MIDI system (ret=%d)\n", ret); - } - -#ifdef CONFIG_SND_USB_CAIAQ_INPUT - ret = snd_usb_caiaq_input_init(dev); - if (ret < 0) - log("Unable to set up input system (ret=%d)\n", ret); -#endif - - /* finally, register the card and all its sub-instances */ - ret = snd_card_register(dev->chip.card); - if (ret < 0) { - log("snd_card_register() returned %d\n", ret); - snd_card_free(dev->chip.card); - } - - ret = snd_usb_caiaq_control_init(dev); - if (ret < 0) - log("Unable to set up control system (ret=%d)\n", ret); -} - -static struct snd_card* create_card(struct usb_device* usb_dev) -{ - int devnum; - struct snd_card *card; - struct snd_usb_caiaqdev *dev; - - for (devnum = 0; devnum < SNDRV_CARDS; devnum++) - if (enable[devnum] && !snd_card_used[devnum]) - break; - - if (devnum >= SNDRV_CARDS) - return NULL; - - card = snd_card_new(index[devnum], id[devnum], THIS_MODULE, - sizeof(struct snd_usb_caiaqdev)); - if (!card) - return NULL; - - dev = caiaqdev(card); - dev->chip.dev = usb_dev; - dev->chip.card = card; - dev->chip.usb_id = USB_ID(usb_dev->descriptor.idVendor, - usb_dev->descriptor.idProduct); - spin_lock_init(&dev->spinlock); - snd_card_set_dev(card, &usb_dev->dev); - - return card; -} - -static int __devinit init_card(struct snd_usb_caiaqdev *dev) -{ - char *c; - struct usb_device *usb_dev = dev->chip.dev; - struct snd_card *card = dev->chip.card; - int err, len; - - if (usb_set_interface(usb_dev, 0, 1) != 0) { - log("can't set alt interface.\n"); - return -EIO; - } - - usb_init_urb(&dev->ep1_in_urb); - usb_init_urb(&dev->midi_out_urb); - - usb_fill_bulk_urb(&dev->ep1_in_urb, usb_dev, - usb_rcvbulkpipe(usb_dev, 0x1), - dev->ep1_in_buf, EP1_BUFSIZE, - usb_ep1_command_reply_dispatch, dev); - - usb_fill_bulk_urb(&dev->midi_out_urb, usb_dev, - usb_sndbulkpipe(usb_dev, 0x1), - dev->midi_out_buf, EP1_BUFSIZE, - snd_usb_caiaq_midi_output_done, dev); - - init_waitqueue_head(&dev->ep1_wait_queue); - init_waitqueue_head(&dev->prepare_wait_queue); - - if (usb_submit_urb(&dev->ep1_in_urb, GFP_KERNEL) != 0) - return -EIO; - - err = snd_usb_caiaq_send_command(dev, EP1_CMD_GET_DEVICE_INFO, NULL, 0); - if (err) - return err; - - if (!wait_event_timeout(dev->ep1_wait_queue, dev->spec_received, HZ)) - return -ENODEV; - - usb_string(usb_dev, usb_dev->descriptor.iManufacturer, - dev->vendor_name, CAIAQ_USB_STR_LEN); - - usb_string(usb_dev, usb_dev->descriptor.iProduct, - dev->product_name, CAIAQ_USB_STR_LEN); - - usb_string(usb_dev, usb_dev->descriptor.iSerialNumber, - dev->serial, CAIAQ_USB_STR_LEN); - - /* terminate serial string at first white space occurence */ - c = strchr(dev->serial, ' '); - if (c) - *c = '\0'; - - strcpy(card->driver, MODNAME); - strcpy(card->shortname, dev->product_name); - - len = snprintf(card->longname, sizeof(card->longname), - "%s %s (serial %s, ", - dev->vendor_name, dev->product_name, dev->serial); - - if (len < sizeof(card->longname) - 2) - len += usb_make_path(usb_dev, card->longname + len, - sizeof(card->longname) - len); - - card->longname[len++] = ')'; - card->longname[len] = '\0'; - setup_card(dev); - return 0; -} - -static int __devinit snd_probe(struct usb_interface *intf, - const struct usb_device_id *id) -{ - int ret; - struct snd_card *card; - struct usb_device *device = interface_to_usbdev(intf); - - card = create_card(device); - - if (!card) - return -ENOMEM; - - dev_set_drvdata(&intf->dev, card); - ret = init_card(caiaqdev(card)); - if (ret < 0) { - log("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_usb_caiaqdev *dev; - struct snd_card *card = dev_get_drvdata(&intf->dev); - - debug("snd_disconnect(%p)\n", intf); - - if (!card) - return; - - dev = caiaqdev(card); - snd_card_disconnect(card); - -#ifdef CONFIG_SND_USB_CAIAQ_INPUT - snd_usb_caiaq_input_free(dev); -#endif - snd_usb_caiaq_audio_free(dev); - - usb_kill_urb(&dev->ep1_in_urb); - usb_kill_urb(&dev->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, -}; - -static int __init snd_module_init(void) -{ - return usb_register(&snd_usb_driver); -} - -static void __exit snd_module_exit(void) -{ - usb_deregister(&snd_usb_driver); -} - -module_init(snd_module_init) -module_exit(snd_module_exit) - diff --git a/sound/usb/caiaq/caiaq-input.c b/sound/usb/caiaq/caiaq-input.c deleted file mode 100644 index f743847a5e5..00000000000 --- a/sound/usb/caiaq/caiaq-input.c +++ /dev/null @@ -1,363 +0,0 @@ -/* - * 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/init.h> -#include <linux/module.h> -#include <linux/moduleparam.h> -#include <linux/input.h> -#include <linux/usb.h> -#include <linux/usb/input.h> -#include <linux/spinlock.h> -#include <sound/core.h> -#include <sound/rawmidi.h> -#include <sound/pcm.h> -#include "caiaq-device.h" -#include "caiaq-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_5, KEY_6 }; - -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 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 void snd_caiaq_input_read_analog(struct snd_usb_caiaqdev *dev, - const unsigned char *buf, - unsigned int len) -{ - struct input_dev *input_dev = dev->input_dev; - - switch (dev->chip.usb_id) { - case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_RIGKONTROL2): - input_report_abs(input_dev, ABS_X, (buf[4] << 8) | buf[5]); - input_report_abs(input_dev, ABS_Y, (buf[0] << 8) | buf[1]); - input_report_abs(input_dev, ABS_Z, (buf[2] << 8) | buf[3]); - input_sync(input_dev); - break; - case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_RIGKONTROL3): - input_report_abs(input_dev, ABS_X, (buf[0] << 8) | buf[1]); - input_report_abs(input_dev, ABS_Y, (buf[2] << 8) | buf[3]); - input_report_abs(input_dev, ABS_Z, (buf[4] << 8) | buf[5]); - input_sync(input_dev); - break; - case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_KORECONTROLLER): - case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_KORECONTROLLER2): - input_report_abs(input_dev, ABS_X, (buf[0] << 8) | buf[1]); - input_report_abs(input_dev, ABS_Y, (buf[2] << 8) | buf[3]); - input_report_abs(input_dev, ABS_Z, (buf[4] << 8) | buf[5]); - input_sync(input_dev); - break; - } -} - -static void snd_caiaq_input_read_erp(struct snd_usb_caiaqdev *dev, - const char *buf, unsigned int len) -{ - struct input_dev *input_dev = dev->input_dev; - int i; - - switch (dev->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; - } -} - -static void snd_caiaq_input_read_io(struct snd_usb_caiaqdev *dev, - char *buf, unsigned int len) -{ - struct input_dev *input_dev = dev->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))); - - if (dev->chip.usb_id == - USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_KORECONTROLLER) || - dev->chip.usb_id == - USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_KORECONTROLLER2)) - input_report_abs(dev->input_dev, ABS_MISC, 255 - buf[4]); - - input_sync(input_dev); -} - -void snd_usb_caiaq_input_dispatch(struct snd_usb_caiaqdev *dev, - char *buf, - unsigned int len) -{ - if (!dev->input_dev || len < 1) - return; - - switch (buf[0]) { - case EP1_CMD_READ_ANALOG: - snd_caiaq_input_read_analog(dev, buf + 1, len - 1); - break; - case EP1_CMD_READ_ERP: - snd_caiaq_input_read_erp(dev, buf + 1, len - 1); - break; - case EP1_CMD_READ_IO: - snd_caiaq_input_read_io(dev, buf + 1, len - 1); - break; - } -} - -int snd_usb_caiaq_input_init(struct snd_usb_caiaqdev *dev) -{ - struct usb_device *usb_dev = dev->chip.dev; - struct input_dev *input; - int i, ret; - - input = input_allocate_device(); - if (!input) - return -ENOMEM; - - usb_make_path(usb_dev, dev->phys, sizeof(dev->phys)); - strlcat(dev->phys, "/input0", sizeof(dev->phys)); - - input->name = dev->product_name; - input->phys = dev->phys; - usb_to_input_id(usb_dev, &input->id); - input->dev.parent = &usb_dev->dev; - - switch (dev->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(dev->keycode) < sizeof(keycode_rk2)); - memcpy(dev->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(dev, 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(dev->keycode) < sizeof(keycode_rk3)); - memcpy(dev->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(dev, 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(dev->keycode) < sizeof(keycode_ak1)); - memcpy(dev->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(dev, 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(dev->keycode) < sizeof(keycode_kore)); - memcpy(dev->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(dev, 1, 10, 5); - break; - default: - /* no input methods supported on this device */ - input_free_device(input); - return 0; - } - - input->keycode = dev->keycode; - input->keycodesize = sizeof(unsigned short); - for (i = 0; i < input->keycodemax; i++) - __set_bit(dev->keycode[i], input->keybit); - - ret = input_register_device(input); - if (ret < 0) { - input_free_device(input); - return ret; - } - - dev->input_dev = input; - return 0; -} - -void snd_usb_caiaq_input_free(struct snd_usb_caiaqdev *dev) -{ - if (!dev || !dev->input_dev) - return; - - input_unregister_device(dev->input_dev); - dev->input_dev = NULL; -} - diff --git a/sound/usb/caiaq/caiaq-input.h b/sound/usb/caiaq/caiaq-input.h deleted file mode 100644 index ced53557786..00000000000 --- a/sound/usb/caiaq/caiaq-input.h +++ /dev/null @@ -1,8 +0,0 @@ -#ifndef CAIAQ_INPUT_H -#define CAIAQ_INPUT_H - -void snd_usb_caiaq_input_dispatch(struct snd_usb_caiaqdev *dev, char *buf, unsigned int len); -int snd_usb_caiaq_input_init(struct snd_usb_caiaqdev *dev); -void snd_usb_caiaq_input_free(struct snd_usb_caiaqdev *dev); - -#endif diff --git a/sound/usb/caiaq/caiaq-midi.h b/sound/usb/caiaq/caiaq-midi.h deleted file mode 100644 index 9d16db027fc..00000000000 --- a/sound/usb/caiaq/caiaq-midi.h +++ /dev/null @@ -1,8 +0,0 @@ -#ifndef CAIAQ_MIDI_H -#define CAIAQ_MIDI_H - -int snd_usb_caiaq_midi_init(struct snd_usb_caiaqdev *dev); -void snd_usb_caiaq_midi_handle_input(struct snd_usb_caiaqdev *dev, 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/caiaq/control.c b/sound/usb/caiaq/control.c new file mode 100644 index 00000000000..f65fc0987cf --- /dev/null +++ b/sound/usb/caiaq/control.c @@ -0,0 +1,652 @@ +/* + * 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 = EP1_CMD_WRITE_IO; + + if (cdev->chip.usb_id == + USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_TRAKTORKONTROLX1)) + cmd = EP1_CMD_DIMM_LEDS; + + if (cdev->chip.usb_id == + USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_MASCHINECONTROLLER)) + cmd = EP1_CMD_DIMM_LEDS; + + 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/caiaq-control.h b/sound/usb/caiaq/control.h index 2e7ab1aa4fb..501c4883aef 100644 --- a/sound/usb/caiaq/caiaq-control.h +++ b/sound/usb/caiaq/control.h @@ -1,6 +1,6 @@ #ifndef CAIAQ_CONTROL_H #define CAIAQ_CONTROL_H -int snd_usb_caiaq_control_init(struct snd_usb_caiaqdev *dev); +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 00000000000..b871ba407e4 --- /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/caiaq-device.h b/sound/usb/caiaq/device.h index 96a491379c6..ab0f7520a99 100644 --- a/sound/usb/caiaq/caiaq-device.h +++ b/sound/usb/caiaq/device.h @@ -5,27 +5,27 @@ #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_AUDIO8DJ 0x1978 +#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 SND_USB_CAIAQ_DEBUG - #define MODNAME "snd-usb-caiaq" -#define log(x...) snd_printk(KERN_WARNING MODNAME" log: " x) - -#ifdef SND_USB_CAIAQ_DEBUG -#define debug(x...) snd_printk(KERN_WARNING MODNAME " debug: " x) -#else -#define debug(x...) do { } while(0) -#endif #define EP1_CMD_GET_DEVICE_INFO 0x1 #define EP1_CMD_READ_ERP 0x2 @@ -74,10 +74,10 @@ struct snd_usb_caiaqdev { 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]; - char serial[CAIAQ_USB_STR_LEN]; int n_streams, n_audio_in, n_audio_out; int streaming, first_packet, output_running; @@ -85,21 +85,25 @@ struct snd_usb_caiaqdev { int audio_out_buf_pos[MAX_STREAMS]; int period_in_count[MAX_STREAMS]; int period_out_count[MAX_STREAMS]; - int input_panic, output_panic; + int input_panic, output_panic, warned; char *audio_in_buf, *audio_out_buf; - unsigned int samplerates; + 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[64]; + 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[64]; + unsigned short keycode[128]; + struct urb *ep4_in_urb; + unsigned char ep4_in_buf[EP4_BUFSIZE]; #endif /* ALSA */ @@ -111,16 +115,22 @@ struct snd_usb_caiaqdev { }; struct snd_usb_caiaq_cb_info { - struct snd_usb_caiaqdev *dev; + 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 *dev, int rate, int depth, int bbp); -int snd_usb_caiaq_set_auto_msg (struct snd_usb_caiaqdev *dev, int digital, int analog, int erp); -int snd_usb_caiaq_send_command(struct snd_usb_caiaqdev *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); diff --git a/sound/usb/caiaq/input.c b/sound/usb/caiaq/input.c new file mode 100644 index 00000000000..4b3fb91deec --- /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 00000000000..6014e2713a6 --- /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/caiaq-midi.c b/sound/usb/caiaq/midi.c index 30b57f97c6e..2d7588461b3 100644 --- a/sound/usb/caiaq/caiaq-midi.c +++ b/sound/usb/caiaq/midi.c @@ -16,20 +16,15 @@ * 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/interrupt.h> +#include <linux/device.h> #include <linux/usb.h> -#include <linux/input.h> -#include <linux/spinlock.h> -#include <sound/core.h> +#include <linux/gfp.h> #include <sound/rawmidi.h> +#include <sound/core.h> #include <sound/pcm.h> -#include "caiaq-device.h" -#include "caiaq-midi.h" - +#include "device.h" +#include "midi.h" static int snd_usb_caiaq_midi_input_open(struct snd_rawmidi_substream *substream) { @@ -43,12 +38,12 @@ static int snd_usb_caiaq_midi_input_close(struct snd_rawmidi_substream *substrea static void snd_usb_caiaq_midi_input_trigger(struct snd_rawmidi_substream *substream, int up) { - struct snd_usb_caiaqdev *dev = substream->rmidi->private_data; + struct snd_usb_caiaqdev *cdev = substream->rmidi->private_data; - if (!dev) + if (!cdev) return; - - dev->midi_receive_substream = up ? substream : NULL; + + cdev->midi_receive_substream = up ? substream : NULL; } @@ -59,44 +54,51 @@ static int snd_usb_caiaq_midi_output_open(struct snd_rawmidi_substream *substrea 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 *dev, +static void snd_usb_caiaq_midi_send(struct snd_usb_caiaqdev *cdev, struct snd_rawmidi_substream *substream) { int len, ret; - - dev->midi_out_buf[0] = EP1_CMD_MIDI_WRITE; - dev->midi_out_buf[1] = 0; /* port */ - len = snd_rawmidi_transmit_peek(substream, dev->midi_out_buf+3, EP1_BUFSIZE-3); - + 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; - - dev->midi_out_buf[2] = len; - dev->midi_out_urb.transfer_buffer_length = len+3; - - ret = usb_submit_urb(&dev->midi_out_urb, GFP_ATOMIC); + + 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) - log("snd_usb_caiaq_midi_send(%p): usb_submit_urb() failed, %d\n", - substream, ret); + 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 *dev = substream->rmidi->private_data; - - if (dev->midi_out_substream != NULL) - return; - - if (!up) { - dev->midi_out_substream = NULL; - return; + 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; } - - dev->midi_out_substream = substream; - snd_usb_caiaq_midi_send(dev, substream); } @@ -114,13 +116,13 @@ static struct snd_rawmidi_ops snd_usb_caiaq_midi_input = .trigger = snd_usb_caiaq_midi_input_trigger, }; -void snd_usb_caiaq_midi_handle_input(struct snd_usb_caiaqdev *dev, +void snd_usb_caiaq_midi_handle_input(struct snd_usb_caiaqdev *cdev, int port, const char *buf, int len) { - if (!dev->midi_receive_substream) + if (!cdev->midi_receive_substream) return; - - snd_rawmidi_receive(dev->midi_receive_substream, buf, len); + + snd_rawmidi_receive(cdev->midi_receive_substream, buf, len); } int snd_usb_caiaq_midi_init(struct snd_usb_caiaqdev *device) @@ -136,23 +138,23 @@ int snd_usb_caiaq_midi_init(struct snd_usb_caiaqdev *device) if (ret < 0) return ret; - strcpy(rmidi->name, device->product_name); + 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_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_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT, &snd_usb_caiaq_midi_input); } - + device->rmidi = rmidi; return 0; @@ -160,17 +162,14 @@ int snd_usb_caiaq_midi_init(struct snd_usb_caiaqdev *device) void snd_usb_caiaq_midi_output_done(struct urb* urb) { - struct snd_usb_caiaqdev *dev = urb->context; - char *buf = urb->transfer_buffer; - + struct snd_usb_caiaqdev *cdev = urb->context; + + cdev->midi_out_active = 0; if (urb->status != 0) return; - if (!dev->midi_out_substream) + if (!cdev->midi_out_substream) return; - snd_rawmidi_transmit_ack(dev->midi_out_substream, buf[2]); - dev->midi_out_substream = NULL; - snd_usb_caiaq_midi_send(dev, dev->midi_out_substream); + 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 00000000000..60bf3442b28 --- /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 00000000000..a09e5f3519e --- /dev/null +++ b/sound/usb/card.c @@ -0,0 +1,782 @@ +/* + * (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 snd_usb_audio_disconnect() + */ +static void snd_usb_stream_disconnect(struct list_head *head) +{ + int idx; + struct snd_usb_stream *as; + struct snd_usb_substream *subs; + + as = list_entry(head, struct snd_usb_stream, list); + 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 list_head *p, *n; + + list_for_each_safe(p, n, &chip->ep_list) + snd_usb_endpoint_free(p); + + 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); +} + +static void remove_trailing_spaces(char *str) +{ + char *p; + + if (!*str) + return; + for (p = str + strlen(str) - 1; p >= str && isspace(*p); p--) + *p = 0; +} + +/* + * 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)); + } + } + remove_trailing_spaces(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) { + remove_trailing_spaces(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 struct snd_usb_audio * +snd_usb_audio_probe(struct usb_device *dev, + struct usb_interface *intf, + const struct usb_device_id *usb_id) +{ + const struct snd_usb_audio_quirk *quirk = (const struct snd_usb_audio_quirk *)usb_id->driver_info; + int i, err; + struct snd_usb_audio *chip; + 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) + goto __err_val; + + if (snd_usb_apply_boot_quirk(dev, intf, quirk) < 0) + goto __err_val; + + /* + * found a config. now register to ALSA + */ + + /* check whether it's already registered */ + chip = NULL; + mutex_lock(®ister_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"); + 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))) { + if (snd_usb_audio_create(intf, dev, i, quirk, + &chip) < 0) { + goto __error; + } + chip->pm_intf = intf; + break; + } + if (!chip) { + dev_err(&dev->dev, "no available usb audio device\n"); + 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 */ + if ((err = snd_usb_create_quirk(chip, intf, &usb_audio_driver, quirk)) < 0) + goto __error; + } + + if (err > 0) { + /* create normal USB audio interfaces */ + if (snd_usb_create_streams(chip, ifnum) < 0 || + snd_usb_create_mixer(chip, ifnum, ignore_ctl_error) < 0) { + goto __error; + } + } + + /* we are allowed to call snd_card_register() many times */ + if (snd_card_register(chip->card) < 0) { + goto __error; + } + + usb_chip[chip->index] = chip; + chip->num_interfaces++; + chip->probing = 0; + mutex_unlock(®ister_mutex); + return chip; + + __error: + if (chip) { + if (!chip->num_interfaces) + snd_card_free(chip->card); + chip->probing = 0; + } + mutex_unlock(®ister_mutex); + __err_val: + return NULL; +} + +/* + * we need to take care of counter, since disconnection can be called also + * many times as well as usb_audio_probe(). + */ +static void snd_usb_audio_disconnect(struct usb_device *dev, + struct snd_usb_audio *chip) +{ + struct snd_card *card; + struct list_head *p; + + if (chip == (void *)-1L) + return; + + card = chip->card; + down_write(&chip->shutdown_rwsem); + chip->shutdown = 1; + up_write(&chip->shutdown_rwsem); + + mutex_lock(®ister_mutex); + chip->num_interfaces--; + if (chip->num_interfaces <= 0) { + struct snd_usb_endpoint *ep; + + snd_card_disconnect(card); + /* release the pcm resources */ + list_for_each(p, &chip->pcm_list) { + snd_usb_stream_disconnect(p); + } + /* 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(p, &chip->mixer_list) { + snd_usb_mixer_disconnect(p); + } + usb_chip[chip->index] = NULL; + mutex_unlock(®ister_mutex); + snd_card_free_when_closed(card); + } else { + mutex_unlock(®ister_mutex); + } +} + +/* + * new 2.5 USB kernel API + */ +static int usb_audio_probe(struct usb_interface *intf, + const struct usb_device_id *id) +{ + struct snd_usb_audio *chip; + chip = snd_usb_audio_probe(interface_to_usbdev(intf), intf, id); + if (chip) { + usb_set_intfdata(intf, chip); + return 0; + } else + return -EIO; +} + +static void usb_audio_disconnect(struct usb_interface *intf) +{ + snd_usb_audio_disconnect(interface_to_usbdev(intf), + usb_get_intfdata(intf)); +} + +#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; + + 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; + } + } + } 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; + 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; + } + + 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 00000000000..97acb906acc --- /dev/null +++ b/sound/usb/card.h @@ -0,0 +1,167 @@ +#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; +}; + +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 00000000000..03fed6611d9 --- /dev/null +++ b/sound/usb/clock.c @@ -0,0 +1,422 @@ +/* + * 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; + } + + 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 00000000000..d592e4a2985 --- /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 00000000000..58030176f00 --- /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 00000000000..114e3e7ff51 --- /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; + 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)) { + unsigned long flags; + + 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); + //snd_pcm_stop(substream, SNDRV_PCM_STATE_XRUN); + +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 list header of the endpoint to free + * + * This free all resources of the given ep. + */ +void snd_usb_endpoint_free(struct list_head *head) +{ + struct snd_usb_endpoint *ep; + + ep = list_entry(head, struct snd_usb_endpoint, list); + 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 00000000000..e61ee5c356a --- /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 list_head *head); + +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 00000000000..8bcc87cf566 --- /dev/null +++ b/sound/usb/format.c @@ -0,0 +1,521 @@ +/* + * 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 */ && + 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 00000000000..4b8a01129f2 --- /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 00000000000..51ed1ac825f --- /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 00000000000..805c300dd00 --- /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 00000000000..463b136d1d8 --- /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 00000000000..2670d646bda --- /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(®ister_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(®ister_mutex); + + usb_set_intfdata(intf, chip); + return 0; + +err_chip_destroy: + snd_card_free(chip->card); +err: + mutex_unlock(®ister_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 00000000000..189a1371b7c --- /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 00000000000..2c44139b404 --- /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 00000000000..77edd7c12e1 --- /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/usbmidi.c b/sound/usb/midi.c index 6676a177c99..9da74d2e8ee 100644 --- a/sound/usb/usbmidi.c +++ b/sound/usb/midi.c @@ -1,7 +1,7 @@ /* * usbmidi.c - ALSA USB MIDI driver * - * Copyright (c) 2002-2007 Clemens Ladisch + * Copyright (c) 2002-2009 Clemens Ladisch * All rights reserved. * * Based on the OSS usb-midi driver by NAGANO Daisuke, @@ -45,11 +45,18 @@ #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 @@ -62,6 +69,9 @@ */ #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"); @@ -90,14 +100,15 @@ 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*); + 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 snd_usb_audio *chip; + struct usb_device *dev; + struct snd_card *card; struct usb_interface *iface; const struct snd_usb_audio_quirk *quirk; struct snd_rawmidi *rmidi; @@ -105,22 +116,34 @@ struct snd_usb_midi { 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 urb* urb; - int urb_active; + 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 { @@ -139,11 +162,13 @@ struct snd_usb_midi_out_endpoint { 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* urb; + struct urb* urbs[INPUT_URBS]; struct usbmidi_in_port { struct snd_rawmidi_substream *substream; u8 running_status_length; @@ -166,16 +191,16 @@ static int snd_usbmidi_submit_urb(struct urb* urb, gfp_t flags) { int err = usb_submit_urb(urb, flags); if (err < 0 && err != -ENODEV) - snd_printk(KERN_ERR "usb_submit_urb: %d\n", err); + 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(int status) +static int snd_usbmidi_urb_error(const struct urb *urb) { - switch (status) { + switch (urb->status) { /* manually unlinked, or device gone */ case -ENOENT: case -ECONNRESET: @@ -188,7 +213,7 @@ static int snd_usbmidi_urb_error(int status) case -EILSEQ: return -EIO; default: - snd_printk(KERN_ERR "urb status %d\n", status); + dev_err(&urb->dev->dev, "urb status %d\n", urb->status); return 0; /* continue */ } } @@ -202,7 +227,7 @@ static void snd_usbmidi_input_data(struct snd_usb_midi_in_endpoint* ep, int port struct usbmidi_in_port* port = &ep->ports[portidx]; if (!port->substream) { - snd_printd("unexpected port %d!\n", portidx); + dev_dbg(&ep->umidi->dev->dev, "unexpected port %d!\n", portidx); return; } if (!test_bit(port->substream->number, &ep->umidi->input_triggered)) @@ -234,7 +259,7 @@ static void snd_usbmidi_in_urb_complete(struct urb* urb) ep->umidi->usb_protocol_ops->input(ep, urb->transfer_buffer, urb->actual_length); } else { - int err = snd_usbmidi_urb_error(urb->status); + int err = snd_usbmidi_urb_error(urb); if (err < 0) { if (err != -ENODEV) { ep->error_resubmit = 1; @@ -245,19 +270,26 @@ static void snd_usbmidi_in_urb_complete(struct urb* urb) } } - urb->dev = ep->umidi->chip->dev; + urb->dev = ep->umidi->dev; snd_usbmidi_submit_urb(urb, GFP_ATOMIC); } static void snd_usbmidi_out_urb_complete(struct urb* urb) { - struct snd_usb_midi_out_endpoint* ep = urb->context; + 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); - ep->urb_active = 0; + 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->status); + int err = snd_usbmidi_urb_error(urb); if (err < 0) { if (err != -ENODEV) mod_timer(&ep->umidi->error_timer, @@ -274,24 +306,38 @@ static void snd_usbmidi_out_urb_complete(struct urb* urb) */ static void snd_usbmidi_do_output(struct snd_usb_midi_out_endpoint* ep) { - struct urb* urb = ep->urb; + unsigned int urb_index; + struct urb* urb; unsigned long flags; spin_lock_irqsave(&ep->buffer_lock, flags); - if (ep->urb_active || ep->umidi->chip->shutdown) { + if (ep->umidi->disconnected) { spin_unlock_irqrestore(&ep->buffer_lock, flags); return; } - urb->transfer_buffer_length = 0; - ep->umidi->usb_protocol_ops->output(ep); + 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; - if (urb->transfer_buffer_length > 0) { - dump_urb("sending", urb->transfer_buffer, - urb->transfer_buffer_length); - urb->dev = ep->umidi->chip->dev; - ep->urb_active = snd_usbmidi_submit_urb(urb, GFP_ATOMIC) >= 0; + 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); } @@ -306,7 +352,7 @@ static void snd_usbmidi_out_tasklet(unsigned long data) static void snd_usbmidi_error_timer(unsigned long data) { struct snd_usb_midi *umidi = (struct snd_usb_midi *)data; - int i; + unsigned int i, j; spin_lock(&umidi->disc_lock); if (umidi->disconnected) { @@ -317,8 +363,10 @@ static void snd_usbmidi_error_timer(unsigned long data) struct snd_usb_midi_in_endpoint *in = umidi->endpoints[i].in; if (in && in->error_resubmit) { in->error_resubmit = 0; - in->urb->dev = umidi->chip->dev; - snd_usbmidi_submit_urb(in->urb, GFP_ATOMIC); + for (j = 0; j < INPUT_URBS; ++j) { + 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); @@ -330,13 +378,14 @@ static void snd_usbmidi_error_timer(unsigned long data) static int send_bulk_static_data(struct snd_usb_midi_out_endpoint* ep, const void *data, int len) { - int err; + int err = 0; void *buf = kmemdup(data, len, GFP_KERNEL); if (!buf) return -ENOMEM; dump_urb("sending", buf, len); - err = usb_bulk_msg(ep->umidi->chip->dev, ep->urb->pipe, buf, len, - NULL, 250); + 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; } @@ -389,7 +438,7 @@ static void snd_usbmidi_maudio_broken_running_status_input( 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 */ @@ -554,9 +603,9 @@ static void snd_usbmidi_transmit_byte(struct usbmidi_out_port* port, } } -static void snd_usbmidi_standard_output(struct snd_usb_midi_out_endpoint* ep) +static void snd_usbmidi_standard_output(struct snd_usb_midi_out_endpoint* ep, + struct urb *urb) { - struct urb* urb = ep->urb; int p; /* FIXME: lower-numbered ports can starve higher-numbered ports */ @@ -583,13 +632,13 @@ static struct usb_protocol_ops snd_usbmidi_standard_ops = { static struct usb_protocol_ops snd_usbmidi_midiman_ops = { .input = snd_usbmidi_midiman_input, - .output = snd_usbmidi_standard_output, + .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 = snd_usbmidi_standard_output, .output_packet = snd_usbmidi_output_standard_packet, }; @@ -600,6 +649,105 @@ static struct usb_protocol_ops snd_usbmidi_cme_ops = { }; /* + * 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. @@ -613,14 +761,15 @@ static void snd_usbmidi_novation_input(struct snd_usb_midi_in_endpoint* ep, snd_usbmidi_input_data(ep, 0, &buffer[2], buffer[0] - 1); } -static void snd_usbmidi_novation_output(struct snd_usb_midi_out_endpoint* ep) +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 = ep->urb->transfer_buffer; + transfer_buffer = urb->transfer_buffer; count = snd_rawmidi_transmit(ep->ports[0].substream, &transfer_buffer[2], ep->max_transfer - 2); @@ -630,7 +779,7 @@ static void snd_usbmidi_novation_output(struct snd_usb_midi_out_endpoint* ep) } transfer_buffer[0] = 0; transfer_buffer[1] = count; - ep->urb->transfer_buffer_length = 2 + count; + urb->transfer_buffer_length = 2 + count; } static struct usb_protocol_ops snd_usbmidi_novation_ops = { @@ -639,7 +788,7 @@ static struct usb_protocol_ops snd_usbmidi_novation_ops = { }; /* - * "raw" protocol: used by the MOTU FastLane. + * "raw" protocol: just move raw MIDI bytes from/to the endpoint */ static void snd_usbmidi_raw_input(struct snd_usb_midi_in_endpoint* ep, @@ -648,20 +797,21 @@ static void snd_usbmidi_raw_input(struct snd_usb_midi_in_endpoint* ep, snd_usbmidi_input_data(ep, 0, buffer, buffer_length); } -static void snd_usbmidi_raw_output(struct snd_usb_midi_out_endpoint* ep) +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, - ep->urb->transfer_buffer, + urb->transfer_buffer, ep->max_transfer); if (count < 1) { ep->ports[0].active = 0; return; } - ep->urb->transfer_buffer_length = count; + urb->transfer_buffer_length = count; } static struct usb_protocol_ops snd_usbmidi_raw_ops = { @@ -670,6 +820,66 @@ static struct usb_protocol_ops snd_usbmidi_raw_ops = { }; /* + * 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. */ @@ -750,10 +960,11 @@ static void snd_usbmidi_emagic_input(struct snd_usb_midi_in_endpoint* ep, } } -static void snd_usbmidi_emagic_output(struct snd_usb_midi_out_endpoint* ep) +static void snd_usbmidi_emagic_output(struct snd_usb_midi_out_endpoint* ep, + struct urb *urb) { int port0 = ep->current_port; - uint8_t* buf = ep->urb->transfer_buffer; + uint8_t* buf = urb->transfer_buffer; int buf_free = ep->max_transfer; int length, i; @@ -793,7 +1004,7 @@ static void snd_usbmidi_emagic_output(struct snd_usb_midi_out_endpoint* ep) *buf = 0xff; --buf_free; } - ep->urb->transfer_buffer_length = ep->max_transfer - buf_free; + urb->transfer_buffer_length = ep->max_transfer - buf_free; } static struct usb_protocol_ops snd_usbmidi_emagic_ops = { @@ -804,6 +1015,69 @@ static struct usb_protocol_ops snd_usbmidi_emagic_ops = { }; +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; @@ -821,14 +1095,15 @@ static int snd_usbmidi_output_open(struct snd_rawmidi_substream *substream) snd_BUG(); return -ENXIO; } + substream->runtime->private_data = port; port->state = STATE_UNKNOWN; - return 0; + return substream_open(substream, 0, 1); } static int snd_usbmidi_output_close(struct snd_rawmidi_substream *substream) { - return 0; + return substream_open(substream, 0, 0); } static void snd_usbmidi_output_trigger(struct snd_rawmidi_substream *substream, int up) @@ -837,25 +1112,56 @@ static void snd_usbmidi_output_trigger(struct snd_rawmidi_substream *substream, port->active = up; if (up) { - if (port->ep->umidi->chip->shutdown) { + 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_hi_schedule(&port->ep->tasklet); + 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 0; + return substream_open(substream, 1, 1); } static int snd_usbmidi_input_close(struct snd_rawmidi_substream *substream) { - return 0; + return substream_open(substream, 1, 0); } static void snd_usbmidi_input_trigger(struct snd_rawmidi_substream *substream, int up) @@ -872,6 +1178,7 @@ 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 = { @@ -880,19 +1187,26 @@ static struct snd_rawmidi_ops snd_usbmidi_input_ops = { .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) { - if (ep->urb) { - usb_buffer_free(ep->umidi->chip->dev, - ep->urb->transfer_buffer_length, - ep->urb->transfer_buffer, - ep->urb->transfer_dma); - usb_free_urb(ep->urb); - } + 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); } @@ -907,6 +1221,7 @@ static int snd_usbmidi_in_endpoint_create(struct snd_usb_midi* umidi, void* buffer; unsigned int pipe; int length; + unsigned int i; rep->in = NULL; ep = kzalloc(sizeof(*ep), GFP_KERNEL); @@ -914,56 +1229,60 @@ static int snd_usbmidi_in_endpoint_create(struct snd_usb_midi* umidi, return -ENOMEM; ep->umidi = umidi; - ep->urb = usb_alloc_urb(0, GFP_KERNEL); - if (!ep->urb) { - snd_usbmidi_in_endpoint_delete(ep); - return -ENOMEM; + 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->chip->dev, ep_info->in_ep); + pipe = usb_rcvintpipe(umidi->dev, ep_info->in_ep); else - pipe = usb_rcvbulkpipe(umidi->chip->dev, ep_info->in_ep); - length = usb_maxpacket(umidi->chip->dev, pipe, 0); - buffer = usb_buffer_alloc(umidi->chip->dev, length, GFP_KERNEL, - &ep->urb->transfer_dma); - if (!buffer) { - snd_usbmidi_in_endpoint_delete(ep); - return -ENOMEM; + 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; } - if (ep_info->in_interval) - usb_fill_int_urb(ep->urb, umidi->chip->dev, pipe, buffer, - length, snd_usbmidi_in_urb_complete, ep, - ep_info->in_interval); - else - usb_fill_bulk_urb(ep->urb, umidi->chip->dev, pipe, buffer, - length, snd_usbmidi_in_urb_complete, ep); - ep->urb->transfer_flags = URB_NO_TRANSFER_DMA_MAP; rep->in = ep; return 0; } -static unsigned int snd_usbmidi_count_bits(unsigned int x) -{ - unsigned int bits; - - for (bits = 0; x; ++bits) - x &= x - 1; - return bits; -} - /* * Frees an output endpoint. * May be called when ep hasn't been initialized completely. */ -static void snd_usbmidi_out_endpoint_delete(struct snd_usb_midi_out_endpoint* ep) +static void snd_usbmidi_out_endpoint_clear(struct snd_usb_midi_out_endpoint *ep) { - if (ep->urb) { - usb_buffer_free(ep->umidi->chip->dev, ep->max_transfer, - ep->urb->transfer_buffer, - ep->urb->transfer_dma); - usb_free_urb(ep->urb); - } + 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); } @@ -972,10 +1291,10 @@ static void snd_usbmidi_out_endpoint_delete(struct snd_usb_midi_out_endpoint* ep */ 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_endpoint* rep) { struct snd_usb_midi_out_endpoint* ep; - int i; + unsigned int i; unsigned int pipe; void* buffer; @@ -985,38 +1304,66 @@ static int snd_usbmidi_out_endpoint_create(struct snd_usb_midi* umidi, return -ENOMEM; ep->umidi = umidi; - ep->urb = usb_alloc_urb(0, GFP_KERNEL); - if (!ep->urb) { - snd_usbmidi_out_endpoint_delete(ep); - return -ENOMEM; + 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->chip->dev, ep_info->out_ep); + pipe = usb_sndintpipe(umidi->dev, ep_info->out_ep); else - pipe = usb_sndbulkpipe(umidi->chip->dev, ep_info->out_ep); - if (umidi->chip->usb_id == USB_ID(0x0a92, 0x1020)) /* ESI M4U */ - /* FIXME: we need more URBs to get reasonable bandwidth here: */ + 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; - else - ep->max_transfer = usb_maxpacket(umidi->chip->dev, pipe, 1); - buffer = usb_buffer_alloc(umidi->chip->dev, ep->max_transfer, - GFP_KERNEL, &ep->urb->transfer_dma); - if (!buffer) { - snd_usbmidi_out_endpoint_delete(ep); - return -ENOMEM; + 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; } - if (ep_info->out_interval) - usb_fill_int_urb(ep->urb, umidi->chip->dev, pipe, buffer, - ep->max_transfer, snd_usbmidi_out_urb_complete, - ep, ep_info->out_interval); - else - usb_fill_bulk_urb(ep->urb, umidi->chip->dev, - pipe, buffer, ep->max_transfer, - snd_usbmidi_out_urb_complete, ep); - ep->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)) { @@ -1045,6 +1392,7 @@ static void snd_usbmidi_free(struct snd_usb_midi* umidi) if (ep->in) snd_usbmidi_in_endpoint_delete(ep->in); } + mutex_destroy(&umidi->mutex); kfree(umidi); } @@ -1054,7 +1402,7 @@ static void snd_usbmidi_free(struct snd_usb_midi* umidi) void snd_usbmidi_disconnect(struct list_head* p) { struct snd_usb_midi* umidi; - int i; + unsigned int i, j; umidi = list_entry(p, struct snd_usb_midi, list); /* @@ -1062,23 +1410,41 @@ void snd_usbmidi_disconnect(struct list_head* p) * 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 && ep->out->urb) { - usb_kill_urb(ep->out->urb); + 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) - usb_kill_urb(ep->in->urb); + 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) { @@ -1087,12 +1453,11 @@ static void snd_usbmidi_rawmidi_free(struct snd_rawmidi *rmidi) } static struct snd_rawmidi_substream *snd_usbmidi_find_substream(struct snd_usb_midi* umidi, - int stream, int number) + int stream, int number) { - struct list_head* list; + struct snd_rawmidi_substream *substream; - list_for_each(list, &umidi->rmidi->streams[stream].substreams) { - struct snd_rawmidi_substream *substream = list_entry(list, struct snd_rawmidi_substream, list); + list_for_each_entry(substream, &umidi->rmidi->streams[stream].substreams, list) { if (substream->number == number) return substream; } @@ -1210,8 +1575,41 @@ static struct port_info { 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"), @@ -1222,6 +1620,17 @@ static struct port_info { 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) @@ -1229,7 +1638,7 @@ 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->chip->usb_id && + if (snd_usbmidi_port_info[i].id == umidi->usb_id && snd_usbmidi_port_info[i].port == number) return &snd_usbmidi_port_info[i]; } @@ -1259,7 +1668,7 @@ static void snd_usbmidi_init_substream(struct snd_usb_midi* umidi, struct snd_rawmidi_substream *substream = snd_usbmidi_find_substream(umidi, stream, number); if (!substream) { - snd_printd(KERN_ERR "substream %d:%d not found\n", stream, number); + dev_err(&umidi->dev->dev, "substream %d:%d not found\n", stream, number); return; } @@ -1267,7 +1676,7 @@ static void snd_usbmidi_init_substream(struct snd_usb_midi* umidi, 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->chip->card->shortname, number + 1); + name_format, umidi->card->shortname, number + 1); *rsubstream = substream; } @@ -1308,7 +1717,7 @@ static int snd_usbmidi_create_endpoints(struct snd_usb_midi* umidi, } } } - snd_printdd(KERN_INFO "created %d output and %d input ports\n", + dev_dbg(&umidi->dev->dev, "created %d output and %d input ports\n", out_ports, in_ports); return 0; } @@ -1337,36 +1746,37 @@ static int snd_usbmidi_get_ms_info(struct snd_usb_midi* umidi, if (hostif->extralen >= 7 && ms_header->bLength >= 7 && ms_header->bDescriptorType == USB_DT_CS_INTERFACE && - ms_header->bDescriptorSubtype == HEADER) - snd_printdd(KERN_INFO "MIDIStreaming version %02x.%02x\n", + ms_header->bDescriptorSubtype == UAC_HEADER) + dev_dbg(&umidi->dev->dev, "MIDIStreaming version %02x.%02x\n", ms_header->bcdMSC[1], ms_header->bcdMSC[0]); else - snd_printk(KERN_WARNING "MIDIStreaming interface descriptor not found\n"); + 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 ((ep->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) != USB_ENDPOINT_XFER_BULK && - (ep->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) != USB_ENDPOINT_XFER_INT) + 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 != MS_GENERAL) + ms_ep->bDescriptorSubtype != UAC_MS_GENERAL) continue; - if ((ep->bEndpointAddress & USB_ENDPOINT_DIR_MASK) == USB_DIR_OUT) { + if (usb_endpoint_dir_out(ep)) { if (endpoints[epidx].out_ep) { if (++epidx >= MIDI_MAX_ENDPOINTS) { - snd_printk(KERN_WARNING "too many endpoints\n"); + dev_warn(&umidi->dev->dev, + "too many endpoints\n"); break; } } - endpoints[epidx].out_ep = ep->bEndpointAddress & USB_ENDPOINT_NUMBER_MASK; - if ((ep->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) == USB_ENDPOINT_XFER_INT) + endpoints[epidx].out_ep = usb_endpoint_num(ep); + if (usb_endpoint_xfer_int(ep)) endpoints[epidx].out_interval = ep->bInterval; - else if (snd_usb_get_speed(umidi->chip->dev) == USB_SPEED_LOW) + 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 @@ -1374,28 +1784,69 @@ static int snd_usbmidi_get_ms_info(struct snd_usb_midi* umidi, */ endpoints[epidx].out_interval = 1; endpoints[epidx].out_cables = (1 << ms_ep->bNumEmbMIDIJack) - 1; - snd_printdd(KERN_INFO "EP %02X: %d jack(s)\n", + 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) { - snd_printk(KERN_WARNING "too many endpoints\n"); + dev_warn(&umidi->dev->dev, + "too many endpoints\n"); break; } } - endpoints[epidx].in_ep = ep->bEndpointAddress & USB_ENDPOINT_NUMBER_MASK; - if ((ep->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) == USB_ENDPOINT_XFER_INT) + endpoints[epidx].in_ep = usb_endpoint_num(ep); + if (usb_endpoint_xfer_int(ep)) endpoints[epidx].in_interval = ep->bInterval; - else if (snd_usb_get_speed(umidi->chip->dev) == USB_SPEED_LOW) + 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; - snd_printdd(KERN_INFO "EP %02X: %d jack(s)\n", + 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. @@ -1417,10 +1868,14 @@ static void snd_usbmidi_switch_roland_altsetting(struct snd_usb_midi* umidi) (get_endpoint(hostif, 1)->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) != USB_ENDPOINT_XFER_INT) return; - snd_printdd(KERN_INFO "switching to altsetting %d with int ep\n", + dev_dbg(&umidi->dev->dev, "switching to altsetting %d with int ep\n", intfd->bAlternateSetting); - usb_set_interface(umidi->chip->dev, intfd->bInterfaceNumber, + 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; } /* @@ -1436,11 +1891,11 @@ static int snd_usbmidi_detect_endpoints(struct snd_usb_midi* umidi, struct usb_endpoint_descriptor* epd; int i, out_eps = 0, in_eps = 0; - if (USB_ID_VENDOR(umidi->chip->usb_id) == 0x0582) + 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; + return 0; intf = umidi->iface; if (!intf || intf->num_altsetting < 1) @@ -1450,20 +1905,20 @@ static int snd_usbmidi_detect_endpoints(struct snd_usb_midi* umidi, for (i = 0; i < intfd->bNumEndpoints; ++i) { epd = get_endpoint(hostif, i); - if ((epd->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) != USB_ENDPOINT_XFER_BULK && - (epd->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) != USB_ENDPOINT_XFER_INT) + if (!usb_endpoint_xfer_bulk(epd) && + !usb_endpoint_xfer_int(epd)) continue; if (out_eps < max_endpoints && - (epd->bEndpointAddress & USB_ENDPOINT_DIR_MASK) == USB_DIR_OUT) { - endpoint[out_eps].out_ep = epd->bEndpointAddress & USB_ENDPOINT_NUMBER_MASK; - if ((epd->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) == USB_ENDPOINT_XFER_INT) + 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 && - (epd->bEndpointAddress & USB_ENDPOINT_DIR_MASK) == USB_DIR_IN) { - endpoint[in_eps].in_ep = epd->bEndpointAddress & USB_ENDPOINT_NUMBER_MASK; - if ((epd->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) == USB_ENDPOINT_XFER_INT) + 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; } @@ -1478,7 +1933,7 @@ 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) @@ -1516,9 +1971,9 @@ static int snd_usbmidi_detect_yamaha(struct snd_usb_midi* umidi, 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] == MIDI_IN_JACK) + if (cs_desc[2] == UAC_MIDI_IN_JACK) endpoint->in_cables = (endpoint->in_cables << 1) | 1; - else if (cs_desc[2] == MIDI_OUT_JACK) + else if (cs_desc[2] == UAC_MIDI_OUT_JACK) endpoint->out_cables = (endpoint->out_cables << 1) | 1; } } @@ -1529,6 +1984,44 @@ static int snd_usbmidi_detect_yamaha(struct snd_usb_midi* umidi, } /* + * 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, @@ -1557,32 +2050,31 @@ static int snd_usbmidi_create_endpoints_midiman(struct snd_usb_midi* umidi, * input bulk endpoints (at indices 1 and 3) which aren't used. */ if (intfd->bNumEndpoints < (endpoint->out_cables > 0x0001 ? 5 : 3)) { - snd_printdd(KERN_ERR "not enough endpoints\n"); + dev_dbg(&umidi->dev->dev, "not enough endpoints\n"); return -ENOENT; } epd = get_endpoint(hostif, 0); - if ((epd->bEndpointAddress & USB_ENDPOINT_DIR_MASK) != USB_DIR_IN || - (epd->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) != USB_ENDPOINT_XFER_INT) { - snd_printdd(KERN_ERR "endpoint[0] isn't interrupt\n"); + 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 ((epd->bEndpointAddress & USB_ENDPOINT_DIR_MASK) != USB_DIR_OUT || - (epd->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) != USB_ENDPOINT_XFER_BULK) { - snd_printdd(KERN_ERR "endpoint[2] isn't bulk output\n"); + 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 ((epd->bEndpointAddress & USB_ENDPOINT_DIR_MASK) != USB_DIR_OUT || - (epd->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) != USB_ENDPOINT_XFER_BULK) { - snd_printdd(KERN_ERR "endpoint[4] isn't bulk output\n"); + 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) @@ -1624,12 +2116,12 @@ static int snd_usbmidi_create_rawmidi(struct snd_usb_midi* umidi, struct snd_rawmidi *rmidi; int err; - err = snd_rawmidi_new(umidi->chip->card, "USB MIDI", - umidi->chip->next_midi_device++, + 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->chip->card->shortname); + strcpy(rmidi->name, umidi->card->shortname); rmidi->info_flags = SNDRV_RAWMIDI_INFO_OUTPUT | SNDRV_RAWMIDI_INFO_INPUT | SNDRV_RAWMIDI_INFO_DUPLEX; @@ -1649,21 +2141,30 @@ static int snd_usbmidi_create_rawmidi(struct snd_usb_midi* umidi, void snd_usbmidi_input_stop(struct list_head* p) { struct snd_usb_midi* umidi; - int i; + 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) - usb_kill_urb(ep->in->urb); + 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) { - if (ep) { - struct urb* urb = ep->urb; - urb->dev = ep->umidi->chip->dev; + 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); } } @@ -1677,16 +2178,21 @@ void snd_usbmidi_input_start(struct list_head* p) 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); /* * Creates and registers everything needed for a MIDI streaming interface. */ -int snd_usb_create_midi_interface(struct snd_usb_audio* chip, - struct usb_interface* iface, - const struct snd_usb_audio_quirk* quirk) +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]; @@ -1696,12 +2202,17 @@ int snd_usb_create_midi_interface(struct snd_usb_audio* chip, umidi = kzalloc(sizeof(*umidi), GFP_KERNEL); if (!umidi) return -ENOMEM; - umidi->chip = chip; + umidi->dev = interface_to_usbdev(iface); + umidi->card = card; umidi->iface = iface; umidi->quirk = quirk; umidi->usb_protocol_ops = &snd_usbmidi_standard_ops; init_timer(&umidi->error_timer); 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)); umidi->error_timer.function = snd_usbmidi_error_timer; umidi->error_timer.data = (unsigned long)umidi; @@ -1710,10 +2221,13 @@ int snd_usb_create_midi_interface(struct snd_usb_audio* chip, switch (quirk ? quirk->type : QUIRK_MIDI_STANDARD_INTERFACE) { case QUIRK_MIDI_STANDARD_INTERFACE: err = snd_usbmidi_get_ms_info(umidi, endpoints); - if (chip->usb_id == USB_ID(0x0763, 0x0150)) /* M-Audio Uno */ + 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)); @@ -1722,6 +2236,9 @@ int snd_usb_create_midi_interface(struct snd_usb_audio* chip, 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, @@ -1732,8 +2249,19 @@ int snd_usb_create_midi_interface(struct snd_usb_audio* chip, umidi->usb_protocol_ops = &snd_usbmidi_novation_ops; err = snd_usbmidi_detect_per_port_endpoints(umidi, endpoints); break; - case QUIRK_MIDI_RAW: + 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: @@ -1746,8 +2274,25 @@ int snd_usb_create_midi_interface(struct snd_usb_audio* chip, 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: - snd_printd(KERN_ERR "invalid quirk type %d\n", quirk->type); + dev_err(&umidi->dev->dev, "invalid quirk type %d\n", quirk->type); err = -ENXIO; break; } @@ -1760,8 +2305,8 @@ int snd_usb_create_midi_interface(struct snd_usb_audio* chip, out_ports = 0; in_ports = 0; for (i = 0; i < MIDI_MAX_ENDPOINTS; ++i) { - out_ports += snd_usbmidi_count_bits(endpoints[i].out_cables); - in_ports += snd_usbmidi_count_bits(endpoints[i].in_cables); + 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) { @@ -1779,14 +2324,9 @@ int snd_usb_create_midi_interface(struct snd_usb_audio* chip, return err; } - list_add(&umidi->list, &umidi->chip->midi_list); + usb_autopm_get_interface_no_resume(umidi->iface); - for (i = 0; i < MIDI_MAX_ENDPOINTS; ++i) - snd_usbmidi_input_start_ep(umidi->endpoints[i].in); + list_add_tail(&umidi->list, midi_list); return 0; } - -EXPORT_SYMBOL(snd_usb_create_midi_interface); -EXPORT_SYMBOL(snd_usbmidi_input_stop); -EXPORT_SYMBOL(snd_usbmidi_input_start); -EXPORT_SYMBOL(snd_usbmidi_disconnect); +EXPORT_SYMBOL(snd_usbmidi_create); diff --git a/sound/usb/midi.h b/sound/usb/midi.h new file mode 100644 index 00000000000..2fca80b744c --- /dev/null +++ b/sound/usb/midi.h @@ -0,0 +1,50 @@ +#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); + +#endif /* __USBMIDI_H */ diff --git a/sound/usb/misc/Makefile b/sound/usb/misc/Makefile new file mode 100644 index 00000000000..ccefd815893 --- /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 00000000000..a1bab149df4 --- /dev/null +++ b/sound/usb/misc/ua101.c @@ -0,0 +1,1398 @@ +/* + * 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) +{ + unsigned long flags; + + if (test_bit(ALSA_CAPTURE_RUNNING, &ua->states)) { + snd_pcm_stream_lock_irqsave(ua->capture.substream, flags); + snd_pcm_stop(ua->capture.substream, SNDRV_PCM_STATE_XRUN); + snd_pcm_stream_unlock_irqrestore(ua->capture.substream, flags); + } +} + +static void abort_alsa_playback(struct ua101 *ua) +{ + unsigned long flags; + + if (test_bit(ALSA_PLAYBACK_RUNNING, &ua->states)) { + snd_pcm_stream_lock_irqsave(ua->playback.substream, flags); + snd_pcm_stop(ua->playback.substream, SNDRV_PCM_STATE_XRUN); + snd_pcm_stream_unlock_irqrestore(ua->playback.substream, flags); + } +} + +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 00000000000..0b728d886f0 --- /dev/null +++ b/sound/usb/mixer.c @@ -0,0 +1,2574 @@ +/* + * (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); +} + +/* 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->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(cval->mixer->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->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(cval->mixer->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->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->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->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); +} + +static int 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->mixer->ignore_ctl_error) + usb_audio_dbg(cval->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->mixer->chip; + unsigned char buf[2]; + int idx = 0, val_len, err, timeout = 10; + + validx += cval->idx_off; + + if (cval->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->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); +} + +static int 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->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_interface *mixer, + struct snd_kcontrol *kctl) +{ + struct usb_mixer_elem_info *cval = kctl->private_data; + 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; + } + cval->elem_id = &kctl->id; + cval->next_id_elem = mixer->id_elems[cval->id]; + mixer->id_elems[cval->id] = cval; + 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 */ +static void 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->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, 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, 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->mixer->chip, + "%d:%d: cannot get min/max values for control %d (id %d)\n", + cval->id, snd_usb_ctrl_intf(cval->mixer->chip), + cval->control, cval->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 || + 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; + } + 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->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 = get_cur_mix_value(cval, c + 1, cnt, &val); + if (err < 0) + return cval->mixer->ignore_ctl_error ? 0 : err; + val = get_relative_value(cval, val); + ucontrol->value.integer.value[cnt] = val; + cnt++; + } + return 0; + } else { + /* master channel */ + err = get_cur_mix_value(cval, 0, 0, &val); + if (err < 0) + return cval->mixer->ignore_ctl_error ? 0 : 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 = get_cur_mix_value(cval, c + 1, cnt, &oval); + if (err < 0) + return cval->mixer->ignore_ctl_error ? 0 : err; + val = ucontrol->value.integer.value[cnt]; + val = get_abs_value(cval, val); + if (oval != val) { + set_cur_mix_value(cval, c + 1, cnt, val); + changed = 1; + } + cnt++; + } + } else { + /* master channel */ + err = get_cur_mix_value(cval, 0, 0, &oval); + if (err < 0) + return cval->mixer->ignore_ctl_error ? 0 : err; + val = ucontrol->value.integer.value[0]; + val = get_abs_value(cval, val); + if (val != oval) { + 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; + cval->mixer = state->mixer; + cval->id = unitid; + cval->control = control; + cval->cmask = ctl_mask; + cval->val_type = audio_feature_info[control-1].type; + if (ctl_mask == 0) { + cval->channels = 1; /* master channel */ + 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. 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 = 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) + 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) + len = append_ctl_name(kctl, " Capture"); + else + len = 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->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->id, kctl->id.name, cval->channels, + cval->min, cval->max, cval->res); + snd_usb_mixer_add_control(state->mixer, 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; + + cval->mixer = state->mixer; + cval->id = 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 = 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->id, kctl->id.name, cval->channels, cval->min, cval->max); + snd_usb_mixer_add_control(state->mixer, 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; + } + /* no bmControls field (e.g. Maya44) -> ignore */ + if (desc->bLength <= 10 + input_pins) { + usb_audio_dbg(state->chip, "MU %d has no bmControls field\n", + unitid); + return 0; + } + + num_ins = 0; + ich = 0; + for (pin = 0; pin < input_pins; pin++) { + err = parse_audio_unit(state, desc->baSourceID[pin]); + if (err < 0) + 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 && cval->mixer->ignore_ctl_error) { + ucontrol->value.integer.value[0] = cval->min; + return 0; + } + if (err < 0) + return err; + val = get_relative_value(cval, val); + ucontrol->value.integer.value[0] = val; + return 0; +} + +/* put callback for processing/extension unit */ +static int mixer_ctl_procunit_put(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) { + if (cval->mixer->ignore_ctl_error) + return 0; + return err; + } + val = ucontrol->value.integer.value[0]; + val = get_abs_value(cval, val); + if (val != oval) { + set_cur_ctl_value(cval, cval->control << 8, val); + return 1; + } + return 0; +} + +/* alsa control interface for processing/extension unit */ +static 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; + cval->mixer = state->mixer; + cval->id = 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 = 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->id, kctl->id.name, cval->channels, + cval->min, cval->max); + + err = snd_usb_mixer_add_control(state->mixer, 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) { + if (cval->mixer->ignore_ctl_error) { + ucontrol->value.enumerated.item[0] = 0; + return 0; + } + return err; + } + val = get_relative_value(cval, val); + ucontrol->value.enumerated.item[0] = val; + return 0; +} + +/* put callback for selector unit */ +static int mixer_ctl_selector_put(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) { + if (cval->mixer->ignore_ctl_error) + return 0; + return err; + } + val = ucontrol->value.enumerated.item[0]; + val = get_abs_value(cval, val); + if (val != oval) { + set_cur_ctl_value(cval, 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; + cval->mixer = state->mixer; + cval->id = unitid; + cval->val_type = USB_MIXER_U8; + cval->channels = 1; + cval->min = 1; + cval->max = desc->bNrInPins; + cval->res = 1; + cval->initialized = 1; + + if (desc->bDescriptorSubtype == UAC2_CLOCK_SELECTOR) + cval->control = UAC2_CX_CLOCK_SELECTOR; + else + cval->control = 0; + + 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->id, kctl->id.name, desc->bNrInPins); + if ((err = snd_usb_mixer_add_control(state->mixer, kctl)) < 0) + return err; + + return 0; +} + +/* + * 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_info *info; + + for (info = mixer->id_elems[unitid]; info; info = info->next_id_elem) + snd_ctl_notify(mixer->chip->card, SNDRV_CTL_EVENT_MASK_VALUE, + info->elem_id); +} + +static void snd_usb_mixer_dump_cval(struct snd_info_buffer *buffer, + int unitid, + struct usb_mixer_elem_info *cval) +{ + static char *val_types[] = {"BOOLEAN", "INV_BOOLEAN", + "S8", "U8", "S16", "U16"}; + snd_iprintf(buffer, " Unit: %i\n", unitid); + if (cval->elem_id) + snd_iprintf(buffer, " Control: name=\"%s\", index=%i\n", + cval->elem_id->name, cval->elem_id->index); + snd_iprintf(buffer, " Info: id=%i, control=%i, cmask=0x%x, " + "channels=%i, type=\"%s\"\n", cval->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_info *cval; + 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 (cval = mixer->id_elems[unitid]; cval; + cval = cval->next_id_elem) + snd_usb_mixer_dump_cval(buffer, unitid, cval); + } + } +} + +static void snd_usb_mixer_interrupt_v2(struct usb_mixer_interface *mixer, + int attribute, int value, int index) +{ + struct usb_mixer_elem_info *info; + __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 (info = mixer->id_elems[unitid]; info; info = info->next_id_elem) { + 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->elem_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 list_head *p) +{ + struct usb_mixer_interface *mixer; + + mixer = list_entry(p, struct usb_mixer_interface, list); + usb_kill_urb(mixer->urb); + usb_kill_urb(mixer->rc_urb); +} + +#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_info *cval) +{ + 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 = set_cur_mix_value(cval, c + 1, idx, + cval->cache_val[idx]); + if (err < 0) + return err; + } + idx++; + } + } else { + /* master */ + if (cval->cached) { + err = 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_info *cval; + int id, err; + + /* FIXME: any mixer quirks? */ + + if (reset_resume) { + /* restore cached mixer values */ + for (id = 0; id < MAX_ID_ELEMS; id++) { + for (cval = mixer->id_elems[id]; cval; + cval = cval->next_id_elem) { + err = restore_mixer_value(cval); + if (err < 0) + return err; + } + } + } + + return snd_usb_mixer_activate(mixer); +} +#endif diff --git a/sound/usb/mixer.h b/sound/usb/mixer.h new file mode 100644 index 00000000000..73b1f649447 --- /dev/null +++ b/sound/usb/mixer.h @@ -0,0 +1,78 @@ +#ifndef __USBMIXER_H +#define __USBMIXER_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_info **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]; + + u8 audigy2nx_leds[3]; + u8 xonar_u1_status; +}; + +#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, +}; + +struct usb_mixer_elem_info { + struct usb_mixer_interface *mixer; + struct usb_mixer_elem_info *next_id_elem; /* list of controls with same id */ + struct snd_ctl_elem_id *elem_id; + unsigned int id; + unsigned int control; /* CS or ICN (high byte) */ + unsigned int cmask; /* channel mask bitmap: 0 = master */ + 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; +}; + +int snd_usb_create_mixer(struct snd_usb_audio *chip, int ctrlif, + int ignore_error); +void snd_usb_mixer_disconnect(struct list_head *p); + +void snd_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_interface *mixer, + struct snd_kcontrol *kctl); + +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 + +#endif /* __USBMIXER_H */ diff --git a/sound/usb/usbmixer_maps.c b/sound/usb/mixer_maps.c index d755be0ad81..d1d72ff5034 100644 --- a/sound/usb/usbmixer_maps.c +++ b/sound/usb/mixer_maps.c @@ -19,11 +19,16 @@ * */ +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 { @@ -72,7 +77,7 @@ static struct usbmix_name_map extigy_map[] = { { 8, "Line Playback" }, /* FU */ /* 9: IT mic */ { 10, "Mic Playback" }, /* FU */ - { 11, "Capture Input Source" }, /* SU */ + { 11, "Capture Source" }, /* SU */ { 12, "Capture" }, /* FU */ /* 13: OT pcm capture */ /* 14: MU (w/o controls) */ @@ -80,8 +85,8 @@ static struct usbmix_name_map extigy_map[] = { /* 16: MU (w/o controls) */ { 17, NULL, 1 }, /* DISABLED: PU-switch (any effect?) */ { 17, "Channel Routing", 2 }, /* PU: mode select */ - { 18, "Tone Control - Bass", USB_FEATURE_BASS }, /* FU */ - { 18, "Tone Control - Treble", USB_FEATURE_TREBLE }, /* FU */ + { 18, "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 */ @@ -102,6 +107,9 @@ static struct usbmix_name_map extigy_map[] = { * 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 */ @@ -110,16 +118,19 @@ static struct usbmix_name_map mp3plus_map[] = { /* 5: OT digital out */ /* 6: OT speaker */ /* 7: OT pcm capture */ - { 8, "Capture Input Source" }, /* FU, default PCM Capture Source */ + { 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", 2 }, */ /* 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" }, /* FU, default PCM Capture */ + { 11, "Line Capture", .dB = &mp3plus_dB_2 }, + /* FU, default PCM Capture */ { 12, "Digital In Playback" }, /* FU, default PCM 1 */ - /* { 13, "Mic Playback" }, */ /* FU, default Mic Playback */ - { 14, "Line Playback" }, /* FU, default Speaker */ + { 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 */ }; @@ -168,6 +179,15 @@ static struct usbmix_name_map audigy2nx_map[] = { { 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 */ @@ -261,6 +281,58 @@ static struct usbmix_name_map aureon_51_2_map[] = { {} /* 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 */ + {} +}; + +static const struct usbmix_name_map kef_x300a_map[] = { + { 10, NULL }, /* firmware locks up (?) when we try to access this FU */ + { 0 } +}; + /* * Control map entries */ @@ -285,6 +357,23 @@ static struct usbmix_ctl_map usbmix_ctl_maps[] = { .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, @@ -295,6 +384,21 @@ static struct usbmix_ctl_map usbmix_ctl_maps[] = { .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, @@ -311,6 +415,19 @@ static struct usbmix_ctl_map usbmix_ctl_maps[] = { .id = USB_ID(0x0ccd, 0x0028), .map = aureon_51_2_map, }, + { + .id = USB_ID(0x13e5, 0x0001), + .map = scratch_live_map, + .ignore_ctl_error = 1, + }, + { + .id = USB_ID(0x200c, 0x1018), + .map = ebox44_map, + }, + { + .id = USB_ID(0x27ac, 0x1000), + .map = kef_x300a_map, + }, { 0 } /* terminator */ }; diff --git a/sound/usb/mixer_quirks.c b/sound/usb/mixer_quirks.c new file mode 100644 index 00000000000..f119a41ed9a --- /dev/null +++ b/sound/usb/mixer_quirks.c @@ -0,0 +1,1706 @@ +/* + * 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 "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; +}; + +/* private_free callback */ +static void usb_mixer_elem_free(struct snd_kcontrol *kctl) +{ + kfree(kctl->private_data); + kctl->private_data = NULL; +} + +/* 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) +{ + int err; + struct usb_mixer_elem_info *cval; + struct snd_kcontrol *kctl; + + cval = kzalloc(sizeof(*cval), GFP_KERNEL); + if (!cval) + return -ENOMEM; + + cval->id = unitid; + cval->mixer = mixer; + 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 = 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 */ + err = snd_usb_mixer_add_control(mixer, kctl); + if (err < 0) + return err; + + return 0; +} + +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; +} + +/* + * 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, 0x3048), 2, 2, 6, 6, 2, 0x6e91 }, /* Toshiba SB0500 */ +}; + +static void snd_usb_soundblaster_remote_complete(struct urb *urb) +{ + struct usb_mixer_interface *mixer = urb->context; + const struct rc_config *rc = mixer->rc_cfg; + u32 code; + + if (urb->status < 0 || urb->actual_length < rc->min_packet_length) + return; + + code = mixer->rc_buffer[rc->offset]; + if (rc->length == 2) + code |= mixer->rc_buffer[rc->offset + 1] << 8; + + /* the Mute button actually changes the mixer control */ + if (code == rc->mute_code) + snd_usb_mixer_notify_id(mixer, rc->mute_mixer_id); + mixer->rc_code = code; + wmb(); + wake_up(&mixer->rc_waitq); +} + +static long snd_usb_sbrc_hwdep_read(struct snd_hwdep *hw, char __user *buf, + long count, loff_t *offset) +{ + struct usb_mixer_interface *mixer = hw->private_data; + int err; + u32 rc_code; + + if (count != 1 && count != 4) + return -EINVAL; + err = wait_event_interruptible(mixer->rc_waitq, + (rc_code = xchg(&mixer->rc_code, 0)) != 0); + if (err == 0) { + if (count == 1) + err = put_user(rc_code, buf); + else + err = put_user(rc_code, (u32 __user *)buf); + } + return err < 0 ? err : count; +} + +static unsigned int snd_usb_sbrc_hwdep_poll(struct snd_hwdep *hw, struct file *file, + poll_table *wait) +{ + struct usb_mixer_interface *mixer = hw->private_data; + + poll_wait(file, &mixer->rc_waitq, wait); + return mixer->rc_code ? POLLIN | POLLRDNORM : 0; +} + +static int snd_usb_soundblaster_remote_init(struct usb_mixer_interface *mixer) +{ + struct snd_hwdep *hwdep; + int err, len, i; + + for (i = 0; i < ARRAY_SIZE(rc_configs); ++i) + if (rc_configs[i].usb_id == mixer->chip->usb_id) + break; + if (i >= ARRAY_SIZE(rc_configs)) + return 0; + mixer->rc_cfg = &rc_configs[i]; + + len = mixer->rc_cfg->packet_length; + + init_waitqueue_head(&mixer->rc_waitq); + err = snd_hwdep_new(mixer->chip->card, "SB remote control", 0, &hwdep); + if (err < 0) + return err; + snprintf(hwdep->name, sizeof(hwdep->name), + "%s remote control", mixer->chip->card->shortname); + hwdep->iface = SNDRV_HWDEP_IFACE_SB_RC; + hwdep->private_data = mixer; + hwdep->ops.read = snd_usb_sbrc_hwdep_read; + hwdep->ops.poll = snd_usb_sbrc_hwdep_poll; + hwdep->exclusive = 1; + + mixer->rc_urb = usb_alloc_urb(0, GFP_KERNEL); + if (!mixer->rc_urb) + return -ENOMEM; + mixer->rc_setup_packet = kmalloc(sizeof(*mixer->rc_setup_packet), GFP_KERNEL); + if (!mixer->rc_setup_packet) { + usb_free_urb(mixer->rc_urb); + mixer->rc_urb = NULL; + return -ENOMEM; + } + mixer->rc_setup_packet->bRequestType = + USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE; + mixer->rc_setup_packet->bRequest = UAC_GET_MEM; + mixer->rc_setup_packet->wValue = cpu_to_le16(0); + mixer->rc_setup_packet->wIndex = cpu_to_le16(0); + mixer->rc_setup_packet->wLength = cpu_to_le16(len); + usb_fill_control_urb(mixer->rc_urb, mixer->chip->dev, + usb_rcvctrlpipe(mixer->chip->dev, 0), + (u8*)mixer->rc_setup_packet, mixer->rc_buffer, len, + snd_usb_soundblaster_remote_complete, mixer); + return 0; +} + +#define snd_audigy2nx_led_info snd_ctl_boolean_mono_info + +static int snd_audigy2nx_led_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_interface *mixer = snd_kcontrol_chip(kcontrol); + int index = kcontrol->private_value; + + ucontrol->value.integer.value[0] = mixer->audigy2nx_leds[index]; + return 0; +} + +static int snd_audigy2nx_led_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_interface *mixer = snd_kcontrol_chip(kcontrol); + int index = kcontrol->private_value; + int value = ucontrol->value.integer.value[0]; + int err, changed; + + if (value > 1) + return -EINVAL; + changed = value != mixer->audigy2nx_leds[index]; + down_read(&mixer->chip->shutdown_rwsem); + if (mixer->chip->shutdown) { + err = -ENODEV; + goto out; + } + if (mixer->chip->usb_id == USB_ID(0x041e, 0x3042)) + err = snd_usb_ctl_msg(mixer->chip->dev, + usb_sndctrlpipe(mixer->chip->dev, 0), 0x24, + USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_OTHER, + !value, 0, NULL, 0); + /* USB X-Fi S51 Pro */ + if (mixer->chip->usb_id == USB_ID(0x041e, 0x30df)) + err = snd_usb_ctl_msg(mixer->chip->dev, + usb_sndctrlpipe(mixer->chip->dev, 0), 0x24, + USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_OTHER, + !value, 0, NULL, 0); + else + err = snd_usb_ctl_msg(mixer->chip->dev, + usb_sndctrlpipe(mixer->chip->dev, 0), 0x24, + USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_OTHER, + value, index + 2, NULL, 0); + out: + up_read(&mixer->chip->shutdown_rwsem); + if (err < 0) + return err; + mixer->audigy2nx_leds[index] = value; + return changed; +} + +static struct snd_kcontrol_new snd_audigy2nx_controls[] = { + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "CMSS LED Switch", + .info = snd_audigy2nx_led_info, + .get = snd_audigy2nx_led_get, + .put = snd_audigy2nx_led_put, + .private_value = 0, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Power LED Switch", + .info = snd_audigy2nx_led_info, + .get = snd_audigy2nx_led_get, + .put = snd_audigy2nx_led_put, + .private_value = 1, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Dolby Digital LED Switch", + .info = snd_audigy2nx_led_info, + .get = snd_audigy2nx_led_get, + .put = snd_audigy2nx_led_put, + .private_value = 2, + }, +}; + +static int snd_audigy2nx_controls_create(struct usb_mixer_interface *mixer) +{ + int i, err; + + for (i = 0; i < ARRAY_SIZE(snd_audigy2nx_controls); ++i) { + /* 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; + err = snd_ctl_add(mixer->chip->card, + snd_ctl_new1(&snd_audigy2nx_controls[i], mixer)); + if (err < 0) + return err; + } + mixer->audigy2nx_leds[1] = 1; /* Power LED is on by default */ + return 0; +} + +static void snd_audigy2nx_proc_read(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + static const struct sb_jack { + int unitid; + const char *name; + } jacks_audigy2nx[] = { + {4, "dig in "}, + {7, "line in"}, + {19, "spk out"}, + {20, "hph out"}, + {-1, NULL} + }, jacks_live24ext[] = { + {4, "line in"}, /* &1=Line, &2=Mic*/ + {3, "hph out"}, /* headphones */ + {0, "RC "}, /* last command, 6 bytes see rc_config above */ + {-1, NULL} + }; + const struct sb_jack *jacks; + struct usb_mixer_interface *mixer = entry->private_data; + int i, err; + u8 buf[3]; + + snd_iprintf(buffer, "%s jacks\n\n", mixer->chip->card->shortname); + if (mixer->chip->usb_id == USB_ID(0x041e, 0x3020)) + jacks = jacks_audigy2nx; + else if (mixer->chip->usb_id == USB_ID(0x041e, 0x3040) || + mixer->chip->usb_id == USB_ID(0x041e, 0x3048)) + jacks = jacks_live24ext; + else + return; + + for (i = 0; jacks[i].name; ++i) { + snd_iprintf(buffer, "%s: ", jacks[i].name); + 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 *texts[2] = {"1/2", + "3/4" + }; + + uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; + uinfo->count = 1; + uinfo->value.enumerated.items = 2; + if (uinfo->value.enumerated.item > 1) + uinfo->value.enumerated.item = 1; + strcpy(uinfo->value.enumerated.name, + texts[uinfo->value.enumerated.item]); + + return 0; +} + +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_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_interface *mixer = snd_kcontrol_chip(kcontrol); + unsigned int value = ucontrol->value.enumerated.item[0]; + int err, changed; + unsigned char buf[2]; + + if (value > 1) + return -EINVAL; + + buf[0] = 0x01; + buf[1] = value ? 0x02 : 0x01; + + changed = value != kcontrol->private_value; + down_read(&mixer->chip->shutdown_rwsem); + if (mixer->chip->shutdown) { + err = -ENODEV; + goto out; + } + err = snd_usb_ctl_msg(mixer->chip->dev, + usb_sndctrlpipe(mixer->chip->dev, 0), UAC_SET_CUR, + USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_OUT, + 0x0400, 0x0e00, buf, 2); + out: + up_read(&mixer->chip->shutdown_rwsem); + if (err < 0) + return err; + kcontrol->private_value = value; + return changed; +} + + +static struct snd_kcontrol_new snd_emu0204_controls[] = { + { + .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) +{ + int i, err; + + for (i = 0; i < ARRAY_SIZE(snd_emu0204_controls); ++i) { + err = snd_ctl_add(mixer->chip->card, + snd_ctl_new1(&snd_emu0204_controls[i], mixer)); + if (err < 0) + return err; + } + + return 0; +} +/* ASUS Xonar U1 / U3 controls */ + +static int snd_xonar_u1_switch_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_interface *mixer = snd_kcontrol_chip(kcontrol); + + ucontrol->value.integer.value[0] = !!(mixer->xonar_u1_status & 0x02); + return 0; +} + +static int snd_xonar_u1_switch_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_interface *mixer = snd_kcontrol_chip(kcontrol); + u8 old_status, new_status; + int err, changed; + + old_status = mixer->xonar_u1_status; + if (ucontrol->value.integer.value[0]) + new_status = old_status | 0x02; + else + new_status = old_status & ~0x02; + changed = new_status != old_status; + down_read(&mixer->chip->shutdown_rwsem); + if (mixer->chip->shutdown) + err = -ENODEV; + else + err = snd_usb_ctl_msg(mixer->chip->dev, + usb_sndctrlpipe(mixer->chip->dev, 0), 0x08, + USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_OTHER, + 50, 0, &new_status, 1); + up_read(&mixer->chip->shutdown_rwsem); + if (err < 0) + return err; + mixer->xonar_u1_status = new_status; + return changed; +} + +static struct snd_kcontrol_new snd_xonar_u1_output_switch = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Digital Playback Switch", + .info = snd_ctl_boolean_mono_info, + .get = snd_xonar_u1_switch_get, + .put = snd_xonar_u1_switch_put, +}; + +static int snd_xonar_u1_controls_create(struct usb_mixer_interface *mixer) +{ + int err; + + err = snd_ctl_add(mixer->chip->card, + snd_ctl_new1(&snd_xonar_u1_output_switch, mixer)); + if (err < 0) + return err; + mixer->xonar_u1_status = 0x05; + return 0; +} + +/* Native Instruments device quirks */ + +#define _MAKE_NI_CONTROL(bRequest,wIndex) ((bRequest) << 16 | (wIndex)) + +static int snd_nativeinstruments_control_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_interface *mixer = snd_kcontrol_chip(kcontrol); + struct usb_device *dev = mixer->chip->dev; + u8 bRequest = (kcontrol->private_value >> 16) & 0xff; + u16 wIndex = kcontrol->private_value & 0xffff; + u8 tmp; + int ret; + + down_read(&mixer->chip->shutdown_rwsem); + if (mixer->chip->shutdown) + ret = -ENODEV; + else + ret = usb_control_msg(dev, usb_rcvctrlpipe(dev, 0), bRequest, + USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_IN, + 0, wIndex, + &tmp, sizeof(tmp), 1000); + up_read(&mixer->chip->shutdown_rwsem); + + if (ret < 0) { + dev_err(&dev->dev, + "unable to issue vendor read request (ret = %d)", ret); + return ret; + } + + ucontrol->value.integer.value[0] = tmp; + + return 0; +} + +static int snd_nativeinstruments_control_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_interface *mixer = snd_kcontrol_chip(kcontrol); + struct usb_device *dev = mixer->chip->dev; + u8 bRequest = (kcontrol->private_value >> 16) & 0xff; + u16 wIndex = kcontrol->private_value & 0xffff; + u16 wValue = ucontrol->value.integer.value[0]; + int ret; + + down_read(&mixer->chip->shutdown_rwsem); + if (mixer->chip->shutdown) + ret = -ENODEV; + else + ret = usb_control_msg(dev, usb_sndctrlpipe(dev, 0), bRequest, + USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_OUT, + wValue, wIndex, + NULL, 0, 1000); + up_read(&mixer->chip->shutdown_rwsem); + + if (ret < 0) { + dev_err(&dev->dev, + "unable to issue vendor write request (ret = %d)", ret); + return ret; + } + + return 0; +} + +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 snd_kcontrol *c; + + template.name = kc[i].name; + template.private_value = kc[i].private_value; + + c = snd_ctl_new1(&template, mixer); + err = snd_ctl_add(mixer->chip->card, c); + + if (err < 0) + break; + } + + return err; +} + +/* M-Audio FastTrack Ultra quirks */ +/* FTU Effect switch (also used by C400/C600) */ +struct snd_ftu_eff_switch_priv_val { + struct usb_mixer_interface *mixer; + int cached_value; + int is_cached; + int bUnitID; + int validx; +}; + +static int snd_ftu_eff_switch_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + static const char *texts[8] = {"Room 1", + "Room 2", + "Room 3", + "Hall 1", + "Hall 2", + "Plate", + "Delay", + "Echo" + }; + + uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; + uinfo->count = 1; + uinfo->value.enumerated.items = 8; + if (uinfo->value.enumerated.item > 7) + uinfo->value.enumerated.item = 7; + strcpy(uinfo->value.enumerated.name, + texts[uinfo->value.enumerated.item]); + + return 0; +} + +static int snd_ftu_eff_switch_get(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_usb_audio *chip; + struct usb_mixer_interface *mixer; + struct snd_ftu_eff_switch_priv_val *pval; + int err; + unsigned char value[2]; + int id, validx; + + const int val_len = 2; + + value[0] = 0x00; + value[1] = 0x00; + + pval = (struct snd_ftu_eff_switch_priv_val *) + kctl->private_value; + + if (pval->is_cached) { + ucontrol->value.enumerated.item[0] = pval->cached_value; + return 0; + } + + mixer = (struct usb_mixer_interface *) pval->mixer; + if (snd_BUG_ON(!mixer)) + return -EINVAL; + + chip = (struct snd_usb_audio *) mixer->chip; + if (snd_BUG_ON(!chip)) + return -EINVAL; + + id = pval->bUnitID; + validx = pval->validx; + + down_read(&mixer->chip->shutdown_rwsem); + if (mixer->chip->shutdown) + err = -ENODEV; + else + err = snd_usb_ctl_msg(chip->dev, + usb_rcvctrlpipe(chip->dev, 0), UAC_GET_CUR, + USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_IN, + validx << 8, snd_usb_ctrl_intf(chip) | (id << 8), + value, val_len); + up_read(&mixer->chip->shutdown_rwsem); + if (err < 0) + return err; + + ucontrol->value.enumerated.item[0] = value[0]; + pval->cached_value = value[0]; + pval->is_cached = 1; + + return 0; +} + +static int snd_ftu_eff_switch_put(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_usb_audio *chip; + struct snd_ftu_eff_switch_priv_val *pval; + + struct usb_mixer_interface *mixer; + int changed, cur_val, err, new_val; + unsigned char value[2]; + int id, validx; + + const int val_len = 2; + + changed = 0; + + pval = (struct snd_ftu_eff_switch_priv_val *) + kctl->private_value; + cur_val = pval->cached_value; + new_val = ucontrol->value.enumerated.item[0]; + + mixer = (struct usb_mixer_interface *) pval->mixer; + if (snd_BUG_ON(!mixer)) + return -EINVAL; + + chip = (struct snd_usb_audio *) mixer->chip; + if (snd_BUG_ON(!chip)) + return -EINVAL; + + id = pval->bUnitID; + validx = pval->validx; + + if (!pval->is_cached) { + /* Read current value */ + down_read(&mixer->chip->shutdown_rwsem); + if (mixer->chip->shutdown) + err = -ENODEV; + else + err = snd_usb_ctl_msg(chip->dev, + usb_rcvctrlpipe(chip->dev, 0), UAC_GET_CUR, + USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_IN, + validx << 8, snd_usb_ctrl_intf(chip) | (id << 8), + value, val_len); + up_read(&mixer->chip->shutdown_rwsem); + if (err < 0) + return err; + + cur_val = value[0]; + pval->cached_value = cur_val; + pval->is_cached = 1; + } + /* update value if needed */ + if (cur_val != new_val) { + value[0] = new_val; + value[1] = 0; + down_read(&mixer->chip->shutdown_rwsem); + if (mixer->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, + validx << 8, snd_usb_ctrl_intf(chip) | (id << 8), + value, val_len); + up_read(&mixer->chip->shutdown_rwsem); + if (err < 0) + return err; + + pval->cached_value = new_val; + pval->is_cached = 1; + changed = 1; + } + + return changed; +} + +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 + }; + + int err; + struct snd_kcontrol *kctl; + struct snd_ftu_eff_switch_priv_val *pval; + + pval = kzalloc(sizeof(*pval), GFP_KERNEL); + if (!pval) + return -ENOMEM; + + pval->cached_value = 0; + pval->is_cached = 0; + pval->mixer = mixer; + pval->bUnitID = bUnitID; + pval->validx = validx; + + template.private_value = (unsigned long) pval; + kctl = snd_ctl_new1(&template, mixer->chip); + if (!kctl) { + kfree(pval); + return -ENOMEM; + } + + err = snd_ctl_add(mixer->chip->card, kctl); + if (err < 0) + return err; + + return 0; +} + +/* Create volume controls for FTU devices*/ +static int snd_ftu_create_volume_ctls(struct usb_mixer_interface *mixer) +{ + char name[64]; + unsigned int control, cmask; + int in, out, err; + + const unsigned int id = 5; + const int val_type = USB_MIXER_S16; + + for (out = 0; out < 8; out++) { + control = out + 1; + for (in = 0; in < 8; in++) { + cmask = 1 << in; + snprintf(name, sizeof(name), + "AIn%d - Out%d Capture Volume", + in + 1, out + 1); + err = snd_create_std_mono_ctl(mixer, id, control, + cmask, val_type, name, + &snd_usb_mixer_vol_tlv); + if (err < 0) + return err; + } + for (in = 8; in < 16; in++) { + cmask = 1 << in; + snprintf(name, sizeof(name), + "DIn%d - Out%d Playback Volume", + in - 7, out + 1); + err = snd_create_std_mono_ctl(mixer, id, control, + cmask, val_type, name, + &snd_usb_mixer_vol_tlv); + if (err < 0) + return err; + } + } + + return 0; +} + +/* This control needs a volume quirk, see mixer.c */ +static int snd_ftu_create_effect_volume_ctl(struct usb_mixer_interface *mixer) +{ + static const char name[] = "Effect Volume"; + const unsigned int id = 6; + const int val_type = USB_MIXER_U8; + const unsigned int control = 2; + const unsigned int cmask = 0; + + return snd_create_std_mono_ctl(mixer, id, control, cmask, val_type, + name, snd_usb_mixer_vol_tlv); +} + +/* This control needs a volume quirk, see mixer.c */ +static int snd_ftu_create_effect_duration_ctl(struct usb_mixer_interface *mixer) +{ + static const char name[] = "Effect Duration"; + const unsigned int id = 6; + const int val_type = USB_MIXER_S16; + const unsigned int control = 3; + const unsigned int cmask = 0; + + return snd_create_std_mono_ctl(mixer, id, control, cmask, val_type, + name, snd_usb_mixer_vol_tlv); +} + +/* This control needs a volume quirk, see mixer.c */ +static int snd_ftu_create_effect_feedback_ctl(struct usb_mixer_interface *mixer) +{ + static const char name[] = "Effect Feedback Volume"; + const unsigned int id = 6; + const int val_type = USB_MIXER_U8; + const unsigned int control = 4; + const unsigned int cmask = 0; + + return snd_create_std_mono_ctl(mixer, id, control, cmask, val_type, + name, NULL); +} + +static int snd_ftu_create_effect_return_ctls(struct usb_mixer_interface *mixer) +{ + unsigned int cmask; + int err, ch; + char name[48]; + + const unsigned int id = 7; + const int val_type = USB_MIXER_S16; + const unsigned int control = 7; + + for (ch = 0; ch < 4; ++ch) { + cmask = 1 << ch; + snprintf(name, sizeof(name), + "Effect Return %d Volume", ch + 1); + err = snd_create_std_mono_ctl(mixer, id, control, + cmask, val_type, name, + snd_usb_mixer_vol_tlv); + if (err < 0) + return err; + } + + return 0; +} + +static int snd_ftu_create_effect_send_ctls(struct usb_mixer_interface *mixer) +{ + unsigned int cmask; + int err, ch; + char name[48]; + + const unsigned int id = 5; + const int val_type = USB_MIXER_S16; + const unsigned int control = 9; + + for (ch = 0; ch < 8; ++ch) { + cmask = 1 << ch; + snprintf(name, sizeof(name), + "Effect Send AIn%d Volume", ch + 1); + err = snd_create_std_mono_ctl(mixer, id, control, cmask, + val_type, name, + snd_usb_mixer_vol_tlv); + if (err < 0) + return err; + } + for (ch = 8; ch < 16; ++ch) { + cmask = 1 << ch; + snprintf(name, sizeof(name), + "Effect Send DIn%d Volume", ch - 7); + err = snd_create_std_mono_ctl(mixer, id, control, cmask, + val_type, name, + snd_usb_mixer_vol_tlv); + if (err < 0) + return err; + } + return 0; +} + +static int snd_ftu_create_mixer(struct usb_mixer_interface *mixer) +{ + int err; + + err = snd_ftu_create_volume_ctls(mixer); + if (err < 0) + return err; + + err = snd_ftu_create_effect_switch(mixer, 1, 6); + if (err < 0) + return err; + + err = snd_ftu_create_effect_volume_ctl(mixer); + if (err < 0) + return err; + + err = snd_ftu_create_effect_duration_ctl(mixer); + if (err < 0) + return err; + + err = snd_ftu_create_effect_feedback_ctl(mixer); + if (err < 0) + return err; + + err = snd_ftu_create_effect_return_ctls(mixer); + if (err < 0) + return err; + + err = snd_ftu_create_effect_send_ctls(mixer); + if (err < 0) + return err; + + return 0; +} + +void snd_emuusb_set_samplerate(struct snd_usb_audio *chip, + unsigned char samplerate_id) +{ + struct usb_mixer_interface *mixer; + struct usb_mixer_elem_info *cval; + int unitid = 12; /* SamleRate ExtensionUnit ID */ + + list_for_each_entry(mixer, &chip->mixer_list, list) { + cval = mixer->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_interface *mixer = snd_kcontrol_chip(kcontrol); + int err; + struct usb_interface *iface; + struct usb_host_interface *alts; + unsigned int ep; + unsigned char data[3]; + int rate; + + 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(mixer->chip->dev, 1); + alts = &iface->altsetting[1]; + ep = get_endpoint(alts, 0)->bEndpointAddress; + + err = snd_usb_ctl_msg(mixer->chip->dev, + usb_rcvctrlpipe(mixer->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: + return err; +} + +static int snd_microii_spdif_default_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_interface *mixer = snd_kcontrol_chip(kcontrol); + int err; + u8 reg; + unsigned long priv_backup = kcontrol->private_value; + + reg = ((ucontrol->value.iec958.status[1] & 0x0f) << 4) | + (ucontrol->value.iec958.status[0] & 0x0f); + err = snd_usb_ctl_msg(mixer->chip->dev, + usb_sndctrlpipe(mixer->chip->dev, 0), + UAC_SET_CUR, + USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_OTHER, + reg, + 2, + NULL, + 0); + if (err < 0) + goto end; + + kcontrol->private_value &= 0xfffff0f0; + kcontrol->private_value |= (ucontrol->value.iec958.status[1] & 0x0f) << 8; + kcontrol->private_value |= (ucontrol->value.iec958.status[0] & 0x0f); + + reg = (ucontrol->value.iec958.status[0] & IEC958_AES0_NONAUDIO) ? + 0xa0 : 0x20; + reg |= (ucontrol->value.iec958.status[1] >> 4) & 0x0f; + err = snd_usb_ctl_msg(mixer->chip->dev, + usb_sndctrlpipe(mixer->chip->dev, 0), + UAC_SET_CUR, + USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_OTHER, + reg, + 3, + NULL, + 0); + if (err < 0) + goto end; + + kcontrol->private_value &= 0xffff0fff; + kcontrol->private_value |= (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. */ + + err = (priv_backup != kcontrol->private_value); +end: + return err; +} + +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_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_interface *mixer = snd_kcontrol_chip(kcontrol); + int err; + u8 reg = ucontrol->value.integer.value[0] ? 0x28 : 0x2a; + + err = snd_usb_ctl_msg(mixer->chip->dev, + usb_sndctrlpipe(mixer->chip->dev, 0), + UAC_SET_CUR, + USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_OTHER, + reg, + 9, + NULL, + 0); + + if (!err) { + err = (reg != (kcontrol->private_value & 0x0ff)); + if (err) + kcontrol->private_value = reg; + } + + return err; +} + +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; + + for (i = 0; i < ARRAY_SIZE(snd_microii_mixer_spdif); ++i) { + err = snd_ctl_add(mixer->chip->card, + snd_ctl_new1(&snd_microii_mixer_spdif[i], mixer)); + 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(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; + } + + 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 00000000000..bdbfab09381 --- /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/pcm.c b/sound/usb/pcm.c new file mode 100644 index 00000000000..c62a1659106 --- /dev/null +++ b/sound/usb/pcm.c @@ -0,0 +1,1631 @@ +/* + * 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 = 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 */ + + 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: + 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 00000000000..df7a003682a --- /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 00000000000..48ee51dcb71 --- /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 00000000000..5f761ab34c0 --- /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 00000000000..a45b765e4cf --- /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/usbquirks.h b/sound/usb/quirks-table.h index 938dff5f9ce..f652b10ce90 100644 --- a/sound/usb/usbquirks.h +++ b/sound/usb/quirks-table.h @@ -39,6 +39,122 @@ .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. @@ -50,7 +166,7 @@ .idVendor = 0x046d, .idProduct = 0x0850, .bInterfaceClass = USB_CLASS_AUDIO, - .bInterfaceSubClass = USB_SUBCLASS_AUDIO_CONTROL + .bInterfaceSubClass = USB_SUBCLASS_AUDIOCONTROL }, { .match_flags = USB_DEVICE_ID_MATCH_DEVICE | @@ -59,7 +175,7 @@ .idVendor = 0x046d, .idProduct = 0x08ae, .bInterfaceClass = USB_CLASS_AUDIO, - .bInterfaceSubClass = USB_SUBCLASS_AUDIO_CONTROL + .bInterfaceSubClass = USB_SUBCLASS_AUDIOCONTROL }, { .match_flags = USB_DEVICE_ID_MATCH_DEVICE | @@ -68,7 +184,7 @@ .idVendor = 0x046d, .idProduct = 0x08c6, .bInterfaceClass = USB_CLASS_AUDIO, - .bInterfaceSubClass = USB_SUBCLASS_AUDIO_CONTROL + .bInterfaceSubClass = USB_SUBCLASS_AUDIOCONTROL }, { .match_flags = USB_DEVICE_ID_MATCH_DEVICE | @@ -77,7 +193,7 @@ .idVendor = 0x046d, .idProduct = 0x08f0, .bInterfaceClass = USB_CLASS_AUDIO, - .bInterfaceSubClass = USB_SUBCLASS_AUDIO_CONTROL + .bInterfaceSubClass = USB_SUBCLASS_AUDIOCONTROL }, { .match_flags = USB_DEVICE_ID_MATCH_DEVICE | @@ -86,7 +202,7 @@ .idVendor = 0x046d, .idProduct = 0x08f5, .bInterfaceClass = USB_CLASS_AUDIO, - .bInterfaceSubClass = USB_SUBCLASS_AUDIO_CONTROL + .bInterfaceSubClass = USB_SUBCLASS_AUDIOCONTROL }, { .match_flags = USB_DEVICE_ID_MATCH_DEVICE | @@ -95,21 +211,23 @@ .idVendor = 0x046d, .idProduct = 0x08f6, .bInterfaceClass = USB_CLASS_AUDIO, - .bInterfaceSubClass = USB_SUBCLASS_AUDIO_CONTROL + .bInterfaceSubClass = USB_SUBCLASS_AUDIOCONTROL }, -/* E-Mu devices */ { - .match_flags = USB_DEVICE_ID_MATCH_DEVICE, - .idVendor = 0x041e, - .idProduct = 0x3f02, - .bInterfaceClass = USB_CLASS_AUDIO, -}, -{ - .match_flags = USB_DEVICE_ID_MATCH_DEVICE, - .idVendor = 0x041e, - .idProduct = 0x3f04, + .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 */ @@ -198,9 +316,130 @@ 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, 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"), @@ -221,6 +460,17 @@ 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 @@ -237,7 +487,7 @@ YAMAHA_DEVICE(0x7010, "UB99"), .ifnum = 0, .type = QUIRK_AUDIO_FIXED_ENDPOINT, .data = & (const struct audioformat) { - .format = SNDRV_PCM_FORMAT_S16_LE, + .formats = SNDRV_PCM_FMTBIT_S16_LE, .channels = 4, .iface = 0, .altsetting = 1, @@ -254,12 +504,12 @@ YAMAHA_DEVICE(0x7010, "UB99"), .ifnum = 1, .type = QUIRK_AUDIO_FIXED_ENDPOINT, .data = & (const struct audioformat) { - .format = SNDRV_PCM_FORMAT_S16_LE, + .formats = SNDRV_PCM_FMTBIT_S16_LE, .channels = 2, .iface = 1, .altsetting = 1, .altset_idx = 1, - .attributes = EP_CS_ATTR_FILL_MAX, + .attributes = UAC_EP_CS_ATTR_FILL_MAX, .endpoint = 0x81, .ep_attr = 0x05, .rates = SNDRV_PCM_RATE_CONTINUOUS, @@ -538,7 +788,7 @@ YAMAHA_DEVICE(0x7010, "UB99"), .ifnum = 0, .type = QUIRK_AUDIO_FIXED_ENDPOINT, .data = & (const struct audioformat) { - .format = SNDRV_PCM_FORMAT_S24_3LE, + .formats = SNDRV_PCM_FMTBIT_S24_3LE, .channels = 2, .iface = 0, .altsetting = 1, @@ -555,7 +805,7 @@ YAMAHA_DEVICE(0x7010, "UB99"), .ifnum = 1, .type = QUIRK_AUDIO_FIXED_ENDPOINT, .data = & (const struct audioformat) { - .format = SNDRV_PCM_FORMAT_S24_3LE, + .formats = SNDRV_PCM_FMTBIT_S24_3LE, .channels = 2, .iface = 1, .altsetting = 1, @@ -651,11 +901,11 @@ YAMAHA_DEVICE(0x7010, "UB99"), .data = (const struct snd_usb_audio_quirk[]) { { .ifnum = 0, - .type = QUIRK_IGNORE_INTERFACE + .type = QUIRK_AUDIO_STANDARD_INTERFACE }, { .ifnum = 1, - .type = QUIRK_IGNORE_INTERFACE + .type = QUIRK_AUDIO_STANDARD_INTERFACE }, { .ifnum = 2, @@ -751,7 +1001,7 @@ YAMAHA_DEVICE(0x7010, "UB99"), .ifnum = 1, .type = QUIRK_AUDIO_FIXED_ENDPOINT, .data = & (const struct audioformat) { - .format = SNDRV_PCM_FORMAT_S24_3LE, + .formats = SNDRV_PCM_FMTBIT_S24_3LE, .channels = 2, .iface = 1, .altsetting = 1, @@ -768,7 +1018,7 @@ YAMAHA_DEVICE(0x7010, "UB99"), .ifnum = 2, .type = QUIRK_AUDIO_FIXED_ENDPOINT, .data = & (const struct audioformat) { - .format = SNDRV_PCM_FORMAT_S24_3LE, + .formats = SNDRV_PCM_FMTBIT_S24_3LE, .channels = 2, .iface = 2, .altsetting = 1, @@ -838,15 +1088,15 @@ YAMAHA_DEVICE(0x7010, "UB99"), .data = (const struct snd_usb_audio_quirk[]) { { .ifnum = 1, - .type = QUIRK_AUDIO_EDIROL_UA700_UA25 + .type = QUIRK_AUDIO_EDIROL_UAXX }, { .ifnum = 2, - .type = QUIRK_AUDIO_EDIROL_UA700_UA25 + .type = QUIRK_AUDIO_EDIROL_UAXX }, { .ifnum = 3, - .type = QUIRK_AUDIO_EDIROL_UA700_UA25 + .type = QUIRK_AUDIO_EDIROL_UAXX }, { .ifnum = -1 @@ -896,7 +1146,6 @@ YAMAHA_DEVICE(0x7010, "UB99"), } } }, - /* TODO: add Roland M-1000 support */ { /* * Has ID 0x0038 when not in "Advanced Driver" mode; @@ -974,36 +1223,6 @@ YAMAHA_DEVICE(0x7010, "UB99"), } }, { - USB_DEVICE(0x0582, 0x0044), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { - .vendor_name = "Roland", - .product_name = "UA-1000", - .ifnum = QUIRK_ANY_INTERFACE, - .type = QUIRK_COMPOSITE, - .data = (const struct snd_usb_audio_quirk[]) { - { - .ifnum = 1, - .type = QUIRK_AUDIO_EDIROL_UA1000 - }, - { - .ifnum = 2, - .type = QUIRK_AUDIO_EDIROL_UA1000 - }, - { - .ifnum = 3, - .type = QUIRK_MIDI_FIXED_ENDPOINT, - .data = & (const struct snd_usb_midi_endpoint_info) { - .out_cables = 0x0003, - .in_cables = 0x0003 - } - }, - { - .ifnum = -1 - } - } - } -}, -{ /* has ID 0x0049 when not in "Advanced Driver" mode */ USB_DEVICE(0x0582, 0x0047), .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { @@ -1041,7 +1260,6 @@ YAMAHA_DEVICE(0x7010, "UB99"), } } }, - /* TODO: add Edirol M-100FX support */ { /* has ID 0x004e when not in "Advanced Driver" mode */ USB_DEVICE(0x0582, 0x004c), @@ -1124,30 +1342,39 @@ YAMAHA_DEVICE(0x7010, "UB99"), } }, { - /* has ID 0x0067 when not in "Advanced Driver" mode */ - USB_DEVICE(0x0582, 0x0065), + /* 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 = 0, - .type = QUIRK_MIDI_FIXED_ENDPOINT, - .data = & (const struct snd_usb_midi_endpoint_info) { - .out_cables = 0x0001, - .in_cables = 0x0003 + /* .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 0x006b when not in "Advanced Driver" mode */ - USB_DEVICE_VENDOR_SPEC(0x0582, 0x006a), + /* 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 = "Roland", - .product_name = "SP-606", - .ifnum = 3, + /* .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 = 0x0001 + .in_cables = 0x0003 } } }, @@ -1165,19 +1392,6 @@ YAMAHA_DEVICE(0x7010, "UB99"), } } }, -{ - USB_DEVICE(0x582, 0x00a6), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { - .vendor_name = "Roland", - .product_name = "Juno-G", - .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 @@ -1193,15 +1407,15 @@ YAMAHA_DEVICE(0x7010, "UB99"), .data = (const struct snd_usb_audio_quirk[]) { { .ifnum = 0, - .type = QUIRK_AUDIO_EDIROL_UA700_UA25 + .type = QUIRK_AUDIO_EDIROL_UAXX }, { .ifnum = 1, - .type = QUIRK_AUDIO_EDIROL_UA700_UA25 + .type = QUIRK_AUDIO_EDIROL_UAXX }, { .ifnum = 2, - .type = QUIRK_AUDIO_EDIROL_UA700_UA25 + .type = QUIRK_AUDIO_EDIROL_UAXX }, { .ifnum = -1 @@ -1237,37 +1451,6 @@ YAMAHA_DEVICE(0x7010, "UB99"), } } }, -/* Roland UA-101 in High-Speed Mode only */ -{ - USB_DEVICE(0x0582, 0x007d), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { - .vendor_name = "Roland", - .product_name = "UA-101", - .ifnum = QUIRK_ANY_INTERFACE, - .type = QUIRK_COMPOSITE, - .data = (const struct snd_usb_audio_quirk[]) { - { - .ifnum = 0, - .type = QUIRK_AUDIO_EDIROL_UA101 - }, - { - .ifnum = 1, - .type = QUIRK_AUDIO_EDIROL_UA101 - }, - { - .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 0x0081 when not in "Advanced Driver" mode */ USB_DEVICE(0x0582, 0x0080), @@ -1282,8 +1465,6 @@ YAMAHA_DEVICE(0x7010, "UB99"), } } }, - /* TODO: add Roland V-SYNTH XT support */ - /* TODO: add BOSS GT-PRO support */ { /* has ID 0x008c when not in "Advanced Driver" mode */ USB_DEVICE(0x0582, 0x008b), @@ -1298,12 +1479,40 @@ YAMAHA_DEVICE(0x7010, "UB99"), } } }, - /* TODO: add Edirol PC-80 support */ { - USB_DEVICE(0x0582, 0x0096), + /* + * 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-1EX", + .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[]) { @@ -1316,41 +1525,172 @@ YAMAHA_DEVICE(0x7010, "UB99"), .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 } } } }, { - USB_DEVICE(0x0582, 0x009a), + /* 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 = "UM-3EX", + .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 = 0x000f, - .in_cables = 0x000f + .out_cables = 0x0007, + .in_cables = 0x0007 } } }, - /* TODO: add Edirol MD-P1 support */ { - /* Roland SH-201 */ - USB_DEVICE(0x0582, 0x00ad), + /* 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 = "SH-201", + /* .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_STANDARD_INTERFACE + .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_STANDARD_INTERFACE + .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, @@ -1361,11 +1701,30 @@ YAMAHA_DEVICE(0x7010, "UB99"), } }, { + .ifnum = 3, + .type = QUIRK_IGNORE_INTERFACE + }, + { + .ifnum = 4, + .type = QUIRK_IGNORE_INTERFACE + }, + { .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 */ { @@ -1667,6 +2026,282 @@ YAMAHA_DEVICE(0x7010, "UB99"), } } }, +{ + 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 */ { @@ -1706,7 +2341,7 @@ YAMAHA_DEVICE(0x7010, "UB99"), .data = & (const struct snd_usb_audio_quirk[]) { { .ifnum = 0, - .type = QUIRK_MIDI_RAW + .type = QUIRK_MIDI_RAW_BYTES }, { .ifnum = 1, @@ -1719,17 +2354,6 @@ YAMAHA_DEVICE(0x7010, "UB99"), } }, -{ - /* Creative Sound Blaster MP3+ */ - 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 - } - -}, - /* Emagic devices */ { USB_DEVICE(0x086a, 0x0001), @@ -1771,6 +2395,38 @@ YAMAHA_DEVICE(0x7010, "UB99"), } }, +/* 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, + } +}, + /* TerraTec devices */ { USB_DEVICE_VENDOR_SPEC(0x0ccd, 0x0012), @@ -1800,6 +2456,14 @@ YAMAHA_DEVICE(0x7010, "UB99"), } }, { + 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", @@ -1847,6 +2511,95 @@ YAMAHA_DEVICE(0x7010, "UB99"), } }, { + 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", @@ -1855,6 +2608,84 @@ YAMAHA_DEVICE(0x7010, "UB99"), .type = QUIRK_MIDI_NOVATION } }, +{ + /* + * Focusrite Scarlett 18i6 + * + * Avoid mixer creation, which otherwise fails because some of + * the interface descriptor subtypes for interface 0 are + * unknown. That should be fixed or worked-around but this at + * least allows the device to be used successfully with a DAW + * and an external mixer. See comments below about other + * ignored interfaces. + */ + USB_DEVICE(0x1235, 0x8004), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "Focusrite", + .product_name = "Scarlett 18i6", + .ifnum = QUIRK_ANY_INTERFACE, + .type = QUIRK_COMPOSITE, + .data = & (const struct snd_usb_audio_quirk[]) { + { + /* InterfaceSubClass 1 (Control Device) */ + .ifnum = 0, + .type = QUIRK_IGNORE_INTERFACE + }, + { + .ifnum = 1, + .type = QUIRK_AUDIO_STANDARD_INTERFACE + }, + { + .ifnum = 2, + .type = QUIRK_AUDIO_STANDARD_INTERFACE + }, + { + /* InterfaceSubClass 1 (Control Device) */ + .ifnum = 3, + .type = QUIRK_IGNORE_INTERFACE + }, + { + .ifnum = 4, + .type = QUIRK_MIDI_STANDARD_INTERFACE + }, + { + /* InterfaceSubClass 1 (Device Firmware Update) */ + .ifnum = 5, + .type = QUIRK_IGNORE_INTERFACE + }, + { + .ifnum = -1 + } + } + } +}, + +/* 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 + } + } + } +}, /* */ { @@ -1867,6 +2698,32 @@ YAMAHA_DEVICE(0x7010, "UB99"), } }, +/* 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), @@ -1888,6 +2745,403 @@ YAMAHA_DEVICE(0x7010, "UB99"), } }, +/* Hauppauge HVR-950Q and HVR-850 */ +{ + USB_DEVICE_VENDOR_SPEC(0x2040, 0x7200), + .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 = "Hauppauge", + .product_name = "HVR-950Q", + .ifnum = QUIRK_ANY_INTERFACE, + .type = QUIRK_AUDIO_ALIGN_TRANSFER, + } +}, +{ + USB_DEVICE_VENDOR_SPEC(0x2040, 0x7210), + .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 = "Hauppauge", + .product_name = "HVR-950Q", + .ifnum = QUIRK_ANY_INTERFACE, + .type = QUIRK_AUDIO_ALIGN_TRANSFER, + } +}, +{ + USB_DEVICE_VENDOR_SPEC(0x2040, 0x7217), + .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 = "Hauppauge", + .product_name = "HVR-950Q", + .ifnum = QUIRK_ANY_INTERFACE, + .type = QUIRK_AUDIO_ALIGN_TRANSFER, + } +}, +{ + USB_DEVICE_VENDOR_SPEC(0x2040, 0x721b), + .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 = "Hauppauge", + .product_name = "HVR-950Q", + .ifnum = QUIRK_ANY_INTERFACE, + .type = QUIRK_AUDIO_ALIGN_TRANSFER, + } +}, +{ + USB_DEVICE_VENDOR_SPEC(0x2040, 0x721e), + .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 = "Hauppauge", + .product_name = "HVR-950Q", + .ifnum = QUIRK_ANY_INTERFACE, + .type = QUIRK_AUDIO_ALIGN_TRANSFER, + } +}, +{ + USB_DEVICE_VENDOR_SPEC(0x2040, 0x721f), + .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 = "Hauppauge", + .product_name = "HVR-950Q", + .ifnum = QUIRK_ANY_INTERFACE, + .type = QUIRK_AUDIO_ALIGN_TRANSFER, + } +}, +{ + USB_DEVICE_VENDOR_SPEC(0x2040, 0x7240), + .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 = "Hauppauge", + .product_name = "HVR-850", + .ifnum = QUIRK_ANY_INTERFACE, + .type = QUIRK_AUDIO_ALIGN_TRANSFER, + } +}, +{ + USB_DEVICE_VENDOR_SPEC(0x2040, 0x7280), + .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 = "Hauppauge", + .product_name = "HVR-950Q", + .ifnum = QUIRK_ANY_INTERFACE, + .type = QUIRK_AUDIO_ALIGN_TRANSFER, + } +}, +{ + USB_DEVICE_VENDOR_SPEC(0x0fd9, 0x0008), + .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 = "Hauppauge", + .product_name = "HVR-950Q", + .ifnum = QUIRK_ANY_INTERFACE, + .type = QUIRK_AUDIO_ALIGN_TRANSFER, + } +}, + +/* Digidesign Mbox */ +{ + /* Thanks to Clemens Ladisch <clemens@ladisch.de> */ + USB_DEVICE(0x0dba, 0x1000), + .driver_info = (unsigned long) &(const struct snd_usb_audio_quirk) { + .vendor_name = "Digidesign", + .product_name = "MBox", + .ifnum = QUIRK_ANY_INTERFACE, + .type = QUIRK_COMPOSITE, + .data = (const struct snd_usb_audio_quirk[]){ + { + .ifnum = 0, + .type = QUIRK_IGNORE_INTERFACE, + }, + { + .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 = UAC_EP_CS_ATTR_SAMPLE_RATE, + .endpoint = 0x02, + .ep_attr = 0x01, + .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 + } + } + + } +}, + +/* 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 + } + } + } +}, + { /* * Some USB MIDI devices don't have an audio control interface, @@ -1896,11 +3150,23 @@ YAMAHA_DEVICE(0x7010, "UB99"), .match_flags = USB_DEVICE_ID_MATCH_INT_CLASS | USB_DEVICE_ID_MATCH_INT_SUBCLASS, .bInterfaceClass = USB_CLASS_AUDIO, - .bInterfaceSubClass = USB_SUBCLASS_MIDI_STREAMING, + .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 00000000000..7c57f2268dd --- /dev/null +++ b/sound/usb/quirks.c @@ -0,0 +1,1178 @@ +/* + * 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) +{ + int probed_ifnum = get_iface_desc(iface->altsetting)->bInterfaceNumber; + int err; + + for (quirk = quirk->data; quirk->ifnum >= 0; ++quirk) { + iface = usb_ifnum_to_if(chip->dev, quirk->ifnum); + if (!iface) + continue; + if (quirk->ifnum != probed_ifnum && + usb_interface_claimed(iface)) + continue; + err = snd_usb_create_quirk(chip, iface, driver, quirk); + if (err < 0) + return err; + if (quirk->ifnum != probed_ifnum) + 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, 0xdc }; + 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; + } +} + +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); +} + +/* + * 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; + } + } + + return 0; +} diff --git a/sound/usb/quirks.h b/sound/usb/quirks.h new file mode 100644 index 00000000000..665e972a1b4 --- /dev/null +++ b/sound/usb/quirks.h @@ -0,0 +1,38 @@ +#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); + +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); + +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 00000000000..310a3822d2b --- /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 00000000000..c97f679fc84 --- /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.c b/sound/usb/usbaudio.c deleted file mode 100644 index f48838a078c..00000000000 --- a/sound/usb/usbaudio.c +++ /dev/null @@ -1,3758 +0,0 @@ -/* - * (Tentative) USB Audio Driver for ALSA - * - * Main and PCM part - * - * Copyright (c) 2002 by Takashi Iwai <tiwai@suse.de> - * - * Many codes borrowed from audio.c by - * Alan Cox (alan@lxorguk.ukuu.org.uk) - * Thomas Sailer (sailer@ife.ee.ethz.ch) - * - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - * - * - * NOTES: - * - * - async unlink should be used for avoiding the sleep inside lock. - * 2.4.22 usb-uhci seems buggy for async unlinking and results in - * oops. in such a cse, pass async_unlink=0 option. - * - the linked URBs would be preferred but not used so far because of - * the instability of unlinking. - * - type II is not supported properly. there is no device which supports - * this type *correctly*. SB extigy looks as if it supports, but it's - * indeed an AC3 stream packed in SPDIF frames (i.e. no real AC3 stream). - */ - - -#include <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/vmalloc.h> -#include <linux/moduleparam.h> -#include <linux/mutex.h> -#include <sound/core.h> -#include <sound/info.h> -#include <sound/pcm.h> -#include <sound/pcm_params.h> -#include <sound/initval.h> - -#include "usbaudio.h" - - -MODULE_AUTHOR("Takashi Iwai <tiwai@suse.de>"); -MODULE_DESCRIPTION("USB Audio"); -MODULE_LICENSE("GPL"); -MODULE_SUPPORTED_DEVICE("{{Generic,USB Audio}}"); - - -static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 0-MAX */ -static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* ID for this card */ -static int enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP; /* Enable this card */ -static int vid[SNDRV_CARDS] = { [0 ... (SNDRV_CARDS-1)] = -1 }; /* Vendor ID for this card */ -static int pid[SNDRV_CARDS] = { [0 ... (SNDRV_CARDS-1)] = -1 }; /* Product ID for this card */ -static int nrpacks = 8; /* max. number of packets per urb */ -static int async_unlink = 1; -static int device_setup[SNDRV_CARDS]; /* device parameter for this card*/ - -module_param_array(index, int, NULL, 0444); -MODULE_PARM_DESC(index, "Index value for the USB audio adapter."); -module_param_array(id, charp, NULL, 0444); -MODULE_PARM_DESC(id, "ID string for the USB audio adapter."); -module_param_array(enable, bool, NULL, 0444); -MODULE_PARM_DESC(enable, "Enable USB audio adapter."); -module_param_array(vid, int, NULL, 0444); -MODULE_PARM_DESC(vid, "Vendor ID for the USB audio device."); -module_param_array(pid, int, NULL, 0444); -MODULE_PARM_DESC(pid, "Product ID for the USB audio device."); -module_param(nrpacks, int, 0644); -MODULE_PARM_DESC(nrpacks, "Max. number of packets per URB."); -module_param(async_unlink, bool, 0444); -MODULE_PARM_DESC(async_unlink, "Use async unlink mode."); -module_param_array(device_setup, int, NULL, 0444); -MODULE_PARM_DESC(device_setup, "Specific device setup (if needed)."); - - -/* - * debug the h/w constraints - */ -/* #define HW_CONST_DEBUG */ - - -/* - * - */ - -#define MAX_PACKS 20 -#define MAX_PACKS_HS (MAX_PACKS * 8) /* in high speed mode */ -#define MAX_URBS 8 -#define SYNC_URBS 4 /* always four urbs for sync */ -#define MIN_PACKS_URB 1 /* minimum 1 packet per urb */ - -struct audioformat { - struct list_head list; - snd_pcm_format_t format; /* format type */ - unsigned int channels; /* # channels */ - unsigned int fmt_type; /* USB audio format type (1-3) */ - unsigned int frame_size; /* samples per frame for non-audio */ - int iface; /* interface number */ - unsigned char altsetting; /* corresponding alternate setting */ - unsigned char altset_idx; /* array index of altenate setting */ - unsigned char attributes; /* corresponding attributes of cs endpoint */ - unsigned char endpoint; /* endpoint */ - unsigned char ep_attr; /* endpoint attributes */ - unsigned int maxpacksize; /* max. packet size */ - unsigned int rates; /* rate bitmasks */ - unsigned int rate_min, rate_max; /* min/max rates */ - unsigned int nr_rates; /* number of rate table entries */ - unsigned int *rate_table; /* rate table */ -}; - -struct snd_usb_substream; - -struct snd_urb_ctx { - struct urb *urb; - unsigned int buffer_size; /* size of data buffer, if data URB */ - struct snd_usb_substream *subs; - int index; /* index for urb array */ - int packets; /* number of packets per urb */ -}; - -struct snd_urb_ops { - int (*prepare)(struct snd_usb_substream *subs, struct snd_pcm_runtime *runtime, struct urb *u); - int (*retire)(struct snd_usb_substream *subs, struct snd_pcm_runtime *runtime, struct urb *u); - int (*prepare_sync)(struct snd_usb_substream *subs, struct snd_pcm_runtime *runtime, struct urb *u); - int (*retire_sync)(struct snd_usb_substream *subs, struct snd_pcm_runtime *runtime, struct urb *u); -}; - -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) */ - unsigned int cur_rate; /* current rate (for hw_params callback) */ - unsigned int period_bytes; /* current period bytes (for hw_params callback) */ - unsigned int format; /* USB data format */ - unsigned int datapipe; /* the data i/o pipe */ - unsigned int syncpipe; /* 1 - async out or adaptive in */ - unsigned int datainterval; /* log_2 of data packet interval */ - unsigned int syncinterval; /* P for adaptive mode, 0 otherwise */ - unsigned int freqn; /* nominal sampling rate in fs/fps in Q16.16 format */ - unsigned int freqm; /* momentary sampling rate in fs/fps in Q16.16 format */ - unsigned int freqmax; /* maximum sampling rate, used for buffer management */ - unsigned int phase; /* phase accumulator */ - unsigned int maxpacksize; /* max packet size in bytes */ - unsigned int maxframesize; /* max packet size in frames */ - unsigned int curpacksize; /* current packet size in bytes (for capture) */ - unsigned int curframesize; /* current packet size in frames (for capture) */ - unsigned int fill_max: 1; /* fill max packet size always */ - unsigned int fmt_type; /* USB audio format type (1-3) */ - unsigned int packs_per_ms; /* packets per millisecond (for playback) */ - - unsigned int running: 1; /* running status */ - - unsigned int hwptr_done; /* processed frame position in the buffer */ - unsigned int transfer_done; /* processed frames since last period update */ - unsigned long active_mask; /* bitmask of active urbs */ - unsigned long unlink_mask; /* bitmask of unlinked urbs */ - - unsigned int nurbs; /* # urbs */ - struct snd_urb_ctx dataurb[MAX_URBS]; /* data urb table */ - struct snd_urb_ctx syncurb[SYNC_URBS]; /* sync urb table */ - char *syncbuf; /* sync buffer for all sync URBs */ - dma_addr_t sync_dma; /* DMA address of syncbuf */ - - 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; - - struct snd_urb_ops ops; /* callbacks (must be filled at init) */ -}; - - -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; -}; - - -/* - * 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]; - - -/* - * 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; -} - -/* 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; -} - - -/* - * prepare urb for full speed capture sync pipe - * - * fill the length and offset of each urb descriptor. - * the fixed 10.14 frequency is passed through the pipe. - */ -static int prepare_capture_sync_urb(struct snd_usb_substream *subs, - struct snd_pcm_runtime *runtime, - struct urb *urb) -{ - unsigned char *cp = urb->transfer_buffer; - struct snd_urb_ctx *ctx = urb->context; - - urb->dev = ctx->subs->dev; /* we need to set this at each time */ - urb->iso_frame_desc[0].length = 3; - urb->iso_frame_desc[0].offset = 0; - cp[0] = subs->freqn >> 2; - cp[1] = subs->freqn >> 10; - cp[2] = subs->freqn >> 18; - return 0; -} - -/* - * prepare urb for high speed capture sync pipe - * - * fill the length and offset of each urb descriptor. - * the fixed 12.13 frequency is passed as 16.16 through the pipe. - */ -static int prepare_capture_sync_urb_hs(struct snd_usb_substream *subs, - struct snd_pcm_runtime *runtime, - struct urb *urb) -{ - unsigned char *cp = urb->transfer_buffer; - struct snd_urb_ctx *ctx = urb->context; - - urb->dev = ctx->subs->dev; /* we need to set this at each time */ - urb->iso_frame_desc[0].length = 4; - urb->iso_frame_desc[0].offset = 0; - cp[0] = subs->freqn; - cp[1] = subs->freqn >> 8; - cp[2] = subs->freqn >> 16; - cp[3] = subs->freqn >> 24; - return 0; -} - -/* - * process after capture sync complete - * - nothing to do - */ -static int retire_capture_sync_urb(struct snd_usb_substream *subs, - struct snd_pcm_runtime *runtime, - struct urb *urb) -{ - return 0; -} - -/* - * prepare urb for capture data pipe - * - * fill the offset and length of each descriptor. - * - * we use a temporary buffer to write the captured data. - * since the length of written data is determined by host, we cannot - * write onto the pcm buffer directly... the data is thus copied - * later at complete callback to the global buffer. - */ -static int prepare_capture_urb(struct snd_usb_substream *subs, - struct snd_pcm_runtime *runtime, - struct urb *urb) -{ - int i, offs; - struct snd_urb_ctx *ctx = urb->context; - - offs = 0; - urb->dev = ctx->subs->dev; /* we need to set this at each time */ - for (i = 0; i < ctx->packets; i++) { - urb->iso_frame_desc[i].offset = offs; - urb->iso_frame_desc[i].length = subs->curpacksize; - offs += subs->curpacksize; - } - urb->transfer_buffer_length = offs; - urb->number_of_packets = ctx->packets; - return 0; -} - -/* - * process after capture complete - * - * copy the data from each desctiptor to the pcm buffer, and - * update the current position. - */ -static int retire_capture_urb(struct snd_usb_substream *subs, - struct snd_pcm_runtime *runtime, - struct urb *urb) -{ - unsigned long flags; - unsigned char *cp; - int i; - unsigned int stride, len, oldptr; - int period_elapsed = 0; - - stride = runtime->frame_bits >> 3; - - for (i = 0; i < urb->number_of_packets; i++) { - cp = (unsigned char *)urb->transfer_buffer + urb->iso_frame_desc[i].offset; - if (urb->iso_frame_desc[i].status) { - snd_printd(KERN_ERR "frame %d active: %d\n", i, urb->iso_frame_desc[i].status); - // continue; - } - len = urb->iso_frame_desc[i].actual_length / stride; - if (! len) - continue; - /* update the current pointer */ - spin_lock_irqsave(&subs->lock, flags); - oldptr = subs->hwptr_done; - subs->hwptr_done += len; - if (subs->hwptr_done >= runtime->buffer_size) - subs->hwptr_done -= runtime->buffer_size; - subs->transfer_done += len; - if (subs->transfer_done >= runtime->period_size) { - subs->transfer_done -= runtime->period_size; - period_elapsed = 1; - } - spin_unlock_irqrestore(&subs->lock, flags); - /* copy a data chunk */ - if (oldptr + len > runtime->buffer_size) { - unsigned int cnt = runtime->buffer_size - oldptr; - unsigned int blen = cnt * stride; - memcpy(runtime->dma_area + oldptr * stride, cp, blen); - memcpy(runtime->dma_area, cp + blen, len * stride - blen); - } else { - memcpy(runtime->dma_area + oldptr * stride, cp, len * stride); - } - } - if (period_elapsed) - snd_pcm_period_elapsed(subs->pcm_substream); - return 0; -} - -/* - * Process after capture complete when paused. Nothing to do. - */ -static int retire_paused_capture_urb(struct snd_usb_substream *subs, - struct snd_pcm_runtime *runtime, - struct urb *urb) -{ - return 0; -} - - -/* - * prepare urb for full speed playback sync pipe - * - * set up the offset and length to receive the current frequency. - */ - -static int prepare_playback_sync_urb(struct snd_usb_substream *subs, - struct snd_pcm_runtime *runtime, - struct urb *urb) -{ - struct snd_urb_ctx *ctx = urb->context; - - urb->dev = ctx->subs->dev; /* we need to set this at each time */ - urb->iso_frame_desc[0].length = 3; - urb->iso_frame_desc[0].offset = 0; - return 0; -} - -/* - * prepare urb for high speed playback sync pipe - * - * set up the offset and length to receive the current frequency. - */ - -static int prepare_playback_sync_urb_hs(struct snd_usb_substream *subs, - struct snd_pcm_runtime *runtime, - struct urb *urb) -{ - struct snd_urb_ctx *ctx = urb->context; - - urb->dev = ctx->subs->dev; /* we need to set this at each time */ - urb->iso_frame_desc[0].length = 4; - urb->iso_frame_desc[0].offset = 0; - return 0; -} - -/* - * process after full speed playback sync complete - * - * retrieve the current 10.14 frequency from pipe, and set it. - * the value is referred in prepare_playback_urb(). - */ -static int retire_playback_sync_urb(struct snd_usb_substream *subs, - struct snd_pcm_runtime *runtime, - struct urb *urb) -{ - unsigned int f; - unsigned long flags; - - if (urb->iso_frame_desc[0].status == 0 && - urb->iso_frame_desc[0].actual_length == 3) { - f = combine_triple((u8*)urb->transfer_buffer) << 2; - if (f >= subs->freqn - subs->freqn / 8 && f <= subs->freqmax) { - spin_lock_irqsave(&subs->lock, flags); - subs->freqm = f; - spin_unlock_irqrestore(&subs->lock, flags); - } - } - - return 0; -} - -/* - * process after high speed playback sync complete - * - * retrieve the current 12.13 frequency from pipe, and set it. - * the value is referred in prepare_playback_urb(). - */ -static int retire_playback_sync_urb_hs(struct snd_usb_substream *subs, - struct snd_pcm_runtime *runtime, - struct urb *urb) -{ - unsigned int f; - unsigned long flags; - - if (urb->iso_frame_desc[0].status == 0 && - urb->iso_frame_desc[0].actual_length == 4) { - f = combine_quad((u8*)urb->transfer_buffer) & 0x0fffffff; - if (f >= subs->freqn - subs->freqn / 8 && f <= subs->freqmax) { - spin_lock_irqsave(&subs->lock, flags); - subs->freqm = f; - spin_unlock_irqrestore(&subs->lock, flags); - } - } - - return 0; -} - -/* - * process after E-Mu 0202/0404 high speed playback sync complete - * - * These devices return the number of samples per packet instead of the number - * of samples per microframe. - */ -static int retire_playback_sync_urb_hs_emu(struct snd_usb_substream *subs, - struct snd_pcm_runtime *runtime, - struct urb *urb) -{ - unsigned int f; - unsigned long flags; - - if (urb->iso_frame_desc[0].status == 0 && - urb->iso_frame_desc[0].actual_length == 4) { - f = combine_quad((u8*)urb->transfer_buffer) & 0x0fffffff; - f >>= subs->datainterval; - if (f >= subs->freqn - subs->freqn / 8 && f <= subs->freqmax) { - spin_lock_irqsave(&subs->lock, flags); - subs->freqm = f; - spin_unlock_irqrestore(&subs->lock, flags); - } - } - - return 0; -} - -/* determine the number of frames in the next packet */ -static int snd_usb_audio_next_packet_size(struct snd_usb_substream *subs) -{ - if (subs->fill_max) - return subs->maxframesize; - else { - subs->phase = (subs->phase & 0xffff) - + (subs->freqm << subs->datainterval); - return min(subs->phase >> 16, subs->maxframesize); - } -} - -/* - * Prepare urb for streaming before playback starts or when paused. - * - * We don't have any data, so we send a frame of silence. - */ -static int prepare_nodata_playback_urb(struct snd_usb_substream *subs, - struct snd_pcm_runtime *runtime, - struct urb *urb) -{ - unsigned int i, offs, counts; - struct snd_urb_ctx *ctx = urb->context; - int stride = runtime->frame_bits >> 3; - - offs = 0; - urb->dev = ctx->subs->dev; - urb->number_of_packets = subs->packs_per_ms; - for (i = 0; i < subs->packs_per_ms; ++i) { - counts = snd_usb_audio_next_packet_size(subs); - urb->iso_frame_desc[i].offset = offs * stride; - urb->iso_frame_desc[i].length = counts * stride; - offs += counts; - } - urb->transfer_buffer_length = offs * stride; - memset(urb->transfer_buffer, - subs->cur_audiofmt->format == SNDRV_PCM_FORMAT_U8 ? 0x80 : 0, - offs * stride); - return 0; -} - -/* - * prepare urb for playback data pipe - * - * 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 int prepare_playback_urb(struct snd_usb_substream *subs, - struct snd_pcm_runtime *runtime, - struct urb *urb) -{ - int i, stride, offs; - unsigned int counts; - unsigned long flags; - int period_elapsed = 0; - struct snd_urb_ctx *ctx = urb->context; - - stride = runtime->frame_bits >> 3; - - offs = 0; - urb->dev = ctx->subs->dev; /* we need to set this at each time */ - urb->number_of_packets = 0; - spin_lock_irqsave(&subs->lock, flags); - for (i = 0; i < ctx->packets; i++) { - counts = snd_usb_audio_next_packet_size(subs); - /* set up descriptor */ - urb->iso_frame_desc[i].offset = offs * stride; - urb->iso_frame_desc[i].length = counts * stride; - offs += counts; - urb->number_of_packets++; - subs->transfer_done += counts; - if (subs->transfer_done >= runtime->period_size) { - subs->transfer_done -= runtime->period_size; - period_elapsed = 1; - if (subs->fmt_type == USB_FORMAT_TYPE_II) { - if (subs->transfer_done > 0) { - /* FIXME: fill-max mode is not - * supported yet */ - offs -= subs->transfer_done; - counts -= subs->transfer_done; - urb->iso_frame_desc[i].length = - counts * stride; - subs->transfer_done = 0; - } - i++; - if (i < ctx->packets) { - /* add a transfer delimiter */ - urb->iso_frame_desc[i].offset = - offs * stride; - urb->iso_frame_desc[i].length = 0; - urb->number_of_packets++; - } - break; - } - } - /* finish at the frame boundary at/after the period boundary */ - if (period_elapsed && - (i & (subs->packs_per_ms - 1)) == subs->packs_per_ms - 1) - break; - } - if (subs->hwptr_done + offs > runtime->buffer_size) { - /* err, the transferred area goes over buffer boundary. */ - unsigned int len = runtime->buffer_size - subs->hwptr_done; - memcpy(urb->transfer_buffer, - runtime->dma_area + subs->hwptr_done * stride, - len * stride); - memcpy(urb->transfer_buffer + len * stride, - runtime->dma_area, - (offs - len) * stride); - } else { - memcpy(urb->transfer_buffer, - runtime->dma_area + subs->hwptr_done * stride, - offs * stride); - } - subs->hwptr_done += offs; - if (subs->hwptr_done >= runtime->buffer_size) - subs->hwptr_done -= runtime->buffer_size; - spin_unlock_irqrestore(&subs->lock, flags); - urb->transfer_buffer_length = offs * stride; - if (period_elapsed) - snd_pcm_period_elapsed(subs->pcm_substream); - return 0; -} - -/* - * process after playback data complete - * - nothing to do - */ -static int retire_playback_urb(struct snd_usb_substream *subs, - struct snd_pcm_runtime *runtime, - struct urb *urb) -{ - return 0; -} - - -/* - */ -static struct snd_urb_ops audio_urb_ops[2] = { - { - .prepare = prepare_nodata_playback_urb, - .retire = retire_playback_urb, - .prepare_sync = prepare_playback_sync_urb, - .retire_sync = retire_playback_sync_urb, - }, - { - .prepare = prepare_capture_urb, - .retire = retire_capture_urb, - .prepare_sync = prepare_capture_sync_urb, - .retire_sync = retire_capture_sync_urb, - }, -}; - -static struct snd_urb_ops audio_urb_ops_high_speed[2] = { - { - .prepare = prepare_nodata_playback_urb, - .retire = retire_playback_urb, - .prepare_sync = prepare_playback_sync_urb_hs, - .retire_sync = retire_playback_sync_urb_hs, - }, - { - .prepare = prepare_capture_urb, - .retire = retire_capture_urb, - .prepare_sync = prepare_capture_sync_urb_hs, - .retire_sync = retire_capture_sync_urb, - }, -}; - -/* - * complete callback from data urb - */ -static void snd_complete_urb(struct urb *urb) -{ - struct snd_urb_ctx *ctx = urb->context; - struct snd_usb_substream *subs = ctx->subs; - struct snd_pcm_substream *substream = ctx->subs->pcm_substream; - int err = 0; - - if ((subs->running && subs->ops.retire(subs, substream->runtime, urb)) || - ! subs->running || /* can be stopped during retire callback */ - (err = subs->ops.prepare(subs, substream->runtime, urb)) < 0 || - (err = usb_submit_urb(urb, GFP_ATOMIC)) < 0) { - clear_bit(ctx->index, &subs->active_mask); - if (err < 0) { - snd_printd(KERN_ERR "cannot submit urb (err = %d)\n", err); - snd_pcm_stop(substream, SNDRV_PCM_STATE_XRUN); - } - } -} - - -/* - * complete callback from sync urb - */ -static void snd_complete_sync_urb(struct urb *urb) -{ - struct snd_urb_ctx *ctx = urb->context; - struct snd_usb_substream *subs = ctx->subs; - struct snd_pcm_substream *substream = ctx->subs->pcm_substream; - int err = 0; - - if ((subs->running && subs->ops.retire_sync(subs, substream->runtime, urb)) || - ! subs->running || /* can be stopped during retire callback */ - (err = subs->ops.prepare_sync(subs, substream->runtime, urb)) < 0 || - (err = usb_submit_urb(urb, GFP_ATOMIC)) < 0) { - clear_bit(ctx->index + 16, &subs->active_mask); - if (err < 0) { - snd_printd(KERN_ERR "cannot submit sync urb (err = %d)\n", err); - snd_pcm_stop(substream, SNDRV_PCM_STATE_XRUN); - } - } -} - - -/* get the physical page pointer at the given offset */ -static struct page *snd_pcm_get_vmalloc_page(struct snd_pcm_substream *subs, - unsigned long offset) -{ - void *pageptr = subs->runtime->dma_area + offset; - return vmalloc_to_page(pageptr); -} - -/* allocate virtual buffer; may be called more than once */ -static int snd_pcm_alloc_vmalloc_buffer(struct snd_pcm_substream *subs, size_t size) -{ - struct snd_pcm_runtime *runtime = subs->runtime; - if (runtime->dma_area) { - if (runtime->dma_bytes >= size) - return 0; /* already large enough */ - vfree(runtime->dma_area); - } - runtime->dma_area = vmalloc(size); - if (! runtime->dma_area) - return -ENOMEM; - runtime->dma_bytes = size; - return 0; -} - -/* free virtual buffer; may be called more than once */ -static int snd_pcm_free_vmalloc_buffer(struct snd_pcm_substream *subs) -{ - struct snd_pcm_runtime *runtime = subs->runtime; - - vfree(runtime->dma_area); - runtime->dma_area = NULL; - return 0; -} - - -/* - * unlink active urbs. - */ -static int deactivate_urbs(struct snd_usb_substream *subs, int force, int can_sleep) -{ - unsigned int i; - int async; - - subs->running = 0; - - if (!force && subs->stream->chip->shutdown) /* to be sure... */ - return -EBADFD; - - async = !can_sleep && async_unlink; - - if (! async && in_interrupt()) - return 0; - - for (i = 0; i < subs->nurbs; i++) { - if (test_bit(i, &subs->active_mask)) { - if (! test_and_set_bit(i, &subs->unlink_mask)) { - struct urb *u = subs->dataurb[i].urb; - if (async) - usb_unlink_urb(u); - else - usb_kill_urb(u); - } - } - } - if (subs->syncpipe) { - for (i = 0; i < SYNC_URBS; i++) { - if (test_bit(i+16, &subs->active_mask)) { - if (! test_and_set_bit(i+16, &subs->unlink_mask)) { - struct urb *u = subs->syncurb[i].urb; - if (async) - usb_unlink_urb(u); - else - usb_kill_urb(u); - } - } - } - } - return 0; -} - - -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"; -#ifndef CONFIG_USB_EHCI_SPLIT_ISO - case -ENOSYS: - return "enable CONFIG_USB_EHCI_SPLIT_ISO to play through a hub"; -#endif - case -EINVAL: - case -EAGAIN: - case -EFBIG: - case -EMSGSIZE: - return "internal error"; - default: - return "unknown error"; - } -} - -/* - * set up and start data/sync urbs - */ -static int start_urbs(struct snd_usb_substream *subs, struct snd_pcm_runtime *runtime) -{ - unsigned int i; - int err; - - if (subs->stream->chip->shutdown) - return -EBADFD; - - for (i = 0; i < subs->nurbs; i++) { - snd_assert(subs->dataurb[i].urb, return -EINVAL); - if (subs->ops.prepare(subs, runtime, subs->dataurb[i].urb) < 0) { - snd_printk(KERN_ERR "cannot prepare datapipe for urb %d\n", i); - goto __error; - } - } - if (subs->syncpipe) { - for (i = 0; i < SYNC_URBS; i++) { - snd_assert(subs->syncurb[i].urb, return -EINVAL); - if (subs->ops.prepare_sync(subs, runtime, subs->syncurb[i].urb) < 0) { - snd_printk(KERN_ERR "cannot prepare syncpipe for urb %d\n", i); - goto __error; - } - } - } - - subs->active_mask = 0; - subs->unlink_mask = 0; - subs->running = 1; - for (i = 0; i < subs->nurbs; i++) { - err = usb_submit_urb(subs->dataurb[i].urb, GFP_ATOMIC); - if (err < 0) { - snd_printk(KERN_ERR "cannot submit datapipe " - "for urb %d, error %d: %s\n", - i, err, usb_error_string(err)); - goto __error; - } - set_bit(i, &subs->active_mask); - } - if (subs->syncpipe) { - for (i = 0; i < SYNC_URBS; i++) { - err = usb_submit_urb(subs->syncurb[i].urb, GFP_ATOMIC); - if (err < 0) { - snd_printk(KERN_ERR "cannot submit syncpipe " - "for urb %d, error %d: %s\n", - i, err, usb_error_string(err)); - goto __error; - } - set_bit(i + 16, &subs->active_mask); - } - } - return 0; - - __error: - // snd_pcm_stop(subs->pcm_substream, SNDRV_PCM_STATE_XRUN); - deactivate_urbs(subs, 0, 0); - return -EPIPE; -} - - -/* - * wait until all urbs are processed. - */ -static int wait_clear_urbs(struct snd_usb_substream *subs) -{ - unsigned long end_time = jiffies + msecs_to_jiffies(1000); - unsigned int i; - int alive; - - do { - alive = 0; - for (i = 0; i < subs->nurbs; i++) { - if (test_bit(i, &subs->active_mask)) - alive++; - } - if (subs->syncpipe) { - for (i = 0; i < SYNC_URBS; i++) { - if (test_bit(i + 16, &subs->active_mask)) - alive++; - } - } - if (! alive) - break; - schedule_timeout_uninterruptible(1); - } while (time_before(jiffies, end_time)); - if (alive) - snd_printk(KERN_ERR "timeout: still %d active urbs..\n", alive); - return 0; -} - - -/* - * return the current pcm pointer. just return the hwptr_done value. - */ -static snd_pcm_uframes_t snd_usb_pcm_pointer(struct snd_pcm_substream *substream) -{ - struct snd_usb_substream *subs; - snd_pcm_uframes_t hwptr_done; - - subs = (struct snd_usb_substream *)substream->runtime->private_data; - spin_lock(&subs->lock); - hwptr_done = subs->hwptr_done; - spin_unlock(&subs->lock); - return hwptr_done; -} - - -/* - * start/stop playback substream - */ -static int snd_usb_pcm_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: - case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: - subs->ops.prepare = prepare_playback_urb; - return 0; - case SNDRV_PCM_TRIGGER_STOP: - return deactivate_urbs(subs, 0, 0); - case SNDRV_PCM_TRIGGER_PAUSE_PUSH: - subs->ops.prepare = prepare_nodata_playback_urb; - return 0; - default: - return -EINVAL; - } -} - -/* - * start/stop capture substream - */ -static int snd_usb_pcm_capture_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->ops.retire = retire_capture_urb; - return start_urbs(subs, substream->runtime); - case SNDRV_PCM_TRIGGER_STOP: - return deactivate_urbs(subs, 0, 0); - case SNDRV_PCM_TRIGGER_PAUSE_PUSH: - subs->ops.retire = retire_paused_capture_urb; - return 0; - case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: - subs->ops.retire = retire_capture_urb; - return 0; - default: - return -EINVAL; - } -} - - -/* - * release a urb data - */ -static void release_urb_ctx(struct snd_urb_ctx *u) -{ - if (u->urb) { - if (u->buffer_size) - usb_buffer_free(u->subs->dev, u->buffer_size, - u->urb->transfer_buffer, - u->urb->transfer_dma); - usb_free_urb(u->urb); - u->urb = NULL; - } -} - -/* - * release a substream - */ -static void release_substream_urbs(struct snd_usb_substream *subs, int force) -{ - int i; - - /* stop urbs (to be sure) */ - deactivate_urbs(subs, force, 1); - wait_clear_urbs(subs); - - for (i = 0; i < MAX_URBS; i++) - release_urb_ctx(&subs->dataurb[i]); - for (i = 0; i < SYNC_URBS; i++) - release_urb_ctx(&subs->syncurb[i]); - usb_buffer_free(subs->dev, SYNC_URBS * 4, - subs->syncbuf, subs->sync_dma); - subs->syncbuf = NULL; - subs->nurbs = 0; -} - -/* - * initialize a substream for plaback/capture - */ -static int init_substream_urbs(struct snd_usb_substream *subs, unsigned int period_bytes, - unsigned int rate, unsigned int frame_bits) -{ - unsigned int maxsize, n, i; - int is_playback = subs->direction == SNDRV_PCM_STREAM_PLAYBACK; - unsigned int npacks[MAX_URBS], urb_packs, total_packs, packs_per_ms; - - /* calculate the frequency in 16.16 format */ - if (snd_usb_get_speed(subs->dev) == USB_SPEED_FULL) - subs->freqn = get_usb_full_speed_rate(rate); - else - subs->freqn = get_usb_high_speed_rate(rate); - subs->freqm = subs->freqn; - /* calculate max. frequency */ - if (subs->maxpacksize) { - /* whatever fits into a max. size packet */ - maxsize = subs->maxpacksize; - subs->freqmax = (maxsize / (frame_bits >> 3)) - << (16 - subs->datainterval); - } else { - /* no max. packet size: just take 25% higher than nominal */ - subs->freqmax = subs->freqn + (subs->freqn >> 2); - maxsize = ((subs->freqmax + 0xffff) * (frame_bits >> 3)) - >> (16 - subs->datainterval); - } - subs->phase = 0; - - if (subs->fill_max) - subs->curpacksize = subs->maxpacksize; - else - subs->curpacksize = maxsize; - - if (snd_usb_get_speed(subs->dev) == USB_SPEED_HIGH) - packs_per_ms = 8 >> subs->datainterval; - else - packs_per_ms = 1; - subs->packs_per_ms = packs_per_ms; - - if (is_playback) { - urb_packs = nrpacks; - urb_packs = max(urb_packs, (unsigned int)MIN_PACKS_URB); - urb_packs = min(urb_packs, (unsigned int)MAX_PACKS); - } else - urb_packs = 1; - urb_packs *= packs_per_ms; - - /* decide how many packets to be used */ - if (is_playback) { - unsigned int minsize; - /* determine how small a packet can be */ - minsize = (subs->freqn >> (16 - subs->datainterval)) - * (frame_bits >> 3); - /* with sync from device, assume it can be 12% lower */ - if (subs->syncpipe) - minsize -= minsize >> 3; - minsize = max(minsize, 1u); - total_packs = (period_bytes + minsize - 1) / minsize; - /* round up to multiple of packs_per_ms */ - total_packs = (total_packs + packs_per_ms - 1) - & ~(packs_per_ms - 1); - /* we need at least two URBs for queueing */ - if (total_packs < 2 * MIN_PACKS_URB * packs_per_ms) - total_packs = 2 * MIN_PACKS_URB * packs_per_ms; - } else { - total_packs = MAX_URBS * urb_packs; - } - subs->nurbs = (total_packs + urb_packs - 1) / urb_packs; - if (subs->nurbs > MAX_URBS) { - /* too much... */ - subs->nurbs = MAX_URBS; - total_packs = MAX_URBS * urb_packs; - } - n = total_packs; - for (i = 0; i < subs->nurbs; i++) { - npacks[i] = n > urb_packs ? urb_packs : n; - n -= urb_packs; - } - if (subs->nurbs <= 1) { - /* too little - we need at least two packets - * to ensure contiguous playback/capture - */ - subs->nurbs = 2; - npacks[0] = (total_packs + 1) / 2; - npacks[1] = total_packs - npacks[0]; - } else if (npacks[subs->nurbs-1] < MIN_PACKS_URB * packs_per_ms) { - /* the last packet is too small.. */ - if (subs->nurbs > 2) { - /* merge to the first one */ - npacks[0] += npacks[subs->nurbs - 1]; - subs->nurbs--; - } else { - /* divide to two */ - subs->nurbs = 2; - npacks[0] = (total_packs + 1) / 2; - npacks[1] = total_packs - npacks[0]; - } - } - - /* allocate and initialize data urbs */ - for (i = 0; i < subs->nurbs; i++) { - struct snd_urb_ctx *u = &subs->dataurb[i]; - u->index = i; - u->subs = subs; - u->packets = npacks[i]; - u->buffer_size = maxsize * u->packets; - if (subs->fmt_type == USB_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_buffer_alloc(subs->dev, u->buffer_size, GFP_KERNEL, - &u->urb->transfer_dma); - if (! u->urb->transfer_buffer) - goto out_of_memory; - u->urb->pipe = subs->datapipe; - u->urb->transfer_flags = URB_ISO_ASAP | URB_NO_TRANSFER_DMA_MAP; - u->urb->interval = 1 << subs->datainterval; - u->urb->context = u; - u->urb->complete = snd_complete_urb; - } - - if (subs->syncpipe) { - /* allocate and initialize sync urbs */ - subs->syncbuf = usb_buffer_alloc(subs->dev, SYNC_URBS * 4, - GFP_KERNEL, &subs->sync_dma); - if (! subs->syncbuf) - goto out_of_memory; - for (i = 0; i < SYNC_URBS; i++) { - struct snd_urb_ctx *u = &subs->syncurb[i]; - u->index = i; - u->subs = subs; - u->packets = 1; - u->urb = usb_alloc_urb(1, GFP_KERNEL); - if (! u->urb) - goto out_of_memory; - u->urb->transfer_buffer = subs->syncbuf + i * 4; - u->urb->transfer_dma = subs->sync_dma + i * 4; - u->urb->transfer_buffer_length = 4; - u->urb->pipe = subs->syncpipe; - u->urb->transfer_flags = URB_ISO_ASAP | - URB_NO_TRANSFER_DMA_MAP; - u->urb->number_of_packets = 1; - u->urb->interval = 1 << subs->syncinterval; - u->urb->context = u; - u->urb->complete = snd_complete_sync_urb; - } - } - return 0; - -out_of_memory: - release_substream_urbs(subs, 0); - return -ENOMEM; -} - - -/* - * find a matching audio format - */ -static struct audioformat *find_format(struct snd_usb_substream *subs, unsigned int format, - unsigned int rate, unsigned int channels) -{ - struct list_head *p; - struct audioformat *found = NULL; - int cur_attr = 0, attr; - - list_for_each(p, &subs->fmt_list) { - struct audioformat *fp; - fp = list_entry(p, struct audioformat, list); - if (fp->format != format || fp->channels != channels) - continue; - if (rate < fp->rate_min || rate > fp->rate_max) - continue; - if (! (fp->rates & SNDRV_PCM_RATE_CONTINUOUS)) { - unsigned int i; - for (i = 0; i < fp->nr_rates; i++) - if (fp->rate_table[i] == rate) - break; - if (i >= fp->nr_rates) - continue; - } - attr = fp->ep_attr & EP_ATTR_MASK; - if (! found) { - found = fp; - cur_attr = attr; - continue; - } - /* avoid async out and adaptive in if the other method - * supports the same format. - * this is a workaround for the case like - * M-audio audiophile USB. - */ - if (attr != cur_attr) { - if ((attr == EP_ATTR_ASYNC && - subs->direction == SNDRV_PCM_STREAM_PLAYBACK) || - (attr == EP_ATTR_ADAPTIVE && - subs->direction == SNDRV_PCM_STREAM_CAPTURE)) - continue; - if ((cur_attr == EP_ATTR_ASYNC && - subs->direction == SNDRV_PCM_STREAM_PLAYBACK) || - (cur_attr == EP_ATTR_ADAPTIVE && - subs->direction == SNDRV_PCM_STREAM_CAPTURE)) { - found = fp; - cur_attr = attr; - continue; - } - } - /* find the format with the largest max. packet size */ - if (fp->maxpacksize > found->maxpacksize) { - found = fp; - cur_attr = attr; - } - } - return found; -} - - -/* - * initialize the picth control and sample rate - */ -static int init_usb_pitch(struct usb_device *dev, int iface, - struct usb_host_interface *alts, - struct audioformat *fmt) -{ - unsigned int ep; - unsigned char data[1]; - int err; - - ep = get_endpoint(alts, 0)->bEndpointAddress; - /* if endpoint has pitch control, enable it */ - if (fmt->attributes & EP_CS_ATTR_PITCH_CONTROL) { - data[0] = 1; - if ((err = snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), SET_CUR, - USB_TYPE_CLASS|USB_RECIP_ENDPOINT|USB_DIR_OUT, - PITCH_CONTROL << 8, ep, data, 1, 1000)) < 0) { - snd_printk(KERN_ERR "%d:%d:%d: cannot set enable PITCH\n", - dev->devnum, iface, ep); - return err; - } - } - return 0; -} - -static int init_usb_sample_rate(struct usb_device *dev, int iface, - struct usb_host_interface *alts, - struct audioformat *fmt, int rate) -{ - unsigned int ep; - unsigned char data[3]; - int err; - - ep = get_endpoint(alts, 0)->bEndpointAddress; - /* if endpoint has sampling rate control, set it */ - if (fmt->attributes & EP_CS_ATTR_SAMPLE_RATE) { - int crate; - data[0] = rate; - data[1] = rate >> 8; - data[2] = rate >> 16; - if ((err = snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), SET_CUR, - USB_TYPE_CLASS|USB_RECIP_ENDPOINT|USB_DIR_OUT, - SAMPLING_FREQ_CONTROL << 8, ep, data, 3, 1000)) < 0) { - snd_printk(KERN_ERR "%d:%d:%d: cannot set freq %d to ep 0x%x\n", - dev->devnum, iface, fmt->altsetting, rate, ep); - return err; - } - if ((err = snd_usb_ctl_msg(dev, usb_rcvctrlpipe(dev, 0), GET_CUR, - USB_TYPE_CLASS|USB_RECIP_ENDPOINT|USB_DIR_IN, - SAMPLING_FREQ_CONTROL << 8, ep, data, 3, 1000)) < 0) { - snd_printk(KERN_WARNING "%d:%d:%d: cannot get freq at ep 0x%x\n", - dev->devnum, iface, fmt->altsetting, ep); - return 0; /* some devices don't support reading */ - } - crate = data[0] | (data[1] << 8) | (data[2] << 16); - if (crate != rate) { - snd_printd(KERN_WARNING "current rate %d is different from the runtime rate %d\n", crate, rate); - // runtime->rate = crate; - } - } - return 0; -} - -/* - * find a matching format and set up the interface - */ -static int set_format(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; - unsigned int ep, attr; - int is_playback = subs->direction == SNDRV_PCM_STREAM_PLAYBACK; - int err; - - iface = usb_ifnum_to_if(dev, fmt->iface); - snd_assert(iface, return -EINVAL); - alts = &iface->altsetting[fmt->altset_idx]; - altsd = get_iface_desc(alts); - snd_assert(altsd->bAlternateSetting == fmt->altsetting, return -EINVAL); - - if (fmt == subs->cur_audiofmt) - return 0; - - /* close the old interface */ - if (subs->interface >= 0 && subs->interface != fmt->iface) { - if (usb_set_interface(subs->dev, subs->interface, 0) < 0) { - snd_printk(KERN_ERR "%d:%d:%d: return to setting 0 failed\n", - dev->devnum, fmt->iface, fmt->altsetting); - return -EIO; - } - subs->interface = -1; - subs->format = 0; - } - - /* set interface */ - if (subs->interface != fmt->iface || subs->format != fmt->altset_idx) { - if (usb_set_interface(dev, fmt->iface, fmt->altsetting) < 0) { - snd_printk(KERN_ERR "%d:%d:%d: usb_set_interface failed\n", - dev->devnum, fmt->iface, fmt->altsetting); - return -EIO; - } - snd_printdd(KERN_INFO "setting usb interface %d:%d\n", fmt->iface, fmt->altsetting); - subs->interface = fmt->iface; - subs->format = fmt->altset_idx; - } - - /* create a data pipe */ - ep = fmt->endpoint & USB_ENDPOINT_NUMBER_MASK; - if (is_playback) - subs->datapipe = usb_sndisocpipe(dev, ep); - else - subs->datapipe = usb_rcvisocpipe(dev, ep); - if (snd_usb_get_speed(subs->dev) == USB_SPEED_HIGH && - get_endpoint(alts, 0)->bInterval >= 1 && - get_endpoint(alts, 0)->bInterval <= 4) - subs->datainterval = get_endpoint(alts, 0)->bInterval - 1; - else - subs->datainterval = 0; - subs->syncpipe = subs->syncinterval = 0; - subs->maxpacksize = fmt->maxpacksize; - subs->fill_max = 0; - - /* we need a sync pipe in async OUT or adaptive IN mode */ - /* check the number of EP, since some devices have broken - * descriptors which fool us. if it has only one EP, - * assume it as adaptive-out or sync-in. - */ - attr = fmt->ep_attr & EP_ATTR_MASK; - if (((is_playback && attr == EP_ATTR_ASYNC) || - (! is_playback && attr == EP_ATTR_ADAPTIVE)) && - altsd->bNumEndpoints >= 2) { - /* check sync-pipe endpoint */ - /* ... and check descriptor size before accessing bSynchAddress - because there is a version of the SB Audigy 2 NX firmware lacking - the audio fields in the endpoint descriptors */ - if ((get_endpoint(alts, 1)->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) != 0x01 || - (get_endpoint(alts, 1)->bLength >= USB_DT_ENDPOINT_AUDIO_SIZE && - get_endpoint(alts, 1)->bSynchAddress != 0)) { - snd_printk(KERN_ERR "%d:%d:%d : invalid synch pipe\n", - dev->devnum, fmt->iface, fmt->altsetting); - return -EINVAL; - } - ep = get_endpoint(alts, 1)->bEndpointAddress; - if (get_endpoint(alts, 0)->bLength >= USB_DT_ENDPOINT_AUDIO_SIZE && - (( is_playback && ep != (unsigned int)(get_endpoint(alts, 0)->bSynchAddress | USB_DIR_IN)) || - (!is_playback && ep != (unsigned int)(get_endpoint(alts, 0)->bSynchAddress & ~USB_DIR_IN)))) { - snd_printk(KERN_ERR "%d:%d:%d : invalid synch pipe\n", - dev->devnum, fmt->iface, fmt->altsetting); - return -EINVAL; - } - ep &= USB_ENDPOINT_NUMBER_MASK; - if (is_playback) - subs->syncpipe = usb_rcvisocpipe(dev, ep); - else - subs->syncpipe = usb_sndisocpipe(dev, ep); - if (get_endpoint(alts, 1)->bLength >= USB_DT_ENDPOINT_AUDIO_SIZE && - get_endpoint(alts, 1)->bRefresh >= 1 && - get_endpoint(alts, 1)->bRefresh <= 9) - subs->syncinterval = get_endpoint(alts, 1)->bRefresh; - else if (snd_usb_get_speed(subs->dev) == USB_SPEED_FULL) - subs->syncinterval = 1; - else if (get_endpoint(alts, 1)->bInterval >= 1 && - get_endpoint(alts, 1)->bInterval <= 16) - subs->syncinterval = get_endpoint(alts, 1)->bInterval - 1; - else - subs->syncinterval = 3; - } - - /* always fill max packet size */ - if (fmt->attributes & EP_CS_ATTR_FILL_MAX) - subs->fill_max = 1; - - if ((err = init_usb_pitch(dev, subs->interface, alts, fmt)) < 0) - return err; - - subs->cur_audiofmt = fmt; - -#if 0 - printk("setting done: format = %d, rate = %d, channels = %d\n", - fmt->format, fmt->rate, fmt->channels); - printk(" datapipe = 0x%0x, syncpipe = 0x%0x\n", - subs->datapipe, subs->syncpipe); -#endif - - return 0; -} - -/* - * hw_params callback - * - * allocate a buffer and set the given audio format. - * - * so far we use a physically linear buffer although packetize transfer - * doesn't need a continuous area. - * if sg buffer is supported on the later version of alsa, we'll follow - * that. - */ -static int snd_usb_hw_params(struct snd_pcm_substream *substream, - struct snd_pcm_hw_params *hw_params) -{ - struct snd_usb_substream *subs = substream->runtime->private_data; - struct audioformat *fmt; - unsigned int channels, rate, format; - int ret, changed; - - ret = snd_pcm_alloc_vmalloc_buffer(substream, - params_buffer_bytes(hw_params)); - if (ret < 0) - return ret; - - format = params_format(hw_params); - rate = params_rate(hw_params); - channels = params_channels(hw_params); - fmt = find_format(subs, format, rate, channels); - if (! fmt) { - snd_printd(KERN_DEBUG "cannot set format: format = 0x%x, rate = %d, channels = %d\n", - format, rate, channels); - return -EINVAL; - } - - changed = subs->cur_audiofmt != fmt || - subs->period_bytes != params_period_bytes(hw_params) || - subs->cur_rate != rate; - if ((ret = set_format(subs, fmt)) < 0) - return ret; - - if (subs->cur_rate != rate) { - struct usb_host_interface *alts; - struct usb_interface *iface; - iface = usb_ifnum_to_if(subs->dev, fmt->iface); - alts = &iface->altsetting[fmt->altset_idx]; - ret = init_usb_sample_rate(subs->dev, subs->interface, alts, fmt, rate); - if (ret < 0) - return ret; - subs->cur_rate = rate; - } - - if (changed) { - /* format changed */ - release_substream_urbs(subs, 0); - /* influenced: period_bytes, channels, rate, format, */ - ret = init_substream_urbs(subs, params_period_bytes(hw_params), - params_rate(hw_params), - snd_pcm_format_physical_width(params_format(hw_params)) * params_channels(hw_params)); - } - - return ret; -} - -/* - * hw_free callback - * - * reset the audio format and release the buffer - */ -static int snd_usb_hw_free(struct snd_pcm_substream *substream) -{ - struct snd_usb_substream *subs = substream->runtime->private_data; - - subs->cur_audiofmt = NULL; - subs->cur_rate = 0; - subs->period_bytes = 0; - if (!subs->stream->chip->shutdown) - release_substream_urbs(subs, 0); - return snd_pcm_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; - - if (! subs->cur_audiofmt) { - snd_printk(KERN_ERR "usbaudio: no format is specified!\n"); - return -ENXIO; - } - - /* some unit conversions in runtime */ - subs->maxframesize = bytes_to_frames(runtime, subs->maxpacksize); - subs->curframesize = bytes_to_frames(runtime, subs->curpacksize); - - /* reset the pointer */ - subs->hwptr_done = 0; - subs->transfer_done = 0; - subs->phase = 0; - - /* clear urbs (to be sure) */ - deactivate_urbs(subs, 0, 1); - wait_clear_urbs(subs); - - /* 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) { - subs->ops.prepare = prepare_nodata_playback_urb; - return start_urbs(subs, runtime); - } else - return 0; -} - -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, -}; - -/* - * h/w constraints - */ - -#ifdef HW_CONST_DEBUG -#define hwc_debug(fmt, args...) printk(KERN_DEBUG fmt, ##args) -#else -#define hwc_debug(fmt, args...) /**/ -#endif - -static int hw_check_valid_format(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); - - /* check the format */ - if (! snd_mask_test(fmts, fp->format)) { - hwc_debug(" > check: no supported format %d\n", fp->format); - return 0; - } - /* check the channels */ - if (fp->channels < ct->min || fp->channels > ct->max) { - hwc_debug(" > check: no valid channels %d (%d/%d)\n", fp->channels, ct->min, ct->max); - return 0; - } - /* check the rate is within the range */ - if (fp->rate_min > it->max || (fp->rate_min == it->max && it->openmax)) { - hwc_debug(" > check: rate_min %d > max %d\n", fp->rate_min, it->max); - return 0; - } - if (fp->rate_max < it->min || (fp->rate_max == it->min && it->openmin)) { - hwc_debug(" > check: rate_max %d < min %d\n", fp->rate_max, it->min); - return 0; - } - return 1; -} - -static int hw_rule_rate(struct snd_pcm_hw_params *params, - struct snd_pcm_hw_rule *rule) -{ - struct snd_usb_substream *subs = rule->private; - struct list_head *p; - 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(p, &subs->fmt_list) { - struct audioformat *fp; - fp = list_entry(p, struct audioformat, list); - if (! hw_check_valid_format(params, fp)) - continue; - if (changed++) { - if (rmin > fp->rate_min) - rmin = fp->rate_min; - if (rmax < fp->rate_max) - rmax = fp->rate_max; - } else { - rmin = fp->rate_min; - rmax = fp->rate_max; - } - } - - if (! changed) { - hwc_debug(" --> get empty\n"); - it->empty = 1; - return -EINVAL; - } - - changed = 0; - if (it->min < rmin) { - it->min = rmin; - it->openmin = 0; - changed = 1; - } - if (it->max > rmax) { - it->max = rmax; - it->openmax = 0; - changed = 1; - } - if (snd_interval_checkempty(it)) { - it->empty = 1; - return -EINVAL; - } - hwc_debug(" --> (%d, %d) (changed = %d)\n", it->min, it->max, changed); - return changed; -} - - -static int hw_rule_channels(struct snd_pcm_hw_params *params, - struct snd_pcm_hw_rule *rule) -{ - struct snd_usb_substream *subs = rule->private; - struct list_head *p; - 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(p, &subs->fmt_list) { - struct audioformat *fp; - fp = list_entry(p, struct audioformat, list); - if (! hw_check_valid_format(params, fp)) - continue; - if (changed++) { - if (rmin > fp->channels) - rmin = fp->channels; - if (rmax < fp->channels) - rmax = fp->channels; - } else { - rmin = fp->channels; - rmax = fp->channels; - } - } - - if (! changed) { - hwc_debug(" --> get empty\n"); - it->empty = 1; - return -EINVAL; - } - - changed = 0; - if (it->min < rmin) { - it->min = rmin; - it->openmin = 0; - changed = 1; - } - if (it->max > rmax) { - it->max = rmax; - it->openmax = 0; - changed = 1; - } - if (snd_interval_checkempty(it)) { - it->empty = 1; - return -EINVAL; - } - hwc_debug(" --> (%d, %d) (changed = %d)\n", it->min, it->max, changed); - return changed; -} - -static int hw_rule_format(struct snd_pcm_hw_params *params, - struct snd_pcm_hw_rule *rule) -{ - struct snd_usb_substream *subs = rule->private; - struct list_head *p; - 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(p, &subs->fmt_list) { - struct audioformat *fp; - fp = list_entry(p, struct audioformat, list); - if (! hw_check_valid_format(params, fp)) - continue; - fbits |= (1ULL << fp->format); - } - - oldbits[0] = fmt->bits[0]; - oldbits[1] = fmt->bits[1]; - fmt->bits[0] &= (u32)fbits; - fmt->bits[1] &= (u32)(fbits >> 32); - if (! fmt->bits[0] && ! fmt->bits[1]) { - hwc_debug(" --> get empty\n"); - return -EINVAL; - } - changed = (oldbits[0] != fmt->bits[0] || oldbits[1] != fmt->bits[1]); - hwc_debug(" --> %x:%x (changed = %d)\n", fmt->bits[0], fmt->bits[1], changed); - return changed; -} - -#define MAX_MASK 64 - -/* - * check whether the registered audio formats need special hw-constraints - */ -static int check_hw_params_convention(struct snd_usb_substream *subs) -{ - int i; - u32 *channels; - u32 *rates; - u32 cmaster, rmaster; - u32 rate_min = 0, rate_max = 0; - struct list_head *p; - int err = 1; - - channels = kcalloc(MAX_MASK, sizeof(u32), GFP_KERNEL); - rates = kcalloc(MAX_MASK, sizeof(u32), GFP_KERNEL); - if (!channels || !rates) - goto __out; - - list_for_each(p, &subs->fmt_list) { - struct audioformat *f; - f = list_entry(p, struct audioformat, list); - /* unconventional channels? */ - if (f->channels > 32) - goto __out; - /* continuous rate min/max matches? */ - if (f->rates & SNDRV_PCM_RATE_CONTINUOUS) { - if (rate_min && f->rate_min != rate_min) - goto __out; - if (rate_max && f->rate_max != rate_max) - goto __out; - rate_min = f->rate_min; - rate_max = f->rate_max; - } - /* combination of continuous rates and fixed rates? */ - if (rates[f->format] & SNDRV_PCM_RATE_CONTINUOUS) { - if (f->rates != rates[f->format]) - goto __out; - } - if (f->rates & SNDRV_PCM_RATE_CONTINUOUS) { - if (rates[f->format] && rates[f->format] != f->rates) - goto __out; - } - channels[f->format] |= (1 << f->channels); - rates[f->format] |= f->rates; - /* needs knot? */ - if (f->rates & SNDRV_PCM_RATE_KNOT) - goto __out; - } - /* check whether channels and rates match for all formats */ - cmaster = rmaster = 0; - for (i = 0; i < MAX_MASK; i++) { - if (cmaster != channels[i] && cmaster && channels[i]) - goto __out; - if (rmaster != rates[i] && rmaster && rates[i]) - goto __out; - if (channels[i]) - cmaster = channels[i]; - if (rates[i]) - rmaster = rates[i]; - } - /* check whether channels match for all distinct rates */ - memset(channels, 0, MAX_MASK * sizeof(u32)); - list_for_each(p, &subs->fmt_list) { - struct audioformat *f; - f = list_entry(p, struct audioformat, list); - if (f->rates & SNDRV_PCM_RATE_CONTINUOUS) - continue; - for (i = 0; i < 32; i++) { - if (f->rates & (1 << i)) - channels[i] |= (1 << f->channels); - } - } - cmaster = 0; - for (i = 0; i < 32; i++) { - if (cmaster != channels[i] && cmaster && channels[i]) - goto __out; - if (channels[i]) - cmaster = channels[i]; - } - err = 0; - - __out: - kfree(channels); - kfree(rates); - return err; -} - -/* - * 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 count = 0, needs_knot = 0; - int err; - - 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.count = count; - subs->rate_list.list = kmalloc(sizeof(int) * count, GFP_KERNEL); - 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++) - subs->rate_list.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 list_head *p; - int err; - - runtime->hw.formats = subs->formats; - - runtime->hw.rate_min = 0x7fffffff; - runtime->hw.rate_max = 0; - runtime->hw.channels_min = 256; - runtime->hw.channels_max = 0; - runtime->hw.rates = 0; - /* check min/max rates and channels */ - list_for_each(p, &subs->fmt_list) { - struct audioformat *fp; - fp = list_entry(p, struct audioformat, list); - runtime->hw.rates |= fp->rates; - if (runtime->hw.rate_min > fp->rate_min) - runtime->hw.rate_min = fp->rate_min; - if (runtime->hw.rate_max < fp->rate_max) - runtime->hw.rate_max = fp->rate_max; - if (runtime->hw.channels_min > fp->channels) - runtime->hw.channels_min = fp->channels; - if (runtime->hw.channels_max < fp->channels) - runtime->hw.channels_max = fp->channels; - if (fp->fmt_type == USB_FORMAT_TYPE_II && fp->frame_size > 0) { - /* FIXME: there might be more than one audio formats... */ - runtime->hw.period_bytes_min = runtime->hw.period_bytes_max = - fp->frame_size; - } - } - - /* set the period time minimum 1ms */ - /* FIXME: high-speed mode allows 125us minimum period, but many parts - * in the current code assume the 1ms period. - */ - snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_PERIOD_TIME, - 1000 * MIN_PACKS_URB, - /*(nrpacks * MAX_URBS) * 1000*/ UINT_MAX); - - if (check_hw_params_convention(subs)) { - hwc_debug("setting extra hw constraints...\n"); - if ((err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, - hw_rule_rate, subs, - SNDRV_PCM_HW_PARAM_FORMAT, - SNDRV_PCM_HW_PARAM_CHANNELS, - -1)) < 0) - return err; - if ((err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, - hw_rule_channels, subs, - SNDRV_PCM_HW_PARAM_FORMAT, - SNDRV_PCM_HW_PARAM_RATE, - -1)) < 0) - return err; - if ((err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_FORMAT, - hw_rule_format, subs, - SNDRV_PCM_HW_PARAM_RATE, - SNDRV_PCM_HW_PARAM_CHANNELS, - -1)) < 0) - return err; - if ((err = snd_usb_pcm_check_knot(runtime, subs)) < 0) - return err; - } - return 0; -} - -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->format = 0; - runtime->hw = snd_usb_hardware; - runtime->private_data = subs; - subs->pcm_substream = substream; - 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]; - - if (subs->interface >= 0) { - usb_set_interface(subs->dev, subs->interface, 0); - subs->interface = -1; - } - subs->pcm_substream = NULL; - return 0; -} - -static int snd_usb_playback_open(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 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_pcm_playback_trigger, - .pointer = snd_usb_pcm_pointer, - .page = snd_pcm_get_vmalloc_page, -}; - -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_pcm_capture_trigger, - .pointer = snd_usb_pcm_pointer, - .page = snd_pcm_get_vmalloc_page, -}; - - - -/* - * helper functions - */ - -/* - * combine bytes and get an integer value - */ -unsigned int snd_usb_combine_bytes(unsigned char *bytes, int size) -{ - switch (size) { - case 1: return *bytes; - case 2: return combine_word(bytes); - case 3: return combine_triple(bytes); - case 4: return combine_quad(bytes); - default: return 0; - } -} - -/* - * parse descriptor buffer and return the pointer starting the given - * descriptor type. - */ -void *snd_usb_find_desc(void *descstart, int desclen, void *after, u8 dtype) -{ - u8 *p, *end, *next; - - p = descstart; - end = p + desclen; - for (; p < end;) { - if (p[0] < 2) - return NULL; - next = p + p[0]; - if (next > end) - return NULL; - if (p[1] == dtype && (!after || (void *)p > after)) { - return p; - } - p = next; - } - return NULL; -} - -/* - * find a class-specified interface descriptor with the given subtype. - */ -void *snd_usb_find_csint_desc(void *buffer, int buflen, void *after, u8 dsubtype) -{ - unsigned char *p = after; - - while ((p = snd_usb_find_desc(buffer, buflen, p, - USB_DT_CS_INTERFACE)) != NULL) { - if (p[0] >= 3 && p[2] == dsubtype) - return p; - } - return NULL; -} - -/* - * Wrapper for usb_control_msg(). - * Allocates a temp buffer to prevent dmaing from/to the stack. - */ -int snd_usb_ctl_msg(struct usb_device *dev, unsigned int pipe, __u8 request, - __u8 requesttype, __u16 value, __u16 index, void *data, - __u16 size, int timeout) -{ - int err; - void *buf = NULL; - - if (size > 0) { - buf = 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; -} - - -/* - * entry point for linux usb interface - */ - -static int usb_audio_probe(struct usb_interface *intf, - const struct usb_device_id *id); -static void usb_audio_disconnect(struct usb_interface *intf); - -#ifdef CONFIG_PM -static int usb_audio_suspend(struct usb_interface *intf, pm_message_t message); -static int usb_audio_resume(struct usb_interface *intf); -#else -#define usb_audio_suspend NULL -#define usb_audio_resume NULL -#endif - -static struct usb_device_id usb_audio_ids [] = { -#include "usbquirks.h" - { .match_flags = (USB_DEVICE_ID_MATCH_INT_CLASS | USB_DEVICE_ID_MATCH_INT_SUBCLASS), - .bInterfaceClass = USB_CLASS_AUDIO, - .bInterfaceSubClass = USB_SUBCLASS_AUDIO_CONTROL }, - { } /* Terminating entry */ -}; - -MODULE_DEVICE_TABLE (usb, usb_audio_ids); - -static struct usb_driver usb_audio_driver = { - .name = "snd-usb-audio", - .probe = usb_audio_probe, - .disconnect = usb_audio_disconnect, - .suspend = usb_audio_suspend, - .resume = usb_audio_resume, - .id_table = usb_audio_ids, -}; - - -#if defined(CONFIG_PROC_FS) && defined(CONFIG_SND_VERBOSE_PROCFS) - -/* - * 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 list_head *p; - static char *sync_types[4] = { - "NONE", "ASYNC", "ADAPTIVE", "SYNC" - }; - - list_for_each(p, &subs->fmt_list) { - struct audioformat *fp; - fp = list_entry(p, struct audioformat, list); - snd_iprintf(buffer, " Interface %d\n", fp->iface); - snd_iprintf(buffer, " Altset %d\n", fp->altsetting); - snd_iprintf(buffer, " Format: 0x%x\n", fp->format); - snd_iprintf(buffer, " Channels: %d\n", fp->channels); - snd_iprintf(buffer, " Endpoint: %d %s (%s)\n", - fp->endpoint & USB_ENDPOINT_NUMBER_MASK, - fp->endpoint & USB_DIR_IN ? "IN" : "OUT", - sync_types[(fp->ep_attr & EP_ATTR_MASK) >> 2]); - if (fp->rates & SNDRV_PCM_RATE_CONTINUOUS) { - snd_iprintf(buffer, " Rates: %d - %d (continuous)\n", - fp->rate_min, fp->rate_max); - } else { - unsigned int i; - snd_iprintf(buffer, " Rates: "); - for (i = 0; i < fp->nr_rates; i++) { - if (i > 0) - snd_iprintf(buffer, ", "); - snd_iprintf(buffer, "%d", fp->rate_table[i]); - } - snd_iprintf(buffer, "\n"); - } - // snd_iprintf(buffer, " Max Packet Size = %d\n", fp->maxpacksize); - // snd_iprintf(buffer, " EP Attribute = 0x%x\n", fp->attributes); - } -} - -static void proc_dump_substream_status(struct snd_usb_substream *subs, struct snd_info_buffer *buffer) -{ - if (subs->running) { - unsigned int i; - snd_iprintf(buffer, " Status: Running\n"); - snd_iprintf(buffer, " Interface = %d\n", subs->interface); - snd_iprintf(buffer, " Altset = %d\n", subs->format); - snd_iprintf(buffer, " URBs = %d [ ", subs->nurbs); - for (i = 0; i < subs->nurbs; i++) - snd_iprintf(buffer, "%d ", subs->dataurb[i].packets); - snd_iprintf(buffer, "]\n"); - snd_iprintf(buffer, " Packet Size = %d\n", subs->curpacksize); - snd_iprintf(buffer, " Momentary freq = %u Hz (%#x.%04x)\n", - snd_usb_get_speed(subs->dev) == USB_SPEED_FULL - ? get_full_speed_hz(subs->freqm) - : get_high_speed_hz(subs->freqm), - subs->freqm >> 16, subs->freqm & 0xffff); - } 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); - } -} - -static void 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); -} - -#else - -static inline void proc_pcm_format_add(struct snd_usb_stream *stream) -{ -} - -#endif - -/* - * initialize the substream instance. - */ - -static void 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; - if (snd_usb_get_speed(subs->dev) == USB_SPEED_FULL) { - subs->ops = audio_urb_ops[stream]; - } else { - subs->ops = audio_urb_ops_high_speed[stream]; - switch (as->chip->usb_id) { - case USB_ID(0x041e, 0x3f02): /* E-Mu 0202 USB */ - case USB_ID(0x041e, 0x3f04): /* E-Mu 0404 USB */ - subs->ops.retire_sync = retire_playback_sync_urb_hs_emu; - break; - } - } - snd_pcm_set_ops(as->pcm, stream, - stream == SNDRV_PCM_STREAM_PLAYBACK ? - &snd_usb_playback_ops : &snd_usb_capture_ops); - - list_add_tail(&fp->list, &subs->fmt_list); - subs->formats |= 1ULL << fp->format; - subs->endpoint = fp->endpoint; - subs->num_formats++; - subs->fmt_type = fp->fmt_type; -} - - -/* - * free a substream - */ -static void free_substream(struct snd_usb_substream *subs) -{ - struct list_head *p, *n; - - if (! subs->num_formats) - return; /* not initialized */ - list_for_each_safe(p, n, &subs->fmt_list) { - struct audioformat *fp = list_entry(p, struct audioformat, list); - kfree(fp->rate_table); - kfree(fp); - } - 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); - } -} - - -/* - * add this endpoint to the chip instance. - * if a stream with the same endpoint already exists, append to it. - * if not, create a new pcm stream. - */ -static int add_audio_endpoint(struct snd_usb_audio *chip, int stream, struct audioformat *fp) -{ - struct list_head *p; - struct snd_usb_stream *as; - struct snd_usb_substream *subs; - struct snd_pcm *pcm; - int err; - - list_for_each(p, &chip->pcm_list) { - as = list_entry(p, struct snd_usb_stream, list); - if (as->fmt_type != fp->fmt_type) - continue; - subs = &as->substream[stream]; - if (! subs->endpoint) - continue; - if (subs->endpoint == fp->endpoint) { - list_add_tail(&fp->list, &subs->fmt_list); - subs->num_formats++; - subs->formats |= 1ULL << fp->format; - return 0; - } - } - /* look for an empty stream */ - list_for_each(p, &chip->pcm_list) { - as = list_entry(p, struct snd_usb_stream, list); - if (as->fmt_type != fp->fmt_type) - continue; - subs = &as->substream[stream]; - if (subs->endpoint) - continue; - err = snd_pcm_new_stream(as->pcm, stream, 1); - if (err < 0) - return err; - init_substream(as, stream, fp); - return 0; - } - - /* create a new pcm */ - as = 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"); - - init_substream(as, stream, fp); - - list_add(&as->list, &chip->pcm_list); - chip->pcm_devs++; - - proc_pcm_format_add(as); - - return 0; -} - - -/* - * check if the device uses big-endian samples - */ -static int is_big_endian_format(struct snd_usb_audio *chip, struct audioformat *fp) -{ - switch (chip->usb_id) { - case USB_ID(0x0763, 0x2001): /* M-Audio Quattro: captured data only */ - if (fp->endpoint & USB_DIR_IN) - return 1; - break; - case USB_ID(0x0763, 0x2003): /* M-Audio Audiophile USB */ - if (device_setup[chip->index] == 0x00 || - fp->altsetting==1 || fp->altsetting==2 || fp->altsetting==3) - return 1; - } - return 0; -} - -/* - * parse the audio format type I descriptor - * and returns the corresponding pcm format - * - * @dev: usb device - * @fp: audioformat record - * @format: the format tag (wFormatTag) - * @fmt: the format type descriptor - */ -static int parse_audio_format_i_type(struct snd_usb_audio *chip, struct audioformat *fp, - int format, unsigned char *fmt) -{ - int pcm_format; - int sample_width, sample_bytes; - - /* FIXME: correct endianess and sign? */ - pcm_format = -1; - sample_width = fmt[6]; - sample_bytes = fmt[5]; - switch (format) { - case 0: /* some devices don't define this correctly... */ - snd_printdd(KERN_INFO "%d:%u:%d : format type 0 is detected, processed as PCM\n", - chip->dev->devnum, fp->iface, fp->altsetting); - /* fall-through */ - case USB_AUDIO_FORMAT_PCM: - if (sample_width > sample_bytes * 8) { - snd_printk(KERN_INFO "%d:%u:%d : sample bitwidth %d in over sample bytes %d\n", - chip->dev->devnum, fp->iface, fp->altsetting, - sample_width, sample_bytes); - } - /* check the format byte size */ - switch (fmt[5]) { - case 1: - pcm_format = SNDRV_PCM_FORMAT_S8; - break; - case 2: - if (is_big_endian_format(chip, fp)) - pcm_format = SNDRV_PCM_FORMAT_S16_BE; /* grrr, big endian!! */ - else - pcm_format = SNDRV_PCM_FORMAT_S16_LE; - break; - case 3: - if (is_big_endian_format(chip, fp)) - pcm_format = SNDRV_PCM_FORMAT_S24_3BE; /* grrr, big endian!! */ - else - pcm_format = SNDRV_PCM_FORMAT_S24_3LE; - break; - case 4: - pcm_format = SNDRV_PCM_FORMAT_S32_LE; - break; - default: - snd_printk(KERN_INFO "%d:%u:%d : unsupported sample bitwidth %d in %d bytes\n", - chip->dev->devnum, fp->iface, - fp->altsetting, sample_width, sample_bytes); - break; - } - break; - case USB_AUDIO_FORMAT_PCM8: - /* Dallas DS4201 workaround */ - if (chip->usb_id == USB_ID(0x04fa, 0x4201)) - pcm_format = SNDRV_PCM_FORMAT_S8; - else - pcm_format = SNDRV_PCM_FORMAT_U8; - break; - case USB_AUDIO_FORMAT_IEEE_FLOAT: - pcm_format = SNDRV_PCM_FORMAT_FLOAT_LE; - break; - case USB_AUDIO_FORMAT_ALAW: - pcm_format = SNDRV_PCM_FORMAT_A_LAW; - break; - case USB_AUDIO_FORMAT_MU_LAW: - pcm_format = SNDRV_PCM_FORMAT_MU_LAW; - break; - default: - snd_printk(KERN_INFO "%d:%u:%d : unsupported format type %d\n", - chip->dev->devnum, fp->iface, fp->altsetting, format); - break; - } - return pcm_format; -} - - -/* - * parse the format descriptor and stores the possible sample rates - * on the audioformat table. - * - * @dev: usb device - * @fp: audioformat record - * @fmt: the format descriptor - * @offset: the start offset of descriptor pointing the rate type - * (7 for type I and II, 8 for type II) - */ -static int parse_audio_format_rates(struct 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)) { - snd_printk(KERN_ERR "%d:%u:%d : invalid FORMAT_TYPE desc\n", - chip->dev->devnum, fp->iface, fp->altsetting); - return -1; - } - - if (nr_rates) { - /* - * build the rate table and bitmap flags - */ - int r, idx; - unsigned int nonzero_rates = 0; - - fp->rate_table = kmalloc(sizeof(int) * nr_rates, GFP_KERNEL); - if (fp->rate_table == NULL) { - snd_printk(KERN_ERR "cannot malloc\n"); - return -1; - } - - fp->nr_rates = nr_rates; - fp->rate_min = fp->rate_max = combine_triple(&fmt[8]); - for (r = 0, idx = offset + 1; r < nr_rates; r++, idx += 3) { - unsigned int rate = combine_triple(&fmt[idx]); - /* C-Media CM6501 mislabels its 96 kHz altsetting */ - if (rate == 48000 && nr_rates == 1 && - chip->usb_id == USB_ID(0x0d8c, 0x0201) && - fp->altsetting == 5 && fp->maxpacksize == 392) - rate = 96000; - fp->rate_table[r] = rate; - nonzero_rates |= rate; - if (rate < fp->rate_min) - fp->rate_min = rate; - else if (rate > fp->rate_max) - fp->rate_max = rate; - fp->rates |= snd_pcm_rate_to_rate_bit(rate); - } - if (!nonzero_rates) { - hwc_debug("All rates were zero. Skipping format!\n"); - return -1; - } - } else { - /* continuous rates */ - fp->rates = SNDRV_PCM_RATE_CONTINUOUS; - fp->rate_min = combine_triple(&fmt[offset + 1]); - fp->rate_max = combine_triple(&fmt[offset + 4]); - } - return 0; -} - -/* - * parse the format type I and III descriptors - */ -static int parse_audio_format_i(struct snd_usb_audio *chip, struct audioformat *fp, - int format, unsigned char *fmt) -{ - int pcm_format; - - if (fmt[3] == USB_FORMAT_TYPE_III) { - /* FIXME: the format type is really IECxxx - * but we give normal PCM format to get the existing - * apps working... - */ - switch (chip->usb_id) { - - case USB_ID(0x0763, 0x2003): /* M-Audio Audiophile USB */ - if (device_setup[chip->index] == 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; - } - } else { - pcm_format = parse_audio_format_i_type(chip, fp, format, fmt); - if (pcm_format < 0) - return -1; - } - fp->format = pcm_format; - fp->channels = fmt[4]; - if (fp->channels < 1) { - snd_printk(KERN_ERR "%d:%u:%d : invalid channels %d\n", - chip->dev->devnum, fp->iface, fp->altsetting, fp->channels); - return -1; - } - return parse_audio_format_rates(chip, fp, fmt, 7); -} - -/* - * prase the format type II descriptor - */ -static int parse_audio_format_ii(struct snd_usb_audio *chip, struct audioformat *fp, - int format, unsigned char *fmt) -{ - int brate, framesize; - switch (format) { - case USB_AUDIO_FORMAT_AC3: - /* FIXME: there is no AC3 format defined yet */ - // fp->format = SNDRV_PCM_FORMAT_AC3; - fp->format = SNDRV_PCM_FORMAT_U8; /* temporarily hack to receive byte streams */ - break; - case USB_AUDIO_FORMAT_MPEG: - fp->format = SNDRV_PCM_FORMAT_MPEG; - break; - default: - snd_printd(KERN_INFO "%d:%u:%d : unknown format tag 0x%x is detected. processed as MPEG.\n", - chip->dev->devnum, fp->iface, fp->altsetting, format); - fp->format = SNDRV_PCM_FORMAT_MPEG; - break; - } - fp->channels = 1; - brate = combine_word(&fmt[4]); /* fmt[4,5] : wMaxBitRate (in kbps) */ - framesize = combine_word(&fmt[6]); /* fmt[6,7]: wSamplesPerFrame */ - snd_printd(KERN_INFO "found format II with max.bitrate = %d, frame size=%d\n", brate, framesize); - fp->frame_size = framesize; - return parse_audio_format_rates(chip, fp, fmt, 8); /* fmt[8..] sample rates */ -} - -static int parse_audio_format(struct snd_usb_audio *chip, struct audioformat *fp, - int format, unsigned char *fmt, int stream) -{ - int err; - - switch (fmt[3]) { - case USB_FORMAT_TYPE_I: - case USB_FORMAT_TYPE_III: - err = parse_audio_format_i(chip, fp, format, fmt); - break; - case USB_FORMAT_TYPE_II: - err = parse_audio_format_ii(chip, fp, format, fmt); - break; - default: - snd_printd(KERN_INFO "%d:%u:%d : format type %d is not supported yet\n", - chip->dev->devnum, fp->iface, fp->altsetting, fmt[3]); - return -1; - } - fp->fmt_type = fmt[3]; - if (err < 0) - return err; -#if 1 - /* FIXME: temporary hack for extigy/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[3] == USB_FORMAT_TYPE_I && - fp->rates != SNDRV_PCM_RATE_48000 && - fp->rates != SNDRV_PCM_RATE_96000) - return -1; - } -#endif - return 0; -} - -static int audiophile_skip_setting_quirk(struct snd_usb_audio *chip, - int iface, int altno); -static int parse_audio_endpoints(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; - int format; - struct audioformat *fp; - unsigned char *fmt, *csep; - - dev = chip->dev; - - /* parse the interface's altsettings */ - iface = usb_ifnum_to_if(dev, iface_no); - for (i = 0; i < iface->num_altsetting; i++) { - alts = &iface->altsetting[i]; - altsd = get_iface_desc(alts); - /* skip invalid one */ - if ((altsd->bInterfaceClass != USB_CLASS_AUDIO && - altsd->bInterfaceClass != USB_CLASS_VENDOR_SPEC) || - (altsd->bInterfaceSubClass != USB_SUBCLASS_AUDIO_STREAMING && - altsd->bInterfaceSubClass != USB_SUBCLASS_VENDOR_SPEC) || - altsd->bNumEndpoints < 1 || - le16_to_cpu(get_endpoint(alts, 0)->wMaxPacketSize) == 0) - continue; - /* must be isochronous */ - if ((get_endpoint(alts, 0)->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) != - USB_ENDPOINT_XFER_ISOC) - continue; - /* check direction */ - stream = (get_endpoint(alts, 0)->bEndpointAddress & USB_DIR_IN) ? - SNDRV_PCM_STREAM_CAPTURE : SNDRV_PCM_STREAM_PLAYBACK; - altno = altsd->bAlternateSetting; - - /* audiophile usb: skip altsets incompatible with device_setup - */ - if (chip->usb_id == USB_ID(0x0763, 0x2003) && - audiophile_skip_setting_quirk(chip, iface_no, altno)) - continue; - - /* get audio formats */ - fmt = snd_usb_find_csint_desc(alts->extra, alts->extralen, NULL, AS_GENERAL); - if (!fmt) { - snd_printk(KERN_ERR "%d:%u:%d : AS_GENERAL descriptor not found\n", - dev->devnum, iface_no, altno); - continue; - } - - if (fmt[0] < 7) { - snd_printk(KERN_ERR "%d:%u:%d : invalid AS_GENERAL desc\n", - dev->devnum, iface_no, altno); - continue; - } - - format = (fmt[6] << 8) | fmt[5]; /* remember the format value */ - - /* get format type */ - fmt = snd_usb_find_csint_desc(alts->extra, alts->extralen, NULL, FORMAT_TYPE); - if (!fmt) { - snd_printk(KERN_ERR "%d:%u:%d : no FORMAT_TYPE desc\n", - dev->devnum, iface_no, altno); - continue; - } - if (fmt[0] < 8) { - snd_printk(KERN_ERR "%d:%u:%d : invalid FORMAT_TYPE desc\n", - dev->devnum, iface_no, altno); - continue; - } - - csep = snd_usb_find_desc(alts->endpoint[0].extra, alts->endpoint[0].extralen, NULL, USB_DT_CS_ENDPOINT); - /* Creamware Noah has this descriptor after the 2nd endpoint */ - if (!csep && altsd->bNumEndpoints >= 2) - csep = snd_usb_find_desc(alts->endpoint[1].extra, alts->endpoint[1].extralen, NULL, USB_DT_CS_ENDPOINT); - if (!csep || csep[0] < 7 || csep[2] != EP_GENERAL) { - snd_printk(KERN_WARNING "%d:%u:%d : no or invalid" - " class specific endpoint descriptor\n", - dev->devnum, iface_no, altno); - csep = NULL; - } - - fp = kzalloc(sizeof(*fp), GFP_KERNEL); - if (! fp) { - snd_printk(KERN_ERR "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->maxpacksize = le16_to_cpu(get_endpoint(alts, 0)->wMaxPacketSize); - if (snd_usb_get_speed(dev) == USB_SPEED_HIGH) - fp->maxpacksize = (((fp->maxpacksize >> 11) & 3) + 1) - * (fp->maxpacksize & 0x7ff); - fp->attributes = csep ? csep[3] : 0; - - /* 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 &= ~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 |= EP_CS_ATTR_SAMPLE_RATE; - break; - 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 &= ~EP_ATTR_MASK; - if (stream == SNDRV_PCM_STREAM_PLAYBACK) - fp->ep_attr |= EP_ATTR_ADAPTIVE; - else - fp->ep_attr |= EP_ATTR_SYNC; - break; - } - - /* ok, let's parse further... */ - if (parse_audio_format(chip, fp, format, fmt, stream) < 0) { - kfree(fp->rate_table); - kfree(fp); - continue; - } - - snd_printdd(KERN_INFO "%d:%u:%d: add audio endpoint 0x%x\n", dev->devnum, iface_no, altno, fp->endpoint); - err = add_audio_endpoint(chip, stream, fp); - if (err < 0) { - kfree(fp->rate_table); - kfree(fp); - return err; - } - /* try to set the interface... */ - usb_set_interface(chip->dev, iface_no, altno); - init_usb_pitch(chip->dev, iface_no, alts, fp); - init_usb_sample_rate(chip->dev, iface_no, alts, fp, fp->rate_max); - } - return 0; -} - - -/* - * disconnect streams - * called from snd_usb_audio_disconnect() - */ -static void snd_usb_stream_disconnect(struct list_head *head) -{ - int idx; - struct snd_usb_stream *as; - struct snd_usb_substream *subs; - - as = list_entry(head, struct snd_usb_stream, list); - for (idx = 0; idx < 2; idx++) { - subs = &as->substream[idx]; - if (!subs->num_formats) - return; - release_substream_urbs(subs, 1); - subs->interface = -1; - } -} - -/* - * parse audio control descriptor and create pcm/midi streams - */ -static int snd_usb_create_streams(struct snd_usb_audio *chip, int ctrlif) -{ - struct usb_device *dev = chip->dev; - struct usb_host_interface *host_iface; - struct usb_interface *iface; - unsigned char *p1; - int i, j; - - /* find audiocontrol interface */ - host_iface = &usb_ifnum_to_if(dev, ctrlif)->altsetting[0]; - if (!(p1 = snd_usb_find_csint_desc(host_iface->extra, host_iface->extralen, NULL, HEADER))) { - snd_printk(KERN_ERR "cannot find HEADER\n"); - return -EINVAL; - } - if (! p1[7] || p1[0] < 8 + p1[7]) { - snd_printk(KERN_ERR "invalid HEADER\n"); - return -EINVAL; - } - - /* - * parse all USB audio streaming interfaces - */ - for (i = 0; i < p1[7]; i++) { - struct usb_host_interface *alts; - struct usb_interface_descriptor *altsd; - j = p1[8 + i]; - iface = usb_ifnum_to_if(dev, j); - if (!iface) { - snd_printk(KERN_ERR "%d:%u:%d : does not exist\n", - dev->devnum, ctrlif, j); - continue; - } - if (usb_interface_claimed(iface)) { - snd_printdd(KERN_INFO "%d:%d:%d: skipping, already claimed\n", dev->devnum, ctrlif, j); - continue; - } - alts = &iface->altsetting[0]; - altsd = get_iface_desc(alts); - if ((altsd->bInterfaceClass == USB_CLASS_AUDIO || - altsd->bInterfaceClass == USB_CLASS_VENDOR_SPEC) && - altsd->bInterfaceSubClass == USB_SUBCLASS_MIDI_STREAMING) { - if (snd_usb_create_midi_interface(chip, iface, NULL) < 0) { - snd_printk(KERN_ERR "%d:%u:%d: cannot create sequencer device\n", dev->devnum, ctrlif, j); - continue; - } - usb_driver_claim_interface(&usb_audio_driver, iface, (void *)-1L); - continue; - } - if ((altsd->bInterfaceClass != USB_CLASS_AUDIO && - altsd->bInterfaceClass != USB_CLASS_VENDOR_SPEC) || - altsd->bInterfaceSubClass != USB_SUBCLASS_AUDIO_STREAMING) { - snd_printdd(KERN_ERR "%d:%u:%d: skipping non-supported interface %d\n", dev->devnum, ctrlif, j, altsd->bInterfaceClass); - /* skip non-supported classes */ - continue; - } - if (snd_usb_get_speed(dev) == USB_SPEED_LOW) { - snd_printk(KERN_ERR "low speed audio streaming not supported\n"); - continue; - } - if (! parse_audio_endpoints(chip, j)) { - usb_set_interface(dev, j, 0); /* reset the current interface */ - usb_driver_claim_interface(&usb_audio_driver, iface, (void *)-1L); - } - } - - return 0; -} - -/* - * create a stream for an endpoint/altsetting without proper descriptors - */ -static int create_fixed_stream_quirk(struct snd_usb_audio *chip, - struct usb_interface *iface, - const struct snd_usb_audio_quirk *quirk) -{ - struct audioformat *fp; - struct usb_host_interface *alts; - int stream, err; - unsigned *rate_table = NULL; - - fp = kmemdup(quirk->data, sizeof(*fp), GFP_KERNEL); - if (! fp) { - snd_printk(KERN_ERR "cannot memdup\n"); - return -ENOMEM; - } - if (fp->nr_rates > 0) { - rate_table = kmalloc(sizeof(int) * fp->nr_rates, GFP_KERNEL); - if (!rate_table) { - kfree(fp); - return -ENOMEM; - } - memcpy(rate_table, fp->rate_table, sizeof(int) * fp->nr_rates); - fp->rate_table = rate_table; - } - - stream = (fp->endpoint & USB_DIR_IN) - ? SNDRV_PCM_STREAM_CAPTURE : SNDRV_PCM_STREAM_PLAYBACK; - err = add_audio_endpoint(chip, stream, fp); - if (err < 0) { - kfree(fp); - kfree(rate_table); - return err; - } - if (fp->iface != get_iface_desc(&iface->altsetting[0])->bInterfaceNumber || - fp->altset_idx >= iface->num_altsetting) { - kfree(fp); - kfree(rate_table); - return -EINVAL; - } - alts = &iface->altsetting[fp->altset_idx]; - usb_set_interface(chip->dev, fp->iface, 0); - init_usb_pitch(chip->dev, fp->iface, alts, fp); - init_usb_sample_rate(chip->dev, fp->iface, alts, fp, fp->rate_max); - return 0; -} - -/* - * create a stream for an interface with proper descriptors - */ -static int create_standard_audio_quirk(struct snd_usb_audio *chip, - struct usb_interface *iface, - 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 = parse_audio_endpoints(chip, altsd->bInterfaceNumber); - if (err < 0) { - snd_printk(KERN_ERR "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 Edirol UA-700/UA-25 interface. The only way - * to detect the sample rate is by looking at wMaxPacketSize. - */ -static int create_ua700_ua25_quirk(struct snd_usb_audio *chip, - struct usb_interface *iface, - const struct snd_usb_audio_quirk *quirk) -{ - static const struct audioformat ua_format = { - .format = SNDRV_PCM_FORMAT_S24_3LE, - .channels = 2, - .fmt_type = USB_FORMAT_TYPE_I, - .altsetting = 1, - .altset_idx = 1, - .rates = SNDRV_PCM_RATE_CONTINUOUS, - }; - struct usb_host_interface *alts; - struct usb_interface_descriptor *altsd; - struct audioformat *fp; - int stream, err; - - /* both PCM and MIDI interfaces have 2 altsettings */ - if (iface->num_altsetting != 2) - return -ENXIO; - alts = &iface->altsetting[1]; - altsd = get_iface_desc(alts); - - if (altsd->bNumEndpoints == 2) { - static const 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 ua25_ep = { - .out_cables = 0x0001, - .in_cables = 0x0001 - }; - static const struct snd_usb_audio_quirk ua25_quirk = { - .type = QUIRK_MIDI_FIXED_ENDPOINT, - .data = &ua25_ep - }; - if (chip->usb_id == USB_ID(0x0582, 0x002b)) - return snd_usb_create_midi_interface(chip, iface, - &ua700_quirk); - else - return snd_usb_create_midi_interface(chip, iface, - &ua25_quirk); - } - - if (altsd->bNumEndpoints != 1) - return -ENXIO; - - fp = kmalloc(sizeof(*fp), GFP_KERNEL); - if (!fp) - return -ENOMEM; - memcpy(fp, &ua_format, sizeof(*fp)); - - fp->iface = altsd->bInterfaceNumber; - fp->endpoint = get_endpoint(alts, 0)->bEndpointAddress; - fp->ep_attr = get_endpoint(alts, 0)->bmAttributes; - fp->maxpacksize = le16_to_cpu(get_endpoint(alts, 0)->wMaxPacketSize); - - switch (fp->maxpacksize) { - case 0x120: - fp->rate_max = fp->rate_min = 44100; - break; - case 0x138: - case 0x140: - fp->rate_max = fp->rate_min = 48000; - break; - case 0x258: - case 0x260: - fp->rate_max = fp->rate_min = 96000; - break; - default: - snd_printk(KERN_ERR "unknown sample rate\n"); - kfree(fp); - return -ENXIO; - } - - stream = (fp->endpoint & USB_DIR_IN) - ? SNDRV_PCM_STREAM_CAPTURE : SNDRV_PCM_STREAM_PLAYBACK; - err = add_audio_endpoint(chip, stream, fp); - if (err < 0) { - kfree(fp); - return err; - } - usb_set_interface(chip->dev, fp->iface, 0); - return 0; -} - -/* - * Create a stream for an Edirol UA-1000 interface. - */ -static int create_ua1000_quirk(struct snd_usb_audio *chip, - struct usb_interface *iface, - const struct snd_usb_audio_quirk *quirk) -{ - static const struct audioformat ua1000_format = { - .format = SNDRV_PCM_FORMAT_S32_LE, - .fmt_type = USB_FORMAT_TYPE_I, - .altsetting = 1, - .altset_idx = 1, - .attributes = 0, - .rates = SNDRV_PCM_RATE_CONTINUOUS, - }; - struct usb_host_interface *alts; - struct usb_interface_descriptor *altsd; - struct audioformat *fp; - int stream, err; - - if (iface->num_altsetting != 2) - return -ENXIO; - alts = &iface->altsetting[1]; - altsd = get_iface_desc(alts); - if (alts->extralen != 11 || alts->extra[1] != USB_DT_CS_INTERFACE || - altsd->bNumEndpoints != 1) - return -ENXIO; - - fp = kmemdup(&ua1000_format, sizeof(*fp), GFP_KERNEL); - if (!fp) - return -ENOMEM; - - fp->channels = alts->extra[4]; - fp->iface = altsd->bInterfaceNumber; - fp->endpoint = get_endpoint(alts, 0)->bEndpointAddress; - fp->ep_attr = get_endpoint(alts, 0)->bmAttributes; - fp->maxpacksize = le16_to_cpu(get_endpoint(alts, 0)->wMaxPacketSize); - fp->rate_max = fp->rate_min = combine_triple(&alts->extra[8]); - - stream = (fp->endpoint & USB_DIR_IN) - ? SNDRV_PCM_STREAM_CAPTURE : SNDRV_PCM_STREAM_PLAYBACK; - err = add_audio_endpoint(chip, stream, fp); - if (err < 0) { - kfree(fp); - return err; - } - /* FIXME: playback must be synchronized to capture */ - usb_set_interface(chip->dev, fp->iface, 0); - return 0; -} - -/* - * Create a stream for an Edirol UA-101 interface. - * Copy, paste and modify from Edirol UA-1000 - */ -static int create_ua101_quirk(struct snd_usb_audio *chip, - struct usb_interface *iface, - const struct snd_usb_audio_quirk *quirk) -{ - static const struct audioformat ua101_format = { - .format = SNDRV_PCM_FORMAT_S32_LE, - .fmt_type = USB_FORMAT_TYPE_I, - .altsetting = 1, - .altset_idx = 1, - .attributes = 0, - .rates = SNDRV_PCM_RATE_CONTINUOUS, - }; - struct usb_host_interface *alts; - struct usb_interface_descriptor *altsd; - struct audioformat *fp; - int stream, err; - - if (iface->num_altsetting != 2) - return -ENXIO; - alts = &iface->altsetting[1]; - altsd = get_iface_desc(alts); - if (alts->extralen != 18 || alts->extra[1] != USB_DT_CS_INTERFACE || - altsd->bNumEndpoints != 1) - return -ENXIO; - - fp = kmemdup(&ua101_format, sizeof(*fp), GFP_KERNEL); - if (!fp) - return -ENOMEM; - - fp->channels = alts->extra[11]; - fp->iface = altsd->bInterfaceNumber; - fp->endpoint = get_endpoint(alts, 0)->bEndpointAddress; - fp->ep_attr = get_endpoint(alts, 0)->bmAttributes; - fp->maxpacksize = le16_to_cpu(get_endpoint(alts, 0)->wMaxPacketSize); - fp->rate_max = fp->rate_min = combine_triple(&alts->extra[15]); - - stream = (fp->endpoint & USB_DIR_IN) - ? SNDRV_PCM_STREAM_CAPTURE : SNDRV_PCM_STREAM_PLAYBACK; - err = add_audio_endpoint(chip, stream, fp); - if (err < 0) { - kfree(fp); - return err; - } - /* FIXME: playback must be synchronized to capture */ - usb_set_interface(chip->dev, fp->iface, 0); - return 0; -} - -static int snd_usb_create_quirk(struct snd_usb_audio *chip, - struct usb_interface *iface, - const struct snd_usb_audio_quirk *quirk); - -/* - * handle the quirks for the contained interfaces - */ -static int create_composite_quirk(struct snd_usb_audio *chip, - struct usb_interface *iface, - const struct snd_usb_audio_quirk *quirk) -{ - int probed_ifnum = get_iface_desc(iface->altsetting)->bInterfaceNumber; - int err; - - for (quirk = quirk->data; quirk->ifnum >= 0; ++quirk) { - iface = usb_ifnum_to_if(chip->dev, quirk->ifnum); - if (!iface) - continue; - if (quirk->ifnum != probed_ifnum && - usb_interface_claimed(iface)) - continue; - err = snd_usb_create_quirk(chip, iface, quirk); - if (err < 0) - return err; - if (quirk->ifnum != probed_ifnum) - usb_driver_claim_interface(&usb_audio_driver, iface, (void *)-1L); - } - return 0; -} - -static int ignore_interface_quirk(struct snd_usb_audio *chip, - struct usb_interface *iface, - const struct snd_usb_audio_quirk *quirk) -{ - return 0; -} - - -/* - * boot quirks - */ - -#define EXTIGY_FIRMWARE_SIZE_OLD 794 -#define EXTIGY_FIRMWARE_SIZE_NEW 483 - -static int snd_usb_extigy_boot_quirk(struct usb_device *dev, struct usb_interface *intf) -{ - struct usb_host_config *config = dev->actconfig; - int err; - - if (le16_to_cpu(get_cfg_desc(config)->wTotalLength) == EXTIGY_FIRMWARE_SIZE_OLD || - le16_to_cpu(get_cfg_desc(config)->wTotalLength) == EXTIGY_FIRMWARE_SIZE_NEW) { - snd_printdd("sending Extigy boot sequence...\n"); - /* Send message to force it to reconnect with full interface. */ - err = snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev,0), - 0x10, 0x43, 0x0001, 0x000a, NULL, 0, 1000); - if (err < 0) snd_printdd("error sending boot message: %d\n", err); - err = usb_get_descriptor(dev, USB_DT_DEVICE, 0, - &dev->descriptor, sizeof(dev->descriptor)); - config = dev->actconfig; - if (err < 0) snd_printdd("error usb_get_descriptor: %d\n", err); - err = usb_reset_configuration(dev); - if (err < 0) snd_printdd("error usb_reset_configuration: %d\n", err); - snd_printdd("extigy_boot: new boot length = %d\n", - le16_to_cpu(get_cfg_desc(config)->wTotalLength)); - return -ENODEV; /* quit this anyway */ - } - return 0; -} - -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, 1000); - 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, 1000); - return -ENODEV; - } - 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, 1000); -} - -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); -} - - -/* - * Setup quirks - */ -#define AUDIOPHILE_SET 0x01 /* if set, parse device_setup */ -#define AUDIOPHILE_SET_DTS 0x02 /* if set, enable DTS Digital Output */ -#define AUDIOPHILE_SET_96K 0x04 /* 48-96KHz rate if set, 8-48KHz otherwise */ -#define AUDIOPHILE_SET_24B 0x08 /* 24bits sample if set, 16bits otherwise */ -#define AUDIOPHILE_SET_DI 0x10 /* if set, enable Digital Input */ -#define AUDIOPHILE_SET_MASK 0x1F /* bit mask for setup value */ -#define AUDIOPHILE_SET_24B_48K_DI 0x19 /* value for 24bits+48KHz+Digital Input */ -#define AUDIOPHILE_SET_24B_48K_NOTDI 0x09 /* value for 24bits+48KHz+No Digital Input */ -#define AUDIOPHILE_SET_16B_48K_DI 0x11 /* value for 16bits+48KHz+Digital Input */ -#define AUDIOPHILE_SET_16B_48K_NOTDI 0x01 /* value for 16bits+48KHz+No Digital Input */ - -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 (device_setup[chip->index] & AUDIOPHILE_SET) { - if ((device_setup[chip->index] & AUDIOPHILE_SET_DTS) - && altno != 6) - return 1; /* skip this altsetting */ - if ((device_setup[chip->index] & AUDIOPHILE_SET_96K) - && altno != 1) - return 1; /* skip this altsetting */ - if ((device_setup[chip->index] & AUDIOPHILE_SET_MASK) == - AUDIOPHILE_SET_24B_48K_DI && altno != 2) - return 1; /* skip this altsetting */ - if ((device_setup[chip->index] & AUDIOPHILE_SET_MASK) == - AUDIOPHILE_SET_24B_48K_NOTDI && altno != 3) - return 1; /* skip this altsetting */ - if ((device_setup[chip->index] & AUDIOPHILE_SET_MASK) == - AUDIOPHILE_SET_16B_48K_DI && altno != 4) - return 1; /* skip this altsetting */ - if ((device_setup[chip->index] & AUDIOPHILE_SET_MASK) == - AUDIOPHILE_SET_16B_48K_NOTDI && altno != 5) - return 1; /* skip this altsetting */ - } - return 0; /* keep this altsetting */ -} - -/* - * audio-interface quirks - * - * returns zero if no standard audio/MIDI parsing is needed. - * returns a postive value if standard audio/midi interfaces are parsed - * after this. - * returns a negative value at error. - */ -static int snd_usb_create_quirk(struct snd_usb_audio *chip, - struct usb_interface *iface, - const struct snd_usb_audio_quirk *quirk) -{ - typedef int (*quirk_func_t)(struct snd_usb_audio *, struct usb_interface *, - 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_MIDI_STANDARD_INTERFACE] = snd_usb_create_midi_interface, - [QUIRK_MIDI_FIXED_ENDPOINT] = snd_usb_create_midi_interface, - [QUIRK_MIDI_YAMAHA] = snd_usb_create_midi_interface, - [QUIRK_MIDI_MIDIMAN] = snd_usb_create_midi_interface, - [QUIRK_MIDI_NOVATION] = snd_usb_create_midi_interface, - [QUIRK_MIDI_RAW] = snd_usb_create_midi_interface, - [QUIRK_MIDI_EMAGIC] = snd_usb_create_midi_interface, - [QUIRK_MIDI_CME] = snd_usb_create_midi_interface, - [QUIRK_AUDIO_STANDARD_INTERFACE] = create_standard_audio_quirk, - [QUIRK_AUDIO_FIXED_ENDPOINT] = create_fixed_stream_quirk, - [QUIRK_AUDIO_EDIROL_UA700_UA25] = create_ua700_ua25_quirk, - [QUIRK_AUDIO_EDIROL_UA1000] = create_ua1000_quirk, - [QUIRK_AUDIO_EDIROL_UA101] = create_ua101_quirk, - }; - - if (quirk->type < QUIRK_TYPE_COUNT) { - return quirk_funcs[quirk->type](chip, iface, quirk); - } else { - snd_printd(KERN_ERR "invalid quirk type %d\n", quirk->type); - return -ENXIO; - } -} - - -/* - * common proc files to show the usb device info - */ -static void proc_audio_usbbus_read(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)); -} - -static 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); -} - -/* - * 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) -{ - usb_chip[chip->index] = NULL; - 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_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; - - if (snd_usb_get_speed(dev) != USB_SPEED_LOW && - snd_usb_get_speed(dev) != USB_SPEED_FULL && - snd_usb_get_speed(dev) != USB_SPEED_HIGH) { - snd_printk(KERN_ERR "unknown device speed %d\n", snd_usb_get_speed(dev)); - return -ENXIO; - } - - card = snd_card_new(index[idx], id[idx], THIS_MODULE, 0); - if (card == NULL) { - snd_printk(KERN_ERR "cannot create card instance %d\n", idx); - return -ENOMEM; - } - - chip = kzalloc(sizeof(*chip), GFP_KERNEL); - if (! chip) { - snd_card_free(card); - return -ENOMEM; - } - - chip->index = idx; - chip->dev = dev; - chip->card = card; - 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->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) { - 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)); - } - } - - /* retrieve the vendor and device strings as longname */ - if (quirk && quirk->vendor_name) { - len = strlcpy(card->longname, quirk->vendor_name, sizeof(card->longname)); - } else { - if (dev->descriptor.iManufacturer) - len = usb_string(dev, dev->descriptor.iManufacturer, - card->longname, sizeof(card->longname)); - else - len = 0; - /* we don't really care if there isn't any vendor string */ - } - if (len > 0) - strlcat(card->longname, " ", sizeof(card->longname)); - - strlcat(card->longname, card->shortname, sizeof(card->longname)); - - len = strlcat(card->longname, " at ", sizeof(card->longname)); - - if (len < sizeof(card->longname)) - usb_make_path(dev, card->longname + len, sizeof(card->longname) - len); - - strlcat(card->longname, - snd_usb_get_speed(dev) == USB_SPEED_LOW ? ", low speed" : - snd_usb_get_speed(dev) == USB_SPEED_FULL ? ", full speed" : - ", high speed", - sizeof(card->longname)); - - 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 void *snd_usb_audio_probe(struct usb_device *dev, - struct usb_interface *intf, - const struct usb_device_id *usb_id) -{ - const struct snd_usb_audio_quirk *quirk = (const struct snd_usb_audio_quirk *)usb_id->driver_info; - int i, err; - struct snd_usb_audio *chip; - 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) - goto __err_val; - - /* SB Extigy needs special boot-up sequence */ - /* if more models come, this will go to the quirk list. */ - if (id == USB_ID(0x041e, 0x3000)) { - if (snd_usb_extigy_boot_quirk(dev, intf) < 0) - goto __err_val; - } - /* SB Audigy 2 NX needs its own boot-up magic, too */ - if (id == USB_ID(0x041e, 0x3020)) { - if (snd_usb_audigy2nx_boot_quirk(dev) < 0) - goto __err_val; - } - - /* C-Media CM106 / Turtle Beach Audio Advantage Roadie */ - if (id == USB_ID(0x10f5, 0x0200)) { - if (snd_usb_cm106_boot_quirk(dev) < 0) - goto __err_val; - } - - /* - * found a config. now register to ALSA - */ - - /* check whether it's already registered */ - chip = NULL; - mutex_lock(®ister_mutex); - for (i = 0; i < SNDRV_CARDS; i++) { - if (usb_chip[i] && usb_chip[i]->dev == dev) { - if (usb_chip[i]->shutdown) { - snd_printk(KERN_ERR "USB device is in the shutdown state, cannot create a card instance\n"); - goto __error; - } - chip = usb_chip[i]; - break; - } - } - if (! chip) { - /* it's a fresh one. - * now look for an empty slot and create a new card instance - */ - 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))) { - if (snd_usb_audio_create(dev, i, quirk, &chip) < 0) { - goto __error; - } - snd_card_set_dev(chip->card, &intf->dev); - break; - } - if (! chip) { - snd_printk(KERN_ERR "no available usb audio device\n"); - goto __error; - } - } - - err = 1; /* continue */ - if (quirk && quirk->ifnum != QUIRK_NO_INTERFACE) { - /* need some special handlings */ - if ((err = snd_usb_create_quirk(chip, intf, quirk)) < 0) - goto __error; - } - - if (err > 0) { - /* create normal USB audio interfaces */ - if (snd_usb_create_streams(chip, ifnum) < 0 || - snd_usb_create_mixer(chip, ifnum) < 0) { - goto __error; - } - } - - /* we are allowed to call snd_card_register() many times */ - if (snd_card_register(chip->card) < 0) { - goto __error; - } - - usb_chip[chip->index] = chip; - chip->num_interfaces++; - mutex_unlock(®ister_mutex); - return chip; - - __error: - if (chip && !chip->num_interfaces) - snd_card_free(chip->card); - mutex_unlock(®ister_mutex); - __err_val: - return NULL; -} - -/* - * we need to take care of counter, since disconnection can be called also - * many times as well as usb_audio_probe(). - */ -static void snd_usb_audio_disconnect(struct usb_device *dev, void *ptr) -{ - struct snd_usb_audio *chip; - struct snd_card *card; - struct list_head *p; - - if (ptr == (void *)-1L) - return; - - chip = ptr; - card = chip->card; - mutex_lock(®ister_mutex); - chip->shutdown = 1; - chip->num_interfaces--; - if (chip->num_interfaces <= 0) { - snd_card_disconnect(card); - /* release the pcm resources */ - list_for_each(p, &chip->pcm_list) { - snd_usb_stream_disconnect(p); - } - /* release the midi resources */ - list_for_each(p, &chip->midi_list) { - snd_usbmidi_disconnect(p); - } - /* release mixer resources */ - list_for_each(p, &chip->mixer_list) { - snd_usb_mixer_disconnect(p); - } - mutex_unlock(®ister_mutex); - snd_card_free_when_closed(card); - } else { - mutex_unlock(®ister_mutex); - } -} - -/* - * new 2.5 USB kernel API - */ -static int usb_audio_probe(struct usb_interface *intf, - const struct usb_device_id *id) -{ - void *chip; - chip = snd_usb_audio_probe(interface_to_usbdev(intf), intf, id); - if (chip) { - dev_set_drvdata(&intf->dev, chip); - return 0; - } else - return -EIO; -} - -static void usb_audio_disconnect(struct usb_interface *intf) -{ - snd_usb_audio_disconnect(interface_to_usbdev(intf), - dev_get_drvdata(&intf->dev)); -} - -#ifdef CONFIG_PM -static int usb_audio_suspend(struct usb_interface *intf, pm_message_t message) -{ - struct snd_usb_audio *chip = dev_get_drvdata(&intf->dev); - struct list_head *p; - struct snd_usb_stream *as; - - if (chip == (void *)-1L) - return 0; - - snd_power_change_state(chip->card, SNDRV_CTL_POWER_D3hot); - if (!chip->num_suspended_intf++) { - list_for_each(p, &chip->pcm_list) { - as = list_entry(p, struct snd_usb_stream, list); - snd_pcm_suspend_all(as->pcm); - } - } - - return 0; -} - -static int usb_audio_resume(struct usb_interface *intf) -{ - struct snd_usb_audio *chip = dev_get_drvdata(&intf->dev); - - if (chip == (void *)-1L) - return 0; - if (--chip->num_suspended_intf) - return 0; - /* - * ALSA leaves material resumption to user space - * we just notify - */ - - snd_power_change_state(chip->card, SNDRV_CTL_POWER_D0); - - return 0; -} -#endif /* CONFIG_PM */ - -static int __init snd_usb_audio_init(void) -{ - if (nrpacks < MIN_PACKS_URB || nrpacks > MAX_PACKS) { - printk(KERN_WARNING "invalid nrpacks value.\n"); - return -EINVAL; - } - return usb_register(&usb_audio_driver); -} - - -static void __exit snd_usb_audio_cleanup(void) -{ - usb_deregister(&usb_audio_driver); -} - -module_init(snd_usb_audio_init); -module_exit(snd_usb_audio_cleanup); diff --git a/sound/usb/usbaudio.h b/sound/usb/usbaudio.h index 7cf18c38dc4..91d0380431b 100644 --- a/sound/usb/usbaudio.h +++ b/sound/usb/usbaudio.h @@ -21,122 +21,55 @@ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ - -/* - */ - -#define USB_SUBCLASS_AUDIO_CONTROL 0x01 -#define USB_SUBCLASS_AUDIO_STREAMING 0x02 -#define USB_SUBCLASS_MIDI_STREAMING 0x03 -#define USB_SUBCLASS_VENDOR_SPEC 0xff - -#define HEADER 0x01 -#define INPUT_TERMINAL 0x02 -#define OUTPUT_TERMINAL 0x03 -#define MIXER_UNIT 0x04 -#define SELECTOR_UNIT 0x05 -#define FEATURE_UNIT 0x06 -#define PROCESSING_UNIT 0x07 -#define EXTENSION_UNIT 0x08 - -#define AS_GENERAL 0x01 -#define FORMAT_TYPE 0x02 -#define FORMAT_SPECIFIC 0x03 - -#define EP_GENERAL 0x01 - -#define MS_GENERAL 0x01 -#define MIDI_IN_JACK 0x02 -#define MIDI_OUT_JACK 0x03 - -/* endpoint attributes */ -#define EP_ATTR_MASK 0x0c -#define EP_ATTR_ASYNC 0x04 -#define EP_ATTR_ADAPTIVE 0x08 -#define EP_ATTR_SYNC 0x0c - -/* cs endpoint attributes */ -#define EP_CS_ATTR_SAMPLE_RATE 0x01 -#define EP_CS_ATTR_PITCH_CONTROL 0x02 -#define EP_CS_ATTR_FILL_MAX 0x80 - -/* Audio Class specific Request Codes */ - -#define SET_CUR 0x01 -#define GET_CUR 0x81 -#define SET_MIN 0x02 -#define GET_MIN 0x82 -#define SET_MAX 0x03 -#define GET_MAX 0x83 -#define SET_RES 0x04 -#define GET_RES 0x84 -#define SET_MEM 0x05 -#define GET_MEM 0x85 -#define GET_STAT 0xff - -/* Terminal Control Selectors */ - -#define COPY_PROTECT_CONTROL 0x01 - -/* Endpoint Control Selectors */ - -#define SAMPLING_FREQ_CONTROL 0x01 -#define PITCH_CONTROL 0x02 - -/* Format Types */ -#define USB_FORMAT_TYPE_I 0x01 -#define USB_FORMAT_TYPE_II 0x02 -#define USB_FORMAT_TYPE_III 0x03 - -/* type I */ -#define USB_AUDIO_FORMAT_PCM 0x01 -#define USB_AUDIO_FORMAT_PCM8 0x02 -#define USB_AUDIO_FORMAT_IEEE_FLOAT 0x03 -#define USB_AUDIO_FORMAT_ALAW 0x04 -#define USB_AUDIO_FORMAT_MU_LAW 0x05 - -/* type II */ -#define USB_AUDIO_FORMAT_MPEG 0x1001 -#define USB_AUDIO_FORMAT_AC3 0x1002 - -/* type III */ -#define USB_AUDIO_FORMAT_IEC1937_AC3 0x2001 -#define USB_AUDIO_FORMAT_IEC1937_MPEG1_LAYER1 0x2002 -#define USB_AUDIO_FORMAT_IEC1937_MPEG2_NOEXT 0x2003 -#define USB_AUDIO_FORMAT_IEC1937_MPEG2_EXT 0x2004 -#define USB_AUDIO_FORMAT_IEC1937_MPEG2_LAYER1_LS 0x2005 -#define USB_AUDIO_FORMAT_IEC1937_MPEG2_LAYER23_LS 0x2006 - - -/* maximum number of endpoints per interface */ -#define MIDI_MAX_ENDPOINTS 2 - /* 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; - int shutdown; + 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 */ - int next_midi_device; 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 */ @@ -148,19 +81,24 @@ struct snd_usb_audio { 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, + 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_UA700_UA25, - QUIRK_AUDIO_EDIROL_UA1000, - QUIRK_AUDIO_EDIROL_UA101, + QUIRK_AUDIO_EDIROL_UAXX, + QUIRK_AUDIO_ALIGN_TRANSFER, + QUIRK_AUDIO_STANDARD_MIXER, QUIRK_TYPE_COUNT }; @@ -173,77 +111,8 @@ struct snd_usb_audio_quirk { const void *data; }; -/* data for QUIRK_MIDI_FIXED_ENDPOINT */ -struct snd_usb_midi_endpoint_info { - int8_t out_ep; /* ep number, 0 autodetect */ - uint8_t out_interval; /* interval for interrupt endpoints */ - int8_t in_ep; - uint8_t in_interval; - uint16_t out_cables; /* bitmask */ - uint16_t in_cables; /* bitmask */ -}; - -/* for QUIRK_MIDI_YAMAHA, data is NULL */ - -/* for QUIRK_MIDI_MIDIMAN, data points to a snd_usb_midi_endpoint_info - * structure (out_cables and in_cables only) */ - -/* for QUIRK_COMPOSITE, data points to an array of snd_usb_audio_quirk - * structures, terminated with .ifnum = -1 */ - -/* for QUIRK_AUDIO_FIXED_ENDPOINT, data points to an audioformat structure */ - -/* for QUIRK_AUDIO/MIDI_STANDARD_INTERFACE, data is NULL */ - -/* for QUIRK_AUDIO_EDIROL_UA700_UA25/UA1000, data is NULL */ - -/* for QUIRK_IGNORE_INTERFACE, data is NULL */ - -/* for QUIRK_MIDI_NOVATION and _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 */ - -/* - */ - -#define combine_word(s) ((*s) | ((unsigned int)(s)[1] << 8)) +#define combine_word(s) ((*(s)) | ((unsigned int)(s)[1] << 8)) #define combine_triple(s) (combine_word(s) | ((unsigned int)(s)[2] << 16)) #define combine_quad(s) (combine_triple(s) | ((unsigned int)(s)[3] << 24)) -unsigned int snd_usb_combine_bytes(unsigned char *bytes, int size); - -void *snd_usb_find_desc(void *descstart, int desclen, void *after, u8 dtype); -void *snd_usb_find_csint_desc(void *descstart, int desclen, void *after, u8 dsubtype); - -int snd_usb_ctl_msg(struct usb_device *dev, unsigned int pipe, - __u8 request, __u8 requesttype, __u16 value, __u16 index, - void *data, __u16 size, int timeout); - -int snd_usb_create_mixer(struct snd_usb_audio *chip, int ctrlif); -void snd_usb_mixer_disconnect(struct list_head *p); - -int snd_usb_create_midi_interface(struct snd_usb_audio *chip, struct usb_interface *iface, - 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); - -/* - * 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 - #endif /* __USBAUDIO_H */ diff --git a/sound/usb/usbmixer.c b/sound/usb/usbmixer.c deleted file mode 100644 index 89c63d073cc..00000000000 --- a/sound/usb/usbmixer.c +++ /dev/null @@ -1,2075 +0,0 @@ -/* - * (Tentative) USB Audio Driver for ALSA - * - * Mixer control part - * - * Copyright (c) 2002 by Takashi Iwai <tiwai@suse.de> - * - * Many codes borrowed from audio.c by - * Alan Cox (alan@lxorguk.ukuu.org.uk) - * Thomas Sailer (sailer@ife.ee.ethz.ch) - * - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - * - */ - -#include <linux/bitops.h> -#include <linux/init.h> -#include <linux/list.h> -#include <linux/slab.h> -#include <linux/string.h> -#include <linux/usb.h> -#include <sound/core.h> -#include <sound/control.h> -#include <sound/hwdep.h> -#include <sound/info.h> -#include <sound/tlv.h> - -#include "usbaudio.h" - -/* - */ - -/* ignore error from controls - for debugging */ -/* #define IGNORE_CTL_ERROR */ - -/* - * Sound Blaster remote control configuration - * - * format of remote control data: - * Extigy: xx 00 - * Audigy 2 NX: 06 80 xx 00 00 00 - * Live! 24-bit: 06 80 xx yy 22 83 - */ -static const struct rc_config { - u32 usb_id; - u8 offset; - u8 length; - u8 packet_length; - u8 mute_mixer_id; - u32 mute_code; -} rc_configs[] = { - { USB_ID(0x041e, 0x3000), 0, 1, 2, 18, 0x0013 }, /* Extigy */ - { USB_ID(0x041e, 0x3020), 2, 1, 6, 18, 0x0013 }, /* Audigy 2 NX */ - { USB_ID(0x041e, 0x3040), 2, 2, 6, 2, 0x6e91 }, /* Live! 24-bit */ -}; - -struct usb_mixer_interface { - struct snd_usb_audio *chip; - unsigned int ctrlif; - struct list_head list; - unsigned int ignore_ctl_error; - struct urb *urb; - struct usb_mixer_elem_info **id_elems; /* array[256], indexed by unit id */ - - /* Sound Blaster remote control stuff */ - const struct rc_config *rc_cfg; - unsigned long rc_hwdep_open; - u32 rc_code; - wait_queue_head_t rc_waitq; - struct urb *rc_urb; - struct usb_ctrlrequest *rc_setup_packet; - u8 rc_buffer[6]; - - u8 audigy2nx_leds[3]; -}; - - -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, 256); - struct usb_audio_term oterm; - const struct usbmix_name_map *map; - const struct usbmix_selector_map *selector_map; -}; - -struct usb_mixer_elem_info { - struct usb_mixer_interface *mixer; - struct usb_mixer_elem_info *next_id_elem; /* list of controls with same id */ - struct snd_ctl_elem_id *elem_id; - unsigned int id; - unsigned int control; /* CS or ICN (high byte) */ - unsigned int cmask; /* channel mask bitmap: 0 = master */ - int channels; - int val_type; - int min, max, res; - u8 initialized; -}; - - -enum { - USB_FEATURE_NONE = 0, - USB_FEATURE_MUTE = 1, - USB_FEATURE_VOLUME, - USB_FEATURE_BASS, - USB_FEATURE_MID, - USB_FEATURE_TREBLE, - USB_FEATURE_GEQ, - USB_FEATURE_AGC, - USB_FEATURE_DELAY, - USB_FEATURE_BASSBOOST, - USB_FEATURE_LOUDNESS -}; - -enum { - USB_MIXER_BOOLEAN, - USB_MIXER_INV_BOOLEAN, - USB_MIXER_S8, - USB_MIXER_U8, - USB_MIXER_S16, - USB_MIXER_U16, -}; - -enum { - USB_PROC_UPDOWN = 1, - USB_PROC_UPDOWN_SWITCH = 1, - USB_PROC_UPDOWN_MODE_SEL = 2, - - USB_PROC_PROLOGIC = 2, - USB_PROC_PROLOGIC_SWITCH = 1, - USB_PROC_PROLOGIC_MODE_SEL = 2, - - USB_PROC_3DENH = 3, - USB_PROC_3DENH_SWITCH = 1, - USB_PROC_3DENH_SPACE = 2, - - USB_PROC_REVERB = 4, - USB_PROC_REVERB_SWITCH = 1, - USB_PROC_REVERB_LEVEL = 2, - USB_PROC_REVERB_TIME = 3, - USB_PROC_REVERB_DELAY = 4, - - USB_PROC_CHORUS = 5, - USB_PROC_CHORUS_SWITCH = 1, - USB_PROC_CHORUS_LEVEL = 2, - USB_PROC_CHORUS_RATE = 3, - USB_PROC_CHORUS_DEPTH = 4, - - USB_PROC_DCR = 6, - USB_PROC_DCR_SWITCH = 1, - USB_PROC_DCR_RATIO = 2, - USB_PROC_DCR_MAX_AMP = 3, - USB_PROC_DCR_THRESHOLD = 4, - USB_PROC_DCR_ATTACK = 5, - USB_PROC_DCR_RELEASE = 6, -}; - -#define MAX_CHANNELS 10 /* max logical channels */ - - -/* - * manual mapping of mixer names - * if the mixer topology is too complicated and the parsed names are - * ambiguous, add the entries in usbmixer_maps.c. - */ -#include "usbmixer_maps.c" - -/* get the mapped name if the unit matches */ -static int check_mapped_name(struct mixer_build *state, int unitid, int control, char *buf, int buflen) -{ - const struct usbmix_name_map *p; - - if (! state->map) - return 0; - - for (p = state->map; p->id; p++) { - if (p->id == unitid && p->name && - (! control || ! p->control || control == p->control)) { - buflen--; - return strlcpy(buf, p->name, buflen); - } - } - return 0; -} - -/* check whether the control should be ignored */ -static int check_ignored_ctl(struct mixer_build *state, int unitid, int control) -{ - const struct usbmix_name_map *p; - - if (! state->map) - return 0; - for (p = state->map; p->id; p++) { - if (p->id == unitid && ! p->name && - (! control || ! p->control || control == p->control)) { - // printk("ignored control %d:%d\n", unitid, control); - return 1; - } - } - return 0; -} - -/* 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) -{ - unsigned char *p; - - p = NULL; - while ((p = snd_usb_find_desc(state->buffer, state->buflen, p, - USB_DT_CS_INTERFACE)) != NULL) { - if (p[0] >= 4 && p[2] >= INPUT_TERMINAL && p[2] <= EXTENSION_UNIT && p[3] == unit) - return p; - } - return NULL; -} - - -/* - * copy a string with the given id - */ -static int snd_usb_copy_string_desc(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(struct usb_mixer_elem_info *cval, int request, int validx, int *value_ret) -{ - unsigned char buf[2]; - int val_len = cval->val_type >= USB_MIXER_S16 ? 2 : 1; - int timeout = 10; - - while (timeout-- > 0) { - if (snd_usb_ctl_msg(cval->mixer->chip->dev, - usb_rcvctrlpipe(cval->mixer->chip->dev, 0), - request, - USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_IN, - validx, cval->mixer->ctrlif | (cval->id << 8), - buf, val_len, 100) >= val_len) { - *value_ret = convert_signed_value(cval, snd_usb_combine_bytes(buf, val_len)); - return 0; - } - } - snd_printdd(KERN_ERR "cannot get ctl value: req = %#x, wValue = %#x, wIndex = %#x, type = %d\n", - request, validx, cval->mixer->ctrlif | (cval->id << 8), cval->val_type); - return -EINVAL; -} - -static int get_cur_ctl_value(struct usb_mixer_elem_info *cval, int validx, int *value) -{ - return get_ctl_value(cval, GET_CUR, validx, value); -} - -/* channel = 0: master, 1 = first channel */ -static inline int get_cur_mix_value(struct usb_mixer_elem_info *cval, int channel, int *value) -{ - return get_ctl_value(cval, GET_CUR, (cval->control << 8) | channel, value); -} - -/* - * set a mixer value - */ - -static int set_ctl_value(struct usb_mixer_elem_info *cval, int request, int validx, int value_set) -{ - unsigned char buf[2]; - int val_len = cval->val_type >= USB_MIXER_S16 ? 2 : 1; - int timeout = 10; - - value_set = convert_bytes_value(cval, value_set); - buf[0] = value_set & 0xff; - buf[1] = (value_set >> 8) & 0xff; - while (timeout -- > 0) - if (snd_usb_ctl_msg(cval->mixer->chip->dev, - usb_sndctrlpipe(cval->mixer->chip->dev, 0), - request, - USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_OUT, - validx, cval->mixer->ctrlif | (cval->id << 8), - buf, val_len, 100) >= 0) - return 0; - snd_printdd(KERN_ERR "cannot set ctl value: req = %#x, wValue = %#x, wIndex = %#x, type = %d, data = %#x/%#x\n", - request, validx, cval->mixer->ctrlif | (cval->id << 8), cval->val_type, buf[0], buf[1]); - return -EINVAL; -} - -static int set_cur_ctl_value(struct usb_mixer_elem_info *cval, int validx, int value) -{ - return set_ctl_value(cval, SET_CUR, validx, value); -} - -static inline int set_cur_mix_value(struct usb_mixer_elem_info *cval, int channel, int value) -{ - return set_ctl_value(cval, SET_CUR, (cval->control << 8) | channel, value); -} - -/* - * TLV callback for mixer volume controls - */ -static int 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_SCALE(scale, 0, 0, 0); - - if (size < sizeof(scale)) - return -ENOMEM; - /* USB descriptions contain the dB scale in 1/256 dB unit - * while ALSA TLV contains in 1/100 dB unit - */ - scale[2] = (convert_signed_value(cval, cval->min) * 100) / 256; - scale[3] = (convert_signed_value(cval, cval->res) * 100) / 256; - 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. - */ - -static int add_control_to_empty(struct mixer_build *state, struct snd_kcontrol *kctl) -{ - struct usb_mixer_elem_info *cval = kctl->private_data; - int err; - - while (snd_ctl_find_id(state->chip->card, &kctl->id)) - kctl->id.index++; - if ((err = snd_ctl_add(state->chip->card, kctl)) < 0) { - snd_printd(KERN_ERR "cannot add control (err = %d)\n", err); - return err; - } - cval->elem_id = &kctl->id; - cval->next_id_elem = state->mixer->id_elems[cval->id]; - state->mixer->id_elems[cval->id] = cval; - 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 SELECTOR_UNIT: - strcpy(name, "Selector"); return 8; - case PROCESSING_UNIT: - strcpy(name, "Process Unit"); return 12; - case EXTENSION_UNIT: - strcpy(name, "Ext Unit"); return 8; - case MIXER_UNIT: - strcpy(name, "Mixer"); return 5; - default: - return sprintf(name, "Unit %d", iterm->id); - } - } - - switch (iterm->type & 0xff00) { - case 0x0100: - strcpy(name, "PCM"); return 3; - case 0x0200: - strcpy(name, "Mic"); return 3; - case 0x0400: - strcpy(name, "Headset"); return 7; - case 0x0500: - strcpy(name, "Phone"); return 5; - } - - for (names = iterm_names; names->type; names++) - if (names->type == iterm->type) { - strcpy(name, names->name); - return strlen(names->name); - } - return 0; -} - - -/* - * parse the source unit recursively until it reaches to a terminal - * or a branched unit. - */ -static int check_input_term(struct mixer_build *state, int id, struct usb_audio_term *term) -{ - unsigned char *p1; - - memset(term, 0, sizeof(*term)); - while ((p1 = find_audio_control_unit(state, id)) != NULL) { - term->id = id; - switch (p1[2]) { - case INPUT_TERMINAL: - term->type = combine_word(p1 + 4); - term->channels = p1[7]; - term->chconfig = combine_word(p1 + 8); - term->name = p1[11]; - return 0; - case FEATURE_UNIT: - id = p1[4]; - break; /* continue to parse */ - case MIXER_UNIT: - term->type = p1[2] << 16; /* virtual type */ - term->channels = p1[5 + p1[4]]; - term->chconfig = combine_word(p1 + 6 + p1[4]); - term->name = p1[p1[0] - 1]; - return 0; - case SELECTOR_UNIT: - /* call recursively to retrieve the channel info */ - if (check_input_term(state, p1[5], term) < 0) - return -ENODEV; - term->type = p1[2] << 16; /* virtual type */ - term->id = id; - term->name = p1[9 + p1[0] - 1]; - return 0; - case PROCESSING_UNIT: - case EXTENSION_UNIT: - if (p1[6] == 1) { - id = p1[7]; - break; /* continue to parse */ - } - term->type = p1[2] << 16; /* virtual type */ - term->channels = p1[7 + p1[6]]; - term->chconfig = combine_word(p1 + 8 + p1[6]); - term->name = p1[12 + p1[6] + p1[11 + p1[6]]]; - return 0; - default: - return -ENODEV; - } - } - return -ENODEV; -} - - -/* - * Feature Unit - */ - -/* feature unit control information */ -struct usb_feature_control_info { - const char *name; - unsigned int type; /* control type (mute, volume, etc.) */ -}; - -static struct usb_feature_control_info audio_feature_info[] = { - { "Mute", USB_MIXER_INV_BOOLEAN }, - { "Volume", USB_MIXER_S16 }, - { "Tone Control - Bass", USB_MIXER_S8 }, - { "Tone Control - Mid", USB_MIXER_S8 }, - { "Tone Control - Treble", USB_MIXER_S8 }, - { "Graphic Equalizer", USB_MIXER_S8 }, /* FIXME: not implemeted yet */ - { "Auto Gain Control", USB_MIXER_BOOLEAN }, - { "Delay Control", USB_MIXER_U16 }, - { "Bass Boost", USB_MIXER_BOOLEAN }, - { "Loudness", USB_MIXER_BOOLEAN }, -}; - - -/* private_free callback */ -static void usb_mixer_elem_free(struct snd_kcontrol *kctl) -{ - kfree(kctl->private_data); - kctl->private_data = NULL; -} - - -/* - * interface to ALSA control for feature/mixer units - */ - -/* - * retrieve the minimum and maximum values for the specified control - */ -static int get_min_max(struct usb_mixer_elem_info *cval, int default_min) -{ - /* for failsafe */ - cval->min = default_min; - cval->max = cval->min + 1; - cval->res = 1; - - if (cval->val_type == USB_MIXER_BOOLEAN || - cval->val_type == USB_MIXER_INV_BOOLEAN) { - cval->initialized = 1; - } else { - int minchn = 0; - if (cval->cmask) { - int i; - for (i = 0; i < MAX_CHANNELS; i++) - if (cval->cmask & (1 << i)) { - minchn = i + 1; - break; - } - } - if (get_ctl_value(cval, GET_MAX, (cval->control << 8) | minchn, &cval->max) < 0 || - get_ctl_value(cval, GET_MIN, (cval->control << 8) | minchn, &cval->min) < 0) { - snd_printd(KERN_ERR "%d:%d: cannot get min/max values for control %d (id %d)\n", - cval->id, cval->mixer->ctrlif, cval->control, cval->id); - return -EINVAL; - } - if (get_ctl_value(cval, GET_RES, (cval->control << 8) | minchn, &cval->res) < 0) { - cval->res = 1; - } else { - int last_valid_res = cval->res; - - while (cval->res > 1) { - if (set_ctl_value(cval, SET_RES, (cval->control << 8) | minchn, cval->res / 2) < 0) - break; - cval->res /= 2; - } - if (get_ctl_value(cval, GET_RES, (cval->control << 8) | minchn, &cval->res) < 0) - cval->res = last_valid_res; - } - if (cval->res == 0) - cval->res = 1; - - /* 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_value(cval, minchn, &saved); - for (;;) { - test = saved; - if (test < cval->max) - test += cval->res; - else - test -= cval->res; - if (test < cval->min || test > cval->max || - set_cur_mix_value(cval, minchn, test) || - get_cur_mix_value(cval, minchn, &check)) { - cval->res = last_valid_res; - break; - } - if (test == check) - break; - cval->res *= 2; - } - set_cur_mix_value(cval, minchn, saved); - } - - cval->initialized = 1; - } - return 0; -} - - -/* 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(cval, 0); - 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; - - if (cval->cmask) { - cnt = 0; - for (c = 0; c < MAX_CHANNELS; c++) { - if (cval->cmask & (1 << c)) { - err = get_cur_mix_value(cval, c + 1, &val); - if (err < 0) { - if (cval->mixer->ignore_ctl_error) { - ucontrol->value.integer.value[0] = cval->min; - return 0; - } - snd_printd(KERN_ERR "cannot get current value for control %d ch %d: err = %d\n", cval->control, c + 1, err); - return err; - } - val = get_relative_value(cval, val); - ucontrol->value.integer.value[cnt] = val; - cnt++; - } - } - } else { - /* master channel */ - err = get_cur_mix_value(cval, 0, &val); - if (err < 0) { - if (cval->mixer->ignore_ctl_error) { - ucontrol->value.integer.value[0] = cval->min; - return 0; - } - snd_printd(KERN_ERR "cannot get current value for control %d master ch: err = %d\n", cval->control, err); - return err; - } - val = get_relative_value(cval, val); - ucontrol->value.integer.value[0] = val; - } - return 0; -} - -/* put the current value to feature/mixer unit */ -static int mixer_ctl_feature_put(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)) { - err = get_cur_mix_value(cval, c + 1, &oval); - if (err < 0) { - if (cval->mixer->ignore_ctl_error) - return 0; - return err; - } - val = ucontrol->value.integer.value[cnt]; - val = get_abs_value(cval, val); - if (oval != val) { - set_cur_mix_value(cval, c + 1, val); - changed = 1; - } - get_cur_mix_value(cval, c + 1, &val); - cnt++; - } - } - } else { - /* master channel */ - err = get_cur_mix_value(cval, 0, &oval); - if (err < 0 && cval->mixer->ignore_ctl_error) - return 0; - if (err < 0) - return err; - val = ucontrol->value.integer.value[0]; - val = get_abs_value(cval, val); - if (val != oval) { - set_cur_mix_value(cval, 0, val); - changed = 1; - } - } - return changed; -} - -static 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, -}; - - -/* - * build a feature control - */ - -static void build_feature_ctl(struct mixer_build *state, unsigned char *desc, - unsigned int ctl_mask, int control, - struct usb_audio_term *iterm, int unitid) -{ - unsigned int len = 0; - int mapped_name = 0; - int nameid = desc[desc[0] - 1]; - struct snd_kcontrol *kctl; - struct usb_mixer_elem_info *cval; - - control++; /* change from zero-based to 1-based value */ - - if (control == USB_FEATURE_GEQ) { - /* FIXME: not supported yet */ - return; - } - - if (check_ignored_ctl(state, unitid, control)) - return; - - cval = kzalloc(sizeof(*cval), GFP_KERNEL); - if (! cval) { - snd_printk(KERN_ERR "cannot malloc kcontrol\n"); - return; - } - cval->mixer = state->mixer; - cval->id = unitid; - cval->control = control; - cval->cmask = ctl_mask; - cval->val_type = audio_feature_info[control-1].type; - if (ctl_mask == 0) - cval->channels = 1; /* master channel */ - else { - int i, c = 0; - for (i = 0; i < 16; i++) - if (ctl_mask & (1 << i)) - c++; - cval->channels = c; - } - - /* get min/max values */ - get_min_max(cval, 0); - - kctl = snd_ctl_new1(&usb_feature_unit_ctl, cval); - if (! kctl) { - snd_printk(KERN_ERR "cannot malloc kcontrol\n"); - kfree(cval); - return; - } - kctl->private_free = usb_mixer_elem_free; - - len = check_mapped_name(state, unitid, control, kctl->id.name, sizeof(kctl->id.name)); - mapped_name = len != 0; - if (! len && nameid) - len = snd_usb_copy_string_desc(state, nameid, kctl->id.name, sizeof(kctl->id.name)); - - switch (control) { - case USB_FEATURE_MUTE: - case USB_FEATURE_VOLUME: - /* determine the control name. the rule is: - * - if a name id is given in descriptor, use it. - * - if the connected input can be determined, then use the name - * of terminal type. - * - if the connected output can be determined, use it. - * - otherwise, anonymous name. - */ - if (! len) { - len = get_term_name(state, iterm, kctl->id.name, sizeof(kctl->id.name), 1); - if (! len) - len = get_term_name(state, &state->oterm, kctl->id.name, sizeof(kctl->id.name), 1); - if (! len) - len = snprintf(kctl->id.name, sizeof(kctl->id.name), - "Feature %d", unitid); - } - /* determine the stream direction: - * if the connected output is USB stream, then it's likely a - * capture stream. otherwise it should be playback (hopefully :) - */ - if (! mapped_name && ! (state->oterm.type >> 16)) { - if ((state->oterm.type & 0xff00) == 0x0100) { - len = strlcat(kctl->id.name, " Capture", sizeof(kctl->id.name)); - } else { - len = strlcat(kctl->id.name + len, " Playback", sizeof(kctl->id.name)); - } - } - strlcat(kctl->id.name + len, control == USB_FEATURE_MUTE ? " Switch" : " Volume", - sizeof(kctl->id.name)); - if (control == USB_FEATURE_VOLUME) { - kctl->tlv.c = mixer_vol_tlv; - kctl->vd[0].access |= - SNDRV_CTL_ELEM_ACCESS_TLV_READ | - SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK; - } - break; - - default: - if (! len) - strlcpy(kctl->id.name, audio_feature_info[control-1].name, - sizeof(kctl->id.name)); - break; - } - - /* quirk for UDA1321/N101 */ - /* note that detection between firmware 2.1.1.7 (N101) and later 2.1.1.21 */ - /* is not very clear from datasheets */ - /* I hope that the min value is -15360 for newer firmware --jk */ - switch (state->chip->usb_id) { - case USB_ID(0x0471, 0x0101): - case USB_ID(0x0471, 0x0104): - case USB_ID(0x0471, 0x0105): - case USB_ID(0x0672, 0x1041): - if (!strcmp(kctl->id.name, "PCM Playback Volume") && - cval->min == -15616) { - snd_printk(KERN_INFO "using volume control quirk for the UDA1321/N101 chip\n"); - cval->max = -256; - } - } - - snd_printdd(KERN_INFO "[%d] FU [%s] ch = %d, val = %d/%d/%d\n", - cval->id, kctl->id.name, cval->channels, cval->min, cval->max, cval->res); - add_control_to_empty(state, kctl); -} - - - -/* - * parse a feature unit - * - * most of controlls are defined here. - */ -static int parse_audio_feature_unit(struct mixer_build *state, int unitid, unsigned char *ftr) -{ - int channels, i, j; - struct usb_audio_term iterm; - unsigned int master_bits, first_ch_bits; - int err, csize; - - if (ftr[0] < 7 || ! (csize = ftr[5]) || ftr[0] < 7 + csize) { - snd_printk(KERN_ERR "usbaudio: unit %u: invalid FEATURE_UNIT descriptor\n", unitid); - return -EINVAL; - } - - /* parse the source unit */ - if ((err = parse_audio_unit(state, ftr[4])) < 0) - return err; - - /* determine the input source type and name */ - if (check_input_term(state, ftr[4], &iterm) < 0) - return -EINVAL; - - channels = (ftr[0] - 7) / csize - 1; - - master_bits = snd_usb_combine_bytes(ftr + 6, csize); - if (channels > 0) - first_ch_bits = snd_usb_combine_bytes(ftr + 6 + csize, csize); - else - first_ch_bits = 0; - /* check all control types */ - for (i = 0; i < 10; i++) { - unsigned int ch_bits = 0; - for (j = 0; j < channels; j++) { - unsigned int mask = snd_usb_combine_bytes(ftr + 6 + csize * (j+1), csize); - if (mask & (1 << i)) - ch_bits |= (1 << j); - } - if (ch_bits & 1) /* the first channel must be set (for ease of programming) */ - build_feature_ctl(state, ftr, ch_bits, i, &iterm, unitid); - if (master_bits & (1 << i)) - build_feature_ctl(state, ftr, 0, i, &iterm, unitid); - } - - return 0; -} - - -/* - * Mixer Unit - */ - -/* - * build a mixer unit control - * - * the callbacks are identical with feature unit. - * input channel number (zero based) is given in control field instead. - */ - -static void build_mixer_unit_ctl(struct mixer_build *state, unsigned char *desc, - int in_pin, int in_ch, int unitid, - struct usb_audio_term *iterm) -{ - struct usb_mixer_elem_info *cval; - unsigned int input_pins = desc[4]; - unsigned int num_outs = desc[5 + input_pins]; - unsigned int i, len; - struct snd_kcontrol *kctl; - - if (check_ignored_ctl(state, unitid, 0)) - return; - - cval = kzalloc(sizeof(*cval), GFP_KERNEL); - if (! cval) - return; - - cval->mixer = state->mixer; - cval->id = unitid; - cval->control = in_ch + 1; /* based on 1 */ - cval->val_type = USB_MIXER_S16; - for (i = 0; i < num_outs; i++) { - if (check_matrix_bitmap(desc + 9 + input_pins, in_ch, i, num_outs)) { - cval->cmask |= (1 << i); - cval->channels++; - } - } - - /* get min/max values */ - get_min_max(cval, 0); - - kctl = snd_ctl_new1(&usb_feature_unit_ctl, cval); - if (! kctl) { - snd_printk(KERN_ERR "cannot malloc kcontrol\n"); - kfree(cval); - return; - } - kctl->private_free = usb_mixer_elem_free; - - len = check_mapped_name(state, unitid, 0, kctl->id.name, sizeof(kctl->id.name)); - if (! len) - len = get_term_name(state, iterm, kctl->id.name, sizeof(kctl->id.name), 0); - if (! len) - len = sprintf(kctl->id.name, "Mixer Source %d", in_ch + 1); - strlcat(kctl->id.name + len, " Volume", sizeof(kctl->id.name)); - - snd_printdd(KERN_INFO "[%d] MU [%s] ch = %d, val = %d/%d\n", - cval->id, kctl->id.name, cval->channels, cval->min, cval->max); - add_control_to_empty(state, kctl); -} - - -/* - * parse a mixer unit - */ -static int parse_audio_mixer_unit(struct mixer_build *state, int unitid, unsigned char *desc) -{ - struct usb_audio_term iterm; - int input_pins, num_ins, num_outs; - int pin, ich, err; - - if (desc[0] < 11 || ! (input_pins = desc[4]) || ! (num_outs = desc[5 + input_pins])) { - snd_printk(KERN_ERR "invalid MIXER UNIT descriptor %d\n", unitid); - return -EINVAL; - } - /* no bmControls field (e.g. Maya44) -> ignore */ - if (desc[0] <= 10 + input_pins) { - snd_printdd(KERN_INFO "MU %d has no bmControls field\n", unitid); - return 0; - } - - num_ins = 0; - ich = 0; - for (pin = 0; pin < input_pins; pin++) { - err = parse_audio_unit(state, desc[5 + pin]); - if (err < 0) - return err; - err = check_input_term(state, desc[5 + pin], &iterm); - if (err < 0) - return err; - num_ins += iterm.channels; - for (; ich < num_ins; ++ich) { - int och, ich_has_controls = 0; - - for (och = 0; och < num_outs; ++och) { - if (check_matrix_bitmap(desc + 9 + input_pins, - ich, och, num_outs)) { - ich_has_controls = 1; - break; - } - } - if (ich_has_controls) - build_mixer_unit_ctl(state, desc, pin, ich, - unitid, &iterm); - } - } - return 0; -} - - -/* - * Processing Unit / Extension Unit - */ - -/* get callback for processing/extension unit */ -static int mixer_ctl_procunit_get(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 && cval->mixer->ignore_ctl_error) { - ucontrol->value.integer.value[0] = cval->min; - return 0; - } - if (err < 0) - return err; - val = get_relative_value(cval, val); - ucontrol->value.integer.value[0] = val; - return 0; -} - -/* put callback for processing/extension unit */ -static int mixer_ctl_procunit_put(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) { - if (cval->mixer->ignore_ctl_error) - return 0; - return err; - } - val = ucontrol->value.integer.value[0]; - val = get_abs_value(cval, val); - if (val != oval) { - set_cur_ctl_value(cval, cval->control << 8, val); - return 1; - } - return 0; -} - -/* alsa control interface for processing/extension unit */ -static 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[] = { - { USB_PROC_UPDOWN_SWITCH, "Switch", USB_MIXER_BOOLEAN }, - { USB_PROC_UPDOWN_MODE_SEL, "Mode Select", USB_MIXER_U8, 1 }, - { 0 } -}; -static struct procunit_value_info prologic_proc_info[] = { - { USB_PROC_PROLOGIC_SWITCH, "Switch", USB_MIXER_BOOLEAN }, - { USB_PROC_PROLOGIC_MODE_SEL, "Mode Select", USB_MIXER_U8, 1 }, - { 0 } -}; -static struct procunit_value_info threed_enh_proc_info[] = { - { USB_PROC_3DENH_SWITCH, "Switch", USB_MIXER_BOOLEAN }, - { USB_PROC_3DENH_SPACE, "Spaciousness", USB_MIXER_U8 }, - { 0 } -}; -static struct procunit_value_info reverb_proc_info[] = { - { USB_PROC_REVERB_SWITCH, "Switch", USB_MIXER_BOOLEAN }, - { USB_PROC_REVERB_LEVEL, "Level", USB_MIXER_U8 }, - { USB_PROC_REVERB_TIME, "Time", USB_MIXER_U16 }, - { USB_PROC_REVERB_DELAY, "Delay", USB_MIXER_U8 }, - { 0 } -}; -static struct procunit_value_info chorus_proc_info[] = { - { USB_PROC_CHORUS_SWITCH, "Switch", USB_MIXER_BOOLEAN }, - { USB_PROC_CHORUS_LEVEL, "Level", USB_MIXER_U8 }, - { USB_PROC_CHORUS_RATE, "Rate", USB_MIXER_U16 }, - { USB_PROC_CHORUS_DEPTH, "Depth", USB_MIXER_U16 }, - { 0 } -}; -static struct procunit_value_info dcr_proc_info[] = { - { USB_PROC_DCR_SWITCH, "Switch", USB_MIXER_BOOLEAN }, - { USB_PROC_DCR_RATIO, "Ratio", USB_MIXER_U16 }, - { USB_PROC_DCR_MAX_AMP, "Max Amp", USB_MIXER_S16 }, - { USB_PROC_DCR_THRESHOLD, "Threshold", USB_MIXER_S16 }, - { USB_PROC_DCR_ATTACK, "Attack Time", USB_MIXER_U16 }, - { USB_PROC_DCR_RELEASE, "Release Time", USB_MIXER_U16 }, - { 0 } -}; - -static struct procunit_info procunits[] = { - { USB_PROC_UPDOWN, "Up Down", updown_proc_info }, - { USB_PROC_PROLOGIC, "Dolby Prologic", prologic_proc_info }, - { USB_PROC_3DENH, "3D Stereo Extender", threed_enh_proc_info }, - { USB_PROC_REVERB, "Reverb", reverb_proc_info }, - { USB_PROC_CHORUS, "Chorus", chorus_proc_info }, - { USB_PROC_DCR, "DCR", dcr_proc_info }, - { 0 }, -}; - -/* - * build a processing/extension unit - */ -static int build_audio_procunit(struct mixer_build *state, int unitid, unsigned char *dsc, struct procunit_info *list, char *name) -{ - int num_ins = dsc[6]; - 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; - static struct procunit_value_info default_value_info[] = { - { 0x01, "Switch", USB_MIXER_BOOLEAN }, - { 0 } - }; - static struct procunit_info default_info = { - 0, NULL, default_value_info - }; - - if (dsc[0] < 13 || dsc[0] < 13 + num_ins || dsc[0] < num_ins + dsc[11 + num_ins]) { - snd_printk(KERN_ERR "invalid %s descriptor (id %d)\n", name, unitid); - return -EINVAL; - } - - for (i = 0; i < num_ins; i++) { - if ((err = parse_audio_unit(state, dsc[7 + i])) < 0) - return err; - } - - type = combine_word(&dsc[4]); - for (info = list; info && info->type; info++) - if (info->type == type) - break; - if (! info || ! info->type) - info = &default_info; - - for (valinfo = info->values; valinfo->control; valinfo++) { - /* FIXME: bitmap might be longer than 8bit */ - if (! (dsc[12 + num_ins] & (1 << (valinfo->control - 1)))) - continue; - if (check_ignored_ctl(state, unitid, valinfo->control)) - continue; - cval = kzalloc(sizeof(*cval), GFP_KERNEL); - if (! cval) { - snd_printk(KERN_ERR "cannot malloc kcontrol\n"); - return -ENOMEM; - } - cval->mixer = state->mixer; - cval->id = unitid; - cval->control = valinfo->control; - cval->val_type = valinfo->val_type; - cval->channels = 1; - - /* get min/max values */ - if (type == USB_PROC_UPDOWN && cval->control == USB_PROC_UPDOWN_MODE_SEL) { - /* FIXME: hard-coded */ - cval->min = 1; - cval->max = dsc[15]; - cval->res = 1; - cval->initialized = 1; - } else - get_min_max(cval, valinfo->min_value); - - kctl = snd_ctl_new1(&mixer_procunit_ctl, cval); - if (! kctl) { - snd_printk(KERN_ERR "cannot malloc kcontrol\n"); - kfree(cval); - return -ENOMEM; - } - kctl->private_free = usb_mixer_elem_free; - - if (check_mapped_name(state, unitid, cval->control, kctl->id.name, sizeof(kctl->id.name))) - ; - else if (info->name) - strlcpy(kctl->id.name, info->name, sizeof(kctl->id.name)); - else { - nameid = dsc[12 + num_ins + dsc[11 + num_ins]]; - len = 0; - if (nameid) - len = snd_usb_copy_string_desc(state, nameid, kctl->id.name, sizeof(kctl->id.name)); - if (! len) - strlcpy(kctl->id.name, name, sizeof(kctl->id.name)); - } - strlcat(kctl->id.name, " ", sizeof(kctl->id.name)); - strlcat(kctl->id.name, valinfo->suffix, sizeof(kctl->id.name)); - - snd_printdd(KERN_INFO "[%d] PU [%s] ch = %d, val = %d/%d\n", - cval->id, kctl->id.name, cval->channels, cval->min, cval->max); - if ((err = add_control_to_empty(state, kctl)) < 0) - return err; - } - return 0; -} - - -static int parse_audio_processing_unit(struct mixer_build *state, int unitid, unsigned char *desc) -{ - return build_audio_procunit(state, unitid, desc, procunits, "Processing Unit"); -} - -static int parse_audio_extension_unit(struct mixer_build *state, int unitid, unsigned char *desc) -{ - return build_audio_procunit(state, unitid, desc, NULL, "Extension Unit"); -} - - -/* - * Selector Unit - */ - -/* info callback for selector unit - * use an enumerator type for routing - */ -static int mixer_ctl_selector_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) -{ - struct usb_mixer_elem_info *cval = kcontrol->private_data; - char **itemlist = (char **)kcontrol->private_value; - - snd_assert(itemlist, return -EINVAL); - uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; - uinfo->count = 1; - uinfo->value.enumerated.items = cval->max; - if ((int)uinfo->value.enumerated.item >= cval->max) - uinfo->value.enumerated.item = cval->max - 1; - strcpy(uinfo->value.enumerated.name, itemlist[uinfo->value.enumerated.item]); - return 0; -} - -/* get callback for selector unit */ -static int mixer_ctl_selector_get(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, 0, &val); - if (err < 0) { - if (cval->mixer->ignore_ctl_error) { - ucontrol->value.enumerated.item[0] = 0; - return 0; - } - return err; - } - val = get_relative_value(cval, val); - ucontrol->value.enumerated.item[0] = val; - return 0; -} - -/* put callback for selector unit */ -static int mixer_ctl_selector_put(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, 0, &oval); - if (err < 0) { - if (cval->mixer->ignore_ctl_error) - return 0; - return err; - } - val = ucontrol->value.enumerated.item[0]; - val = get_abs_value(cval, val); - if (val != oval) { - set_cur_ctl_value(cval, 0, val); - return 1; - } - return 0; -} - -/* alsa control interface for selector unit */ -static 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, unsigned char *desc) -{ - unsigned int num_ins = desc[4]; - unsigned int i, nameid, len; - int err; - struct usb_mixer_elem_info *cval; - struct snd_kcontrol *kctl; - char **namelist; - - if (! num_ins || desc[0] < 5 + num_ins) { - snd_printk(KERN_ERR "invalid SELECTOR UNIT descriptor %d\n", unitid); - return -EINVAL; - } - - for (i = 0; i < num_ins; i++) { - if ((err = parse_audio_unit(state, desc[5 + i])) < 0) - return err; - } - - if (num_ins == 1) /* only one ? nonsense! */ - return 0; - - if (check_ignored_ctl(state, unitid, 0)) - return 0; - - cval = kzalloc(sizeof(*cval), GFP_KERNEL); - if (! cval) { - snd_printk(KERN_ERR "cannot malloc kcontrol\n"); - return -ENOMEM; - } - cval->mixer = state->mixer; - cval->id = unitid; - cval->val_type = USB_MIXER_U8; - cval->channels = 1; - cval->min = 1; - cval->max = num_ins; - cval->res = 1; - cval->initialized = 1; - - namelist = kmalloc(sizeof(char *) * num_ins, GFP_KERNEL); - if (! namelist) { - snd_printk(KERN_ERR "cannot malloc\n"); - kfree(cval); - return -ENOMEM; - } -#define MAX_ITEM_NAME_LEN 64 - for (i = 0; i < num_ins; i++) { - struct usb_audio_term iterm; - len = 0; - namelist[i] = kmalloc(MAX_ITEM_NAME_LEN, GFP_KERNEL); - if (! namelist[i]) { - snd_printk(KERN_ERR "cannot malloc\n"); - 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[5 + i], &iterm) >= 0) - len = get_term_name(state, &iterm, namelist[i], MAX_ITEM_NAME_LEN, 0); - if (! len) - sprintf(namelist[i], "Input %d", i); - } - - kctl = snd_ctl_new1(&mixer_selectunit_ctl, cval); - if (! kctl) { - snd_printk(KERN_ERR "cannot malloc kcontrol\n"); - kfree(namelist); - kfree(cval); - return -ENOMEM; - } - kctl->private_value = (unsigned long)namelist; - kctl->private_free = usb_mixer_selector_elem_free; - - nameid = desc[desc[0] - 1]; - len = check_mapped_name(state, unitid, 0, kctl->id.name, sizeof(kctl->id.name)); - if (len) - ; - else if (nameid) - snd_usb_copy_string_desc(state, nameid, kctl->id.name, sizeof(kctl->id.name)); - else { - len = get_term_name(state, &state->oterm, - kctl->id.name, sizeof(kctl->id.name), 0); - if (! len) - strlcpy(kctl->id.name, "USB", sizeof(kctl->id.name)); - - if ((state->oterm.type & 0xff00) == 0x0100) - strlcat(kctl->id.name, " Capture Source", sizeof(kctl->id.name)); - else - strlcat(kctl->id.name, " Playback Source", sizeof(kctl->id.name)); - } - - snd_printdd(KERN_INFO "[%d] SU [%s] items = %d\n", - cval->id, kctl->id.name, num_ins); - if ((err = add_control_to_empty(state, kctl)) < 0) - return err; - - return 0; -} - - -/* - * 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) { - snd_printk(KERN_ERR "usbaudio: unit %d not found!\n", unitid); - return -EINVAL; - } - - switch (p1[2]) { - case INPUT_TERMINAL: - return 0; /* NOP */ - case MIXER_UNIT: - return parse_audio_mixer_unit(state, unitid, p1); - case SELECTOR_UNIT: - return parse_audio_selector_unit(state, unitid, p1); - case FEATURE_UNIT: - return parse_audio_feature_unit(state, unitid, p1); - case PROCESSING_UNIT: - return parse_audio_processing_unit(state, unitid, p1); - case EXTENSION_UNIT: - return parse_audio_extension_unit(state, unitid, p1); - default: - snd_printk(KERN_ERR "usbaudio: unit %u: unexpected type 0x%02x\n", unitid, p1[2]); - return -EINVAL; - } -} - -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 OUTPUT_TERMINAL descriptors to search for mixers - */ -static int snd_usb_mixer_controls(struct usb_mixer_interface *mixer) -{ - unsigned char *desc; - struct mixer_build state; - int err; - const struct usbmix_ctl_map *map; - struct usb_host_interface *hostif; - - hostif = &usb_ifnum_to_if(mixer->chip->dev, mixer->ctrlif)->altsetting[0]; - memset(&state, 0, sizeof(state)); - state.chip = mixer->chip; - state.mixer = mixer; - state.buffer = hostif->extra; - state.buflen = 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; - } - } - - desc = NULL; - while ((desc = snd_usb_find_csint_desc(hostif->extra, hostif->extralen, desc, OUTPUT_TERMINAL)) != NULL) { - if (desc[0] < 9) - continue; /* invalid descriptor? */ - set_bit(desc[3], state.unitbitmap); /* mark terminal ID as visited */ - state.oterm.id = desc[3]; - state.oterm.type = combine_word(&desc[4]); - state.oterm.name = desc[8]; - err = parse_audio_unit(&state, desc[7]); - if (err < 0) - return err; - } - return 0; -} - -static void snd_usb_mixer_notify_id(struct usb_mixer_interface *mixer, - int unitid) -{ - struct usb_mixer_elem_info *info; - - for (info = mixer->id_elems[unitid]; info; info = info->next_id_elem) - snd_ctl_notify(mixer->chip->card, SNDRV_CTL_EVENT_MASK_VALUE, - info->elem_id); -} - -static void snd_usb_mixer_memory_change(struct usb_mixer_interface *mixer, - int unitid) -{ - if (!mixer->rc_cfg) - return; - /* unit ids specific to Extigy/Audigy 2 NX: */ - switch (unitid) { - case 0: /* remote control */ - mixer->rc_urb->dev = mixer->chip->dev; - usb_submit_urb(mixer->rc_urb, GFP_ATOMIC); - break; - case 4: /* digital in jack */ - case 7: /* line in jacks */ - case 19: /* speaker out jacks */ - case 20: /* headphones out jack */ - break; - /* live24ext: 4 = line-in jack */ - case 3: /* hp-out jack (may actuate Mute) */ - if (mixer->chip->usb_id == USB_ID(0x041e, 0x3040)) - snd_usb_mixer_notify_id(mixer, mixer->rc_cfg->mute_mixer_id); - break; - default: - snd_printd(KERN_DEBUG "memory change in unknown unit %d\n", unitid); - break; - } -} - -static void snd_usb_mixer_status_complete(struct urb *urb) -{ - struct usb_mixer_interface *mixer = urb->context; - - if (urb->status == 0) { - u8 *buf = urb->transfer_buffer; - int i; - - for (i = urb->actual_length; i >= 2; buf += 2, i -= 2) { - snd_printd(KERN_DEBUG "status interrupt: %02x %02x\n", - buf[0], buf[1]); - /* ignore any notifications not from the control interface */ - if ((buf[0] & 0x0f) != 0) - continue; - if (!(buf[0] & 0x40)) - snd_usb_mixer_notify_id(mixer, buf[1]); - else - snd_usb_mixer_memory_change(mixer, buf[1]); - } - } - if (urb->status != -ENOENT && urb->status != -ECONNRESET) { - 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_host_interface *hostif; - struct usb_endpoint_descriptor *ep; - void *transfer_buffer; - int buffer_length; - unsigned int epnum; - - hostif = &usb_ifnum_to_if(mixer->chip->dev, mixer->ctrlif)->altsetting[0]; - /* we need one interrupt input endpoint */ - if (get_iface_desc(hostif)->bNumEndpoints < 1) - return 0; - ep = get_endpoint(hostif, 0); - if ((ep->bEndpointAddress & USB_ENDPOINT_DIR_MASK) != USB_DIR_IN || - (ep->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) != USB_ENDPOINT_XFER_INT) - return 0; - - epnum = ep->bEndpointAddress & USB_ENDPOINT_NUMBER_MASK; - 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_status_complete, mixer, ep->bInterval); - usb_submit_urb(mixer->urb, GFP_KERNEL); - return 0; -} - -static void snd_usb_soundblaster_remote_complete(struct urb *urb) -{ - struct usb_mixer_interface *mixer = urb->context; - const struct rc_config *rc = mixer->rc_cfg; - u32 code; - - if (urb->status < 0 || urb->actual_length < rc->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 int snd_usb_sbrc_hwdep_open(struct snd_hwdep *hw, struct file *file) -{ - struct usb_mixer_interface *mixer = hw->private_data; - - if (test_and_set_bit(0, &mixer->rc_hwdep_open)) - return -EBUSY; - return 0; -} - -static int snd_usb_sbrc_hwdep_release(struct snd_hwdep *hw, struct file *file) -{ - struct usb_mixer_interface *mixer = hw->private_data; - - clear_bit(0, &mixer->rc_hwdep_open); - smp_mb__after_clear_bit(); - return 0; -} - -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.open = snd_usb_sbrc_hwdep_open; - hwdep->ops.release = snd_usb_sbrc_hwdep_release; - hwdep->ops.poll = snd_usb_sbrc_hwdep_poll; - - 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 = GET_MEM; - mixer->rc_setup_packet->wValue = cpu_to_le16(0); - mixer->rc_setup_packet->wIndex = cpu_to_le16(0); - mixer->rc_setup_packet->wLength = cpu_to_le16(len); - usb_fill_control_urb(mixer->rc_urb, mixer->chip->dev, - usb_rcvctrlpipe(mixer->chip->dev, 0), - (u8*)mixer->rc_setup_packet, mixer->rc_buffer, len, - snd_usb_soundblaster_remote_complete, mixer); - return 0; -} - -#define snd_audigy2nx_led_info snd_ctl_boolean_mono_info - -static int snd_audigy2nx_led_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) -{ - struct usb_mixer_interface *mixer = snd_kcontrol_chip(kcontrol); - int index = kcontrol->private_value; - - ucontrol->value.integer.value[0] = mixer->audigy2nx_leds[index]; - return 0; -} - -static int snd_audigy2nx_led_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) -{ - struct usb_mixer_interface *mixer = snd_kcontrol_chip(kcontrol); - int index = kcontrol->private_value; - int value = ucontrol->value.integer.value[0]; - int err, changed; - - if (value > 1) - return -EINVAL; - changed = value != mixer->audigy2nx_leds[index]; - err = snd_usb_ctl_msg(mixer->chip->dev, - usb_sndctrlpipe(mixer->chip->dev, 0), 0x24, - USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_OTHER, - value, index + 2, NULL, 0, 100); - if (err < 0) - return err; - mixer->audigy2nx_leds[index] = value; - return changed; -} - -static struct snd_kcontrol_new snd_audigy2nx_controls[] = { - { - .iface = SNDRV_CTL_ELEM_IFACE_MIXER, - .name = "CMSS LED Switch", - .info = snd_audigy2nx_led_info, - .get = snd_audigy2nx_led_get, - .put = snd_audigy2nx_led_put, - .private_value = 0, - }, - { - .iface = SNDRV_CTL_ELEM_IFACE_MIXER, - .name = "Power LED Switch", - .info = snd_audigy2nx_led_info, - .get = snd_audigy2nx_led_get, - .put = snd_audigy2nx_led_put, - .private_value = 1, - }, - { - .iface = SNDRV_CTL_ELEM_IFACE_MIXER, - .name = "Dolby Digital LED Switch", - .info = snd_audigy2nx_led_info, - .get = snd_audigy2nx_led_get, - .put = snd_audigy2nx_led_put, - .private_value = 2, - }, -}; - -static int snd_audigy2nx_controls_create(struct usb_mixer_interface *mixer) -{ - int i, err; - - for (i = 0; i < ARRAY_SIZE(snd_audigy2nx_controls); ++i) { - if (i > 1 && /* Live24ext has 2 LEDs only */ - mixer->chip->usb_id == USB_ID(0x041e, 0x3040)) - break; - err = snd_ctl_add(mixer->chip->card, - snd_ctl_new1(&snd_audigy2nx_controls[i], mixer)); - if (err < 0) - return err; - } - mixer->audigy2nx_leds[1] = 1; /* Power LED is on by default */ - return 0; -} - -static void snd_audigy2nx_proc_read(struct snd_info_entry *entry, - struct snd_info_buffer *buffer) -{ - static const struct sb_jack { - int unitid; - const char *name; - } jacks_audigy2nx[] = { - {4, "dig in "}, - {7, "line in"}, - {19, "spk out"}, - {20, "hph out"}, - {-1, NULL} - }, jacks_live24ext[] = { - {4, "line in"}, /* &1=Line, &2=Mic*/ - {3, "hph out"}, /* headphones */ - {0, "RC "}, /* last command, 6 bytes see rc_config above */ - {-1, NULL} - }; - const struct sb_jack *jacks; - struct usb_mixer_interface *mixer = entry->private_data; - int i, err; - u8 buf[3]; - - snd_iprintf(buffer, "%s jacks\n\n", mixer->chip->card->shortname); - if (mixer->chip->usb_id == USB_ID(0x041e, 0x3020)) - jacks = jacks_audigy2nx; - else if (mixer->chip->usb_id == USB_ID(0x041e, 0x3040)) - jacks = jacks_live24ext; - else - return; - - for (i = 0; jacks[i].name; ++i) { - snd_iprintf(buffer, "%s: ", jacks[i].name); - err = snd_usb_ctl_msg(mixer->chip->dev, - usb_rcvctrlpipe(mixer->chip->dev, 0), - GET_MEM, USB_DIR_IN | USB_TYPE_CLASS | - USB_RECIP_INTERFACE, 0, - jacks[i].unitid << 8, buf, 3, 100); - if (err == 3 && (buf[0] == 3 || buf[0] == 6)) - snd_iprintf(buffer, "%02x %02x\n", buf[1], buf[2]); - else - snd_iprintf(buffer, "?\n"); - } -} - -int snd_usb_create_mixer(struct snd_usb_audio *chip, int ctrlif) -{ - static struct snd_device_ops dev_ops = { - .dev_free = snd_usb_mixer_dev_free - }; - struct usb_mixer_interface *mixer; - int err; - - strcpy(chip->card->mixername, "USB Mixer"); - - mixer = kzalloc(sizeof(*mixer), GFP_KERNEL); - if (!mixer) - return -ENOMEM; - mixer->chip = chip; - mixer->ctrlif = ctrlif; -#ifdef IGNORE_CTL_ERROR - mixer->ignore_ctl_error = 1; -#endif - mixer->id_elems = kcalloc(256, sizeof(*mixer->id_elems), GFP_KERNEL); - if (!mixer->id_elems) { - kfree(mixer); - return -ENOMEM; - } - - if ((err = snd_usb_mixer_controls(mixer)) < 0 || - (err = snd_usb_mixer_status_create(mixer)) < 0) - goto _error; - - if ((err = snd_usb_soundblaster_remote_init(mixer)) < 0) - goto _error; - - if (mixer->chip->usb_id == USB_ID(0x041e, 0x3020) || - mixer->chip->usb_id == USB_ID(0x041e, 0x3040)) { - struct snd_info_entry *entry; - - if ((err = snd_audigy2nx_controls_create(mixer)) < 0) - goto _error; - if (!snd_card_proc_new(chip->card, "audigy2nx", &entry)) - snd_info_set_text_ops(entry, mixer, - snd_audigy2nx_proc_read); - } - - err = snd_device_new(chip->card, SNDRV_DEV_LOWLEVEL, mixer, &dev_ops); - if (err < 0) - goto _error; - list_add(&mixer->list, &chip->mixer_list); - return 0; - -_error: - snd_usb_mixer_free(mixer); - return err; -} - -void snd_usb_mixer_disconnect(struct list_head *p) -{ - struct usb_mixer_interface *mixer; - - mixer = list_entry(p, struct usb_mixer_interface, list); - usb_kill_urb(mixer->urb); - usb_kill_urb(mixer->rc_urb); -} diff --git a/sound/usb/usx2y/Makefile b/sound/usb/usx2y/Makefile index 9ac22bce112..748933054b6 100644 --- a/sound/usb/usx2y/Makefile +++ b/sound/usb/usx2y/Makefile @@ -1,3 +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 00000000000..cf5dc33f4a6 --- /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 00000000000..f263b3f96c8 --- /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 index 1558a5c4094..0b34dbc8f30 100644 --- a/sound/usb/usx2y/usX2Yhwdep.c +++ b/sound/usb/usx2y/usX2Yhwdep.c @@ -21,6 +21,7 @@ */ #include <linux/interrupt.h> +#include <linux/slab.h> #include <linux/usb.h> #include <sound/core.h> #include <sound/memalloc.h> @@ -30,9 +31,6 @@ #include "usbusx2y.h" #include "usX2Yhwdep.h" -int usX2Y_hwdep_pcm_new(struct snd_card *card); - - static int snd_us428ctls_vm_fault(struct vm_area_struct *area, struct vm_fault *vmf) { @@ -56,7 +54,7 @@ static int snd_us428ctls_vm_fault(struct vm_area_struct *area, return 0; } -static struct vm_operations_struct us428ctls_vm_ops = { +static const struct vm_operations_struct us428ctls_vm_ops = { .fault = snd_us428ctls_vm_fault, }; @@ -84,7 +82,7 @@ static int snd_us428ctls_mmap(struct snd_hwdep * hw, struct file *filp, struct v us428->us428ctls_sharedmem->CtlSnapShotLast = -2; } area->vm_ops = &us428ctls_vm_ops; - area->vm_flags |= VM_RESERVED | VM_DONTEXPAND; + area->vm_flags |= VM_DONTEXPAND | VM_DONTDUMP; area->vm_private_data = hw->private_data; return 0; } @@ -106,16 +104,6 @@ static unsigned int snd_us428ctls_poll(struct snd_hwdep *hw, struct file *file, } -static int snd_usX2Y_hwdep_open(struct snd_hwdep *hw, struct file *file) -{ - return 0; -} - -static int snd_usX2Y_hwdep_release(struct snd_hwdep *hw, struct file *file) -{ - return 0; -} - static int snd_usX2Y_hwdep_dsp_status(struct snd_hwdep *hw, struct snd_hwdep_dsp_status *info) { @@ -127,7 +115,7 @@ static int snd_usX2Y_hwdep_dsp_status(struct snd_hwdep *hw, struct usX2Ydev *us428 = hw->private_data; int id = -1; - switch (le16_to_cpu(us428->chip.dev->descriptor.idProduct)) { + switch (le16_to_cpu(us428->dev->descriptor.idProduct)) { case USB_ID_US122: id = USX2Y_TYPE_122; break; @@ -177,14 +165,14 @@ static int usX2Y_create_usbmidi(struct snd_card *card) .type = QUIRK_MIDI_FIXED_ENDPOINT, .data = &quirk_data_2 }; - struct usb_device *dev = usX2Y(card)->chip.dev; + 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_usb_create_midi_interface(&usX2Y(card)->chip, iface, quirk); + return snd_usbmidi_create(card, iface, &usX2Y(card)->midi_list, quirk); } static int usX2Y_create_alsa_devices(struct snd_card *card) @@ -215,14 +203,13 @@ static int snd_usX2Y_hwdep_dsp_load(struct snd_hwdep *hw, snd_printdd( "dsp_load %s\n", dsp->name); if (access_ok(VERIFY_READ, dsp->image, dsp->length)) { - struct usb_device* dev = priv->chip.dev; - char *buf = kmalloc(dsp->length, GFP_KERNEL); - if (!buf) - return -ENOMEM; - if (copy_from_user(buf, dsp->image, dsp->length)) { - kfree(buf); - return -EFAULT; - } + 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"); @@ -267,8 +254,6 @@ int usX2Y_hwdep_new(struct snd_card *card, struct usb_device* device) hw->iface = SNDRV_HWDEP_IFACE_USX2Y; hw->private_data = usX2Y(card); - hw->ops.open = snd_usX2Y_hwdep_open; - hw->ops.release = snd_usX2Y_hwdep_release; hw->ops.dsp_status = snd_usX2Y_hwdep_dsp_status; hw->ops.dsp_load = snd_usX2Y_hwdep_dsp_load; hw->ops.mmap = snd_us428ctls_mmap; diff --git a/sound/usb/usx2y/usb_stream.c b/sound/usb/usx2y/usb_stream.c new file mode 100644 index 00000000000..bf618e1500a --- /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 00000000000..4dd74ab1e9c --- /dev/null +++ b/sound/usb/usx2y/usb_stream.h @@ -0,0 +1,112 @@ +/* + * 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. + */ + +#define USB_STREAM_INTERFACE_VERSION 2 + +#define SNDRV_USB_STREAM_IOCTL_SET_PARAMS \ + _IOW('H', 0x90, struct usb_stream_config) + +struct usb_stream_packet { + unsigned offset; + unsigned length; +}; + + +struct usb_stream_config { + unsigned version; + unsigned sample_rate; + unsigned period_frames; + unsigned frame_size; +}; + +struct usb_stream { + struct usb_stream_config cfg; + unsigned read_size; + unsigned write_size; + + int period_size; + + unsigned state; + + int idle_insize; + int idle_outsize; + int sync_packet; + unsigned insize_done; + unsigned periods_done; + unsigned periods_polled; + + struct usb_stream_packet outpacket[2]; + unsigned inpackets; + unsigned inpacket_head; + unsigned inpacket_split; + unsigned inpacket_split_at; + unsigned next_inpacket_split; + unsigned next_inpacket_split_at; + struct usb_stream_packet inpacket[0]; +}; + +enum usb_stream_state { + usb_stream_invalid, + usb_stream_stopped, + usb_stream_sync0, + usb_stream_sync1, + usb_stream_ready, + usb_stream_running, + usb_stream_xrun, +}; + +#if __KERNEL__ + +#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 diff --git a/sound/usb/usx2y/usbusx2y.c b/sound/usb/usx2y/usbusx2y.c index e5981a63031..91e0e2a4808 100644 --- a/sound/usb/usx2y/usbusx2y.c +++ b/sound/usb/usx2y/usbusx2y.c @@ -133,6 +133,7 @@ #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> @@ -149,11 +150,11 @@ 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) }}"); +MODULE_SUPPORTED_DEVICE("{{TASCAM(0x1604),"NAME_ALLCAPS"(0x8001)(0x8005)(0x8007)}}"); static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 0-max */ static char* id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* Id for this card */ -static int enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP; /* Enable this card */ +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"."); @@ -227,9 +228,9 @@ static void i_usX2Y_In04Int(struct urb *urb) if (usX2Y->US04) { if (0 == usX2Y->US04->submitted) - do + do { err = usb_submit_urb(usX2Y->US04->urb[usX2Y->US04->submitted++], GFP_ATOMIC); - while (!err && usX2Y->US04->submitted < usX2Y->US04->len); + } 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) { @@ -238,9 +239,9 @@ static void i_usX2Y_In04Int(struct urb *urb) 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 then 1 p4out is new, 1 gets lost. - usb_fill_bulk_urb(usX2Y->AS04.urb[j], usX2Y->chip.dev, - usb_sndbulkpipe(usX2Y->chip.dev, 0x04), &p4out->val.vol, + 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); @@ -253,7 +254,7 @@ static void i_usX2Y_In04Int(struct urb *urb) if (err) snd_printk(KERN_ERR "In04Int() usb_submit_urb err=%i\n", err); - urb->dev = usX2Y->chip.dev; + urb->dev = usX2Y->dev; usb_submit_urb(urb, GFP_ATOMIC); } @@ -273,8 +274,8 @@ int usX2Y_AsyncSeq04_init(struct usX2Ydev *usX2Y) err = -ENOMEM; break; } - usb_fill_bulk_urb( usX2Y->AS04.urb[i], usX2Y->chip.dev, - usb_sndbulkpipe(usX2Y->chip.dev, 0x04), + 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 ); @@ -293,7 +294,7 @@ int usX2Y_In04_init(struct usX2Ydev *usX2Y) } init_waitqueue_head(&usX2Y->In04WaitQueue); - usb_fill_int_urb(usX2Y->In04urb, usX2Y->chip.dev, usb_rcvintpipe(usX2Y->chip.dev, 0x4), + usb_fill_int_urb(usX2Y->In04urb, usX2Y->dev, usb_rcvintpipe(usX2Y->dev, 0x4), usX2Y->In04Buf, 21, i_usX2Y_In04Int, usX2Y, 10); @@ -304,11 +305,9 @@ static void usX2Y_unlinkSeq(struct snd_usX2Y_AsyncSeq *S) { int i; for (i = 0; i < URBS_AsyncSeq; ++i) { - if (S[i].urb) { - usb_kill_urb(S->urb[i]); - usb_free_urb(S->urb[i]); - S->urb[i] = NULL; - } + usb_kill_urb(S->urb[i]); + usb_free_urb(S->urb[i]); + S->urb[i] = NULL; } kfree(S->buffer); } @@ -333,25 +332,29 @@ static struct usb_device_id snd_usX2Y_usb_id_table[] = { { /* terminator */ } }; -static struct snd_card *usX2Y_create_card(struct usb_device *device) +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 NULL; - card = snd_card_new(index[dev], id[dev], THIS_MODULE, sizeof(struct usX2Ydev)); - if (!card) - return NULL; - snd_usX2Y_card_used[usX2Y(card)->chip.index = dev] = 1; + 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)->chip.dev = device; - usX2Y(card)->chip.card = card; + usX2Y(card)->dev = device; init_waitqueue_head(&usX2Y(card)->prepare_wait_queue); - mutex_init(&usX2Y(card)->prepare_mutex); - INIT_LIST_HEAD(&usX2Y(card)->chip.midi_list); + 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)", @@ -359,29 +362,38 @@ static struct snd_card *usX2Y_create_card(struct usb_device *device) le16_to_cpu(device->descriptor.idVendor), le16_to_cpu(device->descriptor.idProduct), 0,//us428(card)->usbmidi.ifnum, - usX2Y(card)->chip.dev->bus->busnum, usX2Y(card)->chip.dev->devnum + usX2Y(card)->dev->bus->busnum, usX2Y(card)->dev->devnum ); - snd_card_set_dev(card, &device->dev); - return card; + *cardp = card; + return 0; } -static void *usX2Y_usb_probe(struct usb_device *device, struct usb_interface *intf, const struct usb_device_id *device_id) +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) || - !(card = usX2Y_create_card(device))) - return NULL; + 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 NULL; + return err; } - return card; + *cardp = card; + return 0; } /* @@ -389,19 +401,20 @@ static void *usX2Y_usb_probe(struct usb_device *device, struct usb_interface *in */ static int snd_usX2Y_probe(struct usb_interface *intf, const struct usb_device_id *id) { - void *chip; - chip = usX2Y_usb_probe(interface_to_usbdev(intf), intf, id); - if (chip) { - dev_set_drvdata(&intf->dev, chip); - return 0; - } else - return -EIO; + 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), - dev_get_drvdata(&intf->dev)); + usb_get_intfdata(intf)); } MODULE_DEVICE_TABLE(usb, snd_usX2Y_usb_id_table); @@ -418,8 +431,8 @@ static void snd_usX2Y_card_private_free(struct snd_card *card) usb_free_urb(usX2Y(card)->In04urb); if (usX2Y(card)->us428ctls_sharedmem) snd_free_pages(usX2Y(card)->us428ctls_sharedmem, sizeof(*usX2Y(card)->us428ctls_sharedmem)); - if (usX2Y(card)->chip.index >= 0 && usX2Y(card)->chip.index < SNDRV_CARDS) - snd_usX2Y_card_used[usX2Y(card)->chip.index] = 0; + if (usX2Y(card)->card_index >= 0 && usX2Y(card)->card_index < SNDRV_CARDS) + snd_usX2Y_card_used[usX2Y(card)->card_index] = 0; } /* @@ -431,13 +444,12 @@ static void usX2Y_usb_disconnect(struct usb_device *device, void* ptr) struct snd_card *card = ptr; struct usX2Ydev *usX2Y = usX2Y(card); struct list_head *p; - usX2Y->chip.shutdown = 1; 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->chip.midi_list) { + list_for_each(p, &usX2Y->midi_list) { snd_usbmidi_disconnect(p); } if (usX2Y->us428ctls_sharedmem) @@ -446,15 +458,4 @@ static void usX2Y_usb_disconnect(struct usb_device *device, void* ptr) } } -static int __init snd_usX2Y_module_init(void) -{ - return usb_register(&snd_usX2Y_usb_driver); -} - -static void __exit snd_usX2Y_module_exit(void) -{ - usb_deregister(&snd_usX2Y_usb_driver); -} - -module_init(snd_usX2Y_module_init) -module_exit(snd_usX2Y_module_exit) +module_usb_driver(snd_usX2Y_usb_driver); diff --git a/sound/usb/usx2y/usbusx2y.h b/sound/usb/usx2y/usbusx2y.h index 456b5fdbc33..6ae6b080693 100644 --- a/sound/usb/usx2y/usbusx2y.h +++ b/sound/usb/usx2y/usbusx2y.h @@ -1,6 +1,7 @@ #ifndef USBUSX2Y_H #define USBUSX2Y_H #include "../usbaudio.h" +#include "../midi.h" #include "usbus428ctldefs.h" #define NRURBS 2 @@ -22,7 +23,8 @@ struct snd_usX2Y_urbSeq { #include "usx2yhwdeppcm.h" struct usX2Ydev { - struct snd_usb_audio chip; + struct usb_device *dev; + int card_index; int stride; struct urb *In04urb; void *In04Buf; @@ -34,7 +36,7 @@ struct usX2Ydev { unsigned int rate, format; int chip_status; - struct mutex prepare_mutex; + struct mutex pcm_mutex; struct us428ctls_sharedmem *us428ctls_sharedmem; int wait_iso_frame; wait_queue_head_t us428ctls_wait_queue_head; @@ -42,6 +44,9 @@ struct usX2Ydev { 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; }; diff --git a/sound/usb/usx2y/usbusx2yaudio.c b/sound/usb/usx2y/usbusx2yaudio.c index 9a608fa8515..a63330dd140 100644 --- a/sound/usb/usx2y/usbusx2yaudio.c +++ b/sound/usb/usx2y/usbusx2yaudio.c @@ -32,7 +32,9 @@ #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> @@ -78,7 +80,7 @@ static int usX2Y_urb_capt_retire(struct snd_usX2Y_substream *subs) 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 propably some hardware problem.\n", + "Most probably some hardware problem.\n", urb->iso_frame_desc[i].status); return urb->iso_frame_desc[i].status; } @@ -199,7 +201,7 @@ static int usX2Y_urb_submit(struct snd_usX2Y_substream *subs, struct urb *urb, i return -ENODEV; urb->start_frame = (frame + NRURBS * nr_of_packs()); // let hcd do rollover sanity checks urb->hcpriv = NULL; - urb->dev = subs->usX2Y->chip.dev; /* we need to set this at each time */ + 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; @@ -271,7 +273,11 @@ static void usX2Y_clients_stop(struct usX2Ydev *usX2Y) struct snd_usX2Y_substream *subs = usX2Y->subs[s]; if (subs) { if (atomic_read(&subs->state) >= state_PRERUNNING) { + unsigned long flags; + + snd_pcm_stream_lock_irqsave(subs->pcm_substream, flags); snd_pcm_stop(subs->pcm_substream, SNDRV_PCM_STATE_XRUN); + snd_pcm_stream_unlock_irqrestore(subs->pcm_substream, flags); } for (u = 0; u < NRURBS; u++) { struct urb *urb = subs->urb[u]; @@ -293,18 +299,6 @@ static void usX2Y_error_urb_status(struct usX2Ydev *usX2Y, usX2Y_clients_stop(usX2Y); } -static void usX2Y_error_sequence(struct usX2Ydev *usX2Y, - struct snd_usX2Y_substream *subs, struct urb *urb) -{ - snd_printk(KERN_ERR "Sequence Error!(hcd_frame=%i ep=%i%s;wait=%i,frame=%i).\n" - KERN_ERR "Most propably some urb of usb-frame %i is still missing.\n" - KERN_ERR "Cause could be too long delays in usb-hcd interrupt handling.\n", - usb_get_current_frame_number(usX2Y->chip.dev), - subs->endpoint, usb_pipein(urb->pipe) ? "in" : "out", - usX2Y->wait_iso_frame, urb->start_frame, usX2Y->wait_iso_frame); - usX2Y_clients_stop(usX2Y); -} - static void i_usX2Y_urb_complete(struct urb *urb) { struct snd_usX2Y_substream *subs = urb->context; @@ -312,7 +306,7 @@ static void i_usX2Y_urb_complete(struct urb *urb) if (unlikely(atomic_read(&subs->state) < state_PREPARED)) { snd_printdd("hcd_frame=%i ep=%i%s status=%i start_frame=%i\n", - usb_get_current_frame_number(usX2Y->chip.dev), + usb_get_current_frame_number(usX2Y->dev), subs->endpoint, usb_pipein(urb->pipe) ? "in" : "out", urb->status, urb->start_frame); return; @@ -321,12 +315,9 @@ static void i_usX2Y_urb_complete(struct urb *urb) usX2Y_error_urb_status(usX2Y, subs, urb); return; } - if (likely((urb->start_frame & 0xFFFF) == (usX2Y->wait_iso_frame & 0xFFFF))) - subs->completed_urb = urb; - else { - usX2Y_error_sequence(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]; @@ -423,7 +414,7 @@ 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->chip.dev; + struct usb_device *dev = subs->usX2Y->dev; pipe = is_playback ? usb_sndisocpipe(dev, subs->endpoint) : usb_rcvisocpipe(dev, subs->endpoint); @@ -499,8 +490,7 @@ static int usX2Y_urbs_start(struct snd_usX2Y_substream *subs) unsigned long pack; if (0 == i) atomic_set(&subs->state, state_STARTING3); - urb->dev = usX2Y->chip.dev; - urb->transfer_flags = URB_ISO_ASAP; + 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; @@ -691,11 +681,8 @@ static int usX2Y_rate_set(struct usX2Ydev *usX2Y, int rate) } ((char*)(usbdata + i))[0] = ra[i].c1; ((char*)(usbdata + i))[1] = ra[i].c2; - usb_fill_bulk_urb(us->urb[i], usX2Y->chip.dev, usb_sndbulkpipe(usX2Y->chip.dev, 4), + usb_fill_bulk_urb(us->urb[i], usX2Y->dev, usb_sndbulkpipe(usX2Y->dev, 4), usbdata + i, 2, i_usX2Y_04Int, usX2Y); -#ifdef OLD_USB - us->urb[i]->transfer_flags = USB_QUEUE_BULK; -#endif } us->submitted = 0; us->len = NOOF_SETRATE_URBS; @@ -739,17 +726,17 @@ static int usX2Y_format_set(struct usX2Ydev *usX2Y, snd_pcm_format_t format) alternate = 1; usX2Y->stride = 4; } - list_for_each(p, &usX2Y->chip.midi_list) { + list_for_each(p, &usX2Y->midi_list) { snd_usbmidi_input_stop(p); } usb_kill_urb(usX2Y->In04urb); - if ((err = usb_set_interface(usX2Y->chip.dev, 0, alternate))) { + if ((err = usb_set_interface(usX2Y->dev, 0, alternate))) { snd_printk(KERN_ERR "usb_set_interface error \n"); return err; } - usX2Y->In04urb->dev = usX2Y->chip.dev; + usX2Y->In04urb->dev = usX2Y->dev; err = usb_submit_urb(usX2Y->In04urb, GFP_KERNEL); - list_for_each(p, &usX2Y->chip.midi_list) { + list_for_each(p, &usX2Y->midi_list) { snd_usbmidi_input_start(p); } usX2Y->format = format; @@ -765,36 +752,44 @@ static int snd_usX2Y_pcm_hw_params(struct snd_pcm_substream *substream, 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 list_head *list; + 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 - list_for_each(list, &card->devices) { - struct snd_device *dev; - struct snd_pcm *pcm; - int s; - dev = snd_device(list); - if (dev->type != SNDRV_DEV_PCM) + /* 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; - pcm = dev->device_data; - for (s = 0; s < 2; ++s) { - struct snd_pcm_substream *test_substream; - test_substream = pcm->streams[s].substream; - if (test_substream && test_substream != substream && - test_substream->runtime && - ((test_substream->runtime->format && - test_substream->runtime->format != format) || - (test_substream->runtime->rate && - test_substream->runtime->rate != rate))) - return -EINVAL; + if ((test_substream->runtime->format && + test_substream->runtime->format != format) || + (test_substream->runtime->rate && + test_substream->runtime->rate != rate)) { + err = -EINVAL; + goto error; } } - if (0 > (err = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params)))) { + + 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); - return err; + goto error; } - return 0; + + error: + mutex_unlock(&usX2Y(card)->pcm_mutex); + return err; } /* @@ -804,7 +799,7 @@ 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->prepare_mutex); + mutex_lock(&subs->usX2Y->pcm_mutex); snd_printdd("snd_usX2Y_hw_free(%p)\n", substream); if (SNDRV_PCM_STREAM_PLAYBACK == substream->stream) { @@ -825,7 +820,7 @@ static int snd_usX2Y_pcm_hw_free(struct snd_pcm_substream *substream) usX2Y_urbs_release(subs); } } - mutex_unlock(&subs->usX2Y->prepare_mutex); + mutex_unlock(&subs->usX2Y->pcm_mutex); return snd_pcm_lib_free_pages(substream); } /* @@ -842,7 +837,7 @@ static int snd_usX2Y_pcm_prepare(struct snd_pcm_substream *substream) int err = 0; snd_printdd("snd_usX2Y_pcm_prepare(%p)\n", substream); - mutex_lock(&usX2Y->prepare_mutex); + mutex_lock(&usX2Y->pcm_mutex); usX2Y_subs_prepare(subs); // Start hardware streams // SyncStream first.... @@ -862,7 +857,7 @@ static int snd_usX2Y_pcm_prepare(struct snd_pcm_substream *substream) err = usX2Y_urbs_start(subs); up_prepare_mutex: - mutex_unlock(&usX2Y->prepare_mutex); + mutex_unlock(&usX2Y->pcm_mutex); return err; } @@ -870,7 +865,8 @@ 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_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, @@ -953,7 +949,7 @@ static int usX2Y_audio_stream_new(struct snd_card *card, int playback_endpoint, struct snd_pcm *pcm; int err, i; struct snd_usX2Y_substream **usX2Y_substream = - usX2Y(card)->subs + 2 * usX2Y(card)->chip.pcm_devs; + 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) { @@ -969,7 +965,7 @@ static int usX2Y_audio_stream_new(struct snd_card *card, int playback_endpoint, usX2Y_substream[SNDRV_PCM_STREAM_PLAYBACK]->endpoint = playback_endpoint; usX2Y_substream[SNDRV_PCM_STREAM_CAPTURE]->endpoint = capture_endpoint; - err = snd_pcm_new(card, NAME_ALLCAPS" Audio", usX2Y(card)->chip.pcm_devs, + err = snd_pcm_new(card, NAME_ALLCAPS" Audio", usX2Y(card)->pcm_devs, playback_endpoint ? 1 : 0, 1, &pcm); if (err < 0) { @@ -985,7 +981,7 @@ static int usX2Y_audio_stream_new(struct snd_card *card, int playback_endpoint, pcm->private_free = snd_usX2Y_pcm_private_free; pcm->info_flags = 0; - sprintf(pcm->name, NAME_ALLCAPS" Audio #%d", usX2Y(card)->chip.pcm_devs); + 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, @@ -999,7 +995,7 @@ static int usX2Y_audio_stream_new(struct snd_card *card, int playback_endpoint, snd_usX2Y_pcm_private_free(pcm); return err; } - usX2Y(card)->chip.pcm_devs++; + usX2Y(card)->pcm_devs++; return 0; } @@ -1011,14 +1007,14 @@ int usX2Y_audio_create(struct snd_card *card) { int err = 0; - INIT_LIST_HEAD(&usX2Y(card)->chip.pcm_list); + 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)->chip.dev->descriptor.idProduct) == USB_ID_US428) + 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)->chip.dev->descriptor.idProduct) != USB_ID_US122) + 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/usx2yhwdeppcm.c b/sound/usb/usx2y/usx2yhwdeppcm.c index 117946f2deb..90766a92e7f 100644 --- a/sound/usb/usx2y/usx2yhwdeppcm.c +++ b/sound/usb/usx2y/usx2yhwdeppcm.c @@ -20,7 +20,7 @@ at standard samplerates, what led to this part of the usx2y module: It provides the alsa kernel half of the usx2y-alsa-jack driver pair. - The pair uses a hardware dependant alsa-device for mmaped pcm transport. + 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. @@ -36,9 +36,9 @@ plain usx2y alsa mode is able to achieve 64frames, 4periods, but only at the cost of easier triggered i.e. aeolus xruns (128 or 256frames, 2periods works but is useless cause of crackling). - + This is a first "proof of concept" implementation. - Later, funcionalities should migrate to more apropriate places: + 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 @@ -51,9 +51,10 @@ */ #include <linux/delay.h> +#include <linux/gfp.h> #include "usbusx2yaudio.c" -#if defined(USX2Y_NRPACKS_VARIABLE) || (!defined(USX2Y_NRPACKS_VARIABLE) && USX2Y_NRPACKS == 1) +#if defined(USX2Y_NRPACKS_VARIABLE) || USX2Y_NRPACKS == 1 #include <sound/hwdep.h> @@ -73,7 +74,7 @@ static int usX2Y_usbpcm_urb_capt_retire(struct snd_usX2Y_substream *subs) } for (i = 0; i < nr_of_packs(); i++) { if (urb->iso_frame_desc[i].status) { /* active? hmm, skip this */ - snd_printk(KERN_ERR "activ frame status %i. Most propably some hardware problem.\n", urb->iso_frame_desc[i].status); + 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; @@ -234,7 +235,7 @@ static void i_usX2Y_usbpcm_urb_complete(struct urb *urb) if (unlikely(atomic_read(&subs->state) < state_PREPARED)) { snd_printdd("hcd_frame=%i ep=%i%s status=%i start_frame=%i\n", - usb_get_current_frame_number(usX2Y->chip.dev), + usb_get_current_frame_number(usX2Y->dev), subs->endpoint, usb_pipein(urb->pipe) ? "in" : "out", urb->status, urb->start_frame); return; @@ -243,13 +244,8 @@ static void i_usX2Y_usbpcm_urb_complete(struct urb *urb) usX2Y_error_urb_status(usX2Y, subs, urb); return; } - if (likely((urb->start_frame & 0xFFFF) == (usX2Y->wait_iso_frame & 0xFFFF))) - subs->completed_urb = urb; - else { - usX2Y_error_sequence(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]; @@ -318,7 +314,7 @@ 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->chip.dev; + struct usb_device *dev = subs->usX2Y->dev; pipe = is_playback ? usb_sndisocpipe(dev, subs->endpoint) : usb_rcvisocpipe(dev, subs->endpoint); @@ -362,7 +358,7 @@ 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->prepare_mutex); + mutex_lock(&subs->usX2Y->pcm_mutex); snd_printdd("snd_usX2Y_usbpcm_hw_free(%p)\n", substream); if (SNDRV_PCM_STREAM_PLAYBACK == substream->stream) { @@ -391,7 +387,7 @@ static int snd_usX2Y_usbpcm_hw_free(struct snd_pcm_substream *substream) usX2Y_usbpcm_urbs_release(cap_subs2); } } - mutex_unlock(&subs->usX2Y->prepare_mutex); + mutex_unlock(&subs->usX2Y->pcm_mutex); return snd_pcm_lib_free_pages(substream); } @@ -441,8 +437,7 @@ static int usX2Y_usbpcm_urbs_start(struct snd_usX2Y_substream *subs) unsigned long pack; if (0 == u) atomic_set(&subs->state, state_STARTING3); - urb->dev = usX2Y->chip.dev; - urb->transfer_flags = URB_ISO_ASAP; + 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; @@ -498,7 +493,7 @@ static int snd_usX2Y_usbpcm_prepare(struct snd_pcm_substream *substream) memset(usX2Y->hwdep_pcm_shm, 0, sizeof(struct snd_usX2Y_hwdep_pcm_shm)); } - mutex_lock(&usX2Y->prepare_mutex); + mutex_lock(&usX2Y->pcm_mutex); usX2Y_subs_prepare(subs); // Start hardware streams // SyncStream first.... @@ -539,7 +534,7 @@ static int snd_usX2Y_usbpcm_prepare(struct snd_pcm_substream *substream) usX2Y->hwdep_pcm_shm->capture_iso_start = -1; up_prepare_mutex: - mutex_unlock(&usX2Y->prepare_mutex); + mutex_unlock(&usX2Y->pcm_mutex); return err; } @@ -605,59 +600,30 @@ static struct snd_pcm_ops snd_usX2Y_usbpcm_ops = }; -static int usX2Y_pcms_lock_check(struct snd_card *card) +static int usX2Y_pcms_busy_check(struct snd_card *card) { - struct list_head *list; - struct snd_device *dev; - struct snd_pcm *pcm; - int err = 0; - list_for_each(list, &card->devices) { - dev = snd_device(list); - if (dev->type != SNDRV_DEV_PCM) - continue; - pcm = dev->device_data; - mutex_lock(&pcm->open_mutex); - } - list_for_each(list, &card->devices) { - int s; - dev = snd_device(list); - if (dev->type != SNDRV_DEV_PCM) - continue; - pcm = dev->device_data; - for (s = 0; s < 2; ++s) { - struct snd_pcm_substream *substream; - substream = pcm->streams[s].substream; - if (substream && SUBSTREAM_BUSY(substream)) - err = -EBUSY; - } - } - return err; -} - + struct usX2Ydev *dev = usX2Y(card); + int i; -static void usX2Y_pcms_unlock(struct snd_card *card) -{ - struct list_head *list; - struct snd_device *dev; - struct snd_pcm *pcm; - list_for_each(list, &card->devices) { - dev = snd_device(list); - if (dev->type != SNDRV_DEV_PCM) - continue; - pcm = dev->device_data; - mutex_unlock(&pcm->open_mutex); + 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) { - // we need to be the first struct snd_card *card = hw->card; - int err = usX2Y_pcms_lock_check(card); - if (0 == err) + 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; - usX2Y_pcms_unlock(card); + mutex_unlock(&usX2Y(card)->pcm_mutex); return err; } @@ -665,10 +631,13 @@ static int snd_usX2Y_hwdep_pcm_open(struct snd_hwdep *hw, struct file *file) static int snd_usX2Y_hwdep_pcm_release(struct snd_hwdep *hw, struct file *file) { struct snd_card *card = hw->card; - int err = usX2Y_pcms_lock_check(card); - if (0 == err) + 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; - usX2Y_pcms_unlock(card); + mutex_unlock(&usX2Y(card)->pcm_mutex); return err; } @@ -697,7 +666,7 @@ static int snd_usX2Y_hwdep_pcm_vm_fault(struct vm_area_struct *area, } -static struct vm_operations_struct snd_usX2Y_hwdep_pcm_vm_ops = { +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, @@ -722,7 +691,7 @@ static int snd_usX2Y_hwdep_pcm_mmap(struct snd_hwdep * hw, struct file *filp, st return -ENODEV; } area->vm_ops = &snd_usX2Y_hwdep_pcm_vm_ops; - area->vm_flags |= VM_RESERVED | VM_DONTEXPAND; + area->vm_flags |= VM_DONTEXPAND | VM_DONTDUMP; area->vm_private_data = hw->private_data; return 0; } @@ -741,7 +710,7 @@ 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)->chip.dev; + struct usb_device *dev = usX2Y(card)->dev; if (1 != nr_of_packs()) return 0; diff --git a/sound/usb/usx2y/usx2yhwdeppcm.h b/sound/usb/usx2y/usx2yhwdeppcm.h index c3382fdc386..9c4fb84b2aa 100644 --- a/sound/usb/usx2y/usx2yhwdeppcm.h +++ b/sound/usb/usx2y/usx2yhwdeppcm.h @@ -18,3 +18,5 @@ struct snd_usX2Y_hwdep_pcm_shm { volatile unsigned captured_iso_frames; int capture_iso_start; }; + +int usX2Y_hwdep_pcm_new(struct snd_card *card); |
