From c6d43ba816d1cf1d125bfbfc938f2a28a87facf9 Mon Sep 17 00:00:00 2001 From: Torsten Schenk Date: Mon, 24 Jan 2011 18:45:30 +0100 Subject: ALSA: usb/6fire - Driver for TerraTec DMX 6Fire USB What is working: Everything except SPDIF - Hardware Master volume - PCM 44-192kHz@24 bits, 6 channels out, 4 channels in (analog) - MIDI in/out - firmware loading after cold start - phono/line switching Signed-off-by: Torsten Schenk Signed-off-by: Takashi Iwai --- sound/usb/6fire/Makefile | 3 + sound/usb/6fire/chip.c | 232 +++++++++++++++ sound/usb/6fire/chip.h | 32 +++ sound/usb/6fire/comm.c | 176 ++++++++++++ sound/usb/6fire/comm.h | 44 +++ sound/usb/6fire/common.h | 30 ++ sound/usb/6fire/control.c | 275 ++++++++++++++++++ sound/usb/6fire/control.h | 37 +++ sound/usb/6fire/firmware.c | 426 ++++++++++++++++++++++++++++ sound/usb/6fire/firmware.h | 27 ++ sound/usb/6fire/midi.c | 203 +++++++++++++ sound/usb/6fire/midi.h | 46 +++ sound/usb/6fire/pcm.c | 689 +++++++++++++++++++++++++++++++++++++++++++++ sound/usb/6fire/pcm.h | 76 +++++ sound/usb/Kconfig | 16 ++ sound/usb/Makefile | 2 +- 16 files changed, 2313 insertions(+), 1 deletion(-) create mode 100644 sound/usb/6fire/Makefile create mode 100644 sound/usb/6fire/chip.c create mode 100644 sound/usb/6fire/chip.h create mode 100644 sound/usb/6fire/comm.c create mode 100644 sound/usb/6fire/comm.h create mode 100644 sound/usb/6fire/common.h create mode 100644 sound/usb/6fire/control.c create mode 100644 sound/usb/6fire/control.h create mode 100644 sound/usb/6fire/firmware.c create mode 100644 sound/usb/6fire/firmware.h create mode 100644 sound/usb/6fire/midi.c create mode 100644 sound/usb/6fire/midi.h create mode 100644 sound/usb/6fire/pcm.c create mode 100644 sound/usb/6fire/pcm.h (limited to 'sound/usb') diff --git a/sound/usb/6fire/Makefile b/sound/usb/6fire/Makefile new file mode 100644 index 0000000..dfce6ec --- /dev/null +++ b/sound/usb/6fire/Makefile @@ -0,0 +1,3 @@ +snd-usb-6fire-objs += chip.o comm.o midi.o control.o firmware.o pcm.o +obj-$(CONFIG_SND_USB_6FIRE) += snd-usb-6fire.o + diff --git a/sound/usb/6fire/chip.c b/sound/usb/6fire/chip.c new file mode 100644 index 0000000..c7dca7b --- /dev/null +++ b/sound/usb/6fire/chip.c @@ -0,0 +1,232 @@ +/* + * Linux driver for TerraTec DMX 6Fire USB + * + * Main routines and module definitions. + * + * Author: Torsten Schenk + * Created: Jan 01, 2011 + * Version: 0.3.0 + * 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 +#include +#include +#include +#include +#include + +MODULE_AUTHOR("Torsten Schenk "); +MODULE_DESCRIPTION("TerraTec DMX 6Fire USB audio driver, version 0.3.0"); +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 int 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 __devinit 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 (regidx < 0) + regidx = i; + } + if (regidx < 0) { + mutex_unlock(®ister_mutex); + snd_printk(KERN_ERR PREFIX "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) { + snd_printk(KERN_ERR PREFIX "can't set first interface.\n"); + return -EIO; + } + ret = snd_card_create(index[regidx], id[regidx], THIS_MODULE, + sizeof(struct sfire_chip), &card); + if (ret < 0) { + snd_printk(KERN_ERR PREFIX "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); + snd_card_set_dev(card, &intf->dev); + + 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) { + snd_printk(KERN_ERR PREFIX "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 driver = { + .name = "snd-usb-6fire", + .probe = usb6fire_chip_probe, + .disconnect = usb6fire_chip_disconnect, + .id_table = device_table, +}; + +static int __init usb6fire_chip_init(void) +{ + return usb_register(&driver); +} + +static void __exit usb6fire_chip_cleanup(void) +{ + usb_deregister(&driver); +} + +module_init(usb6fire_chip_init); +module_exit(usb6fire_chip_cleanup); diff --git a/sound/usb/6fire/chip.h b/sound/usb/6fire/chip.h new file mode 100644 index 0000000..d11e5cb --- /dev/null +++ b/sound/usb/6fire/chip.h @@ -0,0 +1,32 @@ +/* + * Linux driver for TerraTec DMX 6Fire USB + * + * Author: Torsten Schenk + * Created: Jan 01, 2011 + * Version: 0.3.0 + * Copyright: (C) Torsten Schenk + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ +#ifndef USB6FIRE_CHIP_H +#define USB6FIRE_CHIP_H + +#include "common.h" + +struct sfire_chip { + struct usb_device *dev; + struct snd_card *card; + int intf_count; /* number of registered interfaces */ + int regidx; /* index in module parameter arrays */ + bool shutdown; + + struct midi_runtime *midi; + struct pcm_runtime *pcm; + struct control_runtime *control; + struct comm_runtime *comm; +}; +#endif /* USB6FIRE_CHIP_H */ + diff --git a/sound/usb/6fire/comm.c b/sound/usb/6fire/comm.c new file mode 100644 index 0000000..c994daa --- /dev/null +++ b/sound/usb/6fire/comm.c @@ -0,0 +1,176 @@ +/* + * Linux driver for TerraTec DMX 6Fire USB + * + * Device communications + * + * Author: Torsten Schenk + * Created: Jan 01, 2011 + * Version: 0.3.0 + * 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) + snd_printk(KERN_WARNING PREFIX + "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[13]; /* 13: maximum length of message */ + + usb6fire_comm_init_buffer(buffer, 0x00, request, reg, value, 0x00); + return usb6fire_comm_send_buffer(buffer, rt->chip->dev); +} + +static int usb6fire_comm_write16(struct comm_runtime *rt, u8 request, + u8 reg, u8 vl, u8 vh) +{ + u8 buffer[13]; /* 13: maximum length of message */ + + usb6fire_comm_init_buffer(buffer, 0x00, request, reg, vl, vh); + return usb6fire_comm_send_buffer(buffer, rt->chip->dev); +} + +int __devinit usb6fire_comm_init(struct sfire_chip *chip) +{ + struct comm_runtime *rt = kzalloc(sizeof(struct comm_runtime), + GFP_KERNEL); + struct urb *urb = &rt->receiver; + int ret; + + if (!rt) + return -ENOMEM; + + 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); + snd_printk(KERN_ERR PREFIX "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) +{ + kfree(chip->comm); + chip->comm = NULL; +} diff --git a/sound/usb/6fire/comm.h b/sound/usb/6fire/comm.h new file mode 100644 index 0000000..edc5dc8 --- /dev/null +++ b/sound/usb/6fire/comm.h @@ -0,0 +1,44 @@ +/* + * Linux driver for TerraTec DMX 6Fire USB + * + * Author: Torsten Schenk + * Created: Jan 01, 2011 + * Version: 0.3.0 + * 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[COMM_RECEIVER_BUFSIZE]; + + 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 __devinit usb6fire_comm_init(struct sfire_chip *chip); +void usb6fire_comm_abort(struct sfire_chip *chip); +void usb6fire_comm_destroy(struct sfire_chip *chip); +#endif /* USB6FIRE_COMM_H */ + diff --git a/sound/usb/6fire/common.h b/sound/usb/6fire/common.h new file mode 100644 index 0000000..7dbeb4a --- /dev/null +++ b/sound/usb/6fire/common.h @@ -0,0 +1,30 @@ +/* + * Linux driver for TerraTec DMX 6Fire USB + * + * Author: Torsten Schenk + * Created: Jan 01, 2011 + * Version: 0.3.0 + * 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 +#include +#include + +#define PREFIX "6fire: " + +struct sfire_chip; +struct midi_runtime; +struct pcm_runtime; +struct control_runtime; +struct comm_runtime; +#endif /* USB6FIRE_COMMON_H */ + diff --git a/sound/usb/6fire/control.c b/sound/usb/6fire/control.c new file mode 100644 index 0000000..2484635 --- /dev/null +++ b/sound/usb/6fire/control.c @@ -0,0 +1,275 @@ +/* + * Linux driver for TerraTec DMX 6Fire USB + * + * Mixer control + * + * Author: Torsten Schenk + * Created: Jan 01, 2011 + * Version: 0.3.0 + * 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 +#include + +#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" }; + +/* + * calculated with $value\[i\] = 128 \cdot sqrt[3]{\frac{i}{128}}$ + * this is done because the linear values cause rapid degredation + * of volume in the uppermost region. + */ +static const u8 log_volume_table[128] = { + 0x00, 0x19, 0x20, 0x24, 0x28, 0x2b, 0x2e, 0x30, 0x32, 0x34, + 0x36, 0x38, 0x3a, 0x3b, 0x3d, 0x3e, 0x40, 0x41, 0x42, 0x43, + 0x44, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, + 0x4e, 0x4f, 0x50, 0x51, 0x52, 0x53, 0x53, 0x54, 0x55, 0x56, + 0x56, 0x57, 0x58, 0x58, 0x59, 0x5a, 0x5b, 0x5b, 0x5c, 0x5c, + 0x5d, 0x5e, 0x5e, 0x5f, 0x60, 0x60, 0x61, 0x61, 0x62, 0x62, + 0x63, 0x63, 0x64, 0x65, 0x65, 0x66, 0x66, 0x67, 0x67, 0x68, + 0x68, 0x69, 0x69, 0x6a, 0x6a, 0x6b, 0x6b, 0x6c, 0x6c, 0x6c, + 0x6d, 0x6d, 0x6e, 0x6e, 0x6f, 0x6f, 0x70, 0x70, 0x70, 0x71, + 0x71, 0x72, 0x72, 0x73, 0x73, 0x73, 0x74, 0x74, 0x75, 0x75, + 0x75, 0x76, 0x76, 0x77, 0x77, 0x77, 0x78, 0x78, 0x78, 0x79, + 0x79, 0x7a, 0x7a, 0x7a, 0x7b, 0x7b, 0x7b, 0x7c, 0x7c, 0x7c, + 0x7d, 0x7d, 0x7d, 0x7e, 0x7e, 0x7e, 0x7f, 0x7f }; + +/* + * 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, 0x78 }, { 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 void usb6fire_control_master_vol_update(struct control_runtime *rt) +{ + struct comm_runtime *comm_rt = rt->chip->comm; + if (comm_rt) { + /* set volume */ + comm_rt->write8(comm_rt, 0x12, 0x0f, 0x7f - + log_volume_table[rt->master_vol]); + /* unmute */ + comm_rt->write8(comm_rt, 0x12, 0x0e, 0x00); + } +} + +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_master_vol_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 127; + return 0; +} + +static int usb6fire_control_master_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->master_vol != ucontrol->value.integer.value[0]) { + rt->master_vol = ucontrol->value.integer.value[0]; + usb6fire_control_master_vol_update(rt); + changed = 1; + } + return changed; +} + +static int usb6fire_control_master_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->master_vol; + 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 struct __devinitdata snd_kcontrol_new elements[] = { + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Master Playback Volume", + .index = 0, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .info = usb6fire_control_master_vol_info, + .get = usb6fire_control_master_vol_get, + .put = usb6fire_control_master_vol_put + }, + { + .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 + }, + {} +}; + +int __devinit 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; + + 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_master_vol_update(rt); + + i = 0; + while (elements[i].name) { + ret = snd_ctl_add(chip->card, snd_ctl_new1(&elements[i], rt)); + if (ret < 0) { + kfree(rt); + snd_printk(KERN_ERR PREFIX "cannot add control.\n"); + return ret; + } + i++; + } + + chip->control = rt; + return 0; +} + +void usb6fire_control_abort(struct sfire_chip *chip) +{} + +void usb6fire_control_destroy(struct sfire_chip *chip) +{ + kfree(chip->control); + chip->control = NULL; +} diff --git a/sound/usb/6fire/control.h b/sound/usb/6fire/control.h new file mode 100644 index 0000000..b534c77 --- /dev/null +++ b/sound/usb/6fire/control.h @@ -0,0 +1,37 @@ +/* + * Linux driver for TerraTec DMX 6Fire USB + * + * Author: Torsten Schenk + * Created: Jan 01, 2011 + * Version: 0.3.0 + * 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 +}; + +struct control_runtime { + struct sfire_chip *chip; + + struct snd_kcontrol *element[CONTROL_MAX_ELEMENTS]; + bool opt_coax_switch; + bool line_phono_switch; + u8 master_vol; +}; + +int __devinit usb6fire_control_init(struct sfire_chip *chip); +void usb6fire_control_abort(struct sfire_chip *chip); +void usb6fire_control_destroy(struct sfire_chip *chip); +#endif /* USB6FIRE_CONTROL_H */ + diff --git a/sound/usb/6fire/firmware.c b/sound/usb/6fire/firmware.c new file mode 100644 index 0000000..9081a54 --- /dev/null +++ b/sound/usb/6fire/firmware.c @@ -0,0 +1,426 @@ +/* + * Linux driver for TerraTec DMX 6Fire USB + * + * Firmware loader + * + * Currently not working for all devices. To be able to use the device + * in linux, it is also possible to let the windows driver upload the firmware. + * For that, start the computer in windows and reboot. + * As long as the device is connected to the power supply, no firmware reload + * needs to be performed. + * + * Author: Torsten Schenk + * Created: Jan 01, 2011 + * Version: 0.3.0 + * 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 + +#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 +}; + +static const u8 BIT_REVERSE_TABLE[256] = { + 0x00, 0x80, 0x40, 0xc0, 0x20, 0xa0, 0x60, 0xe0, 0x10, 0x90, 0x50, + 0xd0, 0x30, 0xb0, 0x70, 0xf0, 0x08, 0x88, 0x48, 0xc8, 0x28, 0xa8, + 0x68, 0xe8, 0x18, 0x98, 0x58, 0xd8, 0x38, 0xb8, 0x78, 0xf8, 0x04, + 0x84, 0x44, 0xc4, 0x24, 0xa4, 0x64, 0xe4, 0x14, 0x94, 0x54, 0xd4, + 0x34, 0xb4, 0x74, 0xf4, 0x0c, 0x8c, 0x4c, 0xcc, 0x2c, 0xac, 0x6c, + 0xec, 0x1c, 0x9c, 0x5c, 0xdc, 0x3c, 0xbc, 0x7c, 0xfc, 0x02, 0x82, + 0x42, 0xc2, 0x22, 0xa2, 0x62, 0xe2, 0x12, 0x92, 0x52, 0xd2, 0x32, + 0xb2, 0x72, 0xf2, 0x0a, 0x8a, 0x4a, 0xca, 0x2a, 0xaa, 0x6a, 0xea, + 0x1a, 0x9a, 0x5a, 0xda, 0x3a, 0xba, 0x7a, 0xfa, 0x06, 0x86, 0x46, + 0xc6, 0x26, 0xa6, 0x66, 0xe6, 0x16, 0x96, 0x56, 0xd6, 0x36, 0xb6, + 0x76, 0xf6, 0x0e, 0x8e, 0x4e, 0xce, 0x2e, 0xae, 0x6e, 0xee, 0x1e, + 0x9e, 0x5e, 0xde, 0x3e, 0xbe, 0x7e, 0xfe, 0x01, 0x81, 0x41, 0xc1, + 0x21, 0xa1, 0x61, 0xe1, 0x11, 0x91, 0x51, 0xd1, 0x31, 0xb1, 0x71, + 0xf1, 0x09, 0x89, 0x49, 0xc9, 0x29, 0xa9, 0x69, 0xe9, 0x19, 0x99, + 0x59, 0xd9, 0x39, 0xb9, 0x79, 0xf9, 0x05, 0x85, 0x45, 0xc5, 0x25, + 0xa5, 0x65, 0xe5, 0x15, 0x95, 0x55, 0xd5, 0x35, 0xb5, 0x75, 0xf5, + 0x0d, 0x8d, 0x4d, 0xcd, 0x2d, 0xad, 0x6d, 0xed, 0x1d, 0x9d, 0x5d, + 0xdd, 0x3d, 0xbd, 0x7d, 0xfd, 0x03, 0x83, 0x43, 0xc3, 0x23, 0xa3, + 0x63, 0xe3, 0x13, 0x93, 0x53, 0xd3, 0x33, 0xb3, 0x73, 0xf3, 0x0b, + 0x8b, 0x4b, 0xcb, 0x2b, 0xab, 0x6b, 0xeb, 0x1b, 0x9b, 0x5b, 0xdb, + 0x3b, 0xbb, 0x7b, 0xfb, 0x07, 0x87, 0x47, 0xc7, 0x27, 0xa7, 0x67, + 0xe7, 0x17, 0x97, 0x57, 0xd7, 0x37, 0xb7, 0x77, 0xf7, 0x0f, 0x8f, + 0x4f, 0xcf, 0x2f, 0xaf, 0x6f, 0xef, 0x1f, 0x9f, 0x5f, 0xdf, 0x3f, + 0xbf, 0x7f, 0xff }; + +/* + * 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) */ +}; + +struct ihex_record { + u16 address; + u8 len; + u8 data[256]; + char error; /* true if an error occured 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_nibble(const u8 n) +{ + if (n >= '0' && n <= '9') + return n - '0'; + else if (n >= 'A' && n <= 'F') + return n - ('A' - 10); + else if (n >= 'a' && n <= 'f') + return n - ('a' - 10); + return 0; +} + +static u8 usb6fire_fw_ihex_hex(const u8 *data, u8 *crc) +{ + u8 val = (usb6fire_fw_ihex_nibble(data[0]) << 4) | + usb6fire_fw_ihex_nibble(data[1]); + *crc += val; + return val; +} + +/* + * returns true if record is available, false otherwise. + * iff an error occured, 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 = 0; + 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); + snd_printk(KERN_ERR PREFIX "error requesting ezusb " + "firmware %s.\n", fwname); + return ret; + } + ret = usb6fire_fw_ihex_init(fw, rec); + if (ret < 0) { + kfree(rec); + snd_printk(KERN_ERR PREFIX "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); + snd_printk(KERN_ERR PREFIX "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); + snd_printk(KERN_ERR PREFIX "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) { + snd_printk(KERN_ERR PREFIX "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) { + release_firmware(fw); + snd_printk(KERN_ERR PREFIX "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) { + snd_printk(KERN_ERR PREFIX "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); + snd_printk(KERN_ERR PREFIX "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] = BIT_REVERSE_TABLE[(u8) *c]; + + ret = usb6fire_fw_fpga_write(device, buffer, i); + if (ret < 0) { + release_firmware(fw); + kfree(buffer); + snd_printk(KERN_ERR PREFIX "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) { + snd_printk(KERN_ERR PREFIX "unable to upload fpga firmware: " + "end urb.\n"); + return ret; + } + return 0; +} + +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) { + snd_printk(KERN_ERR PREFIX "unable to receive device " + "firmware state.\n"); + return ret; + } + if (buffer[0] != 0xeb || buffer[1] != 0xaa || buffer[2] != 0x55 + || buffer[4] != 0x03 || buffer[5] != 0x01 || buffer[7] + != 0x00) { + snd_printk(KERN_ERR PREFIX "unknown device firmware state " + "received from device: "); + for (i = 0; i < 8; i++) + snd_printk("%02x ", buffer[i]); + snd_printk("\n"); + return -EIO; + } + /* do we need fpga loader ezusb firmware? */ + if (buffer[3] == 0x01 && buffer[6] == 0x19) { + 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 && buffer[6] == 0x0b) { + 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 && buffer[6] == 0x0b) + return 0; + /* unknown data? */ + else { + snd_printk(KERN_ERR PREFIX "unknown device firmware state " + "received from device: "); + for (i = 0; i < 8; i++) + snd_printk("%02x ", buffer[i]); + snd_printk("\n"); + return -EIO; + } + return 0; +} + diff --git a/sound/usb/6fire/firmware.h b/sound/usb/6fire/firmware.h new file mode 100644 index 0000000..0085698 --- /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 __devinit usb6fire_fw_init(struct usb_interface *intf); +#endif /* USB6FIRE_FIRMWARE_H */ + diff --git a/sound/usb/6fire/midi.c b/sound/usb/6fire/midi.c new file mode 100644 index 0000000..13f4509 --- /dev/null +++ b/sound/usb/6fire/midi.c @@ -0,0 +1,203 @@ +/* + * Linux driver for TerraTec DMX 6Fire USB + * + * Rawmidi driver + * + * Author: Torsten Schenk + * Created: Jan 01, 2011 + * Version: 0.3.0 + * 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 + +#include "midi.h" +#include "chip.h" +#include "comm.h" + +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) + snd_printk(KERN_ERR PREFIX "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) + snd_printk(KERN_ERR PREFIX "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 __devinit 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->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); + snd_printk(KERN_ERR PREFIX "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) +{ + kfree(chip->midi); + chip->midi = NULL; +} diff --git a/sound/usb/6fire/midi.h b/sound/usb/6fire/midi.h new file mode 100644 index 0000000..97a7bf6 --- /dev/null +++ b/sound/usb/6fire/midi.h @@ -0,0 +1,46 @@ +/* + * Linux driver for TerraTec DMX 6Fire USB + * + * Author: Torsten Schenk + * Created: Jan 01, 2011 + * Version: 0.3.0 + * 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" + +enum { + MIDI_BUFSIZE = 64 +}; + +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[MIDI_BUFSIZE]; + int buffer_offset; + + void (*in_received)(struct midi_runtime *rt, u8 *data, int length); +}; + +int __devinit usb6fire_midi_init(struct sfire_chip *chip); +void usb6fire_midi_abort(struct sfire_chip *chip); +void usb6fire_midi_destroy(struct sfire_chip *chip); +#endif /* USB6FIRE_MIDI_H */ + diff --git a/sound/usb/6fire/pcm.c b/sound/usb/6fire/pcm.c new file mode 100644 index 0000000..705c88e --- /dev/null +++ b/sound/usb/6fire/pcm.c @@ -0,0 +1,689 @@ +/* + * Linux driver for TerraTec DMX 6Fire USB + * + * PCM driver + * + * Author: Torsten Schenk + * Created: Jan 01, 2011 + * Version: 0.3.0 + * 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" + +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 */ +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_altsetting[] = { 1, 1, 2, 2, 3, 3 }; +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 }; + +/* 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}; + +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 +}; + +enum { /* pcm sample rates (also index into RATES_XXX[]) */ + RATE_44KHZ, + RATE_48KHZ, + RATE_88KHZ, + RATE_96KHZ, + RATE_176KHZ, + RATE_192KHZ +}; + +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, + + .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 usb_device *device = rt->chip->dev; + struct comm_runtime *comm_rt = rt->chip->comm; + + if (rt->rate >= ARRAY_SIZE(rates)) + return -EINVAL; + /* disable streaming */ + ret = comm_rt->write16(comm_rt, 0x02, 0x00, 0x00, 0x00); + if (ret < 0) { + snd_printk(KERN_ERR PREFIX "error stopping streaming while " + "setting samplerate %d.\n", rates[rt->rate]); + return ret; + } + + ret = usb_set_interface(device, 1, rates_altsetting[rt->rate]); + if (ret < 0) { + snd_printk(KERN_ERR PREFIX "error setting interface " + "altsetting %d for samplerate %d.\n", + rates_altsetting[rt->rate], rates[rt->rate]); + return ret; + } + + /* set soundcard clock */ + ret = comm_rt->write16(comm_rt, 0x02, 0x01, rates_6fire_vl[rt->rate], + rates_6fire_vh[rt->rate]); + if (ret < 0) { + snd_printk(KERN_ERR PREFIX "error setting samplerate %d.\n", + rates[rt->rate]); + return ret; + } + + /* enable analog inputs and outputs + * (one bit per stereo-channel) */ + ret = comm_rt->write16(comm_rt, 0x02, 0x02, + (1 << (OUT_N_CHANNELS / 2)) - 1, + (1 << (IN_N_CHANNELS / 2)) - 1); + if (ret < 0) { + snd_printk(KERN_ERR PREFIX "error initializing analog channels " + "while setting samplerate %d.\n", + rates[rt->rate]); + return ret; + } + /* disable digital inputs and outputs */ + ret = comm_rt->write16(comm_rt, 0x02, 0x03, 0x00, 0x00); + if (ret < 0) { + snd_printk(KERN_ERR PREFIX "error initializing digital " + "channels while setting samplerate %d.\n", + rates[rt->rate]); + return ret; + } + + ret = comm_rt->write16(comm_rt, 0x02, 0x00, 0x00, 0x01); + if (ret < 0) { + snd_printk(KERN_ERR PREFIX "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; + snd_printk(KERN_ERR PREFIX "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; + + if (rt->stream_state != STREAM_DISABLED) { + for (i = 0; i < PCM_N_URBS; i++) { + usb_kill_urb(&rt->in_urbs[i].instance); + usb_kill_urb(&rt->out_urbs[i].instance); + } + 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 = (u32 *) urb->buffer; + 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; + + src = (u32 *) (urb->buffer + total_length); + 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 = (u32 *) urb->buffer; + 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].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) { + snd_printk(KERN_ERR PREFIX "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 >= 0) + 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 >= 0) + 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); + snd_printk(KERN_ERR PREFIX "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 = -1; + } + } + 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_malloc_pages(alsa_sub, + params_buffer_bytes(hw_params)); +} + +static int usb6fire_pcm_hw_free(struct snd_pcm_substream *alsa_sub) +{ + return snd_pcm_lib_free_pages(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 i; + 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) { + rt->rate = -1; + for (i = 0; i < ARRAY_SIZE(rates); i++) + if (alsa_rt->rate == rates[i]) { + rt->rate = i; + break; + } + if (rt->rate == -1) { + mutex_unlock(&rt->stream_mutex); + snd_printk("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); + snd_printk(KERN_ERR PREFIX + "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_STATE_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, +}; + +static void __devinit 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.transfer_flags = URB_ISO_ASAP; + urb->instance.complete = handler; + urb->instance.context = urb; + urb->instance.number_of_packets = PCM_N_PACKETS_PER_URB; +} + +int __devinit 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; + + rt->chip = chip; + rt->stream_state = STREAM_DISABLED; + rt->rate = -1; + 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) { + kfree(rt); + snd_printk(KERN_ERR PREFIX "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); + + ret = snd_pcm_lib_preallocate_pages_for_all(pcm, + SNDRV_DMA_TYPE_CONTINUOUS, + snd_dma_continuous_data(GFP_KERNEL), + MAX_BUFSIZE, MAX_BUFSIZE); + if (ret) { + kfree(rt); + snd_printk(KERN_ERR PREFIX + "error preallocating pcm buffers.\n"); + return ret; + } + rt->instance = pcm; + + chip->pcm = rt; + return 0; +} + +void usb6fire_pcm_abort(struct sfire_chip *chip) +{ + struct pcm_runtime *rt = chip->pcm; + int i; + + if (rt) { + rt->panic = true; + + if (rt->playback.instance) + snd_pcm_stop(rt->playback.instance, + SNDRV_PCM_STATE_XRUN); + if (rt->capture.instance) + snd_pcm_stop(rt->capture.instance, + SNDRV_PCM_STATE_XRUN); + + 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) +{ + kfree(chip->pcm); + chip->pcm = NULL; +} diff --git a/sound/usb/6fire/pcm.h b/sound/usb/6fire/pcm.h new file mode 100644 index 0000000..2bee813 --- /dev/null +++ b/sound/usb/6fire/pcm.h @@ -0,0 +1,76 @@ +/* + * Linux driver for TerraTec DMX 6Fire USB + * + * Author: Torsten Schenk + * Created: Jan 01, 2011 + * Version: 0.3.0 + * 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 +#include + +#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[PCM_N_PACKETS_PER_URB * PCM_MAX_PACKET_SIZE]; + + 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 __devinit 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 112984f..0fefdb4 100644 --- a/sound/usb/Kconfig +++ b/sound/usb/Kconfig @@ -97,5 +97,21 @@ config SND_USB_US122L 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" + depends on EXPERIMENTAL + select FW_LOADER + select SND_RAWMIDI + select SND_PCM + 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. This driver currently does not support + firmware loading for all devices. If you own such a device, + you could start windows and let the windows driver upload + the firmware. As long as you do not unplug your device from power, + it should be usable. + endif # SND_USB diff --git a/sound/usb/Makefile b/sound/usb/Makefile index 1e362bf..cf9ed66 100644 --- a/sound/usb/Makefile +++ b/sound/usb/Makefile @@ -23,4 +23,4 @@ obj-$(CONFIG_SND_USB_UA101) += snd-usbmidi-lib.o obj-$(CONFIG_SND_USB_USX2Y) += snd-usbmidi-lib.o obj-$(CONFIG_SND_USB_US122L) += snd-usbmidi-lib.o -obj-$(CONFIG_SND) += misc/ usx2y/ caiaq/ +obj-$(CONFIG_SND) += misc/ usx2y/ caiaq/ 6fire/ -- cgit v1.1 From 8121d91c02a1be4884821599472acf405efdc3de Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Mon, 7 Feb 2011 20:25:19 +0300 Subject: ALSA: USB: 6fire: signedness bug in usb6fire_pcm_prepare() rt->rate is an unsigned char so it's never equal to -1. It's not a huge problem because the invalid rate is caught inside the call to usb6fire_pcm_set_rate() which returns -EINVAL. But if we fix the test then it prints out the correct error message so that's good. Signed-off-by: Dan Carpenter Signed-off-by: Takashi Iwai --- sound/usb/6fire/pcm.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'sound/usb') diff --git a/sound/usb/6fire/pcm.c b/sound/usb/6fire/pcm.c index 705c88e..ba62c74 100644 --- a/sound/usb/6fire/pcm.c +++ b/sound/usb/6fire/pcm.c @@ -493,13 +493,12 @@ static int usb6fire_pcm_prepare(struct snd_pcm_substream *alsa_sub) sub->period_off = 0; if (rt->stream_state == STREAM_DISABLED) { - rt->rate = -1; for (i = 0; i < ARRAY_SIZE(rates); i++) if (alsa_rt->rate == rates[i]) { rt->rate = i; break; } - if (rt->rate == -1) { + if (i == ARRAY_SIZE(rates)) { mutex_unlock(&rt->stream_mutex); snd_printk("invalid rate %d in prepare.\n", alsa_rt->rate); -- cgit v1.1 From df8d81a32fa0309d64726fc62d83cb70adc899e8 Mon Sep 17 00:00:00 2001 From: Daniel Mack Date: Wed, 1 Sep 2010 16:23:46 +0800 Subject: ALSA: snd-usb-caiaq: Add support for Traktor Audio 2 Signed-off-by: Daniel Mack Signed-off-by: Takashi Iwai --- sound/usb/Kconfig | 1 + sound/usb/caiaq/audio.c | 1 + sound/usb/caiaq/device.c | 6 ++++++ sound/usb/caiaq/device.h | 1 + 4 files changed, 9 insertions(+) (limited to 'sound/usb') diff --git a/sound/usb/Kconfig b/sound/usb/Kconfig index 0fefdb4..97724d8 100644 --- a/sound/usb/Kconfig +++ b/sound/usb/Kconfig @@ -62,6 +62,7 @@ config SND_USB_CAIAQ * 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 diff --git a/sound/usb/caiaq/audio.c b/sound/usb/caiaq/audio.c index 68b9747..e411cd3 100644 --- a/sound/usb/caiaq/audio.c +++ b/sound/usb/caiaq/audio.c @@ -805,6 +805,7 @@ int snd_usb_caiaq_audio_init(struct snd_usb_caiaqdev *dev) 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): dev->samplerates |= SNDRV_PCM_RATE_88200; break; } diff --git a/sound/usb/caiaq/device.c b/sound/usb/caiaq/device.c index 6480c32..45bc4a2 100644 --- a/sound/usb/caiaq/device.c +++ b/sound/usb/caiaq/device.c @@ -46,6 +46,7 @@ MODULE_SUPPORTED_DEVICE("{{Native Instruments, RigKontrol2}," "{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}" @@ -140,6 +141,11 @@ static struct usb_device_id snd_usb_id_table[] = { .idVendor = USB_VID_NATIVEINSTRUMENTS, .idProduct = USB_PID_TRAKTORKONTROLS4 }, + { + .match_flags = USB_DEVICE_ID_MATCH_DEVICE, + .idVendor = USB_VID_NATIVEINSTRUMENTS, + .idProduct = USB_PID_TRAKTORAUDIO2 + }, { /* terminator */ } }; diff --git a/sound/usb/caiaq/device.h b/sound/usb/caiaq/device.h index e3d8a3e..b2b3101 100644 --- a/sound/usb/caiaq/device.h +++ b/sound/usb/caiaq/device.h @@ -17,6 +17,7 @@ #define USB_PID_GUITARRIGMOBILE 0x0d8d #define USB_PID_TRAKTORKONTROLX1 0x2305 #define USB_PID_TRAKTORKONTROLS4 0xbaff +#define USB_PID_TRAKTORAUDIO2 0x041d #define EP1_BUFSIZE 64 #define EP4_BUFSIZE 512 -- cgit v1.1 From 54a8c500d5b80c83e0f14cbcfcfd4a84abff8a80 Mon Sep 17 00:00:00 2001 From: Daniel Mack Date: Fri, 11 Feb 2011 11:08:06 +0000 Subject: ALSA: usb-audio: add support for Native Instruments MK2 devices The MK2 generation of Native Instruments' sound cards are in fact compliant to the USB audio standard of version 2 and other approved USB standards. However, they come up as vendor-specific device when first connected but can be told to come up with a new set of descriptors upon their next enumeration. The interfaces announced by the new descriptors will be handled by the kernel's class drivers. This is done by issuing a vendor specific device request and sending the device to reset. There are also some vendor-specific USB requests for some mixer elements that can't be exported in a standard compliant way. The driver now supports them with quirks handling mechanisms. Signed-off-by: Daniel Mack Signed-off-by: Takashi Iwai --- sound/usb/mixer_quirks.c | 153 +++++++++++++++++++++++++++++++++++++++++++++++ sound/usb/quirks-table.h | 14 +++++ sound/usb/quirks.c | 33 ++++++++++ 3 files changed, 200 insertions(+) (limited to 'sound/usb') diff --git a/sound/usb/mixer_quirks.c b/sound/usb/mixer_quirks.c index 782f741..3fe612a 100644 --- a/sound/usb/mixer_quirks.c +++ b/sound/usb/mixer_quirks.c @@ -346,6 +346,141 @@ static int snd_xonar_u1_controls_create(struct usb_mixer_interface *mixer) 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 = usb_control_msg(dev, usb_rcvctrlpipe(dev, 0), bRequest, + USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_IN, + 0, cpu_to_le16(wIndex), + &tmp, sizeof(tmp), 1000); + + if (ret < 0) { + snd_printk(KERN_ERR + "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 = usb_control_msg(dev, usb_sndctrlpipe(dev, 0), bRequest, + USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_OUT, + cpu_to_le16(wValue), cpu_to_le16(wIndex), + NULL, 0, 1000); + + if (ret < 0) { + snd_printk(KERN_ERR + "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; +} + void snd_emuusb_set_samplerate(struct snd_usb_audio *chip, unsigned char samplerate_id) { @@ -391,6 +526,24 @@ int snd_usb_mixer_apply_create_quirk(struct usb_mixer_interface *mixer) return err; } + /* Traktor Audio 6 */ + if (mixer->chip->usb_id == USB_ID(0x17cc, 0x1011)) { + err = snd_nativeinstruments_create_mixer(mixer, + snd_nativeinstruments_ta6_mixers, + ARRAY_SIZE(snd_nativeinstruments_ta6_mixers)); + if (err < 0) + return err; + } + + /* Traktor Audio 10 */ + if (mixer->chip->usb_id == USB_ID(0x17cc, 0x1021)) { + err = snd_nativeinstruments_create_mixer(mixer, + snd_nativeinstruments_ta10_mixers, + ARRAY_SIZE(snd_nativeinstruments_ta10_mixers)); + if (err < 0) + return err; + } + return 0; } diff --git a/sound/usb/quirks-table.h b/sound/usb/quirks-table.h index 3599987..e1e245d 100644 --- a/sound/usb/quirks-table.h +++ b/sound/usb/quirks-table.h @@ -2283,6 +2283,20 @@ YAMAHA_DEVICE(0x7010, "UB99"), } }, +/* Native Instruments MK2 series */ +{ + /* 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, +}, + /* Miditech devices */ { USB_DEVICE(0x4752, 0x0011), diff --git a/sound/usb/quirks.c b/sound/usb/quirks.c index cf8bf08..e55bd1c 100644 --- a/sound/usb/quirks.c +++ b/sound/usb/quirks.c @@ -425,6 +425,34 @@ static int snd_usb_accessmusic_boot_quirk(struct usb_device *dev) } /* + * 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, + cpu_to_le16(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; +} + +/* * Setup quirks */ #define AUDIOPHILE_SET 0x01 /* if set, parse device_setup */ @@ -510,6 +538,11 @@ int snd_usb_apply_boot_quirk(struct usb_device *dev, if (id == USB_ID(0x133e, 0x0815)) return snd_usb_accessmusic_boot_quirk(dev); + /* Native Instruments Devices */ + if (id == USB_ID(0x17cc, 0x1010) || /* Traktor Audio 6 */ + id == USB_ID(0x17cc, 0x1020)) /* Traktor Audio 10 */ + return snd_usb_nativeinstruments_boot_quirk(dev); + return 0; } -- cgit v1.1 From 3347b26cab60e20534b9a46ceea70b9fb74297fb Mon Sep 17 00:00:00 2001 From: Daniel Mack Date: Fri, 11 Feb 2011 11:34:12 +0000 Subject: ALSA: usb-audio: reconstruct some dispatcher functions to use switch-case The number of cases has increased so use switch-case rather than if-statements. Signed-off-by: Daniel Mack Signed-off-by: Takashi Iwai --- sound/usb/mixer_quirks.c | 41 ++++++++++++++++++----------------------- sound/usb/quirks.c | 29 +++++++++++++++-------------- 2 files changed, 33 insertions(+), 37 deletions(-) (limited to 'sound/usb') diff --git a/sound/usb/mixer_quirks.c b/sound/usb/mixer_quirks.c index 3fe612a..73dcc82 100644 --- a/sound/usb/mixer_quirks.c +++ b/sound/usb/mixer_quirks.c @@ -502,49 +502,44 @@ void snd_emuusb_set_samplerate(struct snd_usb_audio *chip, int snd_usb_mixer_apply_create_quirk(struct usb_mixer_interface *mixer) { - int err; + int err = 0; struct snd_info_entry *entry; if ((err = snd_usb_soundblaster_remote_init(mixer)) < 0) return err; - if (mixer->chip->usb_id == USB_ID(0x041e, 0x3020) || - mixer->chip->usb_id == USB_ID(0x041e, 0x3040) || - mixer->chip->usb_id == USB_ID(0x041e, 0x3042) || - mixer->chip->usb_id == USB_ID(0x041e, 0x3048)) { - if ((err = snd_audigy2nx_controls_create(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, 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; - if (mixer->chip->usb_id == USB_ID(0x0b05, 0x1739) || - mixer->chip->usb_id == USB_ID(0x0b05, 0x1743)) { + case USB_ID(0x0b05, 0x1739): + case USB_ID(0x0b05, 0x1743): err = snd_xonar_u1_controls_create(mixer); - if (err < 0) - return err; - } + break; - /* Traktor Audio 6 */ - if (mixer->chip->usb_id == USB_ID(0x17cc, 0x1011)) { + case USB_ID(0x17cc, 0x1011): /* Traktor Audio 6 */ err = snd_nativeinstruments_create_mixer(mixer, snd_nativeinstruments_ta6_mixers, ARRAY_SIZE(snd_nativeinstruments_ta6_mixers)); - if (err < 0) - return err; - } + break; - /* Traktor Audio 10 */ - if (mixer->chip->usb_id == USB_ID(0x17cc, 0x1021)) { + case USB_ID(0x17cc, 0x1021): /* Traktor Audio 10 */ err = snd_nativeinstruments_create_mixer(mixer, snd_nativeinstruments_ta10_mixers, ARRAY_SIZE(snd_nativeinstruments_ta10_mixers)); - if (err < 0) - return err; + break; } - return 0; + return err; } void snd_usb_mixer_rc_memory_change(struct usb_mixer_interface *mixer, diff --git a/sound/usb/quirks.c b/sound/usb/quirks.c index e55bd1c..ca860e6 100644 --- a/sound/usb/quirks.c +++ b/sound/usb/quirks.c @@ -517,31 +517,32 @@ int snd_usb_apply_boot_quirk(struct usb_device *dev, u32 id = USB_ID(le16_to_cpu(dev->descriptor.idVendor), le16_to_cpu(dev->descriptor.idProduct)); - /* SB Extigy needs special boot-up sequence */ - /* if more models come, this will go to the quirk list. */ - if (id == USB_ID(0x041e, 0x3000)) + 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); - /* SB Audigy 2 NX needs its own boot-up magic, too */ - if (id == USB_ID(0x041e, 0x3020)) + case USB_ID(0x041e, 0x3020): + /* SB Audigy 2 NX needs its own boot-up magic, too */ return snd_usb_audigy2nx_boot_quirk(dev); - /* C-Media CM106 / Turtle Beach Audio Advantage Roadie */ - if (id == USB_ID(0x10f5, 0x0200)) + case USB_ID(0x10f5, 0x0200): + /* C-Media CM106 / Turtle Beach Audio Advantage Roadie */ return snd_usb_cm106_boot_quirk(dev); - /* C-Media CM6206 / CM106-Like Sound Device */ - if (id == USB_ID(0x0d8c, 0x0102)) + case USB_ID(0x0d8c, 0x0102): + /* C-Media CM6206 / CM106-Like Sound Device */ return snd_usb_cm6206_boot_quirk(dev); - /* Access Music VirusTI Desktop */ - if (id == USB_ID(0x133e, 0x0815)) + case USB_ID(0x133e, 0x0815): + /* Access Music VirusTI Desktop */ return snd_usb_accessmusic_boot_quirk(dev); - /* Native Instruments Devices */ - if (id == USB_ID(0x17cc, 0x1010) || /* Traktor Audio 6 */ - id == USB_ID(0x17cc, 0x1020)) /* Traktor Audio 10 */ + case USB_ID(0x17cc, 0x1010): /* Traktor Audio 6 */ + case USB_ID(0x17cc, 0x1020): /* Traktor Audio 10 */ return snd_usb_nativeinstruments_boot_quirk(dev); + } return 0; } -- cgit v1.1 From 80acefff3bc7bd53d46c16c683ab66c0ece20810 Mon Sep 17 00:00:00 2001 From: Alexey Fisher Date: Thu, 10 Mar 2011 14:53:38 +0100 Subject: ALSA: usb-audio - Add volume range check and warn if it too big Signed-off-by: Alexey Fisher Signed-off-by: Takashi Iwai --- sound/usb/mixer.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) (limited to 'sound/usb') diff --git a/sound/usb/mixer.c b/sound/usb/mixer.c index 7df89b3..3e3116c 100644 --- a/sound/usb/mixer.c +++ b/sound/usb/mixer.c @@ -987,6 +987,7 @@ static void build_feature_ctl(struct mixer_build *state, void *raw_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 */ @@ -1136,6 +1137,21 @@ static void build_feature_ctl(struct mixer_build *state, void *raw_desc, } + 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) { + snd_printk(KERN_WARNING "usb_audio: Warning! Unlikely big " + "volume range (=%u), cval->res is probably wrong.", + range); + snd_printk(KERN_WARNING "usb_audio: [%d] FU [%s] ch = %d, " + "val = %d/%d/%d", cval->id, + kctl->id.name, cval->channels, + cval->min, cval->max, cval->res); + } + 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); -- cgit v1.1 From bc3a8a01def4fb0ec0ef8a20d097277b9c6b2a1f Mon Sep 17 00:00:00 2001 From: Alexey Fisher Date: Thu, 10 Mar 2011 14:53:39 +0100 Subject: ALSA: usb-audio - Add "cval->res = 384" quirk for Logitech Webcam C600 One more affected devices: Logitech Webcam C600 (046d:0808) Volume range before quirk is 6400, after (also real) is 16. Signed-off-by: Alexey Fisher Signed-off-by: Takashi Iwai --- sound/usb/mixer.c | 1 + 1 file changed, 1 insertion(+) (limited to 'sound/usb') diff --git a/sound/usb/mixer.c b/sound/usb/mixer.c index 3e3116c..844cf78 100644 --- a/sound/usb/mixer.c +++ b/sound/usb/mixer.c @@ -1122,6 +1122,7 @@ static void build_feature_ctl(struct mixer_build *state, void *raw_desc, } break; + case USB_ID(0x046d, 0x0808): case USB_ID(0x046d, 0x0809): case USB_ID(0x046d, 0x0991): /* Most audio usb devices lie about volume resolution. -- cgit v1.1 From edf7de31c25ce72f163bf7d1fc0d2711869d073c Mon Sep 17 00:00:00 2001 From: Oliver Neukum Date: Fri, 11 Mar 2011 13:19:43 +0100 Subject: ALSA: usbaudio: fix suspend/resume - ESHUTDOWN must be correctly handled - the optional interrupt endpoint's URB must be stopped and restarted Signed-off-by: Oliver Neukum Signed-off-by: Takashi Iwai --- sound/usb/card.c | 10 +++++++++- sound/usb/mixer.c | 25 +++++++++++++++++++++++-- sound/usb/mixer.h | 2 ++ 3 files changed, 34 insertions(+), 3 deletions(-) (limited to 'sound/usb') diff --git a/sound/usb/card.c b/sound/usb/card.c index c0f8270..7fa53d9 100644 --- a/sound/usb/card.c +++ b/sound/usb/card.c @@ -586,6 +586,7 @@ static int usb_audio_suspend(struct usb_interface *intf, pm_message_t message) struct snd_usb_audio *chip = usb_get_intfdata(intf); struct list_head *p; struct snd_usb_stream *as; + struct usb_mixer_interface *mixer; if (chip == (void *)-1L) return 0; @@ -596,6 +597,10 @@ static int usb_audio_suspend(struct usb_interface *intf, pm_message_t message) as = list_entry(p, struct snd_usb_stream, list); snd_pcm_suspend_all(as->pcm); } + + list_for_each_entry(mixer, &chip->mixer_list, list) { + snd_usb_mixer_inactivate(mixer); + } } return 0; @@ -604,6 +609,7 @@ static int usb_audio_suspend(struct usb_interface *intf, pm_message_t message) static int usb_audio_resume(struct usb_interface *intf) { struct snd_usb_audio *chip = usb_get_intfdata(intf); + struct usb_mixer_interface *mixer; if (chip == (void *)-1L) return 0; @@ -611,8 +617,10 @@ static int usb_audio_resume(struct usb_interface *intf) return 0; /* * ALSA leaves material resumption to user space - * we just notify + * we just notify and restart the mixers */ + list_for_each_entry(mixer, &chip->mixer_list, list) + snd_usb_mixer_activate(mixer); snd_power_change_state(chip->card, SNDRV_CTL_POWER_D0); diff --git a/sound/usb/mixer.c b/sound/usb/mixer.c index 675a4f1..09e5934 100644 --- a/sound/usb/mixer.c +++ b/sound/usb/mixer.c @@ -2075,8 +2075,9 @@ 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 (urb->status != 0) + if (ustatus != 0) goto requeue; if (mixer->protocol == UAC_VERSION_1) { @@ -2117,12 +2118,32 @@ static void snd_usb_mixer_interrupt(struct urb *urb) } requeue: - if (urb->status != -ENOENT && urb->status != -ECONNRESET) { + if (ustatus != -ENOENT && ustatus != -ECONNRESET && ustatus != -ESHUTDOWN) { urb->dev = mixer->chip->dev; usb_submit_urb(urb, GFP_ATOMIC); } } +/* stop any bus activity of a mixer */ +void snd_usb_mixer_inactivate(struct usb_mixer_interface *mixer) +{ + usb_kill_urb(mixer->urb); + usb_kill_urb(mixer->rc_urb); +} + +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; +} + /* create the handler for the optional status interrupt endpoint */ static int snd_usb_mixer_status_create(struct usb_mixer_interface *mixer) { diff --git a/sound/usb/mixer.h b/sound/usb/mixer.h index 26c636c..b4a2c81 100644 --- a/sound/usb/mixer.h +++ b/sound/usb/mixer.h @@ -52,5 +52,7 @@ 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); +void snd_usb_mixer_inactivate(struct usb_mixer_interface *mixer); +int snd_usb_mixer_activate(struct usb_mixer_interface *mixer); #endif /* __USBMIXER_H */ -- cgit v1.1 From 88a8516a2128a6d078a106ead48092240e8a138f Mon Sep 17 00:00:00 2001 From: Oliver Neukum Date: Fri, 11 Mar 2011 14:51:12 +0100 Subject: ALSA: usbaudio: implement USB autosuspend Devices are autosuspended if no pcm nor midi channel is open Mixer devices may be opened. This way they are active when in use to play or record sound, but can be suspended while users have a mixer application running. [Small clean-ups using static inline by tiwai] Signed-off-by: Oliver Neukum Signed-off-by: Takashi Iwai --- sound/usb/card.c | 66 +++++++++++++++++++++++++++++++++++++++++----------- sound/usb/midi.c | 8 +++++++ sound/usb/mixer.c | 23 ++++++++++++++++-- sound/usb/pcm.c | 20 ++++++++++++---- sound/usb/power.h | 17 ++++++++++++++ sound/usb/usbaudio.h | 6 ++++- 6 files changed, 118 insertions(+), 22 deletions(-) create mode 100644 sound/usb/power.h (limited to 'sound/usb') diff --git a/sound/usb/card.c b/sound/usb/card.c index 7fa53d9..40722f8 100644 --- a/sound/usb/card.c +++ b/sound/usb/card.c @@ -65,6 +65,7 @@ #include "pcm.h" #include "urb.h" #include "format.h" +#include "power.h" MODULE_AUTHOR("Takashi Iwai "); MODULE_DESCRIPTION("USB Audio"); @@ -330,6 +331,7 @@ static int snd_usb_audio_create(struct usb_device *dev, int idx, chip->setup = device_setup[idx]; chip->nrpacks = nrpacks; chip->async_unlink = async_unlink; + chip->probing = 1; chip->usb_id = USB_ID(le16_to_cpu(dev->descriptor.idVendor), le16_to_cpu(dev->descriptor.idProduct)); @@ -451,6 +453,7 @@ static void *snd_usb_audio_probe(struct usb_device *dev, goto __error; } chip = usb_chip[i]; + chip->probing = 1; break; } } @@ -466,6 +469,7 @@ static void *snd_usb_audio_probe(struct usb_device *dev, goto __error; } snd_card_set_dev(chip->card, &intf->dev); + chip->pm_intf = intf; break; } if (!chip) { @@ -505,6 +509,7 @@ static void *snd_usb_audio_probe(struct usb_device *dev, usb_chip[chip->index] = chip; chip->num_interfaces++; + chip->probing = 0; mutex_unlock(®ister_mutex); return chip; @@ -581,6 +586,23 @@ static void usb_audio_disconnect(struct usb_interface *intf) } #ifdef CONFIG_PM + +int snd_usb_autoresume(struct snd_usb_audio *chip) +{ + int err = -ENODEV; + + if (!chip->shutdown && !chip->probing) + err = usb_autopm_get_interface(chip->pm_intf); + + return err; +} + +void snd_usb_autosuspend(struct snd_usb_audio *chip) +{ + if (!chip->shutdown && !chip->probing) + usb_autopm_put_interface(chip->pm_intf); +} + static int usb_audio_suspend(struct usb_interface *intf, pm_message_t message) { struct snd_usb_audio *chip = usb_get_intfdata(intf); @@ -591,18 +613,26 @@ static int usb_audio_suspend(struct usb_interface *intf, pm_message_t message) 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); - } - - list_for_each_entry(mixer, &chip->mixer_list, list) { - snd_usb_mixer_inactivate(mixer); - } + if (!(message.event & PM_EVENT_AUTO)) { + 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); + } + } + } else { + /* + * otherwise we keep the rest of the system in the dark + * to keep this transparent + */ + if (!chip->num_suspended_intf++) + chip->autosuspended = 1; } + list_for_each_entry(mixer, &chip->mixer_list, list) + snd_usb_mixer_inactivate(mixer); + return 0; } @@ -610,6 +640,7 @@ static int usb_audio_resume(struct usb_interface *intf) { struct snd_usb_audio *chip = usb_get_intfdata(intf); struct usb_mixer_interface *mixer; + int err = 0; if (chip == (void *)-1L) return 0; @@ -619,12 +650,18 @@ static int usb_audio_resume(struct usb_interface *intf) * ALSA leaves material resumption to user space * we just notify and restart the mixers */ - list_for_each_entry(mixer, &chip->mixer_list, list) - snd_usb_mixer_activate(mixer); + list_for_each_entry(mixer, &chip->mixer_list, list) { + err = snd_usb_mixer_activate(mixer); + if (err < 0) + goto err_out; + } - snd_power_change_state(chip->card, SNDRV_CTL_POWER_D0); + if (!chip->autosuspended) + snd_power_change_state(chip->card, SNDRV_CTL_POWER_D0); + chip->autosuspended = 0; - return 0; +err_out: + return err; } #else #define usb_audio_suspend NULL @@ -652,6 +689,7 @@ static struct usb_driver usb_audio_driver = { .suspend = usb_audio_suspend, .resume = usb_audio_resume, .id_table = usb_audio_ids, + .supports_autosuspend = 1, }; static int __init snd_usb_audio_init(void) diff --git a/sound/usb/midi.c b/sound/usb/midi.c index db2dc5f..b4b39c0 100644 --- a/sound/usb/midi.c +++ b/sound/usb/midi.c @@ -54,6 +54,7 @@ #include #include "usbaudio.h" #include "midi.h" +#include "power.h" #include "helper.h" /* @@ -1044,6 +1045,7 @@ static int snd_usbmidi_output_open(struct snd_rawmidi_substream *substream) struct snd_usb_midi* umidi = substream->rmidi->private_data; struct usbmidi_out_port* port = NULL; int i, j; + int err; for (i = 0; i < MIDI_MAX_ENDPOINTS; ++i) if (umidi->endpoints[i].out) @@ -1056,6 +1058,9 @@ static int snd_usbmidi_output_open(struct snd_rawmidi_substream *substream) snd_BUG(); return -ENXIO; } + err = usb_autopm_get_interface(umidi->iface); + if (err < 0) + return -EIO; substream->runtime->private_data = port; port->state = STATE_UNKNOWN; substream_open(substream, 1); @@ -1064,7 +1069,10 @@ static int snd_usbmidi_output_open(struct snd_rawmidi_substream *substream) static int snd_usbmidi_output_close(struct snd_rawmidi_substream *substream) { + struct snd_usb_midi* umidi = substream->rmidi->private_data; + substream_open(substream, 0); + usb_autopm_put_interface(umidi->iface); return 0; } diff --git a/sound/usb/mixer.c b/sound/usb/mixer.c index 09e5934..5e47757 100644 --- a/sound/usb/mixer.c +++ b/sound/usb/mixer.c @@ -61,6 +61,7 @@ #include "mixer.h" #include "helper.h" #include "mixer_quirks.h" +#include "power.h" #define MAX_ID_ELEMS 256 @@ -295,16 +296,22 @@ static int get_ctl_value_v1(struct usb_mixer_elem_info *cval, int request, int v unsigned char buf[2]; int val_len = cval->val_type >= USB_MIXER_S16 ? 2 : 1; int timeout = 10; + int err; + err = snd_usb_autoresume(cval->mixer->chip); + if (err < 0) + return -EIO; while (timeout-- > 0) { if (snd_usb_ctl_msg(chip->dev, usb_rcvctrlpipe(chip->dev, 0), request, USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_IN, validx, snd_usb_ctrl_intf(chip) | (cval->id << 8), buf, val_len, 100) >= val_len) { *value_ret = convert_signed_value(cval, snd_usb_combine_bytes(buf, val_len)); + snd_usb_autosuspend(cval->mixer->chip); return 0; } } + snd_usb_autosuspend(cval->mixer->chip); snd_printdd(KERN_ERR "cannot get ctl value: req = %#x, wValue = %#x, wIndex = %#x, type = %d\n", request, validx, snd_usb_ctrl_intf(chip) | (cval->id << 8), cval->val_type); return -EINVAL; @@ -328,12 +335,18 @@ static int get_ctl_value_v2(struct usb_mixer_elem_info *cval, int request, int v memset(buf, 0, sizeof(buf)); + ret = snd_usb_autoresume(chip) ? -EIO : 0; + if (ret) + goto error; + ret = snd_usb_ctl_msg(chip->dev, usb_rcvctrlpipe(chip->dev, 0), bRequest, USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_IN, validx, snd_usb_ctrl_intf(chip) | (cval->id << 8), buf, size, 1000); + snd_usb_autosuspend(chip); if (ret < 0) { +error: snd_printk(KERN_ERR "cannot get ctl value: req = %#x, wValue = %#x, wIndex = %#x, type = %d\n", request, validx, snd_usb_ctrl_intf(chip) | (cval->id << 8), cval->val_type); return ret; @@ -413,7 +426,7 @@ int snd_usb_mixer_set_ctl_value(struct usb_mixer_elem_info *cval, { struct snd_usb_audio *chip = cval->mixer->chip; unsigned char buf[2]; - int val_len, timeout = 10; + int val_len, err, timeout = 10; if (cval->mixer->protocol == UAC_VERSION_1) { val_len = cval->val_type >= USB_MIXER_S16 ? 2 : 1; @@ -433,13 +446,19 @@ int snd_usb_mixer_set_ctl_value(struct usb_mixer_elem_info *cval, 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; while (timeout-- > 0) if (snd_usb_ctl_msg(chip->dev, usb_sndctrlpipe(chip->dev, 0), request, USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_OUT, validx, snd_usb_ctrl_intf(chip) | (cval->id << 8), - buf, val_len, 100) >= 0) + buf, val_len, 100) >= 0) { + snd_usb_autosuspend(chip); return 0; + } + snd_usb_autosuspend(chip); snd_printdd(KERN_ERR "cannot set ctl value: req = %#x, wValue = %#x, wIndex = %#x, type = %d, data = %#x/%#x\n", request, validx, snd_usb_ctrl_intf(chip) | (cval->id << 8), cval->val_type, buf[0], buf[1]); return -EINVAL; diff --git a/sound/usb/pcm.c b/sound/usb/pcm.c index e3f6805..b8dcbf4 100644 --- a/sound/usb/pcm.c +++ b/sound/usb/pcm.c @@ -32,6 +32,7 @@ #include "helper.h" #include "pcm.h" #include "clock.h" +#include "power.h" /* * return the current pcm pointer. just based on the hwptr_done value. @@ -739,6 +740,9 @@ static int setup_hw_info(struct snd_pcm_runtime *runtime, struct snd_usb_substre 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 (snd_usb_get_speed(subs->dev) == USB_SPEED_FULL) @@ -756,21 +760,21 @@ static int setup_hw_info(struct snd_pcm_runtime *runtime, struct snd_usb_substre SNDRV_PCM_HW_PARAM_CHANNELS, param_period_time_if_needed, -1)) < 0) - return err; + 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) - return err; + 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) - return err; + goto rep_err; if (param_period_time_if_needed >= 0) { err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_TIME, @@ -780,11 +784,15 @@ static int setup_hw_info(struct snd_pcm_runtime *runtime, struct snd_usb_substre SNDRV_PCM_HW_PARAM_RATE, -1); if (err < 0) - return err; + goto rep_err; } if ((err = snd_usb_pcm_check_knot(runtime, subs)) < 0) - return err; + 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) @@ -798,6 +806,7 @@ static int snd_usb_pcm_open(struct snd_pcm_substream *substream, int direction) runtime->hw = snd_usb_hardware; runtime->private_data = subs; subs->pcm_substream = substream; + /* runtime PM is also done there */ return setup_hw_info(runtime, subs); } @@ -811,6 +820,7 @@ static int snd_usb_pcm_close(struct snd_pcm_substream *substream, int direction) subs->interface = -1; } subs->pcm_substream = NULL; + snd_usb_autosuspend(subs->stream->chip); return 0; } diff --git a/sound/usb/power.h b/sound/usb/power.h new file mode 100644 index 0000000..48ee51d --- /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/usbaudio.h b/sound/usb/usbaudio.h index 6e66fff..32f2a97 100644 --- a/sound/usb/usbaudio.h +++ b/sound/usb/usbaudio.h @@ -34,10 +34,14 @@ 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 shutdown_mutex; + unsigned int shutdown:1; + unsigned int probing:1; + unsigned int autosuspended:1; unsigned int txfr_quirk:1; /* Subframe boundaries on transfers */ + int num_interfaces; int num_suspended_intf; -- cgit v1.1