Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

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

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
Loading
Loading
Loading
Loading
+3 −0
Original line number Diff line number Diff line
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

sound/usb/6fire/chip.c

0 → 100644
+232 −0
Original line number Diff line number Diff line
/*
 * 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);

sound/usb/6fire/chip.h

0 → 100644
+32 −0
Original line number Diff line number Diff line
/*
 * 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 */

sound/usb/6fire/comm.c

0 → 100644
+176 −0
Original line number Diff line number Diff line
/*
 * 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;
}

sound/usb/6fire/comm.h

0 → 100644
+44 −0
Original line number Diff line number Diff line
/*
 * 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 */
Loading