kernel-fxtec-pro1x/sound/usb/caiaq/audio.c
Daniel Mack a9b487fa1e ALSA: snd-usb-caiaq: fix reported elapsed periods
Reset the internal period position counter upon stream startup. This
fixes initial aplay underruns and problems related to latency picky
applications such as pulseaudio.

Bumped the version number to 1.3.14.

Signed-off-by: Daniel Mack <daniel@caiaq.de>
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2009-04-27 12:32:52 +02:00

702 lines
18 KiB
C

/*
* Copyright (c) 2006-2008 Daniel Mack, Karsten Wiese
*
* 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; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <linux/spinlock.h>
#include <linux/init.h>
#include <linux/usb.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include "device.h"
#include "audio.h"
#define N_URBS 32
#define CLOCK_DRIFT_TOLERANCE 5
#define FRAMES_PER_URB 8
#define BYTES_PER_FRAME 512
#define CHANNELS_PER_STREAM 2
#define BYTES_PER_SAMPLE 3
#define BYTES_PER_SAMPLE_USB 4
#define MAX_BUFFER_SIZE (128*1024)
#define MAX_ENDPOINT_SIZE 512
#define ENDPOINT_CAPTURE 2
#define ENDPOINT_PLAYBACK 6
#define MAKE_CHECKBYTE(dev,stream,i) \
(stream << 1) | (~(i / (dev->n_streams * BYTES_PER_SAMPLE_USB)) & 1)
static struct snd_pcm_hardware snd_usb_caiaq_pcm_hardware = {
.info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_BLOCK_TRANSFER),
.formats = SNDRV_PCM_FMTBIT_S24_3BE,
.rates = (SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 |
SNDRV_PCM_RATE_96000),
.rate_min = 44100,
.rate_max = 0, /* will overwrite later */
.channels_min = CHANNELS_PER_STREAM,
.channels_max = CHANNELS_PER_STREAM,
.buffer_bytes_max = MAX_BUFFER_SIZE,
.period_bytes_min = 128,
.period_bytes_max = MAX_BUFFER_SIZE,
.periods_min = 1,
.periods_max = 1024,
};
static void
activate_substream(struct snd_usb_caiaqdev *dev,
struct snd_pcm_substream *sub)
{
if (sub->stream == SNDRV_PCM_STREAM_PLAYBACK)
dev->sub_playback[sub->number] = sub;
else
dev->sub_capture[sub->number] = sub;
}
static void
deactivate_substream(struct snd_usb_caiaqdev *dev,
struct snd_pcm_substream *sub)
{
unsigned long flags;
spin_lock_irqsave(&dev->spinlock, flags);
if (sub->stream == SNDRV_PCM_STREAM_PLAYBACK)
dev->sub_playback[sub->number] = NULL;
else
dev->sub_capture[sub->number] = NULL;
spin_unlock_irqrestore(&dev->spinlock, flags);
}
static int
all_substreams_zero(struct snd_pcm_substream **subs)
{
int i;
for (i = 0; i < MAX_STREAMS; i++)
if (subs[i] != NULL)
return 0;
return 1;
}
static int stream_start(struct snd_usb_caiaqdev *dev)
{
int i, ret;
debug("%s(%p)\n", __func__, dev);
if (dev->streaming)
return -EINVAL;
memset(dev->sub_playback, 0, sizeof(dev->sub_playback));
memset(dev->sub_capture, 0, sizeof(dev->sub_capture));
dev->input_panic = 0;
dev->output_panic = 0;
dev->first_packet = 1;
dev->streaming = 1;
dev->warned = 0;
for (i = 0; i < N_URBS; i++) {
ret = usb_submit_urb(dev->data_urbs_in[i], GFP_ATOMIC);
if (ret) {
log("unable to trigger read #%d! (ret %d)\n", i, ret);
dev->streaming = 0;
return -EPIPE;
}
}
return 0;
}
static void stream_stop(struct snd_usb_caiaqdev *dev)
{
int i;
debug("%s(%p)\n", __func__, dev);
if (!dev->streaming)
return;
dev->streaming = 0;
for (i = 0; i < N_URBS; i++) {
usb_kill_urb(dev->data_urbs_in[i]);
usb_kill_urb(dev->data_urbs_out[i]);
}
}
static int snd_usb_caiaq_substream_open(struct snd_pcm_substream *substream)
{
struct snd_usb_caiaqdev *dev = snd_pcm_substream_chip(substream);
debug("%s(%p)\n", __func__, substream);
substream->runtime->hw = dev->pcm_info;
snd_pcm_limit_hw_rates(substream->runtime);
return 0;
}
static int snd_usb_caiaq_substream_close(struct snd_pcm_substream *substream)
{
struct snd_usb_caiaqdev *dev = snd_pcm_substream_chip(substream);
debug("%s(%p)\n", __func__, substream);
if (all_substreams_zero(dev->sub_playback) &&
all_substreams_zero(dev->sub_capture)) {
/* when the last client has stopped streaming,
* all sample rates are allowed again */
stream_stop(dev);
dev->pcm_info.rates = dev->samplerates;
}
return 0;
}
static int snd_usb_caiaq_pcm_hw_params(struct snd_pcm_substream *sub,
struct snd_pcm_hw_params *hw_params)
{
debug("%s(%p)\n", __func__, sub);
return snd_pcm_lib_malloc_pages(sub, params_buffer_bytes(hw_params));
}
static int snd_usb_caiaq_pcm_hw_free(struct snd_pcm_substream *sub)
{
struct snd_usb_caiaqdev *dev = snd_pcm_substream_chip(sub);
debug("%s(%p)\n", __func__, sub);
deactivate_substream(dev, sub);
return snd_pcm_lib_free_pages(sub);
}
/* this should probably go upstream */
#if SNDRV_PCM_RATE_5512 != 1 << 0 || SNDRV_PCM_RATE_192000 != 1 << 12
#error "Change this table"
#endif
static unsigned int rates[] = { 5512, 8000, 11025, 16000, 22050, 32000, 44100,
48000, 64000, 88200, 96000, 176400, 192000 };
static int snd_usb_caiaq_pcm_prepare(struct snd_pcm_substream *substream)
{
int bytes_per_sample, bpp, ret, i;
int index = substream->number;
struct snd_usb_caiaqdev *dev = snd_pcm_substream_chip(substream);
struct snd_pcm_runtime *runtime = substream->runtime;
debug("%s(%p)\n", __func__, substream);
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
dev->period_out_count[index] = BYTES_PER_SAMPLE + 1;
dev->audio_out_buf_pos[index] = BYTES_PER_SAMPLE + 1;
} else {
dev->period_in_count[index] = BYTES_PER_SAMPLE;
dev->audio_in_buf_pos[index] = BYTES_PER_SAMPLE;
}
if (dev->streaming)
return 0;
/* the first client that opens a stream defines the sample rate
* setting for all subsequent calls, until the last client closed. */
for (i=0; i < ARRAY_SIZE(rates); i++)
if (runtime->rate == rates[i])
dev->pcm_info.rates = 1 << i;
snd_pcm_limit_hw_rates(runtime);
bytes_per_sample = BYTES_PER_SAMPLE;
if (dev->spec.data_alignment == 2)
bytes_per_sample++;
bpp = ((runtime->rate / 8000) + CLOCK_DRIFT_TOLERANCE)
* bytes_per_sample * CHANNELS_PER_STREAM * dev->n_streams;
if (bpp > MAX_ENDPOINT_SIZE)
bpp = MAX_ENDPOINT_SIZE;
ret = snd_usb_caiaq_set_audio_params(dev, runtime->rate,
runtime->sample_bits, bpp);
if (ret)
return ret;
ret = stream_start(dev);
if (ret)
return ret;
dev->output_running = 0;
wait_event_timeout(dev->prepare_wait_queue, dev->output_running, HZ);
if (!dev->output_running) {
stream_stop(dev);
return -EPIPE;
}
return 0;
}
static int snd_usb_caiaq_pcm_trigger(struct snd_pcm_substream *sub, int cmd)
{
struct snd_usb_caiaqdev *dev = snd_pcm_substream_chip(sub);
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
activate_substream(dev, sub);
break;
case SNDRV_PCM_TRIGGER_STOP:
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
deactivate_substream(dev, sub);
break;
default:
return -EINVAL;
}
return 0;
}
static snd_pcm_uframes_t
snd_usb_caiaq_pcm_pointer(struct snd_pcm_substream *sub)
{
int index = sub->number;
struct snd_usb_caiaqdev *dev = snd_pcm_substream_chip(sub);
if (dev->input_panic || dev->output_panic)
return SNDRV_PCM_POS_XRUN;
if (sub->stream == SNDRV_PCM_STREAM_PLAYBACK)
return bytes_to_frames(sub->runtime,
dev->audio_out_buf_pos[index]);
else
return bytes_to_frames(sub->runtime,
dev->audio_in_buf_pos[index]);
}
/* operators for both playback and capture */
static struct snd_pcm_ops snd_usb_caiaq_ops = {
.open = snd_usb_caiaq_substream_open,
.close = snd_usb_caiaq_substream_close,
.ioctl = snd_pcm_lib_ioctl,
.hw_params = snd_usb_caiaq_pcm_hw_params,
.hw_free = snd_usb_caiaq_pcm_hw_free,
.prepare = snd_usb_caiaq_pcm_prepare,
.trigger = snd_usb_caiaq_pcm_trigger,
.pointer = snd_usb_caiaq_pcm_pointer
};
static void check_for_elapsed_periods(struct snd_usb_caiaqdev *dev,
struct snd_pcm_substream **subs)
{
int stream, pb, *cnt;
struct snd_pcm_substream *sub;
for (stream = 0; stream < dev->n_streams; stream++) {
sub = subs[stream];
if (!sub)
continue;
pb = snd_pcm_lib_period_bytes(sub);
cnt = (sub->stream == SNDRV_PCM_STREAM_PLAYBACK) ?
&dev->period_out_count[stream] :
&dev->period_in_count[stream];
if (*cnt >= pb) {
snd_pcm_period_elapsed(sub);
*cnt %= pb;
}
}
}
static void read_in_urb_mode0(struct snd_usb_caiaqdev *dev,
const struct urb *urb,
const struct usb_iso_packet_descriptor *iso)
{
unsigned char *usb_buf = urb->transfer_buffer + iso->offset;
struct snd_pcm_substream *sub;
int stream, i;
if (all_substreams_zero(dev->sub_capture))
return;
for (i = 0; i < iso->actual_length;) {
for (stream = 0; stream < dev->n_streams; stream++, i++) {
sub = dev->sub_capture[stream];
if (sub) {
struct snd_pcm_runtime *rt = sub->runtime;
char *audio_buf = rt->dma_area;
int sz = frames_to_bytes(rt, rt->buffer_size);
audio_buf[dev->audio_in_buf_pos[stream]++]
= usb_buf[i];
dev->period_in_count[stream]++;
if (dev->audio_in_buf_pos[stream] == sz)
dev->audio_in_buf_pos[stream] = 0;
}
}
}
}
static void read_in_urb_mode2(struct snd_usb_caiaqdev *dev,
const struct urb *urb,
const struct usb_iso_packet_descriptor *iso)
{
unsigned char *usb_buf = urb->transfer_buffer + iso->offset;
unsigned char check_byte;
struct snd_pcm_substream *sub;
int stream, i;
for (i = 0; i < iso->actual_length;) {
if (i % (dev->n_streams * BYTES_PER_SAMPLE_USB) == 0) {
for (stream = 0;
stream < dev->n_streams;
stream++, i++) {
if (dev->first_packet)
continue;
check_byte = MAKE_CHECKBYTE(dev, stream, i);
if ((usb_buf[i] & 0x3f) != check_byte)
dev->input_panic = 1;
if (usb_buf[i] & 0x80)
dev->output_panic = 1;
}
}
dev->first_packet = 0;
for (stream = 0; stream < dev->n_streams; stream++, i++) {
sub = dev->sub_capture[stream];
if (dev->input_panic)
usb_buf[i] = 0;
if (sub) {
struct snd_pcm_runtime *rt = sub->runtime;
char *audio_buf = rt->dma_area;
int sz = frames_to_bytes(rt, rt->buffer_size);
audio_buf[dev->audio_in_buf_pos[stream]++] =
usb_buf[i];
dev->period_in_count[stream]++;
if (dev->audio_in_buf_pos[stream] == sz)
dev->audio_in_buf_pos[stream] = 0;
}
}
}
}
static void read_in_urb(struct snd_usb_caiaqdev *dev,
const struct urb *urb,
const struct usb_iso_packet_descriptor *iso)
{
if (!dev->streaming)
return;
if (iso->actual_length < dev->bpp)
return;
switch (dev->spec.data_alignment) {
case 0:
read_in_urb_mode0(dev, urb, iso);
break;
case 2:
read_in_urb_mode2(dev, urb, iso);
break;
}
if ((dev->input_panic || dev->output_panic) && !dev->warned) {
debug("streaming error detected %s %s\n",
dev->input_panic ? "(input)" : "",
dev->output_panic ? "(output)" : "");
dev->warned = 1;
}
}
static void fill_out_urb(struct snd_usb_caiaqdev *dev,
struct urb *urb,
const struct usb_iso_packet_descriptor *iso)
{
unsigned char *usb_buf = urb->transfer_buffer + iso->offset;
struct snd_pcm_substream *sub;
int stream, i;
for (i = 0; i < iso->length;) {
for (stream = 0; stream < dev->n_streams; stream++, i++) {
sub = dev->sub_playback[stream];
if (sub) {
struct snd_pcm_runtime *rt = sub->runtime;
char *audio_buf = rt->dma_area;
int sz = frames_to_bytes(rt, rt->buffer_size);
usb_buf[i] =
audio_buf[dev->audio_out_buf_pos[stream]];
dev->period_out_count[stream]++;
dev->audio_out_buf_pos[stream]++;
if (dev->audio_out_buf_pos[stream] == sz)
dev->audio_out_buf_pos[stream] = 0;
} else
usb_buf[i] = 0;
}
/* fill in the check bytes */
if (dev->spec.data_alignment == 2 &&
i % (dev->n_streams * BYTES_PER_SAMPLE_USB) ==
(dev->n_streams * CHANNELS_PER_STREAM))
for (stream = 0; stream < dev->n_streams; stream++, i++)
usb_buf[i] = MAKE_CHECKBYTE(dev, stream, i);
}
}
static void read_completed(struct urb *urb)
{
struct snd_usb_caiaq_cb_info *info = urb->context;
struct snd_usb_caiaqdev *dev;
struct urb *out;
int frame, len, send_it = 0, outframe = 0;
if (urb->status || !info)
return;
dev = info->dev;
if (!dev->streaming)
return;
out = dev->data_urbs_out[info->index];
/* read the recently received packet and send back one which has
* the same layout */
for (frame = 0; frame < FRAMES_PER_URB; frame++) {
if (urb->iso_frame_desc[frame].status)
continue;
len = urb->iso_frame_desc[outframe].actual_length;
out->iso_frame_desc[outframe].length = len;
out->iso_frame_desc[outframe].actual_length = 0;
out->iso_frame_desc[outframe].offset = BYTES_PER_FRAME * frame;
if (len > 0) {
spin_lock(&dev->spinlock);
fill_out_urb(dev, out, &out->iso_frame_desc[outframe]);
read_in_urb(dev, urb, &urb->iso_frame_desc[frame]);
spin_unlock(&dev->spinlock);
check_for_elapsed_periods(dev, dev->sub_playback);
check_for_elapsed_periods(dev, dev->sub_capture);
send_it = 1;
}
outframe++;
}
if (send_it) {
out->number_of_packets = FRAMES_PER_URB;
out->transfer_flags = URB_ISO_ASAP;
usb_submit_urb(out, GFP_ATOMIC);
}
/* re-submit inbound urb */
for (frame = 0; frame < FRAMES_PER_URB; frame++) {
urb->iso_frame_desc[frame].offset = BYTES_PER_FRAME * frame;
urb->iso_frame_desc[frame].length = BYTES_PER_FRAME;
urb->iso_frame_desc[frame].actual_length = 0;
}
urb->number_of_packets = FRAMES_PER_URB;
urb->transfer_flags = URB_ISO_ASAP;
usb_submit_urb(urb, GFP_ATOMIC);
}
static void write_completed(struct urb *urb)
{
struct snd_usb_caiaq_cb_info *info = urb->context;
struct snd_usb_caiaqdev *dev = info->dev;
if (!dev->output_running) {
dev->output_running = 1;
wake_up(&dev->prepare_wait_queue);
}
}
static struct urb **alloc_urbs(struct snd_usb_caiaqdev *dev, int dir, int *ret)
{
int i, frame;
struct urb **urbs;
struct usb_device *usb_dev = dev->chip.dev;
unsigned int pipe;
pipe = (dir == SNDRV_PCM_STREAM_PLAYBACK) ?
usb_sndisocpipe(usb_dev, ENDPOINT_PLAYBACK) :
usb_rcvisocpipe(usb_dev, ENDPOINT_CAPTURE);
urbs = kmalloc(N_URBS * sizeof(*urbs), GFP_KERNEL);
if (!urbs) {
log("unable to kmalloc() urbs, OOM!?\n");
*ret = -ENOMEM;
return NULL;
}
for (i = 0; i < N_URBS; i++) {
urbs[i] = usb_alloc_urb(FRAMES_PER_URB, GFP_KERNEL);
if (!urbs[i]) {
log("unable to usb_alloc_urb(), OOM!?\n");
*ret = -ENOMEM;
return urbs;
}
urbs[i]->transfer_buffer =
kmalloc(FRAMES_PER_URB * BYTES_PER_FRAME, GFP_KERNEL);
if (!urbs[i]->transfer_buffer) {
log("unable to kmalloc() transfer buffer, OOM!?\n");
*ret = -ENOMEM;
return urbs;
}
for (frame = 0; frame < FRAMES_PER_URB; frame++) {
struct usb_iso_packet_descriptor *iso =
&urbs[i]->iso_frame_desc[frame];
iso->offset = BYTES_PER_FRAME * frame;
iso->length = BYTES_PER_FRAME;
}
urbs[i]->dev = usb_dev;
urbs[i]->pipe = pipe;
urbs[i]->transfer_buffer_length = FRAMES_PER_URB
* BYTES_PER_FRAME;
urbs[i]->context = &dev->data_cb_info[i];
urbs[i]->interval = 1;
urbs[i]->transfer_flags = URB_ISO_ASAP;
urbs[i]->number_of_packets = FRAMES_PER_URB;
urbs[i]->complete = (dir == SNDRV_PCM_STREAM_CAPTURE) ?
read_completed : write_completed;
}
*ret = 0;
return urbs;
}
static void free_urbs(struct urb **urbs)
{
int i;
if (!urbs)
return;
for (i = 0; i < N_URBS; i++) {
if (!urbs[i])
continue;
usb_kill_urb(urbs[i]);
kfree(urbs[i]->transfer_buffer);
usb_free_urb(urbs[i]);
}
kfree(urbs);
}
int snd_usb_caiaq_audio_init(struct snd_usb_caiaqdev *dev)
{
int i, ret;
dev->n_audio_in = max(dev->spec.num_analog_audio_in,
dev->spec.num_digital_audio_in) /
CHANNELS_PER_STREAM;
dev->n_audio_out = max(dev->spec.num_analog_audio_out,
dev->spec.num_digital_audio_out) /
CHANNELS_PER_STREAM;
dev->n_streams = max(dev->n_audio_in, dev->n_audio_out);
debug("dev->n_audio_in = %d\n", dev->n_audio_in);
debug("dev->n_audio_out = %d\n", dev->n_audio_out);
debug("dev->n_streams = %d\n", dev->n_streams);
if (dev->n_streams > MAX_STREAMS) {
log("unable to initialize device, too many streams.\n");
return -EINVAL;
}
ret = snd_pcm_new(dev->chip.card, dev->product_name, 0,
dev->n_audio_out, dev->n_audio_in, &dev->pcm);
if (ret < 0) {
log("snd_pcm_new() returned %d\n", ret);
return ret;
}
dev->pcm->private_data = dev;
strcpy(dev->pcm->name, dev->product_name);
memset(dev->sub_playback, 0, sizeof(dev->sub_playback));
memset(dev->sub_capture, 0, sizeof(dev->sub_capture));
memcpy(&dev->pcm_info, &snd_usb_caiaq_pcm_hardware,
sizeof(snd_usb_caiaq_pcm_hardware));
/* setup samplerates */
dev->samplerates = dev->pcm_info.rates;
switch (dev->chip.usb_id) {
case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_AK1):
case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_RIGKONTROL3):
case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_SESSIONIO):
case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_GUITARRIGMOBILE):
dev->samplerates |= SNDRV_PCM_RATE_192000;
/* fall thru */
case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_AUDIO4DJ):
case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_AUDIO8DJ):
dev->samplerates |= SNDRV_PCM_RATE_88200;
break;
}
snd_pcm_set_ops(dev->pcm, SNDRV_PCM_STREAM_PLAYBACK,
&snd_usb_caiaq_ops);
snd_pcm_set_ops(dev->pcm, SNDRV_PCM_STREAM_CAPTURE,
&snd_usb_caiaq_ops);
snd_pcm_lib_preallocate_pages_for_all(dev->pcm,
SNDRV_DMA_TYPE_CONTINUOUS,
snd_dma_continuous_data(GFP_KERNEL),
MAX_BUFFER_SIZE, MAX_BUFFER_SIZE);
dev->data_cb_info =
kmalloc(sizeof(struct snd_usb_caiaq_cb_info) * N_URBS,
GFP_KERNEL);
if (!dev->data_cb_info)
return -ENOMEM;
for (i = 0; i < N_URBS; i++) {
dev->data_cb_info[i].dev = dev;
dev->data_cb_info[i].index = i;
}
dev->data_urbs_in = alloc_urbs(dev, SNDRV_PCM_STREAM_CAPTURE, &ret);
if (ret < 0) {
kfree(dev->data_cb_info);
free_urbs(dev->data_urbs_in);
return ret;
}
dev->data_urbs_out = alloc_urbs(dev, SNDRV_PCM_STREAM_PLAYBACK, &ret);
if (ret < 0) {
kfree(dev->data_cb_info);
free_urbs(dev->data_urbs_in);
free_urbs(dev->data_urbs_out);
return ret;
}
return 0;
}
void snd_usb_caiaq_audio_free(struct snd_usb_caiaqdev *dev)
{
debug("%s(%p)\n", __func__, dev);
stream_stop(dev);
free_urbs(dev->data_urbs_in);
free_urbs(dev->data_urbs_out);
kfree(dev->data_cb_info);
}