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 */
This diff is collapsed.
/*
* 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 */
This diff is collapsed.
/*
* 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 ...@@ -97,5 +97,21 @@ config SND_USB_US122L
To compile this driver as a module, choose M here: the module To compile this driver as a module, choose M here: the module
will be called snd-usb-us122l. 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 endif # SND_USB
...@@ -23,4 +23,4 @@ obj-$(CONFIG_SND_USB_UA101) += snd-usbmidi-lib.o ...@@ -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_USX2Y) += snd-usbmidi-lib.o
obj-$(CONFIG_SND_USB_US122L) += 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