From 221c1933a7b55906493d1f8292aee71dda812fb3 Mon Sep 17 00:00:00 2001 From: Titouan Christophe Date: Sun, 10 Nov 2024 18:54:33 +0100 Subject: [PATCH] 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_callback(device, void (device, pkt){...})` 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 --- .../usb/device_next/api/index.rst | 1 + .../usb/device_next/api/usb_midi.rst | 11 + dts/bindings/usb/zephyr,usb-midi.yaml | 51 ++ include/zephyr/usb/class/midi.h | 138 ++++ include/zephyr/usb/class/usbd_midi.h | 51 ++ subsys/usb/device_next/CMakeLists.txt | 5 + subsys/usb/device_next/class/Kconfig | 1 + subsys/usb/device_next/class/Kconfig.midi | 17 + subsys/usb/device_next/class/usbd_midi.c | 727 ++++++++++++++++++ .../usb/device_next/class/usbd_uac2_macros.h | 2 + 10 files changed, 1004 insertions(+) create mode 100644 doc/connectivity/usb/device_next/api/usb_midi.rst create mode 100644 dts/bindings/usb/zephyr,usb-midi.yaml create mode 100644 include/zephyr/usb/class/midi.h create mode 100644 include/zephyr/usb/class/usbd_midi.h create mode 100644 subsys/usb/device_next/class/Kconfig.midi create mode 100644 subsys/usb/device_next/class/usbd_midi.c diff --git a/doc/connectivity/usb/device_next/api/index.rst b/doc/connectivity/usb/device_next/api/index.rst index 260658206640403..8468dedef3269e3 100644 --- a/doc/connectivity/usb/device_next/api/index.rst +++ b/doc/connectivity/usb/device_next/api/index.rst @@ -11,3 +11,4 @@ New USB device support APIs usbd_hid_device.rst uac2_device.rst usbd_msc_device.rst + usb_midi.rst diff --git a/doc/connectivity/usb/device_next/api/usb_midi.rst b/doc/connectivity/usb/device_next/api/usb_midi.rst new file mode 100644 index 000000000000000..bb38e8a3de8cf2d --- /dev/null +++ b/doc/connectivity/usb/device_next/api/usb_midi.rst @@ -0,0 +1,11 @@ +.. _usb_midi: + +MIDI 2.0 Class device API +######################### + +USB MIDI 2.0 device specific API defined in :zephyr_file:`include/zephyr/usb/class/usb_midi.h`. + +API Reference +************* + +.. doxygengroup:: usb_midi diff --git a/dts/bindings/usb/zephyr,usb-midi.yaml b/dts/bindings/usb/zephyr,usb-midi.yaml new file mode 100644 index 000000000000000..743f11ef265342e --- /dev/null +++ b/dts/bindings/usb/zephyr,usb-midi.yaml @@ -0,0 +1,51 @@ +# Copyright (c) 2024 Titouan Christophe +# SPDX-License-Identifier: Apache-2.0 + +description: USB MIDI Class + +compatible: "zephyr,usb-midi" + +properties: + "#address-cells": + type: int + const: 1 + + "#size-cells": + type: int + const: 1 + +child-binding: + description: | + USB MIDI Group terminal block. + This represent a set of contiguous Universal MIDI groups through which the + device exchange Universal MIDI Packets with the host. + + properties: + reg: + type: array + required: true + description: | + First MIDI Group number (address) and number of Group Terminals (size) + in this USB-MIDI Group Terminal Block. + The MIDI Groups 1 to 16 corresponds to address 0x0 to 0xf. There are at + most 16 addressable groups (of 16 channels each) per USB-MIDI 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. diff --git a/include/zephyr/usb/class/midi.h b/include/zephyr/usb/class/midi.h new file mode 100644 index 000000000000000..87176934f5bfe3c --- /dev/null +++ b/include/zephyr/usb/class/midi.h @@ -0,0 +1,138 @@ +/* + * Copyright (c) 2024 Titouan Christophe + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_INCLUDE_USB_CLASS_MIDI_H_ +#define ZEPHYR_INCLUDE_USB_CLASS_MIDI_H_ + +/** + * @brief Universal MIDI Packet definitions + * @defgroup usb_midi_ump USB MIDI 2.0 Universal MIDI Packet definitions + * @ingroup usb + * @since 4.1 + * @version 0.1.0 + * @see ump112: "Universal MIDI Packet (UMP) Format and MIDI 2.0 Protocol" + * Document version 1.1.2 + * @{ + * + * @defgroup usb_midi_ump_mt Message types + * @ingroup usb_midi_ump + * @see ump112: 2.1.4 Message Type (MT) Allocation + * @{ + */ + +#define UMP_MT_UTILITY 0x00 /**< Utility Messages */ +/** System Real Time and System Common Messages (except System Exclusive) */ +#define UMP_MT_SYS_RT_COMMON 0x01 +#define UMP_MT_MIDI1_CHANNEL_VOICE 0x02 /**< MIDI 1.0 Channel Voice Messages */ +#define UMP_MT_DATA 0x03 /**< Data Messages (including System Exclusive) */ +#define UMP_MT_MIDI2_CHANNEL_VOICE 0x04 /**< MIDI 2.0 Channel Voice Messages */ +#define UMP_MT_FLEX_DATA 0x0d /**< Flex Data Messages */ +#define UMP_MT_UMP_STREAM 0x0f /**< UMP Stream Message */ +/** @} */ + +/** + * @brief Message Type field of a Universal MIDI Packet + * @param[in] ump The universal MIDI Packet + */ +#define UMP_MT(ump) ((ump)[0] >> 28) + +/** + * @brief Size of a Universal MIDI Packet, in 32bit words + * @param[in] ump The universal MIDI Packet + * @see ump112: 2.1.4 Message Type (MT) Allocation + */ +#define UMP_WORDS(ump) (1 + ((0xfe950d40 >> (2 * UMP_MT(ump))) & 3)) + +/** + * @brief MIDI group field of a Universal MIDI Packet + * @param[in] ump The universal MIDI Packet + */ +#define UMP_GROUP(ump) (((ump)[0] >> 24) & 0x0f) + +/** + * @brief Status byte of a MIDI channel voice or system message + * @param[in] ump A universal MIDI Packet (containing a MIDI1 event) + */ +#define UMP_MIDI_STATUS(ump) (((ump)[0] >> 16) & 0xff) +/** + * @brief Command of a MIDI channel voice message + * @param[in] ump A universal MIDI Packet (containing a MIDI event) + * @see usb_midi_cmd + */ +#define UMP_MIDI_COMMAND(ump) (UMP_MIDI_STATUS(ump) >> 4) +/** + * @brief Channel of a MIDI channel voice message + * @param[in] ump A 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 A universal MIDI Packet (containing a MIDI1 event) + */ +#define UMP_MIDI1_P1(ump) (((ump)[0] >> 8) & 0x7f) +/** + * @brief Second parameter of a MIDI1 channel voice or system message + * @param[in] ump A universal MIDI Packet (containing a MIDI1 event) + */ +#define UMP_MIDI1_P2(ump) ((ump)[0] & 0x7f) + +/** + * @brief Initialize a MIDI1 Universal Midi Packet + * @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(group, command, channel, p1, p2) \ + (const uint32_t[]) \ + { \ + (UMP_MT_MIDI1_CHANNEL_VOICE << 28) | ((group & 0x0f) << 24) | \ + ((command & 0x0f) << 20) | ((channel & 0x0f) << 16) | ((p1 & 0x7f) << 8) | \ + (p2 & 0x7f) \ + } + +/** + * @defgroup usb_midi_ump_cmd MIDI commands + * @ingroup usb_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) */ +/** @} */ + +/** + * @defgroup usb_midi_ump_sys System common and System Real Time message status + * @ingroup usb_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) */ +/** @} */ + +/** @} */ + +#endif diff --git a/include/zephyr/usb/class/usbd_midi.h b/include/zephyr/usb/class/usbd_midi.h new file mode 100644 index 000000000000000..22ee919ce913e43 --- /dev/null +++ b/include/zephyr/usb/class/usbd_midi.h @@ -0,0 +1,51 @@ +/* + * 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_ + +/** + * @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 + +/** + * @brief Send a Universal MIDI Packet to the host + * @param[in] dev The USB-MIDI interface + * @param[in] ump The packet to send, in Universal MIDI Packet format + * @return 0 on success + * -EIO if MIDI2.0 is not enabled by the host + * -EAGAIN if there isn't room in the transmission buffer + */ +int usbd_midi_send(const struct device *dev, const uint32_t *ump); + +/** + * @brief Callback type for incoming Universal MIDI Packets from host + * @param[in] dev The USB-MIDI interface receiving the packet + * @param[in] ump The received packet in Universal MIDI Packet format + */ +typedef void (*usbd_midi_callback)(const struct device *dev, const uint32_t *ump); + +/** + * @brief Set a user callback to invoke when receiving a packet from host + * @param[in] dev The USB-MIDI interface + * @param[in] cb The function to call + */ +void usbd_midi_set_callback(const struct device *dev, usbd_midi_callback cb); + +/** + * @} + */ + +#endif diff --git a/subsys/usb/device_next/CMakeLists.txt b/subsys/usb/device_next/CMakeLists.txt index 7ef32c4dcdb7ab5..2e2d863f073a50a 100644 --- a/subsys/usb/device_next/CMakeLists.txt +++ b/subsys/usb/device_next/CMakeLists.txt @@ -71,6 +71,11 @@ zephyr_library_sources_ifdef( class/usbd_uac2.c ) +zephyr_library_sources_ifdef( + CONFIG_USBD_MIDI_CLASS + class/usbd_midi.c +) + zephyr_library_sources_ifdef( CONFIG_USBD_HID_SUPPORT class/usbd_hid.c diff --git a/subsys/usb/device_next/class/Kconfig b/subsys/usb/device_next/class/Kconfig index 9e7393fd3df8fa9..33fa6e47692c21b 100644 --- a/subsys/usb/device_next/class/Kconfig +++ b/subsys/usb/device_next/class/Kconfig @@ -10,3 +10,4 @@ rsource "Kconfig.bt" rsource "Kconfig.msc" rsource "Kconfig.uac2" rsource "Kconfig.hid" +rsource "Kconfig.midi" diff --git a/subsys/usb/device_next/class/Kconfig.midi b/subsys/usb/device_next/class/Kconfig.midi new file mode 100644 index 000000000000000..c6b01c8f41ee90d --- /dev/null +++ b/subsys/usb/device_next/class/Kconfig.midi @@ -0,0 +1,17 @@ +# Copyright (c) 2024 Titouan Christophe +# +# SPDX-License-Identifier: Apache-2.0 + +config USBD_MIDI_CLASS + bool "USB MIDI 2.0 class support [EXPERIMENTAL]" + select RING_BUFFER + help + Enable the USB MIDI 2.0 device class support. + +if USBD_MIDI_CLASS + +module = USBD_MIDI +module-str = usbd midi +source "subsys/logging/Kconfig.template.log_config" + +endif diff --git a/subsys/usb/device_next/class/usbd_midi.c b/subsys/usb/device_next/class/usbd_midi.c new file mode 100644 index 000000000000000..114252a3b1f2f76 --- /dev/null +++ b/subsys/usb/device_next/class/usbd_midi.c @@ -0,0 +1,727 @@ +/* + * Copyright (c) 2024 Titouan Christophe + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#define DT_DRV_COMPAT zephyr_usb_midi + +#include +#include +#include +#include +#include +#include +#include + +#include "usbd_uac2_macros.h" + +LOG_MODULE_REGISTER(usbd_midi, CONFIG_USBD_MIDI_LOG_LEVEL); + +#define ALT_USB_MIDI_1 0x00 +#define ALT_USB_MIDI_2 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 Subtypes */ +#define MS_DESCRIPTOR_UNDEFINED 0x00 +#define MS_HEADER 0x01 +#define MIDI_IN_JACK 0x02 +#define MIDI_OUT_JACK 0x03 +#define ELEMENT 0x04 + +/* midi20 A.2 MS Class-Specific Endpoint Descriptor Subtypes */ +#define DESCRIPTOR_UNDEFINED 0x00 +#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_UNDEFINED 0x00 +#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_AND_JRTS 0x02 +#define MIDI_1_0_UP_TO_128_BITS 0x03 +#define MIDI_1_0_UP_TO_128_BITS_AND_JRTS 0x04 +#define MIDI_2_0 0x11 +#define MIDI_2_0_AND_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 { + /* 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; + struct k_mutex tx_mutex; + uint8_t midi_if_index; + uint8_t altsetting; + usbd_midi_callback cb; +}; + +static void usbd_midi2_recv(const struct device *dev, struct net_buf *const buf) +{ + struct usbd_midi_data *data = dev->data; + uint32_t ump[4]; + + LOG_HEXDUMP_DBG(buf->data, buf->len, "MIDI2 - Rx DATA"); + while (buf->len >= 4) { + ump[0] = net_buf_pull_le32(buf); + for (size_t i = 1; i < UMP_WORDS(ump); i++) { + ump[i] = net_buf_pull_le32(buf); + } + + if (data->cb) { + data->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("USB-MIDI request for %s ep=%d len=%d err=%d", dev->name, info->ep, buf->len, err); + + if (err) { + LOG_ERR("Class request 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); + struct usbd_midi_data *data = dev->data; + + LOG_DBG("update for %s: if=%u, alt=%u", dev->name, iface, alternate); + + switch (alternate) { + case ALT_USB_MIDI_1: + data->altsetting = ALT_USB_MIDI_1; + LOG_WRN("%s set USB-MIDI1.0 altsetting (not implemented !)", dev->name); + break; + case ALT_USB_MIDI_2: + data->altsetting = ALT_USB_MIDI_2; + LOG_INF("%s set USB-MIDI2.0 altsetting", dev->name); + break; + } +} + +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; + + LOG_DBG("USB-MIDI enable for %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; + + LOG_DBG("USB-MIDI disable for %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; + + LOG_DBG("USB-MIDI suspended for %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; + + LOG_DBG("USB-MIDI resumed for %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; + + LOG_DBG("USB-MIDI 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); + + /* midi20 6. Class Specific Command: Group Terminal Blocks Descriptors Request */ + if (data->altsetting == ALT_USB_MIDI_2 && setup->bRequest == USB_SREQ_GET_DESCRIPTOR) { + if (setup->wValue != ((CS_GR_TRM_BLOCK << 8) | ALT_USB_MIDI_2)) { + errno = -ENOTSUP; + return 0; + } + + size_t head_len = config->desc->grptrm_header.bLength; + size_t total_len = sys_le16_to_cpu(config->desc->grptrm_header.wTotalLength); + + net_buf_add_mem(buf, (void *) &config->desc->grptrm_header, + MIN(head_len, setup->wLength)); + + if (setup->wLength > 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 USB-MIDI device class for %s", 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 void usbd_midi_rx_work(struct k_work *work) +{ + int ret; + struct net_buf *buf; + struct usbd_midi_data *data = CONTAINER_OF(work, struct usbd_midi_data, rx_work); + + buf = usbd_midi_buf_alloc(data->midi_if_index + FIRST_OUT_EP_ADDR); + 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) +{ + int ret; + struct net_buf *buf; + struct usbd_midi_data *data = CONTAINER_OF(work, struct usbd_midi_data, tx_work); + + buf = usbd_midi_buf_alloc(data->midi_if_index + FIRST_IN_EP_ADDR); + if (buf == NULL) { + LOG_ERR("Unable to allocate Tx net_buf"); + return; + } + + k_mutex_lock(&data->tx_mutex, K_FOREVER); + net_buf_add(buf, ring_buf_get(&data->tx_queue, buf->data, buf->size)); + k_mutex_unlock(&data->tx_mutex); + + 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 USB-MIDI device %s", dev->name); + k_mutex_init(&data->tx_mutex); + 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 uint32_t *ump) +{ + struct usbd_midi_data *data = dev->data; + size_t words = UMP_WORDS(ump); + size_t buflen = 4 * words; + int ret = 0; + uint32_t word; + + LOG_DBG("Send MT=%X group=%X", UMP_MT(ump), UMP_GROUP(ump)); + if (data->altsetting != ALT_USB_MIDI_2) { + LOG_WRN("MIDI2.0 is not enabled"); + return -EIO; + } + + k_mutex_lock(&data->tx_mutex, K_FOREVER); + + if (buflen > ring_buf_space_get(&data->tx_queue)) { + LOG_WRN("Not enough space in tx queue"); + ret = -EAGAIN; + } else { + for (size_t i = 0; i < words; i++) { + word = sys_cpu_to_le32(ump[i]); + ring_buf_put(&data->tx_queue, (const uint8_t *)&word, 4); + } + k_work_submit(&data->tx_work); + } + + k_mutex_unlock(&data->tx_mutex); + + return ret; +} + +void usbd_midi_set_callback(const struct device *dev, const usbd_midi_callback cb) +{ + struct usbd_midi_data *data = dev->data; + + LOG_DBG("Set callback for %s to %p", dev->name, cb); + data->cb = cb; +} + +/* 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) \ + COND_CODE_1( \ + 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(inst) \ + DT_INST_FOREACH_CHILD_VARGS(inst, GRPTRM_BLOCK_ID_SEP_IF, output_only) + +/* All unique identification numbers of input+bidir group terminal blocks */ +#define GRPTRM_INPUT_BLOCK_IDS(inst) \ + DT_INST_FOREACH_CHILD_VARGS(inst, GRPTRM_BLOCK_ID_SEP_IF, input_only) + +#define N_INPUTS(inst) sizeof((uint8_t[]){GRPTRM_INPUT_BLOCK_IDS(inst)}) +#define N_OUTPUTS(inst) sizeof((uint8_t[]){GRPTRM_OUTPUT_BLOCK_IDS(inst)}) + +#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(inst) \ + DT_INST_FOREACH_CHILD(inst, 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(inst) \ + sizeof(struct usb_midi_grptrm_header_descriptor) \ + + DT_INST_CHILD_NUM_STATUS_OKAY(inst) \ + * sizeof(struct usb_midi_grptrm_block_descriptor) + +#define USBD_MIDI_DEFINE_DESCRIPTORS(inst) \ + static struct usbd_midi_descriptors usbd_midi_desc_##inst = { \ + .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 = 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 = ALT_USB_MIDI_1, \ + .bNumEndpoints = 2, \ + .bInterfaceClass = AUDIO, \ + .bInterfaceSubClass = MIDISTREAMING, \ + }, \ + .if1_0_ms_header = { \ + .bLength = sizeof(struct usb_midi_header_descriptor), \ + .bDescriptorType = 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 = inst + 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 = inst + FIRST_OUT_EP_ADDR, \ + .bmAttributes = USB_EP_TYPE_BULK, \ + .wMaxPacketSize = sys_cpu_to_le16(512U), \ + }, \ + .if1_0_cs_out_ep = { \ + .bLength = 4, \ + .bDescriptorType = CS_ENDPOINT, \ + .bDescriptorSubtype = MS_GENERAL, \ + .bNumGrpTrmBlock = 0, \ + }, \ + .if1_0_in_ep_fs = { \ + .bLength = sizeof(struct usb_ep_descriptor), \ + .bDescriptorType = USB_DESC_ENDPOINT, \ + .bEndpointAddress = inst + 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 = inst + FIRST_IN_EP_ADDR, \ + .bmAttributes = USB_EP_TYPE_BULK, \ + .wMaxPacketSize = sys_cpu_to_le16(512U), \ + }, \ + .if1_0_cs_in_ep = { \ + .bLength = 4 + N_INPUTS(inst), \ + .bDescriptorType = CS_ENDPOINT, \ + .bDescriptorSubtype = MS_GENERAL, \ + .bNumGrpTrmBlock = 0, \ + }, \ + .if1_1_std = { \ + .bLength = sizeof(struct usb_if_descriptor), \ + .bDescriptorType = USB_DESC_INTERFACE, \ + .bInterfaceNumber = 1, \ + .bAlternateSetting = ALT_USB_MIDI_2, \ + .bNumEndpoints = 2, \ + .bInterfaceClass = AUDIO, \ + .bInterfaceSubClass = MIDISTREAMING, \ + }, \ + .if1_1_ms_header = { \ + .bLength = sizeof(struct usb_midi_header_descriptor), \ + .bDescriptorType = 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 = inst + 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 = inst + FIRST_OUT_EP_ADDR, \ + .bmAttributes = USB_EP_TYPE_BULK, \ + .wMaxPacketSize = sys_cpu_to_le16(512U), \ + }, \ + .if1_1_cs_out_ep = { \ + .bLength = 4 + N_OUTPUTS(inst), \ + .bDescriptorType = CS_ENDPOINT, \ + .bDescriptorSubtype = MS_GENERAL_2_0, \ + .bNumGrpTrmBlock = N_OUTPUTS(inst), \ + .baAssoGrpTrmBlkID = {GRPTRM_OUTPUT_BLOCK_IDS(inst)}, \ + }, \ + .if1_1_in_ep_fs = { \ + .bLength = sizeof(struct usb_ep_descriptor), \ + .bDescriptorType = USB_DESC_ENDPOINT, \ + .bEndpointAddress = inst + 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 = inst + FIRST_IN_EP_ADDR, \ + .bmAttributes = USB_EP_TYPE_BULK, \ + .wMaxPacketSize = sys_cpu_to_le16(512U), \ + }, \ + .if1_1_cs_in_ep = { \ + .bLength = 4 + N_INPUTS(inst), \ + .bDescriptorType = CS_ENDPOINT, \ + .bDescriptorSubtype = MS_GENERAL_2_0, \ + .bNumGrpTrmBlock = N_INPUTS(inst), \ + .baAssoGrpTrmBlkID = {GRPTRM_INPUT_BLOCK_IDS(inst)}, \ + }, \ + .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(inst)), \ + },\ + .grptrm_blocks = {\ + DT_INST_FOREACH_CHILD_SEP( \ + inst, USBD_MIDI2_INIT_GRPTRM_BLOCK_DESCRIPTOR, (,) \ + ) \ + },\ + }; \ + static const struct usb_desc_header *usbd_midi_desc_array_fs_##inst[] = { \ + (struct usb_desc_header *)&usbd_midi_desc_##inst.if0_std, \ + (struct usb_desc_header *)&usbd_midi_desc_##inst.if0_cs, \ + (struct usb_desc_header *)&usbd_midi_desc_##inst.if1_0_std, \ + (struct usb_desc_header *)&usbd_midi_desc_##inst.if1_0_ms_header, \ + (struct usb_desc_header *)&usbd_midi_desc_##inst.if1_0_out_ep_fs, \ + (struct usb_desc_header *)&usbd_midi_desc_##inst.if1_0_cs_out_ep, \ + (struct usb_desc_header *)&usbd_midi_desc_##inst.if1_0_in_ep_fs, \ + (struct usb_desc_header *)&usbd_midi_desc_##inst.if1_0_cs_in_ep, \ + (struct usb_desc_header *)&usbd_midi_desc_##inst.if1_1_std, \ + (struct usb_desc_header *)&usbd_midi_desc_##inst.if1_1_ms_header, \ + (struct usb_desc_header *)&usbd_midi_desc_##inst.if1_1_out_ep_fs, \ + (struct usb_desc_header *)&usbd_midi_desc_##inst.if1_1_cs_out_ep, \ + (struct usb_desc_header *)&usbd_midi_desc_##inst.if1_1_in_ep_fs, \ + (struct usb_desc_header *)&usbd_midi_desc_##inst.if1_1_cs_in_ep, \ + }; \ + static const struct usb_desc_header *usbd_midi_desc_array_hs_##inst[] = { \ + (struct usb_desc_header *)&usbd_midi_desc_##inst.if0_std, \ + (struct usb_desc_header *)&usbd_midi_desc_##inst.if0_cs, \ + (struct usb_desc_header *)&usbd_midi_desc_##inst.if1_0_std, \ + (struct usb_desc_header *)&usbd_midi_desc_##inst.if1_0_ms_header, \ + (struct usb_desc_header *)&usbd_midi_desc_##inst.if1_0_out_ep_hs, \ + (struct usb_desc_header *)&usbd_midi_desc_##inst.if1_0_cs_out_ep, \ + (struct usb_desc_header *)&usbd_midi_desc_##inst.if1_0_in_ep_hs, \ + (struct usb_desc_header *)&usbd_midi_desc_##inst.if1_0_cs_in_ep, \ + (struct usb_desc_header *)&usbd_midi_desc_##inst.if1_1_std, \ + (struct usb_desc_header *)&usbd_midi_desc_##inst.if1_1_ms_header, \ + (struct usb_desc_header *)&usbd_midi_desc_##inst.if1_1_out_ep_hs, \ + (struct usb_desc_header *)&usbd_midi_desc_##inst.if1_1_cs_out_ep, \ + (struct usb_desc_header *)&usbd_midi_desc_##inst.if1_1_in_ep_hs, \ + (struct usb_desc_header *)&usbd_midi_desc_##inst.if1_1_cs_in_ep, \ + }; + +#define USBD_MIDI_DEFINE_DEVICE(inst) \ + USBD_MIDI_VALIDATE_INSTANCE(inst) \ + USBD_MIDI_DEFINE_DESCRIPTORS(inst); \ + USBD_DEFINE_CLASS(midi_##inst, &usbd_midi_class_api, \ + (void *)DEVICE_DT_GET(DT_DRV_INST(inst)), NULL); \ + static const struct usbd_midi_config usbd_midi_config_##inst = { \ + .desc = &usbd_midi_desc_##inst, \ + .fs_descs = usbd_midi_desc_array_fs_##inst, \ + .hs_descs = usbd_midi_desc_array_hs_##inst, \ + }; \ + static struct usbd_midi_data usbd_midi_data_##inst = { \ + .class_data = &midi_##inst, \ + .altsetting = ALT_USB_MIDI_1, \ + .midi_if_index = inst, \ + }; \ + DEVICE_DT_INST_DEFINE(inst, usbd_midi_preinit, NULL, &usbd_midi_data_##inst, \ + &usbd_midi_config_##inst, POST_KERNEL, CONFIG_SERIAL_INIT_PRIORITY, \ + NULL); + +DT_INST_FOREACH_STATUS_OKAY(USBD_MIDI_DEFINE_DEVICE) diff --git a/subsys/usb/device_next/class/usbd_uac2_macros.h b/subsys/usb/device_next/class/usbd_uac2_macros.h index a31278d9cf8ac1f..216c192af0809b0 100644 --- a/subsys/usb/device_next/class/usbd_uac2_macros.h +++ b/subsys/usb/device_next/class/usbd_uac2_macros.h @@ -67,6 +67,8 @@ #define CS_STRING 0x23 #define CS_INTERFACE 0x24 #define CS_ENDPOINT 0x25 +/* USB MIDI 2.0: A.1 MS Class-Specific Interface Descriptor Types */ +#define CS_GR_TRM_BLOCK 0x26 /* A.9 Audio Class-Specific AC Interface Descriptor Subtypes */ #define AC_DESCRIPTOR_UNDEFINED 0x00