[alsa-devel] ALSA driver for Native Instruments sound hardware

Daniel Mack daniel at caiaq.org
Mon Mar 26 17:46:37 CEST 2007


On Mon, Mar 26, 2007 at 03:45:47PM +0200, Takashi Iwai wrote:
> > diff -Nur alsa-kernel-ni/usb/caiaq/caiaq-audio.c alsa-kernel/usb/caiaq/caiaq-audio.c
> > --- alsa-kernel-ni/usb/caiaq/caiaq-audio.c	1970-01-01 01:00:00.000000000 +0100
> > +++ alsa-kernel/usb/caiaq/caiaq-audio.c	2007-03-23 17:33:59.000000000 +0100
> > +
> > +static int snd_usb_caiaq_pcm_trigger(struct snd_pcm_substream *substream, 
> > +				     int cmd)
> > +{
> > +	struct snd_usb_caiaqdev *dev = snd_pcm_substream_chip(substream);
> > +
> > +	switch (cmd) {
> > +		case SNDRV_PCM_TRIGGER_START:
> > +		case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
> 
> Put 'case' with the same indent level as switch.
> (Ditto for all switch blocks in other places.)

Done.

> > +	switch(buf[0]) {
> > +	case EP1_CMD_GET_DEVICE_INFO:
> > +	{
> > +	 	memcpy(&dev->spec, buf+1, sizeof(struct caiaq_device_spec));
> 
> Don't you need to convert 16bit values for big-endian?

The only value which uses more than one byte of storage is the firmware
revision which is unimportant as no runtime decision depends on it.
All others are chars and need no endianess care.

> Also, some lines are too long.  Please try to keep lines in 80 chars
> as much as possible.

Also done. New patch applied.

Daniel

-------------- next part --------------
diff -Nur alsa-driver/usb/caiaq/caiaq-audio.c alsa-driver-ni/usb/caiaq/caiaq-audio.c
--- alsa-driver/usb/caiaq/caiaq-audio.c	1970-01-01 01:00:00.000000000 +0100
+++ alsa-driver-ni/usb/caiaq/caiaq-audio.c	2007-03-26 16:09:30.000000000 +0200
@@ -0,0 +1,699 @@
+/*
+ *   Copyright (c) 2006,2007 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 <sound/driver.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/interrupt.h>
+#include <linux/usb.h>
+#include <linux/spinlock.h>
+#include <sound/core.h>
+#include <sound/initval.h>
+#include <sound/pcm.h>
+#include <sound/rawmidi.h>
+#ifdef CONFIG_SND_USB_CAIAQ_INPUT
+#include <linux/input.h>
+#endif
+
+#include "caiaq-device.h"
+#include "caiaq-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 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 = 4096,
+	.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)
+{
+	if (sub->stream == SNDRV_PCM_STREAM_PLAYBACK)
+		dev->sub_playback[sub->number] = NULL;
+	else
+		dev->sub_capture[sub->number] = NULL;
+}
+
+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("stream_start(%p)\n", dev);
+	spin_lock_irq(&dev->spinlock);
+	if (dev->streaming) {
+		spin_unlock_irq(&dev->spinlock);
+		return -EINVAL;
+	}
+
+	dev->input_panic = 0;
+	dev->output_panic = 0;
+	dev->first_packet = 1;
+	dev->streaming = 1;
+
+	for (i = 0; i < N_URBS; i++) {
+		ret = usb_submit_urb(dev->data_urbs_in[i], GFP_ATOMIC);
+		if (ret) {
+			log("unable to trigger initial read #%d! (ret = %d)\n",
+				i, ret);
+			dev->streaming = 0;
+			spin_unlock_irq(&dev->spinlock);
+			return -EPIPE;
+		}
+	}
+	
+	spin_unlock_irq(&dev->spinlock);
+	return 0;
+}
+
+static void stream_stop(struct snd_usb_caiaqdev *dev)
+{
+	int i;
+	
+	debug("stream_stop(%p)\n", dev);
+	if (!dev->streaming)
+		return;
+	
+	dev->streaming = 0;
+	for (i = 0; i < N_URBS; i++) {
+		usb_unlink_urb(dev->data_urbs_in[i]);
+		usb_unlink_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("snd_usb_caiaq_substream_open(%p)\n", 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("snd_usb_caiaq_substream_close(%p)\n", 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("snd_usb_caiaq_pcm_hw_params(%p)\n", 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("snd_usb_caiaq_pcm_hw_free(%p)\n", sub);
+	spin_lock_irq(&dev->spinlock);
+	deactivate_substream(dev, sub);
+	spin_unlock_irq(&dev->spinlock);
+	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("snd_usb_caiaq_pcm_prepare(%p)\n", substream);
+	
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+		dev->audio_out_buf_pos[index] = BYTES_PER_SAMPLE + 1;
+	else
+		dev->audio_in_buf_pos[index] = 0;
+	
+	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;
+	
+	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:
+		spin_lock(&dev->spinlock);
+		activate_substream(dev, sub);
+		spin_unlock(&dev->spinlock);
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+		spin_lock(&dev->spinlock);
+		deactivate_substream(dev, sub);
+		spin_unlock(&dev->spinlock);
+		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++) {
+		if (!(sub = subs[stream]))
+			continue;
+
+		pb = frames_to_bytes(sub->runtime, 
+				     sub->runtime->period_size);
+		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;
+
+	spin_lock(&dev->spinlock);
+	
+	for (i = 0; i < iso->actual_length;) {
+		for (stream = 0; stream < dev->n_streams; stream++, i++)
+			if ((sub = dev->sub_capture[stream])) {
+				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;
+			}
+	}
+	
+	spin_unlock(&dev->spinlock);
+}
+
+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;
+
+	spin_lock(&dev->spinlock);
+	
+	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++)
+			if ((sub = dev->sub_capture[stream])) {
+				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;
+			}
+	}
+
+	spin_unlock(&dev->spinlock);
+}
+
+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;
+
+	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) {
+		debug("streaming error detected %s %s\n", 
+				dev->input_panic ? "(input)" : "",
+				dev->output_panic ? "(output)" : "");
+	}
+
+	check_for_elapsed_periods(dev, dev->sub_capture);
+}
+
+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;
+
+	spin_lock(&dev->spinlock);
+	
+	for (i = 0; i < iso->length;) {
+		for (stream = 0; stream < dev->n_streams; stream++)
+			if ((sub = dev->sub_playback[stream])) {
+				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->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);
+	}
+
+	spin_unlock(&dev->spinlock);
+	check_for_elapsed_periods(dev, dev->sub_playback);
+}
+
+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) {
+			fill_out_urb(dev, out, &out->iso_frame_desc[outframe]);
+			read_in_urb(dev, urb, &urb->iso_frame_desc[frame]);
+			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 = (struct urb **) 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 __devinit 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):
+		dev->samplerates |= SNDRV_PCM_RATE_88200;
+		dev->samplerates |= SNDRV_PCM_RATE_192000;
+		break;
+	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 = (struct snd_usb_caiaq_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("snd_usb_caiaq_audio_free (%p)\n", dev);
+	stream_stop(dev);
+	free_urbs(dev->data_urbs_in);
+	free_urbs(dev->data_urbs_out);
+	kfree(dev->data_cb_info);
+}
+
diff -Nur alsa-driver/usb/caiaq/caiaq-audio.h alsa-driver-ni/usb/caiaq/caiaq-audio.h
--- alsa-driver/usb/caiaq/caiaq-audio.h	1970-01-01 01:00:00.000000000 +0100
+++ alsa-driver-ni/usb/caiaq/caiaq-audio.h	2007-02-28 15:46:47.000000000 +0100
@@ -0,0 +1,7 @@
+#ifndef CAIAQ_AUDIO_H
+#define CAIAQ_AUDIO_H
+
+int snd_usb_caiaq_audio_init(struct snd_usb_caiaqdev *dev);
+void snd_usb_caiaq_audio_free(struct snd_usb_caiaqdev *dev);
+
+#endif /* CAIAQ_AUDIO_H */
diff -Nur alsa-driver/usb/caiaq/caiaq-device.c alsa-driver-ni/usb/caiaq/caiaq-device.c
--- alsa-driver/usb/caiaq/caiaq-device.c	1970-01-01 01:00:00.000000000 +0100
+++ alsa-driver-ni/usb/caiaq/caiaq-device.c	2007-03-26 16:06:09.000000000 +0200
@@ -0,0 +1,435 @@
+/*
+ * caiaq.c: ALSA driver for caiaq/NativeInstruments devices
+ *
+ *   Copyright (c) 2007 Daniel Mack <daniel at caiaq.de>
+ *                      Karsten Wiese <fzu at wemgehoertderstaat.de>
+ *
+ *   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/init.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/interrupt.h>
+#include <linux/usb.h>
+#include <linux/input.h>
+#include <linux/spinlock.h>
+#include <sound/driver.h>
+#include <sound/core.h>
+#include <sound/initval.h>
+#include <sound/pcm.h>
+#include <sound/rawmidi.h>
+
+#include "caiaq-device.h"
+#include "caiaq-audio.h"
+#include "caiaq-midi.h"
+
+#ifdef CONFIG_SND_USB_CAIAQ_INPUT
+#include "caiaq-input.h"
+#endif
+
+MODULE_AUTHOR("Daniel Mack <daniel at caiaq.de>");
+MODULE_DESCRIPTION("caiaq USB audio, version 1.1.0");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("{{Native Instruments, RigKontrol2},"
+			 "{Native Instruments, Kore Controller},"
+			 "{Native Instruments, Audio Kontrol 1}"
+			 "{Native Instruments, Audio 8 DJ}}");
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 0-max */
+static char* id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* Id for this card */
+static int enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP; /* Enable this card */
+static int snd_card_used[SNDRV_CARDS];
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for the caiaq sound device");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for the caiaq soundcard.");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "Enable the caiaq soundcard.");
+
+enum {
+	SAMPLERATE_44100	= 0,
+	SAMPLERATE_48000	= 1,
+	SAMPLERATE_96000	= 2,
+	SAMPLERATE_192000	= 3,
+	SAMPLERATE_88200	= 4,
+	SAMPLERATE_INVALID	= 0xff
+};
+
+enum {
+	DEPTH_NONE	= 0,
+	DEPTH_16	= 1,
+	DEPTH_24	= 2,
+	DEPTH_32	= 3
+};
+
+static struct usb_device_id snd_usb_id_table[] = {
+	{
+		.match_flags =	USB_DEVICE_ID_MATCH_DEVICE,
+		.idVendor =	USB_VID_NATIVEINSTRUMENTS,
+		.idProduct =	USB_PID_RIGKONTROL2 
+	},
+	{
+		.match_flags =	USB_DEVICE_ID_MATCH_DEVICE,
+		.idVendor =	USB_VID_NATIVEINSTRUMENTS,
+		.idProduct =	USB_PID_KORECONTROLLER
+	},
+	{
+		.match_flags =  USB_DEVICE_ID_MATCH_DEVICE,
+		.idVendor =     USB_VID_NATIVEINSTRUMENTS,
+		.idProduct =    USB_PID_AK1
+	},
+	{
+		.match_flags =  USB_DEVICE_ID_MATCH_DEVICE,
+		.idVendor =     USB_VID_NATIVEINSTRUMENTS,
+		.idProduct =    USB_PID_AUDIO8DJ
+	},
+	{ /* terminator */ }
+};
+
+static void usb_ep1_command_reply_dispatch (struct urb* urb)
+{
+	int ret;
+	struct snd_usb_caiaqdev *dev = urb->context;
+	unsigned char *buf = urb->transfer_buffer;
+
+	if (urb->status || !dev) {
+		log("received EP1 urb->status = %i\n", urb->status);
+		return;
+	}
+
+	switch(buf[0]) {
+	case EP1_CMD_GET_DEVICE_INFO:
+	 	memcpy(&dev->spec, buf+1, sizeof(struct caiaq_device_spec));
+		debug("device spec (firmware %d): audio: %d in, %d out, "
+			"MIDI: %d in, %d out, data alignment %d\n",
+			dev->spec.fw_version,
+			dev->spec.num_analog_audio_in,
+			dev->spec.num_analog_audio_out,
+			dev->spec.num_midi_in,
+			dev->spec.num_midi_out,
+			dev->spec.data_alignment);
+
+		dev->spec_received++;
+		wake_up(&dev->ep1_wait_queue);
+		break;
+	case EP1_CMD_AUDIO_PARAMS:
+		dev->audio_parm_answer = buf[1];
+		wake_up(&dev->ep1_wait_queue);
+		break;
+	case EP1_CMD_MIDI_READ:
+		snd_usb_caiaq_midi_handle_input(dev, buf[1], buf + 3, buf[2]);
+		break;
+
+#ifdef CONFIG_SND_USB_CAIAQ_INPUT
+	case EP1_CMD_READ_ERP:
+	case EP1_CMD_READ_ANALOG:
+	case EP1_CMD_READ_IO:
+		snd_usb_caiaq_input_dispatch(dev, buf, urb->actual_length);
+		break;
+#endif
+	}
+
+	dev->ep1_in_urb.actual_length = 0;
+	ret = usb_submit_urb(&dev->ep1_in_urb, GFP_ATOMIC);
+	if (ret < 0)
+		log("unable to submit urb. OOM!?\n");
+}
+
+static int send_command (struct snd_usb_caiaqdev *dev,
+			 unsigned char command, 
+			 const unsigned char *buffer,
+			 int len)
+{
+	int actual_len;
+	struct usb_device *usb_dev = dev->chip.dev;
+
+	if (!usb_dev)
+		return -EIO;
+
+	if (len > EP1_BUFSIZE - 1)
+		len = EP1_BUFSIZE - 1;
+
+	if (buffer && len > 0)
+		memcpy(dev->ep1_out_buf+1, buffer, len);
+	
+	dev->ep1_out_buf[0] = command;
+	return usb_bulk_msg(usb_dev, usb_sndbulkpipe(usb_dev, 1),
+			   dev->ep1_out_buf, len+1, &actual_len, 200);
+}
+
+int snd_usb_caiaq_set_audio_params (struct snd_usb_caiaqdev *dev,
+		   		    int rate, int depth, int bpp)
+{
+	int ret;
+	char tmp[5];
+	
+	switch (rate) {
+	case 44100:	tmp[0] = SAMPLERATE_44100;   break;
+	case 48000:	tmp[0] = SAMPLERATE_48000;   break;
+	case 88200:	tmp[0] = SAMPLERATE_88200;   break;
+	case 96000:	tmp[0] = SAMPLERATE_96000;   break;
+	case 192000:	tmp[0] = SAMPLERATE_192000;  break;
+	default:	return -EINVAL;
+	}
+
+	switch (depth) {
+	case 16:	tmp[1] = DEPTH_16;   break;
+	case 24:	tmp[1] = DEPTH_24;   break;
+	default:	return -EINVAL;
+	}
+
+	tmp[2] = bpp & 0xff;
+	tmp[3] = bpp >> 8;
+	tmp[4] = 1; /* packets per microframe */
+
+	debug("setting audio params: %d Hz, %d bits, %d bpp\n",
+		rate, depth, bpp);
+
+	dev->audio_parm_answer = -1;
+	ret = send_command(dev, EP1_CMD_AUDIO_PARAMS, tmp, sizeof(tmp));
+
+	if (ret)
+		return ret;
+	
+	if (!wait_event_timeout(dev->ep1_wait_queue, 
+	    dev->audio_parm_answer >= 0, HZ))
+		return -EPIPE;
+		
+	if (dev->audio_parm_answer != 1) 
+		debug("unable to set the device's audio params\n");
+
+	return dev->audio_parm_answer == 1 ? 0 : -EINVAL;
+}
+
+int snd_usb_caiaq_set_auto_msg (struct snd_usb_caiaqdev *dev, 
+				int digital, int analog, int erp)
+{
+	char tmp[3] = { digital, analog, erp };
+	return send_command(dev, EP1_CMD_AUTO_MSG, tmp, sizeof(tmp));
+}
+
+static void setup_card(struct snd_usb_caiaqdev *dev)
+{
+	int ret;
+	char val[3];
+	
+	/* device-specific startup specials */
+	switch (dev->chip.usb_id) {
+	case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_RIGKONTROL2):
+		/* RigKontrol2 - display centered dash ('-') */
+		val[0] = 0x00;
+		val[1] = 0x00;
+		val[2] = 0x01;
+		send_command(dev, EP1_CMD_WRITE_IO, val, 3);
+		break;
+	case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_AK1):
+		/* Audio Kontrol 1 - make USB-LED stop blinking */
+		val[0] = 0x00;
+		send_command(dev, EP1_CMD_WRITE_IO, val, 1);
+		break;
+	}
+	
+	ret = snd_usb_caiaq_audio_init(dev);
+	if (ret < 0)
+		log("Unable to set up audio system (ret=%d)\n", ret);
+	
+	ret = snd_usb_caiaq_midi_init(dev);
+	if (ret < 0)
+		log("Unable to set up MIDI system (ret=%d)\n", ret);
+
+#ifdef CONFIG_SND_USB_CAIAQ_INPUT
+	ret = snd_usb_caiaq_input_init(dev);
+	if (ret < 0)
+		log("Unable to set up input system (ret=%d)\n", ret);
+#endif
+
+	/* finally, register the card and all its sub-instances */
+	ret = snd_card_register(dev->chip.card);
+	if (ret < 0) {
+		log("snd_card_register() returned %d\n", ret);
+		snd_card_free(dev->chip.card);
+	}
+}
+
+static struct snd_card* create_card(struct usb_device* usb_dev)
+{
+	int devnum;
+	struct snd_card *card;
+	struct snd_usb_caiaqdev *dev;
+
+	for (devnum = 0; devnum < SNDRV_CARDS; devnum++)
+		if (enable[devnum] && !snd_card_used[devnum])
+			break;
+
+	if (devnum >= SNDRV_CARDS)
+		return NULL;
+
+	card = snd_card_new(index[devnum], id[devnum], THIS_MODULE, 
+					sizeof(struct snd_usb_caiaqdev));
+	if (!card)
+		return NULL;
+
+	dev = caiaqdev(card);
+	dev->chip.dev = usb_dev;
+	dev->chip.card = card;
+	dev->chip.usb_id = USB_ID(usb_dev->descriptor.idVendor,
+					usb_dev->descriptor.idProduct);
+	spin_lock_init(&dev->spinlock);
+	snd_card_set_dev(card, &usb_dev->dev);
+
+	return card;
+}
+
+static int init_card(struct snd_usb_caiaqdev *dev)
+{
+	char *c;
+	struct usb_device *usb_dev = dev->chip.dev;
+	struct snd_card *card = dev->chip.card;
+	int err, len;
+	
+	if (usb_set_interface(usb_dev, 0, 1) != 0) {
+		log("can't set alt interface.\n");
+		return -EIO;
+	}
+
+	usb_init_urb(&dev->ep1_in_urb);
+	usb_init_urb(&dev->midi_out_urb);
+
+	usb_fill_bulk_urb(&dev->ep1_in_urb, usb_dev, 
+			  usb_rcvbulkpipe(usb_dev, 0x1),
+			  dev->ep1_in_buf, EP1_BUFSIZE, 
+			  usb_ep1_command_reply_dispatch, dev);
+
+	usb_fill_bulk_urb(&dev->midi_out_urb, usb_dev, 
+			  usb_sndbulkpipe(usb_dev, 0x1),
+			  dev->midi_out_buf, EP1_BUFSIZE, 
+			  snd_usb_caiaq_midi_output_done, dev);
+	
+	init_waitqueue_head(&dev->ep1_wait_queue);
+	init_waitqueue_head(&dev->prepare_wait_queue);
+	
+	if (usb_submit_urb(&dev->ep1_in_urb, GFP_KERNEL) != 0)
+		return -EIO;
+
+	err = send_command(dev, EP1_CMD_GET_DEVICE_INFO, NULL, 0);
+	if (err)
+		return err;
+
+	if (!wait_event_timeout(dev->ep1_wait_queue, dev->spec_received, HZ))
+		return -ENODEV;
+
+	usb_string(usb_dev, usb_dev->descriptor.iManufacturer,
+		   dev->vendor_name, CAIAQ_USB_STR_LEN);
+	
+	usb_string(usb_dev, usb_dev->descriptor.iProduct,
+		   dev->product_name, CAIAQ_USB_STR_LEN);
+	
+	usb_string(usb_dev, usb_dev->descriptor.iSerialNumber,
+		   dev->serial, CAIAQ_USB_STR_LEN);
+
+	/* terminate serial string at first white space occurence */
+	c = strchr(dev->serial, ' ');
+	if (c)
+		*c = '\0';
+	
+	strcpy(card->driver, MODNAME);
+	strcpy(card->shortname, dev->product_name);
+
+	len = snprintf(card->longname, sizeof(card->longname),
+		       "%s %s (serial %s, ",
+		       dev->vendor_name, dev->product_name, dev->serial);
+
+	if (len < sizeof(card->longname) - 2)
+		len += usb_make_path(usb_dev, card->longname + len,
+				     sizeof(card->longname) - len);
+
+	card->longname[len++] = ')';
+	card->longname[len] = '\0';
+	setup_card(dev);
+	return 0;
+}
+
+static int snd_probe(struct usb_interface *intf, 
+		     const struct usb_device_id *id)
+{
+	int ret;
+	struct snd_card *card;
+	struct usb_device *device = interface_to_usbdev(intf);
+	
+	card = create_card(device);
+	
+	if (!card)
+		return -ENOMEM;
+			
+	dev_set_drvdata(&intf->dev, card);
+	ret = init_card(caiaqdev(card));
+	if (ret < 0) {
+		log("unable to init card! (ret=%d)\n", ret);
+		snd_card_free(card);
+		return ret;
+	}
+	
+	return 0;
+}
+
+static void snd_disconnect(struct usb_interface *intf)
+{
+	struct snd_usb_caiaqdev *dev;
+	struct snd_card *card = dev_get_drvdata(&intf->dev);
+
+	debug("snd_disconnect(%p)\n", intf);
+
+	if (!card)
+		return;
+
+	dev = caiaqdev(card);
+	snd_card_disconnect(card);
+
+#ifdef CONFIG_SND_USB_CAIAQ_INPUT
+	snd_usb_caiaq_input_free(dev);
+#endif
+	snd_usb_caiaq_audio_free(dev);
+	
+	usb_kill_urb(&dev->ep1_in_urb);
+	usb_kill_urb(&dev->midi_out_urb);
+	
+	snd_card_free(card);
+	usb_reset_device(interface_to_usbdev(intf));
+}
+
+
+MODULE_DEVICE_TABLE(usb, snd_usb_id_table);
+static struct usb_driver snd_usb_driver = {
+	.name 		= MODNAME,
+	.probe 		= snd_probe,
+	.disconnect	= snd_disconnect,
+	.id_table 	= snd_usb_id_table,
+};
+
+static int __init snd_module_init(void)
+{
+	return usb_register(&snd_usb_driver);
+}
+
+static void __exit snd_module_exit(void)
+{
+	usb_deregister(&snd_usb_driver);
+}
+
+module_init(snd_module_init)
+module_exit(snd_module_exit)
+
diff -Nur alsa-driver/usb/caiaq/caiaq-device.h alsa-driver-ni/usb/caiaq/caiaq-device.h
--- alsa-driver/usb/caiaq/caiaq-device.h	1970-01-01 01:00:00.000000000 +0100
+++ alsa-driver-ni/usb/caiaq/caiaq-device.h	2007-03-23 17:33:15.000000000 +0100
@@ -0,0 +1,116 @@
+#ifndef CAIAQ_DEVICE_H
+#define CAIAQ_DEVICE_H
+
+#include "../usbaudio.h"
+
+#define USB_VID_NATIVEINSTRUMENTS 0x17cc
+
+#define USB_PID_RIGKONTROL2	0x1969
+#define USB_PID_KORECONTROLLER 	0x4711
+#define USB_PID_AK1		0x0815
+#define USB_PID_AUDIO8DJ	0x1978
+
+#define EP1_BUFSIZE 64
+#define CAIAQ_USB_STR_LEN 0xff
+#define MAX_STREAMS 32
+
+//#define	SND_USB_CAIAQ_DEBUG
+
+#define MODNAME "snd-usb-caiaq"
+#define log(x...) snd_printk(KERN_WARNING MODNAME" log: " x)
+
+#ifdef SND_USB_CAIAQ_DEBUG
+#define debug(x...) snd_printk(KERN_WARNING MODNAME " debug: " x)
+#else
+#define debug(x...) do { } while(0)
+#endif
+
+#define EP1_CMD_GET_DEVICE_INFO	0x1
+#define EP1_CMD_READ_ERP	0x2
+#define EP1_CMD_READ_ANALOG	0x3
+#define EP1_CMD_READ_IO		0x4
+#define EP1_CMD_WRITE_IO	0x5
+#define EP1_CMD_MIDI_READ	0x6
+#define EP1_CMD_MIDI_WRITE	0x7
+#define EP1_CMD_AUDIO_PARAMS	0x9
+#define EP1_CMD_AUTO_MSG	0xb
+
+struct caiaq_device_spec {
+	unsigned short fw_version;
+	unsigned char hw_subtype;
+	unsigned char num_erp;
+	unsigned char num_analog_in;
+	unsigned char num_digital_in;
+	unsigned char num_digital_out;
+	unsigned char num_analog_audio_out;
+	unsigned char num_analog_audio_in;
+	unsigned char num_digital_audio_out;
+	unsigned char num_digital_audio_in;
+	unsigned char num_midi_out;
+	unsigned char num_midi_in;
+	unsigned char data_alignment;
+} __attribute__ ((packed));
+
+struct snd_usb_caiaq_cb_info;
+
+struct snd_usb_caiaqdev {
+	struct snd_usb_audio chip;
+
+	struct urb ep1_in_urb;
+	struct urb midi_out_urb;
+	struct urb **data_urbs_in;
+	struct urb **data_urbs_out;
+	struct snd_usb_caiaq_cb_info *data_cb_info;
+	
+	unsigned char ep1_in_buf[EP1_BUFSIZE];
+	unsigned char ep1_out_buf[EP1_BUFSIZE];
+	unsigned char midi_out_buf[EP1_BUFSIZE];
+
+	struct caiaq_device_spec spec;
+	spinlock_t spinlock;
+	wait_queue_head_t ep1_wait_queue;
+	wait_queue_head_t prepare_wait_queue;
+	int spec_received, audio_parm_answer;
+	
+	char vendor_name[CAIAQ_USB_STR_LEN];
+	char product_name[CAIAQ_USB_STR_LEN];
+	char serial[CAIAQ_USB_STR_LEN];
+
+	int n_streams, n_audio_in, n_audio_out;
+	int streaming, first_packet, output_running;
+	int audio_in_buf_pos[MAX_STREAMS];
+	int audio_out_buf_pos[MAX_STREAMS];
+	int period_in_count[MAX_STREAMS];
+	int period_out_count[MAX_STREAMS];
+	int input_panic, output_panic;
+	char *audio_in_buf, *audio_out_buf;
+	unsigned int samplerates;
+
+	struct snd_pcm_substream *sub_playback[MAX_STREAMS];
+	struct snd_pcm_substream *sub_capture[MAX_STREAMS];
+
+	/* Linux input */
+#ifdef CONFIG_SND_USB_CAIAQ_INPUT
+	struct input_dev *input_dev;
+#endif
+	
+	/* ALSA */
+	struct snd_pcm *pcm;
+	struct snd_pcm_hardware pcm_info;
+	struct snd_rawmidi *rmidi;
+	struct snd_rawmidi_substream *midi_receive_substream;
+	struct snd_rawmidi_substream *midi_out_substream;
+};
+
+struct snd_usb_caiaq_cb_info {
+	struct snd_usb_caiaqdev *dev;
+	int index;
+};
+
+#define caiaqdev(c) ((struct snd_usb_caiaqdev*)(c)->private_data)
+
+int snd_usb_caiaq_set_audio_params (struct snd_usb_caiaqdev *dev, int rate, int depth, int bbp);
+int snd_usb_caiaq_set_auto_msg (struct snd_usb_caiaqdev *dev, int digital, int analog, int erp);
+
+
+#endif /* CAIAQ_DEVICE_H */
diff -Nur alsa-driver/usb/caiaq/caiaq-input.c alsa-driver-ni/usb/caiaq/caiaq-input.c
--- alsa-driver/usb/caiaq/caiaq-input.c	1970-01-01 01:00:00.000000000 +0100
+++ alsa-driver-ni/usb/caiaq/caiaq-input.c	2007-03-26 16:04:58.000000000 +0200
@@ -0,0 +1,246 @@
+/*
+ *   Copyright (c) 2006,2007 Daniel Mack, Tim Ruetz
+ *
+ *   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/init.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/input.h>
+#include <linux/usb.h>
+#include <linux/spinlock.h>
+#include <sound/driver.h>
+#include <sound/core.h>
+#include <sound/rawmidi.h>
+#include <sound/pcm.h>
+#include "caiaq-device.h"
+#include "caiaq-input.h"
+
+#ifdef CONFIG_SND_USB_CAIAQ_INPUT
+
+static unsigned char keycode_ak1[] =  { KEY_C, KEY_B, KEY_A };
+static unsigned char keycode_rk2[] =  { KEY_1, KEY_2, KEY_3, KEY_4, 
+					KEY_5, KEY_6, KEY_7 };
+
+#define DEG90  (range/2)
+#define DEG180 (range)
+#define DEG270 (DEG90 + DEG180)
+#define DEG360 (DEG180 * 2)
+#define HIGH_PEAK (268)
+#define LOW_PEAK (-7)
+
+/* some of these devices have endless rotation potentiometers
+ * built in which use two tapers, 90 degrees phase shifted.
+ * this algorithm decodes them to one single value, ranging
+ * from 0 to 999 */
+unsigned int decode_erp(unsigned char a, unsigned char b)
+{
+	int weight_a, weight_b;
+	int pos_a, pos_b;
+	int ret;
+	int range = HIGH_PEAK - LOW_PEAK;
+	int mid_value = (HIGH_PEAK + LOW_PEAK) / 2;
+
+	weight_b = abs(mid_value-a) - (range/2 - 100)/2;
+	
+	if (weight_b < 0)
+		weight_b = 0;
+
+	if (weight_b > 100)
+		weight_b = 100;
+
+	weight_a = 100 - weight_b;
+
+	if (a < mid_value) {
+		/* 0..90 and 270..360 degrees */
+		pos_b = b - LOW_PEAK + DEG270;
+		if (pos_b >= DEG360)
+			pos_b -= DEG360;
+	} else
+		/* 90..270 degrees */
+		pos_b = HIGH_PEAK - b + DEG90;
+
+
+	if (b > mid_value)
+		/* 0..180 degrees */
+		pos_a = a - LOW_PEAK;
+	else
+		/* 180..360 degrees */
+		pos_a = HIGH_PEAK - a + DEG180;
+
+	/* interpolate both slider values, depending on weight factors */
+	/* 0..99 x DEG360 */
+	ret = pos_a * weight_a + pos_b * weight_b;
+
+	/* normalize to 0..999 */
+	ret *= 10;
+	ret /= DEG360;
+
+	if (ret < 0)
+		ret += 1000;
+	
+	if (ret >= 1000)
+		ret -= 1000;
+
+	return ret;
+}
+
+#undef DEG90
+#undef DEG180
+#undef DEG270
+#undef DEG360
+#undef HIGH_PEAK
+#undef LOW_PEAK
+
+
+static void snd_caiaq_input_read_analog(struct snd_usb_caiaqdev *dev, 
+					const char *buf, unsigned int len)
+{
+	switch(dev->input_dev->id.product) {
+	case USB_PID_RIGKONTROL2:
+		input_report_abs(dev->input_dev, ABS_X, (buf[4] << 8) |buf[5]);
+		input_report_abs(dev->input_dev, ABS_Y, (buf[0] << 8) |buf[1]);
+		input_report_abs(dev->input_dev, ABS_Z, (buf[2] << 8) |buf[3]);
+		input_sync(dev->input_dev);
+		break;
+	}
+}
+
+static void snd_caiaq_input_read_erp(struct snd_usb_caiaqdev *dev, 
+				     const char *buf, unsigned int len)
+{
+	int i;
+
+	switch(dev->input_dev->id.product) {
+	case USB_PID_AK1:
+		i = decode_erp(buf[0], buf[1]);
+		input_report_abs(dev->input_dev, ABS_X, i);
+		input_sync(dev->input_dev);	
+		break;
+	}
+}
+
+static void snd_caiaq_input_read_io(struct snd_usb_caiaqdev *dev, 
+				    char *buf, unsigned int len)
+{
+	int i;
+	unsigned char *keycode = dev->input_dev->keycode;
+
+	if (!keycode)
+		return;
+
+	if (dev->input_dev->id.product == USB_PID_RIGKONTROL2)
+		for (i=0; i<len; i++)
+			buf[i] = ~buf[i];
+
+	for (i=0; (i<dev->input_dev->keycodemax) && (i < len); i++)
+		input_report_key(dev->input_dev, keycode[i], 
+					buf[i/8] & (1 << (i%8)));
+
+	input_sync(dev->input_dev);
+}
+
+void snd_usb_caiaq_input_dispatch(struct snd_usb_caiaqdev *dev, 
+				  char *buf, 
+				  unsigned int len)
+{
+	if (!dev->input_dev || (len < 1))
+		return;
+
+	switch (buf[0]) {
+	case EP1_CMD_READ_ANALOG:
+		snd_caiaq_input_read_analog(dev, buf+1, len-1);
+		break;
+	case EP1_CMD_READ_ERP:
+		snd_caiaq_input_read_erp(dev, buf+1, len-1);
+		break;
+	case EP1_CMD_READ_IO:
+		snd_caiaq_input_read_io(dev, buf+1, len-1);
+		break;
+	}
+}
+
+int snd_usb_caiaq_input_init(struct snd_usb_caiaqdev *dev)
+{
+	struct usb_device *usb_dev = dev->chip.dev;
+	struct input_device *input;
+	int i, ret;
+
+	input = input_allocate_device();
+	if (!input)
+		return -ENOMEM;
+
+	input->name = dev->product_name;
+	input->id.bustype = BUS_USB;
+	input->id.vendor  = usb_dev->descriptor.idVendor;
+	input->id.product = usb_dev->descriptor.idProduct;
+	input->id.version = usb_dev->descriptor.bcdDevice;
+
+        switch (dev->chip.usb_id) {
+	case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_RIGKONTROL2):
+		input->evbit[0] = BIT(EV_KEY) | BIT(EV_ABS);
+		input->absbit[0] = BIT(ABS_X) | BIT(ABS_Y) | BIT(ABS_Z);
+		input->keycode = keycode_rk2;
+		input->keycodesize = sizeof(char);
+		input->keycodemax = ARRAY_SIZE(keycode_rk2);
+		for (i=0; i<ARRAY_SIZE(keycode_rk2); i++)
+			set_bit(keycode_rk2[i], input->keybit);
+
+		input_set_abs_params(input, ABS_X, 0, 4096, 0, 10);
+		input_set_abs_params(input, ABS_Y, 0, 4096, 0, 10);
+		input_set_abs_params(input, ABS_Z, 0, 4096, 0, 10);
+		snd_usb_caiaq_set_auto_msg(dev, 1, 10, 0);
+		break;
+	case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_AK1):
+		input->evbit[0] = BIT(EV_KEY) | BIT(EV_ABS);
+		input->absbit[0] = BIT(ABS_X);
+		input->keycode = keycode_ak1;
+		input->keycodesize = sizeof(char);
+		input->keycodemax = ARRAY_SIZE(keycode_ak1);
+		for (i=0; i<ARRAY_SIZE(keycode_ak1); i++)
+			set_bit(keycode_ak1[i], input->keybit);
+
+		input_set_abs_params(input, ABS_X, 0, 999, 0, 10);
+		snd_usb_caiaq_set_auto_msg(dev, 1, 0, 5);
+		break;
+	default:
+		/* no input methods supported on this device */
+		input_free_device(input);
+		return 0;
+	}
+
+	ret = input_register_device(input);
+	if (ret < 0) {
+		input_free_device(input);
+		return ret;
+	}
+
+	dev->input_dev = input;
+	return 0;
+}
+
+void snd_usb_caiaq_input_free(struct snd_usb_caiaqdev *dev)
+{
+	if (!dev || !dev->input_dev)
+		return;
+
+	input_unregister_device(dev->input_dev);
+	input_free_device(dev->input_dev);
+	dev->input_dev = NULL;
+}
+
+#endif /* CONFIG_SND_USB_CAIAQ_INPUT */
+
diff -Nur alsa-driver/usb/caiaq/caiaq-input.h alsa-driver-ni/usb/caiaq/caiaq-input.h
--- alsa-driver/usb/caiaq/caiaq-input.h	1970-01-01 01:00:00.000000000 +0100
+++ alsa-driver-ni/usb/caiaq/caiaq-input.h	2007-02-28 15:46:47.000000000 +0100
@@ -0,0 +1,8 @@
+#ifndef CAIAQ_INPUT_H
+#define CAIAQ_INPUT_H
+
+void snd_usb_caiaq_input_dispatch(struct snd_usb_caiaqdev *dev, char *buf, unsigned int len);
+int snd_usb_caiaq_input_init(struct snd_usb_caiaqdev *dev);
+void snd_usb_caiaq_input_free(struct snd_usb_caiaqdev *dev);
+
+#endif
diff -Nur alsa-driver/usb/caiaq/caiaq-midi.c alsa-driver-ni/usb/caiaq/caiaq-midi.c
--- alsa-driver/usb/caiaq/caiaq-midi.c	1970-01-01 01:00:00.000000000 +0100
+++ alsa-driver-ni/usb/caiaq/caiaq-midi.c	2007-03-05 13:24:38.000000000 +0100
@@ -0,0 +1,179 @@
+/*
+ *   Copyright (c) 2006,2007 Daniel Mack
+ *
+ *   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/init.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/interrupt.h>
+#include <linux/usb.h>
+#include <linux/input.h>
+#include <linux/spinlock.h>
+#include <sound/driver.h>
+#include <sound/core.h>
+#include <sound/rawmidi.h>
+#include <sound/pcm.h>
+
+#include "caiaq-device.h"
+#include "caiaq-midi.h"
+
+
+static int snd_usb_caiaq_midi_input_open(struct snd_rawmidi_substream *substream)
+{
+	return 0;
+}
+
+static int snd_usb_caiaq_midi_input_close(struct snd_rawmidi_substream *substream)
+{
+	return 0;
+}
+
+static void snd_usb_caiaq_midi_input_trigger(struct snd_rawmidi_substream *substream, int up)
+{
+	struct snd_usb_caiaqdev *dev = (struct snd_usb_caiaqdev *)
+		substream->rmidi->private_data;
+
+	if (!dev)
+		return;
+	
+	dev->midi_receive_substream = up ? substream : NULL;
+}
+
+
+static int snd_usb_caiaq_midi_output_open(struct snd_rawmidi_substream *substream)
+{
+	return 0;
+}
+
+static int snd_usb_caiaq_midi_output_close(struct snd_rawmidi_substream *substream)
+{
+	return 0;
+}
+
+static void snd_usb_caiaq_midi_send(struct snd_usb_caiaqdev *dev,
+				    struct snd_rawmidi_substream *substream)
+{
+	int len, ret;
+	
+	dev->midi_out_buf[0] = EP1_CMD_MIDI_WRITE;
+	dev->midi_out_buf[1] = 0; /* port */
+	len = snd_rawmidi_transmit_peek(substream, dev->midi_out_buf+3, EP1_BUFSIZE-3);
+	
+	if (len <= 0)
+		return;
+	
+	dev->midi_out_buf[2] = len;
+	dev->midi_out_urb.transfer_buffer_length = len+3;
+	
+	ret = usb_submit_urb(&dev->midi_out_urb, GFP_ATOMIC);
+	if (ret < 0)
+		log("snd_usb_caiaq_midi_send(%p): usb_submit_urb() failed, %d\n",
+				substream, ret);
+}
+
+static void snd_usb_caiaq_midi_output_trigger(struct snd_rawmidi_substream *substream, int up)
+{
+	struct snd_usb_caiaqdev *dev = (struct snd_usb_caiaqdev *) 
+					substream->rmidi->private_data;
+	
+	if (dev->midi_out_substream != NULL)
+		return;
+	
+	if (!up) {
+		dev->midi_out_substream = NULL;
+		return;
+	}
+	
+	dev->midi_out_substream = substream;
+	snd_usb_caiaq_midi_send(dev, substream);
+}
+
+
+static struct snd_rawmidi_ops snd_usb_caiaq_midi_output =
+{
+	.open =		snd_usb_caiaq_midi_output_open,
+	.close =	snd_usb_caiaq_midi_output_close,
+	.trigger =      snd_usb_caiaq_midi_output_trigger,
+};
+
+static struct snd_rawmidi_ops snd_usb_caiaq_midi_input =
+{
+	.open =		snd_usb_caiaq_midi_input_open,
+	.close =	snd_usb_caiaq_midi_input_close,
+	.trigger =      snd_usb_caiaq_midi_input_trigger,
+};
+
+void snd_usb_caiaq_midi_handle_input(struct snd_usb_caiaqdev *dev, 
+				     int port, const char *buf, int len)
+{
+	if (!dev->midi_receive_substream)
+		return;
+	
+	snd_rawmidi_receive(dev->midi_receive_substream, buf, len);
+}
+
+int __devinit snd_usb_caiaq_midi_init(struct snd_usb_caiaqdev *device)
+{
+	int ret;
+	struct snd_rawmidi *rmidi;
+
+	ret = snd_rawmidi_new(device->chip.card, device->product_name, 0,
+					device->spec.num_midi_out,
+					device->spec.num_midi_in,
+					&rmidi);
+
+	if (ret < 0)
+		return ret;
+
+	strcpy(rmidi->name, device->product_name);
+
+	rmidi->info_flags = SNDRV_RAWMIDI_INFO_DUPLEX;
+	rmidi->private_data = device;
+
+	if (device->spec.num_midi_out > 0) {
+		rmidi->info_flags |= SNDRV_RAWMIDI_INFO_OUTPUT;
+		snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, 
+				    &snd_usb_caiaq_midi_output);
+	}
+
+	if (device->spec.num_midi_in > 0) {
+		rmidi->info_flags |= SNDRV_RAWMIDI_INFO_INPUT;
+		snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT, 
+				    &snd_usb_caiaq_midi_input);
+	}
+	
+	device->rmidi = rmidi;
+
+	return 0;
+}
+
+void snd_usb_caiaq_midi_output_done(struct urb* urb)
+{
+	struct snd_usb_caiaqdev *dev = (struct snd_usb_caiaqdev *) urb->context;
+      	char *buf = urb->transfer_buffer;
+	
+	if (urb->status != 0)
+		return;
+
+	if (!dev->midi_out_substream)
+		return;
+
+	snd_rawmidi_transmit_ack(dev->midi_out_substream, buf[2]);
+	dev->midi_out_substream = NULL;
+	snd_usb_caiaq_midi_send(dev, dev->midi_out_substream);
+}
+
diff -Nur alsa-driver/usb/caiaq/caiaq-midi.h alsa-driver-ni/usb/caiaq/caiaq-midi.h
--- alsa-driver/usb/caiaq/caiaq-midi.h	1970-01-01 01:00:00.000000000 +0100
+++ alsa-driver-ni/usb/caiaq/caiaq-midi.h	2007-02-28 15:46:47.000000000 +0100
@@ -0,0 +1,8 @@
+#ifndef CAIAQ_MIDI_H
+#define CAIAQ_MIDI_H
+
+int snd_usb_caiaq_midi_init(struct snd_usb_caiaqdev *dev);
+void snd_usb_caiaq_midi_handle_input(struct snd_usb_caiaqdev *dev, int port, const char *buf, int len);
+void snd_usb_caiaq_midi_output_done(struct urb* urb);
+
+#endif /* CAIAQ_MIDI_H */


More information about the Alsa-devel mailing list