usb: device_next: add new MIDI 2.0 device class

This adds a new USB device class (based on usb/device_next) that implements
revision 2.0 of the MIDIStreaming interface, a sub-class of the USB audio
device class. In practice, the MIDI interface is much more simple and has
little in common with Audio, so it makes sense to have it as a separate
class driver.

MIDI inputs and outputs are configured through the device tree, under a
node `compatible = "zephyr,usb-midi"`. As per the USB-MIDI2.0 spec,
a single usb-midi interface can convey up to 16 Universal MIDI groups,
comprising 16 channels each. Data is carried from/to the host via
so-called Group Terminals, that are organized in Group Terminal Blocks.
They are represented as children of the usb-midi interface in the device
tree.

From the Zephyr application programmer perspective, MIDI data is exchanged
with the host through the device associated with the `zephyr,usb-midi`
interface, using the following API:

* Send a Universal MIDI Packet to the host: `usb_midi_send(device, pkt)`
* Universal MIDI Packets from the host are delivered to the function passed
  in `usb_midi_set_ops(device, &{.rx_packet_cb = handler})`

Compliant USB-MIDI 2.0 devices are required to expose a USB-MIDI1.0
interface as alt setting 0, and the 2.0 interface on alt setting 1.
To avoid the extra complexity of generating backward compatible USB
descriptors and translating Universal MIDI Packets from/to the old
USB-MIDI1.0 format, this driver generates an empty MIDI1.0 interface
(without any input/output); and therefore will only be able to exchange
MIDI data when the host has explicitely enabled MIDI2.0 (alt setting 1).

This implementation is based on the following documents, which are referred
to in the inline comments:

* `midi20`:
    Universal Serial Bus Device Class Definition for MIDI Devices
    Release 2.0
    https://www.usb.org/sites/default/files/USB%20MIDI%20v2_0.pdf
* `ump112`:
    Universal MIDI Packet (UMP) Format and MIDI 2.0 Protocol
      With MIDI 1.0 Protocol in UMP Format
    Document Version 1.1.2
    https://midi.org/universal-midi-packet-ump-and-midi-2-0-protocol-specification

Signed-off-by: Titouan Christophe <moiandme@gmail.com>
This commit is contained in:
Titouan Christophe 2024-11-10 18:54:33 +01:00 committed by Benjamin Cabé
commit c525e7a0a5
9 changed files with 1150 additions and 0 deletions

View file

@ -11,3 +11,4 @@ New USB device support APIs
usbd_hid_device.rst usbd_hid_device.rst
uac2_device.rst uac2_device.rst
usbd_msc_device.rst usbd_msc_device.rst
usb_midi.rst

View file

@ -0,0 +1,12 @@
.. _usb_midi:
MIDI 2.0 Class device API
#########################
USB MIDI 2.0 device specific API defined in :zephyr_file:`include/zephyr/usb/class/usbd_midi2.h`.
API Reference
*************
.. doxygengroup:: usb_midi
.. doxygengroup:: midi_ump

View file

@ -0,0 +1,51 @@
# Copyright (c) 2024 Titouan Christophe
# SPDX-License-Identifier: Apache-2.0
description: MIDI2 device
compatible: "zephyr,midi2-device"
properties:
"#address-cells":
type: int
const: 1
"#size-cells":
type: int
const: 1
child-binding:
description: |
MIDI2 Group terminal block.
This represent a set of contiguous MIDI2 groups through which the
device exchange Universal MIDI Packets with the host.
properties:
reg:
type: array
required: true
description: |
First MIDI2 Group number (address) and number of Group Terminals (size)
in this MIDI2 Group Terminal Block.
The MIDI2 Groups 1 to 16 corresponds to address 0x0 to 0xf. There are
at most 16 addressable groups (of 16 chans each) per MIDI2 interface.
protocol:
type: string
enum:
- "use-midi-ci"
- "midi1-up-to-64b"
- "midi1-up-to-128b"
- "midi2"
description: |
Default MIDI protocol of the Group Terminals in this Block.
terminal-type:
type: string
default: "bidirectional"
enum:
- "bidirectional"
- "input-only"
- "output-only"
description: |
Type (data direction) of Group Terminals in this Block.

204
include/zephyr/audio/midi.h Normal file
View file

@ -0,0 +1,204 @@
/*
* Copyright (c) 2024 Titouan Christophe
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef ZEPHYR_INCLUDE_AUDIO_MIDI_H_
#define ZEPHYR_INCLUDE_AUDIO_MIDI_H_
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h>
/**
* @brief Universal MIDI Packet definitions
* @defgroup midi_ump MIDI2 Universal MIDI Packet definitions
* @ingroup audio_interface
* @since 4.1
* @version 0.1.0
* @see ump112: "Universal MIDI Packet (UMP) Format and MIDI 2.0 Protocol"
* Document version 1.1.2
* @{
*/
/**
* @brief Universal MIDI Packet container
*/
struct midi_ump {
uint32_t data[4]; /**< Raw content, in the CPU native endianness */
};
/**
* @defgroup midi_ump_mt Message types
* @ingroup midi_ump
* @see ump112: 2.1.4 Message Type (MT) Allocation
* @{
*/
/** Utility Messages */
#define UMP_MT_UTILITY 0x00
/** System Real Time and System Common Messages (except System Exclusive) */
#define UMP_MT_SYS_RT_COMMON 0x01
/** MIDI 1.0 Channel Voice Messages */
#define UMP_MT_MIDI1_CHANNEL_VOICE 0x02
/** 64 bits Data Messages (including System Exclusive) */
#define UMP_MT_DATA_64 0x03
/** MIDI 2.0 Channel Voice Messages */
#define UMP_MT_MIDI2_CHANNEL_VOICE 0x04
/** 128 bits Data Messages */
#define UMP_MT_DATA_128 0x05
/** Flex Data Messages */
#define UMP_MT_FLEX_DATA 0x0d
/** UMP Stream Message */
#define UMP_MT_UMP_STREAM 0x0f
/** @} */
/**
* @brief Message Type field of a Universal MIDI Packet
* @param[in] ump Universal MIDI Packet
*/
#define UMP_MT(ump) \
((ump).data[0] >> 28)
/**
* There are 16 UMP message types, each of which can be 1 to 4 uint32 long.
* Hence this packed representation of 16x2b array as an uint32 lookup table
*/
#define UMP_NUM_WORDS_LOOKUP_TABLE \
((0U << 0) | (0U << 2) | (0U << 4) | (1U << 6) | \
(1U << 8) | (3U << 10) | (0U << 12) | (0U << 14) | \
(1U << 16) | (1U << 18) | (1U << 20) | (2U << 22) | \
(2U << 24) | (3U << 26) | (3U << 28) | (3U << 30))
/**
* @brief Size of a Universal MIDI Packet, in 32bit words
* @param[in] ump Universal MIDI Packet
* @see ump112: 2.1.4 Message Type (MT) Allocation
*/
#define UMP_NUM_WORDS(ump) \
(1 + ((UMP_NUM_WORDS_LOOKUP_TABLE >> (2 * UMP_MT(ump))) & 3))
/**
* @brief MIDI group field of a Universal MIDI Packet
* @param[in] ump Universal MIDI Packet
*/
#define UMP_GROUP(ump) \
(((ump).data[0] >> 24) & 0x0f)
/**
* @brief Status byte of a MIDI channel voice or system message
* @param[in] ump Universal MIDI Packet (containing a MIDI1 event)
*/
#define UMP_MIDI_STATUS(ump) \
(((ump).data[0] >> 16) & 0xff)
/**
* @brief Command of a MIDI channel voice message
* @param[in] ump Universal MIDI Packet (containing a MIDI event)
* @see midi_ump_cmd
*/
#define UMP_MIDI_COMMAND(ump) \
(UMP_MIDI_STATUS(ump) >> 4)
/**
* @brief Channel of a MIDI channel voice message
* @param[in] ump Universal MIDI Packet (containing a MIDI event)
*/
#define UMP_MIDI_CHANNEL(ump) \
(UMP_MIDI_STATUS(ump) & 0x0f)
/**
* @brief First parameter of a MIDI1 channel voice or system message
* @param[in] ump Universal MIDI Packet (containing a MIDI1 message)
*/
#define UMP_MIDI1_P1(ump) \
(((ump).data[0] >> 8) & 0x7f)
/**
* @brief Second parameter of a MIDI1 channel voice or system message
* @param[in] ump Universal MIDI Packet (containing a MIDI1 message)
*/
#define UMP_MIDI1_P2(ump) \
((ump).data[0] & 0x7f)
/**
* @brief Initialize a UMP with a MIDI1 channel voice message
* @remark For messages that take a single parameter, p2 is ignored by the receiver.
* @param group The UMP group
* @param command The MIDI1 command
* @param channel The MIDI1 channel number
* @param p1 The 1st MIDI1 parameter
* @param p2 The 2nd MIDI1 parameter
*/
#define UMP_MIDI1_CHANNEL_VOICE(group, command, channel, p1, p2) \
(struct midi_ump) {.data = { \
(UMP_MT_MIDI1_CHANNEL_VOICE << 28) \
| (((group) & 0x0f) << 24) \
| (((command) & 0x0f) << 20) \
| (((channel) & 0x0f) << 16) \
| (((p1) & 0x7f) << 8) \
| ((p2) & 0x7f) \
}}
/**
* @defgroup midi_ump_cmd MIDI commands
* @ingroup midi_ump
* @see ump112: 7.3 MIDI 1.0 Channel Voice Messages
*
* When UMP_MT(x)=UMP_MT_MIDI1_CHANNEL_VOICE or UMP_MT_MIDI2_CHANNEL_VOICE, then
* UMP_MIDI_COMMAND(x) may be one of:
* @{
*/
#define UMP_MIDI_NOTE_OFF 0x8 /**< Note Off (p1=note number, p2=velocity) */
#define UMP_MIDI_NOTE_ON 0x9 /**< Note On (p1=note number, p2=velocity) */
#define UMP_MIDI_AFTERTOUCH 0xa /**< Polyphonic aftertouch (p1=note number, p2=data) */
#define UMP_MIDI_CONTROL_CHANGE 0xb /**< Control Change (p1=index, p2=data) */
#define UMP_MIDI_PROGRAM_CHANGE 0xc /**< Control Change (p1=program) */
#define UMP_MIDI_CHAN_AFTERTOUCH 0xd /**< Channel aftertouch (p1=data) */
#define UMP_MIDI_PITCH_BEND 0xe /**< Pitch bend (p1=lsb, p2=msb) */
/** @} */
/**
* @brief Initialize a UMP with a System Real Time and System Common Message
* @remark For messages that take only one (or no) parameter, p2 (and p1)
* are ignored by the receiver.
* @param group The UMP group
* @param status The status byte
* @param p1 The 1st parameter
* @param p2 The 2nd parameter
*/
#define UMP_SYS_RT_COMMON(group, status, p1, p2) \
(struct midi_ump) {.data = { \
(UMP_MT_SYS_RT_COMMON << 28) \
| (((group) & 0x0f) << 24) \
| ((status) << 16) \
| (((p1) & 0x7f) << 8) \
| ((p2) & 0x7f) \
}}
/**
* @defgroup midi_ump_sys System common and System Real Time message status
* @ingroup midi_ump
* @see ump112: 7.6 System Common and System Real Time Messages
*
* When UMP_MT(x)=UMP_MT_SYS_RT_COMMON, UMP_MIDI_STATUS(x) may be one of:
* @{
*/
#define UMP_SYS_MIDI_TIME_CODE 0xf1 /**< MIDI Time Code (no param) */
#define UMP_SYS_SONG_POSITION 0xf2 /**< Song Position Pointer (p1=lsb, p2=msb) */
#define UMP_SYS_SONG_SELECT 0xf3 /**< Song Select (p1=song number) */
#define UMP_SYS_TUNE_REQUEST 0xf6 /**< Tune Request (no param) */
#define UMP_SYS_TIMING_CLOCK 0xf8 /**< Timing Clock (no param) */
#define UMP_SYS_START 0xfa /**< Start (no param) */
#define UMP_SYS_CONTINUE 0xfb /**< Continue (no param) */
#define UMP_SYS_STOP 0xfc /**< Stop (no param) */
#define UMP_SYS_ACTIVE_SENSING 0xfe /**< Active sensing (no param) */
#define UMP_SYS_RESET 0xff /**< Reset (no param) */
/** @} */
/** @} */
#ifdef __cplusplus
}
#endif
#endif

View file

@ -0,0 +1,72 @@
/*
* Copyright (c) 2024 Titouan Christophe
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef ZEPHYR_INCLUDE_USB_CLASS_USBD_MIDI_H_
#define ZEPHYR_INCLUDE_USB_CLASS_USBD_MIDI_H_
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief USB MIDI 2.0 class device API
* @defgroup usb_midi USB MIDI 2.0 Class device API
* @ingroup usb
* @since 4.1
* @version 0.1.0
* @see midi20: "Universal Serial Bus Device Class Definition for MIDI Devices"
* Document Release 2.0 (May 5, 2020)
* @{
*/
#include <zephyr/device.h>
#include <zephyr/audio/midi.h>
/**
* @brief MIDI2 application event handlers
*/
struct usbd_midi_ops {
/**
* @brief Callback type for incoming Universal MIDI Packets from host
* @param[in] dev The MIDI2 device receiving the packet
* @param[in] ump The received packet in Universal MIDI Packet format
*/
void (*rx_packet_cb)(const struct device *dev, const struct midi_ump ump);
/**
* @brief Callback type for MIDI2 interface runtime status change
* @param[in] dev The MIDI2 device
* @param[in] ready True if the interface is enabled by the host
*/
void (*ready_cb)(const struct device *dev, const bool ready);
};
/**
* @brief Send a Universal MIDI Packet to the host
* @param[in] dev The MIDI2 device
* @param[in] ump The packet to send, in Universal MIDI Packet format
* @return 0 on success, all other values should be treated as error
* -EIO if USB MIDI 2.0 is not enabled by the host
* -ENOBUFS if there is no space in the TX buffer
*/
int usbd_midi_send(const struct device *dev, const struct midi_ump ump);
/**
* @brief Set the application event handlers on a USB MIDI device
* @param[in] dev The MIDI2 device
* @param[in] ops The event handlers. Pass NULL to reset all callbacks
*/
void usbd_midi_set_ops(const struct device *dev, const struct usbd_midi_ops *ops);
/**
* @}
*/
#ifdef __cplusplus
}
#endif
#endif

View file

@ -71,6 +71,11 @@ zephyr_library_sources_ifdef(
class/usbd_uac2.c class/usbd_uac2.c
) )
zephyr_library_sources_ifdef(
CONFIG_USBD_MIDI2_CLASS
class/usbd_midi2.c
)
zephyr_library_sources_ifdef( zephyr_library_sources_ifdef(
CONFIG_USBD_HID_SUPPORT CONFIG_USBD_HID_SUPPORT
class/usbd_hid.c class/usbd_hid.c

View file

@ -10,3 +10,4 @@ rsource "Kconfig.bt"
rsource "Kconfig.msc" rsource "Kconfig.msc"
rsource "Kconfig.uac2" rsource "Kconfig.uac2"
rsource "Kconfig.hid" rsource "Kconfig.hid"
rsource "Kconfig.midi2"

View file

@ -0,0 +1,17 @@
# Copyright (c) 2024 Titouan Christophe
#
# SPDX-License-Identifier: Apache-2.0
config USBD_MIDI2_CLASS
bool "USB MIDI 2.0 class support [EXPERIMENTAL]"
select RING_BUFFER
help
Enable the USB MIDI 2.0 device class support.
if USBD_MIDI2_CLASS
module = USBD_MIDI2
module-str = usbd midi2
source "subsys/logging/Kconfig.template.log_config"
endif

View file

@ -0,0 +1,787 @@
/*
* Copyright (c) 2024 Titouan Christophe
*
* SPDX-License-Identifier: Apache-2.0
*/
#define DT_DRV_COMPAT zephyr_midi2_device
#include <zephyr/drivers/usb/udc.h>
#include <zephyr/sys/byteorder.h>
#include <zephyr/sys/ring_buffer.h>
#include <zephyr/usb/class/usbd_midi2.h>
#include <zephyr/usb/usbd.h>
#include "usbd_uac2_macros.h"
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(usbd_midi2, CONFIG_USBD_MIDI2_LOG_LEVEL);
#define MIDI1_ALTERNATE 0x00
#define MIDI2_ALTERNATE 0x01
UDC_BUF_POOL_DEFINE(usbd_midi_buf_pool, DT_NUM_INST_STATUS_OKAY(DT_DRV_COMPAT) * 2, 512U,
sizeof(struct udc_buf_info), NULL);
#define MIDI_QUEUE_SIZE 64
/* midi20 A.1 MS Class-Specific Interface Descriptor Types */
#define CS_GR_TRM_BLOCK 0x26
/* midi20 A.1 MS Class-Specific Interface Descriptor Subtypes */
#define MS_HEADER 0x01
/* midi20 A.2 MS Class-Specific Endpoint Descriptor Subtypes */
#define MS_GENERAL 0x01
#define MS_GENERAL_2_0 0x02
/* midi20 A.3 MS Class-Specific Group Terminal Block Descriptor Subtypes */
#define GR_TRM_BLOCK_HEADER 0x01
#define GR_TRM_BLOCK 0x02
/* midi20 A.6 Group Terminal Block Type */
#define GR_TRM_BIDIRECTIONAL 0x00
#define GR_TRM_INPUT_ONLY 0x01
#define GR_TRM_OUTPUT_ONLY 0x02
/* midi20 A.7 Group Terminal Default MIDI Protocol */
#define USE_MIDI_CI 0x00
#define MIDI_1_0_UP_TO_64_BITS 0x01
#define MIDI_1_0_UP_TO_64_BITS_JRTS 0x02
#define MIDI_1_0_UP_TO_128_BITS 0x03
#define MIDI_1_0_UP_TO_128_BITS_JRTS 0x04
#define MIDI_2_0 0x11
#define MIDI_2_0_JRTS 0x12
/* midi20: B.2.2 Class-specific AC Interface Descriptor */
struct usb_midi_cs_ac_header_descriptor {
uint8_t bLength;
uint8_t bDescriptorType;
uint8_t bDescriptorSubtype;
uint16_t bcdADC;
uint16_t wTotalLength;
uint8_t bInCollection;
uint8_t baInterfaceNr1;
} __packed;
/* midi20 5.2.2.1 Class-Specific MS Interface Header Descriptor */
struct usb_midi_header_descriptor {
uint8_t bLength;
uint8_t bDescriptorType;
uint8_t bDescriptorSubtype;
uint16_t bcdMSC;
uint16_t wTotalLength;
} __packed;
/* midi20 5.3.2 Class-Specific MIDI Streaming Data Endpoint Descriptor */
struct usb_midi_cs_endpoint_descriptor {
uint8_t bLength;
uint8_t bDescriptorType;
uint8_t bDescriptorSubtype;
uint8_t bNumGrpTrmBlock;
uint8_t baAssoGrpTrmBlkID[16];
} __packed;
/* midi20 5.4.1 Class Specific Group Terminal Block Header Descriptor */
struct usb_midi_grptrm_header_descriptor {
uint8_t bLength;
uint8_t bDescriptorType;
uint8_t bDescriptorSubtype;
uint16_t wTotalLength;
} __packed;
/* midi20 5.4.2.1 Group Terminal Block Descriptor */
struct usb_midi_grptrm_block_descriptor {
uint8_t bLength;
uint8_t bDescriptorType;
uint8_t bDescriptorSubtype;
uint8_t bGrpTrmBlkID;
uint8_t bGrpTrmBlkType;
uint8_t nGroupTrm;
uint8_t nNumGroupTrm;
uint8_t iBlockItem;
uint8_t bMIDIProtocol;
uint16_t wMaxInputBandwidth;
uint16_t wMaxOutputBandwidth;
} __packed;
struct usbd_midi_descriptors {
struct usb_association_descriptor iad;
/* Standard AudioControl (AC) Interface Descriptor */
struct usb_if_descriptor if0_std;
struct usb_midi_cs_ac_header_descriptor if0_cs;
/* Empty MidiStreaming 1.0 on altsetting 0 */
struct usb_if_descriptor if1_0_std;
struct usb_midi_header_descriptor if1_0_ms_header;
struct usb_ep_descriptor if1_0_out_ep_fs;
struct usb_ep_descriptor if1_0_out_ep_hs;
struct usb_midi_cs_endpoint_descriptor if1_0_cs_out_ep;
struct usb_ep_descriptor if1_0_in_ep_fs;
struct usb_ep_descriptor if1_0_in_ep_hs;
struct usb_midi_cs_endpoint_descriptor if1_0_cs_in_ep;
/* MidiStreaming 2.0 on altsetting 1 */
struct usb_if_descriptor if1_1_std;
struct usb_midi_header_descriptor if1_1_ms_header;
struct usb_ep_descriptor if1_1_out_ep_fs;
struct usb_ep_descriptor if1_1_out_ep_hs;
struct usb_midi_cs_endpoint_descriptor if1_1_cs_out_ep;
struct usb_ep_descriptor if1_1_in_ep_fs;
struct usb_ep_descriptor if1_1_in_ep_hs;
struct usb_midi_cs_endpoint_descriptor if1_1_cs_in_ep;
/* MidiStreaming 2.0 Class-Specific Group Terminal Block Descriptors
* Retrievable by a Separate Get Request
*/
struct usb_midi_grptrm_header_descriptor grptrm_header;
struct usb_midi_grptrm_block_descriptor grptrm_blocks[16];
};
/* Device driver configuration */
struct usbd_midi_config {
struct usbd_midi_descriptors *desc;
struct usb_desc_header const **fs_descs;
struct usb_desc_header const **hs_descs;
};
/* Device driver data */
struct usbd_midi_data {
struct usbd_class_data *class_data;
struct k_work rx_work;
struct k_work tx_work;
uint8_t tx_queue_buf[MIDI_QUEUE_SIZE];
struct ring_buf tx_queue;
uint8_t altsetting;
struct usbd_midi_ops ops;
};
static void usbd_midi2_recv(const struct device *dev, struct net_buf *const buf)
{
struct usbd_midi_data *data = dev->data;
struct midi_ump ump;
LOG_HEXDUMP_DBG(buf->data, buf->len, "MIDI2 - Rx DATA");
while (buf->len >= 4) {
ump.data[0] = net_buf_pull_le32(buf);
for (size_t i = 1; i < UMP_NUM_WORDS(ump); i++) {
if (buf->len < 4) {
LOG_ERR("Incomplete UMP");
return;
}
ump.data[i] = net_buf_pull_le32(buf);
}
if (data->ops.rx_packet_cb) {
data->ops.rx_packet_cb(dev, ump);
}
}
if (buf->len) {
LOG_HEXDUMP_WRN(buf->data, buf->len, "Trailing data in Rx buffer");
}
}
static int usbd_midi_class_request(struct usbd_class_data *const class_data,
struct net_buf *const buf, const int err)
{
struct usbd_context *uds_ctx = usbd_class_get_ctx(class_data);
const struct device *dev = usbd_class_get_private(class_data);
struct usbd_midi_data *data = dev->data;
struct udc_buf_info *info = udc_get_buf_info(buf);
LOG_DBG("MIDI2 request for %s ep=%02X len=%d err=%d",
dev->name, info->ep, buf->len, err);
if (err && err != -ECONNABORTED) {
LOG_ERR("Transfer error %d", err);
}
if (USB_EP_DIR_IS_OUT(info->ep)) {
usbd_midi2_recv(dev, buf);
k_work_submit(&data->rx_work);
} else {
LOG_HEXDUMP_DBG(buf->data, buf->len, "Tx DATA complete");
if (ring_buf_size_get(&data->tx_queue)) {
k_work_submit(&data->tx_work);
}
}
return usbd_ep_buf_free(uds_ctx, buf);
}
static void usbd_midi_class_update(struct usbd_class_data *const class_data,
const uint8_t iface, const uint8_t alternate)
{
const struct device *dev = usbd_class_get_private(class_data);
bool ready = false;
struct usbd_midi_data *data = dev->data;
LOG_DBG("update for %s: if=%u, alt=%u", dev->name, iface, alternate);
switch (alternate) {
case MIDI1_ALTERNATE:
data->altsetting = MIDI1_ALTERNATE;
LOG_WRN("%s set USB-MIDI1.0 altsetting (not implemented !)", dev->name);
break;
case MIDI2_ALTERNATE:
data->altsetting = MIDI2_ALTERNATE;
ready = true;
LOG_INF("%s set USB-MIDI2.0 altsetting", dev->name);
break;
}
if (data->ops.ready_cb) {
data->ops.ready_cb(dev, ready);
}
}
static void usbd_midi_class_enable(struct usbd_class_data *const class_data)
{
const struct device *dev = usbd_class_get_private(class_data);
struct usbd_midi_data *data = dev->data;
if (data->altsetting == MIDI2_ALTERNATE && data->ops.ready_cb) {
data->ops.ready_cb(dev, true);
}
LOG_DBG("Enable %s", dev->name);
k_work_submit(&data->rx_work);
}
static void usbd_midi_class_disable(struct usbd_class_data *const class_data)
{
const struct device *dev = usbd_class_get_private(class_data);
struct usbd_midi_data *data = dev->data;
if (data->ops.ready_cb) {
data->ops.ready_cb(dev, false);
}
LOG_DBG("Disable %s", dev->name);
k_work_cancel(&data->rx_work);
}
static void usbd_midi_class_suspended(struct usbd_class_data *const class_data)
{
const struct device *dev = usbd_class_get_private(class_data);
struct usbd_midi_data *data = dev->data;
if (data->ops.ready_cb) {
data->ops.ready_cb(dev, false);
}
LOG_DBG("Suspend %s", dev->name);
k_work_cancel(&data->rx_work);
}
static void usbd_midi_class_resumed(struct usbd_class_data *const class_data)
{
const struct device *dev = usbd_class_get_private(class_data);
struct usbd_midi_data *data = dev->data;
if (data->altsetting == MIDI2_ALTERNATE && data->ops.ready_cb) {
data->ops.ready_cb(dev, true);
}
LOG_DBG("Resume %s", dev->name);
k_work_submit(&data->rx_work);
}
static int usbd_midi_class_cth(struct usbd_class_data *const class_data,
const struct usb_setup_packet *const setup,
struct net_buf *const buf)
{
const struct device *dev = usbd_class_get_private(class_data);
const struct usbd_midi_config *config = dev->config;
struct usbd_midi_data *data = dev->data;
size_t head_len = config->desc->grptrm_header.bLength;
size_t total_len = sys_le16_to_cpu(config->desc->grptrm_header.wTotalLength);
LOG_DBG("Control to host for %s", dev->name);
LOG_DBG(" bmRequestType=%02X bRequest=%02X wValue=%04X wIndex=%04X wLength=%04X",
setup->bmRequestType, setup->bRequest, setup->wValue, setup->wIndex,
setup->wLength);
/* Only support Group Terminal blocks retrieved with
* midi20 6. Class Specific Command: Group Terminal Blocks Descriptors Request
*/
if (data->altsetting != MIDI2_ALTERNATE ||
setup->bRequest != USB_SREQ_GET_DESCRIPTOR ||
setup->wValue != ((CS_GR_TRM_BLOCK << 8) | MIDI2_ALTERNATE)) {
errno = -ENOTSUP;
return 0;
}
/* Group terminal block header */
net_buf_add_mem(buf, (void *) &config->desc->grptrm_header,
MIN(head_len, setup->wLength));
/* Group terminal blocks */
if (setup->wLength > head_len && total_len > head_len) {
net_buf_add_mem(buf, (void *) &config->desc->grptrm_blocks,
MIN(total_len, setup->wLength) - head_len);
}
LOG_HEXDUMP_DBG(buf->data, buf->len, "Control to host");
return 0;
}
static int usbd_midi_class_init(struct usbd_class_data *const class_data)
{
const struct device *dev = usbd_class_get_private(class_data);
LOG_DBG("Init %s device class", dev->name);
return 0;
}
static void *usbd_midi_class_get_desc(struct usbd_class_data *const class_data,
const enum usbd_speed speed)
{
const struct device *dev = usbd_class_get_private(class_data);
const struct usbd_midi_config *config = dev->config;
LOG_DBG("Get descriptors for %s", dev->name);
return (speed == USBD_SPEED_HS) ? config->hs_descs : config->fs_descs;
}
static struct usbd_class_api usbd_midi_class_api = {
.request = usbd_midi_class_request,
.update = usbd_midi_class_update,
.enable = usbd_midi_class_enable,
.disable = usbd_midi_class_disable,
.suspended = usbd_midi_class_suspended,
.resumed = usbd_midi_class_resumed,
.control_to_host = usbd_midi_class_cth,
.init = usbd_midi_class_init,
.get_desc = usbd_midi_class_get_desc,
};
static struct net_buf *usbd_midi_buf_alloc(uint8_t ep)
{
struct udc_buf_info *info;
struct net_buf *buf;
buf = net_buf_alloc(&usbd_midi_buf_pool, K_NO_WAIT);
if (!buf) {
return NULL;
}
info = udc_get_buf_info(buf);
info->ep = ep;
return buf;
}
static uint8_t usbd_midi_get_bulk_in(struct usbd_class_data *const class_data)
{
struct usbd_context *uds_ctx = usbd_class_get_ctx(class_data);
const struct device *dev = usbd_class_get_private(class_data);
const struct usbd_midi_config *cfg = dev->config;
if (usbd_bus_speed(uds_ctx) == USBD_SPEED_HS) {
return cfg->desc->if1_1_in_ep_hs.bEndpointAddress;
}
return cfg->desc->if1_1_in_ep_fs.bEndpointAddress;
}
static uint8_t usbd_midi_get_bulk_out(struct usbd_class_data *const class_data)
{
struct usbd_context *uds_ctx = usbd_class_get_ctx(class_data);
const struct device *dev = usbd_class_get_private(class_data);
const struct usbd_midi_config *cfg = dev->config;
if (usbd_bus_speed(uds_ctx) == USBD_SPEED_HS) {
return cfg->desc->if1_1_out_ep_hs.bEndpointAddress;
}
return cfg->desc->if1_1_out_ep_fs.bEndpointAddress;
}
static void usbd_midi_rx_work(struct k_work *work)
{
struct usbd_midi_data *data = CONTAINER_OF(work, struct usbd_midi_data, rx_work);
struct net_buf *buf;
int ret;
buf = usbd_midi_buf_alloc(usbd_midi_get_bulk_out(data->class_data));
if (buf == NULL) {
LOG_WRN("Unable to allocate Rx net_buf");
return;
}
LOG_DBG("Enqueue Rx...");
ret = usbd_ep_enqueue(data->class_data, buf);
if (ret) {
LOG_ERR("Failed to enqueue Rx net_buf -> %d", ret);
net_buf_unref(buf);
}
}
static void usbd_midi_tx_work(struct k_work *work)
{
struct usbd_midi_data *data = CONTAINER_OF(work, struct usbd_midi_data, tx_work);
struct net_buf *buf;
int ret;
buf = usbd_midi_buf_alloc(usbd_midi_get_bulk_in(data->class_data));
if (buf == NULL) {
LOG_ERR("Unable to allocate Tx net_buf");
return;
}
net_buf_add(buf, ring_buf_get(&data->tx_queue, buf->data, buf->size));
LOG_HEXDUMP_DBG(buf->data, buf->len, "MIDI2 - Tx DATA");
ret = usbd_ep_enqueue(data->class_data, buf);
if (ret) {
LOG_ERR("Failed to enqueue Tx net_buf -> %d", ret);
net_buf_unref(buf);
}
}
static int usbd_midi_preinit(const struct device *dev)
{
struct usbd_midi_data *data = dev->data;
LOG_DBG("Init device %s", dev->name);
ring_buf_init(&data->tx_queue, MIDI_QUEUE_SIZE, data->tx_queue_buf);
k_work_init(&data->rx_work, usbd_midi_rx_work);
k_work_init(&data->tx_work, usbd_midi_tx_work);
return 0;
}
int usbd_midi_send(const struct device *dev, const struct midi_ump ump)
{
struct usbd_midi_data *data = dev->data;
size_t words = UMP_NUM_WORDS(ump);
size_t buflen = 4 * words;
uint32_t word;
LOG_DBG("Send MT=%X group=%X", UMP_MT(ump), UMP_GROUP(ump));
if (data->altsetting != MIDI2_ALTERNATE) {
LOG_WRN("MIDI2.0 is not enabled");
return -EIO;
}
if (buflen > ring_buf_space_get(&data->tx_queue)) {
LOG_WRN("Not enough space in tx queue");
return -ENOBUFS;
}
for (size_t i = 0; i < words; i++) {
word = sys_cpu_to_le32(ump.data[i]);
ring_buf_put(&data->tx_queue, (const uint8_t *)&word, 4);
}
k_work_submit(&data->tx_work);
return 0;
}
void usbd_midi_set_ops(const struct device *dev, const struct usbd_midi_ops *ops)
{
struct usbd_midi_data *data = dev->data;
if (ops == NULL) {
memset(&data->ops, 0, sizeof(struct usbd_midi_ops));
} else {
memcpy(&data->ops, ops, sizeof(struct usbd_midi_ops));
}
LOG_DBG("Set ops for %s to %p", dev->name, ops);
}
/* Group Terminal Block unique identification number, type and protocol
* see midi20 5.4.2 Group Terminal Block Descriptor
*/
#define GRPTRM_BLOCK_ID(node) UTIL_INC(DT_NODE_CHILD_IDX(node))
#define GRPTRM_BLOCK_TYPE(node) \
COND_CODE_1(DT_ENUM_HAS_VALUE(node, terminal_type, input_only), \
(GR_TRM_INPUT_ONLY), \
(COND_CODE_1(DT_ENUM_HAS_VALUE(node, terminal_type, output_only), \
(GR_TRM_OUTPUT_ONLY), \
(GR_TRM_BIDIRECTIONAL) \
)) \
)
#define GRPTRM_PROTOCOL(node) \
COND_CODE_1(DT_ENUM_HAS_VALUE(node, protocol, midi2), \
(MIDI_2_0), \
(COND_CODE_1(DT_ENUM_HAS_VALUE(node, protocol, midi1_up_to_64b), \
(MIDI_1_0_UP_TO_64_BITS), \
(COND_CODE_1(DT_ENUM_HAS_VALUE(node, protocol, midi1_up_to_128b), \
(MIDI_1_0_UP_TO_128_BITS), \
(USE_MIDI_CI) \
)) \
)) \
)
/* Group Terminal Block unique identification number with a trailing comma
* if that block is bidirectional or of given terminal type; otherwise empty
*/
#define GRPTRM_BLOCK_ID_SEP_IF(node, ttype) \
IF_ENABLED( \
UTIL_OR(DT_ENUM_HAS_VALUE(node, terminal_type, bidirectional), \
DT_ENUM_HAS_VALUE(node, terminal_type, ttype)), \
(GRPTRM_BLOCK_ID(node),))
/* All unique identification numbers of output+bidir group terminal blocks */
#define GRPTRM_OUTPUT_BLOCK_IDS(n) \
DT_INST_FOREACH_CHILD_VARGS(n, GRPTRM_BLOCK_ID_SEP_IF, output_only)
/* All unique identification numbers of input+bidir group terminal blocks */
#define GRPTRM_INPUT_BLOCK_IDS(n) \
DT_INST_FOREACH_CHILD_VARGS(n, GRPTRM_BLOCK_ID_SEP_IF, input_only)
#define N_INPUTS(n) sizeof((uint8_t[]){GRPTRM_INPUT_BLOCK_IDS(n)})
#define N_OUTPUTS(n) sizeof((uint8_t[]){GRPTRM_OUTPUT_BLOCK_IDS(n)})
#define USBD_MIDI_VALIDATE_GRPTRM_BLOCK(node) \
BUILD_ASSERT(DT_REG_ADDR(node) < 16, \
"Group Terminal Block address must be within 0..15"); \
BUILD_ASSERT(DT_REG_ADDR(node) + DT_REG_SIZE(node) <= 16, \
"Too many Group Terminals in this Block");
#define USBD_MIDI_VALIDATE_INSTANCE(n) \
DT_INST_FOREACH_CHILD(n, USBD_MIDI_VALIDATE_GRPTRM_BLOCK)
#define USBD_MIDI2_INIT_GRPTRM_BLOCK_DESCRIPTOR(node) \
{ \
.bLength = sizeof(struct usb_midi_grptrm_block_descriptor), \
.bDescriptorType = CS_GR_TRM_BLOCK, \
.bDescriptorSubtype = GR_TRM_BLOCK, \
.bGrpTrmBlkID = GRPTRM_BLOCK_ID(node), \
.bGrpTrmBlkType = GRPTRM_BLOCK_TYPE(node), \
.nGroupTrm = DT_REG_ADDR(node), \
.nNumGroupTrm = DT_REG_SIZE(node), \
.iBlockItem = 0, \
.bMIDIProtocol = GRPTRM_PROTOCOL(node), \
.wMaxInputBandwidth = 0x0000, \
.wMaxOutputBandwidth = 0x0000, \
}
#define USBD_MIDI2_GRPTRM_TOTAL_LEN(n) \
sizeof(struct usb_midi_grptrm_header_descriptor) \
+ DT_INST_CHILD_NUM_STATUS_OKAY(n) \
* sizeof(struct usb_midi_grptrm_block_descriptor)
#define USBD_MIDI_DEFINE_DESCRIPTORS(n) \
static struct usbd_midi_descriptors usbd_midi_desc_##n = { \
.iad = { \
.bLength = sizeof(struct usb_association_descriptor), \
.bDescriptorType = USB_DESC_INTERFACE_ASSOC, \
.bFirstInterface = 0, \
.bInterfaceCount = 2, \
.bFunctionClass = AUDIO, \
.bFunctionSubClass = MIDISTREAMING, \
}, \
.if0_std = { \
.bLength = sizeof(struct usb_if_descriptor), \
.bDescriptorType = USB_DESC_INTERFACE, \
.bInterfaceNumber = 0, \
.bAlternateSetting = 0, \
.bNumEndpoints = 0, \
.bInterfaceClass = AUDIO, \
.bInterfaceSubClass = AUDIOCONTROL, \
}, \
.if0_cs = { \
.bLength = sizeof(struct usb_midi_cs_ac_header_descriptor), \
.bDescriptorType = USB_DESC_CS_INTERFACE, \
.bDescriptorSubtype = MS_HEADER, \
.bcdADC = sys_cpu_to_le16(0x0100), \
.wTotalLength = sizeof(struct usb_midi_cs_ac_header_descriptor), \
.bInCollection = 1, \
.baInterfaceNr1 = 1, \
}, \
.if1_0_std = { \
.bLength = sizeof(struct usb_if_descriptor), \
.bDescriptorType = USB_DESC_INTERFACE, \
.bInterfaceNumber = 1, \
.bAlternateSetting = MIDI1_ALTERNATE, \
.bNumEndpoints = 2, \
.bInterfaceClass = AUDIO, \
.bInterfaceSubClass = MIDISTREAMING, \
}, \
.if1_0_ms_header = { \
.bLength = sizeof(struct usb_midi_header_descriptor), \
.bDescriptorType = USB_DESC_CS_INTERFACE, \
.bDescriptorSubtype = MS_HEADER, \
.bcdMSC = sys_cpu_to_le16(0x0100), \
.wTotalLength = sys_cpu_to_le16( \
sizeof(struct usb_midi_header_descriptor) \
+ 2 * (sizeof(struct usb_ep_descriptor) + 4) \
), \
}, \
.if1_0_out_ep_fs = { \
.bLength = sizeof(struct usb_ep_descriptor), \
.bDescriptorType = USB_DESC_ENDPOINT, \
.bEndpointAddress = n + FIRST_OUT_EP_ADDR, \
.bmAttributes = USB_EP_TYPE_BULK, \
.wMaxPacketSize = sys_cpu_to_le16(64U), \
}, \
.if1_0_out_ep_hs = { \
.bLength = sizeof(struct usb_ep_descriptor), \
.bDescriptorType = USB_DESC_ENDPOINT, \
.bEndpointAddress = n + FIRST_OUT_EP_ADDR, \
.bmAttributes = USB_EP_TYPE_BULK, \
.wMaxPacketSize = sys_cpu_to_le16(512U), \
}, \
.if1_0_cs_out_ep = { \
.bLength = 4, \
.bDescriptorType = USB_DESC_CS_ENDPOINT, \
.bDescriptorSubtype = MS_GENERAL, \
.bNumGrpTrmBlock = 0, \
}, \
.if1_0_in_ep_fs = { \
.bLength = sizeof(struct usb_ep_descriptor), \
.bDescriptorType = USB_DESC_ENDPOINT, \
.bEndpointAddress = n + FIRST_IN_EP_ADDR, \
.bmAttributes = USB_EP_TYPE_BULK, \
.wMaxPacketSize = sys_cpu_to_le16(64U), \
}, \
.if1_0_in_ep_hs = { \
.bLength = sizeof(struct usb_ep_descriptor), \
.bDescriptorType = USB_DESC_ENDPOINT, \
.bEndpointAddress = n + FIRST_IN_EP_ADDR, \
.bmAttributes = USB_EP_TYPE_BULK, \
.wMaxPacketSize = sys_cpu_to_le16(512U), \
}, \
.if1_0_cs_in_ep = { \
.bLength = 4 + N_INPUTS(n), \
.bDescriptorType = USB_DESC_CS_ENDPOINT, \
.bDescriptorSubtype = MS_GENERAL, \
.bNumGrpTrmBlock = 0, \
}, \
.if1_1_std = { \
.bLength = sizeof(struct usb_if_descriptor), \
.bDescriptorType = USB_DESC_INTERFACE, \
.bInterfaceNumber = 1, \
.bAlternateSetting = MIDI2_ALTERNATE, \
.bNumEndpoints = 2, \
.bInterfaceClass = AUDIO, \
.bInterfaceSubClass = MIDISTREAMING, \
}, \
.if1_1_ms_header = { \
.bLength = sizeof(struct usb_midi_header_descriptor), \
.bDescriptorType = USB_DESC_CS_INTERFACE, \
.bDescriptorSubtype = MS_HEADER, \
.bcdMSC = sys_cpu_to_le16(0x0200), \
.wTotalLength = sys_cpu_to_le16( \
sizeof(struct usb_midi_header_descriptor)), \
}, \
.if1_1_out_ep_fs = { \
.bLength = sizeof(struct usb_ep_descriptor), \
.bDescriptorType = USB_DESC_ENDPOINT, \
.bEndpointAddress = n + FIRST_OUT_EP_ADDR, \
.bmAttributes = USB_EP_TYPE_BULK, \
.wMaxPacketSize = sys_cpu_to_le16(64U), \
}, \
.if1_1_out_ep_hs = { \
.bLength = sizeof(struct usb_ep_descriptor), \
.bDescriptorType = USB_DESC_ENDPOINT, \
.bEndpointAddress = n + FIRST_OUT_EP_ADDR, \
.bmAttributes = USB_EP_TYPE_BULK, \
.wMaxPacketSize = sys_cpu_to_le16(512U), \
}, \
.if1_1_cs_out_ep = { \
.bLength = 4 + N_OUTPUTS(n), \
.bDescriptorType = USB_DESC_CS_ENDPOINT, \
.bDescriptorSubtype = MS_GENERAL_2_0, \
.bNumGrpTrmBlock = N_OUTPUTS(n), \
.baAssoGrpTrmBlkID = {GRPTRM_OUTPUT_BLOCK_IDS(n)}, \
}, \
.if1_1_in_ep_fs = { \
.bLength = sizeof(struct usb_ep_descriptor), \
.bDescriptorType = USB_DESC_ENDPOINT, \
.bEndpointAddress = n + FIRST_IN_EP_ADDR, \
.bmAttributes = USB_EP_TYPE_BULK, \
.wMaxPacketSize = sys_cpu_to_le16(64U), \
}, \
.if1_1_in_ep_hs = { \
.bLength = sizeof(struct usb_ep_descriptor), \
.bDescriptorType = USB_DESC_ENDPOINT, \
.bEndpointAddress = n + FIRST_IN_EP_ADDR, \
.bmAttributes = USB_EP_TYPE_BULK, \
.wMaxPacketSize = sys_cpu_to_le16(512U), \
}, \
.if1_1_cs_in_ep = { \
.bLength = 4 + N_INPUTS(n), \
.bDescriptorType = USB_DESC_CS_ENDPOINT, \
.bDescriptorSubtype = MS_GENERAL_2_0, \
.bNumGrpTrmBlock = N_INPUTS(n), \
.baAssoGrpTrmBlkID = {GRPTRM_INPUT_BLOCK_IDS(n)}, \
}, \
.grptrm_header = { \
.bLength = sizeof(struct usb_midi_grptrm_header_descriptor), \
.bDescriptorType = CS_GR_TRM_BLOCK, \
.bDescriptorSubtype = GR_TRM_BLOCK_HEADER, \
.wTotalLength = sys_cpu_to_le16( \
USBD_MIDI2_GRPTRM_TOTAL_LEN(n) \
), \
}, \
.grptrm_blocks = { \
DT_INST_FOREACH_CHILD_SEP( \
n, USBD_MIDI2_INIT_GRPTRM_BLOCK_DESCRIPTOR, (,) \
) \
}, \
}; \
static const struct usb_desc_header *usbd_midi_desc_array_fs_##n[] = { \
(struct usb_desc_header *)&usbd_midi_desc_##n.iad, \
(struct usb_desc_header *)&usbd_midi_desc_##n.if0_std, \
(struct usb_desc_header *)&usbd_midi_desc_##n.if0_cs, \
(struct usb_desc_header *)&usbd_midi_desc_##n.if1_0_std, \
(struct usb_desc_header *)&usbd_midi_desc_##n.if1_0_ms_header, \
(struct usb_desc_header *)&usbd_midi_desc_##n.if1_0_out_ep_fs, \
(struct usb_desc_header *)&usbd_midi_desc_##n.if1_0_cs_out_ep, \
(struct usb_desc_header *)&usbd_midi_desc_##n.if1_0_in_ep_fs, \
(struct usb_desc_header *)&usbd_midi_desc_##n.if1_0_cs_in_ep, \
(struct usb_desc_header *)&usbd_midi_desc_##n.if1_1_std, \
(struct usb_desc_header *)&usbd_midi_desc_##n.if1_1_ms_header, \
(struct usb_desc_header *)&usbd_midi_desc_##n.if1_1_out_ep_fs, \
(struct usb_desc_header *)&usbd_midi_desc_##n.if1_1_cs_out_ep, \
(struct usb_desc_header *)&usbd_midi_desc_##n.if1_1_in_ep_fs, \
(struct usb_desc_header *)&usbd_midi_desc_##n.if1_1_cs_in_ep, \
NULL, \
}; \
static const struct usb_desc_header *usbd_midi_desc_array_hs_##n[] = { \
(struct usb_desc_header *)&usbd_midi_desc_##n.iad, \
(struct usb_desc_header *)&usbd_midi_desc_##n.if0_std, \
(struct usb_desc_header *)&usbd_midi_desc_##n.if0_cs, \
(struct usb_desc_header *)&usbd_midi_desc_##n.if1_0_std, \
(struct usb_desc_header *)&usbd_midi_desc_##n.if1_0_ms_header, \
(struct usb_desc_header *)&usbd_midi_desc_##n.if1_0_out_ep_hs, \
(struct usb_desc_header *)&usbd_midi_desc_##n.if1_0_cs_out_ep, \
(struct usb_desc_header *)&usbd_midi_desc_##n.if1_0_in_ep_hs, \
(struct usb_desc_header *)&usbd_midi_desc_##n.if1_0_cs_in_ep, \
(struct usb_desc_header *)&usbd_midi_desc_##n.if1_1_std, \
(struct usb_desc_header *)&usbd_midi_desc_##n.if1_1_ms_header, \
(struct usb_desc_header *)&usbd_midi_desc_##n.if1_1_out_ep_hs, \
(struct usb_desc_header *)&usbd_midi_desc_##n.if1_1_cs_out_ep, \
(struct usb_desc_header *)&usbd_midi_desc_##n.if1_1_in_ep_hs, \
(struct usb_desc_header *)&usbd_midi_desc_##n.if1_1_cs_in_ep, \
NULL, \
};
#define USBD_MIDI_DEFINE_DEVICE(n) \
USBD_MIDI_VALIDATE_INSTANCE(n) \
USBD_MIDI_DEFINE_DESCRIPTORS(n); \
USBD_DEFINE_CLASS(midi_##n, &usbd_midi_class_api, \
(void *)DEVICE_DT_GET(DT_DRV_INST(n)), NULL); \
static const struct usbd_midi_config usbd_midi_config_##n = { \
.desc = &usbd_midi_desc_##n, \
.fs_descs = usbd_midi_desc_array_fs_##n, \
.hs_descs = usbd_midi_desc_array_hs_##n, \
}; \
static struct usbd_midi_data usbd_midi_data_##n = { \
.class_data = &midi_##n, \
.altsetting = MIDI1_ALTERNATE, \
}; \
DEVICE_DT_INST_DEFINE(n, usbd_midi_preinit, NULL, \
&usbd_midi_data_##n, &usbd_midi_config_##n, \
POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEVICE, NULL);
DT_INST_FOREACH_STATUS_OKAY(USBD_MIDI_DEFINE_DEVICE)