summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authoralt-0191 <[email protected]>2026-02-26 20:46:39 +0800
committeralt-0191 <[email protected]>2026-03-18 15:23:28 +0800
commit6b0d20d70dbe0462fc89a92ab0c0ca04a9c625d0 (patch)
treec3106c92bfa1be6f199c59f4bc2470f41b84dfee /src
parent8f7f286ae347ec1adbfd0888d65fb1aeb263b7d2 (diff)
add WCH CH58x (CH582/CH583) BSP and USB FS device/host driver
Diffstat (limited to 'src')
-rw-r--r--src/common/tusb_mcu.h14
-rw-r--r--src/portable/wch/ch58x_usbfs_reg.h299
-rw-r--r--src/portable/wch/dcd_ch58x_usbfs.c583
-rw-r--r--src/portable/wch/hcd_ch58x_usbfs.c712
-rw-r--r--src/tusb_option.h1
5 files changed, 1609 insertions, 0 deletions
diff --git a/src/common/tusb_mcu.h b/src/common/tusb_mcu.h
index 34dd6af8d..02364d580 100644
--- a/src/common/tusb_mcu.h
+++ b/src/common/tusb_mcu.h
@@ -629,6 +629,20 @@
#define TUP_DCD_EDPT_CLOSE_API
#endif
+#elif TU_CHECK_MCU(OPT_MCU_CH582)
+ // CH58x has 2 independent USBFS controllers with merged EP registers, FS only.
+ #define TUP_USBIP_WCH_CH58X
+
+ #ifndef CFG_TUD_WCH_USBIP_USBFS
+ #define CFG_TUD_WCH_USBIP_USBFS 1
+ #endif
+
+ #ifndef CFG_TUH_WCH_USBIP_USBFS
+ #define CFG_TUH_WCH_USBIP_USBFS 1
+ #endif
+
+ #define TUP_DCD_ENDPOINT_MAX 8
+
//--------------------------------------------------------------------+
// Analog Devices
//--------------------------------------------------------------------+
diff --git a/src/portable/wch/ch58x_usbfs_reg.h b/src/portable/wch/ch58x_usbfs_reg.h
new file mode 100644
index 000000000..11479a4f6
--- /dev/null
+++ b/src/portable/wch/ch58x_usbfs_reg.h
@@ -0,0 +1,299 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2024 TinyUSB contributors
+ *
+ * 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 CH58X_USBFS_REG_H
+#define CH58X_USBFS_REG_H
+
+#include <stdint.h>
+
+//--------------------------------------------------------------------+
+// USB Base Addresses
+//--------------------------------------------------------------------+
+#define CH58X_USB_BASE 0x40008000u
+#define CH58X_USB2_BASE 0x40008400u
+
+//--------------------------------------------------------------------+
+// Global Control / Status Registers
+//--------------------------------------------------------------------+
+#define CH58X_USB_CTRL(base) (*(volatile uint8_t *)((base) + 0x00))
+#define CH58X_UDEV_CTRL(base) (*(volatile uint8_t *)((base) + 0x01))
+#define CH58X_USB_INT_EN(base) (*(volatile uint8_t *)((base) + 0x02))
+#define CH58X_USB_DEV_AD(base) (*(volatile uint8_t *)((base) + 0x03))
+#define CH58X_USB_MIS_ST(base) (*(volatile uint8_t *)((base) + 0x05))
+#define CH58X_USB_INT_FG(base) (*(volatile uint8_t *)((base) + 0x06))
+#define CH58X_USB_INT_ST(base) (*(volatile uint8_t *)((base) + 0x07))
+#define CH58X_USB_RX_LEN(base) (*(volatile uint8_t *)((base) + 0x08))
+
+//--------------------------------------------------------------------+
+// Endpoint Mode Registers
+//--------------------------------------------------------------------+
+#define CH58X_UEP4_1_MOD(base) (*(volatile uint8_t *)((base) + 0x0C))
+#define CH58X_UEP2_3_MOD(base) (*(volatile uint8_t *)((base) + 0x0D))
+#define CH58X_UEP567_MOD(base) (*(volatile uint8_t *)((base) + 0x0E))
+
+//--------------------------------------------------------------------+
+// Endpoint DMA / T_LEN / CTRL offset lookup tables
+// EP0-EP4 are at low offsets, EP5-EP7 jump to higher offsets
+// EP4 shares DMA with EP0 (no independent DMA register)
+//--------------------------------------------------------------------+
+static const uint8_t ch58x_ep_dma_offset[] = {
+ 0x10, 0x14, 0x18, 0x1C, /* EP0, EP1, EP2, EP3 */
+ 0xFF, /* EP4: shares with EP0, sentinel */
+ 0x54, 0x58, 0x5C /* EP5, EP6, EP7 */
+};
+
+static const uint8_t ch58x_ep_tlen_offset[] = {
+ 0x20, 0x24, 0x28, 0x2C, 0x30, /* EP0-EP4 */
+ 0x64, 0x68, 0x6C /* EP5-EP7 */
+};
+
+static const uint8_t ch58x_ep_ctrl_offset[] = {
+ 0x22, 0x26, 0x2A, 0x2E, 0x32, /* EP0-EP4 */
+ 0x66, 0x6A, 0x6E /* EP5-EP7 */
+};
+
+// EP DMA is 16-bit (only low 16 bits of RAM address, high bits implied 0x2000)
+#define CH58X_EP_DMA(base, ep) (*(volatile uint16_t *)((base) + ch58x_ep_dma_offset[ep]))
+#define CH58X_EP_TLEN(base, ep) (*(volatile uint8_t *)((base) + ch58x_ep_tlen_offset[ep]))
+#define CH58X_EP_CTRL(base, ep) (*(volatile uint8_t *)((base) + ch58x_ep_ctrl_offset[ep]))
+
+//--------------------------------------------------------------------+
+// USB_CTRL (R8_USB_CTRL) bit definitions
+//--------------------------------------------------------------------+
+#define CH58X_UC_DMA_EN 0x01
+#define CH58X_UC_CLR_ALL 0x02
+#define CH58X_UC_RESET_SIE 0x04
+#define CH58X_UC_INT_BUSY 0x08
+#define CH58X_UC_SYS_CTRL 0x10
+#define CH58X_UC_DEV_PU_EN 0x20
+#define CH58X_UC_LOW_SPEED 0x40
+#define CH58X_UC_HOST_MODE 0x80
+
+//--------------------------------------------------------------------+
+// UDEV_CTRL (R8_UDEV_CTRL) bit definitions
+//--------------------------------------------------------------------+
+#define CH58X_UD_PORT_EN 0x01
+#define CH58X_UD_GP_BIT 0x02
+#define CH58X_UD_LOW_SPEED 0x04
+#define CH58X_UD_PD_DIS 0x80
+
+//--------------------------------------------------------------------+
+// INT_EN (R8_USB_INT_EN) bit definitions
+//--------------------------------------------------------------------+
+#define CH58X_UIE_BUS_RST 0x01
+#define CH58X_UIE_DETECT 0x01 /* host mode alias */
+#define CH58X_UIE_TRANSFER 0x02
+#define CH58X_UIE_SUSPEND 0x04
+#define CH58X_UIE_HST_SOF 0x08
+#define CH58X_UIE_FIFO_OV 0x10
+
+//--------------------------------------------------------------------+
+// INT_FG (R8_USB_INT_FG) bit definitions
+//--------------------------------------------------------------------+
+#define CH58X_UIF_BUS_RST 0x01
+#define CH58X_UIF_DETECT 0x01 /* host mode alias */
+#define CH58X_UIF_TRANSFER 0x02
+#define CH58X_UIF_SUSPEND 0x04
+#define CH58X_UIF_HST_SOF 0x08
+#define CH58X_UIF_FIFO_OV 0x10
+#define CH58X_U_SIE_FREE 0x20
+#define CH58X_U_TOG_OK 0x40
+#define CH58X_U_IS_NAK 0x80
+
+//--------------------------------------------------------------------+
+// INT_ST (R8_USB_INT_ST) parsing
+//--------------------------------------------------------------------+
+#define CH58X_INT_ST_ENDP(x) (((x) >> 0) & 0x0F)
+#define CH58X_INT_ST_TOKEN(x) (((x) >> 4) & 0x03)
+#define CH58X_UIS_SETUP_ACT 0x80
+
+// Token PID values
+#define CH58X_PID_OUT 0
+#define CH58X_PID_SOF 1
+#define CH58X_PID_IN 2
+#define CH58X_PID_SETUP 3
+
+//--------------------------------------------------------------------+
+// MIS_ST (R8_USB_MIS_ST) bit definitions
+//--------------------------------------------------------------------+
+#define CH58X_UMS_DEV_ATTACH 0x01
+#define CH58X_UMS_DM_LEVEL 0x02
+#define CH58X_UMS_SUSPEND 0x04
+#define CH58X_UMS_BUS_RESET 0x08
+#define CH58X_UMS_R_FIFO_RDY 0x10
+#define CH58X_UMS_SIE_FREE 0x20
+#define CH58X_UMS_SOF_ACT 0x40
+#define CH58X_UMS_SOF_PRES 0x80
+
+//--------------------------------------------------------------------+
+// EP CTRL register bit definitions (merged TX+RX in single 8-bit reg)
+//
+// Bit 7: RB_UEP_R_TOG RX data toggle
+// Bit 6: RB_UEP_T_TOG TX data toggle
+// Bit 5: (reserved)
+// Bit 4: RB_UEP_AUTO_TOG auto toggle (EP1/2/3 only)
+// Bit 3: R_RES1 RX response high
+// Bit 2: R_RES0 RX response low
+// Bit 1: T_RES1 TX response high
+// Bit 0: T_RES0 TX response low
+//--------------------------------------------------------------------+
+
+// TX response bits[1:0]
+#define CH58X_EP_T_RES_MASK 0x03
+#define CH58X_EP_T_RES_ACK 0x00
+#define CH58X_EP_T_RES_TOUT 0x01 /* ISO: no handshake */
+#define CH58X_EP_T_RES_NAK 0x02
+#define CH58X_EP_T_RES_STALL 0x03
+
+// RX response bits[3:2]
+#define CH58X_EP_R_RES_MASK 0x0C
+#define CH58X_EP_R_RES_ACK 0x00
+#define CH58X_EP_R_RES_TOUT 0x04 /* ISO: no handshake */
+#define CH58X_EP_R_RES_NAK 0x08
+#define CH58X_EP_R_RES_STALL 0x0C
+
+// Toggle and auto-toggle
+#define CH58X_EP_AUTO_TOG 0x10 /* bit 4, shared TX/RX */
+#define CH58X_EP_T_TOG 0x40 /* bit 6, TX DATA toggle */
+#define CH58X_EP_R_TOG 0x80 /* bit 7, RX DATA toggle */
+
+//--------------------------------------------------------------------+
+// EP Mode register (UEP4_1_MOD) bit definitions
+//--------------------------------------------------------------------+
+// R8_UEP4_1_MOD: EP4 in bits[3:2], EP1 in bits[7:4]
+#define CH58X_UEP1_BUF_MOD 0x10
+#define CH58X_UEP1_TX_EN 0x40
+#define CH58X_UEP1_RX_EN 0x80
+#define CH58X_UEP4_TX_EN 0x04
+#define CH58X_UEP4_RX_EN 0x08
+
+// R8_UEP2_3_MOD: EP3 in bits[7:4], EP2 in bits[3:0]
+#define CH58X_UEP2_BUF_MOD 0x01
+#define CH58X_UEP2_TX_EN 0x04
+#define CH58X_UEP2_RX_EN 0x08
+#define CH58X_UEP3_BUF_MOD 0x10
+#define CH58X_UEP3_TX_EN 0x40
+#define CH58X_UEP3_RX_EN 0x80
+
+// R8_UEP567_MOD
+#define CH58X_UEP5_TX_EN 0x01
+#define CH58X_UEP5_RX_EN 0x02
+#define CH58X_UEP6_TX_EN 0x04
+#define CH58X_UEP6_RX_EN 0x08
+#define CH58X_UEP7_TX_EN 0x10
+#define CH58X_UEP7_RX_EN 0x20
+
+//--------------------------------------------------------------------+
+// Host-mode Register Aliases
+// In host mode: EP2 -> RX, EP3 -> TX, EP1_CTRL -> SETUP
+//--------------------------------------------------------------------+
+#define CH58X_UHOST_CTRL(base) (*(volatile uint8_t *)((base) + 0x01)) // = UDEV_CTRL
+#define CH58X_UH_EP_MOD(base) (*(volatile uint8_t *)((base) + 0x0D)) // = UEP2_3_MOD
+#define CH58X_UH_RX_DMA(base) (*(volatile uint16_t *)((base) + 0x18)) // = UEP2_DMA
+#define CH58X_UH_TX_DMA(base) (*(volatile uint16_t *)((base) + 0x1C)) // = UEP3_DMA
+#define CH58X_UH_SETUP(base) (*(volatile uint8_t *)((base) + 0x26)) // = UEP1_CTRL
+#define CH58X_UH_EP_PID(base) (*(volatile uint8_t *)((base) + 0x28)) // = UEP2_T_LEN
+#define CH58X_UH_RX_CTRL(base) (*(volatile uint8_t *)((base) + 0x2A)) // = UEP2_CTRL
+#define CH58X_UH_TX_LEN(base) (*(volatile uint8_t *)((base) + 0x2C)) // = UEP3_T_LEN
+#define CH58X_UH_TX_CTRL(base) (*(volatile uint8_t *)((base) + 0x2E)) // = UEP3_CTRL
+
+//--------------------------------------------------------------------+
+// UHOST_CTRL (R8_UHOST_CTRL) bit definitions
+//--------------------------------------------------------------------+
+#define CH58X_UH_PD_DIS 0x80
+#define CH58X_UH_LOW_SPEED 0x04
+#define CH58X_UH_BUS_RESET 0x02
+#define CH58X_UH_PORT_EN 0x01
+
+//--------------------------------------------------------------------+
+// UH_EP_MOD (R8_UH_EP_MOD) bit definitions
+//--------------------------------------------------------------------+
+#define CH58X_UH_EP_TX_EN 0x40
+#define CH58X_UH_EP_TBUF_MOD 0x10
+#define CH58X_UH_EP_RX_EN 0x08
+#define CH58X_UH_EP_RBUF_MOD 0x01
+
+//--------------------------------------------------------------------+
+// UH_SETUP (R8_UH_SETUP) bit definitions
+//--------------------------------------------------------------------+
+#define CH58X_UH_PRE_PID_EN 0x80
+#define CH58X_UH_SOF_EN 0x40
+
+//--------------------------------------------------------------------+
+// UH_RX_CTRL (R8_UH_RX_CTRL) bit definitions
+//--------------------------------------------------------------------+
+#define CH58X_UH_R_TOG 0x80
+#define CH58X_UH_R_AUTO_TOG 0x10
+#define CH58X_UH_R_RES 0x04
+
+//--------------------------------------------------------------------+
+// UH_TX_CTRL (R8_UH_TX_CTRL) bit definitions
+//--------------------------------------------------------------------+
+#define CH58X_UH_T_TOG 0x40
+#define CH58X_UH_T_AUTO_TOG 0x10
+#define CH58X_UH_T_RES 0x01
+
+//--------------------------------------------------------------------+
+// USB_INT_ST host-mode bits
+//--------------------------------------------------------------------+
+#define CH58X_UIS_H_RES_MASK 0x0F
+#define CH58X_UIS_TOG_OK 0x40
+
+//--------------------------------------------------------------------+
+// USB_DEV_AD bits
+//--------------------------------------------------------------------+
+#define CH58X_UDA_GP_BIT 0x80
+#define CH58X_USB_ADDR_MASK 0x7F
+
+//--------------------------------------------------------------------+
+// Standard USB PID values (for host token and response)
+//--------------------------------------------------------------------+
+#define CH58X_USB_PID_OUT 0x01
+#define CH58X_USB_PID_IN 0x09
+#define CH58X_USB_PID_SOF 0x05
+#define CH58X_USB_PID_SETUP 0x0D
+#define CH58X_USB_PID_DATA0 0x03
+#define CH58X_USB_PID_DATA1 0x0B
+#define CH58X_USB_PID_ACK 0x02
+#define CH58X_USB_PID_NAK 0x0A
+#define CH58X_USB_PID_STALL 0x0E
+
+//--------------------------------------------------------------------+
+// PIN_ANALOG_IE register
+//--------------------------------------------------------------------+
+#define CH58X_PIN_ANALOG_IE (*(volatile uint16_t *)0x4000101A)
+#define CH58X_PIN_USB_DP_PU 0x40
+#define CH58X_PIN_USB_IE 0x80
+#define CH58X_PIN_USB2_DP_PU 0x10
+#define CH58X_PIN_USB2_IE 0x20
+
+//--------------------------------------------------------------------+
+// Sleep clock control
+//--------------------------------------------------------------------+
+#define CH58X_SLP_CLK_OFF1 (*(volatile uint8_t *)0x4000100D)
+#define CH58X_SLP_CLK_USB 0x10
+
+#endif /* CH58X_USBFS_REG_H */
diff --git a/src/portable/wch/dcd_ch58x_usbfs.c b/src/portable/wch/dcd_ch58x_usbfs.c
new file mode 100644
index 000000000..11ff2c7b1
--- /dev/null
+++ b/src/portable/wch/dcd_ch58x_usbfs.c
@@ -0,0 +1,583 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2024 TinyUSB contributors
+ *
+ * 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 && defined(TUP_USBIP_WCH_CH58X) && CFG_TUD_WCH_USBIP_USBFS
+
+#include "device/dcd.h"
+#include "ch58x_usbfs_reg.h"
+
+//--------------------------------------------------------------------+
+// Configuration
+//--------------------------------------------------------------------+
+#define EP_MAX 8
+#define EP_BUF_SIZE 64
+
+//--------------------------------------------------------------------+
+// USB base address selection by rhport
+//--------------------------------------------------------------------+
+static inline uint32_t get_usb_base(uint8_t rhport) {
+ return (rhport == 0) ? CH58X_USB_BASE : CH58X_USB2_BASE;
+}
+
+//--------------------------------------------------------------------+
+// Register access helpers using base address
+//--------------------------------------------------------------------+
+#define USB_CTRL(base) CH58X_USB_CTRL(base)
+#define USB_UDEV_CTRL(base) CH58X_UDEV_CTRL(base)
+#define USB_INT_EN(base) CH58X_USB_INT_EN(base)
+#define USB_DEV_AD(base) CH58X_USB_DEV_AD(base)
+#define USB_MIS_ST(base) CH58X_USB_MIS_ST(base)
+#define USB_INT_FG(base) CH58X_USB_INT_FG(base)
+#define USB_INT_ST(base) CH58X_USB_INT_ST(base)
+#define USB_RX_LEN(base) CH58X_USB_RX_LEN(base)
+
+#define EP_DMA(base, ep) CH58X_EP_DMA(base, ep)
+#define EP_TLEN(base, ep) CH58X_EP_TLEN(base, ep)
+#define EP_CTRL(base, ep) CH58X_EP_CTRL(base, ep)
+
+//--------------------------------------------------------------------+
+// Inline helpers for merged EP CTRL register
+//--------------------------------------------------------------------+
+static inline void ep_set_tx_response(uint32_t base, uint8_t ep, uint8_t resp) {
+ uint8_t ctrl = EP_CTRL(base, ep);
+ ctrl = (ctrl & ~CH58X_EP_T_RES_MASK) | resp;
+ EP_CTRL(base, ep) = ctrl;
+}
+
+static inline void ep_set_rx_response(uint32_t base, uint8_t ep, uint8_t resp) {
+ uint8_t ctrl = EP_CTRL(base, ep);
+ ctrl = (ctrl & ~CH58X_EP_R_RES_MASK) | resp;
+ EP_CTRL(base, ep) = ctrl;
+}
+
+static inline void ep_set_both_response(uint32_t base, uint8_t ep, uint8_t tx_resp, uint8_t rx_resp) {
+ uint8_t ctrl = EP_CTRL(base, ep);
+ ctrl = (ctrl & ~(CH58X_EP_T_RES_MASK | CH58X_EP_R_RES_MASK)) | tx_resp | rx_resp;
+ EP_CTRL(base, ep) = ctrl;
+}
+
+//--------------------------------------------------------------------+
+// Private data structures
+//--------------------------------------------------------------------+
+struct usb_xfer {
+ bool valid;
+ uint8_t* buffer;
+ size_t len;
+ size_t processed_len;
+ size_t max_size;
+};
+
+typedef struct {
+ uint32_t usb_base;
+ bool ep0_tog;
+ bool isochronous[EP_MAX];
+ struct usb_xfer xfer[EP_MAX][2]; // [ep][dir]
+
+ // EP0 + EP4 shared buffer: EP0 OUT(64) + EP4 OUT(64) + EP4 IN(64)
+ TU_ATTR_ALIGNED(4) uint8_t ep0_buffer[EP_BUF_SIZE + EP_BUF_SIZE + EP_BUF_SIZE];
+
+ // EP1-EP3: OUT(64) + IN(64)
+ TU_ATTR_ALIGNED(4) uint8_t ep1_buffer[2][EP_BUF_SIZE];
+ TU_ATTR_ALIGNED(4) uint8_t ep2_buffer[2][EP_BUF_SIZE];
+ TU_ATTR_ALIGNED(4) uint8_t ep3_buffer[2][EP_BUF_SIZE];
+
+ // EP5-EP7: OUT(64) + IN(64)
+ TU_ATTR_ALIGNED(4) uint8_t ep5_buffer[2][EP_BUF_SIZE];
+ TU_ATTR_ALIGNED(4) uint8_t ep6_buffer[2][EP_BUF_SIZE];
+ TU_ATTR_ALIGNED(4) uint8_t ep7_buffer[2][EP_BUF_SIZE];
+} dcd_data_t;
+
+// Per-port data (support up to 2 USB ports)
+static dcd_data_t _dcd_data[2];
+
+//--------------------------------------------------------------------+
+// Buffer address helpers
+// EP0: ep0_buffer[0..63]
+// EP4 OUT: ep0_buffer[64..127] EP4 IN: ep0_buffer[128..191]
+// Other EPs: epX_buffer[0] = OUT, epX_buffer[1] = IN
+//--------------------------------------------------------------------+
+static uint8_t* ep_out_buffer(dcd_data_t* d, uint8_t ep) {
+ switch (ep) {
+ case 0: return &d->ep0_buffer[0];
+ case 1: return d->ep1_buffer[0];
+ case 2: return d->ep2_buffer[0];
+ case 3: return d->ep3_buffer[0];
+ case 4: return &d->ep0_buffer[EP_BUF_SIZE];
+ case 5: return d->ep5_buffer[0];
+ case 6: return d->ep6_buffer[0];
+ case 7: return d->ep7_buffer[0];
+ default: return NULL;
+ }
+}
+
+static uint8_t* ep_in_buffer(dcd_data_t* d, uint8_t ep) {
+ switch (ep) {
+ case 0: return &d->ep0_buffer[0]; // EP0 IN uses same buffer as OUT
+ case 1: return d->ep1_buffer[1];
+ case 2: return d->ep2_buffer[1];
+ case 3: return d->ep3_buffer[1];
+ case 4: return &d->ep0_buffer[2 * EP_BUF_SIZE];
+ case 5: return d->ep5_buffer[1];
+ case 6: return d->ep6_buffer[1];
+ case 7: return d->ep7_buffer[1];
+ default: return NULL;
+ }
+}
+
+// Get DMA base pointer for endpoint (the buffer whose address is set to DMA register)
+// For EP4, DMA is shared with EP0, so we return EP0 buffer
+static uint8_t* ep_dma_buffer(dcd_data_t* d, uint8_t ep) {
+ switch (ep) {
+ case 0: case 4: return &d->ep0_buffer[0];
+ case 1: return d->ep1_buffer[0];
+ case 2: return d->ep2_buffer[0];
+ case 3: return d->ep3_buffer[0];
+ case 5: return d->ep5_buffer[0];
+ case 6: return d->ep6_buffer[0];
+ case 7: return d->ep7_buffer[0];
+ default: return NULL;
+ }
+}
+
+//--------------------------------------------------------------------+
+// EP has auto-toggle support?
+//--------------------------------------------------------------------+
+static inline bool ep_has_auto_toggle(uint8_t ep) {
+ return (ep >= 1 && ep <= 3);
+}
+
+//--------------------------------------------------------------------+
+// Private transfer helpers
+//--------------------------------------------------------------------+
+static void update_in(uint8_t rhport, uint8_t ep, bool force) {
+ dcd_data_t* d = &_dcd_data[rhport];
+ uint32_t base = d->usb_base;
+ struct usb_xfer* xfer = &d->xfer[ep][TUSB_DIR_IN];
+
+ if (xfer->valid) {
+ if (force || xfer->len) {
+ size_t len = TU_MIN(xfer->max_size, xfer->len);
+
+ // Copy data to IN buffer
+ uint8_t* buf = ep_in_buffer(d, ep);
+ if (len > 0 && xfer->buffer != NULL) {
+ memcpy(buf, xfer->buffer, len);
+ }
+
+ xfer->buffer += len;
+ xfer->len -= len;
+ xfer->processed_len += len;
+
+ EP_TLEN(base, ep) = (uint8_t) len;
+
+ if (ep == 0) {
+ // EP0: manual toggle
+ uint8_t ctrl = EP_CTRL(base, 0);
+ ctrl = (ctrl & ~(CH58X_EP_T_RES_MASK | CH58X_EP_T_TOG));
+ ctrl |= CH58X_EP_T_RES_ACK;
+ if (d->ep0_tog) ctrl |= CH58X_EP_T_TOG;
+ EP_CTRL(base, 0) = ctrl;
+ d->ep0_tog = !d->ep0_tog;
+ } else if (d->isochronous[ep]) {
+ ep_set_tx_response(base, ep, CH58X_EP_T_RES_TOUT);
+ } else {
+ ep_set_tx_response(base, ep, CH58X_EP_T_RES_ACK);
+ }
+ } else {
+ // Transfer complete
+ xfer->valid = false;
+ ep_set_tx_response(base, ep, CH58X_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) {
+ dcd_data_t* d = &_dcd_data[rhport];
+ uint32_t base = d->usb_base;
+ struct usb_xfer* xfer = &d->xfer[ep][TUSB_DIR_OUT];
+
+ if (xfer->valid) {
+ size_t len = TU_MIN(xfer->max_size, TU_MIN(xfer->len, rx_len));
+
+ // Copy from OUT buffer
+ if (len > 0 && xfer->buffer != NULL) {
+ uint8_t* buf = ep_out_buffer(d, ep);
+ memcpy(xfer->buffer, buf, len);
+ }
+
+ xfer->buffer += len;
+ xfer->len -= len;
+ xfer->processed_len += len;
+
+ if (xfer->len == 0 || len < xfer->max_size) {
+ xfer->valid = false;
+ dcd_event_xfer_complete(rhport, ep, xfer->processed_len,
+ XFER_RESULT_SUCCESS, true);
+ }
+
+ if (ep == 0) {
+ ep_set_rx_response(base, 0, CH58X_EP_R_RES_ACK);
+ }
+ }
+}
+
+//--------------------------------------------------------------------+
+// DCD API Implementation
+//--------------------------------------------------------------------+
+
+bool dcd_init(uint8_t rhport, const tusb_rhport_init_t* rh_init) {
+ (void) rh_init;
+ dcd_data_t* d = &_dcd_data[rhport];
+ uint32_t base = get_usb_base(rhport);
+ d->usb_base = base;
+
+ // Clear state
+ tu_memclr(d->xfer, sizeof(d->xfer));
+ tu_memclr(d->isochronous, sizeof(d->isochronous));
+ d->ep0_tog = true;
+
+ // Init control registers
+ USB_CTRL(base) = CH58X_UC_DEV_PU_EN | CH58X_UC_INT_BUSY | CH58X_UC_DMA_EN;
+ USB_UDEV_CTRL(base) = CH58X_UD_PD_DIS | CH58X_UD_PORT_EN;
+ USB_DEV_AD(base) = 0x00;
+
+ // Clear all interrupt flags, then enable interrupts
+ USB_INT_FG(base) = 0xFF;
+ USB_INT_EN(base) = CH58X_UIE_BUS_RST | CH58X_UIE_TRANSFER | CH58X_UIE_SUSPEND;
+
+ // EP0 setup (also sets EP4 DMA since they share)
+ EP_DMA(base, 0) = (uint16_t)(uint32_t) &d->ep0_buffer[0];
+ EP_TLEN(base, 0) = 0;
+ EP_CTRL(base, 0) = CH58X_EP_R_RES_ACK | CH58X_EP_T_RES_NAK;
+
+ // Enable all endpoints TX+RX
+ CH58X_UEP4_1_MOD(base) = CH58X_UEP1_RX_EN | CH58X_UEP1_TX_EN |
+ CH58X_UEP4_RX_EN | CH58X_UEP4_TX_EN;
+ CH58X_UEP2_3_MOD(base) = CH58X_UEP2_RX_EN | CH58X_UEP2_TX_EN |
+ CH58X_UEP3_RX_EN | CH58X_UEP3_TX_EN;
+ CH58X_UEP567_MOD(base) = CH58X_UEP5_RX_EN | CH58X_UEP5_TX_EN |
+ CH58X_UEP6_RX_EN | CH58X_UEP6_TX_EN |
+ CH58X_UEP7_RX_EN | CH58X_UEP7_TX_EN;
+
+ // EP1-3: DMA + auto-toggle + NAK both directions
+ for (uint8_t ep = 1; ep <= 3; ep++) {
+ EP_DMA(base, ep) = (uint16_t)(uint32_t) ep_dma_buffer(d, ep);
+ EP_TLEN(base, ep) = 0;
+ EP_CTRL(base, ep) = CH58X_EP_AUTO_TOG | CH58X_EP_R_RES_NAK | CH58X_EP_T_RES_NAK;
+ }
+
+ // EP4: no independent DMA, no auto-toggle
+ EP_TLEN(base, 4) = 0;
+ EP_CTRL(base, 4) = CH58X_EP_R_RES_NAK | CH58X_EP_T_RES_NAK;
+
+ // EP5-7: DMA + no auto-toggle
+ for (uint8_t ep = 5; ep <= 7; ep++) {
+ EP_DMA(base, ep) = (uint16_t)(uint32_t) ep_dma_buffer(d, ep);
+ EP_TLEN(base, ep) = 0;
+ EP_CTRL(base, ep) = CH58X_EP_R_RES_NAK | CH58X_EP_T_RES_NAK;
+ }
+
+ // Set EP0 max size
+ d->xfer[0][TUSB_DIR_OUT].max_size = EP_BUF_SIZE;
+ d->xfer[0][TUSB_DIR_IN].max_size = EP_BUF_SIZE;
+
+ dcd_connect(rhport);
+ return true;
+}
+
+void dcd_int_handler(uint8_t rhport) {
+ dcd_data_t* d = &_dcd_data[rhport];
+ uint32_t base = d->usb_base;
+ uint8_t status = USB_INT_FG(base);
+
+ if (status & CH58X_UIF_TRANSFER) {
+ uint8_t int_st = USB_INT_ST(base);
+ uint8_t ep = CH58X_INT_ST_ENDP(int_st);
+ uint8_t token = CH58X_INT_ST_TOKEN(int_st);
+
+ // Check SETUP first via dedicated flag
+ if (int_st & CH58X_UIS_SETUP_ACT) {
+ // SETUP packet received on EP0
+ ep_set_both_response(base, 0, CH58X_EP_T_RES_NAK, CH58X_EP_R_RES_ACK);
+ d->ep0_tog = true;
+ dcd_event_setup_received(rhport, ep_out_buffer(d, 0), true);
+ } else {
+ switch (token) {
+ case CH58X_PID_OUT: {
+ uint8_t rx_len = USB_RX_LEN(base);
+ if (!ep_has_auto_toggle(ep) && ep != 0) {
+ // Manual toggle for EP4-7
+ EP_CTRL(base, ep) ^= CH58X_EP_R_TOG;
+ }
+ update_out(rhport, ep, rx_len);
+ break;
+ }
+
+ case CH58X_PID_IN: {
+ if (!ep_has_auto_toggle(ep) && ep != 0) {
+ // Manual toggle for EP4-7
+ EP_CTRL(base, ep) ^= CH58X_EP_T_TOG;
+ }
+ update_in(rhport, ep, false);
+ break;
+ }
+
+ default:
+ break;
+ }
+ }
+
+ USB_INT_FG(base) = CH58X_UIF_TRANSFER;
+ } else if (status & CH58X_UIF_BUS_RST) {
+ // Bus reset
+ d->ep0_tog = true;
+ d->xfer[0][TUSB_DIR_OUT].max_size = EP_BUF_SIZE;
+ d->xfer[0][TUSB_DIR_IN].max_size = EP_BUF_SIZE;
+
+ USB_DEV_AD(base) = 0x00;
+ ep_set_rx_response(base, 0, CH58X_EP_R_RES_ACK);
+
+ tusb_speed_t speed = (USB_MIS_ST(base) & CH58X_UMS_DM_LEVEL) ?
+ TUSB_SPEED_LOW : TUSB_SPEED_FULL;
+ dcd_event_bus_reset(rhport, speed, true);
+
+ USB_INT_FG(base) = CH58X_UIF_BUS_RST;
+ } else if (status & CH58X_UIF_SUSPEND) {
+ if (USB_MIS_ST(base) & CH58X_UMS_SUSPEND) {
+ dcd_event_t event = {.rhport = rhport, .event_id = DCD_EVENT_SUSPEND};
+ dcd_event_handler(&event, true);
+ } else {
+ dcd_event_t event = {.rhport = rhport, .event_id = DCD_EVENT_RESUME};
+ dcd_event_handler(&event, true);
+ }
+ USB_INT_FG(base) = CH58X_UIF_SUSPEND;
+ }
+}
+
+void dcd_int_enable(uint8_t rhport) {
+ (void) rhport;
+ // PFIC enable: USB_IRQn=22, USB2_IRQn=23
+ volatile uint32_t* pfic_ienr = (volatile uint32_t*) 0xE000E100;
+ uint8_t irqn = (rhport == 0) ? 22 : 23;
+ pfic_ienr[irqn / 32] = (1u << (irqn % 32));
+}
+
+void dcd_int_disable(uint8_t rhport) {
+ (void) rhport;
+ volatile uint32_t* pfic_irer = (volatile uint32_t*) 0xE000E180;
+ uint8_t irqn = (rhport == 0) ? 22 : 23;
+ pfic_irer[irqn / 32] = (1u << (irqn % 32));
+ __asm volatile ("fence.i");
+}
+
+void dcd_set_address(uint8_t rhport, uint8_t dev_addr) {
+ (void) dev_addr;
+ // Address is set in status stage complete callback
+ dcd_edpt_xfer(rhport, 0x80, NULL, 0, false);
+}
+
+void dcd_remote_wakeup(uint8_t rhport) {
+ uint32_t base = get_usb_base(rhport);
+ USB_UDEV_CTRL(base) |= CH58X_UD_GP_BIT;
+ for (volatile int i = 0; i < 60000; i++) { } // ~1ms resume signal
+ USB_UDEV_CTRL(base) &= ~CH58X_UD_GP_BIT;
+}
+
+void dcd_connect(uint8_t rhport) {
+ uint32_t base = get_usb_base(rhport);
+ USB_CTRL(base) |= CH58X_UC_DEV_PU_EN;
+}
+
+void dcd_disconnect(uint8_t rhport) {
+ uint32_t base = get_usb_base(rhport);
+ USB_CTRL(base) &= ~CH58X_UC_DEV_PU_EN;
+}
+
+void dcd_sof_enable(uint8_t rhport, bool en) {
+ uint32_t base = get_usb_base(rhport);
+ if (en) {
+ USB_INT_EN(base) |= CH58X_UIE_HST_SOF;
+ } else {
+ USB_INT_EN(base) &= ~CH58X_UIE_HST_SOF;
+ }
+}
+
+void dcd_edpt0_status_complete(uint8_t rhport, tusb_control_request_t const* request) {
+ dcd_data_t* d = &_dcd_data[rhport];
+ uint32_t base = d->usb_base;
+
+ if (request->bmRequestType_bit.recipient == TUSB_REQ_RCPT_DEVICE &&
+ request->bmRequestType_bit.type == TUSB_REQ_TYPE_STANDARD &&
+ request->bRequest == TUSB_REQ_SET_ADDRESS) {
+ USB_DEV_AD(base) = (uint8_t) request->wValue;
+ }
+ ep_set_both_response(base, 0, CH58X_EP_T_RES_NAK, CH58X_EP_R_RES_ACK);
+}
+
+bool dcd_edpt_open(uint8_t rhport, tusb_desc_endpoint_t const* desc_ep) {
+ dcd_data_t* d = &_dcd_data[rhport];
+ uint32_t base = d->usb_base;
+ uint8_t ep = tu_edpt_number(desc_ep->bEndpointAddress);
+ uint8_t dir = tu_edpt_dir(desc_ep->bEndpointAddress);
+ TU_ASSERT(ep < EP_MAX);
+
+ d->isochronous[ep] = (desc_ep->bmAttributes.xfer == TUSB_XFER_ISOCHRONOUS);
+ d->xfer[ep][dir].max_size = tu_edpt_packet_size(desc_ep);
+
+ if (ep != 0) {
+ dcd_int_disable(rhport);
+ uint8_t ctrl = EP_CTRL(base, ep);
+ if (dir == TUSB_DIR_OUT) {
+ if (d->isochronous[ep]) {
+ ctrl = (ctrl & ~CH58X_EP_R_RES_MASK) | CH58X_EP_R_RES_TOUT;
+ } else {
+ // Start with NAK; dcd_edpt_xfer will set ACK when a transfer is submitted
+ ctrl = (ctrl & ~CH58X_EP_R_RES_MASK) | CH58X_EP_R_RES_NAK;
+ }
+ // Enable auto toggle for EP1-3
+ if (ep_has_auto_toggle(ep)) {
+ ctrl |= CH58X_EP_AUTO_TOG;
+ }
+ } else {
+ EP_TLEN(base, ep) = 0;
+ ctrl = (ctrl & ~CH58X_EP_T_RES_MASK) | CH58X_EP_T_RES_NAK;
+ if (ep_has_auto_toggle(ep)) {
+ ctrl |= CH58X_EP_AUTO_TOG;
+ }
+ }
+ EP_CTRL(base, ep) = ctrl;
+ dcd_int_enable(rhport);
+ }
+ return true;
+}
+
+void dcd_edpt_close_all(uint8_t rhport) {
+ dcd_data_t* d = &_dcd_data[rhport];
+ uint32_t base = d->usb_base;
+
+ for (uint8_t ep = 1; ep < EP_MAX; ep++) {
+ d->xfer[ep][TUSB_DIR_IN].valid = false;
+ d->xfer[ep][TUSB_DIR_OUT].valid = false;
+ d->isochronous[ep] = false;
+ EP_CTRL(base, ep) = CH58X_EP_R_RES_NAK | CH58X_EP_T_RES_NAK;
+ }
+}
+
+bool dcd_edpt_iso_alloc(uint8_t rhport, uint8_t ep_addr, uint16_t largest_packet_size) {
+ (void) rhport;
+ (void) ep_addr;
+ (void) largest_packet_size;
+ return false;
+}
+
+bool dcd_edpt_iso_activate(uint8_t rhport, const tusb_desc_endpoint_t* desc_ep) {
+ (void) rhport;
+ (void) desc_ep;
+ return 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;
+ uint8_t ep = tu_edpt_number(ep_addr);
+ uint8_t dir = tu_edpt_dir(ep_addr);
+
+ dcd_data_t* d = &_dcd_data[rhport];
+ struct usb_xfer* xfer = &d->xfer[ep][dir];
+
+ dcd_int_disable(rhport);
+ 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 {
+ // For OUT direction, set endpoint to ACK to start receiving data
+ if (ep != 0) {
+ if (d->isochronous[ep]) {
+ ep_set_rx_response(d->usb_base, ep, CH58X_EP_R_RES_TOUT);
+ } else {
+ ep_set_rx_response(d->usb_base, ep, CH58X_EP_R_RES_ACK);
+ }
+ }
+ }
+ return true;
+}
+
+void dcd_edpt_stall(uint8_t rhport, uint8_t ep_addr) {
+ dcd_data_t* d = &_dcd_data[rhport];
+ uint32_t base = d->usb_base;
+ uint8_t ep = tu_edpt_number(ep_addr);
+ uint8_t dir = tu_edpt_dir(ep_addr);
+
+ dcd_int_disable(rhport);
+ if (ep == 0) {
+ // EP0: stall both directions
+ EP_CTRL(base, 0) = CH58X_EP_R_RES_STALL | CH58X_EP_T_RES_STALL |
+ CH58X_EP_R_TOG | CH58X_EP_T_TOG;
+ } else {
+ if (dir == TUSB_DIR_OUT) {
+ ep_set_rx_response(base, ep, CH58X_EP_R_RES_STALL);
+ } else {
+ ep_set_tx_response(base, ep, CH58X_EP_T_RES_STALL);
+ }
+ }
+ dcd_int_enable(rhport);
+}
+
+void dcd_edpt_clear_stall(uint8_t rhport, uint8_t ep_addr) {
+ dcd_data_t* d = &_dcd_data[rhport];
+ uint32_t base = d->usb_base;
+ uint8_t ep = tu_edpt_number(ep_addr);
+ uint8_t dir = tu_edpt_dir(ep_addr);
+
+ dcd_int_disable(rhport);
+ if (ep == 0) {
+ if (dir == TUSB_DIR_OUT) {
+ ep_set_rx_response(base, 0, CH58X_EP_R_RES_ACK);
+ }
+ } else {
+ uint8_t ctrl = EP_CTRL(base, ep);
+ if (dir == TUSB_DIR_OUT) {
+ ctrl &= ~(CH58X_EP_R_RES_MASK | CH58X_EP_R_TOG);
+ ctrl |= CH58X_EP_R_RES_ACK;
+ } else {
+ ctrl &= ~(CH58X_EP_T_RES_MASK | CH58X_EP_T_TOG);
+ ctrl |= CH58X_EP_T_RES_NAK;
+ }
+ EP_CTRL(base, ep) = ctrl;
+ }
+ dcd_int_enable(rhport);
+}
+
+#endif /* CFG_TUD_ENABLED && TUP_USBIP_WCH_CH58X */
diff --git a/src/portable/wch/hcd_ch58x_usbfs.c b/src/portable/wch/hcd_ch58x_usbfs.c
new file mode 100644
index 000000000..629c2a789
--- /dev/null
+++ b/src/portable/wch/hcd_ch58x_usbfs.c
@@ -0,0 +1,712 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2024 TinyUSB contributors
+ *
+ * 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 && defined(TUP_USBIP_WCH_CH58X) && \
+ defined(CFG_TUH_WCH_USBIP_USBFS) && CFG_TUH_WCH_USBIP_USBFS
+
+#include <string.h>
+
+#include "host/hcd.h"
+#include "host/usbh.h"
+#include "host/usbh_pvt.h"
+
+#include "ch58x_usbfs_reg.h"
+
+//--------------------------------------------------------------------+
+// Configuration
+//--------------------------------------------------------------------+
+#define USBFS_MAX_PACKET_SIZE 64
+
+#define LOG_CH58X_HCD(...) TU_LOG3(__VA_ARGS__)
+
+//--------------------------------------------------------------------+
+// RX/TX buffers (must be 4-byte aligned and in lower 64KB of RAM)
+// Separate buffers for each USB port to avoid DMA conflicts
+//--------------------------------------------------------------------+
+TU_ATTR_ALIGNED(4) static uint8_t _rx_buf[2][USBFS_MAX_PACKET_SIZE];
+TU_ATTR_ALIGNED(4) static uint8_t _tx_buf[2][USBFS_MAX_PACKET_SIZE];
+
+//--------------------------------------------------------------------+
+// USB base address selection by rhport
+//--------------------------------------------------------------------+
+static inline uint32_t _get_usb_base(uint8_t rhport) {
+ return (rhport == 0) ? CH58X_USB_BASE : CH58X_USB2_BASE;
+}
+
+//--------------------------------------------------------------------+
+// Endpoint record
+//--------------------------------------------------------------------+
+typedef struct {
+ bool configured;
+ uint8_t dev_addr;
+ uint8_t ep_addr;
+ uint8_t max_packet_size;
+ uint8_t xfer_type;
+ uint8_t data_toggle; // 0=DATA0, 1=DATA1
+ bool is_nak_pending;
+ uint16_t buflen;
+ uint8_t* buf;
+} hcd_edpt_t;
+
+static hcd_edpt_t _edpt_list[CFG_TUH_DEVICE_MAX * 6] = {};
+
+//--------------------------------------------------------------------+
+// Current transfer state (only one transfer at a time per root port)
+//--------------------------------------------------------------------+
+typedef struct {
+ volatile bool is_busy;
+ uint8_t rhport;
+ uint8_t dev_addr;
+ uint8_t ep_addr;
+ uint32_t start_ms;
+ uint8_t* buffer;
+ uint16_t bufferlen;
+ uint16_t xferred_len;
+ bool nak_pending;
+} hcd_xfer_t;
+
+static volatile hcd_xfer_t _current_xfer = {};
+
+//--------------------------------------------------------------------+
+// Per-port state
+//--------------------------------------------------------------------+
+typedef struct {
+ uint32_t usb_base;
+ bool int_enabled;
+} hcd_port_t;
+
+static hcd_port_t _port_data[2] = {};
+
+//--------------------------------------------------------------------+
+// Endpoint record management
+//--------------------------------------------------------------------+
+static hcd_edpt_t* _get_edpt(uint8_t dev_addr, uint8_t ep_addr) {
+ for (size_t i = 0; i < TU_ARRAY_SIZE(_edpt_list); i++) {
+ hcd_edpt_t* e = &_edpt_list[i];
+ if (e->configured && e->dev_addr == dev_addr && e->ep_addr == ep_addr) {
+ return e;
+ }
+ }
+ return NULL;
+}
+
+static hcd_edpt_t* _alloc_edpt(void) {
+ for (size_t i = 0; i < TU_ARRAY_SIZE(_edpt_list); i++) {
+ if (!_edpt_list[i].configured) {
+ return &_edpt_list[i];
+ }
+ }
+ return NULL;
+}
+
+static hcd_edpt_t* _add_edpt(uint8_t dev_addr, uint8_t ep_addr,
+ uint16_t max_packet_size, uint8_t xfer_type) {
+ hcd_edpt_t* e = _alloc_edpt();
+ TU_ASSERT(e != NULL, NULL);
+
+ e->dev_addr = dev_addr;
+ e->ep_addr = ep_addr;
+ e->max_packet_size = (uint8_t) TU_MIN(max_packet_size, USBFS_MAX_PACKET_SIZE);
+ e->xfer_type = xfer_type;
+ e->data_toggle = 0;
+ e->is_nak_pending = false;
+ e->buflen = 0;
+ e->buf = NULL;
+ e->configured = true;
+
+ return e;
+}
+
+static hcd_edpt_t* _get_or_add_edpt(uint8_t dev_addr, uint8_t ep_addr,
+ uint16_t max_packet_size, uint8_t xfer_type) {
+ hcd_edpt_t* e = _get_edpt(dev_addr, ep_addr);
+ if (e != NULL) return e;
+ return _add_edpt(dev_addr, ep_addr, max_packet_size, xfer_type);
+}
+
+static void _remove_edpts_for_device(uint8_t dev_addr) {
+ for (size_t i = 0; i < TU_ARRAY_SIZE(_edpt_list); i++) {
+ if (_edpt_list[i].configured && _edpt_list[i].dev_addr == dev_addr) {
+ _edpt_list[i].configured = false;
+ }
+ }
+}
+
+//--------------------------------------------------------------------+
+// Low-level hardware helpers
+//--------------------------------------------------------------------+
+
+// Busywait delay (approximate microseconds at ~60MHz)
+TU_ATTR_ALWAYS_INLINE static inline void _delay_loops(uint32_t count) {
+ volatile uint32_t c = count / 3;
+ if (c == 0) return;
+ while (c-- != 0) {}
+}
+
+static void _hw_init_host(uint8_t rhport, bool enabled) {
+ uint32_t base = _port_data[rhport].usb_base;
+
+ if (!enabled) {
+ // Reset SIE when disabling
+ CH58X_USB_CTRL(base) = CH58X_UC_RESET_SIE | CH58X_UC_CLR_ALL;
+ _delay_loops(600); // ~10us at 60MHz
+ CH58X_USB_CTRL(base) = 0;
+ return;
+ }
+
+ // host mode, pull-down enabled, clear addr
+ CH58X_USB_CTRL(base) = CH58X_UC_HOST_MODE;
+ while (!(CH58X_USB_CTRL(base) & CH58X_UC_HOST_MODE)) {}
+ CH58X_UHOST_CTRL(base) = 0;
+ CH58X_USB_DEV_AD(base) = 0x00;
+
+ // EP2 RX (IN), EP3 TX (OUT/SETUP)
+ CH58X_UH_EP_MOD(base) = CH58X_UH_EP_TX_EN | CH58X_UH_EP_RX_EN;
+
+ // DMA: 16-bit address, lower 64KB only
+ CH58X_UH_RX_DMA(base) = (uint16_t)(uint32_t)_rx_buf[rhport];
+ CH58X_UH_TX_DMA(base) = (uint16_t)(uint32_t)_tx_buf[rhport];
+
+ CH58X_UH_RX_CTRL(base) = 0x00;
+ CH58X_UH_TX_CTRL(base) = 0x00;
+
+ CH58X_USB_CTRL(base) = CH58X_UC_HOST_MODE | CH58X_UC_INT_BUSY | CH58X_UC_DMA_EN;
+ CH58X_UH_SETUP(base) = CH58X_UH_SOF_EN;
+
+ CH58X_USB_INT_FG(base) = 0xFF; // clear all flags
+ CH58X_USB_INT_EN(base) = CH58X_UIE_TRANSFER | CH58X_UIE_DETECT;
+}
+
+static bool _hw_start_xfer(uint8_t rhport, uint8_t pid, uint8_t ep_addr, uint8_t data_toggle) {
+ uint32_t base = _port_data[rhport].usb_base;
+
+ LOG_CH58X_HCD("_hw_start_xfer(pid=0x%02x, ep=0x%02x, tog=%d)\r\n", pid, ep_addr, data_toggle);
+
+ // Workaround: small delay for low-speed devices
+ bool is_lowspeed = tuh_speed_get(_current_xfer.dev_addr) == TUSB_SPEED_LOW;
+ if (is_lowspeed) {
+ _delay_loops(60000000 / 1000000 * 40); // ~40us at 60MHz
+ }
+
+ // Set toggle controls (same as SDK: R8_UH_RX_CTRL = R8_UH_TX_CTRL = tog)
+ uint8_t tog_ctrl = (data_toggle != 0) ? CH58X_UH_T_TOG : 0;
+ CH58X_UH_TX_CTRL(base) = tog_ctrl;
+ CH58X_UH_RX_CTRL(base) = (data_toggle != 0) ? CH58X_UH_R_TOG : 0;
+
+ uint8_t pid_endp = (pid << 4) | (tu_edpt_number(ep_addr) & 0x0F);
+
+ // clear flag, enable int, then set PID to start transfer
+ CH58X_USB_INT_FG(base) = CH58X_UIF_TRANSFER;
+ CH58X_USB_INT_EN(base) |= CH58X_UIE_TRANSFER;
+ CH58X_UH_EP_PID(base) = pid_endp;
+
+ return true;
+}
+
+static void _hw_set_device_addr(uint8_t rhport, uint8_t dev_addr) {
+ uint32_t base = _port_data[rhport].usb_base;
+ CH58X_USB_DEV_AD(base) = (CH58X_USB_DEV_AD(base) & CH58X_UDA_GP_BIT) |
+ (dev_addr & CH58X_USB_ADDR_MASK);
+}
+
+static void _hw_set_speed(uint8_t rhport, tusb_speed_t speed) {
+ uint32_t base = _port_data[rhport].usb_base;
+
+ LOG_CH58X_HCD("_hw_set_speed(%s)\r\n",
+ speed == TUSB_SPEED_FULL ? "Full" : "Low");
+
+ if (speed == TUSB_SPEED_LOW) {
+ CH58X_USB_CTRL(base) |= CH58X_UC_LOW_SPEED;
+ CH58X_UHOST_CTRL(base) |= CH58X_UH_LOW_SPEED;
+ } else {
+ CH58X_USB_CTRL(base) &= ~CH58X_UC_LOW_SPEED;
+ CH58X_UHOST_CTRL(base) &= ~CH58X_UH_LOW_SPEED;
+ CH58X_UH_SETUP(base) &= ~CH58X_UH_PRE_PID_EN;
+ }
+}
+
+static void _hw_set_addr_speed(uint8_t rhport, uint8_t dev_addr) {
+ _hw_set_device_addr(rhport, dev_addr);
+
+ tusb_speed_t rhport_speed = hcd_port_speed_get(rhport);
+ tusb_speed_t dev_speed = tuh_speed_get(dev_addr);
+ _hw_set_speed(rhport, dev_speed);
+
+ // FS root + LS device: hub uses PRE PID, clear LS on host ctrl
+ if (rhport_speed == TUSB_SPEED_FULL && dev_speed == TUSB_SPEED_LOW) {
+ uint32_t base = _port_data[rhport].usb_base;
+ CH58X_UHOST_CTRL(base) &= ~CH58X_UH_LOW_SPEED;
+ }
+}
+
+static bool _hw_device_attached(uint8_t rhport) {
+ uint32_t base = _port_data[rhport].usb_base;
+ return (CH58X_USB_MIS_ST(base) & CH58X_UMS_DEV_ATTACH) != 0;
+}
+
+//--------------------------------------------------------------------+
+// NAK retry callback
+//--------------------------------------------------------------------+
+static void _xfer_retry(void* param) {
+ LOG_CH58X_HCD("_xfer_retry()\r\n");
+ hcd_edpt_t* edpt = (hcd_edpt_t*)param;
+
+ if (_current_xfer.nak_pending) {
+ uint8_t rhport = _current_xfer.rhport;
+ _current_xfer.nak_pending = false;
+ edpt->is_nak_pending = false;
+
+ uint8_t dev_addr = edpt->dev_addr;
+ uint8_t ep_addr = edpt->ep_addr;
+ uint16_t buflen = edpt->buflen;
+ uint8_t* buf = edpt->buf;
+
+ // Check if endpoint is still valid
+ hcd_edpt_t* current = _get_edpt(dev_addr, ep_addr);
+ if (current) {
+ hcd_edpt_xfer(rhport, dev_addr, ep_addr, buf, buflen);
+ }
+ }
+}
+
+//--------------------------------------------------------------------+
+// HCD API: Controller
+//--------------------------------------------------------------------+
+bool hcd_init(uint8_t rhport, const tusb_rhport_init_t* rh_init) {
+ (void)rh_init;
+
+ _port_data[rhport].usb_base = _get_usb_base(rhport);
+ _port_data[rhport].int_enabled = false;
+
+ // Clear endpoint records
+ tu_memclr(_edpt_list, sizeof(_edpt_list));
+ tu_memclr((void*)&_current_xfer, sizeof(_current_xfer));
+
+ _hw_init_host(rhport, true);
+
+ return true;
+}
+
+bool hcd_deinit(uint8_t rhport) {
+ _hw_init_host(rhport, false);
+ return true;
+}
+
+uint32_t hcd_frame_number(uint8_t rhport) {
+ (void)rhport;
+ return tusb_time_millis_api();
+}
+
+void hcd_int_enable(uint8_t rhport) {
+ // PFIC interrupt enable
+ volatile uint32_t* pfic_ienr = (volatile uint32_t*)0xE000E100;
+ uint8_t irqn = (rhport == 0) ? 22 : 23; // USB_IRQn or USB2_IRQn
+ pfic_ienr[irqn / 32] = (1u << (irqn % 32));
+ _port_data[rhport].int_enabled = true;
+}
+
+void hcd_int_disable(uint8_t rhport) {
+ volatile uint32_t* pfic_irer = (volatile uint32_t*)0xE000E180;
+ uint8_t irqn = (rhport == 0) ? 22 : 23;
+ pfic_irer[irqn / 32] = (1u << (irqn % 32));
+ __asm volatile("fence.i");
+ _port_data[rhport].int_enabled = false;
+}
+
+//--------------------------------------------------------------------+
+// HCD API: Port
+//--------------------------------------------------------------------+
+bool hcd_port_connect_status(uint8_t rhport) {
+ return _hw_device_attached(rhport);
+}
+
+tusb_speed_t hcd_port_speed_get(uint8_t rhport) {
+ uint32_t base = _port_data[rhport].usb_base;
+ // DM level high = low-speed device, low = full-speed
+ if (CH58X_USB_MIS_ST(base) & CH58X_UMS_DM_LEVEL) {
+ return TUSB_SPEED_LOW;
+ }
+ return TUSB_SPEED_FULL;
+}
+
+static bool _int_state_before_reset = false;
+
+void hcd_port_reset(uint8_t rhport) {
+ uint32_t base = _port_data[rhport].usb_base;
+
+ LOG_CH58X_HCD("hcd_port_reset()\r\n");
+
+ _int_state_before_reset = _port_data[rhport].int_enabled;
+ hcd_int_disable(rhport);
+
+ _hw_set_device_addr(rhport, 0x00);
+
+ // Disable port and default to full-speed before reset (matches SDK ResetRootHubPort)
+ CH58X_UHOST_CTRL(base) &= ~CH58X_UH_PORT_EN;
+ _hw_set_speed(rhport, TUSB_SPEED_FULL);
+
+ // Start bus reset (clear low-speed bit simultaneously)
+ CH58X_UHOST_CTRL(base) = (CH58X_UHOST_CTRL(base) & ~CH58X_UH_LOW_SPEED) | CH58X_UH_BUS_RESET;
+}
+
+void hcd_port_reset_end(uint8_t rhport) {
+ uint32_t base = _port_data[rhport].usb_base;
+
+ LOG_CH58X_HCD("hcd_port_reset_end()\r\n");
+
+ // End bus reset
+ CH58X_UHOST_CTRL(base) &= ~CH58X_UH_BUS_RESET;
+ tusb_time_delay_ms_api(2);
+
+ // Detect speed and configure
+ if ((CH58X_UHOST_CTRL(base) & CH58X_UH_PORT_EN) == 0) {
+ if (hcd_port_speed_get(rhport) == TUSB_SPEED_LOW) {
+ _hw_set_speed(rhport, TUSB_SPEED_LOW);
+ }
+ }
+
+ // Enable port and SOF
+ CH58X_UHOST_CTRL(base) |= CH58X_UH_PORT_EN;
+ CH58X_UH_SETUP(base) |= CH58X_UH_SOF_EN;
+
+ // Suppress stale detect event
+ CH58X_USB_INT_FG(base) = CH58X_UIF_DETECT;
+
+ if (_int_state_before_reset) {
+ hcd_int_enable(rhport);
+ }
+}
+
+void hcd_device_close(uint8_t rhport, uint8_t dev_addr) {
+ (void)rhport;
+ LOG_CH58X_HCD("hcd_device_close(dev=0x%02x)\r\n", dev_addr);
+ _remove_edpts_for_device(dev_addr);
+}
+
+//--------------------------------------------------------------------+
+// HCD API: Endpoint
+//--------------------------------------------------------------------+
+bool hcd_edpt_open(uint8_t rhport, uint8_t dev_addr, tusb_desc_endpoint_t const* ep_desc) {
+ uint8_t ep_addr = ep_desc->bEndpointAddress;
+ uint8_t ep_num = tu_edpt_number(ep_addr);
+ uint16_t max_packet_size = ep_desc->wMaxPacketSize;
+ uint8_t xfer_type = ep_desc->bmAttributes.xfer;
+ uint32_t base = _port_data[rhport].usb_base;
+
+ LOG_CH58X_HCD("hcd_edpt_open(dev=0x%02x, ep=0x%02x, mps=%d, type=%d)\r\n",
+ dev_addr, ep_addr, max_packet_size, xfer_type);
+
+ // Wait for any pending transfer
+ while (_current_xfer.is_busy) {}
+
+ if (ep_num == 0) {
+ TU_ASSERT(_get_or_add_edpt(dev_addr, 0x00, max_packet_size, xfer_type) != NULL, false);
+ TU_ASSERT(_get_or_add_edpt(dev_addr, 0x80, max_packet_size, xfer_type) != NULL, false);
+ } else {
+ TU_ASSERT(_get_or_add_edpt(dev_addr, ep_addr, max_packet_size, xfer_type) != NULL, false);
+ }
+
+ // Ensure port is enabled with SOF
+ CH58X_UHOST_CTRL(base) |= CH58X_UH_PORT_EN;
+ CH58X_UH_SETUP(base) |= CH58X_UH_SOF_EN;
+
+ _hw_set_addr_speed(rhport, dev_addr);
+
+ return true;
+}
+
+bool hcd_edpt_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr,
+ uint8_t* buffer, uint16_t buflen) {
+ uint32_t base = _port_data[rhport].usb_base;
+
+ LOG_CH58X_HCD("hcd_edpt_xfer(dev=0x%02x, ep=0x%02x, len=%d)\r\n",
+ dev_addr, ep_addr, buflen);
+
+ // Wait for any pending transfer
+ while (_current_xfer.is_busy) {}
+ _current_xfer.is_busy = true;
+
+ hcd_edpt_t* edpt = _get_edpt(dev_addr, ep_addr);
+ TU_ASSERT(edpt != NULL);
+
+ _hw_set_addr_speed(rhport, dev_addr);
+
+ _current_xfer.rhport = rhport;
+ _current_xfer.dev_addr = dev_addr;
+ _current_xfer.ep_addr = ep_addr;
+ _current_xfer.buffer = buffer;
+ _current_xfer.bufferlen = buflen;
+ _current_xfer.start_ms = tusb_time_millis_api();
+ _current_xfer.xferred_len = 0;
+ _current_xfer.nak_pending = false;
+
+ if (tu_edpt_dir(ep_addr) == TUSB_DIR_IN) {
+ // IN transfer: host receives data
+ return _hw_start_xfer(rhport, CH58X_USB_PID_IN, ep_addr, edpt->data_toggle);
+ } else {
+ // OUT transfer: host sends data
+ uint16_t copylen = TU_MIN(edpt->max_packet_size, buflen);
+ CH58X_UH_TX_LEN(base) = (uint8_t)copylen;
+ memcpy(_tx_buf[rhport], buffer, copylen);
+ return _hw_start_xfer(rhport, CH58X_USB_PID_OUT, ep_addr, edpt->data_toggle);
+ }
+}
+
+bool hcd_edpt_abort_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr) {
+ (void)rhport;
+ (void)dev_addr;
+ (void)ep_addr;
+ return false;
+}
+
+bool hcd_setup_send(uint8_t rhport, uint8_t dev_addr, uint8_t const setup_packet[8]) {
+ uint32_t base = _port_data[rhport].usb_base;
+
+ LOG_CH58X_HCD("hcd_setup_send(dev=0x%02x)\r\n", dev_addr);
+
+ // Wait for any pending transfer
+ while (_current_xfer.is_busy) {}
+ _current_xfer.is_busy = true;
+
+ _hw_set_addr_speed(rhport, dev_addr);
+
+ hcd_edpt_t* edpt_tx = _get_edpt(dev_addr, 0x00);
+ hcd_edpt_t* edpt_rx = _get_edpt(dev_addr, 0x80);
+ TU_ASSERT(edpt_tx != NULL, false);
+ TU_ASSERT(edpt_rx != NULL, false);
+
+ // SETUP always starts with DATA0; after SETUP, IN data starts with DATA1
+ edpt_tx->data_toggle = 0;
+ edpt_rx->data_toggle = 1;
+
+ memcpy(_tx_buf[rhport], setup_packet, 8);
+ CH58X_UH_TX_LEN(base) = 8;
+
+ _current_xfer.rhport = rhport;
+ _current_xfer.dev_addr = dev_addr;
+ _current_xfer.ep_addr = 0x00; // SETUP always targets EP0 OUT
+ _current_xfer.start_ms = tusb_time_millis_api();
+ _current_xfer.buffer = _tx_buf[rhport];
+ _current_xfer.bufferlen = 8;
+ _current_xfer.xferred_len = 0;
+ _current_xfer.nak_pending = false;
+
+ _hw_start_xfer(rhport, CH58X_USB_PID_SETUP, 0, 0);
+
+ return true;
+}
+
+bool hcd_edpt_clear_stall(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr) {
+ uint32_t base = _port_data[rhport].usb_base;
+ uint8_t ep_num = tu_edpt_number(ep_addr);
+
+ LOG_CH58X_HCD("hcd_edpt_clear_stall(dev=0x%02x, ep=0x%02x)\r\n", dev_addr, ep_addr);
+
+ // Send CLEAR_FEATURE(ENDPOINT_HALT) via blocking transfer
+ uint8_t setup[8] = {
+ 0x02, 0x01, 0x00, 0x00, ep_addr, 0x00, 0x00, 0x00
+ };
+ memcpy(_tx_buf[rhport], setup, 8);
+ CH58X_UH_TX_LEN(base) = 8;
+
+ bool prev_int = _port_data[rhport].int_enabled;
+ hcd_int_disable(rhport);
+
+ CH58X_UH_EP_PID(base) = (CH58X_USB_PID_SETUP << 4) | 0x00;
+ CH58X_USB_INT_FG(base) = CH58X_UIF_TRANSFER;
+ while ((CH58X_USB_INT_FG(base) & CH58X_UIF_TRANSFER) == 0) {}
+ CH58X_UH_EP_PID(base) = 0;
+
+ uint8_t response = CH58X_USB_INT_ST(base) & CH58X_UIS_H_RES_MASK;
+ (void)response;
+
+ LOG_CH58X_HCD("clear_stall response=0x%02x\r\n", response);
+
+ if (prev_int) {
+ hcd_int_enable(rhport);
+ }
+
+ return true;
+}
+
+//--------------------------------------------------------------------+
+// Interrupt Handler
+//--------------------------------------------------------------------+
+void hcd_int_handler(uint8_t rhport, bool in_isr) {
+ uint32_t base = _port_data[rhport].usb_base;
+
+ //-- Device attach/detach detection --
+ if (CH58X_USB_INT_FG(base) & CH58X_UIF_DETECT) {
+ CH58X_USB_INT_FG(base) = CH58X_UIF_DETECT;
+
+ bool attached = _hw_device_attached(rhport);
+ LOG_CH58X_HCD("hcd_int: detect, attached=%d\r\n", attached);
+
+ if (attached) {
+ hcd_event_device_attach(rhport, in_isr);
+ } else {
+ hcd_event_device_remove(rhport, in_isr);
+ }
+ return;
+ }
+
+ //-- Transfer complete --
+ if (CH58X_USB_INT_FG(base) & CH58X_UIF_TRANSFER) {
+ // Read PID/endpoint before stopping (must read first!)
+ uint8_t pid_endp = CH58X_UH_EP_PID(base);
+ uint8_t int_st = CH58X_USB_INT_ST(base);
+ uint8_t int_fg = CH58X_USB_INT_FG(base);
+ uint8_t dev_addr = CH58X_USB_DEV_AD(base) & CH58X_USB_ADDR_MASK;
+
+ // Stop USB transaction immediately (SDK: R8_UH_EP_PID = 0x00)
+ CH58X_UH_EP_PID(base) = 0x00;
+
+ // Disable transfer interrupt (re-enabled when next transfer starts)
+ CH58X_USB_INT_EN(base) &= ~CH58X_UIE_TRANSFER;
+
+ uint8_t request_pid = pid_endp >> 4;
+ uint8_t response_pid = int_st & CH58X_UIS_H_RES_MASK;
+ uint8_t ep_addr = pid_endp & 0x0F;
+ if (request_pid == CH58X_USB_PID_IN) {
+ ep_addr |= 0x80;
+ }
+
+ LOG_CH58X_HCD("hcd_int: xfer pid=0x%02x ep=0x%02x resp=0x%02x\r\n",
+ request_pid, ep_addr, response_pid);
+
+ hcd_edpt_t* edpt = _get_edpt(dev_addr, ep_addr);
+ if (edpt == NULL) {
+ // Unknown endpoint, discard
+ LOG_CH58X_HCD("hcd_int: unknown edpt dev=0x%02x ep=0x%02x\r\n", dev_addr, ep_addr);
+ _current_xfer.is_busy = false;
+ CH58X_USB_INT_FG(base) = CH58X_UIF_TRANSFER;
+ return;
+ }
+
+ // Check toggle match - SDK uses R8_USB_INT_ST & RB_UIS_TOG_OK
+ if (int_st & CH58X_UIS_TOG_OK) {
+ edpt->data_toggle ^= 0x01;
+
+ switch (request_pid) {
+ case CH58X_USB_PID_SETUP:
+ case CH58X_USB_PID_OUT: {
+ uint8_t tx_len = CH58X_UH_TX_LEN(base);
+ _current_xfer.bufferlen -= tx_len;
+ _current_xfer.xferred_len += tx_len;
+
+ if (_current_xfer.bufferlen == 0) {
+ LOG_CH58X_HCD("OUT/SETUP complete, %d bytes\r\n", _current_xfer.xferred_len);
+ _current_xfer.is_busy = false;
+ hcd_event_xfer_complete(dev_addr, ep_addr,
+ _current_xfer.xferred_len, XFER_RESULT_SUCCESS, in_isr);
+ } else {
+ // Multi-packet OUT: send next chunk
+ _current_xfer.buffer += tx_len;
+ uint16_t copylen = TU_MIN(edpt->max_packet_size, _current_xfer.bufferlen);
+ memcpy(_tx_buf[rhport], _current_xfer.buffer, copylen);
+ CH58X_UH_TX_LEN(base) = (uint8_t)copylen;
+ _hw_start_xfer(rhport, CH58X_USB_PID_OUT, ep_addr, edpt->data_toggle);
+ }
+ break;
+ }
+
+ case CH58X_USB_PID_IN: {
+ uint8_t rx_len = CH58X_USB_RX_LEN(base);
+ _current_xfer.xferred_len += rx_len;
+ uint16_t xferred = _current_xfer.xferred_len;
+
+ if (rx_len > 0 && _current_xfer.buffer != NULL) {
+ memcpy(_current_xfer.buffer, _rx_buf[rhport], rx_len);
+ _current_xfer.buffer += rx_len;
+ }
+
+ if ((rx_len < edpt->max_packet_size) || (xferred == _current_xfer.bufferlen)) {
+ // Short packet or transfer complete
+ LOG_CH58X_HCD("IN complete, %d bytes\r\n", xferred);
+ _current_xfer.is_busy = false;
+ hcd_event_xfer_complete(dev_addr, ep_addr, xferred,
+ XFER_RESULT_SUCCESS, in_isr);
+ } else {
+ // More data expected
+ _hw_start_xfer(rhport, CH58X_USB_PID_IN, ep_addr, edpt->data_toggle);
+ }
+ break;
+ }
+
+ default: {
+ LOG_CH58X_HCD("unexpected PID 0x%02x\r\n", request_pid);
+ _current_xfer.is_busy = false;
+ hcd_event_xfer_complete(dev_addr, ep_addr, 0, XFER_RESULT_FAILED, in_isr);
+ break;
+ }
+ }
+ } else {
+ // Toggle mismatch, check response PID
+ if (response_pid == CH58X_USB_PID_STALL) {
+ LOG_CH58X_HCD("STALL response\r\n");
+ edpt->data_toggle = 0;
+ _current_xfer.is_busy = false;
+ hcd_event_xfer_complete(dev_addr, ep_addr, 0, XFER_RESULT_STALLED, in_isr);
+ } else if (response_pid == CH58X_USB_PID_NAK) {
+ LOG_CH58X_HCD("NAK response\r\n");
+ // NAK: schedule retry via deferred callback for all endpoint types
+ // This avoids tight polling loops and allows other tasks to run
+ _current_xfer.is_busy = false;
+ _current_xfer.nak_pending = true;
+
+ edpt->is_nak_pending = true;
+ edpt->buflen = _current_xfer.bufferlen;
+ edpt->buf = _current_xfer.buffer;
+
+ hcd_event_t event = {
+ .rhport = rhport,
+ .dev_addr = dev_addr,
+ .event_id = USBH_EVENT_FUNC_CALL,
+ .func_call = {
+ .func = _xfer_retry,
+ .param = edpt
+ }
+ };
+ hcd_event_handler(&event, in_isr);
+ } else if (response_pid == CH58X_USB_PID_DATA0 || response_pid == CH58X_USB_PID_DATA1) {
+ LOG_CH58X_HCD("toggle mismatch, DATA0/1 rx_len=%d\r\n", CH58X_USB_RX_LEN(base));
+ _current_xfer.is_busy = false;
+ hcd_event_xfer_complete(dev_addr, ep_addr, 0, XFER_RESULT_FAILED, in_isr);
+ } else {
+ LOG_CH58X_HCD("unexpected response 0x%02x\r\n", response_pid);
+ _current_xfer.is_busy = false;
+ hcd_event_xfer_complete(dev_addr, ep_addr, 0, XFER_RESULT_FAILED, in_isr);
+ }
+ }
+
+ // Clear transfer flag
+ CH58X_USB_INT_FG(base) = CH58X_UIF_TRANSFER;
+ }
+}
+
+#endif /* CFG_TUH_ENABLED && TUP_USBIP_WCH_CH58X */
diff --git a/src/tusb_option.h b/src/tusb_option.h
index 9eb8ee533..50998613a 100644
--- a/src/tusb_option.h
+++ b/src/tusb_option.h
@@ -193,6 +193,7 @@
#define OPT_MCU_CH32F20X 2210 ///< WCH CH32F20x
#define OPT_MCU_CH32V20X 2220 ///< WCH CH32V20X
#define OPT_MCU_CH32V103 2230 ///< WCH CH32V103
+#define OPT_MCU_CH58X 2240 ///< WCH CH582/CH583
// NXP LPC MCX
#define OPT_MCU_MCXN9 2300 ///< NXP MCX N9 Series