The main reasons are different settings for USB low/high speed and possible different channel counts for in/out; required by POD X3.
This consists of two related parts:
Support for high-speed USB: * USB_INTERVALS_PER_SECOND -> LOW/HIGH settings (high needs 8000, instead of 1000) * LINE6_ISO_BUFFERS -> iso_buffers (count of iso buffers depends on USB speed, 2 is not enough for high speed)
Support for assymetrical in/out configurations: * bytes_per_frame -> bytes_per_channel * max_packet_size -> max_packet_size_in/out
Signed-off-by: Andrej Krutak dev@andree.sk --- sound/usb/line6/capture.c | 45 ++++++++++++++++++++++++++++++-------------- sound/usb/line6/driver.c | 15 ++++++++++----- sound/usb/line6/driver.h | 19 +++++++++++++++---- sound/usb/line6/pcm.c | 47 ++++++++++++++++++++++++++++------------------ sound/usb/line6/pcm.h | 15 +++++++-------- sound/usb/line6/playback.c | 37 ++++++++++++++++++++++++------------ sound/usb/line6/pod.c | 3 +-- sound/usb/line6/podhd.c | 4 +--- sound/usb/line6/toneport.c | 2 +- 9 files changed, 120 insertions(+), 67 deletions(-)
diff --git a/sound/usb/line6/capture.c b/sound/usb/line6/capture.c index f518fbb..91d1562 100644 --- a/sound/usb/line6/capture.c +++ b/sound/usb/line6/capture.c @@ -29,10 +29,10 @@ static int submit_audio_in_urb(struct snd_line6_pcm *line6pcm) int ret; struct urb *urb_in;
- index = - find_first_zero_bit(&line6pcm->in.active_urbs, LINE6_ISO_BUFFERS); + index = find_first_zero_bit( + &line6pcm->in.active_urbs, line6pcm->line6->iso_buffers);
- if (index < 0 || index >= LINE6_ISO_BUFFERS) { + if (index < 0 || index >= line6pcm->line6->iso_buffers) { dev_err(line6pcm->line6->ifcdev, "no free URB found\n"); return -EINVAL; } @@ -44,13 +44,13 @@ static int submit_audio_in_urb(struct snd_line6_pcm *line6pcm) struct usb_iso_packet_descriptor *fin = &urb_in->iso_frame_desc[i]; fin->offset = urb_size; - fin->length = line6pcm->max_packet_size; - urb_size += line6pcm->max_packet_size; + 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; + index * LINE6_ISO_PACKETS * line6pcm->max_packet_size_in; urb_in->transfer_buffer_length = urb_size; urb_in->context = line6pcm;
@@ -73,7 +73,7 @@ int line6_submit_audio_in_all_urbs(struct snd_line6_pcm *line6pcm) { int ret = 0, i;
- for (i = 0; i < LINE6_ISO_BUFFERS; ++i) { + for (i = 0; i < line6pcm->line6->iso_buffers; ++i) { ret = submit_audio_in_urb(line6pcm); if (ret < 0) break; @@ -90,7 +90,9 @@ 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_frame; + 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) @@ -154,7 +156,7 @@ static void audio_in_callback(struct urb *urb) line6pcm->in.last_frame = urb->start_frame;
/* find index of URB */ - for (index = 0; index < LINE6_ISO_BUFFERS; ++index) + for (index = 0; index < line6pcm->line6->iso_buffers; ++index) if (urb == line6pcm->in.urbs[index]) break;
@@ -173,17 +175,27 @@ static void audio_in_callback(struct urb *urb) fbuf = urb->transfer_buffer + fin->offset; fsize = fin->actual_length;
- if (fsize > line6pcm->max_packet_size) { + 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); + fsize, line6pcm->max_packet_size_in); }
length += fsize;
- /* the following assumes LINE6_ISO_PACKETS == 1: */ + 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->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) && @@ -247,8 +259,13 @@ int line6_create_audio_in_urbs(struct snd_line6_pcm *line6pcm) struct usb_line6 *line6 = line6pcm->line6; int i;
+ line6pcm->in.urbs = kzalloc( + sizeof(struct urb *) * line6->iso_buffers, GFP_KERNEL); + if (line6pcm->in.urbs == NULL) + return -ENOMEM; + /* create audio URBs and fill in constant values: */ - for (i = 0; i < LINE6_ISO_BUFFERS; ++i) { + for (i = 0; i < line6->iso_buffers; ++i) { struct urb *urb;
/* URB for audio in: */ diff --git a/sound/usb/line6/driver.c b/sound/usb/line6/driver.c index 81b7da8..efeb16a8 100644 --- a/sound/usb/line6/driver.c +++ b/sound/usb/line6/driver.c @@ -462,13 +462,17 @@ static void line6_destruct(struct snd_card *card) static void line6_get_interval(struct usb_line6 *line6) { struct usb_device *usbdev = line6->usbdev; - struct usb_host_endpoint *ep; - unsigned pipe = usb_rcvintpipe(usbdev, line6->properties->ep_ctrl_r); - unsigned epnum = usb_pipeendpoint(pipe); - - ep = usbdev->ep_in[epnum]; + struct usb_host_endpoint *ep = usbdev->ep_in[line6->properties->ep_ctrl_r]; if (ep) { line6->interval = ep->desc.bInterval; + 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; + } + line6->max_packet_size = le16_to_cpu(ep->desc.wMaxPacketSize); } else { dev_err(line6->ifcdev, @@ -558,6 +562,7 @@ int line6_probe(struct usb_interface *interface, /* 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) { diff --git a/sound/usb/line6/driver.h b/sound/usb/line6/driver.h index 7da643e..2d32139 100644 --- a/sound/usb/line6/driver.h +++ b/sound/usb/line6/driver.h @@ -18,14 +18,20 @@
#include "midi.h"
-#define USB_INTERVALS_PER_SECOND 1000 +/* 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 1 -#define LINE6_BUFSIZE_LISTEN 32 +#define LINE6_BUFSIZE_LISTEN 64 #define LINE6_MESSAGE_MAXLEN 256
/* @@ -109,10 +115,15 @@ struct usb_line6 { /* Properties */ const struct line6_properties *properties;
- /* Interval (ms) */ + /* 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 USB packet */ + /* Maximum size of data USB packet */ int max_packet_size;
/* Device representing the USB interface */ diff --git a/sound/usb/line6/pcm.c b/sound/usb/line6/pcm.c index 41aa335..e1913d3 100644 --- a/sound/usb/line6/pcm.c +++ b/sound/usb/line6/pcm.c @@ -104,7 +104,7 @@ static void line6_unlink_audio_urbs(struct snd_line6_pcm *line6pcm, { int i;
- for (i = 0; i < LINE6_ISO_BUFFERS; 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]); @@ -124,7 +124,7 @@ static void line6_wait_clear_audio_urbs(struct snd_line6_pcm *line6pcm,
do { alive = 0; - for (i = 0; i < LINE6_ISO_BUFFERS; i++) { + for (i = 0; i < line6pcm->line6->iso_buffers; i++) { if (test_bit(i, &pcms->active_urbs)) alive++; } @@ -146,15 +146,20 @@ get_stream(struct snd_line6_pcm *line6pcm, int direction) }
/* allocate a buffer if not opened yet; - * call this in line6pcm.state_change mutex + * call this in line6pcm.state_mutex */ static int line6_buffer_acquire(struct snd_line6_pcm *line6pcm, - struct line6_pcm_stream *pstr, int type) + 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(LINE6_ISO_BUFFERS * LINE6_ISO_PACKETS * - line6pcm->max_packet_size, GFP_KERNEL); + pstr->buffer = kmalloc(line6pcm->line6->iso_buffers * + LINE6_ISO_PACKETS * pkt_size, GFP_KERNEL); if (!pstr->buffer) return -ENOMEM; } @@ -162,12 +167,11 @@ static int line6_buffer_acquire(struct snd_line6_pcm *line6pcm, }
/* free a buffer if all streams are closed; - * call this in line6pcm.state_change mutex + * 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); @@ -194,6 +198,7 @@ static int line6_stream_start(struct snd_line6_pcm *line6pcm, int direction, else ret = line6_submit_audio_in_all_urbs(line6pcm); } + if (ret < 0) clear_bit(type, &pstr->running); spin_unlock_irqrestore(&pstr->lock, flags); @@ -434,24 +439,30 @@ static struct snd_kcontrol_new line6_controls[] = { /* Cleanup the PCM device. */ -static void cleanup_urbs(struct line6_pcm_stream *pcms) +static void cleanup_urbs(struct line6_pcm_stream *pcms, int iso_buffers) { int i;
- for (i = 0; i < LINE6_ISO_BUFFERS; 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); - cleanup_urbs(&line6pcm->in); + cleanup_urbs(&line6pcm->out, line6pcm->line6->iso_buffers); + cleanup_urbs(&line6pcm->in, line6pcm->line6->iso_buffers); kfree(line6pcm); }
@@ -523,12 +534,12 @@ int line6_init_pcm(struct usb_line6 *line6, line6pcm->volume_monitor = 255; line6pcm->line6 = line6;
- /* Read and write buffers are sized identically, so choose minimum */ - line6pcm->max_packet_size = min( - usb_maxpacket(line6->usbdev, - usb_rcvisocpipe(line6->usbdev, ep_read), 0), - usb_maxpacket(line6->usbdev, - usb_sndisocpipe(line6->usbdev, ep_write), 1)); + line6pcm->max_packet_size_in = + usb_maxpacket(line6->usbdev, + usb_rcvisocpipe(line6->usbdev, ep_read), 0); + line6pcm->max_packet_size_out = + usb_maxpacket(line6->usbdev, + usb_sndisocpipe(line6->usbdev, ep_write), 1);
spin_lock_init(&line6pcm->out.lock); spin_lock_init(&line6pcm->in.lock); diff --git a/sound/usb/line6/pcm.h b/sound/usb/line6/pcm.h index 508410a..58d36f9 100644 --- a/sound/usb/line6/pcm.h +++ b/sound/usb/line6/pcm.h @@ -20,9 +20,6 @@
#include "driver.h"
-/* number of URBs */ -#define LINE6_ISO_BUFFERS 2 - /* number of USB frames per URB The Line 6 Windows driver always transmits two frames per packet, but @@ -31,7 +28,8 @@ */ #define LINE6_ISO_PACKETS 1
-/* in a "full speed" device (such as the PODxt Pro) this means 1ms */ +/* 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 @@ -85,12 +83,12 @@ enum { struct line6_pcm_properties { struct snd_pcm_hardware playback_hw, capture_hw; struct snd_pcm_hw_constraint_ratdens rates; - int bytes_per_frame; + int bytes_per_channel; };
struct line6_pcm_stream { /* allocated URBs */ - struct urb *urbs[LINE6_ISO_BUFFERS]; + struct urb **urbs;
/* Temporary buffer; * Since the packet size is not known in advance, this buffer is @@ -157,11 +155,12 @@ struct snd_line6_pcm { /* Previously captured frame (for software monitoring) */ unsigned char *prev_fbuf;
- /* Size of previously captured frame (for software monitoring) */ + /* Size of previously captured frame (for software monitoring/sync) */ int prev_fsize;
/* Maximum size of USB packet */ - int max_packet_size; + int max_packet_size_in; + int max_packet_size_out;
/* PCM playback volume (left and right) */ int volume_playback[2]; diff --git a/sound/usb/line6/playback.c b/sound/usb/line6/playback.c index 97ed593..4833c51 100644 --- a/sound/usb/line6/playback.c +++ b/sound/usb/line6/playback.c @@ -146,18 +146,20 @@ 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_frame; + 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 * - (USB_INTERVALS_PER_SECOND / LINE6_ISO_INTERVAL); + (line6pcm->line6->intervals_per_second / LINE6_ISO_INTERVAL); struct urb *urb_out;
- index = - find_first_zero_bit(&line6pcm->out.active_urbs, LINE6_ISO_BUFFERS); + index = find_first_zero_bit( + &line6pcm->out.active_urbs, line6pcm->line6->iso_buffers);
- if (index < 0 || index >= LINE6_ISO_BUFFERS) { + if (index < 0 || index >= line6pcm->line6->iso_buffers) { dev_err(line6pcm->line6->ifcdev, "no free URB found\n"); return -EINVAL; } @@ -165,6 +167,7 @@ static int submit_audio_out_urb(struct snd_line6_pcm *line6pcm) 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; @@ -178,9 +181,11 @@ static int submit_audio_out_urb(struct snd_line6_pcm *line6pcm) line6pcm->out.count += frame_increment; n = line6pcm->out.count / frame_factor; line6pcm->out.count -= n * frame_factor; - fsize = n * bytes_per_frame; + fsize = n; }
+ fsize *= bytes_per_frame; + fout->offset = urb_size; fout->length = fsize; urb_size += fsize; @@ -195,7 +200,7 @@ static int submit_audio_out_urb(struct snd_line6_pcm *line6pcm) urb_frames = urb_size / bytes_per_frame; urb_out->transfer_buffer = line6pcm->out.buffer + - index * LINE6_ISO_PACKETS * line6pcm->max_packet_size; + index * LINE6_ISO_PACKETS * line6pcm->max_packet_size_out; urb_out->transfer_buffer_length = urb_size; urb_out->context = line6pcm;
@@ -286,7 +291,7 @@ int line6_submit_audio_out_all_urbs(struct snd_line6_pcm *line6pcm) { int ret = 0, i;
- for (i = 0; i < LINE6_ISO_BUFFERS; ++i) { + for (i = 0; i < line6pcm->line6->iso_buffers; ++i) { ret = submit_audio_out_urb(line6pcm); if (ret < 0) break; @@ -305,6 +310,9 @@ static void audio_out_callback(struct urb *urb) 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); @@ -313,11 +321,11 @@ static void audio_out_callback(struct urb *urb) line6pcm->out.last_frame = urb->start_frame;
/* find index of URB */ - for (index = 0; index < LINE6_ISO_BUFFERS; index++) + for (index = 0; index < line6pcm->line6->iso_buffers; index++) if (urb == line6pcm->out.urbs[index]) break;
- if (index >= LINE6_ISO_BUFFERS) + if (index >= line6pcm->line6->iso_buffers) return; /* URB has been unlinked asynchronously */
for (i = 0; i < LINE6_ISO_PACKETS; i++) @@ -329,7 +337,7 @@ static void audio_out_callback(struct urb *urb) struct snd_pcm_runtime *runtime = substream->runtime;
line6pcm->out.pos_done += - length / line6pcm->properties->bytes_per_frame; + length / bytes_per_frame;
if (line6pcm->out.pos_done >= runtime->buffer_size) line6pcm->out.pos_done -= runtime->buffer_size; @@ -401,8 +409,13 @@ int line6_create_audio_out_urbs(struct snd_line6_pcm *line6pcm) struct usb_line6 *line6 = line6pcm->line6; int i;
+ line6pcm->out.urbs = kzalloc( + sizeof(struct urb *) * line6->iso_buffers, GFP_KERNEL); + if (line6pcm->out.urbs == NULL) + return -ENOMEM; + /* create audio URBs and fill in constant values: */ - for (i = 0; i < LINE6_ISO_BUFFERS; ++i) { + for (i = 0; i < line6->iso_buffers; ++i) { struct urb *urb;
/* URB for audio out: */ diff --git a/sound/usb/line6/pod.c b/sound/usb/line6/pod.c index 45dd348..36e7274 100644 --- a/sound/usb/line6/pod.c +++ b/sound/usb/line6/pod.c @@ -83,7 +83,6 @@ struct usb_line6_pod { };
#define POD_SYSEX_CODE 3 -#define POD_BYTES_PER_FRAME 6 /* 24bit audio (stereo) */
/* *INDENT-OFF* */
@@ -167,7 +166,7 @@ static struct line6_pcm_properties pod_pcm_properties = { .rates = { .nrats = 1, .rats = &pod_ratden}, - .bytes_per_frame = POD_BYTES_PER_FRAME + .bytes_per_channel = 3 /* SNDRV_PCM_FMTBIT_S24_3LE */ };
static const char pod_version_header[] = { diff --git a/sound/usb/line6/podhd.c b/sound/usb/line6/podhd.c index 63dcaef..4fc4789 100644 --- a/sound/usb/line6/podhd.c +++ b/sound/usb/line6/podhd.c @@ -25,8 +25,6 @@ enum { LINE6_PODHD500_1, };
-#define PODHD_BYTES_PER_FRAME 6 /* 24bit audio (stereo) */ - static struct snd_ratden podhd_ratden = { .num_min = 48000, .num_max = 48000, @@ -73,7 +71,7 @@ static struct line6_pcm_properties podhd_pcm_properties = { .rates = { .nrats = 1, .rats = &podhd_ratden}, - .bytes_per_frame = PODHD_BYTES_PER_FRAME + .bytes_per_channel = 3 /* 24bit audio (stereo) */ };
/* diff --git a/sound/usb/line6/toneport.c b/sound/usb/line6/toneport.c index 6d4c50c..da76e03 100644 --- a/sound/usb/line6/toneport.c +++ b/sound/usb/line6/toneport.c @@ -114,7 +114,7 @@ static struct line6_pcm_properties toneport_pcm_properties = { .rates = { .nrats = 1, .rats = &toneport_ratden}, - .bytes_per_frame = 4 + .bytes_per_channel = 2 };
static const struct {