Discussion:
[WIP PATCH] Scarlett mixer interface for 6i6, 18i6, 18i8 and 18i20.
David Henningsson
2014-10-07 15:16:26 UTC
Permalink
Author: Tobias Hoffman <***@gmx.de>
Author: Robin Gareus <***@gareus.org>
Signed-off-by: David Henningsson <***@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 <***@gareus.org>
+ * Copyright (c) 2002 by Takashi Iwai <***@suse.de>
+ *
+ * Many codes borrowed from audio.c by
+ * Alan Cox (***@lxorguk.ukuu.org.uk)
+ * Thomas Sailer (***@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
Tobias Hoffmann
2014-10-07 15:24:54 UTC
Permalink
Post by David Henningsson
+#define CTLS_OUTPUT(index, name) \
+ do { \
+ err = add_output_ctls(mixer, index, name, info); \
+ if (err< 0) \
+ return err; \
+ while (0)
AFAICS the last line should be:

} while (0)


Tobias
Daniel Mack
2014-10-08 19:04:06 UTC
Permalink
Hi,
Post by David Henningsson
---
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.
Thanks for doing that!

I've done a first round of superficial review. In general, the #ifdefs
have to be cleaned up, and dead code (such that is inside of comments)
has to be removed or turned into something useful.

Also, some comments imply that not all code paths have been tested
really. It would be nice if that could be done. AFAIR there were some
volunteers that wanted to help with testing.

Some more comments inline.


Best regards,
Daniel
Post by David Henningsson
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
...
Post by David Henningsson
+#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 */
These should either be converted to module parameters, or removed
alltogether. Why are they configurable, anyway?
Post by David Henningsson
+#define LEVEL_BIAS 128 /* some gui mixers can't handle negative ctl values (alsamixergui, qasmixer, ...) */
+
+#ifndef LEVEL_BIAS
+ #define LEVEL_BIAS 0
+#endif
Same here.
Post by David Henningsson
+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) {
+ 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
That can be moved to snd_printdd()
Post by David Henningsson
+ 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;
+
+ 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
Same here.

...
Post by David Henningsson
+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 */
+ }
+ }
I'd say a lookup table is nicer here, but that's a nit.
Post by David Henningsson
+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); */
No dead code in general. Either remove it or use snd_printd[d].
Post by David Henningsson
+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
Could someone with access to such hardware sort that out, maybe? :)
Post by David Henningsson
+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; /* (?) */
What's the confusion here? :)

...
Post by David Henningsson
+#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);
Hmm, I don't really like the style of magically returning from macros.
That makes the control flow uneasy to read IMO. Couldn't something with
statically initialized structs be done here, and then keep the code that
does the work generic? I haven't tried to implement that, just an idea.

...
Post by David Henningsson
+/* untested... */
+static const struct scarlett_device_info s6i6_info = {
Would be nice to get some testers here.
Post by David Henningsson
+ .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
+ },
Here, and in some other occasions, ARRAY_SIZE() should be used.
Post by David Henningsson
+ .opt_matrix = {
+ .start = -1,
+ .len = 27,
+ .texts = s18i8_texts
+ },
Same here.
Post by David Henningsson
+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
+ },
Dito
Post by David Henningsson
+
+ .opt_matrix = {
+ .start = -1,
+ .len = 39,
+ .texts = s18i20_texts
+ },
Dito
Post by David Henningsson
+
+ .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)
+}
+*/
Should be removed, or get a real implementation.
Robin Gareus
2014-10-08 21:21:54 UTC
Permalink
Thanks David for picking this up.
Post by Daniel Mack
Post by David Henningsson
+
+/* #define WITH_METER */
+/* #define WITH_LOGSCALEMETER */
These should either be converted to module parameters, or removed
alltogether. Why are they configurable, anyway?
Mainly because I lost interest in Focusrite devices before finishing the
work. They were handy during initial development and the idea was to
just enable them by default at some point.

Last I checked they both worked fine for the 18i6, but it seems support
other Scarlett devices is missing and/or untested, so I guess they're
commented out for that reason.
Post by Daniel Mack
+#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); \
Post by Daniel Mack
+ if (err < 0) \
+ return err; \
+ } while (0)
curious, why "do { } while (0)" and not just "{ }" ?
Post by Daniel Mack
Hmm, I don't really like the style of magically returning from macros
Yes, same here. I'm initially responsible for one such macro and wanted
to get rid of it. I'm sorry if that lead Tobias to adding more in that
style.

Cheers!
robin
Tobias Hoffmann
2014-10-08 22:06:09 UTC
Permalink
Post by Robin Gareus
Thanks David for picking this up.
Post by Daniel Mack
Post by David Henningsson
+
+/* #define WITH_METER */
+/* #define WITH_LOGSCALEMETER */
These should either be converted to module parameters, or removed
alltogether. Why are they configurable, anyway?
Mainly because I lost interest in Focusrite devices before finishing the
work. They were handy during initial development and the idea was to
just enable them by default at some point.
Last I checked they both worked fine for the 18i6, but it seems support
other Scarlett devices is missing and/or untested, so I guess they're
commented out for that reason.
AFAIR in the latest version something crashes when opening such
"channels" with (I think) alsamixer.
I didn't even try to debug that, because no Mixer-GUI could actually
display "realtime" metering data by periodically polling channel values,
IIRC. The even bigger problem is, that the Scarlett HW returns all
metering data at once (for a given metering point), e.g. all 18 inputs
together, in contrast to the mixer-channels, where each value can be set
separately.
Naively mapped, one would get three snd_kcontrols (metering points:
input, mix, pcm) with snd_ctl_elem_info::count = 18, 6, 18 (e.g.).
Alternatively, the driver would have to read all values, cache them
internally and expose them as separate snd_kcontrols. In either case, a
mixer GUI probably still has to have special knowledge about the card to
display the values in a helpful way.

Other drivers seem to use the hwdep-API for things like metering.
I do not understand the hwdep-API enough to implement it as such.
And as trrichad wrote a GUI [1], that can be used to display the hwdep
data correctly.

The one reason why the metering code is still in the driver (and
disabled), is because it basically documents how to read the metering
values...
Post by Robin Gareus
Post by Daniel Mack
+#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); \
Post by Daniel Mack
+ if (err< 0) \
+ return err; \
+ } while (0)
curious, why "do { } while (0)" and not just "{ }" ?
I didn't even have { }, it was just
err = ...;
if (err<0) return err;

I would guess checkpatch suggested to add "do {} while(0)" for style
reasons?
Post by Robin Gareus
Post by Daniel Mack
Hmm, I don't really like the style of magically returning from macros
Yes, same here. I'm initially responsible for one such macro and wanted
to get rid of it. I'm sorry if that lead Tobias to adding more in that
style.
No, it's not your fault. I tried other ways, but the code only became
less readable:
- each operation can return an error, which has to be checked. Adding
the error checks everywhere (i.e. inlining the macros) just clutters the
code.
- CTL_ENUM, INIT and CTL_MIXER are called in a loop, based on data in
the static sXiY_info device info structs. And you don't want arrays with
Post by Robin Gareus
100 entries *for each supported Scarlett type*.
- also add_output_ctls() calculates some of the values from the
function arguments.
- In many places the same macro (e.g. CTL_SWITCH) isn't actually
repeated that often, but different CTL_* are used.

Still, some macros can probably be inlined, and esp. the device-specific
scarlett_sXiY_controls init functions now turn out to be similar enough
that a static struct entries per device and a generic init function
based on these entris seem to make sense.

Tobias

[1] https://github.com/trrichard/ScarlettMixer
Takashi Iwai
2014-10-09 08:39:06 UTC
Permalink
At Wed, 08 Oct 2014 23:21:54 +0200,
Post by David Henningsson
Post by David Henningsson
+#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); \
Post by David Henningsson
+ if (err < 0) \
+ return err; \
+ } while (0)
curious, why "do { } while (0)" and not just "{ }" ?
It's a standard trick to make the macro working with if/else in kernel
codes.

Suppose a macro like

#define FOO() { .... }

and use it like

if (something)
FOO();
else
bar();

then you'll get a build error.
But it can be avoided by surrounding do and while (0).

#define FOO() do { .... } while (0)


Takashi
Tobias Hoffmann
2014-10-08 22:49:57 UTC
Permalink
Post by David Henningsson
+
+/* #define WITH_METER */
+/* #define WITH_LOGSCALEMETER */
These should either be converted to module parameters, or removed
alltogether. Why are they configurable, anyway?
As I've said in my earlier mail, code is current not working: Metering
should be removed from this patch (or rewritten to use hwdep-API).
Post by David Henningsson
Post by David Henningsson
+#define LEVEL_BIAS 128 /* some gui mixers can't handle negative ctl values (alsamixergui, qasmixer, ...) */
+
+#ifndef LEVEL_BIAS
+ #define LEVEL_BIAS 0
+#endif
Same here.
As LEVEL_BIAS = 0 definitely causes problems with some mixer GUIs, the
#ifndef ... #endif should be removed completely.
Post by David Henningsson
Post by David Henningsson
+#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
That can be moved to snd_printdd()
These are left overs from Robins code, even I didn't need them. It's
probably safe to just remove all the
#if 0 /* rg debug XXX */ ... #endif
from the patch.
Post by David Henningsson
Post by David Henningsson
+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
Could someone with access to such hardware sort that out, maybe? :)
The current code works as-is, so #if 0 ... #endif can be removed.
Post by David Henningsson
Post by David Henningsson
+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; /* (?) */
What's the confusion here? :)
The "Save to HW" enum uses a special kcontrol .get/.put combo:
.get always returns 0, and .put issues a special command to the hardware.

I not 100% sure what the "best" return value of the .put function would
be in that case.
AFAIUI, .put functions have to return whether the value has changed...
and here:
- something happend (supporting return 1;)
- but .get still returns 0 (supporting return 0;)
Post by David Henningsson
+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,
+};
BUG! .get is set twice instead of .get and .put...

Testing is definitely needed for that change.
Post by David Henningsson
Post by David Henningsson
+/* untested... */
+static const struct scarlett_device_info s6i6_info = {
Would be nice to get some testers here.
I did heard of one person who used it (successfully) on s6i6.
That's not to say that we should not test the "final" patch on each device.
Post by David Henningsson
+
+/* untested... */
+static const struct scarlett_device_info s8i6_info = {
I can't remember any users with s8i6 right now...

I have a s18i8.

s18i6 is what the original code did, but I have not heard of tests with
the new code.

A few s18i20 users reported "no problems".
Post by David Henningsson
+
+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
+ },
Here, and in some other occasions, ARRAY_SIZE() should be used.
Post by David Henningsson
+ .opt_matrix = {
+ .start = -1,
+ .len = 27,
+ .texts = s18i8_texts
+ },
Same here.
ARRAY_SIZE(s18i8_text) == 35, so .len = (ARRAY_SIZE()-8) ?
I not sure that's helpful...
The better way to think about it is:
.opt_matrix.len = .mix_start + 1,
.opt_master.len = .opt_matrix.len + .matrix_out // == ARRAY_SIZE()
Post by David Henningsson
+/*
+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)
+}
+*/
Should be removed, or get a real implementation.
Remove. Just some random ideas.

Tobias
Daniel Mack
2014-10-09 08:31:15 UTC
Permalink
Hi Tobias,
Post by Tobias Hoffmann
Post by David Henningsson
+
+/* #define WITH_METER */
+/* #define WITH_LOGSCALEMETER */
These should either be converted to module parameters, or removed
alltogether. Why are they configurable, anyway?
As I've said in my earlier mail, code is current not working: Metering
should be removed from this patch (or rewritten to use hwdep-API).
Post by David Henningsson
Post by David Henningsson
+#define LEVEL_BIAS 128 /* some gui mixers can't handle negative ctl values (alsamixergui, qasmixer, ...) */
+
+#ifndef LEVEL_BIAS
+ #define LEVEL_BIAS 0
+#endif
Same here.
As LEVEL_BIAS = 0 definitely causes problems with some mixer GUIs, the
#ifndef ... #endif should be removed completely.
Alright. David, will you be working on new version of the patch?
Post by Tobias Hoffmann
Post by David Henningsson
Post by David Henningsson
+#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
That can be moved to snd_printdd()
These are left overs from Robins code, even I didn't need them. It's
probably safe to just remove all the
#if 0 /* rg debug XXX */ ... #endif
from the patch.
Sure it's safe, as dead code doesn't do anything :) The question is
whether the line produces any useful information for people having
trouble with their device. If so, we can just convert it to snd_printdd().
Post by Tobias Hoffmann
Post by David Henningsson
Post by David Henningsson
+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");
Btw - this should become a debug print as well.
Post by Tobias Hoffmann
Post by David Henningsson
Post by David Henningsson
+ }
+
+ return 0; /* (?) */
What's the confusion here? :)
.get always returns 0, and .put issues a special command to the hardware.
I not 100% sure what the "best" return value of the .put function would
be in that case.
AFAIUI, .put functions have to return whether the value has changed...
- something happend (supporting return 1;)
- but .get still returns 0 (supporting return 0;)
Ah, I see. Yes, I'd consider that a misuse of the ALSA control API, as
you're not dealing with an actual control here. I think that should
become a hwdep interface then.
Post by Tobias Hoffmann
Post by David Henningsson
+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,
+};
BUG! .get is set twice instead of .get and .put...
Right, so the code in .put was never actually used, right? Did the
compiler not warn about that?
Post by Tobias Hoffmann
Testing is definitely needed for that change.
Post by David Henningsson
Post by David Henningsson
+/* untested... */
+static const struct scarlett_device_info s6i6_info = {
Would be nice to get some testers here.
I did heard of one person who used it (successfully) on s6i6.
That's not to say that we should not test the "final" patch on each device.
Yes, that would be good.
Post by Tobias Hoffmann
Post by David Henningsson
Here, and in some other occasions, ARRAY_SIZE() should be used.
Post by David Henningsson
+ .opt_matrix = {
+ .start = -1,
+ .len = 27,
+ .texts = s18i8_texts
+ },
Same here.
ARRAY_SIZE(s18i8_text) == 35, so .len = (ARRAY_SIZE()-8) ?
I not sure that's helpful...
.opt_matrix.len = .mix_start + 1,
.opt_master.len = .opt_matrix.len + .matrix_out // == ARRAY_SIZE()
Hmm, yes. I was just thinking about something that makes the values look
a bit less magical.


Daniel
Tobias Hoffmann
2014-10-09 10:13:33 UTC
Permalink
Post by Daniel Mack
Post by Tobias Hoffmann
Post by Daniel Mack
Post by David Henningsson
+#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
That can be moved to snd_printdd()
These are left overs from Robins code, even I didn't need them. It's
probably safe to just remove all the
#if 0 /* rg debug XXX */ ... #endif
from the patch.
Sure it's safe, as dead code doesn't do anything :) The question is
whether the line produces any useful information for people having
trouble with their device. If so, we can just convert it to snd_printdd().
Well, yes. I was looking for a short word for "no one will miss it",
when I wrote "safe"...
Post by Daniel Mack
Post by Tobias Hoffmann
.get always returns 0, and .put issues a special command to the hardware.
I not 100% sure what the "best" return value of the .put function would
be in that case.
AFAIUI, .put functions have to return whether the value has changed...
- something happend (supporting return 1;)
- but .get still returns 0 (supporting return 0;)
Ah, I see. Yes, I'd consider that a misuse of the ALSA control API, as
you're not dealing with an actual control here. I think that should
become a hwdep interface then.
I think I've seen the same idea ("abuse") in another driver(?).
Post by Daniel Mack
Post by Tobias Hoffmann
Post by Daniel Mack
+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,
+};
BUG! .get is set twice instead of .get and .put...
Right, so the code in .put was never actually used, right? Did the
compiler not warn about that?
No, it does not warn...
I do have some memories of testing that code (i.e. the printk was
called), and of experimenting with .get / .put for that particular
control, esp. concerning alsactl store/restore.

I will do a full compile / test cycle, when David has incorporated all
the comments into a new patch.
Post by Daniel Mack
ARRAY_SIZE(s18i8_text) == 35, so .len = (ARRAY_SIZE()-8) ?
I not sure that's helpful...
.opt_matrix.len = .mix_start + 1,
.opt_master.len = .opt_matrix.len + .matrix_out // == ARRAY_SIZE()
Hmm, yes. I was just thinking about something that makes the values look
a bit less magical.
Maybe a comment at the first such definition helps.

Tobias
David Henningsson
2014-10-13 13:30:29 UTC
Permalink
Post by Daniel Mack
Hi Tobias,
Post by Tobias Hoffmann
Post by David Henningsson
+
+/* #define WITH_METER */
+/* #define WITH_LOGSCALEMETER */
These should either be converted to module parameters, or removed
alltogether. Why are they configurable, anyway?
As I've said in my earlier mail, code is current not working: Metering
should be removed from this patch (or rewritten to use hwdep-API).
Post by David Henningsson
Post by David Henningsson
+#define LEVEL_BIAS 128 /* some gui mixers can't handle negative ctl values (alsamixergui, qasmixer, ...) */
+
+#ifndef LEVEL_BIAS
+ #define LEVEL_BIAS 0
+#endif
Same here.
As LEVEL_BIAS = 0 definitely causes problems with some mixer GUIs, the
#ifndef ... #endif should be removed completely.
Alright. David, will you be working on new version of the patch?
Yes, time permitting.

Thanks for the comments - so far they have mostly been about polishing
and cleanup, and that's not too difficult, if that's all that's needed.
But at this point, it would be nice if the maintainer(s) took a
birds-eye view of this driver and looked at the over all structure. I e,
so I don't spend time polishing things, that would anyway need a big
rewrite before they are accepted.

// David

Takashi Iwai
2014-10-09 08:35:56 UTC
Permalink
At Wed, 08 Oct 2014 21:04:06 +0200,
Post by Daniel Mack
Post by David Henningsson
+ if (ret < 0) {
+ 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
That can be moved to snd_printdd()
We've been moving from snd_print*() to dev_*() recently, so try to use
dev_dbg() here. Also, for other use of printk (or snd_printk() etc),
try to convert to dev_info(), dev_err(), etc, appropriately.


thanks,

Takashi
Tobias Hoffmann
2014-10-13 10:58:21 UTC
Permalink
Post by David Henningsson
+ /* 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;
+
There have been two reports of users that had to replace the
set_ctl_urb2 line with:

char buffer[4] = { '\x80', '\xbb', '\x00', '\x00' };
err = set_ctl_urb2(mixer->chip, UAC2_CS_CUR, 0x0100, 0x29, buffer, 4);

otherwise they would get a kernel oops, e.g.:

BUG: unable to handle kernel paging request at ffffffffa0b43440
IP: [<ffffffff8136f3c2>] memcpy+0x12/0x110
...
Call Trace:
[<ffffffffa0b311d2>] ? snd_usb_ctl_msg+0xc2/0x160 [snd_usb_audio]
[<ffffffffa0b3b7aa>] set_ctl_urb2+0xaa/0x100 [snd_usb_audio]
[<ffffffffa0b3c9fa>] scarlett_mixer_controls+0x28a/0x4c0
[snd_usb_audio]
...

I'm not sure, why this happen, and why only for very few users.
Maybe it has to do with a particular compiler, or some kernel config
setting that places the bytes "somewhere else" when they are passed as
string?

Tobias
Clemens Ladisch
2014-10-13 11:38:55 UTC
Permalink
Post by Tobias Hoffmann
+ err = set_ctl_urb2(mixer->chip, UAC2_CS_CUR, 0x0100, 0x29, "\x80\xbb\x00\x00", 4);
char buffer[4] = { '\x80', '\xbb', '\x00', '\x00' };
err = set_ctl_urb2(mixer->chip, UAC2_CS_CUR, 0x0100, 0x29, buffer, 4);
BUG: unable to handle kernel paging request at ffffffffa0b43440
IP: [<ffffffff8136f3c2>] memcpy+0x12/0x110
...
[<ffffffffa0b311d2>] ? snd_usb_ctl_msg+0xc2/0x160 [snd_usb_audio]
[<ffffffffa0b3b7aa>] set_ctl_urb2+0xaa/0x100 [snd_usb_audio]
[<ffffffffa0b3c9fa>] scarlett_mixer_controls+0x28a/0x4c0 [snd_usb_audio]
Strange.
Post by Tobias Hoffmann
Maybe it has to do with a particular compiler, or some kernel config
setting that places the bytes "somewhere else" when they are passed as
string?
Buffers on the stack or in modules cannot be used for DMA on some
architectures, but that is why snd_usb_ctl_msg() creates a copy of the
data.

This crash is inside memcpy(), where this memory type cannot make any
difference.

Please show the entire oops.


Regards,
Clemens
Tobias Hoffmann
2014-10-13 12:02:12 UTC
Permalink
Post by Clemens Ladisch
Post by Tobias Hoffmann
+ err = set_ctl_urb2(mixer->chip, UAC2_CS_CUR, 0x0100, 0x29, "\x80\xbb\x00\x00", 4);
char buffer[4] = { '\x80', '\xbb', '\x00', '\x00' };
err = set_ctl_urb2(mixer->chip, UAC2_CS_CUR, 0x0100, 0x29, buffer, 4);
[...]
This crash is inside memcpy(), where this memory type cannot make any
difference.
Please show the entire oops.
The first reporter (18i8) only sent me a patch with exactly that change,
Post by Clemens Ladisch
It also oopses my kernel because of some constness issues.
Here are two reports (6i6) with oops trace:
https://github.com/smilingthax/alsa-driver_scarlett/issues/7

https://github.com/smilingthax/alsa-driver_scarlett/issues/2#issuecomment-54364354

Tobias
Clemens Ladisch
2014-10-13 12:09:42 UTC
Permalink
Post by Tobias Hoffmann
https://github.com/smilingthax/alsa-driver_scarlett/issues/7
https://github.com/smilingthax/alsa-driver_scarlett/issues/2#issuecomment-54364354
Both kernels are tainted.
Anyway, in both cases, the simplest explanation would be that the driver
was not compiled for the correct kernel version/configuration.


Regards,
Clemens
Tobias Hoffmann
2014-10-13 12:21:58 UTC
Permalink
Post by Clemens Ladisch
Post by Tobias Hoffmann
https://github.com/smilingthax/alsa-driver_scarlett/issues/7
https://github.com/smilingthax/alsa-driver_scarlett/issues/2#issuecomment-54364354
Both kernels are tainted.
Anyway, in both cases, the simplest explanation would be that the driver
was not compiled for the correct kernel version/configuration.
But it does not explain why it works after applying the patch (issue 7).
In issue 2 I apparently did not connect the dots by then, and did not
give the patch to the reporter, so we don't know if it would have worked
there.

And the original reporter's problem also occurred at the same location,
i.e. where I initialize the sampling rate register with a defined value.
I can (try to) ask him, whether he can provide more information for his
case.

Tobias
Loading...