// SPDX-License-Identifier: GPL-2.0
/*
- * Focusrite Scarlett 6i6/18i8/18i20 Gen 2 Driver for ALSA
+ * Focusrite Scarlett Gen 2/3 Driver for ALSA
*
- * Copyright (c) 2018-2019 by Geoffrey D. Bennett <g at b4.vu>
+ * Supported models:
+ * - 6i6/18i8/18i20 Gen 2
+ * - 4i4/8i6/18i8/18i20 Gen 3
+ *
+ * Copyright (c) 2018-2021 by Geoffrey D. Bennett <g at b4.vu>
*
* Based on the Scarlett (Gen 1) Driver for ALSA:
*
* David Henningsson <david.henningsson at canonical.com>
*/
-/* Mixer Interface for the Focusrite Scarlett 6i6/18i8/18i20 Gen 2 audio
- * interface. Based on the Gen 1 driver and rewritten.
- */
-
/* The protocol was reverse engineered by looking at the communication
* between Focusrite Control 2.3.4 and the Focusrite(R) Scarlett 18i20
* (firmware 1083) using usbmon in July-August 2018.
* Scarlett 6i6 support added in June 2019 (thanks to Martin Wittmann
* for providing usbmon output and testing).
*
+ * Scarlett 4i4/8i6 Gen 3 support added in May 2020 (thanks to Laurent
+ * Debricon for donating a 4i4 and to Fredrik Unger for providing 8i6
+ * usbmon output and testing).
+ *
+ * Scarlett 18i8/18i20 Gen 3 support added in June 2020 (thanks to
+ * Darren Jaeckel, Alex Sedlack, and Clovis Lunel for providing usbmon
+ * output, protocol traces and testing).
+ *
* Support for loading mixer volume and mux configuration from the
* interface during driver initialisation added in May 2021 (thanks to
* Vladimir Sadovnikov for figuring out how).
*
- * This ALSA mixer gives access to:
+ * This ALSA mixer gives access to (model-dependent):
* - input, output, mixer-matrix muxes
- * - 18x10 mixer-matrix gain stages
+ * - mixer-matrix gain stages
* - gain/volume/mute controls
* - level meters
* - line/inst level and pad controls
+ * - disable/enable MSD mode
*
* <ditaa>
* /--------------\ 18chn 20chn /--------------\
* \--------------/
* </ditaa>
*
+ * Gen 3 devices have a Mass Storage Device (MSD) mode where a small
+ * disk with registration and driver download information is presented
+ * to the host. To access the full functionality of the device without
+ * proprietary software, MSD mode can be disabled by:
+ * - holding down the 48V button for five seconds while powering on
+ * the device, or
+ * - using this driver and alsamixer to change the "MSD Mode" setting
+ * to Off and power-cycling the device
*/
#include <linux/slab.h>
/* device_setup value to enable */
#define SCARLETT2_ENABLE 0x01
+/* device_setup value to allow turning MSD mode back on */
+#define SCARLETT2_MSD_ENABLE 0x02
+
/* some gui mixers can't handle negative ctl values */
#define SCARLETT2_VOLUME_BIAS 127
/* Maximum number of level and pad switches */
#define SCARLETT2_LEVEL_SWITCH_MAX 2
-#define SCARLETT2_PAD_SWITCH_MAX 4
+#define SCARLETT2_PAD_SWITCH_MAX 8
/* Maximum number of inputs to the mixer */
-#define SCARLETT2_INPUT_MIX_MAX 18
+#define SCARLETT2_INPUT_MIX_MAX 25
/* Maximum number of outputs from the mixer */
-#define SCARLETT2_OUTPUT_MIX_MAX 10
+#define SCARLETT2_OUTPUT_MIX_MAX 12
/* Maximum size of the data in the USB mux assignment message:
- * 18 inputs, 20 outputs, 18 matrix inputs, 8 spare
+ * 20 inputs, 20 outputs, 25 matrix inputs, 12 spare
*/
-#define SCARLETT2_MUX_MAX 64
+#define SCARLETT2_MUX_MAX 77
-/* Number of meters:
- * 18 inputs, 20 outputs, 18 matrix inputs
- */
-#define SCARLETT2_NUM_METERS 56
+/* Maximum number of meters (sum of output port counts) */
+#define SCARLETT2_MAX_METERS 65
/* Hardware port types:
* - None (no input to mux)
#define SCARLETT2_MUX_TABLES 3
/* Maximum number of entries in a mux table */
-#define SCARLETT2_MAX_MUX_ENTRIES 7
+#define SCARLETT2_MAX_MUX_ENTRIES 10
/* One entry within mux_assignment defines the port type and range of
* ports to add to the set_mux message. The end of the list is marked
struct scarlett2_device_info {
u32 usb_id; /* USB device identifier */
+ /* Gen 3 devices have an internal MSD mode switch that needs
+ * to be disabled in order to access the full functionality of
+ * the device.
+ */
+ u8 has_msd_mode;
+
/* line out hw volume is sw controlled */
u8 line_out_hw_vol;
u16 scarlett2_seq;
u8 sync_updated;
u8 vol_updated;
+ u8 input_other_updated;
u8 sync;
u8 master_vol;
u8 vol[SCARLETT2_ANALOGUE_MAX];
u8 level_switch[SCARLETT2_LEVEL_SWITCH_MAX];
u8 pad_switch[SCARLETT2_PAD_SWITCH_MAX];
u8 dim_mute[SCARLETT2_DIM_MUTE_COUNT];
+ u8 msd_switch;
struct snd_kcontrol *sync_ctl;
struct snd_kcontrol *master_vol_ctl;
struct snd_kcontrol *vol_ctls[SCARLETT2_ANALOGUE_MAX];
struct snd_kcontrol *mute_ctls[SCARLETT2_ANALOGUE_MAX];
struct snd_kcontrol *dim_mute_ctls[SCARLETT2_DIM_MUTE_COUNT];
+ struct snd_kcontrol *level_ctls[SCARLETT2_LEVEL_SWITCH_MAX];
+ struct snd_kcontrol *pad_ctls[SCARLETT2_PAD_SWITCH_MAX];
u8 mux[SCARLETT2_MUX_MAX];
u8 mix[SCARLETT2_INPUT_MIX_MAX * SCARLETT2_OUTPUT_MIX_MAX];
};
} },
};
+static const struct scarlett2_device_info s4i4_gen3_info = {
+ .usb_id = USB_ID(0x1235, 0x8212),
+
+ .has_msd_mode = 1,
+ .level_input_count = 2,
+ .pad_input_count = 2,
+
+ .line_out_descrs = {
+ "Monitor L",
+ "Monitor R",
+ "Headphones L",
+ "Headphones R",
+ },
+
+ .port_count = {
+ [SCARLETT2_PORT_TYPE_NONE] = { 1, 0 },
+ [SCARLETT2_PORT_TYPE_ANALOGUE] = { 4, 4 },
+ [SCARLETT2_PORT_TYPE_MIX] = { 6, 8 },
+ [SCARLETT2_PORT_TYPE_PCM] = { 4, 6 },
+ },
+
+ .mux_assignment = { {
+ { SCARLETT2_PORT_TYPE_PCM, 0, 6 },
+ { SCARLETT2_PORT_TYPE_ANALOGUE, 0, 4 },
+ { SCARLETT2_PORT_TYPE_MIX, 0, 8 },
+ { SCARLETT2_PORT_TYPE_NONE, 0, 16 },
+ { 0, 0, 0 },
+ }, {
+ { SCARLETT2_PORT_TYPE_PCM, 0, 6 },
+ { SCARLETT2_PORT_TYPE_ANALOGUE, 0, 4 },
+ { SCARLETT2_PORT_TYPE_MIX, 0, 8 },
+ { SCARLETT2_PORT_TYPE_NONE, 0, 16 },
+ { 0, 0, 0 },
+ }, {
+ { SCARLETT2_PORT_TYPE_PCM, 0, 6 },
+ { SCARLETT2_PORT_TYPE_ANALOGUE, 0, 4 },
+ { SCARLETT2_PORT_TYPE_MIX, 0, 8 },
+ { SCARLETT2_PORT_TYPE_NONE, 0, 16 },
+ { 0, 0, 0 },
+ } },
+};
+
+static const struct scarlett2_device_info s8i6_gen3_info = {
+ .usb_id = USB_ID(0x1235, 0x8213),
+
+ .has_msd_mode = 1,
+ .level_input_count = 2,
+ .pad_input_count = 2,
+
+ .line_out_descrs = {
+ "Headphones 1 L",
+ "Headphones 1 R",
+ "Headphones 2 L",
+ "Headphones 2 R",
+ },
+
+ .port_count = {
+ [SCARLETT2_PORT_TYPE_NONE] = { 1, 0 },
+ [SCARLETT2_PORT_TYPE_ANALOGUE] = { 6, 4 },
+ [SCARLETT2_PORT_TYPE_SPDIF] = { 2, 2 },
+ [SCARLETT2_PORT_TYPE_MIX] = { 8, 8 },
+ [SCARLETT2_PORT_TYPE_PCM] = { 6, 10 },
+ },
+
+ .mux_assignment = { {
+ { SCARLETT2_PORT_TYPE_PCM, 0, 8 },
+ { SCARLETT2_PORT_TYPE_ANALOGUE, 0, 4 },
+ { SCARLETT2_PORT_TYPE_SPDIF, 0, 2 },
+ { SCARLETT2_PORT_TYPE_PCM, 8, 2 },
+ { SCARLETT2_PORT_TYPE_MIX, 0, 8 },
+ { SCARLETT2_PORT_TYPE_NONE, 0, 18 },
+ { 0, 0, 0 },
+ }, {
+ { SCARLETT2_PORT_TYPE_PCM, 0, 8 },
+ { SCARLETT2_PORT_TYPE_ANALOGUE, 0, 4 },
+ { SCARLETT2_PORT_TYPE_SPDIF, 0, 2 },
+ { SCARLETT2_PORT_TYPE_PCM, 8, 2 },
+ { SCARLETT2_PORT_TYPE_MIX, 0, 8 },
+ { SCARLETT2_PORT_TYPE_NONE, 0, 18 },
+ { 0, 0, 0 },
+ }, {
+ { SCARLETT2_PORT_TYPE_PCM, 0, 8 },
+ { SCARLETT2_PORT_TYPE_ANALOGUE, 0, 4 },
+ { SCARLETT2_PORT_TYPE_SPDIF, 0, 2 },
+ { SCARLETT2_PORT_TYPE_PCM, 8, 2 },
+ { SCARLETT2_PORT_TYPE_MIX, 0, 8 },
+ { SCARLETT2_PORT_TYPE_NONE, 0, 18 },
+ { 0, 0, 0 },
+ } },
+};
+
+static const struct scarlett2_device_info s18i8_gen3_info = {
+ .usb_id = USB_ID(0x1235, 0x8214),
+
+ .has_msd_mode = 1,
+ .line_out_hw_vol = 1,
+ .level_input_count = 2,
+ .pad_input_count = 2,
+
+ .line_out_descrs = {
+ "Monitor L",
+ "Monitor R",
+ "Headphones 1 L",
+ "Headphones 1 R",
+ "Headphones 2 L",
+ "Headphones 2 R",
+ "Alt Monitor L",
+ "Alt Monitor R",
+ },
+
+ .port_count = {
+ [SCARLETT2_PORT_TYPE_NONE] = { 1, 0 },
+ [SCARLETT2_PORT_TYPE_ANALOGUE] = { 8, 8 },
+ [SCARLETT2_PORT_TYPE_SPDIF] = { 2, 2 },
+ [SCARLETT2_PORT_TYPE_ADAT] = { 8, 0 },
+ [SCARLETT2_PORT_TYPE_MIX] = { 10, 20 },
+ [SCARLETT2_PORT_TYPE_PCM] = { 8, 20 },
+ },
+
+ .mux_assignment = { {
+ { SCARLETT2_PORT_TYPE_PCM, 0, 10 },
+ { SCARLETT2_PORT_TYPE_PCM, 12, 8 },
+ { SCARLETT2_PORT_TYPE_ANALOGUE, 0, 2 },
+ { SCARLETT2_PORT_TYPE_ANALOGUE, 6, 2 },
+ { SCARLETT2_PORT_TYPE_ANALOGUE, 2, 4 },
+ { SCARLETT2_PORT_TYPE_SPDIF, 0, 2 },
+ { SCARLETT2_PORT_TYPE_PCM, 10, 2 },
+ { SCARLETT2_PORT_TYPE_MIX, 0, 20 },
+ { SCARLETT2_PORT_TYPE_NONE, 0, 10 },
+ { 0, 0, 0 },
+ }, {
+ { SCARLETT2_PORT_TYPE_PCM, 0, 10 },
+ { SCARLETT2_PORT_TYPE_PCM, 12, 4 },
+ { SCARLETT2_PORT_TYPE_ANALOGUE, 0, 2 },
+ { SCARLETT2_PORT_TYPE_ANALOGUE, 6, 2 },
+ { SCARLETT2_PORT_TYPE_ANALOGUE, 2, 4 },
+ { SCARLETT2_PORT_TYPE_SPDIF, 0, 2 },
+ { SCARLETT2_PORT_TYPE_PCM, 10, 2 },
+ { SCARLETT2_PORT_TYPE_MIX, 0, 20 },
+ { SCARLETT2_PORT_TYPE_NONE, 0, 10 },
+ { 0, 0, 0 },
+ }, {
+ { SCARLETT2_PORT_TYPE_PCM, 0, 10 },
+ { SCARLETT2_PORT_TYPE_ANALOGUE, 0, 2 },
+ { SCARLETT2_PORT_TYPE_ANALOGUE, 6, 2 },
+ { SCARLETT2_PORT_TYPE_ANALOGUE, 2, 4 },
+ { SCARLETT2_PORT_TYPE_SPDIF, 0, 2 },
+ { SCARLETT2_PORT_TYPE_MIX, 0, 20 },
+ { SCARLETT2_PORT_TYPE_NONE, 0, 10 },
+ { 0, 0, 0 },
+ } },
+};
+
+static const struct scarlett2_device_info s18i20_gen3_info = {
+ .usb_id = USB_ID(0x1235, 0x8215),
+
+ .has_msd_mode = 1,
+ .line_out_hw_vol = 1,
+ .level_input_count = 2,
+ .pad_input_count = 8,
+
+ .line_out_descrs = {
+ "Monitor 1 L",
+ "Monitor 1 R",
+ "Monitor 2 L",
+ "Monitor 2 R",
+ NULL,
+ NULL,
+ "Headphones 1 L",
+ "Headphones 1 R",
+ "Headphones 2 L",
+ "Headphones 2 R",
+ },
+
+ .port_count = {
+ [SCARLETT2_PORT_TYPE_NONE] = { 1, 0 },
+ [SCARLETT2_PORT_TYPE_ANALOGUE] = { 9, 10 },
+ [SCARLETT2_PORT_TYPE_SPDIF] = { 2, 2 },
+ [SCARLETT2_PORT_TYPE_ADAT] = { 8, 8 },
+ [SCARLETT2_PORT_TYPE_MIX] = { 12, 25 },
+ [SCARLETT2_PORT_TYPE_PCM] = { 20, 20 },
+ },
+
+ .mux_assignment = { {
+ { SCARLETT2_PORT_TYPE_PCM, 0, 8 },
+ { SCARLETT2_PORT_TYPE_PCM, 10, 10 },
+ { SCARLETT2_PORT_TYPE_ANALOGUE, 0, 10 },
+ { SCARLETT2_PORT_TYPE_SPDIF, 0, 2 },
+ { SCARLETT2_PORT_TYPE_ADAT, 0, 8 },
+ { SCARLETT2_PORT_TYPE_PCM, 8, 2 },
+ { SCARLETT2_PORT_TYPE_MIX, 0, 25 },
+ { SCARLETT2_PORT_TYPE_NONE, 0, 12 },
+ { 0, 0, 0 },
+ }, {
+ { SCARLETT2_PORT_TYPE_PCM, 0, 8 },
+ { SCARLETT2_PORT_TYPE_PCM, 10, 8 },
+ { SCARLETT2_PORT_TYPE_ANALOGUE, 0, 10 },
+ { SCARLETT2_PORT_TYPE_SPDIF, 0, 2 },
+ { SCARLETT2_PORT_TYPE_ADAT, 0, 8 },
+ { SCARLETT2_PORT_TYPE_PCM, 8, 2 },
+ { SCARLETT2_PORT_TYPE_MIX, 0, 25 },
+ { SCARLETT2_PORT_TYPE_NONE, 0, 10 },
+ { 0, 0, 0 },
+ }, {
+ { SCARLETT2_PORT_TYPE_PCM, 0, 10 },
+ { SCARLETT2_PORT_TYPE_ANALOGUE, 0, 10 },
+ { SCARLETT2_PORT_TYPE_SPDIF, 0, 2 },
+ { SCARLETT2_PORT_TYPE_NONE, 0, 24 },
+ { 0, 0, 0 },
+ } },
+};
+
static const struct scarlett2_device_info *scarlett2_devices[] = {
/* Supported Gen 2 devices */
&s6i6_gen2_info,
&s18i8_gen2_info,
&s18i20_gen2_info,
+ /* Supported Gen 3 devices */
+ &s4i4_gen3_info,
+ &s8i6_gen3_info,
+ &s18i8_gen3_info,
+ &s18i20_gen3_info,
+
/* End of list */
NULL
};
/*** USB Interactions ***/
/* Notifications from the interface */
-#define SCARLETT2_USB_NOTIFY_SYNC 0x00000008
-#define SCARLETT2_USB_NOTIFY_DIM_MUTE 0x00200000
-#define SCARLETT2_USB_NOTIFY_MONITOR 0x00400000
+#define SCARLETT2_USB_NOTIFY_SYNC 0x00000008
+#define SCARLETT2_USB_NOTIFY_DIM_MUTE 0x00200000
+#define SCARLETT2_USB_NOTIFY_MONITOR 0x00400000
+#define SCARLETT2_USB_NOTIFY_INPUT_OTHER 0x00800000
/* Commands for sending/receiving requests/responses */
#define SCARLETT2_USB_CMD_INIT 0
SCARLETT2_CONFIG_SW_HW_SWITCH = 3,
SCARLETT2_CONFIG_LEVEL_SWITCH = 4,
SCARLETT2_CONFIG_PAD_SWITCH = 5,
- SCARLETT2_CONFIG_COUNT = 6
+ SCARLETT2_CONFIG_MSD_SWITCH = 6,
+ SCARLETT2_CONFIG_COUNT = 7
};
/* Location, size, and activation command number for the configuration
[SCARLETT2_CONFIG_PAD_SWITCH] = {
.offset = 0x84, .size = 1, .activate = 8 },
+
+ [SCARLETT2_CONFIG_MSD_SWITCH] = {
+ .offset = 0x9d, .size = 1, .activate = 6 },
};
/* proprietary request/response format */
if (err != req_buf_size) {
usb_audio_err(
mixer->chip,
- "Scarlett Gen 2 USB request result cmd %x was %d\n",
+ "Scarlett Gen 2/3 USB request result cmd %x was %d\n",
cmd, err);
err = -EINVAL;
goto unlock;
if (err != resp_buf_size) {
usb_audio_err(
mixer->chip,
- "Scarlett Gen 2 USB response result cmd %x was %d "
+ "Scarlett Gen 2/3 USB response result cmd %x was %d "
"expected %d\n",
cmd, err, resp_buf_size);
err = -EINVAL;
resp->pad) {
usb_audio_err(
mixer->chip,
- "Scarlett Gen 2 USB invalid response; "
+ "Scarlett Gen 2/3 USB invalid response; "
"cmd tx/rx %d/%d seq %d/%d size %d/%d "
"error %d pad %d\n",
le32_to_cpu(req->cmd), le32_to_cpu(resp->cmd),
return err;
}
+/* Send a USB message to get data; result placed in *buf */
+static int scarlett2_usb_get(
+ struct usb_mixer_interface *mixer,
+ int offset, void *buf, int size)
+{
+ struct {
+ __le32 offset;
+ __le32 size;
+ } __packed req;
+
+ req.offset = cpu_to_le32(offset);
+ req.size = cpu_to_le32(size);
+ return scarlett2_usb(mixer, SCARLETT2_USB_GET_DATA,
+ &req, sizeof(req), buf, size);
+}
+
+/* Send a USB message to get configuration parameters; result placed in *buf */
+static int scarlett2_usb_get_config(
+ struct usb_mixer_interface *mixer,
+ int config_item_num, int count, void *buf)
+{
+ const struct scarlett2_config *config_item =
+ &scarlett2_config_items[config_item_num];
+ int size = config_item->size * count;
+
+ return scarlett2_usb_get(mixer, config_item->offset, buf, size);
+}
+
/* Send SCARLETT2_USB_DATA_CMD SCARLETT2_USB_CONFIG_SAVE */
static void scarlett2_config_save(struct usb_mixer_interface *mixer)
{
return err;
/* Schedule the change to be written to NVRAM */
- schedule_delayed_work(&private->work, msecs_to_jiffies(2000));
+ if (config_item->activate != SCARLETT2_USB_CONFIG_SAVE)
+ schedule_delayed_work(&private->work, msecs_to_jiffies(2000));
return 0;
}
-/* Send a USB message to get data; result placed in *buf */
-static int scarlett2_usb_get(
- struct usb_mixer_interface *mixer,
- int offset, void *buf, int size)
-{
- struct {
- __le32 offset;
- __le32 size;
- } __packed req;
-
- req.offset = cpu_to_le32(offset);
- req.size = cpu_to_le32(size);
- return scarlett2_usb(mixer, SCARLETT2_USB_GET_DATA,
- &req, sizeof(req), buf, size);
-}
-
-/* Send a USB message to get configuration parameters; result placed in *buf */
-static int scarlett2_usb_get_config(
- struct usb_mixer_interface *mixer,
- int config_item_num, int count, void *buf)
-{
- const struct scarlett2_config *config_item =
- &scarlett2_config_items[config_item_num];
- int size = config_item->size * count;
-
- return scarlett2_usb_get(mixer, config_item->offset, buf, size);
-}
-
/* Send a USB message to get sync status; result placed in *sync */
static int scarlett2_usb_get_sync_status(
struct usb_mixer_interface *mixer,
/* Send USB message to get meter levels */
static int scarlett2_usb_get_meter_levels(struct usb_mixer_interface *mixer,
- u16 *levels)
+ u16 num_meters, u16 *levels)
{
struct {
__le16 pad;
__le16 num_meters;
__le32 magic;
} __packed req;
- u32 resp[SCARLETT2_NUM_METERS];
+ u32 resp[SCARLETT2_MAX_METERS];
int i, err;
req.pad = 0;
- req.num_meters = cpu_to_le16(SCARLETT2_NUM_METERS);
+ req.num_meters = cpu_to_le16(num_meters);
req.magic = cpu_to_le32(SCARLETT2_USB_METER_LEVELS_GET_MAGIC);
err = scarlett2_usb(mixer, SCARLETT2_USB_GET_METER,
- &req, sizeof(req), resp, sizeof(resp));
+ &req, sizeof(req), resp, num_meters * sizeof(u32));
if (err < 0)
return err;
/* copy, convert to u16 */
- for (i = 0; i < SCARLETT2_NUM_METERS; i++)
+ for (i = 0; i < num_meters; i++)
levels[i] = resp[i];
return 0;
if (!elem)
return -ENOMEM;
+ /* We set USB_MIXER_BESPOKEN type, so that the core USB mixer code
+ * ignores them for resume and other operations.
+ * Also, the head.id field is set to 0, as we don't use this field.
+ */
elem->head.mixer = mixer;
elem->control = index;
- elem->head.id = index;
+ elem->head.id = 0;
elem->channels = channels;
+ elem->val_type = USB_MIXER_BESPOKEN;
kctl = snd_ctl_new1(ncontrol, elem);
if (!kctl) {
/*** Line Level/Instrument Level Switch Controls ***/
+static int scarlett2_update_input_other(struct usb_mixer_interface *mixer)
+{
+ struct scarlett2_data *private = mixer->private_data;
+ const struct scarlett2_device_info *info = private->info;
+
+ private->input_other_updated = 0;
+
+ if (info->level_input_count) {
+ int err = scarlett2_usb_get_config(
+ mixer, SCARLETT2_CONFIG_LEVEL_SWITCH,
+ info->level_input_count, private->level_switch);
+ if (err < 0)
+ return err;
+ }
+
+ if (info->pad_input_count) {
+ int err = scarlett2_usb_get_config(
+ mixer, SCARLETT2_CONFIG_PAD_SWITCH,
+ info->pad_input_count, private->pad_switch);
+ if (err < 0)
+ return err;
+ }
+
+ return 0;
+}
+
static int scarlett2_level_enum_ctl_info(struct snd_kcontrol *kctl,
struct snd_ctl_elem_info *uinfo)
{
struct snd_ctl_elem_value *ucontrol)
{
struct usb_mixer_elem_info *elem = kctl->private_data;
- struct scarlett2_data *private = elem->head.mixer->private_data;
+ struct usb_mixer_interface *mixer = elem->head.mixer;
+ struct scarlett2_data *private = mixer->private_data;
+ mutex_lock(&private->data_mutex);
+ if (private->input_other_updated)
+ scarlett2_update_input_other(mixer);
ucontrol->value.enumerated.item[0] =
private->level_switch[elem->control];
+ mutex_unlock(&private->data_mutex);
+
return 0;
}
struct snd_ctl_elem_value *ucontrol)
{
struct usb_mixer_elem_info *elem = kctl->private_data;
- struct scarlett2_data *private = elem->head.mixer->private_data;
+ struct usb_mixer_interface *mixer = elem->head.mixer;
+ struct scarlett2_data *private = mixer->private_data;
+ mutex_lock(&private->data_mutex);
+ if (private->input_other_updated)
+ scarlett2_update_input_other(mixer);
ucontrol->value.integer.value[0] =
private->pad_switch[elem->control];
+ mutex_unlock(&private->data_mutex);
+
return 0;
}
for (i = 0; i < info->level_input_count; i++) {
snprintf(s, sizeof(s), fmt, i + 1, "Level", "Enum");
err = scarlett2_add_new_ctl(mixer, &scarlett2_level_enum_ctl,
- i, 1, s, NULL);
+ i, 1, s, &private->level_ctls[i]);
if (err < 0)
return err;
}
for (i = 0; i < info->pad_input_count; i++) {
snprintf(s, sizeof(s), fmt, i + 1, "Pad", "Switch");
err = scarlett2_add_new_ctl(mixer, &scarlett2_pad_ctl,
- i, 1, s, NULL);
+ i, 1, s, &private->pad_ctls[i]);
if (err < 0)
return err;
}
struct snd_ctl_elem_value *ucontrol)
{
struct usb_mixer_elem_info *elem = kctl->private_data;
- u16 meter_levels[SCARLETT2_NUM_METERS];
+ u16 meter_levels[SCARLETT2_MAX_METERS];
int i, err;
- err = scarlett2_usb_get_meter_levels(elem->head.mixer, meter_levels);
+ err = scarlett2_usb_get_meter_levels(elem->head.mixer, elem->channels,
+ meter_levels);
if (err < 0)
return err;
static int scarlett2_add_meter_ctl(struct usb_mixer_interface *mixer)
{
+ struct scarlett2_data *private = mixer->private_data;
+
return scarlett2_add_new_ctl(mixer, &scarlett2_meter_ctl,
- 0, SCARLETT2_NUM_METERS,
+ 0, private->num_mux_dsts,
"Level Meter", NULL);
}
+/*** MSD Controls ***/
+
+static int scarlett2_msd_ctl_get(struct snd_kcontrol *kctl,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct usb_mixer_elem_info *elem = kctl->private_data;
+ struct scarlett2_data *private = elem->head.mixer->private_data;
+
+ ucontrol->value.integer.value[0] = private->msd_switch;
+ return 0;
+}
+
+static int scarlett2_msd_ctl_put(struct snd_kcontrol *kctl,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct usb_mixer_elem_info *elem = kctl->private_data;
+ struct usb_mixer_interface *mixer = elem->head.mixer;
+ struct scarlett2_data *private = mixer->private_data;
+
+ int oval, val, err = 0;
+
+ mutex_lock(&private->data_mutex);
+
+ oval = private->msd_switch;
+ val = !!ucontrol->value.integer.value[0];
+
+ if (oval == val)
+ goto unlock;
+
+ private->msd_switch = val;
+
+ /* Send switch change to the device */
+ err = scarlett2_usb_set_config(mixer, SCARLETT2_CONFIG_MSD_SWITCH,
+ 0, val);
+
+unlock:
+ mutex_unlock(&private->data_mutex);
+ return err;
+}
+
+static const struct snd_kcontrol_new scarlett2_msd_ctl = {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "",
+ .info = snd_ctl_boolean_mono_info,
+ .get = scarlett2_msd_ctl_get,
+ .put = scarlett2_msd_ctl_put,
+};
+
+static int scarlett2_add_msd_ctl(struct usb_mixer_interface *mixer)
+{
+ struct scarlett2_data *private = mixer->private_data;
+ const struct scarlett2_device_info *info = private->info;
+
+ if (!info->has_msd_mode)
+ return 0;
+
+ /* If MSD mode is off, hide the switch by default */
+ if (!private->msd_switch && !(mixer->chip->setup & SCARLETT2_MSD_ENABLE))
+ return 0;
+
+ /* Add MSD control */
+ return scarlett2_add_new_ctl(mixer, &scarlett2_msd_ctl,
+ 0, 1, "MSD Mode", NULL);
+}
+
/*** Cleanup/Suspend Callbacks ***/
static void scarlett2_private_free(struct usb_mixer_interface *mixer)
struct scarlett2_usb_volume_status volume_status;
int err, i;
- if (info->level_input_count) {
+ if (info->has_msd_mode) {
err = scarlett2_usb_get_config(
- mixer,
- SCARLETT2_CONFIG_LEVEL_SWITCH,
- info->level_input_count,
- private->level_switch);
+ mixer, SCARLETT2_CONFIG_MSD_SWITCH,
+ 1, &private->msd_switch);
if (err < 0)
return err;
- }
- if (info->pad_input_count) {
- err = scarlett2_usb_get_config(
- mixer,
- SCARLETT2_CONFIG_PAD_SWITCH,
- info->pad_input_count,
- private->pad_switch);
- if (err < 0)
- return err;
+ /* no other controls are created if MSD mode is on */
+ if (private->msd_switch)
+ return 0;
}
+ err = scarlett2_update_input_other(mixer);
+ if (err < 0)
+ return err;
+
err = scarlett2_update_sync(mixer);
if (err < 0)
return err;
&private->mute_ctls[i]->id);
}
+/* Notify on "input other" change (level/pad) */
+static void scarlett2_notify_input_other(
+ struct usb_mixer_interface *mixer)
+{
+ struct snd_card *card = mixer->chip->card;
+ struct scarlett2_data *private = mixer->private_data;
+ const struct scarlett2_device_info *info = private->info;
+ int i;
+
+ private->input_other_updated = 1;
+
+ for (i = 0; i < info->level_input_count; i++)
+ snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE,
+ &private->level_ctls[i]->id);
+ for (i = 0; i < info->pad_input_count; i++)
+ snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE,
+ &private->pad_ctls[i]->id);
+}
+
/* Interrupt callback */
static void scarlett2_notify(struct urb *urb)
{
scarlett2_notify_monitor(mixer);
if (data & SCARLETT2_USB_NOTIFY_DIM_MUTE)
scarlett2_notify_dim_mute(mixer);
+ if (data & SCARLETT2_USB_NOTIFY_INPUT_OTHER)
+ scarlett2_notify_input_other(mixer);
requeue:
if (ustatus != -ENOENT &&
if (err < 0)
return err;
+ /* Create the MSD control */
+ err = scarlett2_add_msd_ctl(mixer);
+ if (err < 0)
+ return err;
+
+ /* If MSD mode is enabled, don't create any other controls */
+ if (((struct scarlett2_data *)mixer->private_data)->msd_switch)
+ return 0;
+
/* Create the analogue output controls */
err = scarlett2_add_line_out_ctls(mixer);
if (err < 0)
if (!(chip->setup & SCARLETT2_ENABLE)) {
usb_audio_info(chip,
- "Focusrite Scarlett Gen 2 Mixer Driver disabled; "
+ "Focusrite Scarlett Gen 2/3 Mixer Driver disabled; "
"use options snd_usb_audio vid=0x%04x pid=0x%04x "
"device_setup=1 to enable and report any issues "
"to g@b4.vu",
}
usb_audio_info(chip,
- "Focusrite Scarlett Gen 2 Mixer Driver enabled pid=0x%04x",
+ "Focusrite Scarlett Gen 2/3 Mixer Driver enabled pid=0x%04x",
USB_ID_PRODUCT(chip->usb_id));
err = snd_scarlett_gen2_controls_create(mixer);