* [PATCH 1/3] ALSA: scarlett2: Allow selecting config_set by firmware version
2026-04-25 21:07 [PATCH 0/3] ALSA: scarlett2: Fix 2i2 Gen 4 direct monitor on firmware 2417 Geoffrey D. Bennett
@ 2026-04-25 21:16 ` Geoffrey D. Bennett
2026-04-25 21:16 ` [PATCH 2/3] ALSA: scarlett2: Fold min_firmware_version into config_sets Geoffrey D. Bennett
` (2 subsequent siblings)
3 siblings, 0 replies; 5+ messages in thread
From: Geoffrey D. Bennett @ 2026-04-25 21:16 UTC (permalink / raw)
To: Takashi Iwai; +Cc: Takashi Iwai, linux-sound
The Scarlett 2i2 Gen 4 firmware 2417 moved the direct monitor gain
parameters, so we now need to allow each device to list multiple
scarlett2_config_set entries, one per applicable firmware version
range, and pick the matching one at probe time.
No functional change yet: each device gets a single config_sets
entry whose from_firmware_version matches the existing
min_firmware_version (0 where none was set). This both prepares for
selection and lets a follow-up commit remove the now-redundant
min_firmware_version field.
scarlett2_count_io() depends on the resolved config_set so it moves
out of scarlett2_init_private() into snd_scarlett2_controls_create()
after the firmware version has been read.
Signed-off-by: Geoffrey D. Bennett <g@b4.vu>
---
sound/usb/mixer_scarlett2.c | 143 ++++++++++++++++++++++++++++++------
1 file changed, 121 insertions(+), 22 deletions(-)
diff --git a/sound/usb/mixer_scarlett2.c b/sound/usb/mixer_scarlett2.c
index 7b31504c5f24..15abca6d34ef 100644
--- a/sound/usb/mixer_scarlett2.c
+++ b/sound/usb/mixer_scarlett2.c
@@ -612,6 +612,20 @@ struct scarlett2_config_set {
const struct scarlett2_config items[SCARLETT2_CONFIG_COUNT];
};
+/* Map firmware versions to config sets per-device.
+ *
+ * Each device lists one or more entries, sorted in ascending order of
+ * from_firmware_version. At probe time the running firmware version
+ * is looked up against this list and the last entry whose
+ * from_firmware_version is <= the running version is selected.
+ *
+ * The list is terminated by a sentinel entry with config_set == NULL.
+ */
+struct scarlett2_config_set_entry {
+ u16 from_firmware_version;
+ const struct scarlett2_config_set *config_set;
+};
+
/* Input gain TLV dB ranges */
static const DECLARE_TLV_DB_MINMAX(
@@ -1100,8 +1114,8 @@ struct scarlett2_meter_entry {
};
struct scarlett2_device_info {
- /* which set of configuration parameters the device uses */
- const struct scarlett2_config_set *config_set;
+ /* which sets of configuration parameters the device uses */
+ const struct scarlett2_config_set_entry *config_sets;
/* minimum firmware version required */
u16 min_firmware_version;
@@ -1343,7 +1357,10 @@ struct scarlett2_data {
/*** Model-specific data ***/
static const struct scarlett2_device_info s6i6_gen2_info = {
- .config_set = &scarlett2_config_set_gen2a,
+ .config_sets = (const struct scarlett2_config_set_entry[]) {
+ { 0, &scarlett2_config_set_gen2a },
+ { }
+ },
.level_input_count = 2,
.pad_input_count = 2,
@@ -1393,7 +1410,10 @@ static const struct scarlett2_device_info s6i6_gen2_info = {
};
static const struct scarlett2_device_info s18i8_gen2_info = {
- .config_set = &scarlett2_config_set_gen2a,
+ .config_sets = (const struct scarlett2_config_set_entry[]) {
+ { 0, &scarlett2_config_set_gen2a },
+ { }
+ },
.level_input_count = 2,
.pad_input_count = 4,
@@ -1446,7 +1466,10 @@ static const struct scarlett2_device_info s18i8_gen2_info = {
};
static const struct scarlett2_device_info s18i20_gen2_info = {
- .config_set = &scarlett2_config_set_gen2b,
+ .config_sets = (const struct scarlett2_config_set_entry[]) {
+ { 0, &scarlett2_config_set_gen2b },
+ { }
+ },
.line_out_descrs = {
"Monitor L",
@@ -1503,7 +1526,10 @@ static const struct scarlett2_device_info s18i20_gen2_info = {
};
static const struct scarlett2_device_info solo_gen3_info = {
- .config_set = &scarlett2_config_set_gen3a,
+ .config_sets = (const struct scarlett2_config_set_entry[]) {
+ { 0, &scarlett2_config_set_gen3a },
+ { }
+ },
.level_input_count = 1,
.level_input_first = 1,
.air_input_count = 1,
@@ -1513,7 +1539,10 @@ static const struct scarlett2_device_info solo_gen3_info = {
};
static const struct scarlett2_device_info s2i2_gen3_info = {
- .config_set = &scarlett2_config_set_gen3a,
+ .config_sets = (const struct scarlett2_config_set_entry[]) {
+ { 0, &scarlett2_config_set_gen3a },
+ { }
+ },
.level_input_count = 2,
.air_input_count = 2,
.phantom_count = 1,
@@ -1522,7 +1551,10 @@ static const struct scarlett2_device_info s2i2_gen3_info = {
};
static const struct scarlett2_device_info s4i4_gen3_info = {
- .config_set = &scarlett2_config_set_gen3b,
+ .config_sets = (const struct scarlett2_config_set_entry[]) {
+ { 0, &scarlett2_config_set_gen3b },
+ { }
+ },
.level_input_count = 2,
.pad_input_count = 2,
.air_input_count = 2,
@@ -1571,7 +1603,10 @@ static const struct scarlett2_device_info s4i4_gen3_info = {
};
static const struct scarlett2_device_info s8i6_gen3_info = {
- .config_set = &scarlett2_config_set_gen3b,
+ .config_sets = (const struct scarlett2_config_set_entry[]) {
+ { 0, &scarlett2_config_set_gen3b },
+ { }
+ },
.level_input_count = 2,
.pad_input_count = 2,
.air_input_count = 2,
@@ -1637,7 +1672,10 @@ static const char * const scarlett2_spdif_s18i8_gen3_texts[] = {
};
static const struct scarlett2_device_info s18i8_gen3_info = {
- .config_set = &scarlett2_config_set_gen3c,
+ .config_sets = (const struct scarlett2_config_set_entry[]) {
+ { 0, &scarlett2_config_set_gen3c },
+ { }
+ },
.has_speaker_switching = 1,
.level_input_count = 2,
.pad_input_count = 4,
@@ -1729,7 +1767,10 @@ static const char * const scarlett2_spdif_s18i20_gen3_texts[] = {
};
static const struct scarlett2_device_info s18i20_gen3_info = {
- .config_set = &scarlett2_config_set_gen3c,
+ .config_sets = (const struct scarlett2_config_set_entry[]) {
+ { 0, &scarlett2_config_set_gen3c },
+ { }
+ },
.has_speaker_switching = 1,
.has_talkback = 1,
.level_input_count = 2,
@@ -1803,7 +1844,10 @@ static const struct scarlett2_device_info s18i20_gen3_info = {
};
static const struct scarlett2_device_info vocaster_one_info = {
- .config_set = &scarlett2_config_set_vocaster,
+ .config_sets = (const struct scarlett2_config_set_entry[]) {
+ { 1769, &scarlett2_config_set_vocaster },
+ { }
+ },
.min_firmware_version = 1769,
.has_devmap = 1,
@@ -1847,7 +1891,10 @@ static const struct scarlett2_device_info vocaster_one_info = {
};
static const struct scarlett2_device_info vocaster_two_info = {
- .config_set = &scarlett2_config_set_vocaster,
+ .config_sets = (const struct scarlett2_config_set_entry[]) {
+ { 1769, &scarlett2_config_set_vocaster },
+ { }
+ },
.min_firmware_version = 1769,
.has_devmap = 1,
@@ -1892,7 +1939,10 @@ static const struct scarlett2_device_info vocaster_two_info = {
};
static const struct scarlett2_device_info solo_gen4_info = {
- .config_set = &scarlett2_config_set_gen4_solo,
+ .config_sets = (const struct scarlett2_config_set_entry[]) {
+ { 2115, &scarlett2_config_set_gen4_solo },
+ { }
+ },
.min_firmware_version = 2115,
.has_devmap = 1,
@@ -1947,7 +1997,10 @@ static const struct scarlett2_device_info solo_gen4_info = {
};
static const struct scarlett2_device_info s2i2_gen4_info = {
- .config_set = &scarlett2_config_set_gen4_2i2,
+ .config_sets = (const struct scarlett2_config_set_entry[]) {
+ { 2115, &scarlett2_config_set_gen4_2i2 },
+ { }
+ },
.min_firmware_version = 2115,
.has_devmap = 1,
@@ -2002,7 +2055,10 @@ static const struct scarlett2_device_info s2i2_gen4_info = {
};
static const struct scarlett2_device_info s4i4_gen4_info = {
- .config_set = &scarlett2_config_set_gen4_4i4,
+ .config_sets = (const struct scarlett2_config_set_entry[]) {
+ { 2089, &scarlett2_config_set_gen4_4i4 },
+ { }
+ },
.min_firmware_version = 2089,
.has_devmap = 1,
@@ -2051,7 +2107,10 @@ static const struct scarlett2_device_info s4i4_gen4_info = {
};
static const struct scarlett2_device_info clarett_2pre_info = {
- .config_set = &scarlett2_config_set_clarett,
+ .config_sets = (const struct scarlett2_config_set_entry[]) {
+ { 0, &scarlett2_config_set_clarett },
+ { }
+ },
.level_input_count = 2,
.air_input_count = 2,
@@ -2107,7 +2166,10 @@ static const char * const scarlett2_spdif_clarett_texts[] = {
};
static const struct scarlett2_device_info clarett_4pre_info = {
- .config_set = &scarlett2_config_set_clarett,
+ .config_sets = (const struct scarlett2_config_set_entry[]) {
+ { 0, &scarlett2_config_set_clarett },
+ { }
+ },
.level_input_count = 2,
.air_input_count = 4,
@@ -2163,7 +2225,10 @@ static const struct scarlett2_device_info clarett_4pre_info = {
};
static const struct scarlett2_device_info clarett_8pre_info = {
- .config_set = &scarlett2_config_set_clarett,
+ .config_sets = (const struct scarlett2_config_set_entry[]) {
+ { 0, &scarlett2_config_set_clarett },
+ { }
+ },
.level_input_count = 2,
.air_input_count = 8,
@@ -8184,10 +8249,32 @@ static void scarlett2_private_suspend(struct usb_mixer_interface *mixer)
/*** Initialisation ***/
+/* Select the config_set matching the running firmware version.
+ *
+ * The device info's config_sets array is ordered by ascending
+ * from_firmware_version; pick the last entry whose version is <= the
+ * running firmware version. If the running firmware is older than the
+ * first entry's from_firmware_version (i.e. older than the driver's
+ * minimum supported version for this device), the first entry's
+ * config_set is selected anyway so firmware updates can still be done
+ * (requires only the ACK handler), but the usual mixer controls
+ * aren't created.
+ */
+static void scarlett2_resolve_config_set(struct scarlett2_data *private)
+{
+ const struct scarlett2_config_set_entry *entry =
+ private->info->config_sets;
+
+ private->config_set = entry->config_set;
+ for (entry++; entry->config_set; entry++)
+ if (entry->from_firmware_version <= private->firmware_version)
+ private->config_set = entry->config_set;
+}
+
static void scarlett2_count_io(struct scarlett2_data *private)
{
const struct scarlett2_device_info *info = private->info;
- const struct scarlett2_config_set *config_set = info->config_set;
+ const struct scarlett2_config_set *config_set = private->config_set;
const int (*port_count)[SCARLETT2_PORT_DIRNS] = info->port_count;
int port_type, srcs = 0, dsts = 0, i;
@@ -8282,9 +8369,14 @@ static int scarlett2_init_private(struct usb_mixer_interface *mixer,
mixer->private_suspend = scarlett2_private_suspend;
private->info = entry->info;
- private->config_set = entry->info->config_set;
+
+ /* Set config_set to the first entry's config_set so the
+ * notify handler has a valid pointer while USB init runs; it
+ * is re-resolved once the firmware version has been read.
+ */
+ private->config_set = entry->info->config_sets[0].config_set;
+
private->series_name = entry->series_name;
- scarlett2_count_io(private);
private->scarlett2_seq = 0;
private->mixer = mixer;
@@ -8688,6 +8780,13 @@ static int snd_scarlett2_controls_create(
if (err < 0)
return err;
+ /* Now that the firmware version is known, pick the matching
+ * config_set
+ */
+ scarlett2_resolve_config_set(private);
+
+ scarlett2_count_io(private);
+
/* Get the upgrade & settings flash segment numbers */
err = scarlett2_get_flash_segment_nums(mixer);
if (err < 0)
--
2.53.0
^ permalink raw reply related [flat|nested] 5+ messages in thread* [PATCH 2/3] ALSA: scarlett2: Fold min_firmware_version into config_sets
2026-04-25 21:07 [PATCH 0/3] ALSA: scarlett2: Fix 2i2 Gen 4 direct monitor on firmware 2417 Geoffrey D. Bennett
2026-04-25 21:16 ` [PATCH 1/3] ALSA: scarlett2: Allow selecting config_set by firmware version Geoffrey D. Bennett
@ 2026-04-25 21:16 ` Geoffrey D. Bennett
2026-04-25 21:17 ` [PATCH 3/3] ALSA: scarlett2: Update offsets for 2i2 Gen 4 firmware 2417 Geoffrey D. Bennett
2026-04-27 12:20 ` [PATCH 0/3] ALSA: scarlett2: Fix 2i2 Gen 4 direct monitor on " Takashi Iwai
3 siblings, 0 replies; 5+ messages in thread
From: Geoffrey D. Bennett @ 2026-04-25 21:16 UTC (permalink / raw)
To: Takashi Iwai; +Cc: Takashi Iwai, linux-sound
The device info's min_firmware_version field encoded the oldest
firmware the driver supports on each device. With the config_sets
array in place, the first entry's from_firmware_version carries the
same meaning (it's the oldest firmware range any config_set applies
to). Merge the two by dropping min_firmware_version from
scarlett2_device_info and reading config_sets[0].from_firmware_version
at the three call sites that used it.
No functional change.
Signed-off-by: Geoffrey D. Bennett <g@b4.vu>
---
sound/usb/mixer_scarlett2.c | 21 +++++++++------------
1 file changed, 9 insertions(+), 12 deletions(-)
diff --git a/sound/usb/mixer_scarlett2.c b/sound/usb/mixer_scarlett2.c
index 15abca6d34ef..68adec4f21d9 100644
--- a/sound/usb/mixer_scarlett2.c
+++ b/sound/usb/mixer_scarlett2.c
@@ -1117,9 +1117,6 @@ struct scarlett2_device_info {
/* which sets of configuration parameters the device uses */
const struct scarlett2_config_set_entry *config_sets;
- /* minimum firmware version required */
- u16 min_firmware_version;
-
/* has a downloadable device map */
u8 has_devmap;
@@ -1848,7 +1845,6 @@ static const struct scarlett2_device_info vocaster_one_info = {
{ 1769, &scarlett2_config_set_vocaster },
{ }
},
- .min_firmware_version = 1769,
.has_devmap = 1,
.phantom_count = 1,
@@ -1895,7 +1891,6 @@ static const struct scarlett2_device_info vocaster_two_info = {
{ 1769, &scarlett2_config_set_vocaster },
{ }
},
- .min_firmware_version = 1769,
.has_devmap = 1,
.phantom_count = 2,
@@ -1943,7 +1938,6 @@ static const struct scarlett2_device_info solo_gen4_info = {
{ 2115, &scarlett2_config_set_gen4_solo },
{ }
},
- .min_firmware_version = 2115,
.has_devmap = 1,
.level_input_count = 1,
@@ -2001,7 +1995,6 @@ static const struct scarlett2_device_info s2i2_gen4_info = {
{ 2115, &scarlett2_config_set_gen4_2i2 },
{ }
},
- .min_firmware_version = 2115,
.has_devmap = 1,
.level_input_count = 2,
@@ -2059,7 +2052,6 @@ static const struct scarlett2_device_info s4i4_gen4_info = {
{ 2089, &scarlett2_config_set_gen4_4i4 },
{ }
},
- .min_firmware_version = 2089,
.has_devmap = 1,
.level_input_count = 2,
@@ -3341,7 +3333,8 @@ static int scarlett2_min_firmware_version_ctl_get(
struct usb_mixer_elem_info *elem = kctl->private_data;
struct scarlett2_data *private = elem->head.mixer->private_data;
- ucontrol->value.integer.value[0] = private->info->min_firmware_version;
+ ucontrol->value.integer.value[0] =
+ private->info->config_sets[0].from_firmware_version;
return 0;
}
@@ -8566,6 +8559,7 @@ static int scarlett2_read_configs(struct usb_mixer_interface *mixer)
{
struct scarlett2_data *private = mixer->private_data;
const struct scarlett2_device_info *info = private->info;
+ u16 min_firmware_version = info->config_sets[0].from_firmware_version;
int err, i;
if (scarlett2_has_config_item(private, SCARLETT2_CONFIG_MSD_SWITCH)) {
@@ -8576,13 +8570,13 @@ static int scarlett2_read_configs(struct usb_mixer_interface *mixer)
return err;
}
- if (private->firmware_version < info->min_firmware_version) {
+ if (private->firmware_version < min_firmware_version) {
usb_audio_err(mixer->chip,
"Focusrite %s firmware version %d is too old; "
"need %d",
private->series_name,
private->firmware_version,
- info->min_firmware_version);
+ min_firmware_version);
return 0;
}
@@ -8766,6 +8760,7 @@ static int snd_scarlett2_controls_create(
const struct scarlett2_device_entry *entry)
{
struct scarlett2_data *private;
+ u16 min_firmware_version;
int err;
/* Initialise private data */
@@ -8774,6 +8769,8 @@ static int snd_scarlett2_controls_create(
return err;
private = mixer->private_data;
+ min_firmware_version =
+ private->info->config_sets[0].from_firmware_version;
/* Send proprietary USB initialisation sequence */
err = scarlett2_usb_init(mixer);
@@ -8816,7 +8813,7 @@ static int snd_scarlett2_controls_create(
* old, don't create any other controls
*/
if (private->msd_switch ||
- private->firmware_version < private->info->min_firmware_version)
+ private->firmware_version < min_firmware_version)
return 0;
/* Create the analogue output controls */
--
2.53.0
^ permalink raw reply related [flat|nested] 5+ messages in thread* [PATCH 3/3] ALSA: scarlett2: Update offsets for 2i2 Gen 4 firmware 2417
2026-04-25 21:07 [PATCH 0/3] ALSA: scarlett2: Fix 2i2 Gen 4 direct monitor on firmware 2417 Geoffrey D. Bennett
2026-04-25 21:16 ` [PATCH 1/3] ALSA: scarlett2: Allow selecting config_set by firmware version Geoffrey D. Bennett
2026-04-25 21:16 ` [PATCH 2/3] ALSA: scarlett2: Fold min_firmware_version into config_sets Geoffrey D. Bennett
@ 2026-04-25 21:17 ` Geoffrey D. Bennett
2026-04-27 12:20 ` [PATCH 0/3] ALSA: scarlett2: Fix 2i2 Gen 4 direct monitor on " Takashi Iwai
3 siblings, 0 replies; 5+ messages in thread
From: Geoffrey D. Bennett @ 2026-04-25 21:17 UTC (permalink / raw)
To: Takashi Iwai; +Cc: Takashi Iwai, linux-sound
Firmware 2417 for the Scarlett 4th Gen 2i2 moved the direct monitor gain
parameters, so add a second config_set with the shifted offset and
select it for firmware versions >= 2417.
Fixes: 4e809a299677 ("ALSA: scarlett2: Add support for Solo, 2i2, and 4i4 Gen 4")
Cc: <stable@vger.kernel.org> # ALSA: scarlett2: Allow selecting config_set by firmware version
Cc: <stable@vger.kernel.org> # ALSA: scarlett2: Fold min_firmware_version into config_sets
Cc: <stable@vger.kernel.org>
Signed-off-by: Geoffrey D. Bennett <g@b4.vu>
---
sound/usb/mixer_scarlett2.c | 58 +++++++++++++++++++++++++++++++++++++
1 file changed, 58 insertions(+)
diff --git a/sound/usb/mixer_scarlett2.c b/sound/usb/mixer_scarlett2.c
index 68adec4f21d9..bb7373fac63a 100644
--- a/sound/usb/mixer_scarlett2.c
+++ b/sound/usb/mixer_scarlett2.c
@@ -937,6 +937,63 @@ static const struct scarlett2_config_set scarlett2_config_set_gen4_2i2 = {
}
};
+/* 2i2 Gen 4, firmware version 2417 and above
+ *
+ * Firmware 2417 shifted DIRECT_MONITOR_GAIN by 4 bytes; all other
+ * offsets are unchanged from scarlett2_config_set_gen4_2i2.
+ */
+static const struct scarlett2_config_set scarlett2_config_set_gen4_2i2_2417 = {
+ .notifications = scarlett4_2i2_notifications,
+ .param_buf_addr = 0xfc,
+ .input_gain_tlv = db_scale_gen4_gain,
+ .autogain_status_texts = scarlett2_autogain_status_gen4,
+ .items = {
+ [SCARLETT2_CONFIG_MSD_SWITCH] = {
+ .offset = 0x49, .size = 8, .activate = 4 },
+
+ [SCARLETT2_CONFIG_DIRECT_MONITOR] = {
+ .offset = 0x14a, .size = 8, .activate = 16, .pbuf = 1 },
+
+ [SCARLETT2_CONFIG_AUTOGAIN_SWITCH] = {
+ .offset = 0x135, .size = 8, .activate = 10, .pbuf = 1 },
+
+ [SCARLETT2_CONFIG_AUTOGAIN_STATUS] = {
+ .offset = 0x137, .size = 8 },
+
+ [SCARLETT2_CONFIG_AG_MEAN_TARGET] = {
+ .offset = 0x131, .size = 8, .activate = 29, .pbuf = 1 },
+
+ [SCARLETT2_CONFIG_AG_PEAK_TARGET] = {
+ .offset = 0x132, .size = 8, .activate = 30, .pbuf = 1 },
+
+ [SCARLETT2_CONFIG_PHANTOM_SWITCH] = {
+ .offset = 0x48, .size = 8, .activate = 11, .pbuf = 1,
+ .mute = 1 },
+
+ [SCARLETT2_CONFIG_INPUT_GAIN] = {
+ .offset = 0x4b, .size = 8, .activate = 12, .pbuf = 1 },
+
+ [SCARLETT2_CONFIG_LEVEL_SWITCH] = {
+ .offset = 0x3c, .size = 8, .activate = 13, .pbuf = 1,
+ .mute = 1 },
+
+ [SCARLETT2_CONFIG_SAFE_SWITCH] = {
+ .offset = 0x147, .size = 8, .activate = 14, .pbuf = 1 },
+
+ [SCARLETT2_CONFIG_AIR_SWITCH] = {
+ .offset = 0x3e, .size = 8, .activate = 15, .pbuf = 1 },
+
+ [SCARLETT2_CONFIG_INPUT_SELECT_SWITCH] = {
+ .offset = 0x14b, .size = 8, .activate = 17, .pbuf = 1 },
+
+ [SCARLETT2_CONFIG_INPUT_LINK_SWITCH] = {
+ .offset = 0x14e, .size = 8, .activate = 18, .pbuf = 1 },
+
+ [SCARLETT2_CONFIG_DIRECT_MONITOR_GAIN] = {
+ .offset = 0x2a4, .size = 16, .activate = 36 }
+ }
+};
+
/* 4i4 Gen 4 */
static const struct scarlett2_config_set scarlett2_config_set_gen4_4i4 = {
.notifications = scarlett4_4i4_notifications,
@@ -1993,6 +2050,7 @@ static const struct scarlett2_device_info solo_gen4_info = {
static const struct scarlett2_device_info s2i2_gen4_info = {
.config_sets = (const struct scarlett2_config_set_entry[]) {
{ 2115, &scarlett2_config_set_gen4_2i2 },
+ { 2417, &scarlett2_config_set_gen4_2i2_2417 },
{ }
},
.has_devmap = 1,
--
2.53.0
^ permalink raw reply related [flat|nested] 5+ messages in thread