16345
 };
 
-/* Flash segments that we may manipulate */
-#define SCARLETT2_SEGMENT_ID_SETTINGS 0
-#define SCARLETT2_SEGMENT_ID_FIRMWARE 1
-#define SCARLETT2_SEGMENT_ID_COUNT 2
-
 /* Maximum number of analogue outputs */
 #define SCARLETT2_ANALOGUE_MAX 10
 
        SCARLETT2_DIM_MUTE_COUNT = 2,
 };
 
+/* Flash Write State */
+enum {
+       SCARLETT2_FLASH_WRITE_STATE_IDLE = 0,
+       SCARLETT2_FLASH_WRITE_STATE_SELECTED = 1,
+       SCARLETT2_FLASH_WRITE_STATE_ERASING = 2
+};
+
 static const char *const scarlett2_dim_mute_names[SCARLETT2_DIM_MUTE_COUNT] = {
        "Mute Playback Switch", "Dim Playback Switch"
 };
        struct usb_mixer_interface *mixer;
        struct mutex usb_mutex; /* prevent sending concurrent USB requests */
        struct mutex data_mutex; /* lock access to this data */
+       u8 hwdep_in_use;
+       u8 selected_flash_segment_id;
+       u8 flash_write_state;
        struct delayed_work work;
        const struct scarlett2_device_info *info;
        const char *series_name;
 
        mutex_lock(&private->data_mutex);
 
+       if (private->hwdep_in_use) {
+               err = -EBUSY;
+               goto unlock;
+       }
+
        if (private->sync_updated) {
                err = scarlett2_update_sync(mixer);
                if (err < 0)
 
        mutex_lock(&private->data_mutex);
 
+       if (private->hwdep_in_use) {
+               err = -EBUSY;
+               goto unlock;
+       }
+
        if (private->vol_updated) {
                err = scarlett2_update_volumes(mixer);
                if (err < 0)
 
        mutex_lock(&private->data_mutex);
 
+       if (private->hwdep_in_use) {
+               err = -EBUSY;
+               goto unlock;
+       }
+
        if (private->vol_updated) {
                err = scarlett2_update_volumes(mixer);
                if (err < 0)
 
        mutex_lock(&private->data_mutex);
 
+       if (private->hwdep_in_use) {
+               err = -EBUSY;
+               goto unlock;
+       }
+
        oval = private->vol[index];
        val = ucontrol->value.integer.value[0];
 
 
        mutex_lock(&private->data_mutex);
 
+       if (private->hwdep_in_use) {
+               err = -EBUSY;
+               goto unlock;
+       }
+
        if (private->vol_updated) {
                err = scarlett2_update_volumes(mixer);
                if (err < 0)
 
        mutex_lock(&private->data_mutex);
 
+       if (private->hwdep_in_use) {
+               err = -EBUSY;
+               goto unlock;
+       }
+
        oval = private->mute_switch[index];
        val = !!ucontrol->value.integer.value[0];
 
 
        mutex_lock(&private->data_mutex);
 
+       if (private->hwdep_in_use) {
+               err = -EBUSY;
+               goto unlock;
+       }
+
        oval = private->vol_sw_hw_switch[index];
        val = !!ucontrol->value.enumerated.item[0];
 
 
        mutex_lock(&private->data_mutex);
 
+       if (private->hwdep_in_use) {
+               err = -EBUSY;
+               goto unlock;
+       }
+
        if (private->input_other_updated) {
                err = scarlett2_update_input_other(mixer);
                if (err < 0)
 
        mutex_lock(&private->data_mutex);
 
+       if (private->hwdep_in_use) {
+               err = -EBUSY;
+               goto unlock;
+       }
+
        oval = private->level_switch[index];
        val = !!ucontrol->value.enumerated.item[0];
 
 
        mutex_lock(&private->data_mutex);
 
+       if (private->hwdep_in_use) {
+               err = -EBUSY;
+               goto unlock;
+       }
+
        if (private->input_other_updated) {
                err = scarlett2_update_input_other(mixer);
                if (err < 0)
 
        mutex_lock(&private->data_mutex);
 
+       if (private->hwdep_in_use) {
+               err = -EBUSY;
+               goto unlock;
+       }
+
        oval = private->pad_switch[index];
        val = !!ucontrol->value.integer.value[0];
 
 
        mutex_lock(&private->data_mutex);
 
+       if (private->hwdep_in_use) {
+               err = -EBUSY;
+               goto unlock;
+       }
+
        if (private->input_other_updated) {
                err = scarlett2_update_input_other(mixer);
                if (err < 0)
 
        mutex_lock(&private->data_mutex);
 
+       if (private->hwdep_in_use) {
+               err = -EBUSY;
+               goto unlock;
+       }
+
        oval = private->air_switch[index];
        val = !!ucontrol->value.integer.value[0];
 
 
        mutex_lock(&private->data_mutex);
 
+       if (private->hwdep_in_use) {
+               err = -EBUSY;
+               goto unlock;
+       }
+
        if (private->input_other_updated) {
                err = scarlett2_update_input_other(mixer);
                if (err < 0)
 
        mutex_lock(&private->data_mutex);
 
+       if (private->hwdep_in_use) {
+               err = -EBUSY;
+               goto unlock;
+       }
+
        oval = private->phantom_switch[index];
        val = !!ucontrol->value.integer.value[0];
 
 
        mutex_lock(&private->data_mutex);
 
+       if (private->hwdep_in_use) {
+               err = -EBUSY;
+               goto unlock;
+       }
+
        oval = private->phantom_persistence;
        val = !!ucontrol->value.integer.value[0];
 
 
        mutex_lock(&private->data_mutex);
 
+       if (private->hwdep_in_use) {
+               err = -EBUSY;
+               goto unlock;
+       }
+
        if (private->monitor_other_updated) {
                err = scarlett2_update_monitor_other(mixer);
                if (err < 0)
 
        mutex_lock(&private->data_mutex);
 
+       if (private->hwdep_in_use) {
+               err = -EBUSY;
+               goto unlock;
+       }
+
        oval = private->direct_monitor_switch;
        val = min(ucontrol->value.enumerated.item[0], 2U);
 
 
        mutex_lock(&private->data_mutex);
 
+       if (private->hwdep_in_use) {
+               err = -EBUSY;
+               goto unlock;
+       }
+
        if (private->monitor_other_updated) {
                err = scarlett2_update_monitor_other(mixer);
                if (err < 0)
 
        mutex_lock(&private->data_mutex);
 
+       if (private->hwdep_in_use) {
+               err = -EBUSY;
+               goto unlock;
+       }
+
        oval = private->speaker_switching_switch;
        val = min(ucontrol->value.enumerated.item[0], 2U);
 
 
        mutex_lock(&private->data_mutex);
 
+       if (private->hwdep_in_use) {
+               err = -EBUSY;
+               goto unlock;
+       }
+
        if (private->monitor_other_updated) {
                err = scarlett2_update_monitor_other(mixer);
                if (err < 0)
 
        mutex_lock(&private->data_mutex);
 
+       if (private->hwdep_in_use) {
+               err = -EBUSY;
+               goto unlock;
+       }
+
        oval = private->talkback_switch;
        val = min(ucontrol->value.enumerated.item[0], 2U);
 
 
        mutex_lock(&private->data_mutex);
 
+       if (private->hwdep_in_use) {
+               err = -EBUSY;
+               goto unlock;
+       }
+
        oval = private->talkback_map[index];
        val = !!ucontrol->value.integer.value[0];
 
 
        mutex_lock(&private->data_mutex);
 
+       if (private->hwdep_in_use) {
+               err = -EBUSY;
+               goto unlock;
+       }
+
        if (private->vol_updated) {
                err = scarlett2_update_volumes(mixer);
                if (err < 0)
 
        mutex_lock(&private->data_mutex);
 
+       if (private->hwdep_in_use) {
+               err = -EBUSY;
+               goto unlock;
+       }
+
        oval = private->dim_mute[index];
        val = !!ucontrol->value.integer.value[0];
 
 
        mutex_lock(&private->data_mutex);
 
+       if (private->hwdep_in_use) {
+               err = -EBUSY;
+               goto unlock;
+       }
+
        oval = private->mix[index];
        val = clamp(ucontrol->value.integer.value[0],
                    0L, (long)SCARLETT2_MIXER_MAX_VALUE);
 
        mutex_lock(&private->data_mutex);
 
+       if (private->hwdep_in_use) {
+               err = -EBUSY;
+               goto unlock;
+       }
+
        if (private->mux_updated) {
                err = scarlett2_usb_get_mux(mixer);
                if (err < 0)
 
        mutex_lock(&private->data_mutex);
 
+       if (private->hwdep_in_use) {
+               err = -EBUSY;
+               goto unlock;
+       }
+
        oval = private->mux[index];
        val = min(ucontrol->value.enumerated.item[0],
                  private->num_mux_srcs - 1U);
 
        mutex_lock(&private->data_mutex);
 
+       if (private->hwdep_in_use) {
+               err = -EBUSY;
+               goto unlock;
+       }
+
        err = scarlett2_usb_get_meter_levels(elem->head.mixer, elem->channels,
                                             meter_levels);
        if (err < 0)
 
        mutex_lock(&private->data_mutex);
 
+       if (private->hwdep_in_use) {
+               err = -EBUSY;
+               goto unlock;
+       }
+
        oval = private->msd_switch;
        val = !!ucontrol->value.integer.value[0];
 
 
        mutex_lock(&private->data_mutex);
 
+       if (private->hwdep_in_use) {
+               err = -EBUSY;
+               goto unlock;
+       }
+
        oval = private->standalone_switch;
        val = !!ucontrol->value.integer.value[0];
 
 
 /*** hwdep interface ***/
 
-/* Reboot the device. */
+/* Set private->hwdep_in_use; prevents access to the ALSA controls
+ * while doing a config erase/firmware upgrade.
+ */
+static void scarlett2_lock(struct scarlett2_data *private)
+{
+       mutex_lock(&private->data_mutex);
+       private->hwdep_in_use = 1;
+       mutex_unlock(&private->data_mutex);
+}
+
+/* Call SCARLETT2_USB_GET_ERASE to get the erase progress */
+static int scarlett2_get_erase_progress(struct usb_mixer_interface *mixer)
+{
+       struct scarlett2_data *private = mixer->private_data;
+       int segment_id, segment_num, err;
+       u8 erase_resp;
+
+       struct {
+               __le32 segment_num;
+               __le32 pad;
+       } __packed erase_req;
+
+       segment_id = private->selected_flash_segment_id;
+       segment_num = private->flash_segment_nums[segment_id];
+
+       if (segment_num < SCARLETT2_SEGMENT_NUM_MIN ||
+           segment_num > SCARLETT2_SEGMENT_NUM_MAX)
+               return -EFAULT;
+
+       /* Send the erase progress request */
+       erase_req.segment_num = cpu_to_le32(segment_num);
+       erase_req.pad = 0;
+
+       err = scarlett2_usb(mixer, SCARLETT2_USB_GET_ERASE,
+                           &erase_req, sizeof(erase_req),
+                           &erase_resp, sizeof(erase_resp));
+       if (err < 0)
+               return err;
+
+       return erase_resp;
+}
+
+/* Repeatedly call scarlett2_get_erase_progress() until it returns
+ * 0xff (erase complete) or we've waited 10 seconds (it usually takes
+ * <3 seconds).
+ */
+static int scarlett2_wait_for_erase(struct usb_mixer_interface *mixer)
+{
+       int i, err;
+
+       for (i = 0; i < 100; i++) {
+               err = scarlett2_get_erase_progress(mixer);
+               if (err < 0)
+                       return err;
+
+               if (err == 0xff)
+                       return 0;
+
+               msleep(100);
+       }
+
+       return -ETIMEDOUT;
+}
+
+/* Reboot the device; wait for the erase to complete if one is in
+ * progress.
+ */
 static int scarlett2_reboot(struct usb_mixer_interface *mixer)
 {
+       struct scarlett2_data *private = mixer->private_data;
+
+       if (private->flash_write_state ==
+             SCARLETT2_FLASH_WRITE_STATE_ERASING) {
+               int err = scarlett2_wait_for_erase(mixer);
+
+               if (err < 0)
+                       return err;
+       }
+
        return scarlett2_usb(mixer, SCARLETT2_USB_REBOOT, NULL, 0, NULL, 0);
 }
 
+/* Select a flash segment for erasing (and possibly writing to) */
+static int scarlett2_ioctl_select_flash_segment(
+       struct usb_mixer_interface *mixer,
+       unsigned long arg)
+{
+       struct scarlett2_data *private = mixer->private_data;
+       int segment_id, segment_num;
+
+       if (get_user(segment_id, (int __user *)arg))
+               return -EFAULT;
+
+       /* Check the segment ID and segment number */
+       if (segment_id < 0 || segment_id >= SCARLETT2_SEGMENT_ID_COUNT)
+               return -EINVAL;
+
+       segment_num = private->flash_segment_nums[segment_id];
+       if (segment_num < SCARLETT2_SEGMENT_NUM_MIN ||
+           segment_num > SCARLETT2_SEGMENT_NUM_MAX) {
+               usb_audio_err(mixer->chip,
+                             "%s: invalid segment number %d\n",
+                             __func__, segment_id);
+               return -EFAULT;
+       }
+
+       /* If erasing, wait for it to complete */
+       if (private->flash_write_state == SCARLETT2_FLASH_WRITE_STATE_ERASING) {
+               int err = scarlett2_wait_for_erase(mixer);
+
+               if (err < 0)
+                       return err;
+       }
+
+       /* Save the selected segment ID and set the state to SELECTED */
+       private->selected_flash_segment_id = segment_id;
+       private->flash_write_state = SCARLETT2_FLASH_WRITE_STATE_SELECTED;
+
+       return 0;
+}
+
+/* Erase the previously-selected flash segment */
+static int scarlett2_ioctl_erase_flash_segment(
+       struct usb_mixer_interface *mixer)
+{
+       struct scarlett2_data *private = mixer->private_data;
+       int segment_id, segment_num, err;
+
+       struct {
+               __le32 segment_num;
+               __le32 pad;
+       } __packed erase_req;
+
+       if (private->flash_write_state != SCARLETT2_FLASH_WRITE_STATE_SELECTED)
+               return -EINVAL;
+
+       segment_id = private->selected_flash_segment_id;
+       segment_num = private->flash_segment_nums[segment_id];
+
+       if (segment_num < SCARLETT2_SEGMENT_NUM_MIN ||
+           segment_num > SCARLETT2_SEGMENT_NUM_MAX)
+               return -EFAULT;
+
+       /* Prevent access to ALSA controls that access the device from
+        * here on
+        */
+       scarlett2_lock(private);
+
+       /* Send the erase request */
+       erase_req.segment_num = cpu_to_le32(segment_num);
+       erase_req.pad = 0;
+
+       err = scarlett2_usb(mixer, SCARLETT2_USB_ERASE_SEGMENT,
+                           &erase_req, sizeof(erase_req),
+                           NULL, 0);
+       if (err < 0)
+               return err;
+
+       /* On success, change the state from SELECTED to ERASING */
+       private->flash_write_state = SCARLETT2_FLASH_WRITE_STATE_ERASING;
+
+       return 0;
+}
+
+/* Get the erase progress from the device */
+static int scarlett2_ioctl_get_erase_progress(
+       struct usb_mixer_interface *mixer,
+       unsigned long arg)
+{
+       struct scarlett2_data *private = mixer->private_data;
+       struct scarlett2_flash_segment_erase_progress progress;
+       int segment_id, segment_num, err;
+       u8 erase_resp;
+
+       struct {
+               __le32 segment_num;
+               __le32 pad;
+       } __packed erase_req;
+
+       /* Check that we're erasing */
+       if (private->flash_write_state != SCARLETT2_FLASH_WRITE_STATE_ERASING)
+               return -EINVAL;
+
+       segment_id = private->selected_flash_segment_id;
+       segment_num = private->flash_segment_nums[segment_id];
+
+       if (segment_num < SCARLETT2_SEGMENT_NUM_MIN ||
+           segment_num > SCARLETT2_SEGMENT_NUM_MAX)
+               return -EFAULT;
+
+       /* Send the erase progress request */
+       erase_req.segment_num = cpu_to_le32(segment_num);
+       erase_req.pad = 0;
+
+       err = scarlett2_usb(mixer, SCARLETT2_USB_GET_ERASE,
+                           &erase_req, sizeof(erase_req),
+                           &erase_resp, sizeof(erase_resp));
+       if (err < 0)
+               return err;
+
+       progress.progress = erase_resp;
+       progress.num_blocks = private->flash_segment_blocks[segment_id];
+
+       if (copy_to_user((void __user *)arg, &progress, sizeof(progress)))
+               return -EFAULT;
+
+       /* If the erase is complete, change the state from ERASING to
+        * IDLE.
+        */
+       if (progress.progress == 0xff)
+               private->flash_write_state = SCARLETT2_FLASH_WRITE_STATE_IDLE;
+
+       return 0;
+}
+
+static int scarlett2_hwdep_open(struct snd_hwdep *hw, struct file *file)
+{
+       struct usb_mixer_interface *mixer = hw->private_data;
+       struct scarlett2_data *private = mixer->private_data;
+
+       /* If erasing, wait for it to complete */
+       if (private->flash_write_state ==
+             SCARLETT2_FLASH_WRITE_STATE_ERASING) {
+               int err = scarlett2_wait_for_erase(mixer);
+
+               if (err < 0)
+                       return err;
+       }
+
+       /* Set the state to IDLE */
+       private->flash_write_state = SCARLETT2_FLASH_WRITE_STATE_IDLE;
+
+       return 0;
+}
+
 static int scarlett2_hwdep_ioctl(struct snd_hwdep *hw, struct file *file,
                                 unsigned int cmd, unsigned long arg)
 {
        case SCARLETT2_IOCTL_REBOOT:
                return scarlett2_reboot(mixer);
 
+       case SCARLETT2_IOCTL_SELECT_FLASH_SEGMENT:
+               return scarlett2_ioctl_select_flash_segment(mixer, arg);
+
+       case SCARLETT2_IOCTL_ERASE_FLASH_SEGMENT:
+               return scarlett2_ioctl_erase_flash_segment(mixer);
+
+       case SCARLETT2_IOCTL_GET_ERASE_PROGRESS:
+               return scarlett2_ioctl_get_erase_progress(mixer, arg);
+
        default:
                return -ENOIOCTLCMD;
        }
 }
 
+static int scarlett2_hwdep_release(struct snd_hwdep *hw, struct file *file)
+{
+       struct usb_mixer_interface *mixer = hw->private_data;
+       struct scarlett2_data *private = mixer->private_data;
+
+       /* Return from the SELECTED or WRITE state to IDLE.
+        * The ERASING state is left as-is, and checked on next open.
+        */
+       if (private &&
+           private->hwdep_in_use &&
+           private->flash_write_state != SCARLETT2_FLASH_WRITE_STATE_ERASING)
+               private->flash_write_state = SCARLETT2_FLASH_WRITE_STATE_IDLE;
+
+       return 0;
+}
+
 static int scarlett2_hwdep_init(struct usb_mixer_interface *mixer)
 {
        struct snd_hwdep *hw;
 
        hw->private_data = mixer;
        hw->exclusive = 1;
+       hw->ops.open = scarlett2_hwdep_open;
        hw->ops.ioctl = scarlett2_hwdep_ioctl;
+       hw->ops.release = scarlett2_hwdep_release;
 
        return 0;
 }