summaryrefslogtreecommitdiffstats
path: root/sound/usb/line6
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-27 10:05:51 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-27 10:05:51 +0000
commit5d1646d90e1f2cceb9f0828f4b28318cd0ec7744 (patch)
treea94efe259b9009378be6d90eb30d2b019d95c194 /sound/usb/line6
parentInitial commit. (diff)
downloadlinux-5d1646d90e1f2cceb9f0828f4b28318cd0ec7744.tar.xz
linux-5d1646d90e1f2cceb9f0828f4b28318cd0ec7744.zip
Adding upstream version 5.10.209.upstream/5.10.209
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'sound/usb/line6')
-rw-r--r--sound/usb/line6/Kconfig44
-rw-r--r--sound/usb/line6/Makefile19
-rw-r--r--sound/usb/line6/capture.c294
-rw-r--r--sound/usb/line6/capture.h25
-rw-r--r--sound/usb/line6/driver.c900
-rw-r--r--sound/usb/line6/driver.h216
-rw-r--r--sound/usb/line6/midi.c295
-rw-r--r--sound/usb/line6/midi.h47
-rw-r--r--sound/usb/line6/midibuf.c257
-rw-r--r--sound/usb/line6/midibuf.h34
-rw-r--r--sound/usb/line6/pcm.c607
-rw-r--r--sound/usb/line6/pcm.h195
-rw-r--r--sound/usb/line6/playback.c439
-rw-r--r--sound/usb/line6/playback.h31
-rw-r--r--sound/usb/line6/pod.c542
-rw-r--r--sound/usb/line6/podhd.c559
-rw-r--r--sound/usb/line6/toneport.c582
-rw-r--r--sound/usb/line6/variax.c242
18 files changed, 5328 insertions, 0 deletions
diff --git a/sound/usb/line6/Kconfig b/sound/usb/line6/Kconfig
new file mode 100644
index 000000000..ab557e5ee
--- /dev/null
+++ b/sound/usb/line6/Kconfig
@@ -0,0 +1,44 @@
+# SPDX-License-Identifier: GPL-2.0-only
+config SND_USB_LINE6
+ tristate
+ select SND_RAWMIDI
+ select SND_PCM
+ select SND_HWDEP
+
+config SND_USB_POD
+ tristate "Line 6 POD USB support"
+ select SND_USB_LINE6
+ help
+ This is a driver for PODxt and other 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)
+
+config SND_USB_PODHD
+ tristate "Line 6 POD X3/HD300/400/500 USB support"
+ select SND_USB_LINE6
+ help
+ This is a driver for POD X3, HD300, 400 and 500 devices.
+
+config SND_USB_TONEPORT
+ tristate "TonePort GX, UX1 and UX2 USB support"
+ select SND_USB_LINE6
+ select NEW_LEDS
+ select LEDS_CLASS
+ help
+ This is a driver for TonePort GX, UX1 and UX2 devices.
+
+config SND_USB_VARIAX
+ tristate "Variax Workbench USB support"
+ select SND_USB_LINE6
+ help
+ This is a driver for Variax Workbench device.
+
diff --git a/sound/usb/line6/Makefile b/sound/usb/line6/Makefile
new file mode 100644
index 000000000..4ba98eb32
--- /dev/null
+++ b/sound/usb/line6/Makefile
@@ -0,0 +1,19 @@
+# SPDX-License-Identifier: GPL-2.0
+snd-usb-line6-y := \
+ capture.o \
+ driver.o \
+ midi.o \
+ midibuf.o \
+ pcm.o \
+ playback.o
+
+snd-usb-pod-y := pod.o
+snd-usb-podhd-y := podhd.o
+snd-usb-toneport-y := toneport.o
+snd-usb-variax-y := variax.o
+
+obj-$(CONFIG_SND_USB_LINE6) += snd-usb-line6.o
+obj-$(CONFIG_SND_USB_POD) += snd-usb-pod.o
+obj-$(CONFIG_SND_USB_PODHD) += snd-usb-podhd.o
+obj-$(CONFIG_SND_USB_TONEPORT) += snd-usb-toneport.o
+obj-$(CONFIG_SND_USB_VARIAX) += snd-usb-variax.o
diff --git a/sound/usb/line6/capture.c b/sound/usb/line6/capture.c
new file mode 100644
index 000000000..970c9bdce
--- /dev/null
+++ b/sound/usb/line6/capture.c
@@ -0,0 +1,294 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Line 6 Linux USB driver
+ *
+ * Copyright (C) 2004-2010 Markus Grabner (grabner@icg.tugraz.at)
+ */
+
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+
+#include "capture.h"
+#include "driver.h"
+#include "pcm.h"
+
+/*
+ Find a free URB and submit it.
+ must be called in line6pcm->in.lock context
+*/
+static int submit_audio_in_urb(struct snd_line6_pcm *line6pcm)
+{
+ int index;
+ int i, urb_size;
+ int ret;
+ struct urb *urb_in;
+
+ index = find_first_zero_bit(&line6pcm->in.active_urbs,
+ line6pcm->line6->iso_buffers);
+
+ if (index < 0 || index >= line6pcm->line6->iso_buffers) {
+ dev_err(line6pcm->line6->ifcdev, "no free URB found\n");
+ return -EINVAL;
+ }
+
+ urb_in = line6pcm->in.urbs[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_in;
+ urb_size += line6pcm->max_packet_size_in;
+ }
+
+ urb_in->transfer_buffer =
+ line6pcm->in.buffer +
+ index * LINE6_ISO_PACKETS * line6pcm->max_packet_size_in;
+ urb_in->transfer_buffer_length = urb_size;
+ urb_in->context = line6pcm;
+
+ ret = usb_submit_urb(urb_in, GFP_ATOMIC);
+
+ if (ret == 0)
+ set_bit(index, &line6pcm->in.active_urbs);
+ else
+ dev_err(line6pcm->line6->ifcdev,
+ "URB in #%d submission failed (%d)\n", index, ret);
+
+ return 0;
+}
+
+/*
+ Submit all currently available capture URBs.
+ must be called in line6pcm->in.lock context
+*/
+int line6_submit_audio_in_all_urbs(struct snd_line6_pcm *line6pcm)
+{
+ int ret = 0, i;
+
+ for (i = 0; i < line6pcm->line6->iso_buffers; ++i) {
+ ret = submit_audio_in_urb(line6pcm);
+ if (ret < 0)
+ break;
+ }
+
+ return ret;
+}
+
+/*
+ Copy data into ALSA capture buffer.
+*/
+void line6_capture_copy(struct snd_line6_pcm *line6pcm, char *fbuf, int fsize)
+{
+ struct snd_pcm_substream *substream =
+ get_substream(line6pcm, SNDRV_PCM_STREAM_CAPTURE);
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ const int bytes_per_frame =
+ line6pcm->properties->bytes_per_channel *
+ line6pcm->properties->capture_hw.channels_max;
+ int frames = fsize / bytes_per_frame;
+
+ if (runtime == NULL)
+ return;
+
+ if (line6pcm->in.pos_done + frames > runtime->buffer_size) {
+ /*
+ The transferred area goes over buffer boundary,
+ copy two separate chunks.
+ */
+ int len;
+
+ len = runtime->buffer_size - line6pcm->in.pos_done;
+
+ if (len > 0) {
+ memcpy(runtime->dma_area +
+ line6pcm->in.pos_done * bytes_per_frame, fbuf,
+ len * bytes_per_frame);
+ memcpy(runtime->dma_area, fbuf + len * bytes_per_frame,
+ (frames - len) * bytes_per_frame);
+ } else {
+ /* this is somewhat paranoid */
+ dev_err(line6pcm->line6->ifcdev,
+ "driver bug: len = %d\n", len);
+ }
+ } else {
+ /* copy single chunk */
+ memcpy(runtime->dma_area +
+ line6pcm->in.pos_done * bytes_per_frame, fbuf, fsize);
+ }
+
+ line6pcm->in.pos_done += frames;
+ if (line6pcm->in.pos_done >= runtime->buffer_size)
+ line6pcm->in.pos_done -= runtime->buffer_size;
+}
+
+void line6_capture_check_period(struct snd_line6_pcm *line6pcm, int length)
+{
+ struct snd_pcm_substream *substream =
+ get_substream(line6pcm, SNDRV_PCM_STREAM_CAPTURE);
+
+ line6pcm->in.bytes += length;
+ if (line6pcm->in.bytes >= line6pcm->in.period) {
+ line6pcm->in.bytes %= line6pcm->in.period;
+ spin_unlock(&line6pcm->in.lock);
+ snd_pcm_period_elapsed(substream);
+ spin_lock(&line6pcm->in.lock);
+ }
+}
+
+/*
+ * Callback for completed capture URB.
+ */
+static void audio_in_callback(struct urb *urb)
+{
+ int i, index, length = 0, shutdown = 0;
+ unsigned long flags;
+
+ struct snd_line6_pcm *line6pcm = (struct snd_line6_pcm *)urb->context;
+
+ line6pcm->in.last_frame = urb->start_frame;
+
+ /* find index of URB */
+ for (index = 0; index < line6pcm->line6->iso_buffers; ++index)
+ if (urb == line6pcm->in.urbs[index])
+ break;
+
+ spin_lock_irqsave(&line6pcm->in.lock, 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 == -EXDEV) {
+ shutdown = 1;
+ break;
+ }
+
+ fbuf = urb->transfer_buffer + fin->offset;
+ fsize = fin->actual_length;
+
+ if (fsize > line6pcm->max_packet_size_in) {
+ dev_err(line6pcm->line6->ifcdev,
+ "driver and/or device bug: packet too large (%d > %d)\n",
+ fsize, line6pcm->max_packet_size_in);
+ }
+
+ length += fsize;
+
+ BUILD_BUG_ON_MSG(LINE6_ISO_PACKETS != 1,
+ "The following code assumes LINE6_ISO_PACKETS == 1");
+ /* TODO:
+ * Also, if iso_buffers != 2, the prev frame is almost random at
+ * playback side.
+ * This needs to be redesigned. It should be "stable", but we may
+ * experience sync problems on such high-speed configs.
+ */
+
+ line6pcm->prev_fbuf = fbuf;
+ line6pcm->prev_fsize = fsize /
+ (line6pcm->properties->bytes_per_channel *
+ line6pcm->properties->capture_hw.channels_max);
+
+ if (!test_bit(LINE6_STREAM_IMPULSE, &line6pcm->in.running) &&
+ test_bit(LINE6_STREAM_PCM, &line6pcm->in.running) &&
+ fsize > 0)
+ line6_capture_copy(line6pcm, fbuf, fsize);
+ }
+
+ clear_bit(index, &line6pcm->in.active_urbs);
+
+ if (test_and_clear_bit(index, &line6pcm->in.unlink_urbs))
+ shutdown = 1;
+
+ if (!shutdown) {
+ submit_audio_in_urb(line6pcm);
+
+ if (!test_bit(LINE6_STREAM_IMPULSE, &line6pcm->in.running) &&
+ test_bit(LINE6_STREAM_PCM, &line6pcm->in.running))
+ line6_capture_check_period(line6pcm, length);
+ }
+
+ spin_unlock_irqrestore(&line6pcm->in.lock, flags);
+}
+
+/* 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);
+
+ err = snd_pcm_hw_constraint_ratdens(runtime, 0,
+ SNDRV_PCM_HW_PARAM_RATE,
+ &line6pcm->properties->rates);
+ if (err < 0)
+ return err;
+
+ line6_pcm_acquire(line6pcm, LINE6_STREAM_CAPTURE_HELPER, false);
+
+ runtime->hw = line6pcm->properties->capture_hw;
+ return 0;
+}
+
+/* close capture callback */
+static int snd_line6_capture_close(struct snd_pcm_substream *substream)
+{
+ struct snd_line6_pcm *line6pcm = snd_pcm_substream_chip(substream);
+
+ line6_pcm_release(line6pcm, LINE6_STREAM_CAPTURE_HELPER);
+ return 0;
+}
+
+/* capture operators */
+const struct snd_pcm_ops snd_line6_capture_ops = {
+ .open = snd_line6_capture_open,
+ .close = snd_line6_capture_close,
+ .hw_params = snd_line6_hw_params,
+ .hw_free = snd_line6_hw_free,
+ .prepare = snd_line6_prepare,
+ .trigger = snd_line6_trigger,
+ .pointer = snd_line6_pointer,
+};
+
+int line6_create_audio_in_urbs(struct snd_line6_pcm *line6pcm)
+{
+ struct usb_line6 *line6 = line6pcm->line6;
+ int i;
+
+ line6pcm->in.urbs = kcalloc(line6->iso_buffers, sizeof(struct urb *),
+ GFP_KERNEL);
+ if (line6pcm->in.urbs == NULL)
+ return -ENOMEM;
+
+ /* 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->in.urbs[i] =
+ usb_alloc_urb(LINE6_ISO_PACKETS, GFP_KERNEL);
+
+ if (urb == NULL)
+ return -ENOMEM;
+
+ urb->dev = line6->usbdev;
+ urb->pipe =
+ usb_rcvisocpipe(line6->usbdev,
+ line6->properties->ep_audio_r &
+ 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;
+ if (usb_urb_ep_type_check(urb))
+ return -EINVAL;
+ }
+
+ return 0;
+}
diff --git a/sound/usb/line6/capture.h b/sound/usb/line6/capture.h
new file mode 100644
index 000000000..20e05a5ec
--- /dev/null
+++ b/sound/usb/line6/capture.h
@@ -0,0 +1,25 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Line 6 Linux USB driver
+ *
+ * Copyright (C) 2004-2010 Markus Grabner (grabner@icg.tugraz.at)
+ */
+
+#ifndef CAPTURE_H
+#define CAPTURE_H
+
+#include <sound/pcm.h>
+
+#include "driver.h"
+#include "pcm.h"
+
+extern const struct snd_pcm_ops snd_line6_capture_ops;
+
+extern void line6_capture_copy(struct snd_line6_pcm *line6pcm, char *fbuf,
+ int fsize);
+extern void line6_capture_check_period(struct snd_line6_pcm *line6pcm,
+ int length);
+extern int line6_create_audio_in_urbs(struct snd_line6_pcm *line6pcm);
+extern int line6_submit_audio_in_all_urbs(struct snd_line6_pcm *line6pcm);
+
+#endif
diff --git a/sound/usb/line6/driver.c b/sound/usb/line6/driver.c
new file mode 100644
index 000000000..b67617b68
--- /dev/null
+++ b/sound/usb/line6/driver.c
@@ -0,0 +1,900 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Line 6 Linux USB driver
+ *
+ * Copyright (C) 2004-2010 Markus Grabner (grabner@icg.tugraz.at)
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/export.h>
+#include <linux/slab.h>
+#include <linux/usb.h>
+
+#include <sound/core.h>
+#include <sound/initval.h>
+#include <sound/hwdep.h>
+
+#include "capture.h"
+#include "driver.h"
+#include "midi.h"
+#include "playback.h"
+
+#define DRIVER_AUTHOR "Markus Grabner <grabner@icg.tugraz.at>"
+#define DRIVER_DESC "Line 6 USB Driver"
+
+/*
+ This is Line 6's MIDI manufacturer ID.
+*/
+const unsigned char line6_midi_id[3] = {
+ 0x00, 0x01, 0x0c
+};
+EXPORT_SYMBOL_GPL(line6_midi_id);
+
+/*
+ Code to request version of POD, Variax interface
+ (and maybe other devices).
+*/
+static const char line6_request_version[] = {
+ 0xf0, 0x7e, 0x7f, 0x06, 0x01, 0xf7
+};
+
+/*
+ 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);
+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)
+{
+ int err;
+
+ if (line6->properties->capabilities & LINE6_CAP_CONTROL_MIDI) {
+ usb_fill_int_urb(line6->urb_listen, line6->usbdev,
+ usb_rcvintpipe(line6->usbdev, line6->properties->ep_ctrl_r),
+ line6->buffer_listen, LINE6_BUFSIZE_LISTEN,
+ line6_data_received, line6, line6->interval);
+ } else {
+ usb_fill_bulk_urb(line6->urb_listen, line6->usbdev,
+ usb_rcvbulkpipe(line6->usbdev, line6->properties->ep_ctrl_r),
+ line6->buffer_listen, LINE6_BUFSIZE_LISTEN,
+ line6_data_received, line6);
+ }
+
+ /* sanity checks of EP before actually submitting */
+ if (usb_urb_ep_type_check(line6->urb_listen)) {
+ dev_err(line6->ifcdev, "invalid control EP\n");
+ return -EINVAL;
+ }
+
+ line6->urb_listen->actual_length = 0;
+ err = usb_submit_urb(line6->urb_listen, GFP_ATOMIC);
+ return err;
+}
+
+/*
+ Stop listening on endpoint.
+*/
+static void line6_stop_listen(struct usb_line6 *line6)
+{
+ usb_kill_urb(line6->urb_listen);
+}
+
+/*
+ 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;
+ const struct line6_properties *properties = line6->properties;
+
+ 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;
+
+ if (properties->capabilities & LINE6_CAP_CONTROL_MIDI) {
+ retval = usb_interrupt_msg(line6->usbdev,
+ usb_sndintpipe(line6->usbdev, properties->ep_ctrl_w),
+ (char *)frag_buf, frag_size,
+ &partial, LINE6_TIMEOUT);
+ } else {
+ retval = usb_bulk_msg(line6->usbdev,
+ usb_sndbulkpipe(line6->usbdev, properties->ep_ctrl_w),
+ (char *)frag_buf, frag_size,
+ &partial, LINE6_TIMEOUT);
+ }
+
+ if (retval) {
+ dev_err(line6->ifcdev,
+ "usb_bulk_msg failed (%d)\n", retval);
+ break;
+ }
+
+ done += frag_size;
+ }
+
+ return done;
+}
+EXPORT_SYMBOL_GPL(line6_send_raw_message);
+
+/*
+ Notification of completion of asynchronous request transmission.
+*/
+static void line6_async_request_sent(struct urb *urb)
+{
+ 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);
+
+ if (line6->properties->capabilities & LINE6_CAP_CONTROL_MIDI) {
+ usb_fill_int_urb(urb, line6->usbdev,
+ usb_sndintpipe(line6->usbdev, line6->properties->ep_ctrl_w),
+ (char *)msg->buffer + done, bytes,
+ line6_async_request_sent, msg, line6->interval);
+ } else {
+ usb_fill_bulk_urb(urb, line6->usbdev,
+ usb_sndbulkpipe(line6->usbdev, line6->properties->ep_ctrl_w),
+ (char *)msg->buffer + done, bytes,
+ line6_async_request_sent, msg);
+ }
+
+ msg->done += bytes;
+
+ /* sanity checks of EP before actually submitting */
+ retval = usb_urb_ep_type_check(urb);
+ if (retval < 0)
+ goto error;
+
+ retval = usb_submit_urb(urb, GFP_ATOMIC);
+ if (retval < 0)
+ goto error;
+
+ return 0;
+
+ error:
+ dev_err(line6->ifcdev, "%s: usb_submit_urb failed (%d)\n",
+ __func__, retval);
+ usb_free_urb(urb);
+ kfree(msg);
+ return retval;
+}
+
+/*
+ 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)
+ return -ENOMEM;
+
+ /* create URB: */
+ urb = usb_alloc_urb(0, GFP_ATOMIC);
+
+ if (urb == NULL) {
+ kfree(msg);
+ 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);
+}
+EXPORT_SYMBOL_GPL(line6_send_raw_message_async);
+
+/*
+ Send asynchronous device version request.
+*/
+int line6_version_request_async(struct usb_line6 *line6)
+{
+ char *buffer;
+ int retval;
+
+ buffer = kmemdup(line6_request_version,
+ sizeof(line6_request_version), GFP_ATOMIC);
+ if (buffer == NULL)
+ return -ENOMEM;
+
+ retval = line6_send_raw_message_async(line6, buffer,
+ sizeof(line6_request_version));
+ kfree(buffer);
+ return retval;
+}
+EXPORT_SYMBOL_GPL(line6_version_request_async);
+
+/*
+ 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;
+}
+EXPORT_SYMBOL_GPL(line6_send_sysex_message);
+
+/*
+ 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_ATOMIC);
+
+ if (!buffer)
+ return NULL;
+
+ 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;
+}
+EXPORT_SYMBOL_GPL(line6_alloc_sysex_buffer);
+
+/*
+ Notification of data received from the Line 6 device.
+*/
+static void line6_data_received(struct urb *urb)
+{
+ struct usb_line6 *line6 = (struct usb_line6 *)urb->context;
+ struct midi_buffer *mb = &line6->line6midi->midibuf_in;
+ int done;
+
+ if (urb->status == -ESHUTDOWN)
+ return;
+
+ if (line6->properties->capabilities & LINE6_CAP_CONTROL_MIDI) {
+ done =
+ line6_midibuf_write(mb, urb->transfer_buffer, urb->actual_length);
+
+ if (done < urb->actual_length) {
+ line6_midibuf_ignore(mb, done);
+ dev_dbg(line6->ifcdev, "%d %d buffer overflow - message skipped\n",
+ done, urb->actual_length);
+ }
+
+ for (;;) {
+ done =
+ line6_midibuf_read(mb, line6->buffer_message,
+ LINE6_MIDI_MESSAGE_MAXLEN,
+ LINE6_MIDIBUF_READ_RX);
+
+ if (done <= 0)
+ break;
+
+ line6->message_length = done;
+ line6_midi_receive(line6, line6->buffer_message, done);
+
+ if (line6->process_message)
+ line6->process_message(line6);
+ }
+ } else {
+ line6->buffer_message = urb->transfer_buffer;
+ line6->message_length = urb->actual_length;
+ if (line6->process_message)
+ line6->process_message(line6);
+ line6->buffer_message = NULL;
+ }
+
+ line6_start_listen(line6);
+}
+
+#define LINE6_READ_WRITE_STATUS_DELAY 2 /* milliseconds */
+#define LINE6_READ_WRITE_MAX_RETRIES 50
+
+/*
+ Read data from device.
+*/
+int line6_read_data(struct usb_line6 *line6, unsigned address, void *data,
+ unsigned datalen)
+{
+ struct usb_device *usbdev = line6->usbdev;
+ int ret;
+ u8 len;
+ unsigned count;
+
+ if (address > 0xffff || datalen > 0xff)
+ return -EINVAL;
+
+ /* query the serial number: */
+ ret = usb_control_msg_send(usbdev, 0, 0x67,
+ USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_OUT,
+ (datalen << 8) | 0x21, address, NULL, 0,
+ LINE6_TIMEOUT, GFP_KERNEL);
+ if (ret) {
+ dev_err(line6->ifcdev, "read request failed (error %d)\n", ret);
+ goto exit;
+ }
+
+ /* Wait for data length. We'll get 0xff until length arrives. */
+ for (count = 0; count < LINE6_READ_WRITE_MAX_RETRIES; count++) {
+ mdelay(LINE6_READ_WRITE_STATUS_DELAY);
+
+ ret = usb_control_msg_recv(usbdev, 0, 0x67,
+ USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_IN,
+ 0x0012, 0x0000, &len, 1,
+ LINE6_TIMEOUT, GFP_KERNEL);
+ if (ret) {
+ dev_err(line6->ifcdev,
+ "receive length failed (error %d)\n", ret);
+ goto exit;
+ }
+
+ if (len != 0xff)
+ break;
+ }
+
+ ret = -EIO;
+ if (len == 0xff) {
+ dev_err(line6->ifcdev, "read failed after %d retries\n",
+ count);
+ goto exit;
+ } else if (len != datalen) {
+ /* should be equal or something went wrong */
+ dev_err(line6->ifcdev,
+ "length mismatch (expected %d, got %d)\n",
+ (int)datalen, len);
+ goto exit;
+ }
+
+ /* receive the result: */
+ ret = usb_control_msg_recv(usbdev, 0, 0x67,
+ USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_IN,
+ 0x0013, 0x0000, data, datalen, LINE6_TIMEOUT,
+ GFP_KERNEL);
+ if (ret)
+ dev_err(line6->ifcdev, "read failed (error %d)\n", ret);
+
+exit:
+ return ret;
+}
+EXPORT_SYMBOL_GPL(line6_read_data);
+
+/*
+ Write data to device.
+*/
+int line6_write_data(struct usb_line6 *line6, unsigned address, void *data,
+ unsigned datalen)
+{
+ struct usb_device *usbdev = line6->usbdev;
+ int ret;
+ unsigned char *status;
+ int count;
+
+ if (address > 0xffff || datalen > 0xffff)
+ return -EINVAL;
+
+ status = kmalloc(1, GFP_KERNEL);
+ if (!status)
+ return -ENOMEM;
+
+ ret = usb_control_msg_send(usbdev, 0, 0x67,
+ USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_OUT,
+ 0x0022, address, data, datalen, LINE6_TIMEOUT,
+ GFP_KERNEL);
+ if (ret) {
+ dev_err(line6->ifcdev,
+ "write request failed (error %d)\n", ret);
+ goto exit;
+ }
+
+ for (count = 0; count < LINE6_READ_WRITE_MAX_RETRIES; count++) {
+ mdelay(LINE6_READ_WRITE_STATUS_DELAY);
+
+ ret = usb_control_msg_recv(usbdev, 0, 0x67,
+ USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_IN,
+ 0x0012, 0x0000, status, 1, LINE6_TIMEOUT,
+ GFP_KERNEL);
+ if (ret) {
+ dev_err(line6->ifcdev,
+ "receiving status failed (error %d)\n", ret);
+ goto exit;
+ }
+
+ if (*status != 0xff)
+ break;
+ }
+
+ if (*status == 0xff) {
+ dev_err(line6->ifcdev, "write failed after %d retries\n",
+ count);
+ ret = -EIO;
+ } else if (*status != 0) {
+ dev_err(line6->ifcdev, "write failed (error %d)\n", ret);
+ ret = -EIO;
+ }
+exit:
+ kfree(status);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(line6_write_data);
+
+/*
+ Read Line 6 device serial number.
+ (POD, TonePort, GuitarPort)
+*/
+int line6_read_serial_number(struct usb_line6 *line6, u32 *serial_number)
+{
+ return line6_read_data(line6, 0x80d0, serial_number,
+ sizeof(*serial_number));
+}
+EXPORT_SYMBOL_GPL(line6_read_serial_number);
+
+/*
+ Card destructor.
+*/
+static void line6_destruct(struct snd_card *card)
+{
+ struct usb_line6 *line6 = card->private_data;
+ struct usb_device *usbdev = line6->usbdev;
+
+ /* Free buffer memory first. We cannot depend on the existence of private
+ * data from the (podhd) module, it may be gone already during this call
+ */
+ kfree(line6->buffer_message);
+
+ kfree(line6->buffer_listen);
+
+ /* then free URBs: */
+ usb_free_urb(line6->urb_listen);
+ line6->urb_listen = NULL;
+
+ /* decrement reference counters: */
+ usb_put_dev(usbdev);
+}
+
+static void line6_get_usb_properties(struct usb_line6 *line6)
+{
+ struct usb_device *usbdev = line6->usbdev;
+ const struct line6_properties *properties = line6->properties;
+ int pipe;
+ struct usb_host_endpoint *ep = NULL;
+
+ if (properties->capabilities & LINE6_CAP_CONTROL) {
+ if (properties->capabilities & LINE6_CAP_CONTROL_MIDI) {
+ pipe = usb_rcvintpipe(line6->usbdev,
+ line6->properties->ep_ctrl_r);
+ } else {
+ pipe = usb_rcvbulkpipe(line6->usbdev,
+ line6->properties->ep_ctrl_r);
+ }
+ ep = usbdev->ep_in[usb_pipeendpoint(pipe)];
+ }
+
+ /* Control data transfer properties */
+ if (ep) {
+ line6->interval = ep->desc.bInterval;
+ line6->max_packet_size = le16_to_cpu(ep->desc.wMaxPacketSize);
+ } else {
+ if (properties->capabilities & LINE6_CAP_CONTROL) {
+ dev_err(line6->ifcdev,
+ "endpoint not available, using fallback values");
+ }
+ line6->interval = LINE6_FALLBACK_INTERVAL;
+ line6->max_packet_size = LINE6_FALLBACK_MAXPACKETSIZE;
+ }
+
+ /* Isochronous transfer properties */
+ if (usbdev->speed == USB_SPEED_LOW) {
+ line6->intervals_per_second = USB_LOW_INTERVALS_PER_SECOND;
+ line6->iso_buffers = USB_LOW_ISO_BUFFERS;
+ } else {
+ line6->intervals_per_second = USB_HIGH_INTERVALS_PER_SECOND;
+ line6->iso_buffers = USB_HIGH_ISO_BUFFERS;
+ }
+}
+
+/* Enable buffering of incoming messages, flush the buffer */
+static int line6_hwdep_open(struct snd_hwdep *hw, struct file *file)
+{
+ struct usb_line6 *line6 = hw->private_data;
+
+ /* NOTE: hwdep layer provides atomicity here */
+
+ line6->messages.active = 1;
+ line6->messages.nonblock = file->f_flags & O_NONBLOCK ? 1 : 0;
+
+ return 0;
+}
+
+/* Stop buffering */
+static int line6_hwdep_release(struct snd_hwdep *hw, struct file *file)
+{
+ struct usb_line6 *line6 = hw->private_data;
+
+ line6->messages.active = 0;
+
+ return 0;
+}
+
+/* Read from circular buffer, return to user */
+static long
+line6_hwdep_read(struct snd_hwdep *hwdep, char __user *buf, long count,
+ loff_t *offset)
+{
+ struct usb_line6 *line6 = hwdep->private_data;
+ long rv = 0;
+ unsigned int out_count;
+
+ if (mutex_lock_interruptible(&line6->messages.read_lock))
+ return -ERESTARTSYS;
+
+ while (kfifo_len(&line6->messages.fifo) == 0) {
+ mutex_unlock(&line6->messages.read_lock);
+
+ if (line6->messages.nonblock)
+ return -EAGAIN;
+
+ rv = wait_event_interruptible(
+ line6->messages.wait_queue,
+ kfifo_len(&line6->messages.fifo) != 0);
+ if (rv < 0)
+ return rv;
+
+ if (mutex_lock_interruptible(&line6->messages.read_lock))
+ return -ERESTARTSYS;
+ }
+
+ if (kfifo_peek_len(&line6->messages.fifo) > count) {
+ /* Buffer too small; allow re-read of the current item... */
+ rv = -EINVAL;
+ } else {
+ rv = kfifo_to_user(&line6->messages.fifo, buf, count, &out_count);
+ if (rv == 0)
+ rv = out_count;
+ }
+
+ mutex_unlock(&line6->messages.read_lock);
+ return rv;
+}
+
+/* Write directly (no buffering) to device by user*/
+static long
+line6_hwdep_write(struct snd_hwdep *hwdep, const char __user *data, long count,
+ loff_t *offset)
+{
+ struct usb_line6 *line6 = hwdep->private_data;
+ int rv;
+ char *data_copy;
+
+ if (count > line6->max_packet_size * LINE6_RAW_MESSAGES_MAXCOUNT) {
+ /* This is an arbitrary limit - still better than nothing... */
+ return -EINVAL;
+ }
+
+ data_copy = memdup_user(data, count);
+ if (IS_ERR(data_copy))
+ return PTR_ERR(data_copy);
+
+ rv = line6_send_raw_message(line6, data_copy, count);
+
+ kfree(data_copy);
+ return rv;
+}
+
+static __poll_t
+line6_hwdep_poll(struct snd_hwdep *hwdep, struct file *file, poll_table *wait)
+{
+ __poll_t rv;
+ struct usb_line6 *line6 = hwdep->private_data;
+
+ poll_wait(file, &line6->messages.wait_queue, wait);
+
+ mutex_lock(&line6->messages.read_lock);
+ rv = kfifo_len(&line6->messages.fifo) == 0 ? 0 : EPOLLIN | EPOLLRDNORM;
+ mutex_unlock(&line6->messages.read_lock);
+
+ return rv;
+}
+
+static const struct snd_hwdep_ops hwdep_ops = {
+ .open = line6_hwdep_open,
+ .release = line6_hwdep_release,
+ .read = line6_hwdep_read,
+ .write = line6_hwdep_write,
+ .poll = line6_hwdep_poll,
+};
+
+/* Insert into circular buffer */
+static void line6_hwdep_push_message(struct usb_line6 *line6)
+{
+ if (!line6->messages.active)
+ return;
+
+ if (kfifo_avail(&line6->messages.fifo) >= line6->message_length) {
+ /* No race condition here, there's only one writer */
+ kfifo_in(&line6->messages.fifo,
+ line6->buffer_message, line6->message_length);
+ } /* else TODO: signal overflow */
+
+ wake_up_interruptible(&line6->messages.wait_queue);
+}
+
+static int line6_hwdep_init(struct usb_line6 *line6)
+{
+ int err;
+ struct snd_hwdep *hwdep;
+
+ /* TODO: usb_driver_claim_interface(); */
+ line6->process_message = line6_hwdep_push_message;
+ line6->messages.active = 0;
+ init_waitqueue_head(&line6->messages.wait_queue);
+ mutex_init(&line6->messages.read_lock);
+ INIT_KFIFO(line6->messages.fifo);
+
+ err = snd_hwdep_new(line6->card, "config", 0, &hwdep);
+ if (err < 0)
+ goto end;
+ strcpy(hwdep->name, "config");
+ hwdep->iface = SNDRV_HWDEP_IFACE_LINE6;
+ hwdep->ops = hwdep_ops;
+ hwdep->private_data = line6;
+ hwdep->exclusive = true;
+
+end:
+ return err;
+}
+
+static int line6_init_cap_control(struct usb_line6 *line6)
+{
+ int ret;
+
+ /* initialize USB buffers: */
+ line6->buffer_listen = kmalloc(LINE6_BUFSIZE_LISTEN, GFP_KERNEL);
+ if (!line6->buffer_listen)
+ return -ENOMEM;
+
+ line6->urb_listen = usb_alloc_urb(0, GFP_KERNEL);
+ if (!line6->urb_listen)
+ return -ENOMEM;
+
+ if (line6->properties->capabilities & LINE6_CAP_CONTROL_MIDI) {
+ line6->buffer_message = kmalloc(LINE6_MIDI_MESSAGE_MAXLEN, GFP_KERNEL);
+ if (!line6->buffer_message)
+ return -ENOMEM;
+
+ ret = line6_init_midi(line6);
+ if (ret < 0)
+ return ret;
+ } else {
+ ret = line6_hwdep_init(line6);
+ if (ret < 0)
+ return ret;
+ }
+
+ ret = line6_start_listen(line6);
+ if (ret < 0) {
+ dev_err(line6->ifcdev, "cannot start listening: %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static void line6_startup_work(struct work_struct *work)
+{
+ struct usb_line6 *line6 =
+ container_of(work, struct usb_line6, startup_work.work);
+
+ if (line6->startup)
+ line6->startup(line6);
+}
+
+/*
+ Probe USB device.
+*/
+int line6_probe(struct usb_interface *interface,
+ const struct usb_device_id *id,
+ const char *driver_name,
+ const struct line6_properties *properties,
+ int (*private_init)(struct usb_line6 *, const struct usb_device_id *id),
+ size_t data_size)
+{
+ struct usb_device *usbdev = interface_to_usbdev(interface);
+ struct snd_card *card;
+ struct usb_line6 *line6;
+ int interface_number;
+ int ret;
+
+ if (WARN_ON(data_size < sizeof(*line6)))
+ return -EINVAL;
+
+ /* we don't handle multiple configurations */
+ if (usbdev->descriptor.bNumConfigurations != 1)
+ return -ENODEV;
+
+ ret = snd_card_new(&interface->dev,
+ SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1,
+ THIS_MODULE, data_size, &card);
+ if (ret < 0)
+ return ret;
+
+ /* store basic data: */
+ line6 = card->private_data;
+ line6->card = card;
+ line6->properties = properties;
+ line6->usbdev = usbdev;
+ line6->ifcdev = &interface->dev;
+ INIT_DELAYED_WORK(&line6->startup_work, line6_startup_work);
+
+ strcpy(card->id, properties->id);
+ strcpy(card->driver, driver_name);
+ strcpy(card->shortname, properties->name);
+ sprintf(card->longname, "Line 6 %s at USB %s", properties->name,
+ dev_name(line6->ifcdev));
+ card->private_free = line6_destruct;
+
+ usb_set_intfdata(interface, line6);
+
+ /* increment reference counters: */
+ usb_get_dev(usbdev);
+
+ /* initialize device info: */
+ dev_info(&interface->dev, "Line 6 %s found\n", properties->name);
+
+ /* query interface number */
+ interface_number = interface->cur_altsetting->desc.bInterfaceNumber;
+
+ /* TODO reserves the bus bandwidth even without actual transfer */
+ ret = usb_set_interface(usbdev, interface_number,
+ properties->altsetting);
+ if (ret < 0) {
+ dev_err(&interface->dev, "set_interface failed\n");
+ goto error;
+ }
+
+ line6_get_usb_properties(line6);
+
+ if (properties->capabilities & LINE6_CAP_CONTROL) {
+ ret = line6_init_cap_control(line6);
+ if (ret < 0)
+ goto error;
+ }
+
+ /* initialize device data based on device: */
+ ret = private_init(line6, id);
+ if (ret < 0)
+ goto error;
+
+ /* creation of additional special files should go here */
+
+ dev_info(&interface->dev, "Line 6 %s now attached\n",
+ properties->name);
+
+ return 0;
+
+ error:
+ /* we can call disconnect callback here because no close-sync is
+ * needed yet at this point
+ */
+ line6_disconnect(interface);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(line6_probe);
+
+/*
+ Line 6 device disconnected.
+*/
+void line6_disconnect(struct usb_interface *interface)
+{
+ struct usb_line6 *line6 = usb_get_intfdata(interface);
+ struct usb_device *usbdev = interface_to_usbdev(interface);
+
+ if (!line6)
+ return;
+
+ if (WARN_ON(usbdev != line6->usbdev))
+ return;
+
+ cancel_delayed_work_sync(&line6->startup_work);
+
+ if (line6->urb_listen != NULL)
+ line6_stop_listen(line6);
+
+ snd_card_disconnect(line6->card);
+ if (line6->line6pcm)
+ line6_pcm_disconnect(line6->line6pcm);
+ if (line6->disconnect)
+ line6->disconnect(line6);
+
+ dev_info(&interface->dev, "Line 6 %s now disconnected\n",
+ line6->properties->name);
+
+ /* make sure the device isn't destructed twice: */
+ usb_set_intfdata(interface, NULL);
+
+ snd_card_free_when_closed(line6->card);
+}
+EXPORT_SYMBOL_GPL(line6_disconnect);
+
+#ifdef CONFIG_PM
+
+/*
+ Suspend Line 6 device.
+*/
+int line6_suspend(struct usb_interface *interface, pm_message_t message)
+{
+ struct usb_line6 *line6 = usb_get_intfdata(interface);
+ struct snd_line6_pcm *line6pcm = line6->line6pcm;
+
+ snd_power_change_state(line6->card, SNDRV_CTL_POWER_D3hot);
+
+ if (line6->properties->capabilities & LINE6_CAP_CONTROL)
+ line6_stop_listen(line6);
+
+ if (line6pcm != NULL)
+ line6pcm->flags = 0;
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(line6_suspend);
+
+/*
+ Resume Line 6 device.
+*/
+int line6_resume(struct usb_interface *interface)
+{
+ struct usb_line6 *line6 = usb_get_intfdata(interface);
+
+ if (line6->properties->capabilities & LINE6_CAP_CONTROL)
+ line6_start_listen(line6);
+
+ snd_power_change_state(line6->card, SNDRV_CTL_POWER_D0);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(line6_resume);
+
+#endif /* CONFIG_PM */
+
+MODULE_AUTHOR(DRIVER_AUTHOR);
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_LICENSE("GPL");
+
diff --git a/sound/usb/line6/driver.h b/sound/usb/line6/driver.h
new file mode 100644
index 000000000..ecf3a2b39
--- /dev/null
+++ b/sound/usb/line6/driver.h
@@ -0,0 +1,216 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Line 6 Linux USB driver
+ *
+ * Copyright (C) 2004-2010 Markus Grabner (grabner@icg.tugraz.at)
+ */
+
+#ifndef DRIVER_H
+#define DRIVER_H
+
+#include <linux/usb.h>
+#include <linux/mutex.h>
+#include <linux/kfifo.h>
+#include <sound/core.h>
+
+#include "midi.h"
+
+/* USB 1.1 speed configuration */
+#define USB_LOW_INTERVALS_PER_SECOND 1000
+#define USB_LOW_ISO_BUFFERS 2
+
+/* USB 2.0+ speed configuration */
+#define USB_HIGH_INTERVALS_PER_SECOND 8000
+#define USB_HIGH_ISO_BUFFERS 16
+
+/* Fallback USB interval and max packet size values */
+#define LINE6_FALLBACK_INTERVAL 10
+#define LINE6_FALLBACK_MAXPACKETSIZE 16
+
+#define LINE6_TIMEOUT 1000
+#define LINE6_BUFSIZE_LISTEN 64
+#define LINE6_MIDI_MESSAGE_MAXLEN 256
+
+#define LINE6_RAW_MESSAGES_MAXCOUNT_ORDER 7
+/* 4k packets are common, BUFSIZE * MAXCOUNT should be bigger... */
+#define LINE6_RAW_MESSAGES_MAXCOUNT (1 << LINE6_RAW_MESSAGES_MAXCOUNT_ORDER)
+
+
+#if LINE6_BUFSIZE_LISTEN > 65535
+#error "Use dynamic fifo instead"
+#endif
+
+/*
+ Line 6 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
+
+extern const unsigned char line6_midi_id[3];
+
+#define SYSEX_DATA_OFS (sizeof(line6_midi_id) + 3)
+#define SYSEX_EXTRA_SIZE (sizeof(line6_midi_id) + 4)
+
+/*
+ Common properties of Line 6 devices.
+*/
+struct line6_properties {
+ /* Card id string (maximum 16 characters).
+ * This can be used to address the device in ALSA programs as
+ * "default:CARD=<id>"
+ */
+ const char *id;
+
+ /* Card short name (maximum 32 characters) */
+ const char *name;
+
+ /* Bit vector defining this device's capabilities in line6usb driver */
+ int capabilities;
+
+ int altsetting;
+
+ unsigned int ctrl_if;
+ unsigned int ep_ctrl_r;
+ unsigned int ep_ctrl_w;
+ unsigned int ep_audio_r;
+ unsigned int ep_audio_w;
+};
+
+/* Capability bits */
+enum {
+ /* device supports settings parameter via USB */
+ LINE6_CAP_CONTROL = 1 << 0,
+ /* device supports PCM input/output via USB */
+ LINE6_CAP_PCM = 1 << 1,
+ /* device supports hardware monitoring */
+ LINE6_CAP_HWMON = 1 << 2,
+ /* device requires output data when input is read */
+ LINE6_CAP_IN_NEEDS_OUT = 1 << 3,
+ /* device uses raw MIDI via USB (data endpoints) */
+ LINE6_CAP_CONTROL_MIDI = 1 << 4,
+ /* device provides low-level information */
+ LINE6_CAP_CONTROL_INFO = 1 << 5,
+ /* device provides hardware monitoring volume control */
+ LINE6_CAP_HWMON_CTL = 1 << 6,
+};
+
+/*
+ Common data shared by all Line 6 devices.
+ Corresponds to a pair of USB endpoints.
+*/
+struct usb_line6 {
+ /* USB device */
+ struct usb_device *usbdev;
+
+ /* Properties */
+ const struct line6_properties *properties;
+
+ /* Interval for data USB packets */
+ int interval;
+ /* ...for isochronous transfers framing */
+ int intervals_per_second;
+
+ /* Number of isochronous URBs used for frame transfers */
+ int iso_buffers;
+
+ /* Maximum size of data USB packet */
+ int max_packet_size;
+
+ /* Device representing the USB interface */
+ struct device *ifcdev;
+
+ /* Line 6 sound card data structure.
+ * Each device has at least MIDI or PCM.
+ */
+ struct snd_card *card;
+
+ /* Line 6 PCM device data structure */
+ struct snd_line6_pcm *line6pcm;
+
+ /* Line 6 MIDI device data structure */
+ struct snd_line6_midi *line6midi;
+
+ /* URB for listening to POD data endpoint */
+ struct urb *urb_listen;
+
+ /* Buffer for incoming data from POD data endpoint */
+ unsigned char *buffer_listen;
+
+ /* Buffer for message to be processed, generated from MIDI layer */
+ unsigned char *buffer_message;
+
+ /* Length of message to be processed, generated from MIDI layer */
+ int message_length;
+
+ /* Circular buffer for non-MIDI control messages */
+ struct {
+ struct mutex read_lock;
+ wait_queue_head_t wait_queue;
+ unsigned int active:1;
+ unsigned int nonblock:1;
+ STRUCT_KFIFO_REC_2(LINE6_BUFSIZE_LISTEN * LINE6_RAW_MESSAGES_MAXCOUNT)
+ fifo;
+ } messages;
+
+ /* Work for delayed PCM startup */
+ struct delayed_work startup_work;
+
+ /* If MIDI is supported, buffer_message contains the pre-processed data;
+ * otherwise the data is only in urb_listen (buffer_incoming).
+ */
+ void (*process_message)(struct usb_line6 *);
+ void (*disconnect)(struct usb_line6 *line6);
+ void (*startup)(struct usb_line6 *line6);
+};
+
+extern char *line6_alloc_sysex_buffer(struct usb_line6 *line6, int code1,
+ int code2, int size);
+extern int line6_read_data(struct usb_line6 *line6, unsigned address,
+ void *data, unsigned datalen);
+extern int line6_read_serial_number(struct usb_line6 *line6,
+ u32 *serial_number);
+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, struct device_attribute *attr,
+ const char *buf, size_t count);
+extern int line6_version_request_async(struct usb_line6 *line6);
+extern int line6_write_data(struct usb_line6 *line6, unsigned address,
+ void *data, unsigned datalen);
+
+int line6_probe(struct usb_interface *interface,
+ const struct usb_device_id *id,
+ const char *driver_name,
+ const struct line6_properties *properties,
+ int (*private_init)(struct usb_line6 *, const struct usb_device_id *id),
+ size_t data_size);
+
+void line6_disconnect(struct usb_interface *interface);
+
+#ifdef CONFIG_PM
+int line6_suspend(struct usb_interface *interface, pm_message_t message);
+int line6_resume(struct usb_interface *interface);
+#endif
+
+#endif
diff --git a/sound/usb/line6/midi.c b/sound/usb/line6/midi.c
new file mode 100644
index 000000000..0838632c7
--- /dev/null
+++ b/sound/usb/line6/midi.c
@@ -0,0 +1,295 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Line 6 Linux USB driver
+ *
+ * Copyright (C) 2004-2010 Markus Grabner (grabner@icg.tugraz.at)
+ */
+
+#include <linux/slab.h>
+#include <linux/usb.h>
+#include <linux/export.h>
+#include <sound/core.h>
+#include <sound/rawmidi.h>
+
+#include "driver.h"
+#include "midi.h"
+
+#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 midi_buffer *mb = &line6midi->midibuf_out;
+ unsigned char chunk[LINE6_FALLBACK_MAXPACKETSIZE];
+ int req, done;
+
+ for (;;) {
+ req = min3(line6_midibuf_bytes_free(mb), line6->max_packet_size,
+ LINE6_FALLBACK_MAXPACKETSIZE);
+ done = snd_rawmidi_transmit_peek(substream, chunk, req);
+
+ if (done == 0)
+ break;
+
+ line6_midibuf_write(mb, chunk, done);
+ snd_rawmidi_transmit_ack(substream, done);
+ }
+
+ for (;;) {
+ done = line6_midibuf_read(mb, chunk,
+ LINE6_FALLBACK_MAXPACKETSIZE,
+ LINE6_MIDIBUF_READ_TX);
+
+ if (done == 0)
+ break;
+
+ send_midi_async(line6, chunk, done);
+ }
+}
+
+/*
+ Notification of completion of MIDI transmission.
+*/
+static void midi_sent(struct urb *urb)
+{
+ 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->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(&line6->line6midi->send_wait);
+
+ spin_unlock_irqrestore(&line6->line6midi->lock, flags);
+}
+
+/*
+ Send an asynchronous MIDI message.
+ Assumes that line6->line6midi->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 == NULL)
+ return -ENOMEM;
+
+ transfer_buffer = kmemdup(data, length, GFP_ATOMIC);
+
+ if (transfer_buffer == NULL) {
+ usb_free_urb(urb);
+ return -ENOMEM;
+ }
+
+ usb_fill_int_urb(urb, line6->usbdev,
+ usb_sndintpipe(line6->usbdev,
+ line6->properties->ep_ctrl_w),
+ transfer_buffer, length, midi_sent, line6,
+ line6->interval);
+ urb->actual_length = 0;
+ retval = usb_urb_ep_type_check(urb);
+ if (retval < 0)
+ goto error;
+
+ retval = usb_submit_urb(urb, GFP_ATOMIC);
+ if (retval < 0)
+ goto error;
+
+ ++line6->line6midi->num_active_send_urbs;
+ return 0;
+
+ error:
+ dev_err(line6->ifcdev, "usb_submit_urb failed\n");
+ usb_free_urb(urb);
+ return retval;
+}
+
+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->lock, flags);
+
+ if (line6->line6midi->num_active_send_urbs == 0)
+ line6_midi_transmit(substream);
+
+ spin_unlock_irqrestore(&line6->line6midi->lock, flags);
+}
+
+static void line6_midi_output_drain(struct snd_rawmidi_substream *substream)
+{
+ struct usb_line6 *line6 =
+ line6_rawmidi_substream_midi(substream)->line6;
+ struct snd_line6_midi *midi = line6->line6midi;
+
+ wait_event_interruptible(midi->send_wait,
+ midi->num_active_send_urbs == 0);
+}
+
+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 = NULL;
+}
+
+static const 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 const struct snd_rawmidi_ops line6_midi_input_ops = {
+ .open = line6_midi_input_open,
+ .close = line6_midi_input_close,
+ .trigger = line6_midi_input_trigger,
+};
+
+/* Create a MIDI device */
+static int snd_line6_new_midi(struct usb_line6 *line6,
+ struct snd_rawmidi **rmidi_ret)
+{
+ struct snd_rawmidi *rmidi;
+ int err;
+
+ err = snd_rawmidi_new(line6->card, "Line 6 MIDI", 0, 1, 1, rmidi_ret);
+ if (err < 0)
+ return err;
+
+ rmidi = *rmidi_ret;
+ strcpy(rmidi->id, line6->properties->id);
+ strcpy(rmidi->name, 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;
+}
+
+/* MIDI device destructor */
+static void snd_line6_midi_free(struct snd_rawmidi *rmidi)
+{
+ struct snd_line6_midi *line6midi = rmidi->private_data;
+
+ line6_midibuf_destroy(&line6midi->midibuf_in);
+ line6_midibuf_destroy(&line6midi->midibuf_out);
+ kfree(line6midi);
+}
+
+/*
+ Initialize the Line 6 MIDI subsystem.
+*/
+int line6_init_midi(struct usb_line6 *line6)
+{
+ int err;
+ struct snd_rawmidi *rmidi;
+ struct snd_line6_midi *line6midi;
+
+ if (!(line6->properties->capabilities & LINE6_CAP_CONTROL_MIDI)) {
+ /* skip MIDI initialization and report success */
+ return 0;
+ }
+
+ err = snd_line6_new_midi(line6, &rmidi);
+ if (err < 0)
+ return err;
+
+ line6midi = kzalloc(sizeof(struct snd_line6_midi), GFP_KERNEL);
+ if (!line6midi)
+ return -ENOMEM;
+
+ rmidi->private_data = line6midi;
+ rmidi->private_free = snd_line6_midi_free;
+
+ init_waitqueue_head(&line6midi->send_wait);
+ spin_lock_init(&line6midi->lock);
+ line6midi->line6 = line6;
+
+ err = line6_midibuf_init(&line6midi->midibuf_in, MIDI_BUFFER_SIZE, 0);
+ if (err < 0)
+ return err;
+
+ err = line6_midibuf_init(&line6midi->midibuf_out, MIDI_BUFFER_SIZE, 1);
+ if (err < 0)
+ return err;
+
+ line6->line6midi = line6midi;
+ return 0;
+}
+EXPORT_SYMBOL_GPL(line6_init_midi);
diff --git a/sound/usb/line6/midi.h b/sound/usb/line6/midi.h
new file mode 100644
index 000000000..918754e79
--- /dev/null
+++ b/sound/usb/line6/midi.h
@@ -0,0 +1,47 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Line 6 Linux USB driver
+ *
+ * Copyright (C) 2004-2010 Markus Grabner (grabner@icg.tugraz.at)
+ */
+
+#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 Line 6 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 MIDI buffer handling */
+ spinlock_t lock;
+
+ /* Wait queue for MIDI transmission */
+ wait_queue_head_t send_wait;
+
+ /* Buffer for incoming MIDI stream */
+ struct midi_buffer midibuf_in;
+
+ /* Buffer for outgoing MIDI stream */
+ struct midi_buffer 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/sound/usb/line6/midibuf.c b/sound/usb/line6/midibuf.c
new file mode 100644
index 000000000..e7f830f75
--- /dev/null
+++ b/sound/usb/line6/midibuf.c
@@ -0,0 +1,257 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Line 6 Linux USB driver
+ *
+ * Copyright (C) 2004-2010 Markus Grabner (grabner@icg.tugraz.at)
+ */
+
+#include <linux/slab.h>
+
+#include "midibuf.h"
+
+
+static int midibuf_message_length(unsigned char code)
+{
+ int message_length;
+
+ if (code < 0x80)
+ message_length = -1;
+ else if (code < 0xf0) {
+ static const int length[] = { 3, 3, 3, 3, 2, 2, 3 };
+
+ message_length = length[(code >> 4) - 8];
+ } else {
+ static const int length[] = { -1, 2, 2, 2, -1, -1, 1, 1, 1, -1,
+ 1, 1, 1, -1, 1, 1
+ };
+ message_length = length[code & 0x0f];
+ }
+
+ return message_length;
+}
+
+static int midibuf_is_empty(struct midi_buffer *this)
+{
+ return (this->pos_read == this->pos_write) && !this->full;
+}
+
+static int midibuf_is_full(struct midi_buffer *this)
+{
+ return this->full;
+}
+
+void line6_midibuf_reset(struct midi_buffer *this)
+{
+ this->pos_read = this->pos_write = this->full = 0;
+ this->command_prev = -1;
+}
+
+int line6_midibuf_init(struct midi_buffer *this, int size, int split)
+{
+ this->buf = kmalloc(size, GFP_KERNEL);
+
+ if (this->buf == NULL)
+ return -ENOMEM;
+
+ this->size = size;
+ this->split = split;
+ line6_midibuf_reset(this);
+ return 0;
+}
+
+int line6_midibuf_bytes_free(struct midi_buffer *this)
+{
+ return
+ midibuf_is_full(this) ?
+ 0 :
+ (this->pos_read - this->pos_write + this->size - 1) % this->size +
+ 1;
+}
+
+int line6_midibuf_bytes_used(struct midi_buffer *this)
+{
+ return
+ midibuf_is_empty(this) ?
+ 0 :
+ (this->pos_write - this->pos_read + this->size - 1) % this->size +
+ 1;
+}
+
+int line6_midibuf_write(struct midi_buffer *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 = line6_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 line6_midibuf_read(struct midi_buffer *this, unsigned char *data,
+ int length, int read_type)
+{
+ int bytes_used;
+ int length1, length2;
+ int command;
+ int midi_length;
+ int repeat = 0;
+ int i;
+
+ /* we need to be able to store at least a 3 byte MIDI message */
+ if (length < 3)
+ return -EINVAL;
+
+ if (midibuf_is_empty(this))
+ return 0;
+
+ bytes_used = line6_midibuf_bytes_used(this);
+
+ if (length > bytes_used)
+ length = bytes_used;
+
+ length1 = this->size - this->pos_read;
+
+ command = this->buf[this->pos_read];
+ /*
+ PODxt always has status byte lower nibble set to 0010,
+ when it means to send 0000, so we correct if here so
+ that control/program changes come on channel 1 and
+ sysex message status byte is correct
+ */
+ if (read_type == LINE6_MIDIBUF_READ_RX) {
+ if (command == 0xb2 || command == 0xc2 || command == 0xf2) {
+ unsigned char fixed = command & 0xf0;
+ this->buf[this->pos_read] = fixed;
+ command = fixed;
+ }
+ }
+
+ /* check MIDI command length */
+ 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 > 1) {
+ 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 line6_midibuf_ignore(struct midi_buffer *this, int length)
+{
+ int bytes_used = line6_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;
+}
+
+void line6_midibuf_destroy(struct midi_buffer *this)
+{
+ kfree(this->buf);
+ this->buf = NULL;
+}
diff --git a/sound/usb/line6/midibuf.h b/sound/usb/line6/midibuf.h
new file mode 100644
index 000000000..542e8d836
--- /dev/null
+++ b/sound/usb/line6/midibuf.h
@@ -0,0 +1,34 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Line 6 Linux USB driver
+ *
+ * Copyright (C) 2004-2010 Markus Grabner (grabner@icg.tugraz.at)
+ */
+
+#ifndef MIDIBUF_H
+#define MIDIBUF_H
+
+#define LINE6_MIDIBUF_READ_TX 0
+#define LINE6_MIDIBUF_READ_RX 1
+
+struct midi_buffer {
+ unsigned char *buf;
+ int size;
+ int split;
+ int pos_read, pos_write;
+ int full;
+ int command_prev;
+};
+
+extern int line6_midibuf_bytes_used(struct midi_buffer *mb);
+extern int line6_midibuf_bytes_free(struct midi_buffer *mb);
+extern void line6_midibuf_destroy(struct midi_buffer *mb);
+extern int line6_midibuf_ignore(struct midi_buffer *mb, int length);
+extern int line6_midibuf_init(struct midi_buffer *mb, int size, int split);
+extern int line6_midibuf_read(struct midi_buffer *mb, unsigned char *data,
+ int length, int read_type);
+extern void line6_midibuf_reset(struct midi_buffer *mb);
+extern int line6_midibuf_write(struct midi_buffer *mb, unsigned char *data,
+ int length);
+
+#endif
diff --git a/sound/usb/line6/pcm.c b/sound/usb/line6/pcm.c
new file mode 100644
index 000000000..fdbdfb7bc
--- /dev/null
+++ b/sound/usb/line6/pcm.c
@@ -0,0 +1,607 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Line 6 Linux USB driver
+ *
+ * Copyright (C) 2004-2010 Markus Grabner (grabner@icg.tugraz.at)
+ */
+
+#include <linux/slab.h>
+#include <linux/export.h>
+#include <sound/core.h>
+#include <sound/control.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+
+#include "capture.h"
+#include "driver.h"
+#include "playback.h"
+
+/* impulse response volume controls */
+static int snd_line6_impulse_volume_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = 1;
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = 255;
+ return 0;
+}
+
+static int snd_line6_impulse_volume_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_line6_pcm *line6pcm = snd_kcontrol_chip(kcontrol);
+
+ ucontrol->value.integer.value[0] = line6pcm->impulse_volume;
+ return 0;
+}
+
+static int snd_line6_impulse_volume_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_line6_pcm *line6pcm = snd_kcontrol_chip(kcontrol);
+ int value = ucontrol->value.integer.value[0];
+ int err;
+
+ if (line6pcm->impulse_volume == value)
+ return 0;
+
+ line6pcm->impulse_volume = value;
+ if (value > 0) {
+ err = line6_pcm_acquire(line6pcm, LINE6_STREAM_IMPULSE, true);
+ if (err < 0) {
+ line6pcm->impulse_volume = 0;
+ return err;
+ }
+ } else {
+ line6_pcm_release(line6pcm, LINE6_STREAM_IMPULSE);
+ }
+ return 1;
+}
+
+/* impulse response period controls */
+static int snd_line6_impulse_period_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = 1;
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = 2000;
+ return 0;
+}
+
+static int snd_line6_impulse_period_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_line6_pcm *line6pcm = snd_kcontrol_chip(kcontrol);
+
+ ucontrol->value.integer.value[0] = line6pcm->impulse_period;
+ return 0;
+}
+
+static int snd_line6_impulse_period_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_line6_pcm *line6pcm = snd_kcontrol_chip(kcontrol);
+ int value = ucontrol->value.integer.value[0];
+
+ if (line6pcm->impulse_period == value)
+ return 0;
+
+ line6pcm->impulse_period = value;
+ return 1;
+}
+
+/*
+ Unlink all currently active URBs.
+*/
+static void line6_unlink_audio_urbs(struct snd_line6_pcm *line6pcm,
+ struct line6_pcm_stream *pcms)
+{
+ int i;
+
+ for (i = 0; i < line6pcm->line6->iso_buffers; i++) {
+ if (test_bit(i, &pcms->active_urbs)) {
+ if (!test_and_set_bit(i, &pcms->unlink_urbs))
+ usb_unlink_urb(pcms->urbs[i]);
+ }
+ }
+}
+
+/*
+ Wait until unlinking of all currently active URBs has been finished.
+*/
+static void line6_wait_clear_audio_urbs(struct snd_line6_pcm *line6pcm,
+ struct line6_pcm_stream *pcms)
+{
+ int timeout = HZ;
+ int i;
+ int alive;
+
+ do {
+ alive = 0;
+ for (i = 0; i < line6pcm->line6->iso_buffers; i++) {
+ if (test_bit(i, &pcms->active_urbs))
+ alive++;
+ }
+ if (!alive)
+ break;
+ set_current_state(TASK_UNINTERRUPTIBLE);
+ schedule_timeout(1);
+ } while (--timeout > 0);
+ if (alive)
+ dev_err(line6pcm->line6->ifcdev,
+ "timeout: still %d active urbs..\n", alive);
+}
+
+static inline struct line6_pcm_stream *
+get_stream(struct snd_line6_pcm *line6pcm, int direction)
+{
+ return (direction == SNDRV_PCM_STREAM_PLAYBACK) ?
+ &line6pcm->out : &line6pcm->in;
+}
+
+/* allocate a buffer if not opened yet;
+ * call this in line6pcm.state_mutex
+ */
+static int line6_buffer_acquire(struct snd_line6_pcm *line6pcm,
+ struct line6_pcm_stream *pstr, int direction, int type)
+{
+ const int pkt_size =
+ (direction == SNDRV_PCM_STREAM_PLAYBACK) ?
+ line6pcm->max_packet_size_out :
+ line6pcm->max_packet_size_in;
+
+ /* Invoked multiple times in a row so allocate once only */
+ if (!test_and_set_bit(type, &pstr->opened) && !pstr->buffer) {
+ pstr->buffer =
+ kmalloc(array3_size(line6pcm->line6->iso_buffers,
+ LINE6_ISO_PACKETS, pkt_size),
+ GFP_KERNEL);
+ if (!pstr->buffer)
+ return -ENOMEM;
+ }
+ return 0;
+}
+
+/* free a buffer if all streams are closed;
+ * call this in line6pcm.state_mutex
+ */
+static void line6_buffer_release(struct snd_line6_pcm *line6pcm,
+ struct line6_pcm_stream *pstr, int type)
+{
+ clear_bit(type, &pstr->opened);
+ if (!pstr->opened) {
+ line6_wait_clear_audio_urbs(line6pcm, pstr);
+ kfree(pstr->buffer);
+ pstr->buffer = NULL;
+ }
+}
+
+/* start a PCM stream */
+static int line6_stream_start(struct snd_line6_pcm *line6pcm, int direction,
+ int type)
+{
+ unsigned long flags;
+ struct line6_pcm_stream *pstr = get_stream(line6pcm, direction);
+ int ret = 0;
+
+ spin_lock_irqsave(&pstr->lock, flags);
+ if (!test_and_set_bit(type, &pstr->running) &&
+ !(pstr->active_urbs || pstr->unlink_urbs)) {
+ pstr->count = 0;
+ /* Submit all currently available URBs */
+ if (direction == SNDRV_PCM_STREAM_PLAYBACK)
+ ret = line6_submit_audio_out_all_urbs(line6pcm);
+ else
+ ret = line6_submit_audio_in_all_urbs(line6pcm);
+ }
+
+ if (ret < 0)
+ clear_bit(type, &pstr->running);
+ spin_unlock_irqrestore(&pstr->lock, flags);
+ return ret;
+}
+
+/* stop a PCM stream; this doesn't sync with the unlinked URBs */
+static void line6_stream_stop(struct snd_line6_pcm *line6pcm, int direction,
+ int type)
+{
+ unsigned long flags;
+ struct line6_pcm_stream *pstr = get_stream(line6pcm, direction);
+
+ spin_lock_irqsave(&pstr->lock, flags);
+ clear_bit(type, &pstr->running);
+ if (!pstr->running) {
+ spin_unlock_irqrestore(&pstr->lock, flags);
+ line6_unlink_audio_urbs(line6pcm, pstr);
+ spin_lock_irqsave(&pstr->lock, flags);
+ if (direction == SNDRV_PCM_STREAM_CAPTURE) {
+ line6pcm->prev_fbuf = NULL;
+ line6pcm->prev_fsize = 0;
+ }
+ }
+ spin_unlock_irqrestore(&pstr->lock, flags);
+}
+
+/* common PCM trigger callback */
+int snd_line6_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+ struct snd_line6_pcm *line6pcm = snd_pcm_substream_chip(substream);
+ struct snd_pcm_substream *s;
+ int err;
+
+ clear_bit(LINE6_FLAG_PREPARED, &line6pcm->flags);
+
+ snd_pcm_group_for_each_entry(s, substream) {
+ if (s->pcm->card != substream->pcm->card)
+ continue;
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ if (s->stream == SNDRV_PCM_STREAM_CAPTURE &&
+ (line6pcm->line6->properties->capabilities &
+ LINE6_CAP_IN_NEEDS_OUT)) {
+ err = line6_stream_start(line6pcm, SNDRV_PCM_STREAM_PLAYBACK,
+ LINE6_STREAM_CAPTURE_HELPER);
+ if (err < 0)
+ return err;
+ }
+ err = line6_stream_start(line6pcm, s->stream,
+ LINE6_STREAM_PCM);
+ if (err < 0)
+ return err;
+ break;
+
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ if (s->stream == SNDRV_PCM_STREAM_CAPTURE &&
+ (line6pcm->line6->properties->capabilities &
+ LINE6_CAP_IN_NEEDS_OUT)) {
+ line6_stream_stop(line6pcm, SNDRV_PCM_STREAM_PLAYBACK,
+ LINE6_STREAM_CAPTURE_HELPER);
+ }
+ line6_stream_stop(line6pcm, s->stream,
+ LINE6_STREAM_PCM);
+ break;
+
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ if (s->stream != SNDRV_PCM_STREAM_PLAYBACK)
+ return -EINVAL;
+ set_bit(LINE6_FLAG_PAUSE_PLAYBACK, &line6pcm->flags);
+ break;
+
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ if (s->stream != SNDRV_PCM_STREAM_PLAYBACK)
+ return -EINVAL;
+ clear_bit(LINE6_FLAG_PAUSE_PLAYBACK, &line6pcm->flags);
+ break;
+
+ default:
+ return -EINVAL;
+ }
+ }
+
+ return 0;
+}
+
+/* common PCM pointer callback */
+snd_pcm_uframes_t snd_line6_pointer(struct snd_pcm_substream *substream)
+{
+ struct snd_line6_pcm *line6pcm = snd_pcm_substream_chip(substream);
+ struct line6_pcm_stream *pstr = get_stream(line6pcm, substream->stream);
+
+ return pstr->pos_done;
+}
+
+/* Acquire and optionally start duplex streams:
+ * type is either LINE6_STREAM_IMPULSE or LINE6_STREAM_MONITOR
+ */
+int line6_pcm_acquire(struct snd_line6_pcm *line6pcm, int type, bool start)
+{
+ struct line6_pcm_stream *pstr;
+ int ret = 0, dir;
+
+ /* TODO: We should assert SNDRV_PCM_STREAM_PLAYBACK/CAPTURE == 0/1 */
+ mutex_lock(&line6pcm->state_mutex);
+ for (dir = 0; dir < 2; dir++) {
+ pstr = get_stream(line6pcm, dir);
+ ret = line6_buffer_acquire(line6pcm, pstr, dir, type);
+ if (ret < 0)
+ goto error;
+ if (!pstr->running)
+ line6_wait_clear_audio_urbs(line6pcm, pstr);
+ }
+ if (start) {
+ for (dir = 0; dir < 2; dir++) {
+ ret = line6_stream_start(line6pcm, dir, type);
+ if (ret < 0)
+ goto error;
+ }
+ }
+ error:
+ mutex_unlock(&line6pcm->state_mutex);
+ if (ret < 0)
+ line6_pcm_release(line6pcm, type);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(line6_pcm_acquire);
+
+/* Stop and release duplex streams */
+void line6_pcm_release(struct snd_line6_pcm *line6pcm, int type)
+{
+ struct line6_pcm_stream *pstr;
+ int dir;
+
+ mutex_lock(&line6pcm->state_mutex);
+ for (dir = 0; dir < 2; dir++)
+ line6_stream_stop(line6pcm, dir, type);
+ for (dir = 0; dir < 2; dir++) {
+ pstr = get_stream(line6pcm, dir);
+ line6_buffer_release(line6pcm, pstr, type);
+ }
+ mutex_unlock(&line6pcm->state_mutex);
+}
+EXPORT_SYMBOL_GPL(line6_pcm_release);
+
+/* common PCM hw_params callback */
+int snd_line6_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);
+ struct line6_pcm_stream *pstr = get_stream(line6pcm, substream->stream);
+
+ mutex_lock(&line6pcm->state_mutex);
+ ret = line6_buffer_acquire(line6pcm, pstr, substream->stream,
+ LINE6_STREAM_PCM);
+ if (ret < 0)
+ goto error;
+
+ pstr->period = params_period_bytes(hw_params);
+ error:
+ mutex_unlock(&line6pcm->state_mutex);
+ return ret;
+}
+
+/* common PCM hw_free callback */
+int snd_line6_hw_free(struct snd_pcm_substream *substream)
+{
+ struct snd_line6_pcm *line6pcm = snd_pcm_substream_chip(substream);
+ struct line6_pcm_stream *pstr = get_stream(line6pcm, substream->stream);
+
+ mutex_lock(&line6pcm->state_mutex);
+ line6_buffer_release(line6pcm, pstr, LINE6_STREAM_PCM);
+ mutex_unlock(&line6pcm->state_mutex);
+ return 0;
+}
+
+
+/* control info callback */
+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;
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = 256;
+ return 0;
+}
+
+/* control get callback */
+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 = 0; i < 2; i++)
+ ucontrol->value.integer.value[i] = line6pcm->volume_playback[i];
+
+ return 0;
+}
+
+/* control put callback */
+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 = 0; i < 2; i++)
+ if (line6pcm->volume_playback[i] !=
+ ucontrol->value.integer.value[i]) {
+ line6pcm->volume_playback[i] =
+ ucontrol->value.integer.value[i];
+ changed = 1;
+ }
+
+ return changed;
+}
+
+/* control definition */
+static const struct snd_kcontrol_new line6_controls[] = {
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "PCM Playback Volume",
+ .info = snd_line6_control_playback_info,
+ .get = snd_line6_control_playback_get,
+ .put = snd_line6_control_playback_put
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Impulse Response Volume",
+ .info = snd_line6_impulse_volume_info,
+ .get = snd_line6_impulse_volume_get,
+ .put = snd_line6_impulse_volume_put
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Impulse Response Period",
+ .info = snd_line6_impulse_period_info,
+ .get = snd_line6_impulse_period_get,
+ .put = snd_line6_impulse_period_put
+ },
+};
+
+/*
+ Cleanup the PCM device.
+*/
+static void cleanup_urbs(struct line6_pcm_stream *pcms, int iso_buffers)
+{
+ int i;
+
+ /* Most likely impossible in current code... */
+ if (pcms->urbs == NULL)
+ return;
+
+ for (i = 0; i < iso_buffers; i++) {
+ if (pcms->urbs[i]) {
+ usb_kill_urb(pcms->urbs[i]);
+ usb_free_urb(pcms->urbs[i]);
+ }
+ }
+ kfree(pcms->urbs);
+ pcms->urbs = NULL;
+}
+
+static void line6_cleanup_pcm(struct snd_pcm *pcm)
+{
+ struct snd_line6_pcm *line6pcm = snd_pcm_chip(pcm);
+
+ cleanup_urbs(&line6pcm->out, line6pcm->line6->iso_buffers);
+ cleanup_urbs(&line6pcm->in, line6pcm->line6->iso_buffers);
+ kfree(line6pcm);
+}
+
+/* create a PCM device */
+static int snd_line6_new_pcm(struct usb_line6 *line6, struct snd_pcm **pcm_ret)
+{
+ struct snd_pcm *pcm;
+ int err;
+
+ err = snd_pcm_new(line6->card, (char *)line6->properties->name,
+ 0, 1, 1, pcm_ret);
+ if (err < 0)
+ return err;
+ pcm = *pcm_ret;
+ strcpy(pcm->name, 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_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_CONTINUOUS,
+ NULL, 64 * 1024, 128 * 1024);
+ return 0;
+}
+
+/*
+ Sync with PCM stream stops.
+*/
+void line6_pcm_disconnect(struct snd_line6_pcm *line6pcm)
+{
+ line6_unlink_audio_urbs(line6pcm, &line6pcm->out);
+ line6_unlink_audio_urbs(line6pcm, &line6pcm->in);
+ line6_wait_clear_audio_urbs(line6pcm, &line6pcm->out);
+ line6_wait_clear_audio_urbs(line6pcm, &line6pcm->in);
+}
+
+/*
+ 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)
+{
+ int i, err;
+ unsigned ep_read = line6->properties->ep_audio_r;
+ unsigned ep_write = line6->properties->ep_audio_w;
+ struct snd_pcm *pcm;
+ struct snd_line6_pcm *line6pcm;
+
+ if (!(line6->properties->capabilities & LINE6_CAP_PCM))
+ return 0; /* skip PCM initialization and report success */
+
+ err = snd_line6_new_pcm(line6, &pcm);
+ if (err < 0)
+ return err;
+
+ line6pcm = kzalloc(sizeof(*line6pcm), GFP_KERNEL);
+ if (!line6pcm)
+ return -ENOMEM;
+
+ mutex_init(&line6pcm->state_mutex);
+ line6pcm->pcm = pcm;
+ line6pcm->properties = properties;
+ line6pcm->volume_playback[0] = line6pcm->volume_playback[1] = 255;
+ line6pcm->volume_monitor = 255;
+ line6pcm->line6 = line6;
+
+ spin_lock_init(&line6pcm->out.lock);
+ spin_lock_init(&line6pcm->in.lock);
+ line6pcm->impulse_period = LINE6_IMPULSE_DEFAULT_PERIOD;
+
+ line6->line6pcm = line6pcm;
+
+ pcm->private_data = line6pcm;
+ pcm->private_free = line6_cleanup_pcm;
+
+ line6pcm->max_packet_size_in =
+ usb_maxpacket(line6->usbdev,
+ usb_rcvisocpipe(line6->usbdev, ep_read), 0);
+ line6pcm->max_packet_size_out =
+ usb_maxpacket(line6->usbdev,
+ usb_sndisocpipe(line6->usbdev, ep_write), 1);
+ if (!line6pcm->max_packet_size_in || !line6pcm->max_packet_size_out) {
+ dev_err(line6pcm->line6->ifcdev,
+ "cannot get proper max packet size\n");
+ return -EINVAL;
+ }
+
+ err = line6_create_audio_out_urbs(line6pcm);
+ if (err < 0)
+ return err;
+
+ err = line6_create_audio_in_urbs(line6pcm);
+ if (err < 0)
+ return err;
+
+ /* mixer: */
+ for (i = 0; i < ARRAY_SIZE(line6_controls); i++) {
+ err = snd_ctl_add(line6->card,
+ snd_ctl_new1(&line6_controls[i], line6pcm));
+ if (err < 0)
+ return err;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(line6_init_pcm);
+
+/* prepare pcm callback */
+int snd_line6_prepare(struct snd_pcm_substream *substream)
+{
+ struct snd_line6_pcm *line6pcm = snd_pcm_substream_chip(substream);
+ struct line6_pcm_stream *pstr = get_stream(line6pcm, substream->stream);
+
+ mutex_lock(&line6pcm->state_mutex);
+ if (!pstr->running)
+ line6_wait_clear_audio_urbs(line6pcm, pstr);
+
+ if (!test_and_set_bit(LINE6_FLAG_PREPARED, &line6pcm->flags)) {
+ line6pcm->out.count = 0;
+ line6pcm->out.pos = 0;
+ line6pcm->out.pos_done = 0;
+ line6pcm->out.bytes = 0;
+ line6pcm->in.count = 0;
+ line6pcm->in.pos_done = 0;
+ line6pcm->in.bytes = 0;
+ }
+
+ mutex_unlock(&line6pcm->state_mutex);
+ return 0;
+}
diff --git a/sound/usb/line6/pcm.h b/sound/usb/line6/pcm.h
new file mode 100644
index 000000000..9c683042f
--- /dev/null
+++ b/sound/usb/line6/pcm.h
@@ -0,0 +1,195 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Line 6 Linux USB driver
+ *
+ * Copyright (C) 2004-2010 Markus Grabner (grabner@icg.tugraz.at)
+ */
+
+/*
+ PCM interface to POD series devices.
+*/
+
+#ifndef PCM_H
+#define PCM_H
+
+#include <sound/pcm.h>
+
+#include "driver.h"
+
+/*
+ number of USB frames per URB
+ The Line 6 Windows driver always transmits two frames per packet, but
+ the Linux driver performs significantly better (i.e., lower latency)
+ with only one frame per packet.
+*/
+#define LINE6_ISO_PACKETS 1
+
+/* in a "full speed" device (such as the PODxt Pro) this means 1ms,
+ * for "high speed" it's 1/8ms
+ */
+#define LINE6_ISO_INTERVAL 1
+
+#define LINE6_IMPULSE_DEFAULT_PERIOD 100
+
+/*
+ Get substream from Line 6 PCM data structure
+*/
+#define get_substream(line6pcm, stream) \
+ (line6pcm->pcm->streams[stream].substream)
+
+/*
+ PCM mode bits.
+
+ There are several features of the Line 6 USB driver which require PCM
+ data to be exchanged with the device:
+ *) PCM playback and capture via ALSA
+ *) software monitoring (for devices without hardware monitoring)
+ *) optional impulse response measurement
+ However, from the device's point of view, there is just a single
+ capture and playback stream, which must be shared between these
+ subsystems. It is therefore necessary to maintain the state of the
+ subsystems with respect to PCM usage.
+
+ We define two bit flags, "opened" and "running", for each playback
+ or capture stream. Both can contain the bit flag corresponding to
+ LINE6_STREAM_* type,
+ LINE6_STREAM_PCM = ALSA PCM playback or capture
+ LINE6_STREAM_MONITOR = software monitoring
+ IMPULSE = optional impulse response measurement
+ The opened flag indicates whether the buffer is allocated while
+ the running flag indicates whether the stream is running.
+
+ For monitor or impulse operations, the driver needs to call
+ line6_pcm_acquire() or line6_pcm_release() with the appropriate
+ LINE6_STREAM_* flag.
+*/
+
+/* stream types */
+enum {
+ LINE6_STREAM_PCM,
+ LINE6_STREAM_MONITOR,
+ LINE6_STREAM_IMPULSE,
+ LINE6_STREAM_CAPTURE_HELPER,
+};
+
+/* misc bit flags for PCM operation */
+enum {
+ LINE6_FLAG_PAUSE_PLAYBACK,
+ LINE6_FLAG_PREPARED,
+};
+
+struct line6_pcm_properties {
+ struct snd_pcm_hardware playback_hw, capture_hw;
+ struct snd_pcm_hw_constraint_ratdens rates;
+ int bytes_per_channel;
+};
+
+struct line6_pcm_stream {
+ /* allocated URBs */
+ struct urb **urbs;
+
+ /* Temporary buffer;
+ * Since the packet size is not known in advance, this buffer is
+ * large enough to store maximum size packets.
+ */
+ unsigned char *buffer;
+
+ /* Free frame position in the buffer. */
+ snd_pcm_uframes_t pos;
+
+ /* Count processed bytes;
+ * This is modulo period size (to determine when a period is finished).
+ */
+ unsigned bytes;
+
+ /* Counter to create desired sample rate */
+ unsigned count;
+
+ /* period size in bytes */
+ unsigned period;
+
+ /* Processed frame position in the buffer;
+ * The contents of the 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_done;
+
+ /* Bit mask of active URBs */
+ unsigned long active_urbs;
+
+ /* Bit mask of URBs currently being unlinked */
+ unsigned long unlink_urbs;
+
+ /* Spin lock to protect updates of the buffer positions (not contents)
+ */
+ spinlock_t lock;
+
+ /* Bit flags for operational stream types */
+ unsigned long opened;
+
+ /* Bit flags for running stream types */
+ unsigned long running;
+
+ int last_frame;
+};
+
+struct snd_line6_pcm {
+ /* Pointer back to the Line 6 driver data structure */
+ struct usb_line6 *line6;
+
+ /* Properties. */
+ struct line6_pcm_properties *properties;
+
+ /* ALSA pcm stream */
+ struct snd_pcm *pcm;
+
+ /* protection to state changes of in/out streams */
+ struct mutex state_mutex;
+
+ /* Capture and playback streams */
+ struct line6_pcm_stream in;
+ struct line6_pcm_stream out;
+
+ /* Previously captured frame (for software monitoring) */
+ unsigned char *prev_fbuf;
+
+ /* Size of previously captured frame (for software monitoring/sync) */
+ int prev_fsize;
+
+ /* Maximum size of USB packet */
+ int max_packet_size_in;
+ int max_packet_size_out;
+
+ /* PCM playback volume (left and right) */
+ int volume_playback[2];
+
+ /* PCM monitor volume */
+ int volume_monitor;
+
+ /* Volume of impulse response test signal (if zero, test is disabled) */
+ int impulse_volume;
+
+ /* Period of impulse response test signal */
+ int impulse_period;
+
+ /* Counter for impulse response test signal */
+ int impulse_count;
+
+ /* Several status bits (see LINE6_FLAG_*) */
+ 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);
+extern int snd_line6_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *hw_params);
+extern int snd_line6_hw_free(struct snd_pcm_substream *substream);
+extern snd_pcm_uframes_t snd_line6_pointer(struct snd_pcm_substream *substream);
+extern void line6_pcm_disconnect(struct snd_line6_pcm *line6pcm);
+extern int line6_pcm_acquire(struct snd_line6_pcm *line6pcm, int type,
+ bool start);
+extern void line6_pcm_release(struct snd_line6_pcm *line6pcm, int type);
+
+#endif
diff --git a/sound/usb/line6/playback.c b/sound/usb/line6/playback.c
new file mode 100644
index 000000000..8233c61e2
--- /dev/null
+++ b/sound/usb/line6/playback.c
@@ -0,0 +1,439 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Line 6 Linux USB driver
+ *
+ * Copyright (C) 2004-2010 Markus Grabner (grabner@icg.tugraz.at)
+ */
+
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+
+#include "capture.h"
+#include "driver.h"
+#include "pcm.h"
+#include "playback.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) {
+ __le16 *p, *buf_end;
+
+ p = (__le16 *)urb_out->transfer_buffer;
+ buf_end = p + urb_out->transfer_buffer_length / sizeof(*p);
+
+ for (; p < buf_end; ++p) {
+ short pv = le16_to_cpu(*p);
+ int val = (pv * volume[chn & 1]) >> 8;
+ pv = clamp(val, -0x8000, 0x7fff);
+ *p = cpu_to_le16(pv);
+ ++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;
+
+ val = p[0] + (p[1] << 8) + ((signed char)p[2] << 16);
+ val = (val * volume[chn & 1]) >> 8;
+ val = clamp(val, -0x800000, 0x7fffff);
+ p[0] = val;
+ p[1] = val >> 8;
+ p[2] = val >> 16;
+ ++chn;
+ }
+ }
+}
+
+/*
+ Create signal for impulse response test.
+*/
+static void create_impulse_test_signal(struct snd_line6_pcm *line6pcm,
+ struct urb *urb_out, int bytes_per_frame)
+{
+ int frames = urb_out->transfer_buffer_length / bytes_per_frame;
+
+ if (bytes_per_frame == 4) {
+ int i;
+ short *pi = (short *)line6pcm->prev_fbuf;
+ short *po = (short *)urb_out->transfer_buffer;
+
+ for (i = 0; i < frames; ++i) {
+ po[0] = pi[0];
+ po[1] = 0;
+ pi += 2;
+ po += 2;
+ }
+ } else if (bytes_per_frame == 6) {
+ int i, j;
+ unsigned char *pi = line6pcm->prev_fbuf;
+ unsigned char *po = urb_out->transfer_buffer;
+
+ for (i = 0; i < frames; ++i) {
+ for (j = 0; j < bytes_per_frame / 2; ++j)
+ po[j] = pi[j];
+
+ for (; j < bytes_per_frame; ++j)
+ po[j] = 0;
+
+ pi += bytes_per_frame;
+ po += bytes_per_frame;
+ }
+ }
+ if (--line6pcm->impulse_count <= 0) {
+ ((unsigned char *)(urb_out->transfer_buffer))[bytes_per_frame -
+ 1] =
+ line6pcm->impulse_volume;
+ line6pcm->impulse_count = line6pcm->impulse_period;
+ }
+}
+
+/*
+ Add signal to buffer for software monitoring.
+*/
+static void add_monitor_signal(struct urb *urb_out, unsigned char *signal,
+ int volume, int bytes_per_frame)
+{
+ if (volume == 0)
+ return; /* zero volume - no change */
+
+ if (bytes_per_frame == 4) {
+ __le16 *pi, *po, *buf_end;
+
+ pi = (__le16 *)signal;
+ po = (__le16 *)urb_out->transfer_buffer;
+ buf_end = po + urb_out->transfer_buffer_length / sizeof(*po);
+
+ for (; po < buf_end; ++pi, ++po) {
+ short pov = le16_to_cpu(*po);
+ short piv = le16_to_cpu(*pi);
+ int val = pov + ((piv * volume) >> 8);
+ pov = clamp(val, -0x8000, 0x7fff);
+ *po = cpu_to_le16(pov);
+ }
+ }
+
+ /*
+ We don't need to handle devices with 6 bytes per frame here
+ since they all support hardware monitoring.
+ */
+}
+
+/*
+ Find a free URB, prepare audio data, and submit URB.
+ must be called in line6pcm->out.lock context
+*/
+static int submit_audio_out_urb(struct snd_line6_pcm *line6pcm)
+{
+ int index;
+ int i, urb_size, urb_frames;
+ int ret;
+ const int bytes_per_frame =
+ line6pcm->properties->bytes_per_channel *
+ line6pcm->properties->playback_hw.channels_max;
+ const int frame_increment =
+ line6pcm->properties->rates.rats[0].num_min;
+ const int frame_factor =
+ line6pcm->properties->rates.rats[0].den *
+ (line6pcm->line6->intervals_per_second / LINE6_ISO_INTERVAL);
+ struct urb *urb_out;
+
+ index = find_first_zero_bit(&line6pcm->out.active_urbs,
+ line6pcm->line6->iso_buffers);
+
+ if (index < 0 || index >= line6pcm->line6->iso_buffers) {
+ dev_err(line6pcm->line6->ifcdev, "no free URB found\n");
+ return -EINVAL;
+ }
+
+ urb_out = line6pcm->out.urbs[index];
+ urb_size = 0;
+
+ /* TODO: this may not work for LINE6_ISO_PACKETS != 1 */
+ for (i = 0; i < LINE6_ISO_PACKETS; ++i) {
+ /* compute frame size for given sampling rate */
+ int fsize = 0;
+ struct usb_iso_packet_descriptor *fout =
+ &urb_out->iso_frame_desc[i];
+
+ fsize = line6pcm->prev_fsize;
+ if (fsize == 0) {
+ int n;
+
+ line6pcm->out.count += frame_increment;
+ n = line6pcm->out.count / frame_factor;
+ line6pcm->out.count -= n * frame_factor;
+ fsize = n;
+ }
+
+ fsize *= bytes_per_frame;
+
+ fout->offset = urb_size;
+ fout->length = fsize;
+ urb_size += fsize;
+ }
+
+ if (urb_size == 0) {
+ /* can't determine URB size */
+ dev_err(line6pcm->line6->ifcdev, "driver bug: urb_size = 0\n");
+ return -EINVAL;
+ }
+
+ urb_frames = urb_size / bytes_per_frame;
+ urb_out->transfer_buffer =
+ line6pcm->out.buffer +
+ index * LINE6_ISO_PACKETS * line6pcm->max_packet_size_out;
+ urb_out->transfer_buffer_length = urb_size;
+ urb_out->context = line6pcm;
+
+ if (test_bit(LINE6_STREAM_PCM, &line6pcm->out.running) &&
+ !test_bit(LINE6_FLAG_PAUSE_PLAYBACK, &line6pcm->flags)) {
+ struct snd_pcm_runtime *runtime =
+ get_substream(line6pcm, SNDRV_PCM_STREAM_PLAYBACK)->runtime;
+
+ if (line6pcm->out.pos + 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->out.pos;
+
+ if (len > 0) {
+ memcpy(urb_out->transfer_buffer,
+ runtime->dma_area +
+ line6pcm->out.pos * bytes_per_frame,
+ len * bytes_per_frame);
+ memcpy(urb_out->transfer_buffer +
+ len * bytes_per_frame, runtime->dma_area,
+ (urb_frames - len) * bytes_per_frame);
+ } else
+ dev_err(line6pcm->line6->ifcdev, "driver bug: len = %d\n",
+ len);
+ } else {
+ memcpy(urb_out->transfer_buffer,
+ runtime->dma_area +
+ line6pcm->out.pos * bytes_per_frame,
+ urb_out->transfer_buffer_length);
+ }
+
+ line6pcm->out.pos += urb_frames;
+ if (line6pcm->out.pos >= runtime->buffer_size)
+ line6pcm->out.pos -= runtime->buffer_size;
+
+ change_volume(urb_out, line6pcm->volume_playback,
+ bytes_per_frame);
+ } else {
+ memset(urb_out->transfer_buffer, 0,
+ urb_out->transfer_buffer_length);
+ }
+
+ spin_lock_nested(&line6pcm->in.lock, SINGLE_DEPTH_NESTING);
+ if (line6pcm->prev_fbuf) {
+ if (test_bit(LINE6_STREAM_IMPULSE, &line6pcm->out.running)) {
+ create_impulse_test_signal(line6pcm, urb_out,
+ bytes_per_frame);
+ if (test_bit(LINE6_STREAM_PCM, &line6pcm->in.running)) {
+ line6_capture_copy(line6pcm,
+ urb_out->transfer_buffer,
+ urb_out->
+ transfer_buffer_length);
+ line6_capture_check_period(line6pcm,
+ urb_out->transfer_buffer_length);
+ }
+ } else {
+ if (!(line6pcm->line6->properties->capabilities & LINE6_CAP_HWMON)
+ && line6pcm->out.running && line6pcm->in.running)
+ add_monitor_signal(urb_out, line6pcm->prev_fbuf,
+ line6pcm->volume_monitor,
+ bytes_per_frame);
+ }
+ line6pcm->prev_fbuf = NULL;
+ line6pcm->prev_fsize = 0;
+ }
+ spin_unlock(&line6pcm->in.lock);
+
+ ret = usb_submit_urb(urb_out, GFP_ATOMIC);
+
+ if (ret == 0)
+ set_bit(index, &line6pcm->out.active_urbs);
+ else
+ dev_err(line6pcm->line6->ifcdev,
+ "URB out #%d submission failed (%d)\n", index, ret);
+
+ return 0;
+}
+
+/*
+ Submit all currently available playback URBs.
+ must be called in line6pcm->out.lock context
+ */
+int line6_submit_audio_out_all_urbs(struct snd_line6_pcm *line6pcm)
+{
+ int ret = 0, i;
+
+ for (i = 0; i < line6pcm->line6->iso_buffers; ++i) {
+ ret = submit_audio_out_urb(line6pcm);
+ if (ret < 0)
+ break;
+ }
+
+ return ret;
+}
+
+/*
+ Callback for completed playback URB.
+*/
+static void audio_out_callback(struct urb *urb)
+{
+ int i, index, length = 0, shutdown = 0;
+ unsigned long flags;
+ struct snd_line6_pcm *line6pcm = (struct snd_line6_pcm *)urb->context;
+ struct snd_pcm_substream *substream =
+ get_substream(line6pcm, SNDRV_PCM_STREAM_PLAYBACK);
+ const int bytes_per_frame =
+ line6pcm->properties->bytes_per_channel *
+ line6pcm->properties->playback_hw.channels_max;
+
+#if USE_CLEAR_BUFFER_WORKAROUND
+ memset(urb->transfer_buffer, 0, urb->transfer_buffer_length);
+#endif
+
+ line6pcm->out.last_frame = urb->start_frame;
+
+ /* find index of URB */
+ for (index = 0; index < line6pcm->line6->iso_buffers; index++)
+ if (urb == line6pcm->out.urbs[index])
+ break;
+
+ if (index >= line6pcm->line6->iso_buffers)
+ return; /* URB has been unlinked asynchronously */
+
+ for (i = 0; i < LINE6_ISO_PACKETS; i++)
+ length += urb->iso_frame_desc[i].length;
+
+ spin_lock_irqsave(&line6pcm->out.lock, flags);
+
+ if (test_bit(LINE6_STREAM_PCM, &line6pcm->out.running)) {
+ struct snd_pcm_runtime *runtime = substream->runtime;
+
+ line6pcm->out.pos_done +=
+ length / bytes_per_frame;
+
+ if (line6pcm->out.pos_done >= runtime->buffer_size)
+ line6pcm->out.pos_done -= runtime->buffer_size;
+ }
+
+ clear_bit(index, &line6pcm->out.active_urbs);
+
+ for (i = 0; i < LINE6_ISO_PACKETS; i++)
+ if (urb->iso_frame_desc[i].status == -EXDEV) {
+ shutdown = 1;
+ break;
+ }
+
+ if (test_and_clear_bit(index, &line6pcm->out.unlink_urbs))
+ shutdown = 1;
+
+ if (!shutdown) {
+ submit_audio_out_urb(line6pcm);
+
+ if (test_bit(LINE6_STREAM_PCM, &line6pcm->out.running)) {
+ line6pcm->out.bytes += length;
+ if (line6pcm->out.bytes >= line6pcm->out.period) {
+ line6pcm->out.bytes %= line6pcm->out.period;
+ spin_unlock(&line6pcm->out.lock);
+ snd_pcm_period_elapsed(substream);
+ spin_lock(&line6pcm->out.lock);
+ }
+ }
+ }
+ spin_unlock_irqrestore(&line6pcm->out.lock, flags);
+}
+
+/* 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);
+
+ err = snd_pcm_hw_constraint_ratdens(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
+ &line6pcm->properties->rates);
+ if (err < 0)
+ return err;
+
+ runtime->hw = line6pcm->properties->playback_hw;
+ return 0;
+}
+
+/* close playback callback */
+static int snd_line6_playback_close(struct snd_pcm_substream *substream)
+{
+ return 0;
+}
+
+/* playback operators */
+const struct snd_pcm_ops snd_line6_playback_ops = {
+ .open = snd_line6_playback_open,
+ .close = snd_line6_playback_close,
+ .hw_params = snd_line6_hw_params,
+ .hw_free = snd_line6_hw_free,
+ .prepare = snd_line6_prepare,
+ .trigger = snd_line6_trigger,
+ .pointer = snd_line6_pointer,
+};
+
+int line6_create_audio_out_urbs(struct snd_line6_pcm *line6pcm)
+{
+ struct usb_line6 *line6 = line6pcm->line6;
+ int i;
+
+ line6pcm->out.urbs = kcalloc(line6->iso_buffers, sizeof(struct urb *),
+ GFP_KERNEL);
+ if (line6pcm->out.urbs == NULL)
+ return -ENOMEM;
+
+ /* 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->out.urbs[i] =
+ usb_alloc_urb(LINE6_ISO_PACKETS, GFP_KERNEL);
+
+ if (urb == NULL)
+ return -ENOMEM;
+
+ urb->dev = line6->usbdev;
+ urb->pipe =
+ usb_sndisocpipe(line6->usbdev,
+ line6->properties->ep_audio_w &
+ 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;
+ if (usb_urb_ep_type_check(urb))
+ return -EINVAL;
+ }
+
+ return 0;
+}
diff --git a/sound/usb/line6/playback.h b/sound/usb/line6/playback.h
new file mode 100644
index 000000000..2ca832c83
--- /dev/null
+++ b/sound/usb/line6/playback.h
@@ -0,0 +1,31 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Line 6 Linux USB driver
+ *
+ * Copyright (C) 2004-2010 Markus Grabner (grabner@icg.tugraz.at)
+ */
+
+#ifndef PLAYBACK_H
+#define PLAYBACK_H
+
+#include <sound/pcm.h>
+
+#include "driver.h"
+
+/*
+ * When the TonePort is used with jack in full duplex mode and the outputs are
+ * not connected, the software monitor produces an ugly noise since everything
+ * written to the output buffer (i.e., the input signal) will be repeated in
+ * the next period (sounds like a delay effect). As a workaround, the output
+ * buffer is cleared after the data have been read, but there must be a better
+ * solution. Until one is found, this workaround can be used to fix the
+ * problem.
+ */
+#define USE_CLEAR_BUFFER_WORKAROUND 1
+
+extern const struct snd_pcm_ops snd_line6_playback_ops;
+
+extern int line6_create_audio_out_urbs(struct snd_line6_pcm *line6pcm);
+extern int line6_submit_audio_out_all_urbs(struct snd_line6_pcm *line6pcm);
+
+#endif
diff --git a/sound/usb/line6/pod.c b/sound/usb/line6/pod.c
new file mode 100644
index 000000000..54be5ac91
--- /dev/null
+++ b/sound/usb/line6/pod.c
@@ -0,0 +1,542 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Line 6 Linux USB driver
+ *
+ * Copyright (C) 2004-2010 Markus Grabner (grabner@icg.tugraz.at)
+ */
+
+#include <linux/slab.h>
+#include <linux/wait.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/usb.h>
+
+#include <sound/core.h>
+#include <sound/control.h>
+
+#include "capture.h"
+#include "driver.h"
+#include "playback.h"
+
+/*
+ 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 1000
+
+/*
+ Stages of POD startup procedure
+*/
+enum {
+ POD_STARTUP_VERSIONREQ,
+ POD_STARTUP_SETUP,
+ POD_STARTUP_DONE,
+};
+
+enum {
+ LINE6_BASSPODXT,
+ LINE6_BASSPODXTLIVE,
+ LINE6_BASSPODXTPRO,
+ LINE6_POCKETPOD,
+ LINE6_PODXT,
+ LINE6_PODXTLIVE_POD,
+ LINE6_PODXTPRO,
+};
+
+struct usb_line6_pod {
+ /* Generic Line 6 USB data */
+ struct usb_line6 line6;
+
+ /* Instrument monitor level */
+ int monitor_level;
+
+ /* Current progress in startup procedure */
+ int startup_progress;
+
+ /* Serial number of device */
+ u32 serial_number;
+
+ /* Firmware version (x 100) */
+ int firmware_version;
+
+ /* Device ID */
+ int device_id;
+};
+
+#define line6_to_pod(x) container_of(x, struct usb_line6_pod, line6)
+
+#define POD_SYSEX_CODE 3
+
+/* *INDENT-OFF* */
+
+enum {
+ 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
+
+ /* dumps entire internal memory of PODxt Pro */
+ /* POD_SYSEX_DUMPMEM2 = 0x76 */
+};
+
+enum {
+ POD_MONITOR_LEVEL = 0x04,
+ POD_SYSTEM_INVALID = 0x10000
+};
+
+/* *INDENT-ON* */
+
+enum {
+ POD_DUMP_MEMORY = 2
+};
+
+enum {
+ POD_BUSY_READ,
+ POD_BUSY_WRITE,
+ POD_CHANNEL_DIRTY,
+ POD_SAVE_PRESSED,
+ POD_BUSY_MIDISEND
+};
+
+static const struct snd_ratden pod_ratden = {
+ .num_min = 78125,
+ .num_max = 78125,
+ .num_step = 1,
+ .den = 2
+};
+
+static struct line6_pcm_properties pod_pcm_properties = {
+ .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 = 64,
+ .period_bytes_max = 8192,
+ .periods_min = 1,
+ .periods_max = 1024},
+ .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 = 64,
+ .period_bytes_max = 8192,
+ .periods_min = 1,
+ .periods_max = 1024},
+ .rates = {
+ .nrats = 1,
+ .rats = &pod_ratden},
+ .bytes_per_channel = 3 /* SNDRV_PCM_FMTBIT_S24_3LE */
+};
+
+
+static const char pod_version_header[] = {
+ 0xf0, 0x7e, 0x7f, 0x06, 0x02
+};
+
+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);
+}
+
+/*
+ Process a completely received message.
+*/
+static void line6_pod_process_message(struct usb_line6 *line6)
+{
+ struct usb_line6_pod *pod = line6_to_pod(line6);
+ const unsigned char *buf = pod->line6.buffer_message;
+
+ if (memcmp(buf, pod_version_header, sizeof(pod_version_header)) == 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];
+ if (pod->startup_progress == POD_STARTUP_VERSIONREQ) {
+ pod->startup_progress = POD_STARTUP_SETUP;
+ schedule_delayed_work(&line6->startup_work, 0);
+ }
+ return;
+ }
+
+ /* Only look for sysex messages from this device */
+ if (buf[0] != (LINE6_SYSEX_BEGIN | LINE6_CHANNEL_DEVICE) &&
+ buf[0] != (LINE6_SYSEX_BEGIN | LINE6_CHANNEL_UNKNOWN)) {
+ return;
+ }
+ if (memcmp(buf + 1, line6_midi_id, sizeof(line6_midi_id)) != 0)
+ return;
+
+ if (buf[5] == POD_SYSEX_SYSTEM && buf[6] == POD_MONITOR_LEVEL) {
+ short value = ((int)buf[7] << 12) | ((int)buf[8] << 8) |
+ ((int)buf[9] << 4) | (int)buf[10];
+ pod->monitor_level = value;
+ }
+}
+
+/*
+ Send system parameter (from integer).
+*/
+static int pod_set_system_param_int(struct usb_line6_pod *pod, int value,
+ int code)
+{
+ char *sysex;
+ static const int size = 5;
+
+ sysex = pod_alloc_sysex_buffer(pod, POD_SYSEX_SYSTEM, size);
+ if (!sysex)
+ return -ENOMEM;
+ 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 0;
+}
+
+/*
+ "read" request on "serial_number" special file.
+*/
+static ssize_t serial_number_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct snd_card *card = dev_to_snd_card(dev);
+ struct usb_line6_pod *pod = card->private_data;
+
+ return sprintf(buf, "%u\n", pod->serial_number);
+}
+
+/*
+ "read" request on "firmware_version" special file.
+*/
+static ssize_t firmware_version_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct snd_card *card = dev_to_snd_card(dev);
+ struct usb_line6_pod *pod = card->private_data;
+
+ return sprintf(buf, "%d.%02d\n", pod->firmware_version / 100,
+ pod->firmware_version % 100);
+}
+
+/*
+ "read" request on "device_id" special file.
+*/
+static ssize_t device_id_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct snd_card *card = dev_to_snd_card(dev);
+ struct usb_line6_pod *pod = card->private_data;
+
+ return sprintf(buf, "%d\n", pod->device_id);
+}
+
+/*
+ POD startup procedure.
+ This is a sequence of functions with special requirements (e.g., must
+ not run immediately after initialization, must not run in interrupt
+ context). After the last one has finished, the device is ready to use.
+*/
+
+static void pod_startup(struct usb_line6 *line6)
+{
+ struct usb_line6_pod *pod = line6_to_pod(line6);
+
+ switch (pod->startup_progress) {
+ case POD_STARTUP_VERSIONREQ:
+ /* request firmware version: */
+ line6_version_request_async(line6);
+ break;
+ case POD_STARTUP_SETUP:
+ /* serial number: */
+ line6_read_serial_number(&pod->line6, &pod->serial_number);
+
+ /* ALSA audio interface: */
+ if (snd_card_register(line6->card))
+ dev_err(line6->ifcdev, "Failed to register POD card.\n");
+ pod->startup_progress = POD_STARTUP_DONE;
+ break;
+ default:
+ break;
+ }
+}
+
+/* POD special files: */
+static DEVICE_ATTR_RO(device_id);
+static DEVICE_ATTR_RO(firmware_version);
+static DEVICE_ATTR_RO(serial_number);
+
+static struct attribute *pod_dev_attrs[] = {
+ &dev_attr_device_id.attr,
+ &dev_attr_firmware_version.attr,
+ &dev_attr_serial_number.attr,
+ NULL
+};
+
+static const struct attribute_group pod_dev_attr_group = {
+ .name = "pod",
+ .attrs = pod_dev_attrs,
+};
+
+/* control info callback */
+static int snd_pod_control_monitor_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = 1;
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = 65535;
+ return 0;
+}
+
+/* control get callback */
+static int snd_pod_control_monitor_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_line6_pcm *line6pcm = snd_kcontrol_chip(kcontrol);
+ struct usb_line6_pod *pod = line6_to_pod(line6pcm->line6);
+
+ ucontrol->value.integer.value[0] = pod->monitor_level;
+ return 0;
+}
+
+/* control put callback */
+static int snd_pod_control_monitor_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_line6_pcm *line6pcm = snd_kcontrol_chip(kcontrol);
+ struct usb_line6_pod *pod = line6_to_pod(line6pcm->line6);
+
+ if (ucontrol->value.integer.value[0] == pod->monitor_level)
+ return 0;
+
+ pod->monitor_level = ucontrol->value.integer.value[0];
+ pod_set_system_param_int(pod, ucontrol->value.integer.value[0],
+ POD_MONITOR_LEVEL);
+ return 1;
+}
+
+/* control definition */
+static const struct snd_kcontrol_new pod_control_monitor = {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Monitor Playback Volume",
+ .index = 0,
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+ .info = snd_pod_control_monitor_info,
+ .get = snd_pod_control_monitor_get,
+ .put = snd_pod_control_monitor_put
+};
+
+/*
+ Try to init POD device.
+*/
+static int pod_init(struct usb_line6 *line6,
+ const struct usb_device_id *id)
+{
+ int err;
+ struct usb_line6_pod *pod = line6_to_pod(line6);
+
+ line6->process_message = line6_pod_process_message;
+ line6->startup = pod_startup;
+
+ /* create sysfs entries: */
+ err = snd_card_add_dev_attr(line6->card, &pod_dev_attr_group);
+ if (err < 0)
+ return err;
+
+ /* initialize PCM subsystem: */
+ err = line6_init_pcm(line6, &pod_pcm_properties);
+ if (err < 0)
+ return err;
+
+ /* register monitor control: */
+ err = snd_ctl_add(line6->card,
+ snd_ctl_new1(&pod_control_monitor, line6->line6pcm));
+ if (err < 0)
+ return err;
+
+ /*
+ When the sound card is registered at this point, the PODxt Live
+ displays "Invalid Code Error 07", so we do it later in the event
+ handler.
+ */
+
+ if (pod->line6.properties->capabilities & LINE6_CAP_CONTROL) {
+ pod->monitor_level = POD_SYSTEM_INVALID;
+
+ /* initiate startup procedure: */
+ schedule_delayed_work(&line6->startup_work,
+ msecs_to_jiffies(POD_STARTUP_DELAY));
+ }
+
+ return 0;
+}
+
+#define LINE6_DEVICE(prod) USB_DEVICE(0x0e41, prod)
+#define LINE6_IF_NUM(prod, n) USB_DEVICE_INTERFACE_NUMBER(0x0e41, prod, n)
+
+/* table of devices that work with this driver */
+static const struct usb_device_id pod_id_table[] = {
+ { LINE6_DEVICE(0x4250), .driver_info = LINE6_BASSPODXT },
+ { LINE6_DEVICE(0x4642), .driver_info = LINE6_BASSPODXTLIVE },
+ { LINE6_DEVICE(0x4252), .driver_info = LINE6_BASSPODXTPRO },
+ { LINE6_IF_NUM(0x5051, 1), .driver_info = LINE6_POCKETPOD },
+ { LINE6_DEVICE(0x5044), .driver_info = LINE6_PODXT },
+ { LINE6_IF_NUM(0x4650, 0), .driver_info = LINE6_PODXTLIVE_POD },
+ { LINE6_DEVICE(0x5050), .driver_info = LINE6_PODXTPRO },
+ {}
+};
+
+MODULE_DEVICE_TABLE(usb, pod_id_table);
+
+static const struct line6_properties pod_properties_table[] = {
+ [LINE6_BASSPODXT] = {
+ .id = "BassPODxt",
+ .name = "BassPODxt",
+ .capabilities = LINE6_CAP_CONTROL
+ | LINE6_CAP_CONTROL_MIDI
+ | LINE6_CAP_PCM
+ | LINE6_CAP_HWMON,
+ .altsetting = 5,
+ .ep_ctrl_r = 0x84,
+ .ep_ctrl_w = 0x03,
+ .ep_audio_r = 0x82,
+ .ep_audio_w = 0x01,
+ },
+ [LINE6_BASSPODXTLIVE] = {
+ .id = "BassPODxtLive",
+ .name = "BassPODxt Live",
+ .capabilities = LINE6_CAP_CONTROL
+ | LINE6_CAP_CONTROL_MIDI
+ | LINE6_CAP_PCM
+ | LINE6_CAP_HWMON,
+ .altsetting = 1,
+ .ep_ctrl_r = 0x84,
+ .ep_ctrl_w = 0x03,
+ .ep_audio_r = 0x82,
+ .ep_audio_w = 0x01,
+ },
+ [LINE6_BASSPODXTPRO] = {
+ .id = "BassPODxtPro",
+ .name = "BassPODxt Pro",
+ .capabilities = LINE6_CAP_CONTROL
+ | LINE6_CAP_CONTROL_MIDI
+ | LINE6_CAP_PCM
+ | LINE6_CAP_HWMON,
+ .altsetting = 5,
+ .ep_ctrl_r = 0x84,
+ .ep_ctrl_w = 0x03,
+ .ep_audio_r = 0x82,
+ .ep_audio_w = 0x01,
+ },
+ [LINE6_POCKETPOD] = {
+ .id = "PocketPOD",
+ .name = "Pocket POD",
+ .capabilities = LINE6_CAP_CONTROL
+ | LINE6_CAP_CONTROL_MIDI,
+ .altsetting = 0,
+ .ep_ctrl_r = 0x82,
+ .ep_ctrl_w = 0x02,
+ /* no audio channel */
+ },
+ [LINE6_PODXT] = {
+ .id = "PODxt",
+ .name = "PODxt",
+ .capabilities = LINE6_CAP_CONTROL
+ | LINE6_CAP_CONTROL_MIDI
+ | LINE6_CAP_PCM
+ | LINE6_CAP_HWMON,
+ .altsetting = 5,
+ .ep_ctrl_r = 0x84,
+ .ep_ctrl_w = 0x03,
+ .ep_audio_r = 0x82,
+ .ep_audio_w = 0x01,
+ },
+ [LINE6_PODXTLIVE_POD] = {
+ .id = "PODxtLive",
+ .name = "PODxt Live",
+ .capabilities = LINE6_CAP_CONTROL
+ | LINE6_CAP_CONTROL_MIDI
+ | LINE6_CAP_PCM
+ | LINE6_CAP_HWMON,
+ .altsetting = 1,
+ .ep_ctrl_r = 0x84,
+ .ep_ctrl_w = 0x03,
+ .ep_audio_r = 0x82,
+ .ep_audio_w = 0x01,
+ },
+ [LINE6_PODXTPRO] = {
+ .id = "PODxtPro",
+ .name = "PODxt Pro",
+ .capabilities = LINE6_CAP_CONTROL
+ | LINE6_CAP_CONTROL_MIDI
+ | LINE6_CAP_PCM
+ | LINE6_CAP_HWMON,
+ .altsetting = 5,
+ .ep_ctrl_r = 0x84,
+ .ep_ctrl_w = 0x03,
+ .ep_audio_r = 0x82,
+ .ep_audio_w = 0x01,
+ },
+};
+
+/*
+ Probe USB device.
+*/
+static int pod_probe(struct usb_interface *interface,
+ const struct usb_device_id *id)
+{
+ return line6_probe(interface, id, "Line6-POD",
+ &pod_properties_table[id->driver_info],
+ pod_init, sizeof(struct usb_line6_pod));
+}
+
+static struct usb_driver pod_driver = {
+ .name = KBUILD_MODNAME,
+ .probe = pod_probe,
+ .disconnect = line6_disconnect,
+#ifdef CONFIG_PM
+ .suspend = line6_suspend,
+ .resume = line6_resume,
+ .reset_resume = line6_resume,
+#endif
+ .id_table = pod_id_table,
+};
+
+module_usb_driver(pod_driver);
+
+MODULE_DESCRIPTION("Line 6 POD USB driver");
+MODULE_LICENSE("GPL");
diff --git a/sound/usb/line6/podhd.c b/sound/usb/line6/podhd.c
new file mode 100644
index 000000000..b24bc82f8
--- /dev/null
+++ b/sound/usb/line6/podhd.c
@@ -0,0 +1,559 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Line 6 Pod HD
+ *
+ * Copyright (C) 2011 Stefan Hajnoczi <stefanha@gmail.com>
+ * Copyright (C) 2015 Andrej Krutak <dev@andree.sk>
+ * Copyright (C) 2017 Hans P. Moller <hmoller@uc.cl>
+ */
+
+#include <linux/usb.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <sound/core.h>
+#include <sound/control.h>
+#include <sound/pcm.h>
+
+#include "driver.h"
+#include "pcm.h"
+
+#define PODHD_STARTUP_DELAY 500
+
+enum {
+ LINE6_PODHD300,
+ LINE6_PODHD400,
+ LINE6_PODHD500,
+ LINE6_PODX3,
+ LINE6_PODX3LIVE,
+ LINE6_PODHD500X,
+ LINE6_PODHDDESKTOP
+};
+
+struct usb_line6_podhd {
+ /* Generic Line 6 USB data */
+ struct usb_line6 line6;
+
+ /* Serial number of device */
+ u32 serial_number;
+
+ /* Firmware version */
+ int firmware_version;
+
+ /* Monitor level */
+ int monitor_level;
+};
+
+#define line6_to_podhd(x) container_of(x, struct usb_line6_podhd, line6)
+
+static const struct snd_ratden podhd_ratden = {
+ .num_min = 48000,
+ .num_max = 48000,
+ .num_step = 1,
+ .den = 1,
+};
+
+static struct line6_pcm_properties podhd_pcm_properties = {
+ .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_48000,
+ .rate_min = 48000,
+ .rate_max = 48000,
+ .channels_min = 2,
+ .channels_max = 2,
+ .buffer_bytes_max = 60000,
+ .period_bytes_min = 64,
+ .period_bytes_max = 8192,
+ .periods_min = 1,
+ .periods_max = 1024},
+ .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_48000,
+ .rate_min = 48000,
+ .rate_max = 48000,
+ .channels_min = 2,
+ .channels_max = 2,
+ .buffer_bytes_max = 60000,
+ .period_bytes_min = 64,
+ .period_bytes_max = 8192,
+ .periods_min = 1,
+ .periods_max = 1024},
+ .rates = {
+ .nrats = 1,
+ .rats = &podhd_ratden},
+ .bytes_per_channel = 3 /* SNDRV_PCM_FMTBIT_S24_3LE */
+};
+
+static struct line6_pcm_properties podx3_pcm_properties = {
+ .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_48000,
+ .rate_min = 48000,
+ .rate_max = 48000,
+ .channels_min = 2,
+ .channels_max = 2,
+ .buffer_bytes_max = 60000,
+ .period_bytes_min = 64,
+ .period_bytes_max = 8192,
+ .periods_min = 1,
+ .periods_max = 1024},
+ .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_48000,
+ .rate_min = 48000,
+ .rate_max = 48000,
+ /* 1+2: Main signal (out), 3+4: Tone 1,
+ * 5+6: Tone 2, 7+8: raw
+ */
+ .channels_min = 8,
+ .channels_max = 8,
+ .buffer_bytes_max = 60000,
+ .period_bytes_min = 64,
+ .period_bytes_max = 8192,
+ .periods_min = 1,
+ .periods_max = 1024},
+ .rates = {
+ .nrats = 1,
+ .rats = &podhd_ratden},
+ .bytes_per_channel = 3 /* SNDRV_PCM_FMTBIT_S24_3LE */
+};
+static struct usb_driver podhd_driver;
+
+static ssize_t serial_number_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct snd_card *card = dev_to_snd_card(dev);
+ struct usb_line6_podhd *pod = card->private_data;
+
+ return sprintf(buf, "%u\n", pod->serial_number);
+}
+
+static ssize_t firmware_version_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct snd_card *card = dev_to_snd_card(dev);
+ struct usb_line6_podhd *pod = card->private_data;
+
+ return sprintf(buf, "%06x\n", pod->firmware_version);
+}
+
+static DEVICE_ATTR_RO(firmware_version);
+static DEVICE_ATTR_RO(serial_number);
+
+static struct attribute *podhd_dev_attrs[] = {
+ &dev_attr_firmware_version.attr,
+ &dev_attr_serial_number.attr,
+ NULL
+};
+
+static const struct attribute_group podhd_dev_attr_group = {
+ .name = "podhd",
+ .attrs = podhd_dev_attrs,
+};
+
+/*
+ * POD X3 startup procedure.
+ *
+ * May be compatible with other POD HD's, since it's also similar to the
+ * previous POD setup. In any case, it doesn't seem to be required for the
+ * audio nor bulk interfaces to work.
+ */
+
+static int podhd_dev_start(struct usb_line6_podhd *pod)
+{
+ int ret;
+ u8 init_bytes[8];
+ int i;
+ struct usb_device *usbdev = pod->line6.usbdev;
+
+ ret = usb_control_msg_send(usbdev, 0,
+ 0x67, USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_OUT,
+ 0x11, 0,
+ NULL, 0, LINE6_TIMEOUT, GFP_KERNEL);
+ if (ret) {
+ dev_err(pod->line6.ifcdev, "read request failed (error %d)\n", ret);
+ goto exit;
+ }
+
+ /* NOTE: looks like some kind of ping message */
+ ret = usb_control_msg_recv(usbdev, 0, 0x67,
+ USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_IN,
+ 0x11, 0x0,
+ init_bytes, 3, LINE6_TIMEOUT, GFP_KERNEL);
+ if (ret) {
+ dev_err(pod->line6.ifcdev,
+ "receive length failed (error %d)\n", ret);
+ goto exit;
+ }
+
+ pod->firmware_version =
+ (init_bytes[0] << 16) | (init_bytes[1] << 8) | (init_bytes[2] << 0);
+
+ for (i = 0; i <= 16; i++) {
+ ret = line6_read_data(&pod->line6, 0xf000 + 0x08 * i, init_bytes, 8);
+ if (ret < 0)
+ goto exit;
+ }
+
+ ret = usb_control_msg_send(usbdev, 0,
+ USB_REQ_SET_FEATURE,
+ USB_TYPE_STANDARD | USB_RECIP_DEVICE | USB_DIR_OUT,
+ 1, 0,
+ NULL, 0, LINE6_TIMEOUT, GFP_KERNEL);
+exit:
+ return ret;
+}
+
+static void podhd_startup(struct usb_line6 *line6)
+{
+ struct usb_line6_podhd *pod = line6_to_podhd(line6);
+
+ podhd_dev_start(pod);
+ line6_read_serial_number(&pod->line6, &pod->serial_number);
+ if (snd_card_register(line6->card))
+ dev_err(line6->ifcdev, "Failed to register POD HD card.\n");
+}
+
+static void podhd_disconnect(struct usb_line6 *line6)
+{
+ struct usb_line6_podhd *pod = line6_to_podhd(line6);
+
+ if (pod->line6.properties->capabilities & LINE6_CAP_CONTROL_INFO) {
+ struct usb_interface *intf;
+
+ intf = usb_ifnum_to_if(line6->usbdev,
+ pod->line6.properties->ctrl_if);
+ if (intf)
+ usb_driver_release_interface(&podhd_driver, intf);
+ }
+}
+
+static const unsigned int float_zero_to_one_lookup[] = {
+0x00000000, 0x3c23d70a, 0x3ca3d70a, 0x3cf5c28f, 0x3d23d70a, 0x3d4ccccd,
+0x3d75c28f, 0x3d8f5c29, 0x3da3d70a, 0x3db851ec, 0x3dcccccd, 0x3de147ae,
+0x3df5c28f, 0x3e051eb8, 0x3e0f5c29, 0x3e19999a, 0x3e23d70a, 0x3e2e147b,
+0x3e3851ec, 0x3e428f5c, 0x3e4ccccd, 0x3e570a3d, 0x3e6147ae, 0x3e6b851f,
+0x3e75c28f, 0x3e800000, 0x3e851eb8, 0x3e8a3d71, 0x3e8f5c29, 0x3e947ae1,
+0x3e99999a, 0x3e9eb852, 0x3ea3d70a, 0x3ea8f5c3, 0x3eae147b, 0x3eb33333,
+0x3eb851ec, 0x3ebd70a4, 0x3ec28f5c, 0x3ec7ae14, 0x3ecccccd, 0x3ed1eb85,
+0x3ed70a3d, 0x3edc28f6, 0x3ee147ae, 0x3ee66666, 0x3eeb851f, 0x3ef0a3d7,
+0x3ef5c28f, 0x3efae148, 0x3f000000, 0x3f028f5c, 0x3f051eb8, 0x3f07ae14,
+0x3f0a3d71, 0x3f0ccccd, 0x3f0f5c29, 0x3f11eb85, 0x3f147ae1, 0x3f170a3d,
+0x3f19999a, 0x3f1c28f6, 0x3f1eb852, 0x3f2147ae, 0x3f23d70a, 0x3f266666,
+0x3f28f5c3, 0x3f2b851f, 0x3f2e147b, 0x3f30a3d7, 0x3f333333, 0x3f35c28f,
+0x3f3851ec, 0x3f3ae148, 0x3f3d70a4, 0x3f400000, 0x3f428f5c, 0x3f451eb8,
+0x3f47ae14, 0x3f4a3d71, 0x3f4ccccd, 0x3f4f5c29, 0x3f51eb85, 0x3f547ae1,
+0x3f570a3d, 0x3f59999a, 0x3f5c28f6, 0x3f5eb852, 0x3f6147ae, 0x3f63d70a,
+0x3f666666, 0x3f68f5c3, 0x3f6b851f, 0x3f6e147b, 0x3f70a3d7, 0x3f733333,
+0x3f75c28f, 0x3f7851ec, 0x3f7ae148, 0x3f7d70a4, 0x3f800000
+};
+
+static void podhd_set_monitor_level(struct usb_line6_podhd *podhd, int value)
+{
+ unsigned int fl;
+ static const unsigned char msg[16] = {
+ /* Chunk is 0xc bytes (without first word) */
+ 0x0c, 0x00,
+ /* First chunk in the message */
+ 0x01, 0x00,
+ /* Message size is 2 4-byte words */
+ 0x02, 0x00,
+ /* Unknown */
+ 0x04, 0x41,
+ /* Unknown */
+ 0x04, 0x00, 0x13, 0x00,
+ /* Volume, LE float32, 0.0 - 1.0 */
+ 0x00, 0x00, 0x00, 0x00
+ };
+ unsigned char *buf;
+
+ buf = kmemdup(msg, sizeof(msg), GFP_KERNEL);
+ if (!buf)
+ return;
+
+ if (value < 0)
+ value = 0;
+
+ if (value >= ARRAY_SIZE(float_zero_to_one_lookup))
+ value = ARRAY_SIZE(float_zero_to_one_lookup) - 1;
+
+ fl = float_zero_to_one_lookup[value];
+
+ buf[12] = (fl >> 0) & 0xff;
+ buf[13] = (fl >> 8) & 0xff;
+ buf[14] = (fl >> 16) & 0xff;
+ buf[15] = (fl >> 24) & 0xff;
+
+ line6_send_raw_message(&podhd->line6, buf, sizeof(msg));
+ kfree(buf);
+
+ podhd->monitor_level = value;
+}
+
+/* control info callback */
+static int snd_podhd_control_monitor_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = 1;
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = 100;
+ uinfo->value.integer.step = 1;
+ return 0;
+}
+
+/* control get callback */
+static int snd_podhd_control_monitor_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_line6_pcm *line6pcm = snd_kcontrol_chip(kcontrol);
+ struct usb_line6_podhd *podhd = line6_to_podhd(line6pcm->line6);
+
+ ucontrol->value.integer.value[0] = podhd->monitor_level;
+ return 0;
+}
+
+/* control put callback */
+static int snd_podhd_control_monitor_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_line6_pcm *line6pcm = snd_kcontrol_chip(kcontrol);
+ struct usb_line6_podhd *podhd = line6_to_podhd(line6pcm->line6);
+
+ if (ucontrol->value.integer.value[0] == podhd->monitor_level)
+ return 0;
+
+ podhd_set_monitor_level(podhd, ucontrol->value.integer.value[0]);
+ return 1;
+}
+
+/* control definition */
+static const struct snd_kcontrol_new podhd_control_monitor = {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Monitor Playback Volume",
+ .index = 0,
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+ .info = snd_podhd_control_monitor_info,
+ .get = snd_podhd_control_monitor_get,
+ .put = snd_podhd_control_monitor_put
+};
+
+/*
+ Try to init POD HD device.
+*/
+static int podhd_init(struct usb_line6 *line6,
+ const struct usb_device_id *id)
+{
+ int err;
+ struct usb_line6_podhd *pod = line6_to_podhd(line6);
+ struct usb_interface *intf;
+
+ line6->disconnect = podhd_disconnect;
+ line6->startup = podhd_startup;
+
+ if (pod->line6.properties->capabilities & LINE6_CAP_CONTROL) {
+ /* claim the data interface */
+ intf = usb_ifnum_to_if(line6->usbdev,
+ pod->line6.properties->ctrl_if);
+ if (!intf) {
+ dev_err(pod->line6.ifcdev, "interface %d not found\n",
+ pod->line6.properties->ctrl_if);
+ return -ENODEV;
+ }
+
+ err = usb_driver_claim_interface(&podhd_driver, intf, NULL);
+ if (err != 0) {
+ dev_err(pod->line6.ifcdev, "can't claim interface %d, error %d\n",
+ pod->line6.properties->ctrl_if, err);
+ return err;
+ }
+ }
+
+ if (pod->line6.properties->capabilities & LINE6_CAP_CONTROL_INFO) {
+ /* create sysfs entries: */
+ err = snd_card_add_dev_attr(line6->card, &podhd_dev_attr_group);
+ if (err < 0)
+ return err;
+ }
+
+ if (pod->line6.properties->capabilities & LINE6_CAP_PCM) {
+ /* initialize PCM subsystem: */
+ err = line6_init_pcm(line6,
+ (id->driver_info == LINE6_PODX3 ||
+ id->driver_info == LINE6_PODX3LIVE) ? &podx3_pcm_properties :
+ &podhd_pcm_properties);
+ if (err < 0)
+ return err;
+ }
+
+ if (pod->line6.properties->capabilities & LINE6_CAP_HWMON_CTL) {
+ podhd_set_monitor_level(pod, 100);
+ err = snd_ctl_add(line6->card,
+ snd_ctl_new1(&podhd_control_monitor,
+ line6->line6pcm));
+ if (err < 0)
+ return err;
+ }
+
+ if (!(pod->line6.properties->capabilities & LINE6_CAP_CONTROL_INFO)) {
+ /* register USB audio system directly */
+ return snd_card_register(line6->card);
+ }
+
+ /* init device and delay registering */
+ schedule_delayed_work(&line6->startup_work,
+ msecs_to_jiffies(PODHD_STARTUP_DELAY));
+ return 0;
+}
+
+#define LINE6_DEVICE(prod) USB_DEVICE(0x0e41, prod)
+#define LINE6_IF_NUM(prod, n) USB_DEVICE_INTERFACE_NUMBER(0x0e41, prod, n)
+
+/* table of devices that work with this driver */
+static const struct usb_device_id podhd_id_table[] = {
+ /* TODO: no need to alloc data interfaces when only audio is used */
+ { LINE6_DEVICE(0x5057), .driver_info = LINE6_PODHD300 },
+ { LINE6_DEVICE(0x5058), .driver_info = LINE6_PODHD400 },
+ { LINE6_IF_NUM(0x414D, 0), .driver_info = LINE6_PODHD500 },
+ { LINE6_IF_NUM(0x414A, 0), .driver_info = LINE6_PODX3 },
+ { LINE6_IF_NUM(0x414B, 0), .driver_info = LINE6_PODX3LIVE },
+ { LINE6_IF_NUM(0x4159, 0), .driver_info = LINE6_PODHD500X },
+ { LINE6_IF_NUM(0x4156, 0), .driver_info = LINE6_PODHDDESKTOP },
+ {}
+};
+
+MODULE_DEVICE_TABLE(usb, podhd_id_table);
+
+static const struct line6_properties podhd_properties_table[] = {
+ [LINE6_PODHD300] = {
+ .id = "PODHD300",
+ .name = "POD HD300",
+ .capabilities = LINE6_CAP_PCM
+ | LINE6_CAP_HWMON,
+ .altsetting = 5,
+ .ep_ctrl_r = 0x84,
+ .ep_ctrl_w = 0x03,
+ .ep_audio_r = 0x82,
+ .ep_audio_w = 0x01,
+ },
+ [LINE6_PODHD400] = {
+ .id = "PODHD400",
+ .name = "POD HD400",
+ .capabilities = LINE6_CAP_PCM
+ | LINE6_CAP_HWMON,
+ .altsetting = 5,
+ .ep_ctrl_r = 0x84,
+ .ep_ctrl_w = 0x03,
+ .ep_audio_r = 0x82,
+ .ep_audio_w = 0x01,
+ },
+ [LINE6_PODHD500] = {
+ .id = "PODHD500",
+ .name = "POD HD500",
+ .capabilities = LINE6_CAP_PCM | LINE6_CAP_CONTROL
+ | LINE6_CAP_HWMON | LINE6_CAP_HWMON_CTL,
+ .altsetting = 1,
+ .ctrl_if = 1,
+ .ep_ctrl_r = 0x81,
+ .ep_ctrl_w = 0x01,
+ .ep_audio_r = 0x86,
+ .ep_audio_w = 0x02,
+ },
+ [LINE6_PODX3] = {
+ .id = "PODX3",
+ .name = "POD X3",
+ .capabilities = LINE6_CAP_CONTROL | LINE6_CAP_CONTROL_INFO
+ | LINE6_CAP_PCM | LINE6_CAP_HWMON | LINE6_CAP_IN_NEEDS_OUT,
+ .altsetting = 1,
+ .ep_ctrl_r = 0x81,
+ .ep_ctrl_w = 0x01,
+ .ctrl_if = 1,
+ .ep_audio_r = 0x86,
+ .ep_audio_w = 0x02,
+ },
+ [LINE6_PODX3LIVE] = {
+ .id = "PODX3LIVE",
+ .name = "POD X3 LIVE",
+ .capabilities = LINE6_CAP_CONTROL | LINE6_CAP_CONTROL_INFO
+ | LINE6_CAP_PCM | LINE6_CAP_HWMON | LINE6_CAP_IN_NEEDS_OUT,
+ .altsetting = 1,
+ .ep_ctrl_r = 0x81,
+ .ep_ctrl_w = 0x01,
+ .ctrl_if = 1,
+ .ep_audio_r = 0x86,
+ .ep_audio_w = 0x02,
+ },
+ [LINE6_PODHD500X] = {
+ .id = "PODHD500X",
+ .name = "POD HD500X",
+ .capabilities = LINE6_CAP_CONTROL
+ | LINE6_CAP_PCM | LINE6_CAP_HWMON,
+ .altsetting = 1,
+ .ep_ctrl_r = 0x81,
+ .ep_ctrl_w = 0x01,
+ .ctrl_if = 1,
+ .ep_audio_r = 0x86,
+ .ep_audio_w = 0x02,
+ },
+ [LINE6_PODHDDESKTOP] = {
+ .id = "PODHDDESKTOP",
+ .name = "POD HDDESKTOP",
+ .capabilities = LINE6_CAP_CONTROL
+ | LINE6_CAP_PCM | LINE6_CAP_HWMON,
+ .altsetting = 1,
+ .ep_ctrl_r = 0x81,
+ .ep_ctrl_w = 0x01,
+ .ctrl_if = 1,
+ .ep_audio_r = 0x86,
+ .ep_audio_w = 0x02,
+ },
+};
+
+/*
+ Probe USB device.
+*/
+static int podhd_probe(struct usb_interface *interface,
+ const struct usb_device_id *id)
+{
+ return line6_probe(interface, id, "Line6-PODHD",
+ &podhd_properties_table[id->driver_info],
+ podhd_init, sizeof(struct usb_line6_podhd));
+}
+
+static struct usb_driver podhd_driver = {
+ .name = KBUILD_MODNAME,
+ .probe = podhd_probe,
+ .disconnect = line6_disconnect,
+#ifdef CONFIG_PM
+ .suspend = line6_suspend,
+ .resume = line6_resume,
+ .reset_resume = line6_resume,
+#endif
+ .id_table = podhd_id_table,
+};
+
+module_usb_driver(podhd_driver);
+
+MODULE_DESCRIPTION("Line 6 PODHD USB driver");
+MODULE_LICENSE("GPL");
diff --git a/sound/usb/line6/toneport.c b/sound/usb/line6/toneport.c
new file mode 100644
index 000000000..e33df5874
--- /dev/null
+++ b/sound/usb/line6/toneport.c
@@ -0,0 +1,582 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Line 6 Linux USB driver
+ *
+ * Copyright (C) 2004-2010 Markus Grabner (grabner@icg.tugraz.at)
+ * Emil Myhrman (emil.myhrman@gmail.com)
+ */
+
+#include <linux/wait.h>
+#include <linux/usb.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/leds.h>
+#include <sound/core.h>
+#include <sound/control.h>
+
+#include "capture.h"
+#include "driver.h"
+#include "playback.h"
+
+enum line6_device_type {
+ LINE6_GUITARPORT,
+ LINE6_PODSTUDIO_GX,
+ LINE6_PODSTUDIO_UX1,
+ LINE6_PODSTUDIO_UX2,
+ LINE6_TONEPORT_GX,
+ LINE6_TONEPORT_UX1,
+ LINE6_TONEPORT_UX2,
+};
+
+struct usb_line6_toneport;
+
+struct toneport_led {
+ struct led_classdev dev;
+ char name[64];
+ struct usb_line6_toneport *toneport;
+ bool registered;
+};
+
+struct usb_line6_toneport {
+ /* Generic Line 6 USB data */
+ struct usb_line6 line6;
+
+ /* Source selector */
+ int source;
+
+ /* Serial number of device */
+ u32 serial_number;
+
+ /* Firmware version (x 100) */
+ u8 firmware_version;
+
+ /* Device type */
+ enum line6_device_type type;
+
+ /* LED instances */
+ struct toneport_led leds[2];
+};
+
+#define line6_to_toneport(x) container_of(x, struct usb_line6_toneport, line6)
+
+static int toneport_send_cmd(struct usb_device *usbdev, int cmd1, int cmd2);
+
+#define TONEPORT_PCM_DELAY 1
+
+static const struct snd_ratden toneport_ratden = {
+ .num_min = 44100,
+ .num_max = 44100,
+ .num_step = 1,
+ .den = 1
+};
+
+static struct line6_pcm_properties toneport_pcm_properties = {
+ .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 = 64,
+ .period_bytes_max = 8192,
+ .periods_min = 1,
+ .periods_max = 1024},
+ .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 = 64,
+ .period_bytes_max = 8192,
+ .periods_min = 1,
+ .periods_max = 1024},
+ .rates = {
+ .nrats = 1,
+ .rats = &toneport_ratden},
+ .bytes_per_channel = 2
+};
+
+static const struct {
+ const char *name;
+ int code;
+} toneport_source_info[] = {
+ {"Microphone", 0x0a01},
+ {"Line", 0x0801},
+ {"Instrument", 0x0b01},
+ {"Inst & Mic", 0x0901}
+};
+
+static int toneport_send_cmd(struct usb_device *usbdev, int cmd1, int cmd2)
+{
+ int ret;
+
+ ret = usb_control_msg_send(usbdev, 0, 0x67,
+ USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_OUT,
+ cmd1, cmd2, NULL, 0, LINE6_TIMEOUT,
+ GFP_KERNEL);
+
+ if (ret) {
+ dev_err(&usbdev->dev, "send failed (error %d)\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+/* monitor info callback */
+static int snd_toneport_monitor_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = 1;
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = 256;
+ return 0;
+}
+
+/* monitor get callback */
+static int snd_toneport_monitor_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_line6_pcm *line6pcm = snd_kcontrol_chip(kcontrol);
+
+ ucontrol->value.integer.value[0] = line6pcm->volume_monitor;
+ return 0;
+}
+
+/* monitor put callback */
+static int snd_toneport_monitor_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_line6_pcm *line6pcm = snd_kcontrol_chip(kcontrol);
+ int err;
+
+ if (ucontrol->value.integer.value[0] == line6pcm->volume_monitor)
+ return 0;
+
+ line6pcm->volume_monitor = ucontrol->value.integer.value[0];
+
+ if (line6pcm->volume_monitor > 0) {
+ err = line6_pcm_acquire(line6pcm, LINE6_STREAM_MONITOR, true);
+ if (err < 0) {
+ line6pcm->volume_monitor = 0;
+ line6_pcm_release(line6pcm, LINE6_STREAM_MONITOR);
+ return err;
+ }
+ } else {
+ line6_pcm_release(line6pcm, LINE6_STREAM_MONITOR);
+ }
+
+ return 1;
+}
+
+/* source info callback */
+static int snd_toneport_source_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ const int size = ARRAY_SIZE(toneport_source_info);
+
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+ uinfo->count = 1;
+ uinfo->value.enumerated.items = size;
+
+ if (uinfo->value.enumerated.item >= size)
+ uinfo->value.enumerated.item = size - 1;
+
+ strcpy(uinfo->value.enumerated.name,
+ toneport_source_info[uinfo->value.enumerated.item].name);
+
+ return 0;
+}
+
+/* source get callback */
+static int snd_toneport_source_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_line6_pcm *line6pcm = snd_kcontrol_chip(kcontrol);
+ struct usb_line6_toneport *toneport = line6_to_toneport(line6pcm->line6);
+
+ ucontrol->value.enumerated.item[0] = toneport->source;
+ return 0;
+}
+
+/* source put callback */
+static int snd_toneport_source_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_line6_pcm *line6pcm = snd_kcontrol_chip(kcontrol);
+ struct usb_line6_toneport *toneport = line6_to_toneport(line6pcm->line6);
+ unsigned int source;
+
+ source = ucontrol->value.enumerated.item[0];
+ if (source >= ARRAY_SIZE(toneport_source_info))
+ return -EINVAL;
+ if (source == toneport->source)
+ return 0;
+
+ toneport->source = source;
+ toneport_send_cmd(toneport->line6.usbdev,
+ toneport_source_info[source].code, 0x0000);
+ return 1;
+}
+
+static void toneport_startup(struct usb_line6 *line6)
+{
+ line6_pcm_acquire(line6->line6pcm, LINE6_STREAM_MONITOR, true);
+}
+
+/* control definition */
+static const struct snd_kcontrol_new toneport_control_monitor = {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Monitor Playback Volume",
+ .index = 0,
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+ .info = snd_toneport_monitor_info,
+ .get = snd_toneport_monitor_get,
+ .put = snd_toneport_monitor_put
+};
+
+/* source selector definition */
+static const struct snd_kcontrol_new toneport_control_source = {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "PCM Capture Source",
+ .index = 0,
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+ .info = snd_toneport_source_info,
+ .get = snd_toneport_source_get,
+ .put = snd_toneport_source_put
+};
+
+/*
+ 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 bool toneport_has_led(struct usb_line6_toneport *toneport)
+{
+ switch (toneport->type) {
+ case LINE6_GUITARPORT:
+ case LINE6_TONEPORT_GX:
+ /* add your device here if you are missing support for the LEDs */
+ return true;
+
+ default:
+ return false;
+ }
+}
+
+static const char * const toneport_led_colors[2] = { "red", "green" };
+static const int toneport_led_init_vals[2] = { 0x00, 0x26 };
+
+static void toneport_update_led(struct usb_line6_toneport *toneport)
+{
+ toneport_send_cmd(toneport->line6.usbdev,
+ (toneport->leds[0].dev.brightness << 8) | 0x0002,
+ toneport->leds[1].dev.brightness);
+}
+
+static void toneport_led_brightness_set(struct led_classdev *led_cdev,
+ enum led_brightness brightness)
+{
+ struct toneport_led *leds =
+ container_of(led_cdev, struct toneport_led, dev);
+ toneport_update_led(leds->toneport);
+}
+
+static int toneport_init_leds(struct usb_line6_toneport *toneport)
+{
+ struct device *dev = &toneport->line6.usbdev->dev;
+ int i, err;
+
+ for (i = 0; i < 2; i++) {
+ struct toneport_led *led = &toneport->leds[i];
+ struct led_classdev *leddev = &led->dev;
+
+ led->toneport = toneport;
+ snprintf(led->name, sizeof(led->name), "%s::%s",
+ dev_name(dev), toneport_led_colors[i]);
+ leddev->name = led->name;
+ leddev->brightness = toneport_led_init_vals[i];
+ leddev->max_brightness = 0x26;
+ leddev->brightness_set = toneport_led_brightness_set;
+ err = led_classdev_register(dev, leddev);
+ if (err)
+ return err;
+ led->registered = true;
+ }
+
+ return 0;
+}
+
+static void toneport_remove_leds(struct usb_line6_toneport *toneport)
+{
+ struct toneport_led *led;
+ int i;
+
+ for (i = 0; i < 2; i++) {
+ led = &toneport->leds[i];
+ if (!led->registered)
+ break;
+ led_classdev_unregister(&led->dev);
+ led->registered = false;
+ }
+}
+
+static bool toneport_has_source_select(struct usb_line6_toneport *toneport)
+{
+ switch (toneport->type) {
+ case LINE6_TONEPORT_UX1:
+ case LINE6_TONEPORT_UX2:
+ case LINE6_PODSTUDIO_UX1:
+ case LINE6_PODSTUDIO_UX2:
+ return true;
+
+ default:
+ return false;
+ }
+}
+
+/*
+ Setup Toneport device.
+*/
+static int toneport_setup(struct usb_line6_toneport *toneport)
+{
+ u32 *ticks;
+ struct usb_line6 *line6 = &toneport->line6;
+ struct usb_device *usbdev = line6->usbdev;
+
+ ticks = kmalloc(sizeof(*ticks), GFP_KERNEL);
+ if (!ticks)
+ return -ENOMEM;
+
+ /* sync time on device with host: */
+ /* note: 32-bit timestamps overflow in year 2106 */
+ *ticks = (u32)ktime_get_real_seconds();
+ line6_write_data(line6, 0x80c6, ticks, 4);
+ kfree(ticks);
+
+ /* enable device: */
+ toneport_send_cmd(usbdev, 0x0301, 0x0000);
+
+ /* initialize source select: */
+ if (toneport_has_source_select(toneport))
+ toneport_send_cmd(usbdev,
+ toneport_source_info[toneport->source].code,
+ 0x0000);
+
+ if (toneport_has_led(toneport))
+ toneport_update_led(toneport);
+
+ schedule_delayed_work(&toneport->line6.startup_work,
+ msecs_to_jiffies(TONEPORT_PCM_DELAY * 1000));
+ return 0;
+}
+
+/*
+ Toneport device disconnected.
+*/
+static void line6_toneport_disconnect(struct usb_line6 *line6)
+{
+ struct usb_line6_toneport *toneport = line6_to_toneport(line6);
+
+ if (toneport_has_led(toneport))
+ toneport_remove_leds(toneport);
+}
+
+
+/*
+ Try to init Toneport device.
+*/
+static int toneport_init(struct usb_line6 *line6,
+ const struct usb_device_id *id)
+{
+ int err;
+ struct usb_line6_toneport *toneport = line6_to_toneport(line6);
+
+ toneport->type = id->driver_info;
+
+ line6->disconnect = line6_toneport_disconnect;
+ line6->startup = toneport_startup;
+
+ /* initialize PCM subsystem: */
+ err = line6_init_pcm(line6, &toneport_pcm_properties);
+ if (err < 0)
+ return err;
+
+ /* register monitor control: */
+ err = snd_ctl_add(line6->card,
+ snd_ctl_new1(&toneport_control_monitor,
+ line6->line6pcm));
+ if (err < 0)
+ return err;
+
+ /* register source select control: */
+ if (toneport_has_source_select(toneport)) {
+ err =
+ snd_ctl_add(line6->card,
+ snd_ctl_new1(&toneport_control_source,
+ line6->line6pcm));
+ if (err < 0)
+ return err;
+ }
+
+ line6_read_serial_number(line6, &toneport->serial_number);
+ line6_read_data(line6, 0x80c2, &toneport->firmware_version, 1);
+
+ if (toneport_has_led(toneport)) {
+ err = toneport_init_leds(toneport);
+ if (err < 0)
+ return err;
+ }
+
+ err = toneport_setup(toneport);
+ if (err)
+ return err;
+
+ /* register audio system: */
+ return snd_card_register(line6->card);
+}
+
+#ifdef CONFIG_PM
+/*
+ Resume Toneport device after reset.
+*/
+static int toneport_reset_resume(struct usb_interface *interface)
+{
+ int err;
+
+ err = toneport_setup(usb_get_intfdata(interface));
+ if (err)
+ return err;
+ return line6_resume(interface);
+}
+#endif
+
+#define LINE6_DEVICE(prod) USB_DEVICE(0x0e41, prod)
+#define LINE6_IF_NUM(prod, n) USB_DEVICE_INTERFACE_NUMBER(0x0e41, prod, n)
+
+/* table of devices that work with this driver */
+static const struct usb_device_id toneport_id_table[] = {
+ { LINE6_DEVICE(0x4750), .driver_info = LINE6_GUITARPORT },
+ { LINE6_DEVICE(0x4153), .driver_info = LINE6_PODSTUDIO_GX },
+ { LINE6_DEVICE(0x4150), .driver_info = LINE6_PODSTUDIO_UX1 },
+ { LINE6_IF_NUM(0x4151, 0), .driver_info = LINE6_PODSTUDIO_UX2 },
+ { LINE6_DEVICE(0x4147), .driver_info = LINE6_TONEPORT_GX },
+ { LINE6_DEVICE(0x4141), .driver_info = LINE6_TONEPORT_UX1 },
+ { LINE6_IF_NUM(0x4142, 0), .driver_info = LINE6_TONEPORT_UX2 },
+ {}
+};
+
+MODULE_DEVICE_TABLE(usb, toneport_id_table);
+
+static const struct line6_properties toneport_properties_table[] = {
+ [LINE6_GUITARPORT] = {
+ .id = "GuitarPort",
+ .name = "GuitarPort",
+ .capabilities = LINE6_CAP_PCM,
+ .altsetting = 2, /* 1..4 seem to be ok */
+ /* no control channel */
+ .ep_audio_r = 0x82,
+ .ep_audio_w = 0x01,
+ },
+ [LINE6_PODSTUDIO_GX] = {
+ .id = "PODStudioGX",
+ .name = "POD Studio GX",
+ .capabilities = LINE6_CAP_PCM,
+ .altsetting = 2, /* 1..4 seem to be ok */
+ /* no control channel */
+ .ep_audio_r = 0x82,
+ .ep_audio_w = 0x01,
+ },
+ [LINE6_PODSTUDIO_UX1] = {
+ .id = "PODStudioUX1",
+ .name = "POD Studio UX1",
+ .capabilities = LINE6_CAP_PCM,
+ .altsetting = 2, /* 1..4 seem to be ok */
+ /* no control channel */
+ .ep_audio_r = 0x82,
+ .ep_audio_w = 0x01,
+ },
+ [LINE6_PODSTUDIO_UX2] = {
+ .id = "PODStudioUX2",
+ .name = "POD Studio UX2",
+ .capabilities = LINE6_CAP_PCM,
+ .altsetting = 2, /* defaults to 44.1kHz, 16-bit */
+ /* no control channel */
+ .ep_audio_r = 0x82,
+ .ep_audio_w = 0x01,
+ },
+ [LINE6_TONEPORT_GX] = {
+ .id = "TonePortGX",
+ .name = "TonePort GX",
+ .capabilities = LINE6_CAP_PCM,
+ .altsetting = 2, /* 1..4 seem to be ok */
+ /* no control channel */
+ .ep_audio_r = 0x82,
+ .ep_audio_w = 0x01,
+ },
+ [LINE6_TONEPORT_UX1] = {
+ .id = "TonePortUX1",
+ .name = "TonePort UX1",
+ .capabilities = LINE6_CAP_PCM,
+ .altsetting = 2, /* 1..4 seem to be ok */
+ /* no control channel */
+ .ep_audio_r = 0x82,
+ .ep_audio_w = 0x01,
+ },
+ [LINE6_TONEPORT_UX2] = {
+ .id = "TonePortUX2",
+ .name = "TonePort UX2",
+ .capabilities = LINE6_CAP_PCM,
+ .altsetting = 2, /* defaults to 44.1kHz, 16-bit */
+ /* no control channel */
+ .ep_audio_r = 0x82,
+ .ep_audio_w = 0x01,
+ },
+};
+
+/*
+ Probe USB device.
+*/
+static int toneport_probe(struct usb_interface *interface,
+ const struct usb_device_id *id)
+{
+ return line6_probe(interface, id, "Line6-TonePort",
+ &toneport_properties_table[id->driver_info],
+ toneport_init, sizeof(struct usb_line6_toneport));
+}
+
+static struct usb_driver toneport_driver = {
+ .name = KBUILD_MODNAME,
+ .probe = toneport_probe,
+ .disconnect = line6_disconnect,
+#ifdef CONFIG_PM
+ .suspend = line6_suspend,
+ .resume = line6_resume,
+ .reset_resume = toneport_reset_resume,
+#endif
+ .id_table = toneport_id_table,
+};
+
+module_usb_driver(toneport_driver);
+
+MODULE_DESCRIPTION("TonePort USB driver");
+MODULE_LICENSE("GPL");
diff --git a/sound/usb/line6/variax.c b/sound/usb/line6/variax.c
new file mode 100644
index 000000000..c2245aa93
--- /dev/null
+++ b/sound/usb/line6/variax.c
@@ -0,0 +1,242 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Line 6 Linux USB driver
+ *
+ * Copyright (C) 2004-2010 Markus Grabner (grabner@icg.tugraz.at)
+ */
+
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <linux/usb.h>
+#include <linux/wait.h>
+#include <linux/module.h>
+#include <sound/core.h>
+
+#include "driver.h"
+
+#define VARIAX_STARTUP_DELAY1 1000
+#define VARIAX_STARTUP_DELAY3 100
+#define VARIAX_STARTUP_DELAY4 100
+
+/*
+ Stages of Variax startup procedure
+*/
+enum {
+ VARIAX_STARTUP_VERSIONREQ,
+ VARIAX_STARTUP_ACTIVATE,
+ VARIAX_STARTUP_SETUP,
+};
+
+enum {
+ LINE6_PODXTLIVE_VARIAX,
+ LINE6_VARIAX
+};
+
+struct usb_line6_variax {
+ /* Generic Line 6 USB data */
+ struct usb_line6 line6;
+
+ /* Buffer for activation code */
+ unsigned char *buffer_activate;
+
+ /* Current progress in startup procedure */
+ int startup_progress;
+};
+
+#define line6_to_variax(x) container_of(x, struct usb_line6_variax, line6)
+
+#define VARIAX_OFFSET_ACTIVATE 7
+
+/*
+ This message is sent by the device during initialization and identifies
+ the connected guitar version.
+*/
+static const char variax_init_version[] = {
+ 0xf0, 0x7e, 0x7f, 0x06, 0x02, 0x00, 0x01, 0x0c,
+ 0x07, 0x00, 0x00, 0x00
+};
+
+/*
+ This message is the last one sent by the device during initialization.
+*/
+static const char variax_init_done[] = {
+ 0xf0, 0x00, 0x01, 0x0c, 0x07, 0x00, 0x6b
+};
+
+static const char variax_activate[] = {
+ 0xf0, 0x00, 0x01, 0x0c, 0x07, 0x00, 0x2a, 0x01,
+ 0xf7
+};
+
+static void variax_activate_async(struct usb_line6_variax *variax, int a)
+{
+ variax->buffer_activate[VARIAX_OFFSET_ACTIVATE] = a;
+ line6_send_raw_message_async(&variax->line6, variax->buffer_activate,
+ sizeof(variax_activate));
+}
+
+/*
+ Variax startup procedure.
+ This is a sequence of functions with special requirements (e.g., must
+ not run immediately after initialization, must not run in interrupt
+ context). After the last one has finished, the device is ready to use.
+*/
+
+static void variax_startup(struct usb_line6 *line6)
+{
+ struct usb_line6_variax *variax = line6_to_variax(line6);
+
+ switch (variax->startup_progress) {
+ case VARIAX_STARTUP_VERSIONREQ:
+ /* repeat request until getting the response */
+ schedule_delayed_work(&line6->startup_work,
+ msecs_to_jiffies(VARIAX_STARTUP_DELAY1));
+ /* request firmware version: */
+ line6_version_request_async(line6);
+ break;
+ case VARIAX_STARTUP_ACTIVATE:
+ /* activate device: */
+ variax_activate_async(variax, 1);
+ variax->startup_progress = VARIAX_STARTUP_SETUP;
+ schedule_delayed_work(&line6->startup_work,
+ msecs_to_jiffies(VARIAX_STARTUP_DELAY4));
+ break;
+ case VARIAX_STARTUP_SETUP:
+ /* ALSA audio interface: */
+ snd_card_register(variax->line6.card);
+ break;
+ }
+}
+
+/*
+ Process a completely received message.
+*/
+static void line6_variax_process_message(struct usb_line6 *line6)
+{
+ struct usb_line6_variax *variax = line6_to_variax(line6);
+ const unsigned char *buf = variax->line6.buffer_message;
+
+ switch (buf[0]) {
+ case LINE6_RESET:
+ dev_info(variax->line6.ifcdev, "VARIAX reset\n");
+ break;
+
+ case LINE6_SYSEX_BEGIN:
+ if (memcmp(buf + 1, variax_init_version + 1,
+ sizeof(variax_init_version) - 1) == 0) {
+ if (variax->startup_progress >= VARIAX_STARTUP_ACTIVATE)
+ break;
+ variax->startup_progress = VARIAX_STARTUP_ACTIVATE;
+ cancel_delayed_work(&line6->startup_work);
+ schedule_delayed_work(&line6->startup_work,
+ msecs_to_jiffies(VARIAX_STARTUP_DELAY3));
+ } else if (memcmp(buf + 1, variax_init_done + 1,
+ sizeof(variax_init_done) - 1) == 0) {
+ /* notify of complete initialization: */
+ if (variax->startup_progress >= VARIAX_STARTUP_SETUP)
+ break;
+ cancel_delayed_work(&line6->startup_work);
+ schedule_delayed_work(&line6->startup_work, 0);
+ }
+ break;
+ }
+}
+
+/*
+ Variax destructor.
+*/
+static void line6_variax_disconnect(struct usb_line6 *line6)
+{
+ struct usb_line6_variax *variax = line6_to_variax(line6);
+
+ kfree(variax->buffer_activate);
+}
+
+/*
+ Try to init workbench device.
+*/
+static int variax_init(struct usb_line6 *line6,
+ const struct usb_device_id *id)
+{
+ struct usb_line6_variax *variax = line6_to_variax(line6);
+
+ line6->process_message = line6_variax_process_message;
+ line6->disconnect = line6_variax_disconnect;
+ line6->startup = variax_startup;
+
+ /* initialize USB buffers: */
+ variax->buffer_activate = kmemdup(variax_activate,
+ sizeof(variax_activate), GFP_KERNEL);
+
+ if (variax->buffer_activate == NULL)
+ return -ENOMEM;
+
+ /* initiate startup procedure: */
+ schedule_delayed_work(&line6->startup_work,
+ msecs_to_jiffies(VARIAX_STARTUP_DELAY1));
+ return 0;
+}
+
+#define LINE6_DEVICE(prod) USB_DEVICE(0x0e41, prod)
+#define LINE6_IF_NUM(prod, n) USB_DEVICE_INTERFACE_NUMBER(0x0e41, prod, n)
+
+/* table of devices that work with this driver */
+static const struct usb_device_id variax_id_table[] = {
+ { LINE6_IF_NUM(0x4650, 1), .driver_info = LINE6_PODXTLIVE_VARIAX },
+ { LINE6_DEVICE(0x534d), .driver_info = LINE6_VARIAX },
+ {}
+};
+
+MODULE_DEVICE_TABLE(usb, variax_id_table);
+
+static const struct line6_properties variax_properties_table[] = {
+ [LINE6_PODXTLIVE_VARIAX] = {
+ .id = "PODxtLive",
+ .name = "PODxt Live",
+ .capabilities = LINE6_CAP_CONTROL
+ | LINE6_CAP_CONTROL_MIDI,
+ .altsetting = 1,
+ .ep_ctrl_r = 0x86,
+ .ep_ctrl_w = 0x05,
+ .ep_audio_r = 0x82,
+ .ep_audio_w = 0x01,
+ },
+ [LINE6_VARIAX] = {
+ .id = "Variax",
+ .name = "Variax Workbench",
+ .capabilities = LINE6_CAP_CONTROL
+ | LINE6_CAP_CONTROL_MIDI,
+ .altsetting = 1,
+ .ep_ctrl_r = 0x82,
+ .ep_ctrl_w = 0x01,
+ /* no audio channel */
+ }
+};
+
+/*
+ Probe USB device.
+*/
+static int variax_probe(struct usb_interface *interface,
+ const struct usb_device_id *id)
+{
+ return line6_probe(interface, id, "Line6-Variax",
+ &variax_properties_table[id->driver_info],
+ variax_init, sizeof(struct usb_line6_variax));
+}
+
+static struct usb_driver variax_driver = {
+ .name = KBUILD_MODNAME,
+ .probe = variax_probe,
+ .disconnect = line6_disconnect,
+#ifdef CONFIG_PM
+ .suspend = line6_suspend,
+ .resume = line6_resume,
+ .reset_resume = line6_resume,
+#endif
+ .id_table = variax_id_table,
+};
+
+module_usb_driver(variax_driver);
+
+MODULE_DESCRIPTION("Variax Workbench USB driver");
+MODULE_LICENSE("GPL");