staging: line6: sync with upstream

Big upstream sync.

Signed-off-by: Markus Grabner <grabner@icg.tugraz.at>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
diff --git a/drivers/staging/line6/pcm.c b/drivers/staging/line6/pcm.c
index fbe4b08..0eac291 100644
--- a/drivers/staging/line6/pcm.c
+++ b/drivers/staging/line6/pcm.c
@@ -1,7 +1,7 @@
 /*
- * Line6 Linux USB driver - 0.8.0
+ * Line6 Linux USB driver - 0.9.0
  *
- * Copyright (C) 2004-2009 Markus Grabner (grabner@icg.tugraz.at)
+ * Copyright (C) 2004-2010 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
@@ -9,10 +9,7 @@
  *
  */
 
-#include "driver.h"
-
 #include <linux/slab.h>
-
 #include <sound/core.h>
 #include <sound/control.h>
 #include <sound/pcm.h>
@@ -20,10 +17,176 @@
 
 #include "audio.h"
 #include "capture.h"
+#include "driver.h"
 #include "playback.h"
 #include "pod.h"
 
 
+#ifdef CONFIG_LINE6_USB_IMPULSE_RESPONSE
+
+static struct snd_line6_pcm* dev2pcm(struct device *dev)
+{
+	struct usb_interface *interface = to_usb_interface(dev);
+	struct usb_line6 *line6 = usb_get_intfdata(interface);
+	struct snd_line6_pcm *line6pcm = line6->line6pcm;
+	return line6pcm;
+}
+
+/*
+	"read" request on "impulse_volume" special file.
+*/
+static ssize_t pcm_get_impulse_volume(struct device *dev,
+				      struct device_attribute *attr,
+				      char *buf)
+{
+	return sprintf(buf, "%d\n", dev2pcm(dev)->impulse_volume);
+}
+
+/*
+	"write" request on "impulse_volume" special file.
+*/
+static ssize_t pcm_set_impulse_volume(struct device *dev,
+				      struct device_attribute *attr,
+				      const char *buf, size_t count)
+{
+	struct snd_line6_pcm *line6pcm = dev2pcm(dev);
+	int value = simple_strtoul(buf, NULL, 10);
+	line6pcm->impulse_volume = value;
+
+	if(value > 0)
+		line6_pcm_start(line6pcm, MASK_PCM_IMPULSE);
+	else
+		line6_pcm_stop(line6pcm, MASK_PCM_IMPULSE);
+
+	return count;
+}
+
+/*
+	"read" request on "impulse_period" special file.
+*/
+static ssize_t pcm_get_impulse_period(struct device *dev,
+				      struct device_attribute *attr,
+				      char *buf)
+{
+	return sprintf(buf, "%d\n", dev2pcm(dev)->impulse_period);
+}
+
+/*
+	"write" request on "impulse_period" special file.
+*/
+static ssize_t pcm_set_impulse_period(struct device *dev,
+				      struct device_attribute *attr,
+				      const char *buf, size_t count)
+{
+	dev2pcm(dev)->impulse_period = simple_strtoul(buf, NULL, 10);
+	return count;
+}
+
+static DEVICE_ATTR(impulse_volume, S_IWUGO | S_IRUGO, pcm_get_impulse_volume, pcm_set_impulse_volume);
+static DEVICE_ATTR(impulse_period, S_IWUGO | S_IRUGO, pcm_get_impulse_period, pcm_set_impulse_period);
+
+#endif
+
+int line6_pcm_start(struct snd_line6_pcm *line6pcm, int channels)
+{
+	unsigned long flags_old = __sync_fetch_and_or(&line6pcm->flags, channels);
+	unsigned long flags_new = flags_old | channels;
+	int err = 0;
+
+#if LINE6_BACKUP_MONITOR_SIGNAL
+	if (!(line6pcm->line6->properties->capabilities & LINE6_BIT_HWMON)) {
+		line6pcm->prev_fbuf = kmalloc(LINE6_ISO_PACKETS * line6pcm->max_packet_size, GFP_KERNEL);
+
+		if (!line6pcm->prev_fbuf) {
+			dev_err(line6pcm->line6->ifcdev, "cannot malloc monitor buffer\n");
+			return -ENOMEM;
+		}
+	}
+#else
+	line6pcm->prev_fbuf = NULL;
+#endif
+		
+	if (((flags_old & MASK_CAPTURE) == 0) &&
+	    ((flags_new & MASK_CAPTURE) != 0)) {
+		/*
+		  Waiting for completion of active URBs in the stop handler is
+		  a bug, we therefore report an error if capturing is restarted
+		  too soon.
+		*/
+		if(line6pcm->active_urb_in | line6pcm->unlink_urb_in)
+			return -EBUSY;
+
+		line6pcm->buffer_in = kmalloc(LINE6_ISO_BUFFERS * LINE6_ISO_PACKETS * line6pcm->max_packet_size, GFP_KERNEL);
+
+		if (!line6pcm->buffer_in) {
+			dev_err(line6pcm->line6->ifcdev, "cannot malloc capture buffer\n");
+			return -ENOMEM;
+		}
+
+		line6pcm->count_in = 0;
+		line6pcm->prev_fsize = 0;
+		err = line6_submit_audio_in_all_urbs(line6pcm);
+		
+		if (err < 0) {
+			__sync_fetch_and_and(&line6pcm->flags, ~channels);
+			return err;
+		}
+	}
+	
+	if (((flags_old & MASK_PLAYBACK) == 0) &&
+	    ((flags_new & MASK_PLAYBACK) != 0)) {
+		/*
+		  See comment above regarding PCM restart.
+		*/
+		if(line6pcm->active_urb_out | line6pcm->unlink_urb_out)
+			return -EBUSY;
+
+		line6pcm->buffer_out = kmalloc(LINE6_ISO_BUFFERS * LINE6_ISO_PACKETS * line6pcm->max_packet_size, GFP_KERNEL);
+
+		if (!line6pcm->buffer_out) {
+			dev_err(line6pcm->line6->ifcdev, "cannot malloc playback buffer\n");
+			return -ENOMEM;
+		}
+
+		line6pcm->count_out = 0;
+		err = line6_submit_audio_out_all_urbs(line6pcm);
+		
+		if (err < 0) {
+			__sync_fetch_and_and(&line6pcm->flags, ~channels);
+			return err;
+		}
+	}
+	
+	return 0;
+}
+
+int line6_pcm_stop(struct snd_line6_pcm *line6pcm, int channels)
+{
+	unsigned long flags_old = __sync_fetch_and_and(&line6pcm->flags, ~channels);
+	unsigned long flags_new = flags_old & ~channels;
+
+	if (((flags_old & MASK_CAPTURE) != 0) &&
+	    ((flags_new & MASK_CAPTURE) == 0)) {
+		line6_unlink_audio_in_urbs(line6pcm);
+		kfree(line6pcm->buffer_in);
+		line6pcm->buffer_in = NULL;
+	}
+
+	if (((flags_old & MASK_PLAYBACK) != 0) &&
+	    ((flags_new & MASK_PLAYBACK) == 0)) {
+		line6_unlink_audio_out_urbs(line6pcm);
+		kfree(line6pcm->buffer_out);
+		line6pcm->buffer_out = NULL;
+	}
+
+#if LINE6_BACKUP_MONITOR_SIGNAL
+	if (line6pcm->prev_fbuf != NULL)
+		kfree(line6pcm->prev_fbuf);
+#endif
+
+	return 0;
+}
+
 /* trigger callback */
 int snd_line6_trigger(struct snd_pcm_substream *substream, int cmd)
 {
@@ -38,7 +201,7 @@
 	snd_pcm_group_for_each_entry(s, substream) {
 		switch (s->stream) {
 		case SNDRV_PCM_STREAM_PLAYBACK:
-			err = snd_line6_playback_trigger(s, cmd);
+			err = snd_line6_playback_trigger(line6pcm, cmd);
 
 			if (err < 0) {
 				spin_unlock_irqrestore(&line6pcm->lock_trigger,
@@ -49,7 +212,7 @@
 			break;
 
 		case SNDRV_PCM_STREAM_CAPTURE:
-			err = snd_line6_capture_trigger(s, cmd);
+			err = snd_line6_capture_trigger(line6pcm, cmd);
 
 			if (err < 0) {
 				spin_unlock_irqrestore(&line6pcm->lock_trigger,
@@ -60,7 +223,7 @@
 			break;
 
 		default:
-			dev_err(s2m(substream), "Unknown stream direction %d\n",
+			dev_err(line6pcm->line6->ifcdev, "Unknown stream direction %d\n",
 				s->stream);
 		}
 	}
@@ -70,8 +233,8 @@
 }
 
 /* control info callback */
-static int snd_line6_control_info(struct snd_kcontrol *kcontrol,
-				  struct snd_ctl_elem_info *uinfo)
+static int snd_line6_control_playback_info(struct snd_kcontrol *kcontrol,
+					   struct snd_ctl_elem_info *uinfo)
 {
 	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
 	uinfo->count = 2;
@@ -81,28 +244,28 @@
 }
 
 /* control get callback */
-static int snd_line6_control_get(struct snd_kcontrol *kcontrol,
-				 struct snd_ctl_elem_value *ucontrol)
+static int snd_line6_control_playback_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];
+		ucontrol->value.integer.value[i] = line6pcm->volume_playback[i];
 
 	return 0;
 }
 
 /* control put callback */
-static int snd_line6_control_put(struct snd_kcontrol *kcontrol,
-				 struct snd_ctl_elem_value *ucontrol)
+static int snd_line6_control_playback_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];
+		if (line6pcm->volume_playback[i] != ucontrol->value.integer.value[i]) {
+			line6pcm->volume_playback[i] = ucontrol->value.integer.value[i];
 			changed = 1;
 		}
 
@@ -110,14 +273,14 @@
 }
 
 /* control definition */
-static struct snd_kcontrol_new line6_control = {
+static struct snd_kcontrol_new line6_control_playback = {
 	.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
+	.info = snd_line6_control_playback_info,
+	.get = snd_line6_control_playback_get,
+	.put = snd_line6_control_playback_put
 };
 
 /*
@@ -128,6 +291,11 @@
 	int i;
 	struct snd_line6_pcm *line6pcm = snd_pcm_chip(pcm);
 
+#ifdef CONFIG_LINE6_USB_IMPULSE_RESPONSE
+	device_remove_file(line6pcm->line6->ifcdev, &dev_attr_impulse_volume);
+	device_remove_file(line6pcm->line6->ifcdev, &dev_attr_impulse_period);
+#endif
+
 	for (i = LINE6_ISO_BUFFERS; i--;) {
 		if (line6pcm->urb_audio_out[i]) {
 			usb_kill_urb(line6pcm->urb_audio_out[i]);
@@ -160,7 +328,8 @@
 	/* 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);
+	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,
@@ -177,6 +346,27 @@
 }
 
 /*
+	Stop substream if still running.
+*/
+static void pcm_disconnect_substream(struct snd_pcm_substream *substream)
+{
+	if(substream->runtime && snd_pcm_running(substream)) {
+		snd_pcm_stop(substream, SNDRV_PCM_STATE_DISCONNECTED);
+	}
+}
+
+/*
+	Stop PCM stream.
+*/
+void line6_pcm_disconnect(struct snd_line6_pcm *line6pcm)
+{
+	pcm_disconnect_substream(get_substream(line6pcm, SNDRV_PCM_STREAM_CAPTURE));
+	pcm_disconnect_substream(get_substream(line6pcm, SNDRV_PCM_STREAM_PLAYBACK));
+	line6_unlink_wait_clear_audio_out_urbs(line6pcm);
+	line6_unlink_wait_clear_audio_in_urbs(line6pcm);
+}
+
+/*
 	Create and register the PCM device and mixer entries.
 	Create URBs for playback and capture.
 */
@@ -218,20 +408,23 @@
 		break;
 
 	case LINE6_DEVID_GUITARPORT:
+	case LINE6_DEVID_PODSTUDIO_GX:
+	case LINE6_DEVID_PODSTUDIO_UX1:
+	case LINE6_DEVID_PODSTUDIO_UX2:
 	case LINE6_DEVID_TONEPORT_GX:
+	case LINE6_DEVID_TONEPORT_UX1:
+	case LINE6_DEVID_TONEPORT_UX2:
 		ep_read  = 0x82;
 		ep_write = 0x01;
 		break;
 
-	case LINE6_DEVID_TONEPORT_UX1:
-		ep_read  = 0x00;
-		ep_write = 0x00;
-		break;
-
+		/* this is for interface_number == 1:
 	case LINE6_DEVID_TONEPORT_UX2:
+	case LINE6_DEVID_PODSTUDIO_UX2:
 		ep_read  = 0x87;
 		ep_write = 0x00;
 		break;
+		*/
 
 	default:
 		MISSING_CASE;
@@ -242,12 +435,13 @@
 	if (line6pcm == NULL)
 		return -ENOMEM;
 
-	line6pcm->volume[0] = line6pcm->volume[1] = 128;
+	line6pcm->volume_playback[0] = line6pcm->volume_playback[1] = 255;
+	line6pcm->volume_monitor = 255;
 	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,
+						  usb_rcvintpipe(line6->usbdev,
 								ep_read),
 						  0);
 	line6pcm->properties = properties;
@@ -268,19 +462,32 @@
 	spin_lock_init(&line6pcm->lock_audio_in);
 	spin_lock_init(&line6pcm->lock_trigger);
 
-	err = create_audio_out_urbs(line6pcm);
+	err = line6_create_audio_out_urbs(line6pcm);
 	if (err < 0)
 		return err;
 
-	err = create_audio_in_urbs(line6pcm);
+	err = line6_create_audio_in_urbs(line6pcm);
 	if (err < 0)
 		return err;
 
 	/* mixer: */
-	err = snd_ctl_add(line6->card, snd_ctl_new1(&line6_control, line6pcm));
+	err = snd_ctl_add(line6->card, snd_ctl_new1(&line6_control_playback, line6pcm));
 	if (err < 0)
 		return err;
 
+#ifdef CONFIG_LINE6_USB_IMPULSE_RESPONSE
+	/* impulse response test: */
+	err = device_create_file(line6->ifcdev, &dev_attr_impulse_volume);
+	if (err < 0)
+		return err;
+
+	err = device_create_file(line6->ifcdev, &dev_attr_impulse_period);
+	if (err < 0)
+		return err;
+
+	line6pcm->impulse_period = LINE6_IMPULSE_DEFAULT_PERIOD;
+#endif
+
 	return 0;
 }
 
@@ -290,12 +497,11 @@
 	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->count_out = 0;
 		line6pcm->pos_out = 0;
 		line6pcm->pos_out_done = 0;
-
-		unlink_wait_clear_audio_in_urbs(line6pcm);
 		line6pcm->bytes_out = 0;
+		line6pcm->count_in = 0;
 		line6pcm->pos_in_done = 0;
 		line6pcm->bytes_in = 0;
 	}