Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
+44
View File
@@ -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.
+19
View File
@@ -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
+289
View File
@@ -0,0 +1,289 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Line 6 Linux USB driver
*
* Copyright (C) 2004-2010 Markus Grabner (line6@grabner-graz.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;
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;
guard(spinlock_irqsave)(&line6pcm->in.lock);
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);
}
}
/* 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 = kzalloc_objs(struct urb *, line6->iso_buffers);
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;
}
+25
View File
@@ -0,0 +1,25 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Line 6 Linux USB driver
*
* Copyright (C) 2004-2010 Markus Grabner (line6@grabner-graz.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
+900
View File
@@ -0,0 +1,900 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Line 6 Linux USB driver
*
* Copyright (C) 2004-2010 Markus Grabner (line6@grabner-graz.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 <line6@grabner-graz.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 = kzalloc_obj(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) {
scoped_guard(spinlock_irqsave, &line6->line6midi->lock) {
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 (;;) {
scoped_guard(spinlock_irqsave, &line6->line6midi->lock) {
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)
{
struct usb_line6 *line6 = hwdep->private_data;
poll_wait(file, &line6->messages.wait_queue, wait);
guard(mutex)(&line6->messages.read_lock);
return kfifo_len(&line6->messages.fifo) == 0 ? 0 : EPOLLIN | EPOLLRDNORM;
}
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;
strscpy(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 = kzalloc(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 = kzalloc(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);
strscpy(card->id, properties->id);
strscpy(card->driver, driver_name);
strscpy(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");
+214
View File
@@ -0,0 +1,214 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Line 6 Linux USB driver
*
* Copyright (C) 2004-2010 Markus Grabner (line6@grabner-graz.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 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
+289
View File
@@ -0,0 +1,289 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Line 6 Linux USB driver
*
* Copyright (C) 2004-2010 Markus Grabner (line6@grabner-graz.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)
{
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;
guard(spinlock_irqsave)(&line6->line6midi->lock);
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);
}
/*
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)
{
struct usb_line6 *line6 =
line6_rawmidi_substream_midi(substream)->line6;
line6->line6midi->substream_transmit = substream;
guard(spinlock_irqsave)(&line6->line6midi->lock);
if (line6->line6midi->num_active_send_urbs == 0)
line6_midi_transmit(substream);
}
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;
strscpy(rmidi->id, line6->properties->id);
strscpy(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_obj(struct snd_line6_midi);
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);
+47
View File
@@ -0,0 +1,47 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Line 6 Linux USB driver
*
* Copyright (C) 2004-2010 Markus Grabner (line6@grabner-graz.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
+257
View File
@@ -0,0 +1,257 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Line 6 Linux USB driver
*
* Copyright (C) 2004-2010 Markus Grabner (line6@grabner-graz.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;
}
+34
View File
@@ -0,0 +1,34 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Line 6 Linux USB driver
*
* Copyright (C) 2004-2010 Markus Grabner (line6@grabner-graz.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
+604
View File
@@ -0,0 +1,604 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Line 6 Linux USB driver
*
* Copyright (C) 2004-2010 Markus Grabner (line6@grabner-graz.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)
{
struct line6_pcm_stream *pstr = get_stream(line6pcm, direction);
int ret = 0;
guard(spinlock_irqsave)(&pstr->lock);
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);
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)
{
struct line6_pcm_stream *pstr = get_stream(line6pcm, direction);
scoped_guard(spinlock_irqsave, &pstr->lock) {
clear_bit(type, &pstr->running);
if (pstr->running)
return;
}
line6_unlink_audio_urbs(line6pcm, pstr);
if (direction == SNDRV_PCM_STREAM_CAPTURE) {
guard(spinlock_irqsave)(&pstr->lock);
line6pcm->prev_fbuf = NULL;
line6pcm->prev_fsize = 0;
}
}
/* 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;
}
/* Stop and release duplex streams */
static void __line6_pcm_release(struct snd_line6_pcm *line6pcm, int type)
{
struct line6_pcm_stream *pstr;
int dir;
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);
}
}
/* Stop and release duplex streams */
void line6_pcm_release(struct snd_line6_pcm *line6pcm, int type)
{
guard(mutex)(&line6pcm->state_mutex);
__line6_pcm_release(line6pcm, type);
}
EXPORT_SYMBOL_GPL(line6_pcm_release);
/* 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 */
guard(mutex)(&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:
if (ret < 0)
__line6_pcm_release(line6pcm, type);
return ret;
}
EXPORT_SYMBOL_GPL(line6_pcm_acquire);
/* 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);
guard(mutex)(&line6pcm->state_mutex);
ret = line6_buffer_acquire(line6pcm, pstr, substream->stream,
LINE6_STREAM_PCM);
if (ret < 0)
return ret;
pstr->period = params_period_bytes(hw_params);
return 0;
}
/* 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);
guard(mutex)(&line6pcm->state_mutex);
line6_buffer_release(line6pcm, pstr, LINE6_STREAM_PCM);
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;
strscpy(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_obj(*line6pcm);
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));
line6pcm->max_packet_size_out =
usb_maxpacket(line6->usbdev,
usb_sndisocpipe(line6->usbdev, ep_write));
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);
guard(mutex)(&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;
}
return 0;
}
+195
View File
@@ -0,0 +1,195 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Line 6 Linux USB driver
*
* Copyright (C) 2004-2010 Markus Grabner (line6@grabner-graz.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
+438
View File
@@ -0,0 +1,438 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Line 6 Linux USB driver
*
* Copyright (C) 2004-2010 Markus Grabner (line6@grabner-graz.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 = kzalloc_objs(struct urb *, line6->iso_buffers);
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;
}
+31
View File
@@ -0,0 +1,31 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Line 6 Linux USB driver
*
* Copyright (C) 2004-2010 Markus Grabner (line6@grabner-graz.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
+542
View File
@@ -0,0 +1,542 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Line 6 Linux USB driver
*
* Copyright (C) 2004-2010 Markus Grabner (line6@grabner-graz.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 sysfs_emit(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 sysfs_emit(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 sysfs_emit(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");
+587
View File
@@ -0,0 +1,587 @@
// 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,
LINE6_PODHDPROX,
LINE6_PODHDPRO,
};
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 sysfs_emit(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 sysfs_emit(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 },
{ LINE6_IF_NUM(0x415A, 0), .driver_info = LINE6_PODHDPROX },
{ LINE6_IF_NUM(0x4157, 0), .driver_info = LINE6_PODHDPRO },
{}
};
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_HWMON_CTL
| 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,
},
[LINE6_PODHDPROX] = {
.id = "PODHDPROX",
.name = "POD HD Pro X",
.capabilities = LINE6_CAP_CONTROL | LINE6_CAP_CONTROL_INFO
| LINE6_CAP_PCM | LINE6_CAP_HWMON | LINE6_CAP_IN_NEEDS_OUT,
.altsetting = 1,
.ctrl_if = 1,
.ep_ctrl_r = 0x81,
.ep_ctrl_w = 0x01,
.ep_audio_r = 0x86,
.ep_audio_w = 0x02,
},
[LINE6_PODHDPRO] = {
.id = "PODHDPRO",
.name = "POD HD PRO",
.capabilities = LINE6_CAP_PCM | LINE6_CAP_CONTROL
| LINE6_CAP_HWMON | LINE6_CAP_HWMON_CTL | LINE6_CAP_IN_NEEDS_OUT,
.altsetting = 1,
.ctrl_if = 1,
.ep_ctrl_r = 0x81,
.ep_ctrl_w = 0x01,
.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");
+582
View File
@@ -0,0 +1,582 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Line 6 Linux USB driver
*
* Copyright (C) 2004-2010 Markus Grabner (line6@grabner-graz.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;
strscpy(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_obj(*ticks);
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,
secs_to_jiffies(TONEPORT_PCM_DELAY));
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");
+242
View File
@@ -0,0 +1,242 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Line 6 Linux USB driver
*
* Copyright (C) 2004-2010 Markus Grabner (line6@grabner-graz.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");