From patchwork Wed Oct 29 20:56:03 2014 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Chris J Arges X-Patchwork-Id: 5191641 X-Patchwork-Delegate: tiwai@suse.de Return-Path: X-Original-To: patchwork-alsa-devel@patchwork.kernel.org Delivered-To: patchwork-parsemail@patchwork2.web.kernel.org Received: from mail.kernel.org (mail.kernel.org [198.145.19.201]) by patchwork2.web.kernel.org (Postfix) with ESMTP id 373F6C11AC for ; Wed, 29 Oct 2014 20:58:39 +0000 (UTC) Received: from mail.kernel.org (localhost [127.0.0.1]) by mail.kernel.org (Postfix) with ESMTP id D2C472015A for ; Wed, 29 Oct 2014 20:58:36 +0000 (UTC) Received: from alsa0.perex.cz (alsa0.perex.cz [77.48.224.243]) by mail.kernel.org (Postfix) with ESMTP id 86448201FA for ; Wed, 29 Oct 2014 20:58:32 +0000 (UTC) Received: by alsa0.perex.cz (Postfix, from userid 1000) id 98D0F2652C4; Wed, 29 Oct 2014 21:58:31 +0100 (CET) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on mail.kernel.org X-Spam-Level: X-Spam-Status: No, score=-1.9 required=5.0 tests=BAYES_00, RCVD_IN_DNSWL_NONE, UNPARSEABLE_RELAY autolearn=unavailable version=3.3.1 Received: from alsa0.perex.cz (localhost [IPv6:::1]) by alsa0.perex.cz (Postfix) with ESMTP id 2C8BB2650BE; Wed, 29 Oct 2014 21:56:30 +0100 (CET) X-Original-To: alsa-devel@alsa-project.org Delivered-To: alsa-devel@alsa-project.org Received: by alsa0.perex.cz (Postfix, from userid 1000) id BABBD2605DB; Wed, 29 Oct 2014 21:56:22 +0100 (CET) Received: from youngberry.canonical.com (youngberry.canonical.com [91.189.89.112]) by alsa0.perex.cz (Postfix) with ESMTP id 934E92605DB for ; Wed, 29 Oct 2014 21:56:13 +0100 (CET) Received: from cpe-173-174-67-34.austin.res.rr.com ([173.174.67.34] helo=localhost.localdomain) by youngberry.canonical.com with esmtpsa (TLS1.0:RSA_AES_128_CBC_SHA1:16) (Exim 4.71) (envelope-from ) id 1XjaHs-0005Xb-UB; Wed, 29 Oct 2014 20:56:13 +0000 From: Chris J Arges To: alsa-devel@alsa-project.org Date: Wed, 29 Oct 2014 15:56:03 -0500 Message-Id: <1414616163-14146-5-git-send-email-chris.j.arges@canonical.com> X-Mailer: git-send-email 2.1.0 In-Reply-To: <1414616163-14146-1-git-send-email-chris.j.arges@canonical.com> References: <1414616163-14146-1-git-send-email-chris.j.arges@canonical.com> MIME-Version: 1.0 Cc: tiwai@suse.de, th55@gmx.de, robin@gareus.org, clemens@ladisch.de, david.henningsson@canonical.com Subject: [alsa-devel] [PATCH v3 4/4] ALSA: usb-audio: Scarlett mixer interface for 6i6, 18i6, 18i8 and 18i20 X-BeenThere: alsa-devel@alsa-project.org X-Mailman-Version: 2.1.14 Precedence: list List-Id: "Alsa-devel mailing list for ALSA developers - http://www.alsa-project.org" List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: alsa-devel-bounces@alsa-project.org Sender: alsa-devel-bounces@alsa-project.org X-Virus-Scanned: ClamAV using ClamSMTP 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. The custom scarlett_mixer_elem_info struct was replaced with the more generic usb_mixer_elem_info to be able to recycle more code from mixer.c. Signed-off-by: Chris J Arges --- sound/usb/Makefile | 1 + sound/usb/mixer_quirks.c | 9 + sound/usb/mixer_scarlett.c | 1110 ++++++++++++++++++++++++++++++++++++++++++++ sound/usb/mixer_scarlett.h | 6 + 4 files changed, 1126 insertions(+) create mode 100644 sound/usb/mixer_scarlett.c create mode 100644 sound/usb/mixer_scarlett.h diff --git a/sound/usb/Makefile b/sound/usb/Makefile index 2b92f0d..bcee406 100644 --- a/sound/usb/Makefile +++ b/sound/usb/Makefile @@ -9,6 +9,7 @@ snd-usb-audio-objs := card.o \ helper.o \ mixer.o \ mixer_quirks.o \ + mixer_scarlett.o \ pcm.o \ proc.o \ quirks.o \ diff --git a/sound/usb/mixer_quirks.c b/sound/usb/mixer_quirks.c index c9665bf..d06d27a 100644 --- a/sound/usb/mixer_quirks.c +++ b/sound/usb/mixer_quirks.c @@ -41,6 +41,7 @@ #include "usbaudio.h" #include "mixer.h" #include "mixer_quirks.h" +#include "mixer_scarlett.h" #include "helper.h" extern struct snd_kcontrol_new *snd_usb_feature_unit_ctl; @@ -1664,6 +1665,14 @@ int snd_usb_mixer_apply_create_quirk(struct usb_mixer_interface *mixer) /* detection is disabled in mixer_maps.c */ err = snd_create_std_mono_table(mixer, ebox44_table); break; + + 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 */ + err = snd_scarlett_controls_create(mixer); + break; } return err; diff --git a/sound/usb/mixer_scarlett.c b/sound/usb/mixer_scarlett.c new file mode 100644 index 0000000..816880f --- /dev/null +++ b/sound/usb/mixer_scarlett.c @@ -0,0 +1,1110 @@ +/* + * Scarlett Driver for ALSA + * + * Copyright (c) 2013 by Tobias Hoffmann + * Copyright (c) 2013 by Robin Gareus + * Copyright (c) 2002 by Takashi Iwai + * Copyright (c) 2014 by Chris J Arges + * + * 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: + * David Henningsson + * + * 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 + * + * + * + * /--------------\ 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 | + * \--------------/ \--------------/ + * + * + */ + +#include +#include +#include + +#include +#include +#include + +#include "usbaudio.h" +#include "mixer.h" +#include "helper.h" +#include "power.h" + +#include "mixer_scarlett.h" + +/* some gui mixers can't handle negative ctl values */ +#define SND_SCARLETT_LEVEL_BIAS 128 + +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 usb_mixer_elem_enum_info opt_master; + struct usb_mixer_elem_enum_info opt_matrix; + + int (*controls_fn)(struct usb_mixer_interface *mixer, + const struct scarlett_device_info *info); + + int matrix_mux_init[]; +}; + +/********************** 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 usb_mixer_elem_enum_info opt_pad = { + .start = 0, + .len = 2, + .names = (const char *[]){ + txtOff, "-10dB" + } +}; + +static const struct usb_mixer_elem_enum_info opt_impedance = { + .start = 0, + .len = 2, + .names = (const char *[]){ + "Line", "Hi-Z" + } +}; + +static const struct usb_mixer_elem_enum_info opt_clock = { + .start = 1, + .len = 3, + .names = (const char *[]){ + "Internal", "SPDIF", "ADAT" + } +}; + +static const struct usb_mixer_elem_enum_info opt_sync = { + .start = 0, + .len = 2, + .names = (const char *[]){ + "No Lock", "Locked" + } +}; + +static const struct usb_mixer_elem_enum_info opt_save = { + .start = 0, + .len = 2, + .names = (const char *[]){ + "---", "Save" + } +}; + +static int scarlett_ctl_switch_info(struct snd_kcontrol *kctl, + struct snd_ctl_elem_info *uinfo) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + + uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; + uinfo->count = elem->channels; + 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 usb_mixer_elem_info *elem = kctl->private_data; + int i, err, val; + + for (i = 0; i < elem->channels; i++) { + err = snd_usb_get_cur_mix_value(elem, i, 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 usb_mixer_elem_info *elem = kctl->private_data; + int i, changed = 0; + int err, oval, val; + + for (i = 0; i < elem->channels; i++) { + err = snd_usb_get_cur_mix_value(elem, i, i, &oval); + if (err < 0) + return err; + + val = ucontrol->value.integer.value[i]; + val = !val; + if (oval != val) { + err = snd_usb_set_cur_mix_value(elem, i, 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 usb_mixer_elem_info *elem = kctl->private_data; + + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = elem->channels; + uinfo->value.integer.min = -128 + SND_SCARLETT_LEVEL_BIAS; + uinfo->value.integer.max = (int)kctl->private_value + + SND_SCARLETT_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 usb_mixer_elem_info *elem = kctl->private_data; + int i, err, val; + + for (i = 0; i < elem->channels; i++) { + err = snd_usb_get_cur_mix_value(elem, i, i, &val); + if (err < 0) + return err; + + val = clamp(val / 256, -128, (int)kctl->private_value) + + SND_SCARLETT_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 usb_mixer_elem_info *elem = kctl->private_data; + int i, changed = 0; + int err, oval, val; + + for (i = 0; i < elem->channels; i++) { + err = snd_usb_get_cur_mix_value(elem, i, i, &oval); + if (err < 0) + return err; + + val = ucontrol->value.integer.value[i] - + SND_SCARLETT_LEVEL_BIAS; + val = val * 256; + if (oval != val) { + err = snd_usb_set_cur_mix_value(elem, i, 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 usb_mixer_elem_info *elem = kctl->private_data; + + uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; + uinfo->count = elem->channels; + 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->names[uinfo->value.enumerated.item]); + return 0; +} + +static int scarlett_ctl_enum_get(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + int err, val; + + err = snd_usb_get_cur_mix_value(elem, 0, 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 usb_mixer_elem_info *elem = kctl->private_data; + int changed = 0; + int err, oval, val; + + err = snd_usb_get_cur_mix_value(elem, 0, 0, &oval); + if (err < 0) + return err; + + val = ucontrol->value.integer.value[0]; + val = val + elem->opt->start; + if (oval != val) { + err = snd_usb_set_cur_mix_value(elem, 0, 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 usb_mixer_elem_info *elem = kctl->private_data; + struct snd_usb_audio *chip = elem->mixer->chip; + char buf[] = { 0x00, 0xa5 }; + int err; + + if (ucontrol->value.enumerated.item[0] > 0) { + err = snd_usb_ctl_msg(chip->dev, + usb_sndctrlpipe(chip->dev, 0), UAC2_CS_MEM, + USB_RECIP_INTERFACE | USB_TYPE_CLASS | + USB_DIR_OUT, 0x005a, snd_usb_ctrl_intf(chip) | + (0x3c << 8), buf, 2); + 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 usb_mixer_elem_info *elem = kctl->private_data; + struct snd_usb_audio *chip = elem->mixer->chip; + unsigned char buf[2 * MAX_CHANNELS] = {0, }; + int wValue = (elem->control << 8) | elem->idx_off; + int idx = snd_usb_ctrl_intf(chip) | (elem->id << 8); + int err; + + err = snd_usb_ctl_msg(chip->dev, + usb_rcvctrlpipe(chip->dev, 0), + UAC2_CS_MEM, + USB_RECIP_INTERFACE | USB_TYPE_CLASS | + USB_DIR_IN, wValue, idx, buf, elem->channels); + if (err < 0) + return err; + + ucontrol->value.enumerated.item[0] = clamp((int)buf[0], 0, 1); + 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_type, int channels, const char *name, + const struct usb_mixer_elem_enum_info *opt, + struct usb_mixer_elem_info **elem_ret +) +{ + struct snd_kcontrol *kctl; + struct usb_mixer_elem_info *elem; + int err; + + elem = kzalloc(sizeof(*elem), GFP_KERNEL); + if (!elem) + return -ENOMEM; + + elem->mixer = mixer; + elem->control = offset; + elem->idx_off = num; + elem->id = index; + elem->val_type = val_type; + + elem->channels = channels; + elem->opt = opt; + + kctl = snd_ctl_new1(ncontrol, elem); + if (!kctl) { + dev_err(&(mixer->chip->dev->dev), "cannot malloc kcontrol\n"); + kfree(elem); + return -ENOMEM; + } + kctl->private_free = snd_usb_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 usb_mixer_elem_info *elem, int value) +{ + int err, channel; + + for (channel = 0; channel < elem->channels; channel++) { + err = snd_usb_set_cur_mix_value(elem, channel, 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, USB_MIXER_S16, 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, USB_MIXER_S16, 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, USB_MIXER_S16, 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, USB_MIXER_S16, count, name, NULL, &elem); \ + if (err < 0) \ + return err; \ + INIT(0); \ + } 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[48]; + struct usb_mixer_elem_info *elem; + + /* Add mute switch */ + snprintf(mx, sizeof(mx), "Master %d (%s) Playback Switch", + index + 1, name); + CTL_SWITCH(0x0a, 0x01, 2*index+1, 2, mx); + + /* Add volume control */ + snprintf(mx, sizeof(mx), "Master %d (%s) Playback Volume", + index + 1, name); + CTL_MASTER(0x0a, 0x02, 2*index+1, 2, mx); + + /* Add L channel source playback enumeration */ + snprintf(mx, sizeof(mx), "Master %dL (%s) Source Playback Enum", + index + 1, name); + CTL_ENUM(0x33, 0x00, 2*index, mx, &info->opt_master); + INIT(info->mix_start); + + /* Add R channel source playback enumeration */ + snprintf(mx, sizeof(mx), "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 usb_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 usb_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 usb_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 usb_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) +{ + 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_names[] = { + 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, + .names = s6i6_names + }, + + .opt_matrix = { + .start = -1, + .len = 19, + .names = s6i6_names + }, + + .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_names[] = { + 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, + .names = s8i6_names + }, + + .opt_matrix = { + .start = -1, + .len = 19, + .names = s8i6_names + }, + + .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_names[] = { + 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, + .names = s18i6_names + }, + + .opt_matrix = { + .start = -1, + .len = 25, + .names = s18i6_names + }, + + .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_names[] = { + 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, + .names = s18i8_names + }, + + .opt_matrix = { + .start = -1, + .len = 27, + .names = s18i8_names + }, + + .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_names[] = { + 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, + .names = s18i20_names + }, + + .opt_matrix = { + .start = -1, + .len = 39, + .names = s18i20_names + }, + + .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 snd_scarlett_controls_create(struct usb_mixer_interface *mixer) +{ + int err, i, o; + char mx[32]; + const struct scarlett_device_info *info; + struct usb_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); /* 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, + USB_MIXER_U8, 1, "Sample Clock Source", + &opt_clock, &elem); + if (err < 0) + return err; + + /* val_len == 1 and UAC2_CS_MEM */ + err = add_new_ctl(mixer, &usb_scarlett_ctl_sync, 0x3c, 0x00, 2, + USB_MIXER_U8, 1, "Sample Clock Sync Status", + &opt_sync, &elem); + if (err < 0) + return err; + + /* val_len == 1 and UAC2_CS_MEM */ + err = add_new_ctl(mixer, &usb_scarlett_ctl_save, 0x3c, 0x00, 0x5a, + USB_MIXER_U8, 1, "Save To HW", &opt_save, &elem); + if (err < 0) + return err; + + /* initialize sampling rate to 48000 */ + err = snd_usb_ctl_msg(mixer->chip->dev, + usb_sndctrlpipe(mixer->chip->dev, 0), UAC2_CS_CUR, + USB_RECIP_INTERFACE | USB_TYPE_CLASS | + USB_DIR_OUT, 0x0100, snd_usb_ctrl_intf(mixer->chip) | + (0x29 << 8), sample_rate_buffer, 4); + if (err < 0) + return err; + + return 0; +} diff --git a/sound/usb/mixer_scarlett.h b/sound/usb/mixer_scarlett.h new file mode 100644 index 0000000..19c592a --- /dev/null +++ b/sound/usb/mixer_scarlett.h @@ -0,0 +1,6 @@ +#ifndef __USB_MIXER_SCARLETT_H +#define __USB_MIXER_SCARLETT_H + +int snd_scarlett_controls_create(struct usb_mixer_interface *mixer); + +#endif /* __USB_MIXER_SCARLETT_H */