Fixed MTP to work with TWRP

This commit is contained in:
awab228 2018-06-19 23:16:04 +02:00
commit f6dfaef42e
50820 changed files with 20846062 additions and 0 deletions

3
sound/usb/6fire/Makefile Normal file
View file

@ -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

219
sound/usb/6fire/chip.c Normal file
View file

@ -0,0 +1,219 @@
/*
* Linux driver for TerraTec DMX 6Fire USB
*
* Main routines and module definitions.
*
* Author: Torsten Schenk <torsten.schenk@zoho.com>
* Created: Jan 01, 2011
* Copyright: (C) Torsten Schenk
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*/
#include "chip.h"
#include "firmware.h"
#include "pcm.h"
#include "control.h"
#include "comm.h"
#include "midi.h"
#include <linux/moduleparam.h>
#include <linux/interrupt.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/gfp.h>
#include <sound/initval.h>
MODULE_AUTHOR("Torsten Schenk <torsten.schenk@zoho.com>");
MODULE_DESCRIPTION("TerraTec DMX 6Fire USB audio driver");
MODULE_LICENSE("GPL v2");
MODULE_SUPPORTED_DEVICE("{{TerraTec,DMX 6Fire USB}}");
static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 0-max */
static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* Id for card */
static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP; /* Enable card */
static struct sfire_chip *chips[SNDRV_CARDS] = SNDRV_DEFAULT_PTR;
static struct usb_device *devices[SNDRV_CARDS] = SNDRV_DEFAULT_PTR;
module_param_array(index, int, NULL, 0444);
MODULE_PARM_DESC(index, "Index value for the 6fire sound device");
module_param_array(id, charp, NULL, 0444);
MODULE_PARM_DESC(id, "ID string for the 6fire sound device.");
module_param_array(enable, bool, NULL, 0444);
MODULE_PARM_DESC(enable, "Enable the 6fire sound device.");
static DEFINE_MUTEX(register_mutex);
static void usb6fire_chip_abort(struct sfire_chip *chip)
{
if (chip) {
if (chip->pcm)
usb6fire_pcm_abort(chip);
if (chip->midi)
usb6fire_midi_abort(chip);
if (chip->comm)
usb6fire_comm_abort(chip);
if (chip->control)
usb6fire_control_abort(chip);
if (chip->card) {
snd_card_disconnect(chip->card);
snd_card_free_when_closed(chip->card);
chip->card = NULL;
}
}
}
static void usb6fire_chip_destroy(struct sfire_chip *chip)
{
if (chip) {
if (chip->pcm)
usb6fire_pcm_destroy(chip);
if (chip->midi)
usb6fire_midi_destroy(chip);
if (chip->comm)
usb6fire_comm_destroy(chip);
if (chip->control)
usb6fire_control_destroy(chip);
if (chip->card)
snd_card_free(chip->card);
}
}
static int usb6fire_chip_probe(struct usb_interface *intf,
const struct usb_device_id *usb_id)
{
int ret;
int i;
struct sfire_chip *chip = NULL;
struct usb_device *device = interface_to_usbdev(intf);
int regidx = -1; /* index in module parameter array */
struct snd_card *card = NULL;
/* look if we already serve this card and return if so */
mutex_lock(&register_mutex);
for (i = 0; i < SNDRV_CARDS; i++) {
if (devices[i] == device) {
if (chips[i])
chips[i]->intf_count++;
usb_set_intfdata(intf, chips[i]);
mutex_unlock(&register_mutex);
return 0;
} else if (!devices[i] && regidx < 0)
regidx = i;
}
if (regidx < 0) {
mutex_unlock(&register_mutex);
dev_err(&intf->dev, "too many cards registered.\n");
return -ENODEV;
}
devices[regidx] = device;
mutex_unlock(&register_mutex);
/* check, if firmware is present on device, upload it if not */
ret = usb6fire_fw_init(intf);
if (ret < 0)
return ret;
else if (ret == FW_NOT_READY) /* firmware update performed */
return 0;
/* if we are here, card can be registered in alsa. */
if (usb_set_interface(device, 0, 0) != 0) {
dev_err(&intf->dev, "can't set first interface.\n");
return -EIO;
}
ret = snd_card_new(&intf->dev, index[regidx], id[regidx],
THIS_MODULE, sizeof(struct sfire_chip), &card);
if (ret < 0) {
dev_err(&intf->dev, "cannot create alsa card.\n");
return ret;
}
strcpy(card->driver, "6FireUSB");
strcpy(card->shortname, "TerraTec DMX6FireUSB");
sprintf(card->longname, "%s at %d:%d", card->shortname,
device->bus->busnum, device->devnum);
chip = card->private_data;
chips[regidx] = chip;
chip->dev = device;
chip->regidx = regidx;
chip->intf_count = 1;
chip->card = card;
ret = usb6fire_comm_init(chip);
if (ret < 0) {
usb6fire_chip_destroy(chip);
return ret;
}
ret = usb6fire_midi_init(chip);
if (ret < 0) {
usb6fire_chip_destroy(chip);
return ret;
}
ret = usb6fire_pcm_init(chip);
if (ret < 0) {
usb6fire_chip_destroy(chip);
return ret;
}
ret = usb6fire_control_init(chip);
if (ret < 0) {
usb6fire_chip_destroy(chip);
return ret;
}
ret = snd_card_register(card);
if (ret < 0) {
dev_err(&intf->dev, "cannot register card.");
usb6fire_chip_destroy(chip);
return ret;
}
usb_set_intfdata(intf, chip);
return 0;
}
static void usb6fire_chip_disconnect(struct usb_interface *intf)
{
struct sfire_chip *chip;
struct snd_card *card;
chip = usb_get_intfdata(intf);
if (chip) { /* if !chip, fw upload has been performed */
card = chip->card;
chip->intf_count--;
if (!chip->intf_count) {
mutex_lock(&register_mutex);
devices[chip->regidx] = NULL;
chips[chip->regidx] = NULL;
mutex_unlock(&register_mutex);
chip->shutdown = true;
usb6fire_chip_abort(chip);
usb6fire_chip_destroy(chip);
}
}
}
static struct usb_device_id device_table[] = {
{
.match_flags = USB_DEVICE_ID_MATCH_DEVICE,
.idVendor = 0x0ccd,
.idProduct = 0x0080
},
{}
};
MODULE_DEVICE_TABLE(usb, device_table);
static struct usb_driver usb_driver = {
.name = "snd-usb-6fire",
.probe = usb6fire_chip_probe,
.disconnect = usb6fire_chip_disconnect,
.id_table = device_table,
};
module_usb_driver(usb_driver);

31
sound/usb/6fire/chip.h Normal file
View file

@ -0,0 +1,31 @@
/*
* Linux driver for TerraTec DMX 6Fire USB
*
* Author: Torsten Schenk <torsten.schenk@zoho.com>
* Created: Jan 01, 2011
* Copyright: (C) Torsten Schenk
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*/
#ifndef USB6FIRE_CHIP_H
#define USB6FIRE_CHIP_H
#include "common.h"
struct sfire_chip {
struct usb_device *dev;
struct snd_card *card;
int intf_count; /* number of registered interfaces */
int regidx; /* index in module parameter arrays */
bool shutdown;
struct midi_runtime *midi;
struct pcm_runtime *pcm;
struct control_runtime *control;
struct comm_runtime *comm;
};
#endif /* USB6FIRE_CHIP_H */

204
sound/usb/6fire/comm.c Normal file
View file

@ -0,0 +1,204 @@
/*
* Linux driver for TerraTec DMX 6Fire USB
*
* Device communications
*
* Author: Torsten Schenk <torsten.schenk@zoho.com>
* Created: Jan 01, 2011
* Copyright: (C) Torsten Schenk
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*/
#include "comm.h"
#include "chip.h"
#include "midi.h"
enum {
COMM_EP = 1,
COMM_FPGA_EP = 2
};
static void usb6fire_comm_init_urb(struct comm_runtime *rt, struct urb *urb,
u8 *buffer, void *context, void(*handler)(struct urb *urb))
{
usb_init_urb(urb);
urb->transfer_buffer = buffer;
urb->pipe = usb_sndintpipe(rt->chip->dev, COMM_EP);
urb->complete = handler;
urb->context = context;
urb->interval = 1;
urb->dev = rt->chip->dev;
}
static void usb6fire_comm_receiver_handler(struct urb *urb)
{
struct comm_runtime *rt = urb->context;
struct midi_runtime *midi_rt = rt->chip->midi;
if (!urb->status) {
if (rt->receiver_buffer[0] == 0x10) /* midi in event */
if (midi_rt)
midi_rt->in_received(midi_rt,
rt->receiver_buffer + 2,
rt->receiver_buffer[1]);
}
if (!rt->chip->shutdown) {
urb->status = 0;
urb->actual_length = 0;
if (usb_submit_urb(urb, GFP_ATOMIC) < 0)
dev_warn(&urb->dev->dev,
"comm data receiver aborted.\n");
}
}
static void usb6fire_comm_init_buffer(u8 *buffer, u8 id, u8 request,
u8 reg, u8 vl, u8 vh)
{
buffer[0] = 0x01;
buffer[2] = request;
buffer[3] = id;
switch (request) {
case 0x02:
buffer[1] = 0x05; /* length (starting at buffer[2]) */
buffer[4] = reg;
buffer[5] = vl;
buffer[6] = vh;
break;
case 0x12:
buffer[1] = 0x0b; /* length (starting at buffer[2]) */
buffer[4] = 0x00;
buffer[5] = 0x18;
buffer[6] = 0x05;
buffer[7] = 0x00;
buffer[8] = 0x01;
buffer[9] = 0x00;
buffer[10] = 0x9e;
buffer[11] = reg;
buffer[12] = vl;
break;
case 0x20:
case 0x21:
case 0x22:
buffer[1] = 0x04;
buffer[4] = reg;
buffer[5] = vl;
break;
}
}
static int usb6fire_comm_send_buffer(u8 *buffer, struct usb_device *dev)
{
int ret;
int actual_len;
ret = usb_interrupt_msg(dev, usb_sndintpipe(dev, COMM_EP),
buffer, buffer[1] + 2, &actual_len, HZ);
if (ret < 0)
return ret;
else if (actual_len != buffer[1] + 2)
return -EIO;
return 0;
}
static int usb6fire_comm_write8(struct comm_runtime *rt, u8 request,
u8 reg, u8 value)
{
u8 *buffer;
int ret;
/* 13: maximum length of message */
buffer = kmalloc(13, GFP_KERNEL);
if (!buffer)
return -ENOMEM;
usb6fire_comm_init_buffer(buffer, 0x00, request, reg, value, 0x00);
ret = usb6fire_comm_send_buffer(buffer, rt->chip->dev);
kfree(buffer);
return ret;
}
static int usb6fire_comm_write16(struct comm_runtime *rt, u8 request,
u8 reg, u8 vl, u8 vh)
{
u8 *buffer;
int ret;
/* 13: maximum length of message */
buffer = kmalloc(13, GFP_KERNEL);
if (!buffer)
return -ENOMEM;
usb6fire_comm_init_buffer(buffer, 0x00, request, reg, vl, vh);
ret = usb6fire_comm_send_buffer(buffer, rt->chip->dev);
kfree(buffer);
return ret;
}
int usb6fire_comm_init(struct sfire_chip *chip)
{
struct comm_runtime *rt = kzalloc(sizeof(struct comm_runtime),
GFP_KERNEL);
struct urb *urb;
int ret;
if (!rt)
return -ENOMEM;
rt->receiver_buffer = kzalloc(COMM_RECEIVER_BUFSIZE, GFP_KERNEL);
if (!rt->receiver_buffer) {
kfree(rt);
return -ENOMEM;
}
urb = &rt->receiver;
rt->serial = 1;
rt->chip = chip;
usb_init_urb(urb);
rt->init_urb = usb6fire_comm_init_urb;
rt->write8 = usb6fire_comm_write8;
rt->write16 = usb6fire_comm_write16;
/* submit an urb that receives communication data from device */
urb->transfer_buffer = rt->receiver_buffer;
urb->transfer_buffer_length = COMM_RECEIVER_BUFSIZE;
urb->pipe = usb_rcvintpipe(chip->dev, COMM_EP);
urb->dev = chip->dev;
urb->complete = usb6fire_comm_receiver_handler;
urb->context = rt;
urb->interval = 1;
ret = usb_submit_urb(urb, GFP_KERNEL);
if (ret < 0) {
kfree(rt->receiver_buffer);
kfree(rt);
dev_err(&chip->dev->dev, "cannot create comm data receiver.");
return ret;
}
chip->comm = rt;
return 0;
}
void usb6fire_comm_abort(struct sfire_chip *chip)
{
struct comm_runtime *rt = chip->comm;
if (rt)
usb_poison_urb(&rt->receiver);
}
void usb6fire_comm_destroy(struct sfire_chip *chip)
{
struct comm_runtime *rt = chip->comm;
kfree(rt->receiver_buffer);
kfree(rt);
chip->comm = NULL;
}

43
sound/usb/6fire/comm.h Normal file
View file

@ -0,0 +1,43 @@
/*
* Linux driver for TerraTec DMX 6Fire USB
*
* Author: Torsten Schenk <torsten.schenk@zoho.com>
* Created: Jan 01, 2011
* Copyright: (C) Torsten Schenk
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*/
#ifndef USB6FIRE_COMM_H
#define USB6FIRE_COMM_H
#include "common.h"
enum /* settings for comm */
{
COMM_RECEIVER_BUFSIZE = 64,
};
struct comm_runtime {
struct sfire_chip *chip;
struct urb receiver;
u8 *receiver_buffer;
u8 serial; /* urb serial */
void (*init_urb)(struct comm_runtime *rt, struct urb *urb, u8 *buffer,
void *context, void(*handler)(struct urb *urb));
/* writes control data to the device */
int (*write8)(struct comm_runtime *rt, u8 request, u8 reg, u8 value);
int (*write16)(struct comm_runtime *rt, u8 request, u8 reg,
u8 vh, u8 vl);
};
int usb6fire_comm_init(struct sfire_chip *chip);
void usb6fire_comm_abort(struct sfire_chip *chip);
void usb6fire_comm_destroy(struct sfire_chip *chip);
#endif /* USB6FIRE_COMM_H */

29
sound/usb/6fire/common.h Normal file
View file

@ -0,0 +1,29 @@
/*
* Linux driver for TerraTec DMX 6Fire USB
*
* Author: Torsten Schenk <torsten.schenk@zoho.com>
* Created: Jan 01, 2011
* Copyright: (C) Torsten Schenk
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*/
#ifndef USB6FIRE_COMMON_H
#define USB6FIRE_COMMON_H
#include <linux/slab.h>
#include <linux/usb.h>
#include <sound/core.h>
#define PREFIX "6fire: "
struct sfire_chip;
struct midi_runtime;
struct pcm_runtime;
struct control_runtime;
struct comm_runtime;
#endif /* USB6FIRE_COMMON_H */

635
sound/usb/6fire/control.c Normal file
View file

@ -0,0 +1,635 @@
/*
* Linux driver for TerraTec DMX 6Fire USB
*
* Mixer control
*
* Author: Torsten Schenk <torsten.schenk@zoho.com>
* Created: Jan 01, 2011
* Copyright: (C) Torsten Schenk
*
* Thanks to:
* - Holger Ruckdeschel: he found out how to control individual channel
* volumes and introduced mute switch
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*/
#include <linux/interrupt.h>
#include <sound/control.h>
#include <sound/tlv.h>
#include "control.h"
#include "comm.h"
#include "chip.h"
static char *opt_coax_texts[2] = { "Optical", "Coax" };
static char *line_phono_texts[2] = { "Line", "Phono" };
/*
* data that needs to be sent to device. sets up card internal stuff.
* values dumped from windows driver and filtered by trial'n'error.
*/
static const struct {
u8 type;
u8 reg;
u8 value;
}
init_data[] = {
{ 0x22, 0x00, 0x00 }, { 0x20, 0x00, 0x08 }, { 0x22, 0x01, 0x01 },
{ 0x20, 0x01, 0x08 }, { 0x22, 0x02, 0x00 }, { 0x20, 0x02, 0x08 },
{ 0x22, 0x03, 0x00 }, { 0x20, 0x03, 0x08 }, { 0x22, 0x04, 0x00 },
{ 0x20, 0x04, 0x08 }, { 0x22, 0x05, 0x01 }, { 0x20, 0x05, 0x08 },
{ 0x22, 0x04, 0x01 }, { 0x12, 0x04, 0x00 }, { 0x12, 0x05, 0x00 },
{ 0x12, 0x0d, 0x38 }, { 0x12, 0x21, 0x82 }, { 0x12, 0x22, 0x80 },
{ 0x12, 0x23, 0x00 }, { 0x12, 0x06, 0x02 }, { 0x12, 0x03, 0x00 },
{ 0x12, 0x02, 0x00 }, { 0x22, 0x03, 0x01 },
{ 0 } /* TERMINATING ENTRY */
};
static const int rates_altsetting[] = { 1, 1, 2, 2, 3, 3 };
/* values to write to soundcard register for all samplerates */
static const u16 rates_6fire_vl[] = {0x00, 0x01, 0x00, 0x01, 0x00, 0x01};
static const u16 rates_6fire_vh[] = {0x11, 0x11, 0x10, 0x10, 0x00, 0x00};
static DECLARE_TLV_DB_MINMAX(tlv_output, -9000, 0);
static DECLARE_TLV_DB_MINMAX(tlv_input, -1500, 1500);
enum {
DIGITAL_THRU_ONLY_SAMPLERATE = 3
};
static void usb6fire_control_output_vol_update(struct control_runtime *rt)
{
struct comm_runtime *comm_rt = rt->chip->comm;
int i;
if (comm_rt)
for (i = 0; i < 6; i++)
if (!(rt->ovol_updated & (1 << i))) {
comm_rt->write8(comm_rt, 0x12, 0x0f + i,
180 - rt->output_vol[i]);
rt->ovol_updated |= 1 << i;
}
}
static void usb6fire_control_output_mute_update(struct control_runtime *rt)
{
struct comm_runtime *comm_rt = rt->chip->comm;
if (comm_rt)
comm_rt->write8(comm_rt, 0x12, 0x0e, ~rt->output_mute);
}
static void usb6fire_control_input_vol_update(struct control_runtime *rt)
{
struct comm_runtime *comm_rt = rt->chip->comm;
int i;
if (comm_rt)
for (i = 0; i < 2; i++)
if (!(rt->ivol_updated & (1 << i))) {
comm_rt->write8(comm_rt, 0x12, 0x1c + i,
rt->input_vol[i] & 0x3f);
rt->ivol_updated |= 1 << i;
}
}
static void usb6fire_control_line_phono_update(struct control_runtime *rt)
{
struct comm_runtime *comm_rt = rt->chip->comm;
if (comm_rt) {
comm_rt->write8(comm_rt, 0x22, 0x02, rt->line_phono_switch);
comm_rt->write8(comm_rt, 0x21, 0x02, rt->line_phono_switch);
}
}
static void usb6fire_control_opt_coax_update(struct control_runtime *rt)
{
struct comm_runtime *comm_rt = rt->chip->comm;
if (comm_rt) {
comm_rt->write8(comm_rt, 0x22, 0x00, rt->opt_coax_switch);
comm_rt->write8(comm_rt, 0x21, 0x00, rt->opt_coax_switch);
}
}
static int usb6fire_control_set_rate(struct control_runtime *rt, int rate)
{
int ret;
struct usb_device *device = rt->chip->dev;
struct comm_runtime *comm_rt = rt->chip->comm;
if (rate < 0 || rate >= CONTROL_N_RATES)
return -EINVAL;
ret = usb_set_interface(device, 1, rates_altsetting[rate]);
if (ret < 0)
return ret;
/* set soundcard clock */
ret = comm_rt->write16(comm_rt, 0x02, 0x01, rates_6fire_vl[rate],
rates_6fire_vh[rate]);
if (ret < 0)
return ret;
return 0;
}
static int usb6fire_control_set_channels(
struct control_runtime *rt, int n_analog_out,
int n_analog_in, bool spdif_out, bool spdif_in)
{
int ret;
struct comm_runtime *comm_rt = rt->chip->comm;
/* enable analog inputs and outputs
* (one bit per stereo-channel) */
ret = comm_rt->write16(comm_rt, 0x02, 0x02,
(1 << (n_analog_out / 2)) - 1,
(1 << (n_analog_in / 2)) - 1);
if (ret < 0)
return ret;
/* disable digital inputs and outputs */
/* TODO: use spdif_x to enable/disable digital channels */
ret = comm_rt->write16(comm_rt, 0x02, 0x03, 0x00, 0x00);
if (ret < 0)
return ret;
return 0;
}
static int usb6fire_control_streaming_update(struct control_runtime *rt)
{
struct comm_runtime *comm_rt = rt->chip->comm;
if (comm_rt) {
if (!rt->usb_streaming && rt->digital_thru_switch)
usb6fire_control_set_rate(rt,
DIGITAL_THRU_ONLY_SAMPLERATE);
return comm_rt->write16(comm_rt, 0x02, 0x00, 0x00,
(rt->usb_streaming ? 0x01 : 0x00) |
(rt->digital_thru_switch ? 0x08 : 0x00));
}
return -EINVAL;
}
static int usb6fire_control_output_vol_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{
uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
uinfo->count = 2;
uinfo->value.integer.min = 0;
uinfo->value.integer.max = 180;
return 0;
}
static int usb6fire_control_output_vol_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct control_runtime *rt = snd_kcontrol_chip(kcontrol);
unsigned int ch = kcontrol->private_value;
int changed = 0;
if (ch > 4) {
dev_err(&rt->chip->dev->dev,
"Invalid channel in volume control.");
return -EINVAL;
}
if (rt->output_vol[ch] != ucontrol->value.integer.value[0]) {
rt->output_vol[ch] = ucontrol->value.integer.value[0];
rt->ovol_updated &= ~(1 << ch);
changed = 1;
}
if (rt->output_vol[ch + 1] != ucontrol->value.integer.value[1]) {
rt->output_vol[ch + 1] = ucontrol->value.integer.value[1];
rt->ovol_updated &= ~(2 << ch);
changed = 1;
}
if (changed)
usb6fire_control_output_vol_update(rt);
return changed;
}
static int usb6fire_control_output_vol_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct control_runtime *rt = snd_kcontrol_chip(kcontrol);
unsigned int ch = kcontrol->private_value;
if (ch > 4) {
dev_err(&rt->chip->dev->dev,
"Invalid channel in volume control.");
return -EINVAL;
}
ucontrol->value.integer.value[0] = rt->output_vol[ch];
ucontrol->value.integer.value[1] = rt->output_vol[ch + 1];
return 0;
}
static int usb6fire_control_output_mute_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct control_runtime *rt = snd_kcontrol_chip(kcontrol);
unsigned int ch = kcontrol->private_value;
u8 old = rt->output_mute;
u8 value = 0;
if (ch > 4) {
dev_err(&rt->chip->dev->dev,
"Invalid channel in volume control.");
return -EINVAL;
}
rt->output_mute &= ~(3 << ch);
if (ucontrol->value.integer.value[0])
value |= 1;
if (ucontrol->value.integer.value[1])
value |= 2;
rt->output_mute |= value << ch;
if (rt->output_mute != old)
usb6fire_control_output_mute_update(rt);
return rt->output_mute != old;
}
static int usb6fire_control_output_mute_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct control_runtime *rt = snd_kcontrol_chip(kcontrol);
unsigned int ch = kcontrol->private_value;
u8 value = rt->output_mute >> ch;
if (ch > 4) {
dev_err(&rt->chip->dev->dev,
"Invalid channel in volume control.");
return -EINVAL;
}
ucontrol->value.integer.value[0] = 1 & value;
value >>= 1;
ucontrol->value.integer.value[1] = 1 & value;
return 0;
}
static int usb6fire_control_input_vol_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{
uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
uinfo->count = 2;
uinfo->value.integer.min = 0;
uinfo->value.integer.max = 30;
return 0;
}
static int usb6fire_control_input_vol_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct control_runtime *rt = snd_kcontrol_chip(kcontrol);
int changed = 0;
if (rt->input_vol[0] != ucontrol->value.integer.value[0]) {
rt->input_vol[0] = ucontrol->value.integer.value[0] - 15;
rt->ivol_updated &= ~(1 << 0);
changed = 1;
}
if (rt->input_vol[1] != ucontrol->value.integer.value[1]) {
rt->input_vol[1] = ucontrol->value.integer.value[1] - 15;
rt->ivol_updated &= ~(1 << 1);
changed = 1;
}
if (changed)
usb6fire_control_input_vol_update(rt);
return changed;
}
static int usb6fire_control_input_vol_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct control_runtime *rt = snd_kcontrol_chip(kcontrol);
ucontrol->value.integer.value[0] = rt->input_vol[0] + 15;
ucontrol->value.integer.value[1] = rt->input_vol[1] + 15;
return 0;
}
static int usb6fire_control_line_phono_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{
uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
uinfo->count = 1;
uinfo->value.enumerated.items = 2;
if (uinfo->value.enumerated.item > 1)
uinfo->value.enumerated.item = 1;
strcpy(uinfo->value.enumerated.name,
line_phono_texts[uinfo->value.enumerated.item]);
return 0;
}
static int usb6fire_control_line_phono_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct control_runtime *rt = snd_kcontrol_chip(kcontrol);
int changed = 0;
if (rt->line_phono_switch != ucontrol->value.integer.value[0]) {
rt->line_phono_switch = ucontrol->value.integer.value[0];
usb6fire_control_line_phono_update(rt);
changed = 1;
}
return changed;
}
static int usb6fire_control_line_phono_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct control_runtime *rt = snd_kcontrol_chip(kcontrol);
ucontrol->value.integer.value[0] = rt->line_phono_switch;
return 0;
}
static int usb6fire_control_opt_coax_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{
uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
uinfo->count = 1;
uinfo->value.enumerated.items = 2;
if (uinfo->value.enumerated.item > 1)
uinfo->value.enumerated.item = 1;
strcpy(uinfo->value.enumerated.name,
opt_coax_texts[uinfo->value.enumerated.item]);
return 0;
}
static int usb6fire_control_opt_coax_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct control_runtime *rt = snd_kcontrol_chip(kcontrol);
int changed = 0;
if (rt->opt_coax_switch != ucontrol->value.enumerated.item[0]) {
rt->opt_coax_switch = ucontrol->value.enumerated.item[0];
usb6fire_control_opt_coax_update(rt);
changed = 1;
}
return changed;
}
static int usb6fire_control_opt_coax_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct control_runtime *rt = snd_kcontrol_chip(kcontrol);
ucontrol->value.enumerated.item[0] = rt->opt_coax_switch;
return 0;
}
static int usb6fire_control_digital_thru_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct control_runtime *rt = snd_kcontrol_chip(kcontrol);
int changed = 0;
if (rt->digital_thru_switch != ucontrol->value.integer.value[0]) {
rt->digital_thru_switch = ucontrol->value.integer.value[0];
usb6fire_control_streaming_update(rt);
changed = 1;
}
return changed;
}
static int usb6fire_control_digital_thru_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct control_runtime *rt = snd_kcontrol_chip(kcontrol);
ucontrol->value.integer.value[0] = rt->digital_thru_switch;
return 0;
}
static struct snd_kcontrol_new vol_elements[] = {
{
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "Analog Playback Volume",
.index = 0,
.private_value = 0,
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE |
SNDRV_CTL_ELEM_ACCESS_TLV_READ,
.info = usb6fire_control_output_vol_info,
.get = usb6fire_control_output_vol_get,
.put = usb6fire_control_output_vol_put,
.tlv = { .p = tlv_output }
},
{
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "Analog Playback Volume",
.index = 1,
.private_value = 2,
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE |
SNDRV_CTL_ELEM_ACCESS_TLV_READ,
.info = usb6fire_control_output_vol_info,
.get = usb6fire_control_output_vol_get,
.put = usb6fire_control_output_vol_put,
.tlv = { .p = tlv_output }
},
{
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "Analog Playback Volume",
.index = 2,
.private_value = 4,
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE |
SNDRV_CTL_ELEM_ACCESS_TLV_READ,
.info = usb6fire_control_output_vol_info,
.get = usb6fire_control_output_vol_get,
.put = usb6fire_control_output_vol_put,
.tlv = { .p = tlv_output }
},
{}
};
static struct snd_kcontrol_new mute_elements[] = {
{
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "Analog Playback Switch",
.index = 0,
.private_value = 0,
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
.info = snd_ctl_boolean_stereo_info,
.get = usb6fire_control_output_mute_get,
.put = usb6fire_control_output_mute_put,
},
{
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "Analog Playback Switch",
.index = 1,
.private_value = 2,
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
.info = snd_ctl_boolean_stereo_info,
.get = usb6fire_control_output_mute_get,
.put = usb6fire_control_output_mute_put,
},
{
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "Analog Playback Switch",
.index = 2,
.private_value = 4,
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
.info = snd_ctl_boolean_stereo_info,
.get = usb6fire_control_output_mute_get,
.put = usb6fire_control_output_mute_put,
},
{}
};
static struct snd_kcontrol_new elements[] = {
{
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "Line/Phono Capture Route",
.index = 0,
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
.info = usb6fire_control_line_phono_info,
.get = usb6fire_control_line_phono_get,
.put = usb6fire_control_line_phono_put
},
{
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "Opt/Coax Capture Route",
.index = 0,
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
.info = usb6fire_control_opt_coax_info,
.get = usb6fire_control_opt_coax_get,
.put = usb6fire_control_opt_coax_put
},
{
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "Digital Thru Playback Route",
.index = 0,
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
.info = snd_ctl_boolean_mono_info,
.get = usb6fire_control_digital_thru_get,
.put = usb6fire_control_digital_thru_put
},
{
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "Analog Capture Volume",
.index = 0,
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE |
SNDRV_CTL_ELEM_ACCESS_TLV_READ,
.info = usb6fire_control_input_vol_info,
.get = usb6fire_control_input_vol_get,
.put = usb6fire_control_input_vol_put,
.tlv = { .p = tlv_input }
},
{}
};
static int usb6fire_control_add_virtual(
struct control_runtime *rt,
struct snd_card *card,
char *name,
struct snd_kcontrol_new *elems)
{
int ret;
int i;
struct snd_kcontrol *vmaster =
snd_ctl_make_virtual_master(name, tlv_output);
struct snd_kcontrol *control;
if (!vmaster)
return -ENOMEM;
ret = snd_ctl_add(card, vmaster);
if (ret < 0)
return ret;
i = 0;
while (elems[i].name) {
control = snd_ctl_new1(&elems[i], rt);
if (!control)
return -ENOMEM;
ret = snd_ctl_add(card, control);
if (ret < 0)
return ret;
ret = snd_ctl_add_slave(vmaster, control);
if (ret < 0)
return ret;
i++;
}
return 0;
}
int usb6fire_control_init(struct sfire_chip *chip)
{
int i;
int ret;
struct control_runtime *rt = kzalloc(sizeof(struct control_runtime),
GFP_KERNEL);
struct comm_runtime *comm_rt = chip->comm;
if (!rt)
return -ENOMEM;
rt->chip = chip;
rt->update_streaming = usb6fire_control_streaming_update;
rt->set_rate = usb6fire_control_set_rate;
rt->set_channels = usb6fire_control_set_channels;
i = 0;
while (init_data[i].type) {
comm_rt->write8(comm_rt, init_data[i].type, init_data[i].reg,
init_data[i].value);
i++;
}
usb6fire_control_opt_coax_update(rt);
usb6fire_control_line_phono_update(rt);
usb6fire_control_output_vol_update(rt);
usb6fire_control_output_mute_update(rt);
usb6fire_control_input_vol_update(rt);
usb6fire_control_streaming_update(rt);
ret = usb6fire_control_add_virtual(rt, chip->card,
"Master Playback Volume", vol_elements);
if (ret) {
dev_err(&chip->dev->dev, "cannot add control.\n");
kfree(rt);
return ret;
}
ret = usb6fire_control_add_virtual(rt, chip->card,
"Master Playback Switch", mute_elements);
if (ret) {
dev_err(&chip->dev->dev, "cannot add control.\n");
kfree(rt);
return ret;
}
i = 0;
while (elements[i].name) {
ret = snd_ctl_add(chip->card, snd_ctl_new1(&elements[i], rt));
if (ret < 0) {
kfree(rt);
dev_err(&chip->dev->dev, "cannot add control.\n");
return ret;
}
i++;
}
chip->control = rt;
return 0;
}
void usb6fire_control_abort(struct sfire_chip *chip)
{}
void usb6fire_control_destroy(struct sfire_chip *chip)
{
kfree(chip->control);
chip->control = NULL;
}

57
sound/usb/6fire/control.h Normal file
View file

@ -0,0 +1,57 @@
/*
* Linux driver for TerraTec DMX 6Fire USB
*
* Author: Torsten Schenk <torsten.schenk@zoho.com>
* Created: Jan 01, 2011
* Copyright: (C) Torsten Schenk
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*/
#ifndef USB6FIRE_CONTROL_H
#define USB6FIRE_CONTROL_H
#include "common.h"
enum {
CONTROL_MAX_ELEMENTS = 32
};
enum {
CONTROL_RATE_44KHZ,
CONTROL_RATE_48KHZ,
CONTROL_RATE_88KHZ,
CONTROL_RATE_96KHZ,
CONTROL_RATE_176KHZ,
CONTROL_RATE_192KHZ,
CONTROL_N_RATES
};
struct control_runtime {
int (*update_streaming)(struct control_runtime *rt);
int (*set_rate)(struct control_runtime *rt, int rate);
int (*set_channels)(struct control_runtime *rt, int n_analog_out,
int n_analog_in, bool spdif_out, bool spdif_in);
struct sfire_chip *chip;
struct snd_kcontrol *element[CONTROL_MAX_ELEMENTS];
bool opt_coax_switch;
bool line_phono_switch;
bool digital_thru_switch;
bool usb_streaming;
u8 output_vol[6];
u8 ovol_updated;
u8 output_mute;
s8 input_vol[2];
u8 ivol_updated;
};
int usb6fire_control_init(struct sfire_chip *chip);
void usb6fire_control_abort(struct sfire_chip *chip);
void usb6fire_control_destroy(struct sfire_chip *chip);
#endif /* USB6FIRE_CONTROL_H */

421
sound/usb/6fire/firmware.c Normal file
View file

@ -0,0 +1,421 @@
/*
* Linux driver for TerraTec DMX 6Fire USB
*
* Firmware loader
*
* Author: Torsten Schenk <torsten.schenk@zoho.com>
* Created: Jan 01, 2011
* Copyright: (C) Torsten Schenk
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*/
#include <linux/firmware.h>
#include <linux/module.h>
#include <linux/bitrev.h>
#include <linux/kernel.h>
#include "firmware.h"
#include "chip.h"
MODULE_FIRMWARE("6fire/dmx6firel2.ihx");
MODULE_FIRMWARE("6fire/dmx6fireap.ihx");
MODULE_FIRMWARE("6fire/dmx6firecf.bin");
enum {
FPGA_BUFSIZE = 512, FPGA_EP = 2
};
/*
* wMaxPacketSize of pcm endpoints.
* keep synced with rates_in_packet_size and rates_out_packet_size in pcm.c
* fpp: frames per isopacket
*
* CAUTION: keep sizeof <= buffer[] in usb6fire_fw_init
*/
static const u8 ep_w_max_packet_size[] = {
0xe4, 0x00, 0xe4, 0x00, /* alt 1: 228 EP2 and EP6 (7 fpp) */
0xa4, 0x01, 0xa4, 0x01, /* alt 2: 420 EP2 and EP6 (13 fpp)*/
0x94, 0x01, 0x5c, 0x02 /* alt 3: 404 EP2 and 604 EP6 (25 fpp) */
};
static const u8 known_fw_versions[][2] = {
{ 0x03, 0x01 }
};
struct ihex_record {
u16 address;
u8 len;
u8 data[256];
char error; /* true if an error occurred parsing this record */
u8 max_len; /* maximum record length in whole ihex */
/* private */
const char *txt_data;
unsigned int txt_length;
unsigned int txt_offset; /* current position in txt_data */
};
static u8 usb6fire_fw_ihex_hex(const u8 *data, u8 *crc)
{
u8 val = 0;
int hval;
hval = hex_to_bin(data[0]);
if (hval >= 0)
val |= (hval << 4);
hval = hex_to_bin(data[1]);
if (hval >= 0)
val |= hval;
*crc += val;
return val;
}
/*
* returns true if record is available, false otherwise.
* iff an error occurred, false will be returned and record->error will be true.
*/
static bool usb6fire_fw_ihex_next_record(struct ihex_record *record)
{
u8 crc = 0;
u8 type;
int i;
record->error = false;
/* find begin of record (marked by a colon) */
while (record->txt_offset < record->txt_length
&& record->txt_data[record->txt_offset] != ':')
record->txt_offset++;
if (record->txt_offset == record->txt_length)
return false;
/* number of characters needed for len, addr and type entries */
record->txt_offset++;
if (record->txt_offset + 8 > record->txt_length) {
record->error = true;
return false;
}
record->len = usb6fire_fw_ihex_hex(record->txt_data +
record->txt_offset, &crc);
record->txt_offset += 2;
record->address = usb6fire_fw_ihex_hex(record->txt_data +
record->txt_offset, &crc) << 8;
record->txt_offset += 2;
record->address |= usb6fire_fw_ihex_hex(record->txt_data +
record->txt_offset, &crc);
record->txt_offset += 2;
type = usb6fire_fw_ihex_hex(record->txt_data +
record->txt_offset, &crc);
record->txt_offset += 2;
/* number of characters needed for data and crc entries */
if (record->txt_offset + 2 * (record->len + 1) > record->txt_length) {
record->error = true;
return false;
}
for (i = 0; i < record->len; i++) {
record->data[i] = usb6fire_fw_ihex_hex(record->txt_data
+ record->txt_offset, &crc);
record->txt_offset += 2;
}
usb6fire_fw_ihex_hex(record->txt_data + record->txt_offset, &crc);
if (crc) {
record->error = true;
return false;
}
if (type == 1 || !record->len) /* eof */
return false;
else if (type == 0)
return true;
else {
record->error = true;
return false;
}
}
static int usb6fire_fw_ihex_init(const struct firmware *fw,
struct ihex_record *record)
{
record->txt_data = fw->data;
record->txt_length = fw->size;
record->txt_offset = 0;
record->max_len = 0;
/* read all records, if loop ends, record->error indicates,
* whether ihex is valid. */
while (usb6fire_fw_ihex_next_record(record))
record->max_len = max(record->len, record->max_len);
if (record->error)
return -EINVAL;
record->txt_offset = 0;
return 0;
}
static int usb6fire_fw_ezusb_write(struct usb_device *device,
int type, int value, char *data, int len)
{
int ret;
ret = usb_control_msg(device, usb_sndctrlpipe(device, 0), type,
USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
value, 0, data, len, HZ);
if (ret < 0)
return ret;
else if (ret != len)
return -EIO;
return 0;
}
static int usb6fire_fw_ezusb_read(struct usb_device *device,
int type, int value, char *data, int len)
{
int ret = usb_control_msg(device, usb_rcvctrlpipe(device, 0), type,
USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE, value,
0, data, len, HZ);
if (ret < 0)
return ret;
else if (ret != len)
return -EIO;
return 0;
}
static int usb6fire_fw_fpga_write(struct usb_device *device,
char *data, int len)
{
int actual_len;
int ret;
ret = usb_bulk_msg(device, usb_sndbulkpipe(device, FPGA_EP), data, len,
&actual_len, HZ);
if (ret < 0)
return ret;
else if (actual_len != len)
return -EIO;
return 0;
}
static int usb6fire_fw_ezusb_upload(
struct usb_interface *intf, const char *fwname,
unsigned int postaddr, u8 *postdata, unsigned int postlen)
{
int ret;
u8 data;
struct usb_device *device = interface_to_usbdev(intf);
const struct firmware *fw = NULL;
struct ihex_record *rec = kmalloc(sizeof(struct ihex_record),
GFP_KERNEL);
if (!rec)
return -ENOMEM;
ret = request_firmware(&fw, fwname, &device->dev);
if (ret < 0) {
kfree(rec);
dev_err(&intf->dev,
"error requesting ezusb firmware %s.\n", fwname);
return ret;
}
ret = usb6fire_fw_ihex_init(fw, rec);
if (ret < 0) {
kfree(rec);
release_firmware(fw);
dev_err(&intf->dev,
"error validating ezusb firmware %s.\n", fwname);
return ret;
}
/* upload firmware image */
data = 0x01; /* stop ezusb cpu */
ret = usb6fire_fw_ezusb_write(device, 0xa0, 0xe600, &data, 1);
if (ret < 0) {
kfree(rec);
release_firmware(fw);
dev_err(&intf->dev,
"unable to upload ezusb firmware %s: begin message.\n",
fwname);
return ret;
}
while (usb6fire_fw_ihex_next_record(rec)) { /* write firmware */
ret = usb6fire_fw_ezusb_write(device, 0xa0, rec->address,
rec->data, rec->len);
if (ret < 0) {
kfree(rec);
release_firmware(fw);
dev_err(&intf->dev,
"unable to upload ezusb firmware %s: data urb.\n",
fwname);
return ret;
}
}
release_firmware(fw);
kfree(rec);
if (postdata) { /* write data after firmware has been uploaded */
ret = usb6fire_fw_ezusb_write(device, 0xa0, postaddr,
postdata, postlen);
if (ret < 0) {
dev_err(&intf->dev,
"unable to upload ezusb firmware %s: post urb.\n",
fwname);
return ret;
}
}
data = 0x00; /* resume ezusb cpu */
ret = usb6fire_fw_ezusb_write(device, 0xa0, 0xe600, &data, 1);
if (ret < 0) {
dev_err(&intf->dev,
"unable to upload ezusb firmware %s: end message.\n",
fwname);
return ret;
}
return 0;
}
static int usb6fire_fw_fpga_upload(
struct usb_interface *intf, const char *fwname)
{
int ret;
int i;
struct usb_device *device = interface_to_usbdev(intf);
u8 *buffer = kmalloc(FPGA_BUFSIZE, GFP_KERNEL);
const char *c;
const char *end;
const struct firmware *fw;
if (!buffer)
return -ENOMEM;
ret = request_firmware(&fw, fwname, &device->dev);
if (ret < 0) {
dev_err(&intf->dev, "unable to get fpga firmware %s.\n",
fwname);
kfree(buffer);
return -EIO;
}
c = fw->data;
end = fw->data + fw->size;
ret = usb6fire_fw_ezusb_write(device, 8, 0, NULL, 0);
if (ret < 0) {
kfree(buffer);
release_firmware(fw);
dev_err(&intf->dev,
"unable to upload fpga firmware: begin urb.\n");
return ret;
}
while (c != end) {
for (i = 0; c != end && i < FPGA_BUFSIZE; i++, c++)
buffer[i] = byte_rev_table[(u8) *c];
ret = usb6fire_fw_fpga_write(device, buffer, i);
if (ret < 0) {
release_firmware(fw);
kfree(buffer);
dev_err(&intf->dev,
"unable to upload fpga firmware: fw urb.\n");
return ret;
}
}
release_firmware(fw);
kfree(buffer);
ret = usb6fire_fw_ezusb_write(device, 9, 0, NULL, 0);
if (ret < 0) {
dev_err(&intf->dev,
"unable to upload fpga firmware: end urb.\n");
return ret;
}
return 0;
}
/* check, if the firmware version the devices has currently loaded
* is known by this driver. 'version' needs to have 4 bytes version
* info data. */
static int usb6fire_fw_check(struct usb_interface *intf, const u8 *version)
{
int i;
for (i = 0; i < ARRAY_SIZE(known_fw_versions); i++)
if (!memcmp(version, known_fw_versions + i, 2))
return 0;
dev_err(&intf->dev, "invalid fimware version in device: %4ph. "
"please reconnect to power. if this failure "
"still happens, check your firmware installation.",
version);
return -EINVAL;
}
int usb6fire_fw_init(struct usb_interface *intf)
{
int i;
int ret;
struct usb_device *device = interface_to_usbdev(intf);
/* buffer: 8 receiving bytes from device and
* sizeof(EP_W_MAX_PACKET_SIZE) bytes for non-const copy */
u8 buffer[12];
ret = usb6fire_fw_ezusb_read(device, 1, 0, buffer, 8);
if (ret < 0) {
dev_err(&intf->dev,
"unable to receive device firmware state.\n");
return ret;
}
if (buffer[0] != 0xeb || buffer[1] != 0xaa || buffer[2] != 0x55) {
dev_err(&intf->dev,
"unknown device firmware state received from device:");
for (i = 0; i < 8; i++)
printk(KERN_CONT "%02x ", buffer[i]);
printk(KERN_CONT "\n");
return -EIO;
}
/* do we need fpga loader ezusb firmware? */
if (buffer[3] == 0x01) {
ret = usb6fire_fw_ezusb_upload(intf,
"6fire/dmx6firel2.ihx", 0, NULL, 0);
if (ret < 0)
return ret;
return FW_NOT_READY;
}
/* do we need fpga firmware and application ezusb firmware? */
else if (buffer[3] == 0x02) {
ret = usb6fire_fw_check(intf, buffer + 4);
if (ret < 0)
return ret;
ret = usb6fire_fw_fpga_upload(intf, "6fire/dmx6firecf.bin");
if (ret < 0)
return ret;
memcpy(buffer, ep_w_max_packet_size,
sizeof(ep_w_max_packet_size));
ret = usb6fire_fw_ezusb_upload(intf, "6fire/dmx6fireap.ihx",
0x0003, buffer, sizeof(ep_w_max_packet_size));
if (ret < 0)
return ret;
return FW_NOT_READY;
}
/* all fw loaded? */
else if (buffer[3] == 0x03)
return usb6fire_fw_check(intf, buffer + 4);
/* unknown data? */
else {
dev_err(&intf->dev,
"unknown device firmware state received from device: ");
for (i = 0; i < 8; i++)
printk(KERN_CONT "%02x ", buffer[i]);
printk(KERN_CONT "\n");
return -EIO;
}
return 0;
}

View file

@ -0,0 +1,27 @@
/*
* Linux driver for TerraTec DMX 6Fire USB
*
* Author: Torsten Schenk
* Created: Jan 01, 2011
* Copyright: (C) Torsten Schenk
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*/
#ifndef USB6FIRE_FIRMWARE_H
#define USB6FIRE_FIRMWARE_H
#include "common.h"
enum /* firmware state of device */
{
FW_READY = 0,
FW_NOT_READY = 1
};
int usb6fire_fw_init(struct usb_interface *intf);
#endif /* USB6FIRE_FIRMWARE_H */

218
sound/usb/6fire/midi.c Normal file
View file

@ -0,0 +1,218 @@
/*
* Linux driver for TerraTec DMX 6Fire USB
*
* Rawmidi driver
*
* Author: Torsten Schenk <torsten.schenk@zoho.com>
* Created: Jan 01, 2011
* Copyright: (C) Torsten Schenk
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*/
#include <sound/rawmidi.h>
#include "midi.h"
#include "chip.h"
#include "comm.h"
enum {
MIDI_BUFSIZE = 64
};
static void usb6fire_midi_out_handler(struct urb *urb)
{
struct midi_runtime *rt = urb->context;
int ret;
unsigned long flags;
spin_lock_irqsave(&rt->out_lock, flags);
if (rt->out) {
ret = snd_rawmidi_transmit(rt->out, rt->out_buffer + 4,
MIDI_BUFSIZE - 4);
if (ret > 0) { /* more data available, send next packet */
rt->out_buffer[1] = ret + 2;
rt->out_buffer[3] = rt->out_serial++;
urb->transfer_buffer_length = ret + 4;
ret = usb_submit_urb(urb, GFP_ATOMIC);
if (ret < 0)
dev_err(&urb->dev->dev,
"midi out urb submit failed: %d\n",
ret);
} else /* no more data to transmit */
rt->out = NULL;
}
spin_unlock_irqrestore(&rt->out_lock, flags);
}
static void usb6fire_midi_in_received(
struct midi_runtime *rt, u8 *data, int length)
{
unsigned long flags;
spin_lock_irqsave(&rt->in_lock, flags);
if (rt->in)
snd_rawmidi_receive(rt->in, data, length);
spin_unlock_irqrestore(&rt->in_lock, flags);
}
static int usb6fire_midi_out_open(struct snd_rawmidi_substream *alsa_sub)
{
return 0;
}
static int usb6fire_midi_out_close(struct snd_rawmidi_substream *alsa_sub)
{
return 0;
}
static void usb6fire_midi_out_trigger(
struct snd_rawmidi_substream *alsa_sub, int up)
{
struct midi_runtime *rt = alsa_sub->rmidi->private_data;
struct urb *urb = &rt->out_urb;
__s8 ret;
unsigned long flags;
spin_lock_irqsave(&rt->out_lock, flags);
if (up) { /* start transfer */
if (rt->out) { /* we are already transmitting so just return */
spin_unlock_irqrestore(&rt->out_lock, flags);
return;
}
ret = snd_rawmidi_transmit(alsa_sub, rt->out_buffer + 4,
MIDI_BUFSIZE - 4);
if (ret > 0) {
rt->out_buffer[1] = ret + 2;
rt->out_buffer[3] = rt->out_serial++;
urb->transfer_buffer_length = ret + 4;
ret = usb_submit_urb(urb, GFP_ATOMIC);
if (ret < 0)
dev_err(&urb->dev->dev,
"midi out urb submit failed: %d\n",
ret);
else
rt->out = alsa_sub;
}
} else if (rt->out == alsa_sub)
rt->out = NULL;
spin_unlock_irqrestore(&rt->out_lock, flags);
}
static void usb6fire_midi_out_drain(struct snd_rawmidi_substream *alsa_sub)
{
struct midi_runtime *rt = alsa_sub->rmidi->private_data;
int retry = 0;
while (rt->out && retry++ < 100)
msleep(10);
}
static int usb6fire_midi_in_open(struct snd_rawmidi_substream *alsa_sub)
{
return 0;
}
static int usb6fire_midi_in_close(struct snd_rawmidi_substream *alsa_sub)
{
return 0;
}
static void usb6fire_midi_in_trigger(
struct snd_rawmidi_substream *alsa_sub, int up)
{
struct midi_runtime *rt = alsa_sub->rmidi->private_data;
unsigned long flags;
spin_lock_irqsave(&rt->in_lock, flags);
if (up)
rt->in = alsa_sub;
else
rt->in = NULL;
spin_unlock_irqrestore(&rt->in_lock, flags);
}
static struct snd_rawmidi_ops out_ops = {
.open = usb6fire_midi_out_open,
.close = usb6fire_midi_out_close,
.trigger = usb6fire_midi_out_trigger,
.drain = usb6fire_midi_out_drain
};
static struct snd_rawmidi_ops in_ops = {
.open = usb6fire_midi_in_open,
.close = usb6fire_midi_in_close,
.trigger = usb6fire_midi_in_trigger
};
int usb6fire_midi_init(struct sfire_chip *chip)
{
int ret;
struct midi_runtime *rt = kzalloc(sizeof(struct midi_runtime),
GFP_KERNEL);
struct comm_runtime *comm_rt = chip->comm;
if (!rt)
return -ENOMEM;
rt->out_buffer = kzalloc(MIDI_BUFSIZE, GFP_KERNEL);
if (!rt->out_buffer) {
kfree(rt);
return -ENOMEM;
}
rt->chip = chip;
rt->in_received = usb6fire_midi_in_received;
rt->out_buffer[0] = 0x80; /* 'send midi' command */
rt->out_buffer[1] = 0x00; /* size of data */
rt->out_buffer[2] = 0x00; /* always 0 */
spin_lock_init(&rt->in_lock);
spin_lock_init(&rt->out_lock);
comm_rt->init_urb(comm_rt, &rt->out_urb, rt->out_buffer, rt,
usb6fire_midi_out_handler);
ret = snd_rawmidi_new(chip->card, "6FireUSB", 0, 1, 1, &rt->instance);
if (ret < 0) {
kfree(rt->out_buffer);
kfree(rt);
dev_err(&chip->dev->dev, "unable to create midi.\n");
return ret;
}
rt->instance->private_data = rt;
strcpy(rt->instance->name, "DMX6FireUSB MIDI");
rt->instance->info_flags = SNDRV_RAWMIDI_INFO_OUTPUT |
SNDRV_RAWMIDI_INFO_INPUT |
SNDRV_RAWMIDI_INFO_DUPLEX;
snd_rawmidi_set_ops(rt->instance, SNDRV_RAWMIDI_STREAM_OUTPUT,
&out_ops);
snd_rawmidi_set_ops(rt->instance, SNDRV_RAWMIDI_STREAM_INPUT,
&in_ops);
chip->midi = rt;
return 0;
}
void usb6fire_midi_abort(struct sfire_chip *chip)
{
struct midi_runtime *rt = chip->midi;
if (rt)
usb_poison_urb(&rt->out_urb);
}
void usb6fire_midi_destroy(struct sfire_chip *chip)
{
struct midi_runtime *rt = chip->midi;
kfree(rt->out_buffer);
kfree(rt);
chip->midi = NULL;
}

41
sound/usb/6fire/midi.h Normal file
View file

@ -0,0 +1,41 @@
/*
* Linux driver for TerraTec DMX 6Fire USB
*
* Author: Torsten Schenk <torsten.schenk@zoho.com>
* Created: Jan 01, 2011
* Copyright: (C) Torsten Schenk
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*/
#ifndef USB6FIRE_MIDI_H
#define USB6FIRE_MIDI_H
#include "common.h"
struct midi_runtime {
struct sfire_chip *chip;
struct snd_rawmidi *instance;
struct snd_rawmidi_substream *in;
char in_active;
spinlock_t in_lock;
spinlock_t out_lock;
struct snd_rawmidi_substream *out;
struct urb out_urb;
u8 out_serial; /* serial number of out packet */
u8 *out_buffer;
int buffer_offset;
void (*in_received)(struct midi_runtime *rt, u8 *data, int length);
};
int usb6fire_midi_init(struct sfire_chip *chip);
void usb6fire_midi_abort(struct sfire_chip *chip);
void usb6fire_midi_destroy(struct sfire_chip *chip);
#endif /* USB6FIRE_MIDI_H */

717
sound/usb/6fire/pcm.c Normal file
View file

@ -0,0 +1,717 @@
/*
* Linux driver for TerraTec DMX 6Fire USB
*
* PCM driver
*
* Author: Torsten Schenk <torsten.schenk@zoho.com>
* Created: Jan 01, 2011
* Copyright: (C) Torsten Schenk
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*/
#include "pcm.h"
#include "chip.h"
#include "comm.h"
#include "control.h"
enum {
OUT_N_CHANNELS = 6, IN_N_CHANNELS = 4
};
/* keep next two synced with
* FW_EP_W_MAX_PACKET_SIZE[] and RATES_MAX_PACKET_SIZE
* and CONTROL_RATE_XXX in control.h */
static const int rates_in_packet_size[] = { 228, 228, 420, 420, 404, 404 };
static const int rates_out_packet_size[] = { 228, 228, 420, 420, 604, 604 };
static const int rates[] = { 44100, 48000, 88200, 96000, 176400, 192000 };
static const int rates_alsaid[] = {
SNDRV_PCM_RATE_44100, SNDRV_PCM_RATE_48000,
SNDRV_PCM_RATE_88200, SNDRV_PCM_RATE_96000,
SNDRV_PCM_RATE_176400, SNDRV_PCM_RATE_192000 };
enum { /* settings for pcm */
OUT_EP = 6, IN_EP = 2, MAX_BUFSIZE = 128 * 1024
};
enum { /* pcm streaming states */
STREAM_DISABLED, /* no pcm streaming */
STREAM_STARTING, /* pcm streaming requested, waiting to become ready */
STREAM_RUNNING, /* pcm streaming running */
STREAM_STOPPING
};
static const struct snd_pcm_hardware pcm_hw = {
.info = SNDRV_PCM_INFO_MMAP |
SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_BLOCK_TRANSFER |
SNDRV_PCM_INFO_MMAP_VALID |
SNDRV_PCM_INFO_BATCH,
.formats = SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE,
.rates = SNDRV_PCM_RATE_44100 |
SNDRV_PCM_RATE_48000 |
SNDRV_PCM_RATE_88200 |
SNDRV_PCM_RATE_96000 |
SNDRV_PCM_RATE_176400 |
SNDRV_PCM_RATE_192000,
.rate_min = 44100,
.rate_max = 192000,
.channels_min = 1,
.channels_max = 0, /* set in pcm_open, depending on capture/playback */
.buffer_bytes_max = MAX_BUFSIZE,
.period_bytes_min = PCM_N_PACKETS_PER_URB * (PCM_MAX_PACKET_SIZE - 4),
.period_bytes_max = MAX_BUFSIZE,
.periods_min = 2,
.periods_max = 1024
};
static int usb6fire_pcm_set_rate(struct pcm_runtime *rt)
{
int ret;
struct control_runtime *ctrl_rt = rt->chip->control;
ctrl_rt->usb_streaming = false;
ret = ctrl_rt->update_streaming(ctrl_rt);
if (ret < 0) {
dev_err(&rt->chip->dev->dev,
"error stopping streaming while setting samplerate %d.\n",
rates[rt->rate]);
return ret;
}
ret = ctrl_rt->set_rate(ctrl_rt, rt->rate);
if (ret < 0) {
dev_err(&rt->chip->dev->dev,
"error setting samplerate %d.\n",
rates[rt->rate]);
return ret;
}
ret = ctrl_rt->set_channels(ctrl_rt, OUT_N_CHANNELS, IN_N_CHANNELS,
false, false);
if (ret < 0) {
dev_err(&rt->chip->dev->dev,
"error initializing channels while setting samplerate %d.\n",
rates[rt->rate]);
return ret;
}
ctrl_rt->usb_streaming = true;
ret = ctrl_rt->update_streaming(ctrl_rt);
if (ret < 0) {
dev_err(&rt->chip->dev->dev,
"error starting streaming while setting samplerate %d.\n",
rates[rt->rate]);
return ret;
}
rt->in_n_analog = IN_N_CHANNELS;
rt->out_n_analog = OUT_N_CHANNELS;
rt->in_packet_size = rates_in_packet_size[rt->rate];
rt->out_packet_size = rates_out_packet_size[rt->rate];
return 0;
}
static struct pcm_substream *usb6fire_pcm_get_substream(
struct snd_pcm_substream *alsa_sub)
{
struct pcm_runtime *rt = snd_pcm_substream_chip(alsa_sub);
if (alsa_sub->stream == SNDRV_PCM_STREAM_PLAYBACK)
return &rt->playback;
else if (alsa_sub->stream == SNDRV_PCM_STREAM_CAPTURE)
return &rt->capture;
dev_err(&rt->chip->dev->dev, "error getting pcm substream slot.\n");
return NULL;
}
/* call with stream_mutex locked */
static void usb6fire_pcm_stream_stop(struct pcm_runtime *rt)
{
int i;
struct control_runtime *ctrl_rt = rt->chip->control;
if (rt->stream_state != STREAM_DISABLED) {
rt->stream_state = STREAM_STOPPING;
for (i = 0; i < PCM_N_URBS; i++) {
usb_kill_urb(&rt->in_urbs[i].instance);
usb_kill_urb(&rt->out_urbs[i].instance);
}
ctrl_rt->usb_streaming = false;
ctrl_rt->update_streaming(ctrl_rt);
rt->stream_state = STREAM_DISABLED;
}
}
/* call with stream_mutex locked */
static int usb6fire_pcm_stream_start(struct pcm_runtime *rt)
{
int ret;
int i;
int k;
struct usb_iso_packet_descriptor *packet;
if (rt->stream_state == STREAM_DISABLED) {
/* submit our in urbs */
rt->stream_wait_cond = false;
rt->stream_state = STREAM_STARTING;
for (i = 0; i < PCM_N_URBS; i++) {
for (k = 0; k < PCM_N_PACKETS_PER_URB; k++) {
packet = &rt->in_urbs[i].packets[k];
packet->offset = k * rt->in_packet_size;
packet->length = rt->in_packet_size;
packet->actual_length = 0;
packet->status = 0;
}
ret = usb_submit_urb(&rt->in_urbs[i].instance,
GFP_ATOMIC);
if (ret) {
usb6fire_pcm_stream_stop(rt);
return ret;
}
}
/* wait for first out urb to return (sent in in urb handler) */
wait_event_timeout(rt->stream_wait_queue, rt->stream_wait_cond,
HZ);
if (rt->stream_wait_cond)
rt->stream_state = STREAM_RUNNING;
else {
usb6fire_pcm_stream_stop(rt);
return -EIO;
}
}
return 0;
}
/* call with substream locked */
static void usb6fire_pcm_capture(struct pcm_substream *sub, struct pcm_urb *urb)
{
int i;
int frame;
int frame_count;
unsigned int total_length = 0;
struct pcm_runtime *rt = snd_pcm_substream_chip(sub->instance);
struct snd_pcm_runtime *alsa_rt = sub->instance->runtime;
u32 *src = NULL;
u32 *dest = (u32 *) (alsa_rt->dma_area + sub->dma_off
* (alsa_rt->frame_bits >> 3));
u32 *dest_end = (u32 *) (alsa_rt->dma_area + alsa_rt->buffer_size
* (alsa_rt->frame_bits >> 3));
int bytes_per_frame = alsa_rt->channels << 2;
for (i = 0; i < PCM_N_PACKETS_PER_URB; i++) {
/* at least 4 header bytes for valid packet.
* after that: 32 bits per sample for analog channels */
if (urb->packets[i].actual_length > 4)
frame_count = (urb->packets[i].actual_length - 4)
/ (rt->in_n_analog << 2);
else
frame_count = 0;
if (alsa_rt->format == SNDRV_PCM_FORMAT_S24_LE)
src = (u32 *) (urb->buffer + total_length);
else if (alsa_rt->format == SNDRV_PCM_FORMAT_S32_LE)
src = (u32 *) (urb->buffer - 1 + total_length);
else
return;
src++; /* skip leading 4 bytes of every packet */
total_length += urb->packets[i].length;
for (frame = 0; frame < frame_count; frame++) {
memcpy(dest, src, bytes_per_frame);
dest += alsa_rt->channels;
src += rt->in_n_analog;
sub->dma_off++;
sub->period_off++;
if (dest == dest_end) {
sub->dma_off = 0;
dest = (u32 *) alsa_rt->dma_area;
}
}
}
}
/* call with substream locked */
static void usb6fire_pcm_playback(struct pcm_substream *sub,
struct pcm_urb *urb)
{
int i;
int frame;
int frame_count;
struct pcm_runtime *rt = snd_pcm_substream_chip(sub->instance);
struct snd_pcm_runtime *alsa_rt = sub->instance->runtime;
u32 *src = (u32 *) (alsa_rt->dma_area + sub->dma_off
* (alsa_rt->frame_bits >> 3));
u32 *src_end = (u32 *) (alsa_rt->dma_area + alsa_rt->buffer_size
* (alsa_rt->frame_bits >> 3));
u32 *dest;
int bytes_per_frame = alsa_rt->channels << 2;
if (alsa_rt->format == SNDRV_PCM_FORMAT_S32_LE)
dest = (u32 *) (urb->buffer - 1);
else if (alsa_rt->format == SNDRV_PCM_FORMAT_S24_LE)
dest = (u32 *) (urb->buffer);
else {
dev_err(&rt->chip->dev->dev, "Unknown sample format.");
return;
}
for (i = 0; i < PCM_N_PACKETS_PER_URB; i++) {
/* at least 4 header bytes for valid packet.
* after that: 32 bits per sample for analog channels */
if (urb->packets[i].length > 4)
frame_count = (urb->packets[i].length - 4)
/ (rt->out_n_analog << 2);
else
frame_count = 0;
dest++; /* skip leading 4 bytes of every frame */
for (frame = 0; frame < frame_count; frame++) {
memcpy(dest, src, bytes_per_frame);
src += alsa_rt->channels;
dest += rt->out_n_analog;
sub->dma_off++;
sub->period_off++;
if (src == src_end) {
src = (u32 *) alsa_rt->dma_area;
sub->dma_off = 0;
}
}
}
}
static void usb6fire_pcm_in_urb_handler(struct urb *usb_urb)
{
struct pcm_urb *in_urb = usb_urb->context;
struct pcm_urb *out_urb = in_urb->peer;
struct pcm_runtime *rt = in_urb->chip->pcm;
struct pcm_substream *sub;
unsigned long flags;
int total_length = 0;
int frame_count;
int frame;
int channel;
int i;
u8 *dest;
if (usb_urb->status || rt->panic || rt->stream_state == STREAM_STOPPING)
return;
for (i = 0; i < PCM_N_PACKETS_PER_URB; i++)
if (in_urb->packets[i].status) {
rt->panic = true;
return;
}
if (rt->stream_state == STREAM_DISABLED) {
dev_err(&rt->chip->dev->dev,
"internal error: stream disabled in in-urb handler.\n");
return;
}
/* receive our capture data */
sub = &rt->capture;
spin_lock_irqsave(&sub->lock, flags);
if (sub->active) {
usb6fire_pcm_capture(sub, in_urb);
if (sub->period_off >= sub->instance->runtime->period_size) {
sub->period_off %= sub->instance->runtime->period_size;
spin_unlock_irqrestore(&sub->lock, flags);
snd_pcm_period_elapsed(sub->instance);
} else
spin_unlock_irqrestore(&sub->lock, flags);
} else
spin_unlock_irqrestore(&sub->lock, flags);
/* setup out urb structure */
for (i = 0; i < PCM_N_PACKETS_PER_URB; i++) {
out_urb->packets[i].offset = total_length;
out_urb->packets[i].length = (in_urb->packets[i].actual_length
- 4) / (rt->in_n_analog << 2)
* (rt->out_n_analog << 2) + 4;
out_urb->packets[i].status = 0;
total_length += out_urb->packets[i].length;
}
memset(out_urb->buffer, 0, total_length);
/* now send our playback data (if a free out urb was found) */
sub = &rt->playback;
spin_lock_irqsave(&sub->lock, flags);
if (sub->active) {
usb6fire_pcm_playback(sub, out_urb);
if (sub->period_off >= sub->instance->runtime->period_size) {
sub->period_off %= sub->instance->runtime->period_size;
spin_unlock_irqrestore(&sub->lock, flags);
snd_pcm_period_elapsed(sub->instance);
} else
spin_unlock_irqrestore(&sub->lock, flags);
} else
spin_unlock_irqrestore(&sub->lock, flags);
/* setup the 4th byte of each sample (0x40 for analog channels) */
dest = out_urb->buffer;
for (i = 0; i < PCM_N_PACKETS_PER_URB; i++)
if (out_urb->packets[i].length >= 4) {
frame_count = (out_urb->packets[i].length - 4)
/ (rt->out_n_analog << 2);
*(dest++) = 0xaa;
*(dest++) = 0xaa;
*(dest++) = frame_count;
*(dest++) = 0x00;
for (frame = 0; frame < frame_count; frame++)
for (channel = 0;
channel < rt->out_n_analog;
channel++) {
dest += 3; /* skip sample data */
*(dest++) = 0x40;
}
}
usb_submit_urb(&out_urb->instance, GFP_ATOMIC);
usb_submit_urb(&in_urb->instance, GFP_ATOMIC);
}
static void usb6fire_pcm_out_urb_handler(struct urb *usb_urb)
{
struct pcm_urb *urb = usb_urb->context;
struct pcm_runtime *rt = urb->chip->pcm;
if (rt->stream_state == STREAM_STARTING) {
rt->stream_wait_cond = true;
wake_up(&rt->stream_wait_queue);
}
}
static int usb6fire_pcm_open(struct snd_pcm_substream *alsa_sub)
{
struct pcm_runtime *rt = snd_pcm_substream_chip(alsa_sub);
struct pcm_substream *sub = NULL;
struct snd_pcm_runtime *alsa_rt = alsa_sub->runtime;
if (rt->panic)
return -EPIPE;
mutex_lock(&rt->stream_mutex);
alsa_rt->hw = pcm_hw;
if (alsa_sub->stream == SNDRV_PCM_STREAM_PLAYBACK) {
if (rt->rate < ARRAY_SIZE(rates))
alsa_rt->hw.rates = rates_alsaid[rt->rate];
alsa_rt->hw.channels_max = OUT_N_CHANNELS;
sub = &rt->playback;
} else if (alsa_sub->stream == SNDRV_PCM_STREAM_CAPTURE) {
if (rt->rate < ARRAY_SIZE(rates))
alsa_rt->hw.rates = rates_alsaid[rt->rate];
alsa_rt->hw.channels_max = IN_N_CHANNELS;
sub = &rt->capture;
}
if (!sub) {
mutex_unlock(&rt->stream_mutex);
dev_err(&rt->chip->dev->dev, "invalid stream type.\n");
return -EINVAL;
}
sub->instance = alsa_sub;
sub->active = false;
mutex_unlock(&rt->stream_mutex);
return 0;
}
static int usb6fire_pcm_close(struct snd_pcm_substream *alsa_sub)
{
struct pcm_runtime *rt = snd_pcm_substream_chip(alsa_sub);
struct pcm_substream *sub = usb6fire_pcm_get_substream(alsa_sub);
unsigned long flags;
if (rt->panic)
return 0;
mutex_lock(&rt->stream_mutex);
if (sub) {
/* deactivate substream */
spin_lock_irqsave(&sub->lock, flags);
sub->instance = NULL;
sub->active = false;
spin_unlock_irqrestore(&sub->lock, flags);
/* all substreams closed? if so, stop streaming */
if (!rt->playback.instance && !rt->capture.instance) {
usb6fire_pcm_stream_stop(rt);
rt->rate = ARRAY_SIZE(rates);
}
}
mutex_unlock(&rt->stream_mutex);
return 0;
}
static int usb6fire_pcm_hw_params(struct snd_pcm_substream *alsa_sub,
struct snd_pcm_hw_params *hw_params)
{
return snd_pcm_lib_alloc_vmalloc_buffer(alsa_sub,
params_buffer_bytes(hw_params));
}
static int usb6fire_pcm_hw_free(struct snd_pcm_substream *alsa_sub)
{
return snd_pcm_lib_free_vmalloc_buffer(alsa_sub);
}
static int usb6fire_pcm_prepare(struct snd_pcm_substream *alsa_sub)
{
struct pcm_runtime *rt = snd_pcm_substream_chip(alsa_sub);
struct pcm_substream *sub = usb6fire_pcm_get_substream(alsa_sub);
struct snd_pcm_runtime *alsa_rt = alsa_sub->runtime;
int ret;
if (rt->panic)
return -EPIPE;
if (!sub)
return -ENODEV;
mutex_lock(&rt->stream_mutex);
sub->dma_off = 0;
sub->period_off = 0;
if (rt->stream_state == STREAM_DISABLED) {
for (rt->rate = 0; rt->rate < ARRAY_SIZE(rates); rt->rate++)
if (alsa_rt->rate == rates[rt->rate])
break;
if (rt->rate == ARRAY_SIZE(rates)) {
mutex_unlock(&rt->stream_mutex);
dev_err(&rt->chip->dev->dev,
"invalid rate %d in prepare.\n",
alsa_rt->rate);
return -EINVAL;
}
ret = usb6fire_pcm_set_rate(rt);
if (ret) {
mutex_unlock(&rt->stream_mutex);
return ret;
}
ret = usb6fire_pcm_stream_start(rt);
if (ret) {
mutex_unlock(&rt->stream_mutex);
dev_err(&rt->chip->dev->dev,
"could not start pcm stream.\n");
return ret;
}
}
mutex_unlock(&rt->stream_mutex);
return 0;
}
static int usb6fire_pcm_trigger(struct snd_pcm_substream *alsa_sub, int cmd)
{
struct pcm_substream *sub = usb6fire_pcm_get_substream(alsa_sub);
struct pcm_runtime *rt = snd_pcm_substream_chip(alsa_sub);
unsigned long flags;
if (rt->panic)
return -EPIPE;
if (!sub)
return -ENODEV;
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
spin_lock_irqsave(&sub->lock, flags);
sub->active = true;
spin_unlock_irqrestore(&sub->lock, flags);
return 0;
case SNDRV_PCM_TRIGGER_STOP:
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
spin_lock_irqsave(&sub->lock, flags);
sub->active = false;
spin_unlock_irqrestore(&sub->lock, flags);
return 0;
default:
return -EINVAL;
}
}
static snd_pcm_uframes_t usb6fire_pcm_pointer(
struct snd_pcm_substream *alsa_sub)
{
struct pcm_substream *sub = usb6fire_pcm_get_substream(alsa_sub);
struct pcm_runtime *rt = snd_pcm_substream_chip(alsa_sub);
unsigned long flags;
snd_pcm_uframes_t ret;
if (rt->panic || !sub)
return SNDRV_PCM_POS_XRUN;
spin_lock_irqsave(&sub->lock, flags);
ret = sub->dma_off;
spin_unlock_irqrestore(&sub->lock, flags);
return ret;
}
static struct snd_pcm_ops pcm_ops = {
.open = usb6fire_pcm_open,
.close = usb6fire_pcm_close,
.ioctl = snd_pcm_lib_ioctl,
.hw_params = usb6fire_pcm_hw_params,
.hw_free = usb6fire_pcm_hw_free,
.prepare = usb6fire_pcm_prepare,
.trigger = usb6fire_pcm_trigger,
.pointer = usb6fire_pcm_pointer,
.page = snd_pcm_lib_get_vmalloc_page,
.mmap = snd_pcm_lib_mmap_vmalloc,
};
static void usb6fire_pcm_init_urb(struct pcm_urb *urb,
struct sfire_chip *chip, bool in, int ep,
void (*handler)(struct urb *))
{
urb->chip = chip;
usb_init_urb(&urb->instance);
urb->instance.transfer_buffer = urb->buffer;
urb->instance.transfer_buffer_length =
PCM_N_PACKETS_PER_URB * PCM_MAX_PACKET_SIZE;
urb->instance.dev = chip->dev;
urb->instance.pipe = in ? usb_rcvisocpipe(chip->dev, ep)
: usb_sndisocpipe(chip->dev, ep);
urb->instance.interval = 1;
urb->instance.complete = handler;
urb->instance.context = urb;
urb->instance.number_of_packets = PCM_N_PACKETS_PER_URB;
}
static int usb6fire_pcm_buffers_init(struct pcm_runtime *rt)
{
int i;
for (i = 0; i < PCM_N_URBS; i++) {
rt->out_urbs[i].buffer = kzalloc(PCM_N_PACKETS_PER_URB
* PCM_MAX_PACKET_SIZE, GFP_KERNEL);
if (!rt->out_urbs[i].buffer)
return -ENOMEM;
rt->in_urbs[i].buffer = kzalloc(PCM_N_PACKETS_PER_URB
* PCM_MAX_PACKET_SIZE, GFP_KERNEL);
if (!rt->in_urbs[i].buffer)
return -ENOMEM;
}
return 0;
}
static void usb6fire_pcm_buffers_destroy(struct pcm_runtime *rt)
{
int i;
for (i = 0; i < PCM_N_URBS; i++) {
kfree(rt->out_urbs[i].buffer);
kfree(rt->in_urbs[i].buffer);
}
}
int usb6fire_pcm_init(struct sfire_chip *chip)
{
int i;
int ret;
struct snd_pcm *pcm;
struct pcm_runtime *rt =
kzalloc(sizeof(struct pcm_runtime), GFP_KERNEL);
if (!rt)
return -ENOMEM;
ret = usb6fire_pcm_buffers_init(rt);
if (ret) {
usb6fire_pcm_buffers_destroy(rt);
kfree(rt);
return ret;
}
rt->chip = chip;
rt->stream_state = STREAM_DISABLED;
rt->rate = ARRAY_SIZE(rates);
init_waitqueue_head(&rt->stream_wait_queue);
mutex_init(&rt->stream_mutex);
spin_lock_init(&rt->playback.lock);
spin_lock_init(&rt->capture.lock);
for (i = 0; i < PCM_N_URBS; i++) {
usb6fire_pcm_init_urb(&rt->in_urbs[i], chip, true, IN_EP,
usb6fire_pcm_in_urb_handler);
usb6fire_pcm_init_urb(&rt->out_urbs[i], chip, false, OUT_EP,
usb6fire_pcm_out_urb_handler);
rt->in_urbs[i].peer = &rt->out_urbs[i];
rt->out_urbs[i].peer = &rt->in_urbs[i];
}
ret = snd_pcm_new(chip->card, "DMX6FireUSB", 0, 1, 1, &pcm);
if (ret < 0) {
usb6fire_pcm_buffers_destroy(rt);
kfree(rt);
dev_err(&chip->dev->dev, "cannot create pcm instance.\n");
return ret;
}
pcm->private_data = rt;
strcpy(pcm->name, "DMX 6Fire USB");
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &pcm_ops);
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &pcm_ops);
if (ret) {
usb6fire_pcm_buffers_destroy(rt);
kfree(rt);
dev_err(&chip->dev->dev,
"error preallocating pcm buffers.\n");
return ret;
}
rt->instance = pcm;
chip->pcm = rt;
return 0;
}
void usb6fire_pcm_abort(struct sfire_chip *chip)
{
struct pcm_runtime *rt = chip->pcm;
unsigned long flags;
int i;
if (rt) {
rt->panic = true;
if (rt->playback.instance) {
snd_pcm_stream_lock_irqsave(rt->playback.instance, flags);
snd_pcm_stop(rt->playback.instance,
SNDRV_PCM_STATE_XRUN);
snd_pcm_stream_unlock_irqrestore(rt->playback.instance, flags);
}
if (rt->capture.instance) {
snd_pcm_stream_lock_irqsave(rt->capture.instance, flags);
snd_pcm_stop(rt->capture.instance,
SNDRV_PCM_STATE_XRUN);
snd_pcm_stream_unlock_irqrestore(rt->capture.instance, flags);
}
for (i = 0; i < PCM_N_URBS; i++) {
usb_poison_urb(&rt->in_urbs[i].instance);
usb_poison_urb(&rt->out_urbs[i].instance);
}
}
}
void usb6fire_pcm_destroy(struct sfire_chip *chip)
{
struct pcm_runtime *rt = chip->pcm;
usb6fire_pcm_buffers_destroy(rt);
kfree(rt);
chip->pcm = NULL;
}

75
sound/usb/6fire/pcm.h Normal file
View file

@ -0,0 +1,75 @@
/*
* Linux driver for TerraTec DMX 6Fire USB
*
* Author: Torsten Schenk <torsten.schenk@zoho.com>
* Created: Jan 01, 2011
* Copyright: (C) Torsten Schenk
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*/
#ifndef USB6FIRE_PCM_H
#define USB6FIRE_PCM_H
#include <sound/pcm.h>
#include <linux/mutex.h>
#include "common.h"
enum /* settings for pcm */
{
/* maximum of EP_W_MAX_PACKET_SIZE[] (see firmware.c) */
PCM_N_URBS = 16, PCM_N_PACKETS_PER_URB = 8, PCM_MAX_PACKET_SIZE = 604
};
struct pcm_urb {
struct sfire_chip *chip;
/* BEGIN DO NOT SEPARATE */
struct urb instance;
struct usb_iso_packet_descriptor packets[PCM_N_PACKETS_PER_URB];
/* END DO NOT SEPARATE */
u8 *buffer;
struct pcm_urb *peer;
};
struct pcm_substream {
spinlock_t lock;
struct snd_pcm_substream *instance;
bool active;
snd_pcm_uframes_t dma_off; /* current position in alsa dma_area */
snd_pcm_uframes_t period_off; /* current position in current period */
};
struct pcm_runtime {
struct sfire_chip *chip;
struct snd_pcm *instance;
struct pcm_substream playback;
struct pcm_substream capture;
bool panic; /* if set driver won't do anymore pcm on device */
struct pcm_urb in_urbs[PCM_N_URBS];
struct pcm_urb out_urbs[PCM_N_URBS];
int in_packet_size;
int out_packet_size;
int in_n_analog; /* number of analog channels soundcard sends */
int out_n_analog; /* number of analog channels soundcard receives */
struct mutex stream_mutex;
u8 stream_state; /* one of STREAM_XXX (pcm.c) */
u8 rate; /* one of PCM_RATE_XXX */
wait_queue_head_t stream_wait_queue;
bool stream_wait_cond;
};
int usb6fire_pcm_init(struct sfire_chip *chip);
void usb6fire_pcm_abort(struct sfire_chip *chip);
void usb6fire_pcm_destroy(struct sfire_chip *chip);
#endif /* USB6FIRE_PCM_H */

164
sound/usb/Kconfig Normal file
View file

@ -0,0 +1,164 @@
# ALSA USB drivers
menuconfig SND_USB
bool "USB sound devices"
depends on USB
default y
help
Support for sound devices connected via the USB bus.
if SND_USB && USB
config SND_USB_AUDIO
tristate "USB Audio/MIDI driver"
select SND_HWDEP
select SND_RAWMIDI
select SND_PCM
select BITREVERSE
help
Say Y here to include support for USB audio and USB MIDI
devices.
To compile this driver as a module, choose M here: the module
will be called snd-usb-audio.
config SND_USB_UA101
tristate "Edirol UA-101/UA-1000 driver"
select SND_PCM
select SND_RAWMIDI
help
Say Y here to include support for the Edirol UA-101 and UA-1000
audio/MIDI interfaces.
To compile this driver as a module, choose M here: the module
will be called snd-ua101.
config SND_USB_USX2Y
tristate "Tascam US-122, US-224 and US-428 USB driver"
depends on X86 || PPC || ALPHA
select SND_HWDEP
select SND_RAWMIDI
select SND_PCM
help
Say Y here to include support for Tascam USB Audio/MIDI
interfaces or controllers US-122, US-224 and US-428.
To compile this driver as a module, choose M here: the module
will be called snd-usb-usx2y.
config SND_USB_CAIAQ
tristate "Native Instruments USB audio devices"
select SND_HWDEP
select SND_RAWMIDI
select SND_PCM
help
Say Y here to include support for caiaq USB audio interfaces,
namely:
* Native Instruments RigKontrol2
* Native Instruments RigKontrol3
* Native Instruments Kore Controller
* Native Instruments Kore Controller 2
* Native Instruments Audio Kontrol 1
* Native Instruments Audio 2 DJ
* Native Instruments Audio 4 DJ
* Native Instruments Audio 8 DJ
* Native Instruments Traktor Audio 2
* Native Instruments Guitar Rig Session I/O
* Native Instruments Guitar Rig mobile
* Native Instruments Traktor Kontrol X1
* Native Instruments Traktor Kontrol S4
* Native Instruments Maschine Controller
To compile this driver as a module, choose M here: the module
will be called snd-usb-caiaq.
config SND_USB_CAIAQ_INPUT
bool "enable input device for controllers"
depends on SND_USB_CAIAQ
depends on INPUT=y || INPUT=SND_USB_CAIAQ
help
Say Y here to support input controllers like buttons, knobs,
alpha dials and analog pedals on the following products:
* Native Instruments RigKontrol2
* Native Instruments RigKontrol3
* Native Instruments Kore Controller
* Native Instruments Kore Controller 2
* Native Instruments Audio Kontrol 1
* Native Instruments Traktor Kontrol S4
* Native Instruments Maschine Controller
config SND_USB_US122L
tristate "Tascam US-122L USB driver"
depends on X86
select SND_HWDEP
select SND_RAWMIDI
help
Say Y here to include support for Tascam US-122L USB Audio/MIDI
interfaces.
To compile this driver as a module, choose M here: the module
will be called snd-usb-us122l.
config SND_USB_6FIRE
tristate "TerraTec DMX 6Fire USB"
select FW_LOADER
select BITREVERSE
select SND_RAWMIDI
select SND_PCM
select SND_VMASTER
help
Say Y here to include support for TerraTec 6fire DMX USB interface.
You will need firmware files in order to be able to use the device
after it has been coldstarted. An install script for the firmware
and further help can be found at
http://sixfireusb.sourceforge.net
config SND_USB_HIFACE
tristate "M2Tech hiFace USB-SPDIF driver"
select SND_PCM
help
Select this option to include support for M2Tech hiFace USB-SPDIF
interface.
This driver supports the original M2Tech hiFace and some other
compatible devices. The supported products are:
* M2Tech Young
* M2Tech hiFace
* M2Tech North Star
* M2Tech W4S Young
* M2Tech Corrson
* M2Tech AUDIA
* M2Tech SL Audio
* M2Tech Empirical
* M2Tech Rockna
* M2Tech Pathos
* M2Tech Metronome
* M2Tech CAD
* M2Tech Audio Esclusive
* M2Tech Rotel
* M2Tech Eeaudio
* The Chord Company CHORD
* AVA Group A/S Vitus
To compile this driver as a module, choose M here: the module
will be called snd-usb-hiface.
config SND_BCD2000
tristate "Behringer BCD2000 MIDI driver"
select SND_RAWMIDI
help
Say Y here to include MIDI support for the Behringer BCD2000 DJ
controller.
Audio support is still work-in-progress at
https://github.com/anyc/snd-usb-bcd2000
To compile this driver as a module, choose M here: the module
will be called snd-bcd2000.
endif # SND_USB

26
sound/usb/Makefile Normal file
View file

@ -0,0 +1,26 @@
#
# Makefile for ALSA
#
snd-usb-audio-objs := card.o \
clock.o \
endpoint.o \
format.o \
helper.o \
mixer.o \
mixer_quirks.o \
pcm.o \
proc.o \
quirks.o \
stream.o
snd-usbmidi-lib-objs := midi.o
# Toplevel Module Dependency
obj-$(CONFIG_SND_USB_AUDIO) += snd-usb-audio.o snd-usbmidi-lib.o
obj-$(CONFIG_SND_USB_UA101) += snd-usbmidi-lib.o
obj-$(CONFIG_SND_USB_USX2Y) += snd-usbmidi-lib.o
obj-$(CONFIG_SND_USB_US122L) += snd-usbmidi-lib.o
obj-$(CONFIG_SND) += misc/ usx2y/ caiaq/ 6fire/ hiface/ bcd2000/

View file

@ -0,0 +1,3 @@
snd-bcd2000-y := bcd2000.o
obj-$(CONFIG_SND_BCD2000) += snd-bcd2000.o

461
sound/usb/bcd2000/bcd2000.c Normal file
View file

@ -0,0 +1,461 @@
/*
* Behringer BCD2000 driver
*
* Copyright (C) 2014 Mario Kicherer (dev@kicherer.org)
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/bitmap.h>
#include <linux/usb.h>
#include <linux/usb/audio.h>
#include <sound/core.h>
#include <sound/initval.h>
#include <sound/rawmidi.h>
#define PREFIX "snd-bcd2000: "
#define BUFSIZE 64
static struct usb_device_id id_table[] = {
{ USB_DEVICE(0x1397, 0x00bd) },
{ },
};
static unsigned char device_cmd_prefix[] = {0x03, 0x00};
static unsigned char bcd2000_init_sequence[] = {
0x07, 0x00, 0x00, 0x00, 0x78, 0x48, 0x1c, 0x81,
0xc4, 0x00, 0x00, 0x00, 0x5e, 0x53, 0x4a, 0xf7,
0x18, 0xfa, 0x11, 0xff, 0x6c, 0xf3, 0x90, 0xff,
0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
0x18, 0xfa, 0x11, 0xff, 0x14, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0xf2, 0x34, 0x4a, 0xf7,
0x18, 0xfa, 0x11, 0xff
};
struct bcd2000 {
struct usb_device *dev;
struct snd_card *card;
struct usb_interface *intf;
int card_index;
int midi_out_active;
struct snd_rawmidi *rmidi;
struct snd_rawmidi_substream *midi_receive_substream;
struct snd_rawmidi_substream *midi_out_substream;
unsigned char midi_in_buf[BUFSIZE];
unsigned char midi_out_buf[BUFSIZE];
struct urb *midi_out_urb;
struct urb *midi_in_urb;
struct usb_anchor anchor;
};
static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;
static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;
static DEFINE_MUTEX(devices_mutex);
DECLARE_BITMAP(devices_used, SNDRV_CARDS);
static struct usb_driver bcd2000_driver;
#ifdef CONFIG_SND_DEBUG
static void bcd2000_dump_buffer(const char *prefix, const char *buf, int len)
{
print_hex_dump(KERN_DEBUG, prefix,
DUMP_PREFIX_NONE, 16, 1,
buf, len, false);
}
#else
static void bcd2000_dump_buffer(const char *prefix, const char *buf, int len) {}
#endif
static int bcd2000_midi_input_open(struct snd_rawmidi_substream *substream)
{
return 0;
}
static int bcd2000_midi_input_close(struct snd_rawmidi_substream *substream)
{
return 0;
}
/* (de)register midi substream from client */
static void bcd2000_midi_input_trigger(struct snd_rawmidi_substream *substream,
int up)
{
struct bcd2000 *bcd2k = substream->rmidi->private_data;
bcd2k->midi_receive_substream = up ? substream : NULL;
}
static void bcd2000_midi_handle_input(struct bcd2000 *bcd2k,
const unsigned char *buf, unsigned int buf_len)
{
unsigned int payload_length, tocopy;
struct snd_rawmidi_substream *midi_receive_substream;
midi_receive_substream = ACCESS_ONCE(bcd2k->midi_receive_substream);
if (!midi_receive_substream)
return;
bcd2000_dump_buffer(PREFIX "received from device: ", buf, buf_len);
if (buf_len < 2)
return;
payload_length = buf[0];
/* ignore packets without payload */
if (payload_length == 0)
return;
tocopy = min(payload_length, buf_len-1);
bcd2000_dump_buffer(PREFIX "sending to userspace: ",
&buf[1], tocopy);
snd_rawmidi_receive(midi_receive_substream,
&buf[1], tocopy);
}
static void bcd2000_midi_send(struct bcd2000 *bcd2k)
{
int len, ret;
struct snd_rawmidi_substream *midi_out_substream;
BUILD_BUG_ON(sizeof(device_cmd_prefix) >= BUFSIZE);
midi_out_substream = ACCESS_ONCE(bcd2k->midi_out_substream);
if (!midi_out_substream)
return;
/* copy command prefix bytes */
memcpy(bcd2k->midi_out_buf, device_cmd_prefix,
sizeof(device_cmd_prefix));
/*
* get MIDI packet and leave space for command prefix
* and payload length
*/
len = snd_rawmidi_transmit(midi_out_substream,
bcd2k->midi_out_buf + 3, BUFSIZE - 3);
if (len < 0)
dev_err(&bcd2k->dev->dev, "%s: snd_rawmidi_transmit error %d\n",
__func__, len);
if (len <= 0)
return;
/* set payload length */
bcd2k->midi_out_buf[2] = len;
bcd2k->midi_out_urb->transfer_buffer_length = BUFSIZE;
bcd2000_dump_buffer(PREFIX "sending to device: ",
bcd2k->midi_out_buf, len+3);
/* send packet to the BCD2000 */
ret = usb_submit_urb(bcd2k->midi_out_urb, GFP_ATOMIC);
if (ret < 0)
dev_err(&bcd2k->dev->dev, PREFIX
"%s (%p): usb_submit_urb() failed, ret=%d, len=%d\n",
__func__, midi_out_substream, ret, len);
else
bcd2k->midi_out_active = 1;
}
static int bcd2000_midi_output_open(struct snd_rawmidi_substream *substream)
{
return 0;
}
static int bcd2000_midi_output_close(struct snd_rawmidi_substream *substream)
{
struct bcd2000 *bcd2k = substream->rmidi->private_data;
if (bcd2k->midi_out_active) {
usb_kill_urb(bcd2k->midi_out_urb);
bcd2k->midi_out_active = 0;
}
return 0;
}
/* (de)register midi substream from client */
static void bcd2000_midi_output_trigger(struct snd_rawmidi_substream *substream,
int up)
{
struct bcd2000 *bcd2k = substream->rmidi->private_data;
if (up) {
bcd2k->midi_out_substream = substream;
/* check if there is data userspace wants to send */
if (!bcd2k->midi_out_active)
bcd2000_midi_send(bcd2k);
} else {
bcd2k->midi_out_substream = NULL;
}
}
static void bcd2000_output_complete(struct urb *urb)
{
struct bcd2000 *bcd2k = urb->context;
bcd2k->midi_out_active = 0;
if (urb->status)
dev_warn(&urb->dev->dev,
PREFIX "output urb->status: %d\n", urb->status);
if (urb->status == -ESHUTDOWN)
return;
/* check if there is more data userspace wants to send */
bcd2000_midi_send(bcd2k);
}
static void bcd2000_input_complete(struct urb *urb)
{
int ret;
struct bcd2000 *bcd2k = urb->context;
if (urb->status)
dev_warn(&urb->dev->dev,
PREFIX "input urb->status: %i\n", urb->status);
if (!bcd2k || urb->status == -ESHUTDOWN)
return;
if (urb->actual_length > 0)
bcd2000_midi_handle_input(bcd2k, urb->transfer_buffer,
urb->actual_length);
/* return URB to device */
ret = usb_submit_urb(bcd2k->midi_in_urb, GFP_ATOMIC);
if (ret < 0)
dev_err(&bcd2k->dev->dev, PREFIX
"%s: usb_submit_urb() failed, ret=%d\n",
__func__, ret);
}
static struct snd_rawmidi_ops bcd2000_midi_output = {
.open = bcd2000_midi_output_open,
.close = bcd2000_midi_output_close,
.trigger = bcd2000_midi_output_trigger,
};
static struct snd_rawmidi_ops bcd2000_midi_input = {
.open = bcd2000_midi_input_open,
.close = bcd2000_midi_input_close,
.trigger = bcd2000_midi_input_trigger,
};
static void bcd2000_init_device(struct bcd2000 *bcd2k)
{
int ret;
init_usb_anchor(&bcd2k->anchor);
usb_anchor_urb(bcd2k->midi_out_urb, &bcd2k->anchor);
usb_anchor_urb(bcd2k->midi_in_urb, &bcd2k->anchor);
/* copy init sequence into buffer */
memcpy(bcd2k->midi_out_buf, bcd2000_init_sequence, 52);
bcd2k->midi_out_urb->transfer_buffer_length = 52;
/* submit sequence */
ret = usb_submit_urb(bcd2k->midi_out_urb, GFP_KERNEL);
if (ret < 0)
dev_err(&bcd2k->dev->dev, PREFIX
"%s: usb_submit_urb() out failed, ret=%d: ",
__func__, ret);
else
bcd2k->midi_out_active = 1;
/* pass URB to device to enable button and controller events */
ret = usb_submit_urb(bcd2k->midi_in_urb, GFP_KERNEL);
if (ret < 0)
dev_err(&bcd2k->dev->dev, PREFIX
"%s: usb_submit_urb() in failed, ret=%d: ",
__func__, ret);
/* ensure initialization is finished */
usb_wait_anchor_empty_timeout(&bcd2k->anchor, 1000);
}
static int bcd2000_init_midi(struct bcd2000 *bcd2k)
{
int ret;
struct snd_rawmidi *rmidi;
ret = snd_rawmidi_new(bcd2k->card, bcd2k->card->shortname, 0,
1, /* output */
1, /* input */
&rmidi);
if (ret < 0)
return ret;
strlcpy(rmidi->name, bcd2k->card->shortname, sizeof(rmidi->name));
rmidi->info_flags = SNDRV_RAWMIDI_INFO_DUPLEX;
rmidi->private_data = bcd2k;
rmidi->info_flags |= SNDRV_RAWMIDI_INFO_OUTPUT;
snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT,
&bcd2000_midi_output);
rmidi->info_flags |= SNDRV_RAWMIDI_INFO_INPUT;
snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT,
&bcd2000_midi_input);
bcd2k->rmidi = rmidi;
bcd2k->midi_in_urb = usb_alloc_urb(0, GFP_KERNEL);
bcd2k->midi_out_urb = usb_alloc_urb(0, GFP_KERNEL);
if (!bcd2k->midi_in_urb || !bcd2k->midi_out_urb) {
dev_err(&bcd2k->dev->dev, PREFIX "usb_alloc_urb failed\n");
return -ENOMEM;
}
usb_fill_int_urb(bcd2k->midi_in_urb, bcd2k->dev,
usb_rcvintpipe(bcd2k->dev, 0x81),
bcd2k->midi_in_buf, BUFSIZE,
bcd2000_input_complete, bcd2k, 1);
usb_fill_int_urb(bcd2k->midi_out_urb, bcd2k->dev,
usb_sndintpipe(bcd2k->dev, 0x1),
bcd2k->midi_out_buf, BUFSIZE,
bcd2000_output_complete, bcd2k, 1);
bcd2000_init_device(bcd2k);
return 0;
}
static void bcd2000_free_usb_related_resources(struct bcd2000 *bcd2k,
struct usb_interface *interface)
{
/* usb_kill_urb not necessary, urb is aborted automatically */
usb_free_urb(bcd2k->midi_out_urb);
usb_free_urb(bcd2k->midi_in_urb);
if (bcd2k->intf) {
usb_set_intfdata(bcd2k->intf, NULL);
bcd2k->intf = NULL;
}
}
static int bcd2000_probe(struct usb_interface *interface,
const struct usb_device_id *usb_id)
{
struct snd_card *card;
struct bcd2000 *bcd2k;
unsigned int card_index;
char usb_path[32];
int err;
mutex_lock(&devices_mutex);
for (card_index = 0; card_index < SNDRV_CARDS; ++card_index)
if (!test_bit(card_index, devices_used))
break;
if (card_index >= SNDRV_CARDS) {
mutex_unlock(&devices_mutex);
return -ENOENT;
}
err = snd_card_new(&interface->dev, index[card_index], id[card_index],
THIS_MODULE, sizeof(*bcd2k), &card);
if (err < 0) {
mutex_unlock(&devices_mutex);
return err;
}
bcd2k = card->private_data;
bcd2k->dev = interface_to_usbdev(interface);
bcd2k->card = card;
bcd2k->card_index = card_index;
bcd2k->intf = interface;
snd_card_set_dev(card, &interface->dev);
strncpy(card->driver, "snd-bcd2000", sizeof(card->driver));
strncpy(card->shortname, "BCD2000", sizeof(card->shortname));
usb_make_path(bcd2k->dev, usb_path, sizeof(usb_path));
snprintf(bcd2k->card->longname, sizeof(bcd2k->card->longname),
"Behringer BCD2000 at %s",
usb_path);
err = bcd2000_init_midi(bcd2k);
if (err < 0)
goto probe_error;
err = snd_card_register(card);
if (err < 0)
goto probe_error;
usb_set_intfdata(interface, bcd2k);
set_bit(card_index, devices_used);
mutex_unlock(&devices_mutex);
return 0;
probe_error:
dev_info(&bcd2k->dev->dev, PREFIX "error during probing");
bcd2000_free_usb_related_resources(bcd2k, interface);
snd_card_free(card);
mutex_unlock(&devices_mutex);
return err;
}
static void bcd2000_disconnect(struct usb_interface *interface)
{
struct bcd2000 *bcd2k = usb_get_intfdata(interface);
if (!bcd2k)
return;
mutex_lock(&devices_mutex);
/* make sure that userspace cannot create new requests */
snd_card_disconnect(bcd2k->card);
bcd2000_free_usb_related_resources(bcd2k, interface);
clear_bit(bcd2k->card_index, devices_used);
snd_card_free_when_closed(bcd2k->card);
mutex_unlock(&devices_mutex);
}
static struct usb_driver bcd2000_driver = {
.name = "snd-bcd2000",
.probe = bcd2000_probe,
.disconnect = bcd2000_disconnect,
.id_table = id_table,
};
module_usb_driver(bcd2000_driver);
MODULE_DEVICE_TABLE(usb, id_table);
MODULE_AUTHOR("Mario Kicherer, dev@kicherer.org");
MODULE_DESCRIPTION("Behringer BCD2000 driver");
MODULE_LICENSE("GPL");

4
sound/usb/caiaq/Makefile Normal file
View file

@ -0,0 +1,4 @@
snd-usb-caiaq-y := device.o audio.o midi.o control.o
snd-usb-caiaq-$(CONFIG_SND_USB_CAIAQ_INPUT) += input.o
obj-$(CONFIG_SND_USB_CAIAQ) += snd-usb-caiaq.o

906
sound/usb/caiaq/audio.c Normal file
View file

@ -0,0 +1,906 @@
/*
* Copyright (c) 2006-2008 Daniel Mack, Karsten Wiese
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <linux/device.h>
#include <linux/spinlock.h>
#include <linux/slab.h>
#include <linux/init.h>
#include <linux/usb.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include "device.h"
#include "audio.h"
#define N_URBS 32
#define CLOCK_DRIFT_TOLERANCE 5
#define FRAMES_PER_URB 8
#define BYTES_PER_FRAME 512
#define CHANNELS_PER_STREAM 2
#define BYTES_PER_SAMPLE 3
#define BYTES_PER_SAMPLE_USB 4
#define MAX_BUFFER_SIZE (128*1024)
#define MAX_ENDPOINT_SIZE 512
#define ENDPOINT_CAPTURE 2
#define ENDPOINT_PLAYBACK 6
#define MAKE_CHECKBYTE(cdev,stream,i) \
(stream << 1) | (~(i / (cdev->n_streams * BYTES_PER_SAMPLE_USB)) & 1)
static struct snd_pcm_hardware snd_usb_caiaq_pcm_hardware = {
.info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_BLOCK_TRANSFER),
.formats = SNDRV_PCM_FMTBIT_S24_3BE,
.rates = (SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 |
SNDRV_PCM_RATE_96000),
.rate_min = 44100,
.rate_max = 0, /* will overwrite later */
.channels_min = CHANNELS_PER_STREAM,
.channels_max = CHANNELS_PER_STREAM,
.buffer_bytes_max = MAX_BUFFER_SIZE,
.period_bytes_min = 128,
.period_bytes_max = MAX_BUFFER_SIZE,
.periods_min = 1,
.periods_max = 1024,
};
static void
activate_substream(struct snd_usb_caiaqdev *cdev,
struct snd_pcm_substream *sub)
{
spin_lock(&cdev->spinlock);
if (sub->stream == SNDRV_PCM_STREAM_PLAYBACK)
cdev->sub_playback[sub->number] = sub;
else
cdev->sub_capture[sub->number] = sub;
spin_unlock(&cdev->spinlock);
}
static void
deactivate_substream(struct snd_usb_caiaqdev *cdev,
struct snd_pcm_substream *sub)
{
unsigned long flags;
spin_lock_irqsave(&cdev->spinlock, flags);
if (sub->stream == SNDRV_PCM_STREAM_PLAYBACK)
cdev->sub_playback[sub->number] = NULL;
else
cdev->sub_capture[sub->number] = NULL;
spin_unlock_irqrestore(&cdev->spinlock, flags);
}
static int
all_substreams_zero(struct snd_pcm_substream **subs)
{
int i;
for (i = 0; i < MAX_STREAMS; i++)
if (subs[i] != NULL)
return 0;
return 1;
}
static int stream_start(struct snd_usb_caiaqdev *cdev)
{
int i, ret;
struct device *dev = caiaqdev_to_dev(cdev);
dev_dbg(dev, "%s(%p)\n", __func__, cdev);
if (cdev->streaming)
return -EINVAL;
memset(cdev->sub_playback, 0, sizeof(cdev->sub_playback));
memset(cdev->sub_capture, 0, sizeof(cdev->sub_capture));
cdev->input_panic = 0;
cdev->output_panic = 0;
cdev->first_packet = 4;
cdev->streaming = 1;
cdev->warned = 0;
for (i = 0; i < N_URBS; i++) {
ret = usb_submit_urb(cdev->data_urbs_in[i], GFP_ATOMIC);
if (ret) {
dev_err(dev, "unable to trigger read #%d! (ret %d)\n",
i, ret);
cdev->streaming = 0;
return -EPIPE;
}
}
return 0;
}
static void stream_stop(struct snd_usb_caiaqdev *cdev)
{
int i;
struct device *dev = caiaqdev_to_dev(cdev);
dev_dbg(dev, "%s(%p)\n", __func__, cdev);
if (!cdev->streaming)
return;
cdev->streaming = 0;
for (i = 0; i < N_URBS; i++) {
usb_kill_urb(cdev->data_urbs_in[i]);
if (test_bit(i, &cdev->outurb_active_mask))
usb_kill_urb(cdev->data_urbs_out[i]);
}
cdev->outurb_active_mask = 0;
}
static int snd_usb_caiaq_substream_open(struct snd_pcm_substream *substream)
{
struct snd_usb_caiaqdev *cdev = snd_pcm_substream_chip(substream);
struct device *dev = caiaqdev_to_dev(cdev);
dev_dbg(dev, "%s(%p)\n", __func__, substream);
substream->runtime->hw = cdev->pcm_info;
snd_pcm_limit_hw_rates(substream->runtime);
return 0;
}
static int snd_usb_caiaq_substream_close(struct snd_pcm_substream *substream)
{
struct snd_usb_caiaqdev *cdev = snd_pcm_substream_chip(substream);
struct device *dev = caiaqdev_to_dev(cdev);
dev_dbg(dev, "%s(%p)\n", __func__, substream);
if (all_substreams_zero(cdev->sub_playback) &&
all_substreams_zero(cdev->sub_capture)) {
/* when the last client has stopped streaming,
* all sample rates are allowed again */
stream_stop(cdev);
cdev->pcm_info.rates = cdev->samplerates;
}
return 0;
}
static int snd_usb_caiaq_pcm_hw_params(struct snd_pcm_substream *sub,
struct snd_pcm_hw_params *hw_params)
{
return snd_pcm_lib_alloc_vmalloc_buffer(sub,
params_buffer_bytes(hw_params));
}
static int snd_usb_caiaq_pcm_hw_free(struct snd_pcm_substream *sub)
{
struct snd_usb_caiaqdev *cdev = snd_pcm_substream_chip(sub);
deactivate_substream(cdev, sub);
return snd_pcm_lib_free_vmalloc_buffer(sub);
}
/* this should probably go upstream */
#if SNDRV_PCM_RATE_5512 != 1 << 0 || SNDRV_PCM_RATE_192000 != 1 << 12
#error "Change this table"
#endif
static unsigned int rates[] = { 5512, 8000, 11025, 16000, 22050, 32000, 44100,
48000, 64000, 88200, 96000, 176400, 192000 };
static int snd_usb_caiaq_pcm_prepare(struct snd_pcm_substream *substream)
{
int bytes_per_sample, bpp, ret, i;
int index = substream->number;
struct snd_usb_caiaqdev *cdev = snd_pcm_substream_chip(substream);
struct snd_pcm_runtime *runtime = substream->runtime;
struct device *dev = caiaqdev_to_dev(cdev);
dev_dbg(dev, "%s(%p)\n", __func__, substream);
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
int out_pos;
switch (cdev->spec.data_alignment) {
case 0:
case 2:
out_pos = BYTES_PER_SAMPLE + 1;
break;
case 3:
default:
out_pos = 0;
break;
}
cdev->period_out_count[index] = out_pos;
cdev->audio_out_buf_pos[index] = out_pos;
} else {
int in_pos;
switch (cdev->spec.data_alignment) {
case 0:
in_pos = BYTES_PER_SAMPLE + 2;
break;
case 2:
in_pos = BYTES_PER_SAMPLE;
break;
case 3:
default:
in_pos = 0;
break;
}
cdev->period_in_count[index] = in_pos;
cdev->audio_in_buf_pos[index] = in_pos;
}
if (cdev->streaming)
return 0;
/* the first client that opens a stream defines the sample rate
* setting for all subsequent calls, until the last client closed. */
for (i=0; i < ARRAY_SIZE(rates); i++)
if (runtime->rate == rates[i])
cdev->pcm_info.rates = 1 << i;
snd_pcm_limit_hw_rates(runtime);
bytes_per_sample = BYTES_PER_SAMPLE;
if (cdev->spec.data_alignment >= 2)
bytes_per_sample++;
bpp = ((runtime->rate / 8000) + CLOCK_DRIFT_TOLERANCE)
* bytes_per_sample * CHANNELS_PER_STREAM * cdev->n_streams;
if (bpp > MAX_ENDPOINT_SIZE)
bpp = MAX_ENDPOINT_SIZE;
ret = snd_usb_caiaq_set_audio_params(cdev, runtime->rate,
runtime->sample_bits, bpp);
if (ret)
return ret;
ret = stream_start(cdev);
if (ret)
return ret;
cdev->output_running = 0;
wait_event_timeout(cdev->prepare_wait_queue, cdev->output_running, HZ);
if (!cdev->output_running) {
stream_stop(cdev);
return -EPIPE;
}
return 0;
}
static int snd_usb_caiaq_pcm_trigger(struct snd_pcm_substream *sub, int cmd)
{
struct snd_usb_caiaqdev *cdev = snd_pcm_substream_chip(sub);
struct device *dev = caiaqdev_to_dev(cdev);
dev_dbg(dev, "%s(%p) cmd %d\n", __func__, sub, cmd);
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
activate_substream(cdev, sub);
break;
case SNDRV_PCM_TRIGGER_STOP:
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
deactivate_substream(cdev, sub);
break;
default:
return -EINVAL;
}
return 0;
}
static snd_pcm_uframes_t
snd_usb_caiaq_pcm_pointer(struct snd_pcm_substream *sub)
{
int index = sub->number;
struct snd_usb_caiaqdev *cdev = snd_pcm_substream_chip(sub);
snd_pcm_uframes_t ptr;
spin_lock(&cdev->spinlock);
if (cdev->input_panic || cdev->output_panic) {
ptr = SNDRV_PCM_POS_XRUN;
goto unlock;
}
if (sub->stream == SNDRV_PCM_STREAM_PLAYBACK)
ptr = bytes_to_frames(sub->runtime,
cdev->audio_out_buf_pos[index]);
else
ptr = bytes_to_frames(sub->runtime,
cdev->audio_in_buf_pos[index]);
unlock:
spin_unlock(&cdev->spinlock);
return ptr;
}
/* operators for both playback and capture */
static struct snd_pcm_ops snd_usb_caiaq_ops = {
.open = snd_usb_caiaq_substream_open,
.close = snd_usb_caiaq_substream_close,
.ioctl = snd_pcm_lib_ioctl,
.hw_params = snd_usb_caiaq_pcm_hw_params,
.hw_free = snd_usb_caiaq_pcm_hw_free,
.prepare = snd_usb_caiaq_pcm_prepare,
.trigger = snd_usb_caiaq_pcm_trigger,
.pointer = snd_usb_caiaq_pcm_pointer,
.page = snd_pcm_lib_get_vmalloc_page,
.mmap = snd_pcm_lib_mmap_vmalloc,
};
static void check_for_elapsed_periods(struct snd_usb_caiaqdev *cdev,
struct snd_pcm_substream **subs)
{
int stream, pb, *cnt;
struct snd_pcm_substream *sub;
for (stream = 0; stream < cdev->n_streams; stream++) {
sub = subs[stream];
if (!sub)
continue;
pb = snd_pcm_lib_period_bytes(sub);
cnt = (sub->stream == SNDRV_PCM_STREAM_PLAYBACK) ?
&cdev->period_out_count[stream] :
&cdev->period_in_count[stream];
if (*cnt >= pb) {
snd_pcm_period_elapsed(sub);
*cnt %= pb;
}
}
}
static void read_in_urb_mode0(struct snd_usb_caiaqdev *cdev,
const struct urb *urb,
const struct usb_iso_packet_descriptor *iso)
{
unsigned char *usb_buf = urb->transfer_buffer + iso->offset;
struct snd_pcm_substream *sub;
int stream, i;
if (all_substreams_zero(cdev->sub_capture))
return;
for (i = 0; i < iso->actual_length;) {
for (stream = 0; stream < cdev->n_streams; stream++, i++) {
sub = cdev->sub_capture[stream];
if (sub) {
struct snd_pcm_runtime *rt = sub->runtime;
char *audio_buf = rt->dma_area;
int sz = frames_to_bytes(rt, rt->buffer_size);
audio_buf[cdev->audio_in_buf_pos[stream]++]
= usb_buf[i];
cdev->period_in_count[stream]++;
if (cdev->audio_in_buf_pos[stream] == sz)
cdev->audio_in_buf_pos[stream] = 0;
}
}
}
}
static void read_in_urb_mode2(struct snd_usb_caiaqdev *cdev,
const struct urb *urb,
const struct usb_iso_packet_descriptor *iso)
{
unsigned char *usb_buf = urb->transfer_buffer + iso->offset;
unsigned char check_byte;
struct snd_pcm_substream *sub;
int stream, i;
for (i = 0; i < iso->actual_length;) {
if (i % (cdev->n_streams * BYTES_PER_SAMPLE_USB) == 0) {
for (stream = 0;
stream < cdev->n_streams;
stream++, i++) {
if (cdev->first_packet)
continue;
check_byte = MAKE_CHECKBYTE(cdev, stream, i);
if ((usb_buf[i] & 0x3f) != check_byte)
cdev->input_panic = 1;
if (usb_buf[i] & 0x80)
cdev->output_panic = 1;
}
}
cdev->first_packet = 0;
for (stream = 0; stream < cdev->n_streams; stream++, i++) {
sub = cdev->sub_capture[stream];
if (cdev->input_panic)
usb_buf[i] = 0;
if (sub) {
struct snd_pcm_runtime *rt = sub->runtime;
char *audio_buf = rt->dma_area;
int sz = frames_to_bytes(rt, rt->buffer_size);
audio_buf[cdev->audio_in_buf_pos[stream]++] =
usb_buf[i];
cdev->period_in_count[stream]++;
if (cdev->audio_in_buf_pos[stream] == sz)
cdev->audio_in_buf_pos[stream] = 0;
}
}
}
}
static void read_in_urb_mode3(struct snd_usb_caiaqdev *cdev,
const struct urb *urb,
const struct usb_iso_packet_descriptor *iso)
{
unsigned char *usb_buf = urb->transfer_buffer + iso->offset;
struct device *dev = caiaqdev_to_dev(cdev);
int stream, i;
/* paranoia check */
if (iso->actual_length % (BYTES_PER_SAMPLE_USB * CHANNELS_PER_STREAM))
return;
for (i = 0; i < iso->actual_length;) {
for (stream = 0; stream < cdev->n_streams; stream++) {
struct snd_pcm_substream *sub = cdev->sub_capture[stream];
char *audio_buf = NULL;
int c, n, sz = 0;
if (sub && !cdev->input_panic) {
struct snd_pcm_runtime *rt = sub->runtime;
audio_buf = rt->dma_area;
sz = frames_to_bytes(rt, rt->buffer_size);
}
for (c = 0; c < CHANNELS_PER_STREAM; c++) {
/* 3 audio data bytes, followed by 1 check byte */
if (audio_buf) {
for (n = 0; n < BYTES_PER_SAMPLE; n++) {
audio_buf[cdev->audio_in_buf_pos[stream]++] = usb_buf[i+n];
if (cdev->audio_in_buf_pos[stream] == sz)
cdev->audio_in_buf_pos[stream] = 0;
}
cdev->period_in_count[stream] += BYTES_PER_SAMPLE;
}
i += BYTES_PER_SAMPLE;
if (usb_buf[i] != ((stream << 1) | c) &&
!cdev->first_packet) {
if (!cdev->input_panic)
dev_warn(dev, " EXPECTED: %02x got %02x, c %d, stream %d, i %d\n",
((stream << 1) | c), usb_buf[i], c, stream, i);
cdev->input_panic = 1;
}
i++;
}
}
}
if (cdev->first_packet > 0)
cdev->first_packet--;
}
static void read_in_urb(struct snd_usb_caiaqdev *cdev,
const struct urb *urb,
const struct usb_iso_packet_descriptor *iso)
{
struct device *dev = caiaqdev_to_dev(cdev);
if (!cdev->streaming)
return;
if (iso->actual_length < cdev->bpp)
return;
switch (cdev->spec.data_alignment) {
case 0:
read_in_urb_mode0(cdev, urb, iso);
break;
case 2:
read_in_urb_mode2(cdev, urb, iso);
break;
case 3:
read_in_urb_mode3(cdev, urb, iso);
break;
}
if ((cdev->input_panic || cdev->output_panic) && !cdev->warned) {
dev_warn(dev, "streaming error detected %s %s\n",
cdev->input_panic ? "(input)" : "",
cdev->output_panic ? "(output)" : "");
cdev->warned = 1;
}
}
static void fill_out_urb_mode_0(struct snd_usb_caiaqdev *cdev,
struct urb *urb,
const struct usb_iso_packet_descriptor *iso)
{
unsigned char *usb_buf = urb->transfer_buffer + iso->offset;
struct snd_pcm_substream *sub;
int stream, i;
for (i = 0; i < iso->length;) {
for (stream = 0; stream < cdev->n_streams; stream++, i++) {
sub = cdev->sub_playback[stream];
if (sub) {
struct snd_pcm_runtime *rt = sub->runtime;
char *audio_buf = rt->dma_area;
int sz = frames_to_bytes(rt, rt->buffer_size);
usb_buf[i] =
audio_buf[cdev->audio_out_buf_pos[stream]];
cdev->period_out_count[stream]++;
cdev->audio_out_buf_pos[stream]++;
if (cdev->audio_out_buf_pos[stream] == sz)
cdev->audio_out_buf_pos[stream] = 0;
} else
usb_buf[i] = 0;
}
/* fill in the check bytes */
if (cdev->spec.data_alignment == 2 &&
i % (cdev->n_streams * BYTES_PER_SAMPLE_USB) ==
(cdev->n_streams * CHANNELS_PER_STREAM))
for (stream = 0; stream < cdev->n_streams; stream++, i++)
usb_buf[i] = MAKE_CHECKBYTE(cdev, stream, i);
}
}
static void fill_out_urb_mode_3(struct snd_usb_caiaqdev *cdev,
struct urb *urb,
const struct usb_iso_packet_descriptor *iso)
{
unsigned char *usb_buf = urb->transfer_buffer + iso->offset;
int stream, i;
for (i = 0; i < iso->length;) {
for (stream = 0; stream < cdev->n_streams; stream++) {
struct snd_pcm_substream *sub = cdev->sub_playback[stream];
char *audio_buf = NULL;
int c, n, sz = 0;
if (sub) {
struct snd_pcm_runtime *rt = sub->runtime;
audio_buf = rt->dma_area;
sz = frames_to_bytes(rt, rt->buffer_size);
}
for (c = 0; c < CHANNELS_PER_STREAM; c++) {
for (n = 0; n < BYTES_PER_SAMPLE; n++) {
if (audio_buf) {
usb_buf[i+n] = audio_buf[cdev->audio_out_buf_pos[stream]++];
if (cdev->audio_out_buf_pos[stream] == sz)
cdev->audio_out_buf_pos[stream] = 0;
} else {
usb_buf[i+n] = 0;
}
}
if (audio_buf)
cdev->period_out_count[stream] += BYTES_PER_SAMPLE;
i += BYTES_PER_SAMPLE;
/* fill in the check byte pattern */
usb_buf[i++] = (stream << 1) | c;
}
}
}
}
static inline void fill_out_urb(struct snd_usb_caiaqdev *cdev,
struct urb *urb,
const struct usb_iso_packet_descriptor *iso)
{
switch (cdev->spec.data_alignment) {
case 0:
case 2:
fill_out_urb_mode_0(cdev, urb, iso);
break;
case 3:
fill_out_urb_mode_3(cdev, urb, iso);
break;
}
}
static void read_completed(struct urb *urb)
{
struct snd_usb_caiaq_cb_info *info = urb->context;
struct snd_usb_caiaqdev *cdev;
struct device *dev;
struct urb *out = NULL;
int i, frame, len, send_it = 0, outframe = 0;
size_t offset = 0;
if (urb->status || !info)
return;
cdev = info->cdev;
dev = caiaqdev_to_dev(cdev);
if (!cdev->streaming)
return;
/* find an unused output urb that is unused */
for (i = 0; i < N_URBS; i++)
if (test_and_set_bit(i, &cdev->outurb_active_mask) == 0) {
out = cdev->data_urbs_out[i];
break;
}
if (!out) {
dev_err(dev, "Unable to find an output urb to use\n");
goto requeue;
}
/* read the recently received packet and send back one which has
* the same layout */
for (frame = 0; frame < FRAMES_PER_URB; frame++) {
if (urb->iso_frame_desc[frame].status)
continue;
len = urb->iso_frame_desc[outframe].actual_length;
out->iso_frame_desc[outframe].length = len;
out->iso_frame_desc[outframe].actual_length = 0;
out->iso_frame_desc[outframe].offset = offset;
offset += len;
if (len > 0) {
spin_lock(&cdev->spinlock);
fill_out_urb(cdev, out, &out->iso_frame_desc[outframe]);
read_in_urb(cdev, urb, &urb->iso_frame_desc[frame]);
spin_unlock(&cdev->spinlock);
check_for_elapsed_periods(cdev, cdev->sub_playback);
check_for_elapsed_periods(cdev, cdev->sub_capture);
send_it = 1;
}
outframe++;
}
if (send_it) {
out->number_of_packets = outframe;
usb_submit_urb(out, GFP_ATOMIC);
} else {
struct snd_usb_caiaq_cb_info *oinfo = out->context;
clear_bit(oinfo->index, &cdev->outurb_active_mask);
}
requeue:
/* re-submit inbound urb */
for (frame = 0; frame < FRAMES_PER_URB; frame++) {
urb->iso_frame_desc[frame].offset = BYTES_PER_FRAME * frame;
urb->iso_frame_desc[frame].length = BYTES_PER_FRAME;
urb->iso_frame_desc[frame].actual_length = 0;
}
urb->number_of_packets = FRAMES_PER_URB;
usb_submit_urb(urb, GFP_ATOMIC);
}
static void write_completed(struct urb *urb)
{
struct snd_usb_caiaq_cb_info *info = urb->context;
struct snd_usb_caiaqdev *cdev = info->cdev;
if (!cdev->output_running) {
cdev->output_running = 1;
wake_up(&cdev->prepare_wait_queue);
}
clear_bit(info->index, &cdev->outurb_active_mask);
}
static struct urb **alloc_urbs(struct snd_usb_caiaqdev *cdev, int dir, int *ret)
{
int i, frame;
struct urb **urbs;
struct usb_device *usb_dev = cdev->chip.dev;
struct device *dev = caiaqdev_to_dev(cdev);
unsigned int pipe;
pipe = (dir == SNDRV_PCM_STREAM_PLAYBACK) ?
usb_sndisocpipe(usb_dev, ENDPOINT_PLAYBACK) :
usb_rcvisocpipe(usb_dev, ENDPOINT_CAPTURE);
urbs = kmalloc(N_URBS * sizeof(*urbs), GFP_KERNEL);
if (!urbs) {
dev_err(dev, "unable to kmalloc() urbs, OOM!?\n");
*ret = -ENOMEM;
return NULL;
}
for (i = 0; i < N_URBS; i++) {
urbs[i] = usb_alloc_urb(FRAMES_PER_URB, GFP_KERNEL);
if (!urbs[i]) {
dev_err(dev, "unable to usb_alloc_urb(), OOM!?\n");
*ret = -ENOMEM;
return urbs;
}
urbs[i]->transfer_buffer =
kmalloc(FRAMES_PER_URB * BYTES_PER_FRAME, GFP_KERNEL);
if (!urbs[i]->transfer_buffer) {
dev_err(dev, "unable to kmalloc() transfer buffer, OOM!?\n");
*ret = -ENOMEM;
return urbs;
}
for (frame = 0; frame < FRAMES_PER_URB; frame++) {
struct usb_iso_packet_descriptor *iso =
&urbs[i]->iso_frame_desc[frame];
iso->offset = BYTES_PER_FRAME * frame;
iso->length = BYTES_PER_FRAME;
}
urbs[i]->dev = usb_dev;
urbs[i]->pipe = pipe;
urbs[i]->transfer_buffer_length = FRAMES_PER_URB
* BYTES_PER_FRAME;
urbs[i]->context = &cdev->data_cb_info[i];
urbs[i]->interval = 1;
urbs[i]->number_of_packets = FRAMES_PER_URB;
urbs[i]->complete = (dir == SNDRV_PCM_STREAM_CAPTURE) ?
read_completed : write_completed;
}
*ret = 0;
return urbs;
}
static void free_urbs(struct urb **urbs)
{
int i;
if (!urbs)
return;
for (i = 0; i < N_URBS; i++) {
if (!urbs[i])
continue;
usb_kill_urb(urbs[i]);
kfree(urbs[i]->transfer_buffer);
usb_free_urb(urbs[i]);
}
kfree(urbs);
}
int snd_usb_caiaq_audio_init(struct snd_usb_caiaqdev *cdev)
{
int i, ret;
struct device *dev = caiaqdev_to_dev(cdev);
cdev->n_audio_in = max(cdev->spec.num_analog_audio_in,
cdev->spec.num_digital_audio_in) /
CHANNELS_PER_STREAM;
cdev->n_audio_out = max(cdev->spec.num_analog_audio_out,
cdev->spec.num_digital_audio_out) /
CHANNELS_PER_STREAM;
cdev->n_streams = max(cdev->n_audio_in, cdev->n_audio_out);
dev_dbg(dev, "cdev->n_audio_in = %d\n", cdev->n_audio_in);
dev_dbg(dev, "cdev->n_audio_out = %d\n", cdev->n_audio_out);
dev_dbg(dev, "cdev->n_streams = %d\n", cdev->n_streams);
if (cdev->n_streams > MAX_STREAMS) {
dev_err(dev, "unable to initialize device, too many streams.\n");
return -EINVAL;
}
if (cdev->n_streams < 1) {
dev_err(dev, "bogus number of streams: %d\n", cdev->n_streams);
return -EINVAL;
}
ret = snd_pcm_new(cdev->chip.card, cdev->product_name, 0,
cdev->n_audio_out, cdev->n_audio_in, &cdev->pcm);
if (ret < 0) {
dev_err(dev, "snd_pcm_new() returned %d\n", ret);
return ret;
}
cdev->pcm->private_data = cdev;
strlcpy(cdev->pcm->name, cdev->product_name, sizeof(cdev->pcm->name));
memset(cdev->sub_playback, 0, sizeof(cdev->sub_playback));
memset(cdev->sub_capture, 0, sizeof(cdev->sub_capture));
memcpy(&cdev->pcm_info, &snd_usb_caiaq_pcm_hardware,
sizeof(snd_usb_caiaq_pcm_hardware));
/* setup samplerates */
cdev->samplerates = cdev->pcm_info.rates;
switch (cdev->chip.usb_id) {
case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_AK1):
case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_RIGKONTROL3):
case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_SESSIONIO):
case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_GUITARRIGMOBILE):
cdev->samplerates |= SNDRV_PCM_RATE_192000;
/* fall thru */
case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_AUDIO2DJ):
case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_AUDIO4DJ):
case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_AUDIO8DJ):
case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_TRAKTORAUDIO2):
cdev->samplerates |= SNDRV_PCM_RATE_88200;
break;
}
snd_pcm_set_ops(cdev->pcm, SNDRV_PCM_STREAM_PLAYBACK,
&snd_usb_caiaq_ops);
snd_pcm_set_ops(cdev->pcm, SNDRV_PCM_STREAM_CAPTURE,
&snd_usb_caiaq_ops);
cdev->data_cb_info =
kmalloc(sizeof(struct snd_usb_caiaq_cb_info) * N_URBS,
GFP_KERNEL);
if (!cdev->data_cb_info)
return -ENOMEM;
cdev->outurb_active_mask = 0;
BUILD_BUG_ON(N_URBS > (sizeof(cdev->outurb_active_mask) * 8));
for (i = 0; i < N_URBS; i++) {
cdev->data_cb_info[i].cdev = cdev;
cdev->data_cb_info[i].index = i;
}
cdev->data_urbs_in = alloc_urbs(cdev, SNDRV_PCM_STREAM_CAPTURE, &ret);
if (ret < 0) {
kfree(cdev->data_cb_info);
free_urbs(cdev->data_urbs_in);
return ret;
}
cdev->data_urbs_out = alloc_urbs(cdev, SNDRV_PCM_STREAM_PLAYBACK, &ret);
if (ret < 0) {
kfree(cdev->data_cb_info);
free_urbs(cdev->data_urbs_in);
free_urbs(cdev->data_urbs_out);
return ret;
}
return 0;
}
void snd_usb_caiaq_audio_free(struct snd_usb_caiaqdev *cdev)
{
struct device *dev = caiaqdev_to_dev(cdev);
dev_dbg(dev, "%s(%p)\n", __func__, cdev);
stream_stop(cdev);
free_urbs(cdev->data_urbs_in);
free_urbs(cdev->data_urbs_out);
kfree(cdev->data_cb_info);
}

7
sound/usb/caiaq/audio.h Normal file
View file

@ -0,0 +1,7 @@
#ifndef CAIAQ_AUDIO_H
#define CAIAQ_AUDIO_H
int snd_usb_caiaq_audio_init(struct snd_usb_caiaqdev *cdev);
void snd_usb_caiaq_audio_free(struct snd_usb_caiaqdev *cdev);
#endif /* CAIAQ_AUDIO_H */

656
sound/usb/caiaq/control.c Normal file
View file

@ -0,0 +1,656 @@
/*
* Copyright (c) 2007 Daniel Mack
* friendly supported by NI.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <linux/device.h>
#include <linux/init.h>
#include <linux/usb.h>
#include <sound/control.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include "device.h"
#include "control.h"
#define CNT_INTVAL 0x10000
#define MASCHINE_BANK_SIZE 32
static int control_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{
struct snd_usb_audio *chip = snd_kcontrol_chip(kcontrol);
struct snd_usb_caiaqdev *cdev = caiaqdev(chip->card);
int pos = kcontrol->private_value;
int is_intval = pos & CNT_INTVAL;
int maxval = 63;
uinfo->count = 1;
pos &= ~CNT_INTVAL;
switch (cdev->chip.usb_id) {
case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_AUDIO8DJ):
case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_AUDIO4DJ):
if (pos == 0) {
/* current input mode of A8DJ and A4DJ */
uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
uinfo->value.integer.min = 0;
uinfo->value.integer.max = 2;
return 0;
}
break;
case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_TRAKTORKONTROLX1):
maxval = 127;
break;
case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_TRAKTORKONTROLS4):
maxval = 31;
break;
}
if (is_intval) {
uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
uinfo->value.integer.min = 0;
uinfo->value.integer.max = maxval;
} else {
uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
uinfo->value.integer.min = 0;
uinfo->value.integer.max = 1;
}
return 0;
}
static int control_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_usb_audio *chip = snd_kcontrol_chip(kcontrol);
struct snd_usb_caiaqdev *cdev = caiaqdev(chip->card);
int pos = kcontrol->private_value;
if (pos & CNT_INTVAL)
ucontrol->value.integer.value[0]
= cdev->control_state[pos & ~CNT_INTVAL];
else
ucontrol->value.integer.value[0]
= !!(cdev->control_state[pos / 8] & (1 << pos % 8));
return 0;
}
static int control_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_usb_audio *chip = snd_kcontrol_chip(kcontrol);
struct snd_usb_caiaqdev *cdev = caiaqdev(chip->card);
int pos = kcontrol->private_value;
int v = ucontrol->value.integer.value[0];
unsigned char cmd;
switch (cdev->chip.usb_id) {
case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_MASCHINECONTROLLER):
case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_TRAKTORKONTROLX1):
case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_KORECONTROLLER2):
case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_KORECONTROLLER):
cmd = EP1_CMD_DIMM_LEDS;
break;
default:
cmd = EP1_CMD_WRITE_IO;
break;
}
if (pos & CNT_INTVAL) {
int i = pos & ~CNT_INTVAL;
cdev->control_state[i] = v;
if (cdev->chip.usb_id ==
USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_TRAKTORKONTROLS4)) {
int actual_len;
cdev->ep8_out_buf[0] = i;
cdev->ep8_out_buf[1] = v;
usb_bulk_msg(cdev->chip.dev,
usb_sndbulkpipe(cdev->chip.dev, 8),
cdev->ep8_out_buf, sizeof(cdev->ep8_out_buf),
&actual_len, 200);
} else if (cdev->chip.usb_id ==
USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_MASCHINECONTROLLER)) {
int bank = 0;
int offset = 0;
if (i >= MASCHINE_BANK_SIZE) {
bank = 0x1e;
offset = MASCHINE_BANK_SIZE;
}
snd_usb_caiaq_send_command_bank(cdev, cmd, bank,
cdev->control_state + offset,
MASCHINE_BANK_SIZE);
} else {
snd_usb_caiaq_send_command(cdev, cmd,
cdev->control_state, sizeof(cdev->control_state));
}
} else {
if (v)
cdev->control_state[pos / 8] |= 1 << (pos % 8);
else
cdev->control_state[pos / 8] &= ~(1 << (pos % 8));
snd_usb_caiaq_send_command(cdev, cmd,
cdev->control_state, sizeof(cdev->control_state));
}
return 1;
}
static struct snd_kcontrol_new kcontrol_template = {
.iface = SNDRV_CTL_ELEM_IFACE_HWDEP,
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
.index = 0,
.info = control_info,
.get = control_get,
.put = control_put,
/* name and private_value filled later */
};
struct caiaq_controller {
char *name;
int index;
};
static struct caiaq_controller ak1_controller[] = {
{ "LED left", 2 },
{ "LED middle", 1 },
{ "LED right", 0 },
{ "LED ring", 3 }
};
static struct caiaq_controller rk2_controller[] = {
{ "LED 1", 5 },
{ "LED 2", 4 },
{ "LED 3", 3 },
{ "LED 4", 2 },
{ "LED 5", 1 },
{ "LED 6", 0 },
{ "LED pedal", 6 },
{ "LED 7seg_1b", 8 },
{ "LED 7seg_1c", 9 },
{ "LED 7seg_2a", 10 },
{ "LED 7seg_2b", 11 },
{ "LED 7seg_2c", 12 },
{ "LED 7seg_2d", 13 },
{ "LED 7seg_2e", 14 },
{ "LED 7seg_2f", 15 },
{ "LED 7seg_2g", 16 },
{ "LED 7seg_3a", 17 },
{ "LED 7seg_3b", 18 },
{ "LED 7seg_3c", 19 },
{ "LED 7seg_3d", 20 },
{ "LED 7seg_3e", 21 },
{ "LED 7seg_3f", 22 },
{ "LED 7seg_3g", 23 }
};
static struct caiaq_controller rk3_controller[] = {
{ "LED 7seg_1a", 0 + 0 },
{ "LED 7seg_1b", 0 + 1 },
{ "LED 7seg_1c", 0 + 2 },
{ "LED 7seg_1d", 0 + 3 },
{ "LED 7seg_1e", 0 + 4 },
{ "LED 7seg_1f", 0 + 5 },
{ "LED 7seg_1g", 0 + 6 },
{ "LED 7seg_1p", 0 + 7 },
{ "LED 7seg_2a", 8 + 0 },
{ "LED 7seg_2b", 8 + 1 },
{ "LED 7seg_2c", 8 + 2 },
{ "LED 7seg_2d", 8 + 3 },
{ "LED 7seg_2e", 8 + 4 },
{ "LED 7seg_2f", 8 + 5 },
{ "LED 7seg_2g", 8 + 6 },
{ "LED 7seg_2p", 8 + 7 },
{ "LED 7seg_3a", 16 + 0 },
{ "LED 7seg_3b", 16 + 1 },
{ "LED 7seg_3c", 16 + 2 },
{ "LED 7seg_3d", 16 + 3 },
{ "LED 7seg_3e", 16 + 4 },
{ "LED 7seg_3f", 16 + 5 },
{ "LED 7seg_3g", 16 + 6 },
{ "LED 7seg_3p", 16 + 7 },
{ "LED 7seg_4a", 24 + 0 },
{ "LED 7seg_4b", 24 + 1 },
{ "LED 7seg_4c", 24 + 2 },
{ "LED 7seg_4d", 24 + 3 },
{ "LED 7seg_4e", 24 + 4 },
{ "LED 7seg_4f", 24 + 5 },
{ "LED 7seg_4g", 24 + 6 },
{ "LED 7seg_4p", 24 + 7 },
{ "LED 1", 32 + 0 },
{ "LED 2", 32 + 1 },
{ "LED 3", 32 + 2 },
{ "LED 4", 32 + 3 },
{ "LED 5", 32 + 4 },
{ "LED 6", 32 + 5 },
{ "LED 7", 32 + 6 },
{ "LED 8", 32 + 7 },
{ "LED pedal", 32 + 8 }
};
static struct caiaq_controller kore_controller[] = {
{ "LED F1", 8 | CNT_INTVAL },
{ "LED F2", 12 | CNT_INTVAL },
{ "LED F3", 0 | CNT_INTVAL },
{ "LED F4", 4 | CNT_INTVAL },
{ "LED F5", 11 | CNT_INTVAL },
{ "LED F6", 15 | CNT_INTVAL },
{ "LED F7", 3 | CNT_INTVAL },
{ "LED F8", 7 | CNT_INTVAL },
{ "LED touch1", 10 | CNT_INTVAL },
{ "LED touch2", 14 | CNT_INTVAL },
{ "LED touch3", 2 | CNT_INTVAL },
{ "LED touch4", 6 | CNT_INTVAL },
{ "LED touch5", 9 | CNT_INTVAL },
{ "LED touch6", 13 | CNT_INTVAL },
{ "LED touch7", 1 | CNT_INTVAL },
{ "LED touch8", 5 | CNT_INTVAL },
{ "LED left", 18 | CNT_INTVAL },
{ "LED right", 22 | CNT_INTVAL },
{ "LED up", 16 | CNT_INTVAL },
{ "LED down", 20 | CNT_INTVAL },
{ "LED stop", 23 | CNT_INTVAL },
{ "LED play", 21 | CNT_INTVAL },
{ "LED record", 19 | CNT_INTVAL },
{ "LED listen", 17 | CNT_INTVAL },
{ "LED lcd", 30 | CNT_INTVAL },
{ "LED menu", 28 | CNT_INTVAL },
{ "LED sound", 31 | CNT_INTVAL },
{ "LED esc", 29 | CNT_INTVAL },
{ "LED view", 27 | CNT_INTVAL },
{ "LED enter", 24 | CNT_INTVAL },
{ "LED control", 26 | CNT_INTVAL }
};
static struct caiaq_controller a8dj_controller[] = {
{ "Current input mode", 0 | CNT_INTVAL },
{ "GND lift for TC Vinyl mode", 24 + 0 },
{ "GND lift for TC CD/Line mode", 24 + 1 },
{ "GND lift for phono mode", 24 + 2 },
{ "Software lock", 40 }
};
static struct caiaq_controller a4dj_controller[] = {
{ "Current input mode", 0 | CNT_INTVAL }
};
static struct caiaq_controller kontrolx1_controller[] = {
{ "LED FX A: ON", 7 | CNT_INTVAL },
{ "LED FX A: 1", 6 | CNT_INTVAL },
{ "LED FX A: 2", 5 | CNT_INTVAL },
{ "LED FX A: 3", 4 | CNT_INTVAL },
{ "LED FX B: ON", 3 | CNT_INTVAL },
{ "LED FX B: 1", 2 | CNT_INTVAL },
{ "LED FX B: 2", 1 | CNT_INTVAL },
{ "LED FX B: 3", 0 | CNT_INTVAL },
{ "LED Hotcue", 28 | CNT_INTVAL },
{ "LED Shift (white)", 29 | CNT_INTVAL },
{ "LED Shift (green)", 30 | CNT_INTVAL },
{ "LED Deck A: FX1", 24 | CNT_INTVAL },
{ "LED Deck A: FX2", 25 | CNT_INTVAL },
{ "LED Deck A: IN", 17 | CNT_INTVAL },
{ "LED Deck A: OUT", 16 | CNT_INTVAL },
{ "LED Deck A: < BEAT", 19 | CNT_INTVAL },
{ "LED Deck A: BEAT >", 18 | CNT_INTVAL },
{ "LED Deck A: CUE/ABS", 21 | CNT_INTVAL },
{ "LED Deck A: CUP/REL", 20 | CNT_INTVAL },
{ "LED Deck A: PLAY", 23 | CNT_INTVAL },
{ "LED Deck A: SYNC", 22 | CNT_INTVAL },
{ "LED Deck B: FX1", 26 | CNT_INTVAL },
{ "LED Deck B: FX2", 27 | CNT_INTVAL },
{ "LED Deck B: IN", 15 | CNT_INTVAL },
{ "LED Deck B: OUT", 14 | CNT_INTVAL },
{ "LED Deck B: < BEAT", 13 | CNT_INTVAL },
{ "LED Deck B: BEAT >", 12 | CNT_INTVAL },
{ "LED Deck B: CUE/ABS", 11 | CNT_INTVAL },
{ "LED Deck B: CUP/REL", 10 | CNT_INTVAL },
{ "LED Deck B: PLAY", 9 | CNT_INTVAL },
{ "LED Deck B: SYNC", 8 | CNT_INTVAL },
};
static struct caiaq_controller kontrols4_controller[] = {
{ "LED: Master: Quant", 10 | CNT_INTVAL },
{ "LED: Master: Headphone", 11 | CNT_INTVAL },
{ "LED: Master: Master", 12 | CNT_INTVAL },
{ "LED: Master: Snap", 14 | CNT_INTVAL },
{ "LED: Master: Warning", 15 | CNT_INTVAL },
{ "LED: Master: Master button", 112 | CNT_INTVAL },
{ "LED: Master: Snap button", 113 | CNT_INTVAL },
{ "LED: Master: Rec", 118 | CNT_INTVAL },
{ "LED: Master: Size", 119 | CNT_INTVAL },
{ "LED: Master: Quant button", 120 | CNT_INTVAL },
{ "LED: Master: Browser button", 121 | CNT_INTVAL },
{ "LED: Master: Play button", 126 | CNT_INTVAL },
{ "LED: Master: Undo button", 127 | CNT_INTVAL },
{ "LED: Channel A: >", 4 | CNT_INTVAL },
{ "LED: Channel A: <", 5 | CNT_INTVAL },
{ "LED: Channel A: Meter 1", 97 | CNT_INTVAL },
{ "LED: Channel A: Meter 2", 98 | CNT_INTVAL },
{ "LED: Channel A: Meter 3", 99 | CNT_INTVAL },
{ "LED: Channel A: Meter 4", 100 | CNT_INTVAL },
{ "LED: Channel A: Meter 5", 101 | CNT_INTVAL },
{ "LED: Channel A: Meter 6", 102 | CNT_INTVAL },
{ "LED: Channel A: Meter clip", 103 | CNT_INTVAL },
{ "LED: Channel A: Active", 114 | CNT_INTVAL },
{ "LED: Channel A: Cue", 116 | CNT_INTVAL },
{ "LED: Channel A: FX1", 149 | CNT_INTVAL },
{ "LED: Channel A: FX2", 148 | CNT_INTVAL },
{ "LED: Channel B: >", 2 | CNT_INTVAL },
{ "LED: Channel B: <", 3 | CNT_INTVAL },
{ "LED: Channel B: Meter 1", 89 | CNT_INTVAL },
{ "LED: Channel B: Meter 2", 90 | CNT_INTVAL },
{ "LED: Channel B: Meter 3", 91 | CNT_INTVAL },
{ "LED: Channel B: Meter 4", 92 | CNT_INTVAL },
{ "LED: Channel B: Meter 5", 93 | CNT_INTVAL },
{ "LED: Channel B: Meter 6", 94 | CNT_INTVAL },
{ "LED: Channel B: Meter clip", 95 | CNT_INTVAL },
{ "LED: Channel B: Active", 122 | CNT_INTVAL },
{ "LED: Channel B: Cue", 125 | CNT_INTVAL },
{ "LED: Channel B: FX1", 147 | CNT_INTVAL },
{ "LED: Channel B: FX2", 146 | CNT_INTVAL },
{ "LED: Channel C: >", 6 | CNT_INTVAL },
{ "LED: Channel C: <", 7 | CNT_INTVAL },
{ "LED: Channel C: Meter 1", 105 | CNT_INTVAL },
{ "LED: Channel C: Meter 2", 106 | CNT_INTVAL },
{ "LED: Channel C: Meter 3", 107 | CNT_INTVAL },
{ "LED: Channel C: Meter 4", 108 | CNT_INTVAL },
{ "LED: Channel C: Meter 5", 109 | CNT_INTVAL },
{ "LED: Channel C: Meter 6", 110 | CNT_INTVAL },
{ "LED: Channel C: Meter clip", 111 | CNT_INTVAL },
{ "LED: Channel C: Active", 115 | CNT_INTVAL },
{ "LED: Channel C: Cue", 117 | CNT_INTVAL },
{ "LED: Channel C: FX1", 151 | CNT_INTVAL },
{ "LED: Channel C: FX2", 150 | CNT_INTVAL },
{ "LED: Channel D: >", 0 | CNT_INTVAL },
{ "LED: Channel D: <", 1 | CNT_INTVAL },
{ "LED: Channel D: Meter 1", 81 | CNT_INTVAL },
{ "LED: Channel D: Meter 2", 82 | CNT_INTVAL },
{ "LED: Channel D: Meter 3", 83 | CNT_INTVAL },
{ "LED: Channel D: Meter 4", 84 | CNT_INTVAL },
{ "LED: Channel D: Meter 5", 85 | CNT_INTVAL },
{ "LED: Channel D: Meter 6", 86 | CNT_INTVAL },
{ "LED: Channel D: Meter clip", 87 | CNT_INTVAL },
{ "LED: Channel D: Active", 123 | CNT_INTVAL },
{ "LED: Channel D: Cue", 124 | CNT_INTVAL },
{ "LED: Channel D: FX1", 145 | CNT_INTVAL },
{ "LED: Channel D: FX2", 144 | CNT_INTVAL },
{ "LED: Deck A: 1 (blue)", 22 | CNT_INTVAL },
{ "LED: Deck A: 1 (green)", 23 | CNT_INTVAL },
{ "LED: Deck A: 2 (blue)", 20 | CNT_INTVAL },
{ "LED: Deck A: 2 (green)", 21 | CNT_INTVAL },
{ "LED: Deck A: 3 (blue)", 18 | CNT_INTVAL },
{ "LED: Deck A: 3 (green)", 19 | CNT_INTVAL },
{ "LED: Deck A: 4 (blue)", 16 | CNT_INTVAL },
{ "LED: Deck A: 4 (green)", 17 | CNT_INTVAL },
{ "LED: Deck A: Load", 44 | CNT_INTVAL },
{ "LED: Deck A: Deck C button", 45 | CNT_INTVAL },
{ "LED: Deck A: In", 47 | CNT_INTVAL },
{ "LED: Deck A: Out", 46 | CNT_INTVAL },
{ "LED: Deck A: Shift", 24 | CNT_INTVAL },
{ "LED: Deck A: Sync", 27 | CNT_INTVAL },
{ "LED: Deck A: Cue", 26 | CNT_INTVAL },
{ "LED: Deck A: Play", 25 | CNT_INTVAL },
{ "LED: Deck A: Tempo up", 33 | CNT_INTVAL },
{ "LED: Deck A: Tempo down", 32 | CNT_INTVAL },
{ "LED: Deck A: Master", 34 | CNT_INTVAL },
{ "LED: Deck A: Keylock", 35 | CNT_INTVAL },
{ "LED: Deck A: Deck A", 37 | CNT_INTVAL },
{ "LED: Deck A: Deck C", 36 | CNT_INTVAL },
{ "LED: Deck A: Samples", 38 | CNT_INTVAL },
{ "LED: Deck A: On Air", 39 | CNT_INTVAL },
{ "LED: Deck A: Sample 1", 31 | CNT_INTVAL },
{ "LED: Deck A: Sample 2", 30 | CNT_INTVAL },
{ "LED: Deck A: Sample 3", 29 | CNT_INTVAL },
{ "LED: Deck A: Sample 4", 28 | CNT_INTVAL },
{ "LED: Deck A: Digit 1 - A", 55 | CNT_INTVAL },
{ "LED: Deck A: Digit 1 - B", 54 | CNT_INTVAL },
{ "LED: Deck A: Digit 1 - C", 53 | CNT_INTVAL },
{ "LED: Deck A: Digit 1 - D", 52 | CNT_INTVAL },
{ "LED: Deck A: Digit 1 - E", 51 | CNT_INTVAL },
{ "LED: Deck A: Digit 1 - F", 50 | CNT_INTVAL },
{ "LED: Deck A: Digit 1 - G", 49 | CNT_INTVAL },
{ "LED: Deck A: Digit 1 - dot", 48 | CNT_INTVAL },
{ "LED: Deck A: Digit 2 - A", 63 | CNT_INTVAL },
{ "LED: Deck A: Digit 2 - B", 62 | CNT_INTVAL },
{ "LED: Deck A: Digit 2 - C", 61 | CNT_INTVAL },
{ "LED: Deck A: Digit 2 - D", 60 | CNT_INTVAL },
{ "LED: Deck A: Digit 2 - E", 59 | CNT_INTVAL },
{ "LED: Deck A: Digit 2 - F", 58 | CNT_INTVAL },
{ "LED: Deck A: Digit 2 - G", 57 | CNT_INTVAL },
{ "LED: Deck A: Digit 2 - dot", 56 | CNT_INTVAL },
{ "LED: Deck B: 1 (blue)", 78 | CNT_INTVAL },
{ "LED: Deck B: 1 (green)", 79 | CNT_INTVAL },
{ "LED: Deck B: 2 (blue)", 76 | CNT_INTVAL },
{ "LED: Deck B: 2 (green)", 77 | CNT_INTVAL },
{ "LED: Deck B: 3 (blue)", 74 | CNT_INTVAL },
{ "LED: Deck B: 3 (green)", 75 | CNT_INTVAL },
{ "LED: Deck B: 4 (blue)", 72 | CNT_INTVAL },
{ "LED: Deck B: 4 (green)", 73 | CNT_INTVAL },
{ "LED: Deck B: Load", 180 | CNT_INTVAL },
{ "LED: Deck B: Deck D button", 181 | CNT_INTVAL },
{ "LED: Deck B: In", 183 | CNT_INTVAL },
{ "LED: Deck B: Out", 182 | CNT_INTVAL },
{ "LED: Deck B: Shift", 64 | CNT_INTVAL },
{ "LED: Deck B: Sync", 67 | CNT_INTVAL },
{ "LED: Deck B: Cue", 66 | CNT_INTVAL },
{ "LED: Deck B: Play", 65 | CNT_INTVAL },
{ "LED: Deck B: Tempo up", 185 | CNT_INTVAL },
{ "LED: Deck B: Tempo down", 184 | CNT_INTVAL },
{ "LED: Deck B: Master", 186 | CNT_INTVAL },
{ "LED: Deck B: Keylock", 187 | CNT_INTVAL },
{ "LED: Deck B: Deck B", 189 | CNT_INTVAL },
{ "LED: Deck B: Deck D", 188 | CNT_INTVAL },
{ "LED: Deck B: Samples", 190 | CNT_INTVAL },
{ "LED: Deck B: On Air", 191 | CNT_INTVAL },
{ "LED: Deck B: Sample 1", 71 | CNT_INTVAL },
{ "LED: Deck B: Sample 2", 70 | CNT_INTVAL },
{ "LED: Deck B: Sample 3", 69 | CNT_INTVAL },
{ "LED: Deck B: Sample 4", 68 | CNT_INTVAL },
{ "LED: Deck B: Digit 1 - A", 175 | CNT_INTVAL },
{ "LED: Deck B: Digit 1 - B", 174 | CNT_INTVAL },
{ "LED: Deck B: Digit 1 - C", 173 | CNT_INTVAL },
{ "LED: Deck B: Digit 1 - D", 172 | CNT_INTVAL },
{ "LED: Deck B: Digit 1 - E", 171 | CNT_INTVAL },
{ "LED: Deck B: Digit 1 - F", 170 | CNT_INTVAL },
{ "LED: Deck B: Digit 1 - G", 169 | CNT_INTVAL },
{ "LED: Deck B: Digit 1 - dot", 168 | CNT_INTVAL },
{ "LED: Deck B: Digit 2 - A", 167 | CNT_INTVAL },
{ "LED: Deck B: Digit 2 - B", 166 | CNT_INTVAL },
{ "LED: Deck B: Digit 2 - C", 165 | CNT_INTVAL },
{ "LED: Deck B: Digit 2 - D", 164 | CNT_INTVAL },
{ "LED: Deck B: Digit 2 - E", 163 | CNT_INTVAL },
{ "LED: Deck B: Digit 2 - F", 162 | CNT_INTVAL },
{ "LED: Deck B: Digit 2 - G", 161 | CNT_INTVAL },
{ "LED: Deck B: Digit 2 - dot", 160 | CNT_INTVAL },
{ "LED: FX1: dry/wet", 153 | CNT_INTVAL },
{ "LED: FX1: 1", 154 | CNT_INTVAL },
{ "LED: FX1: 2", 155 | CNT_INTVAL },
{ "LED: FX1: 3", 156 | CNT_INTVAL },
{ "LED: FX1: Mode", 157 | CNT_INTVAL },
{ "LED: FX2: dry/wet", 129 | CNT_INTVAL },
{ "LED: FX2: 1", 130 | CNT_INTVAL },
{ "LED: FX2: 2", 131 | CNT_INTVAL },
{ "LED: FX2: 3", 132 | CNT_INTVAL },
{ "LED: FX2: Mode", 133 | CNT_INTVAL },
};
static struct caiaq_controller maschine_controller[] = {
{ "LED: Pad 1", 3 | CNT_INTVAL },
{ "LED: Pad 2", 2 | CNT_INTVAL },
{ "LED: Pad 3", 1 | CNT_INTVAL },
{ "LED: Pad 4", 0 | CNT_INTVAL },
{ "LED: Pad 5", 7 | CNT_INTVAL },
{ "LED: Pad 6", 6 | CNT_INTVAL },
{ "LED: Pad 7", 5 | CNT_INTVAL },
{ "LED: Pad 8", 4 | CNT_INTVAL },
{ "LED: Pad 9", 11 | CNT_INTVAL },
{ "LED: Pad 10", 10 | CNT_INTVAL },
{ "LED: Pad 11", 9 | CNT_INTVAL },
{ "LED: Pad 12", 8 | CNT_INTVAL },
{ "LED: Pad 13", 15 | CNT_INTVAL },
{ "LED: Pad 14", 14 | CNT_INTVAL },
{ "LED: Pad 15", 13 | CNT_INTVAL },
{ "LED: Pad 16", 12 | CNT_INTVAL },
{ "LED: Mute", 16 | CNT_INTVAL },
{ "LED: Solo", 17 | CNT_INTVAL },
{ "LED: Select", 18 | CNT_INTVAL },
{ "LED: Duplicate", 19 | CNT_INTVAL },
{ "LED: Navigate", 20 | CNT_INTVAL },
{ "LED: Pad Mode", 21 | CNT_INTVAL },
{ "LED: Pattern", 22 | CNT_INTVAL },
{ "LED: Scene", 23 | CNT_INTVAL },
{ "LED: Shift", 24 | CNT_INTVAL },
{ "LED: Erase", 25 | CNT_INTVAL },
{ "LED: Grid", 26 | CNT_INTVAL },
{ "LED: Right Bottom", 27 | CNT_INTVAL },
{ "LED: Rec", 28 | CNT_INTVAL },
{ "LED: Play", 29 | CNT_INTVAL },
{ "LED: Left Bottom", 32 | CNT_INTVAL },
{ "LED: Restart", 33 | CNT_INTVAL },
{ "LED: Group A", 41 | CNT_INTVAL },
{ "LED: Group B", 40 | CNT_INTVAL },
{ "LED: Group C", 37 | CNT_INTVAL },
{ "LED: Group D", 36 | CNT_INTVAL },
{ "LED: Group E", 39 | CNT_INTVAL },
{ "LED: Group F", 38 | CNT_INTVAL },
{ "LED: Group G", 35 | CNT_INTVAL },
{ "LED: Group H", 34 | CNT_INTVAL },
{ "LED: Auto Write", 42 | CNT_INTVAL },
{ "LED: Snap", 43 | CNT_INTVAL },
{ "LED: Right Top", 44 | CNT_INTVAL },
{ "LED: Left Top", 45 | CNT_INTVAL },
{ "LED: Sampling", 46 | CNT_INTVAL },
{ "LED: Browse", 47 | CNT_INTVAL },
{ "LED: Step", 48 | CNT_INTVAL },
{ "LED: Control", 49 | CNT_INTVAL },
{ "LED: Top Button 1", 57 | CNT_INTVAL },
{ "LED: Top Button 2", 56 | CNT_INTVAL },
{ "LED: Top Button 3", 55 | CNT_INTVAL },
{ "LED: Top Button 4", 54 | CNT_INTVAL },
{ "LED: Top Button 5", 53 | CNT_INTVAL },
{ "LED: Top Button 6", 52 | CNT_INTVAL },
{ "LED: Top Button 7", 51 | CNT_INTVAL },
{ "LED: Top Button 8", 50 | CNT_INTVAL },
{ "LED: Note Repeat", 58 | CNT_INTVAL },
{ "Backlight Display", 59 | CNT_INTVAL }
};
static int add_controls(struct caiaq_controller *c, int num,
struct snd_usb_caiaqdev *cdev)
{
int i, ret;
struct snd_kcontrol *kc;
for (i = 0; i < num; i++, c++) {
kcontrol_template.name = c->name;
kcontrol_template.private_value = c->index;
kc = snd_ctl_new1(&kcontrol_template, cdev);
ret = snd_ctl_add(cdev->chip.card, kc);
if (ret < 0)
return ret;
}
return 0;
}
int snd_usb_caiaq_control_init(struct snd_usb_caiaqdev *cdev)
{
int ret = 0;
switch (cdev->chip.usb_id) {
case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_AK1):
ret = add_controls(ak1_controller,
ARRAY_SIZE(ak1_controller), cdev);
break;
case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_RIGKONTROL2):
ret = add_controls(rk2_controller,
ARRAY_SIZE(rk2_controller), cdev);
break;
case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_RIGKONTROL3):
ret = add_controls(rk3_controller,
ARRAY_SIZE(rk3_controller), cdev);
break;
case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_KORECONTROLLER):
case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_KORECONTROLLER2):
ret = add_controls(kore_controller,
ARRAY_SIZE(kore_controller), cdev);
break;
case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_AUDIO8DJ):
ret = add_controls(a8dj_controller,
ARRAY_SIZE(a8dj_controller), cdev);
break;
case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_AUDIO4DJ):
ret = add_controls(a4dj_controller,
ARRAY_SIZE(a4dj_controller), cdev);
break;
case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_TRAKTORKONTROLX1):
ret = add_controls(kontrolx1_controller,
ARRAY_SIZE(kontrolx1_controller), cdev);
break;
case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_TRAKTORKONTROLS4):
ret = add_controls(kontrols4_controller,
ARRAY_SIZE(kontrols4_controller), cdev);
break;
case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_MASCHINECONTROLLER):
ret = add_controls(maschine_controller,
ARRAY_SIZE(maschine_controller), cdev);
break;
}
return ret;
}

View file

@ -0,0 +1,6 @@
#ifndef CAIAQ_CONTROL_H
#define CAIAQ_CONTROL_H
int snd_usb_caiaq_control_init(struct snd_usb_caiaqdev *cdev);
#endif /* CAIAQ_CONTROL_H */

570
sound/usb/caiaq/device.c Normal file
View file

@ -0,0 +1,570 @@
/*
* caiaq.c: ALSA driver for caiaq/NativeInstruments devices
*
* Copyright (c) 2007 Daniel Mack <daniel@caiaq.de>
* Karsten Wiese <fzu@wemgehoertderstaat.de>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <linux/moduleparam.h>
#include <linux/device.h>
#include <linux/interrupt.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/gfp.h>
#include <linux/usb.h>
#include <sound/initval.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include "device.h"
#include "audio.h"
#include "midi.h"
#include "control.h"
#include "input.h"
MODULE_AUTHOR("Daniel Mack <daniel@caiaq.de>");
MODULE_DESCRIPTION("caiaq USB audio");
MODULE_LICENSE("GPL");
MODULE_SUPPORTED_DEVICE("{{Native Instruments,RigKontrol2},"
"{Native Instruments,RigKontrol3},"
"{Native Instruments,Kore Controller},"
"{Native Instruments,Kore Controller 2},"
"{Native Instruments,Audio Kontrol 1},"
"{Native Instruments,Audio 2 DJ},"
"{Native Instruments,Audio 4 DJ},"
"{Native Instruments,Audio 8 DJ},"
"{Native Instruments,Traktor Audio 2},"
"{Native Instruments,Session I/O},"
"{Native Instruments,GuitarRig mobile},"
"{Native Instruments,Traktor Kontrol X1},"
"{Native Instruments,Traktor Kontrol S4},"
"{Native Instruments,Maschine Controller}}");
static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 0-max */
static char* id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* Id for this card */
static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP; /* Enable this card */
module_param_array(index, int, NULL, 0444);
MODULE_PARM_DESC(index, "Index value for the caiaq sound device");
module_param_array(id, charp, NULL, 0444);
MODULE_PARM_DESC(id, "ID string for the caiaq soundcard.");
module_param_array(enable, bool, NULL, 0444);
MODULE_PARM_DESC(enable, "Enable the caiaq soundcard.");
enum {
SAMPLERATE_44100 = 0,
SAMPLERATE_48000 = 1,
SAMPLERATE_96000 = 2,
SAMPLERATE_192000 = 3,
SAMPLERATE_88200 = 4,
SAMPLERATE_INVALID = 0xff
};
enum {
DEPTH_NONE = 0,
DEPTH_16 = 1,
DEPTH_24 = 2,
DEPTH_32 = 3
};
static struct usb_device_id snd_usb_id_table[] = {
{
.match_flags = USB_DEVICE_ID_MATCH_DEVICE,
.idVendor = USB_VID_NATIVEINSTRUMENTS,
.idProduct = USB_PID_RIGKONTROL2
},
{
.match_flags = USB_DEVICE_ID_MATCH_DEVICE,
.idVendor = USB_VID_NATIVEINSTRUMENTS,
.idProduct = USB_PID_RIGKONTROL3
},
{
.match_flags = USB_DEVICE_ID_MATCH_DEVICE,
.idVendor = USB_VID_NATIVEINSTRUMENTS,
.idProduct = USB_PID_KORECONTROLLER
},
{
.match_flags = USB_DEVICE_ID_MATCH_DEVICE,
.idVendor = USB_VID_NATIVEINSTRUMENTS,
.idProduct = USB_PID_KORECONTROLLER2
},
{
.match_flags = USB_DEVICE_ID_MATCH_DEVICE,
.idVendor = USB_VID_NATIVEINSTRUMENTS,
.idProduct = USB_PID_AK1
},
{
.match_flags = USB_DEVICE_ID_MATCH_DEVICE,
.idVendor = USB_VID_NATIVEINSTRUMENTS,
.idProduct = USB_PID_AUDIO8DJ
},
{
.match_flags = USB_DEVICE_ID_MATCH_DEVICE,
.idVendor = USB_VID_NATIVEINSTRUMENTS,
.idProduct = USB_PID_SESSIONIO
},
{
.match_flags = USB_DEVICE_ID_MATCH_DEVICE,
.idVendor = USB_VID_NATIVEINSTRUMENTS,
.idProduct = USB_PID_GUITARRIGMOBILE
},
{
.match_flags = USB_DEVICE_ID_MATCH_DEVICE,
.idVendor = USB_VID_NATIVEINSTRUMENTS,
.idProduct = USB_PID_AUDIO4DJ
},
{
.match_flags = USB_DEVICE_ID_MATCH_DEVICE,
.idVendor = USB_VID_NATIVEINSTRUMENTS,
.idProduct = USB_PID_AUDIO2DJ
},
{
.match_flags = USB_DEVICE_ID_MATCH_DEVICE,
.idVendor = USB_VID_NATIVEINSTRUMENTS,
.idProduct = USB_PID_TRAKTORKONTROLX1
},
{
.match_flags = USB_DEVICE_ID_MATCH_DEVICE,
.idVendor = USB_VID_NATIVEINSTRUMENTS,
.idProduct = USB_PID_TRAKTORKONTROLS4
},
{
.match_flags = USB_DEVICE_ID_MATCH_DEVICE,
.idVendor = USB_VID_NATIVEINSTRUMENTS,
.idProduct = USB_PID_TRAKTORAUDIO2
},
{
.match_flags = USB_DEVICE_ID_MATCH_DEVICE,
.idVendor = USB_VID_NATIVEINSTRUMENTS,
.idProduct = USB_PID_MASCHINECONTROLLER
},
{ /* terminator */ }
};
static void usb_ep1_command_reply_dispatch (struct urb* urb)
{
int ret;
struct device *dev = &urb->dev->dev;
struct snd_usb_caiaqdev *cdev = urb->context;
unsigned char *buf = urb->transfer_buffer;
if (urb->status || !cdev) {
dev_warn(dev, "received EP1 urb->status = %i\n", urb->status);
return;
}
switch(buf[0]) {
case EP1_CMD_GET_DEVICE_INFO:
memcpy(&cdev->spec, buf+1, sizeof(struct caiaq_device_spec));
cdev->spec.fw_version = le16_to_cpu(cdev->spec.fw_version);
dev_dbg(dev, "device spec (firmware %d): audio: %d in, %d out, "
"MIDI: %d in, %d out, data alignment %d\n",
cdev->spec.fw_version,
cdev->spec.num_analog_audio_in,
cdev->spec.num_analog_audio_out,
cdev->spec.num_midi_in,
cdev->spec.num_midi_out,
cdev->spec.data_alignment);
cdev->spec_received++;
wake_up(&cdev->ep1_wait_queue);
break;
case EP1_CMD_AUDIO_PARAMS:
cdev->audio_parm_answer = buf[1];
wake_up(&cdev->ep1_wait_queue);
break;
case EP1_CMD_MIDI_READ:
snd_usb_caiaq_midi_handle_input(cdev, buf[1], buf + 3, buf[2]);
break;
case EP1_CMD_READ_IO:
if (cdev->chip.usb_id ==
USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_AUDIO8DJ)) {
if (urb->actual_length > sizeof(cdev->control_state))
urb->actual_length = sizeof(cdev->control_state);
memcpy(cdev->control_state, buf + 1, urb->actual_length);
wake_up(&cdev->ep1_wait_queue);
break;
}
#ifdef CONFIG_SND_USB_CAIAQ_INPUT
case EP1_CMD_READ_ERP:
case EP1_CMD_READ_ANALOG:
snd_usb_caiaq_input_dispatch(cdev, buf, urb->actual_length);
#endif
break;
}
cdev->ep1_in_urb.actual_length = 0;
ret = usb_submit_urb(&cdev->ep1_in_urb, GFP_ATOMIC);
if (ret < 0)
dev_err(dev, "unable to submit urb. OOM!?\n");
}
int snd_usb_caiaq_send_command(struct snd_usb_caiaqdev *cdev,
unsigned char command,
const unsigned char *buffer,
int len)
{
int actual_len;
struct usb_device *usb_dev = cdev->chip.dev;
if (!usb_dev)
return -EIO;
if (len > EP1_BUFSIZE - 1)
len = EP1_BUFSIZE - 1;
if (buffer && len > 0)
memcpy(cdev->ep1_out_buf+1, buffer, len);
cdev->ep1_out_buf[0] = command;
return usb_bulk_msg(usb_dev, usb_sndbulkpipe(usb_dev, 1),
cdev->ep1_out_buf, len+1, &actual_len, 200);
}
int snd_usb_caiaq_send_command_bank(struct snd_usb_caiaqdev *cdev,
unsigned char command,
unsigned char bank,
const unsigned char *buffer,
int len)
{
int actual_len;
struct usb_device *usb_dev = cdev->chip.dev;
if (!usb_dev)
return -EIO;
if (len > EP1_BUFSIZE - 2)
len = EP1_BUFSIZE - 2;
if (buffer && len > 0)
memcpy(cdev->ep1_out_buf+2, buffer, len);
cdev->ep1_out_buf[0] = command;
cdev->ep1_out_buf[1] = bank;
return usb_bulk_msg(usb_dev, usb_sndbulkpipe(usb_dev, 1),
cdev->ep1_out_buf, len+2, &actual_len, 200);
}
int snd_usb_caiaq_set_audio_params (struct snd_usb_caiaqdev *cdev,
int rate, int depth, int bpp)
{
int ret;
char tmp[5];
struct device *dev = caiaqdev_to_dev(cdev);
switch (rate) {
case 44100: tmp[0] = SAMPLERATE_44100; break;
case 48000: tmp[0] = SAMPLERATE_48000; break;
case 88200: tmp[0] = SAMPLERATE_88200; break;
case 96000: tmp[0] = SAMPLERATE_96000; break;
case 192000: tmp[0] = SAMPLERATE_192000; break;
default: return -EINVAL;
}
switch (depth) {
case 16: tmp[1] = DEPTH_16; break;
case 24: tmp[1] = DEPTH_24; break;
default: return -EINVAL;
}
tmp[2] = bpp & 0xff;
tmp[3] = bpp >> 8;
tmp[4] = 1; /* packets per microframe */
dev_dbg(dev, "setting audio params: %d Hz, %d bits, %d bpp\n",
rate, depth, bpp);
cdev->audio_parm_answer = -1;
ret = snd_usb_caiaq_send_command(cdev, EP1_CMD_AUDIO_PARAMS,
tmp, sizeof(tmp));
if (ret)
return ret;
if (!wait_event_timeout(cdev->ep1_wait_queue,
cdev->audio_parm_answer >= 0, HZ))
return -EPIPE;
if (cdev->audio_parm_answer != 1)
dev_dbg(dev, "unable to set the device's audio params\n");
else
cdev->bpp = bpp;
return cdev->audio_parm_answer == 1 ? 0 : -EINVAL;
}
int snd_usb_caiaq_set_auto_msg(struct snd_usb_caiaqdev *cdev,
int digital, int analog, int erp)
{
char tmp[3] = { digital, analog, erp };
return snd_usb_caiaq_send_command(cdev, EP1_CMD_AUTO_MSG,
tmp, sizeof(tmp));
}
static void setup_card(struct snd_usb_caiaqdev *cdev)
{
int ret;
char val[4];
struct device *dev = caiaqdev_to_dev(cdev);
/* device-specific startup specials */
switch (cdev->chip.usb_id) {
case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_RIGKONTROL2):
/* RigKontrol2 - display centered dash ('-') */
val[0] = 0x00;
val[1] = 0x00;
val[2] = 0x01;
snd_usb_caiaq_send_command(cdev, EP1_CMD_WRITE_IO, val, 3);
break;
case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_RIGKONTROL3):
/* RigKontrol2 - display two centered dashes ('--') */
val[0] = 0x00;
val[1] = 0x40;
val[2] = 0x40;
val[3] = 0x00;
snd_usb_caiaq_send_command(cdev, EP1_CMD_WRITE_IO, val, 4);
break;
case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_AK1):
/* Audio Kontrol 1 - make USB-LED stop blinking */
val[0] = 0x00;
snd_usb_caiaq_send_command(cdev, EP1_CMD_WRITE_IO, val, 1);
break;
case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_AUDIO8DJ):
/* Audio 8 DJ - trigger read of current settings */
cdev->control_state[0] = 0xff;
snd_usb_caiaq_set_auto_msg(cdev, 1, 0, 0);
snd_usb_caiaq_send_command(cdev, EP1_CMD_READ_IO, NULL, 0);
if (!wait_event_timeout(cdev->ep1_wait_queue,
cdev->control_state[0] != 0xff, HZ))
return;
/* fix up some defaults */
if ((cdev->control_state[1] != 2) ||
(cdev->control_state[2] != 3) ||
(cdev->control_state[4] != 2)) {
cdev->control_state[1] = 2;
cdev->control_state[2] = 3;
cdev->control_state[4] = 2;
snd_usb_caiaq_send_command(cdev,
EP1_CMD_WRITE_IO, cdev->control_state, 6);
}
break;
}
if (cdev->spec.num_analog_audio_out +
cdev->spec.num_analog_audio_in +
cdev->spec.num_digital_audio_out +
cdev->spec.num_digital_audio_in > 0) {
ret = snd_usb_caiaq_audio_init(cdev);
if (ret < 0)
dev_err(dev, "Unable to set up audio system (ret=%d)\n", ret);
}
if (cdev->spec.num_midi_in +
cdev->spec.num_midi_out > 0) {
ret = snd_usb_caiaq_midi_init(cdev);
if (ret < 0)
dev_err(dev, "Unable to set up MIDI system (ret=%d)\n", ret);
}
#ifdef CONFIG_SND_USB_CAIAQ_INPUT
ret = snd_usb_caiaq_input_init(cdev);
if (ret < 0)
dev_err(dev, "Unable to set up input system (ret=%d)\n", ret);
#endif
/* finally, register the card and all its sub-instances */
ret = snd_card_register(cdev->chip.card);
if (ret < 0) {
dev_err(dev, "snd_card_register() returned %d\n", ret);
snd_card_free(cdev->chip.card);
}
ret = snd_usb_caiaq_control_init(cdev);
if (ret < 0)
dev_err(dev, "Unable to set up control system (ret=%d)\n", ret);
}
static int create_card(struct usb_device *usb_dev,
struct usb_interface *intf,
struct snd_card **cardp)
{
int devnum;
int err;
struct snd_card *card;
struct snd_usb_caiaqdev *cdev;
for (devnum = 0; devnum < SNDRV_CARDS; devnum++)
if (enable[devnum])
break;
if (devnum >= SNDRV_CARDS)
return -ENODEV;
err = snd_card_new(&intf->dev,
index[devnum], id[devnum], THIS_MODULE,
sizeof(struct snd_usb_caiaqdev), &card);
if (err < 0)
return err;
cdev = caiaqdev(card);
cdev->chip.dev = usb_dev;
cdev->chip.card = card;
cdev->chip.usb_id = USB_ID(le16_to_cpu(usb_dev->descriptor.idVendor),
le16_to_cpu(usb_dev->descriptor.idProduct));
spin_lock_init(&cdev->spinlock);
*cardp = card;
return 0;
}
static int init_card(struct snd_usb_caiaqdev *cdev)
{
char *c, usbpath[32];
struct usb_device *usb_dev = cdev->chip.dev;
struct snd_card *card = cdev->chip.card;
struct device *dev = caiaqdev_to_dev(cdev);
int err, len;
if (usb_set_interface(usb_dev, 0, 1) != 0) {
dev_err(dev, "can't set alt interface.\n");
return -EIO;
}
usb_init_urb(&cdev->ep1_in_urb);
usb_init_urb(&cdev->midi_out_urb);
usb_fill_bulk_urb(&cdev->ep1_in_urb, usb_dev,
usb_rcvbulkpipe(usb_dev, 0x1),
cdev->ep1_in_buf, EP1_BUFSIZE,
usb_ep1_command_reply_dispatch, cdev);
usb_fill_bulk_urb(&cdev->midi_out_urb, usb_dev,
usb_sndbulkpipe(usb_dev, 0x1),
cdev->midi_out_buf, EP1_BUFSIZE,
snd_usb_caiaq_midi_output_done, cdev);
init_waitqueue_head(&cdev->ep1_wait_queue);
init_waitqueue_head(&cdev->prepare_wait_queue);
if (usb_submit_urb(&cdev->ep1_in_urb, GFP_KERNEL) != 0)
return -EIO;
err = snd_usb_caiaq_send_command(cdev, EP1_CMD_GET_DEVICE_INFO, NULL, 0);
if (err)
return err;
if (!wait_event_timeout(cdev->ep1_wait_queue, cdev->spec_received, HZ))
return -ENODEV;
usb_string(usb_dev, usb_dev->descriptor.iManufacturer,
cdev->vendor_name, CAIAQ_USB_STR_LEN);
usb_string(usb_dev, usb_dev->descriptor.iProduct,
cdev->product_name, CAIAQ_USB_STR_LEN);
strlcpy(card->driver, MODNAME, sizeof(card->driver));
strlcpy(card->shortname, cdev->product_name, sizeof(card->shortname));
strlcpy(card->mixername, cdev->product_name, sizeof(card->mixername));
/* if the id was not passed as module option, fill it with a shortened
* version of the product string which does not contain any
* whitespaces */
if (*card->id == '\0') {
char id[sizeof(card->id)];
memset(id, 0, sizeof(id));
for (c = card->shortname, len = 0;
*c && len < sizeof(card->id); c++)
if (*c != ' ')
id[len++] = *c;
snd_card_set_id(card, id);
}
usb_make_path(usb_dev, usbpath, sizeof(usbpath));
snprintf(card->longname, sizeof(card->longname), "%s %s (%s)",
cdev->vendor_name, cdev->product_name, usbpath);
setup_card(cdev);
return 0;
}
static int snd_probe(struct usb_interface *intf,
const struct usb_device_id *id)
{
int ret;
struct snd_card *card = NULL;
struct usb_device *usb_dev = interface_to_usbdev(intf);
ret = create_card(usb_dev, intf, &card);
if (ret < 0)
return ret;
usb_set_intfdata(intf, card);
ret = init_card(caiaqdev(card));
if (ret < 0) {
dev_err(&usb_dev->dev, "unable to init card! (ret=%d)\n", ret);
snd_card_free(card);
return ret;
}
return 0;
}
static void snd_disconnect(struct usb_interface *intf)
{
struct snd_card *card = usb_get_intfdata(intf);
struct device *dev = intf->usb_dev;
struct snd_usb_caiaqdev *cdev;
if (!card)
return;
cdev = caiaqdev(card);
dev_dbg(dev, "%s(%p)\n", __func__, intf);
snd_card_disconnect(card);
#ifdef CONFIG_SND_USB_CAIAQ_INPUT
snd_usb_caiaq_input_free(cdev);
#endif
snd_usb_caiaq_audio_free(cdev);
usb_kill_urb(&cdev->ep1_in_urb);
usb_kill_urb(&cdev->midi_out_urb);
snd_card_free(card);
usb_reset_device(interface_to_usbdev(intf));
}
MODULE_DEVICE_TABLE(usb, snd_usb_id_table);
static struct usb_driver snd_usb_driver = {
.name = MODNAME,
.probe = snd_probe,
.disconnect = snd_disconnect,
.id_table = snd_usb_id_table,
};
module_usb_driver(snd_usb_driver);

137
sound/usb/caiaq/device.h Normal file
View file

@ -0,0 +1,137 @@
#ifndef CAIAQ_DEVICE_H
#define CAIAQ_DEVICE_H
#include "../usbaudio.h"
#define USB_VID_NATIVEINSTRUMENTS 0x17cc
#define USB_PID_RIGKONTROL2 0x1969
#define USB_PID_RIGKONTROL3 0x1940
#define USB_PID_KORECONTROLLER 0x4711
#define USB_PID_KORECONTROLLER2 0x4712
#define USB_PID_AK1 0x0815
#define USB_PID_AUDIO2DJ 0x041c
#define USB_PID_AUDIO4DJ 0x0839
#define USB_PID_AUDIO8DJ 0x1978
#define USB_PID_SESSIONIO 0x1915
#define USB_PID_GUITARRIGMOBILE 0x0d8d
#define USB_PID_TRAKTORKONTROLX1 0x2305
#define USB_PID_TRAKTORKONTROLS4 0xbaff
#define USB_PID_TRAKTORAUDIO2 0x041d
#define USB_PID_MASCHINECONTROLLER 0x0808
#define EP1_BUFSIZE 64
#define EP4_BUFSIZE 512
#define CAIAQ_USB_STR_LEN 0xff
#define MAX_STREAMS 32
#define MODNAME "snd-usb-caiaq"
#define EP1_CMD_GET_DEVICE_INFO 0x1
#define EP1_CMD_READ_ERP 0x2
#define EP1_CMD_READ_ANALOG 0x3
#define EP1_CMD_READ_IO 0x4
#define EP1_CMD_WRITE_IO 0x5
#define EP1_CMD_MIDI_READ 0x6
#define EP1_CMD_MIDI_WRITE 0x7
#define EP1_CMD_AUDIO_PARAMS 0x9
#define EP1_CMD_AUTO_MSG 0xb
#define EP1_CMD_DIMM_LEDS 0xc
struct caiaq_device_spec {
unsigned short fw_version;
unsigned char hw_subtype;
unsigned char num_erp;
unsigned char num_analog_in;
unsigned char num_digital_in;
unsigned char num_digital_out;
unsigned char num_analog_audio_out;
unsigned char num_analog_audio_in;
unsigned char num_digital_audio_out;
unsigned char num_digital_audio_in;
unsigned char num_midi_out;
unsigned char num_midi_in;
unsigned char data_alignment;
} __attribute__ ((packed));
struct snd_usb_caiaq_cb_info;
struct snd_usb_caiaqdev {
struct snd_usb_audio chip;
struct urb ep1_in_urb;
struct urb midi_out_urb;
struct urb **data_urbs_in;
struct urb **data_urbs_out;
struct snd_usb_caiaq_cb_info *data_cb_info;
unsigned char ep1_in_buf[EP1_BUFSIZE];
unsigned char ep1_out_buf[EP1_BUFSIZE];
unsigned char midi_out_buf[EP1_BUFSIZE];
struct caiaq_device_spec spec;
spinlock_t spinlock;
wait_queue_head_t ep1_wait_queue;
wait_queue_head_t prepare_wait_queue;
int spec_received, audio_parm_answer;
int midi_out_active;
char vendor_name[CAIAQ_USB_STR_LEN];
char product_name[CAIAQ_USB_STR_LEN];
int n_streams, n_audio_in, n_audio_out;
int streaming, first_packet, output_running;
int audio_in_buf_pos[MAX_STREAMS];
int audio_out_buf_pos[MAX_STREAMS];
int period_in_count[MAX_STREAMS];
int period_out_count[MAX_STREAMS];
int input_panic, output_panic, warned;
char *audio_in_buf, *audio_out_buf;
unsigned int samplerates, bpp;
unsigned long outurb_active_mask;
struct snd_pcm_substream *sub_playback[MAX_STREAMS];
struct snd_pcm_substream *sub_capture[MAX_STREAMS];
/* Controls */
unsigned char control_state[256];
unsigned char ep8_out_buf[2];
/* Linux input */
#ifdef CONFIG_SND_USB_CAIAQ_INPUT
struct input_dev *input_dev;
char phys[64]; /* physical device path */
unsigned short keycode[128];
struct urb *ep4_in_urb;
unsigned char ep4_in_buf[EP4_BUFSIZE];
#endif
/* ALSA */
struct snd_pcm *pcm;
struct snd_pcm_hardware pcm_info;
struct snd_rawmidi *rmidi;
struct snd_rawmidi_substream *midi_receive_substream;
struct snd_rawmidi_substream *midi_out_substream;
};
struct snd_usb_caiaq_cb_info {
struct snd_usb_caiaqdev *cdev;
int index;
};
#define caiaqdev(c) ((struct snd_usb_caiaqdev*)(c)->private_data)
#define caiaqdev_to_dev(d) (d->chip.card->dev)
int snd_usb_caiaq_set_audio_params (struct snd_usb_caiaqdev *cdev, int rate, int depth, int bbp);
int snd_usb_caiaq_set_auto_msg (struct snd_usb_caiaqdev *cdev, int digital, int analog, int erp);
int snd_usb_caiaq_send_command(struct snd_usb_caiaqdev *cdev,
unsigned char command,
const unsigned char *buffer,
int len);
int snd_usb_caiaq_send_command_bank(struct snd_usb_caiaqdev *cdev,
unsigned char command,
unsigned char bank,
const unsigned char *buffer,
int len);
#endif /* CAIAQ_DEVICE_H */

846
sound/usb/caiaq/input.c Normal file
View file

@ -0,0 +1,846 @@
/*
* Copyright (c) 2006,2007 Daniel Mack, Tim Ruetz
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <linux/device.h>
#include <linux/gfp.h>
#include <linux/init.h>
#include <linux/usb.h>
#include <linux/usb/input.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include "device.h"
#include "input.h"
static unsigned short keycode_ak1[] = { KEY_C, KEY_B, KEY_A };
static unsigned short keycode_rk2[] = { KEY_1, KEY_2, KEY_3, KEY_4,
KEY_5, KEY_6, KEY_7 };
static unsigned short keycode_rk3[] = { KEY_1, KEY_2, KEY_3, KEY_4,
KEY_5, KEY_6, KEY_7, KEY_8, KEY_9 };
static unsigned short keycode_kore[] = {
KEY_FN_F1, /* "menu" */
KEY_FN_F7, /* "lcd backlight */
KEY_FN_F2, /* "control" */
KEY_FN_F3, /* "enter" */
KEY_FN_F4, /* "view" */
KEY_FN_F5, /* "esc" */
KEY_FN_F6, /* "sound" */
KEY_FN_F8, /* array spacer, never triggered. */
KEY_RIGHT,
KEY_DOWN,
KEY_UP,
KEY_LEFT,
KEY_SOUND, /* "listen" */
KEY_RECORD,
KEY_PLAYPAUSE,
KEY_STOP,
BTN_4, /* 8 softkeys */
BTN_3,
BTN_2,
BTN_1,
BTN_8,
BTN_7,
BTN_6,
BTN_5,
KEY_BRL_DOT4, /* touch sensitive knobs */
KEY_BRL_DOT3,
KEY_BRL_DOT2,
KEY_BRL_DOT1,
KEY_BRL_DOT8,
KEY_BRL_DOT7,
KEY_BRL_DOT6,
KEY_BRL_DOT5
};
#define MASCHINE_BUTTONS (42)
#define MASCHINE_BUTTON(X) ((X) + BTN_MISC)
#define MASCHINE_PADS (16)
#define MASCHINE_PAD(X) ((X) + ABS_PRESSURE)
static unsigned short keycode_maschine[] = {
MASCHINE_BUTTON(40), /* mute */
MASCHINE_BUTTON(39), /* solo */
MASCHINE_BUTTON(38), /* select */
MASCHINE_BUTTON(37), /* duplicate */
MASCHINE_BUTTON(36), /* navigate */
MASCHINE_BUTTON(35), /* pad mode */
MASCHINE_BUTTON(34), /* pattern */
MASCHINE_BUTTON(33), /* scene */
KEY_RESERVED, /* spacer */
MASCHINE_BUTTON(30), /* rec */
MASCHINE_BUTTON(31), /* erase */
MASCHINE_BUTTON(32), /* shift */
MASCHINE_BUTTON(28), /* grid */
MASCHINE_BUTTON(27), /* > */
MASCHINE_BUTTON(26), /* < */
MASCHINE_BUTTON(25), /* restart */
MASCHINE_BUTTON(21), /* E */
MASCHINE_BUTTON(22), /* F */
MASCHINE_BUTTON(23), /* G */
MASCHINE_BUTTON(24), /* H */
MASCHINE_BUTTON(20), /* D */
MASCHINE_BUTTON(19), /* C */
MASCHINE_BUTTON(18), /* B */
MASCHINE_BUTTON(17), /* A */
MASCHINE_BUTTON(0), /* control */
MASCHINE_BUTTON(2), /* browse */
MASCHINE_BUTTON(4), /* < */
MASCHINE_BUTTON(6), /* snap */
MASCHINE_BUTTON(7), /* autowrite */
MASCHINE_BUTTON(5), /* > */
MASCHINE_BUTTON(3), /* sampling */
MASCHINE_BUTTON(1), /* step */
MASCHINE_BUTTON(15), /* 8 softkeys */
MASCHINE_BUTTON(14),
MASCHINE_BUTTON(13),
MASCHINE_BUTTON(12),
MASCHINE_BUTTON(11),
MASCHINE_BUTTON(10),
MASCHINE_BUTTON(9),
MASCHINE_BUTTON(8),
MASCHINE_BUTTON(16), /* note repeat */
MASCHINE_BUTTON(29) /* play */
};
#define KONTROLX1_INPUTS (40)
#define KONTROLS4_BUTTONS (12 * 8)
#define KONTROLS4_AXIS (46)
#define KONTROLS4_BUTTON(X) ((X) + BTN_MISC)
#define KONTROLS4_ABS(X) ((X) + ABS_HAT0X)
#define DEG90 (range / 2)
#define DEG180 (range)
#define DEG270 (DEG90 + DEG180)
#define DEG360 (DEG180 * 2)
#define HIGH_PEAK (268)
#define LOW_PEAK (-7)
/* some of these devices have endless rotation potentiometers
* built in which use two tapers, 90 degrees phase shifted.
* this algorithm decodes them to one single value, ranging
* from 0 to 999 */
static unsigned int decode_erp(unsigned char a, unsigned char b)
{
int weight_a, weight_b;
int pos_a, pos_b;
int ret;
int range = HIGH_PEAK - LOW_PEAK;
int mid_value = (HIGH_PEAK + LOW_PEAK) / 2;
weight_b = abs(mid_value - a) - (range / 2 - 100) / 2;
if (weight_b < 0)
weight_b = 0;
if (weight_b > 100)
weight_b = 100;
weight_a = 100 - weight_b;
if (a < mid_value) {
/* 0..90 and 270..360 degrees */
pos_b = b - LOW_PEAK + DEG270;
if (pos_b >= DEG360)
pos_b -= DEG360;
} else
/* 90..270 degrees */
pos_b = HIGH_PEAK - b + DEG90;
if (b > mid_value)
/* 0..180 degrees */
pos_a = a - LOW_PEAK;
else
/* 180..360 degrees */
pos_a = HIGH_PEAK - a + DEG180;
/* interpolate both slider values, depending on weight factors */
/* 0..99 x DEG360 */
ret = pos_a * weight_a + pos_b * weight_b;
/* normalize to 0..999 */
ret *= 10;
ret /= DEG360;
if (ret < 0)
ret += 1000;
if (ret >= 1000)
ret -= 1000;
return ret;
}
#undef DEG90
#undef DEG180
#undef DEG270
#undef DEG360
#undef HIGH_PEAK
#undef LOW_PEAK
static inline void snd_caiaq_input_report_abs(struct snd_usb_caiaqdev *cdev,
int axis, const unsigned char *buf,
int offset)
{
input_report_abs(cdev->input_dev, axis,
(buf[offset * 2] << 8) | buf[offset * 2 + 1]);
}
static void snd_caiaq_input_read_analog(struct snd_usb_caiaqdev *cdev,
const unsigned char *buf,
unsigned int len)
{
struct input_dev *input_dev = cdev->input_dev;
switch (cdev->chip.usb_id) {
case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_RIGKONTROL2):
snd_caiaq_input_report_abs(cdev, ABS_X, buf, 2);
snd_caiaq_input_report_abs(cdev, ABS_Y, buf, 0);
snd_caiaq_input_report_abs(cdev, ABS_Z, buf, 1);
break;
case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_RIGKONTROL3):
case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_KORECONTROLLER):
case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_KORECONTROLLER2):
snd_caiaq_input_report_abs(cdev, ABS_X, buf, 0);
snd_caiaq_input_report_abs(cdev, ABS_Y, buf, 1);
snd_caiaq_input_report_abs(cdev, ABS_Z, buf, 2);
break;
case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_TRAKTORKONTROLX1):
snd_caiaq_input_report_abs(cdev, ABS_HAT0X, buf, 4);
snd_caiaq_input_report_abs(cdev, ABS_HAT0Y, buf, 2);
snd_caiaq_input_report_abs(cdev, ABS_HAT1X, buf, 6);
snd_caiaq_input_report_abs(cdev, ABS_HAT1Y, buf, 1);
snd_caiaq_input_report_abs(cdev, ABS_HAT2X, buf, 7);
snd_caiaq_input_report_abs(cdev, ABS_HAT2Y, buf, 0);
snd_caiaq_input_report_abs(cdev, ABS_HAT3X, buf, 5);
snd_caiaq_input_report_abs(cdev, ABS_HAT3Y, buf, 3);
break;
}
input_sync(input_dev);
}
static void snd_caiaq_input_read_erp(struct snd_usb_caiaqdev *cdev,
const char *buf, unsigned int len)
{
struct input_dev *input_dev = cdev->input_dev;
int i;
switch (cdev->chip.usb_id) {
case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_AK1):
i = decode_erp(buf[0], buf[1]);
input_report_abs(input_dev, ABS_X, i);
input_sync(input_dev);
break;
case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_KORECONTROLLER):
case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_KORECONTROLLER2):
i = decode_erp(buf[7], buf[5]);
input_report_abs(input_dev, ABS_HAT0X, i);
i = decode_erp(buf[12], buf[14]);
input_report_abs(input_dev, ABS_HAT0Y, i);
i = decode_erp(buf[15], buf[13]);
input_report_abs(input_dev, ABS_HAT1X, i);
i = decode_erp(buf[0], buf[2]);
input_report_abs(input_dev, ABS_HAT1Y, i);
i = decode_erp(buf[3], buf[1]);
input_report_abs(input_dev, ABS_HAT2X, i);
i = decode_erp(buf[8], buf[10]);
input_report_abs(input_dev, ABS_HAT2Y, i);
i = decode_erp(buf[11], buf[9]);
input_report_abs(input_dev, ABS_HAT3X, i);
i = decode_erp(buf[4], buf[6]);
input_report_abs(input_dev, ABS_HAT3Y, i);
input_sync(input_dev);
break;
case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_MASCHINECONTROLLER):
/* 4 under the left screen */
input_report_abs(input_dev, ABS_HAT0X, decode_erp(buf[21], buf[20]));
input_report_abs(input_dev, ABS_HAT0Y, decode_erp(buf[15], buf[14]));
input_report_abs(input_dev, ABS_HAT1X, decode_erp(buf[9], buf[8]));
input_report_abs(input_dev, ABS_HAT1Y, decode_erp(buf[3], buf[2]));
/* 4 under the right screen */
input_report_abs(input_dev, ABS_HAT2X, decode_erp(buf[19], buf[18]));
input_report_abs(input_dev, ABS_HAT2Y, decode_erp(buf[13], buf[12]));
input_report_abs(input_dev, ABS_HAT3X, decode_erp(buf[7], buf[6]));
input_report_abs(input_dev, ABS_HAT3Y, decode_erp(buf[1], buf[0]));
/* volume */
input_report_abs(input_dev, ABS_RX, decode_erp(buf[17], buf[16]));
/* tempo */
input_report_abs(input_dev, ABS_RY, decode_erp(buf[11], buf[10]));
/* swing */
input_report_abs(input_dev, ABS_RZ, decode_erp(buf[5], buf[4]));
input_sync(input_dev);
break;
}
}
static void snd_caiaq_input_read_io(struct snd_usb_caiaqdev *cdev,
unsigned char *buf, unsigned int len)
{
struct input_dev *input_dev = cdev->input_dev;
unsigned short *keycode = input_dev->keycode;
int i;
if (!keycode)
return;
if (input_dev->id.product == USB_PID_RIGKONTROL2)
for (i = 0; i < len; i++)
buf[i] = ~buf[i];
for (i = 0; i < input_dev->keycodemax && i < len * 8; i++)
input_report_key(input_dev, keycode[i],
buf[i / 8] & (1 << (i % 8)));
switch (cdev->chip.usb_id) {
case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_KORECONTROLLER):
case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_KORECONTROLLER2):
input_report_abs(cdev->input_dev, ABS_MISC, 255 - buf[4]);
break;
case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_TRAKTORKONTROLX1):
/* rotary encoders */
input_report_abs(cdev->input_dev, ABS_X, buf[5] & 0xf);
input_report_abs(cdev->input_dev, ABS_Y, buf[5] >> 4);
input_report_abs(cdev->input_dev, ABS_Z, buf[6] & 0xf);
input_report_abs(cdev->input_dev, ABS_MISC, buf[6] >> 4);
break;
}
input_sync(input_dev);
}
#define TKS4_MSGBLOCK_SIZE 16
static void snd_usb_caiaq_tks4_dispatch(struct snd_usb_caiaqdev *cdev,
const unsigned char *buf,
unsigned int len)
{
struct device *dev = caiaqdev_to_dev(cdev);
while (len) {
unsigned int i, block_id = (buf[0] << 8) | buf[1];
switch (block_id) {
case 0:
/* buttons */
for (i = 0; i < KONTROLS4_BUTTONS; i++)
input_report_key(cdev->input_dev, KONTROLS4_BUTTON(i),
(buf[4 + (i / 8)] >> (i % 8)) & 1);
break;
case 1:
/* left wheel */
input_report_abs(cdev->input_dev, KONTROLS4_ABS(36), buf[9] | ((buf[8] & 0x3) << 8));
/* right wheel */
input_report_abs(cdev->input_dev, KONTROLS4_ABS(37), buf[13] | ((buf[12] & 0x3) << 8));
/* rotary encoders */
input_report_abs(cdev->input_dev, KONTROLS4_ABS(38), buf[3] & 0xf);
input_report_abs(cdev->input_dev, KONTROLS4_ABS(39), buf[4] >> 4);
input_report_abs(cdev->input_dev, KONTROLS4_ABS(40), buf[4] & 0xf);
input_report_abs(cdev->input_dev, KONTROLS4_ABS(41), buf[5] >> 4);
input_report_abs(cdev->input_dev, KONTROLS4_ABS(42), buf[5] & 0xf);
input_report_abs(cdev->input_dev, KONTROLS4_ABS(43), buf[6] >> 4);
input_report_abs(cdev->input_dev, KONTROLS4_ABS(44), buf[6] & 0xf);
input_report_abs(cdev->input_dev, KONTROLS4_ABS(45), buf[7] >> 4);
input_report_abs(cdev->input_dev, KONTROLS4_ABS(46), buf[7] & 0xf);
break;
case 2:
/* Volume Fader Channel D */
snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(0), buf, 1);
/* Volume Fader Channel B */
snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(1), buf, 2);
/* Volume Fader Channel A */
snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(2), buf, 3);
/* Volume Fader Channel C */
snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(3), buf, 4);
/* Loop Volume */
snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(4), buf, 6);
/* Crossfader */
snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(7), buf, 7);
break;
case 3:
/* Tempo Fader R */
snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(6), buf, 3);
/* Tempo Fader L */
snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(5), buf, 4);
/* Mic Volume */
snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(8), buf, 6);
/* Cue Mix */
snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(9), buf, 7);
break;
case 4:
/* Wheel distance sensor L */
snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(10), buf, 1);
/* Wheel distance sensor R */
snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(11), buf, 2);
/* Channel D EQ - Filter */
snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(12), buf, 3);
/* Channel D EQ - Low */
snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(13), buf, 4);
/* Channel D EQ - Mid */
snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(14), buf, 5);
/* Channel D EQ - Hi */
snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(15), buf, 6);
/* FX2 - dry/wet */
snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(16), buf, 7);
break;
case 5:
/* FX2 - 1 */
snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(17), buf, 1);
/* FX2 - 2 */
snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(18), buf, 2);
/* FX2 - 3 */
snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(19), buf, 3);
/* Channel B EQ - Filter */
snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(20), buf, 4);
/* Channel B EQ - Low */
snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(21), buf, 5);
/* Channel B EQ - Mid */
snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(22), buf, 6);
/* Channel B EQ - Hi */
snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(23), buf, 7);
break;
case 6:
/* Channel A EQ - Filter */
snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(24), buf, 1);
/* Channel A EQ - Low */
snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(25), buf, 2);
/* Channel A EQ - Mid */
snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(26), buf, 3);
/* Channel A EQ - Hi */
snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(27), buf, 4);
/* Channel C EQ - Filter */
snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(28), buf, 5);
/* Channel C EQ - Low */
snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(29), buf, 6);
/* Channel C EQ - Mid */
snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(30), buf, 7);
break;
case 7:
/* Channel C EQ - Hi */
snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(31), buf, 1);
/* FX1 - wet/dry */
snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(32), buf, 2);
/* FX1 - 1 */
snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(33), buf, 3);
/* FX1 - 2 */
snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(34), buf, 4);
/* FX1 - 3 */
snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(35), buf, 5);
break;
default:
dev_dbg(dev, "%s(): bogus block (id %d)\n",
__func__, block_id);
return;
}
len -= TKS4_MSGBLOCK_SIZE;
buf += TKS4_MSGBLOCK_SIZE;
}
input_sync(cdev->input_dev);
}
#define MASCHINE_MSGBLOCK_SIZE 2
static void snd_usb_caiaq_maschine_dispatch(struct snd_usb_caiaqdev *cdev,
const unsigned char *buf,
unsigned int len)
{
unsigned int i, pad_id;
__le16 *pressure = (__le16 *) buf;
for (i = 0; i < MASCHINE_PADS; i++) {
pad_id = le16_to_cpu(*pressure) >> 12;
input_report_abs(cdev->input_dev, MASCHINE_PAD(pad_id),
le16_to_cpu(*pressure) & 0xfff);
pressure++;
}
input_sync(cdev->input_dev);
}
static void snd_usb_caiaq_ep4_reply_dispatch(struct urb *urb)
{
struct snd_usb_caiaqdev *cdev = urb->context;
unsigned char *buf = urb->transfer_buffer;
struct device *dev = &urb->dev->dev;
int ret;
if (urb->status || !cdev || urb != cdev->ep4_in_urb)
return;
switch (cdev->chip.usb_id) {
case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_TRAKTORKONTROLX1):
if (urb->actual_length < 24)
goto requeue;
if (buf[0] & 0x3)
snd_caiaq_input_read_io(cdev, buf + 1, 7);
if (buf[0] & 0x4)
snd_caiaq_input_read_analog(cdev, buf + 8, 16);
break;
case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_TRAKTORKONTROLS4):
snd_usb_caiaq_tks4_dispatch(cdev, buf, urb->actual_length);
break;
case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_MASCHINECONTROLLER):
if (urb->actual_length < (MASCHINE_PADS * MASCHINE_MSGBLOCK_SIZE))
goto requeue;
snd_usb_caiaq_maschine_dispatch(cdev, buf, urb->actual_length);
break;
}
requeue:
cdev->ep4_in_urb->actual_length = 0;
ret = usb_submit_urb(cdev->ep4_in_urb, GFP_ATOMIC);
if (ret < 0)
dev_err(dev, "unable to submit urb. OOM!?\n");
}
static int snd_usb_caiaq_input_open(struct input_dev *idev)
{
struct snd_usb_caiaqdev *cdev = input_get_drvdata(idev);
if (!cdev)
return -EINVAL;
switch (cdev->chip.usb_id) {
case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_TRAKTORKONTROLX1):
case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_TRAKTORKONTROLS4):
case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_MASCHINECONTROLLER):
if (usb_submit_urb(cdev->ep4_in_urb, GFP_KERNEL) != 0)
return -EIO;
break;
}
return 0;
}
static void snd_usb_caiaq_input_close(struct input_dev *idev)
{
struct snd_usb_caiaqdev *cdev = input_get_drvdata(idev);
if (!cdev)
return;
switch (cdev->chip.usb_id) {
case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_TRAKTORKONTROLX1):
case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_TRAKTORKONTROLS4):
case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_MASCHINECONTROLLER):
usb_kill_urb(cdev->ep4_in_urb);
break;
}
}
void snd_usb_caiaq_input_dispatch(struct snd_usb_caiaqdev *cdev,
char *buf,
unsigned int len)
{
if (!cdev->input_dev || len < 1)
return;
switch (buf[0]) {
case EP1_CMD_READ_ANALOG:
snd_caiaq_input_read_analog(cdev, buf + 1, len - 1);
break;
case EP1_CMD_READ_ERP:
snd_caiaq_input_read_erp(cdev, buf + 1, len - 1);
break;
case EP1_CMD_READ_IO:
snd_caiaq_input_read_io(cdev, buf + 1, len - 1);
break;
}
}
int snd_usb_caiaq_input_init(struct snd_usb_caiaqdev *cdev)
{
struct usb_device *usb_dev = cdev->chip.dev;
struct input_dev *input;
int i, ret = 0;
input = input_allocate_device();
if (!input)
return -ENOMEM;
usb_make_path(usb_dev, cdev->phys, sizeof(cdev->phys));
strlcat(cdev->phys, "/input0", sizeof(cdev->phys));
input->name = cdev->product_name;
input->phys = cdev->phys;
usb_to_input_id(usb_dev, &input->id);
input->dev.parent = &usb_dev->dev;
input_set_drvdata(input, cdev);
switch (cdev->chip.usb_id) {
case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_RIGKONTROL2):
input->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS);
input->absbit[0] = BIT_MASK(ABS_X) | BIT_MASK(ABS_Y) |
BIT_MASK(ABS_Z);
BUILD_BUG_ON(sizeof(cdev->keycode) < sizeof(keycode_rk2));
memcpy(cdev->keycode, keycode_rk2, sizeof(keycode_rk2));
input->keycodemax = ARRAY_SIZE(keycode_rk2);
input_set_abs_params(input, ABS_X, 0, 4096, 0, 10);
input_set_abs_params(input, ABS_Y, 0, 4096, 0, 10);
input_set_abs_params(input, ABS_Z, 0, 4096, 0, 10);
snd_usb_caiaq_set_auto_msg(cdev, 1, 10, 0);
break;
case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_RIGKONTROL3):
input->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS);
input->absbit[0] = BIT_MASK(ABS_X) | BIT_MASK(ABS_Y) |
BIT_MASK(ABS_Z);
BUILD_BUG_ON(sizeof(cdev->keycode) < sizeof(keycode_rk3));
memcpy(cdev->keycode, keycode_rk3, sizeof(keycode_rk3));
input->keycodemax = ARRAY_SIZE(keycode_rk3);
input_set_abs_params(input, ABS_X, 0, 1024, 0, 10);
input_set_abs_params(input, ABS_Y, 0, 1024, 0, 10);
input_set_abs_params(input, ABS_Z, 0, 1024, 0, 10);
snd_usb_caiaq_set_auto_msg(cdev, 1, 10, 0);
break;
case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_AK1):
input->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS);
input->absbit[0] = BIT_MASK(ABS_X);
BUILD_BUG_ON(sizeof(cdev->keycode) < sizeof(keycode_ak1));
memcpy(cdev->keycode, keycode_ak1, sizeof(keycode_ak1));
input->keycodemax = ARRAY_SIZE(keycode_ak1);
input_set_abs_params(input, ABS_X, 0, 999, 0, 10);
snd_usb_caiaq_set_auto_msg(cdev, 1, 0, 5);
break;
case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_KORECONTROLLER):
case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_KORECONTROLLER2):
input->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS);
input->absbit[0] = BIT_MASK(ABS_HAT0X) | BIT_MASK(ABS_HAT0Y) |
BIT_MASK(ABS_HAT1X) | BIT_MASK(ABS_HAT1Y) |
BIT_MASK(ABS_HAT2X) | BIT_MASK(ABS_HAT2Y) |
BIT_MASK(ABS_HAT3X) | BIT_MASK(ABS_HAT3Y) |
BIT_MASK(ABS_X) | BIT_MASK(ABS_Y) |
BIT_MASK(ABS_Z);
input->absbit[BIT_WORD(ABS_MISC)] |= BIT_MASK(ABS_MISC);
BUILD_BUG_ON(sizeof(cdev->keycode) < sizeof(keycode_kore));
memcpy(cdev->keycode, keycode_kore, sizeof(keycode_kore));
input->keycodemax = ARRAY_SIZE(keycode_kore);
input_set_abs_params(input, ABS_HAT0X, 0, 999, 0, 10);
input_set_abs_params(input, ABS_HAT0Y, 0, 999, 0, 10);
input_set_abs_params(input, ABS_HAT1X, 0, 999, 0, 10);
input_set_abs_params(input, ABS_HAT1Y, 0, 999, 0, 10);
input_set_abs_params(input, ABS_HAT2X, 0, 999, 0, 10);
input_set_abs_params(input, ABS_HAT2Y, 0, 999, 0, 10);
input_set_abs_params(input, ABS_HAT3X, 0, 999, 0, 10);
input_set_abs_params(input, ABS_HAT3Y, 0, 999, 0, 10);
input_set_abs_params(input, ABS_X, 0, 4096, 0, 10);
input_set_abs_params(input, ABS_Y, 0, 4096, 0, 10);
input_set_abs_params(input, ABS_Z, 0, 4096, 0, 10);
input_set_abs_params(input, ABS_MISC, 0, 255, 0, 1);
snd_usb_caiaq_set_auto_msg(cdev, 1, 10, 5);
break;
case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_TRAKTORKONTROLX1):
input->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS);
input->absbit[0] = BIT_MASK(ABS_HAT0X) | BIT_MASK(ABS_HAT0Y) |
BIT_MASK(ABS_HAT1X) | BIT_MASK(ABS_HAT1Y) |
BIT_MASK(ABS_HAT2X) | BIT_MASK(ABS_HAT2Y) |
BIT_MASK(ABS_HAT3X) | BIT_MASK(ABS_HAT3Y) |
BIT_MASK(ABS_X) | BIT_MASK(ABS_Y) |
BIT_MASK(ABS_Z);
input->absbit[BIT_WORD(ABS_MISC)] |= BIT_MASK(ABS_MISC);
BUILD_BUG_ON(sizeof(cdev->keycode) < KONTROLX1_INPUTS);
for (i = 0; i < KONTROLX1_INPUTS; i++)
cdev->keycode[i] = BTN_MISC + i;
input->keycodemax = KONTROLX1_INPUTS;
/* analog potentiometers */
input_set_abs_params(input, ABS_HAT0X, 0, 4096, 0, 10);
input_set_abs_params(input, ABS_HAT0Y, 0, 4096, 0, 10);
input_set_abs_params(input, ABS_HAT1X, 0, 4096, 0, 10);
input_set_abs_params(input, ABS_HAT1Y, 0, 4096, 0, 10);
input_set_abs_params(input, ABS_HAT2X, 0, 4096, 0, 10);
input_set_abs_params(input, ABS_HAT2Y, 0, 4096, 0, 10);
input_set_abs_params(input, ABS_HAT3X, 0, 4096, 0, 10);
input_set_abs_params(input, ABS_HAT3Y, 0, 4096, 0, 10);
/* rotary encoders */
input_set_abs_params(input, ABS_X, 0, 0xf, 0, 1);
input_set_abs_params(input, ABS_Y, 0, 0xf, 0, 1);
input_set_abs_params(input, ABS_Z, 0, 0xf, 0, 1);
input_set_abs_params(input, ABS_MISC, 0, 0xf, 0, 1);
cdev->ep4_in_urb = usb_alloc_urb(0, GFP_KERNEL);
if (!cdev->ep4_in_urb) {
ret = -ENOMEM;
goto exit_free_idev;
}
usb_fill_bulk_urb(cdev->ep4_in_urb, usb_dev,
usb_rcvbulkpipe(usb_dev, 0x4),
cdev->ep4_in_buf, EP4_BUFSIZE,
snd_usb_caiaq_ep4_reply_dispatch, cdev);
snd_usb_caiaq_set_auto_msg(cdev, 1, 10, 5);
break;
case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_TRAKTORKONTROLS4):
input->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS);
BUILD_BUG_ON(sizeof(cdev->keycode) < KONTROLS4_BUTTONS);
for (i = 0; i < KONTROLS4_BUTTONS; i++)
cdev->keycode[i] = KONTROLS4_BUTTON(i);
input->keycodemax = KONTROLS4_BUTTONS;
for (i = 0; i < KONTROLS4_AXIS; i++) {
int axis = KONTROLS4_ABS(i);
input->absbit[BIT_WORD(axis)] |= BIT_MASK(axis);
}
/* 36 analog potentiometers and faders */
for (i = 0; i < 36; i++)
input_set_abs_params(input, KONTROLS4_ABS(i), 0, 0xfff, 0, 10);
/* 2 encoder wheels */
input_set_abs_params(input, KONTROLS4_ABS(36), 0, 0x3ff, 0, 1);
input_set_abs_params(input, KONTROLS4_ABS(37), 0, 0x3ff, 0, 1);
/* 9 rotary encoders */
for (i = 0; i < 9; i++)
input_set_abs_params(input, KONTROLS4_ABS(38+i), 0, 0xf, 0, 1);
cdev->ep4_in_urb = usb_alloc_urb(0, GFP_KERNEL);
if (!cdev->ep4_in_urb) {
ret = -ENOMEM;
goto exit_free_idev;
}
usb_fill_bulk_urb(cdev->ep4_in_urb, usb_dev,
usb_rcvbulkpipe(usb_dev, 0x4),
cdev->ep4_in_buf, EP4_BUFSIZE,
snd_usb_caiaq_ep4_reply_dispatch, cdev);
snd_usb_caiaq_set_auto_msg(cdev, 1, 10, 5);
break;
case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_MASCHINECONTROLLER):
input->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS);
input->absbit[0] = BIT_MASK(ABS_HAT0X) | BIT_MASK(ABS_HAT0Y) |
BIT_MASK(ABS_HAT1X) | BIT_MASK(ABS_HAT1Y) |
BIT_MASK(ABS_HAT2X) | BIT_MASK(ABS_HAT2Y) |
BIT_MASK(ABS_HAT3X) | BIT_MASK(ABS_HAT3Y) |
BIT_MASK(ABS_RX) | BIT_MASK(ABS_RY) |
BIT_MASK(ABS_RZ);
BUILD_BUG_ON(sizeof(cdev->keycode) < sizeof(keycode_maschine));
memcpy(cdev->keycode, keycode_maschine, sizeof(keycode_maschine));
input->keycodemax = ARRAY_SIZE(keycode_maschine);
for (i = 0; i < MASCHINE_PADS; i++) {
input->absbit[0] |= MASCHINE_PAD(i);
input_set_abs_params(input, MASCHINE_PAD(i), 0, 0xfff, 5, 10);
}
input_set_abs_params(input, ABS_HAT0X, 0, 999, 0, 10);
input_set_abs_params(input, ABS_HAT0Y, 0, 999, 0, 10);
input_set_abs_params(input, ABS_HAT1X, 0, 999, 0, 10);
input_set_abs_params(input, ABS_HAT1Y, 0, 999, 0, 10);
input_set_abs_params(input, ABS_HAT2X, 0, 999, 0, 10);
input_set_abs_params(input, ABS_HAT2Y, 0, 999, 0, 10);
input_set_abs_params(input, ABS_HAT3X, 0, 999, 0, 10);
input_set_abs_params(input, ABS_HAT3Y, 0, 999, 0, 10);
input_set_abs_params(input, ABS_RX, 0, 999, 0, 10);
input_set_abs_params(input, ABS_RY, 0, 999, 0, 10);
input_set_abs_params(input, ABS_RZ, 0, 999, 0, 10);
cdev->ep4_in_urb = usb_alloc_urb(0, GFP_KERNEL);
if (!cdev->ep4_in_urb) {
ret = -ENOMEM;
goto exit_free_idev;
}
usb_fill_bulk_urb(cdev->ep4_in_urb, usb_dev,
usb_rcvbulkpipe(usb_dev, 0x4),
cdev->ep4_in_buf, EP4_BUFSIZE,
snd_usb_caiaq_ep4_reply_dispatch, cdev);
snd_usb_caiaq_set_auto_msg(cdev, 1, 10, 5);
break;
default:
/* no input methods supported on this device */
goto exit_free_idev;
}
input->open = snd_usb_caiaq_input_open;
input->close = snd_usb_caiaq_input_close;
input->keycode = cdev->keycode;
input->keycodesize = sizeof(unsigned short);
for (i = 0; i < input->keycodemax; i++)
__set_bit(cdev->keycode[i], input->keybit);
cdev->input_dev = input;
ret = input_register_device(input);
if (ret < 0)
goto exit_free_idev;
return 0;
exit_free_idev:
input_free_device(input);
cdev->input_dev = NULL;
return ret;
}
void snd_usb_caiaq_input_free(struct snd_usb_caiaqdev *cdev)
{
if (!cdev || !cdev->input_dev)
return;
usb_kill_urb(cdev->ep4_in_urb);
usb_free_urb(cdev->ep4_in_urb);
cdev->ep4_in_urb = NULL;
input_unregister_device(cdev->input_dev);
cdev->input_dev = NULL;
}

8
sound/usb/caiaq/input.h Normal file
View file

@ -0,0 +1,8 @@
#ifndef CAIAQ_INPUT_H
#define CAIAQ_INPUT_H
void snd_usb_caiaq_input_dispatch(struct snd_usb_caiaqdev *cdev, char *buf, unsigned int len);
int snd_usb_caiaq_input_init(struct snd_usb_caiaqdev *cdev);
void snd_usb_caiaq_input_free(struct snd_usb_caiaqdev *cdev);
#endif

175
sound/usb/caiaq/midi.c Normal file
View file

@ -0,0 +1,175 @@
/*
* Copyright (c) 2006,2007 Daniel Mack
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <linux/device.h>
#include <linux/usb.h>
#include <linux/gfp.h>
#include <sound/rawmidi.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include "device.h"
#include "midi.h"
static int snd_usb_caiaq_midi_input_open(struct snd_rawmidi_substream *substream)
{
return 0;
}
static int snd_usb_caiaq_midi_input_close(struct snd_rawmidi_substream *substream)
{
return 0;
}
static void snd_usb_caiaq_midi_input_trigger(struct snd_rawmidi_substream *substream, int up)
{
struct snd_usb_caiaqdev *cdev = substream->rmidi->private_data;
if (!cdev)
return;
cdev->midi_receive_substream = up ? substream : NULL;
}
static int snd_usb_caiaq_midi_output_open(struct snd_rawmidi_substream *substream)
{
return 0;
}
static int snd_usb_caiaq_midi_output_close(struct snd_rawmidi_substream *substream)
{
struct snd_usb_caiaqdev *cdev = substream->rmidi->private_data;
if (cdev->midi_out_active) {
usb_kill_urb(&cdev->midi_out_urb);
cdev->midi_out_active = 0;
}
return 0;
}
static void snd_usb_caiaq_midi_send(struct snd_usb_caiaqdev *cdev,
struct snd_rawmidi_substream *substream)
{
int len, ret;
struct device *dev = caiaqdev_to_dev(cdev);
cdev->midi_out_buf[0] = EP1_CMD_MIDI_WRITE;
cdev->midi_out_buf[1] = 0; /* port */
len = snd_rawmidi_transmit(substream, cdev->midi_out_buf + 3,
EP1_BUFSIZE - 3);
if (len <= 0)
return;
cdev->midi_out_buf[2] = len;
cdev->midi_out_urb.transfer_buffer_length = len+3;
ret = usb_submit_urb(&cdev->midi_out_urb, GFP_ATOMIC);
if (ret < 0)
dev_err(dev,
"snd_usb_caiaq_midi_send(%p): usb_submit_urb() failed,"
"ret=%d, len=%d\n", substream, ret, len);
else
cdev->midi_out_active = 1;
}
static void snd_usb_caiaq_midi_output_trigger(struct snd_rawmidi_substream *substream, int up)
{
struct snd_usb_caiaqdev *cdev = substream->rmidi->private_data;
if (up) {
cdev->midi_out_substream = substream;
if (!cdev->midi_out_active)
snd_usb_caiaq_midi_send(cdev, substream);
} else {
cdev->midi_out_substream = NULL;
}
}
static struct snd_rawmidi_ops snd_usb_caiaq_midi_output =
{
.open = snd_usb_caiaq_midi_output_open,
.close = snd_usb_caiaq_midi_output_close,
.trigger = snd_usb_caiaq_midi_output_trigger,
};
static struct snd_rawmidi_ops snd_usb_caiaq_midi_input =
{
.open = snd_usb_caiaq_midi_input_open,
.close = snd_usb_caiaq_midi_input_close,
.trigger = snd_usb_caiaq_midi_input_trigger,
};
void snd_usb_caiaq_midi_handle_input(struct snd_usb_caiaqdev *cdev,
int port, const char *buf, int len)
{
if (!cdev->midi_receive_substream)
return;
snd_rawmidi_receive(cdev->midi_receive_substream, buf, len);
}
int snd_usb_caiaq_midi_init(struct snd_usb_caiaqdev *device)
{
int ret;
struct snd_rawmidi *rmidi;
ret = snd_rawmidi_new(device->chip.card, device->product_name, 0,
device->spec.num_midi_out,
device->spec.num_midi_in,
&rmidi);
if (ret < 0)
return ret;
strlcpy(rmidi->name, device->product_name, sizeof(rmidi->name));
rmidi->info_flags = SNDRV_RAWMIDI_INFO_DUPLEX;
rmidi->private_data = device;
if (device->spec.num_midi_out > 0) {
rmidi->info_flags |= SNDRV_RAWMIDI_INFO_OUTPUT;
snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT,
&snd_usb_caiaq_midi_output);
}
if (device->spec.num_midi_in > 0) {
rmidi->info_flags |= SNDRV_RAWMIDI_INFO_INPUT;
snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT,
&snd_usb_caiaq_midi_input);
}
device->rmidi = rmidi;
return 0;
}
void snd_usb_caiaq_midi_output_done(struct urb* urb)
{
struct snd_usb_caiaqdev *cdev = urb->context;
cdev->midi_out_active = 0;
if (urb->status != 0)
return;
if (!cdev->midi_out_substream)
return;
snd_usb_caiaq_midi_send(cdev, cdev->midi_out_substream);
}

9
sound/usb/caiaq/midi.h Normal file
View file

@ -0,0 +1,9 @@
#ifndef CAIAQ_MIDI_H
#define CAIAQ_MIDI_H
int snd_usb_caiaq_midi_init(struct snd_usb_caiaqdev *cdev);
void snd_usb_caiaq_midi_handle_input(struct snd_usb_caiaqdev *cdev,
int port, const char *buf, int len);
void snd_usb_caiaq_midi_output_done(struct urb *urb);
#endif /* CAIAQ_MIDI_H */

796
sound/usb/card.c Normal file
View file

@ -0,0 +1,796 @@
/*
* (Tentative) USB Audio Driver for ALSA
*
* Copyright (c) 2002 by Takashi Iwai <tiwai@suse.de>
*
* Many codes borrowed from audio.c by
* Alan Cox (alan@lxorguk.ukuu.org.uk)
* Thomas Sailer (sailer@ife.ee.ethz.ch)
*
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*
* NOTES:
*
* - the linked URBs would be preferred but not used so far because of
* the instability of unlinking.
* - type II is not supported properly. there is no device which supports
* this type *correctly*. SB extigy looks as if it supports, but it's
* indeed an AC3 stream packed in SPDIF frames (i.e. no real AC3 stream).
*/
#include <linux/bitops.h>
#include <linux/init.h>
#include <linux/list.h>
#include <linux/slab.h>
#include <linux/string.h>
#include <linux/ctype.h>
#include <linux/usb.h>
#include <linux/moduleparam.h>
#include <linux/mutex.h>
#include <linux/usb/audio.h>
#include <linux/usb/audio-v2.h>
#include <linux/module.h>
#include <sound/control.h>
#include <sound/core.h>
#include <sound/info.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/initval.h>
#include "usbaudio.h"
#include "card.h"
#include "midi.h"
#include "mixer.h"
#include "proc.h"
#include "quirks.h"
#include "endpoint.h"
#include "helper.h"
#include "debug.h"
#include "pcm.h"
#include "format.h"
#include "power.h"
#include "stream.h"
MODULE_AUTHOR("Takashi Iwai <tiwai@suse.de>");
MODULE_DESCRIPTION("USB Audio");
MODULE_LICENSE("GPL");
MODULE_SUPPORTED_DEVICE("{{Generic,USB Audio}}");
static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 0-MAX */
static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* ID for this card */
static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP;/* Enable this card */
/* Vendor/product IDs for this card */
static int vid[SNDRV_CARDS] = { [0 ... (SNDRV_CARDS-1)] = -1 };
static int pid[SNDRV_CARDS] = { [0 ... (SNDRV_CARDS-1)] = -1 };
static int device_setup[SNDRV_CARDS]; /* device parameter for this card */
static bool ignore_ctl_error;
static bool autoclock = true;
module_param_array(index, int, NULL, 0444);
MODULE_PARM_DESC(index, "Index value for the USB audio adapter.");
module_param_array(id, charp, NULL, 0444);
MODULE_PARM_DESC(id, "ID string for the USB audio adapter.");
module_param_array(enable, bool, NULL, 0444);
MODULE_PARM_DESC(enable, "Enable USB audio adapter.");
module_param_array(vid, int, NULL, 0444);
MODULE_PARM_DESC(vid, "Vendor ID for the USB audio device.");
module_param_array(pid, int, NULL, 0444);
MODULE_PARM_DESC(pid, "Product ID for the USB audio device.");
module_param_array(device_setup, int, NULL, 0444);
MODULE_PARM_DESC(device_setup, "Specific device setup (if needed).");
module_param(ignore_ctl_error, bool, 0444);
MODULE_PARM_DESC(ignore_ctl_error,
"Ignore errors from USB controller for mixer interfaces.");
module_param(autoclock, bool, 0444);
MODULE_PARM_DESC(autoclock, "Enable auto-clock selection for UAC2 devices (default: yes).");
/*
* we keep the snd_usb_audio_t instances by ourselves for merging
* the all interfaces on the same card as one sound device.
*/
static DEFINE_MUTEX(register_mutex);
static struct snd_usb_audio *usb_chip[SNDRV_CARDS];
static struct usb_driver usb_audio_driver;
/*
* disconnect streams
* called from snd_usb_audio_disconnect()
*/
static void snd_usb_stream_disconnect(struct list_head *head)
{
int idx;
struct snd_usb_stream *as;
struct snd_usb_substream *subs;
as = list_entry(head, struct snd_usb_stream, list);
for (idx = 0; idx < 2; idx++) {
subs = &as->substream[idx];
if (!subs->num_formats)
continue;
subs->interface = -1;
subs->data_endpoint = NULL;
subs->sync_endpoint = NULL;
}
}
static int snd_usb_create_stream(struct snd_usb_audio *chip, int ctrlif, int interface)
{
struct usb_device *dev = chip->dev;
struct usb_host_interface *alts;
struct usb_interface_descriptor *altsd;
struct usb_interface *iface = usb_ifnum_to_if(dev, interface);
if (!iface) {
dev_err(&dev->dev, "%u:%d : does not exist\n",
ctrlif, interface);
return -EINVAL;
}
alts = &iface->altsetting[0];
altsd = get_iface_desc(alts);
/*
* Android with both accessory and audio interfaces enabled gets the
* interface numbers wrong.
*/
if ((chip->usb_id == USB_ID(0x18d1, 0x2d04) ||
chip->usb_id == USB_ID(0x18d1, 0x2d05)) &&
interface == 0 &&
altsd->bInterfaceClass == USB_CLASS_VENDOR_SPEC &&
altsd->bInterfaceSubClass == USB_SUBCLASS_VENDOR_SPEC) {
interface = 2;
iface = usb_ifnum_to_if(dev, interface);
if (!iface)
return -EINVAL;
alts = &iface->altsetting[0];
altsd = get_iface_desc(alts);
}
if (usb_interface_claimed(iface)) {
dev_dbg(&dev->dev, "%d:%d: skipping, already claimed\n",
ctrlif, interface);
return -EINVAL;
}
if ((altsd->bInterfaceClass == USB_CLASS_AUDIO ||
altsd->bInterfaceClass == USB_CLASS_VENDOR_SPEC) &&
altsd->bInterfaceSubClass == USB_SUBCLASS_MIDISTREAMING) {
int err = snd_usbmidi_create(chip->card, iface,
&chip->midi_list, NULL);
if (err < 0) {
dev_err(&dev->dev,
"%u:%d: cannot create sequencer device\n",
ctrlif, interface);
return -EINVAL;
}
usb_driver_claim_interface(&usb_audio_driver, iface, (void *)-1L);
return 0;
}
if ((altsd->bInterfaceClass != USB_CLASS_AUDIO &&
altsd->bInterfaceClass != USB_CLASS_VENDOR_SPEC) ||
altsd->bInterfaceSubClass != USB_SUBCLASS_AUDIOSTREAMING) {
dev_dbg(&dev->dev,
"%u:%d: skipping non-supported interface %d\n",
ctrlif, interface, altsd->bInterfaceClass);
/* skip non-supported classes */
return -EINVAL;
}
if (snd_usb_get_speed(dev) == USB_SPEED_LOW) {
dev_err(&dev->dev, "low speed audio streaming not supported\n");
return -EINVAL;
}
if (! snd_usb_parse_audio_interface(chip, interface)) {
usb_set_interface(dev, interface, 0); /* reset the current interface */
usb_driver_claim_interface(&usb_audio_driver, iface, (void *)-1L);
return -EINVAL;
}
return 0;
}
/*
* parse audio control descriptor and create pcm/midi streams
*/
static int snd_usb_create_streams(struct snd_usb_audio *chip, int ctrlif)
{
struct usb_device *dev = chip->dev;
struct usb_host_interface *host_iface;
struct usb_interface_descriptor *altsd;
void *control_header;
int i, protocol;
/* find audiocontrol interface */
host_iface = &usb_ifnum_to_if(dev, ctrlif)->altsetting[0];
control_header = snd_usb_find_csint_desc(host_iface->extra,
host_iface->extralen,
NULL, UAC_HEADER);
altsd = get_iface_desc(host_iface);
protocol = altsd->bInterfaceProtocol;
if (!control_header) {
dev_err(&dev->dev, "cannot find UAC_HEADER\n");
return -EINVAL;
}
switch (protocol) {
default:
dev_warn(&dev->dev,
"unknown interface protocol %#02x, assuming v1\n",
protocol);
/* fall through */
case UAC_VERSION_1: {
struct uac1_ac_header_descriptor *h1 = control_header;
if (!h1->bInCollection) {
dev_info(&dev->dev, "skipping empty audio interface (v1)\n");
return -EINVAL;
}
if (h1->bLength < sizeof(*h1) + h1->bInCollection) {
dev_err(&dev->dev, "invalid UAC_HEADER (v1)\n");
return -EINVAL;
}
for (i = 0; i < h1->bInCollection; i++)
snd_usb_create_stream(chip, ctrlif, h1->baInterfaceNr[i]);
break;
}
case UAC_VERSION_2: {
struct usb_interface_assoc_descriptor *assoc =
usb_ifnum_to_if(dev, ctrlif)->intf_assoc;
if (!assoc) {
/*
* Firmware writers cannot count to three. So to find
* the IAD on the NuForce UDH-100, also check the next
* interface.
*/
struct usb_interface *iface =
usb_ifnum_to_if(dev, ctrlif + 1);
if (iface &&
iface->intf_assoc &&
iface->intf_assoc->bFunctionClass == USB_CLASS_AUDIO &&
iface->intf_assoc->bFunctionProtocol == UAC_VERSION_2)
assoc = iface->intf_assoc;
}
if (!assoc) {
dev_err(&dev->dev, "Audio class v2 interfaces need an interface association\n");
return -EINVAL;
}
for (i = 0; i < assoc->bInterfaceCount; i++) {
int intf = assoc->bFirstInterface + i;
if (intf != ctrlif)
snd_usb_create_stream(chip, ctrlif, intf);
}
break;
}
}
return 0;
}
/*
* free the chip instance
*
* here we have to do not much, since pcm and controls are already freed
*
*/
static int snd_usb_audio_free(struct snd_usb_audio *chip)
{
struct list_head *p, *n;
list_for_each_safe(p, n, &chip->ep_list)
snd_usb_endpoint_free(p);
mutex_destroy(&chip->mutex);
kfree(chip);
return 0;
}
static int snd_usb_audio_dev_free(struct snd_device *device)
{
struct snd_usb_audio *chip = device->device_data;
return snd_usb_audio_free(chip);
}
static void remove_trailing_spaces(char *str)
{
char *p;
if (!*str)
return;
for (p = str + strlen(str) - 1; p >= str && isspace(*p); p--)
*p = 0;
}
/*
* create a chip instance and set its names.
*/
static int snd_usb_audio_create(struct usb_interface *intf,
struct usb_device *dev, int idx,
const struct snd_usb_audio_quirk *quirk,
struct snd_usb_audio **rchip)
{
struct snd_card *card;
struct snd_usb_audio *chip;
int err, len;
char component[14];
static struct snd_device_ops ops = {
.dev_free = snd_usb_audio_dev_free,
};
*rchip = NULL;
switch (snd_usb_get_speed(dev)) {
case USB_SPEED_LOW:
case USB_SPEED_FULL:
case USB_SPEED_HIGH:
case USB_SPEED_WIRELESS:
case USB_SPEED_SUPER:
break;
default:
dev_err(&dev->dev, "unknown device speed %d\n", snd_usb_get_speed(dev));
return -ENXIO;
}
err = snd_card_new(&intf->dev, index[idx], id[idx], THIS_MODULE,
0, &card);
if (err < 0) {
dev_err(&dev->dev, "cannot create card instance %d\n", idx);
return err;
}
chip = kzalloc(sizeof(*chip), GFP_KERNEL);
if (! chip) {
snd_card_free(card);
return -ENOMEM;
}
mutex_init(&chip->mutex);
init_rwsem(&chip->shutdown_rwsem);
chip->index = idx;
chip->dev = dev;
chip->card = card;
chip->setup = device_setup[idx];
chip->autoclock = autoclock;
chip->probing = 1;
chip->usb_id = USB_ID(le16_to_cpu(dev->descriptor.idVendor),
le16_to_cpu(dev->descriptor.idProduct));
INIT_LIST_HEAD(&chip->pcm_list);
INIT_LIST_HEAD(&chip->ep_list);
INIT_LIST_HEAD(&chip->midi_list);
INIT_LIST_HEAD(&chip->mixer_list);
if ((err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops)) < 0) {
snd_usb_audio_free(chip);
snd_card_free(card);
return err;
}
strcpy(card->driver, "USB-Audio");
sprintf(component, "USB%04x:%04x",
USB_ID_VENDOR(chip->usb_id), USB_ID_PRODUCT(chip->usb_id));
snd_component_add(card, component);
/* retrieve the device string as shortname */
if (quirk && quirk->product_name && *quirk->product_name) {
strlcpy(card->shortname, quirk->product_name, sizeof(card->shortname));
} else {
if (!dev->descriptor.iProduct ||
usb_string(dev, dev->descriptor.iProduct,
card->shortname, sizeof(card->shortname)) <= 0) {
/* no name available from anywhere, so use ID */
sprintf(card->shortname, "USB Device %#04x:%#04x",
USB_ID_VENDOR(chip->usb_id),
USB_ID_PRODUCT(chip->usb_id));
}
}
remove_trailing_spaces(card->shortname);
/* retrieve the vendor and device strings as longname */
if (quirk && quirk->vendor_name && *quirk->vendor_name) {
len = strlcpy(card->longname, quirk->vendor_name, sizeof(card->longname));
} else {
if (dev->descriptor.iManufacturer)
len = usb_string(dev, dev->descriptor.iManufacturer,
card->longname, sizeof(card->longname));
else
len = 0;
/* we don't really care if there isn't any vendor string */
}
if (len > 0) {
remove_trailing_spaces(card->longname);
if (*card->longname)
strlcat(card->longname, " ", sizeof(card->longname));
}
strlcat(card->longname, card->shortname, sizeof(card->longname));
len = strlcat(card->longname, " at ", sizeof(card->longname));
if (len < sizeof(card->longname))
usb_make_path(dev, card->longname + len, sizeof(card->longname) - len);
switch (snd_usb_get_speed(dev)) {
case USB_SPEED_LOW:
strlcat(card->longname, ", low speed", sizeof(card->longname));
break;
case USB_SPEED_FULL:
strlcat(card->longname, ", full speed", sizeof(card->longname));
break;
case USB_SPEED_HIGH:
strlcat(card->longname, ", high speed", sizeof(card->longname));
break;
case USB_SPEED_SUPER:
strlcat(card->longname, ", super speed", sizeof(card->longname));
break;
default:
break;
}
snd_usb_audio_create_proc(chip);
*rchip = chip;
return 0;
}
/*
* probe the active usb device
*
* note that this can be called multiple times per a device, when it
* includes multiple audio control interfaces.
*
* thus we check the usb device pointer and creates the card instance
* only at the first time. the successive calls of this function will
* append the pcm interface to the corresponding card.
*/
static struct snd_usb_audio *
snd_usb_audio_probe(struct usb_device *dev,
struct usb_interface *intf,
const struct usb_device_id *usb_id)
{
const struct snd_usb_audio_quirk *quirk = (const struct snd_usb_audio_quirk *)usb_id->driver_info;
int i, err;
struct snd_usb_audio *chip;
struct usb_host_interface *alts;
int ifnum;
u32 id;
alts = &intf->altsetting[0];
ifnum = get_iface_desc(alts)->bInterfaceNumber;
id = USB_ID(le16_to_cpu(dev->descriptor.idVendor),
le16_to_cpu(dev->descriptor.idProduct));
if (quirk && quirk->ifnum >= 0 && ifnum != quirk->ifnum)
goto __err_val;
if (snd_usb_apply_boot_quirk(dev, intf, quirk) < 0)
goto __err_val;
/*
* found a config. now register to ALSA
*/
/* check whether it's already registered */
chip = NULL;
mutex_lock(&register_mutex);
for (i = 0; i < SNDRV_CARDS; i++) {
if (usb_chip[i] && usb_chip[i]->dev == dev) {
if (usb_chip[i]->shutdown) {
dev_err(&dev->dev, "USB device is in the shutdown state, cannot create a card instance\n");
goto __error;
}
chip = usb_chip[i];
chip->probing = 1;
break;
}
}
if (! chip) {
/* it's a fresh one.
* now look for an empty slot and create a new card instance
*/
for (i = 0; i < SNDRV_CARDS; i++)
if (enable[i] && ! usb_chip[i] &&
(vid[i] == -1 || vid[i] == USB_ID_VENDOR(id)) &&
(pid[i] == -1 || pid[i] == USB_ID_PRODUCT(id))) {
if (snd_usb_audio_create(intf, dev, i, quirk,
&chip) < 0) {
goto __error;
}
chip->pm_intf = intf;
break;
}
if (!chip) {
dev_err(&dev->dev, "no available usb audio device\n");
goto __error;
}
}
/*
* For devices with more than one control interface, we assume the
* first contains the audio controls. We might need a more specific
* check here in the future.
*/
if (!chip->ctrl_intf)
chip->ctrl_intf = alts;
chip->txfr_quirk = 0;
err = 1; /* continue */
if (quirk && quirk->ifnum != QUIRK_NO_INTERFACE) {
/* need some special handlings */
if ((err = snd_usb_create_quirk(chip, intf, &usb_audio_driver, quirk)) < 0)
goto __error;
}
if (err > 0) {
/* create normal USB audio interfaces */
if (snd_usb_create_streams(chip, ifnum) < 0 ||
snd_usb_create_mixer(chip, ifnum, ignore_ctl_error) < 0) {
goto __error;
}
}
/* we are allowed to call snd_card_register() many times */
if (snd_card_register(chip->card) < 0) {
goto __error;
}
usb_chip[chip->index] = chip;
chip->num_interfaces++;
chip->probing = 0;
mutex_unlock(&register_mutex);
return chip;
__error:
if (chip) {
if (!chip->num_interfaces)
snd_card_free(chip->card);
chip->probing = 0;
}
mutex_unlock(&register_mutex);
__err_val:
return NULL;
}
/*
* we need to take care of counter, since disconnection can be called also
* many times as well as usb_audio_probe().
*/
static void snd_usb_audio_disconnect(struct usb_device *dev,
struct snd_usb_audio *chip)
{
struct snd_card *card;
struct list_head *p;
bool was_shutdown;
if (chip == (void *)-1L)
return;
card = chip->card;
down_write(&chip->shutdown_rwsem);
was_shutdown = chip->shutdown;
chip->shutdown = 1;
up_write(&chip->shutdown_rwsem);
mutex_lock(&register_mutex);
if (!was_shutdown) {
struct snd_usb_endpoint *ep;
snd_card_disconnect(card);
/* release the pcm resources */
list_for_each(p, &chip->pcm_list) {
snd_usb_stream_disconnect(p);
}
/* release the endpoint resources */
list_for_each_entry(ep, &chip->ep_list, list) {
snd_usb_endpoint_release(ep);
}
/* release the midi resources */
list_for_each(p, &chip->midi_list) {
snd_usbmidi_disconnect(p);
}
/* release mixer resources */
list_for_each(p, &chip->mixer_list) {
snd_usb_mixer_disconnect(p);
}
}
chip->num_interfaces--;
if (chip->num_interfaces <= 0) {
usb_chip[chip->index] = NULL;
mutex_unlock(&register_mutex);
snd_card_free_when_closed(card);
} else {
mutex_unlock(&register_mutex);
}
}
/*
* new 2.5 USB kernel API
*/
static int usb_audio_probe(struct usb_interface *intf,
const struct usb_device_id *id)
{
struct snd_usb_audio *chip;
chip = snd_usb_audio_probe(interface_to_usbdev(intf), intf, id);
if (chip) {
usb_set_intfdata(intf, chip);
return 0;
} else
return -EIO;
}
static void usb_audio_disconnect(struct usb_interface *intf)
{
snd_usb_audio_disconnect(interface_to_usbdev(intf),
usb_get_intfdata(intf));
}
#ifdef CONFIG_PM
int snd_usb_autoresume(struct snd_usb_audio *chip)
{
int err = -ENODEV;
down_read(&chip->shutdown_rwsem);
if (chip->probing && chip->in_pm)
err = 0;
else if (!chip->shutdown)
err = usb_autopm_get_interface(chip->pm_intf);
up_read(&chip->shutdown_rwsem);
return err;
}
void snd_usb_autosuspend(struct snd_usb_audio *chip)
{
down_read(&chip->shutdown_rwsem);
if (!chip->shutdown && !chip->probing && !chip->in_pm)
usb_autopm_put_interface(chip->pm_intf);
up_read(&chip->shutdown_rwsem);
}
static int usb_audio_suspend(struct usb_interface *intf, pm_message_t message)
{
struct snd_usb_audio *chip = usb_get_intfdata(intf);
struct snd_usb_stream *as;
struct usb_mixer_interface *mixer;
struct list_head *p;
if (chip == (void *)-1L)
return 0;
if (!PMSG_IS_AUTO(message)) {
snd_power_change_state(chip->card, SNDRV_CTL_POWER_D3hot);
if (!chip->num_suspended_intf++) {
list_for_each_entry(as, &chip->pcm_list, list) {
snd_pcm_suspend_all(as->pcm);
as->substream[0].need_setup_ep =
as->substream[1].need_setup_ep = true;
}
list_for_each(p, &chip->midi_list) {
snd_usbmidi_suspend(p);
}
}
} else {
/*
* otherwise we keep the rest of the system in the dark
* to keep this transparent
*/
if (!chip->num_suspended_intf++)
chip->autosuspended = 1;
}
if (chip->num_suspended_intf == 1)
list_for_each_entry(mixer, &chip->mixer_list, list)
snd_usb_mixer_suspend(mixer);
return 0;
}
static int __usb_audio_resume(struct usb_interface *intf, bool reset_resume)
{
struct snd_usb_audio *chip = usb_get_intfdata(intf);
struct usb_mixer_interface *mixer;
struct list_head *p;
int err = 0;
if (chip == (void *)-1L)
return 0;
if (--chip->num_suspended_intf)
return 0;
chip->in_pm = 1;
/*
* ALSA leaves material resumption to user space
* we just notify and restart the mixers
*/
list_for_each_entry(mixer, &chip->mixer_list, list) {
err = snd_usb_mixer_resume(mixer, reset_resume);
if (err < 0)
goto err_out;
}
list_for_each(p, &chip->midi_list) {
snd_usbmidi_resume(p);
}
if (!chip->autosuspended)
snd_power_change_state(chip->card, SNDRV_CTL_POWER_D0);
chip->autosuspended = 0;
err_out:
chip->in_pm = 0;
return err;
}
static int usb_audio_resume(struct usb_interface *intf)
{
return __usb_audio_resume(intf, false);
}
static int usb_audio_reset_resume(struct usb_interface *intf)
{
return __usb_audio_resume(intf, true);
}
#else
#define usb_audio_suspend NULL
#define usb_audio_resume NULL
#define usb_audio_reset_resume NULL
#endif /* CONFIG_PM */
static struct usb_device_id usb_audio_ids [] = {
#include "quirks-table.h"
{ .match_flags = (USB_DEVICE_ID_MATCH_INT_CLASS | USB_DEVICE_ID_MATCH_INT_SUBCLASS),
.bInterfaceClass = USB_CLASS_AUDIO,
.bInterfaceSubClass = USB_SUBCLASS_AUDIOCONTROL },
{ } /* Terminating entry */
};
MODULE_DEVICE_TABLE(usb, usb_audio_ids);
/*
* entry point for linux usb interface
*/
static struct usb_driver usb_audio_driver = {
.name = "snd-usb-audio",
.probe = usb_audio_probe,
.disconnect = usb_audio_disconnect,
.suspend = usb_audio_suspend,
.resume = usb_audio_resume,
.reset_resume = usb_audio_reset_resume,
.id_table = usb_audio_ids,
.supports_autosuspend = 1,
};
module_usb_driver(usb_audio_driver);

167
sound/usb/card.h Normal file
View file

@ -0,0 +1,167 @@
#ifndef __USBAUDIO_CARD_H
#define __USBAUDIO_CARD_H
#define MAX_NR_RATES 1024
#define MAX_PACKS 6 /* per URB */
#define MAX_PACKS_HS (MAX_PACKS * 8) /* in high speed mode */
#define MAX_URBS 12
#define SYNC_URBS 4 /* always four urbs for sync */
#define MAX_QUEUE 18 /* try not to exceed this queue length, in ms */
struct audioformat {
struct list_head list;
u64 formats; /* ALSA format bits */
unsigned int channels; /* # channels */
unsigned int fmt_type; /* USB audio format type (1-3) */
unsigned int frame_size; /* samples per frame for non-audio */
int iface; /* interface number */
unsigned char altsetting; /* corresponding alternate setting */
unsigned char altset_idx; /* array index of altenate setting */
unsigned char attributes; /* corresponding attributes of cs endpoint */
unsigned char endpoint; /* endpoint */
unsigned char ep_attr; /* endpoint attributes */
unsigned char datainterval; /* log_2 of data packet interval */
unsigned char protocol; /* UAC_VERSION_1/2 */
unsigned int maxpacksize; /* max. packet size */
unsigned int rates; /* rate bitmasks */
unsigned int rate_min, rate_max; /* min/max rates */
unsigned int nr_rates; /* number of rate table entries */
unsigned int *rate_table; /* rate table */
unsigned char clock; /* associated clock */
struct snd_pcm_chmap_elem *chmap; /* (optional) channel map */
bool dsd_dop; /* add DOP headers in case of DSD samples */
bool dsd_bitrev; /* reverse the bits of each DSD sample */
};
struct snd_usb_substream;
struct snd_usb_endpoint;
struct snd_urb_ctx {
struct urb *urb;
unsigned int buffer_size; /* size of data buffer, if data URB */
struct snd_usb_substream *subs;
struct snd_usb_endpoint *ep;
int index; /* index for urb array */
int packets; /* number of packets per urb */
int packet_size[MAX_PACKS_HS]; /* size of packets for next submission */
struct list_head ready_list;
};
struct snd_usb_endpoint {
struct snd_usb_audio *chip;
int use_count;
int ep_num; /* the referenced endpoint number */
int type; /* SND_USB_ENDPOINT_TYPE_* */
unsigned long flags;
void (*prepare_data_urb) (struct snd_usb_substream *subs,
struct urb *urb);
void (*retire_data_urb) (struct snd_usb_substream *subs,
struct urb *urb);
struct snd_usb_substream *data_subs;
struct snd_usb_endpoint *sync_master;
struct snd_usb_endpoint *sync_slave;
struct snd_urb_ctx urb[MAX_URBS];
struct snd_usb_packet_info {
uint32_t packet_size[MAX_PACKS_HS];
int packets;
} next_packet[MAX_URBS];
int next_packet_read_pos, next_packet_write_pos;
struct list_head ready_playback_urbs;
unsigned int nurbs; /* # urbs */
unsigned long active_mask; /* bitmask of active urbs */
unsigned long unlink_mask; /* bitmask of unlinked urbs */
char *syncbuf; /* sync buffer for all sync URBs */
dma_addr_t sync_dma; /* DMA address of syncbuf */
unsigned int pipe; /* the data i/o pipe */
unsigned int freqn; /* nominal sampling rate in fs/fps in Q16.16 format */
unsigned int freqm; /* momentary sampling rate in fs/fps in Q16.16 format */
int freqshift; /* how much to shift the feedback value to get Q16.16 */
unsigned int freqmax; /* maximum sampling rate, used for buffer management */
unsigned int phase; /* phase accumulator */
unsigned int maxpacksize; /* max packet size in bytes */
unsigned int maxframesize; /* max packet size in frames */
unsigned int max_urb_frames; /* max URB size in frames */
unsigned int curpacksize; /* current packet size in bytes (for capture) */
unsigned int curframesize; /* current packet size in frames (for capture) */
unsigned int syncmaxsize; /* sync endpoint packet size */
unsigned int fill_max:1; /* fill max packet size always */
unsigned int udh01_fb_quirk:1; /* corrupted feedback data */
unsigned int datainterval; /* log_2 of data packet interval */
unsigned int syncinterval; /* P for adaptive mode, 0 otherwise */
unsigned char silence_value;
unsigned int stride;
int iface, altsetting;
int skip_packets; /* quirks for devices to ignore the first n packets
in a stream */
spinlock_t lock;
struct list_head list;
};
struct snd_usb_substream {
struct snd_usb_stream *stream;
struct usb_device *dev;
struct snd_pcm_substream *pcm_substream;
int direction; /* playback or capture */
int interface; /* current interface */
int endpoint; /* assigned endpoint */
struct audioformat *cur_audiofmt; /* current audioformat pointer (for hw_params callback) */
snd_pcm_format_t pcm_format; /* current audio format (for hw_params callback) */
unsigned int channels; /* current number of channels (for hw_params callback) */
unsigned int channels_max; /* max channels in the all audiofmts */
unsigned int cur_rate; /* current rate (for hw_params callback) */
unsigned int period_bytes; /* current period bytes (for hw_params callback) */
unsigned int period_frames; /* current frames per period */
unsigned int buffer_periods; /* current periods per buffer */
unsigned int altset_idx; /* USB data format: index of alternate setting */
unsigned int txfr_quirk:1; /* allow sub-frame alignment */
unsigned int fmt_type; /* USB audio format type (1-3) */
unsigned int pkt_offset_adj; /* Bytes to drop from beginning of packets (for non-compliant devices) */
unsigned int running: 1; /* running status */
unsigned int hwptr_done; /* processed byte position in the buffer */
unsigned int transfer_done; /* processed frames since last period update */
unsigned int frame_limit; /* limits number of packets in URB */
/* data and sync endpoints for this stream */
unsigned int ep_num; /* the endpoint number */
struct snd_usb_endpoint *data_endpoint;
struct snd_usb_endpoint *sync_endpoint;
unsigned long flags;
bool need_setup_ep; /* (re)configure EP at prepare? */
unsigned int speed; /* USB_SPEED_XXX */
u64 formats; /* format bitmasks (all or'ed) */
unsigned int num_formats; /* number of supported audio formats (list) */
struct list_head fmt_list; /* format list */
struct snd_pcm_hw_constraint_list rate_list; /* limited rates */
spinlock_t lock;
int last_frame_number; /* stored frame number */
int last_delay; /* stored delay */
struct {
int marker;
int channel;
int byte_idx;
} dsd_dop;
};
struct snd_usb_stream {
struct snd_usb_audio *chip;
struct snd_pcm *pcm;
int pcm_index;
unsigned int fmt_type; /* USB audio format type (1-3) */
struct snd_usb_substream substream[2];
struct list_head list;
};
#endif /* __USBAUDIO_CARD_H */

422
sound/usb/clock.c Normal file
View file

@ -0,0 +1,422 @@
/*
* Clock domain and sample rate management functions
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#include <linux/bitops.h>
#include <linux/init.h>
#include <linux/string.h>
#include <linux/usb.h>
#include <linux/usb/audio.h>
#include <linux/usb/audio-v2.h>
#include <sound/core.h>
#include <sound/info.h>
#include <sound/pcm.h>
#include "usbaudio.h"
#include "card.h"
#include "helper.h"
#include "clock.h"
#include "quirks.h"
static struct uac_clock_source_descriptor *
snd_usb_find_clock_source(struct usb_host_interface *ctrl_iface,
int clock_id)
{
struct uac_clock_source_descriptor *cs = NULL;
while ((cs = snd_usb_find_csint_desc(ctrl_iface->extra,
ctrl_iface->extralen,
cs, UAC2_CLOCK_SOURCE))) {
if (cs->bClockID == clock_id)
return cs;
}
return NULL;
}
static struct uac_clock_selector_descriptor *
snd_usb_find_clock_selector(struct usb_host_interface *ctrl_iface,
int clock_id)
{
struct uac_clock_selector_descriptor *cs = NULL;
while ((cs = snd_usb_find_csint_desc(ctrl_iface->extra,
ctrl_iface->extralen,
cs, UAC2_CLOCK_SELECTOR))) {
if (cs->bClockID == clock_id)
return cs;
}
return NULL;
}
static struct uac_clock_multiplier_descriptor *
snd_usb_find_clock_multiplier(struct usb_host_interface *ctrl_iface,
int clock_id)
{
struct uac_clock_multiplier_descriptor *cs = NULL;
while ((cs = snd_usb_find_csint_desc(ctrl_iface->extra,
ctrl_iface->extralen,
cs, UAC2_CLOCK_MULTIPLIER))) {
if (cs->bClockID == clock_id)
return cs;
}
return NULL;
}
static int uac_clock_selector_get_val(struct snd_usb_audio *chip, int selector_id)
{
unsigned char buf;
int ret;
ret = snd_usb_ctl_msg(chip->dev, usb_rcvctrlpipe(chip->dev, 0),
UAC2_CS_CUR,
USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_IN,
UAC2_CX_CLOCK_SELECTOR << 8,
snd_usb_ctrl_intf(chip) | (selector_id << 8),
&buf, sizeof(buf));
if (ret < 0)
return ret;
return buf;
}
static int uac_clock_selector_set_val(struct snd_usb_audio *chip, int selector_id,
unsigned char pin)
{
int ret;
ret = snd_usb_ctl_msg(chip->dev, usb_sndctrlpipe(chip->dev, 0),
UAC2_CS_CUR,
USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_OUT,
UAC2_CX_CLOCK_SELECTOR << 8,
snd_usb_ctrl_intf(chip) | (selector_id << 8),
&pin, sizeof(pin));
if (ret < 0)
return ret;
if (ret != sizeof(pin)) {
usb_audio_err(chip,
"setting selector (id %d) unexpected length %d\n",
selector_id, ret);
return -EINVAL;
}
ret = uac_clock_selector_get_val(chip, selector_id);
if (ret < 0)
return ret;
if (ret != pin) {
usb_audio_err(chip,
"setting selector (id %d) to %x failed (current: %d)\n",
selector_id, pin, ret);
return -EINVAL;
}
return ret;
}
static bool uac_clock_source_is_valid(struct snd_usb_audio *chip, int source_id)
{
int err;
unsigned char data;
struct usb_device *dev = chip->dev;
struct uac_clock_source_descriptor *cs_desc =
snd_usb_find_clock_source(chip->ctrl_intf, source_id);
if (!cs_desc)
return 0;
/* If a clock source can't tell us whether it's valid, we assume it is */
if (!uac2_control_is_readable(cs_desc->bmControls,
UAC2_CS_CONTROL_CLOCK_VALID - 1))
return 1;
err = snd_usb_ctl_msg(dev, usb_rcvctrlpipe(dev, 0), UAC2_CS_CUR,
USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_IN,
UAC2_CS_CONTROL_CLOCK_VALID << 8,
snd_usb_ctrl_intf(chip) | (source_id << 8),
&data, sizeof(data));
if (err < 0) {
dev_warn(&dev->dev,
"%s(): cannot get clock validity for id %d\n",
__func__, source_id);
return 0;
}
return !!data;
}
static int __uac_clock_find_source(struct snd_usb_audio *chip,
int entity_id, unsigned long *visited,
bool validate)
{
struct uac_clock_source_descriptor *source;
struct uac_clock_selector_descriptor *selector;
struct uac_clock_multiplier_descriptor *multiplier;
entity_id &= 0xff;
if (test_and_set_bit(entity_id, visited)) {
usb_audio_warn(chip,
"%s(): recursive clock topology detected, id %d.\n",
__func__, entity_id);
return -EINVAL;
}
/* first, see if the ID we're looking for is a clock source already */
source = snd_usb_find_clock_source(chip->ctrl_intf, entity_id);
if (source) {
entity_id = source->bClockID;
if (validate && !uac_clock_source_is_valid(chip, entity_id)) {
usb_audio_err(chip,
"clock source %d is not valid, cannot use\n",
entity_id);
return -ENXIO;
}
return entity_id;
}
selector = snd_usb_find_clock_selector(chip->ctrl_intf, entity_id);
if (selector) {
int ret, i, cur;
/* the entity ID we are looking for is a selector.
* find out what it currently selects */
ret = uac_clock_selector_get_val(chip, selector->bClockID);
if (ret < 0)
return ret;
/* Selector values are one-based */
if (ret > selector->bNrInPins || ret < 1) {
usb_audio_err(chip,
"%s(): selector reported illegal value, id %d, ret %d\n",
__func__, selector->bClockID, ret);
return -EINVAL;
}
cur = ret;
ret = __uac_clock_find_source(chip, selector->baCSourceID[ret - 1],
visited, validate);
if (!validate || ret > 0 || !chip->autoclock)
return ret;
/* The current clock source is invalid, try others. */
for (i = 1; i <= selector->bNrInPins; i++) {
int err;
if (i == cur)
continue;
ret = __uac_clock_find_source(chip, selector->baCSourceID[i - 1],
visited, true);
if (ret < 0)
continue;
err = uac_clock_selector_set_val(chip, entity_id, i);
if (err < 0)
continue;
usb_audio_info(chip,
"found and selected valid clock source %d\n",
ret);
return ret;
}
return -ENXIO;
}
/* FIXME: multipliers only act as pass-thru element for now */
multiplier = snd_usb_find_clock_multiplier(chip->ctrl_intf, entity_id);
if (multiplier)
return __uac_clock_find_source(chip, multiplier->bCSourceID,
visited, validate);
return -EINVAL;
}
/*
* For all kinds of sample rate settings and other device queries,
* the clock source (end-leaf) must be used. However, clock selectors,
* clock multipliers and sample rate converters may be specified as
* clock source input to terminal. This functions walks the clock path
* to its end and tries to find the source.
*
* The 'visited' bitfield is used internally to detect recursive loops.
*
* Returns the clock source UnitID (>=0) on success, or an error.
*/
int snd_usb_clock_find_source(struct snd_usb_audio *chip, int entity_id,
bool validate)
{
DECLARE_BITMAP(visited, 256);
memset(visited, 0, sizeof(visited));
return __uac_clock_find_source(chip, entity_id, visited, validate);
}
static int set_sample_rate_v1(struct snd_usb_audio *chip, int iface,
struct usb_host_interface *alts,
struct audioformat *fmt, int rate)
{
struct usb_device *dev = chip->dev;
unsigned int ep;
unsigned char data[3];
int err, crate;
ep = get_endpoint(alts, 0)->bEndpointAddress;
/* if endpoint doesn't have sampling rate control, bail out */
if (!(fmt->attributes & UAC_EP_CS_ATTR_SAMPLE_RATE))
return 0;
data[0] = rate;
data[1] = rate >> 8;
data[2] = rate >> 16;
if ((err = snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), UAC_SET_CUR,
USB_TYPE_CLASS | USB_RECIP_ENDPOINT | USB_DIR_OUT,
UAC_EP_CS_ATTR_SAMPLE_RATE << 8, ep,
data, sizeof(data))) < 0) {
dev_err(&dev->dev, "%d:%d: cannot set freq %d to ep %#x\n",
iface, fmt->altsetting, rate, ep);
return err;
}
if ((err = snd_usb_ctl_msg(dev, usb_rcvctrlpipe(dev, 0), UAC_GET_CUR,
USB_TYPE_CLASS | USB_RECIP_ENDPOINT | USB_DIR_IN,
UAC_EP_CS_ATTR_SAMPLE_RATE << 8, ep,
data, sizeof(data))) < 0) {
dev_err(&dev->dev, "%d:%d: cannot get freq at ep %#x\n",
iface, fmt->altsetting, ep);
return 0; /* some devices don't support reading */
}
crate = data[0] | (data[1] << 8) | (data[2] << 16);
if (crate != rate) {
dev_warn(&dev->dev, "current rate %d is different from the runtime rate %d\n", crate, rate);
// runtime->rate = crate;
}
return 0;
}
static int get_sample_rate_v2(struct snd_usb_audio *chip, int iface,
int altsetting, int clock)
{
struct usb_device *dev = chip->dev;
__le32 data;
int err;
err = snd_usb_ctl_msg(dev, usb_rcvctrlpipe(dev, 0), UAC2_CS_CUR,
USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_IN,
UAC2_CS_CONTROL_SAM_FREQ << 8,
snd_usb_ctrl_intf(chip) | (clock << 8),
&data, sizeof(data));
if (err < 0) {
dev_warn(&dev->dev, "%d:%d: cannot get freq (v2): err %d\n",
iface, altsetting, err);
return 0;
}
return le32_to_cpu(data);
}
static int set_sample_rate_v2(struct snd_usb_audio *chip, int iface,
struct usb_host_interface *alts,
struct audioformat *fmt, int rate)
{
struct usb_device *dev = chip->dev;
__le32 data;
int err, cur_rate, prev_rate;
int clock;
bool writeable;
struct uac_clock_source_descriptor *cs_desc;
clock = snd_usb_clock_find_source(chip, fmt->clock, true);
if (clock < 0)
return clock;
prev_rate = get_sample_rate_v2(chip, iface, fmt->altsetting, clock);
if (prev_rate == rate)
return 0;
cs_desc = snd_usb_find_clock_source(chip->ctrl_intf, clock);
writeable = uac2_control_is_writeable(cs_desc->bmControls, UAC2_CS_CONTROL_SAM_FREQ - 1);
if (writeable) {
data = cpu_to_le32(rate);
err = snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), UAC2_CS_CUR,
USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_OUT,
UAC2_CS_CONTROL_SAM_FREQ << 8,
snd_usb_ctrl_intf(chip) | (clock << 8),
&data, sizeof(data));
if (err < 0) {
usb_audio_err(chip,
"%d:%d: cannot set freq %d (v2): err %d\n",
iface, fmt->altsetting, rate, err);
return err;
}
cur_rate = get_sample_rate_v2(chip, iface, fmt->altsetting, clock);
} else {
cur_rate = prev_rate;
}
if (cur_rate != rate) {
if (!writeable) {
usb_audio_warn(chip,
"%d:%d: freq mismatch (RO clock): req %d, clock runs @%d\n",
iface, fmt->altsetting, rate, cur_rate);
return -ENXIO;
}
usb_audio_dbg(chip,
"current rate %d is different from the runtime rate %d\n",
cur_rate, rate);
}
/* Some devices doesn't respond to sample rate changes while the
* interface is active. */
if (rate != prev_rate) {
usb_set_interface(dev, iface, 0);
snd_usb_set_interface_quirk(dev);
usb_set_interface(dev, iface, fmt->altsetting);
snd_usb_set_interface_quirk(dev);
}
return 0;
}
int snd_usb_init_sample_rate(struct snd_usb_audio *chip, int iface,
struct usb_host_interface *alts,
struct audioformat *fmt, int rate)
{
switch (fmt->protocol) {
case UAC_VERSION_1:
default:
return set_sample_rate_v1(chip, iface, alts, fmt, rate);
case UAC_VERSION_2:
return set_sample_rate_v2(chip, iface, alts, fmt, rate);
}
}

11
sound/usb/clock.h Normal file
View file

@ -0,0 +1,11 @@
#ifndef __USBAUDIO_CLOCK_H
#define __USBAUDIO_CLOCK_H
int snd_usb_init_sample_rate(struct snd_usb_audio *chip, int iface,
struct usb_host_interface *alts,
struct audioformat *fmt, int rate);
int snd_usb_clock_find_source(struct snd_usb_audio *chip, int entity_id,
bool validate);
#endif /* __USBAUDIO_CLOCK_H */

15
sound/usb/debug.h Normal file
View file

@ -0,0 +1,15 @@
#ifndef __USBAUDIO_DEBUG_H
#define __USBAUDIO_DEBUG_H
/*
* h/w constraints
*/
#ifdef HW_CONST_DEBUG
#define hwc_debug(fmt, args...) printk(KERN_DEBUG fmt, ##args)
#else
#define hwc_debug(fmt, args...) do { } while(0)
#endif
#endif /* __USBAUDIO_DEBUG_H */

1170
sound/usb/endpoint.c Normal file

File diff suppressed because it is too large Load diff

36
sound/usb/endpoint.h Normal file
View file

@ -0,0 +1,36 @@
#ifndef __USBAUDIO_ENDPOINT_H
#define __USBAUDIO_ENDPOINT_H
#define SND_USB_ENDPOINT_TYPE_DATA 0
#define SND_USB_ENDPOINT_TYPE_SYNC 1
struct snd_usb_endpoint *snd_usb_add_endpoint(struct snd_usb_audio *chip,
struct usb_host_interface *alts,
int ep_num, int direction, int type);
int snd_usb_endpoint_set_params(struct snd_usb_endpoint *ep,
snd_pcm_format_t pcm_format,
unsigned int channels,
unsigned int period_bytes,
unsigned int period_frames,
unsigned int buffer_periods,
unsigned int rate,
struct audioformat *fmt,
struct snd_usb_endpoint *sync_ep);
int snd_usb_endpoint_start(struct snd_usb_endpoint *ep, bool can_sleep);
void snd_usb_endpoint_stop(struct snd_usb_endpoint *ep);
void snd_usb_endpoint_sync_pending_stop(struct snd_usb_endpoint *ep);
int snd_usb_endpoint_activate(struct snd_usb_endpoint *ep);
void snd_usb_endpoint_deactivate(struct snd_usb_endpoint *ep);
void snd_usb_endpoint_release(struct snd_usb_endpoint *ep);
void snd_usb_endpoint_free(struct list_head *head);
int snd_usb_endpoint_implicit_feedback_sink(struct snd_usb_endpoint *ep);
int snd_usb_endpoint_next_packet_size(struct snd_usb_endpoint *ep);
void snd_usb_handle_sync_urb(struct snd_usb_endpoint *ep,
struct snd_usb_endpoint *sender,
const struct urb *urb);
#endif /* __USBAUDIO_ENDPOINT_H */

521
sound/usb/format.c Normal file
View file

@ -0,0 +1,521 @@
/*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/usb.h>
#include <linux/usb/audio.h>
#include <linux/usb/audio-v2.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include "usbaudio.h"
#include "card.h"
#include "quirks.h"
#include "helper.h"
#include "debug.h"
#include "clock.h"
#include "format.h"
/*
* parse the audio format type I descriptor
* and returns the corresponding pcm format
*
* @dev: usb device
* @fp: audioformat record
* @format: the format tag (wFormatTag)
* @fmt: the format type descriptor
*/
static u64 parse_audio_format_i_type(struct snd_usb_audio *chip,
struct audioformat *fp,
unsigned int format, void *_fmt)
{
int sample_width, sample_bytes;
u64 pcm_formats = 0;
switch (fp->protocol) {
case UAC_VERSION_1:
default: {
struct uac_format_type_i_discrete_descriptor *fmt = _fmt;
sample_width = fmt->bBitResolution;
sample_bytes = fmt->bSubframeSize;
format = 1 << format;
break;
}
case UAC_VERSION_2: {
struct uac_format_type_i_ext_descriptor *fmt = _fmt;
sample_width = fmt->bBitResolution;
sample_bytes = fmt->bSubslotSize;
if (format & UAC2_FORMAT_TYPE_I_RAW_DATA)
pcm_formats |= SNDRV_PCM_FMTBIT_SPECIAL;
format <<= 1;
break;
}
}
if ((pcm_formats == 0) &&
(format == 0 || format == (1 << UAC_FORMAT_TYPE_I_UNDEFINED))) {
/* some devices don't define this correctly... */
usb_audio_info(chip, "%u:%d : format type 0 is detected, processed as PCM\n",
fp->iface, fp->altsetting);
format = 1 << UAC_FORMAT_TYPE_I_PCM;
}
if (format & (1 << UAC_FORMAT_TYPE_I_PCM)) {
if (chip->usb_id == USB_ID(0x0582, 0x0016) /* Edirol SD-90 */ &&
sample_width == 24 && sample_bytes == 2)
sample_bytes = 3;
else if (sample_width > sample_bytes * 8) {
usb_audio_info(chip, "%u:%d : sample bitwidth %d in over sample bytes %d\n",
fp->iface, fp->altsetting,
sample_width, sample_bytes);
}
/* check the format byte size */
switch (sample_bytes) {
case 1:
pcm_formats |= SNDRV_PCM_FMTBIT_S8;
break;
case 2:
if (snd_usb_is_big_endian_format(chip, fp))
pcm_formats |= SNDRV_PCM_FMTBIT_S16_BE; /* grrr, big endian!! */
else
pcm_formats |= SNDRV_PCM_FMTBIT_S16_LE;
break;
case 3:
if (snd_usb_is_big_endian_format(chip, fp))
pcm_formats |= SNDRV_PCM_FMTBIT_S24_3BE; /* grrr, big endian!! */
else
pcm_formats |= SNDRV_PCM_FMTBIT_S24_3LE;
break;
case 4:
pcm_formats |= SNDRV_PCM_FMTBIT_S32_LE;
break;
default:
usb_audio_info(chip,
"%u:%d : unsupported sample bitwidth %d in %d bytes\n",
fp->iface, fp->altsetting,
sample_width, sample_bytes);
break;
}
}
if (format & (1 << UAC_FORMAT_TYPE_I_PCM8)) {
/* Dallas DS4201 workaround: it advertises U8 format, but really
supports S8. */
if (chip->usb_id == USB_ID(0x04fa, 0x4201))
pcm_formats |= SNDRV_PCM_FMTBIT_S8;
else
pcm_formats |= SNDRV_PCM_FMTBIT_U8;
}
if (format & (1 << UAC_FORMAT_TYPE_I_IEEE_FLOAT)) {
pcm_formats |= SNDRV_PCM_FMTBIT_FLOAT_LE;
}
if (format & (1 << UAC_FORMAT_TYPE_I_ALAW)) {
pcm_formats |= SNDRV_PCM_FMTBIT_A_LAW;
}
if (format & (1 << UAC_FORMAT_TYPE_I_MULAW)) {
pcm_formats |= SNDRV_PCM_FMTBIT_MU_LAW;
}
if (format & ~0x3f) {
usb_audio_info(chip,
"%u:%d : unsupported format bits %#x\n",
fp->iface, fp->altsetting, format);
}
pcm_formats |= snd_usb_interface_dsd_format_quirks(chip, fp, sample_bytes);
return pcm_formats;
}
/*
* parse the format descriptor and stores the possible sample rates
* on the audioformat table (audio class v1).
*
* @dev: usb device
* @fp: audioformat record
* @fmt: the format descriptor
* @offset: the start offset of descriptor pointing the rate type
* (7 for type I and II, 8 for type II)
*/
static int parse_audio_format_rates_v1(struct snd_usb_audio *chip, struct audioformat *fp,
unsigned char *fmt, int offset)
{
int nr_rates = fmt[offset];
if (fmt[0] < offset + 1 + 3 * (nr_rates ? nr_rates : 2)) {
usb_audio_err(chip,
"%u:%d : invalid UAC_FORMAT_TYPE desc\n",
fp->iface, fp->altsetting);
return -EINVAL;
}
if (nr_rates) {
/*
* build the rate table and bitmap flags
*/
int r, idx;
fp->rate_table = kmalloc(sizeof(int) * nr_rates, GFP_KERNEL);
if (fp->rate_table == NULL) {
usb_audio_err(chip, "cannot malloc\n");
return -ENOMEM;
}
fp->nr_rates = 0;
fp->rate_min = fp->rate_max = 0;
for (r = 0, idx = offset + 1; r < nr_rates; r++, idx += 3) {
unsigned int rate = combine_triple(&fmt[idx]);
if (!rate)
continue;
/* C-Media CM6501 mislabels its 96 kHz altsetting */
/* Terratec Aureon 7.1 USB C-Media 6206, too */
if (rate == 48000 && nr_rates == 1 &&
(chip->usb_id == USB_ID(0x0d8c, 0x0201) ||
chip->usb_id == USB_ID(0x0d8c, 0x0102) ||
chip->usb_id == USB_ID(0x0ccd, 0x00b1)) &&
fp->altsetting == 5 && fp->maxpacksize == 392)
rate = 96000;
/* Creative VF0420/VF0470 Live Cams report 16 kHz instead of 8kHz */
if (rate == 16000 &&
(chip->usb_id == USB_ID(0x041e, 0x4064) ||
chip->usb_id == USB_ID(0x041e, 0x4068)))
rate = 8000;
fp->rate_table[fp->nr_rates] = rate;
if (!fp->rate_min || rate < fp->rate_min)
fp->rate_min = rate;
if (!fp->rate_max || rate > fp->rate_max)
fp->rate_max = rate;
fp->rates |= snd_pcm_rate_to_rate_bit(rate);
fp->nr_rates++;
}
if (!fp->nr_rates) {
hwc_debug("All rates were zero. Skipping format!\n");
return -EINVAL;
}
} else {
/* continuous rates */
fp->rates = SNDRV_PCM_RATE_CONTINUOUS;
fp->rate_min = combine_triple(&fmt[offset + 1]);
fp->rate_max = combine_triple(&fmt[offset + 4]);
}
return 0;
}
/*
* Helper function to walk the array of sample rate triplets reported by
* the device. The problem is that we need to parse whole array first to
* get to know how many sample rates we have to expect.
* Then fp->rate_table can be allocated and filled.
*/
static int parse_uac2_sample_rate_range(struct snd_usb_audio *chip,
struct audioformat *fp, int nr_triplets,
const unsigned char *data)
{
int i, nr_rates = 0;
fp->rates = fp->rate_min = fp->rate_max = 0;
for (i = 0; i < nr_triplets; i++) {
int min = combine_quad(&data[2 + 12 * i]);
int max = combine_quad(&data[6 + 12 * i]);
int res = combine_quad(&data[10 + 12 * i]);
unsigned int rate;
if ((max < 0) || (min < 0) || (res < 0) || (max < min))
continue;
/*
* for ranges with res == 1, we announce a continuous sample
* rate range, and this function should return 0 for no further
* parsing.
*/
if (res == 1) {
fp->rate_min = min;
fp->rate_max = max;
fp->rates = SNDRV_PCM_RATE_CONTINUOUS;
return 0;
}
for (rate = min; rate <= max; rate += res) {
if (fp->rate_table)
fp->rate_table[nr_rates] = rate;
if (!fp->rate_min || rate < fp->rate_min)
fp->rate_min = rate;
if (!fp->rate_max || rate > fp->rate_max)
fp->rate_max = rate;
fp->rates |= snd_pcm_rate_to_rate_bit(rate);
nr_rates++;
if (nr_rates >= MAX_NR_RATES) {
usb_audio_err(chip, "invalid uac2 rates\n");
break;
}
/* avoid endless loop */
if (res == 0)
break;
}
}
return nr_rates;
}
/*
* parse the format descriptor and stores the possible sample rates
* on the audioformat table (audio class v2).
*/
static int parse_audio_format_rates_v2(struct snd_usb_audio *chip,
struct audioformat *fp)
{
struct usb_device *dev = chip->dev;
unsigned char tmp[2], *data;
int nr_triplets, data_size, ret = 0;
int clock = snd_usb_clock_find_source(chip, fp->clock, false);
if (clock < 0) {
dev_err(&dev->dev,
"%s(): unable to find clock source (clock %d)\n",
__func__, clock);
goto err;
}
/* get the number of sample rates first by only fetching 2 bytes */
ret = snd_usb_ctl_msg(dev, usb_rcvctrlpipe(dev, 0), UAC2_CS_RANGE,
USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_IN,
UAC2_CS_CONTROL_SAM_FREQ << 8,
snd_usb_ctrl_intf(chip) | (clock << 8),
tmp, sizeof(tmp));
if (ret < 0) {
dev_err(&dev->dev,
"%s(): unable to retrieve number of sample rates (clock %d)\n",
__func__, clock);
goto err;
}
nr_triplets = (tmp[1] << 8) | tmp[0];
data_size = 2 + 12 * nr_triplets;
data = kzalloc(data_size, GFP_KERNEL);
if (!data) {
ret = -ENOMEM;
goto err;
}
/* now get the full information */
ret = snd_usb_ctl_msg(dev, usb_rcvctrlpipe(dev, 0), UAC2_CS_RANGE,
USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_IN,
UAC2_CS_CONTROL_SAM_FREQ << 8,
snd_usb_ctrl_intf(chip) | (clock << 8),
data, data_size);
if (ret < 0) {
dev_err(&dev->dev,
"%s(): unable to retrieve sample rate range (clock %d)\n",
__func__, clock);
ret = -EINVAL;
goto err_free;
}
/* Call the triplet parser, and make sure fp->rate_table is NULL.
* We just use the return value to know how many sample rates we
* will have to deal with. */
kfree(fp->rate_table);
fp->rate_table = NULL;
fp->nr_rates = parse_uac2_sample_rate_range(chip, fp, nr_triplets, data);
if (fp->nr_rates == 0) {
/* SNDRV_PCM_RATE_CONTINUOUS */
ret = 0;
goto err_free;
}
fp->rate_table = kmalloc(sizeof(int) * fp->nr_rates, GFP_KERNEL);
if (!fp->rate_table) {
ret = -ENOMEM;
goto err_free;
}
/* Call the triplet parser again, but this time, fp->rate_table is
* allocated, so the rates will be stored */
parse_uac2_sample_rate_range(chip, fp, nr_triplets, data);
err_free:
kfree(data);
err:
return ret;
}
/*
* parse the format type I and III descriptors
*/
static int parse_audio_format_i(struct snd_usb_audio *chip,
struct audioformat *fp, unsigned int format,
struct uac_format_type_i_continuous_descriptor *fmt)
{
snd_pcm_format_t pcm_format;
int ret;
if (fmt->bFormatType == UAC_FORMAT_TYPE_III) {
/* FIXME: the format type is really IECxxx
* but we give normal PCM format to get the existing
* apps working...
*/
switch (chip->usb_id) {
case USB_ID(0x0763, 0x2003): /* M-Audio Audiophile USB */
if (chip->setup == 0x00 &&
fp->altsetting == 6)
pcm_format = SNDRV_PCM_FORMAT_S16_BE;
else
pcm_format = SNDRV_PCM_FORMAT_S16_LE;
break;
default:
pcm_format = SNDRV_PCM_FORMAT_S16_LE;
}
fp->formats = pcm_format_to_bits(pcm_format);
} else {
fp->formats = parse_audio_format_i_type(chip, fp, format, fmt);
if (!fp->formats)
return -EINVAL;
}
/* gather possible sample rates */
/* audio class v1 reports possible sample rates as part of the
* proprietary class specific descriptor.
* audio class v2 uses class specific EP0 range requests for that.
*/
switch (fp->protocol) {
default:
case UAC_VERSION_1:
fp->channels = fmt->bNrChannels;
ret = parse_audio_format_rates_v1(chip, fp, (unsigned char *) fmt, 7);
break;
case UAC_VERSION_2:
/* fp->channels is already set in this case */
ret = parse_audio_format_rates_v2(chip, fp);
break;
}
if (fp->channels < 1) {
usb_audio_err(chip,
"%u:%d : invalid channels %d\n",
fp->iface, fp->altsetting, fp->channels);
return -EINVAL;
}
return ret;
}
/*
* parse the format type II descriptor
*/
static int parse_audio_format_ii(struct snd_usb_audio *chip,
struct audioformat *fp,
int format, void *_fmt)
{
int brate, framesize, ret;
switch (format) {
case UAC_FORMAT_TYPE_II_AC3:
/* FIXME: there is no AC3 format defined yet */
// fp->formats = SNDRV_PCM_FMTBIT_AC3;
fp->formats = SNDRV_PCM_FMTBIT_U8; /* temporary hack to receive byte streams */
break;
case UAC_FORMAT_TYPE_II_MPEG:
fp->formats = SNDRV_PCM_FMTBIT_MPEG;
break;
default:
usb_audio_info(chip,
"%u:%d : unknown format tag %#x is detected. processed as MPEG.\n",
fp->iface, fp->altsetting, format);
fp->formats = SNDRV_PCM_FMTBIT_MPEG;
break;
}
fp->channels = 1;
switch (fp->protocol) {
default:
case UAC_VERSION_1: {
struct uac_format_type_ii_discrete_descriptor *fmt = _fmt;
brate = le16_to_cpu(fmt->wMaxBitRate);
framesize = le16_to_cpu(fmt->wSamplesPerFrame);
usb_audio_info(chip, "found format II with max.bitrate = %d, frame size=%d\n", brate, framesize);
fp->frame_size = framesize;
ret = parse_audio_format_rates_v1(chip, fp, _fmt, 8); /* fmt[8..] sample rates */
break;
}
case UAC_VERSION_2: {
struct uac_format_type_ii_ext_descriptor *fmt = _fmt;
brate = le16_to_cpu(fmt->wMaxBitRate);
framesize = le16_to_cpu(fmt->wSamplesPerFrame);
usb_audio_info(chip, "found format II with max.bitrate = %d, frame size=%d\n", brate, framesize);
fp->frame_size = framesize;
ret = parse_audio_format_rates_v2(chip, fp);
break;
}
}
return ret;
}
int snd_usb_parse_audio_format(struct snd_usb_audio *chip,
struct audioformat *fp, unsigned int format,
struct uac_format_type_i_continuous_descriptor *fmt,
int stream)
{
int err;
switch (fmt->bFormatType) {
case UAC_FORMAT_TYPE_I:
case UAC_FORMAT_TYPE_III:
err = parse_audio_format_i(chip, fp, format, fmt);
break;
case UAC_FORMAT_TYPE_II:
err = parse_audio_format_ii(chip, fp, format, fmt);
break;
default:
usb_audio_info(chip,
"%u:%d : format type %d is not supported yet\n",
fp->iface, fp->altsetting,
fmt->bFormatType);
return -ENOTSUPP;
}
fp->fmt_type = fmt->bFormatType;
if (err < 0)
return err;
#if 1
/* FIXME: temporary hack for extigy/audigy 2 nx/zs */
/* extigy apparently supports sample rates other than 48k
* but not in ordinary way. so we enable only 48k atm.
*/
if (chip->usb_id == USB_ID(0x041e, 0x3000) ||
chip->usb_id == USB_ID(0x041e, 0x3020) ||
chip->usb_id == USB_ID(0x041e, 0x3061)) {
if (fmt->bFormatType == UAC_FORMAT_TYPE_I &&
fp->rates != SNDRV_PCM_RATE_48000 &&
fp->rates != SNDRV_PCM_RATE_96000)
return -ENOTSUPP;
}
#endif
return 0;
}

9
sound/usb/format.h Normal file
View file

@ -0,0 +1,9 @@
#ifndef __USBAUDIO_FORMAT_H
#define __USBAUDIO_FORMAT_H
int snd_usb_parse_audio_format(struct snd_usb_audio *chip,
struct audioformat *fp, unsigned int format,
struct uac_format_type_i_continuous_descriptor *fmt,
int stream);
#endif /* __USBAUDIO_FORMAT_H */

132
sound/usb/helper.c Normal file
View file

@ -0,0 +1,132 @@
/*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/usb.h>
#include "usbaudio.h"
#include "helper.h"
#include "quirks.h"
/*
* combine bytes and get an integer value
*/
unsigned int snd_usb_combine_bytes(unsigned char *bytes, int size)
{
switch (size) {
case 1: return *bytes;
case 2: return combine_word(bytes);
case 3: return combine_triple(bytes);
case 4: return combine_quad(bytes);
default: return 0;
}
}
/*
* parse descriptor buffer and return the pointer starting the given
* descriptor type.
*/
void *snd_usb_find_desc(void *descstart, int desclen, void *after, u8 dtype)
{
u8 *p, *end, *next;
p = descstart;
end = p + desclen;
for (; p < end;) {
if (p[0] < 2)
return NULL;
next = p + p[0];
if (next > end)
return NULL;
if (p[1] == dtype && (!after || (void *)p > after)) {
return p;
}
p = next;
}
return NULL;
}
/*
* find a class-specified interface descriptor with the given subtype.
*/
void *snd_usb_find_csint_desc(void *buffer, int buflen, void *after, u8 dsubtype)
{
unsigned char *p = after;
while ((p = snd_usb_find_desc(buffer, buflen, p,
USB_DT_CS_INTERFACE)) != NULL) {
if (p[0] >= 3 && p[2] == dsubtype)
return p;
}
return NULL;
}
/*
* Wrapper for usb_control_msg().
* Allocates a temp buffer to prevent dmaing from/to the stack.
*/
int snd_usb_ctl_msg(struct usb_device *dev, unsigned int pipe, __u8 request,
__u8 requesttype, __u16 value, __u16 index, void *data,
__u16 size)
{
int err;
void *buf = NULL;
int timeout;
if (size > 0) {
buf = kmemdup(data, size, GFP_KERNEL);
if (!buf)
return -ENOMEM;
}
if (requesttype & USB_DIR_IN)
timeout = USB_CTRL_GET_TIMEOUT;
else
timeout = USB_CTRL_SET_TIMEOUT;
err = usb_control_msg(dev, pipe, request, requesttype,
value, index, buf, size, timeout);
if (size > 0) {
memcpy(data, buf, size);
kfree(buf);
}
snd_usb_ctl_msg_quirk(dev, pipe, request, requesttype,
value, index, data, size);
return err;
}
unsigned char snd_usb_parse_datainterval(struct snd_usb_audio *chip,
struct usb_host_interface *alts)
{
switch (snd_usb_get_speed(chip->dev)) {
case USB_SPEED_HIGH:
case USB_SPEED_WIRELESS:
case USB_SPEED_SUPER:
if (get_endpoint(alts, 0)->bInterval >= 1 &&
get_endpoint(alts, 0)->bInterval <= 4)
return get_endpoint(alts, 0)->bInterval - 1;
break;
default:
break;
}
return 0;
}

36
sound/usb/helper.h Normal file
View file

@ -0,0 +1,36 @@
#ifndef __USBAUDIO_HELPER_H
#define __USBAUDIO_HELPER_H
unsigned int snd_usb_combine_bytes(unsigned char *bytes, int size);
void *snd_usb_find_desc(void *descstart, int desclen, void *after, u8 dtype);
void *snd_usb_find_csint_desc(void *descstart, int desclen, void *after, u8 dsubtype);
int snd_usb_ctl_msg(struct usb_device *dev, unsigned int pipe,
__u8 request, __u8 requesttype, __u16 value, __u16 index,
void *data, __u16 size);
unsigned char snd_usb_parse_datainterval(struct snd_usb_audio *chip,
struct usb_host_interface *alts);
/*
* retrieve usb_interface descriptor from the host interface
* (conditional for compatibility with the older API)
*/
#ifndef get_iface_desc
#define get_iface_desc(iface) (&(iface)->desc)
#define get_endpoint(alt,ep) (&(alt)->endpoint[ep].desc)
#define get_ep_desc(ep) (&(ep)->desc)
#define get_cfg_desc(cfg) (&(cfg)->desc)
#endif
#ifndef snd_usb_get_speed
#define snd_usb_get_speed(dev) ((dev)->speed)
#endif
static inline int snd_usb_ctrl_intf(struct snd_usb_audio *chip)
{
return get_iface_desc(chip->ctrl_intf)->bInterfaceNumber;
}
#endif /* __USBAUDIO_HELPER_H */

View file

@ -0,0 +1,2 @@
snd-usb-hiface-objs := chip.o pcm.o
obj-$(CONFIG_SND_USB_HIFACE) += snd-usb-hiface.o

297
sound/usb/hiface/chip.c Normal file
View file

@ -0,0 +1,297 @@
/*
* Linux driver for M2Tech hiFace compatible devices
*
* Copyright 2012-2013 (C) M2TECH S.r.l and Amarula Solutions B.V.
*
* Authors: Michael Trimarchi <michael@amarulasolutions.com>
* Antonio Ospite <ao2@amarulasolutions.com>
*
* The driver is based on the work done in TerraTec DMX 6Fire USB
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*/
#include <linux/module.h>
#include <linux/slab.h>
#include <sound/initval.h>
#include "chip.h"
#include "pcm.h"
MODULE_AUTHOR("Michael Trimarchi <michael@amarulasolutions.com>");
MODULE_AUTHOR("Antonio Ospite <ao2@amarulasolutions.com>");
MODULE_DESCRIPTION("M2Tech hiFace USB-SPDIF audio driver");
MODULE_LICENSE("GPL v2");
MODULE_SUPPORTED_DEVICE("{{M2Tech,Young},"
"{M2Tech,hiFace},"
"{M2Tech,North Star},"
"{M2Tech,W4S Young},"
"{M2Tech,Corrson},"
"{M2Tech,AUDIA},"
"{M2Tech,SL Audio},"
"{M2Tech,Empirical},"
"{M2Tech,Rockna},"
"{M2Tech,Pathos},"
"{M2Tech,Metronome},"
"{M2Tech,CAD},"
"{M2Tech,Audio Esclusive},"
"{M2Tech,Rotel},"
"{M2Tech,Eeaudio},"
"{The Chord Company,CHORD},"
"{AVA Group A/S,Vitus}}");
static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 0-max */
static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* Id for card */
static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP; /* Enable this card */
#define DRIVER_NAME "snd-usb-hiface"
#define CARD_NAME "hiFace"
module_param_array(index, int, NULL, 0444);
MODULE_PARM_DESC(index, "Index value for " CARD_NAME " soundcard.");
module_param_array(id, charp, NULL, 0444);
MODULE_PARM_DESC(id, "ID string for " CARD_NAME " soundcard.");
module_param_array(enable, bool, NULL, 0444);
MODULE_PARM_DESC(enable, "Enable " CARD_NAME " soundcard.");
static DEFINE_MUTEX(register_mutex);
struct hiface_vendor_quirk {
const char *device_name;
u8 extra_freq;
};
static int hiface_chip_create(struct usb_interface *intf,
struct usb_device *device, int idx,
const struct hiface_vendor_quirk *quirk,
struct hiface_chip **rchip)
{
struct snd_card *card = NULL;
struct hiface_chip *chip;
int ret;
int len;
*rchip = NULL;
/* if we are here, card can be registered in alsa. */
ret = snd_card_new(&intf->dev, index[idx], id[idx], THIS_MODULE,
sizeof(*chip), &card);
if (ret < 0) {
dev_err(&device->dev, "cannot create alsa card.\n");
return ret;
}
strlcpy(card->driver, DRIVER_NAME, sizeof(card->driver));
if (quirk && quirk->device_name)
strlcpy(card->shortname, quirk->device_name, sizeof(card->shortname));
else
strlcpy(card->shortname, "M2Tech generic audio", sizeof(card->shortname));
strlcat(card->longname, card->shortname, sizeof(card->longname));
len = strlcat(card->longname, " at ", sizeof(card->longname));
if (len < sizeof(card->longname))
usb_make_path(device, card->longname + len,
sizeof(card->longname) - len);
chip = card->private_data;
chip->dev = device;
chip->card = card;
*rchip = chip;
return 0;
}
static int hiface_chip_probe(struct usb_interface *intf,
const struct usb_device_id *usb_id)
{
const struct hiface_vendor_quirk *quirk = (struct hiface_vendor_quirk *)usb_id->driver_info;
int ret;
int i;
struct hiface_chip *chip;
struct usb_device *device = interface_to_usbdev(intf);
ret = usb_set_interface(device, 0, 0);
if (ret != 0) {
dev_err(&device->dev, "can't set first interface for " CARD_NAME " device.\n");
return -EIO;
}
/* check whether the card is already registered */
chip = NULL;
mutex_lock(&register_mutex);
for (i = 0; i < SNDRV_CARDS; i++)
if (enable[i])
break;
if (i >= SNDRV_CARDS) {
dev_err(&device->dev, "no available " CARD_NAME " audio device\n");
ret = -ENODEV;
goto err;
}
ret = hiface_chip_create(intf, device, i, quirk, &chip);
if (ret < 0)
goto err;
ret = hiface_pcm_init(chip, quirk ? quirk->extra_freq : 0);
if (ret < 0)
goto err_chip_destroy;
ret = snd_card_register(chip->card);
if (ret < 0) {
dev_err(&device->dev, "cannot register " CARD_NAME " card\n");
goto err_chip_destroy;
}
mutex_unlock(&register_mutex);
usb_set_intfdata(intf, chip);
return 0;
err_chip_destroy:
snd_card_free(chip->card);
err:
mutex_unlock(&register_mutex);
return ret;
}
static void hiface_chip_disconnect(struct usb_interface *intf)
{
struct hiface_chip *chip;
struct snd_card *card;
chip = usb_get_intfdata(intf);
if (!chip)
return;
card = chip->card;
/* Make sure that the userspace cannot create new request */
snd_card_disconnect(card);
hiface_pcm_abort(chip);
snd_card_free_when_closed(card);
}
static const struct usb_device_id device_table[] = {
{
USB_DEVICE(0x04b4, 0x0384),
.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
.device_name = "Young",
.extra_freq = 1,
}
},
{
USB_DEVICE(0x04b4, 0x930b),
.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
.device_name = "hiFace",
}
},
{
USB_DEVICE(0x04b4, 0x931b),
.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
.device_name = "North Star",
}
},
{
USB_DEVICE(0x04b4, 0x931c),
.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
.device_name = "W4S Young",
}
},
{
USB_DEVICE(0x04b4, 0x931d),
.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
.device_name = "Corrson",
}
},
{
USB_DEVICE(0x04b4, 0x931e),
.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
.device_name = "AUDIA",
}
},
{
USB_DEVICE(0x04b4, 0x931f),
.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
.device_name = "SL Audio",
}
},
{
USB_DEVICE(0x04b4, 0x9320),
.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
.device_name = "Empirical",
}
},
{
USB_DEVICE(0x04b4, 0x9321),
.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
.device_name = "Rockna",
}
},
{
USB_DEVICE(0x249c, 0x9001),
.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
.device_name = "Pathos",
}
},
{
USB_DEVICE(0x249c, 0x9002),
.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
.device_name = "Metronome",
}
},
{
USB_DEVICE(0x249c, 0x9006),
.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
.device_name = "CAD",
}
},
{
USB_DEVICE(0x249c, 0x9008),
.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
.device_name = "Audio Esclusive",
}
},
{
USB_DEVICE(0x249c, 0x931c),
.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
.device_name = "Rotel",
}
},
{
USB_DEVICE(0x249c, 0x932c),
.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
.device_name = "Eeaudio",
}
},
{
USB_DEVICE(0x245f, 0x931c),
.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
.device_name = "CHORD",
}
},
{
USB_DEVICE(0x25c6, 0x9002),
.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
.device_name = "Vitus",
}
},
{}
};
MODULE_DEVICE_TABLE(usb, device_table);
static struct usb_driver hiface_usb_driver = {
.name = DRIVER_NAME,
.probe = hiface_chip_probe,
.disconnect = hiface_chip_disconnect,
.id_table = device_table,
};
module_usb_driver(hiface_usb_driver);

30
sound/usb/hiface/chip.h Normal file
View file

@ -0,0 +1,30 @@
/*
* Linux driver for M2Tech hiFace compatible devices
*
* Copyright 2012-2013 (C) M2TECH S.r.l and Amarula Solutions B.V.
*
* Authors: Michael Trimarchi <michael@amarulasolutions.com>
* Antonio Ospite <ao2@amarulasolutions.com>
*
* The driver is based on the work done in TerraTec DMX 6Fire USB
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*/
#ifndef HIFACE_CHIP_H
#define HIFACE_CHIP_H
#include <linux/usb.h>
#include <sound/core.h>
struct pcm_runtime;
struct hiface_chip {
struct usb_device *dev;
struct snd_card *card;
struct pcm_runtime *pcm;
};
#endif /* HIFACE_CHIP_H */

621
sound/usb/hiface/pcm.c Normal file
View file

@ -0,0 +1,621 @@
/*
* Linux driver for M2Tech hiFace compatible devices
*
* Copyright 2012-2013 (C) M2TECH S.r.l and Amarula Solutions B.V.
*
* Authors: Michael Trimarchi <michael@amarulasolutions.com>
* Antonio Ospite <ao2@amarulasolutions.com>
*
* The driver is based on the work done in TerraTec DMX 6Fire USB
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*/
#include <linux/slab.h>
#include <sound/pcm.h>
#include "pcm.h"
#include "chip.h"
#define OUT_EP 0x2
#define PCM_N_URBS 8
#define PCM_PACKET_SIZE 4096
#define PCM_BUFFER_SIZE (2 * PCM_N_URBS * PCM_PACKET_SIZE)
struct pcm_urb {
struct hiface_chip *chip;
struct urb instance;
struct usb_anchor submitted;
u8 *buffer;
};
struct pcm_substream {
spinlock_t lock;
struct snd_pcm_substream *instance;
bool active;
snd_pcm_uframes_t dma_off; /* current position in alsa dma_area */
snd_pcm_uframes_t period_off; /* current position in current period */
};
enum { /* pcm streaming states */
STREAM_DISABLED, /* no pcm streaming */
STREAM_STARTING, /* pcm streaming requested, waiting to become ready */
STREAM_RUNNING, /* pcm streaming running */
STREAM_STOPPING
};
struct pcm_runtime {
struct hiface_chip *chip;
struct snd_pcm *instance;
struct pcm_substream playback;
bool panic; /* if set driver won't do anymore pcm on device */
struct pcm_urb out_urbs[PCM_N_URBS];
struct mutex stream_mutex;
u8 stream_state; /* one of STREAM_XXX */
u8 extra_freq;
wait_queue_head_t stream_wait_queue;
bool stream_wait_cond;
};
static const unsigned int rates[] = { 44100, 48000, 88200, 96000, 176400, 192000,
352800, 384000 };
static const struct snd_pcm_hw_constraint_list constraints_extra_rates = {
.count = ARRAY_SIZE(rates),
.list = rates,
.mask = 0,
};
static const struct snd_pcm_hardware pcm_hw = {
.info = SNDRV_PCM_INFO_MMAP |
SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_BLOCK_TRANSFER |
SNDRV_PCM_INFO_PAUSE |
SNDRV_PCM_INFO_MMAP_VALID |
SNDRV_PCM_INFO_BATCH,
.formats = SNDRV_PCM_FMTBIT_S32_LE,
.rates = SNDRV_PCM_RATE_44100 |
SNDRV_PCM_RATE_48000 |
SNDRV_PCM_RATE_88200 |
SNDRV_PCM_RATE_96000 |
SNDRV_PCM_RATE_176400 |
SNDRV_PCM_RATE_192000,
.rate_min = 44100,
.rate_max = 192000, /* changes in hiface_pcm_open to support extra rates */
.channels_min = 2,
.channels_max = 2,
.buffer_bytes_max = PCM_BUFFER_SIZE,
.period_bytes_min = PCM_PACKET_SIZE,
.period_bytes_max = PCM_BUFFER_SIZE,
.periods_min = 2,
.periods_max = 1024
};
/* message values used to change the sample rate */
#define HIFACE_SET_RATE_REQUEST 0xb0
#define HIFACE_RATE_44100 0x43
#define HIFACE_RATE_48000 0x4b
#define HIFACE_RATE_88200 0x42
#define HIFACE_RATE_96000 0x4a
#define HIFACE_RATE_176400 0x40
#define HIFACE_RATE_192000 0x48
#define HIFACE_RATE_352800 0x58
#define HIFACE_RATE_384000 0x68
static int hiface_pcm_set_rate(struct pcm_runtime *rt, unsigned int rate)
{
struct usb_device *device = rt->chip->dev;
u16 rate_value;
int ret;
/* We are already sure that the rate is supported here thanks to
* ALSA constraints
*/
switch (rate) {
case 44100:
rate_value = HIFACE_RATE_44100;
break;
case 48000:
rate_value = HIFACE_RATE_48000;
break;
case 88200:
rate_value = HIFACE_RATE_88200;
break;
case 96000:
rate_value = HIFACE_RATE_96000;
break;
case 176400:
rate_value = HIFACE_RATE_176400;
break;
case 192000:
rate_value = HIFACE_RATE_192000;
break;
case 352800:
rate_value = HIFACE_RATE_352800;
break;
case 384000:
rate_value = HIFACE_RATE_384000;
break;
default:
dev_err(&device->dev, "Unsupported rate %d\n", rate);
return -EINVAL;
}
/*
* USBIO: Vendor 0xb0(wValue=0x0043, wIndex=0x0000)
* 43 b0 43 00 00 00 00 00
* USBIO: Vendor 0xb0(wValue=0x004b, wIndex=0x0000)
* 43 b0 4b 00 00 00 00 00
* This control message doesn't have any ack from the
* other side
*/
ret = usb_control_msg(device, usb_sndctrlpipe(device, 0),
HIFACE_SET_RATE_REQUEST,
USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_OTHER,
rate_value, 0, NULL, 0, 100);
if (ret < 0) {
dev_err(&device->dev, "Error setting samplerate %d.\n", rate);
return ret;
}
return 0;
}
static struct pcm_substream *hiface_pcm_get_substream(struct snd_pcm_substream
*alsa_sub)
{
struct pcm_runtime *rt = snd_pcm_substream_chip(alsa_sub);
struct device *device = &rt->chip->dev->dev;
if (alsa_sub->stream == SNDRV_PCM_STREAM_PLAYBACK)
return &rt->playback;
dev_err(device, "Error getting pcm substream slot.\n");
return NULL;
}
/* call with stream_mutex locked */
static void hiface_pcm_stream_stop(struct pcm_runtime *rt)
{
int i, time;
if (rt->stream_state != STREAM_DISABLED) {
rt->stream_state = STREAM_STOPPING;
for (i = 0; i < PCM_N_URBS; i++) {
time = usb_wait_anchor_empty_timeout(
&rt->out_urbs[i].submitted, 100);
if (!time)
usb_kill_anchored_urbs(
&rt->out_urbs[i].submitted);
usb_kill_urb(&rt->out_urbs[i].instance);
}
rt->stream_state = STREAM_DISABLED;
}
}
/* call with stream_mutex locked */
static int hiface_pcm_stream_start(struct pcm_runtime *rt)
{
int ret = 0;
int i;
if (rt->stream_state == STREAM_DISABLED) {
/* reset panic state when starting a new stream */
rt->panic = false;
/* submit our out urbs zero init */
rt->stream_state = STREAM_STARTING;
for (i = 0; i < PCM_N_URBS; i++) {
memset(rt->out_urbs[i].buffer, 0, PCM_PACKET_SIZE);
usb_anchor_urb(&rt->out_urbs[i].instance,
&rt->out_urbs[i].submitted);
ret = usb_submit_urb(&rt->out_urbs[i].instance,
GFP_ATOMIC);
if (ret) {
hiface_pcm_stream_stop(rt);
return ret;
}
}
/* wait for first out urb to return (sent in in urb handler) */
wait_event_timeout(rt->stream_wait_queue, rt->stream_wait_cond,
HZ);
if (rt->stream_wait_cond) {
struct device *device = &rt->chip->dev->dev;
dev_dbg(device, "%s: Stream is running wakeup event\n",
__func__);
rt->stream_state = STREAM_RUNNING;
} else {
hiface_pcm_stream_stop(rt);
return -EIO;
}
}
return ret;
}
/* The hardware wants word-swapped 32-bit values */
static void memcpy_swahw32(u8 *dest, u8 *src, unsigned int n)
{
unsigned int i;
for (i = 0; i < n / 4; i++)
((u32 *)dest)[i] = swahw32(((u32 *)src)[i]);
}
/* call with substream locked */
/* returns true if a period elapsed */
static bool hiface_pcm_playback(struct pcm_substream *sub, struct pcm_urb *urb)
{
struct snd_pcm_runtime *alsa_rt = sub->instance->runtime;
struct device *device = &urb->chip->dev->dev;
u8 *source;
unsigned int pcm_buffer_size;
WARN_ON(alsa_rt->format != SNDRV_PCM_FORMAT_S32_LE);
pcm_buffer_size = snd_pcm_lib_buffer_bytes(sub->instance);
if (sub->dma_off + PCM_PACKET_SIZE <= pcm_buffer_size) {
dev_dbg(device, "%s: (1) buffer_size %#x dma_offset %#x\n", __func__,
(unsigned int) pcm_buffer_size,
(unsigned int) sub->dma_off);
source = alsa_rt->dma_area + sub->dma_off;
memcpy_swahw32(urb->buffer, source, PCM_PACKET_SIZE);
} else {
/* wrap around at end of ring buffer */
unsigned int len;
dev_dbg(device, "%s: (2) buffer_size %#x dma_offset %#x\n", __func__,
(unsigned int) pcm_buffer_size,
(unsigned int) sub->dma_off);
len = pcm_buffer_size - sub->dma_off;
source = alsa_rt->dma_area + sub->dma_off;
memcpy_swahw32(urb->buffer, source, len);
source = alsa_rt->dma_area;
memcpy_swahw32(urb->buffer + len, source,
PCM_PACKET_SIZE - len);
}
sub->dma_off += PCM_PACKET_SIZE;
if (sub->dma_off >= pcm_buffer_size)
sub->dma_off -= pcm_buffer_size;
sub->period_off += PCM_PACKET_SIZE;
if (sub->period_off >= alsa_rt->period_size) {
sub->period_off %= alsa_rt->period_size;
return true;
}
return false;
}
static void hiface_pcm_out_urb_handler(struct urb *usb_urb)
{
struct pcm_urb *out_urb = usb_urb->context;
struct pcm_runtime *rt = out_urb->chip->pcm;
struct pcm_substream *sub;
bool do_period_elapsed = false;
unsigned long flags;
int ret;
if (rt->panic || rt->stream_state == STREAM_STOPPING)
return;
if (unlikely(usb_urb->status == -ENOENT || /* unlinked */
usb_urb->status == -ENODEV || /* device removed */
usb_urb->status == -ECONNRESET || /* unlinked */
usb_urb->status == -ESHUTDOWN)) { /* device disabled */
goto out_fail;
}
if (rt->stream_state == STREAM_STARTING) {
rt->stream_wait_cond = true;
wake_up(&rt->stream_wait_queue);
}
/* now send our playback data (if a free out urb was found) */
sub = &rt->playback;
spin_lock_irqsave(&sub->lock, flags);
if (sub->active)
do_period_elapsed = hiface_pcm_playback(sub, out_urb);
else
memset(out_urb->buffer, 0, PCM_PACKET_SIZE);
spin_unlock_irqrestore(&sub->lock, flags);
if (do_period_elapsed)
snd_pcm_period_elapsed(sub->instance);
ret = usb_submit_urb(&out_urb->instance, GFP_ATOMIC);
if (ret < 0)
goto out_fail;
return;
out_fail:
rt->panic = true;
}
static int hiface_pcm_open(struct snd_pcm_substream *alsa_sub)
{
struct pcm_runtime *rt = snd_pcm_substream_chip(alsa_sub);
struct pcm_substream *sub = NULL;
struct snd_pcm_runtime *alsa_rt = alsa_sub->runtime;
int ret;
if (rt->panic)
return -EPIPE;
mutex_lock(&rt->stream_mutex);
alsa_rt->hw = pcm_hw;
if (alsa_sub->stream == SNDRV_PCM_STREAM_PLAYBACK)
sub = &rt->playback;
if (!sub) {
struct device *device = &rt->chip->dev->dev;
mutex_unlock(&rt->stream_mutex);
dev_err(device, "Invalid stream type\n");
return -EINVAL;
}
if (rt->extra_freq) {
alsa_rt->hw.rates |= SNDRV_PCM_RATE_KNOT;
alsa_rt->hw.rate_max = 384000;
/* explicit constraints needed as we added SNDRV_PCM_RATE_KNOT */
ret = snd_pcm_hw_constraint_list(alsa_sub->runtime, 0,
SNDRV_PCM_HW_PARAM_RATE,
&constraints_extra_rates);
if (ret < 0) {
mutex_unlock(&rt->stream_mutex);
return ret;
}
}
sub->instance = alsa_sub;
sub->active = false;
mutex_unlock(&rt->stream_mutex);
return 0;
}
static int hiface_pcm_close(struct snd_pcm_substream *alsa_sub)
{
struct pcm_runtime *rt = snd_pcm_substream_chip(alsa_sub);
struct pcm_substream *sub = hiface_pcm_get_substream(alsa_sub);
unsigned long flags;
if (rt->panic)
return 0;
mutex_lock(&rt->stream_mutex);
if (sub) {
hiface_pcm_stream_stop(rt);
/* deactivate substream */
spin_lock_irqsave(&sub->lock, flags);
sub->instance = NULL;
sub->active = false;
spin_unlock_irqrestore(&sub->lock, flags);
}
mutex_unlock(&rt->stream_mutex);
return 0;
}
static int hiface_pcm_hw_params(struct snd_pcm_substream *alsa_sub,
struct snd_pcm_hw_params *hw_params)
{
return snd_pcm_lib_alloc_vmalloc_buffer(alsa_sub,
params_buffer_bytes(hw_params));
}
static int hiface_pcm_hw_free(struct snd_pcm_substream *alsa_sub)
{
return snd_pcm_lib_free_vmalloc_buffer(alsa_sub);
}
static int hiface_pcm_prepare(struct snd_pcm_substream *alsa_sub)
{
struct pcm_runtime *rt = snd_pcm_substream_chip(alsa_sub);
struct pcm_substream *sub = hiface_pcm_get_substream(alsa_sub);
struct snd_pcm_runtime *alsa_rt = alsa_sub->runtime;
int ret;
if (rt->panic)
return -EPIPE;
if (!sub)
return -ENODEV;
mutex_lock(&rt->stream_mutex);
sub->dma_off = 0;
sub->period_off = 0;
if (rt->stream_state == STREAM_DISABLED) {
ret = hiface_pcm_set_rate(rt, alsa_rt->rate);
if (ret) {
mutex_unlock(&rt->stream_mutex);
return ret;
}
ret = hiface_pcm_stream_start(rt);
if (ret) {
mutex_unlock(&rt->stream_mutex);
return ret;
}
}
mutex_unlock(&rt->stream_mutex);
return 0;
}
static int hiface_pcm_trigger(struct snd_pcm_substream *alsa_sub, int cmd)
{
struct pcm_substream *sub = hiface_pcm_get_substream(alsa_sub);
struct pcm_runtime *rt = snd_pcm_substream_chip(alsa_sub);
if (rt->panic)
return -EPIPE;
if (!sub)
return -ENODEV;
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
spin_lock_irq(&sub->lock);
sub->active = true;
spin_unlock_irq(&sub->lock);
return 0;
case SNDRV_PCM_TRIGGER_STOP:
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
spin_lock_irq(&sub->lock);
sub->active = false;
spin_unlock_irq(&sub->lock);
return 0;
default:
return -EINVAL;
}
}
static snd_pcm_uframes_t hiface_pcm_pointer(struct snd_pcm_substream *alsa_sub)
{
struct pcm_substream *sub = hiface_pcm_get_substream(alsa_sub);
struct pcm_runtime *rt = snd_pcm_substream_chip(alsa_sub);
unsigned long flags;
snd_pcm_uframes_t dma_offset;
if (rt->panic || !sub)
return SNDRV_PCM_POS_XRUN;
spin_lock_irqsave(&sub->lock, flags);
dma_offset = sub->dma_off;
spin_unlock_irqrestore(&sub->lock, flags);
return bytes_to_frames(alsa_sub->runtime, dma_offset);
}
static struct snd_pcm_ops pcm_ops = {
.open = hiface_pcm_open,
.close = hiface_pcm_close,
.ioctl = snd_pcm_lib_ioctl,
.hw_params = hiface_pcm_hw_params,
.hw_free = hiface_pcm_hw_free,
.prepare = hiface_pcm_prepare,
.trigger = hiface_pcm_trigger,
.pointer = hiface_pcm_pointer,
.page = snd_pcm_lib_get_vmalloc_page,
.mmap = snd_pcm_lib_mmap_vmalloc,
};
static int hiface_pcm_init_urb(struct pcm_urb *urb,
struct hiface_chip *chip,
unsigned int ep,
void (*handler)(struct urb *))
{
urb->chip = chip;
usb_init_urb(&urb->instance);
urb->buffer = kzalloc(PCM_PACKET_SIZE, GFP_KERNEL);
if (!urb->buffer)
return -ENOMEM;
usb_fill_bulk_urb(&urb->instance, chip->dev,
usb_sndbulkpipe(chip->dev, ep), (void *)urb->buffer,
PCM_PACKET_SIZE, handler, urb);
init_usb_anchor(&urb->submitted);
return 0;
}
void hiface_pcm_abort(struct hiface_chip *chip)
{
struct pcm_runtime *rt = chip->pcm;
if (rt) {
rt->panic = true;
mutex_lock(&rt->stream_mutex);
hiface_pcm_stream_stop(rt);
mutex_unlock(&rt->stream_mutex);
}
}
static void hiface_pcm_destroy(struct hiface_chip *chip)
{
struct pcm_runtime *rt = chip->pcm;
int i;
for (i = 0; i < PCM_N_URBS; i++)
kfree(rt->out_urbs[i].buffer);
kfree(chip->pcm);
chip->pcm = NULL;
}
static void hiface_pcm_free(struct snd_pcm *pcm)
{
struct pcm_runtime *rt = pcm->private_data;
if (rt)
hiface_pcm_destroy(rt->chip);
}
int hiface_pcm_init(struct hiface_chip *chip, u8 extra_freq)
{
int i;
int ret;
struct snd_pcm *pcm;
struct pcm_runtime *rt;
rt = kzalloc(sizeof(*rt), GFP_KERNEL);
if (!rt)
return -ENOMEM;
rt->chip = chip;
rt->stream_state = STREAM_DISABLED;
if (extra_freq)
rt->extra_freq = 1;
init_waitqueue_head(&rt->stream_wait_queue);
mutex_init(&rt->stream_mutex);
spin_lock_init(&rt->playback.lock);
for (i = 0; i < PCM_N_URBS; i++)
hiface_pcm_init_urb(&rt->out_urbs[i], chip, OUT_EP,
hiface_pcm_out_urb_handler);
ret = snd_pcm_new(chip->card, "USB-SPDIF Audio", 0, 1, 0, &pcm);
if (ret < 0) {
kfree(rt);
dev_err(&chip->dev->dev, "Cannot create pcm instance\n");
return ret;
}
pcm->private_data = rt;
pcm->private_free = hiface_pcm_free;
strlcpy(pcm->name, "USB-SPDIF Audio", sizeof(pcm->name));
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &pcm_ops);
rt->instance = pcm;
chip->pcm = rt;
return 0;
}

24
sound/usb/hiface/pcm.h Normal file
View file

@ -0,0 +1,24 @@
/*
* Linux driver for M2Tech hiFace compatible devices
*
* Copyright 2012-2013 (C) M2TECH S.r.l and Amarula Solutions B.V.
*
* Authors: Michael Trimarchi <michael@amarulasolutions.com>
* Antonio Ospite <ao2@amarulasolutions.com>
*
* The driver is based on the work done in TerraTec DMX 6Fire USB
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*/
#ifndef HIFACE_PCM_H
#define HIFACE_PCM_H
struct hiface_chip;
int hiface_pcm_init(struct hiface_chip *chip, u8 extra_freq);
void hiface_pcm_abort(struct hiface_chip *chip);
#endif /* HIFACE_PCM_H */

2417
sound/usb/midi.c Normal file

File diff suppressed because it is too large Load diff

52
sound/usb/midi.h Normal file
View file

@ -0,0 +1,52 @@
#ifndef __USBMIDI_H
#define __USBMIDI_H
/* maximum number of endpoints per interface */
#define MIDI_MAX_ENDPOINTS 2
/* data for QUIRK_MIDI_FIXED_ENDPOINT */
struct snd_usb_midi_endpoint_info {
int8_t out_ep; /* ep number, 0 autodetect */
uint8_t out_interval; /* interval for interrupt endpoints */
int8_t in_ep;
uint8_t in_interval;
uint16_t out_cables; /* bitmask */
uint16_t in_cables; /* bitmask */
};
/* for QUIRK_MIDI_YAMAHA, data is NULL */
/* for QUIRK_MIDI_MIDIMAN, data points to a snd_usb_midi_endpoint_info
* structure (out_cables and in_cables only) */
/* for QUIRK_COMPOSITE, data points to an array of snd_usb_audio_quirk
* structures, terminated with .ifnum = -1 */
/* for QUIRK_AUDIO_FIXED_ENDPOINT, data points to an audioformat structure */
/* for QUIRK_AUDIO/MIDI_STANDARD_INTERFACE, data is NULL */
/* for QUIRK_AUDIO_EDIROL_UA700_UA25/UA1000, data is NULL */
/* for QUIRK_IGNORE_INTERFACE, data is NULL */
/* for QUIRK_MIDI_NOVATION and _RAW, data is NULL */
/* for QUIRK_MIDI_EMAGIC, data points to a snd_usb_midi_endpoint_info
* structure (out_cables and in_cables only) */
/* for QUIRK_MIDI_CME, data is NULL */
/* for QUIRK_MIDI_AKAI, data is NULL */
int snd_usbmidi_create(struct snd_card *card,
struct usb_interface *iface,
struct list_head *midi_list,
const struct snd_usb_audio_quirk *quirk);
void snd_usbmidi_input_stop(struct list_head *p);
void snd_usbmidi_input_start(struct list_head *p);
void snd_usbmidi_disconnect(struct list_head *p);
void snd_usbmidi_suspend(struct list_head *p);
void snd_usbmidi_resume(struct list_head *p);
#endif /* __USBMIDI_H */

2
sound/usb/misc/Makefile Normal file
View file

@ -0,0 +1,2 @@
snd-ua101-objs := ua101.o
obj-$(CONFIG_SND_USB_UA101) += snd-ua101.o

1398
sound/usb/misc/ua101.c Normal file

File diff suppressed because it is too large Load diff

2575
sound/usb/mixer.c Normal file

File diff suppressed because it is too large Load diff

78
sound/usb/mixer.h Normal file
View file

@ -0,0 +1,78 @@
#ifndef __USBMIXER_H
#define __USBMIXER_H
struct usb_mixer_interface {
struct snd_usb_audio *chip;
struct usb_host_interface *hostif;
struct list_head list;
unsigned int ignore_ctl_error;
struct urb *urb;
/* array[MAX_ID_ELEMS], indexed by unit id */
struct usb_mixer_elem_info **id_elems;
/* the usb audio specification version this interface complies to */
int protocol;
/* Sound Blaster remote control stuff */
const struct rc_config *rc_cfg;
u32 rc_code;
wait_queue_head_t rc_waitq;
struct urb *rc_urb;
struct usb_ctrlrequest *rc_setup_packet;
u8 rc_buffer[6];
u8 audigy2nx_leds[3];
u8 xonar_u1_status;
};
#define MAX_CHANNELS 16 /* max logical channels */
enum {
USB_MIXER_BOOLEAN,
USB_MIXER_INV_BOOLEAN,
USB_MIXER_S8,
USB_MIXER_U8,
USB_MIXER_S16,
USB_MIXER_U16,
};
struct usb_mixer_elem_info {
struct usb_mixer_interface *mixer;
struct usb_mixer_elem_info *next_id_elem; /* list of controls with same id */
struct snd_ctl_elem_id *elem_id;
unsigned int id;
unsigned int control; /* CS or ICN (high byte) */
unsigned int cmask; /* channel mask bitmap: 0 = master */
unsigned int idx_off; /* Control index offset */
unsigned int ch_readonly;
unsigned int master_readonly;
int channels;
int val_type;
int min, max, res;
int dBmin, dBmax;
int cached;
int cache_val[MAX_CHANNELS];
u8 initialized;
};
int snd_usb_create_mixer(struct snd_usb_audio *chip, int ctrlif,
int ignore_error);
void snd_usb_mixer_disconnect(struct list_head *p);
void snd_usb_mixer_notify_id(struct usb_mixer_interface *mixer, int unitid);
int snd_usb_mixer_set_ctl_value(struct usb_mixer_elem_info *cval,
int request, int validx, int value_set);
int snd_usb_mixer_add_control(struct usb_mixer_interface *mixer,
struct snd_kcontrol *kctl);
int snd_usb_mixer_vol_tlv(struct snd_kcontrol *kcontrol, int op_flag,
unsigned int size, unsigned int __user *_tlv);
#ifdef CONFIG_PM
int snd_usb_mixer_suspend(struct usb_mixer_interface *mixer);
int snd_usb_mixer_resume(struct usb_mixer_interface *mixer, bool reset_resume);
#endif
#endif /* __USBMIXER_H */

442
sound/usb/mixer_maps.c Normal file
View file

@ -0,0 +1,442 @@
/*
* Additional mixer mapping
*
* Copyright (c) 2002 by Takashi Iwai <tiwai@suse.de>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
struct usbmix_dB_map {
u32 min;
u32 max;
};
struct usbmix_name_map {
int id;
const char *name;
int control;
struct usbmix_dB_map *dB;
};
struct usbmix_selector_map {
int id;
int count;
const char **names;
};
struct usbmix_ctl_map {
u32 id;
const struct usbmix_name_map *map;
const struct usbmix_selector_map *selector_map;
int ignore_ctl_error;
};
/*
* USB control mappers for SB Exitigy
*/
/*
* Topology of SB Extigy (see on the wide screen :)
USB_IN[1] --->FU[2]------------------------------+->MU[16]-->PU[17]-+->FU[18]--+->EU[27]--+->EU[21]-->FU[22]--+->FU[23] > Dig_OUT[24]
^ | | | |
USB_IN[3] -+->SU[5]-->FU[6]--+->MU[14] ->PU[15]->+ | | | +->FU[25] > Dig_OUT[26]
^ ^ | | | |
Dig_IN[4] -+ | | | | +->FU[28]---------------------> Spk_OUT[19]
| | | |
Lin-IN[7] -+-->FU[8]---------+ | | +----------------------------------------> Hph_OUT[20]
| | |
Mic-IN[9] --+->FU[10]----------------------------+ |
|| |
|| +----------------------------------------------------+
VV V
++--+->SU[11]-->FU[12] --------------------------------------------------------------------------------------> USB_OUT[13]
*/
static struct usbmix_name_map extigy_map[] = {
/* 1: IT pcm */
{ 2, "PCM Playback" }, /* FU */
/* 3: IT pcm */
/* 4: IT digital in */
{ 5, NULL }, /* DISABLED: this seems to be bogus on some firmware */
{ 6, "Digital In" }, /* FU */
/* 7: IT line */
{ 8, "Line Playback" }, /* FU */
/* 9: IT mic */
{ 10, "Mic Playback" }, /* FU */
{ 11, "Capture Source" }, /* SU */
{ 12, "Capture" }, /* FU */
/* 13: OT pcm capture */
/* 14: MU (w/o controls) */
/* 15: PU (3D enh) */
/* 16: MU (w/o controls) */
{ 17, NULL, 1 }, /* DISABLED: PU-switch (any effect?) */
{ 17, "Channel Routing", 2 }, /* PU: mode select */
{ 18, "Tone Control - Bass", UAC_FU_BASS }, /* FU */
{ 18, "Tone Control - Treble", UAC_FU_TREBLE }, /* FU */
{ 18, "Master Playback" }, /* FU; others */
/* 19: OT speaker */
/* 20: OT headphone */
{ 21, NULL }, /* DISABLED: EU (for what?) */
{ 22, "Digital Out Playback" }, /* FU */
{ 23, "Digital Out1 Playback" }, /* FU */ /* FIXME: corresponds to 24 */
/* 24: OT digital out */
{ 25, "IEC958 Optical Playback" }, /* FU */
{ 26, "IEC958 Optical Playback" }, /* OT */
{ 27, NULL }, /* DISABLED: EU (for what?) */
/* 28: FU speaker (mute) */
{ 29, NULL }, /* Digital Input Playback Source? */
{ 0 } /* terminator */
};
/* Sound Blaster MP3+ controls mapping
* The default mixer channels have totally misleading names,
* e.g. no Master and fake PCM volume
* Pavel Mihaylov <bin@bash.info>
*/
static struct usbmix_dB_map mp3plus_dB_1 = {-4781, 0}; /* just guess */
static struct usbmix_dB_map mp3plus_dB_2 = {-1781, 618}; /* just guess */
static struct usbmix_name_map mp3plus_map[] = {
/* 1: IT pcm */
/* 2: IT mic */
/* 3: IT line */
/* 4: IT digital in */
/* 5: OT digital out */
/* 6: OT speaker */
/* 7: OT pcm capture */
{ 8, "Capture Source" }, /* FU, default PCM Capture Source */
/* (Mic, Input 1 = Line input, Input 2 = Optical input) */
{ 9, "Master Playback" }, /* FU, default Speaker 1 */
/* { 10, "Mic Capture", 1 }, */ /* FU, Mic Capture */
{ 10, /* "Mic Capture", */ NULL, 2, .dB = &mp3plus_dB_2 },
/* FU, Mic Capture */
{ 10, "Mic Boost", 7 }, /* FU, default Auto Gain Input */
{ 11, "Line Capture", .dB = &mp3plus_dB_2 },
/* FU, default PCM Capture */
{ 12, "Digital In Playback" }, /* FU, default PCM 1 */
{ 13, /* "Mic Playback", */ .dB = &mp3plus_dB_1 },
/* FU, default Mic Playback */
{ 14, "Line Playback", .dB = &mp3plus_dB_1 }, /* FU, default Speaker */
/* 15: MU */
{ 0 } /* terminator */
};
/* Topology of SB Audigy 2 NX
+----------------------------->EU[27]--+
| v
| +----------------------------------->SU[29]---->FU[22]-->Dig_OUT[24]
| | ^
USB_IN[1]-+------------+ +->EU[17]->+->FU[11]-+
| v | v |
Dig_IN[4]---+->FU[6]-->MU[16]->FU[18]-+->EU[21]->SU[31]----->FU[30]->Hph_OUT[20]
| ^ | |
Lin_IN[7]-+--->FU[8]---+ +->EU[23]->FU[28]------------->Spk_OUT[19]
| | v
+--->FU[12]------------------------------------->SU[14]--->USB_OUT[15]
| ^
+->FU[13]--------------------------------------+
*/
static struct usbmix_name_map audigy2nx_map[] = {
/* 1: IT pcm playback */
/* 4: IT digital in */
{ 6, "Digital In Playback" }, /* FU */
/* 7: IT line in */
{ 8, "Line Playback" }, /* FU */
{ 11, "What-U-Hear Capture" }, /* FU */
{ 12, "Line Capture" }, /* FU */
{ 13, "Digital In Capture" }, /* FU */
{ 14, "Capture Source" }, /* SU */
/* 15: OT pcm capture */
/* 16: MU w/o controls */
{ 17, NULL }, /* DISABLED: EU (for what?) */
{ 18, "Master Playback" }, /* FU */
/* 19: OT speaker */
/* 20: OT headphone */
{ 21, NULL }, /* DISABLED: EU (for what?) */
{ 22, "Digital Out Playback" }, /* FU */
{ 23, NULL }, /* DISABLED: EU (for what?) */
/* 24: OT digital out */
{ 27, NULL }, /* DISABLED: EU (for what?) */
{ 28, "Speaker Playback" }, /* FU */
{ 29, "Digital Out Source" }, /* SU */
{ 30, "Headphone Playback" }, /* FU */
{ 31, "Headphone Source" }, /* SU */
{ 0 } /* terminator */
};
static struct usbmix_selector_map c400_selectors[] = {
{
.id = 0x80,
.count = 2,
.names = (const char*[]) {"Internal", "SPDIF"}
},
{ 0 } /* terminator */
};
static struct usbmix_selector_map audigy2nx_selectors[] = {
{
.id = 14, /* Capture Source */
.count = 3,
.names = (const char*[]) {"Line", "Digital In", "What-U-Hear"}
},
{
.id = 29, /* Digital Out Source */
.count = 3,
.names = (const char*[]) {"Front", "PCM", "Digital In"}
},
{
.id = 31, /* Headphone Source */
.count = 2,
.names = (const char*[]) {"Front", "Side"}
},
{ 0 } /* terminator */
};
/* Creative SoundBlaster Live! 24-bit External */
static struct usbmix_name_map live24ext_map[] = {
/* 2: PCM Playback Volume */
{ 5, "Mic Capture" }, /* FU, default PCM Capture Volume */
{ 0 } /* terminator */
};
/* LineX FM Transmitter entry - needed to bypass controls bug */
static struct usbmix_name_map linex_map[] = {
/* 1: IT pcm */
/* 2: OT Speaker */
{ 3, "Master" }, /* FU: master volume - left / right / mute */
{ 0 } /* terminator */
};
static struct usbmix_name_map maya44_map[] = {
/* 1: IT line */
{ 2, "Line Playback" }, /* FU */
/* 3: IT line */
{ 4, "Line Playback" }, /* FU */
/* 5: IT pcm playback */
/* 6: MU */
{ 7, "Master Playback" }, /* FU */
/* 8: OT speaker */
/* 9: IT line */
{ 10, "Line Capture" }, /* FU */
/* 11: MU */
/* 12: OT pcm capture */
{ }
};
/* Section "justlink_map" below added by James Courtier-Dutton <James@superbug.demon.co.uk>
* sourced from Maplin Electronics (http://www.maplin.co.uk), part number A56AK
* Part has 2 connectors that act as a single output. (TOSLINK Optical for digital out, and 3.5mm Jack for Analogue out.)
* The USB Mixer publishes a Microphone and extra Volume controls for it, but none exist on the device,
* so this map removes all unwanted sliders from alsamixer
*/
static struct usbmix_name_map justlink_map[] = {
/* 1: IT pcm playback */
/* 2: Not present */
{ 3, NULL}, /* IT mic (No mic input on device) */
/* 4: Not present */
/* 5: OT speacker */
/* 6: OT pcm capture */
{ 7, "Master Playback" }, /* Mute/volume for speaker */
{ 8, NULL }, /* Capture Switch (No capture inputs on device) */
{ 9, NULL }, /* Capture Mute/volume (No capture inputs on device */
/* 0xa: Not present */
/* 0xb: MU (w/o controls) */
{ 0xc, NULL }, /* Mic feedback Mute/volume (No capture inputs on device) */
{ 0 } /* terminator */
};
/* TerraTec Aureon 5.1 MkII USB */
static struct usbmix_name_map aureon_51_2_map[] = {
/* 1: IT USB */
/* 2: IT Mic */
/* 3: IT Line */
/* 4: IT SPDIF */
/* 5: OT SPDIF */
/* 6: OT Speaker */
/* 7: OT USB */
{ 8, "Capture Source" }, /* SU */
{ 9, "Master Playback" }, /* FU */
{ 10, "Mic Capture" }, /* FU */
{ 11, "Line Capture" }, /* FU */
{ 12, "IEC958 In Capture" }, /* FU */
{ 13, "Mic Playback" }, /* FU */
{ 14, "Line Playback" }, /* FU */
/* 15: MU */
{} /* terminator */
};
static struct usbmix_name_map scratch_live_map[] = {
/* 1: IT Line 1 (USB streaming) */
/* 2: OT Line 1 (Speaker) */
/* 3: IT Line 1 (Line connector) */
{ 4, "Line 1 In" }, /* FU */
/* 5: OT Line 1 (USB streaming) */
/* 6: IT Line 2 (USB streaming) */
/* 7: OT Line 2 (Speaker) */
/* 8: IT Line 2 (Line connector) */
{ 9, "Line 2 In" }, /* FU */
/* 10: OT Line 2 (USB streaming) */
/* 11: IT Mic (Line connector) */
/* 12: OT Mic (USB streaming) */
{ 0 } /* terminator */
};
static struct usbmix_name_map ebox44_map[] = {
{ 4, NULL }, /* FU */
{ 6, NULL }, /* MU */
{ 7, NULL }, /* FU */
{ 10, NULL }, /* FU */
{ 11, NULL }, /* MU */
{ 0 }
};
/* "Gamesurround Muse Pocket LT" looks same like "Sound Blaster MP3+"
* most importand difference is SU[8], it should be set to "Capture Source"
* to make alsamixer and PA working properly.
* FIXME: or mp3plus_map should use "Capture Source" too,
* so this maps can be merget
*/
static struct usbmix_name_map hercules_usb51_map[] = {
{ 8, "Capture Source" }, /* SU, default "PCM Capture Source" */
{ 9, "Master Playback" }, /* FU, default "Speaker Playback" */
{ 10, "Mic Boost", 7 }, /* FU, default "Auto Gain Input" */
{ 11, "Line Capture" }, /* FU, default "PCM Capture" */
{ 13, "Mic Bypass Playback" }, /* FU, default "Mic Playback" */
{ 14, "Line Bypass Playback" }, /* FU, default "Line Playback" */
{ 0 } /* terminator */
};
/* Plantronics Gamecom 780 has a broken volume control, better to disable it */
static struct usbmix_name_map gamecom780_map[] = {
{ 9, NULL }, /* FU, speaker out */
{}
};
/* some (all?) SCMS USB3318 devices are affected by a firmware lock up
* when anything attempts to access FU 10 (control)
*/
static const struct usbmix_name_map scms_usb3318_map[] = {
{ 10, NULL },
{ 0 }
};
/*
* Control map entries
*/
static struct usbmix_ctl_map usbmix_ctl_maps[] = {
{
.id = USB_ID(0x041e, 0x3000),
.map = extigy_map,
.ignore_ctl_error = 1,
},
{
.id = USB_ID(0x041e, 0x3010),
.map = mp3plus_map,
},
{
.id = USB_ID(0x041e, 0x3020),
.map = audigy2nx_map,
.selector_map = audigy2nx_selectors,
},
{
.id = USB_ID(0x041e, 0x3040),
.map = live24ext_map,
},
{
.id = USB_ID(0x041e, 0x3048),
.map = audigy2nx_map,
.selector_map = audigy2nx_selectors,
},
{ /* Logitech, Inc. QuickCam Pro for Notebooks */
.id = USB_ID(0x046d, 0x0991),
.ignore_ctl_error = 1,
},
{ /* Logitech, Inc. QuickCam E 3500 */
.id = USB_ID(0x046d, 0x09a4),
.ignore_ctl_error = 1,
},
{ /* Plantronics GameCom 780 */
.id = USB_ID(0x047f, 0xc010),
.map = gamecom780_map,
},
{
/* Hercules DJ Console (Windows Edition) */
.id = USB_ID(0x06f8, 0xb000),
.ignore_ctl_error = 1,
},
{
/* Hercules DJ Console (Macintosh Edition) */
.id = USB_ID(0x06f8, 0xd002),
.ignore_ctl_error = 1,
},
{
/* Hercules Gamesurround Muse Pocket LT
* (USB 5.1 Channel Audio Adapter)
*/
.id = USB_ID(0x06f8, 0xc000),
.map = hercules_usb51_map,
},
{
.id = USB_ID(0x0763, 0x2030),
.selector_map = c400_selectors,
},
{
.id = USB_ID(0x0763, 0x2031),
.selector_map = c400_selectors,
},
{
.id = USB_ID(0x08bb, 0x2702),
.map = linex_map,
.ignore_ctl_error = 1,
},
{
.id = USB_ID(0x0a92, 0x0091),
.map = maya44_map,
},
{
.id = USB_ID(0x0c45, 0x1158),
.map = justlink_map,
},
{
.id = USB_ID(0x0ccd, 0x0028),
.map = aureon_51_2_map,
},
{
.id = USB_ID(0x13e5, 0x0001),
.map = scratch_live_map,
.ignore_ctl_error = 1,
},
{
.id = USB_ID(0x200c, 0x1018),
.map = ebox44_map,
},
{
/* KEF X300A */
.id = USB_ID(0x27ac, 0x1000),
.map = scms_usb3318_map,
},
{
/* Arcam rPAC */
.id = USB_ID(0x25c4, 0x0003),
.map = scms_usb3318_map,
},
{ 0 } /* terminator */
};

1713
sound/usb/mixer_quirks.c Normal file

File diff suppressed because it is too large Load diff

13
sound/usb/mixer_quirks.h Normal file
View file

@ -0,0 +1,13 @@
#ifndef SND_USB_MIXER_QUIRKS_H
#define SND_USB_MIXER_QUIRKS_H
int snd_usb_mixer_apply_create_quirk(struct usb_mixer_interface *mixer);
void snd_emuusb_set_samplerate(struct snd_usb_audio *chip,
unsigned char samplerate_id);
void snd_usb_mixer_rc_memory_change(struct usb_mixer_interface *mixer,
int unitid);
#endif /* SND_USB_MIXER_QUIRKS_H */

1631
sound/usb/pcm.c Normal file

File diff suppressed because it is too large Load diff

14
sound/usb/pcm.h Normal file
View file

@ -0,0 +1,14 @@
#ifndef __USBAUDIO_PCM_H
#define __USBAUDIO_PCM_H
snd_pcm_uframes_t snd_usb_pcm_delay(struct snd_usb_substream *subs,
unsigned int rate);
void snd_usb_set_pcm_ops(struct snd_pcm *pcm, int stream);
int snd_usb_init_pitch(struct snd_usb_audio *chip, int iface,
struct usb_host_interface *alts,
struct audioformat *fmt);
#endif /* __USBAUDIO_PCM_H */

17
sound/usb/power.h Normal file
View file

@ -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 */

178
sound/usb/proc.c Normal file
View file

@ -0,0 +1,178 @@
/*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#include <linux/init.h>
#include <linux/usb.h>
#include <sound/core.h>
#include <sound/info.h>
#include <sound/pcm.h>
#include "usbaudio.h"
#include "helper.h"
#include "card.h"
#include "endpoint.h"
#include "proc.h"
/* convert our full speed USB rate into sampling rate in Hz */
static inline unsigned get_full_speed_hz(unsigned int usb_rate)
{
return (usb_rate * 125 + (1 << 12)) >> 13;
}
/* convert our high speed USB rate into sampling rate in Hz */
static inline unsigned get_high_speed_hz(unsigned int usb_rate)
{
return (usb_rate * 125 + (1 << 9)) >> 10;
}
/*
* common proc files to show the usb device info
*/
static void proc_audio_usbbus_read(struct snd_info_entry *entry, struct snd_info_buffer *buffer)
{
struct snd_usb_audio *chip = entry->private_data;
if (!chip->shutdown)
snd_iprintf(buffer, "%03d/%03d\n", chip->dev->bus->busnum, chip->dev->devnum);
}
static void proc_audio_usbid_read(struct snd_info_entry *entry, struct snd_info_buffer *buffer)
{
struct snd_usb_audio *chip = entry->private_data;
if (!chip->shutdown)
snd_iprintf(buffer, "%04x:%04x\n",
USB_ID_VENDOR(chip->usb_id),
USB_ID_PRODUCT(chip->usb_id));
}
void snd_usb_audio_create_proc(struct snd_usb_audio *chip)
{
struct snd_info_entry *entry;
if (!snd_card_proc_new(chip->card, "usbbus", &entry))
snd_info_set_text_ops(entry, chip, proc_audio_usbbus_read);
if (!snd_card_proc_new(chip->card, "usbid", &entry))
snd_info_set_text_ops(entry, chip, proc_audio_usbid_read);
}
/*
* proc interface for list the supported pcm formats
*/
static void proc_dump_substream_formats(struct snd_usb_substream *subs, struct snd_info_buffer *buffer)
{
struct audioformat *fp;
static char *sync_types[4] = {
"NONE", "ASYNC", "ADAPTIVE", "SYNC"
};
list_for_each_entry(fp, &subs->fmt_list, list) {
snd_pcm_format_t fmt;
snd_iprintf(buffer, " Interface %d\n", fp->iface);
snd_iprintf(buffer, " Altset %d\n", fp->altsetting);
snd_iprintf(buffer, " Format:");
for (fmt = 0; fmt <= SNDRV_PCM_FORMAT_LAST; ++fmt)
if (fp->formats & pcm_format_to_bits(fmt))
snd_iprintf(buffer, " %s",
snd_pcm_format_name(fmt));
snd_iprintf(buffer, "\n");
snd_iprintf(buffer, " Channels: %d\n", fp->channels);
snd_iprintf(buffer, " Endpoint: %d %s (%s)\n",
fp->endpoint & USB_ENDPOINT_NUMBER_MASK,
fp->endpoint & USB_DIR_IN ? "IN" : "OUT",
sync_types[(fp->ep_attr & USB_ENDPOINT_SYNCTYPE) >> 2]);
if (fp->rates & SNDRV_PCM_RATE_CONTINUOUS) {
snd_iprintf(buffer, " Rates: %d - %d (continuous)\n",
fp->rate_min, fp->rate_max);
} else {
unsigned int i;
snd_iprintf(buffer, " Rates: ");
for (i = 0; i < fp->nr_rates; i++) {
if (i > 0)
snd_iprintf(buffer, ", ");
snd_iprintf(buffer, "%d", fp->rate_table[i]);
}
snd_iprintf(buffer, "\n");
}
if (subs->speed != USB_SPEED_FULL)
snd_iprintf(buffer, " Data packet interval: %d us\n",
125 * (1 << fp->datainterval));
// snd_iprintf(buffer, " Max Packet Size = %d\n", fp->maxpacksize);
// snd_iprintf(buffer, " EP Attribute = %#x\n", fp->attributes);
}
}
static void proc_dump_ep_status(struct snd_usb_substream *subs,
struct snd_usb_endpoint *data_ep,
struct snd_usb_endpoint *sync_ep,
struct snd_info_buffer *buffer)
{
if (!data_ep)
return;
snd_iprintf(buffer, " Packet Size = %d\n", data_ep->curpacksize);
snd_iprintf(buffer, " Momentary freq = %u Hz (%#x.%04x)\n",
subs->speed == USB_SPEED_FULL
? get_full_speed_hz(data_ep->freqm)
: get_high_speed_hz(data_ep->freqm),
data_ep->freqm >> 16, data_ep->freqm & 0xffff);
if (sync_ep && data_ep->freqshift != INT_MIN) {
int res = 16 - data_ep->freqshift;
snd_iprintf(buffer, " Feedback Format = %d.%d\n",
(sync_ep->syncmaxsize > 3 ? 32 : 24) - res, res);
}
}
static void proc_dump_substream_status(struct snd_usb_substream *subs, struct snd_info_buffer *buffer)
{
if (subs->running) {
snd_iprintf(buffer, " Status: Running\n");
snd_iprintf(buffer, " Interface = %d\n", subs->interface);
snd_iprintf(buffer, " Altset = %d\n", subs->altset_idx);
proc_dump_ep_status(subs, subs->data_endpoint, subs->sync_endpoint, buffer);
} else {
snd_iprintf(buffer, " Status: Stop\n");
}
}
static void proc_pcm_format_read(struct snd_info_entry *entry, struct snd_info_buffer *buffer)
{
struct snd_usb_stream *stream = entry->private_data;
snd_iprintf(buffer, "%s : %s\n", stream->chip->card->longname, stream->pcm->name);
if (stream->substream[SNDRV_PCM_STREAM_PLAYBACK].num_formats) {
snd_iprintf(buffer, "\nPlayback:\n");
proc_dump_substream_status(&stream->substream[SNDRV_PCM_STREAM_PLAYBACK], buffer);
proc_dump_substream_formats(&stream->substream[SNDRV_PCM_STREAM_PLAYBACK], buffer);
}
if (stream->substream[SNDRV_PCM_STREAM_CAPTURE].num_formats) {
snd_iprintf(buffer, "\nCapture:\n");
proc_dump_substream_status(&stream->substream[SNDRV_PCM_STREAM_CAPTURE], buffer);
proc_dump_substream_formats(&stream->substream[SNDRV_PCM_STREAM_CAPTURE], buffer);
}
}
void snd_usb_proc_pcm_format_add(struct snd_usb_stream *stream)
{
struct snd_info_entry *entry;
char name[32];
struct snd_card *card = stream->chip->card;
sprintf(name, "stream%d", stream->pcm_index);
if (!snd_card_proc_new(card, name, &entry))
snd_info_set_text_ops(entry, stream, proc_pcm_format_read);
}

8
sound/usb/proc.h Normal file
View file

@ -0,0 +1,8 @@
#ifndef __USBAUDIO_PROC_H
#define __USBAUDIO_PROC_H
void snd_usb_audio_create_proc(struct snd_usb_audio *chip);
void snd_usb_proc_pcm_format_add(struct snd_usb_stream *stream);
#endif /* __USBAUDIO_PROC_H */

3173
sound/usb/quirks-table.h Normal file

File diff suppressed because it is too large Load diff

1214
sound/usb/quirks.c Normal file

File diff suppressed because it is too large Load diff

38
sound/usb/quirks.h Normal file
View file

@ -0,0 +1,38 @@
#ifndef __USBAUDIO_QUIRKS_H
#define __USBAUDIO_QUIRKS_H
struct audioformat;
struct snd_usb_endpoint;
struct snd_usb_substream;
int snd_usb_create_quirk(struct snd_usb_audio *chip,
struct usb_interface *iface,
struct usb_driver *driver,
const struct snd_usb_audio_quirk *quirk);
int snd_usb_apply_interface_quirk(struct snd_usb_audio *chip,
int iface,
int altno);
int snd_usb_apply_boot_quirk(struct usb_device *dev,
struct usb_interface *intf,
const struct snd_usb_audio_quirk *quirk);
void snd_usb_set_format_quirk(struct snd_usb_substream *subs,
struct audioformat *fmt);
int snd_usb_is_big_endian_format(struct snd_usb_audio *chip,
struct audioformat *fp);
void snd_usb_endpoint_start_quirk(struct snd_usb_endpoint *ep);
void snd_usb_set_interface_quirk(struct usb_device *dev);
void snd_usb_ctl_msg_quirk(struct usb_device *dev, unsigned int pipe,
__u8 request, __u8 requesttype, __u16 value,
__u16 index, void *data, __u16 size);
u64 snd_usb_interface_dsd_format_quirks(struct snd_usb_audio *chip,
struct audioformat *fp,
unsigned int sample_bytes);
#endif /* __USBAUDIO_QUIRKS_H */

731
sound/usb/stream.c Normal file
View file

@ -0,0 +1,731 @@
/*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/usb.h>
#include <linux/usb/audio.h>
#include <linux/usb/audio-v2.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/control.h>
#include <sound/tlv.h>
#include "usbaudio.h"
#include "card.h"
#include "proc.h"
#include "quirks.h"
#include "endpoint.h"
#include "pcm.h"
#include "helper.h"
#include "format.h"
#include "clock.h"
#include "stream.h"
/*
* free a substream
*/
static void free_substream(struct snd_usb_substream *subs)
{
struct audioformat *fp, *n;
if (!subs->num_formats)
return; /* not initialized */
list_for_each_entry_safe(fp, n, &subs->fmt_list, list) {
kfree(fp->rate_table);
kfree(fp->chmap);
kfree(fp);
}
kfree(subs->rate_list.list);
}
/*
* free a usb stream instance
*/
static void snd_usb_audio_stream_free(struct snd_usb_stream *stream)
{
free_substream(&stream->substream[0]);
free_substream(&stream->substream[1]);
list_del(&stream->list);
kfree(stream);
}
static void snd_usb_audio_pcm_free(struct snd_pcm *pcm)
{
struct snd_usb_stream *stream = pcm->private_data;
if (stream) {
stream->pcm = NULL;
snd_usb_audio_stream_free(stream);
}
}
/*
* initialize the substream instance.
*/
static void snd_usb_init_substream(struct snd_usb_stream *as,
int stream,
struct audioformat *fp)
{
struct snd_usb_substream *subs = &as->substream[stream];
INIT_LIST_HEAD(&subs->fmt_list);
spin_lock_init(&subs->lock);
subs->stream = as;
subs->direction = stream;
subs->dev = as->chip->dev;
subs->txfr_quirk = as->chip->txfr_quirk;
subs->speed = snd_usb_get_speed(subs->dev);
subs->pkt_offset_adj = 0;
snd_usb_set_pcm_ops(as->pcm, stream);
list_add_tail(&fp->list, &subs->fmt_list);
subs->formats |= fp->formats;
subs->num_formats++;
subs->fmt_type = fp->fmt_type;
subs->ep_num = fp->endpoint;
if (fp->channels > subs->channels_max)
subs->channels_max = fp->channels;
}
/* kctl callbacks for usb-audio channel maps */
static int usb_chmap_ctl_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{
struct snd_pcm_chmap *info = snd_kcontrol_chip(kcontrol);
struct snd_usb_substream *subs = info->private_data;
uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
uinfo->count = subs->channels_max;
uinfo->value.integer.min = 0;
uinfo->value.integer.max = SNDRV_CHMAP_LAST;
return 0;
}
/* check whether a duplicated entry exists in the audiofmt list */
static bool have_dup_chmap(struct snd_usb_substream *subs,
struct audioformat *fp)
{
struct list_head *p;
for (p = fp->list.prev; p != &subs->fmt_list; p = p->prev) {
struct audioformat *prev;
prev = list_entry(p, struct audioformat, list);
if (prev->chmap &&
!memcmp(prev->chmap, fp->chmap, sizeof(*fp->chmap)))
return true;
}
return false;
}
static int usb_chmap_ctl_tlv(struct snd_kcontrol *kcontrol, int op_flag,
unsigned int size, unsigned int __user *tlv)
{
struct snd_pcm_chmap *info = snd_kcontrol_chip(kcontrol);
struct snd_usb_substream *subs = info->private_data;
struct audioformat *fp;
unsigned int __user *dst;
int count = 0;
if (size < 8)
return -ENOMEM;
if (put_user(SNDRV_CTL_TLVT_CONTAINER, tlv))
return -EFAULT;
size -= 8;
dst = tlv + 2;
list_for_each_entry(fp, &subs->fmt_list, list) {
int i, ch_bytes;
if (!fp->chmap)
continue;
if (have_dup_chmap(subs, fp))
continue;
/* copy the entry */
ch_bytes = fp->chmap->channels * 4;
if (size < 8 + ch_bytes)
return -ENOMEM;
if (put_user(SNDRV_CTL_TLVT_CHMAP_FIXED, dst) ||
put_user(ch_bytes, dst + 1))
return -EFAULT;
dst += 2;
for (i = 0; i < fp->chmap->channels; i++, dst++) {
if (put_user(fp->chmap->map[i], dst))
return -EFAULT;
}
count += 8 + ch_bytes;
size -= 8 + ch_bytes;
}
if (put_user(count, tlv + 1))
return -EFAULT;
return 0;
}
static int usb_chmap_ctl_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_pcm_chmap *info = snd_kcontrol_chip(kcontrol);
struct snd_usb_substream *subs = info->private_data;
struct snd_pcm_chmap_elem *chmap = NULL;
int i;
memset(ucontrol->value.integer.value, 0,
sizeof(ucontrol->value.integer.value));
if (subs->cur_audiofmt)
chmap = subs->cur_audiofmt->chmap;
if (chmap) {
for (i = 0; i < chmap->channels; i++)
ucontrol->value.integer.value[i] = chmap->map[i];
}
return 0;
}
/* create a chmap kctl assigned to the given USB substream */
static int add_chmap(struct snd_pcm *pcm, int stream,
struct snd_usb_substream *subs)
{
struct audioformat *fp;
struct snd_pcm_chmap *chmap;
struct snd_kcontrol *kctl;
int err;
list_for_each_entry(fp, &subs->fmt_list, list)
if (fp->chmap)
goto ok;
/* no chmap is found */
return 0;
ok:
err = snd_pcm_add_chmap_ctls(pcm, stream, NULL, 0, 0, &chmap);
if (err < 0)
return err;
/* override handlers */
chmap->private_data = subs;
kctl = chmap->kctl;
kctl->info = usb_chmap_ctl_info;
kctl->get = usb_chmap_ctl_get;
kctl->tlv.c = usb_chmap_ctl_tlv;
return 0;
}
/* convert from USB ChannelConfig bits to ALSA chmap element */
static struct snd_pcm_chmap_elem *convert_chmap(int channels, unsigned int bits,
int protocol)
{
static unsigned int uac1_maps[] = {
SNDRV_CHMAP_FL, /* left front */
SNDRV_CHMAP_FR, /* right front */
SNDRV_CHMAP_FC, /* center front */
SNDRV_CHMAP_LFE, /* LFE */
SNDRV_CHMAP_SL, /* left surround */
SNDRV_CHMAP_SR, /* right surround */
SNDRV_CHMAP_FLC, /* left of center */
SNDRV_CHMAP_FRC, /* right of center */
SNDRV_CHMAP_RC, /* surround */
SNDRV_CHMAP_SL, /* side left */
SNDRV_CHMAP_SR, /* side right */
SNDRV_CHMAP_TC, /* top */
0 /* terminator */
};
static unsigned int uac2_maps[] = {
SNDRV_CHMAP_FL, /* front left */
SNDRV_CHMAP_FR, /* front right */
SNDRV_CHMAP_FC, /* front center */
SNDRV_CHMAP_LFE, /* LFE */
SNDRV_CHMAP_RL, /* back left */
SNDRV_CHMAP_RR, /* back right */
SNDRV_CHMAP_FLC, /* front left of center */
SNDRV_CHMAP_FRC, /* front right of center */
SNDRV_CHMAP_RC, /* back center */
SNDRV_CHMAP_SL, /* side left */
SNDRV_CHMAP_SR, /* side right */
SNDRV_CHMAP_TC, /* top center */
SNDRV_CHMAP_TFL, /* top front left */
SNDRV_CHMAP_TFC, /* top front center */
SNDRV_CHMAP_TFR, /* top front right */
SNDRV_CHMAP_TRL, /* top back left */
SNDRV_CHMAP_TRC, /* top back center */
SNDRV_CHMAP_TRR, /* top back right */
SNDRV_CHMAP_TFLC, /* top front left of center */
SNDRV_CHMAP_TFRC, /* top front right of center */
SNDRV_CHMAP_LLFE, /* left LFE */
SNDRV_CHMAP_RLFE, /* right LFE */
SNDRV_CHMAP_TSL, /* top side left */
SNDRV_CHMAP_TSR, /* top side right */
SNDRV_CHMAP_BC, /* bottom center */
SNDRV_CHMAP_RLC, /* back left of center */
SNDRV_CHMAP_RRC, /* back right of center */
0 /* terminator */
};
struct snd_pcm_chmap_elem *chmap;
const unsigned int *maps;
int c;
if (channels > ARRAY_SIZE(chmap->map))
return NULL;
chmap = kzalloc(sizeof(*chmap), GFP_KERNEL);
if (!chmap)
return NULL;
maps = protocol == UAC_VERSION_2 ? uac2_maps : uac1_maps;
chmap->channels = channels;
c = 0;
if (bits) {
for (; bits && *maps; maps++, bits >>= 1)
if (bits & 1)
chmap->map[c++] = *maps;
} else {
/* If we're missing wChannelConfig, then guess something
to make sure the channel map is not skipped entirely */
if (channels == 1)
chmap->map[c++] = SNDRV_CHMAP_MONO;
else
for (; c < channels && *maps; maps++)
chmap->map[c++] = *maps;
}
for (; c < channels; c++)
chmap->map[c] = SNDRV_CHMAP_UNKNOWN;
return chmap;
}
/*
* add this endpoint to the chip instance.
* if a stream with the same endpoint already exists, append to it.
* if not, create a new pcm stream.
*/
int snd_usb_add_audio_stream(struct snd_usb_audio *chip,
int stream,
struct audioformat *fp)
{
struct snd_usb_stream *as;
struct snd_usb_substream *subs;
struct snd_pcm *pcm;
int err;
list_for_each_entry(as, &chip->pcm_list, list) {
if (as->fmt_type != fp->fmt_type)
continue;
subs = &as->substream[stream];
if (subs->ep_num == fp->endpoint) {
list_add_tail(&fp->list, &subs->fmt_list);
subs->num_formats++;
subs->formats |= fp->formats;
return 0;
}
}
/* look for an empty stream */
list_for_each_entry(as, &chip->pcm_list, list) {
if (as->fmt_type != fp->fmt_type)
continue;
subs = &as->substream[stream];
if (subs->ep_num)
continue;
err = snd_pcm_new_stream(as->pcm, stream, 1);
if (err < 0)
return err;
snd_usb_init_substream(as, stream, fp);
return add_chmap(as->pcm, stream, subs);
}
/* create a new pcm */
as = kzalloc(sizeof(*as), GFP_KERNEL);
if (!as)
return -ENOMEM;
as->pcm_index = chip->pcm_devs;
as->chip = chip;
as->fmt_type = fp->fmt_type;
err = snd_pcm_new(chip->card, "USB Audio", chip->pcm_devs,
stream == SNDRV_PCM_STREAM_PLAYBACK ? 1 : 0,
stream == SNDRV_PCM_STREAM_PLAYBACK ? 0 : 1,
&pcm);
if (err < 0) {
kfree(as);
return err;
}
as->pcm = pcm;
pcm->private_data = as;
pcm->private_free = snd_usb_audio_pcm_free;
pcm->info_flags = 0;
if (chip->pcm_devs > 0)
sprintf(pcm->name, "USB Audio #%d", chip->pcm_devs);
else
strcpy(pcm->name, "USB Audio");
snd_usb_init_substream(as, stream, fp);
list_add(&as->list, &chip->pcm_list);
chip->pcm_devs++;
snd_usb_proc_pcm_format_add(as);
return add_chmap(pcm, stream, &as->substream[stream]);
}
static int parse_uac_endpoint_attributes(struct snd_usb_audio *chip,
struct usb_host_interface *alts,
int protocol, int iface_no)
{
/* parsed with a v1 header here. that's ok as we only look at the
* header first which is the same for both versions */
struct uac_iso_endpoint_descriptor *csep;
struct usb_interface_descriptor *altsd = get_iface_desc(alts);
int attributes = 0;
csep = snd_usb_find_desc(alts->endpoint[0].extra, alts->endpoint[0].extralen, NULL, USB_DT_CS_ENDPOINT);
/* Creamware Noah has this descriptor after the 2nd endpoint */
if (!csep && altsd->bNumEndpoints >= 2)
csep = snd_usb_find_desc(alts->endpoint[1].extra, alts->endpoint[1].extralen, NULL, USB_DT_CS_ENDPOINT);
/*
* If we can't locate the USB_DT_CS_ENDPOINT descriptor in the extra
* bytes after the first endpoint, go search the entire interface.
* Some devices have it directly *before* the standard endpoint.
*/
if (!csep)
csep = snd_usb_find_desc(alts->extra, alts->extralen, NULL, USB_DT_CS_ENDPOINT);
if (!csep || csep->bLength < 7 ||
csep->bDescriptorSubtype != UAC_EP_GENERAL) {
usb_audio_warn(chip,
"%u:%d : no or invalid class specific endpoint descriptor\n",
iface_no, altsd->bAlternateSetting);
return 0;
}
if (protocol == UAC_VERSION_1) {
attributes = csep->bmAttributes;
} else {
struct uac2_iso_endpoint_descriptor *csep2 =
(struct uac2_iso_endpoint_descriptor *) csep;
attributes = csep->bmAttributes & UAC_EP_CS_ATTR_FILL_MAX;
/* emulate the endpoint attributes of a v1 device */
if (csep2->bmControls & UAC2_CONTROL_PITCH)
attributes |= UAC_EP_CS_ATTR_PITCH_CONTROL;
}
return attributes;
}
/* find an input terminal descriptor (either UAC1 or UAC2) with the given
* terminal id
*/
static void *
snd_usb_find_input_terminal_descriptor(struct usb_host_interface *ctrl_iface,
int terminal_id)
{
struct uac2_input_terminal_descriptor *term = NULL;
while ((term = snd_usb_find_csint_desc(ctrl_iface->extra,
ctrl_iface->extralen,
term, UAC_INPUT_TERMINAL))) {
if (term->bTerminalID == terminal_id)
return term;
}
return NULL;
}
static struct uac2_output_terminal_descriptor *
snd_usb_find_output_terminal_descriptor(struct usb_host_interface *ctrl_iface,
int terminal_id)
{
struct uac2_output_terminal_descriptor *term = NULL;
while ((term = snd_usb_find_csint_desc(ctrl_iface->extra,
ctrl_iface->extralen,
term, UAC_OUTPUT_TERMINAL))) {
if (term->bTerminalID == terminal_id)
return term;
}
return NULL;
}
int snd_usb_parse_audio_interface(struct snd_usb_audio *chip, int iface_no)
{
struct usb_device *dev;
struct usb_interface *iface;
struct usb_host_interface *alts;
struct usb_interface_descriptor *altsd;
int i, altno, err, stream;
unsigned int format = 0, num_channels = 0;
struct audioformat *fp = NULL;
int num, protocol, clock = 0;
struct uac_format_type_i_continuous_descriptor *fmt;
unsigned int chconfig;
dev = chip->dev;
/* parse the interface's altsettings */
iface = usb_ifnum_to_if(dev, iface_no);
num = iface->num_altsetting;
/*
* Dallas DS4201 workaround: It presents 5 altsettings, but the last
* one misses syncpipe, and does not produce any sound.
*/
if (chip->usb_id == USB_ID(0x04fa, 0x4201))
num = 4;
for (i = 0; i < num; i++) {
alts = &iface->altsetting[i];
altsd = get_iface_desc(alts);
protocol = altsd->bInterfaceProtocol;
/* skip invalid one */
if (((altsd->bInterfaceClass != USB_CLASS_AUDIO ||
(altsd->bInterfaceSubClass != USB_SUBCLASS_AUDIOSTREAMING &&
altsd->bInterfaceSubClass != USB_SUBCLASS_VENDOR_SPEC)) &&
altsd->bInterfaceClass != USB_CLASS_VENDOR_SPEC) ||
altsd->bNumEndpoints < 1 ||
le16_to_cpu(get_endpoint(alts, 0)->wMaxPacketSize) == 0)
continue;
/* must be isochronous */
if ((get_endpoint(alts, 0)->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) !=
USB_ENDPOINT_XFER_ISOC)
continue;
/* check direction */
stream = (get_endpoint(alts, 0)->bEndpointAddress & USB_DIR_IN) ?
SNDRV_PCM_STREAM_CAPTURE : SNDRV_PCM_STREAM_PLAYBACK;
altno = altsd->bAlternateSetting;
if (snd_usb_apply_interface_quirk(chip, iface_no, altno))
continue;
/*
* Roland audio streaming interfaces are marked with protocols
* 0/1/2, but are UAC 1 compatible.
*/
if (USB_ID_VENDOR(chip->usb_id) == 0x0582 &&
altsd->bInterfaceClass == USB_CLASS_VENDOR_SPEC &&
protocol <= 2)
protocol = UAC_VERSION_1;
chconfig = 0;
/* get audio formats */
switch (protocol) {
default:
dev_dbg(&dev->dev, "%u:%d: unknown interface protocol %#02x, assuming v1\n",
iface_no, altno, protocol);
protocol = UAC_VERSION_1;
/* fall through */
case UAC_VERSION_1: {
struct uac1_as_header_descriptor *as =
snd_usb_find_csint_desc(alts->extra, alts->extralen, NULL, UAC_AS_GENERAL);
struct uac_input_terminal_descriptor *iterm;
if (!as) {
dev_err(&dev->dev,
"%u:%d : UAC_AS_GENERAL descriptor not found\n",
iface_no, altno);
continue;
}
if (as->bLength < sizeof(*as)) {
dev_err(&dev->dev,
"%u:%d : invalid UAC_AS_GENERAL desc\n",
iface_no, altno);
continue;
}
format = le16_to_cpu(as->wFormatTag); /* remember the format value */
iterm = snd_usb_find_input_terminal_descriptor(chip->ctrl_intf,
as->bTerminalLink);
if (iterm) {
num_channels = iterm->bNrChannels;
chconfig = le16_to_cpu(iterm->wChannelConfig);
}
break;
}
case UAC_VERSION_2: {
struct uac2_input_terminal_descriptor *input_term;
struct uac2_output_terminal_descriptor *output_term;
struct uac2_as_header_descriptor *as =
snd_usb_find_csint_desc(alts->extra, alts->extralen, NULL, UAC_AS_GENERAL);
if (!as) {
dev_err(&dev->dev,
"%u:%d : UAC_AS_GENERAL descriptor not found\n",
iface_no, altno);
continue;
}
if (as->bLength < sizeof(*as)) {
dev_err(&dev->dev,
"%u:%d : invalid UAC_AS_GENERAL desc\n",
iface_no, altno);
continue;
}
num_channels = as->bNrChannels;
format = le32_to_cpu(as->bmFormats);
chconfig = le32_to_cpu(as->bmChannelConfig);
/* lookup the terminal associated to this interface
* to extract the clock */
input_term = snd_usb_find_input_terminal_descriptor(chip->ctrl_intf,
as->bTerminalLink);
if (input_term) {
clock = input_term->bCSourceID;
if (!chconfig && (num_channels == input_term->bNrChannels))
chconfig = le32_to_cpu(input_term->bmChannelConfig);
break;
}
output_term = snd_usb_find_output_terminal_descriptor(chip->ctrl_intf,
as->bTerminalLink);
if (output_term) {
clock = output_term->bCSourceID;
break;
}
dev_err(&dev->dev,
"%u:%d : bogus bTerminalLink %d\n",
iface_no, altno, as->bTerminalLink);
continue;
}
}
/* get format type */
fmt = snd_usb_find_csint_desc(alts->extra, alts->extralen, NULL, UAC_FORMAT_TYPE);
if (!fmt) {
dev_err(&dev->dev,
"%u:%d : no UAC_FORMAT_TYPE desc\n",
iface_no, altno);
continue;
}
if (((protocol == UAC_VERSION_1) && (fmt->bLength < 8)) ||
((protocol == UAC_VERSION_2) && (fmt->bLength < 6))) {
dev_err(&dev->dev,
"%u:%d : invalid UAC_FORMAT_TYPE desc\n",
iface_no, altno);
continue;
}
/*
* Blue Microphones workaround: The last altsetting is identical
* with the previous one, except for a larger packet size, but
* is actually a mislabeled two-channel setting; ignore it.
*/
if (fmt->bNrChannels == 1 &&
fmt->bSubframeSize == 2 &&
altno == 2 && num == 3 &&
fp && fp->altsetting == 1 && fp->channels == 1 &&
fp->formats == SNDRV_PCM_FMTBIT_S16_LE &&
protocol == UAC_VERSION_1 &&
le16_to_cpu(get_endpoint(alts, 0)->wMaxPacketSize) ==
fp->maxpacksize * 2)
continue;
fp = kzalloc(sizeof(*fp), GFP_KERNEL);
if (! fp) {
dev_err(&dev->dev, "cannot malloc\n");
return -ENOMEM;
}
fp->iface = iface_no;
fp->altsetting = altno;
fp->altset_idx = i;
fp->endpoint = get_endpoint(alts, 0)->bEndpointAddress;
fp->ep_attr = get_endpoint(alts, 0)->bmAttributes;
fp->datainterval = snd_usb_parse_datainterval(chip, alts);
fp->protocol = protocol;
fp->maxpacksize = le16_to_cpu(get_endpoint(alts, 0)->wMaxPacketSize);
fp->channels = num_channels;
if (snd_usb_get_speed(dev) == USB_SPEED_HIGH)
fp->maxpacksize = (((fp->maxpacksize >> 11) & 3) + 1)
* (fp->maxpacksize & 0x7ff);
fp->attributes = parse_uac_endpoint_attributes(chip, alts, protocol, iface_no);
fp->clock = clock;
/* some quirks for attributes here */
switch (chip->usb_id) {
case USB_ID(0x0a92, 0x0053): /* AudioTrak Optoplay */
/* Optoplay sets the sample rate attribute although
* it seems not supporting it in fact.
*/
fp->attributes &= ~UAC_EP_CS_ATTR_SAMPLE_RATE;
break;
case USB_ID(0x041e, 0x3020): /* Creative SB Audigy 2 NX */
case USB_ID(0x0763, 0x2003): /* M-Audio Audiophile USB */
/* doesn't set the sample rate attribute, but supports it */
fp->attributes |= UAC_EP_CS_ATTR_SAMPLE_RATE;
break;
case USB_ID(0x0763, 0x2001): /* M-Audio Quattro USB */
case USB_ID(0x0763, 0x2012): /* M-Audio Fast Track Pro USB */
case USB_ID(0x047f, 0x0ca1): /* plantronics headset */
case USB_ID(0x077d, 0x07af): /* Griffin iMic (note that there is
an older model 77d:223) */
/*
* plantronics headset and Griffin iMic have set adaptive-in
* although it's really not...
*/
fp->ep_attr &= ~USB_ENDPOINT_SYNCTYPE;
if (stream == SNDRV_PCM_STREAM_PLAYBACK)
fp->ep_attr |= USB_ENDPOINT_SYNC_ADAPTIVE;
else
fp->ep_attr |= USB_ENDPOINT_SYNC_SYNC;
break;
}
/* ok, let's parse further... */
if (snd_usb_parse_audio_format(chip, fp, format, fmt, stream) < 0) {
kfree(fp->rate_table);
kfree(fp);
fp = NULL;
continue;
}
/* Create chmap */
if (fp->channels != num_channels)
chconfig = 0;
fp->chmap = convert_chmap(fp->channels, chconfig, protocol);
dev_dbg(&dev->dev, "%u:%d: add audio endpoint %#x\n", iface_no, altno, fp->endpoint);
err = snd_usb_add_audio_stream(chip, stream, fp);
if (err < 0) {
kfree(fp->rate_table);
kfree(fp->chmap);
kfree(fp);
return err;
}
/* try to set the interface... */
usb_set_interface(chip->dev, iface_no, altno);
snd_usb_init_pitch(chip, iface_no, alts, fp);
snd_usb_init_sample_rate(chip, iface_no, alts, fp, fp->rate_max);
}
return 0;
}

12
sound/usb/stream.h Normal file
View file

@ -0,0 +1,12 @@
#ifndef __USBAUDIO_STREAM_H
#define __USBAUDIO_STREAM_H
int snd_usb_parse_audio_interface(struct snd_usb_audio *chip,
int iface_no);
int snd_usb_add_audio_stream(struct snd_usb_audio *chip,
int stream,
struct audioformat *fp);
#endif /* __USBAUDIO_STREAM_H */

118
sound/usb/usbaudio.h Normal file
View file

@ -0,0 +1,118 @@
#ifndef __USBAUDIO_H
#define __USBAUDIO_H
/*
* (Tentative) USB Audio Driver for ALSA
*
* Copyright (c) 2002 by Takashi Iwai <tiwai@suse.de>
*
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
/* handling of USB vendor/product ID pairs as 32-bit numbers */
#define USB_ID(vendor, product) (((vendor) << 16) | (product))
#define USB_ID_VENDOR(id) ((id) >> 16)
#define USB_ID_PRODUCT(id) ((u16)(id))
/*
*
*/
struct snd_usb_audio {
int index;
struct usb_device *dev;
struct snd_card *card;
struct usb_interface *pm_intf;
u32 usb_id;
struct mutex mutex;
struct rw_semaphore shutdown_rwsem;
unsigned int shutdown:1;
unsigned int probing:1;
unsigned int in_pm:1;
unsigned int autosuspended:1;
unsigned int txfr_quirk:1; /* Subframe boundaries on transfers */
int num_interfaces;
int num_suspended_intf;
struct list_head pcm_list; /* list of pcm streams */
struct list_head ep_list; /* list of audio-related endpoints */
int pcm_devs;
struct list_head midi_list; /* list of midi interfaces */
struct list_head mixer_list; /* list of mixer interfaces */
int setup; /* from the 'device_setup' module param */
bool autoclock; /* from the 'autoclock' module param */
struct usb_host_interface *ctrl_intf; /* the audio control interface */
};
#define usb_audio_err(chip, fmt, args...) \
dev_err(&(chip)->dev->dev, fmt, ##args)
#define usb_audio_warn(chip, fmt, args...) \
dev_warn(&(chip)->dev->dev, fmt, ##args)
#define usb_audio_info(chip, fmt, args...) \
dev_info(&(chip)->dev->dev, fmt, ##args)
#define usb_audio_dbg(chip, fmt, args...) \
dev_dbg(&(chip)->dev->dev, fmt, ##args)
/*
* Information about devices with broken descriptors
*/
/* special values for .ifnum */
#define QUIRK_NO_INTERFACE -2
#define QUIRK_ANY_INTERFACE -1
enum quirk_type {
QUIRK_IGNORE_INTERFACE,
QUIRK_COMPOSITE,
QUIRK_AUTODETECT,
QUIRK_MIDI_STANDARD_INTERFACE,
QUIRK_MIDI_FIXED_ENDPOINT,
QUIRK_MIDI_YAMAHA,
QUIRK_MIDI_ROLAND,
QUIRK_MIDI_MIDIMAN,
QUIRK_MIDI_NOVATION,
QUIRK_MIDI_RAW_BYTES,
QUIRK_MIDI_EMAGIC,
QUIRK_MIDI_CME,
QUIRK_MIDI_AKAI,
QUIRK_MIDI_US122L,
QUIRK_MIDI_FTDI,
QUIRK_AUDIO_STANDARD_INTERFACE,
QUIRK_AUDIO_FIXED_ENDPOINT,
QUIRK_AUDIO_EDIROL_UAXX,
QUIRK_AUDIO_ALIGN_TRANSFER,
QUIRK_AUDIO_STANDARD_MIXER,
QUIRK_TYPE_COUNT
};
struct snd_usb_audio_quirk {
const char *vendor_name;
const char *product_name;
int16_t ifnum;
uint16_t type;
const void *data;
};
#define combine_word(s) ((*(s)) | ((unsigned int)(s)[1] << 8))
#define combine_triple(s) (combine_word(s) | ((unsigned int)(s)[2] << 16))
#define combine_quad(s) (combine_triple(s) | ((unsigned int)(s)[3] << 24))
#endif /* __USBAUDIO_H */

5
sound/usb/usx2y/Makefile Normal file
View file

@ -0,0 +1,5 @@
snd-usb-usx2y-objs := usbusx2y.o usX2Yhwdep.o usx2yhwdeppcm.o
snd-usb-us122l-objs := us122l.o
obj-$(CONFIG_SND_USB_USX2Y) += snd-usb-usx2y.o
obj-$(CONFIG_SND_USB_US122L) += snd-usb-us122l.o

778
sound/usb/usx2y/us122l.c Normal file
View file

@ -0,0 +1,778 @@
/*
* Copyright (C) 2007, 2008 Karsten Wiese <fzu@wemgehoertderstaat.de>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include <linux/slab.h>
#include <linux/usb.h>
#include <linux/usb/audio.h>
#include <linux/module.h>
#include <sound/core.h>
#include <sound/hwdep.h>
#include <sound/pcm.h>
#include <sound/initval.h>
#define MODNAME "US122L"
#include "usb_stream.c"
#include "../usbaudio.h"
#include "../midi.h"
#include "us122l.h"
MODULE_AUTHOR("Karsten Wiese <fzu@wemgehoertderstaat.de>");
MODULE_DESCRIPTION("TASCAM "NAME_ALLCAPS" Version 0.5");
MODULE_LICENSE("GPL");
static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 0-max */
static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* Id for this card */
/* Enable this card */
static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP;
module_param_array(index, int, NULL, 0444);
MODULE_PARM_DESC(index, "Index value for "NAME_ALLCAPS".");
module_param_array(id, charp, NULL, 0444);
MODULE_PARM_DESC(id, "ID string for "NAME_ALLCAPS".");
module_param_array(enable, bool, NULL, 0444);
MODULE_PARM_DESC(enable, "Enable "NAME_ALLCAPS".");
static int snd_us122l_card_used[SNDRV_CARDS];
static int us122l_create_usbmidi(struct snd_card *card)
{
static struct snd_usb_midi_endpoint_info quirk_data = {
.out_ep = 4,
.in_ep = 3,
.out_cables = 0x001,
.in_cables = 0x001
};
static struct snd_usb_audio_quirk quirk = {
.vendor_name = "US122L",
.product_name = NAME_ALLCAPS,
.ifnum = 1,
.type = QUIRK_MIDI_US122L,
.data = &quirk_data
};
struct usb_device *dev = US122L(card)->dev;
struct usb_interface *iface = usb_ifnum_to_if(dev, 1);
return snd_usbmidi_create(card, iface,
&US122L(card)->midi_list, &quirk);
}
static int us144_create_usbmidi(struct snd_card *card)
{
static struct snd_usb_midi_endpoint_info quirk_data = {
.out_ep = 4,
.in_ep = 3,
.out_cables = 0x001,
.in_cables = 0x001
};
static struct snd_usb_audio_quirk quirk = {
.vendor_name = "US144",
.product_name = NAME_ALLCAPS,
.ifnum = 0,
.type = QUIRK_MIDI_US122L,
.data = &quirk_data
};
struct usb_device *dev = US122L(card)->dev;
struct usb_interface *iface = usb_ifnum_to_if(dev, 0);
return snd_usbmidi_create(card, iface,
&US122L(card)->midi_list, &quirk);
}
/*
* Wrapper for usb_control_msg().
* Allocates a temp buffer to prevent dmaing from/to the stack.
*/
static int us122l_ctl_msg(struct usb_device *dev, unsigned int pipe,
__u8 request, __u8 requesttype,
__u16 value, __u16 index, void *data,
__u16 size, int timeout)
{
int err;
void *buf = NULL;
if (size > 0) {
buf = kmemdup(data, size, GFP_KERNEL);
if (!buf)
return -ENOMEM;
}
err = usb_control_msg(dev, pipe, request, requesttype,
value, index, buf, size, timeout);
if (size > 0) {
memcpy(data, buf, size);
kfree(buf);
}
return err;
}
static void pt_info_set(struct usb_device *dev, u8 v)
{
int ret;
ret = usb_control_msg(dev, usb_sndctrlpipe(dev, 0),
'I',
USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
v, 0, NULL, 0, 1000);
snd_printdd(KERN_DEBUG "%i\n", ret);
}
static void usb_stream_hwdep_vm_open(struct vm_area_struct *area)
{
struct us122l *us122l = area->vm_private_data;
atomic_inc(&us122l->mmap_count);
snd_printdd(KERN_DEBUG "%i\n", atomic_read(&us122l->mmap_count));
}
static int usb_stream_hwdep_vm_fault(struct vm_area_struct *area,
struct vm_fault *vmf)
{
unsigned long offset;
struct page *page;
void *vaddr;
struct us122l *us122l = area->vm_private_data;
struct usb_stream *s;
mutex_lock(&us122l->mutex);
s = us122l->sk.s;
if (!s)
goto unlock;
offset = vmf->pgoff << PAGE_SHIFT;
if (offset < PAGE_ALIGN(s->read_size))
vaddr = (char *)s + offset;
else {
offset -= PAGE_ALIGN(s->read_size);
if (offset >= PAGE_ALIGN(s->write_size))
goto unlock;
vaddr = us122l->sk.write_page + offset;
}
page = virt_to_page(vaddr);
get_page(page);
mutex_unlock(&us122l->mutex);
vmf->page = page;
return 0;
unlock:
mutex_unlock(&us122l->mutex);
return VM_FAULT_SIGBUS;
}
static void usb_stream_hwdep_vm_close(struct vm_area_struct *area)
{
struct us122l *us122l = area->vm_private_data;
atomic_dec(&us122l->mmap_count);
snd_printdd(KERN_DEBUG "%i\n", atomic_read(&us122l->mmap_count));
}
static const struct vm_operations_struct usb_stream_hwdep_vm_ops = {
.open = usb_stream_hwdep_vm_open,
.fault = usb_stream_hwdep_vm_fault,
.close = usb_stream_hwdep_vm_close,
};
static int usb_stream_hwdep_open(struct snd_hwdep *hw, struct file *file)
{
struct us122l *us122l = hw->private_data;
struct usb_interface *iface;
snd_printdd(KERN_DEBUG "%p %p\n", hw, file);
if (hw->used >= 2)
return -EBUSY;
if (!us122l->first)
us122l->first = file;
if (us122l->dev->descriptor.idProduct == USB_ID_US144 ||
us122l->dev->descriptor.idProduct == USB_ID_US144MKII) {
iface = usb_ifnum_to_if(us122l->dev, 0);
usb_autopm_get_interface(iface);
}
iface = usb_ifnum_to_if(us122l->dev, 1);
usb_autopm_get_interface(iface);
return 0;
}
static int usb_stream_hwdep_release(struct snd_hwdep *hw, struct file *file)
{
struct us122l *us122l = hw->private_data;
struct usb_interface *iface;
snd_printdd(KERN_DEBUG "%p %p\n", hw, file);
if (us122l->dev->descriptor.idProduct == USB_ID_US144 ||
us122l->dev->descriptor.idProduct == USB_ID_US144MKII) {
iface = usb_ifnum_to_if(us122l->dev, 0);
usb_autopm_put_interface(iface);
}
iface = usb_ifnum_to_if(us122l->dev, 1);
usb_autopm_put_interface(iface);
if (us122l->first == file)
us122l->first = NULL;
mutex_lock(&us122l->mutex);
if (us122l->master == file)
us122l->master = us122l->slave;
us122l->slave = NULL;
mutex_unlock(&us122l->mutex);
return 0;
}
static int usb_stream_hwdep_mmap(struct snd_hwdep *hw,
struct file *filp, struct vm_area_struct *area)
{
unsigned long size = area->vm_end - area->vm_start;
struct us122l *us122l = hw->private_data;
unsigned long offset;
struct usb_stream *s;
int err = 0;
bool read;
offset = area->vm_pgoff << PAGE_SHIFT;
mutex_lock(&us122l->mutex);
s = us122l->sk.s;
read = offset < s->read_size;
if (read && area->vm_flags & VM_WRITE) {
err = -EPERM;
goto out;
}
snd_printdd(KERN_DEBUG "%lu %u\n", size,
read ? s->read_size : s->write_size);
/* if userspace tries to mmap beyond end of our buffer, fail */
if (size > PAGE_ALIGN(read ? s->read_size : s->write_size)) {
snd_printk(KERN_WARNING "%lu > %u\n", size,
read ? s->read_size : s->write_size);
err = -EINVAL;
goto out;
}
area->vm_ops = &usb_stream_hwdep_vm_ops;
area->vm_flags |= VM_DONTDUMP;
if (!read)
area->vm_flags |= VM_DONTEXPAND;
area->vm_private_data = us122l;
atomic_inc(&us122l->mmap_count);
out:
mutex_unlock(&us122l->mutex);
return err;
}
static unsigned int usb_stream_hwdep_poll(struct snd_hwdep *hw,
struct file *file, poll_table *wait)
{
struct us122l *us122l = hw->private_data;
unsigned *polled;
unsigned int mask;
poll_wait(file, &us122l->sk.sleep, wait);
mask = POLLIN | POLLOUT | POLLWRNORM | POLLERR;
if (mutex_trylock(&us122l->mutex)) {
struct usb_stream *s = us122l->sk.s;
if (s && s->state == usb_stream_ready) {
if (us122l->first == file)
polled = &s->periods_polled;
else
polled = &us122l->second_periods_polled;
if (*polled != s->periods_done) {
*polled = s->periods_done;
mask = POLLIN | POLLOUT | POLLWRNORM;
} else
mask = 0;
}
mutex_unlock(&us122l->mutex);
}
return mask;
}
static void us122l_stop(struct us122l *us122l)
{
struct list_head *p;
list_for_each(p, &us122l->midi_list)
snd_usbmidi_input_stop(p);
usb_stream_stop(&us122l->sk);
usb_stream_free(&us122l->sk);
}
static int us122l_set_sample_rate(struct usb_device *dev, int rate)
{
unsigned int ep = 0x81;
unsigned char data[3];
int err;
data[0] = rate;
data[1] = rate >> 8;
data[2] = rate >> 16;
err = us122l_ctl_msg(dev, usb_sndctrlpipe(dev, 0), UAC_SET_CUR,
USB_TYPE_CLASS|USB_RECIP_ENDPOINT|USB_DIR_OUT,
UAC_EP_CS_ATTR_SAMPLE_RATE << 8, ep, data, 3, 1000);
if (err < 0)
snd_printk(KERN_ERR "%d: cannot set freq %d to ep 0x%x\n",
dev->devnum, rate, ep);
return err;
}
static bool us122l_start(struct us122l *us122l,
unsigned rate, unsigned period_frames)
{
struct list_head *p;
int err;
unsigned use_packsize = 0;
bool success = false;
if (us122l->dev->speed == USB_SPEED_HIGH) {
/* The us-122l's descriptor defaults to iso max_packsize 78,
which isn't needed for samplerates <= 48000.
Lets save some memory:
*/
switch (rate) {
case 44100:
use_packsize = 36;
break;
case 48000:
use_packsize = 42;
break;
case 88200:
use_packsize = 72;
break;
}
}
if (!usb_stream_new(&us122l->sk, us122l->dev, 1, 2,
rate, use_packsize, period_frames, 6))
goto out;
err = us122l_set_sample_rate(us122l->dev, rate);
if (err < 0) {
us122l_stop(us122l);
snd_printk(KERN_ERR "us122l_set_sample_rate error \n");
goto out;
}
err = usb_stream_start(&us122l->sk);
if (err < 0) {
us122l_stop(us122l);
snd_printk(KERN_ERR "us122l_start error %i \n", err);
goto out;
}
list_for_each(p, &us122l->midi_list)
snd_usbmidi_input_start(p);
success = true;
out:
return success;
}
static int usb_stream_hwdep_ioctl(struct snd_hwdep *hw, struct file *file,
unsigned cmd, unsigned long arg)
{
struct usb_stream_config *cfg;
struct us122l *us122l = hw->private_data;
struct usb_stream *s;
unsigned min_period_frames;
int err = 0;
bool high_speed;
if (cmd != SNDRV_USB_STREAM_IOCTL_SET_PARAMS)
return -ENOTTY;
cfg = memdup_user((void *)arg, sizeof(*cfg));
if (IS_ERR(cfg))
return PTR_ERR(cfg);
if (cfg->version != USB_STREAM_INTERFACE_VERSION) {
err = -ENXIO;
goto free;
}
high_speed = us122l->dev->speed == USB_SPEED_HIGH;
if ((cfg->sample_rate != 44100 && cfg->sample_rate != 48000 &&
(!high_speed ||
(cfg->sample_rate != 88200 && cfg->sample_rate != 96000))) ||
cfg->frame_size != 6 ||
cfg->period_frames > 0x3000) {
err = -EINVAL;
goto free;
}
switch (cfg->sample_rate) {
case 44100:
min_period_frames = 48;
break;
case 48000:
min_period_frames = 52;
break;
default:
min_period_frames = 104;
break;
}
if (!high_speed)
min_period_frames <<= 1;
if (cfg->period_frames < min_period_frames) {
err = -EINVAL;
goto free;
}
snd_power_wait(hw->card, SNDRV_CTL_POWER_D0);
mutex_lock(&us122l->mutex);
s = us122l->sk.s;
if (!us122l->master)
us122l->master = file;
else if (us122l->master != file) {
if (!s || memcmp(cfg, &s->cfg, sizeof(*cfg))) {
err = -EIO;
goto unlock;
}
us122l->slave = file;
}
if (!s || memcmp(cfg, &s->cfg, sizeof(*cfg)) ||
s->state == usb_stream_xrun) {
us122l_stop(us122l);
if (!us122l_start(us122l, cfg->sample_rate, cfg->period_frames))
err = -EIO;
else
err = 1;
}
unlock:
mutex_unlock(&us122l->mutex);
free:
kfree(cfg);
wake_up_all(&us122l->sk.sleep);
return err;
}
#define SND_USB_STREAM_ID "USB STREAM"
static int usb_stream_hwdep_new(struct snd_card *card)
{
int err;
struct snd_hwdep *hw;
struct usb_device *dev = US122L(card)->dev;
err = snd_hwdep_new(card, SND_USB_STREAM_ID, 0, &hw);
if (err < 0)
return err;
hw->iface = SNDRV_HWDEP_IFACE_USB_STREAM;
hw->private_data = US122L(card);
hw->ops.open = usb_stream_hwdep_open;
hw->ops.release = usb_stream_hwdep_release;
hw->ops.ioctl = usb_stream_hwdep_ioctl;
hw->ops.ioctl_compat = usb_stream_hwdep_ioctl;
hw->ops.mmap = usb_stream_hwdep_mmap;
hw->ops.poll = usb_stream_hwdep_poll;
sprintf(hw->name, "/proc/bus/usb/%03d/%03d/hwdeppcm",
dev->bus->busnum, dev->devnum);
return 0;
}
static bool us122l_create_card(struct snd_card *card)
{
int err;
struct us122l *us122l = US122L(card);
if (us122l->dev->descriptor.idProduct == USB_ID_US144 ||
us122l->dev->descriptor.idProduct == USB_ID_US144MKII) {
err = usb_set_interface(us122l->dev, 0, 1);
if (err) {
snd_printk(KERN_ERR "usb_set_interface error \n");
return false;
}
}
err = usb_set_interface(us122l->dev, 1, 1);
if (err) {
snd_printk(KERN_ERR "usb_set_interface error \n");
return false;
}
pt_info_set(us122l->dev, 0x11);
pt_info_set(us122l->dev, 0x10);
if (!us122l_start(us122l, 44100, 256))
return false;
if (us122l->dev->descriptor.idProduct == USB_ID_US144 ||
us122l->dev->descriptor.idProduct == USB_ID_US144MKII)
err = us144_create_usbmidi(card);
else
err = us122l_create_usbmidi(card);
if (err < 0) {
snd_printk(KERN_ERR "us122l_create_usbmidi error %i \n", err);
us122l_stop(us122l);
return false;
}
err = usb_stream_hwdep_new(card);
if (err < 0) {
/* release the midi resources */
struct list_head *p;
list_for_each(p, &us122l->midi_list)
snd_usbmidi_disconnect(p);
us122l_stop(us122l);
return false;
}
return true;
}
static void snd_us122l_free(struct snd_card *card)
{
struct us122l *us122l = US122L(card);
int index = us122l->card_index;
if (index >= 0 && index < SNDRV_CARDS)
snd_us122l_card_used[index] = 0;
}
static int usx2y_create_card(struct usb_device *device,
struct usb_interface *intf,
struct snd_card **cardp)
{
int dev;
struct snd_card *card;
int err;
for (dev = 0; dev < SNDRV_CARDS; ++dev)
if (enable[dev] && !snd_us122l_card_used[dev])
break;
if (dev >= SNDRV_CARDS)
return -ENODEV;
err = snd_card_new(&intf->dev, index[dev], id[dev], THIS_MODULE,
sizeof(struct us122l), &card);
if (err < 0)
return err;
snd_us122l_card_used[US122L(card)->card_index = dev] = 1;
card->private_free = snd_us122l_free;
US122L(card)->dev = device;
mutex_init(&US122L(card)->mutex);
init_waitqueue_head(&US122L(card)->sk.sleep);
INIT_LIST_HEAD(&US122L(card)->midi_list);
strcpy(card->driver, "USB "NAME_ALLCAPS"");
sprintf(card->shortname, "TASCAM "NAME_ALLCAPS"");
sprintf(card->longname, "%s (%x:%x if %d at %03d/%03d)",
card->shortname,
le16_to_cpu(device->descriptor.idVendor),
le16_to_cpu(device->descriptor.idProduct),
0,
US122L(card)->dev->bus->busnum,
US122L(card)->dev->devnum
);
*cardp = card;
return 0;
}
static int us122l_usb_probe(struct usb_interface *intf,
const struct usb_device_id *device_id,
struct snd_card **cardp)
{
struct usb_device *device = interface_to_usbdev(intf);
struct snd_card *card;
int err;
err = usx2y_create_card(device, intf, &card);
if (err < 0)
return err;
if (!us122l_create_card(card)) {
snd_card_free(card);
return -EINVAL;
}
err = snd_card_register(card);
if (err < 0) {
snd_card_free(card);
return err;
}
usb_get_intf(usb_ifnum_to_if(device, 0));
usb_get_dev(device);
*cardp = card;
return 0;
}
static int snd_us122l_probe(struct usb_interface *intf,
const struct usb_device_id *id)
{
struct usb_device *device = interface_to_usbdev(intf);
struct snd_card *card;
int err;
if ((device->descriptor.idProduct == USB_ID_US144 ||
device->descriptor.idProduct == USB_ID_US144MKII)
&& device->speed == USB_SPEED_HIGH) {
snd_printk(KERN_ERR "disable ehci-hcd to run US-144 \n");
return -ENODEV;
}
snd_printdd(KERN_DEBUG"%p:%i\n",
intf, intf->cur_altsetting->desc.bInterfaceNumber);
if (intf->cur_altsetting->desc.bInterfaceNumber != 1)
return 0;
err = us122l_usb_probe(usb_get_intf(intf), id, &card);
if (err < 0) {
usb_put_intf(intf);
return err;
}
usb_set_intfdata(intf, card);
return 0;
}
static void snd_us122l_disconnect(struct usb_interface *intf)
{
struct snd_card *card;
struct us122l *us122l;
struct list_head *p;
card = usb_get_intfdata(intf);
if (!card)
return;
snd_card_disconnect(card);
us122l = US122L(card);
mutex_lock(&us122l->mutex);
us122l_stop(us122l);
mutex_unlock(&us122l->mutex);
/* release the midi resources */
list_for_each(p, &us122l->midi_list) {
snd_usbmidi_disconnect(p);
}
usb_put_intf(usb_ifnum_to_if(us122l->dev, 0));
usb_put_intf(usb_ifnum_to_if(us122l->dev, 1));
usb_put_dev(us122l->dev);
while (atomic_read(&us122l->mmap_count))
msleep(500);
snd_card_free(card);
}
static int snd_us122l_suspend(struct usb_interface *intf, pm_message_t message)
{
struct snd_card *card;
struct us122l *us122l;
struct list_head *p;
card = usb_get_intfdata(intf);
if (!card)
return 0;
snd_power_change_state(card, SNDRV_CTL_POWER_D3hot);
us122l = US122L(card);
if (!us122l)
return 0;
list_for_each(p, &us122l->midi_list)
snd_usbmidi_input_stop(p);
mutex_lock(&us122l->mutex);
usb_stream_stop(&us122l->sk);
mutex_unlock(&us122l->mutex);
return 0;
}
static int snd_us122l_resume(struct usb_interface *intf)
{
struct snd_card *card;
struct us122l *us122l;
struct list_head *p;
int err;
card = usb_get_intfdata(intf);
if (!card)
return 0;
us122l = US122L(card);
if (!us122l)
return 0;
mutex_lock(&us122l->mutex);
/* needed, doesn't restart without: */
if (us122l->dev->descriptor.idProduct == USB_ID_US144 ||
us122l->dev->descriptor.idProduct == USB_ID_US144MKII) {
err = usb_set_interface(us122l->dev, 0, 1);
if (err) {
snd_printk(KERN_ERR "usb_set_interface error \n");
goto unlock;
}
}
err = usb_set_interface(us122l->dev, 1, 1);
if (err) {
snd_printk(KERN_ERR "usb_set_interface error \n");
goto unlock;
}
pt_info_set(us122l->dev, 0x11);
pt_info_set(us122l->dev, 0x10);
err = us122l_set_sample_rate(us122l->dev,
us122l->sk.s->cfg.sample_rate);
if (err < 0) {
snd_printk(KERN_ERR "us122l_set_sample_rate error \n");
goto unlock;
}
err = usb_stream_start(&us122l->sk);
if (err)
goto unlock;
list_for_each(p, &us122l->midi_list)
snd_usbmidi_input_start(p);
unlock:
mutex_unlock(&us122l->mutex);
snd_power_change_state(card, SNDRV_CTL_POWER_D0);
return err;
}
static struct usb_device_id snd_us122l_usb_id_table[] = {
{
.match_flags = USB_DEVICE_ID_MATCH_DEVICE,
.idVendor = 0x0644,
.idProduct = USB_ID_US122L
},
{ /* US-144 only works at USB1.1! Disable module ehci-hcd. */
.match_flags = USB_DEVICE_ID_MATCH_DEVICE,
.idVendor = 0x0644,
.idProduct = USB_ID_US144
},
{
.match_flags = USB_DEVICE_ID_MATCH_DEVICE,
.idVendor = 0x0644,
.idProduct = USB_ID_US122MKII
},
{
.match_flags = USB_DEVICE_ID_MATCH_DEVICE,
.idVendor = 0x0644,
.idProduct = USB_ID_US144MKII
},
{ /* terminator */ }
};
MODULE_DEVICE_TABLE(usb, snd_us122l_usb_id_table);
static struct usb_driver snd_us122l_usb_driver = {
.name = "snd-usb-us122l",
.probe = snd_us122l_probe,
.disconnect = snd_us122l_disconnect,
.suspend = snd_us122l_suspend,
.resume = snd_us122l_resume,
.reset_resume = snd_us122l_resume,
.id_table = snd_us122l_usb_id_table,
.supports_autosuspend = 1
};
module_usb_driver(snd_us122l_usb_driver);

31
sound/usb/usx2y/us122l.h Normal file
View file

@ -0,0 +1,31 @@
#ifndef US122L_H
#define US122L_H
struct us122l {
struct usb_device *dev;
int card_index;
int stride;
struct usb_stream_kernel sk;
struct mutex mutex;
struct file *first;
unsigned second_periods_polled;
struct file *master;
struct file *slave;
struct list_head midi_list;
atomic_t mmap_count;
};
#define US122L(c) ((struct us122l *)(c)->private_data)
#define NAME_ALLCAPS "US-122L"
#define USB_ID_US122L 0x800E
#define USB_ID_US144 0x800F
#define USB_ID_US122MKII 0x8021
#define USB_ID_US144MKII 0x8020
#endif

View file

@ -0,0 +1,265 @@
/*
* Driver for Tascam US-X2Y USB soundcards
*
* FPGA Loader + ALSA Startup
*
* Copyright (c) 2003 by Karsten Wiese <annabellesgarden@yahoo.de>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <linux/interrupt.h>
#include <linux/slab.h>
#include <linux/usb.h>
#include <sound/core.h>
#include <sound/memalloc.h>
#include <sound/pcm.h>
#include <sound/hwdep.h>
#include "usx2y.h"
#include "usbusx2y.h"
#include "usX2Yhwdep.h"
static int snd_us428ctls_vm_fault(struct vm_area_struct *area,
struct vm_fault *vmf)
{
unsigned long offset;
struct page * page;
void *vaddr;
snd_printdd("ENTER, start %lXh, pgoff %ld\n",
area->vm_start,
vmf->pgoff);
offset = vmf->pgoff << PAGE_SHIFT;
vaddr = (char*)((struct usX2Ydev *)area->vm_private_data)->us428ctls_sharedmem + offset;
page = virt_to_page(vaddr);
get_page(page);
vmf->page = page;
snd_printdd("vaddr=%p made us428ctls_vm_fault() page %p\n",
vaddr, page);
return 0;
}
static const struct vm_operations_struct us428ctls_vm_ops = {
.fault = snd_us428ctls_vm_fault,
};
static int snd_us428ctls_mmap(struct snd_hwdep * hw, struct file *filp, struct vm_area_struct *area)
{
unsigned long size = (unsigned long)(area->vm_end - area->vm_start);
struct usX2Ydev *us428 = hw->private_data;
// FIXME this hwdep interface is used twice: fpga download and mmap for controlling Lights etc. Maybe better using 2 hwdep devs?
// so as long as the device isn't fully initialised yet we return -EBUSY here.
if (!(us428->chip_status & USX2Y_STAT_CHIP_INIT))
return -EBUSY;
/* if userspace tries to mmap beyond end of our buffer, fail */
if (size > PAGE_ALIGN(sizeof(struct us428ctls_sharedmem))) {
snd_printd( "%lu > %lu\n", size, (unsigned long)sizeof(struct us428ctls_sharedmem));
return -EINVAL;
}
if (!us428->us428ctls_sharedmem) {
init_waitqueue_head(&us428->us428ctls_wait_queue_head);
if(!(us428->us428ctls_sharedmem = snd_malloc_pages(sizeof(struct us428ctls_sharedmem), GFP_KERNEL)))
return -ENOMEM;
memset(us428->us428ctls_sharedmem, -1, sizeof(struct us428ctls_sharedmem));
us428->us428ctls_sharedmem->CtlSnapShotLast = -2;
}
area->vm_ops = &us428ctls_vm_ops;
area->vm_flags |= VM_DONTEXPAND | VM_DONTDUMP;
area->vm_private_data = hw->private_data;
return 0;
}
static unsigned int snd_us428ctls_poll(struct snd_hwdep *hw, struct file *file, poll_table *wait)
{
unsigned int mask = 0;
struct usX2Ydev *us428 = hw->private_data;
struct us428ctls_sharedmem *shm = us428->us428ctls_sharedmem;
if (us428->chip_status & USX2Y_STAT_CHIP_HUP)
return POLLHUP;
poll_wait(file, &us428->us428ctls_wait_queue_head, wait);
if (shm != NULL && shm->CtlSnapShotLast != shm->CtlSnapShotRed)
mask |= POLLIN;
return mask;
}
static int snd_usX2Y_hwdep_dsp_status(struct snd_hwdep *hw,
struct snd_hwdep_dsp_status *info)
{
static char *type_ids[USX2Y_TYPE_NUMS] = {
[USX2Y_TYPE_122] = "us122",
[USX2Y_TYPE_224] = "us224",
[USX2Y_TYPE_428] = "us428",
};
struct usX2Ydev *us428 = hw->private_data;
int id = -1;
switch (le16_to_cpu(us428->dev->descriptor.idProduct)) {
case USB_ID_US122:
id = USX2Y_TYPE_122;
break;
case USB_ID_US224:
id = USX2Y_TYPE_224;
break;
case USB_ID_US428:
id = USX2Y_TYPE_428;
break;
}
if (0 > id)
return -ENODEV;
strcpy(info->id, type_ids[id]);
info->num_dsps = 2; // 0: Prepad Data, 1: FPGA Code
if (us428->chip_status & USX2Y_STAT_CHIP_INIT)
info->chip_ready = 1;
info->version = USX2Y_DRIVER_VERSION;
return 0;
}
static int usX2Y_create_usbmidi(struct snd_card *card)
{
static struct snd_usb_midi_endpoint_info quirk_data_1 = {
.out_ep = 0x06,
.in_ep = 0x06,
.out_cables = 0x001,
.in_cables = 0x001
};
static struct snd_usb_audio_quirk quirk_1 = {
.vendor_name = "TASCAM",
.product_name = NAME_ALLCAPS,
.ifnum = 0,
.type = QUIRK_MIDI_FIXED_ENDPOINT,
.data = &quirk_data_1
};
static struct snd_usb_midi_endpoint_info quirk_data_2 = {
.out_ep = 0x06,
.in_ep = 0x06,
.out_cables = 0x003,
.in_cables = 0x003
};
static struct snd_usb_audio_quirk quirk_2 = {
.vendor_name = "TASCAM",
.product_name = "US428",
.ifnum = 0,
.type = QUIRK_MIDI_FIXED_ENDPOINT,
.data = &quirk_data_2
};
struct usb_device *dev = usX2Y(card)->dev;
struct usb_interface *iface = usb_ifnum_to_if(dev, 0);
struct snd_usb_audio_quirk *quirk =
le16_to_cpu(dev->descriptor.idProduct) == USB_ID_US428 ?
&quirk_2 : &quirk_1;
snd_printdd("usX2Y_create_usbmidi \n");
return snd_usbmidi_create(card, iface, &usX2Y(card)->midi_list, quirk);
}
static int usX2Y_create_alsa_devices(struct snd_card *card)
{
int err;
do {
if ((err = usX2Y_create_usbmidi(card)) < 0) {
snd_printk(KERN_ERR "usX2Y_create_alsa_devices: usX2Y_create_usbmidi error %i \n", err);
break;
}
if ((err = usX2Y_audio_create(card)) < 0)
break;
if ((err = usX2Y_hwdep_pcm_new(card)) < 0)
break;
if ((err = snd_card_register(card)) < 0)
break;
} while (0);
return err;
}
static int snd_usX2Y_hwdep_dsp_load(struct snd_hwdep *hw,
struct snd_hwdep_dsp_image *dsp)
{
struct usX2Ydev *priv = hw->private_data;
int lret, err = -EINVAL;
snd_printdd( "dsp_load %s\n", dsp->name);
if (access_ok(VERIFY_READ, dsp->image, dsp->length)) {
struct usb_device* dev = priv->dev;
char *buf;
buf = memdup_user(dsp->image, dsp->length);
if (IS_ERR(buf))
return PTR_ERR(buf);
err = usb_set_interface(dev, 0, 1);
if (err)
snd_printk(KERN_ERR "usb_set_interface error \n");
else
err = usb_bulk_msg(dev, usb_sndbulkpipe(dev, 2), buf, dsp->length, &lret, 6000);
kfree(buf);
}
if (err)
return err;
if (dsp->index == 1) {
msleep(250); // give the device some time
err = usX2Y_AsyncSeq04_init(priv);
if (err) {
snd_printk(KERN_ERR "usX2Y_AsyncSeq04_init error \n");
return err;
}
err = usX2Y_In04_init(priv);
if (err) {
snd_printk(KERN_ERR "usX2Y_In04_init error \n");
return err;
}
err = usX2Y_create_alsa_devices(hw->card);
if (err) {
snd_printk(KERN_ERR "usX2Y_create_alsa_devices error %i \n", err);
snd_card_free(hw->card);
return err;
}
priv->chip_status |= USX2Y_STAT_CHIP_INIT;
snd_printdd("%s: alsa all started\n", hw->name);
}
return err;
}
int usX2Y_hwdep_new(struct snd_card *card, struct usb_device* device)
{
int err;
struct snd_hwdep *hw;
if ((err = snd_hwdep_new(card, SND_USX2Y_LOADER_ID, 0, &hw)) < 0)
return err;
hw->iface = SNDRV_HWDEP_IFACE_USX2Y;
hw->private_data = usX2Y(card);
hw->ops.dsp_status = snd_usX2Y_hwdep_dsp_status;
hw->ops.dsp_load = snd_usX2Y_hwdep_dsp_load;
hw->ops.mmap = snd_us428ctls_mmap;
hw->ops.poll = snd_us428ctls_poll;
hw->exclusive = 1;
sprintf(hw->name, "/proc/bus/usb/%03d/%03d", device->bus->busnum, device->devnum);
return 0;
}

View file

@ -0,0 +1,6 @@
#ifndef USX2YHWDEP_H
#define USX2YHWDEP_H
int usX2Y_hwdep_new(struct snd_card *card, struct usb_device* device);
#endif

View file

@ -0,0 +1,753 @@
/*
* Copyright (C) 2007, 2008 Karsten Wiese <fzu@wemgehoertderstaat.de>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include <linux/usb.h>
#include <linux/gfp.h>
#include "usb_stream.h"
/* setup */
static unsigned usb_stream_next_packet_size(struct usb_stream_kernel *sk)
{
struct usb_stream *s = sk->s;
sk->out_phase_peeked = (sk->out_phase & 0xffff) + sk->freqn;
return (sk->out_phase_peeked >> 16) * s->cfg.frame_size;
}
static void playback_prep_freqn(struct usb_stream_kernel *sk, struct urb *urb)
{
struct usb_stream *s = sk->s;
int pack, lb = 0;
for (pack = 0; pack < sk->n_o_ps; pack++) {
int l = usb_stream_next_packet_size(sk);
if (s->idle_outsize + lb + l > s->period_size)
goto check;
sk->out_phase = sk->out_phase_peeked;
urb->iso_frame_desc[pack].offset = lb;
urb->iso_frame_desc[pack].length = l;
lb += l;
}
snd_printdd(KERN_DEBUG "%i\n", lb);
check:
urb->number_of_packets = pack;
urb->transfer_buffer_length = lb;
s->idle_outsize += lb - s->period_size;
snd_printdd(KERN_DEBUG "idle=%i ul=%i ps=%i\n", s->idle_outsize,
lb, s->period_size);
}
static void init_pipe_urbs(struct usb_stream_kernel *sk, unsigned use_packsize,
struct urb **urbs, char *transfer,
struct usb_device *dev, int pipe)
{
int u, p;
int maxpacket = use_packsize ?
use_packsize : usb_maxpacket(dev, pipe, usb_pipeout(pipe));
int transfer_length = maxpacket * sk->n_o_ps;
for (u = 0; u < USB_STREAM_NURBS;
++u, transfer += transfer_length) {
struct urb *urb = urbs[u];
struct usb_iso_packet_descriptor *desc;
urb->transfer_buffer = transfer;
urb->dev = dev;
urb->pipe = pipe;
urb->number_of_packets = sk->n_o_ps;
urb->context = sk;
urb->interval = 1;
if (usb_pipeout(pipe))
continue;
urb->transfer_buffer_length = transfer_length;
desc = urb->iso_frame_desc;
desc->offset = 0;
desc->length = maxpacket;
for (p = 1; p < sk->n_o_ps; ++p) {
desc[p].offset = desc[p - 1].offset + maxpacket;
desc[p].length = maxpacket;
}
}
}
static void init_urbs(struct usb_stream_kernel *sk, unsigned use_packsize,
struct usb_device *dev, int in_pipe, int out_pipe)
{
struct usb_stream *s = sk->s;
char *indata = (char *)s + sizeof(*s) +
sizeof(struct usb_stream_packet) *
s->inpackets;
int u;
for (u = 0; u < USB_STREAM_NURBS; ++u) {
sk->inurb[u] = usb_alloc_urb(sk->n_o_ps, GFP_KERNEL);
sk->outurb[u] = usb_alloc_urb(sk->n_o_ps, GFP_KERNEL);
}
init_pipe_urbs(sk, use_packsize, sk->inurb, indata, dev, in_pipe);
init_pipe_urbs(sk, use_packsize, sk->outurb, sk->write_page, dev,
out_pipe);
}
/*
* convert a sampling rate into our full speed format (fs/1000 in Q16.16)
* this will overflow at approx 524 kHz
*/
static inline unsigned get_usb_full_speed_rate(unsigned rate)
{
return ((rate << 13) + 62) / 125;
}
/*
* convert a sampling rate into USB high speed format (fs/8000 in Q16.16)
* this will overflow at approx 4 MHz
*/
static inline unsigned get_usb_high_speed_rate(unsigned rate)
{
return ((rate << 10) + 62) / 125;
}
void usb_stream_free(struct usb_stream_kernel *sk)
{
struct usb_stream *s;
unsigned u;
for (u = 0; u < USB_STREAM_NURBS; ++u) {
usb_free_urb(sk->inurb[u]);
sk->inurb[u] = NULL;
usb_free_urb(sk->outurb[u]);
sk->outurb[u] = NULL;
}
s = sk->s;
if (!s)
return;
free_pages((unsigned long)sk->write_page, get_order(s->write_size));
sk->write_page = NULL;
free_pages((unsigned long)s, get_order(s->read_size));
sk->s = NULL;
}
struct usb_stream *usb_stream_new(struct usb_stream_kernel *sk,
struct usb_device *dev,
unsigned in_endpoint, unsigned out_endpoint,
unsigned sample_rate, unsigned use_packsize,
unsigned period_frames, unsigned frame_size)
{
int packets, max_packsize;
int in_pipe, out_pipe;
int read_size = sizeof(struct usb_stream);
int write_size;
int usb_frames = dev->speed == USB_SPEED_HIGH ? 8000 : 1000;
int pg;
in_pipe = usb_rcvisocpipe(dev, in_endpoint);
out_pipe = usb_sndisocpipe(dev, out_endpoint);
max_packsize = use_packsize ?
use_packsize : usb_maxpacket(dev, in_pipe, 0);
/*
t_period = period_frames / sample_rate
iso_packs = t_period / t_iso_frame
= (period_frames / sample_rate) * (1 / t_iso_frame)
*/
packets = period_frames * usb_frames / sample_rate + 1;
if (dev->speed == USB_SPEED_HIGH)
packets = (packets + 7) & ~7;
read_size += packets * USB_STREAM_URBDEPTH *
(max_packsize + sizeof(struct usb_stream_packet));
max_packsize = usb_maxpacket(dev, out_pipe, 1);
write_size = max_packsize * packets * USB_STREAM_URBDEPTH;
if (read_size >= 256*PAGE_SIZE || write_size >= 256*PAGE_SIZE) {
snd_printk(KERN_WARNING "a size exceeds 128*PAGE_SIZE\n");
goto out;
}
pg = get_order(read_size);
sk->s = (void *) __get_free_pages(GFP_KERNEL|__GFP_COMP|__GFP_ZERO, pg);
if (!sk->s) {
snd_printk(KERN_WARNING "couldn't __get_free_pages()\n");
goto out;
}
sk->s->cfg.version = USB_STREAM_INTERFACE_VERSION;
sk->s->read_size = read_size;
sk->s->cfg.sample_rate = sample_rate;
sk->s->cfg.frame_size = frame_size;
sk->n_o_ps = packets;
sk->s->inpackets = packets * USB_STREAM_URBDEPTH;
sk->s->cfg.period_frames = period_frames;
sk->s->period_size = frame_size * period_frames;
sk->s->write_size = write_size;
pg = get_order(write_size);
sk->write_page =
(void *)__get_free_pages(GFP_KERNEL|__GFP_COMP|__GFP_ZERO, pg);
if (!sk->write_page) {
snd_printk(KERN_WARNING "couldn't __get_free_pages()\n");
usb_stream_free(sk);
return NULL;
}
/* calculate the frequency in 16.16 format */
if (dev->speed == USB_SPEED_FULL)
sk->freqn = get_usb_full_speed_rate(sample_rate);
else
sk->freqn = get_usb_high_speed_rate(sample_rate);
init_urbs(sk, use_packsize, dev, in_pipe, out_pipe);
sk->s->state = usb_stream_stopped;
out:
return sk->s;
}
/* start */
static bool balance_check(struct usb_stream_kernel *sk, struct urb *urb)
{
bool r;
if (unlikely(urb->status)) {
if (urb->status != -ESHUTDOWN && urb->status != -ENOENT)
snd_printk(KERN_WARNING "status=%i\n", urb->status);
sk->iso_frame_balance = 0x7FFFFFFF;
return false;
}
r = sk->iso_frame_balance == 0;
if (!r)
sk->i_urb = urb;
return r;
}
static bool balance_playback(struct usb_stream_kernel *sk, struct urb *urb)
{
sk->iso_frame_balance += urb->number_of_packets;
return balance_check(sk, urb);
}
static bool balance_capture(struct usb_stream_kernel *sk, struct urb *urb)
{
sk->iso_frame_balance -= urb->number_of_packets;
return balance_check(sk, urb);
}
static void subs_set_complete(struct urb **urbs, void (*complete)(struct urb *))
{
int u;
for (u = 0; u < USB_STREAM_NURBS; u++) {
struct urb *urb = urbs[u];
urb->complete = complete;
}
}
static int usb_stream_prepare_playback(struct usb_stream_kernel *sk,
struct urb *inurb)
{
struct usb_stream *s = sk->s;
struct urb *io;
struct usb_iso_packet_descriptor *id, *od;
int p = 0, lb = 0, l = 0;
io = sk->idle_outurb;
od = io->iso_frame_desc;
for (; s->sync_packet < 0; ++p, ++s->sync_packet) {
struct urb *ii = sk->completed_inurb;
id = ii->iso_frame_desc +
ii->number_of_packets + s->sync_packet;
l = id->actual_length;
od[p].length = l;
od[p].offset = lb;
lb += l;
}
for (;
s->sync_packet < inurb->number_of_packets && p < sk->n_o_ps;
++p, ++s->sync_packet) {
l = inurb->iso_frame_desc[s->sync_packet].actual_length;
if (s->idle_outsize + lb + l > s->period_size)
goto check_ok;
od[p].length = l;
od[p].offset = lb;
lb += l;
}
check_ok:
s->sync_packet -= inurb->number_of_packets;
if (unlikely(s->sync_packet < -2 || s->sync_packet > 0)) {
snd_printk(KERN_WARNING "invalid sync_packet = %i;"
" p=%i nop=%i %i %x %x %x > %x\n",
s->sync_packet, p, inurb->number_of_packets,
s->idle_outsize + lb + l,
s->idle_outsize, lb, l,
s->period_size);
return -1;
}
if (unlikely(lb % s->cfg.frame_size)) {
snd_printk(KERN_WARNING"invalid outsize = %i\n",
lb);
return -1;
}
s->idle_outsize += lb - s->period_size;
io->number_of_packets = p;
io->transfer_buffer_length = lb;
if (s->idle_outsize <= 0)
return 0;
snd_printk(KERN_WARNING "idle=%i\n", s->idle_outsize);
return -1;
}
static void prepare_inurb(int number_of_packets, struct urb *iu)
{
struct usb_iso_packet_descriptor *id;
int p;
iu->number_of_packets = number_of_packets;
id = iu->iso_frame_desc;
id->offset = 0;
for (p = 0; p < iu->number_of_packets - 1; ++p)
id[p + 1].offset = id[p].offset + id[p].length;
iu->transfer_buffer_length =
id[0].length * iu->number_of_packets;
}
static int submit_urbs(struct usb_stream_kernel *sk,
struct urb *inurb, struct urb *outurb)
{
int err;
prepare_inurb(sk->idle_outurb->number_of_packets, sk->idle_inurb);
err = usb_submit_urb(sk->idle_inurb, GFP_ATOMIC);
if (err < 0) {
snd_printk(KERN_ERR "%i\n", err);
return err;
}
sk->idle_inurb = sk->completed_inurb;
sk->completed_inurb = inurb;
err = usb_submit_urb(sk->idle_outurb, GFP_ATOMIC);
if (err < 0) {
snd_printk(KERN_ERR "%i\n", err);
return err;
}
sk->idle_outurb = sk->completed_outurb;
sk->completed_outurb = outurb;
return 0;
}
#ifdef DEBUG_LOOP_BACK
/*
This loop_back() shows how to read/write the period data.
*/
static void loop_back(struct usb_stream *s)
{
char *i, *o;
int il, ol, l, p;
struct urb *iu;
struct usb_iso_packet_descriptor *id;
o = s->playback1st_to;
ol = s->playback1st_size;
l = 0;
if (s->insplit_pack >= 0) {
iu = sk->idle_inurb;
id = iu->iso_frame_desc;
p = s->insplit_pack;
} else
goto second;
loop:
for (; p < iu->number_of_packets && l < s->period_size; ++p) {
i = iu->transfer_buffer + id[p].offset;
il = id[p].actual_length;
if (l + il > s->period_size)
il = s->period_size - l;
if (il <= ol) {
memcpy(o, i, il);
o += il;
ol -= il;
} else {
memcpy(o, i, ol);
singen_6pack(o, ol);
o = s->playback_to;
memcpy(o, i + ol, il - ol);
o += il - ol;
ol = s->period_size - s->playback1st_size;
}
l += il;
}
if (iu == sk->completed_inurb) {
if (l != s->period_size)
printk(KERN_DEBUG"%s:%i %i\n", __func__, __LINE__,
l/(int)s->cfg.frame_size);
return;
}
second:
iu = sk->completed_inurb;
id = iu->iso_frame_desc;
p = 0;
goto loop;
}
#else
static void loop_back(struct usb_stream *s)
{
}
#endif
static void stream_idle(struct usb_stream_kernel *sk,
struct urb *inurb, struct urb *outurb)
{
struct usb_stream *s = sk->s;
int l, p;
int insize = s->idle_insize;
int urb_size = 0;
s->inpacket_split = s->next_inpacket_split;
s->inpacket_split_at = s->next_inpacket_split_at;
s->next_inpacket_split = -1;
s->next_inpacket_split_at = 0;
for (p = 0; p < inurb->number_of_packets; ++p) {
struct usb_iso_packet_descriptor *id = inurb->iso_frame_desc;
l = id[p].actual_length;
if (unlikely(l == 0 || id[p].status)) {
snd_printk(KERN_WARNING "underrun, status=%u\n",
id[p].status);
goto err_out;
}
s->inpacket_head++;
s->inpacket_head %= s->inpackets;
if (s->inpacket_split == -1)
s->inpacket_split = s->inpacket_head;
s->inpacket[s->inpacket_head].offset =
id[p].offset + (inurb->transfer_buffer - (void *)s);
s->inpacket[s->inpacket_head].length = l;
if (insize + l > s->period_size &&
s->next_inpacket_split == -1) {
s->next_inpacket_split = s->inpacket_head;
s->next_inpacket_split_at = s->period_size - insize;
}
insize += l;
urb_size += l;
}
s->idle_insize += urb_size - s->period_size;
if (s->idle_insize < 0) {
snd_printk(KERN_WARNING "%i\n",
(s->idle_insize)/(int)s->cfg.frame_size);
goto err_out;
}
s->insize_done += urb_size;
l = s->idle_outsize;
s->outpacket[0].offset = (sk->idle_outurb->transfer_buffer -
sk->write_page) - l;
if (usb_stream_prepare_playback(sk, inurb) < 0)
goto err_out;
s->outpacket[0].length = sk->idle_outurb->transfer_buffer_length + l;
s->outpacket[1].offset = sk->completed_outurb->transfer_buffer -
sk->write_page;
if (submit_urbs(sk, inurb, outurb) < 0)
goto err_out;
loop_back(s);
s->periods_done++;
wake_up_all(&sk->sleep);
return;
err_out:
s->state = usb_stream_xrun;
wake_up_all(&sk->sleep);
}
static void i_capture_idle(struct urb *urb)
{
struct usb_stream_kernel *sk = urb->context;
if (balance_capture(sk, urb))
stream_idle(sk, urb, sk->i_urb);
}
static void i_playback_idle(struct urb *urb)
{
struct usb_stream_kernel *sk = urb->context;
if (balance_playback(sk, urb))
stream_idle(sk, sk->i_urb, urb);
}
static void stream_start(struct usb_stream_kernel *sk,
struct urb *inurb, struct urb *outurb)
{
struct usb_stream *s = sk->s;
if (s->state >= usb_stream_sync1) {
int l, p, max_diff, max_diff_0;
int urb_size = 0;
unsigned frames_per_packet, min_frames = 0;
frames_per_packet = (s->period_size - s->idle_insize);
frames_per_packet <<= 8;
frames_per_packet /=
s->cfg.frame_size * inurb->number_of_packets;
frames_per_packet++;
max_diff_0 = s->cfg.frame_size;
if (s->cfg.period_frames >= 256)
max_diff_0 <<= 1;
if (s->cfg.period_frames >= 1024)
max_diff_0 <<= 1;
max_diff = max_diff_0;
for (p = 0; p < inurb->number_of_packets; ++p) {
int diff;
l = inurb->iso_frame_desc[p].actual_length;
urb_size += l;
min_frames += frames_per_packet;
diff = urb_size -
(min_frames >> 8) * s->cfg.frame_size;
if (diff < max_diff) {
snd_printdd(KERN_DEBUG "%i %i %i %i\n",
s->insize_done,
urb_size / (int)s->cfg.frame_size,
inurb->number_of_packets, diff);
max_diff = diff;
}
}
s->idle_insize -= max_diff - max_diff_0;
s->idle_insize += urb_size - s->period_size;
if (s->idle_insize < 0) {
snd_printk(KERN_WARNING "%i %i %i\n",
s->idle_insize, urb_size, s->period_size);
return;
} else if (s->idle_insize == 0) {
s->next_inpacket_split =
(s->inpacket_head + 1) % s->inpackets;
s->next_inpacket_split_at = 0;
} else {
unsigned split = s->inpacket_head;
l = s->idle_insize;
while (l > s->inpacket[split].length) {
l -= s->inpacket[split].length;
if (split == 0)
split = s->inpackets - 1;
else
split--;
}
s->next_inpacket_split = split;
s->next_inpacket_split_at =
s->inpacket[split].length - l;
}
s->insize_done += urb_size;
if (usb_stream_prepare_playback(sk, inurb) < 0)
return;
} else
playback_prep_freqn(sk, sk->idle_outurb);
if (submit_urbs(sk, inurb, outurb) < 0)
return;
if (s->state == usb_stream_sync1 && s->insize_done > 360000) {
/* just guesswork ^^^^^^ */
s->state = usb_stream_ready;
subs_set_complete(sk->inurb, i_capture_idle);
subs_set_complete(sk->outurb, i_playback_idle);
}
}
static void i_capture_start(struct urb *urb)
{
struct usb_iso_packet_descriptor *id = urb->iso_frame_desc;
struct usb_stream_kernel *sk = urb->context;
struct usb_stream *s = sk->s;
int p;
int empty = 0;
if (urb->status) {
snd_printk(KERN_WARNING "status=%i\n", urb->status);
return;
}
for (p = 0; p < urb->number_of_packets; ++p) {
int l = id[p].actual_length;
if (l < s->cfg.frame_size) {
++empty;
if (s->state >= usb_stream_sync0) {
snd_printk(KERN_WARNING "%i\n", l);
return;
}
}
s->inpacket_head++;
s->inpacket_head %= s->inpackets;
s->inpacket[s->inpacket_head].offset =
id[p].offset + (urb->transfer_buffer - (void *)s);
s->inpacket[s->inpacket_head].length = l;
}
#ifdef SHOW_EMPTY
if (empty) {
printk(KERN_DEBUG"%s:%i: %i", __func__, __LINE__,
urb->iso_frame_desc[0].actual_length);
for (pack = 1; pack < urb->number_of_packets; ++pack) {
int l = urb->iso_frame_desc[pack].actual_length;
printk(" %i", l);
}
printk("\n");
}
#endif
if (!empty && s->state < usb_stream_sync1)
++s->state;
if (balance_capture(sk, urb))
stream_start(sk, urb, sk->i_urb);
}
static void i_playback_start(struct urb *urb)
{
struct usb_stream_kernel *sk = urb->context;
if (balance_playback(sk, urb))
stream_start(sk, sk->i_urb, urb);
}
int usb_stream_start(struct usb_stream_kernel *sk)
{
struct usb_stream *s = sk->s;
int frame = 0, iters = 0;
int u, err;
int try = 0;
if (s->state != usb_stream_stopped)
return -EAGAIN;
subs_set_complete(sk->inurb, i_capture_start);
subs_set_complete(sk->outurb, i_playback_start);
memset(sk->write_page, 0, s->write_size);
dotry:
s->insize_done = 0;
s->idle_insize = 0;
s->idle_outsize = 0;
s->sync_packet = -1;
s->inpacket_head = -1;
sk->iso_frame_balance = 0;
++try;
for (u = 0; u < 2; u++) {
struct urb *inurb = sk->inurb[u];
struct urb *outurb = sk->outurb[u];
playback_prep_freqn(sk, outurb);
inurb->number_of_packets = outurb->number_of_packets;
inurb->transfer_buffer_length =
inurb->number_of_packets *
inurb->iso_frame_desc[0].length;
if (u == 0) {
int now;
struct usb_device *dev = inurb->dev;
frame = usb_get_current_frame_number(dev);
do {
now = usb_get_current_frame_number(dev);
++iters;
} while (now > -1 && now == frame);
}
err = usb_submit_urb(inurb, GFP_ATOMIC);
if (err < 0) {
snd_printk(KERN_ERR"usb_submit_urb(sk->inurb[%i])"
" returned %i\n", u, err);
return err;
}
err = usb_submit_urb(outurb, GFP_ATOMIC);
if (err < 0) {
snd_printk(KERN_ERR"usb_submit_urb(sk->outurb[%i])"
" returned %i\n", u, err);
return err;
}
if (inurb->start_frame != outurb->start_frame) {
snd_printd(KERN_DEBUG
"u[%i] start_frames differ in:%u out:%u\n",
u, inurb->start_frame, outurb->start_frame);
goto check_retry;
}
}
snd_printdd(KERN_DEBUG "%i %i\n", frame, iters);
try = 0;
check_retry:
if (try) {
usb_stream_stop(sk);
if (try < 5) {
msleep(1500);
snd_printd(KERN_DEBUG "goto dotry;\n");
goto dotry;
}
snd_printk(KERN_WARNING"couldn't start"
" all urbs on the same start_frame.\n");
return -EFAULT;
}
sk->idle_inurb = sk->inurb[USB_STREAM_NURBS - 2];
sk->idle_outurb = sk->outurb[USB_STREAM_NURBS - 2];
sk->completed_inurb = sk->inurb[USB_STREAM_NURBS - 1];
sk->completed_outurb = sk->outurb[USB_STREAM_NURBS - 1];
/* wait, check */
{
int wait_ms = 3000;
while (s->state != usb_stream_ready && wait_ms > 0) {
snd_printdd(KERN_DEBUG "%i\n", s->state);
msleep(200);
wait_ms -= 200;
}
}
return s->state == usb_stream_ready ? 0 : -EFAULT;
}
/* stop */
void usb_stream_stop(struct usb_stream_kernel *sk)
{
int u;
if (!sk->s)
return;
for (u = 0; u < USB_STREAM_NURBS; ++u) {
usb_kill_urb(sk->inurb[u]);
usb_kill_urb(sk->outurb[u]);
}
sk->s->state = usb_stream_stopped;
msleep(400);
}

View file

@ -0,0 +1,112 @@
/*
* Copyright (C) 2007, 2008 Karsten Wiese <fzu@wemgehoertderstaat.de>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#define USB_STREAM_INTERFACE_VERSION 2
#define SNDRV_USB_STREAM_IOCTL_SET_PARAMS \
_IOW('H', 0x90, struct usb_stream_config)
struct usb_stream_packet {
unsigned offset;
unsigned length;
};
struct usb_stream_config {
unsigned version;
unsigned sample_rate;
unsigned period_frames;
unsigned frame_size;
};
struct usb_stream {
struct usb_stream_config cfg;
unsigned read_size;
unsigned write_size;
int period_size;
unsigned state;
int idle_insize;
int idle_outsize;
int sync_packet;
unsigned insize_done;
unsigned periods_done;
unsigned periods_polled;
struct usb_stream_packet outpacket[2];
unsigned inpackets;
unsigned inpacket_head;
unsigned inpacket_split;
unsigned inpacket_split_at;
unsigned next_inpacket_split;
unsigned next_inpacket_split_at;
struct usb_stream_packet inpacket[0];
};
enum usb_stream_state {
usb_stream_invalid,
usb_stream_stopped,
usb_stream_sync0,
usb_stream_sync1,
usb_stream_ready,
usb_stream_running,
usb_stream_xrun,
};
#if __KERNEL__
#define USB_STREAM_NURBS 4
#define USB_STREAM_URBDEPTH 4
struct usb_stream_kernel {
struct usb_stream *s;
void *write_page;
unsigned n_o_ps;
struct urb *inurb[USB_STREAM_NURBS];
struct urb *idle_inurb;
struct urb *completed_inurb;
struct urb *outurb[USB_STREAM_NURBS];
struct urb *idle_outurb;
struct urb *completed_outurb;
struct urb *i_urb;
int iso_frame_balance;
wait_queue_head_t sleep;
unsigned out_phase;
unsigned out_phase_peeked;
unsigned freqn;
};
struct usb_stream *usb_stream_new(struct usb_stream_kernel *sk,
struct usb_device *dev,
unsigned in_endpoint, unsigned out_endpoint,
unsigned sample_rate, unsigned use_packsize,
unsigned period_frames, unsigned frame_size);
void usb_stream_free(struct usb_stream_kernel *);
int usb_stream_start(struct usb_stream_kernel *);
void usb_stream_stop(struct usb_stream_kernel *);
#endif

View file

@ -0,0 +1,104 @@
/*
*
* Copyright (c) 2003 by Karsten Wiese <annabellesgarden@yahoo.de>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
enum E_In84{
eFader0 = 0,
eFader1,
eFader2,
eFader3,
eFader4,
eFader5,
eFader6,
eFader7,
eFaderM,
eTransport,
eModifier = 10,
eFilterSelect,
eSelect,
eMute,
eSwitch = 15,
eWheelGain,
eWheelFreq,
eWheelQ,
eWheelPan,
eWheel = 20
};
#define T_RECORD 1
#define T_PLAY 2
#define T_STOP 4
#define T_F_FWD 8
#define T_REW 0x10
#define T_SOLO 0x20
#define T_REC 0x40
#define T_NULL 0x80
struct us428_ctls {
unsigned char Fader[9];
unsigned char Transport;
unsigned char Modifier;
unsigned char FilterSelect;
unsigned char Select;
unsigned char Mute;
unsigned char UNKNOWN;
unsigned char Switch;
unsigned char Wheel[5];
};
struct us428_setByte {
unsigned char Offset,
Value;
};
enum {
eLT_Volume = 0,
eLT_Light
};
struct usX2Y_volume {
unsigned char Channel,
LH,
LL,
RH,
RL;
};
struct us428_lights {
struct us428_setByte Light[7];
};
struct us428_p4out {
char type;
union {
struct usX2Y_volume vol;
struct us428_lights lights;
} val;
};
#define N_us428_ctl_BUFS 16
#define N_us428_p4out_BUFS 16
struct us428ctls_sharedmem{
struct us428_ctls CtlSnapShot[N_us428_ctl_BUFS];
int CtlSnapShotDiffersAt[N_us428_ctl_BUFS];
int CtlSnapShotLast, CtlSnapShotRed;
struct us428_p4out p4out[N_us428_p4out_BUFS];
int p4outLast, p4outSent;
};

461
sound/usb/usx2y/usbusx2y.c Normal file
View file

@ -0,0 +1,461 @@
/*
* usbusy2y.c - ALSA USB US-428 Driver
*
2005-04-14 Karsten Wiese
Version 0.8.7.2:
Call snd_card_free() instead of snd_card_free_in_thread() to prevent oops with dead keyboard symptom.
Tested ok with kernel 2.6.12-rc2.
2004-12-14 Karsten Wiese
Version 0.8.7.1:
snd_pcm_open for rawusb pcm-devices now returns -EBUSY if called without rawusb's hwdep device being open.
2004-12-02 Karsten Wiese
Version 0.8.7:
Use macro usb_maxpacket() for portability.
2004-10-26 Karsten Wiese
Version 0.8.6:
wake_up() process waiting in usX2Y_urbs_start() on error.
2004-10-21 Karsten Wiese
Version 0.8.5:
nrpacks is runtime or compiletime configurable now with tested values from 1 to 4.
2004-10-03 Karsten Wiese
Version 0.8.2:
Avoid any possible racing while in prepare callback.
2004-09-30 Karsten Wiese
Version 0.8.0:
Simplified things and made ohci work again.
2004-09-20 Karsten Wiese
Version 0.7.3:
Use usb_kill_urb() instead of deprecated (kernel 2.6.9) usb_unlink_urb().
2004-07-13 Karsten Wiese
Version 0.7.1:
Don't sleep in START/STOP callbacks anymore.
us428 channels C/D not handled just for this version, sorry.
2004-06-21 Karsten Wiese
Version 0.6.4:
Temporarely suspend midi input
to sanely call usb_set_interface() when setting format.
2004-06-12 Karsten Wiese
Version 0.6.3:
Made it thus the following rule is enforced:
"All pcm substreams of one usX2Y have to operate at the same rate & format."
2004-04-06 Karsten Wiese
Version 0.6.0:
Runs on 2.6.5 kernel without any "--with-debug=" things.
us224 reported running.
2004-01-14 Karsten Wiese
Version 0.5.1:
Runs with 2.6.1 kernel.
2003-12-30 Karsten Wiese
Version 0.4.1:
Fix 24Bit 4Channel capturing for the us428.
2003-11-27 Karsten Wiese, Martin Langer
Version 0.4:
us122 support.
us224 could be tested by uncommenting the sections containing USB_ID_US224
2003-11-03 Karsten Wiese
Version 0.3:
24Bit support.
"arecord -D hw:1 -c 2 -r 48000 -M -f S24_3LE|aplay -D hw:1 -c 2 -r 48000 -M -f S24_3LE" works.
2003-08-22 Karsten Wiese
Version 0.0.8:
Removed EZUSB Firmware. First Stage Firmwaredownload is now done by tascam-firmware downloader.
See:
http://usb-midi-fw.sourceforge.net/tascam-firmware.tar.gz
2003-06-18 Karsten Wiese
Version 0.0.5:
changed to compile with kernel 2.4.21 and alsa 0.9.4
2002-10-16 Karsten Wiese
Version 0.0.4:
compiles again with alsa-current.
USB_ISO_ASAP not used anymore (most of the time), instead
urb->start_frame is calculated here now, some calls inside usb-driver don't need to happen anymore.
To get the best out of this:
Disable APM-support in the kernel as APM-BIOS calls (once each second) hard disable interrupt for many precious milliseconds.
This helped me much on my slowish PII 400 & PIII 500.
ACPI yet untested but might cause the same bad behaviour.
Use a kernel with lowlatency and preemptiv patches applied.
To autoload snd-usb-midi append a line
post-install snd-usb-us428 modprobe snd-usb-midi
to /etc/modules.conf.
known problems:
sliders, knobs, lights not yet handled except MASTER Volume slider.
"pcm -c 2" doesn't work. "pcm -c 2 -m direct_interleaved" does.
KDE3: "Enable full duplex operation" deadlocks.
2002-08-31 Karsten Wiese
Version 0.0.3: audio also simplex;
simplifying: iso urbs only 1 packet, melted structs.
ASYNC_UNLINK not used anymore: no more crashes so far.....
for alsa 0.9 rc3.
2002-08-09 Karsten Wiese
Version 0.0.2: midi works with snd-usb-midi, audio (only fullduplex now) with i.e. bristol.
The firmware has been sniffed from win2k us-428 driver 3.09.
* Copyright (c) 2002 - 2004 Karsten Wiese
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/slab.h>
#include <linux/interrupt.h>
#include <linux/usb.h>
#include <sound/core.h>
#include <sound/initval.h>
#include <sound/pcm.h>
#include <sound/rawmidi.h>
#include "usx2y.h"
#include "usbusx2y.h"
#include "usX2Yhwdep.h"
MODULE_AUTHOR("Karsten Wiese <annabellesgarden@yahoo.de>");
MODULE_DESCRIPTION("TASCAM "NAME_ALLCAPS" Version 0.8.7.2");
MODULE_LICENSE("GPL");
MODULE_SUPPORTED_DEVICE("{{TASCAM(0x1604),"NAME_ALLCAPS"(0x8001)(0x8005)(0x8007)}}");
static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 0-max */
static char* id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* Id for this card */
static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP; /* Enable this card */
module_param_array(index, int, NULL, 0444);
MODULE_PARM_DESC(index, "Index value for "NAME_ALLCAPS".");
module_param_array(id, charp, NULL, 0444);
MODULE_PARM_DESC(id, "ID string for "NAME_ALLCAPS".");
module_param_array(enable, bool, NULL, 0444);
MODULE_PARM_DESC(enable, "Enable "NAME_ALLCAPS".");
static int snd_usX2Y_card_used[SNDRV_CARDS];
static void usX2Y_usb_disconnect(struct usb_device* usb_device, void* ptr);
static void snd_usX2Y_card_private_free(struct snd_card *card);
/*
* pipe 4 is used for switching the lamps, setting samplerate, volumes ....
*/
static void i_usX2Y_Out04Int(struct urb *urb)
{
#ifdef CONFIG_SND_DEBUG
if (urb->status) {
int i;
struct usX2Ydev *usX2Y = urb->context;
for (i = 0; i < 10 && usX2Y->AS04.urb[i] != urb; i++);
snd_printdd("i_usX2Y_Out04Int() urb %i status=%i\n", i, urb->status);
}
#endif
}
static void i_usX2Y_In04Int(struct urb *urb)
{
int err = 0;
struct usX2Ydev *usX2Y = urb->context;
struct us428ctls_sharedmem *us428ctls = usX2Y->us428ctls_sharedmem;
usX2Y->In04IntCalls++;
if (urb->status) {
snd_printdd("Interrupt Pipe 4 came back with status=%i\n", urb->status);
return;
}
// printk("%i:0x%02X ", 8, (int)((unsigned char*)usX2Y->In04Buf)[8]); Master volume shows 0 here if fader is at max during boot ?!?
if (us428ctls) {
int diff = -1;
if (-2 == us428ctls->CtlSnapShotLast) {
diff = 0;
memcpy(usX2Y->In04Last, usX2Y->In04Buf, sizeof(usX2Y->In04Last));
us428ctls->CtlSnapShotLast = -1;
} else {
int i;
for (i = 0; i < 21; i++) {
if (usX2Y->In04Last[i] != ((char*)usX2Y->In04Buf)[i]) {
if (diff < 0)
diff = i;
usX2Y->In04Last[i] = ((char*)usX2Y->In04Buf)[i];
}
}
}
if (0 <= diff) {
int n = us428ctls->CtlSnapShotLast + 1;
if (n >= N_us428_ctl_BUFS || n < 0)
n = 0;
memcpy(us428ctls->CtlSnapShot + n, usX2Y->In04Buf, sizeof(us428ctls->CtlSnapShot[0]));
us428ctls->CtlSnapShotDiffersAt[n] = diff;
us428ctls->CtlSnapShotLast = n;
wake_up(&usX2Y->us428ctls_wait_queue_head);
}
}
if (usX2Y->US04) {
if (0 == usX2Y->US04->submitted)
do {
err = usb_submit_urb(usX2Y->US04->urb[usX2Y->US04->submitted++], GFP_ATOMIC);
} while (!err && usX2Y->US04->submitted < usX2Y->US04->len);
} else
if (us428ctls && us428ctls->p4outLast >= 0 && us428ctls->p4outLast < N_us428_p4out_BUFS) {
if (us428ctls->p4outLast != us428ctls->p4outSent) {
int j, send = us428ctls->p4outSent + 1;
if (send >= N_us428_p4out_BUFS)
send = 0;
for (j = 0; j < URBS_AsyncSeq && !err; ++j)
if (0 == usX2Y->AS04.urb[j]->status) {
struct us428_p4out *p4out = us428ctls->p4out + send; // FIXME if more than 1 p4out is new, 1 gets lost.
usb_fill_bulk_urb(usX2Y->AS04.urb[j], usX2Y->dev,
usb_sndbulkpipe(usX2Y->dev, 0x04), &p4out->val.vol,
p4out->type == eLT_Light ? sizeof(struct us428_lights) : 5,
i_usX2Y_Out04Int, usX2Y);
err = usb_submit_urb(usX2Y->AS04.urb[j], GFP_ATOMIC);
us428ctls->p4outSent = send;
break;
}
}
}
if (err)
snd_printk(KERN_ERR "In04Int() usb_submit_urb err=%i\n", err);
urb->dev = usX2Y->dev;
usb_submit_urb(urb, GFP_ATOMIC);
}
/*
* Prepare some urbs
*/
int usX2Y_AsyncSeq04_init(struct usX2Ydev *usX2Y)
{
int err = 0,
i;
if (NULL == (usX2Y->AS04.buffer = kmalloc(URB_DataLen_AsyncSeq*URBS_AsyncSeq, GFP_KERNEL))) {
err = -ENOMEM;
} else
for (i = 0; i < URBS_AsyncSeq; ++i) {
if (NULL == (usX2Y->AS04.urb[i] = usb_alloc_urb(0, GFP_KERNEL))) {
err = -ENOMEM;
break;
}
usb_fill_bulk_urb( usX2Y->AS04.urb[i], usX2Y->dev,
usb_sndbulkpipe(usX2Y->dev, 0x04),
usX2Y->AS04.buffer + URB_DataLen_AsyncSeq*i, 0,
i_usX2Y_Out04Int, usX2Y
);
}
return err;
}
int usX2Y_In04_init(struct usX2Ydev *usX2Y)
{
if (! (usX2Y->In04urb = usb_alloc_urb(0, GFP_KERNEL)))
return -ENOMEM;
if (! (usX2Y->In04Buf = kmalloc(21, GFP_KERNEL))) {
usb_free_urb(usX2Y->In04urb);
return -ENOMEM;
}
init_waitqueue_head(&usX2Y->In04WaitQueue);
usb_fill_int_urb(usX2Y->In04urb, usX2Y->dev, usb_rcvintpipe(usX2Y->dev, 0x4),
usX2Y->In04Buf, 21,
i_usX2Y_In04Int, usX2Y,
10);
return usb_submit_urb(usX2Y->In04urb, GFP_KERNEL);
}
static void usX2Y_unlinkSeq(struct snd_usX2Y_AsyncSeq *S)
{
int i;
for (i = 0; i < URBS_AsyncSeq; ++i) {
usb_kill_urb(S->urb[i]);
usb_free_urb(S->urb[i]);
S->urb[i] = NULL;
}
kfree(S->buffer);
}
static struct usb_device_id snd_usX2Y_usb_id_table[] = {
{
.match_flags = USB_DEVICE_ID_MATCH_DEVICE,
.idVendor = 0x1604,
.idProduct = USB_ID_US428
},
{
.match_flags = USB_DEVICE_ID_MATCH_DEVICE,
.idVendor = 0x1604,
.idProduct = USB_ID_US122
},
{
.match_flags = USB_DEVICE_ID_MATCH_DEVICE,
.idVendor = 0x1604,
.idProduct = USB_ID_US224
},
{ /* terminator */ }
};
static int usX2Y_create_card(struct usb_device *device,
struct usb_interface *intf,
struct snd_card **cardp)
{
int dev;
struct snd_card * card;
int err;
for (dev = 0; dev < SNDRV_CARDS; ++dev)
if (enable[dev] && !snd_usX2Y_card_used[dev])
break;
if (dev >= SNDRV_CARDS)
return -ENODEV;
err = snd_card_new(&intf->dev, index[dev], id[dev], THIS_MODULE,
sizeof(struct usX2Ydev), &card);
if (err < 0)
return err;
snd_usX2Y_card_used[usX2Y(card)->card_index = dev] = 1;
card->private_free = snd_usX2Y_card_private_free;
usX2Y(card)->dev = device;
init_waitqueue_head(&usX2Y(card)->prepare_wait_queue);
mutex_init(&usX2Y(card)->pcm_mutex);
INIT_LIST_HEAD(&usX2Y(card)->midi_list);
strcpy(card->driver, "USB "NAME_ALLCAPS"");
sprintf(card->shortname, "TASCAM "NAME_ALLCAPS"");
sprintf(card->longname, "%s (%x:%x if %d at %03d/%03d)",
card->shortname,
le16_to_cpu(device->descriptor.idVendor),
le16_to_cpu(device->descriptor.idProduct),
0,//us428(card)->usbmidi.ifnum,
usX2Y(card)->dev->bus->busnum, usX2Y(card)->dev->devnum
);
*cardp = card;
return 0;
}
static int usX2Y_usb_probe(struct usb_device *device,
struct usb_interface *intf,
const struct usb_device_id *device_id,
struct snd_card **cardp)
{
int err;
struct snd_card * card;
*cardp = NULL;
if (le16_to_cpu(device->descriptor.idVendor) != 0x1604 ||
(le16_to_cpu(device->descriptor.idProduct) != USB_ID_US122 &&
le16_to_cpu(device->descriptor.idProduct) != USB_ID_US224 &&
le16_to_cpu(device->descriptor.idProduct) != USB_ID_US428))
return -EINVAL;
err = usX2Y_create_card(device, intf, &card);
if (err < 0)
return err;
if ((err = usX2Y_hwdep_new(card, device)) < 0 ||
(err = snd_card_register(card)) < 0) {
snd_card_free(card);
return err;
}
*cardp = card;
return 0;
}
/*
* new 2.5 USB kernel API
*/
static int snd_usX2Y_probe(struct usb_interface *intf, const struct usb_device_id *id)
{
struct snd_card *card;
int err;
err = usX2Y_usb_probe(interface_to_usbdev(intf), intf, id, &card);
if (err < 0)
return err;
dev_set_drvdata(&intf->dev, card);
return 0;
}
static void snd_usX2Y_disconnect(struct usb_interface *intf)
{
usX2Y_usb_disconnect(interface_to_usbdev(intf),
usb_get_intfdata(intf));
}
MODULE_DEVICE_TABLE(usb, snd_usX2Y_usb_id_table);
static struct usb_driver snd_usX2Y_usb_driver = {
.name = "snd-usb-usx2y",
.probe = snd_usX2Y_probe,
.disconnect = snd_usX2Y_disconnect,
.id_table = snd_usX2Y_usb_id_table,
};
static void snd_usX2Y_card_private_free(struct snd_card *card)
{
kfree(usX2Y(card)->In04Buf);
usb_free_urb(usX2Y(card)->In04urb);
if (usX2Y(card)->us428ctls_sharedmem)
snd_free_pages(usX2Y(card)->us428ctls_sharedmem, sizeof(*usX2Y(card)->us428ctls_sharedmem));
if (usX2Y(card)->card_index >= 0 && usX2Y(card)->card_index < SNDRV_CARDS)
snd_usX2Y_card_used[usX2Y(card)->card_index] = 0;
}
/*
* Frees the device.
*/
static void usX2Y_usb_disconnect(struct usb_device *device, void* ptr)
{
if (ptr) {
struct snd_card *card = ptr;
struct usX2Ydev *usX2Y = usX2Y(card);
struct list_head *p;
usX2Y->chip_status = USX2Y_STAT_CHIP_HUP;
usX2Y_unlinkSeq(&usX2Y->AS04);
usb_kill_urb(usX2Y->In04urb);
snd_card_disconnect(card);
/* release the midi resources */
list_for_each(p, &usX2Y->midi_list) {
snd_usbmidi_disconnect(p);
}
if (usX2Y->us428ctls_sharedmem)
wake_up(&usX2Y->us428ctls_wait_queue_head);
snd_card_free(card);
}
}
module_usb_driver(snd_usX2Y_usb_driver);

View file

@ -0,0 +1,88 @@
#ifndef USBUSX2Y_H
#define USBUSX2Y_H
#include "../usbaudio.h"
#include "../midi.h"
#include "usbus428ctldefs.h"
#define NRURBS 2
#define URBS_AsyncSeq 10
#define URB_DataLen_AsyncSeq 32
struct snd_usX2Y_AsyncSeq {
struct urb *urb[URBS_AsyncSeq];
char *buffer;
};
struct snd_usX2Y_urbSeq {
int submitted;
int len;
struct urb *urb[0];
};
#include "usx2yhwdeppcm.h"
struct usX2Ydev {
struct usb_device *dev;
int card_index;
int stride;
struct urb *In04urb;
void *In04Buf;
char In04Last[24];
unsigned In04IntCalls;
struct snd_usX2Y_urbSeq *US04;
wait_queue_head_t In04WaitQueue;
struct snd_usX2Y_AsyncSeq AS04;
unsigned int rate,
format;
int chip_status;
struct mutex pcm_mutex;
struct us428ctls_sharedmem *us428ctls_sharedmem;
int wait_iso_frame;
wait_queue_head_t us428ctls_wait_queue_head;
struct snd_usX2Y_hwdep_pcm_shm *hwdep_pcm_shm;
struct snd_usX2Y_substream *subs[4];
struct snd_usX2Y_substream * volatile prepare_subs;
wait_queue_head_t prepare_wait_queue;
struct list_head midi_list;
struct list_head pcm_list;
int pcm_devs;
};
struct snd_usX2Y_substream {
struct usX2Ydev *usX2Y;
struct snd_pcm_substream *pcm_substream;
int endpoint;
unsigned int maxpacksize; /* max packet size in bytes */
atomic_t state;
#define state_STOPPED 0
#define state_STARTING1 1
#define state_STARTING2 2
#define state_STARTING3 3
#define state_PREPARED 4
#define state_PRERUNNING 6
#define state_RUNNING 8
int hwptr; /* free frame position in the buffer (only for playback) */
int hwptr_done; /* processed frame position in the buffer */
int transfer_done; /* processed frames since last period update */
struct urb *urb[NRURBS]; /* data urb table */
struct urb *completed_urb;
char *tmpbuf; /* temporary buffer for playback */
};
#define usX2Y(c) ((struct usX2Ydev *)(c)->private_data)
int usX2Y_audio_create(struct snd_card *card);
int usX2Y_AsyncSeq04_init(struct usX2Ydev *usX2Y);
int usX2Y_In04_init(struct usX2Ydev *usX2Y);
#define NAME_ALLCAPS "US-X2Y"
#endif

File diff suppressed because it is too large Load diff

51
sound/usb/usx2y/usx2y.h Normal file
View file

@ -0,0 +1,51 @@
/*
* Driver for Tascam US-X2Y USB soundcards
*
* Copyright (c) 2003 by Karsten Wiese <annabellesgarden@yahoo.de>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#ifndef __SOUND_USX2Y_COMMON_H
#define __SOUND_USX2Y_COMMON_H
#define USX2Y_DRIVER_VERSION 0x0100 /* 0.1.0 */
/* hwdep id string */
#define SND_USX2Y_LOADER_ID "USX2Y Loader"
#define SND_USX2Y_USBPCM_ID "USX2Y USBPCM"
/* hardware type */
enum {
USX2Y_TYPE_122,
USX2Y_TYPE_224,
USX2Y_TYPE_428,
USX2Y_TYPE_NUMS
};
#define USB_ID_US122 0x8007
#define USB_ID_US224 0x8005
#define USB_ID_US428 0x8001
/* chip status */
enum {
USX2Y_STAT_CHIP_INIT = (1 << 0), /* all operational */
USX2Y_STAT_CHIP_MMAP_PCM_URBS = (1 << 1), /* pcm transport over mmaped urbs */
USX2Y_STAT_CHIP_HUP = (1 << 31), /* all operational */
};
#endif /* __SOUND_USX2Y_COMMON_H */

View file

@ -0,0 +1,762 @@
/*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
/* USX2Y "rawusb" aka hwdep_pcm implementation
Its usb's unableness to atomically handle power of 2 period sized data chuncs
at standard samplerates,
what led to this part of the usx2y module:
It provides the alsa kernel half of the usx2y-alsa-jack driver pair.
The pair uses a hardware dependent alsa-device for mmaped pcm transport.
Advantage achieved:
The usb_hc moves pcm data from/into memory via DMA.
That memory is mmaped by jack's usx2y driver.
Jack's usx2y driver is the first/last to read/write pcm data.
Read/write is a combination of power of 2 period shaping and
float/int conversation.
Compared to mainline alsa/jack we leave out power of 2 period shaping inside
snd-usb-usx2y which needs memcpy() and additional buffers.
As a side effect possible unwanted pcm-data coruption resulting of
standard alsa's snd-usb-usx2y period shaping scheme falls away.
Result is sane jack operation at buffering schemes down to 128frames,
2 periods.
plain usx2y alsa mode is able to achieve 64frames, 4periods, but only at the
cost of easier triggered i.e. aeolus xruns (128 or 256frames,
2periods works but is useless cause of crackling).
This is a first "proof of concept" implementation.
Later, functionalities should migrate to more appropriate places:
Userland:
- The jackd could mmap its float-pcm buffers directly from alsa-lib.
- alsa-lib could provide power of 2 period sized shaping combined with int/float
conversation.
Currently the usx2y jack driver provides above 2 services.
Kernel:
- rawusb dma pcm buffer transport should go to snd-usb-lib, so also snd-usb-audio
devices can use it.
Currently rawusb dma pcm buffer transport (this file) is only available to snd-usb-usx2y.
*/
#include <linux/delay.h>
#include <linux/gfp.h>
#include "usbusx2yaudio.c"
#if defined(USX2Y_NRPACKS_VARIABLE) || USX2Y_NRPACKS == 1
#include <sound/hwdep.h>
static int usX2Y_usbpcm_urb_capt_retire(struct snd_usX2Y_substream *subs)
{
struct urb *urb = subs->completed_urb;
struct snd_pcm_runtime *runtime = subs->pcm_substream->runtime;
int i, lens = 0, hwptr_done = subs->hwptr_done;
struct usX2Ydev *usX2Y = subs->usX2Y;
if (0 > usX2Y->hwdep_pcm_shm->capture_iso_start) { //FIXME
int head = usX2Y->hwdep_pcm_shm->captured_iso_head + 1;
if (head >= ARRAY_SIZE(usX2Y->hwdep_pcm_shm->captured_iso))
head = 0;
usX2Y->hwdep_pcm_shm->capture_iso_start = head;
snd_printdd("cap start %i\n", head);
}
for (i = 0; i < nr_of_packs(); i++) {
if (urb->iso_frame_desc[i].status) { /* active? hmm, skip this */
snd_printk(KERN_ERR "active frame status %i. Most probably some hardware problem.\n", urb->iso_frame_desc[i].status);
return urb->iso_frame_desc[i].status;
}
lens += urb->iso_frame_desc[i].actual_length / usX2Y->stride;
}
if ((hwptr_done += lens) >= runtime->buffer_size)
hwptr_done -= runtime->buffer_size;
subs->hwptr_done = hwptr_done;
subs->transfer_done += lens;
/* update the pointer, call callback if necessary */
if (subs->transfer_done >= runtime->period_size) {
subs->transfer_done -= runtime->period_size;
snd_pcm_period_elapsed(subs->pcm_substream);
}
return 0;
}
static inline int usX2Y_iso_frames_per_buffer(struct snd_pcm_runtime *runtime,
struct usX2Ydev * usX2Y)
{
return (runtime->buffer_size * 1000) / usX2Y->rate + 1; //FIXME: so far only correct period_size == 2^x ?
}
/*
* prepare urb for playback data pipe
*
* we copy the data directly from the pcm buffer.
* the current position to be copied is held in hwptr field.
* since a urb can handle only a single linear buffer, if the total
* transferred area overflows the buffer boundary, we cannot send
* it directly from the buffer. thus the data is once copied to
* a temporary buffer and urb points to that.
*/
static int usX2Y_hwdep_urb_play_prepare(struct snd_usX2Y_substream *subs,
struct urb *urb)
{
int count, counts, pack;
struct usX2Ydev *usX2Y = subs->usX2Y;
struct snd_usX2Y_hwdep_pcm_shm *shm = usX2Y->hwdep_pcm_shm;
struct snd_pcm_runtime *runtime = subs->pcm_substream->runtime;
if (0 > shm->playback_iso_start) {
shm->playback_iso_start = shm->captured_iso_head -
usX2Y_iso_frames_per_buffer(runtime, usX2Y);
if (0 > shm->playback_iso_start)
shm->playback_iso_start += ARRAY_SIZE(shm->captured_iso);
shm->playback_iso_head = shm->playback_iso_start;
}
count = 0;
for (pack = 0; pack < nr_of_packs(); pack++) {
/* calculate the size of a packet */
counts = shm->captured_iso[shm->playback_iso_head].length / usX2Y->stride;
if (counts < 43 || counts > 50) {
snd_printk(KERN_ERR "should not be here with counts=%i\n", counts);
return -EPIPE;
}
/* set up descriptor */
urb->iso_frame_desc[pack].offset = shm->captured_iso[shm->playback_iso_head].offset;
urb->iso_frame_desc[pack].length = shm->captured_iso[shm->playback_iso_head].length;
if (atomic_read(&subs->state) != state_RUNNING)
memset((char *)urb->transfer_buffer + urb->iso_frame_desc[pack].offset, 0,
urb->iso_frame_desc[pack].length);
if (++shm->playback_iso_head >= ARRAY_SIZE(shm->captured_iso))
shm->playback_iso_head = 0;
count += counts;
}
urb->transfer_buffer_length = count * usX2Y->stride;
return 0;
}
static inline void usX2Y_usbpcm_urb_capt_iso_advance(struct snd_usX2Y_substream *subs,
struct urb *urb)
{
int pack;
for (pack = 0; pack < nr_of_packs(); ++pack) {
struct usb_iso_packet_descriptor *desc = urb->iso_frame_desc + pack;
if (NULL != subs) {
struct snd_usX2Y_hwdep_pcm_shm *shm = subs->usX2Y->hwdep_pcm_shm;
int head = shm->captured_iso_head + 1;
if (head >= ARRAY_SIZE(shm->captured_iso))
head = 0;
shm->captured_iso[head].frame = urb->start_frame + pack;
shm->captured_iso[head].offset = desc->offset;
shm->captured_iso[head].length = desc->actual_length;
shm->captured_iso_head = head;
shm->captured_iso_frames++;
}
if ((desc->offset += desc->length * NRURBS*nr_of_packs()) +
desc->length >= SSS)
desc->offset -= (SSS - desc->length);
}
}
static inline int usX2Y_usbpcm_usbframe_complete(struct snd_usX2Y_substream *capsubs,
struct snd_usX2Y_substream *capsubs2,
struct snd_usX2Y_substream *playbacksubs,
int frame)
{
int err, state;
struct urb *urb = playbacksubs->completed_urb;
state = atomic_read(&playbacksubs->state);
if (NULL != urb) {
if (state == state_RUNNING)
usX2Y_urb_play_retire(playbacksubs, urb);
else if (state >= state_PRERUNNING)
atomic_inc(&playbacksubs->state);
} else {
switch (state) {
case state_STARTING1:
urb = playbacksubs->urb[0];
atomic_inc(&playbacksubs->state);
break;
case state_STARTING2:
urb = playbacksubs->urb[1];
atomic_inc(&playbacksubs->state);
break;
}
}
if (urb) {
if ((err = usX2Y_hwdep_urb_play_prepare(playbacksubs, urb)) ||
(err = usX2Y_urb_submit(playbacksubs, urb, frame))) {
return err;
}
}
playbacksubs->completed_urb = NULL;
state = atomic_read(&capsubs->state);
if (state >= state_PREPARED) {
if (state == state_RUNNING) {
if ((err = usX2Y_usbpcm_urb_capt_retire(capsubs)))
return err;
} else if (state >= state_PRERUNNING)
atomic_inc(&capsubs->state);
usX2Y_usbpcm_urb_capt_iso_advance(capsubs, capsubs->completed_urb);
if (NULL != capsubs2)
usX2Y_usbpcm_urb_capt_iso_advance(NULL, capsubs2->completed_urb);
if ((err = usX2Y_urb_submit(capsubs, capsubs->completed_urb, frame)))
return err;
if (NULL != capsubs2)
if ((err = usX2Y_urb_submit(capsubs2, capsubs2->completed_urb, frame)))
return err;
}
capsubs->completed_urb = NULL;
if (NULL != capsubs2)
capsubs2->completed_urb = NULL;
return 0;
}
static void i_usX2Y_usbpcm_urb_complete(struct urb *urb)
{
struct snd_usX2Y_substream *subs = urb->context;
struct usX2Ydev *usX2Y = subs->usX2Y;
struct snd_usX2Y_substream *capsubs, *capsubs2, *playbacksubs;
if (unlikely(atomic_read(&subs->state) < state_PREPARED)) {
snd_printdd("hcd_frame=%i ep=%i%s status=%i start_frame=%i\n",
usb_get_current_frame_number(usX2Y->dev),
subs->endpoint, usb_pipein(urb->pipe) ? "in" : "out",
urb->status, urb->start_frame);
return;
}
if (unlikely(urb->status)) {
usX2Y_error_urb_status(usX2Y, subs, urb);
return;
}
subs->completed_urb = urb;
capsubs = usX2Y->subs[SNDRV_PCM_STREAM_CAPTURE];
capsubs2 = usX2Y->subs[SNDRV_PCM_STREAM_CAPTURE + 2];
playbacksubs = usX2Y->subs[SNDRV_PCM_STREAM_PLAYBACK];
if (capsubs->completed_urb && atomic_read(&capsubs->state) >= state_PREPARED &&
(NULL == capsubs2 || capsubs2->completed_urb) &&
(playbacksubs->completed_urb || atomic_read(&playbacksubs->state) < state_PREPARED)) {
if (!usX2Y_usbpcm_usbframe_complete(capsubs, capsubs2, playbacksubs, urb->start_frame))
usX2Y->wait_iso_frame += nr_of_packs();
else {
snd_printdd("\n");
usX2Y_clients_stop(usX2Y);
}
}
}
static void usX2Y_hwdep_urb_release(struct urb **urb)
{
usb_kill_urb(*urb);
usb_free_urb(*urb);
*urb = NULL;
}
/*
* release a substream
*/
static void usX2Y_usbpcm_urbs_release(struct snd_usX2Y_substream *subs)
{
int i;
snd_printdd("snd_usX2Y_urbs_release() %i\n", subs->endpoint);
for (i = 0; i < NRURBS; i++)
usX2Y_hwdep_urb_release(subs->urb + i);
}
static void usX2Y_usbpcm_subs_startup_finish(struct usX2Ydev * usX2Y)
{
usX2Y_urbs_set_complete(usX2Y, i_usX2Y_usbpcm_urb_complete);
usX2Y->prepare_subs = NULL;
}
static void i_usX2Y_usbpcm_subs_startup(struct urb *urb)
{
struct snd_usX2Y_substream *subs = urb->context;
struct usX2Ydev *usX2Y = subs->usX2Y;
struct snd_usX2Y_substream *prepare_subs = usX2Y->prepare_subs;
if (NULL != prepare_subs &&
urb->start_frame == prepare_subs->urb[0]->start_frame) {
atomic_inc(&prepare_subs->state);
if (prepare_subs == usX2Y->subs[SNDRV_PCM_STREAM_CAPTURE]) {
struct snd_usX2Y_substream *cap_subs2 = usX2Y->subs[SNDRV_PCM_STREAM_CAPTURE + 2];
if (cap_subs2 != NULL)
atomic_inc(&cap_subs2->state);
}
usX2Y_usbpcm_subs_startup_finish(usX2Y);
wake_up(&usX2Y->prepare_wait_queue);
}
i_usX2Y_usbpcm_urb_complete(urb);
}
/*
* initialize a substream's urbs
*/
static int usX2Y_usbpcm_urbs_allocate(struct snd_usX2Y_substream *subs)
{
int i;
unsigned int pipe;
int is_playback = subs == subs->usX2Y->subs[SNDRV_PCM_STREAM_PLAYBACK];
struct usb_device *dev = subs->usX2Y->dev;
pipe = is_playback ? usb_sndisocpipe(dev, subs->endpoint) :
usb_rcvisocpipe(dev, subs->endpoint);
subs->maxpacksize = usb_maxpacket(dev, pipe, is_playback);
if (!subs->maxpacksize)
return -EINVAL;
/* allocate and initialize data urbs */
for (i = 0; i < NRURBS; i++) {
struct urb **purb = subs->urb + i;
if (*purb) {
usb_kill_urb(*purb);
continue;
}
*purb = usb_alloc_urb(nr_of_packs(), GFP_KERNEL);
if (NULL == *purb) {
usX2Y_usbpcm_urbs_release(subs);
return -ENOMEM;
}
(*purb)->transfer_buffer = is_playback ?
subs->usX2Y->hwdep_pcm_shm->playback : (
subs->endpoint == 0x8 ?
subs->usX2Y->hwdep_pcm_shm->capture0x8 :
subs->usX2Y->hwdep_pcm_shm->capture0xA);
(*purb)->dev = dev;
(*purb)->pipe = pipe;
(*purb)->number_of_packets = nr_of_packs();
(*purb)->context = subs;
(*purb)->interval = 1;
(*purb)->complete = i_usX2Y_usbpcm_subs_startup;
}
return 0;
}
/*
* free the buffer
*/
static int snd_usX2Y_usbpcm_hw_free(struct snd_pcm_substream *substream)
{
struct snd_pcm_runtime *runtime = substream->runtime;
struct snd_usX2Y_substream *subs = runtime->private_data,
*cap_subs2 = subs->usX2Y->subs[SNDRV_PCM_STREAM_CAPTURE + 2];
mutex_lock(&subs->usX2Y->pcm_mutex);
snd_printdd("snd_usX2Y_usbpcm_hw_free(%p)\n", substream);
if (SNDRV_PCM_STREAM_PLAYBACK == substream->stream) {
struct snd_usX2Y_substream *cap_subs = subs->usX2Y->subs[SNDRV_PCM_STREAM_CAPTURE];
atomic_set(&subs->state, state_STOPPED);
usX2Y_usbpcm_urbs_release(subs);
if (!cap_subs->pcm_substream ||
!cap_subs->pcm_substream->runtime ||
!cap_subs->pcm_substream->runtime->status ||
cap_subs->pcm_substream->runtime->status->state < SNDRV_PCM_STATE_PREPARED) {
atomic_set(&cap_subs->state, state_STOPPED);
if (NULL != cap_subs2)
atomic_set(&cap_subs2->state, state_STOPPED);
usX2Y_usbpcm_urbs_release(cap_subs);
if (NULL != cap_subs2)
usX2Y_usbpcm_urbs_release(cap_subs2);
}
} else {
struct snd_usX2Y_substream *playback_subs = subs->usX2Y->subs[SNDRV_PCM_STREAM_PLAYBACK];
if (atomic_read(&playback_subs->state) < state_PREPARED) {
atomic_set(&subs->state, state_STOPPED);
if (NULL != cap_subs2)
atomic_set(&cap_subs2->state, state_STOPPED);
usX2Y_usbpcm_urbs_release(subs);
if (NULL != cap_subs2)
usX2Y_usbpcm_urbs_release(cap_subs2);
}
}
mutex_unlock(&subs->usX2Y->pcm_mutex);
return snd_pcm_lib_free_pages(substream);
}
static void usX2Y_usbpcm_subs_startup(struct snd_usX2Y_substream *subs)
{
struct usX2Ydev * usX2Y = subs->usX2Y;
usX2Y->prepare_subs = subs;
subs->urb[0]->start_frame = -1;
smp_wmb(); // Make sure above modifications are seen by i_usX2Y_subs_startup()
usX2Y_urbs_set_complete(usX2Y, i_usX2Y_usbpcm_subs_startup);
}
static int usX2Y_usbpcm_urbs_start(struct snd_usX2Y_substream *subs)
{
int p, u, err,
stream = subs->pcm_substream->stream;
struct usX2Ydev *usX2Y = subs->usX2Y;
if (SNDRV_PCM_STREAM_CAPTURE == stream) {
usX2Y->hwdep_pcm_shm->captured_iso_head = -1;
usX2Y->hwdep_pcm_shm->captured_iso_frames = 0;
}
for (p = 0; 3 >= (stream + p); p += 2) {
struct snd_usX2Y_substream *subs = usX2Y->subs[stream + p];
if (subs != NULL) {
if ((err = usX2Y_usbpcm_urbs_allocate(subs)) < 0)
return err;
subs->completed_urb = NULL;
}
}
for (p = 0; p < 4; p++) {
struct snd_usX2Y_substream *subs = usX2Y->subs[p];
if (subs != NULL && atomic_read(&subs->state) >= state_PREPARED)
goto start;
}
start:
usX2Y_usbpcm_subs_startup(subs);
for (u = 0; u < NRURBS; u++) {
for (p = 0; 3 >= (stream + p); p += 2) {
struct snd_usX2Y_substream *subs = usX2Y->subs[stream + p];
if (subs != NULL) {
struct urb *urb = subs->urb[u];
if (usb_pipein(urb->pipe)) {
unsigned long pack;
if (0 == u)
atomic_set(&subs->state, state_STARTING3);
urb->dev = usX2Y->dev;
for (pack = 0; pack < nr_of_packs(); pack++) {
urb->iso_frame_desc[pack].offset = subs->maxpacksize * (pack + u * nr_of_packs());
urb->iso_frame_desc[pack].length = subs->maxpacksize;
}
urb->transfer_buffer_length = subs->maxpacksize * nr_of_packs();
if ((err = usb_submit_urb(urb, GFP_KERNEL)) < 0) {
snd_printk (KERN_ERR "cannot usb_submit_urb() for urb %d, err = %d\n", u, err);
err = -EPIPE;
goto cleanup;
} else {
snd_printdd("%i\n", urb->start_frame);
if (u == 0)
usX2Y->wait_iso_frame = urb->start_frame;
}
urb->transfer_flags = 0;
} else {
atomic_set(&subs->state, state_STARTING1);
break;
}
}
}
}
err = 0;
wait_event(usX2Y->prepare_wait_queue, NULL == usX2Y->prepare_subs);
if (atomic_read(&subs->state) != state_PREPARED)
err = -EPIPE;
cleanup:
if (err) {
usX2Y_subs_startup_finish(usX2Y); // Call it now
usX2Y_clients_stop(usX2Y); // something is completely wroong > stop evrything
}
return err;
}
/*
* prepare callback
*
* set format and initialize urbs
*/
static int snd_usX2Y_usbpcm_prepare(struct snd_pcm_substream *substream)
{
struct snd_pcm_runtime *runtime = substream->runtime;
struct snd_usX2Y_substream *subs = runtime->private_data;
struct usX2Ydev *usX2Y = subs->usX2Y;
struct snd_usX2Y_substream *capsubs = subs->usX2Y->subs[SNDRV_PCM_STREAM_CAPTURE];
int err = 0;
snd_printdd("snd_usX2Y_pcm_prepare(%p)\n", substream);
if (NULL == usX2Y->hwdep_pcm_shm) {
if (NULL == (usX2Y->hwdep_pcm_shm = snd_malloc_pages(sizeof(struct snd_usX2Y_hwdep_pcm_shm), GFP_KERNEL)))
return -ENOMEM;
memset(usX2Y->hwdep_pcm_shm, 0, sizeof(struct snd_usX2Y_hwdep_pcm_shm));
}
mutex_lock(&usX2Y->pcm_mutex);
usX2Y_subs_prepare(subs);
// Start hardware streams
// SyncStream first....
if (atomic_read(&capsubs->state) < state_PREPARED) {
if (usX2Y->format != runtime->format)
if ((err = usX2Y_format_set(usX2Y, runtime->format)) < 0)
goto up_prepare_mutex;
if (usX2Y->rate != runtime->rate)
if ((err = usX2Y_rate_set(usX2Y, runtime->rate)) < 0)
goto up_prepare_mutex;
snd_printdd("starting capture pipe for %s\n", subs == capsubs ?
"self" : "playpipe");
if (0 > (err = usX2Y_usbpcm_urbs_start(capsubs)))
goto up_prepare_mutex;
}
if (subs != capsubs) {
usX2Y->hwdep_pcm_shm->playback_iso_start = -1;
if (atomic_read(&subs->state) < state_PREPARED) {
while (usX2Y_iso_frames_per_buffer(runtime, usX2Y) >
usX2Y->hwdep_pcm_shm->captured_iso_frames) {
snd_printdd("Wait: iso_frames_per_buffer=%i,"
"captured_iso_frames=%i\n",
usX2Y_iso_frames_per_buffer(runtime, usX2Y),
usX2Y->hwdep_pcm_shm->captured_iso_frames);
if (msleep_interruptible(10)) {
err = -ERESTARTSYS;
goto up_prepare_mutex;
}
}
if (0 > (err = usX2Y_usbpcm_urbs_start(subs)))
goto up_prepare_mutex;
}
snd_printdd("Ready: iso_frames_per_buffer=%i,captured_iso_frames=%i\n",
usX2Y_iso_frames_per_buffer(runtime, usX2Y),
usX2Y->hwdep_pcm_shm->captured_iso_frames);
} else
usX2Y->hwdep_pcm_shm->capture_iso_start = -1;
up_prepare_mutex:
mutex_unlock(&usX2Y->pcm_mutex);
return err;
}
static struct snd_pcm_hardware snd_usX2Y_4c =
{
.info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_BLOCK_TRANSFER |
SNDRV_PCM_INFO_MMAP_VALID),
.formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_3LE,
.rates = SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000,
.rate_min = 44100,
.rate_max = 48000,
.channels_min = 2,
.channels_max = 4,
.buffer_bytes_max = (2*128*1024),
.period_bytes_min = 64,
.period_bytes_max = (128*1024),
.periods_min = 2,
.periods_max = 1024,
.fifo_size = 0
};
static int snd_usX2Y_usbpcm_open(struct snd_pcm_substream *substream)
{
struct snd_usX2Y_substream *subs = ((struct snd_usX2Y_substream **)
snd_pcm_substream_chip(substream))[substream->stream];
struct snd_pcm_runtime *runtime = substream->runtime;
if (!(subs->usX2Y->chip_status & USX2Y_STAT_CHIP_MMAP_PCM_URBS))
return -EBUSY;
runtime->hw = SNDRV_PCM_STREAM_PLAYBACK == substream->stream ? snd_usX2Y_2c :
(subs->usX2Y->subs[3] ? snd_usX2Y_4c : snd_usX2Y_2c);
runtime->private_data = subs;
subs->pcm_substream = substream;
snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_PERIOD_TIME, 1000, 200000);
return 0;
}
static int snd_usX2Y_usbpcm_close(struct snd_pcm_substream *substream)
{
struct snd_pcm_runtime *runtime = substream->runtime;
struct snd_usX2Y_substream *subs = runtime->private_data;
subs->pcm_substream = NULL;
return 0;
}
static struct snd_pcm_ops snd_usX2Y_usbpcm_ops =
{
.open = snd_usX2Y_usbpcm_open,
.close = snd_usX2Y_usbpcm_close,
.ioctl = snd_pcm_lib_ioctl,
.hw_params = snd_usX2Y_pcm_hw_params,
.hw_free = snd_usX2Y_usbpcm_hw_free,
.prepare = snd_usX2Y_usbpcm_prepare,
.trigger = snd_usX2Y_pcm_trigger,
.pointer = snd_usX2Y_pcm_pointer,
};
static int usX2Y_pcms_busy_check(struct snd_card *card)
{
struct usX2Ydev *dev = usX2Y(card);
int i;
for (i = 0; i < dev->pcm_devs * 2; i++) {
struct snd_usX2Y_substream *subs = dev->subs[i];
if (subs && subs->pcm_substream &&
SUBSTREAM_BUSY(subs->pcm_substream))
return -EBUSY;
}
return 0;
}
static int snd_usX2Y_hwdep_pcm_open(struct snd_hwdep *hw, struct file *file)
{
struct snd_card *card = hw->card;
int err;
mutex_lock(&usX2Y(card)->pcm_mutex);
err = usX2Y_pcms_busy_check(card);
if (!err)
usX2Y(card)->chip_status |= USX2Y_STAT_CHIP_MMAP_PCM_URBS;
mutex_unlock(&usX2Y(card)->pcm_mutex);
return err;
}
static int snd_usX2Y_hwdep_pcm_release(struct snd_hwdep *hw, struct file *file)
{
struct snd_card *card = hw->card;
int err;
mutex_lock(&usX2Y(card)->pcm_mutex);
err = usX2Y_pcms_busy_check(card);
if (!err)
usX2Y(hw->card)->chip_status &= ~USX2Y_STAT_CHIP_MMAP_PCM_URBS;
mutex_unlock(&usX2Y(card)->pcm_mutex);
return err;
}
static void snd_usX2Y_hwdep_pcm_vm_open(struct vm_area_struct *area)
{
}
static void snd_usX2Y_hwdep_pcm_vm_close(struct vm_area_struct *area)
{
}
static int snd_usX2Y_hwdep_pcm_vm_fault(struct vm_area_struct *area,
struct vm_fault *vmf)
{
unsigned long offset;
void *vaddr;
offset = vmf->pgoff << PAGE_SHIFT;
vaddr = (char*)((struct usX2Ydev *)area->vm_private_data)->hwdep_pcm_shm + offset;
vmf->page = virt_to_page(vaddr);
get_page(vmf->page);
return 0;
}
static const struct vm_operations_struct snd_usX2Y_hwdep_pcm_vm_ops = {
.open = snd_usX2Y_hwdep_pcm_vm_open,
.close = snd_usX2Y_hwdep_pcm_vm_close,
.fault = snd_usX2Y_hwdep_pcm_vm_fault,
};
static int snd_usX2Y_hwdep_pcm_mmap(struct snd_hwdep * hw, struct file *filp, struct vm_area_struct *area)
{
unsigned long size = (unsigned long)(area->vm_end - area->vm_start);
struct usX2Ydev *usX2Y = hw->private_data;
if (!(usX2Y->chip_status & USX2Y_STAT_CHIP_INIT))
return -EBUSY;
/* if userspace tries to mmap beyond end of our buffer, fail */
if (size > PAGE_ALIGN(sizeof(struct snd_usX2Y_hwdep_pcm_shm))) {
snd_printd("%lu > %lu\n", size, (unsigned long)sizeof(struct snd_usX2Y_hwdep_pcm_shm));
return -EINVAL;
}
if (!usX2Y->hwdep_pcm_shm) {
return -ENODEV;
}
area->vm_ops = &snd_usX2Y_hwdep_pcm_vm_ops;
area->vm_flags |= VM_DONTEXPAND | VM_DONTDUMP;
area->vm_private_data = hw->private_data;
return 0;
}
static void snd_usX2Y_hwdep_pcm_private_free(struct snd_hwdep *hwdep)
{
struct usX2Ydev *usX2Y = hwdep->private_data;
if (NULL != usX2Y->hwdep_pcm_shm)
snd_free_pages(usX2Y->hwdep_pcm_shm, sizeof(struct snd_usX2Y_hwdep_pcm_shm));
}
int usX2Y_hwdep_pcm_new(struct snd_card *card)
{
int err;
struct snd_hwdep *hw;
struct snd_pcm *pcm;
struct usb_device *dev = usX2Y(card)->dev;
if (1 != nr_of_packs())
return 0;
if ((err = snd_hwdep_new(card, SND_USX2Y_USBPCM_ID, 1, &hw)) < 0)
return err;
hw->iface = SNDRV_HWDEP_IFACE_USX2Y_PCM;
hw->private_data = usX2Y(card);
hw->private_free = snd_usX2Y_hwdep_pcm_private_free;
hw->ops.open = snd_usX2Y_hwdep_pcm_open;
hw->ops.release = snd_usX2Y_hwdep_pcm_release;
hw->ops.mmap = snd_usX2Y_hwdep_pcm_mmap;
hw->exclusive = 1;
sprintf(hw->name, "/proc/bus/usb/%03d/%03d/hwdeppcm", dev->bus->busnum, dev->devnum);
err = snd_pcm_new(card, NAME_ALLCAPS" hwdep Audio", 2, 1, 1, &pcm);
if (err < 0) {
return err;
}
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_usX2Y_usbpcm_ops);
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_usX2Y_usbpcm_ops);
pcm->private_data = usX2Y(card)->subs;
pcm->info_flags = 0;
sprintf(pcm->name, NAME_ALLCAPS" hwdep Audio");
if (0 > (err = snd_pcm_lib_preallocate_pages(pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream,
SNDRV_DMA_TYPE_CONTINUOUS,
snd_dma_continuous_data(GFP_KERNEL),
64*1024, 128*1024)) ||
0 > (err = snd_pcm_lib_preallocate_pages(pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream,
SNDRV_DMA_TYPE_CONTINUOUS,
snd_dma_continuous_data(GFP_KERNEL),
64*1024, 128*1024))) {
return err;
}
return 0;
}
#else
int usX2Y_hwdep_pcm_new(struct snd_card *card)
{
return 0;
}
#endif

View file

@ -0,0 +1,22 @@
#define MAXPACK 50
#define MAXBUFFERMS 100
#define MAXSTRIDE 3
#define SSS (((MAXPACK*MAXBUFFERMS*MAXSTRIDE + 4096) / 4096) * 4096)
struct snd_usX2Y_hwdep_pcm_shm {
char playback[SSS];
char capture0x8[SSS];
char capture0xA[SSS];
volatile int playback_iso_head;
int playback_iso_start;
struct {
int frame,
offset,
length;
} captured_iso[128];
volatile int captured_iso_head;
volatile unsigned captured_iso_frames;
int capture_iso_start;
};
int usX2Y_hwdep_pcm_new(struct snd_card *card);