From nobody Sat Feb 7 08:07:19 2026 Received: from mail-wr1-f54.google.com (mail-wr1-f54.google.com [209.85.221.54]) (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 E352A2853F7 for ; Tue, 27 Jan 2026 08:09:14 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.221.54 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1769501358; cv=none; b=a+6xuc+nBrkNdMalMO48XXCCJCSwoF5QNgWZlOvOy0/kHGIWwSvDuBGsLayW9EvSRrS8MOjlEbpv+DcPnFmYeOEH6OwEiiLuN0pdPvuEHFgfh0TW1d13phQVYvtQ3IJoyn6X34HCf+dvDQZlNY4ppN34tT7khJ1wGgBrHnilxzg= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1769501358; c=relaxed/simple; bh=md6Hp/P/U+pLRSWunUDF9PTF1O3oNMGZg5cDOP7A11Q=; h=From:To:Cc:Subject:Date:Message-ID:MIME-Version; b=J86+fLKEw1ouJofqK6Ygx+zOhcX0GrC0rN6q8jjKIhZnnrIjVl/3RJcYsZvQtRPXj9u8ygXxkOcIeYc9cxfoZvzqp1pgPWf62ihX87YGHWDjFU6MIFO8Qa85gEG0LF0NKcXcP4YaxjYLoS0vKaHXpzFaMHxNpi98UCsUk7OjJ4g= 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=K1SEgH0g; arc=none smtp.client-ip=209.85.221.54 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="K1SEgH0g" Received: by mail-wr1-f54.google.com with SMTP id ffacd0b85a97d-432755545fcso3967523f8f.1 for ; Tue, 27 Jan 2026 00:09:14 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1769501353; x=1770106153; 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=8tL+NmFC3uxk3F8MXfdMah5IPiA4oj5opzuBpeVlxj4=; b=K1SEgH0gjGf/efnH5shAcNFiieyR86lcsQXGy2TObpgXbIPQ9XQ1i9sCkzsBlcpjPW avu6uiHIWt0cK1UbBjsX8sp+O5AprISIK9TEaMZOkkI/PaGffMareTTzfFybMg/nUhcQ UuUDFV5tJ32tcXN3eiIIe+WuhxRfSDoce7FNf3Nx0oX5s3gvUNp/HBgz/mVIKAjE1T1W MRK4MQPc6XYwVIYrG/Cd+TuTf0tMfNK8AEvcwAwufaABYpknHSfl5D3VwlptuPOE6eXl 4TiyKqlkHAGjMW8pgjMJcgw1bBYzK2rzLjCr4FNeV+01uPdJoc35q2GwL52mFOTHTGuA TUsw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1769501353; x=1770106153; 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=8tL+NmFC3uxk3F8MXfdMah5IPiA4oj5opzuBpeVlxj4=; b=ihRiMcRWlFrTbgjny4Qnf5TMFH0jrBp5uliEjmHVaBRp6Ap359xj8iDfKOp/5FKPnW YHMGprONcjIv9oddkrgQPqUQnhcaZvcV5zBiV+kT1o7KW/c0AV4IZl7ZvFCPbCw5Q8pe 7In5KhA9cYCRpgCjkDsFHh7RYhzSsHD5o2WJi/6WVUm91u3pH0Kluy7WjU/L+CH/EK1/ 0GfAX8c9TERYDfV4Tv/cCvBSDE7K2iUcdXBmKZkueye7Dn1pMQJvUeHgay12BkoYSRdY dpNhNd/PgwAI50ClJKajmbmHTUI4LGX4kysaT0+lWBeIEbvLxYCP+pFjJ/CpcRP6V5mx SYGQ== X-Forwarded-Encrypted: i=1; AJvYcCVPu0h5nwikK7Sygz3+nIVVHguoPyiGRCRjewUZ9+eiSBgRs+8F6+rs7xEbVJkU68gu+eXkCtUfR2djp/0=@vger.kernel.org X-Gm-Message-State: AOJu0YymnzDf9xaZyViXCXrl5sciiXtwbdvfuY1W80ATLsIZ6UVj4iau RoJyrqHAf7C/v9+ydukwU5ixNfT9RyzGeeuo8XeFuK+dO4zoKQMxp0W0 X-Gm-Gg: AZuq6aK+jrZvNtbSL9sGlFHXROmcaLEhIogL3sNfhFEoBYp8un3wXiCJJWum3Ahcl33 ib3MaHl5z9VmjzKjS6m/OPz238RRB1g7d8alNj7iQ+XEIPDIuV7drlaJRpAJQQiQEYRGPRKRlJo paN8L0aiKXbJqF+LJUKexYYaOMDos8FHyDN0Y91KjVq/+9wRjQeYbBqa13p6p5WUx+E2Mh5TdD4 fBTiUOqcVz2EvIG/3n4tuMcaz/f223cP3bPYkeUBYiesLHJMnQ/0p9dFD5XQ5JMYNFwqbiV2mhN 90XKblyOp0WwNTTjLEqed9HDZMqS+RYzWOI25pZrCHypSiNsDCbWQv5/Vs2QNai5faj/6zsH5rz rlMSouClqjihU6KiJ3rsDmOTWpQKOJ4lGp2vWcuuJFaZ8dI4VnW8BUMkxr8zEiQaQGNuM0ITVYh oKmChCC2/pD/pPK8mvJPkHiUz7snpqHSVjM58m2xM3T1JdBiXAMNZiEUNoyqlMqFZ1DCROh4o96 Oi4ayoqCGG0 X-Received: by 2002:a05:6000:4023:b0:431:1ae:a3be with SMTP id ffacd0b85a97d-435dd02d9bcmr976504f8f.3.1769501352794; Tue, 27 Jan 2026 00:09:12 -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 ffacd0b85a97d-435b1b6e2besm36177612f8f.0.2026.01.27.00.09.12 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 27 Jan 2026 00:09:12 -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:08:50 +0100 Message-ID: <20260127080850.211208-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/wiki/wiki/Focusrite-Forte Link: https://github.com/alastair-dm/Linux-Forte/wiki 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