diff options
author | Markus Grabner <grabner@icg.tugraz.at> | 2009-02-27 19:43:04 -0800 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@suse.de> | 2009-04-03 14:54:24 -0700 |
commit | 705ececd1c60d0f5d6ef2a719008847883516970 (patch) | |
tree | e2a96ac85e15850919e493181fe47f7fdd471af1 | |
parent | e642f09951f7cbb69983781b07bb9cd881546ac4 (diff) |
Staging: add line6 usb driver
This is an experimental Linux driver for the guitar amp, cab, and
effects modeller PODxt Pro by Line6 (and similar devices), supporting
the following features:
- Reading/writing individual parameters
- Reading/writing complete channel, effects setup, and amp setup data
- Channel switching
- Virtual MIDI interface
- Tuner access
- Playback/capture/mixer device for any ALSA-compatible PCM audio
application
- Signal routing (record clean/processed guitar signal, re-amping)
Moreover, preliminary support for the Variax Workbench is included.
From: Markus Grabner <grabner@icg.tugraz.at>
Cc: Mariusz Kozlowski <m.kozlowski@tuxland.pl>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
29 files changed, 6962 insertions, 0 deletions
diff --git a/drivers/staging/line6/Kconfig b/drivers/staging/line6/Kconfig new file mode 100644 index 000000000000..3c1ffcb8cb12 --- /dev/null +++ b/drivers/staging/line6/Kconfig @@ -0,0 +1,20 @@ +config LINE6_USB + tristate "Line6 USB support" + depends on USB + help + This is a driver for the guitar amp, cab, and effects modeller + PODxt Pro by Line6 (and similar devices), supporting the + following features: + * Reading/writing individual parameters + * Reading/writing complete channel, effects setup, and amp + setup data + * Channel switching + * Virtual MIDI interface + * Tuner access + * Playback/capture/mixer device for any ALSA-compatible PCM + audio application + * Signal routing (record clean/processed guitar signal, + re-amping) + + Preliminary support for the Variax Workbench is included. + diff --git a/drivers/staging/line6/Makefile b/drivers/staging/line6/Makefile new file mode 100644 index 000000000000..a1c93edc6b1d --- /dev/null +++ b/drivers/staging/line6/Makefile @@ -0,0 +1,15 @@ +obj-$(CONFIG_LINE6_USB) += line6usb.o + +line6usb-objs := \ + audio.o \ + capture.o \ + control.o \ + driver.o \ + dumprequest.o \ + midi.o \ + midibuf.o \ + pcm.o \ + playback.o \ + pod.o \ + toneport.o \ + variax.o diff --git a/drivers/staging/line6/audio.c b/drivers/staging/line6/audio.c new file mode 100644 index 000000000000..e15fa1f6ee48 --- /dev/null +++ b/drivers/staging/line6/audio.c @@ -0,0 +1,69 @@ +/* + * Line6 Linux USB driver - 0.8.0 + * + * Copyright (C) 2004-2009 Markus Grabner (grabner@icg.tugraz.at) + * + * 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, version 2. + * + */ + +#include "driver.h" + +#include <sound/core.h> +#include <sound/initval.h> + + +static int line6_index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; +static char *line6_id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; + + +/* + Initialize the Line6 USB audio system. +*/ +int line6_init_audio(struct usb_line6 *line6) +{ + static int dev = 0; + struct snd_card *card; + + card = snd_card_new(line6_index[dev], line6_id[dev], THIS_MODULE, 0); + + if(card == NULL) + return -ENOMEM; + + line6->card = card; + + strcpy(card->driver, DRIVER_NAME); + strcpy(card->shortname, "Line6-USB"); + sprintf(card->longname, "Line6 %s at USB %s", line6->properties->name, line6->ifcdev->bus_id); /* 80 chars - see asound.h */ + return 0; +} + +/* + Register the Line6 USB audio system. +*/ +int line6_register_audio(struct usb_line6 *line6) +{ + int err; + + if((err = snd_card_register(line6->card)) < 0) + return err; + + return 0; +} + +/* + Cleanup the Line6 USB audio system. +*/ +void line6_cleanup_audio(struct usb_line6 *line6) +{ + struct snd_card *card = line6->card; + + if(card == 0) + return; + + snd_card_disconnect(card); + snd_card_free(card); + line6->card = 0; +} diff --git a/drivers/staging/line6/audio.h b/drivers/staging/line6/audio.h new file mode 100644 index 000000000000..cc0245adbcd9 --- /dev/null +++ b/drivers/staging/line6/audio.h @@ -0,0 +1,24 @@ +/* + * Line6 Linux USB driver - 0.8.0 + * + * Copyright (C) 2004-2009 Markus Grabner (grabner@icg.tugraz.at) + * + * 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, version 2. + * + */ + +#ifndef AUDIO_H +#define AUDIO_H + + +#include "driver.h" + + +extern void line6_cleanup_audio(struct usb_line6 *); +extern int line6_init_audio(struct usb_line6 *); +extern int line6_register_audio(struct usb_line6 *); + + +#endif diff --git a/drivers/staging/line6/capture.c b/drivers/staging/line6/capture.c new file mode 100644 index 000000000000..5dec3bfff04d --- /dev/null +++ b/drivers/staging/line6/capture.c @@ -0,0 +1,370 @@ +/* + * Line6 Linux USB driver - 0.8.0 + * + * Copyright (C) 2004-2009 Markus Grabner (grabner@icg.tugraz.at) + * + * 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, version 2. + * + */ + +#include "driver.h" + +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> + +#include "audio.h" +#include "pcm.h" +#include "pod.h" + + +/* + Find a free URB and submit it. +*/ +static int submit_audio_in_urb(struct snd_pcm_substream *substream) +{ + int index; + unsigned long flags; + struct snd_line6_pcm *line6pcm = snd_pcm_substream_chip(substream); + int i, urb_size; + struct urb *urb_in; + + spin_lock_irqsave(&line6pcm->lock_audio_in, flags); + index = find_first_zero_bit(&line6pcm->active_urb_in, LINE6_ISO_BUFFERS); + + if(index < 0 || index >= LINE6_ISO_BUFFERS) { + spin_unlock_irqrestore(&line6pcm->lock_audio_in, flags); + dev_err(s2m(substream), "no free URB found\n"); + return -EINVAL; + } + + urb_in = line6pcm->urb_audio_in[index]; + urb_size = 0; + + for(i = 0; i < LINE6_ISO_PACKETS; ++i) { + struct usb_iso_packet_descriptor *fin = &urb_in->iso_frame_desc[i]; + fin->offset = urb_size; + fin->length = line6pcm->max_packet_size; + urb_size += line6pcm->max_packet_size; + } + + urb_in->transfer_buffer = line6pcm->buffer_in + index * LINE6_ISO_PACKETS * line6pcm->max_packet_size; + urb_in->transfer_buffer_length = urb_size; + urb_in->context = substream; + + if(usb_submit_urb(urb_in, GFP_ATOMIC) == 0) + set_bit(index, &line6pcm->active_urb_in); + else + dev_err(s2m(substream), "URB in #%d submission failed\n", index); + + spin_unlock_irqrestore(&line6pcm->lock_audio_in, flags); + return 0; +} + +/* + Submit all currently available capture URBs. +*/ +static int submit_audio_in_all_urbs(struct snd_pcm_substream *substream) +{ + int ret, i; + + for(i = 0; i < LINE6_ISO_BUFFERS; ++i) + if((ret = submit_audio_in_urb(substream)) < 0) + return ret; + + return 0; +} + +/* + Unlink all currently active capture URBs. +*/ +static void unlink_audio_in_urbs(struct snd_line6_pcm *line6pcm) +{ + unsigned int i; + + for(i = LINE6_ISO_BUFFERS; i--;) { + if(test_bit(i, &line6pcm->active_urb_in)) { + if(!test_and_set_bit(i, &line6pcm->unlink_urb_in)) { + struct urb *u = line6pcm->urb_audio_in[i]; +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 14) + u->transfer_flags |= URB_ASYNC_UNLINK; +#endif + usb_unlink_urb(u); + } + } + } +} + +/* + Wait until unlinking of all currently active capture URBs has been finished. +*/ +static void wait_clear_audio_in_urbs(struct snd_line6_pcm *line6pcm) +{ + int timeout = HZ; + unsigned int i; + int alive; + + do { + alive = 0; + for (i = LINE6_ISO_BUFFERS; i--;) { + if (test_bit(i, &line6pcm->active_urb_in)) + alive++; + } + if (! alive) + break; + set_current_state(TASK_UNINTERRUPTIBLE); + schedule_timeout(1); + } while (--timeout > 0); + if (alive) + snd_printk(KERN_ERR "timeout: still %d active urbs..\n", alive); + + line6pcm->active_urb_in = 0; + line6pcm->unlink_urb_in = 0; +} + +/* + Unlink all currently active capture URBs, and wait for finishing. +*/ +void unlink_wait_clear_audio_in_urbs(struct snd_line6_pcm *line6pcm) +{ + unlink_audio_in_urbs(line6pcm); + wait_clear_audio_in_urbs(line6pcm); +} + +/* + Callback for completed capture URB. +*/ +static void audio_in_callback(struct urb *urb PT_REGS) +{ + int i, index, length = 0, shutdown = 0; + int frames; + unsigned long flags; + + struct snd_pcm_substream *substream = (struct snd_pcm_substream *)urb->context; + struct snd_line6_pcm *line6pcm = snd_pcm_substream_chip(substream); + const int bytes_per_frame = line6pcm->properties->bytes_per_frame; + struct snd_pcm_runtime *runtime = substream->runtime; + + /* find index of URB */ + for(index = 0; index < LINE6_ISO_BUFFERS; ++index) + if(urb == line6pcm->urb_audio_in[index]) + break; + +#if DO_DUMP_PCM_RECEIVE + for(i = 0; i < LINE6_ISO_PACKETS; ++i) { + struct usb_iso_packet_descriptor *fout = &urb->iso_frame_desc[i]; + line6_write_hexdump(line6pcm->line6, 'C', urb->transfer_buffer + fout->offset, fout->length); + } +#endif + + spin_lock_irqsave(&line6pcm->lock_audio_in, flags); + + for(i = 0; i < LINE6_ISO_PACKETS; ++i) { + char *fbuf; + int fsize; + struct usb_iso_packet_descriptor *fin = &urb->iso_frame_desc[i]; + + if(fin->status == -18) { + shutdown = 1; + break; + } + + fbuf = urb->transfer_buffer + fin->offset; + fsize = fin->actual_length; + length += fsize; + + if(fsize > 0) { + frames = fsize / bytes_per_frame; + + if(line6pcm->pos_in_done + frames > runtime->buffer_size) { + /* + The transferred area goes over buffer boundary, + copy two separate chunks. + */ + int len; + len = runtime->buffer_size - line6pcm->pos_in_done; + + if(len > 0) { + memcpy(runtime->dma_area + line6pcm->pos_in_done * bytes_per_frame, fbuf, len * bytes_per_frame); + memcpy(runtime->dma_area, fbuf + len * bytes_per_frame, (frames - len) * bytes_per_frame); + } + else + dev_err(s2m(substream), "driver bug: len = %d\n", len); /* this is somewhat paranoid */ + } + else { + /* copy single chunk */ + memcpy(runtime->dma_area + line6pcm->pos_in_done * bytes_per_frame, fbuf, fsize * bytes_per_frame); + } + + if((line6pcm->pos_in_done += frames) >= runtime->buffer_size) + line6pcm->pos_in_done -= runtime->buffer_size; + } + } + + clear_bit(index, &line6pcm->active_urb_in); + + if(test_bit(index, &line6pcm->unlink_urb_in)) + shutdown = 1; + + spin_unlock_irqrestore(&line6pcm->lock_audio_in, flags); + + if(!shutdown) { + submit_audio_in_urb(substream); + + if((line6pcm->bytes_in += length) >= line6pcm->period_in) { + line6pcm->bytes_in -= line6pcm->period_in; + snd_pcm_period_elapsed(substream); + } + } +} + +/* open capture callback */ +static int snd_line6_capture_open(struct snd_pcm_substream *substream) +{ + int err; + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_line6_pcm *line6pcm = snd_pcm_substream_chip(substream); + + if((err = snd_pcm_hw_constraint_ratdens(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + (&line6pcm->properties->snd_line6_rates))) < 0) + return err; + + runtime->hw = line6pcm->properties->snd_line6_capture_hw; + return 0; +} + +/* close capture callback */ +static int snd_line6_capture_close(struct snd_pcm_substream *substream) +{ + return 0; +} + +/* hw_params capture callback */ +static int snd_line6_capture_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *hw_params) +{ + int ret; + struct snd_line6_pcm *line6pcm = snd_pcm_substream_chip(substream); + + /* -- Florian Demski [FD] */ + /* don't ask me why, but this fixes the bug on my machine */ + if ( line6pcm == NULL ) { + if ( substream->pcm == NULL ) + return -ENOMEM; + if ( substream->pcm->private_data == NULL ) + return -ENOMEM; + substream->private_data = substream->pcm->private_data; + line6pcm = snd_pcm_substream_chip( substream ); + } + /* -- [FD] end */ + + if((ret = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params))) < 0) + return ret; + + line6pcm->period_in = params_period_bytes(hw_params); + line6pcm->buffer_in = kmalloc(LINE6_ISO_BUFFERS * LINE6_ISO_PACKETS * LINE6_ISO_PACKET_SIZE_MAX, GFP_KERNEL); + + if(!line6pcm->buffer_in) { + dev_err(s2m(substream), "cannot malloc buffer_in\n"); + return -ENOMEM; + } + + return 0; +} + +/* hw_free capture callback */ +static int snd_line6_capture_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_line6_pcm *line6pcm = snd_pcm_substream_chip(substream); + unlink_wait_clear_audio_in_urbs(line6pcm); + + if(line6pcm->buffer_in) { + kfree(line6pcm->buffer_in); + line6pcm->buffer_in = 0; + } + + return snd_pcm_lib_free_pages(substream); +} + +/* trigger callback */ +int snd_line6_capture_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_line6_pcm *line6pcm = snd_pcm_substream_chip(substream); + int err; + line6pcm->count_in = 0; + + switch(cmd) { + case SNDRV_PCM_TRIGGER_START: + if(!test_and_set_bit(BIT_RUNNING_CAPTURE, &line6pcm->flags)) { + err = submit_audio_in_all_urbs(substream); + + if(err < 0) { + clear_bit(BIT_RUNNING_CAPTURE, &line6pcm->flags); + return err; + } + } + + break; + + case SNDRV_PCM_TRIGGER_STOP: + if(test_and_clear_bit(BIT_RUNNING_CAPTURE, &line6pcm->flags)) + unlink_audio_in_urbs(line6pcm); + + break; + + default: + return -EINVAL; + } + + return 0; +} + +/* capture pointer callback */ +static snd_pcm_uframes_t +snd_line6_capture_pointer(struct snd_pcm_substream *substream) +{ + struct snd_line6_pcm *line6pcm = snd_pcm_substream_chip(substream); + return line6pcm->pos_in_done; +} + +/* capture operators */ +struct snd_pcm_ops snd_line6_capture_ops = { + .open = snd_line6_capture_open, + .close = snd_line6_capture_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_line6_capture_hw_params, + .hw_free = snd_line6_capture_hw_free, + .prepare = snd_line6_prepare, + .trigger = snd_line6_trigger, + .pointer = snd_line6_capture_pointer, +}; + +int create_audio_in_urbs(struct snd_line6_pcm *line6pcm) +{ + int i; + + /* create audio URBs and fill in constant values: */ + for(i = 0; i < LINE6_ISO_BUFFERS; ++i) { + struct urb *urb; + + /* URB for audio in: */ + urb = line6pcm->urb_audio_in[i] = usb_alloc_urb(LINE6_ISO_PACKETS, GFP_KERNEL); + + if(urb == NULL) { + dev_err(line6pcm->line6->ifcdev, "Out of memory\n"); + return -ENOMEM; + } + + urb->dev = line6pcm->line6->usbdev; + urb->pipe = usb_rcvisocpipe(line6pcm->line6->usbdev, line6pcm->ep_audio_read & USB_ENDPOINT_NUMBER_MASK); + urb->transfer_flags = URB_ISO_ASAP; + urb->start_frame = -1; + urb->number_of_packets = LINE6_ISO_PACKETS; + urb->interval = LINE6_ISO_INTERVAL; + urb->error_count = 0; + urb->complete = audio_in_callback; + } + + return 0; +} diff --git a/drivers/staging/line6/capture.h b/drivers/staging/line6/capture.h new file mode 100644 index 000000000000..7b92e4de3927 --- /dev/null +++ b/drivers/staging/line6/capture.h @@ -0,0 +1,31 @@ +/* + * Line6 Linux USB driver - 0.8.0 + * + * Copyright (C) 2004-2009 Markus Grabner (grabner@icg.tugraz.at) + * + * 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, version 2. + * + */ + +#ifndef CAPTURE_H +#define CAPTURE_H + + +#include "driver.h" + +#include <sound/pcm.h> + +#include "pcm.h" + + +extern struct snd_pcm_ops snd_line6_capture_ops; + + +extern int create_audio_in_urbs(struct snd_line6_pcm *line6pcm); +extern int snd_line6_capture_trigger(struct snd_pcm_substream *substream, int cmd); +extern void unlink_wait_clear_audio_in_urbs(struct snd_line6_pcm *line6pcm); + + +#endif diff --git a/drivers/staging/line6/config.h b/drivers/staging/line6/config.h new file mode 100644 index 000000000000..d5ed1a740b0d --- /dev/null +++ b/drivers/staging/line6/config.h @@ -0,0 +1,73 @@ +/* + * Line6 Linux USB driver - 0.8.0 + * + * Copyright (C) 2004-2009 Markus Grabner (grabner@icg.tugraz.at) + * + * 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, version 2. + * + */ + +#ifndef CONFIG_H +#define CONFIG_H + + +#include <linux/version.h> + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 19) +#include <linux/config.h> +#endif + +#ifdef CONFIG_USB_DEBUG +#define DEBUG 1 +#endif + + +/** + Development tools. +*/ +#define DO_DEBUG_MESSAGES 0 +#define DO_DUMP_URB_SEND DO_DEBUG_MESSAGES +#define DO_DUMP_URB_RECEIVE DO_DEBUG_MESSAGES +#define DO_DUMP_PCM_SEND 0 +#define DO_DUMP_PCM_RECEIVE 0 +#define DO_DUMP_MIDI_SEND DO_DEBUG_MESSAGES +#define DO_DUMP_MIDI_RECEIVE DO_DEBUG_MESSAGES +#define DO_DUMP_ANY (DO_DUMP_URB_SEND || DO_DUMP_URB_RECEIVE || \ + DO_DUMP_PCM_SEND || DO_DUMP_PCM_RECEIVE || \ + DO_DUMP_MIDI_SEND || DO_DUMP_MIDI_RECEIVE) +#define CREATE_RAW_FILE 0 + +#if DO_DEBUG_MESSAGES +#define CHECKPOINT printk("line6usb: %s (%s:%d)\n", __FUNCTION__, __FILE__, __LINE__) +#endif + +/** + In Linux 2.6.13 and later, the device_attribute is passed to the sysfs + get/set functions (see /usr/src/linux/include/linux/device.h). +*/ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 13) +#define DEVICE_ATTRIBUTE struct device_attribute *attr, +#else +#define DEVICE_ATTRIBUTE +#endif + +/** + In Linux 2.6.20 and later, the pt_regs is no longer passed to USB callback + functions. +*/ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 20) +#define PT_REGS +#else +#define PT_REGS , struct pt_regs *regs +#endif + +#if DO_DEBUG_MESSAGES +#define DEBUG_MESSAGES(x) (x) +#else +#define DEBUG_MESSAGES(x) +#endif + + +#endif diff --git a/drivers/staging/line6/control.c b/drivers/staging/line6/control.c new file mode 100644 index 000000000000..d44d06d7b136 --- /dev/null +++ b/drivers/staging/line6/control.c @@ -0,0 +1,702 @@ +/* + * Line6 Linux USB driver - 0.8.0 + * + * Copyright (C) 2004-2009 Markus Grabner (grabner@icg.tugraz.at) + * + * 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, version 2. + * + */ + +#include "driver.h" + +#include <linux/usb.h> + +#include "control.h" +#include "pod.h" +#include "usbdefs.h" +#include "variax.h" + +#define DEVICE_ATTR2(_name1,_name2,_mode,_show,_store) \ +struct device_attribute dev_attr_##_name1 = __ATTR(_name2,_mode,_show,_store) + +#define LINE6_PARAM_R(PREFIX, prefix, type, param) \ +static ssize_t prefix ## _get_ ## param(struct device *dev, DEVICE_ATTRIBUTE char *buf) \ +{ \ + return prefix ## _get_param_ ## type(dev, buf, PREFIX ## _ ## param); \ +} + +#define LINE6_PARAM_RW(PREFIX, prefix, type, param) \ +LINE6_PARAM_R(PREFIX, prefix, type, param); \ +static ssize_t prefix ## _set_ ## param(struct device *dev, DEVICE_ATTRIBUTE const char *buf, size_t count) \ +{ \ + return prefix ## _set_param_ ## type(dev, buf, count, PREFIX ## _ ## param); \ +} + +#define POD_PARAM_R(type, param) LINE6_PARAM_R(POD, pod, type, param) +#define POD_PARAM_RW(type, param) LINE6_PARAM_RW(POD, pod, type, param) +#define VARIAX_PARAM_R(type, param) LINE6_PARAM_R(VARIAX, variax, type, param) +#define VARIAX_PARAM_RW(type, param) LINE6_PARAM_RW(VARIAX, variax, type, param) + + +static ssize_t pod_get_param_int(struct device *dev, char *buf, int param) +{ + struct usb_interface *interface = to_usb_interface(dev); + struct usb_line6_pod *pod = usb_get_intfdata(interface); + int retval = line6_wait_dump(&pod->dumpreq, 0); + if(retval < 0) return retval; + return sprintf(buf, "%d\n", pod->prog_data.control[param]); +} + +static ssize_t pod_set_param_int(struct device *dev, const char *buf, size_t count, int param) +{ + struct usb_interface *interface = to_usb_interface(dev); + struct usb_line6_pod *pod = usb_get_intfdata(interface); + int value = simple_strtoul(buf, NULL, 10); + pod_transmit_parameter(pod, param, value); + return count; +} + +static ssize_t variax_get_param_int(struct device *dev, char *buf, int param) +{ + struct usb_interface *interface = to_usb_interface(dev); + struct usb_line6_variax *variax = usb_get_intfdata(interface); + int retval = line6_wait_dump(&variax->dumpreq, 0); + if(retval < 0) return retval; + return sprintf(buf, "%d\n", variax->model_data.control[param]); +} + +static ssize_t variax_get_param_float(struct device *dev, char *buf, int param) +{ + /* + We do our own floating point handling here since floats in the kernel are + problematic for at least two reasons: + - many distros are still shipped with binary kernels optimized for the + ancient 80386 without FPU + - there isn't a printf("%f") + (see http://www.kernelthread.com/publications/faq/335.html) + */ + + static const int BIAS = 0x7f; + static const int OFFSET = 0xf; + static const int PRECISION = 1000; + + int len = 0; + unsigned part_int, part_frac; + struct usb_interface *interface = to_usb_interface(dev); + struct usb_line6_variax *variax = usb_get_intfdata(interface); + const unsigned char *p = variax->model_data.control + param; + int retval = line6_wait_dump(&variax->dumpreq, 0); + if(retval < 0) return retval; + + if((p[0] == 0) && (p[1] == 0) && (p[2] == 0)) + part_int = part_frac = 0; + else { + int exponent = (((p[0] & 0x7f) << 1) | (p[1] >> 7)) - BIAS; + unsigned mantissa = (p[1] << 8) | p[2] | 0x8000; + exponent -= OFFSET; + + if(exponent >= 0) { + part_int = mantissa << exponent; + part_frac = 0; + } + else { + part_int = mantissa >> -exponent; + part_frac = (mantissa << (32 + exponent)) & 0xffffffff; + } + + part_frac = (part_frac / ((1UL << 31) / (PRECISION / 2 * 10)) + 5) / 10; + } + + len += sprintf(buf + len, "%s%d.%03d\n", ((p[0] & 0x80) ? "-" : ""), part_int, part_frac); + return len; +} + +POD_PARAM_RW(int, tweak); +POD_PARAM_RW(int, wah_position); +POD_PARAM_RW(int, compression_gain); +POD_PARAM_RW(int, vol_pedal_position); +POD_PARAM_RW(int, compression_threshold); +POD_PARAM_RW(int, pan); +POD_PARAM_RW(int, amp_model_setup); +POD_PARAM_RW(int, amp_model); +POD_PARAM_RW(int, drive); +POD_PARAM_RW(int, bass); +POD_PARAM_RW(int, mid); +POD_PARAM_RW(int, lowmid); +POD_PARAM_RW(int, treble); +POD_PARAM_RW(int, highmid); +POD_PARAM_RW(int, chan_vol); +POD_PARAM_RW(int, reverb_mix); +POD_PARAM_RW(int, effect_setup); +POD_PARAM_RW(int, band_1_frequency); +POD_PARAM_RW(int, presence); +POD_PARAM_RW(int, treble__bass); +POD_PARAM_RW(int, noise_gate_enable); +POD_PARAM_RW(int, gate_threshold); +POD_PARAM_RW(int, gate_decay_time); +POD_PARAM_RW(int, stomp_enable); +POD_PARAM_RW(int, comp_enable); +POD_PARAM_RW(int, stomp_time); +POD_PARAM_RW(int, delay_enable); +POD_PARAM_RW(int, mod_param_1); +POD_PARAM_RW(int, delay_param_1); +POD_PARAM_RW(int, delay_param_1_note_value); +POD_PARAM_RW(int, band_2_frequency__bass); +POD_PARAM_RW(int, delay_param_2); +POD_PARAM_RW(int, delay_volume_mix); +POD_PARAM_RW(int, delay_param_3); +POD_PARAM_RW(int, reverb_enable); +POD_PARAM_RW(int, reverb_type); +POD_PARAM_RW(int, reverb_decay); +POD_PARAM_RW(int, reverb_tone); +POD_PARAM_RW(int, reverb_pre_delay); +POD_PARAM_RW(int, reverb_pre_post); +POD_PARAM_RW(int, band_2_frequency); +POD_PARAM_RW(int, band_3_frequency__bass); +POD_PARAM_RW(int, wah_enable); +POD_PARAM_RW(int, modulation_lo_cut); +POD_PARAM_RW(int, delay_reverb_lo_cut); +POD_PARAM_RW(int, volume_pedal_minimum); +POD_PARAM_RW(int, eq_pre_post); +POD_PARAM_RW(int, volume_pre_post); +POD_PARAM_RW(int, di_model); +POD_PARAM_RW(int, di_delay); +POD_PARAM_RW(int, mod_enable); +POD_PARAM_RW(int, mod_param_1_note_value); +POD_PARAM_RW(int, mod_param_2); +POD_PARAM_RW(int, mod_param_3); +POD_PARAM_RW(int, mod_param_4); +POD_PARAM_RW(int, mod_param_5); +POD_PARAM_RW(int, mod_volume_mix); +POD_PARAM_RW(int, mod_pre_post); +POD_PARAM_RW(int, modulation_model); +POD_PARAM_RW(int, band_3_frequency); +POD_PARAM_RW(int, band_4_frequency__bass); +POD_PARAM_RW(int, mod_param_1_double_precision); +POD_PARAM_RW(int, delay_param_1_double_precision); +POD_PARAM_RW(int, eq_enable); +POD_PARAM_RW(int, tap); +POD_PARAM_RW(int, volume_tweak_pedal_assign); +POD_PARAM_RW(int, band_5_frequency); +POD_PARAM_RW(int, tuner); +POD_PARAM_RW(int, mic_selection); +POD_PARAM_RW(int, cabinet_model); +POD_PARAM_RW(int, stomp_model); +POD_PARAM_RW(int, roomlevel); +POD_PARAM_RW(int, band_4_frequency); +POD_PARAM_RW(int, band_6_frequency); +POD_PARAM_RW(int, stomp_param_1_note_value); +POD_PARAM_RW(int, stomp_param_2); +POD_PARAM_RW(int, stomp_param_3); +POD_PARAM_RW(int, stomp_param_4); +POD_PARAM_RW(int, stomp_param_5); +POD_PARAM_RW(int, stomp_param_6); +POD_PARAM_RW(int, amp_switch_select); +POD_PARAM_RW(int, delay_param_4); +POD_PARAM_RW(int, delay_param_5); +POD_PARAM_RW(int, delay_pre_post); +POD_PARAM_RW(int, delay_model); +POD_PARAM_RW(int, delay_verb_model); +POD_PARAM_RW(int, tempo_msb); +POD_PARAM_RW(int, tempo_lsb); +POD_PARAM_RW(int, wah_model); +POD_PARAM_RW(int, bypass_volume); +POD_PARAM_RW(int, fx_loop_on_off); +POD_PARAM_RW(int, tweak_param_select); +POD_PARAM_RW(int, amp1_engage); +POD_PARAM_RW(int, band_1_gain); +POD_PARAM_RW(int, band_2_gain__bass); +POD_PARAM_RW(int, band_2_gain); +POD_PARAM_RW(int, band_3_gain__bass); +POD_PARAM_RW(int, band_3_gain); +POD_PARAM_RW(int, band_4_gain__bass); +POD_PARAM_RW(int, band_5_gain__bass); +POD_PARAM_RW(int, band_4_gain); +POD_PARAM_RW(int, band_6_gain__bass); +VARIAX_PARAM_R(int, body); +VARIAX_PARAM_R(int, pickup1_enable); +VARIAX_PARAM_R(int, pickup1_type); +VARIAX_PARAM_R(float, pickup1_position); +VARIAX_PARAM_R(float, pickup1_angle); +VARIAX_PARAM_R(float, pickup1_level); +VARIAX_PARAM_R(int, pickup2_enable); +VARIAX_PARAM_R(int, pickup2_type); +VARIAX_PARAM_R(float, pickup2_position); +VARIAX_PARAM_R(float, pickup2_angle); +VARIAX_PARAM_R(float, pickup2_level); +VARIAX_PARAM_R(int, pickup_phase); +VARIAX_PARAM_R(float, capacitance); +VARIAX_PARAM_R(float, tone_resistance); +VARIAX_PARAM_R(float, volume_resistance); +VARIAX_PARAM_R(int, taper); +VARIAX_PARAM_R(float, tone_dump); +VARIAX_PARAM_R(int, save_tone); +VARIAX_PARAM_R(float, volume_dump); +VARIAX_PARAM_R(int, tuning_enable); +VARIAX_PARAM_R(int, tuning6); +VARIAX_PARAM_R(int, tuning5); +VARIAX_PARAM_R(int, tuning4); +VARIAX_PARAM_R(int, tuning3); +VARIAX_PARAM_R(int, tuning2); +VARIAX_PARAM_R(int, tuning1); +VARIAX_PARAM_R(float, detune6); +VARIAX_PARAM_R(float, detune5); +VARIAX_PARAM_R(float, detune4); +VARIAX_PARAM_R(float, detune3); +VARIAX_PARAM_R(float, detune2); +VARIAX_PARAM_R(float, detune1); +VARIAX_PARAM_R(float, mix6); +VARIAX_PARAM_R(float, mix5); +VARIAX_PARAM_R(float, mix4); +VARIAX_PARAM_R(float, mix3); +VARIAX_PARAM_R(float, mix2); +VARIAX_PARAM_R(float, mix1); +VARIAX_PARAM_R(int, pickup_wiring); + +static DEVICE_ATTR(tweak, S_IWUGO | S_IRUGO, pod_get_tweak, pod_set_tweak); +static DEVICE_ATTR(wah_position, S_IWUGO | S_IRUGO, pod_get_wah_position, pod_set_wah_position); +static DEVICE_ATTR(compression_gain, S_IWUGO | S_IRUGO, pod_get_compression_gain, pod_set_compression_gain); +static DEVICE_ATTR(vol_pedal_position, S_IWUGO | S_IRUGO, pod_get_vol_pedal_position, pod_set_vol_pedal_position); +static DEVICE_ATTR(compression_threshold, S_IWUGO | S_IRUGO, pod_get_compression_threshold, pod_set_compression_threshold); +static DEVICE_ATTR(pan, S_IWUGO | S_IRUGO, pod_get_pan, pod_set_pan); +static DEVICE_ATTR(amp_model_setup, S_IWUGO | S_IRUGO, pod_get_amp_model_setup, pod_set_amp_model_setup); +static DEVICE_ATTR(amp_model, S_IWUGO | S_IRUGO, pod_get_amp_model, pod_set_amp_model); +static DEVICE_ATTR(drive, S_IWUGO | S_IRUGO, pod_get_drive, pod_set_drive); +static DEVICE_ATTR(bass, S_IWUGO | S_IRUGO, pod_get_bass, pod_set_bass); +static DEVICE_ATTR(mid, S_IWUGO | S_IRUGO, pod_get_mid, pod_set_mid); +static DEVICE_ATTR(lowmid, S_IWUGO | S_IRUGO, pod_get_lowmid, pod_set_lowmid); +static DEVICE_ATTR(treble, S_IWUGO | S_IRUGO, pod_get_treble, pod_set_treble); +static DEVICE_ATTR(highmid, S_IWUGO | S_IRUGO, pod_get_highmid, pod_set_highmid); +static DEVICE_ATTR(chan_vol, S_IWUGO | S_IRUGO, pod_get_chan_vol, pod_set_chan_vol); +static DEVICE_ATTR(reverb_mix, S_IWUGO | S_IRUGO, pod_get_reverb_mix, pod_set_reverb_mix); +static DEVICE_ATTR(effect_setup, S_IWUGO | S_IRUGO, pod_get_effect_setup, pod_set_effect_setup); +static DEVICE_ATTR(band_1_frequency, S_IWUGO | S_IRUGO, pod_get_band_1_frequency, pod_set_band_1_frequency); +static DEVICE_ATTR(presence, S_IWUGO | S_IRUGO, pod_get_presence, pod_set_presence); +static DEVICE_ATTR2(treble__bass, treble, S_IWUGO | S_IRUGO, pod_get_treble__bass, pod_set_treble__bass); +static DEVICE_ATTR(noise_gate_enable, S_IWUGO | S_IRUGO, pod_get_noise_gate_enable, pod_set_noise_gate_enable); +static DEVICE_ATTR(gate_threshold, S_IWUGO | S_IRUGO, pod_get_gate_threshold, pod_set_gate_threshold); +static DEVICE_ATTR(gate_decay_time, S_IWUGO | S_IRUGO, pod_get_gate_decay_time, pod_set_gate_decay_time); +static DEVICE_ATTR(stomp_enable, S_IWUGO | S_IRUGO, pod_get_stomp_enable, pod_set_stomp_enable); +static DEVICE_ATTR(comp_enable, S_IWUGO | S_IRUGO, pod_get_comp_enable, pod_set_comp_enable); +static DEVICE_ATTR(stomp_time, S_IWUGO | S_IRUGO, pod_get_stomp_time, pod_set_stomp_time); +static DEVICE_ATTR(delay_enable, S_IWUGO | S_IRUGO, pod_get_delay_enable, pod_set_delay_enable); +static DEVICE_ATTR(mod_param_1, S_IWUGO | S_IRUGO, pod_get_mod_param_1, pod_set_mod_param_1); +static DEVICE_ATTR(delay_param_1, S_IWUGO | S_IRUGO, pod_get_delay_param_1, pod_set_delay_param_1); +static DEVICE_ATTR(delay_param_1_note_value, S_IWUGO | S_IRUGO, pod_get_delay_param_1_note_value, pod_set_delay_param_1_note_value); +static DEVICE_ATTR2(band_2_frequency__bass, band_2_frequency, S_IWUGO | S_IRUGO, pod_get_band_2_frequency__bass, pod_set_band_2_frequency__bass); +static DEVICE_ATTR(delay_param_2, S_IWUGO | S_IRUGO, pod_get_delay_param_2, pod_set_delay_param_2); +static DEVICE_ATTR(delay_volume_mix, S_IWUGO | S_IRUGO, pod_get_delay_volume_mix, pod_set_delay_volume_mix); +static DEVICE_ATTR(delay_param_3, S_IWUGO | S_IRUGO, pod_get_delay_param_3, pod_set_delay_param_3); +static DEVICE_ATTR(reverb_enable, S_IWUGO | S_IRUGO, pod_get_reverb_enable, pod_set_reverb_enable); +static DEVICE_ATTR(reverb_type, S_IWUGO | S_IRUGO, pod_get_reverb_type, pod_set_reverb_type); +static DEVICE_ATTR(reverb_decay, S_IWUGO | S_IRUGO, pod_get_reverb_decay, pod_set_reverb_decay); +static DEVICE_ATTR(reverb_tone, S_IWUGO | S_IRUGO, pod_get_reverb_tone, pod_set_reverb_tone); +static DEVICE_ATTR(reverb_pre_delay, S_IWUGO | S_IRUGO, pod_get_reverb_pre_delay, pod_set_reverb_pre_delay); +static DEVICE_ATTR(reverb_pre_post, S_IWUGO | S_IRUGO, pod_get_reverb_pre_post, pod_set_reverb_pre_post); +static DEVICE_ATTR(band_2_frequency, S_IWUGO | S_IRUGO, pod_get_band_2_frequency, pod_set_band_2_frequency); +static DEVICE_ATTR2(band_3_frequency__bass, band_3_frequency, S_IWUGO | S_IRUGO, pod_get_band_3_frequency__bass, pod_set_band_3_frequency__bass); +static DEVICE_ATTR(wah_enable, S_IWUGO | S_IRUGO, pod_get_wah_enable, pod_set_wah_enable); +static DEVICE_ATTR(modulation_lo_cut, S_IWUGO | S_IRUGO, pod_get_modulation_lo_cut, pod_set_modulation_lo_cut); +static DEVICE_ATTR(delay_reverb_lo_cut, S_IWUGO | S_IRUGO, pod_get_delay_reverb_lo_cut, pod_set_delay_reverb_lo_cut); +static DEVICE_ATTR(volume_pedal_minimum, S_IWUGO | S_IRUGO, pod_get_volume_pedal_minimum, pod_set_volume_pedal_minimum); +static DEVICE_ATTR(eq_pre_post, S_IWUGO | S_IRUGO, pod_get_eq_pre_post, pod_set_eq_pre_post); +static DEVICE_ATTR(volume_pre_post, S_IWUGO | S_IRUGO, pod_get_volume_pre_post, pod_set_volume_pre_post); +static DEVICE_ATTR(di_model, S_IWUGO | S_IRUGO, pod_get_di_model, pod_set_di_model); +static DEVICE_ATTR(di_delay, S_IWUGO | S_IRUGO, pod_get_di_delay, pod_set_di_delay); +static DEVICE_ATTR(mod_enable, S_IWUGO | S_IRUGO, pod_get_mod_enable, pod_set_mod_enable); +static DEVICE_ATTR(mod_param_1_note_value, S_IWUGO | S_IRUGO, pod_get_mod_param_1_note_value, pod_set_mod_param_1_note_value); +static DEVICE_ATTR(mod_param_2, S_IWUGO | S_IRUGO, pod_get_mod_param_2, pod_set_mod_param_2); +static DEVICE_ATTR(mod_param_3, S_IWUGO | S_IRUGO, pod_get_mod_param_3, pod_set_mod_param_3); +static DEVICE_ATTR(mod_param_4, S_IWUGO | S_IRUGO, pod_get_mod_param_4, pod_set_mod_param_4); +static DEVICE_ATTR(mod_param_5, S_IWUGO | S_IRUGO, pod_get_mod_param_5, pod_set_mod_param_5); +static DEVICE_ATTR(mod_volume_mix, S_IWUGO | S_IRUGO, pod_get_mod_volume_mix, pod_set_mod_volume_mix); +static DEVICE_ATTR(mod_pre_post, S_IWUGO | S_IRUGO, pod_get_mod_pre_post, pod_set_mod_pre_post); +static DEVICE_ATTR(modulation_model, S_IWUGO | S_IRUGO, pod_get_modulation_model, pod_set_modulation_model); +static DEVICE_ATTR(band_3_frequency, S_IWUGO | S_IRUGO, pod_get_band_3_frequency, pod_set_band_3_frequency); +static DEVICE_ATTR2(band_4_frequency__bass, band_4_frequency, S_IWUGO | S_IRUGO, pod_get_band_4_frequency__bass, pod_set_band_4_frequency__bass); +static DEVICE_ATTR(mod_param_1_double_precision, S_IWUGO | S_IRUGO, pod_get_mod_param_1_double_precision, pod_set_mod_param_1_double_precision); +static DEVICE_ATTR(delay_param_1_double_precision, S_IWUGO | S_IRUGO, pod_get_delay_param_1_double_precision, pod_set_delay_param_1_double_precision); +static DEVICE_ATTR(eq_enable, S_IWUGO | S_IRUGO, pod_get_eq_enable, pod_set_eq_enable); +static DEVICE_ATTR(tap, S_IWUGO | S_IRUGO, pod_get_tap, pod_set_tap); +static DEVICE_ATTR(volume_tweak_pedal_assign, S_IWUGO | S_IRUGO, pod_get_volume_tweak_pedal_assign, pod_set_volume_tweak_pedal_assign); +static DEVICE_ATTR(band_5_frequency, S_IWUGO | S_IRUGO, pod_get_band_5_frequency, pod_set_band_5_frequency); +static DEVICE_ATTR(tuner, S_IWUGO | S_IRUGO, pod_get_tuner, pod_set_tuner); +static DEVICE_ATTR(mic_selection, S_IWUGO | S_IRUGO, pod_get_mic_selection, pod_set_mic_selection); +static DEVICE_ATTR(cabinet_model, S_IWUGO | S_IRUGO, pod_get_cabinet_model, pod_set_cabinet_model); +static DEVICE_ATTR(stomp_model, S_IWUGO | S_IRUGO, pod_get_stomp_model, pod_set_stomp_model); +static DEVICE_ATTR(roomlevel, S_IWUGO | S_IRUGO, pod_get_roomlevel, pod_set_roomlevel); +static DEVICE_ATTR(band_4_frequency, S_IWUGO | S_IRUGO, pod_get_band_4_frequency, pod_set_band_4_frequency); +static DEVICE_ATTR(band_6_frequency, S_IWUGO | S_IRUGO, pod_get_band_6_frequency, pod_set_band_6_frequency); +static DEVICE_ATTR(stomp_param_1_note_value, S_IWUGO | S_IRUGO, pod_get_stomp_param_1_note_value, pod_set_stomp_param_1_note_value); +static DEVICE_ATTR(stomp_param_2, S_IWUGO | S_IRUGO, pod_get_stomp_param_2, pod_set_stomp_param_2); +static DEVICE_ATTR(stomp_param_3, S_IWUGO | S_IRUGO, pod_get_stomp_param_3, pod_set_stomp_param_3); +static DEVICE_ATTR(stomp_param_4, S_IWUGO | S_IRUGO, pod_get_stomp_param_4, pod_set_stomp_param_4); +static DEVICE_ATTR(stomp_param_5, S_IWUGO | S_IRUGO, pod_get_stomp_param_5, pod_set_stomp_param_5); +static DEVICE_ATTR(stomp_param_6, S_IWUGO | S_IRUGO, pod_get_stomp_param_6, pod_set_stomp_param_6); +static DEVICE_ATTR(amp_switch_select, S_IWUGO | S_IRUGO, pod_get_amp_switch_select, pod_set_amp_switch_select); +static DEVICE_ATTR(delay_param_4, S_IWUGO | S_IRUGO, pod_get_delay_param_4, pod_set_delay_param_4); +static DEVICE_ATTR(delay_param_5, S_IWUGO | S_IRUGO, pod_get_delay_param_5, pod_set_delay_param_5); +static DEVICE_ATTR(delay_pre_post, S_IWUGO | S_IRUGO, pod_get_delay_pre_post, pod_set_delay_pre_post); +static DEVICE_ATTR(delay_model, S_IWUGO | S_IRUGO, pod_get_delay_model, pod_set_delay_model); +static DEVICE_ATTR(delay_verb_model, S_IWUGO | S_IRUGO, pod_get_delay_verb_model, pod_set_delay_verb_model); +static DEVICE_ATTR(tempo_msb, S_IWUGO | S_IRUGO, pod_get_tempo_msb, pod_set_tempo_msb); +static DEVICE_ATTR(tempo_lsb, S_IWUGO | S_IRUGO, pod_get_tempo_lsb, pod_set_tempo_lsb); +static DEVICE_ATTR(wah_model, S_IWUGO | S_IRUGO, pod_get_wah_model, pod_set_wah_model); +static DEVICE_ATTR(bypass_volume, S_IWUGO | S_IRUGO, pod_get_bypass_volume, pod_set_bypass_volume); +static DEVICE_ATTR(fx_loop_on_off, S_IWUGO | S_IRUGO, pod_get_fx_loop_on_off, pod_set_fx_loop_on_off); +static DEVICE_ATTR(tweak_param_select, S_IWUGO | S_IRUGO, pod_get_tweak_param_select, pod_set_tweak_param_select); +static DEVICE_ATTR(amp1_engage, S_IWUGO | S_IRUGO, pod_get_amp1_engage, pod_set_amp1_engage); +static DEVICE_ATTR(band_1_gain, S_IWUGO | S_IRUGO, pod_get_band_1_gain, pod_set_band_1_gain); +static DEVICE_ATTR2(band_2_gain__bass, band_2_gain, S_IWUGO | S_IRUGO, pod_get_band_2_gain__bass, pod_set_band_2_gain__bass); +static DEVICE_ATTR(band_2_gain, S_IWUGO | S_IRUGO, pod_get_band_2_gain, pod_set_band_2_gain); +static DEVICE_ATTR2(band_3_gain__bass, band_3_gain, S_IWUGO | S_IRUGO, pod_get_band_3_gain__bass, pod_set_band_3_gain__bass); +static DEVICE_ATTR(band_3_gain, S_IWUGO | S_IRUGO, pod_get_band_3_gain, pod_set_band_3_gain); +static DEVICE_ATTR2(band_4_gain__bass, band_4_gain, S_IWUGO | S_IRUGO, pod_get_band_4_gain__bass, pod_set_band_4_gain__bass); +static DEVICE_ATTR2(band_5_gain__bass, band_5_gain, S_IWUGO | S_IRUGO, pod_get_band_5_gain__bass, pod_set_band_5_gain__bass); +static DEVICE_ATTR(band_4_gain, S_IWUGO | S_IRUGO, pod_get_band_4_gain, pod_set_band_4_gain); +static DEVICE_ATTR2(band_6_gain__bass, band_6_gain, S_IWUGO | S_IRUGO, pod_get_band_6_gain__bass, pod_set_band_6_gain__bass); +static DEVICE_ATTR(body, S_IRUGO, variax_get_body, line6_nop_write); +static DEVICE_ATTR(pickup1_enable, S_IRUGO, variax_get_pickup1_enable, line6_nop_write); +static DEVICE_ATTR(pickup1_type, S_IRUGO, variax_get_pickup1_type, line6_nop_write); +static DEVICE_ATTR(pickup1_position, S_IRUGO, variax_get_pickup1_position, line6_nop_write); +static DEVICE_ATTR(pickup1_angle, S_IRUGO, variax_get_pickup1_angle, line6_nop_write); +static DEVICE_ATTR(pickup1_level, S_IRUGO, variax_get_pickup1_level, line6_nop_write); +static DEVICE_ATTR(pickup2_enable, S_IRUGO, variax_get_pickup2_enable, line6_nop_write); +static DEVICE_ATTR(pickup2_type, S_IRUGO, variax_get_pickup2_type, line6_nop_write); +static DEVICE_ATTR(pickup2_position, S_IRUGO, variax_get_pickup2_position, line6_nop_write); +static DEVICE_ATTR(pickup2_angle, S_IRUGO, variax_get_pickup2_angle, line6_nop_write); +static DEVICE_ATTR(pickup2_level, S_IRUGO, variax_get_pickup2_level, line6_nop_write); +static DEVICE_ATTR(pickup_phase, S_IRUGO, variax_get_pickup_phase, line6_nop_write); +static DEVICE_ATTR(capacitance, S_IRUGO, variax_get_capacitance, line6_nop_write); +static DEVICE_ATTR(tone_resistance, S_IRUGO, variax_get_tone_resistance, line6_nop_write); +static DEVICE_ATTR(volume_resistance, S_IRUGO, variax_get_volume_resistance, line6_nop_write); +static DEVICE_ATTR(taper, S_IRUGO, variax_get_taper, line6_nop_write); +static DEVICE_ATTR(tone_dump, S_IRUGO, variax_get_tone_dump, line6_nop_write); +static DEVICE_ATTR(save_tone, S_IRUGO, variax_get_save_tone, line6_nop_write); +static DEVICE_ATTR(volume_dump, S_IRUGO, variax_get_volume_dump, line6_nop_write); +static DEVICE_ATTR(tuning_enable, S_IRUGO, variax_get_tuning_enable, line6_nop_write); +static DEVICE_ATTR(tuning6, S_IRUGO, variax_get_tuning6, line6_nop_write); +static DEVICE_ATTR(tuning5, S_IRUGO, variax_get_tuning5, line6_nop_write); +static DEVICE_ATTR(tuning4, S_IRUGO, variax_get_tuning4, line6_nop_write); +static DEVICE_ATTR(tuning3, S_IRUGO, variax_get_tuning3, line6_nop_write); +static DEVICE_ATTR(tuning2, S_IRUGO, variax_get_tuning2, line6_nop_write); +static DEVICE_ATTR(tuning1, S_IRUGO, variax_get_tuning1, line6_nop_write); +static DEVICE_ATTR(detune6, S_IRUGO, variax_get_detune6, line6_nop_write); +static DEVICE_ATTR(detune5, S_IRUGO, variax_get_detune5, line6_nop_write); +static DEVICE_ATTR(detune4, S_IRUGO, variax_get_detune4, line6_nop_write); +static DEVICE_ATTR(detune3, S_IRUGO, variax_get_detune3, line6_nop_write); +static DEVICE_ATTR(detune2, S_IRUGO, variax_get_detune2, line6_nop_write); +static DEVICE_ATTR(detune1, S_IRUGO, variax_get_detune1, line6_nop_write); +static DEVICE_ATTR(mix6, S_IRUGO, variax_get_mix6, line6_nop_write); +static DEVICE_ATTR(mix5, S_IRUGO, variax_get_mix5, line6_nop_write); +static DEVICE_ATTR(mix4, S_IRUGO, variax_get_mix4, line6_nop_write); +static DEVICE_ATTR(mix3, S_IRUGO, variax_get_mix3, line6_nop_write); +static DEVICE_ATTR(mix2, S_IRUGO, variax_get_mix2, line6_nop_write); +static DEVICE_ATTR(mix1, S_IRUGO, variax_get_mix1, line6_nop_write); +static DEVICE_ATTR(pickup_wiring, S_IRUGO, variax_get_pickup_wiring, line6_nop_write); + +int pod_create_files(int firmware, int type, struct device *dev) { + int err; + CHECK_RETURN(device_create_file(dev, &dev_attr_tweak)); + CHECK_RETURN(device_create_file(dev, &dev_attr_wah_position)); + if((type & (LINE6_BITS_PODXTALL)) != 0) CHECK_RETURN(device_create_file(dev, &dev_attr_compression_gain)); + CHECK_RETURN(device_create_file(dev, &dev_attr_vol_pedal_position)); + CHECK_RETURN(device_create_file(dev, &dev_attr_compression_threshold)); + CHECK_RETURN(device_create_file(dev, &dev_attr_pan)); + CHECK_RETURN(device_create_file(dev, &dev_attr_amp_model_setup)); + if(firmware >= 200) CHECK_RETURN(device_create_file(dev, &dev_attr_amp_model)); + CHECK_RETURN(device_create_file(dev, &dev_attr_drive)); + CHECK_RETURN(device_create_file(dev, &dev_attr_bass)); + if((type & (LINE6_BITS_PODXTALL)) != 0) CHECK_RETURN(device_create_file(dev, &dev_attr_mid)); + if((type & (LINE6_BITS_BASSPODXTALL)) != 0) CHECK_RETURN(device_create_file(dev, &dev_attr_lowmid)); + if((type & (LINE6_BITS_PODXTALL)) != 0) CHECK_RETURN(device_create_file(dev, &dev_attr_treble)); + if((type & (LINE6_BITS_BASSPODXTALL)) != 0) CHECK_RETURN(device_create_file(dev, &dev_attr_highmid)); + CHECK_RETURN(device_create_file(dev, &dev_attr_chan_vol)); + if((type & (LINE6_BITS_PODXTALL)) != 0) CHECK_RETURN(device_create_file(dev, &dev_attr_reverb_mix)); + CHECK_RETURN(device_create_file(dev, &dev_attr_effect_setup)); + if(firmware >= 200) CHECK_RETURN(device_create_file(dev, &dev_attr_band_1_frequency)); + if((type & (LINE6_BITS_PODXTALL)) != 0) CHECK_RETURN(device_create_file(dev, &dev_attr_presence)); + if((type & (LINE6_BITS_BASSPODXTALL)) != 0) CHECK_RETURN(device_create_file(dev, &dev_attr_treble__bass)); + CHECK_RETURN(device_create_file(dev, &dev_attr_noise_gate_enable)); + CHECK_RETURN(device_create_file(dev, &dev_attr_gate_threshold)); + CHECK_RETURN(device_create_file(dev, &dev_attr_gate_decay_time)); + CHECK_RETURN(device_create_file(dev, &dev_attr_stomp_enable)); + CHECK_RETURN(device_create_file(dev, &dev_attr_comp_enable)); + CHECK_RETURN(device_create_file(dev, &dev_attr_stomp_time)); + CHECK_RETURN(device_create_file(dev, &dev_attr_delay_enable)); + CHECK_RETURN(device_create_file(dev, &dev_attr_mod_param_1)); + CHECK_RETURN(device_create_file(dev, &dev_attr_delay_param_1)); + CHECK_RETURN(device_create_file(dev, &dev_attr_delay_param_1_note_value)); + if((type & (LINE6_BITS_BASSPODXTALL)) != 0) if(firmware >= 200) CHECK_RETURN(device_create_file(dev, &dev_attr_band_2_frequency__bass)); + CHECK_RETURN(device_create_file(dev, &dev_attr_delay_param_2)); + CHECK_RETURN(device_create_file(dev, &dev_attr_delay_volume_mix)); + CHECK_RETURN(device_create_file(dev, &dev_attr_delay_param_3)); + if((type & (LINE6_BITS_PODXTALL)) != 0) CHECK_RETURN(device_create_file(dev, &dev_attr_reverb_enable)); + if((type & (LINE6_BITS_PODXTALL)) != 0) CHECK_RETURN(device_create_file(dev, &dev_attr_reverb_type)); + if((type & (LINE6_BITS_PODXTALL)) != 0) CHECK_RETURN(device_create_file(dev, &dev_attr_reverb_decay)); + if((type & (LINE6_BITS_PODXTALL)) != 0) CHECK_RETURN(device_create_file(dev, &dev_attr_reverb_tone)); + if((type & (LINE6_BITS_PODXTALL)) != 0) CHECK_RETURN(device_create_file(dev, &dev_attr_reverb_pre_delay)); + if((type & (LINE6_BITS_PODXTALL)) != 0) CHECK_RETURN(device_create_file(dev, &dev_attr_reverb_pre_post)); + if((type & (LINE6_BITS_PODXTALL)) != 0) if(firmware >= 200) CHECK_RETURN(device_create_file(dev, &dev_attr_band_2_frequency)); + if((type & (LINE6_BITS_BASSPODXTALL)) != 0) if(firmware >= 200) CHECK_RETURN(device_create_file(dev, &dev_attr_band_3_frequency__bass)); + CHECK_RETURN(device_create_file(dev, &dev_attr_wah_enable)); + if((type & (LINE6_BITS_BASSPODXTALL)) != 0) CHECK_RETURN(device_create_file(dev, &dev_attr_modulation_lo_cut)); + if((type & (LINE6_BITS_BASSPODXTALL)) != 0) CHECK_RETURN(device_create_file(dev, &dev_attr_delay_reverb_lo_cut)); + if((type & (LINE6_BITS_PODXTALL)) != 0) if(firmware >= 200) CHECK_RETURN(device_create_file(dev, &dev_attr_volume_pedal_minimum)); + if((type & (LINE6_BITS_BASSPODXTALL)) != 0) if(firmware >= 200) CHECK_RETURN(device_create_file(dev, &dev_attr_eq_pre_post)); + CHECK_RETURN(device_create_file(dev, &dev_attr_volume_pre_post)); + if((type & (LINE6_BITS_BASSPODXTALL)) != 0) CHECK_RETURN(device_create_file(dev, &dev_attr_di_model)); + if((type & (LINE6_BITS_BASSPODXTALL)) != 0) CHECK_RETURN(device_create_file(dev, &dev_attr_di_delay)); + CHECK_RETURN(device_create_file(dev, &dev_attr_mod_enable)); + CHECK_RETURN(device_create_file(dev, &dev_attr_mod_param_1_note_value)); + CHECK_RETURN(device_create_file(dev, &dev_attr_mod_param_2)); + CHECK_RETURN(device_create_file(dev, &dev_attr_mod_param_3)); + CHECK_RETURN(device_create_file(dev, &dev_attr_mod_param_4)); + if((type & (LINE6_BITS_BASSPODXTALL)) != 0) CHECK_RETURN(device_create_file(dev, &dev_attr_mod_param_5)); + CHECK_RETURN(device_create_file(dev, &dev_attr_mod_volume_mix)); + CHECK_RETURN(device_create_file(dev, &dev_attr_mod_pre_post)); + CHECK_RETURN(device_create_file(dev, &dev_attr_modulation_model)); + if((type & (LINE6_BITS_PODXTALL)) != 0) if(firmware >= 200) CHECK_RETURN(device_create_file(dev, &dev_attr_band_3_frequency)); + if((type & (LINE6_BITS_BASSPODXTALL)) != 0) if(firmware >= 200) CHECK_RETURN(device_create_file(dev, &dev_attr_band_4_frequency__bass)); + CHECK_RETURN(device_create_file(dev, &dev_attr_mod_param_1_double_precision)); + CHECK_RETURN(device_create_file(dev, &dev_attr_delay_param_1_double_precision)); + if(firmware >= 200) CHECK_RETURN(device_create_file(dev, &dev_attr_eq_enable)); + CHECK_RETURN(device_create_file(dev, &dev_attr_tap)); + CHECK_RETURN(device_create_file(dev, &dev_attr_volume_tweak_pedal_assign)); + if((type & (LINE6_BITS_BASSPODXTALL)) != 0) if(firmware >= 200) CHECK_RETURN(device_create_file(dev, &dev_attr_band_5_frequency)); + CHECK_RETURN(device_create_file(dev, &dev_attr_tuner)); + CHECK_RETURN(device_create_file(dev, &dev_attr_mic_selection)); + CHECK_RETURN(device_create_file(dev, &dev_attr_cabinet_model)); + CHECK_RETURN(device_create_file(dev, &dev_attr_stomp_model)); + CHECK_RETURN(device_create_file(dev, &dev_attr_roomlevel)); + if((type & (LINE6_BITS_PODXTALL)) != 0) if(firmware >= 200) CHECK_RETURN(device_create_file(dev, &dev_attr_band_4_frequency)); + if((type & (LINE6_BITS_BASSPODXTALL)) != 0) if(firmware >= 200) CHECK_RETURN(device_create_file(dev, &dev_attr_band_6_frequency)); + CHECK_RETURN(device_create_file(dev, &dev_attr_stomp_param_1_note_value)); + CHECK_RETURN(device_create_file(dev, &dev_attr_stomp_param_2)); + CHECK_RETURN(device_create_file(dev, &dev_attr_stomp_param_3)); + CHECK_RETURN(device_create_file(dev, &dev_attr_stomp_param_4)); + CHECK_RETURN(device_create_file(dev, &dev_attr_stomp_param_5)); + CHECK_RETURN(device_create_file(dev, &dev_attr_stomp_param_6)); + if((type & (LINE6_BITS_LIVE)) != 0) CHECK_RETURN(device_create_file(dev, &dev_attr_amp_switch_select)); + CHECK_RETURN(device_create_file(dev, &dev_attr_delay_param_4)); + CHECK_RETURN(device_create_file(dev, &dev_attr_delay_param_5)); + CHECK_RETURN(device_create_file(dev, &dev_attr_delay_pre_post)); + if((type & (LINE6_BITS_PODXTALL)) != 0) CHECK_RETURN(device_create_file(dev, &dev_attr_delay_model)); + if((type & (LINE6_BITS_BASSPODXTALL)) != 0) CHECK_RETURN(device_create_file(dev, &dev_attr_delay_verb_model)); + CHECK_RETURN(device_create_file(dev, &dev_attr_tempo_msb)); + CHECK_RETURN(device_create_file(dev, &dev_attr_tempo_lsb)); + if(firmware >= 300) CHECK_RETURN(device_create_file(dev, &dev_attr_wah_model)); + if(firmware >= 214) CHECK_RETURN(device_create_file(dev, &dev_attr_bypass_volume)); + if((type & (LINE6_BITS_PRO)) != 0) CHECK_RETURN(device_create_file(dev, &dev_attr_fx_loop_on_off)); + CHECK_RETURN(device_create_file(dev, &dev_attr_tweak_param_select)); + CHECK_RETURN(device_create_file(dev, &dev_attr_amp1_engage)); + if(firmware >= 200) CHECK_RETURN(device_create_file(dev, &dev_attr_band_1_gain)); + if((type & (LINE6_BITS_BASSPODXTALL)) != 0) if(firmware >= 200) CHECK_RETURN(device_create_file(dev, &dev_attr_band_2_gain__bass)); + if((type & (LINE6_BITS_PODXTALL)) != 0) if(firmware >= 200) CHECK_RETURN(device_create_file(dev, &dev_attr_band_2_gain)); + if((type & (LINE6_BITS_BASSPODXTALL)) != 0) if(firmware >= 200) CHECK_RETURN(device_create_file(dev, &dev_attr_band_3_gain__bass)); + if((type & (LINE6_BITS_PODXTALL)) != 0) if(firmware >= 200) CHECK_RETURN(device_create_file(dev, &dev_attr_band_3_gain)); + if((type & (LINE6_BITS_BASSPODXTALL)) != 0) if(firmware >= 200) CHECK_RETURN(device_create_file(dev, &dev_attr_band_4_gain__bass)); + if((type & (LINE6_BITS_BASSPODXTALL)) != 0) if(firmware >= 200) CHECK_RETURN(device_create_file(dev, &dev_attr_band_5_gain__bass)); + if((type & (LINE6_BITS_PODXTALL)) != 0) if(firmware >= 200) CHECK_RETURN(device_create_file(dev, &dev_attr_band_4_gain)); + if((type & (LINE6_BITS_BASSPODXTALL)) != 0) if(firmware >= 200) CHECK_RETURN(device_create_file(dev, &dev_attr_band_6_gain__bass)); + return 0; +} + +void pod_remove_files(int firmware, int type, struct device *dev) { + device_remove_file(dev, &dev_attr_tweak); + device_remove_file(dev, &dev_attr_wah_position); + if((type & (LINE6_BITS_PODXTALL)) != 0) device_remove_file(dev, &dev_attr_compression_gain); + device_remove_file(dev, &dev_attr_vol_pedal_position); + device_remove_file(dev, &dev_attr_compression_threshold); + device_remove_file(dev, &dev_attr_pan); + device_remove_file(dev, &dev_attr_amp_model_setup); + if(firmware >= 200) device_remove_file(dev, &dev_attr_amp_model); + device_remove_file(dev, &dev_attr_drive); + device_remove_file(dev, &dev_attr_bass); + if((type & (LINE6_BITS_PODXTALL)) != 0) device_remove_file(dev, &dev_attr_mid); + if((type & (LINE6_BITS_BASSPODXTALL)) != 0) device_remove_file(dev, &dev_attr_lowmid); + if((type & (LINE6_BITS_PODXTALL)) != 0) device_remove_file(dev, &dev_attr_treble); + if((type & (LINE6_BITS_BASSPODXTALL)) != 0) device_remove_file(dev, &dev_attr_highmid); + device_remove_file(dev, &dev_attr_chan_vol); + if((type & (LINE6_BITS_PODXTALL)) != 0) device_remove_file(dev, &dev_attr_reverb_mix); + device_remove_file(dev, &dev_attr_effect_setup); + if(firmware >= 200) device_remove_file(dev, &dev_attr_band_1_frequency); + if((type & (LINE6_BITS_PODXTALL)) != 0) device_remove_file(dev, &dev_attr_presence); + if((type & (LINE6_BITS_BASSPODXTALL)) != 0) device_remove_file(dev, &dev_attr_treble__bass); + device_remove_file(dev, &dev_attr_noise_gate_enable); + device_remove_file(dev, &dev_attr_gate_threshold); + device_remove_file(dev, &dev_attr_gate_decay_time); + device_remove_file(dev, &dev_attr_stomp_enable); + device_remove_file(dev, &dev_attr_comp_enable); + device_remove_file(dev, &dev_attr_stomp_time); + device_remove_file(dev, &dev_attr_delay_enable); + device_remove_file(dev, &dev_attr_mod_param_1); + device_remove_file(dev, &dev_attr_delay_param_1); + device_remove_file(dev, &dev_attr_delay_param_1_note_value); + if((type & (LINE6_BITS_BASSPODXTALL)) != 0) if(firmware >= 200) device_remove_file(dev, &dev_attr_band_2_frequency__bass); + device_remove_file(dev, &dev_attr_delay_param_2); + device_remove_file(dev, &dev_attr_delay_volume_mix); + device_remove_file(dev, &dev_attr_delay_param_3); + if((type & (LINE6_BITS_PODXTALL)) != 0) device_remove_file(dev, &dev_attr_reverb_enable); + if((type & (LINE6_BITS_PODXTALL)) != 0) device_remove_file(dev, &dev_attr_reverb_type); + if((type & (LINE6_BITS_PODXTALL)) != 0) device_remove_file(dev, &dev_attr_reverb_decay); + if((type & (LINE6_BITS_PODXTALL)) != 0) device_remove_file(dev, &dev_attr_reverb_tone); + if((type & (LINE6_BITS_PODXTALL)) != 0) device_remove_file(dev, &dev_attr_reverb_pre_delay); + if((type & (LINE6_BITS_PODXTALL)) != 0) device_remove_file(dev, &dev_attr_reverb_pre_post); + if((type & (LINE6_BITS_PODXTALL)) != 0) if(firmware >= 200) device_remove_file(dev, &dev_attr_band_2_frequency); + if((type & (LINE6_BITS_BASSPODXTALL)) != 0) if(firmware >= 200) device_remove_file(dev, &dev_attr_band_3_frequency__bass); + device_remove_file(dev, &dev_attr_wah_enable); + if((type & (LINE6_BITS_BASSPODXTALL)) != 0) device_remove_file(dev, &dev_attr_modulation_lo_cut); + if((type & (LINE6_BITS_BASSPODXTALL)) != 0) device_remove_file(dev, &dev_attr_delay_reverb_lo_cut); + if((type & (LINE6_BITS_PODXTALL)) != 0) if(firmware >= 200) device_remove_file(dev, &dev_attr_volume_pedal_minimum); + if((type & (LINE6_BITS_BASSPODXTALL)) != 0) if(firmware >= 200) device_remove_file(dev, &dev_attr_eq_pre_post); + device_remove_file(dev, &dev_attr_volume_pre_post); + if((type & (LINE6_BITS_BASSPODXTALL)) != 0) device_remove_file(dev, &dev_attr_di_model); + if((type & (LINE6_BITS_BASSPODXTALL)) != 0) device_remove_file(dev, &dev_attr_di_delay); + device_remove_file(dev, &dev_attr_mod_enable); + device_remove_file(dev, &dev_attr_mod_param_1_note_value); + device_remove_file(dev, &dev_attr_mod_param_2); + device_remove_file(dev, &dev_attr_mod_param_3); + device_remove_file(dev, &dev_attr_mod_param_4); + if((type & (LINE6_BITS_BASSPODXTALL)) != 0) device_remove_file(dev, &dev_attr_mod_param_5); + device_remove_file(dev, &dev_attr_mod_volume_mix); + device_remove_file(dev, &dev_attr_mod_pre_post); + device_remove_file(dev, &dev_attr_modulation_model); + if((type & (LINE6_BITS_PODXTALL)) != 0) if(firmware >= 200) device_remove_file(dev, &dev_attr_band_3_frequency); + if((type & (LINE6_BITS_BASSPODXTALL)) != 0) if(firmware >= 200) device_remove_file(dev, &dev_attr_band_4_frequency__bass); + device_remove_file(dev, &dev_attr_mod_param_1_double_precision); + device_remove_file(dev, &dev_attr_delay_param_1_double_precision); + if(firmware >= 200) device_remove_file(dev, &dev_attr_eq_enable); + device_remove_file(dev, &dev_attr_tap); + device_remove_file(dev, &dev_attr_volume_tweak_pedal_assign); + if((type & (LINE6_BITS_BASSPODXTALL)) != 0) if(firmware >= 200) device_remove_file(dev, &dev_attr_band_5_frequency); + device_remove_file(dev, &dev_attr_tuner); + device_remove_file(dev, &dev_attr_mic_selection); + device_remove_file(dev, &dev_attr_cabinet_model); + device_remove_file(dev, &dev_attr_stomp_model); + device_remove_file(dev, &dev_attr_roomlevel); + if((type & (LINE6_BITS_PODXTALL)) != 0) if(firmware >= 200) device_remove_file(dev, &dev_attr_band_4_frequency); + if((type & (LINE6_BITS_BASSPODXTALL)) != 0) if(firmware >= 200) device_remove_file(dev, &dev_attr_band_6_frequency); + device_remove_file(dev, &dev_attr_stomp_param_1_note_value); + device_remove_file(dev, &dev_attr_stomp_param_2); + device_remove_file(dev, &dev_attr_stomp_param_3); + device_remove_file(dev, &dev_attr_stomp_param_4); + device_remove_file(dev, &dev_attr_stomp_param_5); + device_remove_file(dev, &dev_attr_stomp_param_6); + if((type & (LINE6_BITS_LIVE)) != 0) device_remove_file(dev, &dev_attr_amp_switch_select); + device_remove_file(dev, &dev_attr_delay_param_4); + device_remove_file(dev, &dev_attr_delay_param_5); + device_remove_file(dev, &dev_attr_delay_pre_post); + if((type & (LINE6_BITS_PODXTALL)) != 0) device_remove_file(dev, &dev_attr_delay_model); + if((type & (LINE6_BITS_BASSPODXTALL)) != 0) device_remove_file(dev, &dev_attr_delay_verb_model); + device_remove_file(dev, &dev_attr_tempo_msb); + device_remove_file(dev, &dev_attr_tempo_lsb); + if(firmware >= 300) device_remove_file(dev, &dev_attr_wah_model); + if(firmware >= 214) device_remove_file(dev, &dev_attr_bypass_volume); + if((type & (LINE6_BITS_PRO)) != 0) device_remove_file(dev, &dev_attr_fx_loop_on_off); + device_remove_file(dev, &dev_attr_tweak_param_select); + device_remove_file(dev, &dev_attr_amp1_engage); + if(firmware >= 200) device_remove_file(dev, &dev_attr_band_1_gain); + if((type & (LINE6_BITS_BASSPODXTALL)) != 0) if(firmware >= 200) device_remove_file(dev, &dev_attr_band_2_gain__bass); + if((type & (LINE6_BITS_PODXTALL)) != 0) if(firmware >= 200) device_remove_file(dev, &dev_attr_band_2_gain); + if((type & (LINE6_BITS_BASSPODXTALL)) != 0) if(firmware >= 200) device_remove_file(dev, &dev_attr_band_3_gain__bass); + if((type & (LINE6_BITS_PODXTALL)) != 0) if(firmware >= 200) device_remove_file(dev, &dev_attr_band_3_gain); + if((type & (LINE6_BITS_BASSPODXTALL)) != 0) if(firmware >= 200) device_remove_file(dev, &dev_attr_band_4_gain__bass); + if((type & (LINE6_BITS_BASSPODXTALL)) != 0) if(firmware >= 200) device_remove_file(dev, &dev_attr_band_5_gain__bass); + if((type & (LINE6_BITS_PODXTALL)) != 0) if(firmware >= 200) device_remove_file(dev, &dev_attr_band_4_gain); + if((type & (LINE6_BITS_BASSPODXTALL)) != 0) if(firmware >= 200) device_remove_file(dev, &dev_attr_band_6_gain__bass); +} + +EXPORT_SYMBOL(pod_create_files); +EXPORT_SYMBOL(pod_remove_files); + +int variax_create_files(int firmware, int type, struct device *dev) { + int err; + CHECK_RETURN(device_create_file(dev, &dev_attr_body)); + CHECK_RETURN(device_create_file(dev, &dev_attr_pickup1_enable)); + CHECK_RETURN(device_create_file(dev, &dev_attr_pickup1_type)); + CHECK_RETURN(device_create_file(dev, &dev_attr_pickup1_position)); + CHECK_RETURN(device_create_file(dev, &dev_attr_pickup1_angle)); + CHECK_RETURN(device_create_file(dev, &dev_attr_pickup1_level)); + CHECK_RETURN(device_create_file(dev, &dev_attr_pickup2_enable)); + CHECK_RETURN(device_create_file(dev, &dev_attr_pickup2_type)); + CHECK_RETURN(device_create_file(dev, &dev_attr_pickup2_position)); + CHECK_RETURN(device_create_file(dev, &dev_attr_pickup2_angle)); + CHECK_RETURN(device_create_file(dev, &dev_attr_pickup2_level)); + CHECK_RETURN(device_create_file(dev, &dev_attr_pickup_phase)); + CHECK_RETURN(device_create_file(dev, &dev_attr_capacitance)); + CHECK_RETURN(device_create_file(dev, &dev_attr_tone_resistance)); + CHECK_RETURN(device_create_file(dev, &dev_attr_volume_resistance)); + CHECK_RETURN(device_create_file(dev, &dev_attr_taper)); + CHECK_RETURN(device_create_file(dev, &dev_attr_tone_dump)); + CHECK_RETURN(device_create_file(dev, &dev_attr_save_tone)); + CHECK_RETURN(device_create_file(dev, &dev_attr_volume_dump)); + CHECK_RETURN(device_create_file(dev, &dev_attr_tuning_enable)); + CHECK_RETURN(device_create_file(dev, &dev_attr_tuning6)); + CHECK_RETURN(device_create_file(dev, &dev_attr_tuning5)); + CHECK_RETURN(device_create_file(dev, &dev_attr_tuning4)); + CHECK_RETURN(device_create_file(dev, &dev_attr_tuning3)); + CHECK_RETURN(device_create_file(dev, &dev_attr_tuning2)); + CHECK_RETURN(device_create_file(dev, &dev_attr_tuning1)); + CHECK_RETURN(device_create_file(dev, &dev_attr_detune6)); + CHECK_RETURN(device_create_file(dev, &dev_attr_detune5)); + CHECK_RETURN(device_create_file(dev, &dev_attr_detune4)); + CHECK_RETURN(device_create_file(dev, &dev_attr_detune3)); + CHECK_RETURN(device_create_file(dev, &dev_attr_detune2)); + CHECK_RETURN(device_create_file(dev, &dev_attr_detune1)); + CHECK_RETURN(device_create_file(dev, &dev_attr_mix6)); + CHECK_RETURN(device_create_file(dev, &dev_attr_mix5)); + CHECK_RETURN(device_create_file(dev, &dev_attr_mix4)); + CHECK_RETURN(device_create_file(dev, &dev_attr_mix3)); + CHECK_RETURN(device_create_file(dev, &dev_attr_mix2)); + CHECK_RETURN(device_create_file(dev, &dev_attr_mix1)); + CHECK_RETURN(device_create_file(dev, &dev_attr_pickup_wiring)); + return 0; +} + +void variax_remove_files(int firmware, int type, struct device *dev) { + device_remove_file(dev, &dev_attr_body); + device_remove_file(dev, &dev_attr_pickup1_enable); + device_remove_file(dev, &dev_attr_pickup1_type); + device_remove_file(dev, &dev_attr_pickup1_position); + device_remove_file(dev, &dev_attr_pickup1_angle); + device_remove_file(dev, &dev_attr_pickup1_level); + device_remove_file(dev, &dev_attr_pickup2_enable); + device_remove_file(dev, &dev_attr_pickup2_type); + device_remove_file(dev, &dev_attr_pickup2_position); + device_remove_file(dev, &dev_attr_pickup2_angle); + device_remove_file(dev, &dev_attr_pickup2_level); + device_remove_file(dev, &dev_attr_pickup_phase); + device_remove_file(dev, &dev_attr_capacitance); + device_remove_file(dev, &dev_attr_tone_resistance); + device_remove_file(dev, &dev_attr_volume_resistance); + device_remove_file(dev, &dev_attr_taper); + device_remove_file(dev, &dev_attr_tone_dump); + device_remove_file(dev, &dev_attr_save_tone); + device_remove_file(dev, &dev_attr_volume_dump); + device_remove_file(dev, &dev_attr_tuning_enable); + device_remove_file(dev, &dev_attr_tuning6); + device_remove_file(dev, &dev_attr_tuning5); + device_remove_file(dev, &dev_attr_tuning4); + device_remove_file(dev, &dev_attr_tuning3); + device_remove_file(dev, &dev_attr_tuning2); + device_remove_file(dev, &dev_attr_tuning1); + device_remove_file(dev, &dev_attr_detune6); + device_remove_file(dev, &dev_attr_detune5); + device_remove_file(dev, &dev_attr_detune4); + device_remove_file(dev, &dev_attr_detune3); + device_remove_file(dev, &dev_attr_detune2); + device_remove_file(dev, &dev_attr_detune1); + device_remove_file(dev, &dev_attr_mix6); + device_remove_file(dev, &dev_attr_mix5); + device_remove_file(dev, &dev_attr_mix4); + device_remove_file(dev, &dev_attr_mix3); + device_remove_file(dev, &dev_attr_mix2); + device_remove_file(dev, &dev_attr_mix1); + device_remove_file(dev, &dev_attr_pickup_wiring); +} + +EXPORT_SYMBOL(variax_create_files); +EXPORT_SYMBOL(variax_remove_files); diff --git a/drivers/staging/line6/control.h b/drivers/staging/line6/control.h new file mode 100644 index 000000000000..2f19665d95a9 --- /dev/null +++ b/drivers/staging/line6/control.h @@ -0,0 +1,187 @@ +/* + * Line6 Linux USB driver - 0.8.0 + * + * Copyright (C) 2004-2009 Markus Grabner (grabner@icg.tugraz.at) + * + * 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, version 2. + * + */ + +#ifndef LINE6_CONTROL_H +#define LINE6_CONTROL_H + + +/** + List of PODxt Pro controls. + See Appendix C of the "PODxt (Pro) Pilot's Handbook" by Line6. + Comments after the number refer to the PODxt Pro firmware version required + for this feature. +*/ +enum { + POD_tweak = 1, + POD_wah_position = 4, + POD_compression_gain = 5, /* device: LINE6_BITS_PODXTALL */ + POD_vol_pedal_position = 7, + POD_compression_threshold = 9, + POD_pan = 10, + POD_amp_model_setup = 11, + POD_amp_model = 12, /* firmware: 2.0 */ + POD_drive = 13, + POD_bass = 14, + POD_mid = 15, /* device: LINE6_BITS_PODXTALL */ + POD_lowmid = 15, /* device: LINE6_BITS_BASSPODXTALL */ + POD_treble = 16, /* device: LINE6_BITS_PODXTALL */ + POD_highmid = 16, /* device: LINE6_BITS_BASSPODXTALL */ + POD_chan_vol = 17, + POD_reverb_mix = 18, /* device: LINE6_BITS_PODXTALL */ + POD_effect_setup = 19, + POD_band_1_frequency = 20, /* firmware: 2.0 */ + POD_presence = 21, /* device: LINE6_BITS_PODXTALL */ + POD_treble__bass = 21, /* device: LINE6_BITS_BASSPODXTALL */ + POD_noise_gate_enable = 22, + POD_gate_threshold = 23, + POD_gate_decay_time = 24, + POD_stomp_enable = 25, + POD_comp_enable = 26, + POD_stomp_time = 27, + POD_delay_enable = 28, + POD_mod_param_1 = 29, + POD_delay_param_1 = 30, + POD_delay_param_1_note_value = 31, + POD_band_2_frequency__bass = 32, /* device: LINE6_BITS_BASSPODXTALL */ /* firmware: 2.0 */ + POD_delay_param_2 = 33, + POD_delay_volume_mix = 34, + POD_delay_param_3 = 35, + POD_reverb_enable = 36, /* device: LINE6_BITS_PODXTALL */ + POD_reverb_type = 37, /* device: LINE6_BITS_PODXTALL */ + POD_reverb_decay = 38, /* device: LINE6_BITS_PODXTALL */ + POD_reverb_tone = 39, /* device: LINE6_BITS_PODXTALL */ + POD_reverb_pre_delay = 40, /* device: LINE6_BITS_PODXTALL */ + POD_reverb_pre_post = 41, /* device: LINE6_BITS_PODXTALL */ + POD_band_2_frequency = 42, /* device: LINE6_BITS_PODXTALL */ /* firmware: 2.0 */ + POD_band_3_frequency__bass = 42, /* device: LINE6_BITS_BASSPODXTALL */ /* firmware: 2.0 */ + POD_wah_enable = 43, + POD_modulation_lo_cut = 44, /* device: LINE6_BITS_BASSPODXTALL */ + POD_delay_reverb_lo_cut = 45, /* device: LINE6_BITS_BASSPODXTALL */ + POD_volume_pedal_minimum = 46, /* device: LINE6_BITS_PODXTALL */ /* firmware: 2.0 */ + POD_eq_pre_post = 46, /* device: LINE6_BITS_BASSPODXTALL */ /* firmware: 2.0 */ + POD_volume_pre_post = 47, + POD_di_model = 48, /* device: LINE6_BITS_BASSPODXTALL */ + POD_di_delay = 49, /* device: LINE6_BITS_BASSPODXTALL */ + POD_mod_enable = 50, + POD_mod_param_1_note_value = 51, + POD_mod_param_2 = 52, + POD_mod_param_3 = 53, + POD_mod_param_4 = 54, + POD_mod_param_5 = 55, /* device: LINE6_BITS_BASSPODXTALL */ + POD_mod_volume_mix = 56, + POD_mod_pre_post = 57, + POD_modulation_model = 58, + POD_band_3_frequency = 60, /* device: LINE6_BITS_PODXTALL */ /* firmware: 2.0 */ + POD_band_4_frequency__bass = 60, /* device: LINE6_BITS_BASSPODXTALL */ /* firmware: 2.0 */ + POD_mod_param_1_double_precision = 61, + POD_delay_param_1_double_precision = 62, + POD_eq_enable = 63, /* firmware: 2.0 */ + POD_tap = 64, + POD_volume_tweak_pedal_assign = 65, + POD_band_5_frequency = 68, /* device: LINE6_BITS_BASSPODXTALL */ /* firmware: 2.0 */ + POD_tuner = 69, + POD_mic_selection = 70, + POD_cabinet_model = 71, + POD_stomp_model = 75, + POD_roomlevel = 76, + POD_band_4_frequency = 77, /* device: LINE6_BITS_PODXTALL */ /* firmware: 2.0 */ + POD_band_6_frequency = 77, /* device: LINE6_BITS_BASSPODXTALL */ /* firmware: 2.0 */ + POD_stomp_param_1_note_value = 78, + POD_stomp_param_2 = 79, + POD_stomp_param_3 = 80, + POD_stomp_param_4 = 81, + POD_stomp_param_5 = 82, + POD_stomp_param_6 = 83, + POD_amp_switch_select = 84, /* device: LINE6_BITS_LIVE */ + POD_delay_param_4 = 85, + POD_delay_param_5 = 86, + POD_delay_pre_post = 87, + POD_delay_model = 88, /* device: LINE6_BITS_PODXTALL */ + POD_delay_verb_model = 88, /* device: LINE6_BITS_BASSPODXTALL */ + POD_tempo_msb = 89, + POD_tempo_lsb = 90, + POD_wah_model = 91, /* firmware: 3.0 */ + POD_bypass_volume = 105, /* firmware: 2.14 */ + POD_fx_loop_on_off = 107, /* device: LINE6_BITS_PRO */ + POD_tweak_param_select = 108, + POD_amp1_engage = 111, + POD_band_1_gain = 114, /* firmware: 2.0 */ + POD_band_2_gain__bass = 115, /* device: LINE6_BITS_BASSPODXTALL */ /* firmware: 2.0 */ + POD_band_2_gain = 116, /* device: LINE6_BITS_PODXTALL */ /* firmware: 2.0 */ + POD_band_3_gain__bass = 116, /* device: LINE6_BITS_BASSPODXTALL */ /* firmware: 2.0 */ + POD_band_3_gain = 117, /* device: LINE6_BITS_PODXTALL */ /* firmware: 2.0 */ + POD_band_4_gain__bass = 117, /* device: LINE6_BITS_BASSPODXTALL */ /* firmware: 2.0 */ + POD_band_5_gain__bass = 118, /* device: LINE6_BITS_BASSPODXTALL */ /* firmware: 2.0 */ + POD_band_4_gain = 119, /* device: LINE6_BITS_PODXTALL */ /* firmware: 2.0 */ + POD_band_6_gain__bass = 119 /* device: LINE6_BITS_BASSPODXTALL */ /* firmware: 2.0 */ +}; + +/** + List of Variax workbench controls (dump). +*/ +enum { + VARIAX_body = 3, + VARIAX_pickup1_enable = 4, /* 0: enabled, 1: disabled */ + VARIAX_pickup1_type = 8, + VARIAX_pickup1_position = 9, /* type: 24 bit float */ + VARIAX_pickup1_angle = 12, /* type: 24 bit float */ + VARIAX_pickup1_level = 15, /* type: 24 bit float */ + VARIAX_pickup2_enable = 18, /* 0: enabled, 1: disabled */ + VARIAX_pickup2_type = 22, + VARIAX_pickup2_position = 23, /* type: 24 bit float */ + VARIAX_pickup2_angle = 26, /* type: 24 bit float */ + VARIAX_pickup2_level = 29, /* type: 24 bit float */ + VARIAX_pickup_phase = 32, /* 0: in phase, 1: out of phase */ + VARIAX_capacitance = 33, /* type: 24 bit float */ + VARIAX_tone_resistance = 36, /* type: 24 bit float */ + VARIAX_volume_resistance = 39, /* type: 24 bit float */ + VARIAX_taper = 42, /* 0: Linear, 1: Audio */ + VARIAX_tone_dump = 43, /* type: 24 bit float */ + VARIAX_save_tone = 46, + VARIAX_volume_dump = 47, /* type: 24 bit float */ + VARIAX_tuning_enable = 50, + VARIAX_tuning6 = 51, + VARIAX_tuning5 = 52, + VARIAX_tuning4 = 53, + VARIAX_tuning3 = 54, + VARIAX_tuning2 = 55, + VARIAX_tuning1 = 56, + VARIAX_detune6 = 57, /* type: 24 bit float */ + VARIAX_detune5 = 60, /* type: 24 bit float */ + VARIAX_detune4 = 63, /* type: 24 bit float */ + VARIAX_detune3 = 66, /* type: 24 bit float */ + VARIAX_detune2 = 69, /* type: 24 bit float */ + VARIAX_detune1 = 72, /* type: 24 bit float */ + VARIAX_mix6 = 75, /* type: 24 bit float */ + VARIAX_mix5 = 78, /* type: 24 bit float */ + VARIAX_mix4 = 81, /* type: 24 bit float */ + VARIAX_mix3 = 84, /* type: 24 bit float */ + VARIAX_mix2 = 87, /* type: 24 bit float */ + VARIAX_mix1 = 90, /* type: 24 bit float */ + VARIAX_pickup_wiring = 96 /* 0: parallel, 1: series */ +}; + +/** + List of Variax workbench controls (MIDI). +*/ +enum { + VARIAXMIDI_volume = 7, + VARIAXMIDI_tone = 79, +}; + + +extern int pod_create_files(int firmware, int type, struct device *dev); +extern void pod_remove_files(int firmware, int type, struct device *dev); +extern int variax_create_files(int firmware, int type, struct device *dev); +extern void variax_remove_files(int firmware, int type, struct device *dev); + + +#endif diff --git a/drivers/staging/line6/driver.c b/drivers/staging/line6/driver.c new file mode 100644 index 000000000000..f20efa583691 --- /dev/null +++ b/drivers/staging/line6/driver.c @@ -0,0 +1,1050 @@ +/* + * Line6 Linux USB driver - 0.8.0 + * + * Copyright (C) 2004-2009 Markus Grabner (grabner@icg.tugraz.at) + * + * 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, version 2. + * + */ + +#include "driver.h" + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/usb.h> + +#include "audio.h" +#include "capture.h" +#include "control.h" +#include "midi.h" +#include "playback.h" +#include "pod.h" +#include "revision.h" +#include "toneport.h" +#include "usbdefs.h" +#include "variax.h" + + +#define DRIVER_AUTHOR "Markus Grabner <grabner@icg.tugraz.at>" +#define DRIVER_DESC "Line6 USB Driver" +#define DRIVER_VERSION "0.8.0" + + +/* table of devices that work with this driver */ +static struct usb_device_id line6_id_table[] = { + { USB_DEVICE(LINE6_VENDOR_ID, LINE6_DEVID_BASSPODXT) }, + { USB_DEVICE(LINE6_VENDOR_ID, LINE6_DEVID_BASSPODXTLIVE) }, + { USB_DEVICE(LINE6_VENDOR_ID, LINE6_DEVID_BASSPODXTPRO) }, + { USB_DEVICE(LINE6_VENDOR_ID, LINE6_DEVID_GUITARPORT) }, + { USB_DEVICE(LINE6_VENDOR_ID, LINE6_DEVID_POCKETPOD) }, + { USB_DEVICE(LINE6_VENDOR_ID, LINE6_DEVID_PODX3) }, + { USB_DEVICE(LINE6_VENDOR_ID, LINE6_DEVID_PODX3LIVE) }, + { USB_DEVICE(LINE6_VENDOR_ID, LINE6_DEVID_PODXT) }, + { USB_DEVICE(LINE6_VENDOR_ID, LINE6_DEVID_PODXTLIVE) }, + { USB_DEVICE(LINE6_VENDOR_ID, LINE6_DEVID_PODXTPRO) }, + { USB_DEVICE(LINE6_VENDOR_ID, LINE6_DEVID_TONEPORT_GX) }, + { USB_DEVICE(LINE6_VENDOR_ID, LINE6_DEVID_TONEPORT_UX1) }, + { USB_DEVICE(LINE6_VENDOR_ID, LINE6_DEVID_TONEPORT_UX2) }, + { USB_DEVICE(LINE6_VENDOR_ID, LINE6_DEVID_VARIAX) }, + { }, +}; +MODULE_DEVICE_TABLE (usb, line6_id_table); + +static struct line6_properties line6_properties_table[] = { + { "BassPODxt", LINE6_BIT_BASSPODXT, LINE6_BIT_CONTROL_PCM }, + { "BassPODxt Live", LINE6_BIT_BASSPODXTLIVE, LINE6_BIT_CONTROL_PCM }, + { "BassPODxt Pro", LINE6_BIT_BASSPODXTPRO, LINE6_BIT_CONTROL_PCM }, + { "GuitarPort", LINE6_BIT_GUITARPORT, LINE6_BIT_PCM }, + { "Pocket POD", LINE6_BIT_POCKETPOD, LINE6_BIT_CONTROL_PCM }, + { "POD X3", LINE6_BIT_PODX3, LINE6_BIT_PCM }, + { "POD X3 Live", LINE6_BIT_PODX3LIVE, LINE6_BIT_PCM }, + { "PODxt", LINE6_BIT_PODXT, LINE6_BIT_CONTROL_PCM }, + { "PODxt Live", LINE6_BIT_PODXTLIVE, LINE6_BIT_CONTROL_PCM }, + { "PODxt Pro", LINE6_BIT_PODXTPRO, LINE6_BIT_CONTROL_PCM }, + { "TonePort GX", LINE6_BIT_TONEPORT_GX, LINE6_BIT_PCM }, + { "TonePort UX1", LINE6_BIT_TONEPORT_UX1, LINE6_BIT_PCM }, + { "TonePort UX2", LINE6_BIT_TONEPORT_UX2, LINE6_BIT_PCM }, + { "Variax Workbench", LINE6_BIT_VARIAX, LINE6_BIT_CONTROL } +}; + + +/* + This is Line6's MIDI manufacturer ID. +*/ +const unsigned char line6_midi_id[] = { 0x00, 0x01, 0x0c }; + +struct usb_line6 *line6_devices[LINE6_MAX_DEVICES]; +struct workqueue_struct *line6_workqueue; + + +/** + Class for asynchronous messages. +*/ +struct message +{ + struct usb_line6 *line6; + const char *buffer; + int size; + int done; +}; + + +/* + Forward declarations. +*/ +static void line6_data_received(struct urb *urb PT_REGS); +static int line6_send_raw_message_async_part(struct message *msg, struct urb *urb); + + +/* + Start to listen on endpoint. +*/ +static int line6_start_listen(struct usb_line6 *line6) +{ + usb_fill_int_urb(line6->urb_listen, + line6->usbdev, + usb_rcvintpipe(line6->usbdev, line6->ep_control_read), + line6->buffer_listen, LINE6_BUFSIZE_LISTEN, + line6_data_received, + line6, + line6->interval); + line6->urb_listen->actual_length = 0; + return usb_submit_urb(line6->urb_listen, GFP_KERNEL); +} + +#if DO_DUMP_ANY +/* + Write hexdump to syslog. +*/ +void line6_write_hexdump(struct usb_line6 *line6, char dir, const unsigned char *buffer, int size) +{ + static const int BYTES_PER_LINE = 8; + char hexdump[100]; + char asc[BYTES_PER_LINE + 1]; + int i, j; + + for(i = 0; i < size; i += BYTES_PER_LINE) { + int hexdumpsize = sizeof(hexdump); + char *p = hexdump; + int n = min(size - i, BYTES_PER_LINE); + asc[n] = 0; + + for(j = 0; j < BYTES_PER_LINE; ++j) { + int bytes; + + if(j < n) { + unsigned char val = buffer[i + j]; + bytes = snprintf(p, hexdumpsize, " %02X", val); + asc[j] = ((val >= 0x20) && (val < 0x7f)) ? val : '.'; + } + else + bytes = snprintf(p, hexdumpsize, " "); + + if(bytes > hexdumpsize) + break; /* buffer overflow */ + + p += bytes; + hexdumpsize -= bytes; + } + + dev_info(line6->ifcdev, "%c%04X:%s %s\n", dir, i, hexdump, asc); + } +} +#endif + +#if DO_DUMP_URB_RECEIVE +/* + Dump URB data to syslog. +*/ +static void line6_dump_urb(struct urb *urb) +{ + struct usb_line6 *line6 = (struct usb_line6 *)urb->context; + + if(urb->status < 0) + return; + + line6_write_hexdump(line6, 'R', (unsigned char *)urb->transfer_buffer, urb->actual_length); +} +#endif + +/* + Send raw message in pieces of wMaxPacketSize bytes. +*/ +int line6_send_raw_message(struct usb_line6 *line6, const char *buffer, int size) +{ + int i, done = 0; + +#if DO_DUMP_URB_SEND + line6_write_hexdump(line6, 'S', buffer, size); +#endif + + for(i = 0; i < size; i += line6->max_packet_size) { + int partial; + const char *frag_buf = buffer + i; + int frag_size = min(line6->max_packet_size, size - i); + int retval = usb_interrupt_msg(line6->usbdev, + usb_sndintpipe(line6->usbdev, line6->ep_control_write), + (char *)frag_buf, frag_size, &partial, LINE6_TIMEOUT * HZ); + + if(retval) { + dev_err(line6->ifcdev, "usb_interrupt_msg failed (%d)\n", retval); + break; + } + + done += frag_size; + } + + return done; +} + +/* + Notification of completion of asynchronous request transmission. +*/ +static void line6_async_request_sent(struct urb *urb PT_REGS) +{ + struct message *msg = (struct message *)urb->context; + + if(msg->done >= msg->size) { + usb_free_urb(urb); + kfree(msg); + } + else + line6_send_raw_message_async_part(msg, urb); +} + +/* + Asynchronously send part of a raw message. +*/ +static int line6_send_raw_message_async_part(struct message *msg, struct urb *urb) +{ + int retval; + struct usb_line6 *line6 = msg->line6; + int done = msg->done; + int bytes = min(msg->size - done, line6->max_packet_size); + + usb_fill_int_urb(urb, + line6->usbdev, + usb_sndintpipe(line6->usbdev, line6->ep_control_write), + (char *)msg->buffer + done, bytes, + line6_async_request_sent, msg, line6->interval); + +#if DO_DUMP_URB_SEND + line6_write_hexdump(line6, 'S', (char *)msg->buffer + done, bytes); +#endif + + msg->done += bytes; + retval = usb_submit_urb(urb, GFP_ATOMIC); + + if(retval < 0) { + dev_err(line6->ifcdev, "line6_send_raw_message_async: usb_submit_urb failed (%d)\n", retval); + usb_free_urb(urb); + kfree(msg); + return -EINVAL; + } + + return 0; +} + +/* + Asynchronously send raw message. +*/ +int line6_send_raw_message_async(struct usb_line6 *line6, const char *buffer, int size) +{ + struct message *msg; + struct urb *urb; + + /* create message: */ + msg = kmalloc(sizeof(struct message), GFP_ATOMIC); + + if(msg == NULL) { + dev_err(line6->ifcdev, "Out of memory\n"); + return -ENOMEM; + } + + /* create URB: */ + urb = usb_alloc_urb(0, GFP_ATOMIC); + + if(urb == NULL) { + kfree(msg); + dev_err(line6->ifcdev, "Out of memory\n"); + return -ENOMEM; + } + + /* set message data: */ + msg->line6 = line6; + msg->buffer = buffer; + msg->size = size; + msg->done = 0; + + /* start sending: */ + return line6_send_raw_message_async_part(msg, urb); +} + +/* + Send sysex message in pieces of wMaxPacketSize bytes. +*/ +int line6_send_sysex_message(struct usb_line6 *line6, const char *buffer, int size) +{ + return line6_send_raw_message(line6, buffer, size + SYSEX_EXTRA_SIZE) - SYSEX_EXTRA_SIZE; +} + +/* + Allocate buffer for sysex message and prepare header. + @param code sysex message code + @param size number of bytes between code and sysex end +*/ +char *line6_alloc_sysex_buffer(struct usb_line6 *line6, int code1, int code2, int size) +{ + char *buffer = kmalloc(size + SYSEX_EXTRA_SIZE, GFP_KERNEL); + + if(!buffer) { + dev_err(line6->ifcdev, "out of memory\n"); + return 0; + } + + buffer[0] = LINE6_SYSEX_BEGIN; + memcpy(buffer + 1, line6_midi_id, sizeof(line6_midi_id)); + buffer[sizeof(line6_midi_id) + 1] = code1; + buffer[sizeof(line6_midi_id) + 2] = code2; + buffer[sizeof(line6_midi_id) + 3 + size] = LINE6_SYSEX_END; + return buffer; +} + +/* + Notification of data received from the Line6 device. +*/ +static void line6_data_received(struct urb *urb PT_REGS) +{ + struct usb_line6 *line6 = (struct usb_line6 *)urb->context; + struct MidiBuffer *mb = &line6->line6midi->midibuf_in; + int done; + + if(urb->status == -ESHUTDOWN) + return; + +#if DO_DUMP_URB_RECEIVE + line6_dump_urb(urb); +#endif + + done = midibuf_write(mb, urb->transfer_buffer, urb->actual_length); + + if(done < urb->actual_length) { + midibuf_ignore(mb, done); + DEBUG_MESSAGES(dev_err(line6->ifcdev, "%d %d buffer overflow - message skipped\n", done, urb->actual_length)); + } + + for(;;) { + done = midibuf_read(mb, line6->buffer_message, LINE6_MESSAGE_MAXLEN); + + if(done == 0) + break; + + /* MIDI input filter */ + if(midibuf_skip_message(mb, line6->line6midi->midi_mask_receive)) + continue; + + line6->message_length = done; +#if DO_DUMP_MIDI_RECEIVE + line6_write_hexdump(line6, 'r', line6->buffer_message, done); +#endif + line6_midi_receive(line6, line6->buffer_message, done); + + switch(line6->usbdev->descriptor.idProduct) { + case LINE6_DEVID_BASSPODXT: + case LINE6_DEVID_BASSPODXTLIVE: + case LINE6_DEVID_BASSPODXTPRO: + case LINE6_DEVID_PODXT: + case LINE6_DEVID_PODXTPRO: + case LINE6_DEVID_POCKETPOD: + pod_process_message((struct usb_line6_pod *)line6); + break; + + case LINE6_DEVID_PODXTLIVE: + switch(line6->interface_number) { + case PODXTLIVE_INTERFACE_POD: + pod_process_message((struct usb_line6_pod *)line6); + break; + + case PODXTLIVE_INTERFACE_VARIAX: + variax_process_message((struct usb_line6_variax *)line6); + break; + + default: + dev_err(line6->ifcdev, "PODxt Live interface %d not supported\n", line6->interface_number); + } + break; + + case LINE6_DEVID_VARIAX: + variax_process_message((struct usb_line6_variax *)line6); + break; + + default: + MISSING_CASE; + } + } + + line6_start_listen(line6); +} + +/* + Send channel number (i.e., switch to a different sound). +*/ +int line6_send_program(struct usb_line6 *line6, int value) +{ + int retval; + unsigned char *buffer; + unsigned int partial; + + buffer = kmalloc(2, GFP_KERNEL); + + if(!buffer) { + dev_err(line6->ifcdev, "out of memory\n"); + return -ENOMEM; + } + + buffer[0] = LINE6_PROGRAM_CHANGE | LINE6_CHANNEL_HOST; + buffer[1] = value; + +#if DO_DUMP_URB_SEND + line6_write_hexdump(line6, 'S', buffer, 2); +#endif + + retval = usb_interrupt_msg(line6->usbdev, + usb_sndintpipe(line6->usbdev, line6->ep_control_write), + buffer, 2, &partial, LINE6_TIMEOUT * HZ); + + if(retval) + dev_err(line6->ifcdev, "usb_interrupt_msg failed (%d)\n", retval); + + kfree(buffer); + return retval; +} + +/* + Transmit Line6 control parameter. +*/ +int line6_transmit_parameter(struct usb_line6 *line6, int param, int value) +{ + int retval; + unsigned char *buffer; + unsigned int partial; + + buffer = kmalloc(3, GFP_KERNEL); + + if(!buffer) { + dev_err(line6->ifcdev, "out of memory\n"); + return -ENOMEM; + } + + buffer[0] = LINE6_PARAM_CHANGE | LINE6_CHANNEL_HOST; + buffer[1] = param; + buffer[2] = value; + +#if DO_DUMP_URB_SEND + line6_write_hexdump(line6, 'S', buffer, 3); +#endif + + retval = usb_interrupt_msg(line6->usbdev, + usb_sndintpipe(line6->usbdev, line6->ep_control_write), + buffer, 3, &partial, LINE6_TIMEOUT * HZ); + + if(retval) + dev_err(line6->ifcdev, "usb_interrupt_msg failed (%d)\n", retval); + + kfree(buffer); + return retval; +} + +/* + Read data from device. +*/ +int line6_read_data(struct usb_line6 *line6, int address, void *data, size_t datalen) +{ + struct usb_device *usbdev = line6->usbdev; + int ret; + unsigned char len; + + /* query the serial number: */ + ret = usb_control_msg(usbdev, usb_sndctrlpipe(usbdev, 0), 0x67, + USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_OUT, + (datalen << 8) | 0x21, address, 0, 0, LINE6_TIMEOUT * HZ); + + if(ret < 0) { + dev_err(line6->ifcdev, "read request failed (error %d)\n", ret); + return ret; + } + + /* Wait for data length. We'll get a couple of 0xff until length arrives. */ + do { + ret = usb_control_msg(usbdev, usb_rcvctrlpipe(usbdev, 0), 0x67, + USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_IN, + 0x0012, 0x0000, &len, 1, LINE6_TIMEOUT * HZ); + if(ret < 0) { + dev_err(line6->ifcdev, "receive length failed (error %d)\n", ret); + return ret; + } + } + while(len == 0xff); + + if(len != datalen) { /* should be equal or something went wrong */ + dev_err(line6->ifcdev, "length mismatch (expected %d, got %d)\n", (int)datalen, (int)len); + return -EINVAL; + } + + /* receive the result: */ + ret = usb_control_msg(usbdev, usb_rcvctrlpipe(usbdev, 0), 0x67, + USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_IN, + 0x0013, 0x0000, data, datalen, LINE6_TIMEOUT * HZ); + + if(ret < 0) { + dev_err(line6->ifcdev, "read failed (error %d)\n", ret); + return ret; + } + + return 0; +} + +/* + Write data to device. +*/ +int line6_write_data(struct usb_line6 *line6, int address, void *data, size_t datalen) +{ + struct usb_device *usbdev = line6->usbdev; + int ret; + unsigned char status; + + ret = usb_control_msg(usbdev, usb_sndctrlpipe(usbdev,0), 0x67, + USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_OUT, + 0x0022, address, data, datalen, LINE6_TIMEOUT * HZ); + + if(ret < 0) { + dev_err(line6->ifcdev, "write request failed (error %d)\n", ret); + return ret; + } + + do { + ret = usb_control_msg(usbdev, usb_rcvctrlpipe(usbdev,0), 0x67, + USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_IN, + 0x0012, 0x0000, &status, 1, LINE6_TIMEOUT * HZ); + + if(ret < 0) { + dev_err(line6->ifcdev, "receiving status failed (error %d)\n", ret); + return ret; + } + } + while(status == 0xff); + + if(status != 0) { + dev_err(line6->ifcdev, "write failed (error %d)\n", ret); + return -EINVAL; + } + + return 0; +} + +/* + Read Line6 device serial number. + (POD, TonePort, GuitarPort) +*/ +int line6_read_serial_number(struct usb_line6 *line6, int *serial_number) +{ + return line6_read_data(line6, 0x80d0, serial_number, sizeof(*serial_number)); +} + +/* + No operation (i.e., unsupported). +*/ +ssize_t line6_nop_read(struct device *dev, DEVICE_ATTRIBUTE char *buf) +{ + return 0; +} + +/* + No operation (i.e., unsupported). +*/ +ssize_t line6_nop_write(struct device *dev, DEVICE_ATTRIBUTE const char *buf, size_t count) +{ + return count; +} + +/* + "write" request on "raw" special file. +*/ +#if CREATE_RAW_FILE +ssize_t line6_set_raw(struct device *dev, DEVICE_ATTRIBUTE const char *buf, size_t count) +{ + struct usb_interface *interface = to_usb_interface(dev); + struct usb_line6 *line6 = usb_get_intfdata(interface); + line6_send_raw_message(line6, buf, count); + return count; +} +#endif + +/* + Generic destructor. +*/ +static void line6_destruct(struct usb_interface *interface) +{ + struct usb_line6 *line6; + if(interface == NULL) return; + line6 = usb_get_intfdata(interface); + if(line6 == NULL) return; + + /* free buffer memory first: */ + if(line6->buffer_message != NULL) kfree(line6->buffer_message); + if(line6->buffer_listen != NULL) kfree(line6->buffer_listen); + + /* then free URBs: */ + if(line6->urb_listen != NULL) usb_free_urb(line6->urb_listen); + + /* make sure the device isn't destructed twice: */ + usb_set_intfdata(interface, NULL); + + /* free interface data: */ + kfree(line6); +} + +static void line6_list_devices(void) +{ + int i; + + for(i = 0; i < LINE6_MAX_DEVICES; ++i) { + struct usb_line6 *dev = line6_devices[i]; + printk(KERN_INFO "Line6 device %d: ", i); + + if(dev == NULL) + printk("(not used)\n"); + else + printk("%s:%d\n", dev->properties->name, dev->interface_number); + } +} + +/* + Probe USB device. +*/ +static int line6_probe(struct usb_interface *interface, const struct usb_device_id *id) +{ + int devtype; + struct usb_device *usbdev = 0; + struct usb_line6 *line6 = 0; + const struct line6_properties *properties; + int devnum; + int interface_number, alternate = 0; + int product; + int size = 0; + int ep_read = 0, ep_write = 0; + int ret; + + if(interface == NULL) return -ENODEV; + usbdev = interface_to_usbdev(interface); + if(usbdev == NULL) return -ENODEV; + + /* increment reference counters: */ + usb_get_intf(interface); + usb_get_dev(usbdev); + + /* we don't handle multiple configurations */ + if(usbdev->descriptor.bNumConfigurations != 1) + return -ENODEV; + + /* check vendor and product id */ + for(devtype = sizeof(line6_id_table) / sizeof(line6_id_table[0]) - 1; devtype--;) + if((le16_to_cpu(usbdev->descriptor.idVendor) == line6_id_table[devtype].idVendor) && + (le16_to_cpu(usbdev->descriptor.idProduct) == line6_id_table[devtype].idProduct)) + break; + + if(devtype < 0) + return -ENODEV; + + /* find free slot in device table: */ + for(devnum = 0; devnum < LINE6_MAX_DEVICES; ++devnum) + if(line6_devices[devnum] == NULL) + break; + + if(devnum == LINE6_MAX_DEVICES) + return -ENODEV; + + /* initialize device info: */ + properties = &line6_properties_table[devtype]; + dev_info(&interface->dev, "Line6 %s found\n", properties->name); + product = le16_to_cpu(usbdev->descriptor.idProduct); + + /* query interface number */ + interface_number = interface->cur_altsetting->desc.bInterfaceNumber; + + switch(product) { + case LINE6_DEVID_BASSPODXTLIVE: + case LINE6_DEVID_POCKETPOD: + case LINE6_DEVID_PODXTLIVE: + case LINE6_DEVID_VARIAX: + alternate = 1; + break; + + case LINE6_DEVID_PODX3: + case LINE6_DEVID_PODX3LIVE: + switch(interface_number) { + case 0: alternate = 1; break; + case 1: alternate = 0; break; + default: MISSING_CASE; + } + break; + + case LINE6_DEVID_BASSPODXT: + case LINE6_DEVID_BASSPODXTPRO: + case LINE6_DEVID_PODXT: + case LINE6_DEVID_PODXTPRO: + alternate = 5; + break; + + case LINE6_DEVID_TONEPORT_GX: + case LINE6_DEVID_GUITARPORT: + alternate = 2; // 1..4 seem to be ok + break; + + case LINE6_DEVID_TONEPORT_UX1: + case LINE6_DEVID_TONEPORT_UX2: + switch(interface_number) { + case 0: alternate = 2; break; /* defaults to 44.1kHz, 16-bit */ + case 1: alternate = 0; break; + default: MISSING_CASE; + } + break; + + default: + MISSING_CASE; + return -ENODEV; + } + + if((ret = usb_set_interface(usbdev, interface_number, alternate)) < 0) { + dev_err(&interface->dev, "set_interface failed\n"); + return ret; + } + + /* initialize device data based on product id: */ + switch(product) { + case LINE6_DEVID_BASSPODXT: + case LINE6_DEVID_BASSPODXTLIVE: + case LINE6_DEVID_BASSPODXTPRO: + case LINE6_DEVID_POCKETPOD: + case LINE6_DEVID_PODXT: + case LINE6_DEVID_PODXTPRO: + size = sizeof(struct usb_line6_pod); + ep_read = 0x84; + ep_write = 0x03; + break; + + case LINE6_DEVID_PODX3: + case LINE6_DEVID_PODX3LIVE: + /* currently unused! */ + size = sizeof(struct usb_line6_pod); + ep_read = 0x81; + ep_write = 0x01; + break; + + case LINE6_DEVID_TONEPORT_GX: + case LINE6_DEVID_TONEPORT_UX1: + case LINE6_DEVID_TONEPORT_UX2: + case LINE6_DEVID_GUITARPORT: + size = sizeof(struct usb_line6_toneport); + /* these don't have a control channel */ + break; + + case LINE6_DEVID_PODXTLIVE: + switch(interface_number) { + case PODXTLIVE_INTERFACE_POD: + size = sizeof(struct usb_line6_pod); + ep_read = 0x84; + ep_write = 0x03; + break; + + case PODXTLIVE_INTERFACE_VARIAX: + size = sizeof(struct usb_line6_variax); + ep_read = 0x86; + ep_write = 0x05; + break; + + default: + return -ENODEV; + } + break; + + case LINE6_DEVID_VARIAX: + size = sizeof(struct usb_line6_variax); + ep_read = 0x82; + ep_write = 0x01; + break; + + default: + MISSING_CASE; + return -ENODEV; + } + + if(size == 0) { + dev_err(line6->ifcdev, "driver bug: interface data size not set\n"); + return -ENODEV; + } + + line6 = kzalloc(size, GFP_KERNEL); + + if(line6 == NULL) { + dev_err(&interface->dev, "Out of memory\n"); + return -ENOMEM; + } + + /* store basic data: */ + line6->interface_number = interface_number; + line6->properties = properties; + line6->usbdev = usbdev; + line6->ifcdev = &interface->dev; + line6->ep_control_read = ep_read; + line6->ep_control_write = ep_write; + line6->product = product; + + /* get data from endpoint descriptor (see usb_maxpacket): */ + { + struct usb_host_endpoint *ep; + unsigned epnum = usb_pipeendpoint(usb_rcvintpipe(usbdev, ep_read)); + ep = usbdev->ep_in[epnum]; + + if(ep != NULL) { + line6->interval = ep->desc.bInterval; + line6->max_packet_size = le16_to_cpu(ep->desc.wMaxPacketSize); + } + else { + line6->interval = LINE6_FALLBACK_INTERVAL; + line6->max_packet_size = LINE6_FALLBACK_MAXPACKETSIZE; + dev_err(line6->ifcdev, "endpoint not available, using fallback values"); + } + } + + usb_set_intfdata(interface, line6); + + if(properties->capabilities & LINE6_BIT_CONTROL) { + /* initialize USB buffers: */ + line6->buffer_listen = kmalloc(LINE6_BUFSIZE_LISTEN, GFP_KERNEL); + + if(line6->buffer_listen == NULL) { + dev_err(&interface->dev, "Out of memory\n"); + line6_destruct(interface); + return -ENOMEM; + } + + line6->buffer_message = kmalloc(LINE6_MESSAGE_MAXLEN, GFP_KERNEL); + + if(line6->buffer_message == NULL) { + dev_err(&interface->dev, "Out of memory\n"); + line6_destruct(interface); + return -ENOMEM; + } + + line6->urb_listen = usb_alloc_urb(0, GFP_KERNEL); + + if(line6->urb_listen == NULL) { + dev_err(&interface->dev, "Out of memory\n"); + line6_destruct(interface); + return -ENOMEM; + } + + if((ret = line6_start_listen(line6)) < 0) { + dev_err(&interface->dev, " line6_probe: usb_submit_urb failed\n"); + line6_destruct(interface); + return ret; + } + } + + /* initialize device data based on product id: */ + switch(product) { + case LINE6_DEVID_BASSPODXT: + case LINE6_DEVID_BASSPODXTLIVE: + case LINE6_DEVID_BASSPODXTPRO: + case LINE6_DEVID_POCKETPOD: + case LINE6_DEVID_PODX3: + case LINE6_DEVID_PODX3LIVE: + case LINE6_DEVID_PODXT: + case LINE6_DEVID_PODXTPRO: + ret = pod_init(interface, (struct usb_line6_pod *)line6); + break; + + case LINE6_DEVID_PODXTLIVE: + switch(interface_number) { + case PODXTLIVE_INTERFACE_POD: + ret = pod_init(interface, (struct usb_line6_pod *)line6); + break; + + case PODXTLIVE_INTERFACE_VARIAX: + ret = variax_init(interface, (struct usb_line6_variax *)line6); + break; + + default: + dev_err(&interface->dev, "PODxt Live interface %d not supported\n", interface_number); + ret = -ENODEV; + } + + break; + + case LINE6_DEVID_VARIAX: + ret = variax_init(interface, (struct usb_line6_variax *)line6); + break; + + case LINE6_DEVID_TONEPORT_GX: + case LINE6_DEVID_TONEPORT_UX1: + case LINE6_DEVID_TONEPORT_UX2: + case LINE6_DEVID_GUITARPORT: + ret = toneport_init(interface, (struct usb_line6_toneport *)line6); + break; + + default: + MISSING_CASE; + ret = -ENODEV; + } + + if(ret < 0) { + line6_destruct(interface); + return ret; + } + + if((ret = sysfs_create_link(&interface->dev.kobj, &usbdev->dev.kobj, "usb_device")) < 0) { + line6_destruct(interface); + return ret; + } + + dev_info(&interface->dev, "Line6 %s now attached\n", line6->properties->name); + line6_devices[devnum] = line6; + line6_list_devices(); + return ret; +} + +/* + Line6 device disconnected. +*/ +static void line6_disconnect(struct usb_interface *interface) +{ + struct usb_line6 *line6; + struct usb_device *usbdev; + int interface_number, i; + + if(interface == NULL) return; + usbdev = interface_to_usbdev(interface); + if(usbdev == NULL) return; + + sysfs_remove_link(&interface->dev.kobj, "usb_device"); + + interface_number = interface->cur_altsetting->desc.bInterfaceNumber; + line6 = usb_get_intfdata(interface); + + if(line6 != NULL) { + if(line6->urb_listen != NULL) usb_kill_urb(line6->urb_listen); + + if(usbdev != line6->usbdev) + dev_err(line6->ifcdev, "driver bug: inconsistent usb device\n"); + + switch(line6->usbdev->descriptor.idProduct) { + case LINE6_DEVID_BASSPODXT: + case LINE6_DEVID_BASSPODXTLIVE: + case LINE6_DEVID_BASSPODXTPRO: + case LINE6_DEVID_POCKETPOD: + case LINE6_DEVID_PODX3: + case LINE6_DEVID_PODX3LIVE: + case LINE6_DEVID_PODXT: + case LINE6_DEVID_PODXTPRO: + pod_disconnect(interface); + break; + + case LINE6_DEVID_PODXTLIVE: + switch(interface_number) { + case PODXTLIVE_INTERFACE_POD: + pod_disconnect(interface); + break; + + case PODXTLIVE_INTERFACE_VARIAX: + variax_disconnect(interface); + break; + } + + break; + + case LINE6_DEVID_VARIAX: + variax_disconnect(interface); + break; + + case LINE6_DEVID_TONEPORT_GX: + case LINE6_DEVID_TONEPORT_UX1: + case LINE6_DEVID_TONEPORT_UX2: + case LINE6_DEVID_GUITARPORT: + toneport_disconnect(interface); + break; + + default: + MISSING_CASE; + } + + dev_info(&interface->dev, "Line6 %s now disconnected\n", line6->properties->name); + + for(i = LINE6_MAX_DEVICES; i--;) + if(line6_devices[i] == line6) + line6_devices[i] = 0; + } + + line6_destruct(interface); + + /* decrement reference counters: */ + usb_put_intf(interface); + usb_put_dev(usbdev); + + line6_list_devices(); +} + +static struct usb_driver line6_driver = { +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 16) + .owner = THIS_MODULE, +#endif + .name = DRIVER_NAME, + .probe = line6_probe, + .disconnect = line6_disconnect, + .id_table = line6_id_table, +}; + +/* + Module initialization. +*/ +static int __init line6_init(void) +{ + int i, retval; + + printk("%s driver version %s%s\n", DRIVER_NAME, DRIVER_VERSION, DRIVER_REVISION); + line6_workqueue = create_workqueue(DRIVER_NAME); + + if(line6_workqueue == 0) { + err("couldn't create workqueue"); + return -EINVAL; + } + + for(i = LINE6_MAX_DEVICES; i--;) + line6_devices[i] = 0; + + retval = usb_register(&line6_driver); + + if(retval) + err("usb_register failed. Error number %d", retval); + + return retval; +} + +/* + Module cleanup. +*/ +static void __exit line6_exit(void) +{ + destroy_workqueue(line6_workqueue); + usb_deregister(&line6_driver); +} + +module_init(line6_init); +module_exit(line6_exit); + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); +MODULE_VERSION(DRIVER_VERSION); diff --git a/drivers/staging/line6/driver.h b/drivers/staging/line6/driver.h new file mode 100644 index 000000000000..e5179d99b9f6 --- /dev/null +++ b/drivers/staging/line6/driver.h @@ -0,0 +1,190 @@ +/* + * Line6 Linux USB driver - 0.8.0 + * + * Copyright (C) 2004-2009 Markus Grabner (grabner@icg.tugraz.at) + * + * 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, version 2. + * + */ + +#ifndef DRIVER_H +#define DRIVER_H + + +#include "config.h" + +#include <linux/spinlock.h> +#include <linux/usb.h> +#include <linux/wait.h> + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 25) +#include <sound/driver.h> +#endif + +#include <sound/core.h> + +#include "midi.h" + + +#define DRIVER_NAME "line6usb" + +#define LINE6_TIMEOUT 1 +#define LINE6_MAX_DEVICES 8 +#define LINE6_BUFSIZE_LISTEN 32 +#define LINE6_MESSAGE_MAXLEN 256 + + +/* + Line6 MIDI control commands +*/ +#define LINE6_PARAM_CHANGE 0xb0 +#define LINE6_PROGRAM_CHANGE 0xc0 +#define LINE6_SYSEX_BEGIN 0xf0 +#define LINE6_SYSEX_END 0xf7 +#define LINE6_RESET 0xff + +/* + MIDI channel for messages initiated by the host + (and eventually echoed back by the device) +*/ +#define LINE6_CHANNEL_HOST 0x00 + +/* + MIDI channel for messages initiated by the device +*/ +#define LINE6_CHANNEL_DEVICE 0x02 + +#define LINE6_CHANNEL_UNKNOWN 5 /* don't know yet what this is good for */ + +#define LINE6_CHANNEL_MASK 0x0f + + +#define MISSING_CASE printk("line6usb driver bug: missing case in %s:%d\n", __FILE__, __LINE__) + + +#define CHECK_RETURN(x) if((err = x) < 0) return err + + +extern const unsigned char line6_midi_id[3]; +extern struct usb_line6 *line6_devices[LINE6_MAX_DEVICES]; +extern struct workqueue_struct *line6_workqueue; + +static const int SYSEX_DATA_OFS = sizeof(line6_midi_id) + 3; +static const int SYSEX_EXTRA_SIZE = sizeof(line6_midi_id) + 4; + + +/** + Common properties of Line6 devices. +*/ +struct line6_properties { + const char *name; + int device_bit; + int capabilities; +}; + +/** + Common data shared by all Line6 devices. + Corresponds to a pair of USB endpoints. +*/ +struct usb_line6 { + /** + USB device. + */ + struct usb_device *usbdev; + + /** + Product id. + */ + int product; + + /** + Properties. + */ + const struct line6_properties *properties; + + /** + Interface number. + */ + int interface_number; + + /** + Interval (ms). + */ + int interval; + + /** + Maximum size of USB packet. + */ + int max_packet_size; + + /** + Device representing the USB interface. + */ + struct device *ifcdev; + + /** + Line6 sound card data structure. + Each device has at least MIDI or PCM. + */ + struct snd_card *card; + + /** + Line6 PCM device data structure. + */ + struct snd_line6_pcm *line6pcm; + + /** + Line6 MIDI device data structure. + */ + struct snd_line6_midi *line6midi; + + /** + USB endpoint for listening to control commands. + */ + int ep_control_read; + + /** + USB endpoint for writing control commands. + */ + int ep_control_write; + + /** + URB for listening to PODxt Pro control endpoint. + */ + struct urb *urb_listen; + + /** + Buffer for listening to PODxt Pro control endpoint. + */ + unsigned char *buffer_listen; + + /** + Buffer for message to be processed. + */ + unsigned char *buffer_message; + + /** + Length of message to be processed. + */ + int message_length; +}; + + +extern char *line6_alloc_sysex_buffer(struct usb_line6 *line6, int code1, int code2, int size); +extern ssize_t line6_nop_read(struct device *dev, DEVICE_ATTRIBUTE char *buf); +extern ssize_t line6_nop_write(struct device *dev, DEVICE_ATTRIBUTE const char *buf, size_t count); +extern int line6_read_data(struct usb_line6 *line6, int address, void *data, size_t datalen); +extern int line6_read_serial_number(struct usb_line6 *line6, int *serial_number); +extern int line6_send_program(struct usb_line6 *line6, int value); +extern int line6_send_raw_message(struct usb_line6 *line6, const char *buffer, int size); +extern int line6_send_raw_message_async(struct usb_line6 *line6, const char *buffer, int size); +extern int line6_send_sysex_message(struct usb_line6 *line6, const char *buffer, int size); +extern ssize_t line6_set_raw(struct device *dev, DEVICE_ATTRIBUTE const char *buf, size_t count); +extern int line6_transmit_parameter(struct usb_line6 *line6, int param, int value); +extern int line6_write_data(struct usb_line6 *line6, int address, void *data, size_t datalen); +extern void line6_write_hexdump(struct usb_line6 *line6, char dir, const unsigned char *buffer, int size); + + +#endif diff --git a/drivers/staging/line6/dumprequest.c b/drivers/staging/line6/dumprequest.c new file mode 100644 index 000000000000..89bc0994d07b --- /dev/null +++ b/drivers/staging/line6/dumprequest.c @@ -0,0 +1,143 @@ +/* + * Line6 Linux USB driver - 0.8.0 + * + * Copyright (C) 2004-2009 Markus Grabner (grabner@icg.tugraz.at) + * + * 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, version 2. + * + */ + +#include "driver.h" +#include "dumprequest.h" + + +/* + Set "dump in progress" flag. +*/ +void line6_dump_started(struct line6_dump_request *l6dr, int dest) +{ + l6dr->in_progress = dest; +} + +/* + Invalidate current channel, i.e., set "dump in progress" flag. + Reading from the "dump" special file blocks until dump is completed. +*/ +void line6_invalidate_current(struct line6_dump_request *l6dr) +{ + line6_dump_started(l6dr, LINE6_DUMP_CURRENT); +} + +/* + Clear "dump in progress" flag and notify waiting processes. +*/ +void line6_dump_finished(struct line6_dump_request *l6dr) +{ + l6dr->in_progress = LINE6_DUMP_NONE; + wake_up_interruptible(&l6dr->wait); +} + +/* + Send an asynchronous channel dump request. +*/ +int line6_dump_request_async(struct line6_dump_request *l6dr, struct usb_line6 *line6, int num) +{ + int ret; + line6_invalidate_current(l6dr); + ret = line6_send_raw_message_async(line6, l6dr->reqbufs[num].buffer, l6dr->reqbufs[num].length); + + if(ret < 0) + line6_dump_finished(l6dr); + + return ret; +} + +/* + Send an asynchronous dump request after a given interval. +*/ +void line6_startup_delayed(struct line6_dump_request *l6dr, int seconds, + void (*function)(unsigned long), void *data) +{ + l6dr->timer.expires = jiffies + seconds * HZ; + l6dr->timer.function = function; + l6dr->timer.data = (unsigned long)data; + add_timer(&l6dr->timer); +} + +/* + Wait for completion. +*/ +int line6_wait_dump(struct line6_dump_request *l6dr, int nonblock) +{ + int retval = 0; + DECLARE_WAITQUEUE(wait, current); + add_wait_queue(&l6dr->wait, &wait); + current->state = TASK_INTERRUPTIBLE; + + while(l6dr->in_progress) { + if(nonblock) { + retval = -EAGAIN; + break; + } + + if(signal_pending(current)) { + retval = -ERESTARTSYS; + break; + } + else + schedule(); + } + + current->state = TASK_RUNNING; + remove_wait_queue(&l6dr->wait, &wait); + return retval; +} + +/* + Initialize dump request buffer. +*/ +int line6_dumpreq_initbuf(struct line6_dump_request *l6dr, const void *buf, size_t len, int num) +{ + l6dr->reqbufs[num].buffer = kmalloc(len, GFP_KERNEL); + if(l6dr->reqbufs[num].buffer == NULL) return -ENOMEM; + memcpy(l6dr->reqbufs[num].buffer, buf, len); + l6dr->reqbufs[num].length = len; + return 0; +} + +/* + Initialize dump request data structure (including one buffer). +*/ +int line6_dumpreq_init(struct line6_dump_request *l6dr, const void *buf, size_t len) +{ + int ret; + ret = line6_dumpreq_initbuf(l6dr, buf, len, 0); + if(ret < 0) return ret; + init_waitqueue_head(&l6dr->wait); + init_timer(&l6dr->timer); + return 0; +} + +/* + Destruct dump request data structure. +*/ +void line6_dumpreq_destructbuf(struct line6_dump_request *l6dr, int num) +{ + if(l6dr == NULL) return; + if(l6dr->reqbufs[num].buffer == NULL) return; + kfree(l6dr->reqbufs[num].buffer); + l6dr->reqbufs[num].buffer = NULL; +} + +/* + Destruct dump request data structure. +*/ +void line6_dumpreq_destruct(struct line6_dump_request *l6dr) +{ + if(l6dr->reqbufs[0].buffer == NULL) return; + line6_dumpreq_destructbuf(l6dr, 0); + l6dr->ok = 1; + del_timer_sync(&l6dr->timer); +} diff --git a/drivers/staging/line6/dumprequest.h b/drivers/staging/line6/dumprequest.h new file mode 100644 index 000000000000..e0b38bbc1ad2 --- /dev/null +++ b/drivers/staging/line6/dumprequest.h @@ -0,0 +1,87 @@ +/* + * Line6 Linux USB driver - 0.8.0 + * + * Copyright (C) 2004-2009 Markus Grabner (grabner@icg.tugraz.at) + * + * 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, version 2. + * + */ + +#ifndef DUMPREQUEST_H +#define DUMPREQUEST_H + + +#include <linux/usb.h> +#include <linux/wait.h> + +#include <sound/core.h> + + +enum { + LINE6_DUMP_NONE, + LINE6_DUMP_CURRENT +}; + + +struct line6_dump_reqbuf { + /** + Buffer for dump requests. + */ + unsigned char *buffer; + + /** + Size of dump request. + */ + size_t length; +}; + +/** + Provides the functionality to request channel/model/... dump data from a + Line6 device. +*/ +struct line6_dump_request { + /** + Wait queue for access to program dump data. + */ + wait_queue_head_t wait; + + /** + Indicates an unfinished program dump request. + 0: no dump + 1: dump current settings + Other device-specific values are also allowed. + */ + int in_progress; + + /** + Timer for delayed dump request. + */ + struct timer_list timer; + + /** + Flag if initial dump request has been successful. + */ + char ok; + + /** + Dump request buffers + */ + struct line6_dump_reqbuf reqbufs[1]; +}; + +extern void line6_dump_finished(struct line6_dump_request *l6dr); +extern int line6_dump_request_async(struct line6_dump_request *l6dr, struct usb_line6 *line6, int num); +extern void line6_dump_started(struct line6_dump_request *l6dr, int dest); +extern void line6_dumpreq_destruct(struct line6_dump_request *l6dr); +extern void line6_dumpreq_destructbuf(struct line6_dump_request *l6dr, int num); +extern int line6_dumpreq_init(struct line6_dump_request *l6dr, const void *buf, size_t len); +extern int line6_dumpreq_initbuf(struct line6_dump_request *l6dr, const void *buf, size_t len, int num); +extern void line6_invalidate_current(struct line6_dump_request *l6dr); +extern void line6_startup_delayed(struct line6_dump_request *l6dr, int seconds, + void (*function)(unsigned long), void *data); +extern int line6_wait_dump(struct line6_dump_request *l6dr, int nonblock); + + +#endif diff --git a/drivers/staging/line6/midi.c b/drivers/staging/line6/midi.c new file mode 100644 index 000000000000..74489ee77bff --- /dev/null +++ b/drivers/staging/line6/midi.c @@ -0,0 +1,398 @@ +/* + * Line6 Linux USB driver - 0.8.0 + * + * Copyright (C) 2004-2009 Markus Grabner (grabner@icg.tugraz.at) + * + * 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, version 2. + * + */ + +#include "driver.h" + +#include <linux/usb.h> + +#include <sound/core.h> +#include <sound/rawmidi.h> + +#include "audio.h" +#include "midi.h" +#include "pod.h" +#include "usbdefs.h" + + +#define USE_MIDIBUF 1 +#define OUTPUT_DUMP_ONLY 0 + + +#define line6_rawmidi_substream_midi(substream) ((struct snd_line6_midi *)((substream)->rmidi->private_data)) + + +static int send_midi_async(struct usb_line6 *line6, unsigned char *data, int length); + + +/* + Pass data received via USB to MIDI. +*/ +void line6_midi_receive(struct usb_line6 *line6, unsigned char *data, int length) +{ + if(line6->line6midi->substream_receive) + snd_rawmidi_receive(line6->line6midi->substream_receive, data, length); +} + +/* + Read data from MIDI buffer and transmit them via USB. +*/ +static void line6_midi_transmit(struct snd_rawmidi_substream *substream) +{ + struct usb_line6 *line6 = line6_rawmidi_substream_midi(substream)->line6; + struct snd_line6_midi *line6midi = line6->line6midi; + struct MidiBuffer *mb = &line6midi->midibuf_out; + unsigned long flags; + unsigned char chunk[line6->max_packet_size]; + int req, done; + + spin_lock_irqsave(&line6->line6midi->midi_transmit_lock, flags); + + for(;;) { + req = min(midibuf_bytes_free(mb), line6->max_packet_size); + done = snd_rawmidi_transmit_peek(substream, chunk, req); + + if(done == 0) + break; + +#if DO_DUMP_MIDI_SEND + line6_write_hexdump(line6, 's', chunk, done); +#endif + midibuf_write(mb, chunk, done); + snd_rawmidi_transmit_ack(substream, done); + } + + for(;;) { + done = midibuf_read(mb, chunk, line6->max_packet_size); + + if(done == 0) + break; + + if(midibuf_skip_message(mb, line6midi->midi_mask_transmit)) + continue; + + send_midi_async(line6, chunk, done); + } + + spin_unlock_irqrestore(&line6->line6midi->midi_transmit_lock, flags); +} + +/* + Notification of completion of MIDI transmission. +*/ +static void midi_sent(struct urb *urb PT_REGS) +{ + unsigned long flags; + int status; + int num; + struct usb_line6 *line6 = (struct usb_line6 *)urb->context; + + status = urb->status; + kfree(urb->transfer_buffer); + usb_free_urb(urb); + + if(status == -ESHUTDOWN) + return; + + spin_lock_irqsave(&line6->line6midi->send_urb_lock, flags); + num = --line6->line6midi->num_active_send_urbs; + + if(num == 0) { + line6_midi_transmit(line6->line6midi->substream_transmit); + num = line6->line6midi->num_active_send_urbs; + } + + if(num == 0) + wake_up_interruptible(&line6->line6midi->send_wait); + + spin_unlock_irqrestore(&line6->line6midi->send_urb_lock, flags); +} + +/* + Send an asynchronous MIDI message. + Assumes that line6->line6midi->send_urb_lock is held + (i.e., this function is serialized). +*/ +static int send_midi_async(struct usb_line6 *line6, unsigned char *data, int length) +{ + struct urb *urb; + int retval; + unsigned char *transfer_buffer; + + urb = usb_alloc_urb(0, GFP_ATOMIC); + + if(urb == 0) { + dev_err(line6->ifcdev, "Out of memory\n"); + return -ENOMEM; + } + +#if DO_DUMP_URB_SEND + line6_write_hexdump(line6, 'S', data, length); +#endif + + transfer_buffer = (unsigned char *)kmalloc(length, GFP_ATOMIC); + + if(transfer_buffer == 0) { + usb_free_urb(urb); + dev_err(line6->ifcdev, "Out of memory\n"); + return -ENOMEM; + } + + memcpy(transfer_buffer, data, length); + usb_fill_int_urb(urb, + line6->usbdev, + usb_sndbulkpipe(line6->usbdev, line6->ep_control_write), + transfer_buffer, length, midi_sent, line6, line6->interval); + urb->actual_length = 0; + retval = usb_submit_urb(urb, GFP_ATOMIC); + + if(retval < 0) { + dev_err(line6->ifcdev, "usb_submit_urb failed\n"); + usb_free_urb(urb); + return -EINVAL; + } + + ++line6->line6midi->num_active_send_urbs; + + switch(line6->usbdev->descriptor.idProduct) { + case LINE6_DEVID_BASSPODXT: + case LINE6_DEVID_BASSPODXTLIVE: + case LINE6_DEVID_BASSPODXTPRO: + case LINE6_DEVID_PODXT: + case LINE6_DEVID_PODXTLIVE: + case LINE6_DEVID_PODXTPRO: + case LINE6_DEVID_POCKETPOD: + pod_midi_postprocess((struct usb_line6_pod *)line6, data, length); + break; + + default: + MISSING_CASE; + } + + return 0; +} + +static int line6_midi_output_open(struct snd_rawmidi_substream *substream) +{ + return 0; +} + +static int line6_midi_output_close(struct snd_rawmidi_substream *substream) +{ + return 0; +} + +static void line6_midi_output_trigger(struct snd_rawmidi_substream *substream, int up) +{ + unsigned long flags; + struct usb_line6 *line6 = line6_rawmidi_substream_midi(substream)->line6; + + line6->line6midi->substream_transmit = substream; + spin_lock_irqsave(&line6->line6midi->send_urb_lock, flags); + + if(line6->line6midi->num_active_send_urbs == 0) + line6_midi_transmit(substream); + + spin_unlock_irqrestore(&line6->line6midi->send_urb_lock, flags); +} + +static void line6_midi_output_drain(struct snd_rawmidi_substream *substream) +{ + struct usb_line6 *line6 = line6_rawmidi_substream_midi(substream)->line6; + wait_queue_head_t *head = &line6->line6midi->send_wait; + DECLARE_WAITQUEUE(wait, current); + add_wait_queue(head, &wait); + current->state = TASK_INTERRUPTIBLE; + + while(line6->line6midi->num_active_send_urbs > 0) + if(signal_pending(current)) + break; + else + schedule(); + + current->state = TASK_RUNNING; + remove_wait_queue(head, &wait); +} + +static int line6_midi_input_open(struct snd_rawmidi_substream *substream) +{ + return 0; +} + +static int line6_midi_input_close(struct snd_rawmidi_substream *substream) +{ + return 0; +} + +static void line6_midi_input_trigger(struct snd_rawmidi_substream *substream, int up) +{ + struct usb_line6 *line6 = line6_rawmidi_substream_midi(substream)->line6; + + if(up) + line6->line6midi->substream_receive = substream; + else + line6->line6midi->substream_receive = 0; +} + +static struct snd_rawmidi_ops line6_midi_output_ops = { + .open = line6_midi_output_open, + .close = line6_midi_output_close, + .trigger = line6_midi_output_trigger, + .drain = line6_midi_output_drain, +}; + +static struct snd_rawmidi_ops line6_midi_input_ops = { + .open = line6_midi_input_open, + .close = line6_midi_input_close, + .trigger = line6_midi_input_trigger, +}; + +/* + Cleanup the Line6 MIDI device. +*/ +static void line6_cleanup_midi(struct snd_rawmidi *rmidi) +{ +} + +/* Create a MIDI device */ +static int snd_line6_new_midi(struct snd_line6_midi *line6midi) +{ + struct snd_rawmidi *rmidi; + int err; + + if((err = snd_rawmidi_new(line6midi->line6->card, "Line6 MIDI", 0, 1, 1, &rmidi)) < 0) + return err; + + rmidi->private_data = line6midi; + rmidi->private_free = line6_cleanup_midi; + strcpy(rmidi->name, line6midi->line6->properties->name); + + rmidi->info_flags = + SNDRV_RAWMIDI_INFO_OUTPUT | + SNDRV_RAWMIDI_INFO_INPUT | + SNDRV_RAWMIDI_INFO_DUPLEX; + + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, &line6_midi_output_ops); + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT, &line6_midi_input_ops); + return 0; +} + +/* + "read" request on "midi_mask_transmit" special file. +*/ +static ssize_t midi_get_midi_mask_transmit(struct device *dev, DEVICE_ATTRIBUTE char *buf) +{ + struct usb_interface *interface = to_usb_interface(dev); + struct usb_line6 *line6 = usb_get_intfdata(interface); + return sprintf(buf, "%d\n", line6->line6midi->midi_mask_transmit); +} + +/* + "write" request on "midi_mask" special file. +*/ +static ssize_t midi_set_midi_mask_transmit(struct device *dev, DEVICE_ATTRIBUTE const char *buf, size_t count) +{ + struct usb_interface *interface = to_usb_interface(dev); + struct usb_line6 *line6 = usb_get_intfdata(interface); + int value = simple_strtoul(buf, NULL, 10); + line6->line6midi->midi_mask_transmit = value; + return count; +} + +/* + "read" request on "midi_mask_receive" special file. +*/ +static ssize_t midi_get_midi_mask_receive(struct device *dev, DEVICE_ATTRIBUTE char *buf) +{ + struct usb_interface *interface = to_usb_interface(dev); + struct usb_line6 *line6 = usb_get_intfdata(interface); + return sprintf(buf, "%d\n", line6->line6midi->midi_mask_receive); +} + +/* + "write" request on "midi_mask" special file. +*/ +static ssize_t midi_set_midi_mask_receive(struct device *dev, DEVICE_ATTRIBUTE const char *buf, size_t count) +{ + struct usb_interface *interface = to_usb_interface(dev); + struct usb_line6 *line6 = usb_get_intfdata(interface); + int value = simple_strtoul(buf, NULL, 10); + line6->line6midi->midi_mask_receive = value; + return count; +} + +static DEVICE_ATTR(midi_mask_transmit, S_IWUGO | S_IRUGO, midi_get_midi_mask_transmit, midi_set_midi_mask_transmit); +static DEVICE_ATTR(midi_mask_receive, S_IWUGO | S_IRUGO, midi_get_midi_mask_receive, midi_set_midi_mask_receive); + +/* MIDI device destructor */ +static int snd_line6_midi_free(struct snd_device *device) +{ + struct snd_line6_midi *line6midi = device->device_data; + device_remove_file(line6midi->line6->ifcdev, &dev_attr_midi_mask_transmit); + device_remove_file(line6midi->line6->ifcdev, &dev_attr_midi_mask_receive); + midibuf_destroy(&line6midi->midibuf_in); + midibuf_destroy(&line6midi->midibuf_out); + return 0; +} + +/* + Initialize the Line6 MIDI subsystem. +*/ +int line6_init_midi(struct usb_line6 *line6) +{ + static struct snd_device_ops midi_ops = { + .dev_free = snd_line6_midi_free, + }; + + int err; + struct snd_line6_midi *line6midi; + + if(!(line6->properties->capabilities & LINE6_BIT_CONTROL)) + return 0; /* skip MIDI initialization and report success */ + + line6midi = kzalloc(sizeof(struct snd_line6_midi), GFP_KERNEL); + + if(line6midi == NULL) + return -ENOMEM; + + err = midibuf_init(&line6midi->midibuf_in, MIDI_BUFFER_SIZE, 0); + + if(err < 0) + return err; + + err = midibuf_init(&line6midi->midibuf_out, MIDI_BUFFER_SIZE, 1); + + if(err < 0) + return err; + + line6midi->line6 = line6; + line6midi->midi_mask_transmit = 1; + line6midi->midi_mask_receive = 4; + line6->line6midi = line6midi; + + if((err = snd_device_new(line6->card, SNDRV_DEV_RAWMIDI, line6midi, &midi_ops)) < 0) + return err; + + snd_card_set_dev(line6->card, line6->ifcdev); + + if((err = snd_line6_new_midi(line6midi)) < 0) + return err; + + if((err = device_create_file(line6->ifcdev, &dev_attr_midi_mask_transmit)) < 0) + return err; + + if((err = device_create_file(line6->ifcdev, &dev_attr_midi_mask_receive)) < 0) + return err; + + init_waitqueue_head(&line6midi->send_wait); + spin_lock_init(&line6midi->send_urb_lock); + spin_lock_init(&line6midi->midi_transmit_lock); + return 0; +} diff --git a/drivers/staging/line6/midi.h b/drivers/staging/line6/midi.h new file mode 100644 index 000000000000..be05a54205c4 --- /dev/null +++ b/drivers/staging/line6/midi.h @@ -0,0 +1,87 @@ +/* + * Line6 Linux USB driver - 0.8.0 + * + * Copyright (C) 2004-2009 Markus Grabner (grabner@icg.tugraz.at) + * + * 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, version 2. + * + */ + +#ifndef MIDI_H +#define MIDI_H + + +#include <sound/rawmidi.h> + +#include "midibuf.h" + + +#define MIDI_BUFFER_SIZE 1024 + + +struct snd_line6_midi +{ + /** + Pointer back to the Line6 driver data structure. + */ + struct usb_line6 *line6; + + /** + MIDI substream for receiving (or NULL if not active). + */ + struct snd_rawmidi_substream *substream_receive; + + /** + MIDI substream for transmitting (or NULL if not active). + */ + struct snd_rawmidi_substream *substream_transmit; + + /** + Number of currently active MIDI send URBs. + */ + int num_active_send_urbs; + + /** + Spin lock to protect updates of send_urb. + */ + spinlock_t send_urb_lock; + + /** + Spin lock to protect MIDI buffer handling. + */ + spinlock_t midi_transmit_lock; + + /** + Wait queue for MIDI transmission. + */ + wait_queue_head_t send_wait; + + /** + Bit mask for output MIDI channels. + */ + int midi_mask_transmit; + + /** + Bit mask for input MIDI channels. + */ + int midi_mask_receive; + + /** + Buffer for incoming MIDI stream. + */ + struct MidiBuffer midibuf_in; + + /** + Buffer for outgoing MIDI stream. + */ + struct MidiBuffer midibuf_out; +}; + + +extern int line6_init_midi(struct usb_line6 *line6); +extern void line6_midi_receive(struct usb_line6 *line6, unsigned char *data, int length); + + +#endif diff --git a/drivers/staging/line6/midibuf.c b/drivers/staging/line6/midibuf.c new file mode 100644 index 000000000000..2f86c6692516 --- /dev/null +++ b/drivers/staging/line6/midibuf.c @@ -0,0 +1,268 @@ +/* + * Line6 Linux USB driver - 0.8.0 + * + * Copyright (C) 2004-2009 Markus Grabner (grabner@icg.tugraz.at) + * + * 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, version 2. + * + */ + +#include "config.h" + +#include <linux/slab.h> + +#include "midibuf.h" + + +int midibuf_message_length(unsigned char code) +{ + if(code < 0x80) + return -1; + else if(code < 0xf0) { + static const int length[] = { 3, 3, 3, 3, 2, 2, 3 }; + return length[(code >> 4) - 8]; + } + else { + /* + Note that according to the MIDI specification 0xf2 is the "Song Position + Pointer", but this is used by Line6 to send sysex messages to the host. + */ + static const int length[] = { -1, 2, -1, 2, -1, -1, 1, 1, 1, 1, 1, 1, 1, -1, 1, 1 }; + return length[code & 0x0f]; + } +} + +void midibuf_reset(struct MidiBuffer *this) +{ + this->pos_read = this->pos_write = this->full = 0; + this->command_prev = -1; +} + +int midibuf_init(struct MidiBuffer *this, int size, int split) +{ + this->buf = (unsigned char *)kmalloc(size, GFP_KERNEL); + + if(this->buf == 0) + return -ENOMEM; + + this->size = size; + this->split = split; + midibuf_reset(this); + return 0; +} + +void midibuf_status(struct MidiBuffer *this) +{ + printk("midibuf size=%d split=%d pos_read=%d pos_write=%d full=%d command_prev=%02x\n", + this->size, this->split, this->pos_read, this->pos_write, this->full, this->command_prev); +} + +int midibuf_is_empty(struct MidiBuffer *this) +{ + return (this->pos_read == this->pos_write) && !this->full; +} + +int midibuf_is_full(struct MidiBuffer *this) +{ + return this->full; +} + +int midibuf_bytes_free(struct MidiBuffer *this) +{ + return + midibuf_is_full(this) ? + 0 : + (this->pos_read - this->pos_write + this->size - 1) % this->size + 1; +} + +int midibuf_bytes_used(struct MidiBuffer *this) +{ + return + midibuf_is_empty(this) ? + 0 : + (this->pos_write - this->pos_read + this->size - 1) % this->size + 1; +} + +int midibuf_write(struct MidiBuffer *this, unsigned char *data, int length) +{ + int bytes_free; + int length1, length2; + int skip_active_sense = 0; + + if(midibuf_is_full(this) || (length <= 0)) + return 0; + + /* skip trailing active sense */ + if(data[length - 1] == 0xfe) { + --length; + skip_active_sense = 1; + } + + bytes_free = midibuf_bytes_free(this); + + if(length > bytes_free) + length = bytes_free; + + if(length > 0) { + length1 = this->size - this->pos_write; + + if(length < length1) { + /* no buffer wraparound */ + memcpy(this->buf + this->pos_write, data, length); + this->pos_write += length; + } + else { + /* buffer wraparound */ + length2 = length - length1; + memcpy(this->buf + this->pos_write, data, length1); + memcpy(this->buf, data + length1, length2); + this->pos_write = length2; + } + + if(this->pos_write == this->pos_read) + this->full = 1; + } + + return length + skip_active_sense; +} + +int midibuf_read(struct MidiBuffer *this, unsigned char *data, int length) +{ + int bytes_used; + int length1, length2; + int command; + int midi_length; + int repeat = 0; + int i; + + if(length < 3) + return -EINVAL; /* we need to be able to store at least a 3 byte MIDI message */ + + if(midibuf_is_empty(this)) + return 0; + + bytes_used = midibuf_bytes_used(this); + + if(length > bytes_used) + length = bytes_used; + + length1 = this->size - this->pos_read; + + /* check MIDI command length */ + command = this->buf[this->pos_read]; + + if(command & 0x80) { + midi_length = midibuf_message_length(command); + this->command_prev = command; + } + else { + if(this->command_prev > 0) { + int midi_length_prev = midibuf_message_length(this->command_prev); + + if(midi_length_prev > 0) { + midi_length = midi_length_prev - 1; + repeat = 1; + } + else + midi_length = -1; + } + else + midi_length = -1; + } + + if(midi_length < 0) { + /* search for end of message */ + if(length < length1) { + /* no buffer wraparound */ + for(i = 1; i < length; ++i) + if(this->buf[this->pos_read + i] & 0x80) + break; + + midi_length = i; + } + else { + /* buffer wraparound */ + length2 = length - length1; + + for(i = 1; i < length1; ++i) + if(this->buf[this->pos_read + i] & 0x80) + break; + + if(i < length1) + midi_length = i; + else { + for(i = 0; i < length2; ++i) + if(this->buf[i] & 0x80) + break; + + midi_length = length1 + i; + } + } + + if(midi_length == length) + midi_length = -1; /* end of message not found */ + } + + if(midi_length < 0) { + if(!this->split) + return 0; /* command is not yet complete */ + } + else { + if(length < midi_length) + return 0; /* command is not yet complete */ + + length = midi_length; + } + + if(length < length1) { + /* no buffer wraparound */ + memcpy(data + repeat, this->buf + this->pos_read, length); + this->pos_read += length; + } + else { + /* buffer wraparound */ + length2 = length - length1; + memcpy(data + repeat, this->buf + this->pos_read, length1); + memcpy(data + repeat + length1, this->buf, length2); + this->pos_read = length2; + } + + if(repeat) + data[0] = this->command_prev; + + this->full = 0; + return length + repeat; +} + +int midibuf_ignore(struct MidiBuffer *this, int length) +{ + int bytes_used = midibuf_bytes_used(this); + + if(length > bytes_used) + length = bytes_used; + + this->pos_read = (this->pos_read + length) % this->size; + this->full = 0; + return length; +} + +int midibuf_skip_message(struct MidiBuffer *this, unsigned short mask) +{ + int cmd = this->command_prev; + + if((cmd >= 0x80) && (cmd < 0xf0)) + if((mask & (1 << (cmd & 0x0f))) == 0) + return 1; + + return 0; +} + +void midibuf_destroy(struct MidiBuffer *this) +{ + if(this->buf != 0) { + kfree(this->buf); + this->buf = 0; + } +} diff --git a/drivers/staging/line6/midibuf.h b/drivers/staging/line6/midibuf.h new file mode 100644 index 000000000000..0e7762c677c4 --- /dev/null +++ b/drivers/staging/line6/midibuf.h @@ -0,0 +1,39 @@ +/* + * Line6 Linux USB driver - 0.8.0 + * + * Copyright (C) 2004-2009 Markus Grabner (grabner@icg.tugraz.at) + * + * 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, version 2. + * + */ + +#ifndef MIDIBUF_H +#define MIDIBUF_H + + +struct MidiBuffer +{ + unsigned char *buf; + int size; + int split; + int pos_read, pos_write; + int full; + int command_prev; +}; + + +extern int midibuf_bytes_used(struct MidiBuffer *mb); +extern int midibuf_bytes_free(struct MidiBuffer *mb); +extern void midibuf_destroy(struct MidiBuffer *mb); +extern int midibuf_ignore(struct MidiBuffer *mb, int length); +extern int midibuf_init(struct MidiBuffer *mb, int size, int split); +extern int midibuf_read(struct MidiBuffer *mb, unsigned char *data, int length); +extern void midibuf_reset(struct MidiBuffer *mb); +extern int midibuf_skip_message(struct MidiBuffer *mb, unsigned short mask); +extern void midibuf_status(struct MidiBuffer *mb); +extern int midibuf_write(struct MidiBuffer *mb, unsigned char *data, int length); + + +#endif diff --git a/drivers/staging/line6/pcm.c b/drivers/staging/line6/pcm.c new file mode 100644 index 000000000000..725184b2f308 --- /dev/null +++ b/drivers/staging/line6/pcm.c @@ -0,0 +1,289 @@ +/* + * Line6 Linux USB driver - 0.8.0 + * + * Copyright (C) 2004-2009 Markus Grabner (grabner@icg.tugraz.at) + * + * 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, version 2. + * + */ + +#include "driver.h" + +#include <sound/core.h> +#include <sound/control.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> + +#include "audio.h" +#include "capture.h" +#include "playback.h" +#include "pod.h" + + +/* trigger callback */ +int snd_line6_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_line6_pcm *line6pcm = snd_pcm_substream_chip(substream); +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 22) + struct list_head *pos; +#endif + struct snd_pcm_substream *s; + int err; + unsigned long flags; + + spin_lock_irqsave(&line6pcm->lock_trigger, flags); + clear_bit(BIT_PREPARED, &line6pcm->flags); + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 22) + snd_pcm_group_for_each(pos, substream) { + s = snd_pcm_group_substream_entry(pos); +#else + snd_pcm_group_for_each_entry(s, substream) { +#endif + switch(s->stream) { + case SNDRV_PCM_STREAM_PLAYBACK: + err = snd_line6_playback_trigger(s, cmd); + + if(err < 0) { + spin_unlock_irqrestore(&line6pcm->lock_trigger, flags); + return err; + } + + break; + + case SNDRV_PCM_STREAM_CAPTURE: + err = snd_line6_capture_trigger(s, cmd); + + if(err < 0) { + spin_unlock_irqrestore(&line6pcm->lock_trigger, flags); + return err; + } + + break; + + default: + dev_err(s2m(substream), "Unknown stream direction %d\n", s->stream); + } + } + + spin_unlock_irqrestore(&line6pcm->lock_trigger, flags); + return 0; +} + +/* control info callback */ +static int snd_line6_control_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) { + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 2; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 256; + return 0; +} + +/* control get callback */ +static int snd_line6_control_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { + int i; + struct snd_line6_pcm *line6pcm = snd_kcontrol_chip(kcontrol); + + for(i = 2; i--;) + ucontrol->value.integer.value[i] = line6pcm->volume[i]; + + return 0; +} + +/* control put callback */ +static int snd_line6_control_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { + int i, changed = 0; + struct snd_line6_pcm *line6pcm = snd_kcontrol_chip(kcontrol); + + for(i = 2; i--;) + if(line6pcm->volume[i] != ucontrol->value.integer.value[i]) { + line6pcm->volume[i] = ucontrol->value.integer.value[i]; + changed = 1; + } + + return changed; +} + +/* control definition */ +static struct snd_kcontrol_new line6_control = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "PCM Playback Volume", + .index = 0, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .info = snd_line6_control_info, + .get = snd_line6_control_get, + .put = snd_line6_control_put +}; + +/* + Cleanup the PCM device. +*/ +static void line6_cleanup_pcm(struct snd_pcm *pcm) +{ + int i; + struct snd_line6_pcm *line6pcm = snd_pcm_chip(pcm); + + for(i = LINE6_ISO_BUFFERS; i--;) { + if(line6pcm->urb_audio_out[i]) { + usb_kill_urb(line6pcm->urb_audio_out[i]); + usb_free_urb(line6pcm->urb_audio_out[i]); + } + if(line6pcm->urb_audio_in[i]) { + usb_kill_urb(line6pcm->urb_audio_in[i]); + usb_free_urb(line6pcm->urb_audio_in[i]); + } + } +} + +/* create a PCM device */ +static int snd_line6_new_pcm(struct snd_line6_pcm *line6pcm) +{ + struct snd_pcm *pcm; + int err; + + if((err = snd_pcm_new(line6pcm->line6->card, (char *)line6pcm->line6->properties->name, 0, 1, 1, &pcm)) < 0) + return err; + + pcm->private_data = line6pcm; + pcm->private_free = line6_cleanup_pcm; + line6pcm->pcm = pcm; + strcpy(pcm->name, line6pcm->line6->properties->name); + + /* set operators */ + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_line6_playback_ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_line6_capture_ops); + + /* pre-allocation of buffers */ + snd_pcm_lib_preallocate_pages_for_all(pcm, + SNDRV_DMA_TYPE_CONTINUOUS, + snd_dma_continuous_data(GFP_KERNEL), + 64 * 1024, 128 * 1024); + + return 0; +} + +/* PCM device destructor */ +static int snd_line6_pcm_free(struct snd_device *device) +{ + return 0; +} + +/* + Create and register the PCM device and mixer entries. + Create URBs for playback and capture. +*/ +int line6_init_pcm(struct usb_line6 *line6, struct line6_pcm_properties *properties) +{ + static struct snd_device_ops pcm_ops = { + .dev_free = snd_line6_pcm_free, + }; + + int err; + int ep_read = 0, ep_write = 0; + struct snd_line6_pcm *line6pcm; + + if(!(line6->properties->capabilities & LINE6_BIT_PCM)) + return 0; /* skip PCM initialization and report success */ + + /* initialize PCM subsystem based on product id: */ + switch(line6->product) { + case LINE6_DEVID_BASSPODXT: + case LINE6_DEVID_BASSPODXTLIVE: + case LINE6_DEVID_BASSPODXTPRO: + case LINE6_DEVID_PODXT: + case LINE6_DEVID_PODXTLIVE: + case LINE6_DEVID_PODXTPRO: + ep_read = 0x82; + ep_write = 0x01; + break; + + case LINE6_DEVID_PODX3: + case LINE6_DEVID_PODX3LIVE: + ep_read = 0x86; + ep_write = 0x02; + break; + + case LINE6_DEVID_POCKETPOD: + ep_read = 0x82; + ep_write = 0x02; + break; + + case LINE6_DEVID_GUITARPORT: + case LINE6_DEVID_TONEPORT_GX: + ep_read = 0x82; + ep_write = 0x01; + break; + + case LINE6_DEVID_TONEPORT_UX1: + ep_read = 0x00; + ep_write = 0x00; + break; + + case LINE6_DEVID_TONEPORT_UX2: + ep_read = 0x87; + ep_write = 0x00; + break; + + default: + MISSING_CASE; + } + + line6pcm = kzalloc(sizeof(struct snd_line6_pcm), GFP_KERNEL); + + if(line6pcm == NULL) + return -ENOMEM; + + line6pcm->volume[0] = line6pcm->volume[1] = 128; + line6pcm->line6 = line6; + line6pcm->ep_audio_read = ep_read; + line6pcm->ep_audio_write = ep_write; + line6pcm->max_packet_size = usb_maxpacket(line6->usbdev, usb_rcvintpipe(line6->usbdev, ep_read), 0); + line6pcm->properties = properties; + line6->line6pcm = line6pcm; + + /* PCM device: */ + if((err = snd_device_new(line6->card, SNDRV_DEV_PCM, line6, &pcm_ops)) < 0) + return err; + + snd_card_set_dev(line6->card, line6->ifcdev); + + if((err = snd_line6_new_pcm(line6pcm)) < 0) + return err; + + spin_lock_init(&line6pcm->lock_audio_out); + spin_lock_init(&line6pcm->lock_audio_in); + spin_lock_init(&line6pcm->lock_trigger); + + if((err = create_audio_out_urbs(line6pcm)) < 0) + return err; + + if((err = create_audio_in_urbs(line6pcm)) < 0) + return err; + + /* mixer: */ + if((err = snd_ctl_add(line6->card, snd_ctl_new1(&line6_control, line6pcm))) < 0) + return err; + + return 0; +} + +/* prepare pcm callback */ +int snd_line6_prepare(struct snd_pcm_substream *substream) +{ + struct snd_line6_pcm *line6pcm = snd_pcm_substream_chip(substream); + + if(!test_and_set_bit(BIT_PREPARED, &line6pcm->flags)) { + unlink_wait_clear_audio_out_urbs(line6pcm); + line6pcm->pos_out = 0; + line6pcm->pos_out_done = 0; + + unlink_wait_clear_audio_in_urbs(line6pcm); + line6pcm->bytes_out = 0; + line6pcm->pos_in_done = 0; + line6pcm->bytes_in = 0; + } + + return 0; +} diff --git a/drivers/staging/line6/pcm.h b/drivers/staging/line6/pcm.h new file mode 100644 index 000000000000..90f8bb9816d5 --- /dev/null +++ b/drivers/staging/line6/pcm.h @@ -0,0 +1,210 @@ +/* + * Line6 Linux USB driver - 0.8.0 + * + * Copyright (C) 2004-2009 Markus Grabner (grabner@icg.tugraz.at) + * + * 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, version 2. + * + */ + +/* + PCM interface to POD series devices. +*/ + +#ifndef PCM_H +#define PCM_H + + +#include <sound/pcm.h> + +#include "driver.h" +#include "usbdefs.h" + + +#define LINE6_ISO_BUFFERS 8 /* number of URBs */ +#define LINE6_ISO_PACKETS 2 /* number of USB frames per URB */ +#define LINE6_ISO_INTERVAL 1 /* in a "full speed" device (such as the PODxt Pro) this means 1ms */ +#define LINE6_ISO_PACKET_SIZE_MAX 252 /* this should be queried dynamically from the USB interface! */ + + +/* + Extract the messaging device from the substream instance +*/ +#define s2m(s) (((struct snd_line6_pcm *)snd_pcm_substream_chip(s))->line6->ifcdev) + + +enum { + BIT_RUNNING_PLAYBACK, + BIT_RUNNING_CAPTURE, + BIT_PAUSE_PLAYBACK, + BIT_PREPARED +}; + +struct line6_pcm_properties { + struct snd_pcm_hardware snd_line6_playback_hw, snd_line6_capture_hw; + struct snd_pcm_hw_constraint_ratdens snd_line6_rates; + int bytes_per_frame; +}; + +struct snd_line6_pcm +{ + /** + Pointer back to the Line6 driver data structure. + */ + struct usb_line6 *line6; + + /** + Properties. + */ + struct line6_pcm_properties *properties; + + /** + ALSA pcm stream + */ + struct snd_pcm *pcm; + + /** + URBs for audio playback. + */ + struct urb *urb_audio_out[LINE6_ISO_BUFFERS]; + + /** + URBs for audio capture. + */ + struct urb *urb_audio_in[LINE6_ISO_BUFFERS]; + + /** + Temporary buffer to hold data when playback buffer wraps. + */ + unsigned char *wrap_out; + + /** + Temporary buffer for capture. + Since the packet size is not known in advance, this buffer is large enough + to store maximum size packets. + */ + unsigned char *buffer_in; + + /** + Free frame position in the playback buffer. + */ + snd_pcm_uframes_t pos_out; + + /** + Count processed bytes for playback. + This is modulo period size (to determine when a period is finished). + */ + unsigned bytes_out; + + /** + Counter to create desired playback sample rate. + */ + unsigned count_out; + + /** + Playback period size in bytes + */ + unsigned period_out; + + /** + Processed frame position in the playback buffer. + The contents of the output ring buffer have been consumed by the USB + subsystem (i.e., sent to the USB device) up to this position. + */ + snd_pcm_uframes_t pos_out_done; + + /** + Count processed bytes for capture. + This is modulo period size (to determine when a period is finished). + */ + unsigned bytes_in; + + /** + Counter to create desired capture sample rate. + */ + unsigned count_in; + + /** + Capture period size in bytes + */ + unsigned period_in; + + /** + Processed frame position in the capture buffer. + The contents of the output ring buffer have been consumed by the USB + subsystem (i.e., sent to the USB device) up to this position. + */ + snd_pcm_uframes_t pos_in_done; + + /** + Bit mask of active playback URBs. + */ + unsigned long active_urb_out; + + /** + Maximum size of USB packet. + */ + int max_packet_size; + + /** + USB endpoint for listening to audio data. + */ + int ep_audio_read; + + /** + USB endpoint for writing audio data. + */ + int ep_audio_write; + + /** + Bit mask of active capture URBs. + */ + unsigned long active_urb_in; + + /** + Bit mask of playback URBs currently being unlinked. + */ + unsigned long unlink_urb_out; + + /** + Bit mask of capture URBs currently being unlinked. + */ + unsigned long unlink_urb_in; + + /** + Spin lock to protect updates of the playback buffer positions (not + contents!) + */ + spinlock_t lock_audio_out; + + /** + Spin lock to protect updates of the capture buffer positions (not + contents!) + */ + spinlock_t lock_audio_in; + + /** + Spin lock to protect trigger. + */ + spinlock_t lock_trigger; + + /** + PCM playback volume (left and right). + */ + int volume[2]; + + /** + Several status bits (see BIT_*). + */ + unsigned long flags; +}; + + +extern int line6_init_pcm(struct usb_line6 *line6, struct line6_pcm_properties *properties); +extern int snd_line6_trigger(struct snd_pcm_substream *substream, int cmd); +extern int snd_line6_prepare(struct snd_pcm_substream *substream); + + +#endif diff --git a/drivers/staging/line6/playback.c b/drivers/staging/line6/playback.c new file mode 100644 index 000000000000..f6503c23bd08 --- /dev/null +++ b/drivers/staging/line6/playback.c @@ -0,0 +1,428 @@ +/* + * Line6 Linux USB driver - 0.8.0 + * + * Copyright (C) 2004-2009 Markus Grabner (grabner@icg.tugraz.at) + * + * 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, version 2. + * + */ + +#include "driver.h" + +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> + +#include "audio.h" +#include "pcm.h" +#include "pod.h" + + +/* + Software stereo volume control. +*/ +static void change_volume(struct urb *urb_out, int volume[], int bytes_per_frame) +{ + int chn = 0; + + if(volume[0] == 256 && volume[1] == 256) + return; /* maximum volume - no change */ + + if(bytes_per_frame == 4) { + short *p, *buf_end; + p = (short *)urb_out->transfer_buffer; + buf_end = p + urb_out->transfer_buffer_length / sizeof(*p); + + for(; p < buf_end; ++p) { + *p = (*p * volume[chn & 1]) >> 8; + ++chn; + } + } + else if(bytes_per_frame == 6) { + unsigned char *p, *buf_end; + p = (unsigned char *)urb_out->transfer_buffer; + buf_end = p + urb_out->transfer_buffer_length; + + for(; p < buf_end; p += 3) { + int val = p[0] + (p[1] << 8) + ((signed char)p[2] << 16); + val = (val * volume[chn & 1]) >> 8; + p[0] = val; + p[1] = val >> 8; + p[2] = val >> 16; + ++chn; + } + } +} + +/* + Find a free URB, prepare audio data, and submit URB. +*/ +static int submit_audio_out_urb(struct snd_pcm_substream *substream) +{ + int index; + unsigned long flags; + int i, urb_size, urb_frames; + struct snd_line6_pcm *line6pcm = snd_pcm_substream_chip(substream); + const int bytes_per_frame = line6pcm->properties->bytes_per_frame; + const int frame_increment = line6pcm->properties->snd_line6_rates.rats[0].num_min; + const int frame_factor = line6pcm->properties->snd_line6_rates.rats[0].den * (USB_INTERVALS_PER_SECOND / LINE6_ISO_INTERVAL); + struct snd_pcm_runtime *runtime = substream->runtime; + struct urb *urb_out; + + spin_lock_irqsave(&line6pcm->lock_audio_out, flags); + index = find_first_zero_bit(&line6pcm->active_urb_out, LINE6_ISO_BUFFERS); + + if(index < 0 || index >= LINE6_ISO_BUFFERS) { + spin_unlock_irqrestore(&line6pcm->lock_audio_out, flags); + dev_err(s2m(substream), "no free URB found\n"); + return -EINVAL; + } + + urb_out = line6pcm->urb_audio_out[index]; + urb_size = 0; + + for(i = 0; i < LINE6_ISO_PACKETS; ++i) { + /* compute frame size for given sampling rate */ + int n, fs; + struct usb_iso_packet_descriptor *fout = &urb_out->iso_frame_desc[i]; + line6pcm->count_out += frame_increment; + n = line6pcm->count_out / frame_factor; + line6pcm->count_out -= n * frame_factor; + fs = n * bytes_per_frame; + fout->offset = urb_size; + fout->length = fs; + urb_size += fs; + } + + urb_frames = urb_size / bytes_per_frame; + + if(test_bit(BIT_PAUSE_PLAYBACK, &line6pcm->flags)) { + urb_out->transfer_buffer = line6pcm->wrap_out; + memset(line6pcm->wrap_out, 0, urb_size); + } + else { + if(line6pcm->pos_out + urb_frames > runtime->buffer_size) { + /* + The transferred area goes over buffer boundary, + copy the data to the temp buffer. + */ + int len; + len = runtime->buffer_size - line6pcm->pos_out; + urb_out->transfer_buffer = line6pcm->wrap_out; + + if(len > 0) { + memcpy(line6pcm->wrap_out, runtime->dma_area + line6pcm->pos_out * bytes_per_frame, len * bytes_per_frame); + memcpy(line6pcm->wrap_out + len * bytes_per_frame, runtime->dma_area, (urb_frames - len) * bytes_per_frame); + } + else + dev_err(s2m(substream), "driver bug: len = %d\n", len); /* this is somewhat paranoid */ + } + else { + /* set the buffer pointer */ + urb_out->transfer_buffer = runtime->dma_area + line6pcm->pos_out * bytes_per_frame; + } + } + + if((line6pcm->pos_out += urb_frames) >= runtime->buffer_size) + line6pcm->pos_out -= runtime->buffer_size; + + urb_out->transfer_buffer_length = urb_size; + urb_out->context = substream; + change_volume(urb_out, line6pcm->volume, bytes_per_frame); + +#if DO_DUMP_PCM_SEND + for(i = 0; i < LINE6_ISO_PACKETS; ++i) { + struct usb_iso_packet_descriptor *fout = &urb_out->iso_frame_desc[i]; + line6_write_hexdump(line6pcm->line6, 'P', urb_out->transfer_buffer + fout->offset, fout->length); + } +#endif + + if(usb_submit_urb(urb_out, GFP_ATOMIC) == 0) + set_bit(index, &line6pcm->active_urb_out); + else + dev_err(s2m(substream), "URB out #%d submission failed\n", index); + + spin_unlock_irqrestore(&line6pcm->lock_audio_out, flags); + return 0; +} + +/* + Submit all currently available playback URBs. +*/ +static int submit_audio_out_all_urbs(struct snd_pcm_substream *substream) +{ + int ret, i; + + for(i = 0; i < LINE6_ISO_BUFFERS; ++i) + if((ret = submit_audio_out_urb(substream)) < 0) + return ret; + + return 0; +} + +/* + Unlink all currently active playback URBs. +*/ +static void unlink_audio_out_urbs(struct snd_line6_pcm *line6pcm) +{ + unsigned int i; + + for(i = LINE6_ISO_BUFFERS; i--;) { + if(test_bit(i, &line6pcm->active_urb_out)) { + if(!test_and_set_bit(i, &line6pcm->unlink_urb_out)) { + struct urb *u = line6pcm->urb_audio_out[i]; +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 14) + u->transfer_flags |= URB_ASYNC_UNLINK; +#endif + usb_unlink_urb(u); + } + } + } +} + +/* + Wait until unlinking of all currently active playback URBs has been finished. +*/ +static void wait_clear_audio_out_urbs(struct snd_line6_pcm *line6pcm) +{ + int timeout = HZ; + unsigned int i; + int alive; + + do { + alive = 0; + for (i = LINE6_ISO_BUFFERS; i--;) { + if (test_bit(i, &line6pcm->active_urb_out)) + alive++; + } + if (! alive) + break; + set_current_state(TASK_UNINTERRUPTIBLE); + schedule_timeout(1); + } while (--timeout > 0); + if (alive) + snd_printk(KERN_ERR "timeout: still %d active urbs..\n", alive); + + line6pcm->active_urb_out = 0; + line6pcm->unlink_urb_out = 0; +} + +/* + Unlink all currently active playback URBs, and wait for finishing. +*/ +void unlink_wait_clear_audio_out_urbs(struct snd_line6_pcm *line6pcm) +{ + unlink_audio_out_urbs(line6pcm); + wait_clear_audio_out_urbs(line6pcm); +} + +/* + Callback for completed playback URB. +*/ +static void audio_out_callback(struct urb *urb PT_REGS) +{ + int i, index, length = 0, shutdown = 0; + unsigned long flags; + + struct snd_pcm_substream *substream = (struct snd_pcm_substream *)urb->context; + struct snd_line6_pcm *line6pcm = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + + /* find index of URB */ + for(index = LINE6_ISO_BUFFERS; index--;) + if(urb == line6pcm->urb_audio_out[index]) + break; + + if(index < 0) + return; /* URB has been unlinked asynchronously */ + + for(i = LINE6_ISO_PACKETS; i--;) + length += urb->iso_frame_desc[i].length; + + spin_lock_irqsave(&line6pcm->lock_audio_out, flags); + line6pcm->pos_out_done += length / line6pcm->properties->bytes_per_frame; + + if(line6pcm->pos_out_done >= runtime->buffer_size) + line6pcm->pos_out_done -= runtime->buffer_size; + + clear_bit(index, &line6pcm->active_urb_out); + + for(i = LINE6_ISO_PACKETS; i--;) + if(urb->iso_frame_desc[i].status == -ESHUTDOWN) { + shutdown = 1; + break; + } + + if(test_bit(index, &line6pcm->unlink_urb_out)) + shutdown = 1; + + spin_unlock_irqrestore(&line6pcm->lock_audio_out, flags); + + if(!shutdown) { + submit_audio_out_urb(substream); + + if((line6pcm->bytes_out += length) >= line6pcm->period_out) { + line6pcm->bytes_out -= line6pcm->period_out; + snd_pcm_period_elapsed(substream); + } + } +} + +/* open playback callback */ +static int snd_line6_playback_open(struct snd_pcm_substream *substream) +{ + int err; + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_line6_pcm *line6pcm = snd_pcm_substream_chip(substream); + + if((err = snd_pcm_hw_constraint_ratdens(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + (&line6pcm->properties->snd_line6_rates))) < 0) + return err; + + runtime->hw = line6pcm->properties->snd_line6_playback_hw; + return 0; +} + +/* close playback callback */ +static int snd_line6_playback_close(struct snd_pcm_substream *substream) +{ + return 0; +} + +/* hw_params playback callback */ +static int snd_line6_playback_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *hw_params) +{ + int ret; + struct snd_line6_pcm *line6pcm = snd_pcm_substream_chip(substream); + + /* -- Florian Demski [FD] */ + /* don't ask me why, but this fixes the bug on my machine */ + if ( line6pcm == NULL ) { + if ( substream->pcm == NULL ) + return -ENOMEM; + if ( substream->pcm->private_data == NULL ) + return -ENOMEM; + substream->private_data = substream->pcm->private_data; + line6pcm = snd_pcm_substream_chip( substream ); + } + /* -- [FD] end */ + + if((ret = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params))) < 0) + return ret; + + line6pcm->period_out = params_period_bytes(hw_params); + line6pcm->wrap_out = kmalloc(2 * LINE6_ISO_PACKET_SIZE_MAX, GFP_KERNEL); + + if(!line6pcm->wrap_out) { + dev_err(s2m(substream), "cannot malloc wrap_out\n"); + return -ENOMEM; + } + + return 0; +} + +/* hw_free playback callback */ +static int snd_line6_playback_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_line6_pcm *line6pcm = snd_pcm_substream_chip(substream); + unlink_wait_clear_audio_out_urbs(line6pcm); + + if(line6pcm->wrap_out) { + kfree(line6pcm->wrap_out); + line6pcm->wrap_out = 0; + } + + return snd_pcm_lib_free_pages(substream); +} + +/* trigger playback callback */ +int snd_line6_playback_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_line6_pcm *line6pcm = snd_pcm_substream_chip(substream); + int err; + line6pcm->count_out = 0; + + switch(cmd) { + case SNDRV_PCM_TRIGGER_START: + if(!test_and_set_bit(BIT_RUNNING_PLAYBACK, &line6pcm->flags)) { + err = submit_audio_out_all_urbs(substream); + + if(err < 0) { + clear_bit(BIT_RUNNING_PLAYBACK, &line6pcm->flags); + return err; + } + } + + break; + + case SNDRV_PCM_TRIGGER_STOP: + if(test_and_clear_bit(BIT_RUNNING_PLAYBACK, &line6pcm->flags)) + unlink_audio_out_urbs(line6pcm); + + break; + + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + set_bit(BIT_PAUSE_PLAYBACK, &line6pcm->flags); + break; + + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + clear_bit(BIT_PAUSE_PLAYBACK, &line6pcm->flags); + break; + + default: + return -EINVAL; + } + + return 0; +} + +/* playback pointer callback */ +static snd_pcm_uframes_t +snd_line6_playback_pointer(struct snd_pcm_substream *substream) +{ + struct snd_line6_pcm *line6pcm = snd_pcm_substream_chip(substream); + return line6pcm->pos_out_done; +} + +/* playback operators */ +struct snd_pcm_ops snd_line6_playback_ops = { + .open = snd_line6_playback_open, + .close = snd_line6_playback_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_line6_playback_hw_params, + .hw_free = snd_line6_playback_hw_free, + .prepare = snd_line6_prepare, + .trigger = snd_line6_trigger, + .pointer = snd_line6_playback_pointer, +}; + +int create_audio_out_urbs(struct snd_line6_pcm *line6pcm) +{ + int i; + + /* create audio URBs and fill in constant values: */ + for(i = 0; i < LINE6_ISO_BUFFERS; ++i) { + struct urb *urb; + + /* URB for audio out: */ + urb = line6pcm->urb_audio_out[i] = usb_alloc_urb(LINE6_ISO_PACKETS, GFP_KERNEL); + + if(urb == NULL) { + dev_err(line6pcm->line6->ifcdev, "Out of memory\n"); + return -ENOMEM; + } + + urb->dev = line6pcm->line6->usbdev; + urb->pipe = usb_sndisocpipe(line6pcm->line6->usbdev, line6pcm->ep_audio_write & USB_ENDPOINT_NUMBER_MASK); + urb->transfer_flags = URB_ISO_ASAP; + urb->start_frame = -1; + urb->number_of_packets = LINE6_ISO_PACKETS; + urb->interval = LINE6_ISO_INTERVAL; + urb->error_count = 0; + urb->complete = audio_out_callback; + } + + return 0; +} diff --git a/drivers/staging/line6/playback.h b/drivers/staging/line6/playback.h new file mode 100644 index 000000000000..019c40f2cdb4 --- /dev/null +++ b/drivers/staging/line6/playback.h @@ -0,0 +1,29 @@ +/* + * Line6 Linux USB driver - 0.8.0 + * + * Copyright (C) 2004-2009 Markus Grabner (grabner@icg.tugraz.at) + * + * 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, version 2. + * + */ + +#ifndef PLAYBACK_H +#define PLAYBACK_H + + +#include "driver.h" + +#include <sound/pcm.h> + + +extern struct snd_pcm_ops snd_line6_playback_ops; + + +extern int create_audio_out_urbs(struct snd_line6_pcm *line6pcm); +extern int snd_line6_playback_trigger(struct snd_pcm_substream *substream, int cmd); +extern void unlink_wait_clear_audio_out_urbs(struct snd_line6_pcm *line6pcm); + + +#endif diff --git a/drivers/staging/line6/pod.c b/drivers/staging/line6/pod.c new file mode 100644 index 000000000000..154985aeb698 --- /dev/null +++ b/drivers/staging/line6/pod.c @@ -0,0 +1,1100 @@ +/* + * Line6 Linux USB driver - 0.8.0 + * + * Copyright (C) 2004-2009 Markus Grabner (grabner@icg.tugraz.at) + * + * 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, version 2. + * + */ + +#include "driver.h" + +#include "audio.h" +#include "capture.h" +#include "control.h" +#include "playback.h" +#include "pod.h" + + +#define POD_SYSEX_CODE 3 +#define POD_BYTES_PER_FRAME 6 /* 24bit audio (stereo) */ + + +enum { + POD_SYSEX_CLIP = 0x0f, + POD_SYSEX_SAVE = 0x24, + POD_SYSEX_SYSTEM = 0x56, + POD_SYSEX_SYSTEMREQ = 0x57, + /* POD_SYSEX_UPDATE = 0x6c, */ /* software update! */ + POD_SYSEX_STORE = 0x71, + POD_SYSEX_FINISH = 0x72, + POD_SYSEX_DUMPMEM = 0x73, + POD_SYSEX_DUMP = 0x74, + POD_SYSEX_DUMPREQ = 0x75 + /* POD_SYSEX_DUMPMEM2 = 0x76 */ /* dumps entire internal memory of PODxt Pro */ +}; + +enum { + POD_monitor_level = 0x04, + POD_routing = 0x05, + POD_tuner_mute = 0x13, + POD_tuner_freq = 0x15, + POD_tuner_note = 0x16, + POD_tuner_pitch = 0x17, + POD_system_invalid = 0x7fff +}; + +enum { + POD_DUMP_MEMORY = 2 +}; + +enum { + POD_BUSY_READ, + POD_BUSY_WRITE, + POD_CHANNEL_DIRTY, + POD_SAVE_PRESSED, + POD_BUSY_MIDISEND +}; + + +static struct snd_ratden pod_ratden = { + .num_min = 78125, + .num_max = 78125, + .num_step = 1, + .den = 2 +}; + +static struct line6_pcm_properties pod_pcm_properties = { + .snd_line6_playback_hw = { + .info = (SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_PAUSE | + SNDRV_PCM_INFO_SYNC_START), + .formats = SNDRV_PCM_FMTBIT_S24_3LE, + .rates = SNDRV_PCM_RATE_KNOT, + .rate_min = 39062, + .rate_max = 39063, + .channels_min = 2, + .channels_max = 2, + .buffer_bytes_max = 60000, + .period_bytes_min = LINE6_ISO_PACKET_SIZE_MAX * POD_BYTES_PER_FRAME, /* at least one URB must fit into one period */ + .period_bytes_max = 8192, + .periods_min = 1, + .periods_max = 1024 + }, + .snd_line6_capture_hw = { + .info = (SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_SYNC_START), + .formats = SNDRV_PCM_FMTBIT_S24_3LE, + .rates = SNDRV_PCM_RATE_KNOT, + .rate_min = 39062, + .rate_max = 39063, + .channels_min = 2, + .channels_max = 2, + .buffer_bytes_max = 60000, + .period_bytes_min = LINE6_ISO_PACKET_SIZE_MAX * POD_BYTES_PER_FRAME, /* at least one URB must fit into one period */ + .period_bytes_max = 8192, + .periods_min = 1, + .periods_max = 1024 + }, + .snd_line6_rates = { + .nrats = 1, + .rats = &pod_ratden + }, + .bytes_per_frame = POD_BYTES_PER_FRAME +}; + +static const char pod_request_version[] = { 0xf0, 0x7e, 0x7f, 0x06, 0x01, 0xf7 }; +static const char pod_request_channel[] = { 0xf0, 0x00, 0x01, 0x0c, 0x03, 0x75, 0xf7 }; +static const char pod_version_header [] = { 0xf2, 0x7e, 0x7f, 0x06, 0x02 }; + + +/* + Mark all parameters as dirty and notify waiting processes. +*/ +static void pod_mark_batch_all_dirty(struct usb_line6_pod *pod) +{ + int i; + + for(i = POD_CONTROL_SIZE; i--;) + set_bit(i, pod->param_dirty); +} + +/* + Send an asynchronous request for the POD firmware version and device ID. +*/ +static int pod_version_request_async(struct usb_line6_pod *pod) +{ + return line6_send_raw_message_async(&pod->line6, pod->buffer_versionreq, sizeof(pod_request_version)); +} + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 20) +static void pod_create_files_work(struct work_struct *work) +{ + struct usb_line6_pod *pod = container_of(work, struct usb_line6_pod, create_files_work); +#else +static void pod_create_files_work(void *work) +{ + struct usb_line6_pod *pod = (struct usb_line6_pod *)work; +#endif + + pod_create_files(pod->firmware_version, pod->line6.properties->device_bit, pod->line6.ifcdev); +} + +static void pod_startup_timeout(unsigned long arg) +{ + enum { + REQUEST_NONE, + REQUEST_DUMP, + REQUEST_VERSION + }; + + int request = REQUEST_NONE; + struct usb_line6_pod *pod = (struct usb_line6_pod *)arg; + + if(pod->dumpreq.ok) { + if(!pod->versionreq_ok) + request = REQUEST_VERSION; + } + else { + if(pod->versionreq_ok) + request = REQUEST_DUMP; + else if(pod->startup_count++ & 1) + request = REQUEST_DUMP; + else + request = REQUEST_VERSION; + } + + switch(request) { + case REQUEST_DUMP: + line6_dump_request_async(&pod->dumpreq, &pod->line6, 0); + break; + + case REQUEST_VERSION: + pod_version_request_async(pod); + break; + + default: + return; + } + + line6_startup_delayed(&pod->dumpreq, 1, pod_startup_timeout, pod); +} + +static char *pod_alloc_sysex_buffer(struct usb_line6_pod *pod, int code, int size) +{ + return line6_alloc_sysex_buffer(&pod->line6, POD_SYSEX_CODE, code, size); +} + +/* + Send channel dump data to the PODxt Pro. +*/ +static void pod_dump(struct usb_line6_pod *pod, const unsigned char *data) +{ + int size = 1 + sizeof(pod->prog_data); + char *sysex = pod_alloc_sysex_buffer(pod, POD_SYSEX_DUMP, size); + if(!sysex) return; + sysex[SYSEX_DATA_OFS] = 5; /* Don't know what this is good for, but PODxt Pro transmits it, so we also do... */ + memcpy(sysex + SYSEX_DATA_OFS + 1, data, sizeof(pod->prog_data)); + line6_send_sysex_message(&pod->line6, sysex, size); + memcpy(&pod->prog_data, data, sizeof(pod->prog_data)); + pod_mark_batch_all_dirty(pod); + kfree(sysex); +} + +/* + Store parameter value in driver memory and mark it as dirty. +*/ +static void pod_store_parameter(struct usb_line6_pod *pod, int param, int value) +{ + pod->prog_data.control[param] = value; + set_bit(param, pod->param_dirty); + pod->dirty = 1; +} + +/* + Handle SAVE button +*/ +static void pod_save_button_pressed(struct usb_line6_pod *pod, int type, int index) +{ + pod->dirty = 0; + set_bit(POD_SAVE_PRESSED, &pod->atomic_flags); +} + +/* + Process a completely received message. +*/ +void pod_process_message(struct usb_line6_pod *pod) +{ + const unsigned char *buf = pod->line6.buffer_message; + + /* filter messages by type */ + switch(buf[0] & 0xf0) { + case LINE6_PARAM_CHANGE: + case LINE6_PROGRAM_CHANGE: + case LINE6_SYSEX_BEGIN: + break; /* handle these further down */ + + default: + return; /* ignore all others */ + } + + /* process all remaining messages */ + switch(buf[0]) { + case LINE6_PARAM_CHANGE | LINE6_CHANNEL_DEVICE: + pod_store_parameter(pod, buf[1], buf[2]); + /* intentionally no break here! */ + + case LINE6_PARAM_CHANGE | LINE6_CHANNEL_HOST: + if((buf[1] == POD_amp_model_setup) || (buf[1] == POD_effect_setup)) /* these also affect other settings */ + line6_dump_request_async(&pod->dumpreq, &pod->line6, 0); + + break; + + case LINE6_PROGRAM_CHANGE | LINE6_CHANNEL_DEVICE: + case LINE6_PROGRAM_CHANGE | LINE6_CHANNEL_HOST: + pod->channel_num = buf[1]; + pod->dirty = 0; + set_bit(POD_CHANNEL_DIRTY, &pod->atomic_flags); + line6_dump_request_async(&pod->dumpreq, &pod->line6, 0); + break; + + case LINE6_SYSEX_BEGIN | LINE6_CHANNEL_DEVICE: + case LINE6_SYSEX_BEGIN | LINE6_CHANNEL_UNKNOWN: + if(memcmp(buf + 1, line6_midi_id, sizeof(line6_midi_id)) == 0) { + switch(buf[5]) { + case POD_SYSEX_DUMP: + if(pod->line6.message_length == sizeof(pod->prog_data) + 7) { + switch(pod->dumpreq.in_progress) { + case LINE6_DUMP_CURRENT: + memcpy(&pod->prog_data, buf + 7, sizeof(pod->prog_data)); + pod_mark_batch_all_dirty(pod); + pod->dumpreq.ok = 1; + break; + + case POD_DUMP_MEMORY: + memcpy(&pod->prog_data_buf, buf + 7, sizeof(pod->prog_data_buf)); + break; + + default: + DEBUG_MESSAGES(dev_err(pod->line6.ifcdev, "unknown dump code %02X\n", pod->dumpreq.in_progress)); + } + + line6_dump_finished(&pod->dumpreq); + } + else + DEBUG_MESSAGES(dev_err(pod->line6.ifcdev, "wrong size of channel dump message (%d instead of %d)\n", + pod->line6.message_length, (int)sizeof(pod->prog_data) + 7)); + + break; + + case POD_SYSEX_SYSTEM: { + short value = ((int)buf[7] << 12) | ((int)buf[8] << 8) | ((int)buf[9] << 4) | (int)buf[10]; + +#define PROCESS_SYSTEM_PARAM(x) \ + case POD_ ## x: \ + pod->x.value = value; \ + wake_up_interruptible(&pod->x.wait); \ + break; + + switch(buf[6]) { + PROCESS_SYSTEM_PARAM(monitor_level); + PROCESS_SYSTEM_PARAM(routing); + PROCESS_SYSTEM_PARAM(tuner_mute); + PROCESS_SYSTEM_PARAM(tuner_freq); + PROCESS_SYSTEM_PARAM(tuner_note); + PROCESS_SYSTEM_PARAM(tuner_pitch); + +#undef PROCESS_SYSTEM_PARAM + + default: + DEBUG_MESSAGES(dev_err(pod->line6.ifcdev, "unknown tuner/system response %02X\n", buf[6])); + } + + break; + } + + case POD_SYSEX_FINISH: + /* do we need to respond to this? */ + break; + + case POD_SYSEX_SAVE: + pod_save_button_pressed(pod, buf[6], buf[7]); + break; + + case POD_SYSEX_CLIP: + DEBUG_MESSAGES(dev_err(pod->line6.ifcdev, "audio clipped\n")); + pod->clipping.value = 1; + wake_up_interruptible(&pod->clipping.wait); + break; + + case POD_SYSEX_STORE: + DEBUG_MESSAGES(dev_err(pod->line6.ifcdev, "message %02X not yet implemented\n", buf[5])); + break; + + default: + DEBUG_MESSAGES(dev_err(pod->line6.ifcdev, "unknown sysex message %02X\n", buf[5])); + } + } + else if(memcmp(buf, pod_version_header, sizeof(pod_version_header)) == 0) { + if(pod->versionreq_ok == 0) { + pod->firmware_version = buf[13] * 100 + buf[14] * 10 + buf[15]; + pod->device_id = ((int)buf[8] << 16) | ((int)buf[9] << 8) | (int)buf[10]; + pod->versionreq_ok = 1; + + /* Now we know the firmware version, so we schedule a bottom half + handler to create the special files: */ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 20) + INIT_WORK(&pod->create_files_work, pod_create_files_work); +#else + INIT_WORK(&pod->create_files_work, pod_create_files_work, pod); +#endif + queue_work(line6_workqueue, &pod->create_files_work); + } + else + DEBUG_MESSAGES(dev_err(pod->line6.ifcdev, "multiple firmware version message\n")); + } + else + DEBUG_MESSAGES(dev_err(pod->line6.ifcdev, "unknown sysex header\n")); + + break; + + case LINE6_SYSEX_END: + break; + + default: + DEBUG_MESSAGES(dev_err(pod->line6.ifcdev, "POD: unknown message %02X\n", buf[0])); + } +} + +/* + Detect some cases that require a channel dump after sending a command to the + device. Important notes: + *) The actual dump request can not be sent here since we are not allowed to + wait for the completion of the first message in this context, and sending + the dump request before completion of the previous message leaves the POD + in an undefined state. The dump request will be sent when the echoed + commands are received. + *) This method fails if a param change message is "chopped" after the first + byte. +*/ +void pod_midi_postprocess(struct usb_line6_pod *pod, unsigned char *data, int length) +{ + int i; + + if(!pod->midi_postprocess) + return; + + for(i = 0; i < length; ++i) { + if(data[i] == (LINE6_PROGRAM_CHANGE | LINE6_CHANNEL_HOST)) { + line6_invalidate_current(&pod->dumpreq); + break; + } + else if((data[i] == (LINE6_PARAM_CHANGE | LINE6_CHANNEL_HOST)) && (i < length - 1)) + if((data[i + 1] == POD_amp_model_setup) || (data[i + 1] == POD_effect_setup)) { + line6_invalidate_current(&pod->dumpreq); + break; + } + } +} + +/* + Send channel number (i.e., switch to a different sound). +*/ +void pod_send_channel(struct usb_line6_pod *pod, int value) +{ + line6_invalidate_current(&pod->dumpreq); + + if(line6_send_program(&pod->line6, value) == 0) + pod->channel_num = value; + else + line6_dump_finished(&pod->dumpreq); +} + +/* + Transmit PODxt Pro control parameter. +*/ +void pod_transmit_parameter(struct usb_line6_pod *pod, int param, int value) +{ + if(line6_transmit_parameter(&pod->line6, param, value) == 0) + pod_store_parameter(pod, param, value); + + if((param == POD_amp_model_setup) || (param == POD_effect_setup)) /* these also affect other settings */ + line6_invalidate_current(&pod->dumpreq); +} + +/* + Resolve value to memory location. +*/ +static void pod_resolve(const char *buf, short block0, short block1, unsigned char *location) +{ + int value = simple_strtoul(buf, NULL, 10); + short block = (value < 0x40) ? block0 : block1; + value &= 0x3f; + location[0] = block >> 7; + location[1] = value | (block & 0x7f); +} + +/* + Send command to store channel/effects setup/amp setup to PODxt Pro. +*/ +static ssize_t pod_send_store_command(struct device *dev, const char *buf, size_t count, short block0, short block1) +{ + struct usb_interface *interface = to_usb_interface(dev); + struct usb_line6_pod *pod = usb_get_intfdata(interface); + + int size = 3 + sizeof(pod->prog_data_buf); + char *sysex = pod_alloc_sysex_buffer(pod, POD_SYSEX_STORE, size); + if(!sysex) return 0; + + sysex[SYSEX_DATA_OFS] = 5; /* see pod_dump() */ + pod_resolve(buf, block0, block1, sysex + SYSEX_DATA_OFS + 1); + memcpy(sysex + SYSEX_DATA_OFS + 3, &pod->prog_data_buf, sizeof(pod->prog_data_buf)); + + line6_send_sysex_message(&pod->line6, sysex, size); + kfree(sysex); + /* needs some delay here on AMD64 platform */ + return count; +} + +/* + Send command to retrieve channel/effects setup/amp setup to PODxt Pro. +*/ +static ssize_t pod_send_retrieve_command(struct device *dev, const char *buf, size_t count, short block0, short block1) +{ + struct usb_interface *interface = to_usb_interface(dev); + struct usb_line6_pod *pod = usb_get_intfdata(interface); + + int size = 4; + char *sysex = pod_alloc_sysex_buffer(pod, POD_SYSEX_DUMPMEM, size); + if(!sysex) return 0; + + pod_resolve(buf, block0, block1, sysex + SYSEX_DATA_OFS); + sysex[SYSEX_DATA_OFS + 2] = 0; + sysex[SYSEX_DATA_OFS + 3] = 0; + line6_dump_started(&pod->dumpreq, POD_DUMP_MEMORY); + + if(line6_send_sysex_message(&pod->line6, sysex, size) < size) + line6_dump_finished(&pod->dumpreq); + + kfree(sysex); + /* needs some delay here on AMD64 platform */ + return count; +} + +/* + Generic get name function. +*/ +static ssize_t get_name_generic(struct usb_line6_pod *pod, const char *str, char *buf) +{ + int length = 0; + const char *p1; + char *p2; + char *last_non_space = buf; + + int retval = line6_wait_dump(&pod->dumpreq, 0); + if(retval < 0) return retval; + + for(p1 = str, p2 = buf; *p1; ++p1, ++p2) { + *p2 = *p1; + if(*p2 != ' ') last_non_space = p2; + if(++length == POD_NAME_LENGTH) break; + } + + *(last_non_space + 1) = '\n'; + return last_non_space - buf + 2; +} + +/* + "read" request on "channel" special file. +*/ +static ssize_t pod_get_channel(struct device *dev, DEVICE_ATTRIBUTE char *buf) +{ + struct usb_interface *interface = to_usb_interface(dev); + struct usb_line6_pod *pod = usb_get_intfdata(interface); + return sprintf(buf, "%d\n", pod->channel_num); +} + +/* + "write" request on "channel" special file. +*/ +static ssize_t pod_set_channel(struct device *dev, DEVICE_ATTRIBUTE const char *buf, size_t count) +{ + struct usb_interface *interface = to_usb_interface(dev); + struct usb_line6_pod *pod = usb_get_intfdata(interface); + int value = simple_strtoul(buf, NULL, 10); + pod_send_channel(pod, value); + return count; +} + +/* + "read" request on "name" special file. +*/ +static ssize_t pod_get_name(struct device *dev, DEVICE_ATTRIBUTE char *buf) +{ + struct usb_interface *interface = to_usb_interface(dev); + struct usb_line6_pod *pod = usb_get_intfdata(interface); + return get_name_generic(pod, pod->prog_data.header + POD_NAME_OFFSET, buf); +} + +/* + "read" request on "name" special file. +*/ +static ssize_t pod_get_name_buf(struct device *dev, DEVICE_ATTRIBUTE char *buf) +{ + struct usb_interface *interface = to_usb_interface(dev); + struct usb_line6_pod *pod = usb_get_intfdata(interface); + return get_name_generic(pod, pod->prog_data_buf.header + POD_NAME_OFFSET, buf); +} + +/* + "read" request on "dump" special file. +*/ +static ssize_t pod_get_dump(struct device *dev, DEVICE_ATTRIBUTE char *buf) +{ + struct usb_interface *interface = to_usb_interface(dev); + struct usb_line6_pod *pod = usb_get_intfdata(interface); + int retval = line6_wait_dump(&pod->dumpreq, 0); + if(retval < 0) return retval; + memcpy(buf, &pod->prog_data, sizeof(pod->prog_data)); + return sizeof(pod->prog_data); +} + +/* + "write" request on "dump" special file. +*/ +static ssize_t pod_set_dump(struct device *dev, DEVICE_ATTRIBUTE const char *buf, size_t count) +{ + struct usb_interface *interface = to_usb_interface(dev); + struct usb_line6_pod *pod = usb_get_intfdata(interface); + + if(count != sizeof(pod->prog_data)) { + dev_err(pod->line6.ifcdev, + "data block must be exactly %d bytes\n", + (int)sizeof(pod->prog_data)); + return -EINVAL; + } + + pod_dump(pod, buf); + return sizeof(pod->prog_data); +} + +/* + Request system parameter. + @param tuner non-zero, if code refers to a tuner parameter +*/ +static ssize_t pod_get_system_param(struct usb_line6_pod *pod, char *buf, int code, struct ValueWait *param, int tuner, int sign) +{ + char *sysex; + int value; + static const int size = 1; + int retval = 0; + DECLARE_WAITQUEUE(wait, current); + + if(((pod->prog_data.control[POD_tuner] & 0x40) == 0) && tuner) + return -ENODEV; + + /* send value request to tuner: */ + param->value = POD_system_invalid; + sysex = pod_alloc_sysex_buffer(pod, POD_SYSEX_SYSTEMREQ, size); + if(!sysex) return 0; + sysex[SYSEX_DATA_OFS] = code; + line6_send_sysex_message(&pod->line6, sysex, size); + kfree(sysex); + + /* wait for tuner to respond: */ + add_wait_queue(¶m->wait, &wait); + current->state = TASK_INTERRUPTIBLE; + + while(param->value == POD_system_invalid) { + if(signal_pending(current)) { + retval = -ERESTARTSYS; + break; + } + else + schedule(); + } + + current->state = TASK_RUNNING; + remove_wait_queue(¶m->wait, &wait); + + if(retval < 0) + return retval; + + value = sign ? (int)(signed short)param->value : (int)(unsigned short)param->value; + return sprintf(buf, "%d\n", value); +} + +/* + Send system parameter. + @param tuner non-zero, if code refers to a tuner parameter +*/ +static ssize_t pod_set_system_param(struct usb_line6_pod *pod, const char *buf, int count, int code, unsigned short mask, int tuner) +{ + char *sysex; + static const int size = 5; + unsigned short value; + + if(((pod->prog_data.control[POD_tuner] & 0x40) == 0) && tuner) + return -EINVAL; + + /* send value to tuner: */ + sysex = pod_alloc_sysex_buffer(pod, POD_SYSEX_SYSTEM, size); + if(!sysex) return 0; + value = simple_strtoul(buf, NULL, 10) & mask; + sysex[SYSEX_DATA_OFS] = code; + sysex[SYSEX_DATA_OFS + 1] = (value >> 12) & 0x0f; + sysex[SYSEX_DATA_OFS + 2] = (value >> 8) & 0x0f; + sysex[SYSEX_DATA_OFS + 3] = (value >> 4) & 0x0f; + sysex[SYSEX_DATA_OFS + 4] = (value ) & 0x0f; + line6_send_sysex_message(&pod->line6, sysex, size); + kfree(sysex); + return count; +} + +/* + "read" request on "dump_buf" special file. +*/ +static ssize_t pod_get_dump_buf(struct device *dev, DEVICE_ATTRIBUTE char *buf) +{ + struct usb_interface *interface = to_usb_interface(dev); + struct usb_line6_pod *pod = usb_get_intfdata(interface); + int retval = line6_wait_dump(&pod->dumpreq, 0); + if(retval < 0) return retval; + memcpy(buf, &pod->prog_data_buf, sizeof(pod->prog_data_buf)); + return sizeof(pod->prog_data_buf); +} + +/* + "write" request on "dump_buf" special file. +*/ +static ssize_t pod_set_dump_buf(struct device *dev, DEVICE_ATTRIBUTE const char *buf, size_t count) +{ + struct usb_interface *interface = to_usb_interface(dev); + struct usb_line6_pod *pod = usb_get_intfdata(interface); + + if(count != sizeof(pod->prog_data)) { + dev_err(pod->line6.ifcdev, + "data block must be exactly %d bytes\n", + (int)sizeof(pod->prog_data)); + return -EINVAL; + } + + memcpy(&pod->prog_data_buf, buf, sizeof(pod->prog_data)); + return sizeof(pod->prog_data); +} + +/* + "write" request on "finish" special file. +*/ +static ssize_t pod_set_finish(struct device *dev, DEVICE_ATTRIBUTE const char *buf, size_t count) +{ + struct usb_interface *interface = to_usb_interface(dev); + struct usb_line6_pod *pod = usb_get_intfdata(interface); + int size = 0; + char *sysex = pod_alloc_sysex_buffer(pod, POD_SYSEX_FINISH, size); + if(!sysex) return 0; + line6_send_sysex_message(&pod->line6, sysex, size); + kfree(sysex); + return count; +} + +/* + "write" request on "store_channel" special file. +*/ +static ssize_t pod_set_store_channel(struct device *dev, DEVICE_ATTRIBUTE const char *buf, size_t count) +{ + return pod_send_store_command(dev, buf, count, 0x0000, 0x00c0); +} + +/* + "write" request on "store_effects_setup" special file. +*/ +static ssize_t pod_set_store_effects_setup(struct device *dev, DEVICE_ATTRIBUTE const char *buf, size_t count) +{ + return pod_send_store_command(dev, buf, count, 0x0080, 0x0080); +} + +/* + "write" request on "store_amp_setup" special file. +*/ +static ssize_t pod_set_store_amp_setup(struct device *dev, DEVICE_ATTRIBUTE const char *buf, size_t count) +{ + return pod_send_store_command(dev, buf, count, 0x0040, 0x0100); +} + +/* + "write" request on "retrieve_channel" special file. +*/ +static ssize_t pod_set_retrieve_channel(struct device *dev, DEVICE_ATTRIBUTE const char *buf, size_t count) +{ + return pod_send_retrieve_command(dev, buf, count, 0x0000, 0x00c0); +} + +/* + "write" request on "retrieve_effects_setup" special file. +*/ +static ssize_t pod_set_retrieve_effects_setup(struct device *dev, DEVICE_ATTRIBUTE const char *buf, size_t count) +{ + return pod_send_retrieve_command(dev, buf, count, 0x0080, 0x0080); +} + +/* + "write" request on "retrieve_amp_setup" special file. +*/ +static ssize_t pod_set_retrieve_amp_setup(struct device *dev, DEVICE_ATTRIBUTE const char *buf, size_t count) +{ + return pod_send_retrieve_command(dev, buf, count, 0x0040, 0x0100); +} + +/* + "read" request on "dirty" special file. +*/ +static ssize_t pod_get_dirty(struct device *dev, DEVICE_ATTRIBUTE char *buf) +{ + struct usb_interface *interface = to_usb_interface(dev); + struct usb_line6_pod *pod = usb_get_intfdata(interface); + buf[0] = pod->dirty ? '1' : '0'; + buf[1] = '\n'; + return 2; +} + +/* + "read" request on "midi_postprocess" special file. +*/ +static ssize_t pod_get_midi_postprocess(struct device *dev, DEVICE_ATTRIBUTE char *buf) +{ + struct usb_interface *interface = to_usb_interface(dev); + struct usb_line6_pod *pod = usb_get_intfdata(interface); + return sprintf(buf, "%d\n", pod->midi_postprocess); +} + +/* + "write" request on "midi_postprocess" special file. +*/ +static ssize_t pod_set_midi_postprocess(struct device *dev, DEVICE_ATTRIBUTE const char *buf, size_t count) +{ + struct usb_interface *interface = to_usb_interface(dev); + struct usb_line6_pod *pod = usb_get_intfdata(interface); + int value = simple_strtoul(buf, NULL, 10); + pod->midi_postprocess = value ? 1 : 0; + return count; +} + +/* + "read" request on "serial_number" special file. +*/ +static ssize_t pod_get_serial_number(struct device *dev, DEVICE_ATTRIBUTE char *buf) +{ + struct usb_interface *interface = to_usb_interface(dev); + struct usb_line6_pod *pod = usb_get_intfdata(interface); + return sprintf(buf, "%d\n", pod->serial_number); +} + +/* + "read" request on "firmware_version" special file. +*/ +static ssize_t pod_get_firmware_version(struct device *dev, DEVICE_ATTRIBUTE char *buf) +{ + struct usb_interface *interface = to_usb_interface(dev); + struct usb_line6_pod *pod = usb_get_intfdata(interface); + return sprintf(buf, "%d.%02d\n", pod->firmware_version / 100, pod->firmware_version % 100); +} + +/* + "read" request on "device_id" special file. +*/ +static ssize_t pod_get_device_id(struct device *dev, DEVICE_ATTRIBUTE char *buf) +{ + struct usb_interface *interface = to_usb_interface(dev); + struct usb_line6_pod *pod = usb_get_intfdata(interface); + return sprintf(buf, "%d\n", pod->device_id); +} + +/* + "read" request on "clip" special file. +*/ +static ssize_t pod_wait_for_clip(struct device *dev, DEVICE_ATTRIBUTE char *buf) +{ + struct usb_interface *interface = to_usb_interface(dev); + struct usb_line6_pod *pod = usb_get_intfdata(interface); + int err = 0; + DECLARE_WAITQUEUE(wait, current); + pod->clipping.value = 0; + add_wait_queue(&pod->clipping.wait, &wait); + current->state = TASK_INTERRUPTIBLE; + + while(pod->clipping.value == 0) { + if(signal_pending(current)) { + err = -ERESTARTSYS; + break; + } + else + schedule(); + } + + current->state = TASK_RUNNING; + remove_wait_queue(&pod->clipping.wait, &wait); + return err; +} + +#define POD_GET_SYSTEM_PARAM(code, tuner, sign) \ +static ssize_t pod_get_ ## code(struct device *dev, DEVICE_ATTRIBUTE char *buf) \ +{ \ + struct usb_interface *interface = to_usb_interface(dev); \ + struct usb_line6_pod *pod = usb_get_intfdata(interface); \ + return pod_get_system_param(pod, buf, POD_ ## code, &pod->code, tuner, sign); \ +} + +#define POD_GET_SET_SYSTEM_PARAM(code, mask, tuner, sign) \ +POD_GET_SYSTEM_PARAM(code, tuner, sign) \ +static ssize_t pod_set_ ## code(struct device *dev, DEVICE_ATTRIBUTE const char *buf, size_t count) \ +{ \ + struct usb_interface *interface = to_usb_interface(dev); \ + struct usb_line6_pod *pod = usb_get_intfdata(interface); \ + return pod_set_system_param(pod, buf, count, POD_ ## code, mask, tuner); \ +} + +POD_GET_SET_SYSTEM_PARAM(monitor_level, 0xffff, 0, 0); +POD_GET_SET_SYSTEM_PARAM(routing, 0x0003, 0, 0); +POD_GET_SET_SYSTEM_PARAM(tuner_mute, 0x0001, 1, 0); +POD_GET_SET_SYSTEM_PARAM(tuner_freq, 0xffff, 1, 0); +POD_GET_SYSTEM_PARAM(tuner_note, 1, 1); +POD_GET_SYSTEM_PARAM(tuner_pitch, 1, 1); + +#undef GET_SET_SYSTEM_PARAM +#undef GET_SYSTEM_PARAM + +/* POD special files: */ +static DEVICE_ATTR(channel, S_IWUGO | S_IRUGO, pod_get_channel, pod_set_channel); +static DEVICE_ATTR(clip, S_IRUGO, pod_wait_for_clip, line6_nop_write); +static DEVICE_ATTR(device_id, S_IRUGO, pod_get_device_id, line6_nop_write); +static DEVICE_ATTR(dirty, S_IRUGO, pod_get_dirty, line6_nop_write); +static DEVICE_ATTR(dump, S_IWUGO | S_IRUGO, pod_get_dump, pod_set_dump); +static DEVICE_ATTR(dump_buf, S_IWUGO | S_IRUGO, pod_get_dump_buf, pod_set_dump_buf); +static DEVICE_ATTR(finish, S_IWUGO, line6_nop_read, pod_set_finish); +static DEVICE_ATTR(firmware_version, S_IRUGO, pod_get_firmware_version, line6_nop_write); +static DEVICE_ATTR(midi_postprocess, S_IWUGO | S_IRUGO, pod_get_midi_postprocess, pod_set_midi_postprocess); +static DEVICE_ATTR(monitor_level, S_IWUGO | S_IRUGO, pod_get_monitor_level, pod_set_monitor_level); +static DEVICE_ATTR(name, S_IRUGO, pod_get_name, line6_nop_write); +static DEVICE_ATTR(name_buf, S_IRUGO, pod_get_name_buf, line6_nop_write); +static DEVICE_ATTR(retrieve_amp_setup, S_IWUGO, line6_nop_read, pod_set_retrieve_amp_setup); +static DEVICE_ATTR(retrieve_channel, S_IWUGO, line6_nop_read, pod_set_retrieve_channel); +static DEVICE_ATTR(retrieve_effects_setup, S_IWUGO, line6_nop_read, pod_set_retrieve_effects_setup); +static DEVICE_ATTR(routing, S_IWUGO | S_IRUGO, pod_get_routing, pod_set_routing); +static DEVICE_ATTR(serial_number, S_IRUGO, pod_get_serial_number, line6_nop_write); +static DEVICE_ATTR(store_amp_setup, S_IWUGO, line6_nop_read, pod_set_store_amp_setup); +static DEVICE_ATTR(store_channel, S_IWUGO, line6_nop_read, pod_set_store_channel); +static DEVICE_ATTR(store_effects_setup, S_IWUGO, line6_nop_read, pod_set_store_effects_setup); +static DEVICE_ATTR(tuner_freq, S_IWUGO | S_IRUGO, pod_get_tuner_freq, pod_set_tuner_freq); +static DEVICE_ATTR(tuner_mute, S_IWUGO | S_IRUGO, pod_get_tuner_mute, pod_set_tuner_mute); +static DEVICE_ATTR(tuner_note, S_IRUGO, pod_get_tuner_note, line6_nop_write); +static DEVICE_ATTR(tuner_pitch, S_IRUGO, pod_get_tuner_pitch, line6_nop_write); + +#if CREATE_RAW_FILE +static DEVICE_ATTR(raw, S_IWUGO, line6_nop_read, line6_set_raw); +#endif + +/* + POD destructor. +*/ +static void pod_destruct(struct usb_interface *interface) +{ + struct usb_line6_pod *pod = usb_get_intfdata(interface); + struct usb_line6 *line6; + + if(pod == NULL) return; + line6 = &pod->line6; + if(line6 == NULL) return; + line6_cleanup_audio(line6); + + /* free dump request data: */ + line6_dumpreq_destruct(&pod->dumpreq); + + if(pod->buffer_versionreq) kfree(pod->buffer_versionreq); +} + +/* + Create sysfs entries. +*/ +int pod_create_files2(struct device *dev) +{ + int err; + + CHECK_RETURN(device_create_file(dev, &dev_attr_channel)); + CHECK_RETURN(device_create_file(dev, &dev_attr_clip)); + CHECK_RETURN(device_create_file(dev, &dev_attr_device_id)); + CHECK_RETURN(device_create_file(dev, &dev_attr_dirty)); + CHECK_RETURN(device_create_file(dev, &dev_attr_dump)); + CHECK_RETURN(device_create_file(dev, &dev_attr_dump_buf)); + CHECK_RETURN(device_create_file(dev, &dev_attr_finish)); + CHECK_RETURN(device_create_file(dev, &dev_attr_firmware_version)); + CHECK_RETURN(device_create_file(dev, &dev_attr_midi_postprocess)); + CHECK_RETURN(device_create_file(dev, &dev_attr_monitor_level)); + CHECK_RETURN(device_create_file(dev, &dev_attr_name)); + CHECK_RETURN(device_create_file(dev, &dev_attr_name_buf)); + CHECK_RETURN(device_create_file(dev, &dev_attr_retrieve_amp_setup)); + CHECK_RETURN(device_create_file(dev, &dev_attr_retrieve_channel)); + CHECK_RETURN(device_create_file(dev, &dev_attr_retrieve_effects_setup)); + CHECK_RETURN(device_create_file(dev, &dev_attr_routing)); + CHECK_RETURN(device_create_file(dev, &dev_attr_serial_number)); + CHECK_RETURN(device_create_file(dev, &dev_attr_store_amp_setup)); + CHECK_RETURN(device_create_file(dev, &dev_attr_store_channel)); + CHECK_RETURN(device_create_file(dev, &dev_attr_store_effects_setup)); + CHECK_RETURN(device_create_file(dev, &dev_attr_tuner_freq)); + CHECK_RETURN(device_create_file(dev, &dev_attr_tuner_mute)); + CHECK_RETURN(device_create_file(dev, &dev_attr_tuner_note)); + CHECK_RETURN(device_create_file(dev, &dev_attr_tuner_pitch)); + +#if CREATE_RAW_FILE + CHECK_RETURN(device_create_file(dev, &dev_attr_raw)); +#endif + + return 0; +} + +/* + Init POD device. +*/ +int pod_init(struct usb_interface *interface, struct usb_line6_pod *pod) +{ + int err; + struct usb_line6 *line6 = &pod->line6; + + if((interface == NULL) || (pod == NULL)) return -ENODEV; + + pod->channel_num = 255; + + /* initialize wait queues: */ + init_waitqueue_head(&pod->monitor_level.wait); + init_waitqueue_head(&pod->routing.wait); + init_waitqueue_head(&pod->tuner_mute.wait); + init_waitqueue_head(&pod->tuner_freq.wait); + init_waitqueue_head(&pod->tuner_note.wait); + init_waitqueue_head(&pod->tuner_pitch.wait); + init_waitqueue_head(&pod->clipping.wait); + + memset(pod->param_dirty, 0xff, sizeof(pod->param_dirty)); + + /* initialize USB buffers: */ + err = line6_dumpreq_init(&pod->dumpreq, pod_request_channel, sizeof(pod_request_channel)); + + if(err < 0) { + dev_err(&interface->dev, "Out of memory\n"); + pod_destruct(interface); + return -ENOMEM; + } + + pod->buffer_versionreq = kmalloc(sizeof(pod_request_version), GFP_KERNEL); + + if(pod->buffer_versionreq == NULL) { + dev_err(&interface->dev, "Out of memory\n"); + pod_destruct(interface); + return -ENOMEM; + } + + memcpy(pod->buffer_versionreq, pod_request_version, sizeof(pod_request_version)); + + /* create sysfs entries: */ + if((err = pod_create_files2(&interface->dev)) < 0) { + pod_destruct(interface); + return err; + } + + /* initialize audio system: */ + if((err = line6_init_audio(line6)) < 0) { + pod_destruct(interface); + return err; + } + + /* initialize MIDI subsystem: */ + if((err = line6_init_midi(line6)) < 0) { + pod_destruct(interface); + return err; + } + + /* initialize PCM subsystem: */ + if((err = line6_init_pcm(line6, &pod_pcm_properties)) < 0) { + pod_destruct(interface); + return err; + } + + /* register audio system: */ + if((err = line6_register_audio(line6)) < 0) { + pod_destruct(interface); + return err; + } + + if(pod->line6.properties->capabilities & LINE6_BIT_CONTROL) { + /* query some data: */ + line6_startup_delayed(&pod->dumpreq, POD_STARTUP_DELAY, pod_startup_timeout, pod); + line6_read_serial_number(&pod->line6, &pod->serial_number); + } + + return 0; +} + +/* + POD device disconnected. +*/ +void pod_disconnect(struct usb_interface *interface) +{ + struct usb_line6_pod *pod; + + if(interface == NULL) return; + pod = usb_get_intfdata(interface); + + if(pod != NULL) { + struct snd_line6_pcm *line6pcm = pod->line6.line6pcm; + struct device *dev = &interface->dev; + + if(line6pcm != NULL) { + unlink_wait_clear_audio_out_urbs(line6pcm); + unlink_wait_clear_audio_in_urbs(line6pcm); + } + + if(dev != NULL) { + /* remove sysfs entries: */ + if(pod->versionreq_ok) + pod_remove_files(pod->firmware_version, pod->line6.properties->device_bit, dev); + + device_remove_file(dev, &dev_attr_channel); + device_remove_file(dev, &dev_attr_clip); + device_remove_file(dev, &dev_attr_device_id); + device_remove_file(dev, &dev_attr_dirty); + device_remove_file(dev, &dev_attr_dump); + device_remove_file(dev, &dev_attr_dump_buf); + device_remove_file(dev, &dev_attr_finish); + device_remove_file(dev, &dev_attr_firmware_version); + device_remove_file(dev, &dev_attr_midi_postprocess); + device_remove_file(dev, &dev_attr_monitor_level); + device_remove_file(dev, &dev_attr_name); + device_remove_file(dev, &dev_attr_name_buf); + device_remove_file(dev, &dev_attr_retrieve_amp_setup); + device_remove_file(dev, &dev_attr_retrieve_channel); + device_remove_file(dev, &dev_attr_retrieve_effects_setup); + device_remove_file(dev, &dev_attr_routing); + device_remove_file(dev, &dev_attr_serial_number); + device_remove_file(dev, &dev_attr_store_amp_setup); + device_remove_file(dev, &dev_attr_store_channel); + device_remove_file(dev, &dev_attr_store_effects_setup); + device_remove_file(dev, &dev_attr_tuner_freq); + device_remove_file(dev, &dev_attr_tuner_mute); + device_remove_file(dev, &dev_attr_tuner_note); + device_remove_file(dev, &dev_attr_tuner_pitch); + +#if CREATE_RAW_FILE + device_remove_file(dev, &dev_attr_raw); +#endif + } + } + + pod_destruct(interface); +} diff --git a/drivers/staging/line6/pod.h b/drivers/staging/line6/pod.h new file mode 100644 index 000000000000..0db59484c4ba --- /dev/null +++ b/drivers/staging/line6/pod.h @@ -0,0 +1,203 @@ +/* + * Line6 Linux USB driver - 0.8.0 + * + * Copyright (C) 2004-2009 Markus Grabner (grabner@icg.tugraz.at) + * + * 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, version 2. + * + */ + +#ifndef POD_H +#define POD_H + + +#include "driver.h" + +#include <linux/spinlock.h> +#include <linux/usb.h> +#include <linux/wait.h> +#include <linux/workqueue.h> + +#include <sound/core.h> + +#include "dumprequest.h" + + +/* + PODxt Live interfaces +*/ +#define PODXTLIVE_INTERFACE_POD 0 +#define PODXTLIVE_INTERFACE_VARIAX 1 + +/* + Locate name in binary program dump +*/ +#define POD_NAME_OFFSET 0 +#define POD_NAME_LENGTH 16 + +/* + Other constants +*/ +#define POD_CONTROL_SIZE 0x80 +#define POD_BUFSIZE_DUMPREQ 7 +#define POD_STARTUP_DELAY 3 + + +/** + Data structure for values that need to be requested explicitly. + This is the case for system and tuner settings. +*/ +struct ValueWait +{ + unsigned short value; + wait_queue_head_t wait; +}; + +/** + Binary PodXT Pro program dump +*/ +struct pod_program { + /** + Header information (including program name). + */ + unsigned char header[0x20]; + + /** + Program parameters. + */ + unsigned char control[POD_CONTROL_SIZE]; +}; + +struct usb_line6_pod { + /** + Generic Line6 USB data. + */ + struct usb_line6 line6; + + /** + Dump request structure. + */ + struct line6_dump_request dumpreq; + + /** + Current program number. + */ + unsigned char channel_num; + + /** + Current program settings. + */ + struct pod_program prog_data; + + /** + Buffer for data retrieved from or to be stored on PODxt Pro. + */ + struct pod_program prog_data_buf; + + /** + Buffer for requesting version number. + */ + unsigned char *buffer_versionreq; + + /** + Tuner mute mode. + */ + struct ValueWait tuner_mute; + + /** + Tuner base frequency (typically 440Hz). + */ + struct ValueWait tuner_freq; + + /** + Note received from tuner. + */ + struct ValueWait tuner_note; + + /** + Pitch value received from tuner. + */ + struct ValueWait tuner_pitch; + + /** + Instrument monitor level. + */ + struct ValueWait monitor_level; + + /** + Audio routing mode. + 0: send processed guitar + 1: send clean guitar + 2: send clean guitar re-amp playback + 3: send re-amp playback + */ + struct ValueWait routing; + + /** + Wait for audio clipping event. + */ + struct ValueWait clipping; + + /** + Bottom-half for creation of sysfs special files. + */ + struct work_struct create_files_work; + + /** + Dirty flags for access to parameter data. + */ + unsigned long param_dirty[POD_CONTROL_SIZE / sizeof(unsigned long)]; + + /** + Some atomic flags. + */ + unsigned long atomic_flags; + + /** + Counter for startup process. + */ + int startup_count; + + /** + Serial number of device. + */ + int serial_number; + + /** + Firmware version (x 100). + */ + int firmware_version; + + /** + Device ID. + */ + int device_id; + + /** + Flag to indicate modification of current program settings. + */ + char dirty; + + /** + Flag if initial firmware version request has been successful. + */ + char versionreq_ok; + + /** + Flag to enable MIDI postprocessing. + */ + char midi_postprocess; +}; + + +extern void pod_disconnect(struct usb_interface *interface); +extern int pod_init(struct usb_interface *interface, struct usb_line6_pod *pod); +extern void pod_midi_postprocess(struct usb_line6_pod *pod, unsigned char *data, int length); +extern void pod_process_message(struct usb_line6_pod *pod); +extern void pod_receive_parameter(struct usb_line6_pod *pod, int param); +extern void pod_transmit_parameter(struct usb_line6_pod *pod, int param, int value); + + +#endif diff --git a/drivers/staging/line6/revision.h b/drivers/staging/line6/revision.h new file mode 100644 index 000000000000..b2a0a85efe69 --- /dev/null +++ b/drivers/staging/line6/revision.h @@ -0,0 +1,4 @@ +#ifndef DRIVER_REVISION +/* current subversion revision */ +#define DRIVER_REVISION " (revision 529)" +#endif diff --git a/drivers/staging/line6/toneport.c b/drivers/staging/line6/toneport.c new file mode 100644 index 000000000000..c9fe07feab69 --- /dev/null +++ b/drivers/staging/line6/toneport.c @@ -0,0 +1,219 @@ +/* + * Line6 Linux USB driver - 0.8.0 + * + * Copyright (C) 2004-2009 Markus Grabner (grabner@icg.tugraz.at) + * Emil Myhrman (emil.myhrman@gmail.com) + * + * 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, version 2. + * + */ + +#include "driver.h" + +#include "audio.h" +#include "capture.h" +#include "playback.h" +#include "toneport.h" + + +static int toneport_send_cmd(struct usb_device *usbdev, int cmd1, int cmd2); + + +static struct snd_ratden toneport_ratden = { + .num_min = 44100, + .num_max = 44100, + .num_step = 1, + .den = 1 +}; + +static struct line6_pcm_properties toneport_pcm_properties = { + .snd_line6_playback_hw = { + .info = (SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_PAUSE | + SNDRV_PCM_INFO_SYNC_START), + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .rates = SNDRV_PCM_RATE_KNOT, + .rate_min = 44100, + .rate_max = 44100, + .channels_min = 2, + .channels_max = 2, + .buffer_bytes_max = 60000, + .period_bytes_min = 180 * 4, + .period_bytes_max = 8192, + .periods_min = 1, + .periods_max = 1024 + }, + .snd_line6_capture_hw = { + .info = (SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_SYNC_START), + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .rates = SNDRV_PCM_RATE_KNOT, + .rate_min = 44100, + .rate_max = 44100, + .channels_min = 2, + .channels_max = 2, + .buffer_bytes_max = 60000, + .period_bytes_min = 188 * 4, + .period_bytes_max = 8192, + .periods_min = 1, + .periods_max = 1024 + }, + .snd_line6_rates = { + .nrats = 1, + .rats = &toneport_ratden + }, + .bytes_per_frame = 4 +}; + +/* + For the led on Guitarport. + Brightness goes from 0x00 to 0x26. Set a value above this to have led blink. + (void cmd_0x02(byte red, byte green) +*/ +static int led_red = 0x00; +static int led_green = 0x26; + +static void toneport_update_led(struct device *dev) { + struct usb_interface *interface; + struct usb_line6_toneport *tp; + struct usb_line6* line6; + + if ((interface = to_usb_interface(dev)) && + (tp = usb_get_intfdata(interface)) && + (line6 = &tp->line6)) + toneport_send_cmd(line6->usbdev, (led_red<<8)|0x0002, led_green); // for setting the LED on Guitarport +} +static ssize_t toneport_set_led_red(struct device *dev, DEVICE_ATTRIBUTE const char *buf, size_t count) { + char* c; + led_red = simple_strtol(buf, &c, 10); + toneport_update_led(dev); + return count; +} +static ssize_t toneport_set_led_green(struct device *dev, DEVICE_ATTRIBUTE const char *buf, size_t count) { + char* c; + led_green = simple_strtol(buf, &c, 10); + toneport_update_led(dev); + return count; +} + +static DEVICE_ATTR(led_red, S_IWUGO | S_IRUGO, line6_nop_read, toneport_set_led_red); +static DEVICE_ATTR(led_green, S_IWUGO | S_IRUGO, line6_nop_read, toneport_set_led_green); + + +static int toneport_send_cmd(struct usb_device *usbdev, int cmd1, int cmd2) +{ + int ret; + ret = usb_control_msg(usbdev, usb_sndctrlpipe(usbdev,0), 0x67, + USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_OUT, + cmd1, cmd2, 0, 0, LINE6_TIMEOUT * HZ); + + if(ret < 0) { + err("send failed (error %d)\n", ret); + return ret; + } + + return 0; +} + +/* + Toneport destructor. +*/ +static void toneport_destruct(struct usb_interface *interface) +{ + struct usb_line6_toneport *toneport = usb_get_intfdata(interface); + struct usb_line6 *line6; + + if(toneport == NULL) return; + line6 = &toneport->line6; + if(line6 == NULL) return; + line6_cleanup_audio(line6); +} + +/* + Init Toneport device. +*/ +int toneport_init(struct usb_interface *interface, struct usb_line6_toneport *toneport) +{ + int err, ticks; + struct usb_line6 *line6 = &toneport->line6; + struct usb_device *usbdev; + + if((interface == NULL) || (toneport == NULL)) + return -ENODEV; + + /* initialize audio system: */ + if((err = line6_init_audio(line6)) < 0) { + toneport_destruct(interface); + return err; + } + + /* initialize PCM subsystem: */ + if((err = line6_init_pcm(line6, &toneport_pcm_properties)) < 0) { + toneport_destruct(interface); + return err; + } + + /* register audio system: */ + if((err = line6_register_audio(line6)) < 0) { + toneport_destruct(interface); + return err; + } + + usbdev = line6->usbdev; + line6_read_serial_number(line6, &toneport->serial_number); + line6_read_data(line6, 0x80c2, &toneport->firmware_version, 1); + + /* sync time on device with host: */ + ticks = (int)get_seconds(); + line6_write_data(line6, 0x80c6, &ticks, 4); + + /* + seems to work without the first two... + */ + //toneport_send_cmd(usbdev, 0x0201, 0x0002); // .. + //toneport_send_cmd(usbdev, 0x0801, 0x0000); // .. + toneport_send_cmd(usbdev, 0x0301, 0x0000); // only one that works for me; on GP, TP might be different? + + if (usbdev->descriptor.idProduct!=LINE6_DEVID_GUITARPORT) { + CHECK_RETURN(device_create_file(&interface->dev, &dev_attr_led_red)); + CHECK_RETURN(device_create_file(&interface->dev, &dev_attr_led_green)); + toneport_update_led(&usbdev->dev); + } + + return 0; +} + +/* + Toneport device disconnected. +*/ +void toneport_disconnect(struct usb_interface *interface) +{ + struct usb_line6_toneport *toneport; + + if(interface == NULL) return; + toneport = usb_get_intfdata(interface); + + if (toneport->line6.usbdev->descriptor.idProduct != LINE6_DEVID_GUITARPORT) { + device_remove_file(&interface->dev, &dev_attr_led_red); + device_remove_file(&interface->dev, &dev_attr_led_green); + } + + if(toneport != NULL) { + struct snd_line6_pcm *line6pcm = toneport->line6.line6pcm; + + if(line6pcm != NULL) { + unlink_wait_clear_audio_out_urbs(line6pcm); + unlink_wait_clear_audio_in_urbs(line6pcm); + } + } + + toneport_destruct(interface); +} diff --git a/drivers/staging/line6/toneport.h b/drivers/staging/line6/toneport.h new file mode 100644 index 000000000000..cd0b19fe7c84 --- /dev/null +++ b/drivers/staging/line6/toneport.h @@ -0,0 +1,44 @@ +/* + * Line6 Linux USB driver - 0.8.0 + * + * Copyright (C) 2004-2009 Markus Grabner (grabner@icg.tugraz.at) + * + * 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, version 2. + * + */ + +#ifndef TONEPORT_H +#define TONEPORT_H + + +#include "driver.h" + +#include <linux/usb.h> +#include <sound/core.h> + + +struct usb_line6_toneport { + /** + Generic Line6 USB data. + */ + struct usb_line6 line6; + + /** + Serial number of device. + */ + int serial_number; + + /** + Firmware version (x 100). + */ + int firmware_version; +}; + + +extern void toneport_disconnect(struct usb_interface *interface); +extern int toneport_init(struct usb_interface *interface, struct usb_line6_toneport *toneport); + + +#endif diff --git a/drivers/staging/line6/usbdefs.h b/drivers/staging/line6/usbdefs.h new file mode 100644 index 000000000000..cbf5d0d62a1f --- /dev/null +++ b/drivers/staging/line6/usbdefs.h @@ -0,0 +1,75 @@ +/* + * Line6 Linux USB driver - 0.8.0 + * + * Copyright (C) 2005-2008 Markus Grabner (grabner@icg.tugraz.at) + * + * 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, version 2. + * + */ + +#ifndef USBDEFS_H +#define USBDEFS_H + + +#include <linux/version.h> + + +#define LINE6_VENDOR_ID 0x0e41 + +#define USB_INTERVALS_PER_SECOND 1000 + +/* + Device ids. +*/ +#define LINE6_DEVID_BASSPODXT 0x4250 +#define LINE6_DEVID_BASSPODXTLIVE 0x4642 +#define LINE6_DEVID_BASSPODXTPRO 0x4252 +#define LINE6_DEVID_GUITARPORT 0x4750 +#define LINE6_DEVID_POCKETPOD 0x5051 +#define LINE6_DEVID_PODX3 0x414a +#define LINE6_DEVID_PODX3LIVE 0x414b +#define LINE6_DEVID_PODXT 0x5044 +#define LINE6_DEVID_PODXTLIVE 0x4650 +#define LINE6_DEVID_PODXTPRO 0x5050 +#define LINE6_DEVID_TONEPORT_GX 0x4147 +#define LINE6_DEVID_TONEPORT_UX1 0x4141 +#define LINE6_DEVID_TONEPORT_UX2 0x4142 +#define LINE6_DEVID_VARIAX 0x534d + +#define LINE6_BIT_BASSPODXT (1 << 0) +#define LINE6_BIT_BASSPODXTLIVE (1 << 1) +#define LINE6_BIT_BASSPODXTPRO (1 << 2) +#define LINE6_BIT_GUITARPORT (1 << 3) +#define LINE6_BIT_POCKETPOD (1 << 4) +#define LINE6_BIT_PODX3 (1 << 5) +#define LINE6_BIT_PODX3LIVE (1 << 6) +#define LINE6_BIT_PODXT (1 << 7) +#define LINE6_BIT_PODXTLIVE (1 << 8) +#define LINE6_BIT_PODXTPRO (1 << 9) +#define LINE6_BIT_TONEPORT_GX (1 << 10) +#define LINE6_BIT_TONEPORT_UX1 (1 << 11) +#define LINE6_BIT_TONEPORT_UX2 (1 << 12) +#define LINE6_BIT_VARIAX (1 << 13) + +#define LINE6_BITS_PRO (LINE6_BIT_BASSPODXTPRO | LINE6_BIT_PODXTPRO) +#define LINE6_BITS_LIVE (LINE6_BIT_BASSPODXTLIVE | LINE6_BIT_PODXTLIVE | LINE6_BIT_PODX3LIVE) +#define LINE6_BITS_PODXTALL (LINE6_BIT_PODXT | LINE6_BIT_PODXTLIVE | LINE6_BIT_PODXTPRO) +#define LINE6_BITS_BASSPODXTALL (LINE6_BIT_BASSPODXT | LINE6_BIT_BASSPODXTLIVE | LINE6_BIT_BASSPODXTPRO) + +#define LINE6_BIT_CONTROL (1 << 0) /* device supports settings parameter via USB */ +#define LINE6_BIT_PCM (1 << 1) /* device supports PCM input/output via USB */ +#define LINE6_BIT_CONTROL_PCM (LINE6_BIT_CONTROL | LINE6_BIT_PCM) + +#define LINE6_FALLBACK_INTERVAL 10 +#define LINE6_FALLBACK_MAXPACKETSIZE 16 + + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 18) +#define usb_interrupt_msg(usb_dev, pipe, data, len, actual_length, timeout) \ +usb_bulk_msg(usb_dev, pipe, data, len, actual_length, timeout) +#endif + + +#endif diff --git a/drivers/staging/line6/variax.c b/drivers/staging/line6/variax.c new file mode 100644 index 000000000000..edb02a39d820 --- /dev/null +++ b/drivers/staging/line6/variax.c @@ -0,0 +1,501 @@ +/* + * Line6 Linux USB driver - 0.8.0 + * + * Copyright (C) 2004-2009 Markus Grabner (grabner@icg.tugraz.at) + * + * 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, version 2. + * + */ + +#include "driver.h" + +#include "audio.h" +#include "control.h" +#include "variax.h" + + +#define VARIAX_SYSEX_CODE 7 +#define VARIAX_SYSEX_PARAM 0x3b +#define VARIAX_SYSEX_ACTIVATE 0x2a +#define VARIAX_MODEL_HEADER_LENGTH 7 +#define VARIAX_MODEL_MESSAGE_LENGTH 199 +#define VARIAX_OFFSET_ACTIVATE 7 + + +static const char variax_activate[] = { + 0xf0, 0x00, 0x01, 0x0c, 0x07, 0x00, 0x2a, 0x01, + 0xf7 +}; +static const char variax_request_bank[] = { + 0xf0, 0x00, 0x01, 0x0c, 0x07, 0x00, 0x6d, 0xf7 +}; +static const char variax_request_model1[] = { + 0xf0, 0x00, 0x01, 0x0c, 0x07, 0x00, 0x3c, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x00, 0x03, 0x05, 0x03, + 0x00, 0x00, 0x00, 0xf7 +}; +static const char variax_request_model2[] = { + 0xf0, 0x00, 0x01, 0x0c, 0x07, 0x00, 0x3c, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x00, 0x03, 0x07, 0x03, + 0x00, 0x00, 0x00, 0xf7 +}; + + +/* + Decode data transmitted by workbench. +*/ +static void variax_decode(const unsigned char *raw_data, unsigned char *data, int raw_size) +{ + for(; raw_size > 0; raw_size -= 6) { + data[2] = raw_data[0] | (raw_data[1] << 4); + data[1] = raw_data[2] | (raw_data[3] << 4); + data[0] = raw_data[4] | (raw_data[5] << 4); + raw_data += 6; + data += 3; + } +} + +static void variax_activate_timeout(unsigned long arg) +{ + struct usb_line6_variax *variax = (struct usb_line6_variax *)arg; + variax->buffer_activate[VARIAX_OFFSET_ACTIVATE] = 1; + line6_send_raw_message_async(&variax->line6, variax->buffer_activate, sizeof(variax_activate)); +} + +/* + Send an asynchronous activation request after a given interval. +*/ +static void variax_activate_delayed(struct usb_line6_variax *variax, int seconds) +{ + variax->activate_timer.expires = jiffies + seconds * HZ; + variax->activate_timer.function = variax_activate_timeout; + variax->activate_timer.data = (unsigned long)variax; + add_timer(&variax->activate_timer); +} + +static void variax_startup_timeout(unsigned long arg) +{ + struct usb_line6_variax *variax = (struct usb_line6_variax *)arg; + + if(variax->dumpreq.ok) + return; + + line6_dump_request_async(&variax->dumpreq, &variax->line6, 0); + line6_startup_delayed(&variax->dumpreq, 1, variax_startup_timeout, variax); +} + +/* + Process a completely received message. +*/ +void variax_process_message(struct usb_line6_variax *variax) +{ + const unsigned char *buf = variax->line6.buffer_message; + + switch(buf[0]) { + case LINE6_PARAM_CHANGE | LINE6_CHANNEL_HOST: + switch(buf[1]) { + case VARIAXMIDI_volume: + variax->volume = buf[2]; + break; + + case VARIAXMIDI_tone: + variax->tone = buf[2]; + } + + break; + + case LINE6_PROGRAM_CHANGE | LINE6_CHANNEL_DEVICE: + case LINE6_PROGRAM_CHANGE | LINE6_CHANNEL_HOST: + variax->model = buf[1]; + line6_dump_request_async(&variax->dumpreq, &variax->line6, 0); + break; + + case LINE6_RESET: + dev_info(variax->line6.ifcdev, "VARIAX reset\n"); + variax_activate_delayed(variax, VARIAX_ACTIVATE_DELAY); + break; + + case LINE6_SYSEX_BEGIN: + if(memcmp(buf + 1, variax_request_model1 + 1, VARIAX_MODEL_HEADER_LENGTH - 1) == 0) { + if(variax->line6.message_length == VARIAX_MODEL_MESSAGE_LENGTH) { + switch(variax->dumpreq.in_progress) { + case VARIAX_DUMP_PASS1: + variax_decode(buf + VARIAX_MODEL_HEADER_LENGTH, (unsigned char *)&variax->model_data, + (sizeof(variax->model_data.name) + sizeof(variax->model_data.control) / 2) * 2); + line6_dump_request_async(&variax->dumpreq, &variax->line6, 1); + line6_dump_started(&variax->dumpreq, VARIAX_DUMP_PASS2); + break; + + case VARIAX_DUMP_PASS2: + /* model name is transmitted twice, so skip it here: */ + variax_decode(buf + VARIAX_MODEL_HEADER_LENGTH, + (unsigned char *)&variax->model_data.control + sizeof(variax->model_data.control) / 2, + sizeof(variax->model_data.control) / 2 * 2); + variax->dumpreq.ok = 1; + line6_dump_request_async(&variax->dumpreq, &variax->line6, 2); + line6_dump_started(&variax->dumpreq, VARIAX_DUMP_PASS3); + } + } + else { + DEBUG_MESSAGES(dev_err(variax->line6.ifcdev, "illegal length %d of model data\n", variax->line6.message_length)); + line6_dump_finished(&variax->dumpreq); + } + } + else if(memcmp(buf + 1, variax_request_bank + 1, sizeof(variax_request_bank) - 2) == 0) { + memcpy(variax->bank, buf + sizeof(variax_request_bank) - 1, sizeof(variax->bank)); + variax->dumpreq.ok = 1; + line6_dump_finished(&variax->dumpreq); + } + + break; + + case LINE6_SYSEX_END: + break; + + default: + DEBUG_MESSAGES(dev_err(variax->line6.ifcdev, "Variax: unknown message %02X\n", buf[0])); + } +} + +/* + "read" request on "volume" special file. +*/ +static ssize_t variax_get_volume(struct device *dev, DEVICE_ATTRIBUTE char *buf) +{ + struct usb_line6_variax *variax = usb_get_intfdata(to_usb_interface(dev)); + return sprintf(buf, "%d\n", variax->volume); +} + +/* + "write" request on "volume" special file. +*/ +static ssize_t variax_set_volume(struct device *dev, DEVICE_ATTRIBUTE const char *buf, size_t count) +{ + struct usb_line6_variax *variax = usb_get_intfdata(to_usb_interface(dev)); + int value = simple_strtoul(buf, NULL, 10); + + if(line6_transmit_parameter(&variax->line6, VARIAXMIDI_volume, value) == 0) + variax->volume = value; + + return count; +} + +/* + "read" request on "model" special file. +*/ +static ssize_t variax_get_model(struct device *dev, DEVICE_ATTRIBUTE char *buf) +{ + struct usb_line6_variax *variax = usb_get_intfdata(to_usb_interface(dev)); + return sprintf(buf, "%d\n", variax->model); +} + +/* + "write" request on "model" special file. +*/ +static ssize_t variax_set_model(struct device *dev, DEVICE_ATTRIBUTE const char *buf, size_t count) +{ + struct usb_line6_variax *variax = usb_get_intfdata( to_usb_interface(dev)); + int value = simple_strtoul(buf, NULL, 10); + + if(line6_send_program(&variax->line6, value) == 0) + variax->model = value; + + return count; +} + +/* + "read" request on "active" special file. +*/ +static ssize_t variax_get_active(struct device *dev, DEVICE_ATTRIBUTE char *buf) +{ + struct usb_line6_variax *variax = usb_get_intfdata(to_usb_interface(dev)); + return sprintf(buf, "%d\n", variax->buffer_activate[VARIAX_OFFSET_ACTIVATE]); +} + +/* + "write" request on "active" special file. +*/ +static ssize_t variax_set_active(struct device *dev, DEVICE_ATTRIBUTE const char *buf, size_t count) +{ + struct usb_line6_variax *variax = usb_get_intfdata(to_usb_interface(dev)); + int value = simple_strtoul(buf, NULL, 10) ? 1 : 0; + variax->buffer_activate[VARIAX_OFFSET_ACTIVATE] = value; + line6_send_raw_message_async(&variax->line6, variax->buffer_activate, sizeof(variax_activate)); + return count; +} + +/* + "read" request on "tone" special file. +*/ +static ssize_t variax_get_tone(struct device *dev, DEVICE_ATTRIBUTE char *buf) +{ + struct usb_line6_variax *variax = usb_get_intfdata(to_usb_interface(dev)); + return sprintf(buf, "%d\n", variax->tone); +} + +/* + "write" request on "tone" special file. +*/ +static ssize_t variax_set_tone(struct device *dev, DEVICE_ATTRIBUTE const char *buf, size_t count) +{ + struct usb_line6_variax *variax = usb_get_intfdata(to_usb_interface(dev)); + int value = simple_strtoul(buf, NULL, 10); + + if(line6_transmit_parameter(&variax->line6, VARIAXMIDI_tone, value) == 0) + variax->tone = value; + + return count; +} + +static ssize_t get_string(char *buf, const char *data, int length) +{ + int i; + memcpy(buf, data, length); + + for(i = length; i--;) { + char c = buf[i]; + + if((c != 0) && (c != ' ')) + break; + } + + buf[i + 1] = '\n'; + return i + 2; +} + +/* + "read" request on "name" special file. +*/ +static ssize_t variax_get_name(struct device *dev, DEVICE_ATTRIBUTE char *buf) +{ + struct usb_line6_variax *variax = usb_get_intfdata(to_usb_interface(dev)); + line6_wait_dump(&variax->dumpreq, 0); + return get_string(buf, variax->model_data.name, sizeof(variax->model_data.name)); +} + +/* + "read" request on "bank" special file. +*/ +static ssize_t variax_get_bank(struct device *dev, DEVICE_ATTRIBUTE char *buf) +{ + struct usb_line6_variax *variax = usb_get_intfdata(to_usb_interface(dev)); + line6_wait_dump(&variax->dumpreq, 0); + return get_string(buf, variax->bank, sizeof(variax->bank)); +} + +/* + "read" request on "dump" special file. +*/ +static ssize_t variax_get_dump(struct device *dev, DEVICE_ATTRIBUTE char *buf) +{ + struct usb_line6_variax *variax = usb_get_intfdata(to_usb_interface(dev)); + int retval; + retval = line6_wait_dump(&variax->dumpreq, 0); + if(retval < 0) return retval; + memcpy(buf, &variax->model_data.control, sizeof(variax->model_data.control)); + return sizeof(variax->model_data.control); +} + +#if CREATE_RAW_FILE + +/* + "write" request on "raw" special file. +*/ +static ssize_t variax_set_raw2(struct device *dev, DEVICE_ATTRIBUTE const char *buf, size_t count) +{ + struct usb_line6_variax *variax = usb_get_intfdata(to_usb_interface(dev)); + int size; + int i; + char *sysex; + + count -= count % 3; + size = count * 2; + sysex = variax_alloc_sysex_buffer(variax, VARIAX_SYSEX_PARAM, size); + + if(!sysex) + return 0; + + for(i = 0; i < count; i += 3) { + const unsigned char *p1 = buf + i; + char *p2 = sysex + SYSEX_DATA_OFS + i * 2; + p2[0] = p1[2] & 0x0f; + p2[1] = p1[2] >> 4; + p2[2] = p1[1] & 0x0f; + p2[3] = p1[1] >> 4; + p2[4] = p1[0] & 0x0f; + p2[5] = p1[0] >> 4; + } + + line6_send_sysex_message(&variax->line6, sysex, size); + kfree(sysex); + return count; +} + +#endif + +/* Variax workbench special files: */ +static DEVICE_ATTR(model, S_IWUGO | S_IRUGO, variax_get_model, variax_set_model); +static DEVICE_ATTR(volume, S_IWUGO | S_IRUGO, variax_get_volume, variax_set_volume); +static DEVICE_ATTR(tone, S_IWUGO | S_IRUGO, variax_get_tone, variax_set_tone); +static DEVICE_ATTR(name, S_IRUGO, variax_get_name, line6_nop_write); +static DEVICE_ATTR(bank, S_IRUGO, variax_get_bank, line6_nop_write); +static DEVICE_ATTR(dump, S_IRUGO, variax_get_dump, line6_nop_write); +static DEVICE_ATTR(active, S_IWUGO | S_IRUGO, variax_get_active, variax_set_active); + +#if CREATE_RAW_FILE +static DEVICE_ATTR(raw, S_IWUGO, line6_nop_read, line6_set_raw); +static DEVICE_ATTR(raw2, S_IWUGO, line6_nop_read, variax_set_raw2); +#endif + + +/* + Variax destructor. +*/ +static void variax_destruct(struct usb_interface *interface) +{ + struct usb_line6_variax *variax = usb_get_intfdata(interface); + struct usb_line6 *line6; + + if(variax == NULL) return; + line6 = &variax->line6; + if(line6 == NULL) return; + line6_cleanup_audio(line6); + + /* free dump request data: */ + line6_dumpreq_destructbuf(&variax->dumpreq, 2); + line6_dumpreq_destructbuf(&variax->dumpreq, 1); + line6_dumpreq_destruct(&variax->dumpreq); + + if(variax->buffer_activate) kfree(variax->buffer_activate); + del_timer_sync(&variax->activate_timer); +} + +/* + Create sysfs entries. +*/ +int variax_create_files2(struct device *dev) +{ + int err; + CHECK_RETURN(device_create_file(dev, &dev_attr_model)); + CHECK_RETURN(device_create_file(dev, &dev_attr_volume)); + CHECK_RETURN(device_create_file(dev, &dev_attr_tone)); + CHECK_RETURN(device_create_file(dev, &dev_attr_name)); + CHECK_RETURN(device_create_file(dev, &dev_attr_bank)); + CHECK_RETURN(device_create_file(dev, &dev_attr_dump)); + CHECK_RETURN(device_create_file(dev, &dev_attr_active)); +#if CREATE_RAW_FILE + CHECK_RETURN(device_create_file(dev, &dev_attr_raw)); + CHECK_RETURN(device_create_file(dev, &dev_attr_raw2)); +#endif + return 0; +} + +/* + Init workbench device. +*/ +int variax_init(struct usb_interface *interface, struct usb_line6_variax *variax) +{ + int err; + + if((interface == NULL) || (variax == NULL)) return -ENODEV; + + /* initialize USB buffers: */ + err = line6_dumpreq_init(&variax->dumpreq, variax_request_model1, sizeof(variax_request_model1)); + + if(err < 0) { + dev_err(&interface->dev, "Out of memory\n"); + variax_destruct(interface); + return err; + } + + err = line6_dumpreq_initbuf(&variax->dumpreq, variax_request_model2, sizeof(variax_request_model2), 1); + + if(err < 0) { + dev_err(&interface->dev, "Out of memory\n"); + variax_destruct(interface); + return err; + } + + err = line6_dumpreq_initbuf(&variax->dumpreq, variax_request_bank, sizeof(variax_request_bank), 2); + + if(err < 0) { + dev_err(&interface->dev, "Out of memory\n"); + variax_destruct(interface); + return err; + } + + variax->buffer_activate = kmalloc(sizeof(variax_activate), GFP_KERNEL); + + if(variax->buffer_activate == NULL) { + dev_err(&interface->dev, "Out of memory\n"); + variax_destruct(interface); + return -ENOMEM; + } + + memcpy(variax->buffer_activate, variax_activate, sizeof(variax_activate)); + init_timer(&variax->activate_timer); + + /* create sysfs entries: */ + if((err = variax_create_files(0, 0, &interface->dev)) < 0) { + variax_destruct(interface); + return err; + } + + if((err = variax_create_files2(&interface->dev)) < 0) { + variax_destruct(interface); + return err; + } + + /* initialize audio system: */ + if((err = line6_init_audio(&variax->line6)) < 0) { + variax_destruct(interface); + return err; + } + + /* initialize MIDI subsystem: */ + if((err = line6_init_midi(&variax->line6)) < 0) { + variax_destruct(interface); + return err; + } + + /* register audio system: */ + if((err = line6_register_audio(&variax->line6)) < 0) { + variax_destruct(interface); + return err; + } + + variax_activate_delayed(variax, VARIAX_ACTIVATE_DELAY); + line6_startup_delayed(&variax->dumpreq, VARIAX_STARTUP_DELAY, variax_startup_timeout, variax); + return 0; +} + +/* + Workbench device disconnected. +*/ +void variax_disconnect(struct usb_interface *interface) +{ + struct device *dev; + + if(interface == NULL) return; + dev = &interface->dev; + + if(dev != NULL) { + /* remove sysfs entries: */ + variax_remove_files(0, 0, dev); + device_remove_file(dev, &dev_attr_model); + device_remove_file(dev, &dev_attr_volume); + device_remove_file(dev, &dev_attr_tone); + device_remove_file(dev, &dev_attr_name); + device_remove_file(dev, &dev_attr_bank); + device_remove_file(dev, &dev_attr_dump); + device_remove_file(dev, &dev_attr_active); +#if CREATE_RAW_FILE + device_remove_file(dev, &dev_attr_raw); + device_remove_file(dev, &dev_attr_raw2); +#endif + } + + variax_destruct(interface); +} diff --git a/drivers/staging/line6/variax.h b/drivers/staging/line6/variax.h new file mode 100644 index 000000000000..286df248c816 --- /dev/null +++ b/drivers/staging/line6/variax.h @@ -0,0 +1,107 @@ +/* + * Line6 Linux USB driver - 0.8.0 + * + * Copyright (C) 2004-2009 Markus Grabner (grabner@icg.tugraz.at) + * + * 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, version 2. + * + */ + +#ifndef VARIAX_H +#define VARIAX_H + + +#include "driver.h" + +#include <linux/spinlock.h> +#include <linux/usb.h> +#include <linux/wait.h> + +#include <sound/core.h> + +#include "dumprequest.h" + + +#define VARIAX_ACTIVATE_DELAY 10 +#define VARIAX_STARTUP_DELAY 3 + + +enum { + VARIAX_DUMP_PASS1 = LINE6_DUMP_CURRENT, + VARIAX_DUMP_PASS2, + VARIAX_DUMP_PASS3 +}; + + +/** + Binary Variax model dump +*/ +struct variax_model { + /** + Header information (including program name). + */ + unsigned char name[18]; + + /** + Model parameters. + */ + unsigned char control[78 * 2]; +}; + +struct usb_line6_variax { + /** + Generic Line6 USB data. + */ + struct usb_line6 line6; + + /** + Dump request structure. + Append two extra buffers for 3-pass data query. + */ + struct line6_dump_request dumpreq; struct line6_dump_reqbuf extrabuf[2]; + + /** + Buffer for activation code. + */ + unsigned char *buffer_activate; + + /** + Model number. + */ + int model; + + /** + Current model settings. + */ + struct variax_model model_data; + + /** + Name of current model bank. + */ + unsigned char bank[18]; + + /** + Position of volume dial. + */ + int volume; + + /** + Position of tone control dial. + */ + int tone; + + /** + Timer for delayed activation request. + */ + struct timer_list activate_timer; +}; + + +extern void variax_disconnect(struct usb_interface *interface); +extern int variax_init(struct usb_interface *interface, struct usb_line6_variax *variax); +extern void variax_process_message(struct usb_line6_variax *variax); + + +#endif |