[ALSA] Add Native Instrument usb audio device support
authorDaniel Mack <daniel@caiaq.org>
Mon, 26 Mar 2007 17:11:24 +0000 (19:11 +0200)
committerJaroslav Kysela <perex@suse.cz>
Fri, 11 May 2007 14:55:53 +0000 (16:55 +0200)
Add snd-usb-caiaq driver to support caiaq usb-audio devices from
Native Instrument:
* Native Instruments RigKontrol2
* Native Instruments Kore Controller
* Native Instruments Audio Kontrol 1
* Native Instruments Audio 8 DJ

Signed-off-by: Daniel Mack <daniel@caiaq.org>
Signed-off-by: Karsten Wiese <fzu@wemgehoertderstaat.de>
Signed-off-by: Takashi Iwai <tiwai@suse.de>
Signed-off-by: Jaroslav Kysela <perex@suse.cz>
12 files changed:
Documentation/sound/alsa/ALSA-Configuration.txt
sound/usb/Kconfig
sound/usb/Makefile
sound/usb/caiaq/Makefile [new file with mode: 0644]
sound/usb/caiaq/caiaq-audio.c [new file with mode: 0644]
sound/usb/caiaq/caiaq-audio.h [new file with mode: 0644]
sound/usb/caiaq/caiaq-device.c [new file with mode: 0644]
sound/usb/caiaq/caiaq-device.h [new file with mode: 0644]
sound/usb/caiaq/caiaq-input.c [new file with mode: 0644]
sound/usb/caiaq/caiaq-input.h [new file with mode: 0644]
sound/usb/caiaq/caiaq-midi.c [new file with mode: 0644]
sound/usb/caiaq/caiaq-midi.h [new file with mode: 0644]

index 0dbc95dd66af0c335214d275e0ed01da57aea8a0..62f9e4cebe08148d43ca533a6ce7488b3f4bcd80 100644 (file)
@@ -1697,6 +1697,17 @@ Prior to version 0.9.0rc4 options had a 'snd_' prefix. This was removed.
 
     This module supports multiple devices, autoprobe and hotplugging.
 
+  Module snd-usb-caiaq
+  --------------------
+
+    Module for caiaq UB audio interfaces,
+           * Native Instruments RigKontrol2
+           * Native Instruments Kore Controller
+           * Native Instruments Audio Kontrol 1
+           * Native Instruments Audio 8 DJ
+
+    This module supports multiple devices, autoprobe and hotplugging.
+
   Module snd-usb-usx2y
   --------------------
 
index f05d02f5b69f9233aca85c8927446db08b2fd27e..315360f312787cd0ef116a33d6af9ebd51b5fc07 100644 (file)
@@ -29,5 +29,33 @@ config SND_USB_USX2Y
          To compile this driver as a module, choose M here: the module
          will be called snd-usb-usx2y.
 
+config SND_USB_CAIAQ
+       tristate "Native Instruments USB audio devices"
+        depends on SND && USB
+        select SND_HWDEP
+        select SND_RAWMIDI
+        select SND_PCM
+        help
+          Say Y here to include support for caiaq USB audio interfaces,
+          namely:
+
+           * Native Instruments RigKontrol2
+           * Native Instruments Kore Controller
+           * Native Instruments Audio Kontrol 1
+           * Native Instruments Audio 8 DJ
+
+          To compile this driver as a module, choose M here: the module
+          will be called snd-usb-caiaq.
+
+config SND_USB_CAIAQ_INPUT
+       bool "enable input device for controllers"
+       depends on SND_USB_CAIAQ
+       help
+         Say Y here to support input controllers like buttons, knobs,
+         alpha dials and analog pedals on the following products:
+
+          * Native Instruments RigKontrol2
+          * Native Instruments Audio Kontrol 1
+
 endmenu
 
index 2c1dc11a72e2f979d3f864c21a1db203f97c6d37..aa252ef2ebfbd3ab81da82a89f7883ac49132190 100644 (file)
@@ -9,4 +9,4 @@ snd-usb-lib-objs := usbmidi.o
 obj-$(CONFIG_SND_USB_AUDIO) += snd-usb-audio.o snd-usb-lib.o
 obj-$(CONFIG_SND_USB_USX2Y) += snd-usb-lib.o
 
-obj-$(CONFIG_SND) += usx2y/
+obj-$(CONFIG_SND) += usx2y/ caiaq/
diff --git a/sound/usb/caiaq/Makefile b/sound/usb/caiaq/Makefile
new file mode 100644 (file)
index 0000000..455c8c5
--- /dev/null
@@ -0,0 +1,3 @@
+snd-usb-caiaq-objs := caiaq-device.o caiaq-audio.o caiaq-midi.o caiaq-input.o
+
+obj-$(CONFIG_SND_USB_CAIAQ) += snd-usb-caiaq.o
diff --git a/sound/usb/caiaq/caiaq-audio.c b/sound/usb/caiaq/caiaq-audio.c
new file mode 100644 (file)
index 0000000..e80c8db
--- /dev/null
@@ -0,0 +1,706 @@
+/*
+ *   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++) {
+               sub = subs[stream];
+               if (!sub)
+                       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++) {
+                       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;
+                       }
+               }
+       }
+       
+       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++) {
+                       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;
+                       }
+               }
+       }
+
+       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++) {
+                       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->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 = 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 =
+               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 --git a/sound/usb/caiaq/caiaq-audio.h b/sound/usb/caiaq/caiaq-audio.h
new file mode 100644 (file)
index 0000000..8ab1f8d
--- /dev/null
@@ -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 --git a/sound/usb/caiaq/caiaq-device.c b/sound/usb/caiaq/caiaq-device.c
new file mode 100644 (file)
index 0000000..4709347
--- /dev/null
@@ -0,0 +1,436 @@
+/*
+ * caiaq.c: ALSA driver for caiaq/NativeInstruments devices
+ *
+ *   Copyright (c) 2007 Daniel Mack <daniel@caiaq.de>
+ *                      Karsten Wiese <fzu@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@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));
+               dev->spec.fw_version = le16_to_cpu(dev->spec.fw_version);
+               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 --git a/sound/usb/caiaq/caiaq-device.h b/sound/usb/caiaq/caiaq-device.h
new file mode 100644 (file)
index 0000000..088d5ec
--- /dev/null
@@ -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 --git a/sound/usb/caiaq/caiaq-input.c b/sound/usb/caiaq/caiaq-input.c
new file mode 100644 (file)
index 0000000..3acd12d
--- /dev/null
@@ -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 */
+static 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_dev *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 --git a/sound/usb/caiaq/caiaq-input.h b/sound/usb/caiaq/caiaq-input.h
new file mode 100644 (file)
index 0000000..ced5355
--- /dev/null
@@ -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 --git a/sound/usb/caiaq/caiaq-midi.c b/sound/usb/caiaq/caiaq-midi.c
new file mode 100644 (file)
index 0000000..793ca20
--- /dev/null
@@ -0,0 +1,177 @@
+/*
+ *   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 = 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 = 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 = 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 --git a/sound/usb/caiaq/caiaq-midi.h b/sound/usb/caiaq/caiaq-midi.h
new file mode 100644 (file)
index 0000000..9d16db0
--- /dev/null
@@ -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 */