summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorHiFiPhile <[email protected]>2026-05-25 21:09:53 +0200
committerHiFiPhile <[email protected]>2026-05-25 21:09:53 +0200
commit264472e380bbca90aebabc6457fc53d372fcb66e (patch)
tree53c4e3ebcf504682ee42e9cd8958bfcbad533712 /src
parentae61e6c7e663ad8f2162d8a0c5cf0ce7c6e5f728 (diff)
parent7b9f6eabd8623c2bbe188c8d53072b16a087cdda (diff)
Merge remote-tracking branch 'tinyusb/master' into musb_ep0_race
Diffstat (limited to 'src')
-rw-r--r--src/CMakeLists.txt2
-rw-r--r--src/class/midi/midi.h18
-rw-r--r--src/class/midi/midi2_device.c769
-rw-r--r--src/class/midi/midi2_device.h193
-rw-r--r--src/class/midi/midi2_host.c635
-rw-r--r--src/class/midi/midi2_host.h107
-rw-r--r--src/common/tusb_mcu.h10
-rw-r--r--src/device/usbd.c14
-rw-r--r--src/device/usbd.h37
-rw-r--r--src/host/usbh.c12
-rw-r--r--src/portable/raspberrypi/rp2040/hcd_rp2040.c12
-rw-r--r--src/portable/wch/dcd_ch32_usbfs.c199
-rw-r--r--src/portable/wch/dcd_ch32_usbhs.c434
-rw-r--r--src/tinyusb.mk2
-rw-r--r--src/tusb.h8
-rw-r--r--src/tusb_option.h20
16 files changed, 2174 insertions, 298 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index c7a5184c5..b3e05f60f 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -14,6 +14,7 @@ function(tinyusb_sources_get OUTPUT_VAR)
${CMAKE_CURRENT_FUNCTION_LIST_DIR}/class/dfu/dfu_rt_device.c
${CMAKE_CURRENT_FUNCTION_LIST_DIR}/class/hid/hid_device.c
${CMAKE_CURRENT_FUNCTION_LIST_DIR}/class/midi/midi_device.c
+ ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/class/midi/midi2_device.c
${CMAKE_CURRENT_FUNCTION_LIST_DIR}/class/msc/msc_device.c
${CMAKE_CURRENT_FUNCTION_LIST_DIR}/class/mtp/mtp_device.c
${CMAKE_CURRENT_FUNCTION_LIST_DIR}/class/net/ecm_rndis_device.c
@@ -28,6 +29,7 @@ function(tinyusb_sources_get OUTPUT_VAR)
${CMAKE_CURRENT_FUNCTION_LIST_DIR}/class/cdc/cdc_host.c
${CMAKE_CURRENT_FUNCTION_LIST_DIR}/class/hid/hid_host.c
${CMAKE_CURRENT_FUNCTION_LIST_DIR}/class/midi/midi_host.c
+ ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/class/midi/midi2_host.c
${CMAKE_CURRENT_FUNCTION_LIST_DIR}/class/msc/msc_host.c
${CMAKE_CURRENT_FUNCTION_LIST_DIR}/class/vendor/vendor_host.c
# typec
diff --git a/src/class/midi/midi.h b/src/class/midi/midi.h
index cd67640e4..8121ec016 100644
--- a/src/class/midi/midi.h
+++ b/src/class/midi/midi.h
@@ -186,6 +186,24 @@ typedef midi_desc_cs_endpoint_n_t(1) midi_desc_cs_endpoint_1jack_t;
TU_VERIFY_STATIC(sizeof(midi_desc_cs_endpoint_1jack_t) == 4+1, "size is not correct");
//--------------------------------------------------------------------+
+// MIDI 2.0 UMP Helpers
+//--------------------------------------------------------------------+
+
+// Return the number of 32-bit words for a UMP message given its Message Type
+static inline uint8_t midi2_ump_word_count(uint8_t mt) {
+ switch (mt) {
+ case 0x0: case 0x1: case 0x2: case 0x6: case 0x7:
+ return 1;
+ case 0x3: case 0x4: case 0x8: case 0x9: case 0xA:
+ return 2;
+ case 0xB: case 0xC:
+ return 3;
+ default: // 0x5, 0xD, 0xE, 0xF
+ return 4;
+ }
+}
+
+//--------------------------------------------------------------------+
// For Internal Driver Use
//--------------------------------------------------------------------+
typedef struct {
diff --git a/src/class/midi/midi2_device.c b/src/class/midi/midi2_device.c
new file mode 100644
index 000000000..b14f439f8
--- /dev/null
+++ b/src/class/midi/midi2_device.c
@@ -0,0 +1,769 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2026 Saulo Verissimo
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * This file is part of the TinyUSB stack.
+ */
+
+#include "tusb_option.h"
+
+#if CFG_TUD_ENABLED && CFG_TUD_MIDI2
+
+#include <string.h>
+
+#include "device/usbd.h"
+#include "device/usbd_pvt.h"
+#include "midi2_device.h"
+
+//--------------------------------------------------------------------+
+// Weak stubs
+//--------------------------------------------------------------------+
+TU_ATTR_WEAK void tud_midi2_rx_cb(uint8_t itf) { (void) itf; }
+TU_ATTR_WEAK void tud_midi2_set_itf_cb(uint8_t itf, uint8_t alt) { (void) itf; (void) alt; }
+TU_ATTR_WEAK bool tud_midi2_get_req_itf_cb(uint8_t rhport, const tusb_control_request_t* request) {
+ (void) rhport; (void) request; return false;
+}
+TU_ATTR_WEAK uint8_t tud_midi2_num_groups_cb(uint8_t itf) {
+ (void) itf; return CFG_TUD_MIDI2_NUM_GROUPS;
+}
+TU_ATTR_WEAK uint8_t tud_midi2_num_function_blocks_cb(uint8_t itf) {
+ (void) itf; return CFG_TUD_MIDI2_NUM_FUNCTION_BLOCKS;
+}
+TU_ATTR_WEAK const char* tud_midi2_ep_name_cb(uint8_t itf) {
+ (void) itf; return CFG_TUD_MIDI2_EP_NAME;
+}
+TU_ATTR_WEAK const char* tud_midi2_product_id_cb(uint8_t itf) {
+ (void) itf; return CFG_TUD_MIDI2_PRODUCT_ID;
+}
+
+//--------------------------------------------------------------------+
+// Byte order note
+//--------------------------------------------------------------------+
+// Per USB-MIDI 2.0 Section 3.2.2, each 32-bit UMP word is transmitted with the
+// least significant byte first. This driver reads and writes UMP words as
+// native uint32_t through tu_edpt_stream_read/write. All TinyUSB targets are
+// little-endian, so the in-memory layout already matches the wire order and no
+// swap is needed. If a big-endian target is ever supported, wrap access with
+// tu_htole32 / tu_le32toh at the buffer boundary.
+
+//--------------------------------------------------------------------+
+// UMP Stream Message Constants
+//--------------------------------------------------------------------+
+// UMP Message Type for Stream messages (bits 31:28)
+enum {
+ MT_STREAM = 0x0F,
+};
+
+// UMP Stream Status values (10-bit, bits 25:16)
+enum {
+ STREAM_ENDPOINT_DISCOVERY = 0x000,
+ STREAM_ENDPOINT_INFO = 0x001,
+ STREAM_EP_NAME = 0x003,
+ STREAM_PROD_INSTANCE_ID = 0x004,
+ STREAM_CONFIG_REQUEST = 0x005,
+ STREAM_CONFIG_NOTIFY = 0x006,
+ STREAM_FB_DISCOVERY = 0x010,
+ STREAM_FB_INFO = 0x011,
+};
+
+enum {
+ UMP_VER_MAJOR = 1,
+ UMP_VER_MINOR = 1,
+};
+
+// Group Terminal Block descriptor types (USB-MIDI 2.0)
+enum {
+ MIDI2_CS_GRP_TRM_BLOCK = 0x26,
+ MIDI2_GRP_TRM_BLOCK_HEADER = 0x01,
+ MIDI2_GRP_TRM_BLOCK_ENTRY = 0x02,
+};
+
+//--------------------------------------------------------------------+
+// MACRO CONSTANT TYPEDEF
+//--------------------------------------------------------------------+
+typedef struct {
+ uint8_t ep_addr;
+ uint16_t mps;
+ tu_fifo_t ff;
+
+#if CFG_TUD_EDPT_DEDICATED_HWFIFO == 0
+ uint8_t* ep_buf;
+#endif
+} midi2d_tx_t;
+
+typedef struct {
+ uint8_t rhport;
+ uint8_t itf_num;
+ uint8_t alt_setting;
+ uint8_t protocol;
+ bool negotiated;
+
+ /*------------- From this point, data is not cleared by bus reset -------------*/
+ struct {
+ midi2d_tx_t tx;
+ tu_edpt_stream_t rx;
+
+ uint8_t rx_ff_buf[CFG_TUD_MIDI2_RX_BUFSIZE];
+ uint8_t tx_ff_buf[CFG_TUD_MIDI2_TX_BUFSIZE];
+ } ep_stream;
+} midi2d_interface_t;
+
+// Skip local EP buffer if dedicated hw FIFO is supported
+#if CFG_TUD_EDPT_DEDICATED_HWFIFO == 0
+typedef struct {
+ TUD_EPBUF_DEF(epin, CFG_TUD_MIDI2_TX_EPSIZE);
+ TUD_EPBUF_DEF(epout, CFG_TUD_MIDI2_RX_EPSIZE);
+} midi2d_epbuf_t;
+
+CFG_TUD_MEM_SECTION static midi2d_epbuf_t _midi2d_epbuf[CFG_TUD_MIDI2];
+#endif
+
+TU_VERIFY_STATIC(CFG_TUD_MIDI2_NUM_GROUPS >= 1 && CFG_TUD_MIDI2_NUM_GROUPS <= 16,
+ "CFG_TUD_MIDI2_NUM_GROUPS must be 1..16");
+TU_VERIFY_STATIC(CFG_TUD_MIDI2_NUM_FUNCTION_BLOCKS >= 1 && CFG_TUD_MIDI2_NUM_FUNCTION_BLOCKS <= 32,
+ "CFG_TUD_MIDI2_NUM_FUNCTION_BLOCKS must be 1..32");
+
+#define ITF_MEM_RESET_SIZE offsetof(midi2d_interface_t, ep_stream)
+
+static midi2d_interface_t _midi2d_itf[CFG_TUD_MIDI2];
+
+// Default Group Terminal Block descriptor (USB-MIDI 2.0 spec, Table 5-5/5-6)
+static const uint8_t _default_gtb_desc[] = {
+ // GTB Header (5 bytes)
+ 5, // bLength
+ MIDI2_CS_GRP_TRM_BLOCK, // bDescriptorType
+ MIDI2_GRP_TRM_BLOCK_HEADER, // bDescriptorSubtype
+ U16_TO_U8S_LE(18), // wTotalLength (5 + 13 = 18)
+
+ // GTB Entry (13 bytes)
+ 13, // bLength
+ MIDI2_CS_GRP_TRM_BLOCK, // bDescriptorType
+ MIDI2_GRP_TRM_BLOCK_ENTRY, // bDescriptorSubtype
+ 1, // bGrpTrmBlkID
+ 0x00, // bGrpTrmBlkType: bidirectional
+ 0x00, // nGroupTrm: first group (0)
+ CFG_TUD_MIDI2_NUM_GROUPS, // nNumGroupTrm
+ CFG_TUD_MIDI2_BLOCK_STRIDX, // iBlockItem: string descriptor index (0 = none)
+ 0x00, // bMIDIProtocol: unknown/not fixed
+ 0, 0, // wMaxInputBandwidth: unknown
+ 0, 0 // wMaxOutputBandwidth: unknown
+};
+
+//--------------------------------------------------------------------+
+// Common utility functions
+//--------------------------------------------------------------------+
+
+static inline uint8_t _itf_idx(const midi2d_interface_t* p_midi) {
+ return (uint8_t)(p_midi - _midi2d_itf);
+}
+
+static inline bool _tx_opened(const midi2d_interface_t* p_midi) {
+ return p_midi->ep_stream.tx.ep_addr != 0;
+}
+
+static uint8_t _tx_byte_at(const tu_fifo_buffer_info_t* info, uint16_t offset) {
+ if (offset < info->linear.len) {
+ return info->linear.ptr[offset];
+ }
+
+ offset = (uint16_t) (offset - info->linear.len);
+ if (offset < info->wrapped.len) {
+ return info->wrapped.ptr[offset];
+ }
+
+ return 0;
+}
+
+// Calculate the largest byte count that contains only whole UMP packets and
+// fits in one USB transfer (<= mps).
+static uint16_t _tx_nonseg_len_to_mps(midi2d_tx_t* tx) {
+ tu_fifo_buffer_info_t info;
+ tu_fifo_get_read_info(&tx->ff, &info);
+
+ const uint16_t available = (uint16_t) (info.linear.len + info.wrapped.len);
+ uint16_t bytes = 0;
+
+ while (bytes < tx->mps) {
+ if ((uint16_t) (available - bytes) < 4) break;
+
+ uint8_t mt = (uint8_t)((_tx_byte_at(&info, (uint16_t) (bytes + 3)) >> 4) & 0x0F);
+ uint8_t pkt_words = midi2_ump_word_count(mt);
+ uint16_t pkt_bytes = (uint16_t) pkt_words * 4;
+
+ if (pkt_bytes == 0) break;
+ if ((uint16_t) (available - bytes) < pkt_bytes) break;
+ if ((uint16_t) (bytes + pkt_bytes) > tx->mps) break;
+
+ bytes = (uint16_t) (bytes + pkt_bytes);
+ }
+
+ return bytes;
+}
+
+// Start one IN transfer capped at mps, return number of bytes queued to the controller, or 0 if nothing was queued.
+static uint16_t _tx_start_xfer(midi2d_interface_t* p_midi) {
+ midi2d_tx_t* tx = &p_midi->ep_stream.tx;
+ uint16_t ff_count = tu_fifo_count(&tx->ff);
+
+ if (ff_count == 0) return 0;
+
+ if (!usbd_edpt_claim(p_midi->rhport, tx->ep_addr)) return 0;
+
+ uint16_t bytes;
+ if (p_midi->alt_setting == 1) {
+ bytes = _tx_nonseg_len_to_mps(tx);
+ } else {
+ bytes = tu_min16(tu_fifo_count(&tx->ff), tx->mps);
+ }
+ if (bytes == 0) {
+ usbd_edpt_release(p_midi->rhport, tx->ep_addr);
+ return 0;
+ }
+
+#if CFG_TUD_EDPT_DEDICATED_HWFIFO
+ TU_ASSERT(usbd_edpt_xfer_fifo(p_midi->rhport, tx->ep_addr, &tx->ff, bytes, false), 0);
+#else
+ tu_fifo_read_n(&tx->ff, tx->ep_buf, bytes);
+ TU_ASSERT(usbd_edpt_xfer(p_midi->rhport, tx->ep_addr, tx->ep_buf, bytes, false), 0);
+#endif
+
+ return bytes;
+}
+
+static uint32_t _tx_ump_write(midi2d_interface_t* p_midi, const uint32_t* words, uint32_t count) {
+ uint32_t written = 0;
+ while (written < count) {
+ uint8_t mt = (uint8_t)((words[written] >> 28) & 0x0F);
+ uint8_t pkt_words = midi2_ump_word_count(mt);
+ uint16_t pkt_bytes = (uint16_t) pkt_words * 4;
+
+ if (written + pkt_words > count) break;
+ if (tu_fifo_remaining(&p_midi->ep_stream.tx.ff) < pkt_bytes) break;
+
+ if (tu_fifo_write_n(&p_midi->ep_stream.tx.ff, &words[written], pkt_bytes) != pkt_bytes) break;
+ written += pkt_words;
+ }
+
+ (void) _tx_start_xfer(p_midi);
+ return written;
+}
+
+//--------------------------------------------------------------------+
+// Protocol Negotiation
+//--------------------------------------------------------------------+
+static void _nego_send_ump(midi2d_interface_t* p_midi, const uint32_t* words, uint8_t count) {
+ if (!_tx_opened(p_midi)) return;
+ if (tu_fifo_remaining(&p_midi->ep_stream.tx.ff) < (uint32_t) count * 4) return;
+ (void) _tx_ump_write(p_midi, words, count);
+}
+
+static void _nego_send_endpoint_info(midi2d_interface_t* p_midi) {
+ uint32_t msg[4] = {0};
+ msg[0] = ((uint32_t) MT_STREAM << 28)
+ | ((uint32_t) STREAM_ENDPOINT_INFO << 16)
+ | ((uint32_t) UMP_VER_MAJOR << 8)
+ | (uint32_t) UMP_VER_MINOR;
+ msg[1] = (UINT32_C(1) << 31) // Static Function Blocks flag
+ | ((uint32_t)(tud_midi2_num_function_blocks_cb(_itf_idx(p_midi)) & 0x7F) << 24)
+ | (UINT32_C(1) << 9) // MIDI 2.0 Protocol capability
+ | (UINT32_C(1) << 8); // MIDI 1.0 Protocol capability
+ _nego_send_ump(p_midi, msg, 4);
+}
+
+static void _nego_send_stream_text(midi2d_interface_t* p_midi, uint16_t status, const char* str) {
+ if (!str || str[0] == '\0') return;
+
+ uint16_t total_len = (uint16_t) strlen(str);
+ uint16_t offset = 0;
+
+ while (offset < total_len) {
+ uint16_t remaining = total_len - offset;
+ uint8_t n = (uint8_t)((remaining > 14) ? 14 : remaining);
+ bool is_first = (offset == 0);
+ bool is_last = (remaining <= 14);
+
+ uint8_t form;
+ if (is_first && is_last) form = 0;
+ else if (is_first) form = 1;
+ else if (is_last) form = 3;
+ else form = 2;
+
+ uint32_t msg[4] = {0};
+ msg[0] = ((uint32_t) MT_STREAM << 28)
+ | ((uint32_t) form << 26)
+ | ((uint32_t) status << 16);
+
+ const char* p = str + offset;
+ if (n > 0) msg[0] |= ((uint32_t)(uint8_t) p[0] << 8);
+ if (n > 1) msg[0] |= (uint32_t)(uint8_t) p[1];
+ for (uint8_t i = 2; i < n; i++) {
+ uint8_t word_idx = (uint8_t)(1 + (i - 2) / 4);
+ uint8_t shift = (uint8_t)(24 - ((i - 2) % 4) * 8);
+ msg[word_idx] |= ((uint32_t)(uint8_t) p[i] << shift);
+ }
+
+ _nego_send_ump(p_midi, msg, 4);
+ offset += n;
+ }
+}
+
+static void _nego_send_config_notify(midi2d_interface_t* p_midi, uint8_t protocol) {
+ uint32_t msg[4] = {0};
+ msg[0] = ((uint32_t) MT_STREAM << 28)
+ | ((uint32_t) STREAM_CONFIG_NOTIFY << 16)
+ | ((uint32_t) protocol << 8);
+ _nego_send_ump(p_midi, msg, 4);
+}
+
+static void _nego_send_fb_info(midi2d_interface_t* p_midi, uint8_t fb_idx) {
+ uint32_t msg[4] = {0};
+ msg[0] = ((uint32_t) MT_STREAM << 28)
+ | ((uint32_t) STREAM_FB_INFO << 16)
+ | (UINT32_C(1) << 15)
+ | ((uint32_t) fb_idx << 8)
+ | 0x02; // bDirection: bidirectional
+ msg[1] = ((uint32_t) 0 << 24) // bFirstGroup
+ | ((uint32_t) tud_midi2_num_groups_cb(_itf_idx(p_midi)) << 16);
+ _nego_send_ump(p_midi, msg, 4);
+}
+
+static void _nego_handle_stream_msg(midi2d_interface_t* p_midi, const uint32_t* words) {
+ uint16_t status = (words[0] >> 16) & 0x3FF;
+
+ switch (status) {
+ case STREAM_ENDPOINT_DISCOVERY:
+ _nego_send_endpoint_info(p_midi);
+ _nego_send_stream_text(p_midi, STREAM_EP_NAME, tud_midi2_ep_name_cb(_itf_idx(p_midi)));
+ _nego_send_stream_text(p_midi, STREAM_PROD_INSTANCE_ID, tud_midi2_product_id_cb(_itf_idx(p_midi)));
+ break;
+
+ case STREAM_CONFIG_REQUEST: {
+ uint8_t req_proto = (words[0] >> 8) & 0xFF;
+ if (req_proto == MIDI_PROTOCOL_MIDI1 || req_proto == MIDI_PROTOCOL_MIDI2) {
+ p_midi->protocol = req_proto;
+ }
+ _nego_send_config_notify(p_midi, p_midi->protocol);
+ p_midi->negotiated = true;
+ break;
+ }
+
+ case STREAM_FB_DISCOVERY: {
+ uint8_t fb_idx = (words[0] >> 8) & 0xFF;
+ uint8_t fb_count = tud_midi2_num_function_blocks_cb(_itf_idx(p_midi));
+ if (fb_idx == 0xFF) {
+ for (uint8_t f = 0; f < fb_count; f++) {
+ _nego_send_fb_info(p_midi, f);
+ }
+ } else if (fb_idx < fb_count) {
+ _nego_send_fb_info(p_midi, fb_idx);
+ }
+ break;
+ }
+
+ default:
+ break;
+ }
+}
+
+static void _nego_process_rx(midi2d_interface_t* p_midi) {
+ tu_edpt_stream_t* ep_rx = &p_midi->ep_stream.rx;
+ uint8_t word_bytes[4];
+
+ while (tu_fifo_peek_n(&ep_rx->ff, word_bytes, 4) == 4) {
+ // UMP words travel LSB-first on the wire and in LE memory, so MT is in
+ // the high nibble of byte 3, not byte 0.
+ uint8_t mt = (word_bytes[3] >> 4) & 0x0F;
+ uint8_t pkt_words = midi2_ump_word_count(mt);
+ uint32_t pkt_bytes = (uint32_t)pkt_words * 4;
+
+ if (mt != MT_STREAM) break;
+ if (tu_edpt_stream_read_available(ep_rx) < pkt_bytes) break;
+
+ uint32_t buf[4] = {0};
+ tu_edpt_stream_read(ep_rx, buf, pkt_bytes);
+ _nego_handle_stream_msg(p_midi, buf);
+ }
+}
+
+//--------------------------------------------------------------------+
+// READ API
+//--------------------------------------------------------------------+
+bool tud_midi2_n_mounted(uint8_t itf) {
+ TU_VERIFY(itf < CFG_TUD_MIDI2, false);
+ midi2d_interface_t* p_midi = &_midi2d_itf[itf];
+ return _tx_opened(p_midi) &&
+ tu_edpt_stream_is_opened(&p_midi->ep_stream.rx);
+}
+
+uint32_t tud_midi2_n_available(uint8_t itf) {
+ TU_VERIFY(itf < CFG_TUD_MIDI2, 0);
+ midi2d_interface_t* p_midi = &_midi2d_itf[itf];
+ return tu_edpt_stream_read_available(&p_midi->ep_stream.rx) / 4;
+}
+
+uint32_t tud_midi2_n_ump_read(uint8_t itf, uint32_t* words, uint32_t max_words) {
+ TU_VERIFY(itf < CFG_TUD_MIDI2 && words != NULL && max_words > 0, 0);
+ midi2d_interface_t* p_midi = &_midi2d_itf[itf];
+
+ // UMP API is only valid on Alt Setting 1 (USB-MIDI 2.0).
+ // Alt 0 carries USB-MIDI 1.0 32-bit Event Packets, not UMP words.
+ if (p_midi->alt_setting != 1) { return 0; }
+
+ tu_edpt_stream_t* ep_rx = &p_midi->ep_stream.rx;
+
+ uint32_t total_read = 0;
+ while (total_read < max_words) {
+ uint8_t word_bytes[4];
+ if (tu_fifo_peek_n(&ep_rx->ff, word_bytes, 4) < 4) break;
+
+ // UMP words travel LSB-first; MT is the high nibble of byte 3, not byte 0.
+ uint8_t mt = (word_bytes[3] >> 4) & 0x0F;
+ uint8_t pkt_words = midi2_ump_word_count(mt);
+
+ if (total_read + pkt_words > max_words) break;
+ if (tu_edpt_stream_read_available(ep_rx) < (uint32_t)pkt_words * 4) break;
+
+ tu_edpt_stream_read(ep_rx, &words[total_read], pkt_words * 4);
+ total_read += pkt_words;
+ }
+
+ return total_read;
+}
+
+uint32_t tud_midi2_n_packet_read(uint8_t itf, uint8_t packets[], uint32_t max_packets) {
+ TU_VERIFY(itf < CFG_TUD_MIDI2 && packets != NULL && max_packets > 0, 0);
+ midi2d_interface_t* p_midi = &_midi2d_itf[itf];
+ return tu_edpt_stream_read(&p_midi->ep_stream.rx, packets, max_packets * 4u) >> 2u;
+}
+
+//--------------------------------------------------------------------+
+// WRITE API
+//--------------------------------------------------------------------+
+uint32_t tud_midi2_n_ump_write(uint8_t itf, const uint32_t* words, uint32_t count) {
+ TU_VERIFY(itf < CFG_TUD_MIDI2 && words != NULL && count > 0, 0);
+ midi2d_interface_t* p_midi = &_midi2d_itf[itf];
+
+ // UMP API is only valid on Alt Setting 1 (USB-MIDI 2.0).
+ // Alt 0 carries USB-MIDI 1.0 32-bit Event Packets, not UMP words.
+ if (p_midi->alt_setting != 1) { return 0; }
+ TU_VERIFY(_tx_opened(p_midi), 0);
+
+ return _tx_ump_write(p_midi, words, count);
+}
+
+uint32_t tud_midi2_n_packet_write(uint8_t itf, const uint8_t packets[], uint32_t count) {
+ TU_VERIFY(itf < CFG_TUD_MIDI2 && packets != NULL && count > 0, 0);
+ midi2d_interface_t* p_midi = &_midi2d_itf[itf];
+ midi2d_tx_t* tx = &p_midi->ep_stream.tx;
+
+ // Packet API is for Alt Setting 0 (USB-MIDI 1.0) event packets.
+ TU_VERIFY(p_midi->alt_setting == 0, 0);
+ TU_VERIFY(_tx_opened(p_midi), 0);
+
+ uint32_t written = 0;
+ while (written < count) {
+ if (tu_fifo_remaining(&tx->ff) < 4) break;
+
+ if (tu_fifo_write_n(&tx->ff, packets + written * 4u, 4) != 4) break;
+ written++;
+ }
+
+ (void) _tx_start_xfer(p_midi);
+
+ return written;
+}
+
+//--------------------------------------------------------------------+
+// STATE GETTERS
+//--------------------------------------------------------------------+
+uint8_t tud_midi2_n_alt_setting(uint8_t itf) {
+ TU_VERIFY(itf < CFG_TUD_MIDI2, 0);
+ return _midi2d_itf[itf].alt_setting;
+}
+
+bool tud_midi2_n_negotiated(uint8_t itf) {
+ TU_VERIFY(itf < CFG_TUD_MIDI2, false);
+ return _midi2d_itf[itf].negotiated;
+}
+
+uint8_t tud_midi2_n_protocol(uint8_t itf) {
+ TU_VERIFY(itf < CFG_TUD_MIDI2, 0);
+ return _midi2d_itf[itf].protocol;
+}
+
+//--------------------------------------------------------------------+
+// USBD Driver API
+//--------------------------------------------------------------------+
+void midi2d_init(void) {
+ tu_memclr(_midi2d_itf, sizeof(_midi2d_itf));
+ for (uint8_t i = 0; i < CFG_TUD_MIDI2; i++) {
+ midi2d_interface_t* p_midi = &_midi2d_itf[i];
+ p_midi->protocol = MIDI_PROTOCOL_MIDI2;
+
+ #if CFG_TUD_EDPT_DEDICATED_HWFIFO
+ uint8_t *epout_buf = NULL;
+ uint8_t *epin_buf = NULL;
+ #else
+ uint8_t *epout_buf = _midi2d_epbuf[i].epout;
+ uint8_t *epin_buf = _midi2d_epbuf[i].epin;
+ #endif
+
+ tu_edpt_stream_init(&p_midi->ep_stream.rx, false, false, false,
+ p_midi->ep_stream.rx_ff_buf, CFG_TUD_MIDI2_RX_BUFSIZE, epout_buf);
+
+ midi2d_tx_t* tx = &p_midi->ep_stream.tx;
+ (void) tu_fifo_config(&tx->ff, p_midi->ep_stream.tx_ff_buf, CFG_TUD_MIDI2_TX_BUFSIZE, false);
+#if CFG_TUD_EDPT_DEDICATED_HWFIFO == 0
+ tx->ep_buf = epin_buf;
+#else
+ (void) epin_buf;
+#endif
+ }
+}
+
+bool midi2d_deinit(void) {
+ for (uint8_t i = 0; i < CFG_TUD_MIDI2; i++) {
+ midi2d_interface_t* p_midi = &_midi2d_itf[i];
+ tu_edpt_stream_deinit(&p_midi->ep_stream.rx);
+ }
+ return true;
+}
+
+void midi2d_reset(uint8_t rhport) {
+ (void) rhport;
+ for (uint8_t i = 0; i < CFG_TUD_MIDI2; i++) {
+ midi2d_interface_t* p_midi = &_midi2d_itf[i];
+ tu_memclr(p_midi, ITF_MEM_RESET_SIZE);
+
+ tu_edpt_stream_clear(&p_midi->ep_stream.rx);
+ tu_edpt_stream_close(&p_midi->ep_stream.rx);
+
+ tu_fifo_clear(&p_midi->ep_stream.tx.ff);
+ p_midi->ep_stream.tx.ep_addr = 0;
+ }
+}
+
+TU_ATTR_ALWAYS_INLINE static inline uint8_t find_midi2_itf(uint8_t ep_addr) {
+ for (uint8_t idx = 0; idx < CFG_TUD_MIDI2; idx++) {
+ const midi2d_interface_t* p_midi = &_midi2d_itf[idx];
+ if (ep_addr == p_midi->ep_stream.rx.ep_addr || ep_addr == p_midi->ep_stream.tx.ep_addr) {
+ return idx;
+ }
+ }
+ return TUSB_INDEX_INVALID_8;
+}
+
+static uint8_t find_midi2_itf_by_num(uint8_t itf_num) {
+ for (uint8_t idx = 0; idx < CFG_TUD_MIDI2; idx++) {
+ if (_midi2d_itf[idx].itf_num == itf_num) return idx;
+ }
+ return TUSB_INDEX_INVALID_8;
+}
+
+uint16_t midi2d_open(uint8_t rhport, const tusb_desc_interface_t* desc_itf, uint16_t max_len) {
+ const uint8_t* p_desc = (const uint8_t*) desc_itf;
+ const uint8_t* desc_end = p_desc + max_len;
+
+ // 1st Interface: Audio Control v1 (optional)
+ if (TUSB_CLASS_AUDIO == desc_itf->bInterfaceClass &&
+ AUDIO_SUBCLASS_CONTROL == desc_itf->bInterfaceSubClass &&
+ AUDIO_FUNC_PROTOCOL_CODE_UNDEF == desc_itf->bInterfaceProtocol) {
+ p_desc = tu_desc_next(desc_itf);
+ while (tu_desc_in_bounds(p_desc, desc_end) && TUSB_DESC_CS_INTERFACE == tu_desc_type(p_desc)) {
+ p_desc = tu_desc_next(p_desc);
+ }
+ }
+
+ // 2nd Interface: MIDI Streaming
+ TU_VERIFY(TUSB_DESC_INTERFACE == tu_desc_type(p_desc), 0);
+ const tusb_desc_interface_t* desc_midi = (const tusb_desc_interface_t*) p_desc;
+
+ TU_VERIFY(TUSB_CLASS_AUDIO == desc_midi->bInterfaceClass &&
+ AUDIO_SUBCLASS_MIDI_STREAMING == desc_midi->bInterfaceSubClass &&
+ AUDIO_FUNC_PROTOCOL_CODE_UNDEF == desc_midi->bInterfaceProtocol,
+ 0);
+
+ uint8_t idx = find_midi2_itf(0);
+ TU_ASSERT(idx < CFG_TUD_MIDI2, 0);
+ midi2d_interface_t* p_midi = &_midi2d_itf[idx];
+
+ p_midi->rhport = rhport;
+ p_midi->itf_num = desc_midi->bInterfaceNumber;
+ p_midi->alt_setting = 0;
+ p_midi->protocol = MIDI_PROTOCOL_MIDI2;
+ p_midi->negotiated = false;
+
+ p_desc = tu_desc_next(p_desc);
+
+ // Skip class-specific descriptors
+ while (tu_desc_in_bounds(p_desc, desc_end) && TUSB_DESC_CS_INTERFACE == tu_desc_type(p_desc)) {
+ p_desc = tu_desc_next(p_desc);
+ }
+
+ // Find and open endpoint descriptors
+ uint8_t found_ep = 0;
+ while ((found_ep < desc_midi->bNumEndpoints) && tu_desc_in_bounds(p_desc, desc_end)) {
+ if (TUSB_DESC_ENDPOINT == tu_desc_type(p_desc)) {
+ const tusb_desc_endpoint_t* desc_ep = (const tusb_desc_endpoint_t*) p_desc;
+ TU_ASSERT(usbd_edpt_open(rhport, desc_ep), 0);
+ const uint8_t ep_addr = desc_ep->bEndpointAddress;
+
+ if (tu_edpt_dir(ep_addr) == TUSB_DIR_IN) {
+ p_midi->ep_stream.tx.ep_addr = ep_addr;
+ p_midi->ep_stream.tx.mps = tu_edpt_packet_size(desc_ep);
+ tu_fifo_clear(&p_midi->ep_stream.tx.ff);
+ } else {
+ tu_edpt_stream_open(&p_midi->ep_stream.rx, rhport, desc_ep, tu_edpt_packet_size(desc_ep));
+ tu_edpt_stream_clear(&p_midi->ep_stream.rx);
+ TU_ASSERT(tu_edpt_stream_read_xfer(&p_midi->ep_stream.rx) > 0, 0);
+ }
+
+ found_ep++;
+ }
+
+ p_desc = tu_desc_next(p_desc);
+ }
+
+ // Skip remaining descriptors (alt setting 1, CS endpoints, GTB)
+ // Stop at any interface descriptor that is not our MIDI Streaming alt setting
+ while (tu_desc_in_bounds(p_desc, desc_end)) {
+ uint8_t dtype = tu_desc_type(p_desc);
+
+ if (dtype == TUSB_DESC_INTERFACE) {
+ const tusb_desc_interface_t* next_itf = (const tusb_desc_interface_t*) p_desc;
+ // Continue only if this is an alternate setting of our own interface
+ if (next_itf->bInterfaceNumber != desc_midi->bInterfaceNumber) break;
+ } else if (dtype != TUSB_DESC_CS_INTERFACE && dtype != TUSB_DESC_CS_ENDPOINT &&
+ dtype != TUSB_DESC_ENDPOINT) {
+ break;
+ }
+
+ p_desc = tu_desc_next(p_desc);
+ }
+
+ return (uint16_t)(p_desc - (const uint8_t*) desc_itf);
+}
+
+bool midi2d_control_xfer_cb(uint8_t rhport, uint8_t stage, const tusb_control_request_t* request) {
+ TU_LOG2("MIDI2 ctrl: stage=%u bRequest=0x%02X wValue=0x%04X wIndex=0x%04X wLength=%u\r\n",
+ stage, request->bRequest, request->wValue, request->wIndex, request->wLength);
+
+ if (stage != CONTROL_STAGE_SETUP) return true;
+
+ switch (request->bRequest) {
+ case TUSB_REQ_SET_INTERFACE: {
+ uint8_t itf_num = tu_u16_low(request->wIndex);
+ uint8_t alt = tu_u16_low(request->wValue);
+
+ // Only Alt Setting 0 (MIDI 1.0) and 1 (UMP) are valid
+ if (alt > 1) return false;
+
+ uint8_t idx = find_midi2_itf_by_num(itf_num);
+ if (idx >= CFG_TUD_MIDI2) return false;
+
+ midi2d_interface_t* p_midi = &_midi2d_itf[idx];
+ p_midi->alt_setting = alt;
+
+ tu_edpt_stream_clear(&p_midi->ep_stream.rx);
+ tu_fifo_clear(&p_midi->ep_stream.tx.ff);
+
+ if (alt == 1) {
+ p_midi->negotiated = false;
+ p_midi->protocol = MIDI_PROTOCOL_MIDI2;
+ }
+
+ // Re-arm RX endpoint for receiving data after alt setting change
+ tu_edpt_stream_read_xfer(&p_midi->ep_stream.rx);
+
+ tud_midi2_set_itf_cb(idx, alt);
+ tud_control_status(rhport, request);
+ return true;
+ }
+
+ case TUSB_REQ_GET_DESCRIPTOR: {
+ // USB-MIDI 2.0 Section 6: GTB descriptor retrieval
+ // bmRequestType = 0x81 (Device-to-Host, Standard, Interface)
+ // wValue = CS_GR_TRM_BLOCK (0x26) in high byte, alt setting in low byte
+ // wIndex = interface number
+ if (request->bmRequestType_bit.direction != TUSB_DIR_IN) return false;
+ if (request->bmRequestType_bit.type != TUSB_REQ_TYPE_STANDARD) return false;
+ if (request->bmRequestType_bit.recipient != TUSB_REQ_RCPT_INTERFACE) return false;
+ if (tu_u16_high(request->wValue) != MIDI2_CS_GRP_TRM_BLOCK) return false;
+
+ uint8_t itf_num = tu_u16_low(request->wIndex);
+ uint8_t idx = find_midi2_itf_by_num(itf_num);
+ if (idx >= CFG_TUD_MIDI2) return false;
+
+ // Only Alt Setting 1 exposes Group Terminal Block descriptors.
+ if (tu_u16_low(request->wValue) != 0x01) return false;
+
+ if (tud_midi2_get_req_itf_cb(rhport, request)) return true;
+
+ uint16_t len = request->wLength;
+ if (len > sizeof(_default_gtb_desc)) {
+ len = sizeof(_default_gtb_desc);
+ }
+ tud_control_xfer(rhport, request, (void*)(uintptr_t) _default_gtb_desc, len);
+ return true;
+ }
+
+ default:
+ return false;
+ }
+}
+
+bool midi2d_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes) {
+ (void) rhport;
+
+ uint8_t idx = find_midi2_itf(ep_addr);
+ TU_ASSERT(idx < CFG_TUD_MIDI2);
+ midi2d_interface_t* p_midi = &_midi2d_itf[idx];
+
+ tu_edpt_stream_t* ep_rx = &p_midi->ep_stream.rx;
+ midi2d_tx_t* ep_tx = &p_midi->ep_stream.tx;
+
+ if (ep_addr == ep_rx->ep_addr) {
+ if (result == XFER_RESULT_SUCCESS) {
+ tu_edpt_stream_read_xfer_complete(ep_rx, xferred_bytes);
+ if (p_midi->alt_setting == 1) {
+ _nego_process_rx(p_midi);
+ }
+ tud_midi2_rx_cb(idx);
+ }
+ tu_edpt_stream_read_xfer(ep_rx);
+ } else if (ep_addr == ep_tx->ep_addr && result == XFER_RESULT_SUCCESS) {
+ uint16_t queued = _tx_start_xfer(p_midi);
+ // Send ZLP if no more data is queued but the last transfer was exactly mps
+ if (queued == 0 && tu_fifo_count(&ep_tx->ff) == 0 && xferred_bytes > 0 &&
+ (0 == (xferred_bytes & (ep_tx->mps - 1)))) {
+ if (usbd_edpt_claim(rhport, ep_tx->ep_addr)) {
+ usbd_edpt_xfer(rhport, ep_tx->ep_addr, NULL, 0, false);
+ }
+ }
+ } else {
+ return false;
+ }
+
+ return true;
+}
+
+#endif
diff --git a/src/class/midi/midi2_device.h b/src/class/midi/midi2_device.h
new file mode 100644
index 000000000..e53535693
--- /dev/null
+++ b/src/class/midi/midi2_device.h
@@ -0,0 +1,193 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2026 Saulo Verissimo
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * This file is part of the TinyUSB stack.
+ */
+
+#ifndef TUSB_MIDI2_DEVICE_H_
+#define TUSB_MIDI2_DEVICE_H_
+
+#include "class/audio/audio.h"
+#include "midi.h"
+
+//--------------------------------------------------------------------+
+// Class Driver Configuration
+//--------------------------------------------------------------------+
+
+// Config defaults are in tusb_option.h:
+// CFG_TUD_MIDI2_RX_EPSIZE, CFG_TUD_MIDI2_TX_EPSIZE,
+// CFG_TUD_MIDI2_RX_BUFSIZE, CFG_TUD_MIDI2_TX_BUFSIZE,
+// CFG_TUD_MIDI2_NUM_GROUPS, CFG_TUD_MIDI2_NUM_FUNCTION_BLOCKS,
+// CFG_TUD_MIDI2_EP_NAME, CFG_TUD_MIDI2_PRODUCT_ID
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+//--------------------------------------------------------------------+
+// Class Driver Configuration
+//--------------------------------------------------------------------+
+
+#ifndef CFG_TUD_MIDI2_TX_EPSIZE
+ #define CFG_TUD_MIDI2_TX_EPSIZE TUD_EPSIZE_BULK_MAX
+#endif
+
+#ifndef CFG_TUD_MIDI2_RX_EPSIZE
+ #define CFG_TUD_MIDI2_RX_EPSIZE TUD_EPSIZE_BULK_MAX
+#endif
+
+#ifndef CFG_TUD_MIDI2_TX_BUFSIZE
+ #define CFG_TUD_MIDI2_TX_BUFSIZE CFG_TUD_MIDI2_TX_EPSIZE
+#endif
+
+#ifndef CFG_TUD_MIDI2_RX_BUFSIZE
+ #define CFG_TUD_MIDI2_RX_BUFSIZE CFG_TUD_MIDI2_RX_EPSIZE
+#endif
+
+#ifndef CFG_TUD_MIDI2_NUM_GROUPS
+ #define CFG_TUD_MIDI2_NUM_GROUPS 1
+#endif
+
+#ifndef CFG_TUD_MIDI2_NUM_FUNCTION_BLOCKS
+ #define CFG_TUD_MIDI2_NUM_FUNCTION_BLOCKS 1
+#endif
+
+#ifndef CFG_TUD_MIDI2_EP_NAME
+ #define CFG_TUD_MIDI2_EP_NAME "TinyUSB MIDI 2.0"
+#endif
+
+#ifndef CFG_TUD_MIDI2_PRODUCT_ID
+ #define CFG_TUD_MIDI2_PRODUCT_ID "TinyUSB-MIDI2"
+#endif
+
+// String descriptor index for the Group Terminal Block (iBlockItem, Table 5-6).
+// 0 = no string descriptor (default, spec-allowed).
+#ifndef CFG_TUD_MIDI2_BLOCK_STRIDX
+ #define CFG_TUD_MIDI2_BLOCK_STRIDX 0
+#endif
+
+//--------------------------------------------------------------------+
+// MIDI Protocol Values (returned by tud_midi2_n_protocol)
+//--------------------------------------------------------------------+
+
+// Per USB-MIDI 2.0 spec, UMP Stream Configuration messages.
+enum {
+ MIDI_PROTOCOL_MIDI1 = 0x01,
+ MIDI_PROTOCOL_MIDI2 = 0x02,
+};
+
+//--------------------------------------------------------------------+
+// Application Callback API (weak, optional)
+//--------------------------------------------------------------------+
+void tud_midi2_rx_cb(uint8_t itf);
+void tud_midi2_set_itf_cb(uint8_t itf, uint8_t alt);
+bool tud_midi2_get_req_itf_cb(uint8_t rhport, const tusb_control_request_t* request);
+
+// Per-interface UMP Stream config (override for per-itf values).
+uint8_t tud_midi2_num_groups_cb(uint8_t itf);
+uint8_t tud_midi2_num_function_blocks_cb(uint8_t itf);
+const char* tud_midi2_ep_name_cb(uint8_t itf);
+const char* tud_midi2_product_id_cb(uint8_t itf);
+
+//--------------------------------------------------------------------+
+// Application API (Multiple Interfaces)
+//--------------------------------------------------------------------+
+
+bool tud_midi2_n_mounted(uint8_t itf);
+uint32_t tud_midi2_n_available(uint8_t itf);
+uint8_t tud_midi2_n_alt_setting(uint8_t itf);
+bool tud_midi2_n_negotiated(uint8_t itf);
+uint8_t tud_midi2_n_protocol(uint8_t itf);
+
+// Read up to max_words UMP words from the RX FIFO. Returns the number of
+// words actually read (0 if FIFO is empty).
+//
+// NOTE: this function returns when max_words is reached or when the FIFO is
+// empty, whichever comes first. Applications should invoke it in a loop
+// until it returns 0 to guarantee the RX FIFO is fully drained per
+// tud_midi2_rx_cb callback. Leaving words in the FIFO across callbacks can
+// prevent subsequent bulk OUT transfers from landing.
+uint32_t tud_midi2_n_ump_read(uint8_t itf, uint32_t* words, uint32_t max_words);
+uint32_t tud_midi2_n_ump_write(uint8_t itf, const uint32_t* words, uint32_t count);
+
+uint32_t tud_midi2_n_packet_read(uint8_t itf, uint8_t packets[], uint32_t max_packets);
+uint32_t tud_midi2_n_packet_write(uint8_t itf, const uint8_t packets[], uint32_t count);
+
+//--------------------------------------------------------------------+
+// Application API (Single Interface)
+//--------------------------------------------------------------------+
+TU_ATTR_ALWAYS_INLINE static inline bool tud_midi2_mounted(void) {
+ return tud_midi2_n_mounted(0);
+}
+
+TU_ATTR_ALWAYS_INLINE static inline uint32_t tud_midi2_available(void) {
+ return tud_midi2_n_available(0);
+}
+
+TU_ATTR_ALWAYS_INLINE static inline uint8_t tud_midi2_alt_setting(void) {
+ return tud_midi2_n_alt_setting(0);
+}
+
+TU_ATTR_ALWAYS_INLINE static inline bool tud_midi2_negotiated(void) {
+ return tud_midi2_n_negotiated(0);
+}
+
+TU_ATTR_ALWAYS_INLINE static inline uint8_t tud_midi2_protocol(void) {
+ return tud_midi2_n_protocol(0);
+}
+
+TU_ATTR_ALWAYS_INLINE static inline uint32_t
+tud_midi2_ump_read(uint32_t* words, uint32_t max_words) {
+ return tud_midi2_n_ump_read(0, words, max_words);
+}
+
+TU_ATTR_ALWAYS_INLINE static inline uint32_t
+tud_midi2_ump_write(const uint32_t* words, uint32_t count) {
+ return tud_midi2_n_ump_write(0, words, count);
+}
+
+TU_ATTR_ALWAYS_INLINE static inline uint32_t
+tud_midi2_packet_read(uint8_t packets[], uint32_t max_packets) {
+ return tud_midi2_n_packet_read(0, packets, max_packets);
+}
+
+TU_ATTR_ALWAYS_INLINE static inline uint32_t
+tud_midi2_packet_write(const uint8_t packets[], uint32_t count) {
+ return tud_midi2_n_packet_write(0, packets, count);
+}
+
+//--------------------------------------------------------------------+
+// Internal Class Driver API
+//--------------------------------------------------------------------+
+void midi2d_init(void);
+bool midi2d_deinit(void);
+void midi2d_reset(uint8_t rhport);
+uint16_t midi2d_open(uint8_t rhport, const tusb_desc_interface_t* itf_desc, uint16_t max_len);
+bool midi2d_control_xfer_cb(uint8_t rhport, uint8_t stage, const tusb_control_request_t* request);
+bool midi2d_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/src/class/midi/midi2_host.c b/src/class/midi/midi2_host.c
new file mode 100644
index 000000000..6d8861f4b
--- /dev/null
+++ b/src/class/midi/midi2_host.c
@@ -0,0 +1,635 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2026 Saulo Verissimo
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * This file is part of the TinyUSB stack.
+ */
+
+#include "tusb_option.h"
+
+#if (CFG_TUH_ENABLED && CFG_TUH_MIDI2)
+
+#include "host/usbh.h"
+#include "host/usbh_pvt.h"
+#include "midi2_host.h"
+
+#define TU_LOG_DRV(...) TU_LOG(CFG_TUH_MIDI2_LOG_LEVEL, __VA_ARGS__)
+
+//--------------------------------------------------------------------+
+// Weak stubs for application callbacks
+//--------------------------------------------------------------------+
+
+TU_ATTR_WEAK void tuh_midi2_descriptor_cb(uint8_t idx, const tuh_midi2_descriptor_cb_t *desc_cb_data) {
+ (void) idx; (void) desc_cb_data;
+}
+
+TU_ATTR_WEAK void tuh_midi2_mount_cb(uint8_t idx, const tuh_midi2_mount_cb_t *mount_cb_data) {
+ (void) idx; (void) mount_cb_data;
+}
+
+TU_ATTR_WEAK void tuh_midi2_rx_cb(uint8_t idx, uint32_t xferred_bytes) {
+ (void) idx; (void) xferred_bytes;
+}
+
+TU_ATTR_WEAK void tuh_midi2_tx_cb(uint8_t idx, uint32_t xferred_bytes) {
+ (void) idx; (void) xferred_bytes;
+}
+
+TU_ATTR_WEAK void tuh_midi2_umount_cb(uint8_t idx) {
+ (void) idx;
+}
+
+//--------------------------------------------------------------------+
+// Internal structure and state
+//--------------------------------------------------------------------+
+
+typedef struct {
+ uint8_t ep_addr;
+ uint16_t mps;
+ tu_fifo_t ff;
+ uint8_t* ep_buf;
+} midih2_tx_t;
+
+typedef struct {
+ uint8_t daddr;
+ uint8_t bInterfaceNumber;
+
+ uint8_t alt_setting_current;
+
+ uint8_t protocol_version;
+ uint8_t bcdMSC_hi, bcdMSC_lo;
+ uint8_t rx_cable_count_alt0;
+ uint8_t tx_cable_count_alt0;
+ uint8_t rx_cable_count_alt1;
+ uint8_t tx_cable_count_alt1;
+
+ struct {
+ midih2_tx_t tx;
+ tu_edpt_stream_t rx;
+
+ uint8_t rx_ff_buf[CFG_TUH_MIDI2_RX_BUFSIZE];
+ uint8_t tx_ff_buf[CFG_TUH_MIDI2_TX_BUFSIZE];
+ } ep_stream;
+
+ bool mounted;
+} midih2_interface_t;
+
+static midih2_interface_t _midi2_host[CFG_TUH_MIDI2];
+
+typedef struct {
+ TUH_EPBUF_DEF(tx, TUH_EPSIZE_BULK_MAX);
+ TUH_EPBUF_DEF(rx, TUH_EPSIZE_BULK_MAX);
+} midih2_epbuf_t;
+
+CFG_TUH_MEM_SECTION static midih2_epbuf_t _midi2_epbuf[CFG_TUH_MIDI2];
+
+//--------------------------------------------------------------------+
+// Helper functions
+//--------------------------------------------------------------------+
+
+static inline uint8_t find_new_midi2_index(void) {
+ for (uint8_t idx = 0; idx < CFG_TUH_MIDI2; idx++) {
+ if (_midi2_host[idx].daddr == 0) {
+ return idx;
+ }
+ }
+ return TUSB_INDEX_INVALID_8;
+}
+
+static inline uint8_t get_idx_by_ep_addr(uint8_t daddr, uint8_t ep_addr) {
+ for (uint8_t idx = 0; idx < CFG_TUH_MIDI2; idx++) {
+ const midih2_interface_t *p_midi = &_midi2_host[idx];
+ if ((p_midi->daddr == daddr) &&
+ (ep_addr == p_midi->ep_stream.rx.ep_addr || ep_addr == p_midi->ep_stream.tx.ep_addr)) {
+ return idx;
+ }
+ }
+ return TUSB_INDEX_INVALID_8;
+}
+
+static inline bool _tuh_tx_opened(const midih2_interface_t* p_midi) {
+ return p_midi->ep_stream.tx.ep_addr != 0;
+}
+
+static uint8_t _tuh_tx_byte_at(const tu_fifo_buffer_info_t* info, uint16_t offset) {
+ if (offset < info->linear.len) {
+ return info->linear.ptr[offset];
+ }
+ offset = (uint16_t)(offset - info->linear.len);
+ if (offset < info->wrapped.len) {
+ return info->wrapped.ptr[offset];
+ }
+ return 0;
+}
+
+// Largest byte count containing only whole UMP packets and fitting one xfer.
+static uint16_t _tuh_tx_nonseg_len_to_mps(midih2_tx_t* tx) {
+ tu_fifo_buffer_info_t info;
+ tu_fifo_get_read_info(&tx->ff, &info);
+
+ const uint16_t available = (uint16_t)(info.linear.len + info.wrapped.len);
+ uint16_t bytes = 0;
+
+ while (bytes < tx->mps) {
+ if ((uint16_t)(available - bytes) < 4) break;
+
+ uint8_t mt = (uint8_t)((_tuh_tx_byte_at(&info, (uint16_t)(bytes + 3)) >> 4) & 0x0F);
+ uint8_t pkt_words = midi2_ump_word_count(mt);
+ uint16_t pkt_bytes = (uint16_t)(pkt_words * 4);
+
+ if (pkt_bytes == 0) break;
+ if ((uint16_t)(available - bytes) < pkt_bytes) break;
+ if ((uint16_t)(bytes + pkt_bytes) > tx->mps) break;
+
+ bytes = (uint16_t)(bytes + pkt_bytes);
+ }
+
+ return bytes;
+}
+
+// Start one OUT transfer capped at mps. Returns bytes queued, or 0 if nothing.
+static uint16_t _tuh_tx_start_xfer(midih2_interface_t* p_midi) {
+ midih2_tx_t* tx = &p_midi->ep_stream.tx;
+ uint16_t ff_count = tu_fifo_count(&tx->ff);
+ if (ff_count == 0) return 0;
+ if (!usbh_edpt_claim(p_midi->daddr, tx->ep_addr)) return 0;
+
+ uint16_t bytes;
+ if (p_midi->alt_setting_current == 1) {
+ bytes = _tuh_tx_nonseg_len_to_mps(tx);
+ } else {
+ bytes = tu_min16(tu_fifo_count(&tx->ff), tx->mps);
+ }
+ if (bytes == 0) {
+ usbh_edpt_release(p_midi->daddr, tx->ep_addr);
+ return 0;
+ }
+
+ tu_fifo_read_n(&tx->ff, tx->ep_buf, bytes);
+ TU_ASSERT(usbh_edpt_xfer(p_midi->daddr, tx->ep_addr, tx->ep_buf, bytes), 0);
+ return bytes;
+}
+
+static uint32_t _tuh_tx_ump_write(midih2_interface_t* p_midi, const uint32_t* words, uint32_t count) {
+ uint32_t written = 0;
+ while (written < count) {
+ uint8_t mt = (uint8_t)((words[written] >> 28) & 0x0F);
+ uint8_t pkt_words = midi2_ump_word_count(mt);
+ uint16_t pkt_bytes = (uint16_t)(pkt_words * 4);
+
+ if (written + pkt_words > count) break;
+ if (tu_fifo_remaining(&p_midi->ep_stream.tx.ff) < pkt_bytes) break;
+ if (tu_fifo_write_n(&p_midi->ep_stream.tx.ff, &words[written], pkt_bytes) != pkt_bytes) break;
+ written += pkt_words;
+ }
+
+ (void) _tuh_tx_start_xfer(p_midi);
+ return written;
+}
+
+//--------------------------------------------------------------------+
+// Descriptor parsing
+//--------------------------------------------------------------------+
+
+// Parse Alt Setting 0 (MIDI 1.0) descriptors. Returns pointer past last consumed descriptor.
+static const uint8_t* midih2_parse_descriptors_alt0(midih2_interface_t *p_midi,
+ const tusb_desc_interface_t *desc_itf, const uint8_t *desc_end) {
+ TU_VERIFY(AUDIO_SUBCLASS_MIDI_STREAMING == desc_itf->bInterfaceSubClass, NULL);
+
+ p_midi->bInterfaceNumber = desc_itf->bInterfaceNumber;
+
+ const uint8_t *p_desc = (const uint8_t *) desc_itf;
+ p_desc = tu_desc_next(p_desc);
+
+ uint8_t rx_cable_count = 0;
+ uint8_t tx_cable_count = 0;
+ bool found_new_interface = false;
+
+ while (tu_desc_in_bounds(p_desc, desc_end) && !found_new_interface) {
+ switch (tu_desc_type(p_desc)) {
+ case TUSB_DESC_INTERFACE:
+ found_new_interface = true;
+ break;
+
+ case TUSB_DESC_ENDPOINT: {
+ const tusb_desc_endpoint_t *p_ep = (const tusb_desc_endpoint_t *) p_desc;
+
+ TU_ASSERT(tuh_edpt_open(p_midi->daddr, p_ep), NULL);
+ if (tu_edpt_dir(p_ep->bEndpointAddress) == TUSB_DIR_IN) {
+ tu_edpt_stream_open(&p_midi->ep_stream.rx, p_midi->daddr, p_ep, tu_edpt_packet_size(p_ep));
+ tu_edpt_stream_clear(&p_midi->ep_stream.rx);
+ } else {
+ p_midi->ep_stream.tx.ep_addr = p_ep->bEndpointAddress;
+ p_midi->ep_stream.tx.mps = tu_edpt_packet_size(p_ep);
+ tu_fifo_clear(&p_midi->ep_stream.tx.ff);
+ }
+
+ p_desc = tu_desc_next(p_desc);
+ if (tu_desc_in_bounds(p_desc, desc_end) && tu_desc_type(p_desc) == TUSB_DESC_CS_ENDPOINT) {
+ const midi_desc_cs_endpoint_t *p_csep = (const midi_desc_cs_endpoint_t *) p_desc;
+ if (tu_edpt_dir(p_ep->bEndpointAddress) == TUSB_DIR_OUT) {
+ tx_cable_count = p_csep->bNumEmbMIDIJack;
+ } else {
+ rx_cable_count = p_csep->bNumEmbMIDIJack;
+ }
+ }
+ break;
+ }
+
+ default:
+ break;
+ }
+
+ if (!found_new_interface) {
+ p_desc = tu_desc_next(p_desc);
+ }
+ }
+
+ p_midi->rx_cable_count_alt0 = rx_cable_count;
+ p_midi->tx_cable_count_alt0 = tx_cable_count;
+ return p_desc;
+}
+
+// Parse Alt Setting 1 (MIDI 2.0 UMP) descriptors. Returns pointer past last consumed descriptor.
+static const uint8_t* midih2_parse_descriptors_alt1(midih2_interface_t *p_midi,
+ const tusb_desc_interface_t *desc_itf, const uint8_t *desc_end) {
+ TU_VERIFY(AUDIO_SUBCLASS_MIDI_STREAMING == desc_itf->bInterfaceSubClass, NULL);
+ TU_VERIFY(desc_itf->bAlternateSetting == 1, NULL);
+
+ const uint8_t *p_desc = (const uint8_t *) desc_itf;
+ p_desc = tu_desc_next(p_desc);
+
+ uint8_t rx_cable_count = 0;
+ uint8_t tx_cable_count = 0;
+ bool found_new_interface = false;
+
+ while (tu_desc_in_bounds(p_desc, desc_end) && !found_new_interface) {
+ switch (tu_desc_type(p_desc)) {
+ case TUSB_DESC_INTERFACE:
+ found_new_interface = true;
+ break;
+
+ case TUSB_DESC_CS_INTERFACE:
+ if (tu_desc_subtype(p_desc) == MIDI_CS_INTERFACE_HEADER) {
+ // bcdMSC at offset 3-4 in CS Interface Header
+ const uint8_t *bcd_ptr = p_desc + 3;
+ p_midi->bcdMSC_lo = bcd_ptr[0];
+ p_midi->bcdMSC_hi = bcd_ptr[1];
+ if (p_midi->bcdMSC_hi == 0x02) { // bcdMSC 0x0200 = USB-MIDI 2.0
+ p_midi->protocol_version = 1;
+ }
+ }
+ break;
+
+ case TUSB_DESC_ENDPOINT: {
+ const tusb_desc_endpoint_t *p_ep = (const tusb_desc_endpoint_t *) p_desc;
+ p_desc = tu_desc_next(p_desc);
+
+ if (tu_desc_in_bounds(p_desc, desc_end) && tu_desc_type(p_desc) == TUSB_DESC_CS_ENDPOINT) {
+ // MIDI 2.0 CS Endpoint General 2.0: bNumGrpTrmBlk at offset 3
+ if (p_desc[0] >= 4 && p_desc[2] == MIDI_CS_ENDPOINT_GENERAL_2_0) {
+ uint8_t num_grp_trm_blk = p_desc[3];
+ if (tu_edpt_dir(p_ep->bEndpointAddress) == TUSB_DIR_OUT) {
+ tx_cable_count = num_grp_trm_blk;
+ } else {
+ rx_cable_count = num_grp_trm_blk;
+ }
+ }
+ }
+ break;
+ }
+
+ default:
+ break;
+ }
+
+ if (!found_new_interface) {
+ p_desc = tu_desc_next(p_desc);
+ }
+ }
+
+ p_midi->rx_cable_count_alt1 = rx_cable_count;
+ p_midi->tx_cable_count_alt1 = tx_cable_count;
+ return p_desc;
+}
+
+//--------------------------------------------------------------------+
+// Auto-selection logic
+//--------------------------------------------------------------------+
+
+static void midih2_auto_select_alt_setting(midih2_interface_t *p_midi) {
+ p_midi->alt_setting_current = 0;
+ if (p_midi->protocol_version == 1) {
+ p_midi->alt_setting_current = 1;
+ }
+}
+
+//--------------------------------------------------------------------+
+// Init/Deinit
+//--------------------------------------------------------------------+
+
+bool midih2_init(void) {
+ tu_memclr(&_midi2_host, sizeof(_midi2_host));
+ for (int inst = 0; inst < CFG_TUH_MIDI2; inst++) {
+ midih2_interface_t *p_midi = &_midi2_host[inst];
+
+ tu_edpt_stream_init(&p_midi->ep_stream.rx, true, false, false,
+ p_midi->ep_stream.rx_ff_buf, CFG_TUH_MIDI2_RX_BUFSIZE, _midi2_epbuf[inst].rx);
+
+ // TX uses raw tu_fifo + direct usbh_edpt_xfer (no FIFO wrapper) to preserve
+ // UMP packet boundaries across USB transfers.
+ midih2_tx_t* tx = &p_midi->ep_stream.tx;
+ (void) tu_fifo_config(&tx->ff, p_midi->ep_stream.tx_ff_buf, CFG_TUH_MIDI2_TX_BUFSIZE, false);
+ tx->ep_buf = _midi2_epbuf[inst].tx;
+ }
+ return true;
+}
+
+bool midih2_deinit(void) {
+ for (size_t i = 0; i < CFG_TUH_MIDI2; i++) {
+ midih2_interface_t* p_midi = &_midi2_host[i];
+ tu_edpt_stream_deinit(&p_midi->ep_stream.rx);
+ }
+ return true;
+}
+
+//--------------------------------------------------------------------+
+// Class driver callbacks
+//--------------------------------------------------------------------+
+
+uint16_t midih2_open(uint8_t rhport, uint8_t dev_addr, const tusb_desc_interface_t *desc_itf, uint16_t max_len) {
+ (void) rhport;
+
+ TU_VERIFY(TUSB_CLASS_AUDIO == desc_itf->bInterfaceClass, 0);
+
+ // For Alt Setting 1, reuse existing slot for same device+interface
+ uint8_t idx = TUSB_INDEX_INVALID_8;
+ if (desc_itf->bAlternateSetting > 0) {
+ for (uint8_t i = 0; i < CFG_TUH_MIDI2; i++) {
+ if (_midi2_host[i].daddr == dev_addr &&
+ _midi2_host[i].bInterfaceNumber == desc_itf->bInterfaceNumber) {
+ idx = i;
+ break;
+ }
+ }
+ }
+ if (idx == TUSB_INDEX_INVALID_8) {
+ idx = find_new_midi2_index();
+ }
+ TU_VERIFY(idx < CFG_TUH_MIDI2, 0);
+
+ midih2_interface_t *p_midi = &_midi2_host[idx];
+ p_midi->daddr = dev_addr;
+
+ const uint8_t *desc_start = (const uint8_t *) desc_itf;
+ const uint8_t *desc_end = desc_start + max_len;
+
+ // Skip Audio Control interface and any non-MIDI-Streaming descriptors
+ // (following midi_host.c pattern from Ha Thach)
+ if (AUDIO_SUBCLASS_CONTROL == desc_itf->bInterfaceSubClass) {
+ const uint8_t *p_desc = tu_desc_next((const uint8_t *)desc_itf);
+ // Skip CS_INTERFACE header
+ TU_VERIFY(tu_desc_type(p_desc) == TUSB_DESC_CS_INTERFACE, 0);
+ p_desc = tu_desc_next(p_desc);
+ desc_itf = (const tusb_desc_interface_t *) p_desc;
+ // Skip until we find MIDI Streaming interface
+ while (tu_desc_in_bounds(p_desc, desc_end) &&
+ (desc_itf->bDescriptorType != TUSB_DESC_INTERFACE ||
+ (desc_itf->bInterfaceClass == TUSB_CLASS_AUDIO &&
+ desc_itf->bInterfaceSubClass != AUDIO_SUBCLASS_MIDI_STREAMING))) {
+ p_desc = tu_desc_next(p_desc);
+ desc_itf = (const tusb_desc_interface_t *) p_desc;
+ }
+ TU_VERIFY(tu_desc_in_bounds(p_desc, desc_end), 0);
+ TU_VERIFY(TUSB_CLASS_AUDIO == desc_itf->bInterfaceClass, 0);
+ }
+
+ TU_VERIFY(AUDIO_SUBCLASS_MIDI_STREAMING == desc_itf->bInterfaceSubClass, 0);
+
+ TU_LOG_DRV("MIDI2 opening Interface %u Alt %u (addr = %u)\r\n",
+ desc_itf->bInterfaceNumber, desc_itf->bAlternateSetting, dev_addr);
+
+ // Dispatch to appropriate parser based on Alt Setting
+ const uint8_t *p_end = NULL;
+ if (desc_itf->bAlternateSetting == 0) {
+ p_end = midih2_parse_descriptors_alt0(p_midi, desc_itf, desc_end);
+ } else if (desc_itf->bAlternateSetting == 1) {
+ p_end = midih2_parse_descriptors_alt1(p_midi, desc_itf, desc_end);
+ }
+
+ // Return number of bytes consumed (following midi_host.c pattern)
+ uint16_t const parsed_len = (p_end != NULL) ? (uint16_t)(p_end - desc_start) : 0;
+ return parsed_len;
+}
+
+static void midih2_set_config_complete(midih2_interface_t *p_midi, uint8_t idx) {
+ uint8_t dev_addr = p_midi->daddr;
+
+ // Invoke descriptor_cb
+ tuh_midi2_descriptor_cb_t desc_cb = {
+ .protocol_version = p_midi->protocol_version,
+ .bcdMSC_hi = p_midi->bcdMSC_hi,
+ .bcdMSC_lo = p_midi->bcdMSC_lo,
+ .rx_cable_count = (p_midi->alt_setting_current == 0) ?
+ p_midi->rx_cable_count_alt0 : p_midi->rx_cable_count_alt1,
+ .tx_cable_count = (p_midi->alt_setting_current == 0) ?
+ p_midi->tx_cable_count_alt0 : p_midi->tx_cable_count_alt1,
+ };
+ tuh_midi2_descriptor_cb(idx, &desc_cb);
+
+ // Mark as mounted
+ TU_LOG_DRV("MIDI2 mounted addr = %u, alt = %u, protocol = %u\r\n",
+ dev_addr, p_midi->alt_setting_current, p_midi->protocol_version);
+ p_midi->mounted = true;
+
+ // Invoke mount_cb
+ tuh_midi2_mount_cb_t mount_cb = {
+ .daddr = dev_addr,
+ .bInterfaceNumber = p_midi->bInterfaceNumber,
+ .protocol_version = p_midi->protocol_version,
+ .alt_setting_active = p_midi->alt_setting_current,
+ .rx_cable_count = desc_cb.rx_cable_count,
+ .tx_cable_count = desc_cb.tx_cable_count,
+ };
+ tuh_midi2_mount_cb(idx, &mount_cb);
+
+ // Prepare RX transfer
+ tu_edpt_stream_read_xfer(&p_midi->ep_stream.rx);
+
+ // Signal USBH that configuration is complete
+ usbh_driver_set_config_complete(dev_addr, p_midi->bInterfaceNumber);
+}
+
+static void midih2_set_interface_cb(tuh_xfer_t *xfer) {
+ uint8_t const dev_addr = xfer->daddr;
+ uint8_t const itf_num = (uint8_t) tu_le16toh(xfer->setup->wIndex);
+
+ // Find our interface
+ for (uint8_t idx = 0; idx < CFG_TUH_MIDI2; idx++) {
+ if (_midi2_host[idx].daddr == dev_addr && _midi2_host[idx].bInterfaceNumber == itf_num) {
+ if (xfer->result == XFER_RESULT_SUCCESS) {
+ midih2_set_config_complete(&_midi2_host[idx], idx);
+ } else {
+ // SET_INTERFACE failed, fall back to alt 0
+ TU_LOG_DRV("MIDI2 SET_INTERFACE failed, falling back to alt 0\r\n");
+ _midi2_host[idx].alt_setting_current = 0;
+ midih2_set_config_complete(&_midi2_host[idx], idx);
+ }
+ return;
+ }
+ }
+}
+
+bool midih2_set_config(uint8_t dev_addr, uint8_t itf_num) {
+ uint8_t idx = 0;
+ for (idx = 0; idx < CFG_TUH_MIDI2; idx++) {
+ if (_midi2_host[idx].daddr == dev_addr && _midi2_host[idx].bInterfaceNumber == itf_num) {
+ break;
+ }
+ }
+
+ if (idx >= CFG_TUH_MIDI2) {
+ // Not our interface (e.g. Audio Control) - pass through to next
+ usbh_driver_set_config_complete(dev_addr, itf_num);
+ return true;
+ }
+
+ midih2_interface_t *p_midi = &_midi2_host[idx];
+
+ // Auto-select alt setting
+ midih2_auto_select_alt_setting(p_midi);
+
+ // If MIDI 2.0 detected, issue SET_INTERFACE to activate Alt Setting 1
+ if (p_midi->alt_setting_current == 1) {
+ TU_LOG_DRV("MIDI2 requesting SET_INTERFACE alt 1 for itf %u\r\n", itf_num);
+ TU_ASSERT(tuh_interface_set(dev_addr, itf_num, 1, midih2_set_interface_cb, 0));
+ } else {
+ // MIDI 1.0 only, complete immediately
+ midih2_set_config_complete(p_midi, idx);
+ }
+
+ return true;
+}
+
+void midih2_close(uint8_t dev_addr) {
+ for (uint8_t idx = 0; idx < CFG_TUH_MIDI2; idx++) {
+ midih2_interface_t *p_midi = &_midi2_host[idx];
+ if (p_midi->daddr == dev_addr) {
+ TU_LOG_DRV(" MIDI2 close addr = %u index = %u\r\n", dev_addr, idx);
+ tu_edpt_stream_close(&p_midi->ep_stream.rx);
+ tu_fifo_clear(&p_midi->ep_stream.tx.ff);
+ p_midi->ep_stream.tx.ep_addr = 0;
+ tuh_midi2_umount_cb(idx);
+ tu_memclr(p_midi, sizeof(midih2_interface_t));
+ }
+ }
+}
+
+bool midih2_xfer_cb(uint8_t dev_addr, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes) {
+ uint8_t idx = get_idx_by_ep_addr(dev_addr, ep_addr);
+ TU_VERIFY(idx < CFG_TUH_MIDI2);
+
+ midih2_interface_t *p_midi = &_midi2_host[idx];
+ midih2_tx_t* ep_tx = &p_midi->ep_stream.tx;
+
+ if (ep_addr == p_midi->ep_stream.rx.ep_addr) {
+ if (result == XFER_RESULT_SUCCESS && xferred_bytes > 0) {
+ tu_edpt_stream_read_xfer_complete(&p_midi->ep_stream.rx, xferred_bytes);
+ tuh_midi2_rx_cb(idx, xferred_bytes);
+ }
+ tu_edpt_stream_read_xfer(&p_midi->ep_stream.rx);
+ } else if (ep_addr == ep_tx->ep_addr) {
+ tuh_midi2_tx_cb(idx, xferred_bytes);
+ if (result == XFER_RESULT_SUCCESS) {
+ uint16_t queued = _tuh_tx_start_xfer(p_midi);
+ // Send ZLP if no more data is queued but the last transfer was exactly mps
+ if (queued == 0 && tu_fifo_count(&ep_tx->ff) == 0 && xferred_bytes > 0 &&
+ (0 == (xferred_bytes & (ep_tx->mps - 1)))) {
+ if (usbh_edpt_claim(dev_addr, ep_tx->ep_addr)) {
+ usbh_edpt_xfer(dev_addr, ep_tx->ep_addr, NULL, 0);
+ }
+ }
+ }
+ }
+
+ return true;
+}
+
+//--------------------------------------------------------------------+
+// Public API
+//--------------------------------------------------------------------+
+
+bool tuh_midi2_mounted(uint8_t idx) {
+ TU_VERIFY(idx < CFG_TUH_MIDI2);
+ return _midi2_host[idx].mounted;
+}
+
+uint8_t tuh_midi2_get_protocol_version(uint8_t idx) {
+ TU_VERIFY(idx < CFG_TUH_MIDI2);
+ return _midi2_host[idx].protocol_version;
+}
+
+uint8_t tuh_midi2_get_alt_setting_active(uint8_t idx) {
+ TU_VERIFY(idx < CFG_TUH_MIDI2);
+ return _midi2_host[idx].alt_setting_current;
+}
+
+uint8_t tuh_midi2_get_cable_count(uint8_t idx) {
+ TU_VERIFY(idx < CFG_TUH_MIDI2);
+ return (_midi2_host[idx].alt_setting_current == 0) ?
+ _midi2_host[idx].rx_cable_count_alt0 : _midi2_host[idx].rx_cable_count_alt1;
+}
+
+uint32_t tuh_midi2_ump_read(uint8_t idx, uint32_t* words, uint32_t max_words) {
+ TU_VERIFY(idx < CFG_TUH_MIDI2 && words && max_words);
+
+ midih2_interface_t *p_midi = &_midi2_host[idx];
+ tu_edpt_stream_t *ep_rx = &p_midi->ep_stream.rx;
+
+ uint32_t n_words = 0;
+ for (uint32_t i = 0; i < max_words; i++) {
+ if (tu_edpt_stream_read_available(ep_rx) >= 4) {
+ tu_edpt_stream_read(ep_rx, (uint8_t *) &words[i], 4);
+ n_words++;
+ } else {
+ break;
+ }
+ }
+
+ return n_words;
+}
+
+uint32_t tuh_midi2_ump_write(uint8_t idx, const uint32_t* words, uint32_t count) {
+ TU_VERIFY(idx < CFG_TUH_MIDI2 && words && count);
+
+ midih2_interface_t *p_midi = &_midi2_host[idx];
+ TU_VERIFY(_tuh_tx_opened(p_midi), 0);
+
+ return _tuh_tx_ump_write(p_midi, words, count);
+}
+
+uint32_t tuh_midi2_write_flush(uint8_t idx) {
+ TU_VERIFY(idx < CFG_TUH_MIDI2);
+ return _tuh_tx_start_xfer(&_midi2_host[idx]);
+}
+
+#endif // CFG_TUH_ENABLED && CFG_TUH_MIDI2
diff --git a/src/class/midi/midi2_host.h b/src/class/midi/midi2_host.h
new file mode 100644
index 000000000..47039eb85
--- /dev/null
+++ b/src/class/midi/midi2_host.h
@@ -0,0 +1,107 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2026 Saulo Verissimo
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * This file is part of the TinyUSB stack.
+ */
+
+#ifndef TUSB_MIDI2_HOST_H_
+#define TUSB_MIDI2_HOST_H_
+
+#include "class/audio/audio.h"
+#include "midi.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+//--------------------------------------------------------------------+
+// Callback Type Definitions
+//--------------------------------------------------------------------+
+
+typedef struct {
+ uint8_t protocol_version; // 0 = MIDI 1.0 only, 1 = MIDI 2.0
+ uint8_t bcdMSC_hi, bcdMSC_lo; // MIDI version from descriptor
+ uint8_t rx_cable_count; // For both alt settings (same for Alt 0 and Alt 1)
+ uint8_t tx_cable_count;
+} tuh_midi2_descriptor_cb_t;
+
+typedef struct {
+ uint8_t daddr;
+ uint8_t bInterfaceNumber;
+ uint8_t protocol_version; // 0 = MIDI 1.0, 1 = MIDI 2.0
+ uint8_t alt_setting_active; // 0 or 1
+ uint8_t rx_cable_count;
+ uint8_t tx_cable_count;
+} tuh_midi2_mount_cb_t;
+
+//--------------------------------------------------------------------+
+// Application Callback API (weak, optional)
+//--------------------------------------------------------------------+
+
+void tuh_midi2_descriptor_cb(uint8_t idx, const tuh_midi2_descriptor_cb_t *desc_cb_data);
+void tuh_midi2_mount_cb(uint8_t idx, const tuh_midi2_mount_cb_t *mount_cb_data);
+void tuh_midi2_rx_cb(uint8_t idx, uint32_t xferred_bytes);
+void tuh_midi2_tx_cb(uint8_t idx, uint32_t xferred_bytes);
+void tuh_midi2_umount_cb(uint8_t idx);
+
+//--------------------------------------------------------------------+
+// Application API - Query
+//--------------------------------------------------------------------+
+
+bool tuh_midi2_mounted(uint8_t idx);
+uint8_t tuh_midi2_get_protocol_version(uint8_t idx);
+uint8_t tuh_midi2_get_alt_setting_active(uint8_t idx);
+uint8_t tuh_midi2_get_cable_count(uint8_t idx);
+
+//--------------------------------------------------------------------+
+// Application API - I/O
+//--------------------------------------------------------------------+
+
+// Read up to max_words UMP words from the RX FIFO. Returns the number of
+// words actually read (0 if FIFO is empty).
+//
+// NOTE: this function returns when max_words is reached or when the FIFO is
+// empty, whichever comes first. Applications should invoke it in a loop
+// until it returns 0 to guarantee the RX FIFO is fully drained per
+// tuh_midi2_rx_cb callback. Leaving words in the FIFO across callbacks can
+// prevent subsequent bulk IN transfers from landing.
+uint32_t tuh_midi2_ump_read(uint8_t idx, uint32_t* words, uint32_t max_words);
+uint32_t tuh_midi2_ump_write(uint8_t idx, const uint32_t* words, uint32_t count);
+uint32_t tuh_midi2_write_flush(uint8_t idx);
+
+//--------------------------------------------------------------------+
+// Internal Class Driver API
+//--------------------------------------------------------------------+
+
+bool midih2_init(void);
+bool midih2_deinit(void);
+bool midih2_set_config(uint8_t dev_addr, uint8_t itf_num);
+void midih2_close(uint8_t dev_addr);
+uint16_t midih2_open(uint8_t rhport, uint8_t dev_addr, const tusb_desc_interface_t *desc_itf, uint16_t max_len);
+bool midih2_xfer_cb(uint8_t dev_addr, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/src/common/tusb_mcu.h b/src/common/tusb_mcu.h
index c85ade4d0..413ac4850 100644
--- a/src/common/tusb_mcu.h
+++ b/src/common/tusb_mcu.h
@@ -616,10 +616,6 @@
#define CFG_TUH_WCH_USBIP_USBFS 1
#endif
- #define TUP_USBIP_FSDEV
- #define TUP_USBIP_FSDEV_CH32
- #define CFG_TUSB_FSDEV_PMA_SIZE 512u
-
// default to FSDEV for device
#if !defined(CFG_TUD_WCH_USBIP_USBFS)
#define CFG_TUD_WCH_USBIP_USBFS 0
@@ -629,6 +625,12 @@
#define CFG_TUD_WCH_USBIP_FSDEV (CFG_TUD_WCH_USBIP_USBFS ? 0 : 1)
#endif
+ #if CFG_TUD_WCH_USBIP_FSDEV
+ #define TUP_USBIP_FSDEV
+ #define TUP_USBIP_FSDEV_CH32
+ #define CFG_TUSB_FSDEV_PMA_SIZE 512u
+ #endif
+
#define TUP_DCD_ENDPOINT_MAX 8
#elif TU_CHECK_MCU(OPT_MCU_CH32V307)
diff --git a/src/device/usbd.c b/src/device/usbd.c
index 0e58f70bf..55ad330c1 100644
--- a/src/device/usbd.c
+++ b/src/device/usbd.c
@@ -254,6 +254,20 @@ static const usbd_class_driver_t _usbd_driver[] = {
},
#endif
+ #if CFG_TUD_MIDI2
+ {
+ .name = DRIVER_NAME("MIDI2"),
+ .init = midi2d_init,
+ .deinit = midi2d_deinit,
+ .open = midi2d_open,
+ .reset = midi2d_reset,
+ .control_xfer_cb = midi2d_control_xfer_cb,
+ .xfer_cb = midi2d_xfer_cb,
+ .xfer_isr = NULL,
+ .sof = NULL
+ },
+ #endif
+
#if CFG_TUD_VENDOR
{
.name = DRIVER_NAME("VENDOR"),
diff --git a/src/device/usbd.h b/src/device/usbd.h
index 473e697ac..abce5a887 100644
--- a/src/device/usbd.h
+++ b/src/device/usbd.h
@@ -425,6 +425,43 @@ bool tud_vendor_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_requ
TUD_MIDI_JACKID_OUT_EMB(1)
//--------------------------------------------------------------------+
+// MIDI 2.0 Descriptor Templates (USB-MIDI 2.0)
+//--------------------------------------------------------------------+
+
+// Alt Setting 1: MS Interface + MS Header (bcdMSC=0x0200)
+// Per USB-MIDI 2.0 Table 5-2: wTotalLength in the MS Header is not used in 2.0
+// and shall be set to match bLength (= 0x0007) for conformity with USB-MIDI 1.0.
+#define TUD_MIDI2_DESC_ALT1_HEAD_LEN (9 + 7)
+#define TUD_MIDI2_DESC_ALT1_HEAD(_itfnum, _stridx) \
+ /* MIDI Streaming Interface, Alt Setting 1 */\
+ 9, TUSB_DESC_INTERFACE, (uint8_t)((_itfnum) + 1), 1, 2, TUSB_CLASS_AUDIO, AUDIO_SUBCLASS_MIDI_STREAMING, AUDIO_FUNC_PROTOCOL_CODE_UNDEF, 0,\
+ /* MS Header (MIDI 2.0): wTotalLength = bLength per spec */\
+ 7, TUSB_DESC_CS_INTERFACE, MIDI_CS_INTERFACE_HEADER, U16_TO_U8S_LE(0x0200), U16_TO_U8S_LE(0x0007)
+
+// Alt Setting 1: Standard USB Endpoint (7 bytes) + CS Endpoint General 2.0
+#define TUD_MIDI2_DESC_ALT1_EP_LEN(_numgtbs) (7 + 4 + (_numgtbs))
+#define TUD_MIDI2_DESC_ALT1_EP(_ep, _epsize, _numgtbs, ...) \
+ 7, TUSB_DESC_ENDPOINT, _ep, TUSB_XFER_BULK, U16_TO_U8S_LE(_epsize), 0, \
+ (uint8_t)(4 + (_numgtbs)), TUSB_DESC_CS_ENDPOINT, MIDI_CS_ENDPOINT_GENERAL_2_0, _numgtbs, ## __VA_ARGS__
+
+// Total length: Alt 0 (MIDI 1.0) + Alt 1 (UMP)
+#define TUD_MIDI2_DESC_LEN (TUD_MIDI_DESC_LEN + TUD_MIDI2_DESC_ALT1_HEAD_LEN + TUD_MIDI2_DESC_ALT1_EP_LEN(1) * 2)
+
+// Complete MIDI 2.0 descriptor with both alternate settings (single cable/GTB)
+#define TUD_MIDI2_DESCRIPTOR(_itfnum, _stridx, _epout, _epin, _epsize) \
+ /* Alt Setting 0 (MIDI 1.0) */\
+ TUD_MIDI_DESC_HEAD(_itfnum, _stridx, 1),\
+ TUD_MIDI_DESC_JACK_DESC(1, 0),\
+ TUD_MIDI_DESC_EP(_epout, _epsize, 1),\
+ TUD_MIDI_JACKID_IN_EMB(1),\
+ TUD_MIDI_DESC_EP(_epin, _epsize, 1),\
+ TUD_MIDI_JACKID_OUT_EMB(1),\
+ /* Alt Setting 1 (UMP) */\
+ TUD_MIDI2_DESC_ALT1_HEAD(_itfnum, _stridx),\
+ TUD_MIDI2_DESC_ALT1_EP(_epout, _epsize, 1, 1 /* bAssoGrpTrmBlkID */),\
+ TUD_MIDI2_DESC_ALT1_EP(_epin, _epsize, 1, 1 /* bAssoGrpTrmBlkID */)
+
+//--------------------------------------------------------------------+
// Audio Descriptor Templates
//--------------------------------------------------------------------+
diff --git a/src/host/usbh.c b/src/host/usbh.c
index 490724b02..2e3c93c5e 100644
--- a/src/host/usbh.c
+++ b/src/host/usbh.c
@@ -278,6 +278,18 @@ static usbh_class_driver_t const usbh_class_drivers[] = {
},
#endif
+ #if CFG_TUH_MIDI2
+ {
+ .name = DRIVER_NAME("MIDI2"),
+ .init = midih2_init,
+ .deinit = midih2_deinit,
+ .open = midih2_open,
+ .set_config = midih2_set_config,
+ .xfer_cb = midih2_xfer_cb,
+ .close = midih2_close
+ },
+ #endif
+
#if CFG_TUH_HUB
{
.name = DRIVER_NAME("HUB"),
diff --git a/src/portable/raspberrypi/rp2040/hcd_rp2040.c b/src/portable/raspberrypi/rp2040/hcd_rp2040.c
index 02a4e055e..064834efb 100644
--- a/src/portable/raspberrypi/rp2040/hcd_rp2040.c
+++ b/src/portable/raspberrypi/rp2040/hcd_rp2040.c
@@ -617,10 +617,16 @@ bool hcd_edpt_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr, uint8_t *b
io_rw_32 *buf_reg = dpram_int_ep_buffer_ctrl(ep->interrupt_num);
rp2usb_xfer_start(ep, ep_reg, buf_reg, buffer, NULL, buflen);
} else {
- // Control endpoint can change direction 0x00 <-> 0x80 when changing stages
- if (ep_addr != ep->ep_addr) {
+ // Control transfer data and status stages always start with DATA1, regardless of
+ // whether the direction changed since the previous stage. SET_REPORT (and any other
+ // host-to-device class request with an OUT data stage) keeps the same direction
+ // across SETUP -> DATA, so we cannot key off "direction changed" -- we must reset
+ // next_pid every time hcd_edpt_xfer is invoked on ep 0. Without this, the data stage
+ // of SET_REPORT goes out as DATA0 because ep->next_pid is still 0 from hcd_edpt_open(),
+ // which strict devices treat as a protocol violation and disconnect.
+ if (tu_edpt_number(ep_addr) == 0) {
ep->ep_addr = ep_addr;
- ep->next_pid = 1; // data and status stage start with DATA1
+ ep->next_pid = 1;
}
// If EPX is busy with another transfer, mark as pending
diff --git a/src/portable/wch/dcd_ch32_usbfs.c b/src/portable/wch/dcd_ch32_usbfs.c
index 5cd25e33e..af0f17785 100644
--- a/src/portable/wch/dcd_ch32_usbfs.c
+++ b/src/portable/wch/dcd_ch32_usbfs.c
@@ -29,29 +29,29 @@
#if CFG_TUD_ENABLED && defined(TUP_USBIP_WCH_USBFS) && CFG_TUD_WCH_USBIP_USBFS
-#include "device/dcd.h"
-#include "ch32_usbfs_reg.h"
+ #include "device/dcd.h"
+ #include "ch32_usbfs_reg.h"
-/* private defines */
-#define EP_MAX (8)
+ /* private defines */
+ #define EP_MAX (8)
-#define EP_DMA(ep) ((&USBOTG_FS->UEP0_DMA)[ep])
-#define EP_TX_LEN(ep) ((&USBOTG_FS->UEP0_TX_LEN)[2 * ep])
-#define EP_TX_CTRL(ep) ((&USBOTG_FS->UEP0_TX_CTRL)[4 * ep])
-#define EP_RX_CTRL(ep) ((&USBOTG_FS->UEP0_RX_CTRL)[4 * ep])
+ #define EP_DMA(ep) ((&USBOTG_FS->UEP0_DMA)[ep])
+ #define EP_TX_LEN(ep) ((&USBOTG_FS->UEP0_TX_LEN)[2 * ep])
+ #define EP_TX_CTRL(ep) ((&USBOTG_FS->UEP0_TX_CTRL)[4 * ep])
+ #define EP_RX_CTRL(ep) ((&USBOTG_FS->UEP0_RX_CTRL)[4 * ep])
/* private data */
struct usb_xfer {
- bool valid;
- uint8_t* buffer;
- size_t len;
- size_t processed_len;
- size_t max_size;
+ bool valid;
+ uint8_t *buffer;
+ size_t len;
+ size_t processed_len;
+ size_t max_size;
};
static struct {
- bool ep0_tog;
- bool isochronous[EP_MAX];
+ bool ep0_tog;
+ bool isochronous[EP_MAX];
struct usb_xfer xfer[EP_MAX][2];
TU_ATTR_ALIGNED(4) uint8_t buffer[EP_MAX][2][64];
TU_ATTR_ALIGNED(4) struct {
@@ -64,7 +64,7 @@ static struct {
/* private helpers */
static void update_in(uint8_t rhport, uint8_t ep, bool force) {
- struct usb_xfer* xfer = &data.xfer[ep][TUSB_DIR_IN];
+ struct usb_xfer *xfer = &data.xfer[ep][TUSB_DIR_IN];
if (xfer->valid) {
if (force || xfer->len) {
size_t len = TU_MIN(xfer->max_size, xfer->len);
@@ -82,7 +82,7 @@ static void update_in(uint8_t rhport, uint8_t ep, bool force) {
EP_TX_LEN(ep) = len;
if (ep == 0) {
EP_TX_CTRL(0) = USBFS_EP_T_RES_ACK | (data.ep0_tog ? USBFS_EP_T_TOG : 0);
- data.ep0_tog = !data.ep0_tog;
+ data.ep0_tog = !data.ep0_tog;
} else if (data.isochronous[ep]) {
EP_TX_CTRL(ep) = (EP_TX_CTRL(ep) & ~(USBFS_EP_T_RES_MASK)) | USBFS_EP_T_RES_NYET;
} else {
@@ -90,16 +90,18 @@ static void update_in(uint8_t rhport, uint8_t ep, bool force) {
}
} else {
xfer->valid = false;
- EP_TX_CTRL(ep) = (EP_TX_CTRL(ep) & ~(USBFS_EP_T_RES_MASK)) | USBFS_EP_T_RES_NAK;
- dcd_event_xfer_complete(
- rhport, ep | TUSB_DIR_IN_MASK, xfer->processed_len,
- XFER_RESULT_SUCCESS, true);
+ if (ep == 0) {
+ EP_TX_CTRL(0) = USBFS_EP_T_RES_NAK | (data.ep0_tog ? USBFS_EP_T_TOG : 0);
+ } else if (!data.isochronous[ep]) {
+ EP_TX_CTRL(ep) = (EP_TX_CTRL(ep) & ~(USBFS_EP_T_RES_MASK)) | USBFS_EP_T_RES_NAK;
+ }
+ dcd_event_xfer_complete(rhport, ep | TUSB_DIR_IN_MASK, xfer->processed_len, XFER_RESULT_SUCCESS, true);
}
}
}
static void update_out(uint8_t rhport, uint8_t ep, size_t rx_len) {
- struct usb_xfer* xfer = &data.xfer[ep][TUSB_DIR_OUT];
+ struct usb_xfer *xfer = &data.xfer[ep][TUSB_DIR_OUT];
if (xfer->valid) {
size_t len = TU_MIN(xfer->max_size, TU_MIN(xfer->len, rx_len));
if (ep == 3) {
@@ -117,25 +119,39 @@ static void update_out(uint8_t rhport, uint8_t ep, size_t rx_len) {
}
if (ep == 0) {
- EP_RX_CTRL(0) = USBFS_EP_R_RES_ACK;
+ EP_RX_CTRL(0) = USBFS_EP_R_RES_NAK;
+ } else {
+ uint8_t rx_res =
+ data.isochronous[ep] ? USBFS_EP_R_RES_NYET : (xfer->valid ? USBFS_EP_R_RES_ACK : USBFS_EP_R_RES_NAK);
+ EP_RX_CTRL(ep) = (EP_RX_CTRL(ep) & ~USBFS_EP_R_RES_MASK) | rx_res;
}
}
}
+static void reset_ep_ctrls(void) {
+ for (uint8_t ep = 1; ep < EP_MAX; ep++) {
+ EP_DMA(ep) = (uint32_t)&data.buffer[ep][0];
+ EP_TX_LEN(ep) = 0;
+ EP_TX_CTRL(ep) = USBFS_EP_T_AUTO_TOG | USBFS_EP_T_RES_NYET;
+ EP_RX_CTRL(ep) = USBFS_EP_R_AUTO_TOG | USBFS_EP_R_RES_NYET;
+ }
+ EP_DMA(3) = (uint32_t)&data.ep3_buffer.out[0];
+}
+
/* public functions */
-bool dcd_init(uint8_t rhport, const tusb_rhport_init_t* rh_init) {
- (void) rh_init;
+bool dcd_init(uint8_t rhport, const tusb_rhport_init_t *rh_init) {
+ (void)rh_init;
// init registers
USBOTG_FS->BASE_CTRL = USBFS_CTRL_SYS_CTRL | USBFS_CTRL_INT_BUSY | USBFS_CTRL_DMA_EN;
USBOTG_FS->UDEV_CTRL = USBFS_UDEV_CTRL_PD_DIS | USBFS_UDEV_CTRL_PORT_EN;
- USBOTG_FS->DEV_ADDR = 0x00;
+ USBOTG_FS->DEV_ADDR = 0x00;
USBOTG_FS->INT_FG = 0xFF;
USBOTG_FS->INT_EN = USBFS_INT_EN_BUS_RST | USBFS_INT_EN_TRANSFER | USBFS_INT_EN_SUSPEND;
// setup endpoint 0
- EP_DMA(0) = (uint32_t) &data.buffer[0][0];
- EP_TX_LEN(0) = 0;
+ EP_DMA(0) = (uint32_t)&data.buffer[0][0];
+ EP_TX_LEN(0) = 0;
EP_TX_CTRL(0) = USBFS_EP_T_RES_NAK;
EP_RX_CTRL(0) = USBFS_EP_R_RES_ACK;
@@ -143,15 +159,9 @@ bool dcd_init(uint8_t rhport, const tusb_rhport_init_t* rh_init) {
USBOTG_FS->UEP4_1_MOD = 0xCC;
USBOTG_FS->UEP2_3_MOD = 0xCC;
USBOTG_FS->UEP5_6_MOD = 0xCC;
- USBOTG_FS->UEP7_MOD = 0x0C;
+ USBOTG_FS->UEP7_MOD = 0x0C;
- for (uint8_t ep = 1; ep < EP_MAX; ep++) {
- EP_DMA(ep) = (uint32_t) &data.buffer[ep][0];
- EP_TX_LEN(ep) = 0;
- EP_TX_CTRL(ep) = USBFS_EP_T_AUTO_TOG | USBFS_EP_T_RES_NAK;
- EP_RX_CTRL(ep) = USBFS_EP_R_AUTO_TOG | USBFS_EP_R_RES_NAK;
- }
- EP_DMA(3) = (uint32_t) &data.ep3_buffer.out[0];
+ reset_ep_ctrls();
dcd_connect(rhport);
@@ -159,15 +169,15 @@ bool dcd_init(uint8_t rhport, const tusb_rhport_init_t* rh_init) {
}
void dcd_int_handler(uint8_t rhport) {
- (void) rhport;
+ (void)rhport;
uint8_t status = USBOTG_FS->INT_FG;
if (status & USBFS_INT_FG_TRANSFER) {
- uint8_t ep = USBFS_INT_ST_MASK_UIS_ENDP(USBOTG_FS->INT_ST);
- uint8_t token = USBFS_INT_ST_MASK_UIS_TOKEN(USBOTG_FS->INT_ST);
+ uint8_t ep = USBFS_INT_ST_MASK_UIS_ENDP(USBOTG_FS->INT_ST);
+ uint8_t token = USBFS_INT_ST_MASK_UIS_TOKEN(USBOTG_FS->INT_ST);
+ uint16_t rx_len = USBOTG_FS->RX_LEN;
switch (token) {
case PID_OUT: {
- uint16_t rx_len = USBOTG_FS->RX_LEN;
update_out(rhport, ep, rx_len);
break;
}
@@ -179,24 +189,30 @@ void dcd_int_handler(uint8_t rhport) {
case PID_SETUP:
// setup clears stall
EP_TX_CTRL(0) = USBFS_EP_T_RES_NAK;
- EP_RX_CTRL(0) = USBFS_EP_R_RES_ACK;
+ data.ep0_tog = true;
+
+ const tusb_control_request_t *setup = (const tusb_control_request_t *)&data.buffer[0][TUSB_DIR_OUT][0];
+ EP_RX_CTRL(0) = (setup->wLength == 0) ? USBFS_EP_R_RES_ACK : USBFS_EP_R_RES_NAK;
- data.ep0_tog = true;
dcd_event_setup_received(rhport, &data.buffer[0][TUSB_DIR_OUT][0], true);
break;
}
USBOTG_FS->INT_FG = USBFS_INT_FG_TRANSFER;
} else if (status & USBFS_INT_FG_BUS_RST) {
- data.ep0_tog = true;
+ data.ep0_tog = true;
data.xfer[0][TUSB_DIR_OUT].max_size = 64;
- data.xfer[0][TUSB_DIR_IN].max_size = 64;
+ data.xfer[0][TUSB_DIR_IN].max_size = 64;
- //dcd_event_bus_reset(rhport, (USBOTG_FS->BASE_CTRL & USBFS_CTRL_LOW_SPEED) ? TUSB_SPEED_LOW : TUSB_SPEED_FULL, true);
- dcd_event_bus_reset(rhport, (USBOTG_FS->UDEV_CTRL & USBFS_UDEV_CTRL_LOW_SPEED) ? TUSB_SPEED_LOW : TUSB_SPEED_FULL, true);
+ // dcd_event_bus_reset(rhport, (USBOTG_FS->BASE_CTRL & USBFS_CTRL_LOW_SPEED) ? TUSB_SPEED_LOW : TUSB_SPEED_FULL,
+ // true);
+ dcd_event_bus_reset(rhport, (USBOTG_FS->UDEV_CTRL & USBFS_UDEV_CTRL_LOW_SPEED) ? TUSB_SPEED_LOW : TUSB_SPEED_FULL,
+ true);
USBOTG_FS->DEV_ADDR = 0x00;
- EP_RX_CTRL(0) = USBFS_EP_R_RES_ACK;
+ EP_RX_CTRL(0) = USBFS_EP_R_RES_ACK;
+
+ reset_ep_ctrls();
USBOTG_FS->INT_FG = USBFS_INT_FG_BUS_RST;
} else if (status & USBFS_INT_FG_SUSPEND) {
@@ -207,71 +223,62 @@ void dcd_int_handler(uint8_t rhport) {
}
void dcd_int_enable(uint8_t rhport) {
- (void) rhport;
+ (void)rhport;
NVIC_EnableIRQ(USBHD_IRQn);
}
void dcd_int_disable(uint8_t rhport) {
- (void) rhport;
+ (void)rhport;
NVIC_DisableIRQ(USBHD_IRQn);
}
void dcd_set_address(uint8_t rhport, uint8_t dev_addr) {
- (void) dev_addr;
+ (void)dev_addr;
dcd_edpt_xfer(rhport, 0x80, NULL, 0, false); // zlp status response
}
void dcd_remote_wakeup(uint8_t rhport) {
- (void) rhport;
+ (void)rhport;
// TODO optional
}
void dcd_connect(uint8_t rhport) {
- (void) rhport;
+ (void)rhport;
USBOTG_FS->BASE_CTRL |= USBFS_CTRL_DEV_PUEN;
}
void dcd_disconnect(uint8_t rhport) {
- (void) rhport;
+ (void)rhport;
USBOTG_FS->BASE_CTRL &= ~USBFS_CTRL_DEV_PUEN;
}
void dcd_sof_enable(uint8_t rhport, bool en) {
- (void) rhport;
- (void) en;
+ (void)rhport;
+ (void)en;
// TODO implement later
}
-void dcd_edpt0_status_complete(uint8_t rhport, tusb_control_request_t const* request) {
- (void) rhport;
+void dcd_edpt0_status_complete(uint8_t rhport, const tusb_control_request_t *request) {
+ (void)rhport;
if (request->bmRequestType_bit.recipient == TUSB_REQ_RCPT_DEVICE &&
- request->bmRequestType_bit.type == TUSB_REQ_TYPE_STANDARD &&
- request->bRequest == TUSB_REQ_SET_ADDRESS) {
- USBOTG_FS->DEV_ADDR = (uint8_t) request->wValue;
+ request->bmRequestType_bit.type == TUSB_REQ_TYPE_STANDARD && request->bRequest == TUSB_REQ_SET_ADDRESS) {
+ USBOTG_FS->DEV_ADDR = (uint8_t)request->wValue;
}
- EP_TX_CTRL(0) = USBFS_EP_T_RES_NAK;
- EP_RX_CTRL(0) = USBFS_EP_R_RES_ACK;
}
-bool dcd_edpt_open(uint8_t rhport, tusb_desc_endpoint_t const* desc_ep) {
- (void) rhport;
- uint8_t ep = tu_edpt_number(desc_ep->bEndpointAddress);
+bool dcd_edpt_open(uint8_t rhport, const tusb_desc_endpoint_t *desc_ep) {
+ (void)rhport;
+ uint8_t ep = tu_edpt_number(desc_ep->bEndpointAddress);
uint8_t dir = tu_edpt_dir(desc_ep->bEndpointAddress);
TU_ASSERT(ep < EP_MAX);
- data.isochronous[ep] = desc_ep->bmAttributes.xfer == TUSB_XFER_ISOCHRONOUS;
data.xfer[ep][dir].max_size = tu_edpt_packet_size(desc_ep);
if (ep != 0) {
if (dir == TUSB_DIR_OUT) {
- if (data.isochronous[ep]) {
- EP_RX_CTRL(ep) = USBFS_EP_R_AUTO_TOG | USBFS_EP_R_RES_NYET;
- } else {
- EP_RX_CTRL(ep) = USBFS_EP_R_AUTO_TOG | USBFS_EP_R_RES_ACK;
- }
+ EP_RX_CTRL(ep) = USBFS_EP_R_AUTO_TOG | USBFS_EP_T_RES_NAK;
} else {
- EP_TX_LEN(ep) = 0;
EP_TX_CTRL(ep) = USBFS_EP_T_AUTO_TOG | USBFS_EP_T_RES_NAK;
}
}
@@ -279,52 +286,60 @@ bool dcd_edpt_open(uint8_t rhport, tusb_desc_endpoint_t const* desc_ep) {
}
void dcd_edpt_close_all(uint8_t rhport) {
- (void) rhport;
+ (void)rhport;
// TODO optional
}
bool dcd_edpt_iso_alloc(uint8_t rhport, uint8_t ep_addr, uint16_t largest_packet_size) {
- (void) rhport;
- (void) ep_addr;
+ (void)rhport;
+ (void)ep_addr;
(void)largest_packet_size;
- return false;
+ uint8_t ep = tu_edpt_number(ep_addr);
+ uint8_t dir = tu_edpt_dir(ep_addr);
+
+ data.isochronous[ep] = true;
+ data.xfer[ep][dir].max_size = largest_packet_size;
+ return true;
}
bool dcd_edpt_iso_activate(uint8_t rhport, const tusb_desc_endpoint_t *desc_ep) {
(void)rhport;
(void)desc_ep;
- return false;
+ return true;
}
-bool dcd_edpt_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t * buffer, uint16_t total_bytes, bool is_isr) {
- (void) is_isr;
- (void) rhport;
- uint8_t ep = tu_edpt_number(ep_addr);
+bool dcd_edpt_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t *buffer, uint16_t total_bytes, bool is_isr) {
+ (void)is_isr;
+ (void)rhport;
+ uint8_t ep = tu_edpt_number(ep_addr);
uint8_t dir = tu_edpt_dir(ep_addr);
- struct usb_xfer* xfer = &data.xfer[ep][dir];
+ struct usb_xfer *xfer = &data.xfer[ep][dir];
dcd_int_disable(rhport);
- xfer->valid = true;
- xfer->buffer = buffer;
- xfer->len = total_bytes;
+ xfer->valid = true;
+ xfer->buffer = buffer;
+ xfer->len = total_bytes;
xfer->processed_len = 0;
dcd_int_enable(rhport);
if (dir == TUSB_DIR_IN) {
update_in(rhport, ep, true);
+ } else {
+ uint8_t rx_res = data.isochronous[ep] ? USBFS_EP_R_RES_NYET : USBFS_EP_R_RES_ACK;
+ EP_RX_CTRL(ep) = (EP_RX_CTRL(ep) & ~USBFS_EP_R_RES_MASK) | rx_res;
}
return true;
}
void dcd_edpt_stall(uint8_t rhport, uint8_t ep_addr) {
- (void) rhport;
- uint8_t ep = tu_edpt_number(ep_addr);
+ (void)rhport;
+ uint8_t ep = tu_edpt_number(ep_addr);
uint8_t dir = tu_edpt_dir(ep_addr);
if (ep == 0) {
if (dir == TUSB_DIR_OUT) {
EP_RX_CTRL(0) = USBFS_EP_R_RES_STALL;
} else {
- EP_TX_LEN(0) = 0;
+ EP_TX_LEN(0) = 0;
EP_TX_CTRL(0) = USBFS_EP_T_RES_STALL;
}
} else {
@@ -337,8 +352,8 @@ void dcd_edpt_stall(uint8_t rhport, uint8_t ep_addr) {
}
void dcd_edpt_clear_stall(uint8_t rhport, uint8_t ep_addr) {
- (void) rhport;
- uint8_t ep = tu_edpt_number(ep_addr);
+ (void)rhport;
+ uint8_t ep = tu_edpt_number(ep_addr);
uint8_t dir = tu_edpt_dir(ep_addr);
if (ep == 0) {
if (dir == TUSB_DIR_OUT) {
@@ -346,9 +361,9 @@ void dcd_edpt_clear_stall(uint8_t rhport, uint8_t ep_addr) {
}
} else {
if (dir == TUSB_DIR_OUT) {
- EP_RX_CTRL(ep) = (EP_RX_CTRL(ep) & ~(USBFS_EP_R_RES_MASK | USBFS_EP_R_TOG)) | USBFS_EP_R_RES_ACK;
+ EP_RX_CTRL(ep) = USBFS_EP_R_AUTO_TOG | USBFS_EP_R_RES_NAK;
} else {
- EP_TX_CTRL(ep) = (EP_TX_CTRL(ep) & ~(USBFS_EP_T_RES_MASK | USBFS_EP_T_TOG)) | USBFS_EP_T_RES_NAK;
+ EP_TX_CTRL(ep) = USBFS_EP_T_AUTO_TOG | USBFS_EP_T_RES_NAK;
}
}
}
diff --git a/src/portable/wch/dcd_ch32_usbhs.c b/src/portable/wch/dcd_ch32_usbhs.c
index 11734de37..a6dd5bb79 100644
--- a/src/portable/wch/dcd_ch32_usbhs.c
+++ b/src/portable/wch/dcd_ch32_usbhs.c
@@ -24,7 +24,6 @@
*
* This file is part of the TinyUSB stack.
*/
-
#include "tusb_option.h"
#if CFG_TUD_ENABLED && defined(TUP_USBIP_WCH_USBHS) && defined(CFG_TUD_WCH_USBIP_USBHS) && \
@@ -37,138 +36,182 @@
#define EP_MAX 16
typedef struct {
- uint8_t* buffer;
+ uint8_t *buffer;
uint16_t total_len;
uint16_t queued_len;
uint16_t max_size;
- bool is_last_packet;
- bool is_iso;
+ bool is_iso;
+ bool valid;
} xfer_ctl_t;
-typedef enum {
- EP_RESPONSE_ACK,
- EP_RESPONSE_NAK,
-} ep_response_list_t;
-
-#define XFER_CTL_BASE(_ep, _dir) &xfer_status[_ep][_dir]
+ #define XFER_CTL_BASE(_ep, _dir) &xfer_status[_ep][_dir]
static xfer_ctl_t xfer_status[EP_MAX][2];
-#define EP_TX_LEN(ep) *(volatile uint16_t *)((volatile uint16_t *)&(USBHSD->UEP0_TX_LEN) + (ep) * 2)
-#define EP_TX_CTRL(ep) *(volatile uint8_t *)((volatile uint8_t *)&(USBHSD->UEP0_TX_CTRL) + (ep) * 4)
-#define EP_RX_CTRL(ep) *(volatile uint8_t *)((volatile uint8_t *)&(USBHSD->UEP0_RX_CTRL) + (ep) * 4)
-#define EP_RX_MAX_LEN(ep) *(volatile uint16_t *)((volatile uint16_t *)&(USBHSD->UEP0_MAX_LEN) + (ep) * 2)
+ #define EP_TX_LEN(ep) *(volatile uint16_t *)((volatile uint16_t *)&(USBHSD->UEP0_TX_LEN) + (ep) * 2)
+ #define EP_TX_CTRL(ep) *(volatile uint8_t *)((volatile uint8_t *)&(USBHSD->UEP0_TX_CTRL) + (ep) * 4)
+ #define EP_RX_CTRL(ep) *(volatile uint8_t *)((volatile uint8_t *)&(USBHSD->UEP0_RX_CTRL) + (ep) * 4)
+ #define EP_RX_MAX_LEN(ep) *(volatile uint16_t *)((volatile uint16_t *)&(USBHSD->UEP0_MAX_LEN) + (ep) * 2)
-#define EP_TX_DMA_ADDR(ep) *(volatile uint32_t *)((volatile uint32_t *)&(USBHSD->UEP1_TX_DMA) + (ep - 1))
-#define EP_RX_DMA_ADDR(ep) *(volatile uint32_t *)((volatile uint32_t *)&(USBHSD->UEP1_RX_DMA) + (ep - 1))
+ #define EP_TX_DMA_ADDR(ep) *(volatile uint32_t *)((volatile uint32_t *)&(USBHSD->UEP1_TX_DMA) + (ep - 1))
+ #define EP_RX_DMA_ADDR(ep) *(volatile uint32_t *)((volatile uint32_t *)&(USBHSD->UEP1_RX_DMA) + (ep - 1))
/* Endpoint Buffer */
TU_ATTR_ALIGNED(4) static uint8_t ep0_buffer[CFG_TUD_ENDPOINT0_SIZE];
+static bool ep0_tog;
+static bool ep_data_tog[EP_MAX][2];
-static void ep_set_response_and_toggle(uint8_t ep_num, tusb_dir_t ep_dir, ep_response_list_t response_type) {
+static void set_ep_toggle(uint8_t ep_num, tusb_dir_t ep_dir, bool data1) {
if (ep_dir == TUSB_DIR_IN) {
- uint8_t response = (response_type == EP_RESPONSE_ACK) ? USBHS_EP_T_RES_ACK : USBHS_EP_T_RES_NAK;
- if (ep_num == 0) {
- if (response_type == EP_RESPONSE_ACK) {
- if (EP_TX_LEN(ep_num) == 0) {
- EP_TX_CTRL(ep_num) |= USBHS_EP_T_TOG_1;
- } else {
- EP_TX_CTRL(ep_num) ^= USBHS_EP_T_TOG_1;
- }
- }
- }
- if (xfer_status[ep_num][TUSB_DIR_IN].is_iso == true) {
- EP_TX_CTRL(ep_num) = USBHS_EP_T_AUTOTOG;
- } else {
- EP_TX_CTRL(ep_num) = (EP_TX_CTRL(ep_num) & ~(USBHS_EP_T_RES_MASK)) | response;
- }
+ EP_TX_CTRL(ep_num) = (EP_TX_CTRL(ep_num) & ~(USBHS_EP_T_TOG_MASK)) |
+ (data1 ? USBHS_EP_T_TOG_1 : USBHS_EP_T_TOG_0);
} else {
- uint8_t response = (response_type == EP_RESPONSE_ACK) ? USBHS_EP_R_RES_ACK : USBHS_EP_R_RES_NAK;
- if (ep_num == 0) {
- if (response_type == EP_RESPONSE_ACK) {
- if (xfer_status[ep_num][TUSB_DIR_OUT].queued_len == 0) {
- EP_RX_CTRL(ep_num) |= USBHS_EP_R_TOG_1;
- }
- } else {
- EP_RX_CTRL(ep_num) ^= USBHS_EP_R_TOG_1;
- }
- }
- EP_RX_CTRL(ep_num) = (EP_RX_CTRL(ep_num) & ~(USBHS_EP_R_RES_MASK)) | response;
+ EP_RX_CTRL(ep_num) = (EP_RX_CTRL(ep_num) & ~(USBHS_EP_R_TOG_MASK)) |
+ (data1 ? USBHS_EP_R_TOG_1 : USBHS_EP_R_TOG_0);
}
}
-static void xfer_data_packet(uint8_t ep_num, tusb_dir_t ep_dir, xfer_ctl_t* xfer) {
- if (ep_dir == TUSB_DIR_IN) {
- uint16_t remaining = xfer->total_len - xfer->queued_len;
- uint16_t next_tx_size = TU_MIN(remaining, xfer->max_size);
+static void queue_in_packet(uint8_t ep_num, xfer_ctl_t* xfer) {
+ uint16_t remaining = xfer->total_len - xfer->queued_len;
+ uint16_t tx_len = TU_MIN(remaining, xfer->max_size);
+
+ if (ep_num == 0) {
+ memcpy(ep0_buffer, &xfer->buffer[xfer->queued_len], tx_len);
+ } else {
+ EP_TX_DMA_ADDR(ep_num) = (uint32_t) &xfer->buffer[xfer->queued_len];
+ }
+
+ EP_TX_LEN(ep_num) = tx_len;
+ xfer->queued_len += tx_len;
+
+ if (ep_num == 0) {
+ EP_TX_CTRL(0) = USBHS_EP_T_RES_ACK | (ep0_tog ? USBHS_EP_T_TOG_1 : USBHS_EP_T_TOG_0);
+ ep0_tog = !ep0_tog;
+ } else if (xfer->is_iso) {
+ EP_TX_CTRL(ep_num) = (EP_TX_CTRL(ep_num) & ~(USBHS_EP_T_RES_MASK)) | USBHS_EP_T_RES_NYET;
+ } else {
+ set_ep_toggle(ep_num, TUSB_DIR_IN, ep_data_tog[ep_num][TUSB_DIR_IN]);
+ EP_TX_CTRL(ep_num) = (EP_TX_CTRL(ep_num) & ~(USBHS_EP_T_RES_MASK)) | USBHS_EP_T_RES_ACK;
+ }
+}
+
+static void queue_out_packet(uint8_t ep_num, xfer_ctl_t* xfer) {
+ uint16_t remaining = xfer->total_len - xfer->queued_len;
+ uint16_t rx_len = TU_MIN(remaining, xfer->max_size);
+ if (ep_num > 0) {
+ EP_RX_DMA_ADDR(ep_num) = (uint32_t) &xfer->buffer[xfer->queued_len];
+ EP_RX_MAX_LEN(ep_num) = rx_len;
+ }
+
+ if (ep_num == 0) {
+ EP_RX_CTRL(0) = (EP_RX_CTRL(0) & ~(USBHS_EP_R_RES_MASK)) | USBHS_EP_R_RES_ACK;
+ } else if (xfer->is_iso) {
+ EP_RX_CTRL(ep_num) = (EP_RX_CTRL(ep_num) & ~(USBHS_EP_R_RES_MASK)) | USBHS_EP_R_RES_NYET;
+ } else {
+ set_ep_toggle(ep_num, TUSB_DIR_OUT, ep_data_tog[ep_num][TUSB_DIR_OUT]);
+ EP_RX_CTRL(ep_num) = (EP_RX_CTRL(ep_num) & ~(USBHS_EP_R_RES_MASK)) | USBHS_EP_R_RES_ACK;
+ }
+}
+
+static void update_in(uint8_t rhport, uint8_t ep_num, bool force) {
+ xfer_ctl_t* xfer = XFER_CTL_BASE(ep_num, TUSB_DIR_IN);
+ if (!xfer->valid) {
+ return;
+ }
+
+ if (!force && ep_num != 0 && !xfer->is_iso) {
+ ep_data_tog[ep_num][TUSB_DIR_IN] = !ep_data_tog[ep_num][TUSB_DIR_IN];
+ }
+
+ if (force || (xfer->total_len > xfer->queued_len)) {
+ queue_in_packet(ep_num, xfer);
+ } else {
+ xfer->valid = false;
if (ep_num == 0) {
- memcpy(ep0_buffer, &xfer->buffer[xfer->queued_len], next_tx_size);
+ EP_TX_CTRL(0) = USBHS_EP_T_RES_NAK | (ep0_tog ? USBHS_EP_T_TOG_1 : USBHS_EP_T_TOG_0);
} else {
- EP_TX_DMA_ADDR(ep_num) = (uint32_t) &xfer->buffer[xfer->queued_len];
+ EP_TX_CTRL(ep_num) = (EP_TX_CTRL(ep_num) & ~(USBHS_EP_T_RES_MASK)) | USBHS_EP_T_RES_NAK;
}
+ dcd_event_xfer_complete(rhport, ep_num | TUSB_DIR_IN_MASK, xfer->queued_len, XFER_RESULT_SUCCESS, true);
+ }
+}
- EP_TX_LEN(ep_num) = next_tx_size;
- xfer->queued_len += next_tx_size;
- if (xfer->queued_len == xfer->total_len) {
- xfer->is_last_packet = true;
- }
- if (xfer->is_iso == true) {
- /* Enable EP to generate ISA_ACT interrupt */
- USBHSD->ENDP_CONFIG |= (USBHS_EP0_T_EN << ep_num);
- }
- } else { /* TUSB_DIR_OUT */
- uint16_t left_to_receive = xfer->total_len - xfer->queued_len;
- uint16_t max_possible_rx_size = TU_MIN(xfer->max_size, left_to_receive);
+static void update_out(uint8_t rhport, uint8_t ep_num, uint16_t rx_len) {
+ xfer_ctl_t* xfer = XFER_CTL_BASE(ep_num, TUSB_DIR_OUT);
+ if (!xfer->valid) {
+ return;
+ }
+
+ uint16_t remaining = xfer->total_len - xfer->queued_len;
+ uint16_t len = TU_MIN(rx_len, TU_MIN(remaining, xfer->max_size));
+
+ if (ep_num == 0) {
+ memcpy(&xfer->buffer[xfer->queued_len], ep0_buffer, len);
+ }
+
+ xfer->queued_len += len;
+
+ if (ep_num != 0 && !xfer->is_iso) {
+ ep_data_tog[ep_num][TUSB_DIR_OUT] = !ep_data_tog[ep_num][TUSB_DIR_OUT];
+ }
- if (max_possible_rx_size == left_to_receive) {
- xfer->is_last_packet = true;
+ if ((xfer->queued_len == xfer->total_len) || (len < xfer->max_size)) {
+ xfer->valid = false;
+ if (ep_num == 0) {
+ EP_RX_CTRL(0) = (EP_RX_CTRL(0) & ~(USBHS_EP_R_RES_MASK)) | USBHS_EP_R_RES_NAK;
}
+ dcd_event_xfer_complete(rhport, ep_num, xfer->queued_len, XFER_RESULT_SUCCESS, true);
+ }
- if (ep_num > 0) {
- EP_RX_DMA_ADDR(ep_num) = (uint32_t) &xfer->buffer[xfer->queued_len];
- EP_RX_MAX_LEN(ep_num) = max_possible_rx_size;
+ if (ep_num != 0) {
+ if (xfer->valid) {
+ queue_out_packet(ep_num, xfer);
+ } else {
+ uint8_t rx_res = xfer->is_iso ? USBHS_EP_R_RES_NYET : USBHS_EP_R_RES_NAK;
+ EP_RX_CTRL(ep_num) = (EP_RX_CTRL(ep_num) & ~(USBHS_EP_R_RES_MASK)) | rx_res;
}
}
- ep_set_response_and_toggle(ep_num, ep_dir, USBHS_EP_R_RES_ACK);
}
-bool dcd_init(uint8_t rhport, const tusb_rhport_init_t* rh_init) {
- (void) rhport;
- (void) rh_init;
+bool dcd_init(uint8_t rhport, const tusb_rhport_init_t *rh_init) {
+ (void)rhport;
+ (void)rh_init;
memset(&xfer_status, 0, sizeof(xfer_status));
+ memset(ep_data_tog, 0, sizeof(ep_data_tog));
+ ep0_tog = true;
USBHSD->HOST_CTRL = 0x00;
USBHSD->HOST_CTRL = USBHS_PHY_SUSPENDM;
USBHSD->CONTROL = 0;
-#if TUD_OPT_HIGH_SPEED
+ #if TUD_OPT_HIGH_SPEED
USBHSD->CONTROL = USBHS_DMA_EN | USBHS_INT_BUSY_EN | USBHS_HIGH_SPEED;
-#else
- #error OPT_MODE_FULL_SPEED not currently supported on CH32
+ #else
+ #error OPT_MODE_FULL_SPEED not currently supported on CH32
USBHSD->CONTROL = USBHS_DMA_EN | USBHS_INT_BUSY_EN | USBHS_FULL_SPEED;
-#endif
+ #endif
USBHSD->INT_EN = 0;
- USBHSD->INT_EN = USBHS_SETUP_ACT_EN | USBHS_TRANSFER_EN | USBHS_BUS_RST_EN | USBHS_SUSPEND_EN | USBHS_ISO_ACT_EN;
+ USBHSD->INT_EN = USBHS_SETUP_ACT_EN | USBHS_TRANSFER_EN | USBHS_BUS_RST_EN | USBHS_SUSPEND_EN;
USBHSD->ENDP_CONFIG = USBHS_EP0_T_EN | USBHS_EP0_R_EN;
- USBHSD->ENDP_TYPE = 0x00;
- USBHSD->BUF_MODE = 0x00;
+ USBHSD->ENDP_TYPE = 0x00;
+ USBHSD->BUF_MODE = 0x00;
for (int ep = 0; ep < EP_MAX; ep++) {
- EP_TX_LEN(ep) = 0;
- EP_TX_CTRL(ep) = USBHS_EP_T_AUTOTOG | USBHS_EP_T_RES_NAK;
- EP_RX_CTRL(ep) = USBHS_EP_R_AUTOTOG | USBHS_EP_R_RES_NAK;
+ EP_TX_LEN(ep) = 0;
+ EP_TX_CTRL(ep) = USBHS_EP_T_RES_NAK | USBHS_EP_T_TOG_0;
+ EP_RX_CTRL(ep) = USBHS_EP_R_RES_NAK | USBHS_EP_R_TOG_0;
EP_RX_MAX_LEN(ep) = 0;
}
- USBHSD->UEP0_DMA = (uint32_t) ep0_buffer;
- USBHSD->UEP0_MAX_LEN = CFG_TUD_ENDPOINT0_SIZE;
+ USBHSD->UEP0_DMA = (uint32_t)ep0_buffer;
+ USBHSD->UEP0_MAX_LEN = CFG_TUD_ENDPOINT0_SIZE;
xfer_status[0][TUSB_DIR_OUT].max_size = CFG_TUD_ENDPOINT0_SIZE;
- xfer_status[0][TUSB_DIR_IN].max_size = CFG_TUD_ENDPOINT0_SIZE;
+ xfer_status[0][TUSB_DIR_IN].max_size = CFG_TUD_ENDPOINT0_SIZE;
USBHSD->DEV_AD = 0;
USBHSD->CONTROL |= USBHS_DEV_PU_EN;
@@ -177,22 +220,24 @@ bool dcd_init(uint8_t rhport, const tusb_rhport_init_t* rh_init) {
}
void dcd_int_enable(uint8_t rhport) {
- (void) rhport;
+ (void)rhport;
NVIC_EnableIRQ(USBHS_IRQn);
}
void dcd_int_disable(uint8_t rhport) {
- (void) rhport;
+ (void)rhport;
NVIC_DisableIRQ(USBHS_IRQn);
}
void dcd_edpt_close_all(uint8_t rhport) {
- (void) rhport;
+ (void)rhport;
+
+ memset(ep_data_tog, 0, sizeof(ep_data_tog));
for (size_t ep = 1; ep < EP_MAX; ep++) {
- EP_TX_LEN(ep) = 0;
- EP_TX_CTRL(ep) = USBHS_EP_T_AUTOTOG | USBHS_EP_T_RES_NAK;
- EP_RX_CTRL(ep) = USBHS_EP_R_AUTOTOG | USBHS_EP_R_RES_NAK;
+ EP_TX_LEN(ep) = 0;
+ EP_TX_CTRL(ep) = USBHS_EP_T_RES_NAK | USBHS_EP_T_TOG_0;
+ EP_RX_CTRL(ep) = USBHS_EP_R_RES_NAK | USBHS_EP_R_TOG_0;
EP_RX_MAX_LEN(ep) = 0;
}
@@ -201,18 +246,18 @@ void dcd_edpt_close_all(uint8_t rhport) {
}
void dcd_set_address(uint8_t rhport, uint8_t dev_addr) {
- (void) dev_addr;
+ (void)dev_addr;
// Response with zlp status
dcd_edpt_xfer(rhport, 0x80, NULL, 0, false);
}
void dcd_remote_wakeup(uint8_t rhport) {
- (void) rhport;
+ (void)rhport;
}
void dcd_sof_enable(uint8_t rhport, bool en) {
- (void) rhport;
+ (void)rhport;
if (en) {
USBHSD->INT_EN |= USBHS_SOF_ACT_EN;
} else {
@@ -220,24 +265,19 @@ void dcd_sof_enable(uint8_t rhport, bool en) {
}
}
-void dcd_edpt0_status_complete(uint8_t rhport, tusb_control_request_t const* request) {
- (void) rhport;
-
+void dcd_edpt0_status_complete(uint8_t rhport, const tusb_control_request_t *request) {
+ (void)rhport;
if (request->bmRequestType_bit.recipient == TUSB_REQ_RCPT_DEVICE &&
- request->bmRequestType_bit.type == TUSB_REQ_TYPE_STANDARD &&
- request->bRequest == TUSB_REQ_SET_ADDRESS) {
- USBHSD->DEV_AD = (uint8_t) request->wValue;
+ request->bmRequestType_bit.type == TUSB_REQ_TYPE_STANDARD && request->bRequest == TUSB_REQ_SET_ADDRESS) {
+ USBHSD->DEV_AD = (uint8_t)request->wValue;
}
-
- EP_TX_CTRL(0) = USBHS_EP_T_RES_NAK | USBHS_EP_T_TOG_0;
- EP_RX_CTRL(0) = USBHS_EP_R_RES_NAK | USBHS_EP_R_TOG_0;
}
-bool dcd_edpt_open(uint8_t rhport, tusb_desc_endpoint_t const* desc_edpt) {
- (void) rhport;
+bool dcd_edpt_open(uint8_t rhport, const tusb_desc_endpoint_t *desc_edpt) {
+ (void)rhport;
- uint8_t const ep_num = tu_edpt_number(desc_edpt->bEndpointAddress);
- tusb_dir_t const dir = tu_edpt_dir(desc_edpt->bEndpointAddress);
+ const uint8_t ep_num = tu_edpt_number(desc_edpt->bEndpointAddress);
+ const tusb_dir_t dir = tu_edpt_dir(desc_edpt->bEndpointAddress);
TU_ASSERT(ep_num < EP_MAX);
@@ -245,13 +285,14 @@ bool dcd_edpt_open(uint8_t rhport, tusb_desc_endpoint_t const* desc_edpt) {
return true;
}
- xfer_ctl_t* xfer = XFER_CTL_BASE(ep_num, dir);
- xfer->max_size = tu_edpt_packet_size(desc_edpt);
+ xfer_ctl_t *xfer = XFER_CTL_BASE(ep_num, dir);
+ xfer->max_size = tu_edpt_packet_size(desc_edpt);
+ ep_data_tog[ep_num][dir] = false;
xfer->is_iso = (desc_edpt->bmAttributes.xfer == TUSB_XFER_ISOCHRONOUS);
if (dir == TUSB_DIR_OUT) {
USBHSD->ENDP_CONFIG |= (USBHS_EP0_R_EN << ep_num);
- EP_RX_CTRL(ep_num) = USBHS_EP_R_AUTOTOG | USBHS_EP_R_RES_NAK;
+ EP_RX_CTRL(ep_num) = USBHS_EP_R_RES_NAK | USBHS_EP_R_TOG_0;
if (xfer->is_iso == true) {
USBHSD->ENDP_TYPE |= (USBHS_EP0_R_TYP << ep_num);
}
@@ -259,31 +300,31 @@ bool dcd_edpt_open(uint8_t rhport, tusb_desc_endpoint_t const* desc_edpt) {
} else {
if (xfer->is_iso == true) {
USBHSD->ENDP_TYPE |= (USBHS_EP0_T_TYP << ep_num);
- } else {
- /* Enable all types except Isochronous to avoid ISO_ACT interrupt generation */
- USBHSD->ENDP_CONFIG |= (USBHS_EP0_T_EN << ep_num);
}
- EP_TX_LEN(ep_num) = 0;
- EP_TX_CTRL(ep_num) = USBHS_EP_T_AUTOTOG | USBHS_EP_T_RES_NAK | USBHS_EP_T_TOG_0;
+ USBHSD->ENDP_CONFIG |= (USBHS_EP0_T_EN << ep_num);
+ EP_TX_LEN(ep_num) = 0;
+ EP_TX_CTRL(ep_num) = USBHS_EP_T_RES_NAK | USBHS_EP_T_TOG_0;
}
return true;
}
void dcd_edpt_close(uint8_t rhport, uint8_t ep_addr) {
- (void) rhport;
+ (void)rhport;
- uint8_t const ep_num = tu_edpt_number(ep_addr);
- tusb_dir_t const dir = tu_edpt_dir(ep_addr);
+ const uint8_t ep_num = tu_edpt_number(ep_addr);
+ const tusb_dir_t dir = tu_edpt_dir(ep_addr);
if (dir == TUSB_DIR_OUT) {
- EP_RX_CTRL(ep_num) = USBHS_EP_R_AUTOTOG | USBHS_EP_R_RES_NAK;
+ EP_RX_CTRL(ep_num) = USBHS_EP_R_RES_NAK | USBHS_EP_R_TOG_0;
EP_RX_MAX_LEN(ep_num) = 0;
+ ep_data_tog[ep_num][TUSB_DIR_OUT] = false;
USBHSD->ENDP_TYPE &= ~(USBHS_EP0_R_TYP << ep_num);
USBHSD->ENDP_CONFIG &= ~(USBHS_EP0_R_EN << ep_num);
- } else { // TUSB_DIR_IN
- EP_TX_CTRL(ep_num) = USBHS_EP_T_AUTOTOG | USBHS_EP_T_RES_NAK | USBHS_EP_T_TOG_0;
- EP_TX_LEN(ep_num) = 0;
+ } else { // TUSB_DIR_IN
+ EP_TX_CTRL(ep_num) = USBHS_EP_T_RES_NAK | USBHS_EP_T_TOG_0;
+ EP_TX_LEN(ep_num) = 0;
+ ep_data_tog[ep_num][TUSB_DIR_IN] = false;
USBHSD->ENDP_TYPE &= ~(USBHS_EP0_T_TYP << ep_num);
USBHSD->ENDP_CONFIG &= ~(USBHS_EP0_T_EN << ep_num);
}
@@ -305,128 +346,120 @@ bool dcd_edpt_iso_activate(uint8_t rhport, tusb_desc_endpoint_t const * desc_ep)
#endif
void dcd_edpt_stall(uint8_t rhport, uint8_t ep_addr) {
- (void) rhport;
+ (void)rhport;
- uint8_t const ep_num = tu_edpt_number(ep_addr);
- tusb_dir_t const dir = tu_edpt_dir(ep_addr);
+ const uint8_t ep_num = tu_edpt_number(ep_addr);
+ const tusb_dir_t dir = tu_edpt_dir(ep_addr);
if (dir == TUSB_DIR_OUT) {
EP_RX_CTRL(ep_num) = USBHS_EP_R_RES_STALL;
} else {
- EP_TX_LEN(0) = 0;
+ EP_TX_LEN(0) = 0;
EP_TX_CTRL(ep_num) = USBHS_EP_T_RES_STALL;
}
}
void dcd_edpt_clear_stall(uint8_t rhport, uint8_t ep_addr) {
- (void) rhport;
+ (void)rhport;
- uint8_t const ep_num = tu_edpt_number(ep_addr);
- tusb_dir_t const dir = tu_edpt_dir(ep_addr);
+ const uint8_t ep_num = tu_edpt_number(ep_addr);
+ const tusb_dir_t dir = tu_edpt_dir(ep_addr);
if (dir == TUSB_DIR_OUT) {
- EP_RX_CTRL(ep_num) = USBHS_EP_R_AUTOTOG | USBHS_EP_R_RES_NAK;
+ EP_RX_CTRL(ep_num) = USBHS_EP_R_RES_NAK | USBHS_EP_R_TOG_0;
+ ep_data_tog[ep_num][TUSB_DIR_OUT] = false;
} else {
- EP_TX_CTRL(ep_num) = USBHS_EP_T_AUTOTOG | USBHS_EP_R_RES_NAK;
+ EP_TX_CTRL(ep_num) = USBHS_EP_T_RES_NAK | USBHS_EP_T_TOG_0;
+ ep_data_tog[ep_num][TUSB_DIR_IN] = false;
}
}
-bool dcd_edpt_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t * buffer, uint16_t total_bytes, bool is_isr) {
- (void) is_isr;
- (void) rhport;
- uint8_t const ep_num = tu_edpt_number(ep_addr);
- tusb_dir_t const dir = tu_edpt_dir(ep_addr);
+bool dcd_edpt_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t *buffer, uint16_t total_bytes, bool is_isr) {
+ (void)is_isr;
+ (void)rhport;
+ const uint8_t ep_num = tu_edpt_number(ep_addr);
+ const tusb_dir_t dir = tu_edpt_dir(ep_addr);
+
+ xfer_ctl_t *xfer = XFER_CTL_BASE(ep_num, dir);
+ xfer->buffer = buffer;
+ xfer->total_len = total_bytes;
+ xfer->queued_len = 0;
+ xfer->valid = true;
- xfer_ctl_t* xfer = XFER_CTL_BASE(ep_num, dir);
- xfer->buffer = buffer;
- xfer->total_len = total_bytes;
- xfer->queued_len = 0;
- xfer->is_last_packet = false;
+ if (ep_num == 0 && dir == TUSB_DIR_OUT) {
+ if (total_bytes == 0) {
+ EP_RX_CTRL(0) = (EP_RX_CTRL(0) & ~(USBHS_EP_R_TOG_MASK)) | USBHS_EP_R_TOG_1;
+ } else {
+ EP_RX_CTRL(0) ^= USBHS_EP_R_TOG_1;
+ }
+ }
- xfer_data_packet(ep_num, dir, xfer);
+ if (dir == TUSB_DIR_IN) {
+ update_in(rhport, ep_num, true);
+ } else {
+ queue_out_packet(ep_num, xfer);
+ }
return true;
}
void dcd_int_handler(uint8_t rhport) {
- (void) rhport;
+ (void)rhport;
- uint8_t int_flag = USBHSD->INT_FG;
+ uint8_t int_flag = USBHSD->INT_FG;
uint8_t int_status = USBHSD->INT_ST;
- if (int_flag & (USBHS_ISO_ACT_FLAG | USBHS_TRANSFER_FLAG)) {
- uint8_t const token = int_status & MASK_UIS_TOKEN;
+ if (int_flag & USBHS_TRANSFER_FLAG) {
+ const uint8_t token = int_status & MASK_UIS_TOKEN;
+ const uint8_t ep_num = int_status & MASK_UIS_ENDP;
+ const uint16_t len = USBHSD->RX_LEN;
if (token == USBHS_TOKEN_PID_SOF) {
uint32_t frame_count = USBHSD->FRAME_NO & USBHS_FRAME_NO_NUM_MASK;
dcd_event_sof(rhport, frame_count, true);
- }else {
- uint8_t const ep_num = int_status & MASK_UIS_ENDP;
- tusb_dir_t const ep_dir = (token == USBHS_TOKEN_PID_IN) ? TUSB_DIR_IN : TUSB_DIR_OUT;
- uint8_t const ep_addr = tu_edpt_addr(ep_num, ep_dir);
- xfer_ctl_t* xfer = XFER_CTL_BASE(ep_num, ep_dir);
-
- if (token == USBHS_TOKEN_PID_OUT) {
- uint16_t rx_len = USBHSD->RX_LEN;
-
- if (ep_num == 0) {
- memcpy(&xfer->buffer[xfer->queued_len], ep0_buffer, rx_len);
- }
-
- xfer->queued_len += rx_len;
- if (rx_len < xfer->max_size) {
- xfer->is_last_packet = true;
- }
- } else if (token == USBHS_TOKEN_PID_IN) {
- if (xfer->is_iso && xfer->is_last_packet) {
- /* Disable EP to avoid ISO_ACT interrupt generation */
- USBHSD->ENDP_CONFIG &= ~(USBHS_EP0_T_EN << ep_num);
- } else {
- // Do nothing, no need to update xfer->is_last_packet, it is already updated in xfer_data_packet
- }
- }
-
- if (xfer->is_last_packet == true) {
- ep_set_response_and_toggle(ep_num, ep_dir, EP_RESPONSE_NAK);
- dcd_event_xfer_complete(0, ep_addr, xfer->queued_len, XFER_RESULT_SUCCESS, true);
- } else {
- /* prepare next part of packet to xref */
- xfer_data_packet(ep_num, ep_dir, xfer);
- }
+ } else if (token == USBHS_TOKEN_PID_OUT) {
+ update_out(rhport, ep_num, len);
+ } else if (token == USBHS_TOKEN_PID_IN) {
+ update_in(rhport, ep_num, false);
}
-
- USBHSD->INT_FG = (int_flag & (USBHS_ISO_ACT_FLAG | USBHS_TRANSFER_FLAG)); /* Clear flag */
+ USBHSD->INT_FG = (int_flag & USBHS_TRANSFER_FLAG); /* Clear flag */
} else if (int_flag & USBHS_SETUP_FLAG) {
- ep_set_response_and_toggle(0, TUSB_DIR_IN, EP_RESPONSE_NAK);
- ep_set_response_and_toggle(0, TUSB_DIR_OUT, EP_RESPONSE_NAK);
+ tusb_control_request_t const* setup =
+ (tusb_control_request_t const*) ep0_buffer;
+ ep0_tog = true;
+ EP_RX_CTRL(0) = (setup->wLength == 0) ? USBHS_EP_R_RES_ACK : USBHS_EP_R_RES_NAK;
+ EP_TX_CTRL(0) = USBHS_EP_T_RES_NAK;
+
dcd_event_setup_received(0, ep0_buffer, true);
USBHSD->INT_FG = USBHS_SETUP_FLAG; /* Clear flag */
} else if (int_flag & USBHS_BUS_RST_FLAG) {
// TODO CH32 does not detect actual speed at this time (should be known at end of reset)
// This interrupt probably triggered at start of bus reset
-// tusb_speed_t actual_speed;
-// switch(USBHSD->SPEED_TYPE & USBHS_SPEED_TYPE_MASK){
-// case USBHS_SPEED_TYPE_HIGH:
-// actual_speed = TUSB_SPEED_HIGH;
-// break;
-// case USBHS_SPEED_TYPE_FULL:
-// actual_speed = TUSB_SPEED_FULL;
-// break;
-// case USBHS_SPEED_TYPE_LOW:
-// actual_speed = TUSB_SPEED_LOW;
-// break;
-// default:
-// TU_ASSERT(0,);
-// break;
-// }
-// dcd_event_bus_reset(0, actual_speed, true);
+ // tusb_speed_t actual_speed;
+ // switch(USBHSD->SPEED_TYPE & USBHS_SPEED_TYPE_MASK){
+ // case USBHS_SPEED_TYPE_HIGH:
+ // actual_speed = TUSB_SPEED_HIGH;
+ // break;
+ // case USBHS_SPEED_TYPE_FULL:
+ // actual_speed = TUSB_SPEED_FULL;
+ // break;
+ // case USBHS_SPEED_TYPE_LOW:
+ // actual_speed = TUSB_SPEED_LOW;
+ // break;
+ // default:
+ // TU_ASSERT(0,);
+ // break;
+ // }
+ // dcd_event_bus_reset(0, actual_speed, true);
dcd_event_bus_reset(0, TUSB_SPEED_HIGH, true);
USBHSD->DEV_AD = 0;
- EP_RX_CTRL(0) = USBHS_EP_R_RES_ACK | USBHS_EP_R_TOG_0;
- EP_TX_CTRL(0) = USBHS_EP_T_RES_NAK | USBHS_EP_T_TOG_0;
+ memset(ep_data_tog, 0, sizeof(ep_data_tog));
+ ep0_tog = true;
+ EP_RX_CTRL(0) = USBHS_EP_R_RES_ACK | USBHS_EP_R_TOG_0;
+ EP_TX_CTRL(0) = USBHS_EP_T_RES_NAK | USBHS_EP_T_TOG_0;
USBHSD->INT_FG = USBHS_BUS_RST_FLAG; /* Clear flag */
} else if (int_flag & USBHS_SUSPEND_FLAG) {
@@ -434,6 +467,9 @@ void dcd_int_handler(uint8_t rhport) {
dcd_event_handler(&event, true);
USBHSD->INT_FG = USBHS_SUSPEND_FLAG; /* Clear flag */
+ } else {
+ // Unhandled interrupt
+ USBHSD->INT_FG = int_flag; /* Clear all flags */
}
}
#endif
diff --git a/src/tinyusb.mk b/src/tinyusb.mk
index e3ef35dcf..365043927 100644
--- a/src/tinyusb.mk
+++ b/src/tinyusb.mk
@@ -10,6 +10,7 @@ TINYUSB_SRC_C += \
src/class/dfu/dfu_rt_device.c \
src/class/hid/hid_device.c \
src/class/midi/midi_device.c \
+ src/class/midi/midi2_device.c \
src/class/msc/msc_device.c \
src/class/mtp/mtp_device.c \
src/class/net/ecm_rndis_device.c \
@@ -23,5 +24,6 @@ TINYUSB_SRC_C += \
src/class/cdc/cdc_host.c \
src/class/hid/hid_host.c \
src/class/midi/midi_host.c \
+ src/class/midi/midi2_host.c \
src/class/msc/msc_host.c \
src/class/vendor/vendor_host.c \
diff --git a/src/tusb.h b/src/tusb.h
index c80c8433c..aa6b461e5 100644
--- a/src/tusb.h
+++ b/src/tusb.h
@@ -63,6 +63,10 @@
#include "class/midi/midi_host.h"
#endif
+ #if CFG_TUH_MIDI2
+ #include "class/midi/midi2_host.h"
+ #endif
+
#if CFG_TUH_VENDOR
#include "class/vendor/vendor_host.h"
#endif
@@ -108,6 +112,10 @@
#include "class/midi/midi_device.h"
#endif
+ #if CFG_TUD_MIDI2
+ #include "class/midi/midi2_device.h"
+ #endif
+
#if CFG_TUD_VENDOR
#include "class/vendor/vendor_device.h"
#endif
diff --git a/src/tusb_option.h b/src/tusb_option.h
index 154f8e2a4..74eb8cc06 100644
--- a/src/tusb_option.h
+++ b/src/tusb_option.h
@@ -646,6 +646,10 @@
#define CFG_TUD_MIDI 0
#endif
+#ifndef CFG_TUD_MIDI2
+ #define CFG_TUD_MIDI2 0
+#endif
+
#ifndef CFG_TUD_VENDOR
#define CFG_TUD_VENDOR 0
#endif
@@ -815,6 +819,22 @@
#define CFG_TUH_MIDI 0
#endif
+#ifndef CFG_TUH_MIDI2
+ #define CFG_TUH_MIDI2 0
+#endif
+
+#ifndef CFG_TUH_MIDI2_RX_BUFSIZE
+ #define CFG_TUH_MIDI2_RX_BUFSIZE TUH_EPSIZE_BULK_MAX
+#endif
+
+#ifndef CFG_TUH_MIDI2_TX_BUFSIZE
+ #define CFG_TUH_MIDI2_TX_BUFSIZE TUH_EPSIZE_BULK_MAX
+#endif
+
+#ifndef CFG_TUH_MIDI2_LOG_LEVEL
+ #define CFG_TUH_MIDI2_LOG_LEVEL CFG_TUH_LOG_LEVEL
+#endif
+
#ifndef CFG_TUH_MSC
#define CFG_TUH_MSC 0
#endif