diff options
| author | Jinke Han <jinkehan@didiglobal.com> | 2026-09-08 15:37:42 +0800 |
|---|---|---|
| committer | Ingo Molnar <mingo@kernel.org> | 2026-09-17 09:48:42 +0200 |
| commit | a5f7a5bb3b7f28ba7e4fa246775b29a0e5537255 (patch) | |
| tree | 73fef824f9a846cf935869d0dbc5070ef6e2f741 /sound/usb/caiaq | |
| download | linux-stable-a5f7a5bb3b7f28ba7e4fa246775b29a0e5537255.tar.gz linux-stable-a5f7a5bb3b7f28ba7e4fa246775b29a0e5537255.zip | |
x86/kprobes: Fix crash when probing CS CALL instructionsgrafted
When using eBPF to probe CS CALL instructions within a function,
a crash can be triggered.
The eBPF tool probes offset 257 of the __hrtimer_run_queues()
function:
<__hrtimer_run_queues+249>: nopl 0x0(%rax,%rax,1)
<__hrtimer_run_queues+254>: mov %r14,%rdi
<__hrtimer_run_queues+257>: cs call <__x86_indirect_thunk_r12>
<__hrtimer_run_queues+263>: mov %eax,%r12d
<__hrtimer_run_queues+266>: xchg %ax,%ax
<__hrtimer_run_queues+268>: mov %r13,%rdi
Which triggers this crash:
BUG: unable to handle page fault for address: 00000000000f41c9
#PF: supervisor write access in kernel mode
#PF: error_code(0x0002) - not-present page
PGD 0 P4D 0
Oops: 0002 [#1] SMP NOPTI
CPU: 1 PID: 0 Comm: swapper/1 Kdump: loaded Tainted: P
RIP: 0010:__hrtimer_run_queues+0x106/0x230
Note that __hrtimer_run_queues+0x106 is __hrtimer_run_queues+262, which is
at the 6th byte of the above CS CALL instruction. Since the CS CALL
instruction occupies 6 bytes, the exception occurred in the middle of that
call instruction.
The root cause is that when using eBPF tools to probe in the middle of a
function, a kprobe with INT3 is used as the underlying implementation.
During single-step emulation of the original CALL instruction,
int3_emulate_call() assumes that the probed CALL instruction is 5 bytes
long. However, the actual CS-prefixed CALL instruction occupies 6 bytes,
so it constructs an incorrect exception return address. When the CPU
returns from the kprobe handler, the next instruction to be executed is at
the address of the last byte of that CS CALL instruction. Coincidentally,
starting from that address, the CPU fetches and decodes a completely
different instruction, which ultimately triggers a kernel crash.
Fix the issue by using the actual instruction length obtained from
the instruction decoder when constructing the exception return
address, rather than relying on the hardcoded CALL_INSN_SIZE macro.
[ mingo: Refined the changelog ]
Fixes: 6256e668b7af ("x86/kprobes: Use int3 instead of debug trap for single-step")
Suggested-by: Masami Hiramatsu (Google) <mhiramat@kernel.org>
Signed-off-by: Jinke Han <jinkehan@didiglobal.com>
Signed-off-by: Ingo Molnar <mingo@kernel.org>
Reviewed-by: Masami Hiramatsu (Google) <mhiramat@kernel.org>
Acked-by: Yafang Shao <laoar.shao@gmail.com>
Acked-by: Borislav Petkov <bp@alien8.de>
Cc: Peter Zijlstra <peterz@infradead.org>
Link: https://patch.msgid.link/20260908073742.GA10517@didi-ThinkCentre-M920t-N000
Diffstat (limited to 'sound/usb/caiaq')
| -rw-r--r-- | sound/usb/caiaq/Makefile | 5 | ||||
| -rw-r--r-- | sound/usb/caiaq/audio.c | 863 | ||||
| -rw-r--r-- | sound/usb/caiaq/audio.h | 9 | ||||
| -rw-r--r-- | sound/usb/caiaq/control.c | 663 | ||||
| -rw-r--r-- | sound/usb/caiaq/control.h | 7 | ||||
| -rw-r--r-- | sound/usb/caiaq/device.c | 606 | ||||
| -rw-r--r-- | sound/usb/caiaq/device.h | 138 | ||||
| -rw-r--r-- | sound/usb/caiaq/input.c | 865 | ||||
| -rw-r--r-- | sound/usb/caiaq/input.h | 10 | ||||
| -rw-r--r-- | sound/usb/caiaq/midi.c | 162 | ||||
| -rw-r--r-- | sound/usb/caiaq/midi.h | 10 |
11 files changed, 3338 insertions, 0 deletions
diff --git a/sound/usb/caiaq/Makefile b/sound/usb/caiaq/Makefile new file mode 100644 index 000000000..9a99c17a2 --- /dev/null +++ b/sound/usb/caiaq/Makefile @@ -0,0 +1,5 @@ +# SPDX-License-Identifier: GPL-2.0-only +snd-usb-caiaq-y := device.o audio.o midi.o control.o +snd-usb-caiaq-$(CONFIG_SND_USB_CAIAQ_INPUT) += input.o + +obj-$(CONFIG_SND_USB_CAIAQ) += snd-usb-caiaq.o diff --git a/sound/usb/caiaq/audio.c b/sound/usb/caiaq/audio.c new file mode 100644 index 000000000..bb6280aa3 --- /dev/null +++ b/sound/usb/caiaq/audio.c @@ -0,0 +1,863 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2006-2008 Daniel Mack, Karsten Wiese +*/ + +#include <linux/device.h> +#include <linux/spinlock.h> +#include <linux/slab.h> +#include <linux/init.h> +#include <linux/usb.h> +#include <sound/core.h> +#include <sound/pcm.h> + +#include "device.h" +#include "audio.h" + +#define N_URBS 32 +#define CLOCK_DRIFT_TOLERANCE 5 +#define FRAMES_PER_URB 8 +#define BYTES_PER_FRAME 512 +#define CHANNELS_PER_STREAM 2 +#define BYTES_PER_SAMPLE 3 +#define BYTES_PER_SAMPLE_USB 4 +#define MAX_BUFFER_SIZE (128*1024) +#define MAX_ENDPOINT_SIZE 512 + +#define ENDPOINT_CAPTURE 2 +#define ENDPOINT_PLAYBACK 6 + +#define MAKE_CHECKBYTE(cdev,stream,i) \ + (stream << 1) | (~(i / (cdev->n_streams * BYTES_PER_SAMPLE_USB)) & 1) + +static const struct snd_pcm_hardware snd_usb_caiaq_pcm_hardware = { + .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER), + .formats = SNDRV_PCM_FMTBIT_S24_3BE, + .rates = (SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 | + SNDRV_PCM_RATE_96000), + .rate_min = 44100, + .rate_max = 0, /* will overwrite later */ + .channels_min = CHANNELS_PER_STREAM, + .channels_max = CHANNELS_PER_STREAM, + .buffer_bytes_max = MAX_BUFFER_SIZE, + .period_bytes_min = 128, + .period_bytes_max = MAX_BUFFER_SIZE, + .periods_min = 1, + .periods_max = 1024, +}; + +static void +activate_substream(struct snd_usb_caiaqdev *cdev, + struct snd_pcm_substream *sub) +{ + guard(spinlock)(&cdev->spinlock); + + if (sub->stream == SNDRV_PCM_STREAM_PLAYBACK) + cdev->sub_playback[sub->number] = sub; + else + cdev->sub_capture[sub->number] = sub; +} + +static void +deactivate_substream(struct snd_usb_caiaqdev *cdev, + struct snd_pcm_substream *sub) +{ + guard(spinlock_irqsave)(&cdev->spinlock); + + if (sub->stream == SNDRV_PCM_STREAM_PLAYBACK) + cdev->sub_playback[sub->number] = NULL; + else + cdev->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 *cdev) +{ + int i, ret; + struct device *dev = caiaqdev_to_dev(cdev); + + dev_dbg(dev, "%s(%p)\n", __func__, cdev); + + if (cdev->streaming) + return -EINVAL; + + memset(cdev->sub_playback, 0, sizeof(cdev->sub_playback)); + memset(cdev->sub_capture, 0, sizeof(cdev->sub_capture)); + cdev->input_panic = 0; + cdev->output_panic = 0; + cdev->first_packet = 4; + cdev->streaming = 1; + cdev->warned = 0; + + for (i = 0; i < N_URBS; i++) { + ret = usb_submit_urb(cdev->data_urbs_in[i], GFP_ATOMIC); + if (ret) { + dev_err(dev, "unable to trigger read #%d! (ret %d)\n", + i, ret); + cdev->streaming = 0; + return -EPIPE; + } + } + + return 0; +} + +static void stream_stop(struct snd_usb_caiaqdev *cdev) +{ + int i; + struct device *dev = caiaqdev_to_dev(cdev); + + dev_dbg(dev, "%s(%p)\n", __func__, cdev); + if (!cdev->streaming) + return; + + cdev->streaming = 0; + + for (i = 0; i < N_URBS; i++) { + usb_kill_urb(cdev->data_urbs_in[i]); + + if (test_bit(i, &cdev->outurb_active_mask)) + usb_kill_urb(cdev->data_urbs_out[i]); + } + + cdev->outurb_active_mask = 0; +} + +static int snd_usb_caiaq_substream_open(struct snd_pcm_substream *substream) +{ + struct snd_usb_caiaqdev *cdev = snd_pcm_substream_chip(substream); + struct device *dev = caiaqdev_to_dev(cdev); + + dev_dbg(dev, "%s(%p)\n", __func__, substream); + substream->runtime->hw = cdev->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 *cdev = snd_pcm_substream_chip(substream); + struct device *dev = caiaqdev_to_dev(cdev); + + dev_dbg(dev, "%s(%p)\n", __func__, substream); + if (all_substreams_zero(cdev->sub_playback) && + all_substreams_zero(cdev->sub_capture)) { + /* when the last client has stopped streaming, + * all sample rates are allowed again */ + stream_stop(cdev); + cdev->pcm_info.rates = cdev->samplerates; + } + + return 0; +} + +static int snd_usb_caiaq_pcm_hw_free(struct snd_pcm_substream *sub) +{ + struct snd_usb_caiaqdev *cdev = snd_pcm_substream_chip(sub); + deactivate_substream(cdev, sub); + return 0; +} + +static int snd_usb_caiaq_pcm_prepare(struct snd_pcm_substream *substream) +{ + int bytes_per_sample, bpp, ret; + int index = substream->number; + struct snd_usb_caiaqdev *cdev = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + struct device *dev = caiaqdev_to_dev(cdev); + + dev_dbg(dev, "%s(%p)\n", __func__, substream); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + int out_pos; + + switch (cdev->spec.data_alignment) { + case 0: + case 2: + out_pos = BYTES_PER_SAMPLE + 1; + break; + case 3: + default: + out_pos = 0; + break; + } + + cdev->period_out_count[index] = out_pos; + cdev->audio_out_buf_pos[index] = out_pos; + } else { + int in_pos; + + switch (cdev->spec.data_alignment) { + case 0: + in_pos = BYTES_PER_SAMPLE + 2; + break; + case 2: + in_pos = BYTES_PER_SAMPLE; + break; + case 3: + default: + in_pos = 0; + break; + } + + cdev->period_in_count[index] = in_pos; + cdev->audio_in_buf_pos[index] = in_pos; + } + + if (cdev->streaming) + return 0; + + /* the first client that opens a stream defines the sample rate + * setting for all subsequent calls, until the last client closed. */ + cdev->pcm_info.rates = snd_pcm_rate_to_rate_bit(runtime->rate); + snd_pcm_limit_hw_rates(runtime); + + bytes_per_sample = BYTES_PER_SAMPLE; + if (cdev->spec.data_alignment >= 2) + bytes_per_sample++; + + bpp = ((runtime->rate / 8000) + CLOCK_DRIFT_TOLERANCE) + * bytes_per_sample * CHANNELS_PER_STREAM * cdev->n_streams; + + if (bpp > MAX_ENDPOINT_SIZE) + bpp = MAX_ENDPOINT_SIZE; + + ret = snd_usb_caiaq_set_audio_params(cdev, runtime->rate, + runtime->sample_bits, bpp); + if (ret) + return ret; + + ret = stream_start(cdev); + if (ret) + return ret; + + cdev->output_running = 0; + wait_event_timeout(cdev->prepare_wait_queue, cdev->output_running, HZ); + if (!cdev->output_running) { + stream_stop(cdev); + return -EPIPE; + } + + return 0; +} + +static int snd_usb_caiaq_pcm_trigger(struct snd_pcm_substream *sub, int cmd) +{ + struct snd_usb_caiaqdev *cdev = snd_pcm_substream_chip(sub); + struct device *dev = caiaqdev_to_dev(cdev); + + dev_dbg(dev, "%s(%p) cmd %d\n", __func__, sub, cmd); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + activate_substream(cdev, sub); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + deactivate_substream(cdev, sub); + break; + default: + return -EINVAL; + } + + return 0; +} + +static snd_pcm_uframes_t +snd_usb_caiaq_pcm_pointer(struct snd_pcm_substream *sub) +{ + int index = sub->number; + struct snd_usb_caiaqdev *cdev = snd_pcm_substream_chip(sub); + + guard(spinlock)(&cdev->spinlock); + + if (cdev->input_panic || cdev->output_panic) + return SNDRV_PCM_POS_XRUN; + + if (sub->stream == SNDRV_PCM_STREAM_PLAYBACK) + return bytes_to_frames(sub->runtime, + cdev->audio_out_buf_pos[index]); + else + return bytes_to_frames(sub->runtime, + cdev->audio_in_buf_pos[index]); +} + +/* operators for both playback and capture */ +static const struct snd_pcm_ops snd_usb_caiaq_ops = { + .open = snd_usb_caiaq_substream_open, + .close = snd_usb_caiaq_substream_close, + .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 *cdev, + struct snd_pcm_substream **subs) +{ + int stream, pb, *cnt; + struct snd_pcm_substream *sub; + + for (stream = 0; stream < cdev->n_streams; stream++) { + sub = subs[stream]; + if (!sub) + continue; + + pb = snd_pcm_lib_period_bytes(sub); + cnt = (sub->stream == SNDRV_PCM_STREAM_PLAYBACK) ? + &cdev->period_out_count[stream] : + &cdev->period_in_count[stream]; + + if (*cnt >= pb) { + snd_pcm_period_elapsed(sub); + *cnt %= pb; + } + } +} + +static void read_in_urb_mode0(struct snd_usb_caiaqdev *cdev, + 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(cdev->sub_capture)) + return; + + for (i = 0; i < iso->actual_length;) { + for (stream = 0; stream < cdev->n_streams; stream++, i++) { + sub = cdev->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[cdev->audio_in_buf_pos[stream]++] + = usb_buf[i]; + cdev->period_in_count[stream]++; + if (cdev->audio_in_buf_pos[stream] == sz) + cdev->audio_in_buf_pos[stream] = 0; + } + } + } +} + +static void read_in_urb_mode2(struct snd_usb_caiaqdev *cdev, + const struct urb *urb, + const struct usb_iso_packet_descriptor *iso) +{ + unsigned char *usb_buf = urb->transfer_buffer + iso->offset; + unsigned char check_byte; + struct snd_pcm_substream *sub; + int stream, i; + + for (i = 0; i < iso->actual_length;) { + if (i % (cdev->n_streams * BYTES_PER_SAMPLE_USB) == 0) { + for (stream = 0; + stream < cdev->n_streams; + stream++, i++) { + if (cdev->first_packet) + continue; + + check_byte = MAKE_CHECKBYTE(cdev, stream, i); + + if ((usb_buf[i] & 0x3f) != check_byte) + cdev->input_panic = 1; + + if (usb_buf[i] & 0x80) + cdev->output_panic = 1; + } + } + cdev->first_packet = 0; + + for (stream = 0; stream < cdev->n_streams; stream++, i++) { + sub = cdev->sub_capture[stream]; + if (cdev->input_panic) + usb_buf[i] = 0; + + if (sub) { + struct snd_pcm_runtime *rt = sub->runtime; + char *audio_buf = rt->dma_area; + int sz = frames_to_bytes(rt, rt->buffer_size); + audio_buf[cdev->audio_in_buf_pos[stream]++] = + usb_buf[i]; + cdev->period_in_count[stream]++; + if (cdev->audio_in_buf_pos[stream] == sz) + cdev->audio_in_buf_pos[stream] = 0; + } + } + } +} + +static void read_in_urb_mode3(struct snd_usb_caiaqdev *cdev, + const struct urb *urb, + const struct usb_iso_packet_descriptor *iso) +{ + unsigned char *usb_buf = urb->transfer_buffer + iso->offset; + struct device *dev = caiaqdev_to_dev(cdev); + int stream, i; + + /* paranoia check */ + if (iso->actual_length % (BYTES_PER_SAMPLE_USB * CHANNELS_PER_STREAM)) + return; + + for (i = 0; i < iso->actual_length;) { + for (stream = 0; stream < cdev->n_streams; stream++) { + struct snd_pcm_substream *sub = cdev->sub_capture[stream]; + char *audio_buf = NULL; + int c, n, sz = 0; + + if (sub && !cdev->input_panic) { + struct snd_pcm_runtime *rt = sub->runtime; + audio_buf = rt->dma_area; + sz = frames_to_bytes(rt, rt->buffer_size); + } + + for (c = 0; c < CHANNELS_PER_STREAM; c++) { + /* 3 audio data bytes, followed by 1 check byte */ + if (audio_buf) { + for (n = 0; n < BYTES_PER_SAMPLE; n++) { + audio_buf[cdev->audio_in_buf_pos[stream]++] = usb_buf[i+n]; + + if (cdev->audio_in_buf_pos[stream] == sz) + cdev->audio_in_buf_pos[stream] = 0; + } + + cdev->period_in_count[stream] += BYTES_PER_SAMPLE; + } + + i += BYTES_PER_SAMPLE; + + if (usb_buf[i] != ((stream << 1) | c) && + !cdev->first_packet) { + if (!cdev->input_panic) + dev_warn(dev, " EXPECTED: %02x got %02x, c %d, stream %d, i %d\n", + ((stream << 1) | c), usb_buf[i], c, stream, i); + cdev->input_panic = 1; + } + + i++; + } + } + } + + if (cdev->first_packet > 0) + cdev->first_packet--; +} + +static void read_in_urb(struct snd_usb_caiaqdev *cdev, + const struct urb *urb, + const struct usb_iso_packet_descriptor *iso) +{ + struct device *dev = caiaqdev_to_dev(cdev); + + if (!cdev->streaming) + return; + + if (iso->actual_length < cdev->bpp) + return; + + switch (cdev->spec.data_alignment) { + case 0: + read_in_urb_mode0(cdev, urb, iso); + break; + case 2: + read_in_urb_mode2(cdev, urb, iso); + break; + case 3: + read_in_urb_mode3(cdev, urb, iso); + break; + } + + if ((cdev->input_panic || cdev->output_panic) && !cdev->warned) { + dev_warn(dev, "streaming error detected %s %s\n", + cdev->input_panic ? "(input)" : "", + cdev->output_panic ? "(output)" : ""); + cdev->warned = 1; + } +} + +static void fill_out_urb_mode_0(struct snd_usb_caiaqdev *cdev, + struct urb *urb, + const struct usb_iso_packet_descriptor *iso) +{ + unsigned char *usb_buf = urb->transfer_buffer + iso->offset; + struct snd_pcm_substream *sub; + int stream, i; + + for (i = 0; i < iso->length;) { + for (stream = 0; stream < cdev->n_streams; stream++, i++) { + sub = cdev->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[cdev->audio_out_buf_pos[stream]]; + cdev->period_out_count[stream]++; + cdev->audio_out_buf_pos[stream]++; + if (cdev->audio_out_buf_pos[stream] == sz) + cdev->audio_out_buf_pos[stream] = 0; + } else + usb_buf[i] = 0; + } + + /* fill in the check bytes */ + if (cdev->spec.data_alignment == 2 && + i % (cdev->n_streams * BYTES_PER_SAMPLE_USB) == + (cdev->n_streams * CHANNELS_PER_STREAM)) + for (stream = 0; stream < cdev->n_streams; stream++, i++) + usb_buf[i] = MAKE_CHECKBYTE(cdev, stream, i); + } +} + +static void fill_out_urb_mode_3(struct snd_usb_caiaqdev *cdev, + struct urb *urb, + const struct usb_iso_packet_descriptor *iso) +{ + unsigned char *usb_buf = urb->transfer_buffer + iso->offset; + int stream, i; + + for (i = 0; i < iso->length;) { + for (stream = 0; stream < cdev->n_streams; stream++) { + struct snd_pcm_substream *sub = cdev->sub_playback[stream]; + char *audio_buf = NULL; + int c, n, sz = 0; + + if (sub) { + struct snd_pcm_runtime *rt = sub->runtime; + audio_buf = rt->dma_area; + sz = frames_to_bytes(rt, rt->buffer_size); + } + + for (c = 0; c < CHANNELS_PER_STREAM; c++) { + for (n = 0; n < BYTES_PER_SAMPLE; n++) { + if (audio_buf) { + usb_buf[i+n] = audio_buf[cdev->audio_out_buf_pos[stream]++]; + + if (cdev->audio_out_buf_pos[stream] == sz) + cdev->audio_out_buf_pos[stream] = 0; + } else { + usb_buf[i+n] = 0; + } + } + + if (audio_buf) + cdev->period_out_count[stream] += BYTES_PER_SAMPLE; + + i += BYTES_PER_SAMPLE; + + /* fill in the check byte pattern */ + usb_buf[i++] = (stream << 1) | c; + } + } + } +} + +static inline void fill_out_urb(struct snd_usb_caiaqdev *cdev, + struct urb *urb, + const struct usb_iso_packet_descriptor *iso) +{ + switch (cdev->spec.data_alignment) { + case 0: + case 2: + fill_out_urb_mode_0(cdev, urb, iso); + break; + case 3: + fill_out_urb_mode_3(cdev, urb, iso); + break; + } +} + +static void read_completed(struct urb *urb) +{ + struct snd_usb_caiaq_cb_info *info = urb->context; + struct snd_usb_caiaqdev *cdev; + struct device *dev; + struct urb *out = NULL; + int i, frame, len, send_it = 0, outframe = 0; + size_t offset = 0; + + if (urb->status || !info) + return; + + cdev = info->cdev; + dev = caiaqdev_to_dev(cdev); + + if (!cdev->streaming) + return; + + /* find an unused output urb that is unused */ + for (i = 0; i < N_URBS; i++) + if (test_and_set_bit(i, &cdev->outurb_active_mask) == 0) { + out = cdev->data_urbs_out[i]; + break; + } + + if (!out) { + dev_err(dev, "Unable to find an output urb to use\n"); + goto requeue; + } + + /* 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 = offset; + offset += len; + + if (len > 0) { + scoped_guard(spinlock_irqsave, &cdev->spinlock) { + fill_out_urb(cdev, out, &out->iso_frame_desc[outframe]); + read_in_urb(cdev, urb, &urb->iso_frame_desc[frame]); + } + check_for_elapsed_periods(cdev, cdev->sub_playback); + check_for_elapsed_periods(cdev, cdev->sub_capture); + send_it = 1; + } + + outframe++; + } + + if (send_it) { + out->number_of_packets = outframe; + usb_submit_urb(out, GFP_ATOMIC); + } else { + struct snd_usb_caiaq_cb_info *oinfo = out->context; + clear_bit(oinfo->index, &cdev->outurb_active_mask); + } + +requeue: + /* 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; + 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 *cdev = info->cdev; + + if (!cdev->output_running) { + cdev->output_running = 1; + wake_up(&cdev->prepare_wait_queue); + } + + clear_bit(info->index, &cdev->outurb_active_mask); +} + +static struct urb **alloc_urbs(struct snd_usb_caiaqdev *cdev, int dir, int *ret) +{ + int i, frame; + struct urb **urbs; + struct usb_device *usb_dev = cdev->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_objs(*urbs, N_URBS); + if (!urbs) { + *ret = -ENOMEM; + return NULL; + } + + for (i = 0; i < N_URBS; i++) { + urbs[i] = usb_alloc_urb(FRAMES_PER_URB, GFP_KERNEL); + if (!urbs[i]) { + *ret = -ENOMEM; + return urbs; + } + + urbs[i]->transfer_buffer = + kmalloc_array(BYTES_PER_FRAME, FRAMES_PER_URB, + GFP_KERNEL); + if (!urbs[i]->transfer_buffer) { + *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 = &cdev->data_cb_info[i]; + urbs[i]->interval = 1; + urbs[i]->number_of_packets = FRAMES_PER_URB; + urbs[i]->complete = (dir == SNDRV_PCM_STREAM_CAPTURE) ? + read_completed : write_completed; + } + + *ret = 0; + return urbs; +} + +static void free_urbs(struct urb **urbs) +{ + int i; + + if (!urbs) + return; + + for (i = 0; i < N_URBS; i++) { + if (!urbs[i]) + continue; + + usb_kill_urb(urbs[i]); + kfree(urbs[i]->transfer_buffer); + usb_free_urb(urbs[i]); + } + + kfree(urbs); +} + +int snd_usb_caiaq_audio_init(struct snd_usb_caiaqdev *cdev) +{ + int i, ret; + struct device *dev = caiaqdev_to_dev(cdev); + + cdev->n_audio_in = max(cdev->spec.num_analog_audio_in, + cdev->spec.num_digital_audio_in) / + CHANNELS_PER_STREAM; + cdev->n_audio_out = max(cdev->spec.num_analog_audio_out, + cdev->spec.num_digital_audio_out) / + CHANNELS_PER_STREAM; + cdev->n_streams = max(cdev->n_audio_in, cdev->n_audio_out); + + dev_dbg(dev, "cdev->n_audio_in = %d\n", cdev->n_audio_in); + dev_dbg(dev, "cdev->n_audio_out = %d\n", cdev->n_audio_out); + dev_dbg(dev, "cdev->n_streams = %d\n", cdev->n_streams); + + if (cdev->n_streams > MAX_STREAMS) { + dev_err(dev, "unable to initialize device, too many streams.\n"); + return -EINVAL; + } + + if (cdev->n_streams < 1) { + dev_err(dev, "bogus number of streams: %d\n", cdev->n_streams); + return -EINVAL; + } + + ret = snd_pcm_new(cdev->chip.card, cdev->product_name, 0, + cdev->n_audio_out, cdev->n_audio_in, &cdev->pcm); + + if (ret < 0) { + dev_err(dev, "snd_pcm_new() returned %d\n", ret); + return ret; + } + + cdev->pcm->private_data = cdev; + strscpy(cdev->pcm->name, cdev->product_name, sizeof(cdev->pcm->name)); + + memset(cdev->sub_playback, 0, sizeof(cdev->sub_playback)); + memset(cdev->sub_capture, 0, sizeof(cdev->sub_capture)); + + memcpy(&cdev->pcm_info, &snd_usb_caiaq_pcm_hardware, + sizeof(snd_usb_caiaq_pcm_hardware)); + + /* setup samplerates */ + cdev->samplerates = cdev->pcm_info.rates; + switch (cdev->chip.usb_id) { + case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_AK1): + case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_RIGKONTROL3): + case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_SESSIONIO): + case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_GUITARRIGMOBILE): + cdev->samplerates |= SNDRV_PCM_RATE_192000; + fallthrough; + case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_AUDIO2DJ): + case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_AUDIO4DJ): + case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_AUDIO8DJ): + case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_TRAKTORAUDIO2): + cdev->samplerates |= SNDRV_PCM_RATE_88200; + break; + } + + snd_pcm_set_ops(cdev->pcm, SNDRV_PCM_STREAM_PLAYBACK, + &snd_usb_caiaq_ops); + snd_pcm_set_ops(cdev->pcm, SNDRV_PCM_STREAM_CAPTURE, + &snd_usb_caiaq_ops); + snd_pcm_set_managed_buffer_all(cdev->pcm, SNDRV_DMA_TYPE_VMALLOC, + NULL, 0, 0); + + cdev->data_cb_info = + kmalloc_objs(struct snd_usb_caiaq_cb_info, N_URBS); + + if (!cdev->data_cb_info) + return -ENOMEM; + + cdev->outurb_active_mask = 0; + BUILD_BUG_ON(N_URBS > (sizeof(cdev->outurb_active_mask) * 8)); + + for (i = 0; i < N_URBS; i++) { + cdev->data_cb_info[i].cdev = cdev; + cdev->data_cb_info[i].index = i; + } + + cdev->data_urbs_in = alloc_urbs(cdev, SNDRV_PCM_STREAM_CAPTURE, &ret); + if (ret < 0) { + snd_usb_caiaq_audio_free(cdev); + return ret; + } + + cdev->data_urbs_out = alloc_urbs(cdev, SNDRV_PCM_STREAM_PLAYBACK, &ret); + if (ret < 0) { + snd_usb_caiaq_audio_free(cdev); + return ret; + } + + return 0; +} + +void snd_usb_caiaq_audio_disconnect(struct snd_usb_caiaqdev *cdev) +{ + struct device *dev = caiaqdev_to_dev(cdev); + + dev_dbg(dev, "%s(%p)\n", __func__, cdev); + stream_stop(cdev); +} + +void snd_usb_caiaq_audio_free(struct snd_usb_caiaqdev *cdev) +{ + struct device *dev = caiaqdev_to_dev(cdev); + + dev_dbg(dev, "%s(%p)\n", __func__, cdev); + free_urbs(cdev->data_urbs_in); + cdev->data_urbs_in = NULL; + free_urbs(cdev->data_urbs_out); + cdev->data_urbs_out = NULL; + kfree(cdev->data_cb_info); + cdev->data_cb_info = NULL; +} diff --git a/sound/usb/caiaq/audio.h b/sound/usb/caiaq/audio.h new file mode 100644 index 000000000..07f5d0644 --- /dev/null +++ b/sound/usb/caiaq/audio.h @@ -0,0 +1,9 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef CAIAQ_AUDIO_H +#define CAIAQ_AUDIO_H + +int snd_usb_caiaq_audio_init(struct snd_usb_caiaqdev *cdev); +void snd_usb_caiaq_audio_disconnect(struct snd_usb_caiaqdev *cdev); +void snd_usb_caiaq_audio_free(struct snd_usb_caiaqdev *cdev); + +#endif /* CAIAQ_AUDIO_H */ diff --git a/sound/usb/caiaq/control.c b/sound/usb/caiaq/control.c new file mode 100644 index 000000000..4598fb7e8 --- /dev/null +++ b/sound/usb/caiaq/control.c @@ -0,0 +1,663 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2007 Daniel Mack + * friendly supported by NI. + */ + +#include <linux/device.h> +#include <linux/init.h> +#include <linux/usb.h> +#include <sound/control.h> +#include <sound/core.h> +#include <sound/pcm.h> + +#include "device.h" +#include "control.h" + +#define CNT_INTVAL 0x10000 +#define MASCHINE_BANK_SIZE 32 + +static int control_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct snd_usb_audio *chip = snd_kcontrol_chip(kcontrol); + struct snd_usb_caiaqdev *cdev = caiaqdev(chip->card); + int pos = kcontrol->private_value; + int is_intval = pos & CNT_INTVAL; + int maxval = 63; + + uinfo->count = 1; + pos &= ~CNT_INTVAL; + + switch (cdev->chip.usb_id) { + case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_AUDIO8DJ): + case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_AUDIO4DJ): + if (pos == 0) { + /* current input mode of A8DJ and A4DJ */ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 2; + return 0; + } + break; + + case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_TRAKTORKONTROLX1): + maxval = 127; + break; + + case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_TRAKTORKONTROLS4): + maxval = 31; + break; + } + + if (is_intval) { + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = maxval; + } else { + uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 1; + } + + return 0; +} + +static int control_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_usb_audio *chip = snd_kcontrol_chip(kcontrol); + struct snd_usb_caiaqdev *cdev = caiaqdev(chip->card); + int pos = kcontrol->private_value; + + if (pos & CNT_INTVAL) + ucontrol->value.integer.value[0] + = cdev->control_state[pos & ~CNT_INTVAL]; + else + ucontrol->value.integer.value[0] + = !!(cdev->control_state[pos / 8] & (1 << pos % 8)); + + return 0; +} + +static int control_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_usb_audio *chip = snd_kcontrol_chip(kcontrol); + struct snd_usb_caiaqdev *cdev = caiaqdev(chip->card); + int pos = kcontrol->private_value; + int v = ucontrol->value.integer.value[0]; + int ret; + unsigned char cmd; + + switch (cdev->chip.usb_id) { + case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_MASCHINECONTROLLER): + case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_TRAKTORKONTROLX1): + case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_KORECONTROLLER2): + case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_KORECONTROLLER): + cmd = EP1_CMD_DIMM_LEDS; + break; + default: + cmd = EP1_CMD_WRITE_IO; + break; + } + + if (pos & CNT_INTVAL) { + int i = pos & ~CNT_INTVAL; + unsigned char old = cdev->control_state[i]; + + if (old == v) + return 0; + + cdev->control_state[i] = v; + + if (cdev->chip.usb_id == + USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_TRAKTORKONTROLS4)) { + int actual_len; + + cdev->ep8_out_buf[0] = i; + cdev->ep8_out_buf[1] = v; + + ret = usb_bulk_msg(cdev->chip.dev, + usb_sndbulkpipe(cdev->chip.dev, 8), + cdev->ep8_out_buf, + sizeof(cdev->ep8_out_buf), + &actual_len, 200); + } else if (cdev->chip.usb_id == + USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_MASCHINECONTROLLER)) { + + int bank = 0; + int offset = 0; + + if (i >= MASCHINE_BANK_SIZE) { + bank = 0x1e; + offset = MASCHINE_BANK_SIZE; + } + + ret = snd_usb_caiaq_send_command_bank(cdev, cmd, bank, + cdev->control_state + offset, + MASCHINE_BANK_SIZE); + } else { + ret = snd_usb_caiaq_send_command(cdev, cmd, + cdev->control_state, + sizeof(cdev->control_state)); + } + + if (ret < 0) { + cdev->control_state[i] = old; + return ret; + } + } else { + int idx = pos / 8; + unsigned char mask = 1 << (pos % 8); + unsigned char old = cdev->control_state[idx]; + unsigned char val = v ? (old | mask) : (old & ~mask); + + if (old == val) + return 0; + + cdev->control_state[idx] = val; + ret = snd_usb_caiaq_send_command(cdev, cmd, + cdev->control_state, + sizeof(cdev->control_state)); + if (ret < 0) { + cdev->control_state[idx] = old; + return ret; + } + } + + return 1; +} + +static struct snd_kcontrol_new kcontrol_template = { + .iface = SNDRV_CTL_ELEM_IFACE_HWDEP, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .index = 0, + .info = control_info, + .get = control_get, + .put = control_put, + /* name and private_value filled later */ +}; + +struct caiaq_controller { + char *name; + int index; +}; + +static const struct caiaq_controller ak1_controller[] = { + { "LED left", 2 }, + { "LED middle", 1 }, + { "LED right", 0 }, + { "LED ring", 3 } +}; + +static const struct caiaq_controller rk2_controller[] = { + { "LED 1", 5 }, + { "LED 2", 4 }, + { "LED 3", 3 }, + { "LED 4", 2 }, + { "LED 5", 1 }, + { "LED 6", 0 }, + { "LED pedal", 6 }, + { "LED 7seg_1b", 8 }, + { "LED 7seg_1c", 9 }, + { "LED 7seg_2a", 10 }, + { "LED 7seg_2b", 11 }, + { "LED 7seg_2c", 12 }, + { "LED 7seg_2d", 13 }, + { "LED 7seg_2e", 14 }, + { "LED 7seg_2f", 15 }, + { "LED 7seg_2g", 16 }, + { "LED 7seg_3a", 17 }, + { "LED 7seg_3b", 18 }, + { "LED 7seg_3c", 19 }, + { "LED 7seg_3d", 20 }, + { "LED 7seg_3e", 21 }, + { "LED 7seg_3f", 22 }, + { "LED 7seg_3g", 23 } +}; + +static const struct caiaq_controller rk3_controller[] = { + { "LED 7seg_1a", 0 + 0 }, + { "LED 7seg_1b", 0 + 1 }, + { "LED 7seg_1c", 0 + 2 }, + { "LED 7seg_1d", 0 + 3 }, + { "LED 7seg_1e", 0 + 4 }, + { "LED 7seg_1f", 0 + 5 }, + { "LED 7seg_1g", 0 + 6 }, + { "LED 7seg_1p", 0 + 7 }, + + { "LED 7seg_2a", 8 + 0 }, + { "LED 7seg_2b", 8 + 1 }, + { "LED 7seg_2c", 8 + 2 }, + { "LED 7seg_2d", 8 + 3 }, + { "LED 7seg_2e", 8 + 4 }, + { "LED 7seg_2f", 8 + 5 }, + { "LED 7seg_2g", 8 + 6 }, + { "LED 7seg_2p", 8 + 7 }, + + { "LED 7seg_3a", 16 + 0 }, + { "LED 7seg_3b", 16 + 1 }, + { "LED 7seg_3c", 16 + 2 }, + { "LED 7seg_3d", 16 + 3 }, + { "LED 7seg_3e", 16 + 4 }, + { "LED 7seg_3f", 16 + 5 }, + { "LED 7seg_3g", 16 + 6 }, + { "LED 7seg_3p", 16 + 7 }, + + { "LED 7seg_4a", 24 + 0 }, + { "LED 7seg_4b", 24 + 1 }, + { "LED 7seg_4c", 24 + 2 }, + { "LED 7seg_4d", 24 + 3 }, + { "LED 7seg_4e", 24 + 4 }, + { "LED 7seg_4f", 24 + 5 }, + { "LED 7seg_4g", 24 + 6 }, + { "LED 7seg_4p", 24 + 7 }, + + { "LED 1", 32 + 0 }, + { "LED 2", 32 + 1 }, + { "LED 3", 32 + 2 }, + { "LED 4", 32 + 3 }, + { "LED 5", 32 + 4 }, + { "LED 6", 32 + 5 }, + { "LED 7", 32 + 6 }, + { "LED 8", 32 + 7 }, + { "LED pedal", 32 + 8 } +}; + +static const struct caiaq_controller kore_controller[] = { + { "LED F1", 8 | CNT_INTVAL }, + { "LED F2", 12 | CNT_INTVAL }, + { "LED F3", 0 | CNT_INTVAL }, + { "LED F4", 4 | CNT_INTVAL }, + { "LED F5", 11 | CNT_INTVAL }, + { "LED F6", 15 | CNT_INTVAL }, + { "LED F7", 3 | CNT_INTVAL }, + { "LED F8", 7 | CNT_INTVAL }, + { "LED touch1", 10 | CNT_INTVAL }, + { "LED touch2", 14 | CNT_INTVAL }, + { "LED touch3", 2 | CNT_INTVAL }, + { "LED touch4", 6 | CNT_INTVAL }, + { "LED touch5", 9 | CNT_INTVAL }, + { "LED touch6", 13 | CNT_INTVAL }, + { "LED touch7", 1 | CNT_INTVAL }, + { "LED touch8", 5 | CNT_INTVAL }, + { "LED left", 18 | CNT_INTVAL }, + { "LED right", 22 | CNT_INTVAL }, + { "LED up", 16 | CNT_INTVAL }, + { "LED down", 20 | CNT_INTVAL }, + { "LED stop", 23 | CNT_INTVAL }, + { "LED play", 21 | CNT_INTVAL }, + { "LED record", 19 | CNT_INTVAL }, + { "LED listen", 17 | CNT_INTVAL }, + { "LED lcd", 30 | CNT_INTVAL }, + { "LED menu", 28 | CNT_INTVAL }, + { "LED sound", 31 | CNT_INTVAL }, + { "LED esc", 29 | CNT_INTVAL }, + { "LED view", 27 | CNT_INTVAL }, + { "LED enter", 24 | CNT_INTVAL }, + { "LED control", 26 | CNT_INTVAL } +}; + +static const struct caiaq_controller a8dj_controller[] = { + { "Current input mode", 0 | CNT_INTVAL }, + { "GND lift for TC Vinyl mode", 24 + 0 }, + { "GND lift for TC CD/Line mode", 24 + 1 }, + { "GND lift for phono mode", 24 + 2 }, + { "Software lock", 40 } +}; + +static const struct caiaq_controller a4dj_controller[] = { + { "Current input mode", 0 | CNT_INTVAL } +}; + +static const struct caiaq_controller kontrolx1_controller[] = { + { "LED FX A: ON", 7 | CNT_INTVAL }, + { "LED FX A: 1", 6 | CNT_INTVAL }, + { "LED FX A: 2", 5 | CNT_INTVAL }, + { "LED FX A: 3", 4 | CNT_INTVAL }, + { "LED FX B: ON", 3 | CNT_INTVAL }, + { "LED FX B: 1", 2 | CNT_INTVAL }, + { "LED FX B: 2", 1 | CNT_INTVAL }, + { "LED FX B: 3", 0 | CNT_INTVAL }, + + { "LED Hotcue", 28 | CNT_INTVAL }, + { "LED Shift (white)", 29 | CNT_INTVAL }, + { "LED Shift (green)", 30 | CNT_INTVAL }, + + { "LED Deck A: FX1", 24 | CNT_INTVAL }, + { "LED Deck A: FX2", 25 | CNT_INTVAL }, + { "LED Deck A: IN", 17 | CNT_INTVAL }, + { "LED Deck A: OUT", 16 | CNT_INTVAL }, + { "LED Deck A: < BEAT", 19 | CNT_INTVAL }, + { "LED Deck A: BEAT >", 18 | CNT_INTVAL }, + { "LED Deck A: CUE/ABS", 21 | CNT_INTVAL }, + { "LED Deck A: CUP/REL", 20 | CNT_INTVAL }, + { "LED Deck A: PLAY", 23 | CNT_INTVAL }, + { "LED Deck A: SYNC", 22 | CNT_INTVAL }, + + { "LED Deck B: FX1", 26 | CNT_INTVAL }, + { "LED Deck B: FX2", 27 | CNT_INTVAL }, + { "LED Deck B: IN", 15 | CNT_INTVAL }, + { "LED Deck B: OUT", 14 | CNT_INTVAL }, + { "LED Deck B: < BEAT", 13 | CNT_INTVAL }, + { "LED Deck B: BEAT >", 12 | CNT_INTVAL }, + { "LED Deck B: CUE/ABS", 11 | CNT_INTVAL }, + { "LED Deck B: CUP/REL", 10 | CNT_INTVAL }, + { "LED Deck B: PLAY", 9 | CNT_INTVAL }, + { "LED Deck B: SYNC", 8 | CNT_INTVAL }, +}; + +static const struct caiaq_controller kontrols4_controller[] = { + { "LED: Master: Quant", 10 | CNT_INTVAL }, + { "LED: Master: Headphone", 11 | CNT_INTVAL }, + { "LED: Master: Master", 12 | CNT_INTVAL }, + { "LED: Master: Snap", 14 | CNT_INTVAL }, + { "LED: Master: Warning", 15 | CNT_INTVAL }, + { "LED: Master: Master button", 112 | CNT_INTVAL }, + { "LED: Master: Snap button", 113 | CNT_INTVAL }, + { "LED: Master: Rec", 118 | CNT_INTVAL }, + { "LED: Master: Size", 119 | CNT_INTVAL }, + { "LED: Master: Quant button", 120 | CNT_INTVAL }, + { "LED: Master: Browser button", 121 | CNT_INTVAL }, + { "LED: Master: Play button", 126 | CNT_INTVAL }, + { "LED: Master: Undo button", 127 | CNT_INTVAL }, + + { "LED: Channel A: >", 4 | CNT_INTVAL }, + { "LED: Channel A: <", 5 | CNT_INTVAL }, + { "LED: Channel A: Meter 1", 97 | CNT_INTVAL }, + { "LED: Channel A: Meter 2", 98 | CNT_INTVAL }, + { "LED: Channel A: Meter 3", 99 | CNT_INTVAL }, + { "LED: Channel A: Meter 4", 100 | CNT_INTVAL }, + { "LED: Channel A: Meter 5", 101 | CNT_INTVAL }, + { "LED: Channel A: Meter 6", 102 | CNT_INTVAL }, + { "LED: Channel A: Meter clip", 103 | CNT_INTVAL }, + { "LED: Channel A: Active", 114 | CNT_INTVAL }, + { "LED: Channel A: Cue", 116 | CNT_INTVAL }, + { "LED: Channel A: FX1", 149 | CNT_INTVAL }, + { "LED: Channel A: FX2", 148 | CNT_INTVAL }, + + { "LED: Channel B: >", 2 | CNT_INTVAL }, + { "LED: Channel B: <", 3 | CNT_INTVAL }, + { "LED: Channel B: Meter 1", 89 | CNT_INTVAL }, + { "LED: Channel B: Meter 2", 90 | CNT_INTVAL }, + { "LED: Channel B: Meter 3", 91 | CNT_INTVAL }, + { "LED: Channel B: Meter 4", 92 | CNT_INTVAL }, + { "LED: Channel B: Meter 5", 93 | CNT_INTVAL }, + { "LED: Channel B: Meter 6", 94 | CNT_INTVAL }, + { "LED: Channel B: Meter clip", 95 | CNT_INTVAL }, + { "LED: Channel B: Active", 122 | CNT_INTVAL }, + { "LED: Channel B: Cue", 125 | CNT_INTVAL }, + { "LED: Channel B: FX1", 147 | CNT_INTVAL }, + { "LED: Channel B: FX2", 146 | CNT_INTVAL }, + + { "LED: Channel C: >", 6 | CNT_INTVAL }, + { "LED: Channel C: <", 7 | CNT_INTVAL }, + { "LED: Channel C: Meter 1", 105 | CNT_INTVAL }, + { "LED: Channel C: Meter 2", 106 | CNT_INTVAL }, + { "LED: Channel C: Meter 3", 107 | CNT_INTVAL }, + { "LED: Channel C: Meter 4", 108 | CNT_INTVAL }, + { "LED: Channel C: Meter 5", 109 | CNT_INTVAL }, + { "LED: Channel C: Meter 6", 110 | CNT_INTVAL }, + { "LED: Channel C: Meter clip", 111 | CNT_INTVAL }, + { "LED: Channel C: Active", 115 | CNT_INTVAL }, + { "LED: Channel C: Cue", 117 | CNT_INTVAL }, + { "LED: Channel C: FX1", 151 | CNT_INTVAL }, + { "LED: Channel C: FX2", 150 | CNT_INTVAL }, + + { "LED: Channel D: >", 0 | CNT_INTVAL }, + { "LED: Channel D: <", 1 | CNT_INTVAL }, + { "LED: Channel D: Meter 1", 81 | CNT_INTVAL }, + { "LED: Channel D: Meter 2", 82 | CNT_INTVAL }, + { "LED: Channel D: Meter 3", 83 | CNT_INTVAL }, + { "LED: Channel D: Meter 4", 84 | CNT_INTVAL }, + { "LED: Channel D: Meter 5", 85 | CNT_INTVAL }, + { "LED: Channel D: Meter 6", 86 | CNT_INTVAL }, + { "LED: Channel D: Meter clip", 87 | CNT_INTVAL }, + { "LED: Channel D: Active", 123 | CNT_INTVAL }, + { "LED: Channel D: Cue", 124 | CNT_INTVAL }, + { "LED: Channel D: FX1", 145 | CNT_INTVAL }, + { "LED: Channel D: FX2", 144 | CNT_INTVAL }, + + { "LED: Deck A: 1 (blue)", 22 | CNT_INTVAL }, + { "LED: Deck A: 1 (green)", 23 | CNT_INTVAL }, + { "LED: Deck A: 2 (blue)", 20 | CNT_INTVAL }, + { "LED: Deck A: 2 (green)", 21 | CNT_INTVAL }, + { "LED: Deck A: 3 (blue)", 18 | CNT_INTVAL }, + { "LED: Deck A: 3 (green)", 19 | CNT_INTVAL }, + { "LED: Deck A: 4 (blue)", 16 | CNT_INTVAL }, + { "LED: Deck A: 4 (green)", 17 | CNT_INTVAL }, + { "LED: Deck A: Load", 44 | CNT_INTVAL }, + { "LED: Deck A: Deck C button", 45 | CNT_INTVAL }, + { "LED: Deck A: In", 47 | CNT_INTVAL }, + { "LED: Deck A: Out", 46 | CNT_INTVAL }, + { "LED: Deck A: Shift", 24 | CNT_INTVAL }, + { "LED: Deck A: Sync", 27 | CNT_INTVAL }, + { "LED: Deck A: Cue", 26 | CNT_INTVAL }, + { "LED: Deck A: Play", 25 | CNT_INTVAL }, + { "LED: Deck A: Tempo up", 33 | CNT_INTVAL }, + { "LED: Deck A: Tempo down", 32 | CNT_INTVAL }, + { "LED: Deck A: Master", 34 | CNT_INTVAL }, + { "LED: Deck A: Keylock", 35 | CNT_INTVAL }, + { "LED: Deck A: Deck A", 37 | CNT_INTVAL }, + { "LED: Deck A: Deck C", 36 | CNT_INTVAL }, + { "LED: Deck A: Samples", 38 | CNT_INTVAL }, + { "LED: Deck A: On Air", 39 | CNT_INTVAL }, + { "LED: Deck A: Sample 1", 31 | CNT_INTVAL }, + { "LED: Deck A: Sample 2", 30 | CNT_INTVAL }, + { "LED: Deck A: Sample 3", 29 | CNT_INTVAL }, + { "LED: Deck A: Sample 4", 28 | CNT_INTVAL }, + { "LED: Deck A: Digit 1 - A", 55 | CNT_INTVAL }, + { "LED: Deck A: Digit 1 - B", 54 | CNT_INTVAL }, + { "LED: Deck A: Digit 1 - C", 53 | CNT_INTVAL }, + { "LED: Deck A: Digit 1 - D", 52 | CNT_INTVAL }, + { "LED: Deck A: Digit 1 - E", 51 | CNT_INTVAL }, + { "LED: Deck A: Digit 1 - F", 50 | CNT_INTVAL }, + { "LED: Deck A: Digit 1 - G", 49 | CNT_INTVAL }, + { "LED: Deck A: Digit 1 - dot", 48 | CNT_INTVAL }, + { "LED: Deck A: Digit 2 - A", 63 | CNT_INTVAL }, + { "LED: Deck A: Digit 2 - B", 62 | CNT_INTVAL }, + { "LED: Deck A: Digit 2 - C", 61 | CNT_INTVAL }, + { "LED: Deck A: Digit 2 - D", 60 | CNT_INTVAL }, + { "LED: Deck A: Digit 2 - E", 59 | CNT_INTVAL }, + { "LED: Deck A: Digit 2 - F", 58 | CNT_INTVAL }, + { "LED: Deck A: Digit 2 - G", 57 | CNT_INTVAL }, + { "LED: Deck A: Digit 2 - dot", 56 | CNT_INTVAL }, + + { "LED: Deck B: 1 (blue)", 78 | CNT_INTVAL }, + { "LED: Deck B: 1 (green)", 79 | CNT_INTVAL }, + { "LED: Deck B: 2 (blue)", 76 | CNT_INTVAL }, + { "LED: Deck B: 2 (green)", 77 | CNT_INTVAL }, + { "LED: Deck B: 3 (blue)", 74 | CNT_INTVAL }, + { "LED: Deck B: 3 (green)", 75 | CNT_INTVAL }, + { "LED: Deck B: 4 (blue)", 72 | CNT_INTVAL }, + { "LED: Deck B: 4 (green)", 73 | CNT_INTVAL }, + { "LED: Deck B: Load", 180 | CNT_INTVAL }, + { "LED: Deck B: Deck D button", 181 | CNT_INTVAL }, + { "LED: Deck B: In", 183 | CNT_INTVAL }, + { "LED: Deck B: Out", 182 | CNT_INTVAL }, + { "LED: Deck B: Shift", 64 | CNT_INTVAL }, + { "LED: Deck B: Sync", 67 | CNT_INTVAL }, + { "LED: Deck B: Cue", 66 | CNT_INTVAL }, + { "LED: Deck B: Play", 65 | CNT_INTVAL }, + { "LED: Deck B: Tempo up", 185 | CNT_INTVAL }, + { "LED: Deck B: Tempo down", 184 | CNT_INTVAL }, + { "LED: Deck B: Master", 186 | CNT_INTVAL }, + { "LED: Deck B: Keylock", 187 | CNT_INTVAL }, + { "LED: Deck B: Deck B", 189 | CNT_INTVAL }, + { "LED: Deck B: Deck D", 188 | CNT_INTVAL }, + { "LED: Deck B: Samples", 190 | CNT_INTVAL }, + { "LED: Deck B: On Air", 191 | CNT_INTVAL }, + { "LED: Deck B: Sample 1", 71 | CNT_INTVAL }, + { "LED: Deck B: Sample 2", 70 | CNT_INTVAL }, + { "LED: Deck B: Sample 3", 69 | CNT_INTVAL }, + { "LED: Deck B: Sample 4", 68 | CNT_INTVAL }, + { "LED: Deck B: Digit 1 - A", 175 | CNT_INTVAL }, + { "LED: Deck B: Digit 1 - B", 174 | CNT_INTVAL }, + { "LED: Deck B: Digit 1 - C", 173 | CNT_INTVAL }, + { "LED: Deck B: Digit 1 - D", 172 | CNT_INTVAL }, + { "LED: Deck B: Digit 1 - E", 171 | CNT_INTVAL }, + { "LED: Deck B: Digit 1 - F", 170 | CNT_INTVAL }, + { "LED: Deck B: Digit 1 - G", 169 | CNT_INTVAL }, + { "LED: Deck B: Digit 1 - dot", 168 | CNT_INTVAL }, + { "LED: Deck B: Digit 2 - A", 167 | CNT_INTVAL }, + { "LED: Deck B: Digit 2 - B", 166 | CNT_INTVAL }, + { "LED: Deck B: Digit 2 - C", 165 | CNT_INTVAL }, + { "LED: Deck B: Digit 2 - D", 164 | CNT_INTVAL }, + { "LED: Deck B: Digit 2 - E", 163 | CNT_INTVAL }, + { "LED: Deck B: Digit 2 - F", 162 | CNT_INTVAL }, + { "LED: Deck B: Digit 2 - G", 161 | CNT_INTVAL }, + { "LED: Deck B: Digit 2 - dot", 160 | CNT_INTVAL }, + + { "LED: FX1: dry/wet", 153 | CNT_INTVAL }, + { "LED: FX1: 1", 154 | CNT_INTVAL }, + { "LED: FX1: 2", 155 | CNT_INTVAL }, + { "LED: FX1: 3", 156 | CNT_INTVAL }, + { "LED: FX1: Mode", 157 | CNT_INTVAL }, + { "LED: FX2: dry/wet", 129 | CNT_INTVAL }, + { "LED: FX2: 1", 130 | CNT_INTVAL }, + { "LED: FX2: 2", 131 | CNT_INTVAL }, + { "LED: FX2: 3", 132 | CNT_INTVAL }, + { "LED: FX2: Mode", 133 | CNT_INTVAL }, +}; + +static const struct caiaq_controller maschine_controller[] = { + { "LED: Pad 1", 3 | CNT_INTVAL }, + { "LED: Pad 2", 2 | CNT_INTVAL }, + { "LED: Pad 3", 1 | CNT_INTVAL }, + { "LED: Pad 4", 0 | CNT_INTVAL }, + { "LED: Pad 5", 7 | CNT_INTVAL }, + { "LED: Pad 6", 6 | CNT_INTVAL }, + { "LED: Pad 7", 5 | CNT_INTVAL }, + { "LED: Pad 8", 4 | CNT_INTVAL }, + { "LED: Pad 9", 11 | CNT_INTVAL }, + { "LED: Pad 10", 10 | CNT_INTVAL }, + { "LED: Pad 11", 9 | CNT_INTVAL }, + { "LED: Pad 12", 8 | CNT_INTVAL }, + { "LED: Pad 13", 15 | CNT_INTVAL }, + { "LED: Pad 14", 14 | CNT_INTVAL }, + { "LED: Pad 15", 13 | CNT_INTVAL }, + { "LED: Pad 16", 12 | CNT_INTVAL }, + + { "LED: Mute", 16 | CNT_INTVAL }, + { "LED: Solo", 17 | CNT_INTVAL }, + { "LED: Select", 18 | CNT_INTVAL }, + { "LED: Duplicate", 19 | CNT_INTVAL }, + { "LED: Navigate", 20 | CNT_INTVAL }, + { "LED: Pad Mode", 21 | CNT_INTVAL }, + { "LED: Pattern", 22 | CNT_INTVAL }, + { "LED: Scene", 23 | CNT_INTVAL }, + + { "LED: Shift", 24 | CNT_INTVAL }, + { "LED: Erase", 25 | CNT_INTVAL }, + { "LED: Grid", 26 | CNT_INTVAL }, + { "LED: Right Bottom", 27 | CNT_INTVAL }, + { "LED: Rec", 28 | CNT_INTVAL }, + { "LED: Play", 29 | CNT_INTVAL }, + { "LED: Left Bottom", 32 | CNT_INTVAL }, + { "LED: Restart", 33 | CNT_INTVAL }, + + { "LED: Group A", 41 | CNT_INTVAL }, + { "LED: Group B", 40 | CNT_INTVAL }, + { "LED: Group C", 37 | CNT_INTVAL }, + { "LED: Group D", 36 | CNT_INTVAL }, + { "LED: Group E", 39 | CNT_INTVAL }, + { "LED: Group F", 38 | CNT_INTVAL }, + { "LED: Group G", 35 | CNT_INTVAL }, + { "LED: Group H", 34 | CNT_INTVAL }, + + { "LED: Auto Write", 42 | CNT_INTVAL }, + { "LED: Snap", 43 | CNT_INTVAL }, + { "LED: Right Top", 44 | CNT_INTVAL }, + { "LED: Left Top", 45 | CNT_INTVAL }, + { "LED: Sampling", 46 | CNT_INTVAL }, + { "LED: Browse", 47 | CNT_INTVAL }, + { "LED: Step", 48 | CNT_INTVAL }, + { "LED: Control", 49 | CNT_INTVAL }, + + { "LED: Top Button 1", 57 | CNT_INTVAL }, + { "LED: Top Button 2", 56 | CNT_INTVAL }, + { "LED: Top Button 3", 55 | CNT_INTVAL }, + { "LED: Top Button 4", 54 | CNT_INTVAL }, + { "LED: Top Button 5", 53 | CNT_INTVAL }, + { "LED: Top Button 6", 52 | CNT_INTVAL }, + { "LED: Top Button 7", 51 | CNT_INTVAL }, + { "LED: Top Button 8", 50 | CNT_INTVAL }, + + { "LED: Note Repeat", 58 | CNT_INTVAL }, + + { "Backlight Display", 59 | CNT_INTVAL } +}; + +static int add_controls(const struct caiaq_controller *c, int num, + struct snd_usb_caiaqdev *cdev) +{ + int i, ret; + struct snd_kcontrol *kc; + + for (i = 0; i < num; i++, c++) { + kcontrol_template.name = c->name; + kcontrol_template.private_value = c->index; + kc = snd_ctl_new1(&kcontrol_template, cdev); + ret = snd_ctl_add(cdev->chip.card, kc); + if (ret < 0) + return ret; + } + + return 0; +} + +int snd_usb_caiaq_control_init(struct snd_usb_caiaqdev *cdev) +{ + int ret = 0; + + switch (cdev->chip.usb_id) { + case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_AK1): + ret = add_controls(ak1_controller, + ARRAY_SIZE(ak1_controller), cdev); + break; + + case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_RIGKONTROL2): + ret = add_controls(rk2_controller, + ARRAY_SIZE(rk2_controller), cdev); + break; + + case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_RIGKONTROL3): + ret = add_controls(rk3_controller, + ARRAY_SIZE(rk3_controller), cdev); + break; + + case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_KORECONTROLLER): + case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_KORECONTROLLER2): + ret = add_controls(kore_controller, + ARRAY_SIZE(kore_controller), cdev); + break; + + case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_AUDIO8DJ): + ret = add_controls(a8dj_controller, + ARRAY_SIZE(a8dj_controller), cdev); + break; + + case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_AUDIO4DJ): + ret = add_controls(a4dj_controller, + ARRAY_SIZE(a4dj_controller), cdev); + break; + + case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_TRAKTORKONTROLX1): + ret = add_controls(kontrolx1_controller, + ARRAY_SIZE(kontrolx1_controller), cdev); + break; + + case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_TRAKTORKONTROLS4): + ret = add_controls(kontrols4_controller, + ARRAY_SIZE(kontrols4_controller), cdev); + break; + + case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_MASCHINECONTROLLER): + ret = add_controls(maschine_controller, + ARRAY_SIZE(maschine_controller), cdev); + break; + } + + return ret; +} diff --git a/sound/usb/caiaq/control.h b/sound/usb/caiaq/control.h new file mode 100644 index 000000000..cb204fd45 --- /dev/null +++ b/sound/usb/caiaq/control.h @@ -0,0 +1,7 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef CAIAQ_CONTROL_H +#define CAIAQ_CONTROL_H + +int snd_usb_caiaq_control_init(struct snd_usb_caiaqdev *cdev); + +#endif /* CAIAQ_CONTROL_H */ diff --git a/sound/usb/caiaq/device.c b/sound/usb/caiaq/device.c new file mode 100644 index 000000000..3d821fde4 --- /dev/null +++ b/sound/usb/caiaq/device.c @@ -0,0 +1,606 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * caiaq.c: ALSA driver for caiaq/NativeInstruments devices + * + * Copyright (c) 2007 Daniel Mack <daniel@caiaq.de> + * Karsten Wiese <fzu@wemgehoertderstaat.de> +*/ + +#include <linux/moduleparam.h> +#include <linux/device.h> +#include <linux/interrupt.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/gfp.h> +#include <linux/usb.h> +#include <sound/initval.h> +#include <sound/core.h> +#include <sound/pcm.h> + +#include "device.h" +#include "audio.h" +#include "midi.h" +#include "control.h" +#include "input.h" + +MODULE_AUTHOR("Daniel Mack <daniel@caiaq.de>"); +MODULE_DESCRIPTION("caiaq USB audio"); +MODULE_LICENSE("GPL"); + +static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 0-max */ +static char* id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* Id for this card */ +static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP; /* Enable this card */ + +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 const 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_RIGKONTROL3 + }, + { + .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_KORECONTROLLER2 + }, + { + .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 + }, + { + .match_flags = USB_DEVICE_ID_MATCH_DEVICE, + .idVendor = USB_VID_NATIVEINSTRUMENTS, + .idProduct = USB_PID_SESSIONIO + }, + { + .match_flags = USB_DEVICE_ID_MATCH_DEVICE, + .idVendor = USB_VID_NATIVEINSTRUMENTS, + .idProduct = USB_PID_GUITARRIGMOBILE + }, + { + .match_flags = USB_DEVICE_ID_MATCH_DEVICE, + .idVendor = USB_VID_NATIVEINSTRUMENTS, + .idProduct = USB_PID_AUDIO4DJ + }, + { + .match_flags = USB_DEVICE_ID_MATCH_DEVICE, + .idVendor = USB_VID_NATIVEINSTRUMENTS, + .idProduct = USB_PID_AUDIO2DJ + }, + { + .match_flags = USB_DEVICE_ID_MATCH_DEVICE, + .idVendor = USB_VID_NATIVEINSTRUMENTS, + .idProduct = USB_PID_TRAKTORKONTROLX1 + }, + { + .match_flags = USB_DEVICE_ID_MATCH_DEVICE, + .idVendor = USB_VID_NATIVEINSTRUMENTS, + .idProduct = USB_PID_TRAKTORKONTROLS4 + }, + { + .match_flags = USB_DEVICE_ID_MATCH_DEVICE, + .idVendor = USB_VID_NATIVEINSTRUMENTS, + .idProduct = USB_PID_TRAKTORAUDIO2 + }, + { + .match_flags = USB_DEVICE_ID_MATCH_DEVICE, + .idVendor = USB_VID_NATIVEINSTRUMENTS, + .idProduct = USB_PID_MASCHINECONTROLLER + }, + { /* terminator */ } +}; + +static void usb_ep1_command_reply_dispatch (struct urb* urb) +{ + int ret; + struct device *dev = &urb->dev->dev; + struct snd_usb_caiaqdev *cdev = urb->context; + unsigned char *buf = urb->transfer_buffer; + unsigned int payload_len; + unsigned int copy_len; + + if (urb->status || !cdev) { + dev_warn(dev, "received EP1 urb->status = %i\n", urb->status); + return; + } + if (urb->actual_length < 1) + return; + + payload_len = urb->actual_length - 1; + + switch(buf[0]) { + case EP1_CMD_GET_DEVICE_INFO: + if (payload_len < sizeof(struct caiaq_device_spec)) + break; + memcpy(&cdev->spec, buf+1, sizeof(struct caiaq_device_spec)); + cdev->spec.fw_version = le16_to_cpu(cdev->spec.fw_version); + dev_dbg(dev, "device spec (firmware %d): audio: %d in, %d out, " + "MIDI: %d in, %d out, data alignment %d\n", + cdev->spec.fw_version, + cdev->spec.num_analog_audio_in, + cdev->spec.num_analog_audio_out, + cdev->spec.num_midi_in, + cdev->spec.num_midi_out, + cdev->spec.data_alignment); + + cdev->spec_received++; + wake_up(&cdev->ep1_wait_queue); + break; + case EP1_CMD_AUDIO_PARAMS: + if (payload_len < 1) + break; + cdev->audio_parm_answer = buf[1]; + wake_up(&cdev->ep1_wait_queue); + break; + case EP1_CMD_MIDI_READ: + if (urb->actual_length < 3 || urb->actual_length - 3 < buf[2]) + break; + snd_usb_caiaq_midi_handle_input(cdev, buf[1], buf + 3, buf[2]); + break; + case EP1_CMD_READ_IO: + if (cdev->chip.usb_id == + USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_AUDIO8DJ)) { + copy_len = min_t(unsigned int, payload_len, sizeof(cdev->control_state)); + memcpy(cdev->control_state, buf + 1, copy_len); + wake_up(&cdev->ep1_wait_queue); + break; + } +#ifdef CONFIG_SND_USB_CAIAQ_INPUT + fallthrough; + case EP1_CMD_READ_ERP: + case EP1_CMD_READ_ANALOG: + snd_usb_caiaq_input_dispatch(cdev, buf, urb->actual_length); +#endif + break; + } + + cdev->ep1_in_urb->actual_length = 0; + ret = usb_submit_urb(cdev->ep1_in_urb, GFP_ATOMIC); + if (ret < 0) + dev_err(dev, "unable to submit urb. OOM!?\n"); +} + +int snd_usb_caiaq_send_command(struct snd_usb_caiaqdev *cdev, + unsigned char command, + const unsigned char *buffer, + int len) +{ + int actual_len; + struct usb_device *usb_dev = cdev->chip.dev; + + if (!usb_dev) + return -EIO; + + if (len > EP1_BUFSIZE - 1) + len = EP1_BUFSIZE - 1; + + if (buffer && len > 0) + memcpy(cdev->ep1_out_buf+1, buffer, len); + + cdev->ep1_out_buf[0] = command; + return usb_bulk_msg(usb_dev, usb_sndbulkpipe(usb_dev, 1), + cdev->ep1_out_buf, len+1, &actual_len, 200); +} + +int snd_usb_caiaq_send_command_bank(struct snd_usb_caiaqdev *cdev, + unsigned char command, + unsigned char bank, + const unsigned char *buffer, + int len) +{ + int actual_len; + struct usb_device *usb_dev = cdev->chip.dev; + + if (!usb_dev) + return -EIO; + + if (len > EP1_BUFSIZE - 2) + len = EP1_BUFSIZE - 2; + + if (buffer && len > 0) + memcpy(cdev->ep1_out_buf+2, buffer, len); + + cdev->ep1_out_buf[0] = command; + cdev->ep1_out_buf[1] = bank; + + return usb_bulk_msg(usb_dev, usb_sndbulkpipe(usb_dev, 1), + cdev->ep1_out_buf, len+2, &actual_len, 200); +} + +int snd_usb_caiaq_set_audio_params (struct snd_usb_caiaqdev *cdev, + int rate, int depth, int bpp) +{ + int ret; + char tmp[5]; + struct device *dev = caiaqdev_to_dev(cdev); + + 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 */ + + dev_dbg(dev, "setting audio params: %d Hz, %d bits, %d bpp\n", + rate, depth, bpp); + + cdev->audio_parm_answer = -1; + ret = snd_usb_caiaq_send_command(cdev, EP1_CMD_AUDIO_PARAMS, + tmp, sizeof(tmp)); + + if (ret) + return ret; + + if (!wait_event_timeout(cdev->ep1_wait_queue, + cdev->audio_parm_answer >= 0, HZ)) + return -EPIPE; + + if (cdev->audio_parm_answer != 1) + dev_dbg(dev, "unable to set the device's audio params\n"); + else + cdev->bpp = bpp; + + return cdev->audio_parm_answer == 1 ? 0 : -EINVAL; +} + +int snd_usb_caiaq_set_auto_msg(struct snd_usb_caiaqdev *cdev, + int digital, int analog, int erp) +{ + char tmp[3] = { digital, analog, erp }; + return snd_usb_caiaq_send_command(cdev, EP1_CMD_AUTO_MSG, + tmp, sizeof(tmp)); +} + +static int setup_card(struct snd_usb_caiaqdev *cdev) +{ + int ret; + char val[4]; + struct device *dev = caiaqdev_to_dev(cdev); + + /* device-specific startup specials */ + switch (cdev->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; + snd_usb_caiaq_send_command(cdev, EP1_CMD_WRITE_IO, val, 3); + break; + case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_RIGKONTROL3): + /* RigKontrol2 - display two centered dashes ('--') */ + val[0] = 0x00; + val[1] = 0x40; + val[2] = 0x40; + val[3] = 0x00; + snd_usb_caiaq_send_command(cdev, EP1_CMD_WRITE_IO, val, 4); + break; + case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_AK1): + /* Audio Kontrol 1 - make USB-LED stop blinking */ + val[0] = 0x00; + snd_usb_caiaq_send_command(cdev, EP1_CMD_WRITE_IO, val, 1); + break; + case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_AUDIO8DJ): + /* Audio 8 DJ - trigger read of current settings */ + cdev->control_state[0] = 0xff; + snd_usb_caiaq_set_auto_msg(cdev, 1, 0, 0); + snd_usb_caiaq_send_command(cdev, EP1_CMD_READ_IO, NULL, 0); + + if (!wait_event_timeout(cdev->ep1_wait_queue, + cdev->control_state[0] != 0xff, HZ)) { + dev_err(dev, "Read timeout for control state\n"); + return -EINVAL; + } + + /* fix up some defaults */ + if ((cdev->control_state[1] != 2) || + (cdev->control_state[2] != 3) || + (cdev->control_state[4] != 2)) { + cdev->control_state[1] = 2; + cdev->control_state[2] = 3; + cdev->control_state[4] = 2; + snd_usb_caiaq_send_command(cdev, + EP1_CMD_WRITE_IO, cdev->control_state, 6); + } + + break; + } + + if (cdev->spec.num_analog_audio_out + + cdev->spec.num_analog_audio_in + + cdev->spec.num_digital_audio_out + + cdev->spec.num_digital_audio_in > 0) { + ret = snd_usb_caiaq_audio_init(cdev); + if (ret < 0) { + dev_err(dev, "Unable to set up audio system (ret=%d)\n", ret); + return ret; + } + } + + if (cdev->spec.num_midi_in + + cdev->spec.num_midi_out > 0) { + ret = snd_usb_caiaq_midi_init(cdev); + if (ret < 0) { + dev_err(dev, "Unable to set up MIDI system (ret=%d)\n", ret); + return ret; + } + } + +#ifdef CONFIG_SND_USB_CAIAQ_INPUT + ret = snd_usb_caiaq_input_init(cdev); + if (ret < 0 && ret != -ENODEV) { + dev_err(dev, "Unable to set up input system (ret=%d)\n", ret); + return ret; + } +#endif + + /* finally, register the card and all its sub-instances */ + ret = snd_card_register(cdev->chip.card); + if (ret < 0) { + dev_err(dev, "snd_card_register() returned %d\n", ret); + return ret; + } + + ret = snd_usb_caiaq_control_init(cdev); + if (ret < 0) { + dev_err(dev, "Unable to set up control system (ret=%d)\n", ret); + return ret; + } + + return 0; +} + +static void card_free(struct snd_card *card) +{ + struct snd_usb_caiaqdev *cdev = caiaqdev(card); + +#ifdef CONFIG_SND_USB_CAIAQ_INPUT + snd_usb_caiaq_input_free(cdev); +#endif + snd_usb_caiaq_audio_free(cdev); + usb_put_dev(cdev->chip.dev); + usb_free_urb(cdev->ep1_in_urb); + cdev->ep1_in_urb = NULL; + usb_free_urb(cdev->midi_out_urb); + cdev->midi_out_urb = NULL; +} + +static int create_card(struct usb_device *usb_dev, + struct usb_interface *intf, + struct snd_card **cardp) +{ + int devnum; + int err; + struct snd_card *card; + struct snd_usb_caiaqdev *cdev; + + for (devnum = 0; devnum < SNDRV_CARDS; devnum++) + if (enable[devnum]) + break; + + if (devnum >= SNDRV_CARDS) + return -ENODEV; + + err = snd_card_new(&intf->dev, + index[devnum], id[devnum], THIS_MODULE, + sizeof(struct snd_usb_caiaqdev), &card); + if (err < 0) + return err; + + cdev = caiaqdev(card); + cdev->chip.dev = usb_get_dev(usb_dev); + card->private_free = card_free; + cdev->chip.card = card; + cdev->chip.usb_id = USB_ID(le16_to_cpu(usb_dev->descriptor.idVendor), + le16_to_cpu(usb_dev->descriptor.idProduct)); + spin_lock_init(&cdev->spinlock); + + *cardp = card; + return 0; +} + +static int init_card(struct snd_usb_caiaqdev *cdev) +{ + char *c, usbpath[32]; + struct usb_device *usb_dev = cdev->chip.dev; + struct snd_card *card = cdev->chip.card; + struct device *dev = caiaqdev_to_dev(cdev); + int err, len; + + if (usb_set_interface(usb_dev, 0, 1) != 0) { + dev_err(dev, "can't set alt interface.\n"); + return -EIO; + } + + cdev->ep1_in_urb = usb_alloc_urb(0, GFP_KERNEL); + if (!cdev->ep1_in_urb) + return -ENOMEM; + + cdev->midi_out_urb = usb_alloc_urb(0, GFP_KERNEL); + if (!cdev->midi_out_urb) { + usb_free_urb(cdev->ep1_in_urb); + cdev->ep1_in_urb = NULL; + return -ENOMEM; + } + + usb_fill_bulk_urb(cdev->ep1_in_urb, usb_dev, + usb_rcvbulkpipe(usb_dev, 0x1), + cdev->ep1_in_buf, EP1_BUFSIZE, + usb_ep1_command_reply_dispatch, cdev); + + usb_fill_bulk_urb(cdev->midi_out_urb, usb_dev, + usb_sndbulkpipe(usb_dev, 0x1), + cdev->midi_out_buf, EP1_BUFSIZE, + snd_usb_caiaq_midi_output_done, cdev); + + /* sanity checks of EPs before actually submitting */ + if (usb_urb_ep_type_check(cdev->ep1_in_urb) || + usb_urb_ep_type_check(cdev->midi_out_urb)) { + dev_err(dev, "invalid EPs\n"); + return -EINVAL; + } + + init_waitqueue_head(&cdev->ep1_wait_queue); + init_waitqueue_head(&cdev->prepare_wait_queue); + + if (usb_submit_urb(cdev->ep1_in_urb, GFP_KERNEL) != 0) + return -EIO; + + err = snd_usb_caiaq_send_command(cdev, EP1_CMD_GET_DEVICE_INFO, NULL, 0); + if (err) + goto err_kill_urb; + + if (!wait_event_timeout(cdev->ep1_wait_queue, cdev->spec_received, HZ)) { + err = -ENODEV; + goto err_kill_urb; + } + + usb_string(usb_dev, usb_dev->descriptor.iManufacturer, + cdev->vendor_name, CAIAQ_USB_STR_LEN); + + usb_string(usb_dev, usb_dev->descriptor.iProduct, + cdev->product_name, CAIAQ_USB_STR_LEN); + + strscpy(card->driver, MODNAME, sizeof(card->driver)); + strscpy(card->shortname, cdev->product_name, sizeof(card->shortname)); + strscpy(card->mixername, cdev->product_name, sizeof(card->mixername)); + + /* if the id was not passed as module option, fill it with a shortened + * version of the product string which does not contain any + * whitespaces */ + + if (*card->id == '\0') { + char id[sizeof(card->id)]; + + memset(id, 0, sizeof(id)); + + for (c = card->shortname, len = 0; + *c && len < sizeof(card->id) - 1; c++) + if (*c != ' ') + id[len++] = *c; + + snd_card_set_id(card, id); + } + + usb_make_path(usb_dev, usbpath, sizeof(usbpath)); + scnprintf(card->longname, sizeof(card->longname), "%s %s (%s)", + cdev->vendor_name, cdev->product_name, usbpath); + + err = setup_card(cdev); + if (err < 0) + goto err_kill_urb; + + return 0; + + err_kill_urb: + usb_kill_urb(cdev->ep1_in_urb); + return err; +} + +static int snd_probe(struct usb_interface *intf, + const struct usb_device_id *id) +{ + int ret; + struct snd_card *card = NULL; + struct usb_device *usb_dev = interface_to_usbdev(intf); + + ret = create_card(usb_dev, intf, &card); + + if (ret < 0) + return ret; + + usb_set_intfdata(intf, card); + ret = init_card(caiaqdev(card)); + if (ret < 0) { + dev_err(&usb_dev->dev, "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_card *card = usb_get_intfdata(intf); + struct device *dev = intf->usb_dev; + struct snd_usb_caiaqdev *cdev; + + if (!card) + return; + + cdev = caiaqdev(card); + dev_dbg(dev, "%s(%p)\n", __func__, intf); + + snd_card_disconnect(card); + +#ifdef CONFIG_SND_USB_CAIAQ_INPUT + snd_usb_caiaq_input_disconnect(cdev); +#endif + snd_usb_caiaq_audio_disconnect(cdev); + + usb_kill_urb(cdev->ep1_in_urb); + usb_kill_urb(cdev->midi_out_urb); + + snd_card_free_when_closed(card); +} + + +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, +}; + +module_usb_driver(snd_usb_driver); diff --git a/sound/usb/caiaq/device.h b/sound/usb/caiaq/device.h new file mode 100644 index 000000000..1c6f34693 --- /dev/null +++ b/sound/usb/caiaq/device.h @@ -0,0 +1,138 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef CAIAQ_DEVICE_H +#define CAIAQ_DEVICE_H + +#include "../usbaudio.h" + +#define USB_VID_NATIVEINSTRUMENTS 0x17cc + +#define USB_PID_RIGKONTROL2 0x1969 +#define USB_PID_RIGKONTROL3 0x1940 +#define USB_PID_KORECONTROLLER 0x4711 +#define USB_PID_KORECONTROLLER2 0x4712 +#define USB_PID_AK1 0x0815 +#define USB_PID_AUDIO2DJ 0x041c +#define USB_PID_AUDIO4DJ 0x0839 +#define USB_PID_AUDIO8DJ 0x1978 +#define USB_PID_SESSIONIO 0x1915 +#define USB_PID_GUITARRIGMOBILE 0x0d8d +#define USB_PID_TRAKTORKONTROLX1 0x2305 +#define USB_PID_TRAKTORKONTROLS4 0xbaff +#define USB_PID_TRAKTORAUDIO2 0x041d +#define USB_PID_MASCHINECONTROLLER 0x0808 + +#define EP1_BUFSIZE 64 +#define EP4_BUFSIZE 512 +#define CAIAQ_USB_STR_LEN 0xff +#define MAX_STREAMS 32 + +#define MODNAME "snd-usb-caiaq" + +#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 +#define EP1_CMD_DIMM_LEDS 0xc + +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; +} __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; + int midi_out_active; + + char vendor_name[CAIAQ_USB_STR_LEN]; + char product_name[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, warned; + char *audio_in_buf, *audio_out_buf; + unsigned int samplerates, bpp; + unsigned long outurb_active_mask; + + struct snd_pcm_substream *sub_playback[MAX_STREAMS]; + struct snd_pcm_substream *sub_capture[MAX_STREAMS]; + + /* Controls */ + unsigned char control_state[256]; + unsigned char ep8_out_buf[2]; + + /* Linux input */ +#ifdef CONFIG_SND_USB_CAIAQ_INPUT + struct input_dev *input_dev; + char phys[64]; /* physical device path */ + unsigned short keycode[128]; + struct urb *ep4_in_urb; + unsigned char ep4_in_buf[EP4_BUFSIZE]; +#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 *cdev; + int index; +}; + +#define caiaqdev(c) ((struct snd_usb_caiaqdev*)(c)->private_data) +#define caiaqdev_to_dev(d) (d->chip.card->dev) + +int snd_usb_caiaq_set_audio_params (struct snd_usb_caiaqdev *cdev, int rate, int depth, int bbp); +int snd_usb_caiaq_set_auto_msg (struct snd_usb_caiaqdev *cdev, int digital, int analog, int erp); +int snd_usb_caiaq_send_command(struct snd_usb_caiaqdev *cdev, + unsigned char command, + const unsigned char *buffer, + int len); +int snd_usb_caiaq_send_command_bank(struct snd_usb_caiaqdev *cdev, + unsigned char command, + unsigned char bank, + const unsigned char *buffer, + int len); + +#endif /* CAIAQ_DEVICE_H */ diff --git a/sound/usb/caiaq/input.c b/sound/usb/caiaq/input.c new file mode 100644 index 000000000..8d924330c --- /dev/null +++ b/sound/usb/caiaq/input.c @@ -0,0 +1,865 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2006,2007 Daniel Mack, Tim Ruetz +*/ + +#include <linux/device.h> +#include <linux/gfp.h> +#include <linux/init.h> +#include <linux/usb.h> +#include <linux/usb/input.h> +#include <sound/core.h> +#include <sound/pcm.h> + +#include "device.h" +#include "input.h" + +static const unsigned short keycode_ak1[] = { KEY_C, KEY_B, KEY_A }; +static const unsigned short keycode_rk2[] = { KEY_1, KEY_2, KEY_3, KEY_4, + KEY_5, KEY_6, KEY_7 }; +static const unsigned short keycode_rk3[] = { KEY_1, KEY_2, KEY_3, KEY_4, + KEY_5, KEY_6, KEY_7, KEY_8, KEY_9 }; + +static const unsigned short keycode_kore[] = { + KEY_FN_F1, /* "menu" */ + KEY_FN_F7, /* "lcd backlight */ + KEY_FN_F2, /* "control" */ + KEY_FN_F3, /* "enter" */ + KEY_FN_F4, /* "view" */ + KEY_FN_F5, /* "esc" */ + KEY_FN_F6, /* "sound" */ + KEY_FN_F8, /* array spacer, never triggered. */ + KEY_RIGHT, + KEY_DOWN, + KEY_UP, + KEY_LEFT, + KEY_SOUND, /* "listen" */ + KEY_RECORD, + KEY_PLAYPAUSE, + KEY_STOP, + BTN_4, /* 8 softkeys */ + BTN_3, + BTN_2, + BTN_1, + BTN_8, + BTN_7, + BTN_6, + BTN_5, + KEY_BRL_DOT4, /* touch sensitive knobs */ + KEY_BRL_DOT3, + KEY_BRL_DOT2, + KEY_BRL_DOT1, + KEY_BRL_DOT8, + KEY_BRL_DOT7, + KEY_BRL_DOT6, + KEY_BRL_DOT5 +}; + +#define MASCHINE_BUTTONS (42) +#define MASCHINE_BUTTON(X) ((X) + BTN_MISC) +#define MASCHINE_PADS (16) +#define MASCHINE_PAD(X) ((X) + ABS_PRESSURE) + +static const unsigned short keycode_maschine[] = { + MASCHINE_BUTTON(40), /* mute */ + MASCHINE_BUTTON(39), /* solo */ + MASCHINE_BUTTON(38), /* select */ + MASCHINE_BUTTON(37), /* duplicate */ + MASCHINE_BUTTON(36), /* navigate */ + MASCHINE_BUTTON(35), /* pad mode */ + MASCHINE_BUTTON(34), /* pattern */ + MASCHINE_BUTTON(33), /* scene */ + KEY_RESERVED, /* spacer */ + + MASCHINE_BUTTON(30), /* rec */ + MASCHINE_BUTTON(31), /* erase */ + MASCHINE_BUTTON(32), /* shift */ + MASCHINE_BUTTON(28), /* grid */ + MASCHINE_BUTTON(27), /* > */ + MASCHINE_BUTTON(26), /* < */ + MASCHINE_BUTTON(25), /* restart */ + + MASCHINE_BUTTON(21), /* E */ + MASCHINE_BUTTON(22), /* F */ + MASCHINE_BUTTON(23), /* G */ + MASCHINE_BUTTON(24), /* H */ + MASCHINE_BUTTON(20), /* D */ + MASCHINE_BUTTON(19), /* C */ + MASCHINE_BUTTON(18), /* B */ + MASCHINE_BUTTON(17), /* A */ + + MASCHINE_BUTTON(0), /* control */ + MASCHINE_BUTTON(2), /* browse */ + MASCHINE_BUTTON(4), /* < */ + MASCHINE_BUTTON(6), /* snap */ + MASCHINE_BUTTON(7), /* autowrite */ + MASCHINE_BUTTON(5), /* > */ + MASCHINE_BUTTON(3), /* sampling */ + MASCHINE_BUTTON(1), /* step */ + + MASCHINE_BUTTON(15), /* 8 softkeys */ + MASCHINE_BUTTON(14), + MASCHINE_BUTTON(13), + MASCHINE_BUTTON(12), + MASCHINE_BUTTON(11), + MASCHINE_BUTTON(10), + MASCHINE_BUTTON(9), + MASCHINE_BUTTON(8), + + MASCHINE_BUTTON(16), /* note repeat */ + MASCHINE_BUTTON(29) /* play */ +}; + +#define KONTROLX1_INPUTS (40) +#define KONTROLS4_BUTTONS (12 * 8) +#define KONTROLS4_AXIS (46) + +#define KONTROLS4_BUTTON(X) ((X) + BTN_MISC) +#define KONTROLS4_ABS(X) ((X) + ABS_HAT0X) + +#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 inline void snd_caiaq_input_report_abs(struct snd_usb_caiaqdev *cdev, + int axis, const unsigned char *buf, + int offset) +{ + input_report_abs(cdev->input_dev, axis, + (buf[offset * 2] << 8) | buf[offset * 2 + 1]); +} + +static void snd_caiaq_input_read_analog(struct snd_usb_caiaqdev *cdev, + const unsigned char *buf, + unsigned int len) +{ + struct input_dev *input_dev = cdev->input_dev; + + switch (cdev->chip.usb_id) { + case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_RIGKONTROL2): + if (len < 6) + return; + snd_caiaq_input_report_abs(cdev, ABS_X, buf, 2); + snd_caiaq_input_report_abs(cdev, ABS_Y, buf, 0); + snd_caiaq_input_report_abs(cdev, ABS_Z, buf, 1); + break; + case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_RIGKONTROL3): + case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_KORECONTROLLER): + case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_KORECONTROLLER2): + if (len < 6) + return; + snd_caiaq_input_report_abs(cdev, ABS_X, buf, 0); + snd_caiaq_input_report_abs(cdev, ABS_Y, buf, 1); + snd_caiaq_input_report_abs(cdev, ABS_Z, buf, 2); + break; + case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_TRAKTORKONTROLX1): + if (len < 16) + return; + snd_caiaq_input_report_abs(cdev, ABS_HAT0X, buf, 4); + snd_caiaq_input_report_abs(cdev, ABS_HAT0Y, buf, 2); + snd_caiaq_input_report_abs(cdev, ABS_HAT1X, buf, 6); + snd_caiaq_input_report_abs(cdev, ABS_HAT1Y, buf, 1); + snd_caiaq_input_report_abs(cdev, ABS_HAT2X, buf, 7); + snd_caiaq_input_report_abs(cdev, ABS_HAT2Y, buf, 0); + snd_caiaq_input_report_abs(cdev, ABS_HAT3X, buf, 5); + snd_caiaq_input_report_abs(cdev, ABS_HAT3Y, buf, 3); + break; + } + + input_sync(input_dev); +} + +static void snd_caiaq_input_read_erp(struct snd_usb_caiaqdev *cdev, + const char *buf, unsigned int len) +{ + struct input_dev *input_dev = cdev->input_dev; + int i; + + switch (cdev->chip.usb_id) { + case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_AK1): + if (len < 2) + return; + i = decode_erp(buf[0], buf[1]); + input_report_abs(input_dev, ABS_X, i); + input_sync(input_dev); + break; + case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_KORECONTROLLER): + case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_KORECONTROLLER2): + if (len < 16) + return; + i = decode_erp(buf[7], buf[5]); + input_report_abs(input_dev, ABS_HAT0X, i); + i = decode_erp(buf[12], buf[14]); + input_report_abs(input_dev, ABS_HAT0Y, i); + i = decode_erp(buf[15], buf[13]); + input_report_abs(input_dev, ABS_HAT1X, i); + i = decode_erp(buf[0], buf[2]); + input_report_abs(input_dev, ABS_HAT1Y, i); + i = decode_erp(buf[3], buf[1]); + input_report_abs(input_dev, ABS_HAT2X, i); + i = decode_erp(buf[8], buf[10]); + input_report_abs(input_dev, ABS_HAT2Y, i); + i = decode_erp(buf[11], buf[9]); + input_report_abs(input_dev, ABS_HAT3X, i); + i = decode_erp(buf[4], buf[6]); + input_report_abs(input_dev, ABS_HAT3Y, i); + input_sync(input_dev); + break; + + case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_MASCHINECONTROLLER): + if (len < 22) + return; + /* 4 under the left screen */ + input_report_abs(input_dev, ABS_HAT0X, decode_erp(buf[21], buf[20])); + input_report_abs(input_dev, ABS_HAT0Y, decode_erp(buf[15], buf[14])); + input_report_abs(input_dev, ABS_HAT1X, decode_erp(buf[9], buf[8])); + input_report_abs(input_dev, ABS_HAT1Y, decode_erp(buf[3], buf[2])); + + /* 4 under the right screen */ + input_report_abs(input_dev, ABS_HAT2X, decode_erp(buf[19], buf[18])); + input_report_abs(input_dev, ABS_HAT2Y, decode_erp(buf[13], buf[12])); + input_report_abs(input_dev, ABS_HAT3X, decode_erp(buf[7], buf[6])); + input_report_abs(input_dev, ABS_HAT3Y, decode_erp(buf[1], buf[0])); + + /* volume */ + input_report_abs(input_dev, ABS_RX, decode_erp(buf[17], buf[16])); + /* tempo */ + input_report_abs(input_dev, ABS_RY, decode_erp(buf[11], buf[10])); + /* swing */ + input_report_abs(input_dev, ABS_RZ, decode_erp(buf[5], buf[4])); + + input_sync(input_dev); + break; + } +} + +static void snd_caiaq_input_read_io(struct snd_usb_caiaqdev *cdev, + unsigned char *buf, unsigned int len) +{ + struct input_dev *input_dev = cdev->input_dev; + unsigned short *keycode = input_dev->keycode; + int i; + + if (!keycode) + return; + + if (input_dev->id.product == USB_PID_RIGKONTROL2) + for (i = 0; i < len; i++) + buf[i] = ~buf[i]; + + for (i = 0; i < input_dev->keycodemax && i < len * 8; i++) + input_report_key(input_dev, keycode[i], + buf[i / 8] & (1 << (i % 8))); + + switch (cdev->chip.usb_id) { + case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_KORECONTROLLER): + case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_KORECONTROLLER2): + if (len < 5) + return; + input_report_abs(cdev->input_dev, ABS_MISC, 255 - buf[4]); + break; + case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_TRAKTORKONTROLX1): + if (len < 7) + return; + /* rotary encoders */ + input_report_abs(cdev->input_dev, ABS_X, buf[5] & 0xf); + input_report_abs(cdev->input_dev, ABS_Y, buf[5] >> 4); + input_report_abs(cdev->input_dev, ABS_Z, buf[6] & 0xf); + input_report_abs(cdev->input_dev, ABS_MISC, buf[6] >> 4); + break; + } + + input_sync(input_dev); +} + +#define TKS4_MSGBLOCK_SIZE 16 + +static void snd_usb_caiaq_tks4_dispatch(struct snd_usb_caiaqdev *cdev, + const unsigned char *buf, + unsigned int len) +{ + struct device *dev = caiaqdev_to_dev(cdev); + + while (len >= TKS4_MSGBLOCK_SIZE) { + unsigned int i, block_id = (buf[0] << 8) | buf[1]; + + switch (block_id) { + case 0: + /* buttons */ + for (i = 0; i < KONTROLS4_BUTTONS; i++) + input_report_key(cdev->input_dev, KONTROLS4_BUTTON(i), + (buf[4 + (i / 8)] >> (i % 8)) & 1); + break; + + case 1: + /* left wheel */ + input_report_abs(cdev->input_dev, KONTROLS4_ABS(36), buf[9] | ((buf[8] & 0x3) << 8)); + /* right wheel */ + input_report_abs(cdev->input_dev, KONTROLS4_ABS(37), buf[13] | ((buf[12] & 0x3) << 8)); + + /* rotary encoders */ + input_report_abs(cdev->input_dev, KONTROLS4_ABS(38), buf[3] & 0xf); + input_report_abs(cdev->input_dev, KONTROLS4_ABS(39), buf[4] >> 4); + input_report_abs(cdev->input_dev, KONTROLS4_ABS(40), buf[4] & 0xf); + input_report_abs(cdev->input_dev, KONTROLS4_ABS(41), buf[5] >> 4); + input_report_abs(cdev->input_dev, KONTROLS4_ABS(42), buf[5] & 0xf); + input_report_abs(cdev->input_dev, KONTROLS4_ABS(43), buf[6] >> 4); + input_report_abs(cdev->input_dev, KONTROLS4_ABS(44), buf[6] & 0xf); + input_report_abs(cdev->input_dev, KONTROLS4_ABS(45), buf[7] >> 4); + input_report_abs(cdev->input_dev, KONTROLS4_ABS(46), buf[7] & 0xf); + + break; + case 2: + /* Volume Fader Channel D */ + snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(0), buf, 1); + /* Volume Fader Channel B */ + snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(1), buf, 2); + /* Volume Fader Channel A */ + snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(2), buf, 3); + /* Volume Fader Channel C */ + snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(3), buf, 4); + /* Loop Volume */ + snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(4), buf, 6); + /* Crossfader */ + snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(7), buf, 7); + + break; + + case 3: + /* Tempo Fader R */ + snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(6), buf, 3); + /* Tempo Fader L */ + snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(5), buf, 4); + /* Mic Volume */ + snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(8), buf, 6); + /* Cue Mix */ + snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(9), buf, 7); + + break; + + case 4: + /* Wheel distance sensor L */ + snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(10), buf, 1); + /* Wheel distance sensor R */ + snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(11), buf, 2); + /* Channel D EQ - Filter */ + snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(12), buf, 3); + /* Channel D EQ - Low */ + snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(13), buf, 4); + /* Channel D EQ - Mid */ + snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(14), buf, 5); + /* Channel D EQ - Hi */ + snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(15), buf, 6); + /* FX2 - dry/wet */ + snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(16), buf, 7); + + break; + + case 5: + /* FX2 - 1 */ + snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(17), buf, 1); + /* FX2 - 2 */ + snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(18), buf, 2); + /* FX2 - 3 */ + snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(19), buf, 3); + /* Channel B EQ - Filter */ + snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(20), buf, 4); + /* Channel B EQ - Low */ + snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(21), buf, 5); + /* Channel B EQ - Mid */ + snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(22), buf, 6); + /* Channel B EQ - Hi */ + snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(23), buf, 7); + + break; + + case 6: + /* Channel A EQ - Filter */ + snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(24), buf, 1); + /* Channel A EQ - Low */ + snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(25), buf, 2); + /* Channel A EQ - Mid */ + snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(26), buf, 3); + /* Channel A EQ - Hi */ + snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(27), buf, 4); + /* Channel C EQ - Filter */ + snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(28), buf, 5); + /* Channel C EQ - Low */ + snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(29), buf, 6); + /* Channel C EQ - Mid */ + snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(30), buf, 7); + + break; + + case 7: + /* Channel C EQ - Hi */ + snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(31), buf, 1); + /* FX1 - wet/dry */ + snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(32), buf, 2); + /* FX1 - 1 */ + snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(33), buf, 3); + /* FX1 - 2 */ + snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(34), buf, 4); + /* FX1 - 3 */ + snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(35), buf, 5); + + break; + + default: + dev_dbg(dev, "%s(): bogus block (id %d)\n", + __func__, block_id); + return; + } + + len -= TKS4_MSGBLOCK_SIZE; + buf += TKS4_MSGBLOCK_SIZE; + } + + input_sync(cdev->input_dev); +} + +#define MASCHINE_MSGBLOCK_SIZE 2 + +static void snd_usb_caiaq_maschine_dispatch(struct snd_usb_caiaqdev *cdev, + const unsigned char *buf, + unsigned int len) +{ + unsigned int i, pad_id; + __le16 *pressure = (__le16 *) buf; + + for (i = 0; i < MASCHINE_PADS; i++) { + pad_id = le16_to_cpu(*pressure) >> 12; + input_report_abs(cdev->input_dev, MASCHINE_PAD(pad_id), + le16_to_cpu(*pressure) & 0xfff); + pressure++; + } + + input_sync(cdev->input_dev); +} + +static void snd_usb_caiaq_ep4_reply_dispatch(struct urb *urb) +{ + struct snd_usb_caiaqdev *cdev = urb->context; + unsigned char *buf = urb->transfer_buffer; + struct device *dev = &urb->dev->dev; + int ret; + + if (urb->status || !cdev || urb != cdev->ep4_in_urb) + return; + + switch (cdev->chip.usb_id) { + case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_TRAKTORKONTROLX1): + if (urb->actual_length < 24) + goto requeue; + + if (buf[0] & 0x3) + snd_caiaq_input_read_io(cdev, buf + 1, 7); + + if (buf[0] & 0x4) + snd_caiaq_input_read_analog(cdev, buf + 8, 16); + + break; + + case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_TRAKTORKONTROLS4): + snd_usb_caiaq_tks4_dispatch(cdev, buf, urb->actual_length); + break; + + case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_MASCHINECONTROLLER): + if (urb->actual_length < (MASCHINE_PADS * MASCHINE_MSGBLOCK_SIZE)) + goto requeue; + + snd_usb_caiaq_maschine_dispatch(cdev, buf, urb->actual_length); + break; + } + +requeue: + cdev->ep4_in_urb->actual_length = 0; + ret = usb_submit_urb(cdev->ep4_in_urb, GFP_ATOMIC); + if (ret < 0) + dev_err(dev, "unable to submit urb. OOM!?\n"); +} + +static int snd_usb_caiaq_input_open(struct input_dev *idev) +{ + struct snd_usb_caiaqdev *cdev = input_get_drvdata(idev); + + if (!cdev) + return -EINVAL; + + switch (cdev->chip.usb_id) { + case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_TRAKTORKONTROLX1): + case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_TRAKTORKONTROLS4): + case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_MASCHINECONTROLLER): + if (usb_submit_urb(cdev->ep4_in_urb, GFP_KERNEL) != 0) + return -EIO; + break; + } + + return 0; +} + +static void snd_usb_caiaq_input_close(struct input_dev *idev) +{ + struct snd_usb_caiaqdev *cdev = input_get_drvdata(idev); + + if (!cdev) + return; + + switch (cdev->chip.usb_id) { + case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_TRAKTORKONTROLX1): + case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_TRAKTORKONTROLS4): + case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_MASCHINECONTROLLER): + usb_kill_urb(cdev->ep4_in_urb); + break; + } +} + +void snd_usb_caiaq_input_dispatch(struct snd_usb_caiaqdev *cdev, + char *buf, + unsigned int len) +{ + if (!cdev->input_dev || len < 1) + return; + + switch (buf[0]) { + case EP1_CMD_READ_ANALOG: + snd_caiaq_input_read_analog(cdev, buf + 1, len - 1); + break; + case EP1_CMD_READ_ERP: + snd_caiaq_input_read_erp(cdev, buf + 1, len - 1); + break; + case EP1_CMD_READ_IO: + snd_caiaq_input_read_io(cdev, buf + 1, len - 1); + break; + } +} + +int snd_usb_caiaq_input_init(struct snd_usb_caiaqdev *cdev) +{ + struct usb_device *usb_dev = cdev->chip.dev; + struct input_dev *input; + int i, ret = 0; + + input = input_allocate_device(); + if (!input) + return -ENOMEM; + + usb_make_path(usb_dev, cdev->phys, sizeof(cdev->phys)); + strlcat(cdev->phys, "/input0", sizeof(cdev->phys)); + + input->name = cdev->product_name; + input->phys = cdev->phys; + usb_to_input_id(usb_dev, &input->id); + input->dev.parent = &usb_dev->dev; + + input_set_drvdata(input, cdev); + + switch (cdev->chip.usb_id) { + case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_RIGKONTROL2): + input->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS); + input->absbit[0] = BIT_MASK(ABS_X) | BIT_MASK(ABS_Y) | + BIT_MASK(ABS_Z); + BUILD_BUG_ON(sizeof(cdev->keycode) < sizeof(keycode_rk2)); + memcpy(cdev->keycode, keycode_rk2, sizeof(keycode_rk2)); + input->keycodemax = ARRAY_SIZE(keycode_rk2); + 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(cdev, 1, 10, 0); + break; + case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_RIGKONTROL3): + input->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS); + input->absbit[0] = BIT_MASK(ABS_X) | BIT_MASK(ABS_Y) | + BIT_MASK(ABS_Z); + BUILD_BUG_ON(sizeof(cdev->keycode) < sizeof(keycode_rk3)); + memcpy(cdev->keycode, keycode_rk3, sizeof(keycode_rk3)); + input->keycodemax = ARRAY_SIZE(keycode_rk3); + input_set_abs_params(input, ABS_X, 0, 1024, 0, 10); + input_set_abs_params(input, ABS_Y, 0, 1024, 0, 10); + input_set_abs_params(input, ABS_Z, 0, 1024, 0, 10); + snd_usb_caiaq_set_auto_msg(cdev, 1, 10, 0); + break; + case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_AK1): + input->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS); + input->absbit[0] = BIT_MASK(ABS_X); + BUILD_BUG_ON(sizeof(cdev->keycode) < sizeof(keycode_ak1)); + memcpy(cdev->keycode, keycode_ak1, sizeof(keycode_ak1)); + input->keycodemax = ARRAY_SIZE(keycode_ak1); + input_set_abs_params(input, ABS_X, 0, 999, 0, 10); + snd_usb_caiaq_set_auto_msg(cdev, 1, 0, 5); + break; + case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_KORECONTROLLER): + case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_KORECONTROLLER2): + input->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS); + input->absbit[0] = BIT_MASK(ABS_HAT0X) | BIT_MASK(ABS_HAT0Y) | + BIT_MASK(ABS_HAT1X) | BIT_MASK(ABS_HAT1Y) | + BIT_MASK(ABS_HAT2X) | BIT_MASK(ABS_HAT2Y) | + BIT_MASK(ABS_HAT3X) | BIT_MASK(ABS_HAT3Y) | + BIT_MASK(ABS_X) | BIT_MASK(ABS_Y) | + BIT_MASK(ABS_Z); + input->absbit[BIT_WORD(ABS_MISC)] |= BIT_MASK(ABS_MISC); + BUILD_BUG_ON(sizeof(cdev->keycode) < sizeof(keycode_kore)); + memcpy(cdev->keycode, keycode_kore, sizeof(keycode_kore)); + input->keycodemax = ARRAY_SIZE(keycode_kore); + input_set_abs_params(input, ABS_HAT0X, 0, 999, 0, 10); + input_set_abs_params(input, ABS_HAT0Y, 0, 999, 0, 10); + input_set_abs_params(input, ABS_HAT1X, 0, 999, 0, 10); + input_set_abs_params(input, ABS_HAT1Y, 0, 999, 0, 10); + input_set_abs_params(input, ABS_HAT2X, 0, 999, 0, 10); + input_set_abs_params(input, ABS_HAT2Y, 0, 999, 0, 10); + input_set_abs_params(input, ABS_HAT3X, 0, 999, 0, 10); + input_set_abs_params(input, ABS_HAT3Y, 0, 999, 0, 10); + 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); + input_set_abs_params(input, ABS_MISC, 0, 255, 0, 1); + snd_usb_caiaq_set_auto_msg(cdev, 1, 10, 5); + break; + case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_TRAKTORKONTROLX1): + input->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS); + input->absbit[0] = BIT_MASK(ABS_HAT0X) | BIT_MASK(ABS_HAT0Y) | + BIT_MASK(ABS_HAT1X) | BIT_MASK(ABS_HAT1Y) | + BIT_MASK(ABS_HAT2X) | BIT_MASK(ABS_HAT2Y) | + BIT_MASK(ABS_HAT3X) | BIT_MASK(ABS_HAT3Y) | + BIT_MASK(ABS_X) | BIT_MASK(ABS_Y) | + BIT_MASK(ABS_Z); + input->absbit[BIT_WORD(ABS_MISC)] |= BIT_MASK(ABS_MISC); + BUILD_BUG_ON(sizeof(cdev->keycode) < KONTROLX1_INPUTS); + for (i = 0; i < KONTROLX1_INPUTS; i++) + cdev->keycode[i] = BTN_MISC + i; + input->keycodemax = KONTROLX1_INPUTS; + + /* analog potentiometers */ + input_set_abs_params(input, ABS_HAT0X, 0, 4096, 0, 10); + input_set_abs_params(input, ABS_HAT0Y, 0, 4096, 0, 10); + input_set_abs_params(input, ABS_HAT1X, 0, 4096, 0, 10); + input_set_abs_params(input, ABS_HAT1Y, 0, 4096, 0, 10); + input_set_abs_params(input, ABS_HAT2X, 0, 4096, 0, 10); + input_set_abs_params(input, ABS_HAT2Y, 0, 4096, 0, 10); + input_set_abs_params(input, ABS_HAT3X, 0, 4096, 0, 10); + input_set_abs_params(input, ABS_HAT3Y, 0, 4096, 0, 10); + + /* rotary encoders */ + input_set_abs_params(input, ABS_X, 0, 0xf, 0, 1); + input_set_abs_params(input, ABS_Y, 0, 0xf, 0, 1); + input_set_abs_params(input, ABS_Z, 0, 0xf, 0, 1); + input_set_abs_params(input, ABS_MISC, 0, 0xf, 0, 1); + + cdev->ep4_in_urb = usb_alloc_urb(0, GFP_KERNEL); + if (!cdev->ep4_in_urb) { + ret = -ENOMEM; + goto exit_free_idev; + } + + usb_fill_bulk_urb(cdev->ep4_in_urb, usb_dev, + usb_rcvbulkpipe(usb_dev, 0x4), + cdev->ep4_in_buf, EP4_BUFSIZE, + snd_usb_caiaq_ep4_reply_dispatch, cdev); + ret = usb_urb_ep_type_check(cdev->ep4_in_urb); + if (ret < 0) + goto exit_free_idev; + + snd_usb_caiaq_set_auto_msg(cdev, 1, 10, 5); + + break; + + case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_TRAKTORKONTROLS4): + input->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS); + BUILD_BUG_ON(sizeof(cdev->keycode) < KONTROLS4_BUTTONS); + for (i = 0; i < KONTROLS4_BUTTONS; i++) + cdev->keycode[i] = KONTROLS4_BUTTON(i); + input->keycodemax = KONTROLS4_BUTTONS; + + for (i = 0; i < KONTROLS4_AXIS; i++) { + int axis = KONTROLS4_ABS(i); + input->absbit[BIT_WORD(axis)] |= BIT_MASK(axis); + } + + /* 36 analog potentiometers and faders */ + for (i = 0; i < 36; i++) + input_set_abs_params(input, KONTROLS4_ABS(i), 0, 0xfff, 0, 10); + + /* 2 encoder wheels */ + input_set_abs_params(input, KONTROLS4_ABS(36), 0, 0x3ff, 0, 1); + input_set_abs_params(input, KONTROLS4_ABS(37), 0, 0x3ff, 0, 1); + + /* 9 rotary encoders */ + for (i = 0; i < 9; i++) + input_set_abs_params(input, KONTROLS4_ABS(38+i), 0, 0xf, 0, 1); + + cdev->ep4_in_urb = usb_alloc_urb(0, GFP_KERNEL); + if (!cdev->ep4_in_urb) { + ret = -ENOMEM; + goto exit_free_idev; + } + + usb_fill_bulk_urb(cdev->ep4_in_urb, usb_dev, + usb_rcvbulkpipe(usb_dev, 0x4), + cdev->ep4_in_buf, EP4_BUFSIZE, + snd_usb_caiaq_ep4_reply_dispatch, cdev); + ret = usb_urb_ep_type_check(cdev->ep4_in_urb); + if (ret < 0) + goto exit_free_idev; + + snd_usb_caiaq_set_auto_msg(cdev, 1, 10, 5); + + break; + + case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_MASCHINECONTROLLER): + input->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS); + input->absbit[0] = BIT_MASK(ABS_HAT0X) | BIT_MASK(ABS_HAT0Y) | + BIT_MASK(ABS_HAT1X) | BIT_MASK(ABS_HAT1Y) | + BIT_MASK(ABS_HAT2X) | BIT_MASK(ABS_HAT2Y) | + BIT_MASK(ABS_HAT3X) | BIT_MASK(ABS_HAT3Y) | + BIT_MASK(ABS_RX) | BIT_MASK(ABS_RY) | + BIT_MASK(ABS_RZ); + + BUILD_BUG_ON(sizeof(cdev->keycode) < sizeof(keycode_maschine)); + memcpy(cdev->keycode, keycode_maschine, sizeof(keycode_maschine)); + input->keycodemax = ARRAY_SIZE(keycode_maschine); + + for (i = 0; i < MASCHINE_PADS; i++) { + input->absbit[0] |= MASCHINE_PAD(i); + input_set_abs_params(input, MASCHINE_PAD(i), 0, 0xfff, 5, 10); + } + + input_set_abs_params(input, ABS_HAT0X, 0, 999, 0, 10); + input_set_abs_params(input, ABS_HAT0Y, 0, 999, 0, 10); + input_set_abs_params(input, ABS_HAT1X, 0, 999, 0, 10); + input_set_abs_params(input, ABS_HAT1Y, 0, 999, 0, 10); + input_set_abs_params(input, ABS_HAT2X, 0, 999, 0, 10); + input_set_abs_params(input, ABS_HAT2Y, 0, 999, 0, 10); + input_set_abs_params(input, ABS_HAT3X, 0, 999, 0, 10); + input_set_abs_params(input, ABS_HAT3Y, 0, 999, 0, 10); + input_set_abs_params(input, ABS_RX, 0, 999, 0, 10); + input_set_abs_params(input, ABS_RY, 0, 999, 0, 10); + input_set_abs_params(input, ABS_RZ, 0, 999, 0, 10); + + cdev->ep4_in_urb = usb_alloc_urb(0, GFP_KERNEL); + if (!cdev->ep4_in_urb) { + ret = -ENOMEM; + goto exit_free_idev; + } + + usb_fill_bulk_urb(cdev->ep4_in_urb, usb_dev, + usb_rcvbulkpipe(usb_dev, 0x4), + cdev->ep4_in_buf, EP4_BUFSIZE, + snd_usb_caiaq_ep4_reply_dispatch, cdev); + ret = usb_urb_ep_type_check(cdev->ep4_in_urb); + if (ret < 0) + goto exit_free_idev; + + snd_usb_caiaq_set_auto_msg(cdev, 1, 10, 5); + break; + + default: + /* no input methods supported on this device */ + ret = -ENODEV; + goto exit_free_idev; + } + + input->open = snd_usb_caiaq_input_open; + input->close = snd_usb_caiaq_input_close; + input->keycode = cdev->keycode; + input->keycodesize = sizeof(unsigned short); + for (i = 0; i < input->keycodemax; i++) + __set_bit(cdev->keycode[i], input->keybit); + + cdev->input_dev = input; + + ret = input_register_device(input); + if (ret < 0) + goto exit_free_idev; + + return 0; + +exit_free_idev: + input_free_device(input); + cdev->input_dev = NULL; + return ret; +} + +void snd_usb_caiaq_input_disconnect(struct snd_usb_caiaqdev *cdev) +{ + if (!cdev || !cdev->input_dev) + return; + + usb_kill_urb(cdev->ep4_in_urb); + input_unregister_device(cdev->input_dev); +} + +void snd_usb_caiaq_input_free(struct snd_usb_caiaqdev *cdev) +{ + if (!cdev || !cdev->input_dev) + return; + + usb_free_urb(cdev->ep4_in_urb); + cdev->ep4_in_urb = NULL; + cdev->input_dev = NULL; +} diff --git a/sound/usb/caiaq/input.h b/sound/usb/caiaq/input.h new file mode 100644 index 000000000..fbe267f85 --- /dev/null +++ b/sound/usb/caiaq/input.h @@ -0,0 +1,10 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef CAIAQ_INPUT_H +#define CAIAQ_INPUT_H + +void snd_usb_caiaq_input_dispatch(struct snd_usb_caiaqdev *cdev, char *buf, unsigned int len); +int snd_usb_caiaq_input_init(struct snd_usb_caiaqdev *cdev); +void snd_usb_caiaq_input_disconnect(struct snd_usb_caiaqdev *cdev); +void snd_usb_caiaq_input_free(struct snd_usb_caiaqdev *cdev); + +#endif diff --git a/sound/usb/caiaq/midi.c b/sound/usb/caiaq/midi.c new file mode 100644 index 000000000..18529484c --- /dev/null +++ b/sound/usb/caiaq/midi.c @@ -0,0 +1,162 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2006,2007 Daniel Mack +*/ + +#include <linux/device.h> +#include <linux/usb.h> +#include <linux/gfp.h> +#include <sound/rawmidi.h> +#include <sound/core.h> +#include <sound/pcm.h> + +#include "device.h" +#include "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 *cdev = substream->rmidi->private_data; + + if (!cdev) + return; + + cdev->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) +{ + struct snd_usb_caiaqdev *cdev = substream->rmidi->private_data; + if (cdev->midi_out_active) { + usb_kill_urb(cdev->midi_out_urb); + cdev->midi_out_active = 0; + } + return 0; +} + +static void snd_usb_caiaq_midi_send(struct snd_usb_caiaqdev *cdev, + struct snd_rawmidi_substream *substream) +{ + int len, ret; + struct device *dev = caiaqdev_to_dev(cdev); + + cdev->midi_out_buf[0] = EP1_CMD_MIDI_WRITE; + cdev->midi_out_buf[1] = 0; /* port */ + len = snd_rawmidi_transmit(substream, cdev->midi_out_buf + 3, + EP1_BUFSIZE - 3); + + if (len <= 0) + return; + + cdev->midi_out_buf[2] = len; + cdev->midi_out_urb->transfer_buffer_length = len+3; + + ret = usb_submit_urb(cdev->midi_out_urb, GFP_ATOMIC); + if (ret < 0) + dev_err(dev, + "snd_usb_caiaq_midi_send(%p): usb_submit_urb() failed," + "ret=%d, len=%d\n", substream, ret, len); + else + cdev->midi_out_active = 1; +} + +static void snd_usb_caiaq_midi_output_trigger(struct snd_rawmidi_substream *substream, int up) +{ + struct snd_usb_caiaqdev *cdev = substream->rmidi->private_data; + + if (up) { + cdev->midi_out_substream = substream; + if (!cdev->midi_out_active) + snd_usb_caiaq_midi_send(cdev, substream); + } else { + cdev->midi_out_substream = NULL; + } +} + + +static const 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 const 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 *cdev, + int port, const char *buf, int len) +{ + if (!cdev->midi_receive_substream) + return; + + snd_rawmidi_receive(cdev->midi_receive_substream, buf, len); +} + +int 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; + + strscpy(rmidi->name, device->product_name, sizeof(rmidi->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 *cdev = urb->context; + + cdev->midi_out_active = 0; + if (urb->status != 0) + return; + + if (!cdev->midi_out_substream) + return; + + snd_usb_caiaq_midi_send(cdev, cdev->midi_out_substream); +} diff --git a/sound/usb/caiaq/midi.h b/sound/usb/caiaq/midi.h new file mode 100644 index 000000000..a6ae0c224 --- /dev/null +++ b/sound/usb/caiaq/midi.h @@ -0,0 +1,10 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef CAIAQ_MIDI_H +#define CAIAQ_MIDI_H + +int snd_usb_caiaq_midi_init(struct snd_usb_caiaqdev *cdev); +void snd_usb_caiaq_midi_handle_input(struct snd_usb_caiaqdev *cdev, + int port, const char *buf, int len); +void snd_usb_caiaq_midi_output_done(struct urb *urb); + +#endif /* CAIAQ_MIDI_H */ |
