Contributors: 11
Author Tokens Token Proportion Commits Commit Proportion
Geoffrey D. Bennett 4460 92.78% 1 3.85%
Daniel Mack 155 3.22% 5 19.23%
Clemens Ladisch 52 1.08% 4 15.38%
Jaroslav Kysela 44 0.92% 1 3.85%
Felix Homann 34 0.71% 2 7.69%
Takashi Iwai 27 0.56% 7 26.92%
Mark Hills 19 0.40% 1 3.85%
Eldad Zack 6 0.12% 1 3.85%
Stephen Rothwell 6 0.12% 2 7.69%
Chen Ni 3 0.06% 1 3.85%
Chris J Arges 1 0.02% 1 3.85%
Total 4807 26


// SPDX-License-Identifier: GPL-2.0
/*
 * Focusrite Control Protocol Driver for ALSA
 *
 * Copyright (c) 2024-2025 by Geoffrey D. Bennett <g at b4.vu>
 */
/*
 * DOC: Theory of Operation
 *
 * The Focusrite Control Protocol (FCP) driver provides a minimal
 * kernel interface that allows a user-space driver (primarily
 * fcp-server) to communicate with Focusrite USB audio interfaces
 * using their vendor-specific protocol. This protocol is used by
 * Scarlett 2nd Gen, 3rd Gen, 4th Gen, Clarett USB, Clarett+, and
 * Vocaster series devices.
 *
 * Unlike the existing scarlett2 driver which implements all controls
 * in kernel space, this driver takes a lighter-weight approach by
 * moving most functionality to user space. The only control
 * implemented in kernel space is the Level Meter, since it requires
 * frequent polling of volatile data.
 *
 * The driver provides an hwdep interface that allows the user-space
 * driver to:
 *  - Initialise the protocol
 *  - Send arbitrary FCP commands to the device
 *  - Receive notifications from the device
 *  - Configure the Level Meter control
 *
 * Usage Flow
 * ----------
 * 1. Open the hwdep device (requires CAP_SYS_RAWIO)
 * 2. Get protocol version using FCP_IOCTL_PVERSION
 * 3. Initialise protocol using FCP_IOCTL_INIT
 * 4. Send commands using FCP_IOCTL_CMD
 * 5. Receive notifications using read()
 * 6. Optionally set up the Level Meter control using
 *    FCP_IOCTL_SET_METER_MAP
 * 7. Optionally add labels to the Level Meter control using
 *    FCP_IOCTL_SET_METER_LABELS
 *
 * Level Meter
 * -----------
 * The Level Meter is implemented as an ALSA control that provides
 * real-time level monitoring. When the control is read, the driver
 * requests the current meter levels from the device, translates the
 * levels using the configured mapping, and returns the result to the
 * user. The mapping between device meters and the ALSA control's
 * channels is configured with FCP_IOCTL_SET_METER_MAP.
 *
 * Labels for the Level Meter channels can be set using
 * FCP_IOCTL_SET_METER_LABELS and read by applications through the
 * control's TLV data. The labels are transferred as a sequence of
 * null-terminated strings.
 */

#include <linux/slab.h>
#include <linux/usb.h>

#include <sound/control.h>
#include <sound/hwdep.h>
#include <sound/tlv.h>

#include <uapi/sound/fcp.h>

#include "usbaudio.h"
#include "mixer.h"
#include "helper.h"

#include "fcp.h"

/* notify waiting to send to *file */
struct fcp_notify {
	wait_queue_head_t queue;
	u32               event;
	spinlock_t        lock;
};

struct fcp_data {
	struct usb_mixer_interface *mixer;

	struct mutex mutex;         /* serialise access to the device */
	struct completion cmd_done; /* wait for command completion */
	struct file *file;          /* hwdep file */

	struct fcp_notify notify;

	u8  bInterfaceNumber;
	u8  bEndpointAddress;
	u16 wMaxPacketSize;
	u8  bInterval;

	uint16_t step0_resp_size;
	uint16_t step2_resp_size;
	uint32_t init1_opcode;
	uint32_t init2_opcode;

	u8  init;
	u16 seq;

	u8                   num_meter_slots;
	s16                 *meter_level_map;
	__le32              *meter_levels;
	struct snd_kcontrol *meter_ctl;

	unsigned int *meter_labels_tlv;
	int           meter_labels_tlv_size;
};

/*** USB Interactions ***/

/* FCP Command ACK notification bit */
#define FCP_NOTIFY_ACK 1

/* Vendor-specific USB control requests */
#define FCP_USB_REQ_STEP0  0
#define FCP_USB_REQ_CMD_TX 2
#define FCP_USB_REQ_CMD_RX 3

/* Focusrite Control Protocol opcodes that the kernel side needs to
 * know about
 */
#define FCP_USB_REBOOT      0x00000003
#define FCP_USB_GET_METER   0x00001001
#define FCP_USB_FLASH_ERASE 0x00004002
#define FCP_USB_FLASH_WRITE 0x00004004

#define FCP_USB_METER_LEVELS_GET_MAGIC 1

#define FCP_SEGMENT_APP_GOLD 0

/* Forward declarations */
static int fcp_init(struct usb_mixer_interface *mixer,
		    void *step0_resp, void *step2_resp);

/* FCP command request/response format */
struct fcp_usb_packet {
	__le32 opcode;
	__le16 size;
	__le16 seq;
	__le32 error;
	__le32 pad;
	u8 data[];
};

static void fcp_fill_request_header(struct fcp_data *private,
				    struct fcp_usb_packet *req,
				    u32 opcode, u16 req_size)
{
	/* sequence must go up by 1 for each request */
	u16 seq = private->seq++;

	req->opcode = cpu_to_le32(opcode);
	req->size = cpu_to_le16(req_size);
	req->seq = cpu_to_le16(seq);
	req->error = 0;
	req->pad = 0;
}

static int fcp_usb_tx(struct usb_device *dev, int interface,
		      void *buf, u16 size)
{
	return snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0),
			FCP_USB_REQ_CMD_TX,
			USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_OUT,
			0, interface, buf, size);
}

static int fcp_usb_rx(struct usb_device *dev, int interface,
		      void *buf, u16 size)
{
	return snd_usb_ctl_msg(dev, usb_rcvctrlpipe(dev, 0),
			FCP_USB_REQ_CMD_RX,
			USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_IN,
			0, interface, buf, size);
}

/* Send an FCP command and get the response */
static int fcp_usb(struct usb_mixer_interface *mixer, u32 opcode,
		   const void *req_data, u16 req_size,
		   void *resp_data, u16 resp_size)
{
	struct fcp_data *private = mixer->private_data;
	struct usb_device *dev = mixer->chip->dev;
	struct fcp_usb_packet *req __free(kfree) = NULL;
	struct fcp_usb_packet *resp __free(kfree) = NULL;
	size_t req_buf_size = struct_size(req, data, req_size);
	size_t resp_buf_size = struct_size(resp, data, resp_size);
	int retries = 0;
	const int max_retries = 5;
	int err;

	if (!mixer->urb)
		return -ENODEV;

	req = kmalloc(req_buf_size, GFP_KERNEL);
	if (!req)
		return -ENOMEM;

	resp = kmalloc(resp_buf_size, GFP_KERNEL);
	if (!resp)
		return -ENOMEM;

	/* build request message */
	fcp_fill_request_header(private, req, opcode, req_size);
	if (req_size)
		memcpy(req->data, req_data, req_size);

	/* send the request and retry on EPROTO */
retry:
	err = fcp_usb_tx(dev, private->bInterfaceNumber, req, req_buf_size);
	if (err == -EPROTO && ++retries <= max_retries) {
		msleep(1 << (retries - 1));
		goto retry;
	}

	if (err != req_buf_size) {
		usb_audio_err(mixer->chip,
			      "FCP request %08x failed: %d\n", opcode, err);
		return -EINVAL;
	}

	if (!wait_for_completion_timeout(&private->cmd_done,
					 msecs_to_jiffies(1000))) {
		usb_audio_err(mixer->chip,
			      "FCP request %08x timed out\n", opcode);

		return -ETIMEDOUT;
	}

	/* send a second message to get the response */
	err = fcp_usb_rx(dev, private->bInterfaceNumber, resp, resp_buf_size);

	/* validate the response */

	if (err < 0) {

		/* ESHUTDOWN and EPROTO are valid responses to a
		 * reboot request
		 */
		if (opcode == FCP_USB_REBOOT &&
		    (err == -ESHUTDOWN || err == -EPROTO))
			return 0;

		usb_audio_err(mixer->chip,
			      "FCP read response %08x failed: %d\n",
			      opcode, err);
		return -EINVAL;
	}

	if (err < sizeof(*resp)) {
		usb_audio_err(mixer->chip,
			      "FCP response %08x too short: %d\n",
			      opcode, err);
		return -EINVAL;
	}

	if (req->seq != resp->seq) {
		usb_audio_err(mixer->chip,
			      "FCP response %08x seq mismatch %d/%d\n",
			      opcode,
			      le16_to_cpu(req->seq), le16_to_cpu(resp->seq));
		return -EINVAL;
	}

	if (req->opcode != resp->opcode) {
		usb_audio_err(mixer->chip,
			      "FCP response %08x opcode mismatch %08x\n",
			      opcode, le32_to_cpu(resp->opcode));
		return -EINVAL;
	}

	if (resp->error) {
		usb_audio_err(mixer->chip,
			      "FCP response %08x error %d\n",
			      opcode, le32_to_cpu(resp->error));
		return -EINVAL;
	}

	if (err != resp_buf_size) {
		usb_audio_err(mixer->chip,
			      "FCP response %08x buffer size mismatch %d/%zu\n",
			      opcode, err, resp_buf_size);
		return -EINVAL;
	}

	if (resp_size != le16_to_cpu(resp->size)) {
		usb_audio_err(mixer->chip,
			      "FCP response %08x size mismatch %d/%d\n",
			      opcode, resp_size, le16_to_cpu(resp->size));
		return -EINVAL;
	}

	if (resp_data && resp_size > 0)
		memcpy(resp_data, resp->data, resp_size);

	return 0;
}

static int fcp_reinit(struct usb_mixer_interface *mixer)
{
	struct fcp_data *private = mixer->private_data;
	void *step0_resp __free(kfree) = NULL;
	void *step2_resp __free(kfree) = NULL;

	if (mixer->urb)
		return 0;

	step0_resp = kmalloc(private->step0_resp_size, GFP_KERNEL);
	if (!step0_resp)
		return -ENOMEM;
	step2_resp = kmalloc(private->step2_resp_size, GFP_KERNEL);
	if (!step2_resp)
		return -ENOMEM;

	return fcp_init(mixer, step0_resp, step2_resp);
}

/*** Control Functions ***/

/* helper function to create a new control */
static int fcp_add_new_ctl(struct usb_mixer_interface *mixer,
			   const struct snd_kcontrol_new *ncontrol,
			   int index, int channels, const char *name,
			   struct snd_kcontrol **kctl_return)
{
	struct snd_kcontrol *kctl;
	struct usb_mixer_elem_info *elem;
	int err;

	elem = kzalloc(sizeof(*elem), GFP_KERNEL);
	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 = 0;
	elem->channels = channels;
	elem->val_type = USB_MIXER_BESPOKEN;

	kctl = snd_ctl_new1(ncontrol, elem);
	if (!kctl) {
		kfree(elem);
		return -ENOMEM;
	}
	kctl->private_free = snd_usb_mixer_elem_free;

	strscpy(kctl->id.name, name, sizeof(kctl->id.name));

	err = snd_usb_mixer_add_control(&elem->head, kctl);
	if (err < 0)
		return err;

	if (kctl_return)
		*kctl_return = kctl;

	return 0;
}

/*** Level Meter Control ***/

static int fcp_meter_ctl_info(struct snd_kcontrol *kctl,
			      struct snd_ctl_elem_info *uinfo)
{
	struct usb_mixer_elem_info *elem = kctl->private_data;

	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
	uinfo->count = elem->channels;
	uinfo->value.integer.min = 0;
	uinfo->value.integer.max = 4095;
	uinfo->value.integer.step = 1;
	return 0;
}

static int fcp_meter_ctl_get(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 fcp_data *private = mixer->private_data;
	int num_meter_slots, resp_size;
	__le32 *resp = private->meter_levels;
	int i, err = 0;

	struct {
		__le16 pad;
		__le16 num_meters;
		__le32 magic;
	} __packed req;

	guard(mutex)(&private->mutex);

	err = fcp_reinit(mixer);
	if (err < 0)
		return err;

	num_meter_slots = private->num_meter_slots;
	resp_size = num_meter_slots * sizeof(u32);

	req.pad = 0;
	req.num_meters = cpu_to_le16(num_meter_slots);
	req.magic = cpu_to_le32(FCP_USB_METER_LEVELS_GET_MAGIC);
	err = fcp_usb(mixer, FCP_USB_GET_METER,
		      &req, sizeof(req), resp, resp_size);
	if (err < 0)
		return err;

	/* copy & translate from resp[] using meter_level_map[] */
	for (i = 0; i < elem->channels; i++) {
		int idx = private->meter_level_map[i];
		int value = idx < 0 ? 0 : le32_to_cpu(resp[idx]);

		ucontrol->value.integer.value[i] = value;
	}

	return 0;
}

static int fcp_meter_tlv_callback(struct snd_kcontrol *kctl,
				  int op_flag, unsigned int size,
				  unsigned int __user *tlv)
{
	struct usb_mixer_elem_info *elem = kctl->private_data;
	struct usb_mixer_interface *mixer = elem->head.mixer;
	struct fcp_data *private = mixer->private_data;

	guard(mutex)(&private->mutex);

	if (op_flag == SNDRV_CTL_TLV_OP_READ) {
		if (private->meter_labels_tlv_size == 0)
			return 0;

		if (size > private->meter_labels_tlv_size)
			size = private->meter_labels_tlv_size;

		if (copy_to_user(tlv, private->meter_labels_tlv, size))
			return -EFAULT;

		return size;
	}

	return -EINVAL;
}

static const struct snd_kcontrol_new fcp_meter_ctl = {
	.iface  = SNDRV_CTL_ELEM_IFACE_PCM,
	.access = SNDRV_CTL_ELEM_ACCESS_READ |
		  SNDRV_CTL_ELEM_ACCESS_VOLATILE,
	.info = fcp_meter_ctl_info,
	.get  = fcp_meter_ctl_get,
	.tlv  = { .c = fcp_meter_tlv_callback },
};

/*** hwdep interface ***/

/* FCP initialisation */
static int fcp_ioctl_init(struct usb_mixer_interface *mixer,
			  struct fcp_init __user *arg)
{
	struct fcp_init init;
	struct usb_device *dev = mixer->chip->dev;
	struct fcp_data *private = mixer->private_data;
	void *resp __free(kfree) = NULL;
	void *step2_resp;
	int err, buf_size;

	if (usb_pipe_type_check(dev, usb_sndctrlpipe(dev, 0)))
		return -EINVAL;

	/* Get initialisation parameters */
	if (copy_from_user(&init, arg, sizeof(init)))
		return -EFAULT;

	/* Validate the response sizes */
	if (init.step0_resp_size < 1 ||
	    init.step0_resp_size > 255 ||
	    init.step2_resp_size < 1 ||
	    init.step2_resp_size > 255)
		return -EINVAL;

	/* Allocate response buffer */
	buf_size = init.step0_resp_size + init.step2_resp_size;

	resp = kmalloc(buf_size, GFP_KERNEL);
	if (!resp)
		return -ENOMEM;

	private->step0_resp_size = init.step0_resp_size;
	private->step2_resp_size = init.step2_resp_size;
	private->init1_opcode = init.init1_opcode;
	private->init2_opcode = init.init2_opcode;

	step2_resp = resp + private->step0_resp_size;

	err = fcp_init(mixer, resp, step2_resp);
	if (err < 0)
		return err;

	if (copy_to_user(arg->resp, resp, buf_size))
		return -EFAULT;

	return 0;
}

/* Check that the command is allowed
 * Don't permit erasing/writing segment 0 (App_Gold)
 */
static int fcp_validate_cmd(u32 opcode, void *data, u16 size)
{
	if (opcode == FCP_USB_FLASH_ERASE) {
		struct {
			__le32 segment_num;
			__le32 pad;
		} __packed *req = data;

		if (size != sizeof(*req))
			return -EINVAL;

		if (le32_to_cpu(req->segment_num) == FCP_SEGMENT_APP_GOLD)
			return -EPERM;

		if (req->pad != 0)
			return -EINVAL;

	} else if (opcode == FCP_USB_FLASH_WRITE) {
		struct {
			__le32 segment_num;
			__le32 offset;
			__le32 pad;
			u8 data[];
		} __packed *req = data;

		if (size < sizeof(*req))
			return -EINVAL;

		if (le32_to_cpu(req->segment_num) == FCP_SEGMENT_APP_GOLD)
			return -EPERM;

		if (req->pad != 0)
			return -EINVAL;
	}

	return 0;
}

/* Execute an FCP command specified by the user */
static int fcp_ioctl_cmd(struct usb_mixer_interface *mixer,
			 struct fcp_cmd __user *arg)
{
	struct fcp_cmd cmd;
	int err, buf_size;
	void *data __free(kfree) = NULL;

	/* get opcode and request/response size */
	if (copy_from_user(&cmd, arg, sizeof(cmd)))
		return -EFAULT;

	/* validate request and response sizes */
	if (cmd.req_size > 4096 || cmd.resp_size > 4096)
		return -EINVAL;

	/* reinit if needed */
	err = fcp_reinit(mixer);
	if (err < 0)
		return err;

	/* allocate request/response buffer */
	buf_size = max(cmd.req_size, cmd.resp_size);

	if (buf_size > 0) {
		data = kmalloc(buf_size, GFP_KERNEL);
		if (!data)
			return -ENOMEM;
	}

	/* copy request from user */
	if (cmd.req_size > 0)
		if (copy_from_user(data, arg->data, cmd.req_size))
			return -EFAULT;

	/* check that the command is allowed */
	err = fcp_validate_cmd(cmd.opcode, data, cmd.req_size);
	if (err < 0)
		return err;

	/* send request, get response */
	err = fcp_usb(mixer, cmd.opcode,
		      data, cmd.req_size, data, cmd.resp_size);
	if (err < 0)
		return err;

	/* copy response to user */
	if (cmd.resp_size > 0)
		if (copy_to_user(arg->data, data, cmd.resp_size))
			return -EFAULT;

	return 0;
}

/* Validate the Level Meter map passed by the user */
static int validate_meter_map(const s16 *map, int map_size, int meter_slots)
{
	int i;

	for (i = 0; i < map_size; i++)
		if (map[i] < -1 || map[i] >= meter_slots)
			return -EINVAL;

	return 0;
}

/* Set the Level Meter map and add the control */
static int fcp_ioctl_set_meter_map(struct usb_mixer_interface *mixer,
				   struct fcp_meter_map __user *arg)
{
	struct fcp_meter_map map;
	struct fcp_data *private = mixer->private_data;
	s16 *tmp_map __free(kfree) = NULL;
	int err;

	if (copy_from_user(&map, arg, sizeof(map)))
		return -EFAULT;

	/* Don't allow changing the map size or meter slots once set */
	if (private->meter_ctl) {
		struct usb_mixer_elem_info *elem =
			private->meter_ctl->private_data;

		if (map.map_size != elem->channels ||
		    map.meter_slots != private->num_meter_slots)
			return -EINVAL;
	}

	/* Validate the map size */
	if (map.map_size < 1 || map.map_size > 255 ||
	    map.meter_slots < 1 || map.meter_slots > 255)
		return -EINVAL;

	/* Allocate and copy the map data */
	tmp_map = kmalloc_array(map.map_size, sizeof(s16), GFP_KERNEL);
	if (!tmp_map)
		return -ENOMEM;

	if (copy_from_user(tmp_map, arg->map, map.map_size * sizeof(s16)))
		return -EFAULT;

	err = validate_meter_map(tmp_map, map.map_size, map.meter_slots);
	if (err < 0)
		return err;

	/* If the control doesn't exist, create it */
	if (!private->meter_ctl) {
		s16 *new_map __free(kfree) = NULL;
		__le32 *meter_levels __free(kfree) = NULL;

		/* Allocate buffer for the map */
		new_map = kmalloc_array(map.map_size, sizeof(s16), GFP_KERNEL);
		if (!new_map)
			return -ENOMEM;

		/* Allocate buffer for reading meter levels */
		meter_levels = kmalloc_array(map.meter_slots, sizeof(__le32),
					     GFP_KERNEL);
		if (!meter_levels)
			return -ENOMEM;

		/* Create the Level Meter control */
		err = fcp_add_new_ctl(mixer, &fcp_meter_ctl, 0, map.map_size,
				      "Level Meter", &private->meter_ctl);
		if (err < 0)
			return err;

		/* Success; save the pointers in private and don't free them */
		private->meter_level_map = new_map;
		private->meter_levels = meter_levels;
		private->num_meter_slots = map.meter_slots;
		new_map = NULL;
		meter_levels = NULL;
	}

	/* Install the new map */
	memcpy(private->meter_level_map, tmp_map, map.map_size * sizeof(s16));

	return 0;
}

/* Set the Level Meter labels */
static int fcp_ioctl_set_meter_labels(struct usb_mixer_interface *mixer,
				      struct fcp_meter_labels __user *arg)
{
	struct fcp_meter_labels labels;
	struct fcp_data *private = mixer->private_data;
	unsigned int *tlv_data;
	unsigned int tlv_size, data_size;

	if (copy_from_user(&labels, arg, sizeof(labels)))
		return -EFAULT;

	/* Remove existing labels if size is zero */
	if (!labels.labels_size) {

		/* Clear TLV read/callback bits if labels were present */
		if (private->meter_labels_tlv) {
			private->meter_ctl->vd[0].access &=
				~(SNDRV_CTL_ELEM_ACCESS_TLV_READ |
				  SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK);
			snd_ctl_notify(mixer->chip->card,
				       SNDRV_CTL_EVENT_MASK_INFO,
				       &private->meter_ctl->id);
		}

		kfree(private->meter_labels_tlv);
		private->meter_labels_tlv = NULL;
		private->meter_labels_tlv_size = 0;

		return 0;
	}

	/* Validate size */
	if (labels.labels_size > 4096)
		return -EINVAL;

	/* Calculate padded data size */
	data_size = ALIGN(labels.labels_size, sizeof(unsigned int));

	/* Calculate total TLV size including header */
	tlv_size = sizeof(unsigned int) * 2 + data_size;

	/* Allocate, set up TLV header, and copy the labels data */
	tlv_data = kzalloc(tlv_size, GFP_KERNEL);
	if (!tlv_data)
		return -ENOMEM;
	tlv_data[0] = SNDRV_CTL_TLVT_FCP_CHANNEL_LABELS;
	tlv_data[1] = data_size;
	if (copy_from_user(&tlv_data[2], arg->labels, labels.labels_size)) {
		kfree(tlv_data);
		return -EFAULT;
	}

	/* Set TLV read/callback bits if labels weren't present */
	if (!private->meter_labels_tlv) {
		private->meter_ctl->vd[0].access |=
			SNDRV_CTL_ELEM_ACCESS_TLV_READ |
			SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK;
		snd_ctl_notify(mixer->chip->card,
			       SNDRV_CTL_EVENT_MASK_INFO,
			       &private->meter_ctl->id);
	}

	/* Swap in the new labels */
	kfree(private->meter_labels_tlv);
	private->meter_labels_tlv = tlv_data;
	private->meter_labels_tlv_size = tlv_size;

	return 0;
}

static int fcp_hwdep_open(struct snd_hwdep *hw, struct file *file)
{
	struct usb_mixer_interface *mixer = hw->private_data;
	struct fcp_data *private = mixer->private_data;

	if (!capable(CAP_SYS_RAWIO))
		return -EPERM;

	private->file = file;

	return 0;
}

static int fcp_hwdep_ioctl(struct snd_hwdep *hw, struct file *file,
			   unsigned int cmd, unsigned long arg)
{
	struct usb_mixer_interface *mixer = hw->private_data;
	struct fcp_data *private = mixer->private_data;
	void __user *argp = (void __user *)arg;

	guard(mutex)(&private->mutex);

	switch (cmd) {

	case FCP_IOCTL_PVERSION:
		return put_user(FCP_HWDEP_VERSION,
				(int __user *)argp) ? -EFAULT : 0;
		break;

	case FCP_IOCTL_INIT:
		return fcp_ioctl_init(mixer, argp);

	case FCP_IOCTL_CMD:
		if (!private->init)
			return -EINVAL;
		return fcp_ioctl_cmd(mixer, argp);

	case FCP_IOCTL_SET_METER_MAP:
		if (!private->init)
			return -EINVAL;
		return fcp_ioctl_set_meter_map(mixer, argp);

	case FCP_IOCTL_SET_METER_LABELS:
		if (!private->init)
			return -EINVAL;
		if (!private->meter_ctl)
			return -EINVAL;
		return fcp_ioctl_set_meter_labels(mixer, argp);

	default:
		return -ENOIOCTLCMD;
	}

	/* not reached */
}

static long fcp_hwdep_read(struct snd_hwdep *hw, char __user *buf,
			   long count, loff_t *offset)
{
	struct usb_mixer_interface *mixer = hw->private_data;
	struct fcp_data *private = mixer->private_data;
	unsigned long flags;
	long ret = 0;
	u32 event;

	if (count < sizeof(event))
		return -EINVAL;

	ret = wait_event_interruptible(private->notify.queue,
				       private->notify.event);
	if (ret)
		return ret;

	spin_lock_irqsave(&private->notify.lock, flags);
	event = private->notify.event;
	private->notify.event = 0;
	spin_unlock_irqrestore(&private->notify.lock, flags);

	if (copy_to_user(buf, &event, sizeof(event)))
		return -EFAULT;

	return sizeof(event);
}

static __poll_t fcp_hwdep_poll(struct snd_hwdep *hw,
			       struct file *file,
			       poll_table *wait)
{
	struct usb_mixer_interface *mixer = hw->private_data;
	struct fcp_data *private = mixer->private_data;
	__poll_t mask = 0;

	poll_wait(file, &private->notify.queue, wait);

	if (private->notify.event)
		mask |= EPOLLIN | EPOLLRDNORM;

	return mask;
}

static int fcp_hwdep_release(struct snd_hwdep *hw, struct file *file)
{
	struct usb_mixer_interface *mixer = hw->private_data;
	struct fcp_data *private = mixer->private_data;

	if (!private)
		return 0;

	private->file = NULL;

	return 0;
}

static int fcp_hwdep_init(struct usb_mixer_interface *mixer)
{
	struct snd_hwdep *hw;
	int err;

	err = snd_hwdep_new(mixer->chip->card, "Focusrite Control", 0, &hw);
	if (err < 0)
		return err;

	hw->private_data = mixer;
	hw->exclusive = 1;
	hw->ops.open = fcp_hwdep_open;
	hw->ops.ioctl = fcp_hwdep_ioctl;
	hw->ops.ioctl_compat = fcp_hwdep_ioctl;
	hw->ops.read = fcp_hwdep_read;
	hw->ops.poll = fcp_hwdep_poll;
	hw->ops.release = fcp_hwdep_release;

	return 0;
}

/*** Cleanup ***/

static void fcp_cleanup_urb(struct usb_mixer_interface *mixer)
{
	if (!mixer->urb)
		return;

	usb_kill_urb(mixer->urb);
	kfree(mixer->urb->transfer_buffer);
	usb_free_urb(mixer->urb);
	mixer->urb = NULL;
}

static void fcp_private_free(struct usb_mixer_interface *mixer)
{
	struct fcp_data *private = mixer->private_data;

	fcp_cleanup_urb(mixer);

	kfree(private->meter_level_map);
	kfree(private->meter_levels);
	kfree(private->meter_labels_tlv);
	kfree(private);
	mixer->private_data = NULL;
}

static void fcp_private_suspend(struct usb_mixer_interface *mixer)
{
	fcp_cleanup_urb(mixer);
}

/*** Callbacks ***/

static void fcp_notify(struct urb *urb)
{
	struct usb_mixer_interface *mixer = urb->context;
	struct fcp_data *private = mixer->private_data;
	int len = urb->actual_length;
	int ustatus = urb->status;
	u32 data;

	if (ustatus != 0 || len != 8)
		goto requeue;

	data = le32_to_cpu(*(__le32 *)urb->transfer_buffer);

	/* Handle command acknowledgement */
	if (data & FCP_NOTIFY_ACK) {
		complete(&private->cmd_done);
		data &= ~FCP_NOTIFY_ACK;
	}

	if (data) {
		unsigned long flags;

		spin_lock_irqsave(&private->notify.lock, flags);
		private->notify.event |= data;
		spin_unlock_irqrestore(&private->notify.lock, flags);

		wake_up_interruptible(&private->notify.queue);
	}

requeue:
	if (ustatus != -ENOENT &&
	    ustatus != -ECONNRESET &&
	    ustatus != -ESHUTDOWN) {
		urb->dev = mixer->chip->dev;
		usb_submit_urb(urb, GFP_ATOMIC);
	} else {
		complete(&private->cmd_done);
	}
}

/* Submit a URB to receive notifications from the device */
static int fcp_init_notify(struct usb_mixer_interface *mixer)
{
	struct usb_device *dev = mixer->chip->dev;
	struct fcp_data *private = mixer->private_data;
	unsigned int pipe = usb_rcvintpipe(dev, private->bEndpointAddress);
	void *transfer_buffer;
	int err;

	/* Already set up */
	if (mixer->urb)
		return 0;

	if (usb_pipe_type_check(dev, pipe))
		return -EINVAL;

	mixer->urb = usb_alloc_urb(0, GFP_KERNEL);
	if (!mixer->urb)
		return -ENOMEM;

	transfer_buffer = kmalloc(private->wMaxPacketSize, GFP_KERNEL);
	if (!transfer_buffer) {
		usb_free_urb(mixer->urb);
		mixer->urb = NULL;
		return -ENOMEM;
	}

	usb_fill_int_urb(mixer->urb, dev, pipe,
			 transfer_buffer, private->wMaxPacketSize,
			 fcp_notify, mixer, private->bInterval);

	init_completion(&private->cmd_done);

	err = usb_submit_urb(mixer->urb, GFP_KERNEL);
	if (err) {
		usb_audio_err(mixer->chip,
			      "%s: usb_submit_urb failed: %d\n",
			      __func__, err);
		kfree(transfer_buffer);
		usb_free_urb(mixer->urb);
		mixer->urb = NULL;
	}

	return err;
}

/*** Initialisation ***/

static int fcp_init(struct usb_mixer_interface *mixer,
		    void *step0_resp, void *step2_resp)
{
	struct fcp_data *private = mixer->private_data;
	struct usb_device *dev = mixer->chip->dev;
	int err;

	err = snd_usb_ctl_msg(dev, usb_rcvctrlpipe(dev, 0),
		FCP_USB_REQ_STEP0,
		USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_IN,
		0, private->bInterfaceNumber,
		step0_resp, private->step0_resp_size);
	if (err < 0)
		return err;

	err = fcp_init_notify(mixer);
	if (err < 0)
		return err;

	private->seq = 0;
	private->init = 1;

	err = fcp_usb(mixer, private->init1_opcode, NULL, 0, NULL, 0);
	if (err < 0)
		return err;

	err = fcp_usb(mixer, private->init2_opcode,
		      NULL, 0, step2_resp, private->step2_resp_size);
	if (err < 0)
		return err;

	return 0;
}

static int fcp_init_private(struct usb_mixer_interface *mixer)
{
	struct fcp_data *private =
		kzalloc(sizeof(struct fcp_data), GFP_KERNEL);

	if (!private)
		return -ENOMEM;

	mutex_init(&private->mutex);
	init_waitqueue_head(&private->notify.queue);
	spin_lock_init(&private->notify.lock);

	mixer->private_data = private;
	mixer->private_free = fcp_private_free;
	mixer->private_suspend = fcp_private_suspend;

	private->mixer = mixer;

	return 0;
}

/* Look through the interface descriptors for the Focusrite Control
 * interface (bInterfaceClass = 255 Vendor Specific Class) and set
 * bInterfaceNumber, bEndpointAddress, wMaxPacketSize, and bInterval
 * in private
 */
static int fcp_find_fc_interface(struct usb_mixer_interface *mixer)
{
	struct snd_usb_audio *chip = mixer->chip;
	struct fcp_data *private = mixer->private_data;
	struct usb_host_config *config = chip->dev->actconfig;
	int i;

	for (i = 0; i < config->desc.bNumInterfaces; i++) {
		struct usb_interface *intf = config->interface[i];
		struct usb_interface_descriptor *desc =
			&intf->altsetting[0].desc;
		struct usb_endpoint_descriptor *epd;

		if (desc->bInterfaceClass != 255)
			continue;

		epd = get_endpoint(intf->altsetting, 0);
		private->bInterfaceNumber = desc->bInterfaceNumber;
		private->bEndpointAddress = usb_endpoint_num(epd);
		private->wMaxPacketSize = le16_to_cpu(epd->wMaxPacketSize);
		private->bInterval = epd->bInterval;
		return 0;
	}

	usb_audio_err(chip, "Focusrite vendor-specific interface not found\n");
	return -EINVAL;
}

int snd_fcp_init(struct usb_mixer_interface *mixer)
{
	struct snd_usb_audio *chip = mixer->chip;
	int err;

	/* only use UAC_VERSION_2 */
	if (!mixer->protocol)
		return 0;

	err = fcp_init_private(mixer);
	if (err < 0)
		return err;

	err = fcp_find_fc_interface(mixer);
	if (err < 0)
		return err;

	err = fcp_hwdep_init(mixer);
	if (err < 0)
		return err;

	usb_audio_info(chip,
		"Focusrite Control Protocol Driver ready (pid=0x%04x); "
		"report any issues to "
		"https://github.com/geoffreybennett/fcp-support/issues",
		USB_ID_PRODUCT(chip->usb_id));

	return err;
}