]> www.pilppa.org Git - linux-2.6-omap-h63xx.git/commitdiff
Staging: add line6 usb driver
authorMarkus Grabner <grabner@icg.tugraz.at>
Sat, 28 Feb 2009 03:43:04 +0000 (19:43 -0800)
committerGreg Kroah-Hartman <gregkh@suse.de>
Fri, 3 Apr 2009 21:54:24 +0000 (14:54 -0700)
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:
drivers/staging/line6/Kconfig [new file with mode: 0644]
drivers/staging/line6/Makefile [new file with mode: 0644]
drivers/staging/line6/audio.c [new file with mode: 0644]
drivers/staging/line6/audio.h [new file with mode: 0644]
drivers/staging/line6/capture.c [new file with mode: 0644]
drivers/staging/line6/capture.h [new file with mode: 0644]
drivers/staging/line6/config.h [new file with mode: 0644]
drivers/staging/line6/control.c [new file with mode: 0644]
drivers/staging/line6/control.h [new file with mode: 0644]
drivers/staging/line6/driver.c [new file with mode: 0644]
drivers/staging/line6/driver.h [new file with mode: 0644]
drivers/staging/line6/dumprequest.c [new file with mode: 0644]
drivers/staging/line6/dumprequest.h [new file with mode: 0644]
drivers/staging/line6/midi.c [new file with mode: 0644]
drivers/staging/line6/midi.h [new file with mode: 0644]
drivers/staging/line6/midibuf.c [new file with mode: 0644]
drivers/staging/line6/midibuf.h [new file with mode: 0644]
drivers/staging/line6/pcm.c [new file with mode: 0644]
drivers/staging/line6/pcm.h [new file with mode: 0644]
drivers/staging/line6/playback.c [new file with mode: 0644]
drivers/staging/line6/playback.h [new file with mode: 0644]
drivers/staging/line6/pod.c [new file with mode: 0644]
drivers/staging/line6/pod.h [new file with mode: 0644]
drivers/staging/line6/revision.h [new file with mode: 0644]
drivers/staging/line6/toneport.c [new file with mode: 0644]
drivers/staging/line6/toneport.h [new file with mode: 0644]
drivers/staging/line6/usbdefs.h [new file with mode: 0644]
drivers/staging/line6/variax.c [new file with mode: 0644]
drivers/staging/line6/variax.h [new file with mode: 0644]

diff --git a/drivers/staging/line6/Kconfig b/drivers/staging/line6/Kconfig
new file mode 100644 (file)
index 0000000..3c1ffcb
--- /dev/null
@@ -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 (file)
index 0000000..a1c93ed
--- /dev/null
@@ -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 (file)
index 0000000..e15fa1f
--- /dev/null
@@ -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 (file)
index 0000000..cc0245a
--- /dev/null
@@ -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 (file)
index 0000000..5dec3bf
--- /dev/null
@@ -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 (file)
index 0000000..7b92e4d
--- /dev/null
@@ -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 (file)
index 0000000..d5ed1a7
--- /dev/null
@@ -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 (file)
index 0000000..d44d06d
--- /dev/null
@@ -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 (file)
index 0000000..2f19665
--- /dev/null
@@ -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 (file)
index 0000000..f20efa5
--- /dev/null
@@ -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 (file)
index 0000000..e5179d9
--- /dev/null
@@ -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 (file)
index 0000000..89bc099
--- /dev/null
@@ -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 (file)
index 0000000..e0b38bb
--- /dev/null
@@ -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 (file)
index 0000000..74489ee
--- /dev/null
@@ -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 (file)
index 0000000..be05a54
--- /dev/null
@@ -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 (file)
index 0000000..2f86c66
--- /dev/null
@@ -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 (file)
index 0000000..0e7762c
--- /dev/null
@@ -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 (file)
index 0000000..725184b
--- /dev/null
@@ -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 (file)
index 0000000..90f8bb9
--- /dev/null
@@ -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 (file)
index 0000000..f6503c2
--- /dev/null
@@ -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 (file)
index 0000000..019c40f
--- /dev/null
@@ -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 (file)
index 0000000..154985a
--- /dev/null
@@ -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(&param->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(&param->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 (file)
index 0000000..0db5948
--- /dev/null
@@ -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 (file)
index 0000000..b2a0a85
--- /dev/null
@@ -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 (file)
index 0000000..c9fe07f
--- /dev/null
@@ -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 (file)
index 0000000..cd0b19f
--- /dev/null
@@ -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 (file)
index 0000000..cbf5d0d
--- /dev/null
@@ -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 (file)
index 0000000..edb02a3
--- /dev/null
@@ -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 (file)
index 0000000..286df24
--- /dev/null
@@ -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