// 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