Commit c6d43ba8 authored by Torsten Schenk's avatar Torsten Schenk Committed by Takashi Iwai

ALSA: usb/6fire - Driver for TerraTec DMX 6Fire USB

What is working: Everything except SPDIF
- Hardware Master volume
- PCM 44-192kHz@24 bits, 6 channels out, 4 channels in (analog)
- MIDI in/out
- firmware loading after cold start
- phono/line switching
Signed-off-by: default avatarTorsten Schenk <torsten.schenk@zoho.com>
Signed-off-by: default avatarTakashi Iwai <tiwai@suse.de>
parent 49c6ad43
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
/*
* Linux driver for TerraTec DMX 6Fire USB
*
* Main routines and module definitions.
*
* Author: Torsten Schenk <torsten.schenk@zoho.com>
* Created: Jan 01, 2011
* Version: 0.3.0
* Copyright: (C) Torsten Schenk
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*/
#include "chip.h"
#include "firmware.h"
#include "pcm.h"
#include "control.h"
#include "comm.h"
#include "midi.h"
#include <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, version 0.3.0");
MODULE_LICENSE("GPL v2");
MODULE_SUPPORTED_DEVICE("{{TerraTec, DMX 6Fire USB}}");
static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 0-max */
static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* Id for card */
static int enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP; /* Enable card */
static struct sfire_chip *chips[SNDRV_CARDS] = SNDRV_DEFAULT_PTR;
static struct usb_device *devices[SNDRV_CARDS] = SNDRV_DEFAULT_PTR;
module_param_array(index, int, NULL, 0444);
MODULE_PARM_DESC(index, "Index value for the 6fire sound device");
module_param_array(id, charp, NULL, 0444);
MODULE_PARM_DESC(id, "ID string for the 6fire sound device.");
module_param_array(enable, bool, NULL, 0444);
MODULE_PARM_DESC(enable, "Enable the 6fire sound device.");
static DEFINE_MUTEX(register_mutex);
static void usb6fire_chip_abort(struct sfire_chip *chip)
{
if (chip) {
if (chip->pcm)
usb6fire_pcm_abort(chip);
if (chip->midi)
usb6fire_midi_abort(chip);
if (chip->comm)
usb6fire_comm_abort(chip);
if (chip->control)
usb6fire_control_abort(chip);
if (chip->card) {
snd_card_disconnect(chip->card);
snd_card_free_when_closed(chip->card);
chip->card = NULL;
}
}
}
static void usb6fire_chip_destroy(struct sfire_chip *chip)
{
if (chip) {
if (chip->pcm)
usb6fire_pcm_destroy(chip);
if (chip->midi)
usb6fire_midi_destroy(chip);
if (chip->comm)
usb6fire_comm_destroy(chip);
if (chip->control)
usb6fire_control_destroy(chip);
if (chip->card)
snd_card_free(chip->card);
}
}
static int __devinit usb6fire_chip_probe(struct usb_interface *intf,
const struct usb_device_id *usb_id)
{
int ret;
int i;
struct sfire_chip *chip = NULL;
struct usb_device *device = interface_to_usbdev(intf);
int regidx = -1; /* index in module parameter array */
struct snd_card *card = NULL;
/* look if we already serve this card and return if so */
mutex_lock(&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 (regidx < 0)
regidx = i;
}
if (regidx < 0) {
mutex_unlock(&register_mutex);
snd_printk(KERN_ERR PREFIX "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) {
snd_printk(KERN_ERR PREFIX "can't set first interface.\n");
return -EIO;
}
ret = snd_card_create(index[regidx], id[regidx], THIS_MODULE,
sizeof(struct sfire_chip), &card);
if (ret < 0) {
snd_printk(KERN_ERR PREFIX "cannot create alsa card.\n");
return ret;
}
strcpy(card->driver, "6FireUSB");
strcpy(card->shortname, "TerraTec DMX6FireUSB");
sprintf(card->longname, "%s at %d:%d", card->shortname,
device->bus->busnum, device->devnum);
snd_card_set_dev(card, &intf->dev);
chip = card->private_data;
chips[regidx] = chip;
chip->dev = device;
chip->regidx = regidx;
chip->intf_count = 1;
chip->card = card;
ret = usb6fire_comm_init(chip);
if (ret < 0) {
usb6fire_chip_destroy(chip);
return ret;
}
ret = usb6fire_midi_init(chip);
if (ret < 0) {
usb6fire_chip_destroy(chip);
return ret;
}
ret = usb6fire_pcm_init(chip);
if (ret < 0) {
usb6fire_chip_destroy(chip);
return ret;
}
ret = usb6fire_control_init(chip);
if (ret < 0) {
usb6fire_chip_destroy(chip);
return ret;
}
ret = snd_card_register(card);
if (ret < 0) {
snd_printk(KERN_ERR PREFIX "cannot register card.");
usb6fire_chip_destroy(chip);
return ret;
}
usb_set_intfdata(intf, chip);
return 0;
}
static void usb6fire_chip_disconnect(struct usb_interface *intf)
{
struct sfire_chip *chip;
struct snd_card *card;
chip = usb_get_intfdata(intf);
if (chip) { /* if !chip, fw upload has been performed */
card = chip->card;
chip->intf_count--;
if (!chip->intf_count) {
mutex_lock(&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 driver = {
.name = "snd-usb-6fire",
.probe = usb6fire_chip_probe,
.disconnect = usb6fire_chip_disconnect,
.id_table = device_table,
};
static int __init usb6fire_chip_init(void)
{
return usb_register(&driver);
}
static void __exit usb6fire_chip_cleanup(void)
{
usb_deregister(&driver);
}
module_init(usb6fire_chip_init);
module_exit(usb6fire_chip_cleanup);
/*
* Linux driver for TerraTec DMX 6Fire USB
*
* Author: Torsten Schenk <torsten.schenk@zoho.com>
* Created: Jan 01, 2011
* Version: 0.3.0
* Copyright: (C) Torsten Schenk
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*/
#ifndef USB6FIRE_CHIP_H
#define USB6FIRE_CHIP_H
#include "common.h"
struct sfire_chip {
struct usb_device *dev;
struct snd_card *card;
int intf_count; /* number of registered interfaces */
int regidx; /* index in module parameter arrays */
bool shutdown;
struct midi_runtime *midi;
struct pcm_runtime *pcm;
struct control_runtime *control;
struct comm_runtime *comm;
};
#endif /* USB6FIRE_CHIP_H */
/*
* Linux driver for TerraTec DMX 6Fire USB
*
* Device communications
*
* Author: Torsten Schenk <torsten.schenk@zoho.com>
* Created: Jan 01, 2011
* Version: 0.3.0
* Copyright: (C) Torsten Schenk
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*/
#include "comm.h"
#include "chip.h"
#include "midi.h"
enum {
COMM_EP = 1,
COMM_FPGA_EP = 2
};
static void usb6fire_comm_init_urb(struct comm_runtime *rt, struct urb *urb,
u8 *buffer, void *context, void(*handler)(struct urb *urb))
{
usb_init_urb(urb);
urb->transfer_buffer = buffer;
urb->pipe = usb_sndintpipe(rt->chip->dev, COMM_EP);
urb->complete = handler;
urb->context = context;
urb->interval = 1;
urb->dev = rt->chip->dev;
}
static void usb6fire_comm_receiver_handler(struct urb *urb)
{
struct comm_runtime *rt = urb->context;
struct midi_runtime *midi_rt = rt->chip->midi;
if (!urb->status) {
if (rt->receiver_buffer[0] == 0x10) /* midi in event */
if (midi_rt)
midi_rt->in_received(midi_rt,
rt->receiver_buffer + 2,
rt->receiver_buffer[1]);
}
if (!rt->chip->shutdown) {
urb->status = 0;
urb->actual_length = 0;
if (usb_submit_urb(urb, GFP_ATOMIC) < 0)
snd_printk(KERN_WARNING PREFIX
"comm data receiver aborted.\n");
}
}
static void usb6fire_comm_init_buffer(u8 *buffer, u8 id, u8 request,
u8 reg, u8 vl, u8 vh)
{
buffer[0] = 0x01;
buffer[2] = request;
buffer[3] = id;
switch (request) {
case 0x02:
buffer[1] = 0x05; /* length (starting at buffer[2]) */
buffer[4] = reg;
buffer[5] = vl;
buffer[6] = vh;
break;
case 0x12:
buffer[1] = 0x0b; /* length (starting at buffer[2]) */
buffer[4] = 0x00;
buffer[5] = 0x18;
buffer[6] = 0x05;
buffer[7] = 0x00;
buffer[8] = 0x01;
buffer[9] = 0x00;
buffer[10] = 0x9e;
buffer[11] = reg;
buffer[12] = vl;
break;
case 0x20:
case 0x21:
case 0x22:
buffer[1] = 0x04;
buffer[4] = reg;
buffer[5] = vl;
break;
}
}
static int usb6fire_comm_send_buffer(u8 *buffer, struct usb_device *dev)
{
int ret;
int actual_len;
ret = usb_interrupt_msg(dev, usb_sndintpipe(dev, COMM_EP),
buffer, buffer[1] + 2, &actual_len, HZ);
if (ret < 0)
return ret;
else if (actual_len != buffer[1] + 2)
return -EIO;
return 0;
}
static int usb6fire_comm_write8(struct comm_runtime *rt, u8 request,
u8 reg, u8 value)
{
u8 buffer[13]; /* 13: maximum length of message */
usb6fire_comm_init_buffer(buffer, 0x00, request, reg, value, 0x00);
return usb6fire_comm_send_buffer(buffer, rt->chip->dev);
}
static int usb6fire_comm_write16(struct comm_runtime *rt, u8 request,
u8 reg, u8 vl, u8 vh)
{
u8 buffer[13]; /* 13: maximum length of message */
usb6fire_comm_init_buffer(buffer, 0x00, request, reg, vl, vh);
return usb6fire_comm_send_buffer(buffer, rt->chip->dev);
}
int __devinit usb6fire_comm_init(struct sfire_chip *chip)
{
struct comm_runtime *rt = kzalloc(sizeof(struct comm_runtime),
GFP_KERNEL);
struct urb *urb = &rt->receiver;
int ret;
if (!rt)
return -ENOMEM;
rt->serial = 1;
rt->chip = chip;
usb_init_urb(urb);
rt->init_urb = usb6fire_comm_init_urb;
rt->write8 = usb6fire_comm_write8;
rt->write16 = usb6fire_comm_write16;
/* submit an urb that receives communication data from device */
urb->transfer_buffer = rt->receiver_buffer;
urb->transfer_buffer_length = COMM_RECEIVER_BUFSIZE;
urb->pipe = usb_rcvintpipe(chip->dev, COMM_EP);
urb->dev = chip->dev;
urb->complete = usb6fire_comm_receiver_handler;
urb->context = rt;
urb->interval = 1;
ret = usb_submit_urb(urb, GFP_KERNEL);
if (ret < 0) {
kfree(rt);
snd_printk(KERN_ERR PREFIX "cannot create comm data receiver.");
return ret;
}
chip->comm = rt;
return 0;
}
void usb6fire_comm_abort(struct sfire_chip *chip)
{
struct comm_runtime *rt = chip->comm;
if (rt)
usb_poison_urb(&rt->receiver);
}
void usb6fire_comm_destroy(struct sfire_chip *chip)
{
kfree(chip->comm);
chip->comm = NULL;
}
/*
* Linux driver for TerraTec DMX 6Fire USB
*
* Author: Torsten Schenk <torsten.schenk@zoho.com>
* Created: Jan 01, 2011
* Version: 0.3.0
* Copyright: (C) Torsten Schenk
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*/
#ifndef USB6FIRE_COMM_H
#define USB6FIRE_COMM_H
#include "common.h"
enum /* settings for comm */
{
COMM_RECEIVER_BUFSIZE = 64,
};
struct comm_runtime {
struct sfire_chip *chip;
struct urb receiver;
u8 receiver_buffer[COMM_RECEIVER_BUFSIZE];
u8 serial; /* urb serial */
void (*init_urb)(struct comm_runtime *rt, struct urb *urb, u8 *buffer,
void *context, void(*handler)(struct urb *urb));
/* writes control data to the device */
int (*write8)(struct comm_runtime *rt, u8 request, u8 reg, u8 value);
int (*write16)(struct comm_runtime *rt, u8 request, u8 reg,
u8 vh, u8 vl);
};
int __devinit usb6fire_comm_init(struct sfire_chip *chip);
void usb6fire_comm_abort(struct sfire_chip *chip);
void usb6fire_comm_destroy(struct sfire_chip *chip);
#endif /* USB6FIRE_COMM_H */
/*
* Linux driver for TerraTec DMX 6Fire USB
*
* Author: Torsten Schenk <torsten.schenk@zoho.com>
* Created: Jan 01, 2011
* Version: 0.3.0
* Copyright: (C) Torsten Schenk
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*/
#ifndef USB6FIRE_COMMON_H
#define USB6FIRE_COMMON_H
#include <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 */
/*
* Linux driver for TerraTec DMX 6Fire USB
*
* Mixer control
*
* Author: Torsten Schenk <torsten.schenk@zoho.com>
* Created: Jan 01, 2011
* Version: 0.3.0
* Copyright: (C) Torsten Schenk
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*/
#include <linux/interrupt.h>
#include <sound/control.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" };
/*
* calculated with $value\[i\] = 128 \cdot sqrt[3]{\frac{i}{128}}$
* this is done because the linear values cause rapid degredation
* of volume in the uppermost region.
*/
static const u8 log_volume_table[128] = {
0x00, 0x19, 0x20, 0x24, 0x28, 0x2b, 0x2e, 0x30, 0x32, 0x34,
0x36, 0x38, 0x3a, 0x3b, 0x3d, 0x3e, 0x40, 0x41, 0x42, 0x43,
0x44, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e,
0x4e, 0x4f, 0x50, 0x51, 0x52, 0x53, 0x53, 0x54, 0x55, 0x56,
0x56, 0x57, 0x58, 0x58, 0x59, 0x5a, 0x5b, 0x5b, 0x5c, 0x5c,
0x5d, 0x5e, 0x5e, 0x5f, 0x60, 0x60, 0x61, 0x61, 0x62, 0x62,
0x63, 0x63, 0x64, 0x65, 0x65, 0x66, 0x66, 0x67, 0x67, 0x68,
0x68, 0x69, 0x69, 0x6a, 0x6a, 0x6b, 0x6b, 0x6c, 0x6c, 0x6c,
0x6d, 0x6d, 0x6e, 0x6e, 0x6f, 0x6f, 0x70, 0x70, 0x70, 0x71,
0x71, 0x72, 0x72, 0x73, 0x73, 0x73, 0x74, 0x74, 0x75, 0x75,
0x75, 0x76, 0x76, 0x77, 0x77, 0x77, 0x78, 0x78, 0x78, 0x79,
0x79, 0x7a, 0x7a, 0x7a, 0x7b, 0x7b, 0x7b, 0x7c, 0x7c, 0x7c,
0x7d, 0x7d, 0x7d, 0x7e, 0x7e, 0x7e, 0x7f, 0x7f };
/*
* data that needs to be sent to device. sets up card internal stuff.
* values dumped from windows driver and filtered by trial'n'error.
*/
static const struct {
u8 type;
u8 reg;
u8 value;
}
init_data[] = {
{ 0x22, 0x00, 0x00 }, { 0x20, 0x00, 0x08 }, { 0x22, 0x01, 0x01 },
{ 0x20, 0x01, 0x08 }, { 0x22, 0x02, 0x00 }, { 0x20, 0x02, 0x08 },
{ 0x22, 0x03, 0x00 }, { 0x20, 0x03, 0x08 }, { 0x22, 0x04, 0x00 },
{ 0x20, 0x04, 0x08 }, { 0x22, 0x05, 0x01 }, { 0x20, 0x05, 0x08 },
{ 0x22, 0x04, 0x01 }, { 0x12, 0x04, 0x00 }, { 0x12, 0x05, 0x00 },
{ 0x12, 0x0d, 0x78 }, { 0x12, 0x21, 0x82 }, { 0x12, 0x22, 0x80 },
{ 0x12, 0x23, 0x00 }, { 0x12, 0x06, 0x02 }, { 0x12, 0x03, 0x00 },
{ 0x12, 0x02, 0x00 }, { 0x22, 0x03, 0x01 },
{ 0 } /* TERMINATING ENTRY */
};
static void usb6fire_control_master_vol_update(struct control_runtime *rt)
{
struct comm_runtime *comm_rt = rt->chip->comm;
if (comm_rt) {
/* set volume */
comm_rt->write8(comm_rt, 0x12, 0x0f, 0x7f -
log_volume_table[rt->master_vol]);
/* unmute */
comm_rt->write8(comm_rt, 0x12, 0x0e, 0x00);
}
}
static void usb6fire_control_line_phono_update(struct control_runtime *rt)
{
struct comm_runtime *comm_rt = rt->chip->comm;
if (comm_rt) {
comm_rt->write8(comm_rt, 0x22, 0x02, rt->line_phono_switch);
comm_rt->write8(comm_rt, 0x21, 0x02, rt->line_phono_switch);
}
}
static void usb6fire_control_opt_coax_update(struct control_runtime *rt)
{
struct comm_runtime *comm_rt = rt->chip->comm;
if (comm_rt) {
comm_rt->write8(comm_rt, 0x22, 0x00, rt->opt_coax_switch);
comm_rt->write8(comm_rt, 0x21, 0x00, rt->opt_coax_switch);
}
}
static int usb6fire_control_master_vol_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{
uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
uinfo->count = 1;
uinfo->value.integer.min = 0;
uinfo->value.integer.max = 127;
return 0;
}
static int usb6fire_control_master_vol_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct control_runtime *rt = snd_kcontrol_chip(kcontrol);
int changed = 0;
if (rt->master_vol != ucontrol->value.integer.value[0]) {
rt->master_vol = ucontrol->value.integer.value[0];
usb6fire_control_master_vol_update(rt);
changed = 1;
}
return changed;
}
static int usb6fire_control_master_vol_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct control_runtime *rt = snd_kcontrol_chip(kcontrol);
ucontrol->value.integer.value[0] = rt->master_vol;
return 0;
}
static int usb6fire_control_line_phono_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{
uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
uinfo->count = 1;
uinfo->value.enumerated.items = 2;
if (uinfo->value.enumerated.item > 1)
uinfo->value.enumerated.item = 1;
strcpy(uinfo->value.enumerated.name,
line_phono_texts[uinfo->value.enumerated.item]);
return 0;
}
static int usb6fire_control_line_phono_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct control_runtime *rt = snd_kcontrol_chip(kcontrol);
int changed = 0;
if (rt->line_phono_switch != ucontrol->value.integer.value[0]) {
rt->line_phono_switch = ucontrol->value.integer.value[0];
usb6fire_control_line_phono_update(rt);
changed = 1;
}
return changed;
}
static int usb6fire_control_line_phono_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct control_runtime *rt = snd_kcontrol_chip(kcontrol);
ucontrol->value.integer.value[0] = rt->line_phono_switch;
return 0;
}
static int usb6fire_control_opt_coax_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{
uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
uinfo->count = 1;
uinfo->value.enumerated.items = 2;
if (uinfo->value.enumerated.item > 1)
uinfo->value.enumerated.item = 1;
strcpy(uinfo->value.enumerated.name,
opt_coax_texts[uinfo->value.enumerated.item]);
return 0;
}
static int usb6fire_control_opt_coax_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct control_runtime *rt = snd_kcontrol_chip(kcontrol);
int changed = 0;
if (rt->opt_coax_switch != ucontrol->value.enumerated.item[0]) {
rt->opt_coax_switch = ucontrol->value.enumerated.item[0];
usb6fire_control_opt_coax_update(rt);
changed = 1;
}
return changed;
}
static int usb6fire_control_opt_coax_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct control_runtime *rt = snd_kcontrol_chip(kcontrol);
ucontrol->value.enumerated.item[0] = rt->opt_coax_switch;
return 0;
}
static struct __devinitdata snd_kcontrol_new elements[] = {
{
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "Master Playback Volume",
.index = 0,
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
.info = usb6fire_control_master_vol_info,
.get = usb6fire_control_master_vol_get,
.put = usb6fire_control_master_vol_put
},
{
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "Line/Phono Capture Route",
.index = 0,
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
.info = usb6fire_control_line_phono_info,
.get = usb6fire_control_line_phono_get,
.put = usb6fire_control_line_phono_put
},
{
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "Opt/Coax Capture Route",
.index = 0,
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
.info = usb6fire_control_opt_coax_info,
.get = usb6fire_control_opt_coax_get,
.put = usb6fire_control_opt_coax_put
},
{}
};
int __devinit usb6fire_control_init(struct sfire_chip *chip)
{
int i;
int ret;
struct control_runtime *rt = kzalloc(sizeof(struct control_runtime),
GFP_KERNEL);
struct comm_runtime *comm_rt = chip->comm;
if (!rt)
return -ENOMEM;
rt->chip = chip;
i = 0;
while (init_data[i].type) {
comm_rt->write8(comm_rt, init_data[i].type, init_data[i].reg,
init_data[i].value);
i++;
}
usb6fire_control_opt_coax_update(rt);
usb6fire_control_line_phono_update(rt);
usb6fire_control_master_vol_update(rt);
i = 0;
while (elements[i].name) {
ret = snd_ctl_add(chip->card, snd_ctl_new1(&elements[i], rt));
if (ret < 0) {
kfree(rt);
snd_printk(KERN_ERR PREFIX "cannot add control.\n");
return ret;
}
i++;
}
chip->control = rt;
return 0;
}
void usb6fire_control_abort(struct sfire_chip *chip)
{}
void usb6fire_control_destroy(struct sfire_chip *chip)
{
kfree(chip->control);
chip->control = NULL;
}
/*
* Linux driver for TerraTec DMX 6Fire USB
*
* Author: Torsten Schenk <torsten.schenk@zoho.com>
* Created: Jan 01, 2011
* Version: 0.3.0
* Copyright: (C) Torsten Schenk
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*/
#ifndef USB6FIRE_CONTROL_H
#define USB6FIRE_CONTROL_H
#include "common.h"
enum {
CONTROL_MAX_ELEMENTS = 32
};
struct control_runtime {
struct sfire_chip *chip;
struct snd_kcontrol *element[CONTROL_MAX_ELEMENTS];
bool opt_coax_switch;
bool line_phono_switch;
u8 master_vol;
};
int __devinit usb6fire_control_init(struct sfire_chip *chip);
void usb6fire_control_abort(struct sfire_chip *chip);
void usb6fire_control_destroy(struct sfire_chip *chip);
#endif /* USB6FIRE_CONTROL_H */
/*
* Linux driver for TerraTec DMX 6Fire USB
*
* Firmware loader
*
* Currently not working for all devices. To be able to use the device
* in linux, it is also possible to let the windows driver upload the firmware.
* For that, start the computer in windows and reboot.
* As long as the device is connected to the power supply, no firmware reload
* needs to be performed.
*
* Author: Torsten Schenk <torsten.schenk@zoho.com>
* Created: Jan 01, 2011
* Version: 0.3.0
* Copyright: (C) Torsten Schenk
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*/
#include <linux/firmware.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
};
static const u8 BIT_REVERSE_TABLE[256] = {
0x00, 0x80, 0x40, 0xc0, 0x20, 0xa0, 0x60, 0xe0, 0x10, 0x90, 0x50,
0xd0, 0x30, 0xb0, 0x70, 0xf0, 0x08, 0x88, 0x48, 0xc8, 0x28, 0xa8,
0x68, 0xe8, 0x18, 0x98, 0x58, 0xd8, 0x38, 0xb8, 0x78, 0xf8, 0x04,
0x84, 0x44, 0xc4, 0x24, 0xa4, 0x64, 0xe4, 0x14, 0x94, 0x54, 0xd4,
0x34, 0xb4, 0x74, 0xf4, 0x0c, 0x8c, 0x4c, 0xcc, 0x2c, 0xac, 0x6c,
0xec, 0x1c, 0x9c, 0x5c, 0xdc, 0x3c, 0xbc, 0x7c, 0xfc, 0x02, 0x82,
0x42, 0xc2, 0x22, 0xa2, 0x62, 0xe2, 0x12, 0x92, 0x52, 0xd2, 0x32,
0xb2, 0x72, 0xf2, 0x0a, 0x8a, 0x4a, 0xca, 0x2a, 0xaa, 0x6a, 0xea,
0x1a, 0x9a, 0x5a, 0xda, 0x3a, 0xba, 0x7a, 0xfa, 0x06, 0x86, 0x46,
0xc6, 0x26, 0xa6, 0x66, 0xe6, 0x16, 0x96, 0x56, 0xd6, 0x36, 0xb6,
0x76, 0xf6, 0x0e, 0x8e, 0x4e, 0xce, 0x2e, 0xae, 0x6e, 0xee, 0x1e,
0x9e, 0x5e, 0xde, 0x3e, 0xbe, 0x7e, 0xfe, 0x01, 0x81, 0x41, 0xc1,
0x21, 0xa1, 0x61, 0xe1, 0x11, 0x91, 0x51, 0xd1, 0x31, 0xb1, 0x71,
0xf1, 0x09, 0x89, 0x49, 0xc9, 0x29, 0xa9, 0x69, 0xe9, 0x19, 0x99,
0x59, 0xd9, 0x39, 0xb9, 0x79, 0xf9, 0x05, 0x85, 0x45, 0xc5, 0x25,
0xa5, 0x65, 0xe5, 0x15, 0x95, 0x55, 0xd5, 0x35, 0xb5, 0x75, 0xf5,
0x0d, 0x8d, 0x4d, 0xcd, 0x2d, 0xad, 0x6d, 0xed, 0x1d, 0x9d, 0x5d,
0xdd, 0x3d, 0xbd, 0x7d, 0xfd, 0x03, 0x83, 0x43, 0xc3, 0x23, 0xa3,
0x63, 0xe3, 0x13, 0x93, 0x53, 0xd3, 0x33, 0xb3, 0x73, 0xf3, 0x0b,
0x8b, 0x4b, 0xcb, 0x2b, 0xab, 0x6b, 0xeb, 0x1b, 0x9b, 0x5b, 0xdb,
0x3b, 0xbb, 0x7b, 0xfb, 0x07, 0x87, 0x47, 0xc7, 0x27, 0xa7, 0x67,
0xe7, 0x17, 0x97, 0x57, 0xd7, 0x37, 0xb7, 0x77, 0xf7, 0x0f, 0x8f,
0x4f, 0xcf, 0x2f, 0xaf, 0x6f, 0xef, 0x1f, 0x9f, 0x5f, 0xdf, 0x3f,
0xbf, 0x7f, 0xff };
/*
* wMaxPacketSize of pcm endpoints.
* keep synced with rates_in_packet_size and rates_out_packet_size in pcm.c
* fpp: frames per isopacket
*
* CAUTION: keep sizeof <= buffer[] in usb6fire_fw_init
*/
static const u8 ep_w_max_packet_size[] = {
0xe4, 0x00, 0xe4, 0x00, /* alt 1: 228 EP2 and EP6 (7 fpp) */
0xa4, 0x01, 0xa4, 0x01, /* alt 2: 420 EP2 and EP6 (13 fpp)*/
0x94, 0x01, 0x5c, 0x02 /* alt 3: 404 EP2 and 604 EP6 (25 fpp) */
};
struct ihex_record {
u16 address;
u8 len;
u8 data[256];
char error; /* true if an error occured parsing this record */
u8 max_len; /* maximum record length in whole ihex */
/* private */
const char *txt_data;
unsigned int txt_length;
unsigned int txt_offset; /* current position in txt_data */
};
static u8 usb6fire_fw_ihex_nibble(const u8 n)
{
if (n >= '0' && n <= '9')
return n - '0';
else if (n >= 'A' && n <= 'F')
return n - ('A' - 10);
else if (n >= 'a' && n <= 'f')
return n - ('a' - 10);
return 0;
}
static u8 usb6fire_fw_ihex_hex(const u8 *data, u8 *crc)
{
u8 val = (usb6fire_fw_ihex_nibble(data[0]) << 4) |
usb6fire_fw_ihex_nibble(data[1]);
*crc += val;
return val;
}
/*
* returns true if record is available, false otherwise.
* iff an error occured, false will be returned and record->error will be true.
*/
static bool usb6fire_fw_ihex_next_record(struct ihex_record *record)
{
u8 crc = 0;
u8 type;
int i;
record->error = false;
/* find begin of record (marked by a colon) */
while (record->txt_offset < record->txt_length
&& record->txt_data[record->txt_offset] != ':')
record->txt_offset++;
if (record->txt_offset == record->txt_length)
return false;
/* number of characters needed for len, addr and type entries */
record->txt_offset++;
if (record->txt_offset + 8 > record->txt_length) {
record->error = true;
return false;
}
record->len = usb6fire_fw_ihex_hex(record->txt_data +
record->txt_offset, &crc);
record->txt_offset += 2;
record->address = usb6fire_fw_ihex_hex(record->txt_data +
record->txt_offset, &crc) << 8;
record->txt_offset += 2;
record->address |= usb6fire_fw_ihex_hex(record->txt_data +
record->txt_offset, &crc);
record->txt_offset += 2;
type = usb6fire_fw_ihex_hex(record->txt_data +
record->txt_offset, &crc);
record->txt_offset += 2;
/* number of characters needed for data and crc entries */
if (record->txt_offset + 2 * (record->len + 1) > record->txt_length) {
record->error = true;
return false;
}
for (i = 0; i < record->len; i++) {
record->data[i] = usb6fire_fw_ihex_hex(record->txt_data
+ record->txt_offset, &crc);
record->txt_offset += 2;
}
usb6fire_fw_ihex_hex(record->txt_data + record->txt_offset, &crc);
if (crc) {
record->error = true;
return false;
}
if (type == 1 || !record->len) /* eof */
return false;
else if (type == 0)
return true;
else {
record->error = true;
return false;
}
}
static int usb6fire_fw_ihex_init(const struct firmware *fw,
struct ihex_record *record)
{
record->txt_data = fw->data;
record->txt_length = fw->size;
record->txt_offset = 0;
record->max_len = 0;
/* read all records, if loop ends, record->error indicates,
* whether ihex is valid. */
while (usb6fire_fw_ihex_next_record(record))
record->max_len = max(record->len, record->max_len);
if (record->error)
return -EINVAL;
record->txt_offset = 0;
return 0;
}
static int usb6fire_fw_ezusb_write(struct usb_device *device,
int type, int value, char *data, int len)
{
int ret;
ret = usb_control_msg(device, usb_sndctrlpipe(device, 0), type,
USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
value, 0, data, len, HZ);
if (ret < 0)
return ret;
else if (ret != len)
return -EIO;
return 0;
}
static int usb6fire_fw_ezusb_read(struct usb_device *device,
int type, int value, char *data, int len)
{
int ret = usb_control_msg(device, usb_rcvctrlpipe(device, 0), type,
USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE, value,
0, data, len, HZ);
if (ret < 0)
return ret;
else if (ret != len)
return -EIO;
return 0;
}
static int usb6fire_fw_fpga_write(struct usb_device *device,
char *data, int len)
{
int actual_len;
int ret;
ret = usb_bulk_msg(device, usb_sndbulkpipe(device, FPGA_EP), data, len,
&actual_len, HZ);
if (ret < 0)
return ret;
else if (actual_len != len)
return -EIO;
return 0;
}
static int usb6fire_fw_ezusb_upload(
struct usb_interface *intf, const char *fwname,
unsigned int postaddr, u8 *postdata, unsigned int postlen)
{
int ret;
u8 data;
struct usb_device *device = interface_to_usbdev(intf);
const struct firmware *fw = 0;
struct ihex_record *rec = kmalloc(sizeof(struct ihex_record),
GFP_KERNEL);
if (!rec)
return -ENOMEM;
ret = request_firmware(&fw, fwname, &device->dev);
if (ret < 0) {
kfree(rec);
snd_printk(KERN_ERR PREFIX "error requesting ezusb "
"firmware %s.\n", fwname);
return ret;
}
ret = usb6fire_fw_ihex_init(fw, rec);
if (ret < 0) {
kfree(rec);
snd_printk(KERN_ERR PREFIX "error validating ezusb "
"firmware %s.\n", fwname);
return ret;
}
/* upload firmware image */
data = 0x01; /* stop ezusb cpu */
ret = usb6fire_fw_ezusb_write(device, 0xa0, 0xe600, &data, 1);
if (ret < 0) {
kfree(rec);
release_firmware(fw);
snd_printk(KERN_ERR PREFIX "unable to upload ezusb "
"firmware %s: begin message.\n", fwname);
return ret;
}
while (usb6fire_fw_ihex_next_record(rec)) { /* write firmware */
ret = usb6fire_fw_ezusb_write(device, 0xa0, rec->address,
rec->data, rec->len);
if (ret < 0) {
kfree(rec);
release_firmware(fw);
snd_printk(KERN_ERR PREFIX "unable to upload ezusb "
"firmware %s: data urb.\n", fwname);
return ret;
}
}
release_firmware(fw);
kfree(rec);
if (postdata) { /* write data after firmware has been uploaded */
ret = usb6fire_fw_ezusb_write(device, 0xa0, postaddr,
postdata, postlen);
if (ret < 0) {
snd_printk(KERN_ERR PREFIX "unable to upload ezusb "
"firmware %s: post urb.\n", fwname);
return ret;
}
}
data = 0x00; /* resume ezusb cpu */
ret = usb6fire_fw_ezusb_write(device, 0xa0, 0xe600, &data, 1);
if (ret < 0) {
release_firmware(fw);
snd_printk(KERN_ERR PREFIX "unable to upload ezusb "
"firmware %s: end message.\n", fwname);
return ret;
}
return 0;
}
static int usb6fire_fw_fpga_upload(
struct usb_interface *intf, const char *fwname)
{
int ret;
int i;
struct usb_device *device = interface_to_usbdev(intf);
u8 *buffer = kmalloc(FPGA_BUFSIZE, GFP_KERNEL);
const char *c;
const char *end;
const struct firmware *fw;
if (!buffer)
return -ENOMEM;
ret = request_firmware(&fw, fwname, &device->dev);
if (ret < 0) {
snd_printk(KERN_ERR PREFIX "unable to get fpga firmware %s.\n",
fwname);
kfree(buffer);
return -EIO;
}
c = fw->data;
end = fw->data + fw->size;
ret = usb6fire_fw_ezusb_write(device, 8, 0, NULL, 0);
if (ret < 0) {
kfree(buffer);
release_firmware(fw);
snd_printk(KERN_ERR PREFIX "unable to upload fpga firmware: "
"begin urb.\n");
return ret;
}
while (c != end) {
for (i = 0; c != end && i < FPGA_BUFSIZE; i++, c++)
buffer[i] = BIT_REVERSE_TABLE[(u8) *c];
ret = usb6fire_fw_fpga_write(device, buffer, i);
if (ret < 0) {
release_firmware(fw);
kfree(buffer);
snd_printk(KERN_ERR PREFIX "unable to upload fpga "
"firmware: fw urb.\n");
return ret;
}
}
release_firmware(fw);
kfree(buffer);
ret = usb6fire_fw_ezusb_write(device, 9, 0, NULL, 0);
if (ret < 0) {
snd_printk(KERN_ERR PREFIX "unable to upload fpga firmware: "
"end urb.\n");
return ret;
}
return 0;
}
int usb6fire_fw_init(struct usb_interface *intf)
{
int i;
int ret;
struct usb_device *device = interface_to_usbdev(intf);
/* buffer: 8 receiving bytes from device and
* sizeof(EP_W_MAX_PACKET_SIZE) bytes for non-const copy */
u8 buffer[12];
ret = usb6fire_fw_ezusb_read(device, 1, 0, buffer, 8);
if (ret < 0) {
snd_printk(KERN_ERR PREFIX "unable to receive device "
"firmware state.\n");
return ret;
}
if (buffer[0] != 0xeb || buffer[1] != 0xaa || buffer[2] != 0x55
|| buffer[4] != 0x03 || buffer[5] != 0x01 || buffer[7]
!= 0x00) {
snd_printk(KERN_ERR PREFIX "unknown device firmware state "
"received from device: ");
for (i = 0; i < 8; i++)
snd_printk("%02x ", buffer[i]);
snd_printk("\n");
return -EIO;
}
/* do we need fpga loader ezusb firmware? */
if (buffer[3] == 0x01 && buffer[6] == 0x19) {
ret = usb6fire_fw_ezusb_upload(intf,
"6fire/dmx6firel2.ihx", 0, NULL, 0);
if (ret < 0)
return ret;
return FW_NOT_READY;
}
/* do we need fpga firmware and application ezusb firmware? */
else if (buffer[3] == 0x02 && buffer[6] == 0x0b) {
ret = usb6fire_fw_fpga_upload(intf, "6fire/dmx6firecf.bin");
if (ret < 0)
return ret;
memcpy(buffer, ep_w_max_packet_size,
sizeof(ep_w_max_packet_size));
ret = usb6fire_fw_ezusb_upload(intf, "6fire/dmx6fireap.ihx",
0x0003, buffer, sizeof(ep_w_max_packet_size));
if (ret < 0)
return ret;
return FW_NOT_READY;
}
/* all fw loaded? */
else if (buffer[3] == 0x03 && buffer[6] == 0x0b)
return 0;
/* unknown data? */
else {
snd_printk(KERN_ERR PREFIX "unknown device firmware state "
"received from device: ");
for (i = 0; i < 8; i++)
snd_printk("%02x ", buffer[i]);
snd_printk("\n");
return -EIO;
}
return 0;
}
/*
* Linux driver for TerraTec DMX 6Fire USB
*
* Author: Torsten Schenk
* Created: Jan 01, 2011
* Copyright: (C) Torsten Schenk
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*/
#ifndef USB6FIRE_FIRMWARE_H
#define USB6FIRE_FIRMWARE_H
#include "common.h"
enum /* firmware state of device */
{
FW_READY = 0,
FW_NOT_READY = 1
};
int __devinit usb6fire_fw_init(struct usb_interface *intf);
#endif /* USB6FIRE_FIRMWARE_H */
/*
* Linux driver for TerraTec DMX 6Fire USB
*
* Rawmidi driver
*
* Author: Torsten Schenk <torsten.schenk@zoho.com>
* Created: Jan 01, 2011
* Version: 0.3.0
* Copyright: (C) Torsten Schenk
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*/
#include <sound/rawmidi.h>
#include "midi.h"
#include "chip.h"
#include "comm.h"
static void usb6fire_midi_out_handler(struct urb *urb)
{
struct midi_runtime *rt = urb->context;
int ret;
unsigned long flags;
spin_lock_irqsave(&rt->out_lock, flags);
if (rt->out) {
ret = snd_rawmidi_transmit(rt->out, rt->out_buffer + 4,
MIDI_BUFSIZE - 4);
if (ret > 0) { /* more data available, send next packet */
rt->out_buffer[1] = ret + 2;
rt->out_buffer[3] = rt->out_serial++;
urb->transfer_buffer_length = ret + 4;
ret = usb_submit_urb(urb, GFP_ATOMIC);
if (ret < 0)
snd_printk(KERN_ERR PREFIX "midi out urb "
"submit failed: %d\n", ret);
} else /* no more data to transmit */
rt->out = NULL;
}
spin_unlock_irqrestore(&rt->out_lock, flags);
}
static void usb6fire_midi_in_received(
struct midi_runtime *rt, u8 *data, int length)
{
unsigned long flags;
spin_lock_irqsave(&rt->in_lock, flags);
if (rt->in)
snd_rawmidi_receive(rt->in, data, length);
spin_unlock_irqrestore(&rt->in_lock, flags);
}
static int usb6fire_midi_out_open(struct snd_rawmidi_substream *alsa_sub)
{
return 0;
}
static int usb6fire_midi_out_close(struct snd_rawmidi_substream *alsa_sub)
{
return 0;
}
static void usb6fire_midi_out_trigger(
struct snd_rawmidi_substream *alsa_sub, int up)
{
struct midi_runtime *rt = alsa_sub->rmidi->private_data;
struct urb *urb = &rt->out_urb;
__s8 ret;
unsigned long flags;
spin_lock_irqsave(&rt->out_lock, flags);
if (up) { /* start transfer */
if (rt->out) { /* we are already transmitting so just return */
spin_unlock_irqrestore(&rt->out_lock, flags);
return;
}
ret = snd_rawmidi_transmit(alsa_sub, rt->out_buffer + 4,
MIDI_BUFSIZE - 4);
if (ret > 0) {
rt->out_buffer[1] = ret + 2;
rt->out_buffer[3] = rt->out_serial++;
urb->transfer_buffer_length = ret + 4;
ret = usb_submit_urb(urb, GFP_ATOMIC);
if (ret < 0)
snd_printk(KERN_ERR PREFIX "midi out urb "
"submit failed: %d\n", ret);
else
rt->out = alsa_sub;
}
} else if (rt->out == alsa_sub)
rt->out = NULL;
spin_unlock_irqrestore(&rt->out_lock, flags);
}
static void usb6fire_midi_out_drain(struct snd_rawmidi_substream *alsa_sub)
{
struct midi_runtime *rt = alsa_sub->rmidi->private_data;
int retry = 0;
while (rt->out && retry++ < 100)
msleep(10);
}
static int usb6fire_midi_in_open(struct snd_rawmidi_substream *alsa_sub)
{
return 0;
}
static int usb6fire_midi_in_close(struct snd_rawmidi_substream *alsa_sub)
{
return 0;
}
static void usb6fire_midi_in_trigger(
struct snd_rawmidi_substream *alsa_sub, int up)
{
struct midi_runtime *rt = alsa_sub->rmidi->private_data;
unsigned long flags;
spin_lock_irqsave(&rt->in_lock, flags);
if (up)
rt->in = alsa_sub;
else
rt->in = NULL;
spin_unlock_irqrestore(&rt->in_lock, flags);
}
static struct snd_rawmidi_ops out_ops = {
.open = usb6fire_midi_out_open,
.close = usb6fire_midi_out_close,
.trigger = usb6fire_midi_out_trigger,
.drain = usb6fire_midi_out_drain
};
static struct snd_rawmidi_ops in_ops = {
.open = usb6fire_midi_in_open,
.close = usb6fire_midi_in_close,
.trigger = usb6fire_midi_in_trigger
};
int __devinit usb6fire_midi_init(struct sfire_chip *chip)
{
int ret;
struct midi_runtime *rt = kzalloc(sizeof(struct midi_runtime),
GFP_KERNEL);
struct comm_runtime *comm_rt = chip->comm;
if (!rt)
return -ENOMEM;
rt->chip = chip;
rt->in_received = usb6fire_midi_in_received;
rt->out_buffer[0] = 0x80; /* 'send midi' command */
rt->out_buffer[1] = 0x00; /* size of data */
rt->out_buffer[2] = 0x00; /* always 0 */
spin_lock_init(&rt->in_lock);
spin_lock_init(&rt->out_lock);
comm_rt->init_urb(comm_rt, &rt->out_urb, rt->out_buffer, rt,
usb6fire_midi_out_handler);
ret = snd_rawmidi_new(chip->card, "6FireUSB", 0, 1, 1, &rt->instance);
if (ret < 0) {
kfree(rt);
snd_printk(KERN_ERR PREFIX "unable to create midi.\n");
return ret;
}
rt->instance->private_data = rt;
strcpy(rt->instance->name, "DMX6FireUSB MIDI");
rt->instance->info_flags = SNDRV_RAWMIDI_INFO_OUTPUT |
SNDRV_RAWMIDI_INFO_INPUT |
SNDRV_RAWMIDI_INFO_DUPLEX;
snd_rawmidi_set_ops(rt->instance, SNDRV_RAWMIDI_STREAM_OUTPUT,
&out_ops);
snd_rawmidi_set_ops(rt->instance, SNDRV_RAWMIDI_STREAM_INPUT,
&in_ops);
chip->midi = rt;
return 0;
}
void usb6fire_midi_abort(struct sfire_chip *chip)
{
struct midi_runtime *rt = chip->midi;
if (rt)
usb_poison_urb(&rt->out_urb);
}
void usb6fire_midi_destroy(struct sfire_chip *chip)
{
kfree(chip->midi);
chip->midi = NULL;
}
/*
* Linux driver for TerraTec DMX 6Fire USB
*
* Author: Torsten Schenk <torsten.schenk@zoho.com>
* Created: Jan 01, 2011
* Version: 0.3.0
* Copyright: (C) Torsten Schenk
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*/
#ifndef USB6FIRE_MIDI_H
#define USB6FIRE_MIDI_H
#include "common.h"
enum {
MIDI_BUFSIZE = 64
};
struct midi_runtime {
struct sfire_chip *chip;
struct snd_rawmidi *instance;
struct snd_rawmidi_substream *in;
char in_active;
spinlock_t in_lock;
spinlock_t out_lock;
struct snd_rawmidi_substream *out;
struct urb out_urb;
u8 out_serial; /* serial number of out packet */
u8 out_buffer[MIDI_BUFSIZE];
int buffer_offset;
void (*in_received)(struct midi_runtime *rt, u8 *data, int length);
};
int __devinit usb6fire_midi_init(struct sfire_chip *chip);
void usb6fire_midi_abort(struct sfire_chip *chip);
void usb6fire_midi_destroy(struct sfire_chip *chip);
#endif /* USB6FIRE_MIDI_H */
/*
* Linux driver for TerraTec DMX 6Fire USB
*
* PCM driver
*
* Author: Torsten Schenk <torsten.schenk@zoho.com>
* Created: Jan 01, 2011
* Version: 0.3.0
* Copyright: (C) Torsten Schenk
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*/
#include "pcm.h"
#include "chip.h"
#include "comm.h"
enum {
OUT_N_CHANNELS = 6, IN_N_CHANNELS = 4
};
/* keep next two synced with
* FW_EP_W_MAX_PACKET_SIZE[] and RATES_MAX_PACKET_SIZE */
static const int rates_in_packet_size[] = { 228, 228, 420, 420, 404, 404 };
static const int rates_out_packet_size[] = { 228, 228, 420, 420, 604, 604 };
static const int rates[] = { 44100, 48000, 88200, 96000, 176400, 192000 };
static const int rates_altsetting[] = { 1, 1, 2, 2, 3, 3 };
static const int rates_alsaid[] = {
SNDRV_PCM_RATE_44100, SNDRV_PCM_RATE_48000,
SNDRV_PCM_RATE_88200, SNDRV_PCM_RATE_96000,
SNDRV_PCM_RATE_176400, SNDRV_PCM_RATE_192000 };
/* values to write to soundcard register for all samplerates */
static const u16 rates_6fire_vl[] = {0x00, 0x01, 0x00, 0x01, 0x00, 0x01};
static const u16 rates_6fire_vh[] = {0x11, 0x11, 0x10, 0x10, 0x00, 0x00};
enum { /* settings for pcm */
OUT_EP = 6, IN_EP = 2, MAX_BUFSIZE = 128 * 1024
};
enum { /* pcm streaming states */
STREAM_DISABLED, /* no pcm streaming */
STREAM_STARTING, /* pcm streaming requested, waiting to become ready */
STREAM_RUNNING, /* pcm streaming running */
STREAM_STOPPING
};
enum { /* pcm sample rates (also index into RATES_XXX[]) */
RATE_44KHZ,
RATE_48KHZ,
RATE_88KHZ,
RATE_96KHZ,
RATE_176KHZ,
RATE_192KHZ
};
static const struct snd_pcm_hardware pcm_hw = {
.info = SNDRV_PCM_INFO_MMAP |
SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_BLOCK_TRANSFER |
SNDRV_PCM_INFO_MMAP_VALID |
SNDRV_PCM_INFO_BATCH,
.formats = SNDRV_PCM_FMTBIT_S24_LE,
.rates = SNDRV_PCM_RATE_44100 |
SNDRV_PCM_RATE_48000 |
SNDRV_PCM_RATE_88200 |
SNDRV_PCM_RATE_96000 |
SNDRV_PCM_RATE_176400 |
SNDRV_PCM_RATE_192000,
.rate_min = 44100,
.rate_max = 192000,
.channels_min = 1,
.channels_max = 0, /* set in pcm_open, depending on capture/playback */
.buffer_bytes_max = MAX_BUFSIZE,
.period_bytes_min = PCM_N_PACKETS_PER_URB * (PCM_MAX_PACKET_SIZE - 4),
.period_bytes_max = MAX_BUFSIZE,
.periods_min = 2,
.periods_max = 1024
};
static int usb6fire_pcm_set_rate(struct pcm_runtime *rt)
{
int ret;
struct usb_device *device = rt->chip->dev;
struct comm_runtime *comm_rt = rt->chip->comm;
if (rt->rate >= ARRAY_SIZE(rates))
return -EINVAL;
/* disable streaming */
ret = comm_rt->write16(comm_rt, 0x02, 0x00, 0x00, 0x00);
if (ret < 0) {
snd_printk(KERN_ERR PREFIX "error stopping streaming while "
"setting samplerate %d.\n", rates[rt->rate]);
return ret;
}
ret = usb_set_interface(device, 1, rates_altsetting[rt->rate]);
if (ret < 0) {
snd_printk(KERN_ERR PREFIX "error setting interface "
"altsetting %d for samplerate %d.\n",
rates_altsetting[rt->rate], rates[rt->rate]);
return ret;
}
/* set soundcard clock */
ret = comm_rt->write16(comm_rt, 0x02, 0x01, rates_6fire_vl[rt->rate],
rates_6fire_vh[rt->rate]);
if (ret < 0) {
snd_printk(KERN_ERR PREFIX "error setting samplerate %d.\n",
rates[rt->rate]);
return ret;
}
/* enable analog inputs and outputs
* (one bit per stereo-channel) */
ret = comm_rt->write16(comm_rt, 0x02, 0x02,
(1 << (OUT_N_CHANNELS / 2)) - 1,
(1 << (IN_N_CHANNELS / 2)) - 1);
if (ret < 0) {
snd_printk(KERN_ERR PREFIX "error initializing analog channels "
"while setting samplerate %d.\n",
rates[rt->rate]);
return ret;
}
/* disable digital inputs and outputs */
ret = comm_rt->write16(comm_rt, 0x02, 0x03, 0x00, 0x00);
if (ret < 0) {
snd_printk(KERN_ERR PREFIX "error initializing digital "
"channels while setting samplerate %d.\n",
rates[rt->rate]);
return ret;
}
ret = comm_rt->write16(comm_rt, 0x02, 0x00, 0x00, 0x01);
if (ret < 0) {
snd_printk(KERN_ERR PREFIX "error starting streaming while "
"setting samplerate %d.\n", rates[rt->rate]);
return ret;
}
rt->in_n_analog = IN_N_CHANNELS;
rt->out_n_analog = OUT_N_CHANNELS;
rt->in_packet_size = rates_in_packet_size[rt->rate];
rt->out_packet_size = rates_out_packet_size[rt->rate];
return 0;
}
static struct pcm_substream *usb6fire_pcm_get_substream(
struct snd_pcm_substream *alsa_sub)
{
struct pcm_runtime *rt = snd_pcm_substream_chip(alsa_sub);
if (alsa_sub->stream == SNDRV_PCM_STREAM_PLAYBACK)
return &rt->playback;
else if (alsa_sub->stream == SNDRV_PCM_STREAM_CAPTURE)
return &rt->capture;
snd_printk(KERN_ERR PREFIX "error getting pcm substream slot.\n");
return NULL;
}
/* call with stream_mutex locked */
static void usb6fire_pcm_stream_stop(struct pcm_runtime *rt)
{
int i;
if (rt->stream_state != STREAM_DISABLED) {
for (i = 0; i < PCM_N_URBS; i++) {
usb_kill_urb(&rt->in_urbs[i].instance);
usb_kill_urb(&rt->out_urbs[i].instance);
}
rt->stream_state = STREAM_DISABLED;
}
}
/* call with stream_mutex locked */
static int usb6fire_pcm_stream_start(struct pcm_runtime *rt)
{
int ret;
int i;
int k;
struct usb_iso_packet_descriptor *packet;
if (rt->stream_state == STREAM_DISABLED) {
/* submit our in urbs */
rt->stream_wait_cond = false;
rt->stream_state = STREAM_STARTING;
for (i = 0; i < PCM_N_URBS; i++) {
for (k = 0; k < PCM_N_PACKETS_PER_URB; k++) {
packet = &rt->in_urbs[i].packets[k];
packet->offset = k * rt->in_packet_size;
packet->length = rt->in_packet_size;
packet->actual_length = 0;
packet->status = 0;
}
ret = usb_submit_urb(&rt->in_urbs[i].instance,
GFP_ATOMIC);
if (ret) {
usb6fire_pcm_stream_stop(rt);
return ret;
}
}
/* wait for first out urb to return (sent in in urb handler) */
wait_event_timeout(rt->stream_wait_queue, rt->stream_wait_cond,
HZ);
if (rt->stream_wait_cond)
rt->stream_state = STREAM_RUNNING;
else {
usb6fire_pcm_stream_stop(rt);
return -EIO;
}
}
return 0;
}
/* call with substream locked */
static void usb6fire_pcm_capture(struct pcm_substream *sub, struct pcm_urb *urb)
{
int i;
int frame;
int frame_count;
unsigned int total_length = 0;
struct pcm_runtime *rt = snd_pcm_substream_chip(sub->instance);
struct snd_pcm_runtime *alsa_rt = sub->instance->runtime;
u32 *src = (u32 *) urb->buffer;
u32 *dest = (u32 *) (alsa_rt->dma_area + sub->dma_off
* (alsa_rt->frame_bits >> 3));
u32 *dest_end = (u32 *) (alsa_rt->dma_area + alsa_rt->buffer_size
* (alsa_rt->frame_bits >> 3));
int bytes_per_frame = alsa_rt->channels << 2;
for (i = 0; i < PCM_N_PACKETS_PER_URB; i++) {
/* at least 4 header bytes for valid packet.
* after that: 32 bits per sample for analog channels */
if (urb->packets[i].actual_length > 4)
frame_count = (urb->packets[i].actual_length - 4)
/ (rt->in_n_analog << 2);
else
frame_count = 0;
src = (u32 *) (urb->buffer + total_length);
src++; /* skip leading 4 bytes of every packet */
total_length += urb->packets[i].length;
for (frame = 0; frame < frame_count; frame++) {
memcpy(dest, src, bytes_per_frame);
dest += alsa_rt->channels;
src += rt->in_n_analog;
sub->dma_off++;
sub->period_off++;
if (dest == dest_end) {
sub->dma_off = 0;
dest = (u32 *) alsa_rt->dma_area;
}
}
}
}
/* call with substream locked */
static void usb6fire_pcm_playback(struct pcm_substream *sub,
struct pcm_urb *urb)
{
int i;
int frame;
int frame_count;
struct pcm_runtime *rt = snd_pcm_substream_chip(sub->instance);
struct snd_pcm_runtime *alsa_rt = sub->instance->runtime;
u32 *src = (u32 *) (alsa_rt->dma_area + sub->dma_off
* (alsa_rt->frame_bits >> 3));
u32 *src_end = (u32 *) (alsa_rt->dma_area + alsa_rt->buffer_size
* (alsa_rt->frame_bits >> 3));
u32 *dest = (u32 *) urb->buffer;
int bytes_per_frame = alsa_rt->channels << 2;
for (i = 0; i < PCM_N_PACKETS_PER_URB; i++) {
/* at least 4 header bytes for valid packet.
* after that: 32 bits per sample for analog channels */
if (urb->packets[i].length > 4)
frame_count = (urb->packets[i].length - 4)
/ (rt->out_n_analog << 2);
else
frame_count = 0;
dest++; /* skip leading 4 bytes of every frame */
for (frame = 0; frame < frame_count; frame++) {
memcpy(dest, src, bytes_per_frame);
src += alsa_rt->channels;
dest += rt->out_n_analog;
sub->dma_off++;
sub->period_off++;
if (src == src_end) {
src = (u32 *) alsa_rt->dma_area;
sub->dma_off = 0;
}
}
}
}
static void usb6fire_pcm_in_urb_handler(struct urb *usb_urb)
{
struct pcm_urb *in_urb = usb_urb->context;
struct pcm_urb *out_urb = in_urb->peer;
struct pcm_runtime *rt = in_urb->chip->pcm;
struct pcm_substream *sub;
unsigned long flags;
int total_length = 0;
int frame_count;
int frame;
int channel;
int i;
u8 *dest;
if (usb_urb->status || rt->panic || rt->stream_state == STREAM_STOPPING)
return;
for (i = 0; i < PCM_N_PACKETS_PER_URB; i++)
if (in_urb->packets[i].status) {
rt->panic = true;
return;
}
if (rt->stream_state == STREAM_DISABLED) {
snd_printk(KERN_ERR PREFIX "internal error: "
"stream disabled in in-urb handler.\n");
return;
}
/* receive our capture data */
sub = &rt->capture;
spin_lock_irqsave(&sub->lock, flags);
if (sub->active) {
usb6fire_pcm_capture(sub, in_urb);
if (sub->period_off >= sub->instance->runtime->period_size) {
sub->period_off %= sub->instance->runtime->period_size;
spin_unlock_irqrestore(&sub->lock, flags);
snd_pcm_period_elapsed(sub->instance);
} else
spin_unlock_irqrestore(&sub->lock, flags);
} else
spin_unlock_irqrestore(&sub->lock, flags);
/* setup out urb structure */
for (i = 0; i < PCM_N_PACKETS_PER_URB; i++) {
out_urb->packets[i].offset = total_length;
out_urb->packets[i].length = (in_urb->packets[i].actual_length
- 4) / (rt->in_n_analog << 2)
* (rt->out_n_analog << 2) + 4;
out_urb->packets[i].status = 0;
total_length += out_urb->packets[i].length;
}
memset(out_urb->buffer, 0, total_length);
/* now send our playback data (if a free out urb was found) */
sub = &rt->playback;
spin_lock_irqsave(&sub->lock, flags);
if (sub->active) {
usb6fire_pcm_playback(sub, out_urb);
if (sub->period_off >= sub->instance->runtime->period_size) {
sub->period_off %= sub->instance->runtime->period_size;
spin_unlock_irqrestore(&sub->lock, flags);
snd_pcm_period_elapsed(sub->instance);
} else
spin_unlock_irqrestore(&sub->lock, flags);
} else
spin_unlock_irqrestore(&sub->lock, flags);
/* setup the 4th byte of each sample (0x40 for analog channels) */
dest = out_urb->buffer;
for (i = 0; i < PCM_N_PACKETS_PER_URB; i++)
if (out_urb->packets[i].length >= 4) {
frame_count = (out_urb->packets[i].length - 4)
/ (rt->out_n_analog << 2);
*(dest++) = 0xaa;
*(dest++) = 0xaa;
*(dest++) = frame_count;
*(dest++) = 0x00;
for (frame = 0; frame < frame_count; frame++)
for (channel = 0;
channel < rt->out_n_analog;
channel++) {
dest += 3; /* skip sample data */
*(dest++) = 0x40;
}
}
usb_submit_urb(&out_urb->instance, GFP_ATOMIC);
usb_submit_urb(&in_urb->instance, GFP_ATOMIC);
}
static void usb6fire_pcm_out_urb_handler(struct urb *usb_urb)
{
struct pcm_urb *urb = usb_urb->context;
struct pcm_runtime *rt = urb->chip->pcm;
if (rt->stream_state == STREAM_STARTING) {
rt->stream_wait_cond = true;
wake_up(&rt->stream_wait_queue);
}
}
static int usb6fire_pcm_open(struct snd_pcm_substream *alsa_sub)
{
struct pcm_runtime *rt = snd_pcm_substream_chip(alsa_sub);
struct pcm_substream *sub = NULL;
struct snd_pcm_runtime *alsa_rt = alsa_sub->runtime;
if (rt->panic)
return -EPIPE;
mutex_lock(&rt->stream_mutex);
alsa_rt->hw = pcm_hw;
if (alsa_sub->stream == SNDRV_PCM_STREAM_PLAYBACK) {
if (rt->rate >= 0)
alsa_rt->hw.rates = rates_alsaid[rt->rate];
alsa_rt->hw.channels_max = OUT_N_CHANNELS;
sub = &rt->playback;
} else if (alsa_sub->stream == SNDRV_PCM_STREAM_CAPTURE) {
if (rt->rate >= 0)
alsa_rt->hw.rates = rates_alsaid[rt->rate];
alsa_rt->hw.channels_max = IN_N_CHANNELS;
sub = &rt->capture;
}
if (!sub) {
mutex_unlock(&rt->stream_mutex);
snd_printk(KERN_ERR PREFIX "invalid stream type.\n");
return -EINVAL;
}
sub->instance = alsa_sub;
sub->active = false;
mutex_unlock(&rt->stream_mutex);
return 0;
}
static int usb6fire_pcm_close(struct snd_pcm_substream *alsa_sub)
{
struct pcm_runtime *rt = snd_pcm_substream_chip(alsa_sub);
struct pcm_substream *sub = usb6fire_pcm_get_substream(alsa_sub);
unsigned long flags;
if (rt->panic)
return 0;
mutex_lock(&rt->stream_mutex);
if (sub) {
/* deactivate substream */
spin_lock_irqsave(&sub->lock, flags);
sub->instance = NULL;
sub->active = false;
spin_unlock_irqrestore(&sub->lock, flags);
/* all substreams closed? if so, stop streaming */
if (!rt->playback.instance && !rt->capture.instance) {
usb6fire_pcm_stream_stop(rt);
rt->rate = -1;
}
}
mutex_unlock(&rt->stream_mutex);
return 0;
}
static int usb6fire_pcm_hw_params(struct snd_pcm_substream *alsa_sub,
struct snd_pcm_hw_params *hw_params)
{
return snd_pcm_lib_malloc_pages(alsa_sub,
params_buffer_bytes(hw_params));
}
static int usb6fire_pcm_hw_free(struct snd_pcm_substream *alsa_sub)
{
return snd_pcm_lib_free_pages(alsa_sub);
}
static int usb6fire_pcm_prepare(struct snd_pcm_substream *alsa_sub)
{
struct pcm_runtime *rt = snd_pcm_substream_chip(alsa_sub);
struct pcm_substream *sub = usb6fire_pcm_get_substream(alsa_sub);
struct snd_pcm_runtime *alsa_rt = alsa_sub->runtime;
int i;
int ret;
if (rt->panic)
return -EPIPE;
if (!sub)
return -ENODEV;
mutex_lock(&rt->stream_mutex);
sub->dma_off = 0;
sub->period_off = 0;
if (rt->stream_state == STREAM_DISABLED) {
rt->rate = -1;
for (i = 0; i < ARRAY_SIZE(rates); i++)
if (alsa_rt->rate == rates[i]) {
rt->rate = i;
break;
}
if (rt->rate == -1) {
mutex_unlock(&rt->stream_mutex);
snd_printk("invalid rate %d in prepare.\n",
alsa_rt->rate);
return -EINVAL;
}
ret = usb6fire_pcm_set_rate(rt);
if (ret) {
mutex_unlock(&rt->stream_mutex);
return ret;
}
ret = usb6fire_pcm_stream_start(rt);
if (ret) {
mutex_unlock(&rt->stream_mutex);
snd_printk(KERN_ERR PREFIX
"could not start pcm stream.\n");
return ret;
}
}
mutex_unlock(&rt->stream_mutex);
return 0;
}
static int usb6fire_pcm_trigger(struct snd_pcm_substream *alsa_sub, int cmd)
{
struct pcm_substream *sub = usb6fire_pcm_get_substream(alsa_sub);
struct pcm_runtime *rt = snd_pcm_substream_chip(alsa_sub);
unsigned long flags;
if (rt->panic)
return -EPIPE;
if (!sub)
return -ENODEV;
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
spin_lock_irqsave(&sub->lock, flags);
sub->active = true;
spin_unlock_irqrestore(&sub->lock, flags);
return 0;
case SNDRV_PCM_TRIGGER_STOP:
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
spin_lock_irqsave(&sub->lock, flags);
sub->active = false;
spin_unlock_irqrestore(&sub->lock, flags);
return 0;
default:
return -EINVAL;
}
}
static snd_pcm_uframes_t usb6fire_pcm_pointer(
struct snd_pcm_substream *alsa_sub)
{
struct pcm_substream *sub = usb6fire_pcm_get_substream(alsa_sub);
struct pcm_runtime *rt = snd_pcm_substream_chip(alsa_sub);
unsigned long flags;
snd_pcm_uframes_t ret;
if (rt->panic || !sub)
return SNDRV_PCM_STATE_XRUN;
spin_lock_irqsave(&sub->lock, flags);
ret = sub->dma_off;
spin_unlock_irqrestore(&sub->lock, flags);
return ret;
}
static struct snd_pcm_ops pcm_ops = {
.open = usb6fire_pcm_open,
.close = usb6fire_pcm_close,
.ioctl = snd_pcm_lib_ioctl,
.hw_params = usb6fire_pcm_hw_params,
.hw_free = usb6fire_pcm_hw_free,
.prepare = usb6fire_pcm_prepare,
.trigger = usb6fire_pcm_trigger,
.pointer = usb6fire_pcm_pointer,
};
static void __devinit usb6fire_pcm_init_urb(struct pcm_urb *urb,
struct sfire_chip *chip, bool in, int ep,
void (*handler)(struct urb *))
{
urb->chip = chip;
usb_init_urb(&urb->instance);
urb->instance.transfer_buffer = urb->buffer;
urb->instance.transfer_buffer_length =
PCM_N_PACKETS_PER_URB * PCM_MAX_PACKET_SIZE;
urb->instance.dev = chip->dev;
urb->instance.pipe = in ? usb_rcvisocpipe(chip->dev, ep)
: usb_sndisocpipe(chip->dev, ep);
urb->instance.interval = 1;
urb->instance.transfer_flags = URB_ISO_ASAP;
urb->instance.complete = handler;
urb->instance.context = urb;
urb->instance.number_of_packets = PCM_N_PACKETS_PER_URB;
}
int __devinit usb6fire_pcm_init(struct sfire_chip *chip)
{
int i;
int ret;
struct snd_pcm *pcm;
struct pcm_runtime *rt =
kzalloc(sizeof(struct pcm_runtime), GFP_KERNEL);
if (!rt)
return -ENOMEM;
rt->chip = chip;
rt->stream_state = STREAM_DISABLED;
rt->rate = -1;
init_waitqueue_head(&rt->stream_wait_queue);
mutex_init(&rt->stream_mutex);
spin_lock_init(&rt->playback.lock);
spin_lock_init(&rt->capture.lock);
for (i = 0; i < PCM_N_URBS; i++) {
usb6fire_pcm_init_urb(&rt->in_urbs[i], chip, true, IN_EP,
usb6fire_pcm_in_urb_handler);
usb6fire_pcm_init_urb(&rt->out_urbs[i], chip, false, OUT_EP,
usb6fire_pcm_out_urb_handler);
rt->in_urbs[i].peer = &rt->out_urbs[i];
rt->out_urbs[i].peer = &rt->in_urbs[i];
}
ret = snd_pcm_new(chip->card, "DMX6FireUSB", 0, 1, 1, &pcm);
if (ret < 0) {
kfree(rt);
snd_printk(KERN_ERR PREFIX "cannot create pcm instance.\n");
return ret;
}
pcm->private_data = rt;
strcpy(pcm->name, "DMX 6Fire USB");
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &pcm_ops);
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &pcm_ops);
ret = snd_pcm_lib_preallocate_pages_for_all(pcm,
SNDRV_DMA_TYPE_CONTINUOUS,
snd_dma_continuous_data(GFP_KERNEL),
MAX_BUFSIZE, MAX_BUFSIZE);
if (ret) {
kfree(rt);
snd_printk(KERN_ERR PREFIX
"error preallocating pcm buffers.\n");
return ret;
}
rt->instance = pcm;
chip->pcm = rt;
return 0;
}
void usb6fire_pcm_abort(struct sfire_chip *chip)
{
struct pcm_runtime *rt = chip->pcm;
int i;
if (rt) {
rt->panic = true;
if (rt->playback.instance)
snd_pcm_stop(rt->playback.instance,
SNDRV_PCM_STATE_XRUN);
if (rt->capture.instance)
snd_pcm_stop(rt->capture.instance,
SNDRV_PCM_STATE_XRUN);
for (i = 0; i < PCM_N_URBS; i++) {
usb_poison_urb(&rt->in_urbs[i].instance);
usb_poison_urb(&rt->out_urbs[i].instance);
}
}
}
void usb6fire_pcm_destroy(struct sfire_chip *chip)
{
kfree(chip->pcm);
chip->pcm = NULL;
}
/*
* Linux driver for TerraTec DMX 6Fire USB
*
* Author: Torsten Schenk <torsten.schenk@zoho.com>
* Created: Jan 01, 2011
* Version: 0.3.0
* Copyright: (C) Torsten Schenk
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*/
#ifndef USB6FIRE_PCM_H
#define USB6FIRE_PCM_H
#include <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[PCM_N_PACKETS_PER_URB * PCM_MAX_PACKET_SIZE];
struct pcm_urb *peer;
};
struct pcm_substream {
spinlock_t lock;
struct snd_pcm_substream *instance;
bool active;
snd_pcm_uframes_t dma_off; /* current position in alsa dma_area */
snd_pcm_uframes_t period_off; /* current position in current period */
};
struct pcm_runtime {
struct sfire_chip *chip;
struct snd_pcm *instance;
struct pcm_substream playback;
struct pcm_substream capture;
bool panic; /* if set driver won't do anymore pcm on device */
struct pcm_urb in_urbs[PCM_N_URBS];
struct pcm_urb out_urbs[PCM_N_URBS];
int in_packet_size;
int out_packet_size;
int in_n_analog; /* number of analog channels soundcard sends */
int out_n_analog; /* number of analog channels soundcard receives */
struct mutex stream_mutex;
u8 stream_state; /* one of STREAM_XXX (pcm.c) */
u8 rate; /* one of PCM_RATE_XXX */
wait_queue_head_t stream_wait_queue;
bool stream_wait_cond;
};
int __devinit usb6fire_pcm_init(struct sfire_chip *chip);
void usb6fire_pcm_abort(struct sfire_chip *chip);
void usb6fire_pcm_destroy(struct sfire_chip *chip);
#endif /* USB6FIRE_PCM_H */
......@@ -97,5 +97,21 @@ config SND_USB_US122L
To compile this driver as a module, choose M here: the module
will be called snd-usb-us122l.
config SND_USB_6FIRE
tristate "TerraTec DMX 6Fire USB"
depends on EXPERIMENTAL
select FW_LOADER
select SND_RAWMIDI
select SND_PCM
help
Say Y here to include support for TerraTec 6fire DMX USB interface.
You will need firmware files in order to be able to use the device
after it has been coldstarted. This driver currently does not support
firmware loading for all devices. If you own such a device,
you could start windows and let the windows driver upload
the firmware. As long as you do not unplug your device from power,
it should be usable.
endif # SND_USB
......@@ -23,4 +23,4 @@ obj-$(CONFIG_SND_USB_UA101) += snd-usbmidi-lib.o
obj-$(CONFIG_SND_USB_USX2Y) += snd-usbmidi-lib.o
obj-$(CONFIG_SND_USB_US122L) += snd-usbmidi-lib.o
obj-$(CONFIG_SND) += misc/ usx2y/ caiaq/
obj-$(CONFIG_SND) += misc/ usx2y/ caiaq/ 6fire/
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment