Staging: add line6 usb driver

This is an experimental Linux driver for the guitar amp, cab, and
effects modeller PODxt Pro by Line6 (and similar devices), supporting
the following features:

  - Reading/writing individual parameters
  - Reading/writing complete channel, effects setup, and amp setup data
  - Channel switching
  - Virtual MIDI interface
  - Tuner access
  - Playback/capture/mixer device for any  ALSA-compatible PCM audio
    application
  - Signal routing (record clean/processed  guitar signal, re-amping)

Moreover, preliminary support for the Variax Workbench is included.

From: Markus Grabner <grabner@icg.tugraz.at>
Cc: Mariusz Kozlowski <m.kozlowski@tuxland.pl>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>

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