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 th55@gmx.de Author: Robin Gareus robin@gareus.org Signed-off-by: David Henningsson david.henningsson@canonical.com Signed-off-by: Chris J Arges 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 */