[alsa-devel] [PATCH 2/3] snd-bcd2000: add playback and capture support
Mario Kicherer
dev at kicherer.org
Sat Jan 31 22:34:24 CET 2015
Signed-off-by: Mario Kicherer <dev at kicherer.org>
---
sound/usb/bcd2000/Makefile | 2 +-
sound/usb/bcd2000/audio.c | 632 ++++++++++++++++++++++++++++++++++++++++++++
sound/usb/bcd2000/audio.h | 58 ++++
sound/usb/bcd2000/bcd2000.c | 6 +
sound/usb/bcd2000/bcd2000.h | 2 +
5 files changed, 699 insertions(+), 1 deletion(-)
create mode 100644 sound/usb/bcd2000/audio.c
create mode 100644 sound/usb/bcd2000/audio.h
diff --git a/sound/usb/bcd2000/Makefile b/sound/usb/bcd2000/Makefile
index bc64a21..d424dff 100644
--- a/sound/usb/bcd2000/Makefile
+++ b/sound/usb/bcd2000/Makefile
@@ -1,3 +1,3 @@
-snd-bcd2000-y := bcd2000.o midi.o
+snd-bcd2000-y := bcd2000.o audio.o midi.o
obj-$(CONFIG_SND_BCD2000) += snd-bcd2000.o
\ No newline at end of file
diff --git a/sound/usb/bcd2000/audio.c b/sound/usb/bcd2000/audio.c
new file mode 100644
index 0000000..26f958a
--- /dev/null
+++ b/sound/usb/bcd2000/audio.c
@@ -0,0 +1,632 @@
+/*
+ * Behringer BCD2000 driver
+ *
+ * Copyright (C) 2014 Mario Kicherer (dev at kicherer.org)
+ *
+ * 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.
+ */
+
+#include <linux/slab.h>
+#include <linux/usb.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+
+#include "audio.h"
+#include "bcd2000.h"
+
+static struct snd_pcm_hardware bcd2000_pcm_hardware = {
+ .info = SNDRV_PCM_INFO_MMAP |
+ SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_BATCH |
+ SNDRV_PCM_INFO_BLOCK_TRANSFER,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ .rates = SNDRV_PCM_RATE_44100,
+ .rate_min = 44100,
+ .rate_max = 44100,
+ .channels_min = 4,
+ .channels_max = 4,
+ .buffer_bytes_max = ALSA_BUFFER_SIZE,
+ .period_bytes_min = BYTES_PER_PERIOD,
+ .period_bytes_max = ALSA_BUFFER_SIZE,
+ .periods_min = 1,
+ .periods_max = PERIODS_MAX,
+};
+
+enum {
+ STREAM_DISABLED, /* no pcm streaming */
+ STREAM_STARTING, /* pcm streaming requested, waiting to become ready */
+ STREAM_RUNNING, /* pcm streaming running */
+ STREAM_STOPPING
+};
+
+/* copy the audio frames from the URB packets into the ALSA buffer */
+static void bcd2000_pcm_capture(struct bcd2000_substream *sub,
+ struct bcd2000_urb *urb)
+{
+ int i, frame, frame_count, bytes_per_frame;
+ void *src, *dest, *dest_end;
+ struct bcd2000_pcm *rt;
+ struct snd_pcm_runtime *alsa_rt;
+
+ rt = snd_pcm_substream_chip(sub->instance);
+ alsa_rt = sub->instance->runtime;
+
+ dest = (alsa_rt->dma_area + sub->dma_off);
+ dest_end = alsa_rt->dma_area +
+ frames_to_bytes(alsa_rt, alsa_rt->buffer_size);
+
+ bytes_per_frame = alsa_rt->frame_bits / 8;
+ src = urb->buffer;
+
+ for (i = 0; i < USB_N_PACKETS_PER_URB; i++) {
+ frame_count = urb->packets[i].actual_length / bytes_per_frame;
+
+ for (frame = 0; frame < frame_count; frame++) {
+ memcpy(dest, src, bytes_per_frame);
+
+ dest += bytes_per_frame;
+ src += bytes_per_frame;
+ sub->dma_off += bytes_per_frame;
+ sub->period_off += bytes_per_frame;
+
+ if (dest >= dest_end) {
+ sub->dma_off = 0;
+ dest = alsa_rt->dma_area;
+ }
+ }
+
+ /*
+ * if packet was not full, make src point to the
+ * data of the next packet
+ */
+ src += urb->packets[i].length - urb->packets[i].actual_length;
+ }
+}
+
+/* handle incoming URB with captured data */
+static void bcd2000_pcm_in_urb_handler(struct urb *usb_urb)
+{
+ struct bcd2000_urb *bcd2k_urb = usb_urb->context;
+ struct bcd2000_pcm *pcm = &bcd2k_urb->bcd2k->pcm;
+ struct bcd2000_substream *stream = bcd2k_urb->stream;
+ unsigned long flags;
+ int ret, k, period_bytes;
+ struct usb_iso_packet_descriptor *packet;
+
+ if (pcm->panic || stream->state == STREAM_STOPPING)
+ return;
+
+ if (unlikely(usb_urb->status == -ENOENT || /* unlinked */
+ usb_urb->status == -ENODEV || /* device removed */
+ usb_urb->status == -ECONNRESET || /* unlinked */
+ usb_urb->status == -ESHUTDOWN)) /* device disabled */
+ {
+ goto out_fail;
+ }
+
+ if (stream->state == STREAM_STARTING) {
+ stream->wait_cond = true;
+ wake_up(&stream->wait_queue);
+ }
+
+ if (stream->active) {
+ spin_lock_irqsave(&stream->lock, flags);
+
+ /* copy captured data into ALSA buffer */
+ bcd2000_pcm_capture(stream, bcd2k_urb);
+
+ period_bytes = snd_pcm_lib_period_bytes(stream->instance);
+
+ /* do we have enough data for one period? */
+ if (stream->period_off > period_bytes) {
+ stream->period_off %= period_bytes;
+
+ spin_unlock_irqrestore(&stream->lock, flags);
+
+ /*
+ * call this only once even if multiple periods
+ * are ready
+ */
+ snd_pcm_period_elapsed(stream->instance);
+
+ memset(bcd2k_urb->buffer, 0, USB_BUFFER_SIZE);
+ } else {
+ spin_unlock_irqrestore(&stream->lock, flags);
+ memset(bcd2k_urb->buffer, 0, USB_BUFFER_SIZE);
+ }
+ } else {
+ memset(bcd2k_urb->buffer, 0, USB_BUFFER_SIZE);
+ }
+
+ /* reset URB data */
+ for (k = 0; k < USB_N_PACKETS_PER_URB; k++) {
+ packet = &bcd2k_urb->packets[k];
+ packet->offset = k * USB_PACKET_SIZE;
+ packet->length = USB_PACKET_SIZE;
+ packet->actual_length = 0;
+ packet->status = 0;
+ }
+ bcd2k_urb->instance.number_of_packets = USB_N_PACKETS_PER_URB;
+
+ /* send the URB back to the BCD2000 */
+ ret = usb_submit_urb(&bcd2k_urb->instance, GFP_ATOMIC);
+ if (ret < 0)
+ goto out_fail;
+
+ return;
+
+out_fail:
+ dev_info(&bcd2k_urb->bcd2k->dev->dev, PREFIX "error in in_urb handler");
+ pcm->panic = true;
+}
+
+/* copy audio frame from ALSA buffer into the URB packet */
+static void bcd2000_pcm_playback(struct bcd2000_substream *sub,
+ struct bcd2000_urb *urb)
+{
+ int i, frame, frame_count, bytes_per_frame;
+ void *src, *src_end, *dest;
+ struct bcd2000_pcm *rt;
+ struct snd_pcm_runtime *alsa_rt;
+
+ rt = snd_pcm_substream_chip(sub->instance);
+ alsa_rt = sub->instance->runtime;
+
+ src = (alsa_rt->dma_area + sub->dma_off);
+ src_end = alsa_rt->dma_area +
+ frames_to_bytes(alsa_rt, alsa_rt->buffer_size);
+
+ bytes_per_frame = alsa_rt->frame_bits / 8;
+ dest = urb->buffer;
+
+ for (i = 0; i < USB_N_PACKETS_PER_URB; i++) {
+ frame_count = urb->packets[i].length / bytes_per_frame;
+
+ for (frame = 0; frame < frame_count; frame++) {
+ memcpy(dest, src, bytes_per_frame);
+
+ src += bytes_per_frame;
+ dest += bytes_per_frame;
+ sub->dma_off += bytes_per_frame;
+ sub->period_off += bytes_per_frame;
+
+ if (src >= src_end) {
+ sub->dma_off = 0;
+ src = alsa_rt->dma_area;
+ }
+ }
+ }
+}
+
+/* refill empty URB that comes back from the BCD2000 */
+static void bcd2000_pcm_out_urb_handler(struct urb *usb_urb)
+{
+ struct bcd2000_urb *bcd2k_urb = usb_urb->context;
+ struct bcd2000_pcm *pcm = &bcd2k_urb->bcd2k->pcm;
+ struct bcd2000_substream *stream = bcd2k_urb->stream;
+ unsigned long flags;
+ int ret, k, period_bytes;
+ struct usb_iso_packet_descriptor *packet;
+
+
+ if (pcm->panic || stream->state == STREAM_STOPPING)
+ return;
+
+ if (unlikely(usb_urb->status == -ENOENT || /* unlinked */
+ usb_urb->status == -ENODEV || /* device removed */
+ usb_urb->status == -ECONNRESET || /* unlinked */
+ usb_urb->status == -ESHUTDOWN)) /* device disabled */
+ {
+ goto out_fail;
+ }
+
+ if (stream->state == STREAM_STARTING) {
+ stream->wait_cond = true;
+ wake_up(&stream->wait_queue);
+ }
+
+ if (stream->active) {
+ spin_lock_irqsave(&stream->lock, flags);
+
+ memset(bcd2k_urb->buffer, 0, USB_BUFFER_SIZE);
+
+ /* fill URB with data from ALSA */
+ bcd2000_pcm_playback(stream, bcd2k_urb);
+
+ period_bytes = snd_pcm_lib_period_bytes(stream->instance);
+
+ /* check if a complete period was written into the URB */
+ if (stream->period_off > period_bytes) {
+ stream->period_off %= period_bytes;
+
+ spin_unlock_irqrestore(&stream->lock, flags);
+
+ snd_pcm_period_elapsed(stream->instance);
+ } else {
+ spin_unlock_irqrestore(&stream->lock, flags);
+ }
+
+ for (k = 0; k < USB_N_PACKETS_PER_URB; k++) {
+ packet = &bcd2k_urb->packets[k];
+ packet->offset = k * USB_PACKET_SIZE;
+ packet->length = USB_PACKET_SIZE;
+ packet->actual_length = 0;
+ packet->status = 0;
+ }
+ bcd2k_urb->instance.number_of_packets = USB_N_PACKETS_PER_URB;
+
+ ret = usb_submit_urb(&bcd2k_urb->instance, GFP_ATOMIC);
+ if (ret < 0)
+ goto out_fail;
+ }
+
+ return;
+
+out_fail:
+ dev_info(&bcd2k_urb->bcd2k->dev->dev, PREFIX
+ "error in out_urb handler");
+ pcm->panic = true;
+}
+
+static void bcd2000_pcm_stream_stop(struct bcd2000_pcm *pcm,
+ struct bcd2000_substream *stream)
+{
+ int i;
+
+ if (stream->state != STREAM_DISABLED) {
+ stream->state = STREAM_STOPPING;
+
+ for (i = 0; i < USB_N_URBS; i++)
+ usb_kill_urb(&stream->urbs[i].instance);
+
+ stream->state = STREAM_DISABLED;
+ }
+}
+
+static int bcd2000_substream_open(struct snd_pcm_substream *substream)
+{
+ struct bcd2000_substream *stream = NULL;
+ struct bcd2000_pcm *pcm = snd_pcm_substream_chip(substream);
+
+ substream->runtime->hw = pcm->pcm_info;
+
+ if (pcm->panic)
+ return -EPIPE;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ stream = &pcm->playback;
+ else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
+ stream = &pcm->capture;
+
+ if (!stream) {
+ dev_err(&pcm->bcd2k->dev->dev, PREFIX "invalid stream type\n");
+ return -EINVAL;
+ }
+
+ mutex_lock(&stream->mutex);
+ stream->instance = substream;
+ stream->active = false;
+ mutex_unlock(&stream->mutex);
+
+ return 0;
+}
+
+static int bcd2000_substream_close(struct snd_pcm_substream *substream)
+{
+ unsigned long flags;
+ struct bcd2000_pcm *pcm = snd_pcm_substream_chip(substream);
+ struct bcd2000_substream *stream = NULL;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ stream = &pcm->playback;
+ else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
+ stream = &pcm->capture;
+
+ if (pcm->panic)
+ return 0;
+
+ mutex_lock(&stream->mutex);
+ if (stream) {
+ bcd2000_pcm_stream_stop(pcm, stream);
+
+ spin_lock_irqsave(&stream->lock, flags);
+ stream->instance = NULL;
+ stream->active = false;
+ spin_unlock_irqrestore(&stream->lock, flags);
+ }
+ mutex_unlock(&stream->mutex);
+
+ return 0;
+}
+
+static int bcd2000_pcm_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *hw_params)
+{
+ return snd_pcm_lib_alloc_vmalloc_buffer(substream,
+ params_buffer_bytes(hw_params));
+}
+
+static int bcd2000_pcm_hw_free(struct snd_pcm_substream *substream)
+{
+ return snd_pcm_lib_free_vmalloc_buffer(substream);
+}
+
+static int bcd2000_pcm_stream_start(struct bcd2000_pcm *pcm,
+ struct bcd2000_substream *stream)
+{
+ int ret;
+ int i, k;
+ struct usb_iso_packet_descriptor *packet;
+
+ if (stream->state == STREAM_DISABLED) {
+ /* reset panic state when starting a new stream */
+ pcm->panic = false;
+
+ stream->state = STREAM_STARTING;
+
+ /* initialize data of each URB */
+ for (i = 0; i < USB_N_URBS; i++) {
+ for (k = 0; k < USB_N_PACKETS_PER_URB; k++) {
+ packet = &stream->urbs[i].packets[k];
+ packet->offset = k * USB_PACKET_SIZE;
+ packet->length = USB_PACKET_SIZE;
+ packet->actual_length = 0;
+ packet->status = 0;
+ }
+
+ /* immediately send data with the first audio out URB */
+ if (stream->instance == SNDRV_PCM_STREAM_PLAYBACK)
+ bcd2000_pcm_playback(stream, &stream->urbs[i]);
+
+ ret = usb_submit_urb(&stream->urbs[i].instance,
+ GFP_ATOMIC);
+ if (ret) {
+ bcd2000_pcm_stream_stop(pcm, stream);
+ return ret;
+ }
+ }
+
+ /* wait for first out urb to return (sent in in urb handler) */
+ wait_event_timeout(stream->wait_queue,
+ stream->wait_cond, HZ);
+ if (stream->wait_cond) {
+ dev_dbg(&pcm->bcd2k->dev->dev, PREFIX
+ "%s: stream is running wakeup event\n",
+ __func__);
+ stream->state = STREAM_RUNNING;
+ } else {
+ bcd2000_pcm_stream_stop(pcm, stream);
+ return -EIO;
+ }
+ }
+ return 0;
+}
+
+static int bcd2000_pcm_prepare(struct snd_pcm_substream *substream)
+{
+ struct bcd2000_pcm *pcm = snd_pcm_substream_chip(substream);
+ int ret;
+ struct bcd2000_substream *stream = NULL;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ stream = &pcm->playback;
+ else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
+ stream = &pcm->capture;
+
+ if (pcm->panic)
+ return -EPIPE;
+
+ if (!stream)
+ return -ENODEV;
+
+ mutex_lock(&stream->mutex);
+ stream->dma_off = 0;
+ stream->period_off = 0;
+
+ if (stream->state == STREAM_DISABLED) {
+ ret = bcd2000_pcm_stream_start(pcm, stream);
+ if (ret) {
+ mutex_unlock(&stream->mutex);
+ dev_err(&pcm->bcd2k->dev->dev, PREFIX
+ "could not start pcm stream\n");
+ return ret;
+ }
+ }
+ mutex_unlock(&stream->mutex);
+ return 0;
+}
+
+static int bcd2000_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+ struct bcd2000_pcm *pcm = snd_pcm_substream_chip(substream);
+ struct bcd2000_substream *stream = NULL;
+ unsigned long flags;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ stream = &pcm->playback;
+ else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
+ stream = &pcm->capture;
+
+ if (pcm->panic)
+ return -EPIPE;
+
+ if (!stream)
+ return -ENODEV;
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ spin_lock_irqsave(&stream->lock, flags);
+ stream->active = true;
+ spin_unlock_irqrestore(&stream->lock, flags);
+
+ return 0;
+
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ spin_lock_irqsave(&stream->lock, flags);
+ stream->active = false;
+ spin_unlock_irqrestore(&stream->lock, flags);
+
+ return 0;
+ default:
+ return -EINVAL;
+ }
+}
+
+static snd_pcm_uframes_t
+bcd2000_pcm_pointer(struct snd_pcm_substream *substream)
+{
+ struct bcd2000_pcm *pcm = snd_pcm_substream_chip(substream);
+ unsigned long flags;
+ snd_pcm_uframes_t ret;
+ struct bcd2000_substream *stream = NULL;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ stream = &pcm->playback;
+ else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
+ stream = &pcm->capture;
+
+ if (pcm->panic || !stream)
+ return SNDRV_PCM_POS_XRUN;
+
+ spin_lock_irqsave(&stream->lock, flags);
+ /*
+ * return the number of the last written period in the
+ * ALSA ring buffer
+ */
+ ret = bytes_to_frames(stream->instance->runtime, stream->dma_off);
+ spin_unlock_irqrestore(&stream->lock, flags);
+
+ return ret;
+}
+
+static struct snd_pcm_ops bcd2000_ops = {
+ .open = bcd2000_substream_open,
+ .close = bcd2000_substream_close,
+ .ioctl = snd_pcm_lib_ioctl,
+ .hw_params = bcd2000_pcm_hw_params,
+ .hw_free = bcd2000_pcm_hw_free,
+ .prepare = bcd2000_pcm_prepare,
+ .trigger = bcd2000_pcm_trigger,
+ .pointer = bcd2000_pcm_pointer,
+ .page = snd_pcm_lib_get_vmalloc_page,
+ .mmap = snd_pcm_lib_mmap_vmalloc,
+};
+
+static int bcd2000_pcm_init_urb(struct bcd2000_urb *urb,
+ struct bcd2000 *bcd2k,
+ char in, unsigned int ep,
+ void (*handler)(struct urb *))
+{
+ urb->bcd2k = bcd2k;
+ usb_init_urb(&urb->instance);
+
+ urb->buffer = kzalloc(USB_BUFFER_SIZE, GFP_KERNEL);
+ if (!urb->buffer)
+ return -ENOMEM;
+
+ urb->instance.transfer_buffer = urb->buffer;
+ urb->instance.transfer_buffer_length = USB_BUFFER_SIZE;
+ urb->instance.dev = bcd2k->dev;
+ urb->instance.pipe = in ? usb_rcvisocpipe(bcd2k->dev, ep)
+ : usb_sndisocpipe(bcd2k->dev, ep);
+ urb->instance.interval = 1;
+ urb->instance.complete = handler;
+ urb->instance.context = urb;
+ urb->instance.number_of_packets = USB_N_PACKETS_PER_URB;
+
+ return 0;
+}
+
+static void bcd2000_pcm_destroy(struct bcd2000 *bcd2k)
+{
+ int i;
+
+ for (i = 0; i < USB_N_URBS; i++) {
+ kfree(bcd2k->pcm.playback.urbs[i].buffer);
+ kfree(bcd2k->pcm.capture.urbs[i].buffer);
+ }
+}
+
+static void bcd2000_pcm_free(struct snd_pcm *pcm)
+{
+ struct bcd2000_pcm *bcd2k_pcm = pcm->private_data;
+
+ if (bcd2k_pcm)
+ bcd2000_pcm_destroy(bcd2k_pcm->bcd2k);
+}
+
+int bcd2000_init_stream(struct bcd2000 *bcd2k,
+ struct bcd2000_substream *stream, bool in)
+{
+ int i, ret;
+
+ stream->state = STREAM_DISABLED;
+
+ init_waitqueue_head(&stream->wait_queue);
+ mutex_init(&stream->mutex);
+
+ for (i = 0; i < USB_N_URBS; i++) {
+ ret = bcd2000_pcm_init_urb(&stream->urbs[i], bcd2k, in,
+ in ? 0x83 : 0x2,
+ in ? bcd2000_pcm_in_urb_handler
+ : bcd2000_pcm_out_urb_handler);
+ if (ret) {
+ dev_err(&bcd2k->dev->dev, PREFIX
+ "%s: urb init failed, ret=%d: ",
+ __func__, ret);
+ return ret;
+ }
+ stream->urbs[i].stream = stream;
+ }
+
+ return 0;
+}
+
+int bcd2000_init_audio(struct bcd2000 *bcd2k)
+{
+ int ret;
+ struct bcd2000_pcm *pcm;
+
+ pcm = &bcd2k->pcm;
+ pcm->bcd2k = bcd2k;
+
+ spin_lock_init(&pcm->playback.lock);
+ spin_lock_init(&pcm->capture.lock);
+
+ bcd2000_init_stream(bcd2k, &pcm->playback, 0);
+ bcd2000_init_stream(bcd2k, &pcm->capture, 1);
+
+ ret = snd_pcm_new(bcd2k->card, DEVICENAME, 0, 1, 1, &pcm->instance);
+ if (ret < 0) {
+ dev_err(&bcd2k->dev->dev, PREFIX
+ "%s: snd_pcm_new() failed, ret=%d: ",
+ __func__, ret);
+ return ret;
+ }
+ pcm->instance->private_data = pcm;
+ pcm->instance->private_free = bcd2000_pcm_free;
+
+ strlcpy(pcm->instance->name, DEVICENAME, sizeof(pcm->instance->name));
+
+ memcpy(&pcm->pcm_info, &bcd2000_pcm_hardware,
+ sizeof(bcd2000_pcm_hardware));
+
+ snd_pcm_set_ops(pcm->instance, SNDRV_PCM_STREAM_PLAYBACK, &bcd2000_ops);
+ snd_pcm_set_ops(pcm->instance, SNDRV_PCM_STREAM_CAPTURE, &bcd2000_ops);
+
+ return 0;
+}
+
+void bcd2000_free_audio(struct bcd2000 *bcd2k)
+{
+}
diff --git a/sound/usb/bcd2000/audio.h b/sound/usb/bcd2000/audio.h
new file mode 100644
index 0000000..912e8df
--- /dev/null
+++ b/sound/usb/bcd2000/audio.h
@@ -0,0 +1,58 @@
+#ifndef AUDIO_H
+#define AUDIO_H
+
+#include <sound/pcm.h>
+
+#define USB_N_URBS 4
+#define USB_N_PACKETS_PER_URB 16
+#define USB_PACKET_SIZE 360
+#define USB_BUFFER_SIZE (USB_PACKET_SIZE * USB_N_PACKETS_PER_URB)
+
+#define BYTES_PER_PERIOD 3528
+#define PERIODS_MAX 128
+#define ALSA_BUFFER_SIZE (BYTES_PER_PERIOD * PERIODS_MAX)
+
+struct bcd2000;
+
+struct bcd2000_urb {
+ struct bcd2000 *bcd2k;
+ struct bcd2000_substream *stream;
+
+ /* BEGIN DO NOT SEPARATE */
+ struct urb instance;
+ struct usb_iso_packet_descriptor packets[USB_N_PACKETS_PER_URB];
+ /* END DO NOT SEPARATE */
+ u8 *buffer;
+};
+
+struct bcd2000_substream {
+ struct snd_pcm_substream *instance;
+
+ u8 state;
+ bool active;
+ snd_pcm_uframes_t dma_off; /* current position in alsa dma_area */
+ snd_pcm_uframes_t period_off; /* current position in current period */
+
+ struct bcd2000_urb urbs[USB_N_URBS];
+
+ spinlock_t lock;
+ struct mutex mutex;
+ wait_queue_head_t wait_queue;
+ bool wait_cond;
+};
+
+struct bcd2000_pcm {
+ struct bcd2000 *bcd2k;
+
+ struct snd_pcm *instance;
+ struct snd_pcm_hardware pcm_info;
+
+ struct bcd2000_substream playback;
+ struct bcd2000_substream capture;
+ bool panic; /* if set driver won't do anymore pcm on device */
+};
+
+int bcd2000_init_audio(struct bcd2000 *bcd2k);
+void bcd2000_free_audio(struct bcd2000 *bcd2k);
+
+#endif
diff --git a/sound/usb/bcd2000/bcd2000.c b/sound/usb/bcd2000/bcd2000.c
index 0f22bd9..f7ba9ad 100644
--- a/sound/usb/bcd2000/bcd2000.c
+++ b/sound/usb/bcd2000/bcd2000.c
@@ -59,6 +59,8 @@ static void bcd2000_disconnect(struct usb_interface *interface)
/* make sure that userspace cannot create new requests */
snd_card_disconnect(bcd2k->card);
+ bcd2000_free_audio(bcd2k);
+
bcd2000_free_midi(bcd2k);
if (bcd2k->intf) {
@@ -120,6 +122,10 @@ static int bcd2000_probe(struct usb_interface *interface,
if (err < 0)
goto probe_error;
+ err = bcd2000_init_audio(bcd2k);
+ if (err < 0)
+ goto probe_error;
+
err = snd_card_register(card);
if (err < 0)
goto probe_error;
diff --git a/sound/usb/bcd2000/bcd2000.h b/sound/usb/bcd2000/bcd2000.h
index a0e27c9..802685b 100644
--- a/sound/usb/bcd2000/bcd2000.h
+++ b/sound/usb/bcd2000/bcd2000.h
@@ -9,6 +9,7 @@
#define DEVICENAME "BCD2000"
#define PREFIX "snd-bcd2000: "
+#include "audio.h"
#include "midi.h"
struct bcd2000 {
@@ -18,6 +19,7 @@ struct bcd2000 {
int card_index;
struct bcd2000_midi midi;
+ struct bcd2000_pcm pcm;
};
void bcd2000_dump_buffer(const char *prefix, const char *buf, int len);
--
2.0.5
More information about the Alsa-devel
mailing list