From nobody Sat Feb 7 08:07:44 2026 Received: from mail-wm1-f47.google.com (mail-wm1-f47.google.com [209.85.128.47]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id A7BC72580EE for ; Tue, 27 Jan 2026 08:13:04 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.47 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1769501586; cv=none; b=QEaVY5yXLUyrsmzIsJePM/uJ7z7LSKYbPeliHk/3WPrXotAQUdM5De8GgL2bo//XJuXgRb3qrL2GX/crog2/7ZxRmhDbcD2aTJ/sjK/NLbEGVC/S9AebLz+rBKRJb5i96HO6SCWRgMQpLmQ5CYz++59gLQXJv3UQVgF6fPGE3kU= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1769501586; c=relaxed/simple; bh=ZaHcQrT0lh5Nn6AUb3W/04odQHkN20pWEYvQdQ1VuZI=; h=From:To:Cc:Subject:Date:Message-ID:MIME-Version; b=GqEaolXjKFXHzCo8RS106kiXsmUOFxKikKLBl1C1cz39YE92F1RxAgyEzZBxc8KBczP/o2Dvp3H3ExuivP270tPw1rCTkfjVMqMAwOWgpmQyuW9BoDG0Bi4mN2P+orhGtV9cwz60bNXplNE6saitXo8qKPM5JOMwfIy/Pde9HDQ= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=Dn3J7zvp; arc=none smtp.client-ip=209.85.128.47 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="Dn3J7zvp" Received: by mail-wm1-f47.google.com with SMTP id 5b1f17b1804b1-4806b43beb6so683095e9.3 for ; Tue, 27 Jan 2026 00:13:04 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1769501583; x=1770106383; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:message-id:date:subject:cc :to:from:from:to:cc:subject:date:message-id:reply-to; bh=XjuypISaQFtC+AM1Wis3oNfFfvMNMa9Kd3Ca8E3gltM=; b=Dn3J7zvpNrcX/AkjjmDnPdd0/T6iY6O/ep8u2oaDFvcWtjNT4WkuI9v2FzDEtYiv4E I9b3OnpSUosnXqH6ZO9RyV9t/p9u9c4uI2MMBZgIB4bzrwciaOXYPlWQuylUW1acMaMU /FoQxpv1QuF9e/y/xq62OCO+8hdpSYHoeWWv8AptYAIKxewBP8dm9s9EfXGO3fTJsNGK G4fPxh9eIppr/P2irnxVscbrs+1KlLU74MVAIseHtjb53MrGtZft5z9UgUnQj0N3QitT 6AeTQ/2/AEtXjeRrxbO0CoKU3Xf0Aa9xi3TwoxzA4d+pzFWtTIEZc7c5XxIOSHPlaHYR dIxA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1769501583; x=1770106383; h=content-transfer-encoding:mime-version:message-id:date:subject:cc :to:from:x-gm-gg:x-gm-message-state:from:to:cc:subject:date :message-id:reply-to; bh=XjuypISaQFtC+AM1Wis3oNfFfvMNMa9Kd3Ca8E3gltM=; b=nWW1XrLCKR/9+0IX8IovJkAwf7WcStG6FrkcI/0yxal97rrCH009e4T3Iya9Glgpkw mcg8DT3bbQiqM2NhuQZGxRvTGTwVOCeTdt4YOWbHgj51m7/FGcaRvg1LkSK0g4M+w2Qm IJ0mDOYWR8CNKVfWVNQ4F1ZHTAFRaEynYBERFX/nnmtor1aBMXvBI6fAt5t3YzBBDPQl NA7lPQRhXyeqnthLI1Q5QpnJ64EqRo5NwzoGZ9L2jhAwRD/LGV3JefO8EJHa3aWe2TyE RHvtM1aZWfDgf59B1pVveGzu3czfXwsU3ZzsY4PPmBAIOMTYPwtTNqpbgtzXFExlvryv +jBg== X-Forwarded-Encrypted: i=1; AJvYcCVRXShunp/FlZEVDrmEpC77JEXXk7Cjii15AopuxW2go063c9KPWCJLswi7BCCyfOhG9bfVVrDkVPrgncI=@vger.kernel.org X-Gm-Message-State: AOJu0YyNJ9QooTzOlScUXrutsAQeUWK3rKrkMyfKvkVrb6j70Jr0rw0B KGqBZrRrzFDH10dL4xGVo+0lMpgrgz1eGVXHiDISHUxFywPXR03hzBoyPBlOzw== X-Gm-Gg: AZuq6aJqocOj5pta34TC0RkqRJFkvevlaywsvHPjdp5emD1JgUpYSVM7AgW9QHDVYsp pGFwcWWolZrQWciT6t9VBM/0OceQNKmP82w6SlCrUUy0BPbCMTHaqrrgJhpoTlRidcjLbvrUqR0 0gpjfji+0PmlM8Aq4jxno2kwluf9GDpqkBdvEETVWrCeS7JIzv5lvDhP0masqwTcT/5WPhHvRUt diPgyW0y+XBtFwk28kCzNaHpnGul/87lBLDNeZ0Dz/4we+C5Gm0yhmbshkSyI/d87Byy79a9u0L sYladn+CJ/CSEK7FbfU9i9E0c8ZlxS88JAvkIv6dqjqnInJKO6V/JpqraWXxbAxNUUWugLzPwd1 aqQ4vF4Q7/PFlQKBjrN6SODFl0vn8I1U6WONp6hN/cUWomxVWcDFQRmmZhJq00keBPn56+4njCU XDwJV69StGSpiGehQRyrhH3Depn26kvgk+niIFwVpXtdW13BfQze4TsqBeQ2JMQxtAqBzj7am5D g== X-Received: by 2002:a05:600c:474d:b0:475:dde5:d91b with SMTP id 5b1f17b1804b1-48069c54142mr11853615e9.17.1769501582760; Tue, 27 Jan 2026 00:13:02 -0800 (PST) Received: from jopdorp-linux.tendawifi.com (238-180-99-95.ftth.glasoperator.nl. [95.99.180.238]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-48066beeaf9sm47253445e9.6.2026.01.27.00.13.01 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 27 Jan 2026 00:13:02 -0800 (PST) From: Jegor van Opdorp To: linux-sound@vger.kernel.org Cc: tiwai@suse.com, perex@perex.cz, linux-kernel@vger.kernel.org, Jegor van Opdorp Subject: [PATCH] ALSA: usb-audio: add mixer support for Focusrite Forte Date: Tue, 27 Jan 2026 09:12:58 +0100 Message-ID: <20260127081258.216097-1-jegorvanopdorp@gmail.com> X-Mailer: git-send-email 2.43.0 Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Add mixer control support for the Focusrite Forte (USB ID 0x1235:0x8010), an older USB audio interface that predates the Scarlett 2nd generation. The Forte uses UAC2_CS_MEM (bRequest=3D0x03) for its input controls rather than the standard UAC2_CS_CUR (0x01) used by Scarlett devices. This patch adds Forte-specific control handlers that use the correct USB protocol. Features implemented: - Input source selection (Mic/Line/Inst) for both channels - High pass filter switch - 48V phantom power switch - Phase invert switch - Pad switch - Preamp gain control (0-42 range, ~0-75dB) - Matrix mixer controls (6 inputs x 4 outputs) - Output volume and mute controls The device is registered via mixer_quirks.c and uses the existing mixer_scarlett.c infrastructure with Forte-specific additions. Credit: This work builds on prior reverse-engineering by alastair-dm. Link: https://github.com/alastair-dm/forte-mixer/wiki Link: https://github.com/alastair-dm/forte-mixer Link: https://github.com/jopdorp/forte-mixer Signed-off-by: Jegor van Opdorp --- sound/usb/mixer_quirks.c | 3 + sound/usb/mixer_scarlett.c | 476 +++++++++++++++++++++++++++++++++++-- sound/usb/mixer_scarlett.h | 1 + 3 files changed, 461 insertions(+), 19 deletions(-) diff --git a/sound/usb/mixer_quirks.c b/sound/usb/mixer_quirks.c index ed6127b0389f..c12257ad34dc 100644 --- a/sound/usb/mixer_quirks.c +++ b/sound/usb/mixer_quirks.c @@ -4060,6 +4060,9 @@ int snd_usb_mixer_apply_create_quirk(struct usb_mixer= _interface *mixer) err =3D snd_create_std_mono_table(mixer, ebox44_table); break; =20 + case USB_ID(0x1235, 0x8010): /* Focusrite Forte */ + err =3D snd_forte_controls_create(mixer); + 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 */ diff --git a/sound/usb/mixer_scarlett.c b/sound/usb/mixer_scarlett.c index ff548041679b..eef3942b5d9f 100644 --- a/sound/usb/mixer_scarlett.c +++ b/sound/usb/mixer_scarlett.c @@ -135,7 +135,7 @@ /* some gui mixers can't handle negative ctl values */ #define SND_SCARLETT_LEVEL_BIAS 128 #define SND_SCARLETT_MATRIX_IN_MAX 18 -#define SND_SCARLETT_CONTROLS_MAX 10 +#define SND_SCARLETT_CONTROLS_MAX 14 #define SND_SCARLETT_OFFSETS_MAX 5 =20 enum { @@ -143,6 +143,12 @@ enum { SCARLETT_SWITCH_IMPEDANCE, SCARLETT_SWITCH_PAD, SCARLETT_SWITCH_GAIN, + FORTE_INPUT_SOURCE, /* mic/line/instrument selection */ + FORTE_INPUT_HPF, /* high pass filter */ + FORTE_INPUT_PHANTOM, /* 48V phantom power */ + FORTE_INPUT_PHASE, /* phase invert */ + FORTE_INPUT_PAD, /* pad */ + FORTE_INPUT_GAIN, /* preamp gain 0-42 (~0-75dB for mic) */ }; =20 enum { @@ -172,6 +178,8 @@ struct scarlett_device_info { int input_len; int output_len; =20 + bool has_output_source_routing; + struct scarlett_mixer_elem_enum_info opt_master; struct scarlett_mixer_elem_enum_info opt_matrix; =20 @@ -229,6 +237,239 @@ static const struct scarlett_mixer_elem_enum_info opt= _sync =3D { } }; =20 +/* Forte-specific input control options */ +static const struct scarlett_mixer_elem_enum_info opt_forte_source =3D { + .start =3D 0, + .len =3D 3, + .offsets =3D {}, + .names =3D (char const * const []){ + "Mic", "Line", "Inst" + } +}; + +/* + * Forte-specific USB control functions + * Forte input controls use bRequest=3D0x03 (UAC2_CS_MEM) instead of 0x01 + * wValue =3D (control_code << 8) | channel + * wIndex =3D interface | (0x3c << 8) like Scarlett meter/matrix controls + */ +static int forte_set_ctl_value(struct usb_mixer_elem_info *elem, int value) +{ + struct snd_usb_audio *chip =3D elem->head.mixer->chip; + unsigned char buf[2]; + int wValue =3D elem->control; /* Just control code, NO shift, NO channel= */ + int idx =3D snd_usb_ctrl_intf(elem->head.mixer->hostif) | (elem->head.id = << 8); + int err; + + /* Wiki format: "2 bytes, chan 0,1 and value" + * Data order: [channel, value] - channel FIRST per wiki + */ + buf[0] =3D elem->idx_off; /* Channel: 0 or 1 */ + buf[1] =3D value & 0xff; /* Value: 0-2 for source, 0-1 for switches */ + + err =3D snd_usb_lock_shutdown(chip); + if (err < 0) + return -EIO; + + err =3D snd_usb_ctl_msg(chip->dev, + usb_sndctrlpipe(chip->dev, 0), + UAC2_CS_MEM, /* bRequest =3D 0x03 */ + USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_OUT, + wValue, idx, buf, 2); + + snd_usb_unlock_shutdown(chip); + + if (err < 0) { + usb_audio_err(chip, "forte_set FAILED: req=3D0x03 wVal=3D0x%04x wIdx=3D0= x%04x buf=3D[%02x,%02x] err=3D%d\n", + wValue, idx, buf[0], buf[1], err); + return err; + } + return 0; +} + +static int forte_get_ctl_value(struct usb_mixer_elem_info *elem, int *valu= e) +{ + /* Device may not support reading input controls. + * Return cached value or default to avoid blocking module load. + */ + if (elem->cached) + *value =3D elem->cache_val[0]; + else + *value =3D 0; /* Default: first option */ + + return 0; +} + +/* + * Forte Input Gain control functions + * Gain range is 0-42 (0x00-0x2a) which maps to approximately: + * - Mic: 0 to +75dB (~1.8dB per step) + * - Instrument: +14 to +68dB + * - Line: -12 to +42dB + * We use a TLV scale of 0 to 7500 centidB (0 to 75dB) in ~179 cB steps + */ +#define FORTE_INPUT_GAIN_MAX 42 + +static int forte_input_gain_info(struct snd_kcontrol *kctl, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type =3D SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count =3D 1; + uinfo->value.integer.min =3D 0; + uinfo->value.integer.max =3D FORTE_INPUT_GAIN_MAX; + uinfo->value.integer.step =3D 1; + return 0; +} + +static int forte_input_gain_get(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem =3D kctl->private_data; + int err, val; + + /* Use Forte-specific USB command with UAC2_CS_MEM */ + err =3D forte_get_ctl_value(elem, &val); + if (err < 0) + return err; + + ucontrol->value.integer.value[0] =3D clamp(val, 0, FORTE_INPUT_GAIN_MAX); + return 0; +} + +static int forte_input_gain_put(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem =3D kctl->private_data; + int err, oval, val; + + /* Read current value */ + err =3D forte_get_ctl_value(elem, &oval); + if (err < 0) + return err; + + val =3D clamp((int)ucontrol->value.integer.value[0], 0, FORTE_INPUT_GAIN_= MAX); + if (oval !=3D val) { + /* Use Forte-specific USB command */ + err =3D forte_set_ctl_value(elem, val); + if (err < 0) + return err; + elem->cached |=3D 1; + elem->cache_val[0] =3D val; + return 1; + } + return 0; +} + +static int forte_input_gain_resume(struct usb_mixer_elem_list *list) +{ + struct usb_mixer_elem_info *elem =3D mixer_elem_list_to_info(list); + + if (elem->cached) + forte_set_ctl_value(elem, *elem->cache_val); + return 0; +} + +/* + * Forte-specific enum control functions (for Source selection) + * Uses bRequest=3D0x03 (UAC2_CS_MEM) instead of standard 0x01 + */ +static int forte_ctl_enum_get(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem =3D kctl->private_data; + struct scarlett_mixer_elem_enum_info *opt =3D elem->private_data; + int err, val; + + err =3D forte_get_ctl_value(elem, &val); + if (err < 0) + return err; + + val =3D clamp(val - opt->start, 0, opt->len - 1); + ucontrol->value.enumerated.item[0] =3D val; + return 0; +} + +static int forte_ctl_enum_put(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem =3D kctl->private_data; + struct scarlett_mixer_elem_enum_info *opt =3D elem->private_data; + int err, oval, val; + + err =3D forte_get_ctl_value(elem, &oval); + if (err < 0) + return err; + + val =3D ucontrol->value.integer.value[0] + opt->start; + if (val !=3D oval) { + err =3D forte_set_ctl_value(elem, val); + if (err < 0) + return err; + elem->cached |=3D 1; + elem->cache_val[0] =3D val; + return 1; + } + return 0; +} + +static int forte_ctl_enum_resume(struct usb_mixer_elem_list *list) +{ + struct usb_mixer_elem_info *elem =3D mixer_elem_list_to_info(list); + + if (elem->cached) + forte_set_ctl_value(elem, *elem->cache_val); + return 0; +} + +/* + * Forte-specific switch control functions (for HPF, 48V, Phase, Pad) + * Uses bRequest=3D0x03 (UAC2_CS_MEM) instead of standard 0x01 + */ +static int forte_ctl_switch_get(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem =3D kctl->private_data; + int err, val; + + err =3D forte_get_ctl_value(elem, &val); + if (err < 0) + return err; + + ucontrol->value.integer.value[0] =3D val ? 1 : 0; + return 0; +} + +static int forte_ctl_switch_put(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem =3D kctl->private_data; + int err, oval, val; + + err =3D forte_get_ctl_value(elem, &oval); + if (err < 0) + return err; + + val =3D ucontrol->value.integer.value[0] ? 1 : 0; + if (val !=3D oval) { + err =3D forte_set_ctl_value(elem, val); + if (err < 0) + return err; + elem->cached |=3D 1; + elem->cache_val[0] =3D val; + return 1; + } + return 0; +} + +static int forte_ctl_switch_resume(struct usb_mixer_elem_list *list) +{ + struct usb_mixer_elem_info *elem =3D mixer_elem_list_to_info(list); + + if (elem->cached) + forte_set_ctl_value(elem, *elem->cache_val); + return 0; +} + static int scarlett_ctl_switch_info(struct snd_kcontrol *kctl, struct snd_ctl_elem_info *uinfo) { @@ -533,6 +774,37 @@ static const struct snd_kcontrol_new usb_scarlett_ctl_= sync =3D { .get =3D scarlett_ctl_meter_get, }; =20 +/* Forte-specific control structures - use bRequest=3D0x03 instead of 0x01= */ +static const struct snd_kcontrol_new usb_forte_ctl_enum =3D { + .iface =3D SNDRV_CTL_ELEM_IFACE_MIXER, + .name =3D "", + .info =3D scarlett_ctl_enum_info, + .get =3D forte_ctl_enum_get, + .put =3D forte_ctl_enum_put, +}; + +static const struct snd_kcontrol_new usb_forte_ctl_switch =3D { + .iface =3D SNDRV_CTL_ELEM_IFACE_MIXER, + .name =3D "", + .info =3D snd_ctl_boolean_mono_info, + .get =3D forte_ctl_switch_get, + .put =3D forte_ctl_switch_put, +}; + +/* Forte input gain: 0-42 maps to approximately 0-75dB (~179 cB per step) = */ +static const DECLARE_TLV_DB_SCALE(db_scale_forte_input_gain, 0, 179, 0); + +static const struct snd_kcontrol_new usb_forte_ctl_input_gain =3D { + .iface =3D SNDRV_CTL_ELEM_IFACE_MIXER, + .access =3D SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_TLV_READ, + .name =3D "", + .info =3D forte_input_gain_info, + .get =3D forte_input_gain_get, + .put =3D forte_input_gain_put, + .tlv =3D { .p =3D db_scale_forte_input_gain } +}; + static int add_new_ctl(struct usb_mixer_interface *mixer, const struct snd_kcontrol_new *ncontrol, usb_mixer_elem_resume_func_t resume, @@ -607,37 +879,83 @@ static int add_output_ctls(struct usb_mixer_interface= *mixer, if (err < 0) return err; =20 - /* Add L channel source playback enumeration */ - snprintf(mx, sizeof(mx), "Master %dL (%s) Source Playback Enum", - index + 1, name); - err =3D add_new_ctl(mixer, &usb_scarlett_ctl_dynamic_enum, - scarlett_ctl_enum_resume, 0x33, 0x00, - 2*index, USB_MIXER_S16, 1, mx, &info->opt_master, - &elem); - if (err < 0) - return err; + /* Add L/R source routing if device supports it */ + if (info->has_output_source_routing) { + /* Add L channel source playback enumeration */ + snprintf(mx, sizeof(mx), "Master %dL (%s) Source Playback Enum", + index + 1, name); + err =3D add_new_ctl(mixer, &usb_scarlett_ctl_dynamic_enum, + scarlett_ctl_enum_resume, 0x33, 0x00, + 2*index, USB_MIXER_S16, 1, mx, &info->opt_master, + &elem); + if (err < 0) + return err; =20 - /* Add R channel source playback enumeration */ - snprintf(mx, sizeof(mx), "Master %dR (%s) Source Playback Enum", - index + 1, name); - err =3D add_new_ctl(mixer, &usb_scarlett_ctl_dynamic_enum, - scarlett_ctl_enum_resume, 0x33, 0x00, - 2*index+1, USB_MIXER_S16, 1, mx, &info->opt_master, - &elem); - if (err < 0) - return err; + /* Add R channel source playback enumeration */ + snprintf(mx, sizeof(mx), "Master %dR (%s) Source Playback Enum", + index + 1, name); + err =3D add_new_ctl(mixer, &usb_scarlett_ctl_dynamic_enum, + scarlett_ctl_enum_resume, 0x33, 0x00, + 2*index+1, USB_MIXER_S16, 1, mx, &info->opt_master, + &elem); + if (err < 0) + return err; + } =20 return 0; } =20 /********************** device-specific config *************************/ =20 +static const struct scarlett_device_info forte_info =3D { + .matrix_in =3D 6, + .matrix_out =3D 4, + .input_len =3D 2, + .output_len =3D 4, + .has_output_source_routing =3D false, + + .opt_master =3D { + .start =3D -1, + .len =3D 13, + .offsets =3D {0, 4, 6, 6, 6}, + .names =3D NULL + }, + + .opt_matrix =3D { + .start =3D -1, + .len =3D 7, + .offsets =3D {0, 4, 6, 6, 6}, + .names =3D NULL + }, + + .num_controls =3D 14, + .controls =3D { + { .num =3D 0, .type =3D SCARLETT_OUTPUTS, .name =3D "Line Out" }, + { .num =3D 1, .type =3D SCARLETT_OUTPUTS, .name =3D "Headphone" }, + /* Input 1 controls */ + { .num =3D 1, .type =3D FORTE_INPUT_GAIN, .name =3D NULL}, + { .num =3D 1, .type =3D FORTE_INPUT_SOURCE, .name =3D NULL}, + { .num =3D 1, .type =3D FORTE_INPUT_HPF, .name =3D NULL}, + { .num =3D 1, .type =3D FORTE_INPUT_PHANTOM, .name =3D NULL}, + { .num =3D 1, .type =3D FORTE_INPUT_PHASE, .name =3D NULL}, + { .num =3D 1, .type =3D FORTE_INPUT_PAD, .name =3D NULL}, + /* Input 2 controls */ + { .num =3D 2, .type =3D FORTE_INPUT_GAIN, .name =3D NULL}, + { .num =3D 2, .type =3D FORTE_INPUT_SOURCE, .name =3D NULL}, + { .num =3D 2, .type =3D FORTE_INPUT_HPF, .name =3D NULL}, + { .num =3D 2, .type =3D FORTE_INPUT_PHANTOM, .name =3D NULL}, + { .num =3D 2, .type =3D FORTE_INPUT_PHASE, .name =3D NULL}, + { .num =3D 2, .type =3D FORTE_INPUT_PAD, .name =3D NULL}, + }, +}; + /* untested... */ static const struct scarlett_device_info s6i6_info =3D { .matrix_in =3D 18, .matrix_out =3D 8, .input_len =3D 6, .output_len =3D 6, + .has_output_source_routing =3D true, =20 .opt_master =3D { .start =3D -1, @@ -680,6 +998,7 @@ static const struct scarlett_device_info s8i6_info =3D { .matrix_out =3D 6, .input_len =3D 8, .output_len =3D 6, + .has_output_source_routing =3D true, =20 .opt_master =3D { .start =3D -1, @@ -719,6 +1038,7 @@ static const struct scarlett_device_info s18i6_info = =3D { .matrix_out =3D 6, .input_len =3D 18, .output_len =3D 6, + .has_output_source_routing =3D true, =20 .opt_master =3D { .start =3D -1, @@ -756,6 +1076,7 @@ static const struct scarlett_device_info s18i8_info = =3D { .matrix_out =3D 8, .input_len =3D 18, .output_len =3D 8, + .has_output_source_routing =3D true, =20 .opt_master =3D { .start =3D -1, @@ -798,6 +1119,7 @@ static const struct scarlett_device_info s18i20_info = =3D { .matrix_out =3D 8, .input_len =3D 18, .output_len =3D 20, + .has_output_source_routing =3D true, =20 .opt_master =3D { .start =3D -1, @@ -902,6 +1224,61 @@ static int scarlett_controls_create_generic(struct us= b_mixer_interface *mixer, if (err < 0) return err; break; + /* Forte input controls - use wIndex 0x3c per wiki docs */ + case FORTE_INPUT_GAIN: + sprintf(mx, "Line In %d Gain Capture Volume", ctl->num); + err =3D add_new_ctl(mixer, &usb_forte_ctl_input_gain, + forte_input_gain_resume, 0x3c, + 0x06, ctl->num - 1, USB_MIXER_S16, 1, mx, + NULL, &elem); + if (err < 0) + return err; + break; + case FORTE_INPUT_SOURCE: + sprintf(mx, "Input %d Source", ctl->num); + err =3D add_new_ctl(mixer, &usb_forte_ctl_enum, + forte_ctl_enum_resume, 0x3c, + 0x07, ctl->num - 1, USB_MIXER_S16, 1, mx, + &opt_forte_source, &elem); + if (err < 0) + return err; + break; + case FORTE_INPUT_HPF: + sprintf(mx, "Input %d High Pass Filter Switch", ctl->num); + err =3D add_new_ctl(mixer, &usb_forte_ctl_switch, + forte_ctl_switch_resume, 0x3c, + 0x08, ctl->num - 1, USB_MIXER_S16, 1, mx, + NULL, &elem); + if (err < 0) + return err; + break; + case FORTE_INPUT_PHANTOM: + sprintf(mx, "Input %d 48V Phantom Power Switch", ctl->num); + err =3D add_new_ctl(mixer, &usb_forte_ctl_switch, + forte_ctl_switch_resume, 0x3c, + 0x09, ctl->num - 1, USB_MIXER_S16, 1, mx, + NULL, &elem); + if (err < 0) + return err; + break; + case FORTE_INPUT_PHASE: + sprintf(mx, "Input %d Phase Invert Switch", ctl->num); + err =3D add_new_ctl(mixer, &usb_forte_ctl_switch, + forte_ctl_switch_resume, 0x3c, + 0x0a, ctl->num - 1, USB_MIXER_S16, 1, mx, + NULL, &elem); + if (err < 0) + return err; + break; + case FORTE_INPUT_PAD: + sprintf(mx, "Input %d Pad Switch", ctl->num); + err =3D add_new_ctl(mixer, &usb_forte_ctl_switch, + forte_ctl_switch_resume, 0x3c, + 0x0b, ctl->num - 1, USB_MIXER_S16, 1, mx, + NULL, &elem); + if (err < 0) + return err; + break; } } =20 @@ -1009,3 +1386,64 @@ int snd_scarlett_controls_create(struct usb_mixer_in= terface *mixer) =20 return err; } + +/* + * Create and initialize a mixer for the Focusrite(R) Forte + */ +int snd_forte_controls_create(struct usb_mixer_interface *mixer) +{ + int err, i, o; + char mx[SNDRV_CTL_ELEM_ID_NAME_MAXLEN]; + const struct scarlett_device_info *info; + struct usb_mixer_elem_info *elem; + + /* only use UAC_VERSION_2 */ + if (!mixer->protocol) + return 0; + + switch (mixer->chip->usb_id) { + case USB_ID(0x1235, 0x8010): + info =3D &forte_info; + break; + default: /* device not (yet) supported */ + return -EINVAL; + } + + /* generic function to create controls */ + err =3D scarlett_controls_create_generic(mixer, info); + if (err < 0) + return err; + + /* setup matrix controls */ + for (i =3D 0; i < info->matrix_in; i++) { + snprintf(mx, sizeof(mx), "Matrix %02d Input Playback Route", + i+1); + err =3D add_new_ctl(mixer, &usb_scarlett_ctl_dynamic_enum, + scarlett_ctl_enum_resume, 0x32, + 0x06, i, USB_MIXER_S16, 1, mx, + &info->opt_matrix, &elem); + if (err < 0) + return err; + + for (o =3D 0; o < info->matrix_out; o++) { + sprintf(mx, "Matrix %02d Mix %c Playback Volume", i+1, + o+'A'); + err =3D add_new_ctl(mixer, &usb_scarlett_ctl, + scarlett_ctl_resume, 0x3c, 0x01, + (i << 2) + (o & 0x03), USB_MIXER_S16, + 1, mx, NULL, &elem); + if (err < 0) + return err; + + } + } + + /* val_len =3D=3D 1 and UAC2_CS_MEM */ + err =3D add_new_ctl(mixer, &usb_scarlett_ctl_sync, NULL, 0x3c, 0x00, 2, + USB_MIXER_U8, 1, "Sample Clock Sync Status", + &opt_sync, &elem); + if (err < 0) + return err; + + return err; +} diff --git a/sound/usb/mixer_scarlett.h b/sound/usb/mixer_scarlett.h index bbf063b79370..c44fe7d72ee3 100644 --- a/sound/usb/mixer_scarlett.h +++ b/sound/usb/mixer_scarlett.h @@ -3,5 +3,6 @@ #define __USB_MIXER_SCARLETT_H =20 int snd_scarlett_controls_create(struct usb_mixer_interface *mixer); +int snd_forte_controls_create(struct usb_mixer_interface *mixer); =20 #endif /* __USB_MIXER_SCARLETT_H */ --=20 2.43.0