mirror of
				https://github.com/torvalds/linux.git
				synced 2025-11-04 02:30:34 +02:00 
			
		
		
		
	ALSA: usb/6fire - Driver for TerraTec DMX 6Fire USB
What is working: Everything except SPDIF - Hardware Master volume - PCM 44-192kHz@24 bits, 6 channels out, 4 channels in (analog) - MIDI in/out - firmware loading after cold start - phono/line switching Signed-off-by: Torsten Schenk <torsten.schenk@zoho.com> Signed-off-by: Takashi Iwai <tiwai@suse.de>
This commit is contained in:
		
							parent
							
								
									49c6ad430d
								
							
						
					
					
						commit
						c6d43ba816
					
				
					 16 changed files with 2313 additions and 1 deletions
				
			
		
							
								
								
									
										3
									
								
								sound/usb/6fire/Makefile
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								sound/usb/6fire/Makefile
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,3 @@
 | 
			
		|||
snd-usb-6fire-objs += chip.o comm.o midi.o control.o firmware.o pcm.o
 | 
			
		||||
obj-$(CONFIG_SND_USB_6FIRE) += snd-usb-6fire.o
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										232
									
								
								sound/usb/6fire/chip.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										232
									
								
								sound/usb/6fire/chip.c
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,232 @@
 | 
			
		|||
/*
 | 
			
		||||
 * 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(®ister_mutex);
 | 
			
		||||
	for (i = 0; i < SNDRV_CARDS; i++) {
 | 
			
		||||
		if (devices[i] == device) {
 | 
			
		||||
			if (chips[i])
 | 
			
		||||
				chips[i]->intf_count++;
 | 
			
		||||
			usb_set_intfdata(intf, chips[i]);
 | 
			
		||||
			mutex_unlock(®ister_mutex);
 | 
			
		||||
			return 0;
 | 
			
		||||
		} else if (regidx < 0)
 | 
			
		||||
			regidx = i;
 | 
			
		||||
	}
 | 
			
		||||
	if (regidx < 0) {
 | 
			
		||||
		mutex_unlock(®ister_mutex);
 | 
			
		||||
		snd_printk(KERN_ERR PREFIX "too many cards registered.\n");
 | 
			
		||||
		return -ENODEV;
 | 
			
		||||
	}
 | 
			
		||||
	devices[regidx] = device;
 | 
			
		||||
	mutex_unlock(®ister_mutex);
 | 
			
		||||
 | 
			
		||||
	/* check, if firmware is present on device, upload it if not */
 | 
			
		||||
	ret = usb6fire_fw_init(intf);
 | 
			
		||||
	if (ret < 0)
 | 
			
		||||
		return ret;
 | 
			
		||||
	else if (ret == FW_NOT_READY) /* firmware update performed */
 | 
			
		||||
		return 0;
 | 
			
		||||
 | 
			
		||||
	/* if we are here, card can be registered in alsa. */
 | 
			
		||||
	if (usb_set_interface(device, 0, 0) != 0) {
 | 
			
		||||
		snd_printk(KERN_ERR PREFIX "can't set first interface.\n");
 | 
			
		||||
		return -EIO;
 | 
			
		||||
	}
 | 
			
		||||
	ret = snd_card_create(index[regidx], id[regidx], THIS_MODULE,
 | 
			
		||||
			sizeof(struct sfire_chip), &card);
 | 
			
		||||
	if (ret < 0) {
 | 
			
		||||
		snd_printk(KERN_ERR PREFIX "cannot create alsa card.\n");
 | 
			
		||||
		return ret;
 | 
			
		||||
	}
 | 
			
		||||
	strcpy(card->driver, "6FireUSB");
 | 
			
		||||
	strcpy(card->shortname, "TerraTec DMX6FireUSB");
 | 
			
		||||
	sprintf(card->longname, "%s at %d:%d", card->shortname,
 | 
			
		||||
			device->bus->busnum, device->devnum);
 | 
			
		||||
	snd_card_set_dev(card, &intf->dev);
 | 
			
		||||
 | 
			
		||||
	chip = card->private_data;
 | 
			
		||||
	chips[regidx] = chip;
 | 
			
		||||
	chip->dev = device;
 | 
			
		||||
	chip->regidx = regidx;
 | 
			
		||||
	chip->intf_count = 1;
 | 
			
		||||
	chip->card = card;
 | 
			
		||||
 | 
			
		||||
	ret = usb6fire_comm_init(chip);
 | 
			
		||||
	if (ret < 0) {
 | 
			
		||||
		usb6fire_chip_destroy(chip);
 | 
			
		||||
		return ret;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ret = usb6fire_midi_init(chip);
 | 
			
		||||
	if (ret < 0) {
 | 
			
		||||
		usb6fire_chip_destroy(chip);
 | 
			
		||||
		return ret;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ret = usb6fire_pcm_init(chip);
 | 
			
		||||
	if (ret < 0) {
 | 
			
		||||
		usb6fire_chip_destroy(chip);
 | 
			
		||||
		return ret;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ret = usb6fire_control_init(chip);
 | 
			
		||||
	if (ret < 0) {
 | 
			
		||||
		usb6fire_chip_destroy(chip);
 | 
			
		||||
		return ret;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ret = snd_card_register(card);
 | 
			
		||||
	if (ret < 0) {
 | 
			
		||||
		snd_printk(KERN_ERR PREFIX "cannot register card.");
 | 
			
		||||
		usb6fire_chip_destroy(chip);
 | 
			
		||||
		return ret;
 | 
			
		||||
	}
 | 
			
		||||
	usb_set_intfdata(intf, chip);
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void usb6fire_chip_disconnect(struct usb_interface *intf)
 | 
			
		||||
{
 | 
			
		||||
	struct sfire_chip *chip;
 | 
			
		||||
	struct snd_card *card;
 | 
			
		||||
 | 
			
		||||
	chip = usb_get_intfdata(intf);
 | 
			
		||||
	if (chip) { /* if !chip, fw upload has been performed */
 | 
			
		||||
		card = chip->card;
 | 
			
		||||
		chip->intf_count--;
 | 
			
		||||
		if (!chip->intf_count) {
 | 
			
		||||
			mutex_lock(®ister_mutex);
 | 
			
		||||
			devices[chip->regidx] = NULL;
 | 
			
		||||
			chips[chip->regidx] = NULL;
 | 
			
		||||
			mutex_unlock(®ister_mutex);
 | 
			
		||||
 | 
			
		||||
			chip->shutdown = true;
 | 
			
		||||
			usb6fire_chip_abort(chip);
 | 
			
		||||
			usb6fire_chip_destroy(chip);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static struct usb_device_id device_table[] = {
 | 
			
		||||
	{
 | 
			
		||||
		.match_flags = USB_DEVICE_ID_MATCH_DEVICE,
 | 
			
		||||
		.idVendor = 0x0ccd,
 | 
			
		||||
		.idProduct = 0x0080
 | 
			
		||||
	},
 | 
			
		||||
	{}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
MODULE_DEVICE_TABLE(usb, device_table);
 | 
			
		||||
 | 
			
		||||
static struct usb_driver driver = {
 | 
			
		||||
	.name = "snd-usb-6fire",
 | 
			
		||||
	.probe = usb6fire_chip_probe,
 | 
			
		||||
	.disconnect = usb6fire_chip_disconnect,
 | 
			
		||||
	.id_table = device_table,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static int __init usb6fire_chip_init(void)
 | 
			
		||||
{
 | 
			
		||||
	return usb_register(&driver);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void __exit usb6fire_chip_cleanup(void)
 | 
			
		||||
{
 | 
			
		||||
	usb_deregister(&driver);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
module_init(usb6fire_chip_init);
 | 
			
		||||
module_exit(usb6fire_chip_cleanup);
 | 
			
		||||
							
								
								
									
										32
									
								
								sound/usb/6fire/chip.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								sound/usb/6fire/chip.h
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,32 @@
 | 
			
		|||
/*
 | 
			
		||||
 * 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 */
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										176
									
								
								sound/usb/6fire/comm.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										176
									
								
								sound/usb/6fire/comm.c
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,176 @@
 | 
			
		|||
/*
 | 
			
		||||
 * 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;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										44
									
								
								sound/usb/6fire/comm.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								sound/usb/6fire/comm.h
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,44 @@
 | 
			
		|||
/*
 | 
			
		||||
 * 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 */
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										30
									
								
								sound/usb/6fire/common.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								sound/usb/6fire/common.h
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,30 @@
 | 
			
		|||
/*
 | 
			
		||||
 * 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 */
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										275
									
								
								sound/usb/6fire/control.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										275
									
								
								sound/usb/6fire/control.c
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,275 @@
 | 
			
		|||
/*
 | 
			
		||||
 * 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;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										37
									
								
								sound/usb/6fire/control.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								sound/usb/6fire/control.h
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,37 @@
 | 
			
		|||
/*
 | 
			
		||||
 * 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 */
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										426
									
								
								sound/usb/6fire/firmware.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										426
									
								
								sound/usb/6fire/firmware.c
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,426 @@
 | 
			
		|||
/*
 | 
			
		||||
 * Linux driver for TerraTec DMX 6Fire USB
 | 
			
		||||
 *
 | 
			
		||||
 * Firmware loader
 | 
			
		||||
 *
 | 
			
		||||
 * Currently not working for all devices. To be able to use the device
 | 
			
		||||
 * in linux, it is also possible to let the windows driver upload the firmware.
 | 
			
		||||
 * For that, start the computer in windows and reboot.
 | 
			
		||||
 * As long as the device is connected to the power supply, no firmware reload
 | 
			
		||||
 * needs to be performed.
 | 
			
		||||
 *
 | 
			
		||||
 * Author:	Torsten Schenk <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;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										27
									
								
								sound/usb/6fire/firmware.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								sound/usb/6fire/firmware.h
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,27 @@
 | 
			
		|||
/*
 | 
			
		||||
 * Linux driver for TerraTec DMX 6Fire USB
 | 
			
		||||
 *
 | 
			
		||||
 * Author: Torsten Schenk
 | 
			
		||||
 * Created: Jan 01, 2011
 | 
			
		||||
 * Copyright:	(C) Torsten Schenk
 | 
			
		||||
 *
 | 
			
		||||
 * This program is free software; you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU General Public License as published by
 | 
			
		||||
 * the Free Software Foundation; either version 2 of the License, or
 | 
			
		||||
 * (at your option) any later version.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#ifndef USB6FIRE_FIRMWARE_H
 | 
			
		||||
#define USB6FIRE_FIRMWARE_H
 | 
			
		||||
 | 
			
		||||
#include "common.h"
 | 
			
		||||
 | 
			
		||||
enum /* firmware state of device */
 | 
			
		||||
{
 | 
			
		||||
	FW_READY = 0,
 | 
			
		||||
	FW_NOT_READY = 1
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
int __devinit usb6fire_fw_init(struct usb_interface *intf);
 | 
			
		||||
#endif /* USB6FIRE_FIRMWARE_H */
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										203
									
								
								sound/usb/6fire/midi.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										203
									
								
								sound/usb/6fire/midi.c
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,203 @@
 | 
			
		|||
/*
 | 
			
		||||
 * 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;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										46
									
								
								sound/usb/6fire/midi.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								sound/usb/6fire/midi.h
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,46 @@
 | 
			
		|||
/*
 | 
			
		||||
 * 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 */
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										689
									
								
								sound/usb/6fire/pcm.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										689
									
								
								sound/usb/6fire/pcm.c
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,689 @@
 | 
			
		|||
/*
 | 
			
		||||
 * 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;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										76
									
								
								sound/usb/6fire/pcm.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										76
									
								
								sound/usb/6fire/pcm.h
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,76 @@
 | 
			
		|||
/*
 | 
			
		||||
 * 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/
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in a new issue