Discussion:
[PATCH v2] Scarlett mixer interface inclusion
Chris J Arges
2014-10-21 19:46:26 UTC
Permalink
This is a followup to David Henningsson's PATCH [1] to include the work of
Tobias Hoffman and Robin Gareus in getting the Scarlett mixer interface
working. I've read through the comments and tried to include fixes where
appropriate.

Overall, I'd like to get a good base commit and start working on other commits
that address any major structual changes such as making functions more generic
or any other cleanup.

I've tested this on my Scarlett 18i8 mixer and it does work, however there
are still a few issues which need to be fixed:

1) When loading a device one gets the following messages:
snd-usb-audio 1-1.1:1.0: control 2:0:0:Master Playback Switch:0 is already present
snd-usb-audio: probe of 1-1.1:1.3 failed with error -5

2) When unloading a device there are numerous sysfs_remove_group issues:
usb 1-1.1: USB disconnect, device number 6
------------[ cut here ]------------
WARNING: CPU: 0 PID: 54 at /build/buildd/linux-3.16.0/fs/sysfs/group.c:219 sysfs_remove_group+0x99/0xa0()
sysfs group ffffffff82cbd6e0 not found for kobject 'midi1'
Modules linked in: snd_usb_audio(OE) snd_usbmidi_lib(OE) ctr ccm xt_conntrack ipt_REJECT xt_CHECKSUM iptable_mangle ipt_MASQUERADE iptable_nat nf_conntrack_ipv4 nf_defrag_ipv4 nf_nat_ipv4 nf_nat nf_conntrack xt_tcpudp bridge stp llc iptable_filter ip_tables x_tables arc4 iwldvm snd_hda_codec_hdmi mac80211 snd_hda_codec_conexant snd_hda_codec_generic iwlwifi snd_hda_intel uvcvideo snd_hda_controller videobuf2_vmalloc snd_hda_codec videobuf2_memops videobuf2_core snd_hwdep v4l2_common snd_pcm intel_rapl x86_pkg_temp_thermal videodev intel_powerclamp media coretemp kvm_intel bnep kvm btusb rfcomm cfg80211 joydev bluetooth serio_raw 6lowpan_iphc thinkpad_acpi nvram snd_seq_midi lpc_ich snd_seq_midi_event snd_rawmidi shpchp snd_seq mei_me snd_seq_device snd_timer mei snd soundcore parport_pc
ppdev mac_hid lp parport binfmt_misc nls_iso8859_1 dm_crypt crct10dif_pclmul crc32_pclmul ghash_clmulni_intel aesni_intel aes_x86_64 lrw gf128mul glue_helper ablk_helper cryptd i915 psmouse ahci libahci firewire_ohci sdhci_pci firewire_core sdhci crc_itu_t i2c_algo_bit e1000e drm_kms_helper drm ptp pps_core wmi video
CPU: 0 PID: 54 Comm: khubd Tainted: G OE 3.16.0-23-generic #30-Ubuntu
Hardware name: LENOVO 4177CTO/4177CTO, BIOS 83ET76WW (1.46 ) 07/05/2013
0000000000000009 ffff880407b4f920 ffffffff8277fcbc ffff880407b4f968
ffff880407b4f958 ffffffff8206fd8d 0000000000000000 ffffffff82cbd6e0
ffff880405c7b410 0000000000000001 ffff8803eb059cf8 ffff880407b4f9b8
Call Trace:
[<ffffffff8277fcbc>] dump_stack+0x45/0x56
[<ffffffff8206fd8d>] warn_slowpath_common+0x7d/0xa0
[<ffffffff8206fdfc>] warn_slowpath_fmt+0x4c/0x50
[<ffffffff82256538>] ? kernfs_find_and_get_ns+0x48/0x60
[<ffffffff8225a259>] sysfs_remove_group+0x99/0xa0
[<ffffffff824d6927>] dpm_sysfs_remove+0x57/0x60
[<ffffffff824cb625>] device_del+0x45/0x1d0
[<ffffffff824cb7ce>] device_unregister+0x1e/0x70
[<ffffffff824cb89c>] device_destroy+0x3c/0x50
[<ffffffffc045f34b>] sound_remove_unit+0x5b/0x90 [soundcore]
[<ffffffffc045f3aa>] unregister_sound_special+0x2a/0x30 [soundcore]
[<ffffffffc04cc9b0>] snd_unregister_oss_device+0x60/0x100 [snd]
[<ffffffffc0550ca9>] snd_rawmidi_dev_disconnect+0x119/0x160 [snd_rawmidi]
[<ffffffffc04cc14c>] __snd_device_disconnect.part.1+0x1c/0x60 [snd]
[<ffffffffc04cc437>] snd_device_disconnect_all+0x47/0x60 [snd]
[<ffffffffc04c59f7>] snd_card_disconnect+0x147/0x1f0 [snd]
[<ffffffffc09cf5a8>] usb_audio_disconnect+0x78/0x1a0 [snd_usb_audio]
[<ffffffff825867b4>] usb_unbind_interface+0x74/0x2b0
[<ffffffff824cf76f>] __device_release_driver+0x7f/0xf0
[<ffffffff824cf803>] device_release_driver+0x23/0x30
[<ffffffff824cf0f8>] bus_remove_device+0x108/0x180
[<ffffffff824cb709>] device_del+0x129/0x1d0
[<ffffffff82583f21>] usb_disable_device+0x91/0x290
[<ffffffff82578688>] usb_disconnect+0x98/0x2f0
[<ffffffff8257a6cf>] hub_port_connect+0x7f/0xac0
[<ffffffff8257b9a3>] hub_events+0x893/0xda0
[<ffffffff8257beed>] hub_thread+0x3d/0x160
[<ffffffff820b9590>] ? prepare_to_wait_event+0x100/0x100
[<ffffffff8257beb0>] ? hub_events+0xda0/0xda0
[<ffffffff82094aeb>] kthread+0xdb/0x100
[<ffffffff82094a10>] ? kthread_create_on_node+0x1c0/0x1c0
[<ffffffff82787c3c>] ret_from_fork+0x7c/0xb0
[<ffffffff82094a10>] ? kthread_create_on_node+0x1c0/0x1c0
---[ end trace 7a8cdc4a9c65ddf5 ]---

However this issue occured even without the patch applied.

Please follow up with additional comments and review and I'll be happy to
incorporate them into the next patch version.

[1] http://mailman.alsa-project.org/pipermail/alsa-devel/2014-October/081986.html


Chris J Arges (1):
Scarlett mixer interface for 6i6, 18i6, 18i8 and 18i20

sound/usb/Makefile | 1 +
sound/usb/mixer.c | 27 +-
sound/usb/quirks-table.h | 51 --
sound/usb/scarlett/scarlettmixer.c | 1264 ++++++++++++++++++++++++++++++++++++
sound/usb/scarlett/scarlettmixer.h | 6 +
5 files changed, 1293 insertions(+), 56 deletions(-)
create mode 100644 sound/usb/scarlett/scarlettmixer.c
create mode 100644 sound/usb/scarlett/scarlettmixer.h
--
2.1.0
Chris J Arges
2014-10-21 19:46:27 UTC
Permalink
This code contains the Scarlett mixer interface code that was originally
written by Tobias Hoffman and Robin Gareus. Because the device doesn't
properly implement UAC2 this code adds a mixer quirk for the device.

Changes from the original code include removing the metering code along with
dead code and comments. Compiler warnings were fixed. The code to initialize
the sampling rate was causing a crash this was fixed as discussed on the
mailing list. Error, and info messages were convered to dev_err and dev_info
interfaces. Finally the code was placed in its own subdirectory.

Author: Tobias Hoffman <***@gmx.de>
Author: Robin Gareus <***@gareus.org>
Signed-off-by: David Henningsson <***@canonical.com>
Signed-off-by: Chris J Arges <***@canonical.com>
---
sound/usb/Makefile | 1 +
sound/usb/mixer.c | 27 +-
sound/usb/quirks-table.h | 51 --
sound/usb/scarlett/scarlettmixer.c | 1264 ++++++++++++++++++++++++++++++++++++
sound/usb/scarlett/scarlettmixer.h | 6 +
5 files changed, 1293 insertions(+), 56 deletions(-)
create mode 100644 sound/usb/scarlett/scarlettmixer.c
create mode 100644 sound/usb/scarlett/scarlettmixer.h

diff --git a/sound/usb/Makefile b/sound/usb/Makefile
index 2b92f0d..4267e47 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 \
+ scarlett/scarlettmixer.o \
stream.o

snd-usbmidi-lib-objs := midi.o
diff --git a/sound/usb/mixer.c b/sound/usb/mixer.c
index 2e4a9db..7b18e28 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 "scarlett/scarlettmixer.h"

#define MAX_ID_ELEMS 256

@@ -2462,11 +2463,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/scarlett/scarlettmixer.c b/sound/usb/scarlett/scarlettmixer.c
new file mode 100644
index 0000000..5ae4fc1
--- /dev/null
+++ b/sound/usb/scarlett/scarlettmixer.c
@@ -0,0 +1,1264 @@
+/*
+ * Scarlett Driver for ALSA
+ *
+ * Copyright (c) 2013 by Tobias Hoffmann
+ * Copyright (c) 2013 by Robin Gareus <robin at gareus.org>
+ * Copyright (c) 2002 by Takashi Iwai <tiwai at suse.de>
+ *
+ * Many codes borrowed from audio.c by
+ * Alan Cox (alan at lxorguk.ukuu.org.uk)
+ * Thomas Sailer (sailer at ife.ee.ethz.ch)
+ *
+ * Code cleanup and testing:
+ * David Henningsson <david.henningsson at canonical.com>
+ * Chris J Arges <chris.j.arges at canonical.com>
+ *
+ * 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.
+ *
+ * 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"
+
+/* some gui mixers can't handle negative ctl values */
+#define LEVEL_BIAS 128
+
+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:
+ dev_err(&(chip->dev->dev),
+ "cannot get ctl value: req = %#x, wValue = %#x, wIndex = %#x, size = %d\n",
+ bRequest, wValue, idx, size);
+ return ret;
+ }
+ 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;
+ }
+ }
+ dev_err(&(chip->dev->dev),
+ "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);
+ 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) {
+ dev_err(&(chip->dev->dev),
+ "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"
+ }
+};
+
+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;
+
+ 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];
+ 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;
+
+ dev_info(&(elem->mixer->chip->dev->dev),
+ "Scarlett: Saved settings to hardware.\n");
+ }
+
+ return 0; /* (?) */
+}
+
+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) {
+ dev_err(&(elem->mixer->chip->dev->dev),
+ "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;
+ }
+ }
+
+ 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,
+};
+
+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,
+ .put = 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) {
+ dev_err(&(elem->mixer->chip->dev->dev), "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 *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 *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 *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 *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 *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 */
+ }
+};
+
+/*
+ * 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;
+ static char sample_rate_buffer[4] = { '\x80', '\xbb', '\x00', '\x00' };
+
+ 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;
+
+ /* initialize sampling rate to 48000 */
+ err = set_ctl_urb2(mixer->chip, UAC2_CS_CUR, 0x0100, 0x29, sample_rate_buffer, 4);
+ if (err < 0)
+ return err;
+
+ return 0;
+}
diff --git a/sound/usb/scarlett/scarlettmixer.h b/sound/usb/scarlett/scarlettmixer.h
new file mode 100644
index 0000000..ebde17e
--- /dev/null
+++ b/sound/usb/scarlett/scarlettmixer.h
@@ -0,0 +1,6 @@
+#ifndef __USBSCARLETTMIXER_H
+#define __USBSCARLETTMIXER_H
+
+int scarlett_mixer_controls(struct usb_mixer_interface *mixer);
+
+#endif /* __USBSCARLETTMIXER_H */
--
2.1.0
Takashi Iwai
2014-10-22 06:49:37 UTC
Permalink
At Tue, 21 Oct 2014 14:46:27 -0500,
Post by Chris J Arges
This code contains the Scarlett mixer interface code that was originally
written by Tobias Hoffman and Robin Gareus. Because the device doesn't
properly implement UAC2 this code adds a mixer quirk for the device.
Changes from the original code include removing the metering code along with
dead code and comments. Compiler warnings were fixed. The code to initialize
the sampling rate was causing a crash this was fixed as discussed on the
mailing list. Error, and info messages were convered to dev_err and dev_info
interfaces. Finally the code was placed in its own subdirectory.
---
sound/usb/Makefile | 1 +
sound/usb/mixer.c | 27 +-
sound/usb/quirks-table.h | 51 --
sound/usb/scarlett/scarlettmixer.c | 1264 ++++++++++++++++++++++++++++++++++++
sound/usb/scarlett/scarlettmixer.h | 6 +
5 files changed, 1293 insertions(+), 56 deletions(-)
create mode 100644 sound/usb/scarlett/scarlettmixer.c
create mode 100644 sound/usb/scarlett/scarlettmixer.h
diff --git a/sound/usb/Makefile b/sound/usb/Makefile
index 2b92f0d..4267e47 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 \
+ scarlett/scarlettmixer.o \
Any reason to create a subdirectory although it's no individual
driver? Put rather in the plain directory.

Overall, there seem too many copies from mixer.c. Can't we rather
extend the stuff in mixer.c, make some of them global and let access
from scalettmixer.c?


Takashi
Chris J Arges
2014-10-22 18:44:12 UTC
Permalink
Post by Takashi Iwai
At Tue, 21 Oct 2014 14:46:27 -0500,
Post by Chris J Arges
This code contains the Scarlett mixer interface code that was originally
written by Tobias Hoffman and Robin Gareus. Because the device doesn't
properly implement UAC2 this code adds a mixer quirk for the device.
Changes from the original code include removing the metering code along with
dead code and comments. Compiler warnings were fixed. The code to initialize
the sampling rate was causing a crash this was fixed as discussed on the
mailing list. Error, and info messages were convered to dev_err and dev_info
interfaces. Finally the code was placed in its own subdirectory.
---
sound/usb/Makefile | 1 +
sound/usb/mixer.c | 27 +-
sound/usb/quirks-table.h | 51 --
sound/usb/scarlett/scarlettmixer.c | 1264 ++++++++++++++++++++++++++++++++++++
sound/usb/scarlett/scarlettmixer.h | 6 +
5 files changed, 1293 insertions(+), 56 deletions(-)
create mode 100644 sound/usb/scarlett/scarlettmixer.c
create mode 100644 sound/usb/scarlett/scarlettmixer.h
diff --git a/sound/usb/Makefile b/sound/usb/Makefile
index 2b92f0d..4267e47 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 \
+ scarlett/scarlettmixer.o \
Any reason to create a subdirectory although it's no individual
driver? Put rather in the plain directory.
I thought it would be neater, but I'll put the file back in the main
sound/usb directory. If the code is small enough I could add it to
mixer_quirks.c.
Post by Takashi Iwai
Overall, there seem too many copies from mixer.c. Can't we rather
extend the stuff in mixer.c, make some of them global and let access
from scalettmixer.c?
Yes, I can work on this, I think it would help reduce the code redundancy.
Post by Takashi Iwai
Takashi
Thank you,
--chris

Takashi Iwai
2014-10-22 06:36:50 UTC
Permalink
At Tue, 21 Oct 2014 14:46:26 -0500,
Post by Chris J Arges
This is a followup to David Henningsson's PATCH [1] to include the work of
Tobias Hoffman and Robin Gareus in getting the Scarlett mixer interface
working. I've read through the comments and tried to include fixes where
appropriate.
Overall, I'd like to get a good base commit and start working on other commits
that address any major structual changes such as making functions more generic
or any other cleanup.
Use 3.18-rc1 as the baseline.
Post by Chris J Arges
I've tested this on my Scarlett 18i8 mixer and it does work, however there
snd-usb-audio 1-1.1:1.0: control 2:0:0:Master Playback Switch:0 is already present
snd-usb-audio: probe of 1-1.1:1.3 failed with error -5
This is certainly a driver problem, as the message says.
Post by Chris J Arges
usb 1-1.1: USB disconnect, device number 6
------------[ cut here ]------------
WARNING: CPU: 0 PID: 54 at /build/buildd/linux-3.16.0/fs/sysfs/group.c:219 sysfs_remove_group+0x99/0xa0()
sysfs group ffffffff82cbd6e0 not found for kobject 'midi1'
(snip)
Post by Chris J Arges
However this issue occured even without the patch applied.
Yes, there was a similar report, but we couldn't reproduce here.
It's a Oops coming from OSS midi device. This is fairly harmless, but
annoying. Could you give your kernel config? Maybe there's some
difference.


Takashi
Loading...