]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
ALSA: scarlett2: Add support for device map retrieval
authorGeoffrey D. Bennett <g@b4.vu>
Fri, 4 Oct 2024 14:29:04 +0000 (23:59 +0930)
committerTakashi Iwai <tiwai@suse.de>
Tue, 8 Oct 2024 08:11:49 +0000 (10:11 +0200)
Add support for retrieving the device map from Focusrite Scarlett 4th
Gen and Vocaster devices. The device map is a base64-encoded,
zlib-compressed JSON description of the device's capabilities and
configuration.

This patch adds:
- a has_devmap field to the scarlett2_device_info struct
- a /proc/asound/cardX/device-map.json.zz.b64 file when supported

Signed-off-by: Geoffrey D. Bennett <g@b4.vu>
Signed-off-by: Takashi Iwai <tiwai@suse.de>
Link: https://patch.msgid.link/e86380c6792460d8d05a8ecc37c9ebd072be25a5.1727971672.git.g@b4.vu
sound/usb/mixer_scarlett2.c

index b0eca4785bc66d4eab254b004b8fe2cbaa7b4de9..69fa80b9d5bfcd8319473a20e140f257e9252294 100644 (file)
@@ -1079,6 +1079,9 @@ struct scarlett2_device_info {
        /* minimum firmware version required */
        u16 min_firmware_version;
 
+       /* has a downloadable device map */
+       u8 has_devmap;
+
        /* support for main/alt speaker switching */
        u8 has_speaker_switching;
 
@@ -1773,6 +1776,7 @@ static const struct scarlett2_device_info s18i20_gen3_info = {
 static const struct scarlett2_device_info vocaster_one_info = {
        .config_set = &scarlett2_config_set_vocaster,
        .min_firmware_version = 1769,
+       .has_devmap = 1,
 
        .phantom_count = 1,
        .inputs_per_phantom = 1,
@@ -1815,6 +1819,7 @@ static const struct scarlett2_device_info vocaster_one_info = {
 static const struct scarlett2_device_info vocaster_two_info = {
        .config_set = &scarlett2_config_set_vocaster,
        .min_firmware_version = 1769,
+       .has_devmap = 1,
 
        .phantom_count = 2,
        .inputs_per_phantom = 1,
@@ -1858,6 +1863,7 @@ 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,
        .min_firmware_version = 2115,
+       .has_devmap = 1,
 
        .level_input_count = 1,
        .air_input_count = 1,
@@ -1912,6 +1918,7 @@ 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,
        .min_firmware_version = 2115,
+       .has_devmap = 1,
 
        .level_input_count = 2,
        .air_input_count = 2,
@@ -1966,6 +1973,7 @@ 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,
        .min_firmware_version = 2089,
+       .has_devmap = 1,
 
        .level_input_count = 2,
        .air_input_count = 2,
@@ -2264,6 +2272,8 @@ static int scarlett2_get_port_start_num(
 #define SCARLETT2_USB_GET_DATA      0x00800000
 #define SCARLETT2_USB_SET_DATA      0x00800001
 #define SCARLETT2_USB_DATA_CMD      0x00800002
+#define SCARLETT2_USB_INFO_DEVMAP   0x0080000c
+#define SCARLETT2_USB_GET_DEVMAP    0x0080000d
 
 #define SCARLETT2_USB_CONFIG_SAVE 6
 
@@ -2277,6 +2287,14 @@ static int scarlett2_get_port_start_num(
 #define SCARLETT2_SEGMENT_SETTINGS_NAME "App_Settings"
 #define SCARLETT2_SEGMENT_FIRMWARE_NAME "App_Upgrade"
 
+/* Gen 4 device firmware provides access to a base64-encoded
+ * zlib-compressed JSON description of the device's capabilities and
+ * configuration. This device map is made available in
+ * /proc/asound/cardX/device-map.json.zz.b64
+ */
+#define SCARLETT2_DEVMAP_BLOCK_SIZE 1024
+#define SCARLETT2_DEVMAP_FILENAME "device-map.json.zz.b64"
+
 /* proprietary request/response format */
 struct scarlett2_usb_packet {
        __le32 cmd;
@@ -9562,6 +9580,116 @@ static int scarlett2_hwdep_init(struct usb_mixer_interface *mixer)
        return 0;
 }
 
+/*** device-map file ***/
+
+static ssize_t scarlett2_devmap_read(
+       struct snd_info_entry *entry,
+       void                  *file_private_data,
+       struct file           *file,
+       char __user           *buf,
+       size_t                 count,
+       loff_t                 pos)
+{
+       struct usb_mixer_interface *mixer = entry->private_data;
+       u8           *resp_buf;
+       const size_t  block_size = SCARLETT2_DEVMAP_BLOCK_SIZE;
+       size_t        copied = 0;
+
+       if (pos >= entry->size)
+               return 0;
+
+       if (pos + count > entry->size)
+               count = entry->size - pos;
+
+       resp_buf = kmalloc(block_size, GFP_KERNEL);
+       if (!resp_buf)
+               return -ENOMEM;
+
+       while (count > 0) {
+               /* SCARLETT2_USB_GET_DEVMAP reads only on block boundaries,
+                * so we need to read a whole block and copy the requested
+                * chunk to userspace.
+                */
+
+               __le32 req;
+               int    err;
+
+               /* offset within the block that we're reading */
+               size_t offset = pos % block_size;
+
+               /* read_size is block_size except for the last block */
+               size_t block_start = pos - offset;
+               size_t read_size = min_t(size_t,
+                                        block_size,
+                                        entry->size - block_start);
+
+               /* size of the chunk to copy to userspace */
+               size_t copy_size = min_t(size_t, count, read_size - offset);
+
+               /* request the block */
+               req = cpu_to_le32(pos / block_size);
+               err = scarlett2_usb(mixer, SCARLETT2_USB_GET_DEVMAP,
+                                   &req, sizeof(req), resp_buf, read_size);
+               if (err < 0) {
+                       kfree(resp_buf);
+                       return copied ? copied : err;
+               }
+
+               if (copy_to_user(buf, resp_buf + offset, copy_size)) {
+                       kfree(resp_buf);
+                       return -EFAULT;
+               }
+
+               buf += copy_size;
+               pos += copy_size;
+               copied += copy_size;
+               count -= copy_size;
+       }
+
+       kfree(resp_buf);
+       return copied;
+}
+
+static const struct snd_info_entry_ops scarlett2_devmap_ops = {
+       .read = scarlett2_devmap_read,
+};
+
+static int scarlett2_devmap_init(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;
+       __le16 config_len_buf[2];
+       int config_len;
+       struct snd_info_entry *entry;
+       int err;
+
+       /* If the device doesn't support the DEVMAP commands, don't
+        * create the /proc/asound/cardX/scarlett.json.zlib entry
+        */
+       if (!info->has_devmap)
+               return 0;
+
+       err = scarlett2_usb(mixer, SCARLETT2_USB_INFO_DEVMAP,
+                           NULL, 0, &config_len_buf, sizeof(config_len_buf));
+       if (err < 0)
+               return err;
+
+       config_len = le16_to_cpu(config_len_buf[1]);
+
+       err = snd_card_proc_new(card, SCARLETT2_DEVMAP_FILENAME, &entry);
+       if (err < 0)
+               return err;
+
+       entry->content = SNDRV_INFO_CONTENT_DATA;
+       entry->private_data = mixer;
+       entry->c.ops = &scarlett2_devmap_ops;
+       entry->size = config_len;
+       entry->mode = S_IFREG | 0444;
+
+       return 0;
+}
+
 int snd_scarlett2_init(struct usb_mixer_interface *mixer)
 {
        struct snd_usb_audio *chip = mixer->chip;
@@ -9612,11 +9740,20 @@ int snd_scarlett2_init(struct usb_mixer_interface *mixer)
        }
 
        err = scarlett2_hwdep_init(mixer);
-       if (err < 0)
+       if (err < 0) {
                usb_audio_err(mixer->chip,
                              "Error creating %s hwdep device: %d",
                              entry->series_name,
                              err);
+               return err;
+       }
+
+       err = scarlett2_devmap_init(mixer);
+       if (err < 0)
+               usb_audio_err(mixer->chip,
+                             "Error creating %s devmap entry: %d",
+                             entry->series_name,
+                             err);
 
        return err;
 }