Author: Tobias Hoffman <th55(a)gmx.de>
Author: Robin Gareus <robin(a)gareus.org>
Signed-off-by: David Henningsson <david.henningsson(a)canonical.com>
---
So, this is how Tobias' and Robin's patch look now. I've merged it all to one
big patch, both for my own simplicity and because I thought that made the most
sense. I've also fixed most checkpatch stuff (apart from long lines warnings).
It's not ready for merging yet, I assume. But it would be good with a hint w r t
what the big issues are with this patch as you see it. And then we could see if I
have some spare cycles to fix that up, or not.
sound/usb/Makefile | 1 +
sound/usb/mixer.c | 28 +-
sound/usb/quirks-table.h | 51 --
sound/usb/scarlettmixer.c | 1374 +++++++++++++++++++++++++++++++++++++++++++++
sound/usb/scarlettmixer.h | 6 +
5 files changed, 1404 insertions(+), 56 deletions(-)
create mode 100644 sound/usb/scarlettmixer.c
create mode 100644 sound/usb/scarlettmixer.h
diff --git a/sound/usb/Makefile b/sound/usb/Makefile
index 2b92f0d..5d9008d 100644
--- a/sound/usb/Makefile
+++ b/sound/usb/Makefile
@@ -12,6 +12,7 @@ snd-usb-audio-objs := card.o \
pcm.o \
proc.o \
quirks.o \
+ scarlettmixer.o \
stream.o
snd-usbmidi-lib-objs := midi.o
diff --git a/sound/usb/mixer.c b/sound/usb/mixer.c
index 2e4a9db..8360762 100644
--- a/sound/usb/mixer.c
+++ b/sound/usb/mixer.c
@@ -62,6 +62,7 @@
#include "helper.h"
#include "mixer_quirks.h"
#include "power.h"
+#include "scarlettmixer.h"
#define MAX_ID_ELEMS 256
@@ -2305,6 +2306,7 @@ static void snd_usb_mixer_interrupt_v2(struct usb_mixer_interface *mixer,
__func__, channel);
return;
}
+snd_printk(KERN_INFO "scarlett thobi mixer interrupt %x %x %x\n", attribute, value, index); /* TODO */
for (info = mixer->id_elems[unitid]; info; info = info->next_id_elem) {
if (info->control != control)
@@ -2462,11 +2464,27 @@ int snd_usb_create_mixer(struct snd_usb_audio *chip, int ctrlif,
break;
}
- if ((err = snd_usb_mixer_controls(mixer)) < 0 ||
- (err = snd_usb_mixer_status_create(mixer)) < 0)
- goto _error;
-
- snd_usb_mixer_apply_create_quirk(mixer);
+ switch (chip->usb_id) {
+ case USB_ID(0x1235, 0x8012): /* Focusrite Scarlett 6i6 */
+ case USB_ID(0x1235, 0x8002): /* Focusrite Scarlett 8i6 */
+ case USB_ID(0x1235, 0x8004): /* Focusrite Scarlett 18i6 */
+ case USB_ID(0x1235, 0x8014): /* Focusrite Scarlett 18i8 */
+ case USB_ID(0x1235, 0x800c): /* Focusrite Scarlett 18i20 */
+ /* don't even try to parse UAC2 descriptors */
+ err = scarlett_mixer_controls(mixer);
+ if (err < 0)
+ goto _error;
+ break;
+ default:
+ err = snd_usb_mixer_controls(mixer);
+ if (err < 0)
+ goto _error;
+ err = snd_usb_mixer_status_create(mixer);
+ if (err < 0)
+ goto _error;
+ snd_usb_mixer_apply_create_quirk(mixer);
+ break;
+ }
err = snd_device_new(chip->card, SNDRV_DEV_CODEC, mixer, &dev_ops);
if (err < 0)
diff --git a/sound/usb/quirks-table.h b/sound/usb/quirks-table.h
index 223c47b..70ae637 100644
--- a/sound/usb/quirks-table.h
+++ b/sound/usb/quirks-table.h
@@ -2637,57 +2637,6 @@ YAMAHA_DEVICE(0x7010, "UB99"),
.type = QUIRK_MIDI_NOVATION
}
},
-{
- /*
- * Focusrite Scarlett 18i6
- *
- * Avoid mixer creation, which otherwise fails because some of
- * the interface descriptor subtypes for interface 0 are
- * unknown. That should be fixed or worked-around but this at
- * least allows the device to be used successfully with a DAW
- * and an external mixer. See comments below about other
- * ignored interfaces.
- */
- USB_DEVICE(0x1235, 0x8004),
- .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
- .vendor_name = "Focusrite",
- .product_name = "Scarlett 18i6",
- .ifnum = QUIRK_ANY_INTERFACE,
- .type = QUIRK_COMPOSITE,
- .data = & (const struct snd_usb_audio_quirk[]) {
- {
- /* InterfaceSubClass 1 (Control Device) */
- .ifnum = 0,
- .type = QUIRK_IGNORE_INTERFACE
- },
- {
- .ifnum = 1,
- .type = QUIRK_AUDIO_STANDARD_INTERFACE
- },
- {
- .ifnum = 2,
- .type = QUIRK_AUDIO_STANDARD_INTERFACE
- },
- {
- /* InterfaceSubClass 1 (Control Device) */
- .ifnum = 3,
- .type = QUIRK_IGNORE_INTERFACE
- },
- {
- .ifnum = 4,
- .type = QUIRK_MIDI_STANDARD_INTERFACE
- },
- {
- /* InterfaceSubClass 1 (Device Firmware Update) */
- .ifnum = 5,
- .type = QUIRK_IGNORE_INTERFACE
- },
- {
- .ifnum = -1
- }
- }
- }
-},
/* Access Music devices */
{
diff --git a/sound/usb/scarlettmixer.c b/sound/usb/scarlettmixer.c
new file mode 100644
index 0000000..873d5d6
--- /dev/null
+++ b/sound/usb/scarlettmixer.c
@@ -0,0 +1,1374 @@
+/*
+ * (Tentative) Scarlett 18i6 Driver for ALSA
+ *
+ * Copyright (c) 2013 by Tobias Hoffmann
+ * Copyright (c) 2013 by Robin Gareus <robin(a)gareus.org>
+ * Copyright (c) 2002 by Takashi Iwai <tiwai(a)suse.de>
+ *
+ * Many codes borrowed from audio.c by
+ * Alan Cox (alan(a)lxorguk.ukuu.org.uk)
+ * Thomas Sailer (sailer(a)ife.ee.ethz.ch)
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+
+/*
+ * Rewritten and extended to support more models, e.g. Scarlett 18i8.
+ * TODO? reset_first/ channel init
+ * TODO... test meter?
+ */
+
+/*
+ * Many features of Scarlett 18i6 do not lend themselves to be implemented
+ * as simple mixer-quirk -- or at least I don't see a way how to do that, yet.
+ * Hence the top parts of this file is a 1:1 copy of select static functions
+ * from mixer.c to implement the interface.
+ * Suggestions how to avoid this code duplication are very welcome.
+ *
+ * eventually this should either be integrated as quirk into mixer_quirks.c
+ * or become a standalone module.
+ *
+ * This source hardcodes the URBs for the Scarlett,
+ * Auto-detection via UAC2 is not feasible to properly discover the vast majority
+ * of features. It's related to both Linux/ALSA's UAC2 as well as Focusrite's
+ * implementation of it. Eventually quirks may be sufficient but right now
+ * it's a major headache to work arount these things.
+ *
+ * NB. Neither the OSX nor the win driver provided by Focusrite performs
+ * discovery, they seem to operate the same as this driver.
+ */
+
+/* Mixer Interface for the Focusrite Scarlett 18i6 audio interface.
+ *
+ * The protocol was reverse engineered by looking at communication between
+ * Scarlett MixControl (v 1.2.128.0) and the Focusrite(R) Scarlett 18i6 (firmware
+ * v305) using wireshark and usbmon in January 2013.
+ * Extended in July 2013.
+ *
+ * this mixer gives complete access to all features of the device:
+ * - change Impedance of inputs (Line-in, Mic / Instrument, Hi-Z)
+ * - select clock source
+ * - dynamic input to mixer-matrix assignment
+ * - 18 x 6 mixer-matrix gain stages
+ * - bus routing & volume control
+ * - save setting to hardware
+ * - automatic re-initialization on connect if device was power-cycled
+ * - peak monitoring of all 3 buses (18 input, 6 DAW input, 6 route chanels)
+ * (changing the samplerate and buffersize is supported by the PCM interface)
+ *
+ *
+ * USB URB commands overview (bRequest = 0x01 = UAC2_CS_CUR)
+ * wIndex
+ * 0x01 Analog Input line/instrument impedance switch, wValue=0x0901 + channel, data=Line/Inst (2bytes)
+ * pad (-10dB) switch, wValue=0x0b01 + channel, data=Off/On (2bytes)
+ * ?? wValue=0x0803/04, ?? (2bytes)
+ * 0x0a Master Volume, wValue=0x0200+bus[0:all + only 1..4?] data(2bytes)
+ * Bus Mute/Unmute wValue=0x0100+bus[0:all + only 1..4?], data(2bytes)
+ * 0x28 Clock source, wValue=0x0100, data={1:int,2:spdif,3:adat} (1byte)
+ * 0x29 Set Sample-rate, wValue=0x0100, data=sample-rate(4bytes)
+ * 0x32 Mixer mux, wValue=0x0600 + mixer-channel, data=input-to-connect(2bytes)
+ * 0x33 Output mux, wValue=bus, data=input-to-connect(2bytes)
+ * 0x34 Capture mux, wValue=0...18, data=input-to-connect(2bytes)
+ * 0x3c Matrix Mixer gains, wValue=mixer-node data=gain(2bytes)
+ * ?? [sometimes](4bytes, e.g 0x000003be 0x000003bf ...03ff)
+ *
+ * USB reads: (i.e. actually issued by original software)
+ * 0x01 wValue=0x0901+channel (1byte!!), wValue=0x0b01+channed (1byte!!)
+ * 0x29 wValue=0x0100 sample-rate(4bytes)
+ * wValue=0x0200 ?? 1byte (only once)
+ * 0x2a wValue=0x0100 ?? 4bytes, sample-rate2 ??
+ *
+ * USB reads with bRequest = 0x03 = UAC2_CS_MEM
+ * 0x3c wValue=0x0002 1byte: sync status (locked=1)
+ * wValue=0x0000 18*2byte: peak meter (inputs)
+ * wValue=0x0001 8(?)*2byte: peak meter (mix)
+ * wValue=0x0003 6*2byte: peak meter (pcm/daw)
+ *
+ * USB write with bRequest = 0x03
+ * 0x3c Save settings to hardware: wValue=0x005a, data=0xa5
+ *
+ *
+ * <ditaa>
+ * /--------------\ 18chn 6chn /--------------\
+ * | Hardware in +--+-------\ /------+--+ ALSA PCM out |
+ * \--------------/ | | | | \--------------/
+ * | | | |
+ * | v v |
+ * | +---------------+ |
+ * | \ Matrix Mux / |
+ * | +-----+-----+ |
+ * | | |
+ * | | 18chn |
+ * | v |
+ * | +-----------+ |
+ * | | Mixer | |
+ * | | Matrix | |
+ * | | | |
+ * | | 18x6 Gain | |
+ * | | stages | |
+ * | +-----+-----+ |
+ * | | |
+ * | | |
+ * | 18chn | 6chn | 6chn
+ * v v v
+ * =========================
+ * +---------------+ +--—------------+
+ * \ Output Mux / \ Capture Mux /
+ * +-----+-----+ +-----+-----+
+ * | |
+ * | 6chn |
+ * v |
+ * +-------------+ |
+ * | Master Gain | |
+ * +------+------+ |
+ * | |
+ * | 6chn | 18chn
+ * | (3 stereo pairs) |
+ * /--------------\ | | /--------------\
+ * | Hardware out |<--/ \-->| ALSA PCM in |
+ * \--------------/ \--------------/
+ * </ditaa>
+ *
+ */
+
+#include <linux/slab.h>
+#include <linux/usb.h>
+#include <linux/usb/audio-v2.h>
+
+#include <sound/core.h>
+#include <sound/control.h>
+#include <sound/tlv.h>
+
+#include "usbaudio.h"
+#include "mixer.h"
+#include "helper.h"
+#include "power.h"
+
+#include "scarlettmixer.h"
+
+/* #define WITH_METER */
+/* #define WITH_LOGSCALEMETER */
+
+#define LEVEL_BIAS 128 /* some gui mixers can't handle negative ctl values (alsamixergui, qasmixer, ...) */
+
+#ifndef LEVEL_BIAS
+ #define LEVEL_BIAS 0
+#endif
+
+struct scarlett_enum_info {
+ int start, len;
+ const char **texts;
+};
+
+struct scarlett_device_info {
+ int matrix_in;
+ int matrix_out;
+ int input_len;
+ int output_len;
+
+ int pcm_start;
+ int analog_start;
+ int spdif_start;
+ int adat_start;
+ int mix_start;
+
+ struct scarlett_enum_info opt_master;
+ struct scarlett_enum_info opt_matrix;
+
+ int (*controls_fn)(struct usb_mixer_interface *mixer,
+ const struct scarlett_device_info *info);
+
+ int matrix_mux_init[];
+};
+
+struct scarlett_mixer_elem_info {
+ struct usb_mixer_interface *mixer;
+
+ /* URB command details */
+ int wValue, index;
+ int val_len;
+
+ int count; /* number of channels, using ++wValue */
+
+ const struct scarlett_enum_info *opt;
+
+ int cached;
+ int cache_val[MAX_CHANNELS];
+};
+
+static void scarlett_mixer_elem_free(struct snd_kcontrol *kctl)
+{
+ kfree(kctl->private_data);
+ kctl->private_data = NULL;
+}
+
+/***************************** Low Level USB I/O *****************************/
+
+/* stripped down/adapted from get_ctl_value_v2 */
+static int get_ctl_urb2(struct snd_usb_audio *chip,
+ int bRequest, int wValue, int index,
+ unsigned char *buf, int size)
+{
+ int ret, idx = 0;
+
+ ret = snd_usb_autoresume(chip);
+ if (ret < 0 && ret != -ENODEV) {
+ ret = -EIO;
+ goto error;
+ }
+
+ down_read(&chip->shutdown_rwsem);
+ if (chip->shutdown) {
+ ret = -ENODEV;
+ } else {
+ idx = snd_usb_ctrl_intf(chip) | (index << 8);
+ ret = snd_usb_ctl_msg(chip->dev,
+ usb_rcvctrlpipe(chip->dev, 0),
+ bRequest,
+ USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_IN,
+ wValue, idx, buf, size);
+ }
+ up_read(&chip->shutdown_rwsem);
+ snd_usb_autosuspend(chip);
+
+ if (ret < 0) {
+error:
+ snd_printk(KERN_ERR "cannot get ctl value: req = %#x, wValue = %#x, wIndex = %#x, size = %d\n",
+ bRequest, wValue, idx, size);
+ return ret;
+ }
+#if 0 /* rg debug XXX */
+ snd_printk(KERN_ERR "req ctl value: req = %#x, rtype = %#x, wValue = %#x, wIndex = %#x, size = %d\n",
+ bRequest, (USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_IN), wValue, idx, size);
+#endif
+ return 0;
+}
+
+/* adopted from snd_usb_mixer_set_ctl_value */
+static int set_ctl_urb2(struct snd_usb_audio *chip,
+ int request, int wValue, int index,
+ unsigned char *buf, int val_len)
+{
+ int idx = 0, err, timeout = 10;
+
+ err = snd_usb_autoresume(chip);
+ if (err < 0 && err != -ENODEV)
+ return -EIO;
+ down_read(&chip->shutdown_rwsem);
+ while (timeout-- > 0) {
+ if (chip->shutdown)
+ break;
+ idx = snd_usb_ctrl_intf(chip) | (index << 8);
+ if (snd_usb_ctl_msg(chip->dev,
+ usb_sndctrlpipe(chip->dev, 0), request,
+ USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_OUT,
+ wValue, idx, buf, val_len) >= 0) {
+ err = 0;
+ goto out;
+ }
+ }
+ snd_printdd(KERN_ERR "cannot set ctl value: req = %#x, wValue = %#x, wIndex = %#x, len = %d, data = %#x/%#x\n",
+ request, wValue, idx, val_len, buf[0], buf[1]);
+ err = -EINVAL;
+
+ out:
+ up_read(&chip->shutdown_rwsem);
+ snd_usb_autosuspend(chip);
+#if 0 /* rg debug XXX */
+ snd_printk(KERN_ERR "set ctl value: req = %#x, wValue = %#x, wIndex = %#x, len = %d, data = %#x/%#x\n",
+ request, wValue, idx, val_len, buf[0], buf[1]);
+#endif
+ return err;
+}
+
+/***************************** High Level USB *****************************/
+
+static int set_ctl_value(struct scarlett_mixer_elem_info *elem, int channel, int value)
+{
+ struct snd_usb_audio *chip = elem->mixer->chip;
+ unsigned char buf[2];
+ int err;
+
+ if (elem->val_len == 2) { /* S16 */
+ buf[0] = value & 0xff;
+ buf[1] = (value >> 8) & 0xff;
+ } else { /* U8 */
+ buf[0] = value & 0xff;
+ }
+
+ err = set_ctl_urb2(chip, UAC2_CS_CUR, elem->wValue + channel, elem->index, buf, elem->val_len);
+ if (err < 0)
+ return err;
+
+ elem->cached |= 1 << channel;
+ elem->cache_val[channel] = value;
+ return 0;
+}
+
+/*
+ TODO: can't read back any volume (master/mixer), only cache works [?]
+ [return 0xfe for enums???]
+*/
+static int get_ctl_value(struct scarlett_mixer_elem_info *elem, int channel, int *value)
+{
+ struct snd_usb_audio *chip = elem->mixer->chip;
+ unsigned char buf[2] = {0, 0};
+ int err, val_len;
+
+ if (elem->cached & (1 << channel)) {
+ *value = elem->cache_val[channel];
+ return 0;
+ }
+
+ val_len = elem->val_len;
+ /* quirk: write 2bytes, but read 1byte */
+ if ((elem->index == 0x01) || /* input impedance and input pad switch */
+ ((elem->index == 0x0a) && (elem->wValue < 0x0200)) || /* bus mutes */
+ (elem->index == 0x32) || (elem->index == 0x33)) { /* mux */
+ val_len = 1;
+ }
+
+ err = get_ctl_urb2(chip, UAC2_CS_CUR, elem->wValue + channel, elem->index, buf, val_len);
+ if (err < 0) {
+ snd_printd(KERN_ERR "cannot get current value for control %x ch %d: err = %d\n",
+ elem->wValue, channel, err);
+ return err;
+ }
+
+ if (val_len == 2) { /* S16 */
+ *value = buf[0] | ((unsigned int)buf[1] << 8);
+ if (*value >= 0x8000)
+ (*value) -= 0x10000;
+ } else { /* U8 */
+ *value = buf[0];
+ }
+
+ elem->cached |= 1 << channel;
+ elem->cache_val[channel] = *value;
+
+ return 0;
+}
+
+/********************** Enum Strings *************************/
+static const char txtOff[] = "Off",
+ txtPcm1[] = "PCM 1", txtPcm2[] = "PCM 2",
+ txtPcm3[] = "PCM 3", txtPcm4[] = "PCM 4",
+ txtPcm5[] = "PCM 5", txtPcm6[] = "PCM 6",
+ txtPcm7[] = "PCM 7", txtPcm8[] = "PCM 8",
+ txtPcm9[] = "PCM 9", txtPcm10[] = "PCM 10",
+ txtPcm11[] = "PCM 11", txtPcm12[] = "PCM 12",
+ txtPcm13[] = "PCM 13", txtPcm14[] = "PCM 14",
+ txtPcm15[] = "PCM 15", txtPcm16[] = "PCM 16",
+ txtPcm17[] = "PCM 17", txtPcm18[] = "PCM 18",
+ txtPcm19[] = "PCM 19", txtPcm20[] = "PCM 20",
+ txtAnlg1[] = "Analog 1", txtAnlg2[] = "Analog 2",
+ txtAnlg3[] = "Analog 3", txtAnlg4[] = "Analog 4",
+ txtAnlg5[] = "Analog 5", txtAnlg6[] = "Analog 6",
+ txtAnlg7[] = "Analog 7", txtAnlg8[] = "Analog 8",
+ txtSpdif1[] = "SPDIF 1", txtSpdif2[] = "SPDIF 2",
+ txtAdat1[] = "ADAT 1", txtAdat2[] = "ADAT 2",
+ txtAdat3[] = "ADAT 3", txtAdat4[] = "ADAT 4",
+ txtAdat5[] = "ADAT 5", txtAdat6[] = "ADAT 6",
+ txtAdat7[] = "ADAT 7", txtAdat8[] = "ADAT 8",
+ txtMix1[] = "Mix A", txtMix2[] = "Mix B",
+ txtMix3[] = "Mix C", txtMix4[] = "Mix D",
+ txtMix5[] = "Mix E", txtMix6[] = "Mix F",
+ txtMix7[] = "Mix G", txtMix8[] = "Mix H";
+
+static const struct scarlett_enum_info opt_pad = {
+ .start = 0,
+ .len = 2,
+ .texts = (const char *[]){
+ txtOff, "-10dB"
+ }
+};
+
+static const struct scarlett_enum_info opt_impedance = {
+ .start = 0,
+ .len = 2,
+ .texts = (const char *[]){
+ "Line", "Hi-Z"
+ }
+};
+
+static const struct scarlett_enum_info opt_clock = {
+ .start = 1,
+ .len = 3,
+ .texts = (const char *[]){
+ "Internal", "SPDIF", "ADAT"
+ }
+};
+
+static const struct scarlett_enum_info opt_sync = {
+ .start = 0,
+ .len = 2,
+ .texts = (const char *[]){
+ "No Lock", "Locked"
+ }
+};
+
+static const struct scarlett_enum_info opt_save = {
+ .start = 0,
+ .len = 2,
+ .texts = (const char *[]){
+ "---", "Save"
+ }
+};
+
+#ifdef WITH_LOGSCALEMETER
+/* approx ( 20.0 * log10(x) ) for 16bit
+ * map 0..65535 to range 0..194 = -97.0..0dB in .5dB steps */
+static int sig_to_db(const int sig16bit)
+{
+ int i;
+ const int dbtbl[148] = {
+ 13, 14, 15, 16, 16, 17, 18, 20, 21, 22, 23, 25, 26, 28, 29, 31, 33, 35,
+ 37, 39, 41, 44, 46, 49, 52, 55, 58, 62, 66, 69, 74, 78, 83, 87, 93, 98,
+ 104, 110, 117, 123, 131, 139, 147, 155, 165, 174, 185, 196, 207, 220,
+ 233, 246, 261, 276, 293, 310, 328, 348, 369, 390, 414, 438, 464, 491,
+ 521, 551, 584, 619, 655, 694, 735, 779, 825, 874, 926, 981, 1039, 1100,
+ 1165, 1234, 1308, 1385, 1467, 1554, 1646, 1744, 1847, 1957, 2072, 2195,
+ 2325, 2463, 2609, 2764, 2927, 3101, 3285, 3479, 3685, 3904, 4135, 4380,
+ 4640, 4915, 5206, 5514, 5841, 6187, 6554, 6942, 7353, 7789, 8250, 8739,
+ 9257, 9806, 10387, 11002, 11654, 12345, 13076, 13851, 14672, 15541,
+ 16462, 17437, 18471, 19565, 20724, 21952, 23253, 24631, 26090, 27636,
+ 29274, 31008, 32846, 34792, 36854, 39037, 41350, 43801, 46396, 49145,
+ 52057, 55142, 58409, 61870
+ };
+
+ if (sig16bit < 13) {
+ switch (sig16bit) {
+ case 0: return 0;
+ case 1: return 1; /* -96.0dB */
+ case 2: return 13; /* -90.5dB */
+ case 3: return 20; /* -87.0dB */
+ case 4: return 26; /* -84.0dB */
+ case 5: return 29; /* -82.5dB */
+ case 6: return 32; /* -81.0dB */
+ case 7: return 35; /* -79.5dB */
+ case 8: return 37; /* -78.5dB */
+ case 9: return 39; /* -77.5dB */
+ case 10: return 41; /* -76.5dB */
+ case 11: return 43; /* -75.5dB */
+ case 12: return 45; /* -74.5dB */
+ }
+ }
+ for (i = 0; i < 148; ++i) {
+ if (sig16bit <= dbtbl[i])
+ return 46+i;
+ }
+ return 194;
+}
+#endif
+
+static int scarlett_ctl_switch_info(struct snd_kcontrol *kctl, struct snd_ctl_elem_info *uinfo)
+{
+ struct scarlett_mixer_elem_info *elem = kctl->private_data;
+
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
+ uinfo->count = elem->count;
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = 1;
+ return 0;
+}
+
+static int scarlett_ctl_switch_get(struct snd_kcontrol *kctl, struct snd_ctl_elem_value *ucontrol)
+{
+ struct scarlett_mixer_elem_info *elem = kctl->private_data;
+ int i, err, val;
+
+ for (i = 0; i < elem->count; i++) {
+ err = get_ctl_value(elem, i, &val);
+ if (err < 0)
+ return err;
+
+ val = !val; /* alsa uses 0: on, 1: off */
+ ucontrol->value.integer.value[i] = val;
+ }
+
+ return 0;
+}
+
+static int scarlett_ctl_switch_put(struct snd_kcontrol *kctl, struct snd_ctl_elem_value *ucontrol)
+{
+ struct scarlett_mixer_elem_info *elem = kctl->private_data;
+ int i, changed = 0;
+ int err, oval, val;
+
+ for (i = 0; i < elem->count; i++) {
+ err = get_ctl_value(elem, i, &oval);
+ if (err < 0)
+ return err;
+
+ val = ucontrol->value.integer.value[i];
+ val = !val;
+ if (oval != val) {
+ err = set_ctl_value(elem, i, val);
+ if (err < 0)
+ return err;
+
+ changed = 1;
+ }
+ }
+
+ return changed;
+}
+
+static int scarlett_ctl_info(struct snd_kcontrol *kctl, struct snd_ctl_elem_info *uinfo)
+{
+ struct scarlett_mixer_elem_info *elem = kctl->private_data;
+
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = elem->count;
+ uinfo->value.integer.min = -128 + LEVEL_BIAS;
+ uinfo->value.integer.max = (int)kctl->private_value + LEVEL_BIAS;
+ uinfo->value.integer.step = 1;
+ return 0;
+}
+
+static int scarlett_ctl_get(struct snd_kcontrol *kctl, struct snd_ctl_elem_value *ucontrol)
+{
+ struct scarlett_mixer_elem_info *elem = kctl->private_data;
+ int i, err, val;
+
+ for (i = 0; i < elem->count; i++) {
+ err = get_ctl_value(elem, i, &val);
+ if (err < 0)
+ return err;
+
+ val = clamp(val / 256, -128, (int)kctl->private_value) + LEVEL_BIAS;
+ ucontrol->value.integer.value[i] = val;
+ }
+
+ return 0;
+}
+
+static int scarlett_ctl_put(struct snd_kcontrol *kctl, struct snd_ctl_elem_value *ucontrol)
+{
+ struct scarlett_mixer_elem_info *elem = kctl->private_data;
+ int i, changed = 0;
+ int err, oval, val;
+
+ for (i = 0; i < elem->count; i++) {
+ err = get_ctl_value(elem, i, &oval);
+ if (err < 0)
+ return err;
+
+ val = ucontrol->value.integer.value[i] - LEVEL_BIAS;
+ val = val * 256;
+ if (oval != val) {
+ err = set_ctl_value(elem, i, val);
+ if (err < 0)
+ return err;
+
+ changed = 1;
+ }
+ }
+
+ return changed;
+}
+
+static int scarlett_ctl_enum_info(struct snd_kcontrol *kctl, struct snd_ctl_elem_info *uinfo)
+{
+ struct scarlett_mixer_elem_info *elem = kctl->private_data;
+
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+ uinfo->count = elem->count;
+ uinfo->value.enumerated.items = elem->opt->len;
+ if (uinfo->value.enumerated.item > uinfo->value.enumerated.items - 1)
+ uinfo->value.enumerated.item = uinfo->value.enumerated.items - 1;
+ strcpy(uinfo->value.enumerated.name, elem->opt->texts[uinfo->value.enumerated.item]);
+ return 0;
+}
+
+static int scarlett_ctl_enum_get(struct snd_kcontrol *kctl, struct snd_ctl_elem_value *ucontrol)
+{
+ struct scarlett_mixer_elem_info *elem = kctl->private_data;
+ int err, val;
+
+ err = get_ctl_value(elem, 0, &val);
+ if (err < 0)
+ return err;
+
+ /* snd_printk(KERN_WARNING "enum %s: %x %x\n", ucontrol->id.name, val, elem->opt->len); */
+ if ((elem->opt->start == -1) && (val > elem->opt->len)) /* >= 0x20 ??? */
+ val = 0;
+ else
+ val = clamp(val - elem->opt->start, 0, elem->opt->len-1);
+
+ ucontrol->value.enumerated.item[0] = val;
+
+ return 0;
+}
+
+static int scarlett_ctl_enum_put(struct snd_kcontrol *kctl, struct snd_ctl_elem_value *ucontrol)
+{
+ struct scarlett_mixer_elem_info *elem = kctl->private_data;
+ int changed = 0;
+ int err, oval, val;
+
+ err = get_ctl_value(elem, 0, &oval);
+ if (err < 0)
+ return err;
+
+ val = ucontrol->value.integer.value[0];
+#if 0 /* TODO? */
+ if (val == -1) {
+ val = elem->enum->len + 1; /* only true for master, not for mixer [also master must be used] */
+ /* ... or? > 0x20, 18i8: 0x22 */
+ } else
+#endif
+ val = val + elem->opt->start;
+ if (oval != val) {
+ err = set_ctl_value(elem, 0, val);
+ if (err < 0)
+ return err;
+
+ changed = 1;
+ }
+
+ return changed;
+}
+
+static int scarlett_ctl_save_get(struct snd_kcontrol *kctl, struct snd_ctl_elem_value *ucontrol)
+{
+ ucontrol->value.enumerated.item[0] = 0;
+ return 0;
+}
+
+static int scarlett_ctl_save_put(struct snd_kcontrol *kctl, struct snd_ctl_elem_value *ucontrol)
+{
+ struct scarlett_mixer_elem_info *elem = kctl->private_data;
+ int err;
+
+ if (ucontrol->value.enumerated.item[0] > 0) {
+ char buf[1] = { 0xa5 };
+
+ err = set_ctl_urb2(elem->mixer->chip, UAC2_CS_MEM, 0x005a, 0x3c, buf, 1);
+ if (err < 0)
+ return err;
+
+ snd_printk(KERN_INFO "Scarlett: Saved settings to hardware.\n");
+ }
+
+ return 0; /* (?) */
+}
+
+#ifdef WITH_METER
+static int scarlett_ctl_meter_info(struct snd_kcontrol *kctl, struct snd_ctl_elem_info *uinfo)
+{
+ struct scarlett_mixer_elem_info *elem = kctl->private_data;
+
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = elem->count;
+#ifdef WITH_LOGSCALEMETER
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = 194;
+#else
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = 255; /* 0xffff ? */
+#endif
+ uinfo->value.integer.step = 1;
+ return 0;
+}
+#endif
+
+static int scarlett_ctl_meter_get(struct snd_kcontrol *kctl, struct snd_ctl_elem_value *ucontrol)
+{
+ struct scarlett_mixer_elem_info *elem = kctl->private_data;
+ unsigned char buf[2 * MAX_CHANNELS] = {0, };
+ int err, val, i;
+
+ err = get_ctl_urb2(elem->mixer->chip, UAC2_CS_MEM, elem->wValue, elem->index, buf, elem->val_len * elem->count);
+ if (err < 0) {
+ snd_printd(KERN_ERR "cannot get current value for mem %x: err = %d\n",
+ elem->wValue, err);
+ return err;
+ }
+
+ if (elem->val_len == 1) { /* single U8 */
+ ucontrol->value.enumerated.item[0] = clamp((int)buf[0], 0, 1);
+ } else { /* multiple S16 */
+ for (i = 0; i < elem->count; i++) {
+ val = buf[2*i] | ((unsigned int)buf[2*i + 1] << 8);
+ if (val >= 0x8000)
+ val -= 0x10000;
+
+#ifdef WITH_LOGSCALEMETER
+ ucontrol->value.integer.value[i] = sig_to_db(val);
+#else
+ ucontrol->value.integer.value[i] = clamp(val / 256, 0, 255);
+#endif
+ }
+ }
+
+ return 0;
+}
+
+static struct snd_kcontrol_new usb_scarlett_ctl_switch = {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "",
+ .info = scarlett_ctl_switch_info,
+ .get = scarlett_ctl_switch_get,
+ .put = scarlett_ctl_switch_put,
+};
+
+static const DECLARE_TLV_DB_SCALE(db_scale_scarlett_gain, -12800, 100, 0);
+
+static struct snd_kcontrol_new usb_scarlett_ctl = {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_TLV_READ,
+ .name = "",
+ .info = scarlett_ctl_info,
+ .get = scarlett_ctl_get,
+ .put = scarlett_ctl_put,
+ .private_value = 6, /* max value */
+ .tlv = { .p = db_scale_scarlett_gain }
+};
+
+static struct snd_kcontrol_new usb_scarlett_ctl_master = {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_TLV_READ,
+ .name = "",
+ .info = scarlett_ctl_info,
+ .get = scarlett_ctl_get,
+ .put = scarlett_ctl_put,
+ .private_value = 6, /* max value */
+ .tlv = { .p = db_scale_scarlett_gain }
+};
+
+static struct snd_kcontrol_new usb_scarlett_ctl_enum = {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "",
+ .info = scarlett_ctl_enum_info,
+ .get = scarlett_ctl_enum_get,
+ .put = scarlett_ctl_enum_put,
+};
+
+static struct snd_kcontrol_new usb_scarlett_ctl_sync = {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE,
+ .name = "",
+ .info = scarlett_ctl_enum_info,
+ .get = scarlett_ctl_meter_get,
+};
+
+#ifdef WITH_METER
+#ifdef WITH_LOGSCALEMETER
+static const DECLARE_TLV_DB_SCALE(db_scale_scarlett_peak, -9700, 50, 0);
+#endif
+
+static struct snd_kcontrol_new usb_scarlett_ctl_meter = {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+#ifdef WITH_LOGSCALEMETER
+ .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE | SNDRV_CTL_ELEM_ACCESS_TLV_READ,
+ .tlv = { .p = db_scale_scarlett_peak },
+#else
+ .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE,
+#endif
+ .name = "",
+ .info = scarlett_ctl_meter_info,
+ .get = scarlett_ctl_meter_get,
+};
+#endif
+
+static struct snd_kcontrol_new usb_scarlett_ctl_save = {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "",
+ .info = scarlett_ctl_enum_info,
+ .get = scarlett_ctl_save_get,
+ .get = scarlett_ctl_save_put,
+};
+
+static int add_new_ctl(struct usb_mixer_interface *mixer,
+ const struct snd_kcontrol_new *ncontrol,
+ int index, int offset, int num,
+ int val_len, int count, const char *name,
+ const struct scarlett_enum_info *opt,
+ struct scarlett_mixer_elem_info **elem_ret)
+{
+ struct snd_kcontrol *kctl;
+ struct scarlett_mixer_elem_info *elem;
+ int err;
+
+ elem = kzalloc(sizeof(*elem), GFP_KERNEL);
+ if (!elem)
+ return -ENOMEM;
+
+ elem->mixer = mixer;
+ elem->wValue = (offset << 8) | num;
+ elem->index = index;
+ elem->val_len = val_len;
+ elem->count = count;
+ elem->opt = opt;
+
+ kctl = snd_ctl_new1(ncontrol, elem);
+ if (!kctl) {
+ snd_printk(KERN_ERR "cannot malloc kcontrol\n");
+ kfree(elem);
+ return -ENOMEM;
+ }
+ kctl->private_free = scarlett_mixer_elem_free;
+
+ snprintf(kctl->id.name, sizeof(kctl->id.name), "%s", name);
+
+ err = snd_ctl_add(mixer->chip->card, kctl);
+ if (err < 0)
+ return err;
+
+ if (elem_ret)
+ *elem_ret = elem;
+
+ return 0;
+}
+
+static int init_ctl(struct scarlett_mixer_elem_info *elem, int value)
+{
+ int err, channel;
+
+ for (channel = 0; channel < elem->count; channel++) {
+ err = set_ctl_value(elem, channel, value);
+ if (err < 0)
+ return err;
+ }
+
+ return 0;
+}
+
+#define INIT(value) \
+ do { \
+ err = init_ctl(elem, value); \
+ if (err < 0) \
+ return err; \
+ } while (0)
+
+#define CTL_SWITCH(cmd, off, no, count, name) \
+ do { \
+ err = add_new_ctl(mixer, &usb_scarlett_ctl_switch, cmd, off, no, 2, count, name, NULL, &elem); \
+ if (err < 0) \
+ return err; \
+ } while (0)
+
+/* no multichannel enum, always count == 1 (at least for now) */
+#define CTL_ENUM(cmd, off, no, name, opt) \
+ do { \
+ err = add_new_ctl(mixer, &usb_scarlett_ctl_enum, cmd, off, no, 2, 1, name, opt, &elem); \
+ if (err < 0) \
+ return err; \
+ } while (0)
+
+#define CTL_MIXER(cmd, off, no, count, name) \
+ do { \
+ err = add_new_ctl(mixer, &usb_scarlett_ctl, cmd, off, no, 2, count, name, NULL, &elem); \
+ if (err < 0) \
+ return err; \
+ INIT(-32768); /* -128*256 */ \
+ } while (0)
+
+#define CTL_MASTER(cmd, off, no, count, name) \
+ do { \
+ err = add_new_ctl(mixer, &usb_scarlett_ctl_master, cmd, off, no, 2, count, name, NULL, &elem); \
+ if (err < 0) \
+ return err; \
+ INIT(0); \
+ } while (0)
+
+#define CTL_PEAK(cmd, off, no, count, name) /* but UAC2_CS_MEM */ \
+ do { \
+ err = add_new_ctl(mixer, &usb_scarlett_ctl_meter, cmd, off, no, 2, count, name, NULL, NULL); \
+ if (err < 0) \
+ return err; \
+ } while (0)
+
+static int add_output_ctls(struct usb_mixer_interface *mixer,
+ int index, const char *name,
+ const struct scarlett_device_info *info)
+{
+ int err;
+ char mx[45];
+ struct scarlett_mixer_elem_info *elem;
+
+ snprintf(mx, 45, "Master %d (%s) Playback Switch", index+1, name); /* mute */
+ CTL_SWITCH(0x0a, 0x01, 2*index+1, 2, mx);
+
+ snprintf(mx, 45, "Master %d (%s) Playback Volume", index+1, name);
+ CTL_MASTER(0x0a, 0x02, 2*index+1, 2, mx);
+
+ snprintf(mx, 45, "Master %dL (%s) Source Playback Enum", index+1, name);
+ CTL_ENUM(0x33, 0x00, 2*index, mx, &info->opt_master);
+ INIT(info->mix_start);
+
+ snprintf(mx, 45, "Master %dR (%s) Source Playback Enum", index+1, name);
+ CTL_ENUM(0x33, 0x00, 2*index+1, mx, &info->opt_master);
+ INIT(info->mix_start + 1);
+
+ return 0;
+}
+
+#define CTLS_OUTPUT(index, name) \
+ do { \
+ err = add_output_ctls(mixer, index, name, info); \
+ if (err < 0) \
+ return err; \
+ while (0)
+
+
+/********************** device-specific config *************************/
+static int scarlet_s6i6_controls(struct usb_mixer_interface *mixer,
+ const struct scarlett_device_info *info)
+{
+ struct scarlett_mixer_elem_info *elem;
+ int err;
+
+ CTLS_OUTPUT(0, "Monitor");
+ CTLS_OUTPUT(1, "Headphone 2");
+ CTLS_OUTPUT(2, "SPDIF");
+
+ CTL_ENUM(0x01, 0x09, 1, "Input 1 Impedance Switch", &opt_impedance);
+ CTL_ENUM(0x01, 0x0b, 1, "Input 1 Pad Switch", &opt_pad);
+
+ CTL_ENUM(0x01, 0x09, 2, "Input 2 Impedance Switch", &opt_impedance);
+ CTL_ENUM(0x01, 0x0b, 2, "Input 2 Pad Switch", &opt_pad);
+
+ CTL_ENUM(0x01, 0x0b, 3, "Input 3 Pad Switch", &opt_pad);
+ CTL_ENUM(0x01, 0x0b, 4, "Input 4 Pad Switch", &opt_pad);
+
+ return 0;
+}
+
+static int scarlet_s8i6_controls(struct usb_mixer_interface *mixer,
+ const struct scarlett_device_info *info)
+{
+ struct scarlett_mixer_elem_info *elem;
+ int err;
+
+ CTLS_OUTPUT(0, "Monitor");
+ CTLS_OUTPUT(1, "Headphone");
+ CTLS_OUTPUT(2, "SPDIF");
+
+ CTL_ENUM(0x01, 0x09, 1, "Input 1 Impedance Switch", &opt_impedance);
+ CTL_ENUM(0x01, 0x09, 2, "Input 2 Impedance Switch", &opt_impedance);
+
+ CTL_ENUM(0x01, 0x0b, 3, "Input 3 Pad Switch", &opt_pad);
+ CTL_ENUM(0x01, 0x0b, 4, "Input 4 Pad Switch", &opt_pad);
+
+ return 0;
+}
+
+static int scarlet_s18i6_controls(struct usb_mixer_interface *mixer,
+ const struct scarlett_device_info *info)
+{
+ struct scarlett_mixer_elem_info *elem;
+ int err;
+
+ CTLS_OUTPUT(0, "Monitor");
+ CTLS_OUTPUT(1, "Headphone");
+ CTLS_OUTPUT(2, "SPDIF");
+
+ CTL_ENUM(0x01, 0x09, 1, "Input 1 Impedance Switch", &opt_impedance);
+ CTL_ENUM(0x01, 0x09, 2, "Input 2 Impedance Switch", &opt_impedance);
+
+ return 0;
+}
+
+static int scarlet_s18i8_controls(struct usb_mixer_interface *mixer,
+ const struct scarlett_device_info *info)
+{
+ struct scarlett_mixer_elem_info *elem;
+ int err;
+
+ CTLS_OUTPUT(0, "Monitor");
+ CTLS_OUTPUT(1, "Headphone 1");
+ CTLS_OUTPUT(2, "Headphone 2");
+ CTLS_OUTPUT(3, "SPDIF");
+
+ CTL_ENUM(0x01, 0x09, 1, "Input 1 Impedance Switch", &opt_impedance);
+ CTL_ENUM(0x01, 0x0b, 1, "Input 1 Pad Switch", &opt_pad);
+
+ CTL_ENUM(0x01, 0x09, 2, "Input 2 Impedance Switch", &opt_impedance);
+ CTL_ENUM(0x01, 0x0b, 2, "Input 2 Pad Switch", &opt_pad);
+
+ CTL_ENUM(0x01, 0x0b, 3, "Input 3 Pad Switch", &opt_pad);
+ CTL_ENUM(0x01, 0x0b, 4, "Input 4 Pad Switch", &opt_pad);
+
+ return 0;
+}
+
+static int scarlet_s18i20_controls(struct usb_mixer_interface *mixer,
+ const struct scarlett_device_info *info)
+{
+/* struct scarlett_mixer_elem_info *elem; */
+ int err;
+
+ CTLS_OUTPUT(0, "Monitor"); /* 1/2 */
+ CTLS_OUTPUT(1, "Line 3/4");
+ CTLS_OUTPUT(2, "Line 5/6");
+ CTLS_OUTPUT(3, "Line 7/8"); /* = Headphone 1 */
+ CTLS_OUTPUT(4, "Line 9/10"); /* = Headphone 2 */
+ CTLS_OUTPUT(5, "SPDIF");
+ CTLS_OUTPUT(6, "ADAT 1/2");
+ CTLS_OUTPUT(7, "ADAT 3/4");
+ CTLS_OUTPUT(8, "ADAT 5/6");
+ CTLS_OUTPUT(9, "ADAT 7/8");
+
+/* ? real hardware switches
+ CTL_ENUM (0x01, 0x09, 1, "Input 1 Impedance Switch", &opt_impedance);
+ CTL_ENUM (0x01, 0x0b, 1, "Input 1 Pad Switch", &opt_pad);
+
+ CTL_ENUM (0x01, 0x09, 2, "Input 2 Impedance Switch", &opt_impedance);
+ CTL_ENUM (0x01, 0x0b, 2, "Input 2 Pad Switch", &opt_pad);
+
+ CTL_ENUM (0x01, 0x0b, 3, "Input 3 Pad Switch", &opt_pad);
+ CTL_ENUM (0x01, 0x0b, 4, "Input 4 Pad Switch", &opt_pad);
+*/
+
+ return 0;
+}
+
+static const char * const s6i6_texts[] = {
+ txtOff, /* 'off' == 0xff */
+ txtPcm1, txtPcm2, txtPcm3, txtPcm4,
+ txtPcm5, txtPcm6, txtPcm7, txtPcm8,
+ txtPcm9, txtPcm10, txtPcm11, txtPcm12,
+ txtAnlg1, txtAnlg2, txtAnlg3, txtAnlg4,
+ txtSpdif1, txtSpdif2,
+ txtMix1, txtMix2, txtMix3, txtMix4,
+ txtMix5, txtMix6, txtMix7, txtMix8
+};
+
+/* untested... */
+static const struct scarlett_device_info s6i6_info = {
+ .matrix_in = 18,
+ .matrix_out = 8,
+ .input_len = 6,
+ .output_len = 6,
+
+ .pcm_start = 0,
+ .analog_start = 12,
+ .spdif_start = 16,
+ .adat_start = 18,
+ .mix_start = 18,
+
+ .opt_master = {
+ .start = -1,
+ .len = 27,
+ .texts = s6i6_texts
+ },
+
+ .opt_matrix = {
+ .start = -1,
+ .len = 19,
+ .texts = s6i6_texts
+ },
+
+ .controls_fn = scarlet_s6i6_controls,
+ .matrix_mux_init = {
+ 12, 13, 14, 15, /* Analog -> 1..4 */
+ 16, 17, /* SPDIF -> 5,6 */
+ 0, 1, 2, 3, 4, 5, 6, 7, /* PCM[1..12] -> 7..18 */
+ 8, 9, 10, 11
+ }
+};
+
+/* and 2 loop channels: Mix1, Mix2 */
+static const char * const s8i6_texts[] = {
+ txtOff, /* 'off' == 0xff */
+ txtPcm1, txtPcm2, txtPcm3, txtPcm4,
+ txtPcm5, txtPcm6, txtPcm7, txtPcm8,
+ txtPcm9, txtPcm10, txtPcm11, txtPcm12,
+ txtAnlg1, txtAnlg2, txtAnlg3, txtAnlg4,
+ txtSpdif1, txtSpdif2,
+ txtMix1, txtMix2, txtMix3, txtMix4,
+ txtMix5, txtMix6
+};
+
+/* untested... */
+static const struct scarlett_device_info s8i6_info = {
+ .matrix_in = 18,
+ .matrix_out = 6,
+ .input_len = 8,
+ .output_len = 6,
+
+ .pcm_start = 0,
+ .analog_start = 12,
+ .spdif_start = 16,
+ .adat_start = 18,
+ .mix_start = 18,
+
+ .opt_master = {
+ .start = -1,
+ .len = 25,
+ .texts = s8i6_texts
+ },
+
+ .opt_matrix = {
+ .start = -1,
+ .len = 19,
+ .texts = s8i6_texts
+ },
+
+ .controls_fn = scarlet_s8i6_controls,
+ .matrix_mux_init = {
+ 12, 13, 14, 15, /* Analog -> 1..4 */
+ 16, 17, /* SPDIF -> 5,6 */
+ 0, 1, 2, 3, 4, 5, 6, 7, /* PCM[1..12] -> 7..18 */
+ 8, 9, 10, 11
+ }
+};
+
+static const char * const s18i6_texts[] = {
+ txtOff, /* 'off' == 0xff */
+ txtPcm1, txtPcm2, txtPcm3, txtPcm4,
+ txtPcm5, txtPcm6,
+ txtAnlg1, txtAnlg2, txtAnlg3, txtAnlg4,
+ txtAnlg5, txtAnlg6, txtAnlg7, txtAnlg8,
+ txtSpdif1, txtSpdif2,
+ txtAdat1, txtAdat2, txtAdat3, txtAdat4,
+ txtAdat5, txtAdat6, txtAdat7, txtAdat8,
+ txtMix1, txtMix2, txtMix3, txtMix4,
+ txtMix5, txtMix6
+};
+
+static const struct scarlett_device_info s18i6_info = {
+ .matrix_in = 18,
+ .matrix_out = 6,
+ .input_len = 18,
+ .output_len = 6,
+
+ .pcm_start = 0,
+ .analog_start = 6,
+ .spdif_start = 14,
+ .adat_start = 16,
+ .mix_start = 24,
+
+ .opt_master = {
+ .start = -1,
+ .len = 31,
+ .texts = s18i6_texts
+ },
+
+ .opt_matrix = {
+ .start = -1,
+ .len = 25,
+ .texts = s18i6_texts
+ },
+
+ .controls_fn = scarlet_s18i6_controls,
+ .matrix_mux_init = {
+ 6, 7, 8, 9, 10, 11, 12, 13, /* Analog -> 1..8 */
+ 16, 17, 18, 19, 20, 21, /* ADAT[1..6] -> 9..14 */
+ 14, 15, /* SPDIF -> 15,16 */
+ 0, 1 /* PCM[1,2] -> 17,18 */
+ }
+};
+
+static const char * const s18i8_texts[] = {
+ txtOff, /* 'off' == 0xff (original software: 0x22) */
+ txtPcm1, txtPcm2, txtPcm3, txtPcm4,
+ txtPcm5, txtPcm6, txtPcm7, txtPcm8,
+ txtAnlg1, txtAnlg2, txtAnlg3, txtAnlg4,
+ txtAnlg5, txtAnlg6, txtAnlg7, txtAnlg8,
+ txtSpdif1, txtSpdif2,
+ txtAdat1, txtAdat2, txtAdat3, txtAdat4,
+ txtAdat5, txtAdat6, txtAdat7, txtAdat8,
+ txtMix1, txtMix2, txtMix3, txtMix4,
+ txtMix5, txtMix6, txtMix7, txtMix8
+};
+
+static const struct scarlett_device_info s18i8_info = {
+ .matrix_in = 18,
+ .matrix_out = 8,
+ .input_len = 18,
+ .output_len = 8,
+
+ .pcm_start = 0,
+ .analog_start = 8,
+ .spdif_start = 16,
+ .adat_start = 18,
+ .mix_start = 26,
+
+ .opt_master = {
+ .start = -1,
+ .len = 35,
+ .texts = s18i8_texts
+ },
+
+ .opt_matrix = {
+ .start = -1,
+ .len = 27,
+ .texts = s18i8_texts
+ },
+
+ .controls_fn = scarlet_s18i8_controls,
+ .matrix_mux_init = {
+ 8, 9, 10, 11, 12, 13, 14, 15, /* Analog -> 1..8 */
+ 18, 19, 20, 21, 22, 23, /* ADAT[1..6] -> 9..14 */
+ 16, 17, /* SPDIF -> 15,16 */
+ 0, 1 /* PCM[1,2] -> 17,18 */
+ }
+};
+
+static const char * const s18i20_texts[] = {
+ txtOff, /* 'off' == 0xff (original software: 0x22) */
+ txtPcm1, txtPcm2, txtPcm3, txtPcm4,
+ txtPcm5, txtPcm6, txtPcm7, txtPcm8,
+ txtPcm9, txtPcm10, txtPcm11, txtPcm12,
+ txtPcm13, txtPcm14, txtPcm15, txtPcm16,
+ txtPcm17, txtPcm18, txtPcm19, txtPcm20,
+ txtAnlg1, txtAnlg2, txtAnlg3, txtAnlg4,
+ txtAnlg5, txtAnlg6, txtAnlg7, txtAnlg8,
+ txtSpdif1, txtSpdif2,
+ txtAdat1, txtAdat2, txtAdat3, txtAdat4,
+ txtAdat5, txtAdat6, txtAdat7, txtAdat8,
+ txtMix1, txtMix2, txtMix3, txtMix4,
+ txtMix5, txtMix6, txtMix7, txtMix8
+};
+
+static const struct scarlett_device_info s18i20_info = {
+ .matrix_in = 18,
+ .matrix_out = 8,
+ .input_len = 18,
+ .output_len = 20,
+
+ .pcm_start = 0,
+ .analog_start = 20,
+ .spdif_start = 28,
+ .adat_start = 30,
+ .mix_start = 38,
+
+ .opt_master = {
+ .start = -1,
+ .len = 47,
+ .texts = s18i20_texts
+ },
+
+ .opt_matrix = {
+ .start = -1,
+ .len = 39,
+ .texts = s18i20_texts
+ },
+
+ .controls_fn = scarlet_s18i20_controls,
+ .matrix_mux_init = {
+ 20, 21, 22, 23, 24, 25, 26, 27, /* Analog -> 1..8 */
+ 30, 31, 32, 33, 34, 35, /* ADAT[1..6] -> 9..14 */
+ 28, 29, /* SPDIF -> 15,16 */
+ 0, 1 /* PCM[1,2] -> 17,18 */
+ }
+};
+
+/*
+int scarlett_reset(struct usb_mixer_interface *mixer)
+{
+ TODO? save first-time init flag into device?
+
+ unmute [master +] mixes (switches are currently not initialized)
+ [set(get!) impedance: 0x01, 0x09, 1..2]
+ [set(get!) 0x01, 0x08, 3..4]
+ [set(get!) pad: 0x01, 0x0b, 1..4]
+
+ matrix inputs (currently in scarlett_mixer_controls)
+}
+*/
+
+/*
+ * Create and initialize a mixer for the Focusrite(R) Scarlett
+ */
+int scarlett_mixer_controls(struct usb_mixer_interface *mixer)
+{
+ int err, i, o;
+ char mx[32];
+ const struct scarlett_device_info *info;
+ struct scarlett_mixer_elem_info *elem;
+
+ CTL_SWITCH(0x0a, 0x01, 0, 1, "Master Playback Switch");
+ CTL_MASTER(0x0a, 0x02, 0, 1, "Master Playback Volume");
+
+ switch (mixer->chip->usb_id) {
+ case USB_ID(0x1235, 0x8012):
+ info = &s6i6_info;
+ break;
+ case USB_ID(0x1235, 0x8002):
+ info = &s8i6_info;
+ break;
+ case USB_ID(0x1235, 0x8004):
+ info = &s18i6_info;
+ break;
+ case USB_ID(0x1235, 0x8014):
+ info = &s18i8_info;
+ break;
+ case USB_ID(0x1235, 0x800c):
+ info = &s18i20_info;
+ break;
+ default: /* device not (yet) supported */
+ return -EINVAL;
+ }
+
+ err = (*info->controls_fn)(mixer, info);
+ if (err < 0)
+ return err;
+
+ for (i = 0; i < info->matrix_in; i++) {
+ snprintf(mx, 32, "Matrix %02d Input Playback Route", i+1);
+ CTL_ENUM(0x32, 0x06, i, mx, &info->opt_matrix);
+ INIT(info->matrix_mux_init[i]);
+
+ for (o = 0; o < info->matrix_out; o++) {
+ sprintf(mx, "Matrix %02d Mix %c Playback Volume", i+1, o+'A');
+ CTL_MIXER(0x3c, 0x00, (i << 3) + (o & 0x07), 1, mx);
+ if (((o == 0) && (info->matrix_mux_init[i] == info->pcm_start)) ||
+ ((o == 1) && (info->matrix_mux_init[i] == info->pcm_start + 1))) {
+ INIT(0); /* init hack: enable PCM 1 / 2 on Mix A / B */
+ }
+ }
+ }
+
+ for (i = 0; i < info->input_len; i++) {
+ snprintf(mx, 32, "Input Source %02d Capture Route", i+1);
+ CTL_ENUM(0x34, 0x00, i, mx, &info->opt_master);
+ INIT(info->analog_start + i);
+ }
+
+ /* val_len == 1 needed here */
+ err = add_new_ctl(mixer, &usb_scarlett_ctl_enum, 0x28, 0x01, 0, 1,
+ 1, "Sample Clock Source", &opt_clock, NULL);
+ if (err < 0)
+ return err;
+
+ /* val_len == 1 and UAC2_CS_MEM */
+ err = add_new_ctl(mixer, &usb_scarlett_ctl_sync, 0x3c, 0x00, 2, 1,
+ 1, "Sample Clock Sync Status", &opt_sync, NULL);
+ if (err < 0)
+ return err;
+
+ /* val_len == 1 and UAC2_CS_MEM */
+ err = add_new_ctl(mixer, &usb_scarlett_ctl_save, 0x3c, 0x00, 0x5a, 1,
+ 1, "Save To HW", &opt_save, NULL);
+ if (err < 0)
+ return err;
+
+#ifdef WITH_METER
+ CTL_PEAK(0x3c, 0x00, 0, info->input_len, "Input Meter");
+ CTL_PEAK(0x3c, 0x00, 1, info->matrix_out, "Matrix Meter");
+ CTL_PEAK(0x3c, 0x00, 3, info->output_len, "PCM Meter");
+#endif
+
+ /* initialize sampling rate to 48000 */
+ err = set_ctl_urb2(mixer->chip, UAC2_CS_CUR, 0x0100, 0x29, "\x80\xbb\x00\x00", 4);
+ if (err < 0)
+ return err;
+
+/* TODO(?) scarlett_reset(mixer); */
+
+ return 0;
+}
diff --git a/sound/usb/scarlettmixer.h b/sound/usb/scarlettmixer.h
new file mode 100644
index 0000000..ebde17e
--- /dev/null
+++ b/sound/usb/scarlettmixer.h
@@ -0,0 +1,6 @@
+#ifndef __USBSCARLETTMIXER_H
+#define __USBSCARLETTMIXER_H
+
+int scarlett_mixer_controls(struct usb_mixer_interface *mixer);
+
+#endif /* __USBSCARLETTMIXER_H */
--
1.9.1