2009-02-28 03:43:04 +00:00
|
|
|
/*
|
|
|
|
* Line6 Linux USB driver - 0.8.0
|
|
|
|
*
|
|
|
|
* Copyright (C) 2004-2009 Markus Grabner (grabner@icg.tugraz.at)
|
|
|
|
*
|
|
|
|
* This program is free software; you can redistribute it and/or
|
|
|
|
* modify it under the terms of the GNU General Public License as
|
|
|
|
* published by the Free Software Foundation, version 2.
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include "driver.h"
|
|
|
|
|
|
|
|
#include "audio.h"
|
|
|
|
#include "capture.h"
|
|
|
|
#include "control.h"
|
|
|
|
#include "playback.h"
|
|
|
|
#include "pod.h"
|
|
|
|
|
|
|
|
|
|
|
|
#define POD_SYSEX_CODE 3
|
|
|
|
#define POD_BYTES_PER_FRAME 6 /* 24bit audio (stereo) */
|
|
|
|
|
|
|
|
|
|
|
|
enum {
|
|
|
|
POD_SYSEX_CLIP = 0x0f,
|
|
|
|
POD_SYSEX_SAVE = 0x24,
|
|
|
|
POD_SYSEX_SYSTEM = 0x56,
|
|
|
|
POD_SYSEX_SYSTEMREQ = 0x57,
|
|
|
|
/* POD_SYSEX_UPDATE = 0x6c, */ /* software update! */
|
|
|
|
POD_SYSEX_STORE = 0x71,
|
|
|
|
POD_SYSEX_FINISH = 0x72,
|
|
|
|
POD_SYSEX_DUMPMEM = 0x73,
|
|
|
|
POD_SYSEX_DUMP = 0x74,
|
|
|
|
POD_SYSEX_DUMPREQ = 0x75
|
|
|
|
/* POD_SYSEX_DUMPMEM2 = 0x76 */ /* dumps entire internal memory of PODxt Pro */
|
|
|
|
};
|
|
|
|
|
|
|
|
enum {
|
|
|
|
POD_monitor_level = 0x04,
|
|
|
|
POD_routing = 0x05,
|
|
|
|
POD_tuner_mute = 0x13,
|
|
|
|
POD_tuner_freq = 0x15,
|
|
|
|
POD_tuner_note = 0x16,
|
|
|
|
POD_tuner_pitch = 0x17,
|
|
|
|
POD_system_invalid = 0x7fff
|
|
|
|
};
|
|
|
|
|
|
|
|
enum {
|
|
|
|
POD_DUMP_MEMORY = 2
|
|
|
|
};
|
|
|
|
|
|
|
|
enum {
|
|
|
|
POD_BUSY_READ,
|
|
|
|
POD_BUSY_WRITE,
|
|
|
|
POD_CHANNEL_DIRTY,
|
|
|
|
POD_SAVE_PRESSED,
|
|
|
|
POD_BUSY_MIDISEND
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
static struct snd_ratden pod_ratden = {
|
|
|
|
.num_min = 78125,
|
|
|
|
.num_max = 78125,
|
|
|
|
.num_step = 1,
|
|
|
|
.den = 2
|
|
|
|
};
|
|
|
|
|
|
|
|
static struct line6_pcm_properties pod_pcm_properties = {
|
|
|
|
.snd_line6_playback_hw = {
|
|
|
|
.info = (SNDRV_PCM_INFO_MMAP |
|
|
|
|
SNDRV_PCM_INFO_INTERLEAVED |
|
|
|
|
SNDRV_PCM_INFO_BLOCK_TRANSFER |
|
|
|
|
SNDRV_PCM_INFO_MMAP_VALID |
|
|
|
|
SNDRV_PCM_INFO_PAUSE |
|
|
|
|
SNDRV_PCM_INFO_SYNC_START),
|
|
|
|
.formats = SNDRV_PCM_FMTBIT_S24_3LE,
|
|
|
|
.rates = SNDRV_PCM_RATE_KNOT,
|
|
|
|
.rate_min = 39062,
|
|
|
|
.rate_max = 39063,
|
|
|
|
.channels_min = 2,
|
|
|
|
.channels_max = 2,
|
|
|
|
.buffer_bytes_max = 60000,
|
|
|
|
.period_bytes_min = LINE6_ISO_PACKET_SIZE_MAX * POD_BYTES_PER_FRAME, /* at least one URB must fit into one period */
|
|
|
|
.period_bytes_max = 8192,
|
|
|
|
.periods_min = 1,
|
|
|
|
.periods_max = 1024
|
|
|
|
},
|
|
|
|
.snd_line6_capture_hw = {
|
|
|
|
.info = (SNDRV_PCM_INFO_MMAP |
|
|
|
|
SNDRV_PCM_INFO_INTERLEAVED |
|
|
|
|
SNDRV_PCM_INFO_BLOCK_TRANSFER |
|
|
|
|
SNDRV_PCM_INFO_MMAP_VALID |
|
|
|
|
SNDRV_PCM_INFO_SYNC_START),
|
|
|
|
.formats = SNDRV_PCM_FMTBIT_S24_3LE,
|
|
|
|
.rates = SNDRV_PCM_RATE_KNOT,
|
|
|
|
.rate_min = 39062,
|
|
|
|
.rate_max = 39063,
|
|
|
|
.channels_min = 2,
|
|
|
|
.channels_max = 2,
|
|
|
|
.buffer_bytes_max = 60000,
|
|
|
|
.period_bytes_min = LINE6_ISO_PACKET_SIZE_MAX * POD_BYTES_PER_FRAME, /* at least one URB must fit into one period */
|
|
|
|
.period_bytes_max = 8192,
|
|
|
|
.periods_min = 1,
|
|
|
|
.periods_max = 1024
|
|
|
|
},
|
|
|
|
.snd_line6_rates = {
|
|
|
|
.nrats = 1,
|
|
|
|
.rats = &pod_ratden
|
|
|
|
},
|
|
|
|
.bytes_per_frame = POD_BYTES_PER_FRAME
|
|
|
|
};
|
|
|
|
|
|
|
|
static const char pod_request_version[] = { 0xf0, 0x7e, 0x7f, 0x06, 0x01, 0xf7 };
|
|
|
|
static const char pod_request_channel[] = { 0xf0, 0x00, 0x01, 0x0c, 0x03, 0x75, 0xf7 };
|
2009-02-28 06:41:29 +00:00
|
|
|
static const char pod_version_header[] = { 0xf2, 0x7e, 0x7f, 0x06, 0x02 };
|
2009-02-28 03:43:04 +00:00
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
Mark all parameters as dirty and notify waiting processes.
|
|
|
|
*/
|
|
|
|
static void pod_mark_batch_all_dirty(struct usb_line6_pod *pod)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
|
2009-02-28 06:41:29 +00:00
|
|
|
for (i = POD_CONTROL_SIZE; i--;)
|
2009-02-28 03:43:04 +00:00
|
|
|
set_bit(i, pod->param_dirty);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
Send an asynchronous request for the POD firmware version and device ID.
|
|
|
|
*/
|
|
|
|
static int pod_version_request_async(struct usb_line6_pod *pod)
|
|
|
|
{
|
|
|
|
return line6_send_raw_message_async(&pod->line6, pod->buffer_versionreq, sizeof(pod_request_version));
|
|
|
|
}
|
|
|
|
|
|
|
|
static void pod_create_files_work(struct work_struct *work)
|
|
|
|
{
|
|
|
|
struct usb_line6_pod *pod = container_of(work, struct usb_line6_pod, create_files_work);
|
|
|
|
|
|
|
|
pod_create_files(pod->firmware_version, pod->line6.properties->device_bit, pod->line6.ifcdev);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void pod_startup_timeout(unsigned long arg)
|
|
|
|
{
|
|
|
|
enum {
|
|
|
|
REQUEST_NONE,
|
|
|
|
REQUEST_DUMP,
|
|
|
|
REQUEST_VERSION
|
|
|
|
};
|
|
|
|
|
|
|
|
int request = REQUEST_NONE;
|
|
|
|
struct usb_line6_pod *pod = (struct usb_line6_pod *)arg;
|
|
|
|
|
2009-02-28 06:41:29 +00:00
|
|
|
if (pod->dumpreq.ok) {
|
|
|
|
if (!pod->versionreq_ok)
|
2009-02-28 03:43:04 +00:00
|
|
|
request = REQUEST_VERSION;
|
2009-02-28 06:41:29 +00:00
|
|
|
} else {
|
|
|
|
if (pod->versionreq_ok)
|
2009-02-28 03:43:04 +00:00
|
|
|
request = REQUEST_DUMP;
|
2009-02-28 06:41:29 +00:00
|
|
|
else if (pod->startup_count++ & 1)
|
2009-02-28 03:43:04 +00:00
|
|
|
request = REQUEST_DUMP;
|
|
|
|
else
|
|
|
|
request = REQUEST_VERSION;
|
|
|
|
}
|
|
|
|
|
2009-02-28 06:41:29 +00:00
|
|
|
switch (request) {
|
2009-02-28 03:43:04 +00:00
|
|
|
case REQUEST_DUMP:
|
|
|
|
line6_dump_request_async(&pod->dumpreq, &pod->line6, 0);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case REQUEST_VERSION:
|
|
|
|
pod_version_request_async(pod);
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
line6_startup_delayed(&pod->dumpreq, 1, pod_startup_timeout, pod);
|
|
|
|
}
|
|
|
|
|
|
|
|
static char *pod_alloc_sysex_buffer(struct usb_line6_pod *pod, int code, int size)
|
|
|
|
{
|
|
|
|
return line6_alloc_sysex_buffer(&pod->line6, POD_SYSEX_CODE, code, size);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
Send channel dump data to the PODxt Pro.
|
|
|
|
*/
|
|
|
|
static void pod_dump(struct usb_line6_pod *pod, const unsigned char *data)
|
|
|
|
{
|
|
|
|
int size = 1 + sizeof(pod->prog_data);
|
|
|
|
char *sysex = pod_alloc_sysex_buffer(pod, POD_SYSEX_DUMP, size);
|
2009-02-28 06:41:29 +00:00
|
|
|
if (!sysex)
|
|
|
|
return;
|
|
|
|
/* Don't know what this is good for, but PODxt Pro transmits it, so we
|
|
|
|
* also do... */
|
|
|
|
sysex[SYSEX_DATA_OFS] = 5;
|
2009-02-28 03:43:04 +00:00
|
|
|
memcpy(sysex + SYSEX_DATA_OFS + 1, data, sizeof(pod->prog_data));
|
|
|
|
line6_send_sysex_message(&pod->line6, sysex, size);
|
|
|
|
memcpy(&pod->prog_data, data, sizeof(pod->prog_data));
|
|
|
|
pod_mark_batch_all_dirty(pod);
|
|
|
|
kfree(sysex);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
Store parameter value in driver memory and mark it as dirty.
|
|
|
|
*/
|
|
|
|
static void pod_store_parameter(struct usb_line6_pod *pod, int param, int value)
|
|
|
|
{
|
|
|
|
pod->prog_data.control[param] = value;
|
|
|
|
set_bit(param, pod->param_dirty);
|
|
|
|
pod->dirty = 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
Handle SAVE button
|
|
|
|
*/
|
|
|
|
static void pod_save_button_pressed(struct usb_line6_pod *pod, int type, int index)
|
|
|
|
{
|
|
|
|
pod->dirty = 0;
|
|
|
|
set_bit(POD_SAVE_PRESSED, &pod->atomic_flags);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
Process a completely received message.
|
|
|
|
*/
|
|
|
|
void pod_process_message(struct usb_line6_pod *pod)
|
|
|
|
{
|
|
|
|
const unsigned char *buf = pod->line6.buffer_message;
|
|
|
|
|
|
|
|
/* filter messages by type */
|
2009-02-28 06:41:29 +00:00
|
|
|
switch (buf[0] & 0xf0) {
|
2009-02-28 03:43:04 +00:00
|
|
|
case LINE6_PARAM_CHANGE:
|
|
|
|
case LINE6_PROGRAM_CHANGE:
|
|
|
|
case LINE6_SYSEX_BEGIN:
|
|
|
|
break; /* handle these further down */
|
|
|
|
|
|
|
|
default:
|
|
|
|
return; /* ignore all others */
|
|
|
|
}
|
|
|
|
|
|
|
|
/* process all remaining messages */
|
2009-02-28 06:41:29 +00:00
|
|
|
switch (buf[0]) {
|
2009-02-28 03:43:04 +00:00
|
|
|
case LINE6_PARAM_CHANGE | LINE6_CHANNEL_DEVICE:
|
|
|
|
pod_store_parameter(pod, buf[1], buf[2]);
|
|
|
|
/* intentionally no break here! */
|
|
|
|
|
|
|
|
case LINE6_PARAM_CHANGE | LINE6_CHANNEL_HOST:
|
2009-02-28 06:41:29 +00:00
|
|
|
if ((buf[1] == POD_amp_model_setup) ||
|
|
|
|
(buf[1] == POD_effect_setup))
|
|
|
|
/* these also affect other settings */
|
2009-02-28 03:43:04 +00:00
|
|
|
line6_dump_request_async(&pod->dumpreq, &pod->line6, 0);
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
case LINE6_PROGRAM_CHANGE | LINE6_CHANNEL_DEVICE:
|
|
|
|
case LINE6_PROGRAM_CHANGE | LINE6_CHANNEL_HOST:
|
|
|
|
pod->channel_num = buf[1];
|
|
|
|
pod->dirty = 0;
|
|
|
|
set_bit(POD_CHANNEL_DIRTY, &pod->atomic_flags);
|
|
|
|
line6_dump_request_async(&pod->dumpreq, &pod->line6, 0);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case LINE6_SYSEX_BEGIN | LINE6_CHANNEL_DEVICE:
|
|
|
|
case LINE6_SYSEX_BEGIN | LINE6_CHANNEL_UNKNOWN:
|
2009-02-28 06:41:29 +00:00
|
|
|
if (memcmp(buf + 1, line6_midi_id, sizeof(line6_midi_id)) == 0) {
|
|
|
|
switch (buf[5]) {
|
2009-02-28 03:43:04 +00:00
|
|
|
case POD_SYSEX_DUMP:
|
2009-02-28 06:41:29 +00:00
|
|
|
if (pod->line6.message_length == sizeof(pod->prog_data) + 7) {
|
|
|
|
switch (pod->dumpreq.in_progress) {
|
2009-02-28 03:43:04 +00:00
|
|
|
case LINE6_DUMP_CURRENT:
|
|
|
|
memcpy(&pod->prog_data, buf + 7, sizeof(pod->prog_data));
|
|
|
|
pod_mark_batch_all_dirty(pod);
|
|
|
|
pod->dumpreq.ok = 1;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case POD_DUMP_MEMORY:
|
|
|
|
memcpy(&pod->prog_data_buf, buf + 7, sizeof(pod->prog_data_buf));
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
DEBUG_MESSAGES(dev_err(pod->line6.ifcdev, "unknown dump code %02X\n", pod->dumpreq.in_progress));
|
|
|
|
}
|
|
|
|
|
|
|
|
line6_dump_finished(&pod->dumpreq);
|
2009-02-28 06:41:29 +00:00
|
|
|
} else
|
2009-02-28 03:43:04 +00:00
|
|
|
DEBUG_MESSAGES(dev_err(pod->line6.ifcdev, "wrong size of channel dump message (%d instead of %d)\n",
|
|
|
|
pod->line6.message_length, (int)sizeof(pod->prog_data) + 7));
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
case POD_SYSEX_SYSTEM: {
|
|
|
|
short value = ((int)buf[7] << 12) | ((int)buf[8] << 8) | ((int)buf[9] << 4) | (int)buf[10];
|
|
|
|
|
|
|
|
#define PROCESS_SYSTEM_PARAM(x) \
|
|
|
|
case POD_ ## x: \
|
|
|
|
pod->x.value = value; \
|
|
|
|
wake_up_interruptible(&pod->x.wait); \
|
|
|
|
break;
|
|
|
|
|
2009-02-28 06:41:29 +00:00
|
|
|
switch (buf[6]) {
|
2009-02-28 03:43:04 +00:00
|
|
|
PROCESS_SYSTEM_PARAM(monitor_level);
|
|
|
|
PROCESS_SYSTEM_PARAM(routing);
|
|
|
|
PROCESS_SYSTEM_PARAM(tuner_mute);
|
|
|
|
PROCESS_SYSTEM_PARAM(tuner_freq);
|
|
|
|
PROCESS_SYSTEM_PARAM(tuner_note);
|
|
|
|
PROCESS_SYSTEM_PARAM(tuner_pitch);
|
|
|
|
|
|
|
|
#undef PROCESS_SYSTEM_PARAM
|
|
|
|
|
|
|
|
default:
|
|
|
|
DEBUG_MESSAGES(dev_err(pod->line6.ifcdev, "unknown tuner/system response %02X\n", buf[6]));
|
|
|
|
}
|
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case POD_SYSEX_FINISH:
|
|
|
|
/* do we need to respond to this? */
|
|
|
|
break;
|
|
|
|
|
|
|
|
case POD_SYSEX_SAVE:
|
|
|
|
pod_save_button_pressed(pod, buf[6], buf[7]);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case POD_SYSEX_CLIP:
|
|
|
|
DEBUG_MESSAGES(dev_err(pod->line6.ifcdev, "audio clipped\n"));
|
|
|
|
pod->clipping.value = 1;
|
|
|
|
wake_up_interruptible(&pod->clipping.wait);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case POD_SYSEX_STORE:
|
|
|
|
DEBUG_MESSAGES(dev_err(pod->line6.ifcdev, "message %02X not yet implemented\n", buf[5]));
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
DEBUG_MESSAGES(dev_err(pod->line6.ifcdev, "unknown sysex message %02X\n", buf[5]));
|
|
|
|
}
|
2009-02-28 06:41:29 +00:00
|
|
|
} else if (memcmp(buf, pod_version_header, sizeof(pod_version_header)) == 0) {
|
|
|
|
if (pod->versionreq_ok == 0) {
|
2009-02-28 03:43:04 +00:00
|
|
|
pod->firmware_version = buf[13] * 100 + buf[14] * 10 + buf[15];
|
|
|
|
pod->device_id = ((int)buf[8] << 16) | ((int)buf[9] << 8) | (int)buf[10];
|
|
|
|
pod->versionreq_ok = 1;
|
|
|
|
|
|
|
|
/* Now we know the firmware version, so we schedule a bottom half
|
|
|
|
handler to create the special files: */
|
|
|
|
INIT_WORK(&pod->create_files_work, pod_create_files_work);
|
|
|
|
queue_work(line6_workqueue, &pod->create_files_work);
|
2009-02-28 06:41:29 +00:00
|
|
|
} else
|
2009-02-28 03:43:04 +00:00
|
|
|
DEBUG_MESSAGES(dev_err(pod->line6.ifcdev, "multiple firmware version message\n"));
|
2009-02-28 06:41:29 +00:00
|
|
|
} else
|
2009-02-28 03:43:04 +00:00
|
|
|
DEBUG_MESSAGES(dev_err(pod->line6.ifcdev, "unknown sysex header\n"));
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
case LINE6_SYSEX_END:
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
DEBUG_MESSAGES(dev_err(pod->line6.ifcdev, "POD: unknown message %02X\n", buf[0]));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
Detect some cases that require a channel dump after sending a command to the
|
|
|
|
device. Important notes:
|
|
|
|
*) The actual dump request can not be sent here since we are not allowed to
|
|
|
|
wait for the completion of the first message in this context, and sending
|
|
|
|
the dump request before completion of the previous message leaves the POD
|
|
|
|
in an undefined state. The dump request will be sent when the echoed
|
|
|
|
commands are received.
|
|
|
|
*) This method fails if a param change message is "chopped" after the first
|
|
|
|
byte.
|
|
|
|
*/
|
|
|
|
void pod_midi_postprocess(struct usb_line6_pod *pod, unsigned char *data, int length)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
|
2009-02-28 06:41:29 +00:00
|
|
|
if (!pod->midi_postprocess)
|
2009-02-28 03:43:04 +00:00
|
|
|
return;
|
|
|
|
|
2009-02-28 06:41:29 +00:00
|
|
|
for (i = 0; i < length; ++i) {
|
|
|
|
if (data[i] == (LINE6_PROGRAM_CHANGE | LINE6_CHANNEL_HOST)) {
|
2009-02-28 03:43:04 +00:00
|
|
|
line6_invalidate_current(&pod->dumpreq);
|
|
|
|
break;
|
2009-02-28 06:41:29 +00:00
|
|
|
} else if ((data[i] == (LINE6_PARAM_CHANGE | LINE6_CHANNEL_HOST)) && (i < length - 1))
|
|
|
|
if ((data[i + 1] == POD_amp_model_setup) || (data[i + 1] == POD_effect_setup)) {
|
2009-02-28 03:43:04 +00:00
|
|
|
line6_invalidate_current(&pod->dumpreq);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
Send channel number (i.e., switch to a different sound).
|
|
|
|
*/
|
2009-02-28 04:45:03 +00:00
|
|
|
static void pod_send_channel(struct usb_line6_pod *pod, int value)
|
2009-02-28 03:43:04 +00:00
|
|
|
{
|
|
|
|
line6_invalidate_current(&pod->dumpreq);
|
|
|
|
|
2009-02-28 06:41:29 +00:00
|
|
|
if (line6_send_program(&pod->line6, value) == 0)
|
2009-02-28 03:43:04 +00:00
|
|
|
pod->channel_num = value;
|
|
|
|
else
|
|
|
|
line6_dump_finished(&pod->dumpreq);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
Transmit PODxt Pro control parameter.
|
|
|
|
*/
|
|
|
|
void pod_transmit_parameter(struct usb_line6_pod *pod, int param, int value)
|
|
|
|
{
|
2009-02-28 06:41:29 +00:00
|
|
|
if (line6_transmit_parameter(&pod->line6, param, value) == 0)
|
2009-02-28 03:43:04 +00:00
|
|
|
pod_store_parameter(pod, param, value);
|
|
|
|
|
2009-02-28 06:41:29 +00:00
|
|
|
if ((param == POD_amp_model_setup) || (param == POD_effect_setup)) /* these also affect other settings */
|
2009-02-28 03:43:04 +00:00
|
|
|
line6_invalidate_current(&pod->dumpreq);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
Resolve value to memory location.
|
|
|
|
*/
|
|
|
|
static void pod_resolve(const char *buf, short block0, short block1, unsigned char *location)
|
|
|
|
{
|
|
|
|
int value = simple_strtoul(buf, NULL, 10);
|
|
|
|
short block = (value < 0x40) ? block0 : block1;
|
|
|
|
value &= 0x3f;
|
|
|
|
location[0] = block >> 7;
|
|
|
|
location[1] = value | (block & 0x7f);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
Send command to store channel/effects setup/amp setup to PODxt Pro.
|
|
|
|
*/
|
|
|
|
static ssize_t pod_send_store_command(struct device *dev, const char *buf, size_t count, short block0, short block1)
|
|
|
|
{
|
|
|
|
struct usb_interface *interface = to_usb_interface(dev);
|
|
|
|
struct usb_line6_pod *pod = usb_get_intfdata(interface);
|
|
|
|
|
|
|
|
int size = 3 + sizeof(pod->prog_data_buf);
|
|
|
|
char *sysex = pod_alloc_sysex_buffer(pod, POD_SYSEX_STORE, size);
|
2009-02-28 06:41:29 +00:00
|
|
|
if (!sysex)
|
|
|
|
return 0;
|
2009-02-28 03:43:04 +00:00
|
|
|
|
|
|
|
sysex[SYSEX_DATA_OFS] = 5; /* see pod_dump() */
|
|
|
|
pod_resolve(buf, block0, block1, sysex + SYSEX_DATA_OFS + 1);
|
|
|
|
memcpy(sysex + SYSEX_DATA_OFS + 3, &pod->prog_data_buf, sizeof(pod->prog_data_buf));
|
|
|
|
|
|
|
|
line6_send_sysex_message(&pod->line6, sysex, size);
|
|
|
|
kfree(sysex);
|
|
|
|
/* needs some delay here on AMD64 platform */
|
|
|
|
return count;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
Send command to retrieve channel/effects setup/amp setup to PODxt Pro.
|
|
|
|
*/
|
|
|
|
static ssize_t pod_send_retrieve_command(struct device *dev, const char *buf, size_t count, short block0, short block1)
|
|
|
|
{
|
|
|
|
struct usb_interface *interface = to_usb_interface(dev);
|
|
|
|
struct usb_line6_pod *pod = usb_get_intfdata(interface);
|
|
|
|
int size = 4;
|
|
|
|
char *sysex = pod_alloc_sysex_buffer(pod, POD_SYSEX_DUMPMEM, size);
|
2009-02-28 06:41:29 +00:00
|
|
|
|
|
|
|
if (!sysex)
|
|
|
|
return 0;
|
2009-02-28 03:43:04 +00:00
|
|
|
|
|
|
|
pod_resolve(buf, block0, block1, sysex + SYSEX_DATA_OFS);
|
|
|
|
sysex[SYSEX_DATA_OFS + 2] = 0;
|
|
|
|
sysex[SYSEX_DATA_OFS + 3] = 0;
|
|
|
|
line6_dump_started(&pod->dumpreq, POD_DUMP_MEMORY);
|
|
|
|
|
2009-02-28 06:41:29 +00:00
|
|
|
if (line6_send_sysex_message(&pod->line6, sysex, size) < size)
|
2009-02-28 03:43:04 +00:00
|
|
|
line6_dump_finished(&pod->dumpreq);
|
|
|
|
|
|
|
|
kfree(sysex);
|
|
|
|
/* needs some delay here on AMD64 platform */
|
|
|
|
return count;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
Generic get name function.
|
|
|
|
*/
|
|
|
|
static ssize_t get_name_generic(struct usb_line6_pod *pod, const char *str, char *buf)
|
|
|
|
{
|
|
|
|
int length = 0;
|
|
|
|
const char *p1;
|
|
|
|
char *p2;
|
|
|
|
char *last_non_space = buf;
|
|
|
|
|
|
|
|
int retval = line6_wait_dump(&pod->dumpreq, 0);
|
2009-02-28 06:41:29 +00:00
|
|
|
if (retval < 0)
|
|
|
|
return retval;
|
2009-02-28 03:43:04 +00:00
|
|
|
|
2009-02-28 06:41:29 +00:00
|
|
|
for (p1 = str, p2 = buf; *p1; ++p1, ++p2) {
|
2009-02-28 03:43:04 +00:00
|
|
|
*p2 = *p1;
|
2009-02-28 06:41:29 +00:00
|
|
|
if (*p2 != ' ')
|
|
|
|
last_non_space = p2;
|
|
|
|
if (++length == POD_NAME_LENGTH)
|
|
|
|
break;
|
2009-02-28 03:43:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
*(last_non_space + 1) = '\n';
|
|
|
|
return last_non_space - buf + 2;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
"read" request on "channel" special file.
|
|
|
|
*/
|
2009-02-28 04:25:43 +00:00
|
|
|
static ssize_t pod_get_channel(struct device *dev,
|
|
|
|
struct device_attribute *attr, char *buf)
|
2009-02-28 03:43:04 +00:00
|
|
|
{
|
|
|
|
struct usb_interface *interface = to_usb_interface(dev);
|
|
|
|
struct usb_line6_pod *pod = usb_get_intfdata(interface);
|
|
|
|
return sprintf(buf, "%d\n", pod->channel_num);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
"write" request on "channel" special file.
|
|
|
|
*/
|
2009-02-28 04:25:43 +00:00
|
|
|
static ssize_t pod_set_channel(struct device *dev,
|
|
|
|
struct device_attribute *attr,
|
|
|
|
const char *buf, size_t count)
|
2009-02-28 03:43:04 +00:00
|
|
|
{
|
|
|
|
struct usb_interface *interface = to_usb_interface(dev);
|
|
|
|
struct usb_line6_pod *pod = usb_get_intfdata(interface);
|
|
|
|
int value = simple_strtoul(buf, NULL, 10);
|
|
|
|
pod_send_channel(pod, value);
|
|
|
|
return count;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
"read" request on "name" special file.
|
|
|
|
*/
|
2009-02-28 04:25:43 +00:00
|
|
|
static ssize_t pod_get_name(struct device *dev, struct device_attribute *attr,
|
|
|
|
char *buf)
|
2009-02-28 03:43:04 +00:00
|
|
|
{
|
|
|
|
struct usb_interface *interface = to_usb_interface(dev);
|
|
|
|
struct usb_line6_pod *pod = usb_get_intfdata(interface);
|
|
|
|
return get_name_generic(pod, pod->prog_data.header + POD_NAME_OFFSET, buf);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
"read" request on "name" special file.
|
|
|
|
*/
|
2009-02-28 04:25:43 +00:00
|
|
|
static ssize_t pod_get_name_buf(struct device *dev,
|
|
|
|
struct device_attribute *attr, char *buf)
|
2009-02-28 03:43:04 +00:00
|
|
|
{
|
|
|
|
struct usb_interface *interface = to_usb_interface(dev);
|
|
|
|
struct usb_line6_pod *pod = usb_get_intfdata(interface);
|
|
|
|
return get_name_generic(pod, pod->prog_data_buf.header + POD_NAME_OFFSET, buf);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
"read" request on "dump" special file.
|
|
|
|
*/
|
2009-02-28 04:25:43 +00:00
|
|
|
static ssize_t pod_get_dump(struct device *dev, struct device_attribute *attr,
|
|
|
|
char *buf)
|
2009-02-28 03:43:04 +00:00
|
|
|
{
|
|
|
|
struct usb_interface *interface = to_usb_interface(dev);
|
|
|
|
struct usb_line6_pod *pod = usb_get_intfdata(interface);
|
|
|
|
int retval = line6_wait_dump(&pod->dumpreq, 0);
|
2009-02-28 06:41:29 +00:00
|
|
|
if (retval < 0)
|
|
|
|
return retval;
|
2009-02-28 03:43:04 +00:00
|
|
|
memcpy(buf, &pod->prog_data, sizeof(pod->prog_data));
|
|
|
|
return sizeof(pod->prog_data);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
"write" request on "dump" special file.
|
|
|
|
*/
|
2009-02-28 04:25:43 +00:00
|
|
|
static ssize_t pod_set_dump(struct device *dev, struct device_attribute *attr,
|
|
|
|
const char *buf, size_t count)
|
2009-02-28 03:43:04 +00:00
|
|
|
{
|
|
|
|
struct usb_interface *interface = to_usb_interface(dev);
|
|
|
|
struct usb_line6_pod *pod = usb_get_intfdata(interface);
|
|
|
|
|
2009-02-28 06:41:29 +00:00
|
|
|
if (count != sizeof(pod->prog_data)) {
|
2009-02-28 03:43:04 +00:00
|
|
|
dev_err(pod->line6.ifcdev,
|
|
|
|
"data block must be exactly %d bytes\n",
|
|
|
|
(int)sizeof(pod->prog_data));
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
pod_dump(pod, buf);
|
|
|
|
return sizeof(pod->prog_data);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
Request system parameter.
|
|
|
|
@param tuner non-zero, if code refers to a tuner parameter
|
|
|
|
*/
|
|
|
|
static ssize_t pod_get_system_param(struct usb_line6_pod *pod, char *buf, int code, struct ValueWait *param, int tuner, int sign)
|
|
|
|
{
|
|
|
|
char *sysex;
|
|
|
|
int value;
|
|
|
|
static const int size = 1;
|
|
|
|
int retval = 0;
|
|
|
|
DECLARE_WAITQUEUE(wait, current);
|
|
|
|
|
2009-02-28 06:41:29 +00:00
|
|
|
if (((pod->prog_data.control[POD_tuner] & 0x40) == 0) && tuner)
|
2009-02-28 03:43:04 +00:00
|
|
|
return -ENODEV;
|
|
|
|
|
|
|
|
/* send value request to tuner: */
|
|
|
|
param->value = POD_system_invalid;
|
|
|
|
sysex = pod_alloc_sysex_buffer(pod, POD_SYSEX_SYSTEMREQ, size);
|
2009-02-28 06:41:29 +00:00
|
|
|
if (!sysex)
|
|
|
|
return 0;
|
2009-02-28 03:43:04 +00:00
|
|
|
sysex[SYSEX_DATA_OFS] = code;
|
|
|
|
line6_send_sysex_message(&pod->line6, sysex, size);
|
|
|
|
kfree(sysex);
|
|
|
|
|
|
|
|
/* wait for tuner to respond: */
|
|
|
|
add_wait_queue(¶m->wait, &wait);
|
|
|
|
current->state = TASK_INTERRUPTIBLE;
|
|
|
|
|
2009-02-28 06:41:29 +00:00
|
|
|
while (param->value == POD_system_invalid) {
|
|
|
|
if (signal_pending(current)) {
|
2009-02-28 03:43:04 +00:00
|
|
|
retval = -ERESTARTSYS;
|
|
|
|
break;
|
2009-02-28 06:41:29 +00:00
|
|
|
} else
|
2009-02-28 03:43:04 +00:00
|
|
|
schedule();
|
|
|
|
}
|
|
|
|
|
|
|
|
current->state = TASK_RUNNING;
|
|
|
|
remove_wait_queue(¶m->wait, &wait);
|
|
|
|
|
2009-02-28 06:41:29 +00:00
|
|
|
if (retval < 0)
|
2009-02-28 03:43:04 +00:00
|
|
|
return retval;
|
|
|
|
|
|
|
|
value = sign ? (int)(signed short)param->value : (int)(unsigned short)param->value;
|
|
|
|
return sprintf(buf, "%d\n", value);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
Send system parameter.
|
|
|
|
@param tuner non-zero, if code refers to a tuner parameter
|
|
|
|
*/
|
2009-02-28 06:41:29 +00:00
|
|
|
static ssize_t pod_set_system_param(struct usb_line6_pod *pod, const char *buf,
|
|
|
|
int count, int code, unsigned short mask,
|
|
|
|
int tuner)
|
2009-02-28 03:43:04 +00:00
|
|
|
{
|
|
|
|
char *sysex;
|
|
|
|
static const int size = 5;
|
|
|
|
unsigned short value;
|
|
|
|
|
2009-02-28 06:41:29 +00:00
|
|
|
if (((pod->prog_data.control[POD_tuner] & 0x40) == 0) && tuner)
|
2009-02-28 03:43:04 +00:00
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
/* send value to tuner: */
|
|
|
|
sysex = pod_alloc_sysex_buffer(pod, POD_SYSEX_SYSTEM, size);
|
2009-02-28 06:41:29 +00:00
|
|
|
if (!sysex)
|
|
|
|
return 0;
|
2009-02-28 03:43:04 +00:00
|
|
|
value = simple_strtoul(buf, NULL, 10) & mask;
|
|
|
|
sysex[SYSEX_DATA_OFS] = code;
|
|
|
|
sysex[SYSEX_DATA_OFS + 1] = (value >> 12) & 0x0f;
|
|
|
|
sysex[SYSEX_DATA_OFS + 2] = (value >> 8) & 0x0f;
|
|
|
|
sysex[SYSEX_DATA_OFS + 3] = (value >> 4) & 0x0f;
|
|
|
|
sysex[SYSEX_DATA_OFS + 4] = (value ) & 0x0f;
|
|
|
|
line6_send_sysex_message(&pod->line6, sysex, size);
|
|
|
|
kfree(sysex);
|
|
|
|
return count;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
"read" request on "dump_buf" special file.
|
|
|
|
*/
|
2009-02-28 04:25:43 +00:00
|
|
|
static ssize_t pod_get_dump_buf(struct device *dev,
|
|
|
|
struct device_attribute *attr, char *buf)
|
2009-02-28 03:43:04 +00:00
|
|
|
{
|
|
|
|
struct usb_interface *interface = to_usb_interface(dev);
|
|
|
|
struct usb_line6_pod *pod = usb_get_intfdata(interface);
|
|
|
|
int retval = line6_wait_dump(&pod->dumpreq, 0);
|
2009-02-28 06:41:29 +00:00
|
|
|
if (retval < 0)
|
|
|
|
return retval;
|
2009-02-28 03:43:04 +00:00
|
|
|
memcpy(buf, &pod->prog_data_buf, sizeof(pod->prog_data_buf));
|
|
|
|
return sizeof(pod->prog_data_buf);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
"write" request on "dump_buf" special file.
|
|
|
|
*/
|
2009-02-28 04:25:43 +00:00
|
|
|
static ssize_t pod_set_dump_buf(struct device *dev,
|
|
|
|
struct device_attribute *attr,
|
|
|
|
const char *buf, size_t count)
|
2009-02-28 03:43:04 +00:00
|
|
|
{
|
|
|
|
struct usb_interface *interface = to_usb_interface(dev);
|
|
|
|
struct usb_line6_pod *pod = usb_get_intfdata(interface);
|
|
|
|
|
2009-02-28 06:41:29 +00:00
|
|
|
if (count != sizeof(pod->prog_data)) {
|
2009-02-28 03:43:04 +00:00
|
|
|
dev_err(pod->line6.ifcdev,
|
|
|
|
"data block must be exactly %d bytes\n",
|
|
|
|
(int)sizeof(pod->prog_data));
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
memcpy(&pod->prog_data_buf, buf, sizeof(pod->prog_data));
|
|
|
|
return sizeof(pod->prog_data);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
"write" request on "finish" special file.
|
|
|
|
*/
|
2009-02-28 04:25:43 +00:00
|
|
|
static ssize_t pod_set_finish(struct device *dev,
|
|
|
|
struct device_attribute *attr,
|
|
|
|
const char *buf, size_t count)
|
2009-02-28 03:43:04 +00:00
|
|
|
{
|
|
|
|
struct usb_interface *interface = to_usb_interface(dev);
|
|
|
|
struct usb_line6_pod *pod = usb_get_intfdata(interface);
|
|
|
|
int size = 0;
|
|
|
|
char *sysex = pod_alloc_sysex_buffer(pod, POD_SYSEX_FINISH, size);
|
2009-02-28 06:41:29 +00:00
|
|
|
if (!sysex)
|
|
|
|
return 0;
|
2009-02-28 03:43:04 +00:00
|
|
|
line6_send_sysex_message(&pod->line6, sysex, size);
|
|
|
|
kfree(sysex);
|
|
|
|
return count;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
"write" request on "store_channel" special file.
|
|
|
|
*/
|
2009-02-28 04:25:43 +00:00
|
|
|
static ssize_t pod_set_store_channel(struct device *dev,
|
|
|
|
struct device_attribute *attr,
|
|
|
|
const char *buf, size_t count)
|
2009-02-28 03:43:04 +00:00
|
|
|
{
|
|
|
|
return pod_send_store_command(dev, buf, count, 0x0000, 0x00c0);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
"write" request on "store_effects_setup" special file.
|
|
|
|
*/
|
2009-02-28 04:25:43 +00:00
|
|
|
static ssize_t pod_set_store_effects_setup(struct device *dev,
|
|
|
|
struct device_attribute *attr,
|
|
|
|
const char *buf, size_t count)
|
2009-02-28 03:43:04 +00:00
|
|
|
{
|
|
|
|
return pod_send_store_command(dev, buf, count, 0x0080, 0x0080);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
"write" request on "store_amp_setup" special file.
|
|
|
|
*/
|
2009-02-28 04:25:43 +00:00
|
|
|
static ssize_t pod_set_store_amp_setup(struct device *dev,
|
|
|
|
struct device_attribute *attr,
|
|
|
|
const char *buf, size_t count)
|
2009-02-28 03:43:04 +00:00
|
|
|
{
|
|
|
|
return pod_send_store_command(dev, buf, count, 0x0040, 0x0100);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
"write" request on "retrieve_channel" special file.
|
|
|
|
*/
|
2009-02-28 04:25:43 +00:00
|
|
|
static ssize_t pod_set_retrieve_channel(struct device *dev,
|
|
|
|
struct device_attribute *attr,
|
|
|
|
const char *buf, size_t count)
|
2009-02-28 03:43:04 +00:00
|
|
|
{
|
|
|
|
return pod_send_retrieve_command(dev, buf, count, 0x0000, 0x00c0);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
"write" request on "retrieve_effects_setup" special file.
|
|
|
|
*/
|
2009-02-28 04:25:43 +00:00
|
|
|
static ssize_t pod_set_retrieve_effects_setup(struct device *dev,
|
|
|
|
struct device_attribute *attr,
|
|
|
|
const char *buf, size_t count)
|
2009-02-28 03:43:04 +00:00
|
|
|
{
|
|
|
|
return pod_send_retrieve_command(dev, buf, count, 0x0080, 0x0080);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
"write" request on "retrieve_amp_setup" special file.
|
|
|
|
*/
|
2009-02-28 04:25:43 +00:00
|
|
|
static ssize_t pod_set_retrieve_amp_setup(struct device *dev,
|
|
|
|
struct device_attribute *attr,
|
|
|
|
const char *buf, size_t count)
|
2009-02-28 03:43:04 +00:00
|
|
|
{
|
|
|
|
return pod_send_retrieve_command(dev, buf, count, 0x0040, 0x0100);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
"read" request on "dirty" special file.
|
|
|
|
*/
|
2009-02-28 04:25:43 +00:00
|
|
|
static ssize_t pod_get_dirty(struct device *dev, struct device_attribute *attr,
|
|
|
|
char *buf)
|
2009-02-28 03:43:04 +00:00
|
|
|
{
|
|
|
|
struct usb_interface *interface = to_usb_interface(dev);
|
|
|
|
struct usb_line6_pod *pod = usb_get_intfdata(interface);
|
|
|
|
buf[0] = pod->dirty ? '1' : '0';
|
|
|
|
buf[1] = '\n';
|
|
|
|
return 2;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
"read" request on "midi_postprocess" special file.
|
|
|
|
*/
|
2009-02-28 04:25:43 +00:00
|
|
|
static ssize_t pod_get_midi_postprocess(struct device *dev,
|
|
|
|
struct device_attribute *attr,
|
|
|
|
char *buf)
|
2009-02-28 03:43:04 +00:00
|
|
|
{
|
|
|
|
struct usb_interface *interface = to_usb_interface(dev);
|
|
|
|
struct usb_line6_pod *pod = usb_get_intfdata(interface);
|
|
|
|
return sprintf(buf, "%d\n", pod->midi_postprocess);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
"write" request on "midi_postprocess" special file.
|
|
|
|
*/
|
2009-02-28 04:25:43 +00:00
|
|
|
static ssize_t pod_set_midi_postprocess(struct device *dev,
|
|
|
|
struct device_attribute *attr,
|
|
|
|
const char *buf, size_t count)
|
2009-02-28 03:43:04 +00:00
|
|
|
{
|
|
|
|
struct usb_interface *interface = to_usb_interface(dev);
|
|
|
|
struct usb_line6_pod *pod = usb_get_intfdata(interface);
|
|
|
|
int value = simple_strtoul(buf, NULL, 10);
|
|
|
|
pod->midi_postprocess = value ? 1 : 0;
|
|
|
|
return count;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
"read" request on "serial_number" special file.
|
|
|
|
*/
|
2009-02-28 04:25:43 +00:00
|
|
|
static ssize_t pod_get_serial_number(struct device *dev,
|
|
|
|
struct device_attribute *attr, char *buf)
|
2009-02-28 03:43:04 +00:00
|
|
|
{
|
|
|
|
struct usb_interface *interface = to_usb_interface(dev);
|
|
|
|
struct usb_line6_pod *pod = usb_get_intfdata(interface);
|
|
|
|
return sprintf(buf, "%d\n", pod->serial_number);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
"read" request on "firmware_version" special file.
|
|
|
|
*/
|
2009-02-28 04:25:43 +00:00
|
|
|
static ssize_t pod_get_firmware_version(struct device *dev,
|
|
|
|
struct device_attribute *attr,
|
|
|
|
char *buf)
|
2009-02-28 03:43:04 +00:00
|
|
|
{
|
|
|
|
struct usb_interface *interface = to_usb_interface(dev);
|
|
|
|
struct usb_line6_pod *pod = usb_get_intfdata(interface);
|
2009-02-28 06:41:29 +00:00
|
|
|
return sprintf(buf, "%d.%02d\n", pod->firmware_version / 100,
|
|
|
|
pod->firmware_version % 100);
|
2009-02-28 03:43:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
"read" request on "device_id" special file.
|
|
|
|
*/
|
2009-02-28 04:25:43 +00:00
|
|
|
static ssize_t pod_get_device_id(struct device *dev,
|
|
|
|
struct device_attribute *attr, char *buf)
|
2009-02-28 03:43:04 +00:00
|
|
|
{
|
|
|
|
struct usb_interface *interface = to_usb_interface(dev);
|
|
|
|
struct usb_line6_pod *pod = usb_get_intfdata(interface);
|
|
|
|
return sprintf(buf, "%d\n", pod->device_id);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
"read" request on "clip" special file.
|
|
|
|
*/
|
2009-02-28 04:25:43 +00:00
|
|
|
static ssize_t pod_wait_for_clip(struct device *dev,
|
|
|
|
struct device_attribute *attr, char *buf)
|
2009-02-28 03:43:04 +00:00
|
|
|
{
|
|
|
|
struct usb_interface *interface = to_usb_interface(dev);
|
|
|
|
struct usb_line6_pod *pod = usb_get_intfdata(interface);
|
|
|
|
int err = 0;
|
|
|
|
DECLARE_WAITQUEUE(wait, current);
|
|
|
|
pod->clipping.value = 0;
|
|
|
|
add_wait_queue(&pod->clipping.wait, &wait);
|
|
|
|
current->state = TASK_INTERRUPTIBLE;
|
|
|
|
|
2009-02-28 06:41:29 +00:00
|
|
|
while (pod->clipping.value == 0) {
|
|
|
|
if (signal_pending(current)) {
|
2009-02-28 03:43:04 +00:00
|
|
|
err = -ERESTARTSYS;
|
|
|
|
break;
|
2009-02-28 06:41:29 +00:00
|
|
|
} else
|
2009-02-28 03:43:04 +00:00
|
|
|
schedule();
|
|
|
|
}
|
|
|
|
|
|
|
|
current->state = TASK_RUNNING;
|
|
|
|
remove_wait_queue(&pod->clipping.wait, &wait);
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
|
|
|
#define POD_GET_SYSTEM_PARAM(code, tuner, sign) \
|
2009-02-28 04:25:43 +00:00
|
|
|
static ssize_t pod_get_ ## code(struct device *dev, \
|
|
|
|
struct device_attribute *attr, char *buf) \
|
2009-02-28 03:43:04 +00:00
|
|
|
{ \
|
|
|
|
struct usb_interface *interface = to_usb_interface(dev); \
|
|
|
|
struct usb_line6_pod *pod = usb_get_intfdata(interface); \
|
2009-02-28 06:41:29 +00:00
|
|
|
return pod_get_system_param(pod, buf, POD_ ## code, &pod->code, \
|
|
|
|
tuner, sign); \
|
2009-02-28 03:43:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#define POD_GET_SET_SYSTEM_PARAM(code, mask, tuner, sign) \
|
|
|
|
POD_GET_SYSTEM_PARAM(code, tuner, sign) \
|
2009-02-28 04:25:43 +00:00
|
|
|
static ssize_t pod_set_ ## code(struct device *dev, \
|
2009-02-28 06:41:29 +00:00
|
|
|
struct device_attribute *attr, \
|
|
|
|
const char *buf, size_t count) \
|
2009-02-28 03:43:04 +00:00
|
|
|
{ \
|
|
|
|
struct usb_interface *interface = to_usb_interface(dev); \
|
|
|
|
struct usb_line6_pod *pod = usb_get_intfdata(interface); \
|
2009-02-28 06:41:29 +00:00
|
|
|
return pod_set_system_param(pod, buf, count, POD_ ## code, mask, \
|
|
|
|
tuner); \
|
2009-02-28 03:43:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
POD_GET_SET_SYSTEM_PARAM(monitor_level, 0xffff, 0, 0);
|
|
|
|
POD_GET_SET_SYSTEM_PARAM(routing, 0x0003, 0, 0);
|
|
|
|
POD_GET_SET_SYSTEM_PARAM(tuner_mute, 0x0001, 1, 0);
|
|
|
|
POD_GET_SET_SYSTEM_PARAM(tuner_freq, 0xffff, 1, 0);
|
|
|
|
POD_GET_SYSTEM_PARAM(tuner_note, 1, 1);
|
|
|
|
POD_GET_SYSTEM_PARAM(tuner_pitch, 1, 1);
|
|
|
|
|
|
|
|
#undef GET_SET_SYSTEM_PARAM
|
|
|
|
#undef GET_SYSTEM_PARAM
|
|
|
|
|
|
|
|
/* POD special files: */
|
|
|
|
static DEVICE_ATTR(channel, S_IWUGO | S_IRUGO, pod_get_channel, pod_set_channel);
|
|
|
|
static DEVICE_ATTR(clip, S_IRUGO, pod_wait_for_clip, line6_nop_write);
|
|
|
|
static DEVICE_ATTR(device_id, S_IRUGO, pod_get_device_id, line6_nop_write);
|
|
|
|
static DEVICE_ATTR(dirty, S_IRUGO, pod_get_dirty, line6_nop_write);
|
|
|
|
static DEVICE_ATTR(dump, S_IWUGO | S_IRUGO, pod_get_dump, pod_set_dump);
|
|
|
|
static DEVICE_ATTR(dump_buf, S_IWUGO | S_IRUGO, pod_get_dump_buf, pod_set_dump_buf);
|
|
|
|
static DEVICE_ATTR(finish, S_IWUGO, line6_nop_read, pod_set_finish);
|
|
|
|
static DEVICE_ATTR(firmware_version, S_IRUGO, pod_get_firmware_version, line6_nop_write);
|
|
|
|
static DEVICE_ATTR(midi_postprocess, S_IWUGO | S_IRUGO, pod_get_midi_postprocess, pod_set_midi_postprocess);
|
|
|
|
static DEVICE_ATTR(monitor_level, S_IWUGO | S_IRUGO, pod_get_monitor_level, pod_set_monitor_level);
|
|
|
|
static DEVICE_ATTR(name, S_IRUGO, pod_get_name, line6_nop_write);
|
|
|
|
static DEVICE_ATTR(name_buf, S_IRUGO, pod_get_name_buf, line6_nop_write);
|
|
|
|
static DEVICE_ATTR(retrieve_amp_setup, S_IWUGO, line6_nop_read, pod_set_retrieve_amp_setup);
|
|
|
|
static DEVICE_ATTR(retrieve_channel, S_IWUGO, line6_nop_read, pod_set_retrieve_channel);
|
|
|
|
static DEVICE_ATTR(retrieve_effects_setup, S_IWUGO, line6_nop_read, pod_set_retrieve_effects_setup);
|
|
|
|
static DEVICE_ATTR(routing, S_IWUGO | S_IRUGO, pod_get_routing, pod_set_routing);
|
|
|
|
static DEVICE_ATTR(serial_number, S_IRUGO, pod_get_serial_number, line6_nop_write);
|
|
|
|
static DEVICE_ATTR(store_amp_setup, S_IWUGO, line6_nop_read, pod_set_store_amp_setup);
|
|
|
|
static DEVICE_ATTR(store_channel, S_IWUGO, line6_nop_read, pod_set_store_channel);
|
|
|
|
static DEVICE_ATTR(store_effects_setup, S_IWUGO, line6_nop_read, pod_set_store_effects_setup);
|
|
|
|
static DEVICE_ATTR(tuner_freq, S_IWUGO | S_IRUGO, pod_get_tuner_freq, pod_set_tuner_freq);
|
|
|
|
static DEVICE_ATTR(tuner_mute, S_IWUGO | S_IRUGO, pod_get_tuner_mute, pod_set_tuner_mute);
|
|
|
|
static DEVICE_ATTR(tuner_note, S_IRUGO, pod_get_tuner_note, line6_nop_write);
|
|
|
|
static DEVICE_ATTR(tuner_pitch, S_IRUGO, pod_get_tuner_pitch, line6_nop_write);
|
|
|
|
|
|
|
|
#if CREATE_RAW_FILE
|
|
|
|
static DEVICE_ATTR(raw, S_IWUGO, line6_nop_read, line6_set_raw);
|
|
|
|
#endif
|
|
|
|
|
|
|
|
/*
|
|
|
|
POD destructor.
|
|
|
|
*/
|
|
|
|
static void pod_destruct(struct usb_interface *interface)
|
|
|
|
{
|
|
|
|
struct usb_line6_pod *pod = usb_get_intfdata(interface);
|
|
|
|
struct usb_line6 *line6;
|
|
|
|
|
2009-02-28 06:41:29 +00:00
|
|
|
if (pod == NULL)
|
|
|
|
return;
|
2009-02-28 03:43:04 +00:00
|
|
|
line6 = &pod->line6;
|
2009-02-28 06:41:29 +00:00
|
|
|
if (line6 == NULL)
|
|
|
|
return;
|
2009-02-28 03:43:04 +00:00
|
|
|
line6_cleanup_audio(line6);
|
|
|
|
|
|
|
|
/* free dump request data: */
|
|
|
|
line6_dumpreq_destruct(&pod->dumpreq);
|
|
|
|
|
2009-02-28 06:41:29 +00:00
|
|
|
kfree(pod->buffer_versionreq);
|
2009-02-28 03:43:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
Create sysfs entries.
|
|
|
|
*/
|
2009-02-28 04:45:03 +00:00
|
|
|
static int pod_create_files2(struct device *dev)
|
2009-02-28 03:43:04 +00:00
|
|
|
{
|
|
|
|
int err;
|
|
|
|
|
|
|
|
CHECK_RETURN(device_create_file(dev, &dev_attr_channel));
|
|
|
|
CHECK_RETURN(device_create_file(dev, &dev_attr_clip));
|
|
|
|
CHECK_RETURN(device_create_file(dev, &dev_attr_device_id));
|
|
|
|
CHECK_RETURN(device_create_file(dev, &dev_attr_dirty));
|
|
|
|
CHECK_RETURN(device_create_file(dev, &dev_attr_dump));
|
|
|
|
CHECK_RETURN(device_create_file(dev, &dev_attr_dump_buf));
|
|
|
|
CHECK_RETURN(device_create_file(dev, &dev_attr_finish));
|
|
|
|
CHECK_RETURN(device_create_file(dev, &dev_attr_firmware_version));
|
|
|
|
CHECK_RETURN(device_create_file(dev, &dev_attr_midi_postprocess));
|
|
|
|
CHECK_RETURN(device_create_file(dev, &dev_attr_monitor_level));
|
|
|
|
CHECK_RETURN(device_create_file(dev, &dev_attr_name));
|
|
|
|
CHECK_RETURN(device_create_file(dev, &dev_attr_name_buf));
|
|
|
|
CHECK_RETURN(device_create_file(dev, &dev_attr_retrieve_amp_setup));
|
|
|
|
CHECK_RETURN(device_create_file(dev, &dev_attr_retrieve_channel));
|
|
|
|
CHECK_RETURN(device_create_file(dev, &dev_attr_retrieve_effects_setup));
|
|
|
|
CHECK_RETURN(device_create_file(dev, &dev_attr_routing));
|
|
|
|
CHECK_RETURN(device_create_file(dev, &dev_attr_serial_number));
|
|
|
|
CHECK_RETURN(device_create_file(dev, &dev_attr_store_amp_setup));
|
|
|
|
CHECK_RETURN(device_create_file(dev, &dev_attr_store_channel));
|
|
|
|
CHECK_RETURN(device_create_file(dev, &dev_attr_store_effects_setup));
|
|
|
|
CHECK_RETURN(device_create_file(dev, &dev_attr_tuner_freq));
|
|
|
|
CHECK_RETURN(device_create_file(dev, &dev_attr_tuner_mute));
|
|
|
|
CHECK_RETURN(device_create_file(dev, &dev_attr_tuner_note));
|
|
|
|
CHECK_RETURN(device_create_file(dev, &dev_attr_tuner_pitch));
|
|
|
|
|
|
|
|
#if CREATE_RAW_FILE
|
|
|
|
CHECK_RETURN(device_create_file(dev, &dev_attr_raw));
|
|
|
|
#endif
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
Init POD device.
|
|
|
|
*/
|
|
|
|
int pod_init(struct usb_interface *interface, struct usb_line6_pod *pod)
|
|
|
|
{
|
|
|
|
int err;
|
|
|
|
struct usb_line6 *line6 = &pod->line6;
|
|
|
|
|
2009-02-28 06:41:29 +00:00
|
|
|
if ((interface == NULL) || (pod == NULL))
|
|
|
|
return -ENODEV;
|
2009-02-28 03:43:04 +00:00
|
|
|
|
|
|
|
pod->channel_num = 255;
|
|
|
|
|
|
|
|
/* initialize wait queues: */
|
|
|
|
init_waitqueue_head(&pod->monitor_level.wait);
|
|
|
|
init_waitqueue_head(&pod->routing.wait);
|
|
|
|
init_waitqueue_head(&pod->tuner_mute.wait);
|
|
|
|
init_waitqueue_head(&pod->tuner_freq.wait);
|
|
|
|
init_waitqueue_head(&pod->tuner_note.wait);
|
|
|
|
init_waitqueue_head(&pod->tuner_pitch.wait);
|
|
|
|
init_waitqueue_head(&pod->clipping.wait);
|
|
|
|
|
|
|
|
memset(pod->param_dirty, 0xff, sizeof(pod->param_dirty));
|
|
|
|
|
|
|
|
/* initialize USB buffers: */
|
2009-02-28 06:41:29 +00:00
|
|
|
err = line6_dumpreq_init(&pod->dumpreq, pod_request_channel,
|
|
|
|
sizeof(pod_request_channel));
|
|
|
|
if (err < 0) {
|
2009-02-28 03:43:04 +00:00
|
|
|
dev_err(&interface->dev, "Out of memory\n");
|
|
|
|
pod_destruct(interface);
|
|
|
|
return -ENOMEM;
|
|
|
|
}
|
|
|
|
|
2009-02-28 06:41:29 +00:00
|
|
|
pod->buffer_versionreq = kmalloc(sizeof(pod_request_version),
|
|
|
|
GFP_KERNEL);
|
2009-02-28 03:43:04 +00:00
|
|
|
|
2009-02-28 06:41:29 +00:00
|
|
|
if (pod->buffer_versionreq == NULL) {
|
2009-02-28 03:43:04 +00:00
|
|
|
dev_err(&interface->dev, "Out of memory\n");
|
|
|
|
pod_destruct(interface);
|
|
|
|
return -ENOMEM;
|
|
|
|
}
|
|
|
|
|
2009-02-28 06:41:29 +00:00
|
|
|
memcpy(pod->buffer_versionreq, pod_request_version,
|
|
|
|
sizeof(pod_request_version));
|
2009-02-28 03:43:04 +00:00
|
|
|
|
|
|
|
/* create sysfs entries: */
|
2009-02-28 06:41:29 +00:00
|
|
|
err = pod_create_files2(&interface->dev);
|
|
|
|
if (err < 0) {
|
2009-02-28 03:43:04 +00:00
|
|
|
pod_destruct(interface);
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* initialize audio system: */
|
2009-02-28 06:41:29 +00:00
|
|
|
err = line6_init_audio(line6);
|
|
|
|
if (err < 0) {
|
2009-02-28 03:43:04 +00:00
|
|
|
pod_destruct(interface);
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* initialize MIDI subsystem: */
|
2009-02-28 06:41:29 +00:00
|
|
|
err = line6_init_midi(line6);
|
|
|
|
if (err < 0) {
|
2009-02-28 03:43:04 +00:00
|
|
|
pod_destruct(interface);
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* initialize PCM subsystem: */
|
2009-02-28 06:41:29 +00:00
|
|
|
err = line6_init_pcm(line6, &pod_pcm_properties);
|
|
|
|
if (err < 0) {
|
2009-02-28 03:43:04 +00:00
|
|
|
pod_destruct(interface);
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* register audio system: */
|
2009-02-28 06:41:29 +00:00
|
|
|
err = line6_register_audio(line6);
|
|
|
|
if (err < 0) {
|
2009-02-28 03:43:04 +00:00
|
|
|
pod_destruct(interface);
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
2009-02-28 06:41:29 +00:00
|
|
|
if (pod->line6.properties->capabilities & LINE6_BIT_CONTROL) {
|
2009-02-28 03:43:04 +00:00
|
|
|
/* query some data: */
|
2009-02-28 06:41:29 +00:00
|
|
|
line6_startup_delayed(&pod->dumpreq, POD_STARTUP_DELAY,
|
|
|
|
pod_startup_timeout, pod);
|
2009-02-28 03:43:04 +00:00
|
|
|
line6_read_serial_number(&pod->line6, &pod->serial_number);
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
POD device disconnected.
|
|
|
|
*/
|
|
|
|
void pod_disconnect(struct usb_interface *interface)
|
|
|
|
{
|
|
|
|
struct usb_line6_pod *pod;
|
|
|
|
|
2009-02-28 06:41:29 +00:00
|
|
|
if (interface == NULL)
|
|
|
|
return;
|
2009-02-28 03:43:04 +00:00
|
|
|
pod = usb_get_intfdata(interface);
|
|
|
|
|
2009-02-28 06:41:29 +00:00
|
|
|
if (pod != NULL) {
|
2009-02-28 03:43:04 +00:00
|
|
|
struct snd_line6_pcm *line6pcm = pod->line6.line6pcm;
|
|
|
|
struct device *dev = &interface->dev;
|
|
|
|
|
2009-02-28 06:41:29 +00:00
|
|
|
if (line6pcm != NULL) {
|
2009-02-28 03:43:04 +00:00
|
|
|
unlink_wait_clear_audio_out_urbs(line6pcm);
|
|
|
|
unlink_wait_clear_audio_in_urbs(line6pcm);
|
|
|
|
}
|
|
|
|
|
2009-02-28 06:41:29 +00:00
|
|
|
if (dev != NULL) {
|
2009-02-28 03:43:04 +00:00
|
|
|
/* remove sysfs entries: */
|
2009-02-28 06:41:29 +00:00
|
|
|
if (pod->versionreq_ok)
|
2009-02-28 03:43:04 +00:00
|
|
|
pod_remove_files(pod->firmware_version, pod->line6.properties->device_bit, dev);
|
|
|
|
|
|
|
|
device_remove_file(dev, &dev_attr_channel);
|
|
|
|
device_remove_file(dev, &dev_attr_clip);
|
|
|
|
device_remove_file(dev, &dev_attr_device_id);
|
|
|
|
device_remove_file(dev, &dev_attr_dirty);
|
|
|
|
device_remove_file(dev, &dev_attr_dump);
|
|
|
|
device_remove_file(dev, &dev_attr_dump_buf);
|
|
|
|
device_remove_file(dev, &dev_attr_finish);
|
|
|
|
device_remove_file(dev, &dev_attr_firmware_version);
|
|
|
|
device_remove_file(dev, &dev_attr_midi_postprocess);
|
|
|
|
device_remove_file(dev, &dev_attr_monitor_level);
|
|
|
|
device_remove_file(dev, &dev_attr_name);
|
|
|
|
device_remove_file(dev, &dev_attr_name_buf);
|
|
|
|
device_remove_file(dev, &dev_attr_retrieve_amp_setup);
|
|
|
|
device_remove_file(dev, &dev_attr_retrieve_channel);
|
|
|
|
device_remove_file(dev, &dev_attr_retrieve_effects_setup);
|
|
|
|
device_remove_file(dev, &dev_attr_routing);
|
|
|
|
device_remove_file(dev, &dev_attr_serial_number);
|
|
|
|
device_remove_file(dev, &dev_attr_store_amp_setup);
|
|
|
|
device_remove_file(dev, &dev_attr_store_channel);
|
|
|
|
device_remove_file(dev, &dev_attr_store_effects_setup);
|
|
|
|
device_remove_file(dev, &dev_attr_tuner_freq);
|
|
|
|
device_remove_file(dev, &dev_attr_tuner_mute);
|
|
|
|
device_remove_file(dev, &dev_attr_tuner_note);
|
|
|
|
device_remove_file(dev, &dev_attr_tuner_pitch);
|
|
|
|
|
|
|
|
#if CREATE_RAW_FILE
|
|
|
|
device_remove_file(dev, &dev_attr_raw);
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pod_destruct(interface);
|
|
|
|
}
|