summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorHa Thach <[email protected]>2026-06-22 22:20:29 +0700
committerGitHub <[email protected]>2026-06-22 22:20:29 +0700
commitcd3561bf158afd5a5718904b8139a338d1e3b67c (patch)
tree4b9d46e8b6397ba050d7fba159a67efc0d511eca /src
parent299c0a55629691f6bbded895a4be377633e815ab (diff)
parent706e4a5daaf8bbb55f0a7d00b69d4bc3c4cd70eb (diff)
Merge pull request #3515 from alt-0191/ch58x
Add support for WCH ch582/583 series
Diffstat (limited to 'src')
-rw-r--r--src/common/tusb_mcu.h13
-rw-r--r--src/portable/wch/ch32_usbfs_reg.h71
-rw-r--r--src/portable/wch/dcd_ch32_usbfs.c221
-rw-r--r--src/tusb_option.h2
4 files changed, 276 insertions, 31 deletions
diff --git a/src/common/tusb_mcu.h b/src/common/tusb_mcu.h
index cc9837dcd..b5390a59d 100644
--- a/src/common/tusb_mcu.h
+++ b/src/common/tusb_mcu.h
@@ -659,6 +659,19 @@
#define TUP_DCD_EDPT_CLOSE_API
#endif
+#elif TU_CHECK_MCU(OPT_MCU_CH583)
+ // CH582/583 USBFS: older WCH USBFS IP with a single combined per-endpoint control register
+ // (like CH32V103), driven by the shared dcd_ch32_usbfs.c on USB0 (rhport 0). Device only:
+ // the shared hcd_ch32_usbfs.c is CH32V20x-specific and does not support CH58x, so host /
+ // USB2 (rhport 1) is not provided here.
+ #define TUP_USBIP_WCH_USBFS
+
+ #ifndef CFG_TUD_WCH_USBIP_USBFS
+ #define CFG_TUD_WCH_USBIP_USBFS 1
+ #endif
+
+ #define TUP_DCD_ENDPOINT_MAX 8
+
//--------------------------------------------------------------------+
// Analog Devices
//--------------------------------------------------------------------+
diff --git a/src/portable/wch/ch32_usbfs_reg.h b/src/portable/wch/ch32_usbfs_reg.h
index 7ffdc6cef..415a015dc 100644
--- a/src/portable/wch/ch32_usbfs_reg.h
+++ b/src/portable/wch/ch32_usbfs_reg.h
@@ -130,6 +130,72 @@
#elif CFG_TUSB_MCU == OPT_MCU_CH32V307
#include <ch32v30x.h>
#define USBHD_IRQn OTG_FS_IRQn
+#elif CFG_TUSB_MCU == OPT_MCU_CH583
+ #include "CH58x_common.h"
+ // CH582/583 USBFS device controller: same combined per-endpoint control register as
+ // CH32V103 (IN response bits[1:0], OUT response bits[3:2]) but a different register map -
+ // the EP control/length block sits lower (EP0_CTRL @ +0x22), EP5-7 are split out, EP4
+ // shares EP0's DMA buffer, and EP5/6/7 mode bits live in one UEP567_MOD. The control/status
+ // block matches CH32. Two FS controllers exist (USB @ 0x40008000, USB2 @ 0x40008400); the
+ // device uses USB0. Full register map per CH583/582 datasheet Table 17-2; the parameterized
+ // EP_* macros below index off these named fields.
+ #define CH58X_USBFS_BASE 0x40008000u
+ // Per-endpoint register slots, 4-byte stride each; the EP_* macros index arrays of these.
+ typedef struct {
+ __IO uint16_t DMA; // R16_UEPn_DMA: endpoint n buffer start address
+ __IO uint16_t reserved;
+ } ch58x_ep_dma_t;
+ typedef struct {
+ __IO uint8_t T_LEN; // R8_UEPn_T_LEN (+0): transmit length
+ __IO uint8_t reserved0;
+ __IO uint8_t CTRL; // R8_UEPn_CTRL (+2): endpoint control
+ __IO uint8_t reserved1;
+ } ch58x_ep_ctrl_t;
+ typedef struct {
+ __IO uint8_t BASE_CTRL; // 0x00 R8_USB_CTRL
+ __IO uint8_t UDEV_CTRL; // 0x01 R8_UDEV_CTRL
+ __IO uint8_t INT_EN; // 0x02 R8_USB_INT_EN
+ __IO uint8_t DEV_ADDR; // 0x03 R8_USB_DEV_AD
+ __IO uint8_t Reserve0; // 0x04
+ __IO uint8_t MIS_ST; // 0x05 R8_USB_MIS_ST
+ __IO uint8_t INT_FG; // 0x06 R8_USB_INT_FG
+ __IO uint8_t INT_ST; // 0x07 R8_USB_INT_ST
+ __IO uint8_t RX_LEN; // 0x08 R8_USB_RX_LEN (8-bit on CH58X)
+ __IO uint8_t Reserve1[3]; // 0x09..0x0B
+ __IO uint8_t UEP4_1_MOD; // 0x0C R8_UEP4_1_MOD
+ __IO uint8_t UEP2_3_MOD; // 0x0D R8_UEP2_3_MOD
+ __IO uint8_t UEP567_MOD; // 0x0E R8_UEP567_MOD
+ __IO uint8_t Reserve2; // 0x0F
+ ch58x_ep_dma_t EP_DMA_0_3[4]; // 0x10 EP0-3 DMA (EP4 has no DMA reg; it shares EP0's, index 0)
+ ch58x_ep_ctrl_t EP_CTRL_0_4[5]; // 0x20 EP0-4 length/control
+ __IO uint8_t Reserve3[0x54u - 0x34u]; // 0x34..0x53
+ ch58x_ep_dma_t EP_DMA_5_7[3]; // 0x54 EP5-7 DMA
+ __IO uint8_t Reserve4[0x64u - 0x60u]; // 0x60..0x63
+ ch58x_ep_ctrl_t EP_CTRL_5_7[3]; // 0x64 EP5-7 length/control
+ } USBOTG_FS_TypeDef;
+ #define USBOTG_FS ((USBOTG_FS_TypeDef *) CH58X_USBFS_BASE)
+
+ // 4-byte slot stride + these block offsets pin every EP register to its datasheet address.
+ TU_VERIFY_STATIC(sizeof(ch58x_ep_dma_t) == 4, "CH58x EP DMA slot must be 4 bytes");
+ TU_VERIFY_STATIC(sizeof(ch58x_ep_ctrl_t) == 4, "CH58x EP ctrl slot must be 4 bytes");
+ TU_VERIFY_STATIC(offsetof(USBOTG_FS_TypeDef, EP_DMA_0_3) == 0x10, "CH58x EP_DMA_0_3 @0x10");
+ TU_VERIFY_STATIC(offsetof(USBOTG_FS_TypeDef, EP_CTRL_0_4) == 0x20, "CH58x EP_CTRL_0_4 @0x20");
+ TU_VERIFY_STATIC(offsetof(USBOTG_FS_TypeDef, EP_DMA_5_7) == 0x54, "CH58x EP_DMA_5_7 @0x54");
+ TU_VERIFY_STATIC(offsetof(USBOTG_FS_TypeDef, EP_CTRL_5_7) == 0x64, "CH58x EP_CTRL_5_7 @0x64");
+
+ #define CH32_USBFS_EP_CTRL_COMBINED 1
+ // CH58x's hardware AUTO_TOG does not stay in sync (notably across clear-stall and multi-packet
+ // bulk transfers), causing data-toggle mismatch and bus resets. Drive the toggle manually in
+ // the ISR instead. CH32V103/V20x/V307 keep AUTO_TOG (this macro is undefined for them).
+ #define CH32_USBFS_EP_MANUAL_TOG 1
+ // CH58x EP4 has no DMA register of its own: it overlays EP0's DMA region as
+ // EP0[0:63] + EP4_OUT[64:127] + EP4_IN[128:191], so EP0 needs a 192-byte buffer.
+ #define CH32_USBFS_EP4_SHARES_EP0 1
+ #define USBHD_IRQn USB_IRQn
+ #ifndef NVIC_EnableIRQ
+ #define NVIC_EnableIRQ(n) PFIC_EnableIRQ(n)
+ #define NVIC_DisableIRQ(n) PFIC_DisableIRQ(n)
+ #endif
#endif
#ifdef __GNUC__
@@ -167,9 +233,14 @@
#define USBFS_INT_FG_TOG_OK (1 << 6)
#define USBFS_INT_FG_IS_NAK (1 << 7)
+// MIS_ST: the SUSPEND interrupt fires on both suspend and resume; this bit (R8_USB_MIS_ST) is 1
+// while the bus is suspended and 0 once it has resumed, so it tells the two apart.
+#define USBFS_MIS_ST_SUSPEND (1 << 2)
+
// INT_ST
#define USBFS_INT_ST_MASK_UIS_ENDP(x) (((x) >> 0) & 0x0F)
#define USBFS_INT_ST_MASK_UIS_TOKEN(x) (((x) >> 4) & 0x03)
+#define USBFS_INT_ST_TOG_OK (1 << 6) // received packet's data toggle matched expectation
// UDEV_CTRL
#define USBFS_UDEV_CTRL_PORT_EN (1 << 0)
diff --git a/src/portable/wch/dcd_ch32_usbfs.c b/src/portable/wch/dcd_ch32_usbfs.c
index dae31da91..ece9cde07 100644
--- a/src/portable/wch/dcd_ch32_usbfs.c
+++ b/src/portable/wch/dcd_ch32_usbfs.c
@@ -35,17 +35,35 @@
/* 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])
+ // Struct-based EP register access (uniform layout). CH58X has a different register map and
+ // defines EP_DMA/EP_TX_LEN/EP_CTRL itself in ch32_usbfs_reg.h.
+ #if CFG_TUSB_MCU == OPT_MCU_CH583
+ // CH58X EP registers split into a low block (EP0-4) and a high block (EP5-7). Walk from each
+ // block's first slot by the 4-byte slot stride (pointer arithmetic off slot 0, so the unused
+ // ternary branch's index can't trip -Warray-bounds). EP4 has no DMA register of its own (it
+ // shares EP0's, slot 0) and is never written (see ep_shares_ep0_dma()).
+ #define EP_TX_LEN(ep) (*((ep) <= 4u ? &USBOTG_FS->EP_CTRL_0_4[0].T_LEN + (ep) * 4u \
+ : &USBOTG_FS->EP_CTRL_5_7[0].T_LEN + ((ep) - 5u) * 4u))
+ #define EP_CTRL(ep) (*((ep) <= 4u ? &USBOTG_FS->EP_CTRL_0_4[0].CTRL + (ep) * 4u \
+ : &USBOTG_FS->EP_CTRL_5_7[0].CTRL + ((ep) - 5u) * 4u))
+ #define EP_DMA(ep) (*((ep) <= 3u ? &USBOTG_FS->EP_DMA_0_3[0].DMA + (ep) * 2u \
+ : (ep) == 4u ? &USBOTG_FS->EP_DMA_0_3[0].DMA \
+ : &USBOTG_FS->EP_DMA_5_7[0].DMA + ((ep) - 5u) * 2u))
+ #else
+ #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])
+ #endif
// Endpoint control register access. The newer USBFS IP (CH32V20x/V307/X035) has separate
// TX_CTRL and RX_CTRL bytes per endpoint; the older IP (CH32V103) has a single combined
// UEPn_CTRL register. These helpers hide the difference so the rest of the driver is shared.
// Values use the newer-IP encoding (USBFS_EP_T_*/USBFS_EP_R_*); the combined path remaps them.
#ifdef CH32_USBFS_EP_CTRL_COMBINED
+ #ifndef EP_CTRL // parts with a custom register map (CH58X) define EP_CTRL directly in reg.h
#define EP_CTRL(ep) EP_TX_CTRL(ep) // UEPn_TX_CTRL field aliases the combined UEPn_CTRL register
+ #endif
static inline uint8_t ep_tx_to_comb(uint8_t v) {
uint8_t c = v & USBFS_EP_T_RES_MASK; // IN response: bits [1:0] in both encodings
@@ -86,6 +104,17 @@
#define EP0_SETUP_RX_TOG 0
#endif
+// Hardware auto data-toggle flag. Parts whose AUTO_TOG is reliable OR it into the EP setup so the
+// controller flips DATA0/DATA1 itself; CH58x (CH32_USBFS_EP_MANUAL_TOG) leaves it clear and the
+// ISR flips the toggle bit after each packet instead.
+#ifdef CH32_USBFS_EP_MANUAL_TOG
+ #define EP_T_AUTO_TOG 0
+ #define EP_R_AUTO_TOG 0
+#else
+ #define EP_T_AUTO_TOG USBFS_EP_T_AUTO_TOG
+ #define EP_R_AUTO_TOG USBFS_EP_R_AUTO_TOG
+#endif
+
/* private data */
struct usb_xfer {
bool valid;
@@ -99,28 +128,105 @@ static struct {
bool ep0_tog;
bool isochronous[EP_MAX];
struct usb_xfer xfer[EP_MAX][2];
+#ifdef CH32_USBFS_EP4_SHARES_EP0
+ // CH58X buffers laid out by hand so EP0/EP4 don't burn two unused buffer[] slots. EP0 and EP4
+ // share one contiguous 192-byte DMA region (EP4 has no DMA register of its own):
+ // EP0 [0:63] (half-duplex OUT+IN) + EP4 OUT [64:127] + EP4 IN [128:191]. Every other endpoint
+ // (incl. EP3, which is bulk-only here — CH58X has no isochronous support) gets a plain 128-byte
+ // OUT+IN buffer, so no oversized EP3 buffer is needed.
+ TU_ATTR_ALIGNED(4) uint8_t ep0_ep4_buffer[3 * 64];
+ TU_ATTR_ALIGNED(4) uint8_t ep1_buffer[2][64];
+ TU_ATTR_ALIGNED(4) uint8_t ep2_buffer[2][64];
+ TU_ATTR_ALIGNED(4) uint8_t ep3_buffer[2][64];
+ TU_ATTR_ALIGNED(4) uint8_t ep5_buffer[2][64];
+ TU_ATTR_ALIGNED(4) uint8_t ep6_buffer[2][64];
+ TU_ATTR_ALIGNED(4) uint8_t ep7_buffer[2][64];
+#else
TU_ATTR_ALIGNED(4) uint8_t buffer[EP_MAX][2][64];
+ // EP3 IN gets an enlarged buffer for full-speed isochronous (packets up to 1023 B).
TU_ATTR_ALIGNED(4) struct {
// OUT transfers >64 bytes will overwrite queued IN data!
uint8_t out[64];
uint8_t in[1023];
uint8_t pad;
} ep3_buffer;
+#endif
} data;
+// DMA / copy buffer pointers per endpoint. The WCH USBFS buffer holds OUT (RX) at offset 0 and
+// IN (TX) at +64; EP0 is half-duplex and reuses its OUT chunk for IN; EP3 has an enlarged IN
+// buffer for throughput. On CH58X, EP0/EP4 share ep0_ep4_buffer and the regular endpoints use
+// their own named buffer (see the struct above).
+#ifdef CH32_USBFS_EP4_SHARES_EP0
+// OUT base of the regular CH58X endpoints (EP1/2/3/5/6/7; EP0/EP4 share ep0_ep4_buffer).
+static inline uint8_t* ch58x_ep_buffer(uint8_t ep) {
+ switch (ep) {
+ case 1: return data.ep1_buffer[0];
+ case 2: return data.ep2_buffer[0];
+ case 3: return data.ep3_buffer[0];
+ case 5: return data.ep5_buffer[0];
+ case 6: return data.ep6_buffer[0];
+ default: return data.ep7_buffer[0]; // ep == 7
+ }
+}
+#endif
+
+static inline uint32_t ep_dma_addr(uint8_t ep) {
+#ifdef CH32_USBFS_EP4_SHARES_EP0
+ if (ep == 0 || ep == 4) { return (uint32_t) &data.ep0_ep4_buffer[0]; } // EP4 shares EP0's DMA
+ return (uint32_t) ch58x_ep_buffer(ep);
+#else
+ if (ep == 3) { return (uint32_t) &data.ep3_buffer.out[0]; }
+ return (uint32_t) &data.buffer[ep][0];
+#endif
+}
+
+static inline uint8_t* ep_out_buf(uint8_t ep) {
+#ifdef CH32_USBFS_EP4_SHARES_EP0
+ if (ep == 0) { return &data.ep0_ep4_buffer[0]; }
+ if (ep == 4) { return &data.ep0_ep4_buffer[64]; }
+ return ch58x_ep_buffer(ep);
+#else
+ if (ep == 3) { return data.ep3_buffer.out; }
+ return data.buffer[ep][TUSB_DIR_OUT];
+#endif
+}
+
+static inline uint8_t* ep_in_buf(uint8_t ep) {
+#ifdef CH32_USBFS_EP4_SHARES_EP0
+ if (ep == 0) { return &data.ep0_ep4_buffer[0]; } // EP0 half-duplex: IN reuses OUT chunk
+ if (ep == 4) { return &data.ep0_ep4_buffer[128]; }
+ return ch58x_ep_buffer(ep) + 64; // IN at +64 within the endpoint's 128-byte buffer
+#else
+ if (ep == 0) { return data.buffer[0][TUSB_DIR_OUT]; } // EP0 half-duplex: IN reuses OUT chunk
+ if (ep == 3) { return data.ep3_buffer.in; }
+ return data.buffer[ep][TUSB_DIR_IN];
+#endif
+}
+
+// EP4 on CH58X has no DMA register (shares EP0's); skip its EP_DMA() write.
+static inline bool ep_shares_ep0_dma(uint8_t ep) {
+#ifdef CH32_USBFS_EP4_SHARES_EP0
+ return ep == 4;
+#else
+ (void) ep;
+ return false;
+#endif
+}
+
/* private helpers */
static void update_in(uint8_t rhport, uint8_t ep, bool force) {
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);
- if (ep == 0) {
- memcpy(data.buffer[ep][TUSB_DIR_OUT], xfer->buffer, len); // ep0 uses same chunk
- } else if (ep == 3) {
- memcpy(data.ep3_buffer.in, xfer->buffer, len);
- } else {
- memcpy(data.buffer[ep][TUSB_DIR_IN], xfer->buffer, len);
- }
+#if CFG_TUSB_MCU == OPT_MCU_CH583
+ // Every CH58x endpoint buffer is 64 bytes. Isochronous (which would push max_size up to 1023)
+ // is refused in dcd_edpt_iso_alloc(), but some classes (e.g. video) ignore that result, so cap
+ // the copy here to guarantee we never write past the buffer into a neighbouring endpoint's.
+ len = TU_MIN(len, 64u);
+#endif
+ memcpy(ep_in_buf(ep), xfer->buffer, len);
xfer->buffer += len;
xfer->len -= len;
xfer->processed_len += len;
@@ -150,11 +256,10 @@ static void update_out(uint8_t rhport, uint8_t ep, size_t rx_len) {
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) {
- memcpy(xfer->buffer, data.ep3_buffer.out, len);
- } else {
- memcpy(xfer->buffer, data.buffer[ep][TUSB_DIR_OUT], len);
- }
+#if CFG_TUSB_MCU == OPT_MCU_CH583
+ len = TU_MIN(len, 64u); // cap to the 64-byte EP buffer (see update_in)
+#endif
+ memcpy(xfer->buffer, ep_out_buf(ep), len);
xfer->buffer += len;
xfer->len -= len;
xfer->processed_len += len;
@@ -176,12 +281,11 @@ static void update_out(uint8_t rhport, uint8_t ep, size_t rx_len) {
static void reset_ep_ctrls(void) {
for (uint8_t ep = 1; ep < EP_MAX; ep++) {
- EP_DMA(ep) = (uint32_t)&data.buffer[ep][0];
+ if (!ep_shares_ep0_dma(ep)) { EP_DMA(ep) = ep_dma_addr(ep); }
EP_TX_LEN(ep) = 0;
- ep_tx_ctrl_set(ep, USBFS_EP_T_AUTO_TOG | USBFS_EP_T_RES_NYET);
- ep_rx_ctrl_set(ep, USBFS_EP_R_AUTO_TOG | USBFS_EP_R_RES_NYET);
+ ep_tx_ctrl_set(ep, EP_T_AUTO_TOG | USBFS_EP_T_RES_NYET);
+ ep_rx_ctrl_set(ep, EP_R_AUTO_TOG | USBFS_EP_R_RES_NYET);
}
- EP_DMA(3) = (uint32_t)&data.ep3_buffer.out[0];
}
/* public functions */
@@ -195,8 +299,8 @@ bool dcd_init(uint8_t rhport, const tusb_rhport_init_t *rh_init) {
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];
+ // setup endpoint 0 (also backs EP4's buffer on CH58X via the shared DMA region)
+ EP_DMA(0) = ep_dma_addr(0);
EP_TX_LEN(0) = 0;
ep_tx_ctrl_set(0, USBFS_EP_T_RES_NAK);
ep_rx_ctrl_set(0, USBFS_EP_R_RES_ACK);
@@ -204,8 +308,14 @@ bool dcd_init(uint8_t rhport, const tusb_rhport_init_t *rh_init) {
// enable other endpoints but NAK everything
USBOTG_FS->UEP4_1_MOD = 0xCC;
USBOTG_FS->UEP2_3_MOD = 0xCC;
+#if CFG_TUSB_MCU == OPT_MCU_CH583
+ // CH58X: a single mode register enables EP5/6/7 RX+TX (different bit layout than CH32).
+ USBOTG_FS->UEP567_MOD = RB_UEP5_RX_EN | RB_UEP5_TX_EN | RB_UEP6_RX_EN | RB_UEP6_TX_EN |
+ RB_UEP7_RX_EN | RB_UEP7_TX_EN;
+#else
USBOTG_FS->UEP5_6_MOD = 0xCC;
USBOTG_FS->UEP7_MOD = 0x0C;
+#endif
reset_ep_ctrls();
@@ -218,17 +328,32 @@ void dcd_int_handler(uint8_t 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 int_st = USBOTG_FS->INT_ST;
+ uint8_t ep = USBFS_INT_ST_MASK_UIS_ENDP(int_st);
+ uint8_t token = USBFS_INT_ST_MASK_UIS_TOKEN(int_st);
uint16_t rx_len = USBOTG_FS->RX_LEN;
switch (token) {
case PID_OUT: {
+ // Drop an OUT packet whose data toggle doesn't match what we expect -- a host retransmit
+ // after a lost ACK, or a host that doesn't alternate DATA0/DATA1. The hardware auto-toggle
+ // does not reject these on its own, so the check is needed on every variant. EP0 keeps its
+ // own toggle via the SETUP/status flow and is exempt.
+ if (ep != 0 && !(int_st & USBFS_INT_ST_TOG_OK)) { break; }
+#ifdef CH32_USBFS_EP_MANUAL_TOG
+ // CH58x has no hardware auto-toggle: advance the expected RX toggle after each accepted packet
+ // (EP0 included -- it also has no auto-toggle and a control-OUT data stage can span packets).
+ EP_CTRL(ep) ^= USBFS_EPC_R_TOG;
+#endif
update_out(rhport, ep, rx_len);
break;
}
case PID_IN:
+#ifdef CH32_USBFS_EP_MANUAL_TOG
+ // Manual toggle: flip the TX toggle after each ACK'd IN packet (EP0 manages its own).
+ if (ep != 0) { EP_CTRL(ep) ^= USBFS_EPC_T_TOG; }
+#endif
update_in(rhport, ep, false);
break;
@@ -236,12 +361,17 @@ void dcd_int_handler(uint8_t rhport) {
// setup clears stall
ep_tx_ctrl_set(0, USBFS_EP_T_RES_NAK);
data.ep0_tog = true;
+ // A new SETUP supersedes any control transfer still in flight; drop its stale EP0 state so a
+ // spurious EP0 IN/OUT can't run update_in()/update_out() against the previous request.
+ data.xfer[0][TUSB_DIR_OUT].valid = false;
+ data.xfer[0][TUSB_DIR_IN].valid = false;
- const tusb_control_request_t *setup = (const tusb_control_request_t *)&data.buffer[0][TUSB_DIR_OUT][0];
+ uint8_t *ep0_out = ep_out_buf(0);
+ const tusb_control_request_t *setup = (const tusb_control_request_t *)ep0_out;
// EP0_SETUP_RX_TOG arms the data/status stage at DATA1 on the combined-control IP
ep_rx_ctrl_set(0, ((setup->wLength == 0) ? USBFS_EP_R_RES_ACK : USBFS_EP_R_RES_NAK) | EP0_SETUP_RX_TOG);
- dcd_event_setup_received(rhport, &data.buffer[0][TUSB_DIR_OUT][0], true);
+ dcd_event_setup_received(rhport, ep0_out, true);
break;
}
@@ -263,7 +393,14 @@ void dcd_int_handler(uint8_t rhport) {
USBOTG_FS->INT_FG = USBFS_INT_FG_BUS_RST;
} else if (status & USBFS_INT_FG_SUSPEND) {
+#if CFG_TUSB_MCU == OPT_MCU_CH583
+ // CH58x raises this single interrupt for both suspend and resume; MIS_ST's suspend bit tells
+ // them apart (set while suspended, clear once resumed) so tud_resume_cb() actually fires.
+ dcd_event_t event = {.rhport = rhport,
+ .event_id = (USBOTG_FS->MIS_ST & USBFS_MIS_ST_SUSPEND) ? DCD_EVENT_SUSPEND : DCD_EVENT_RESUME};
+#else
dcd_event_t event = {.rhport = rhport, .event_id = DCD_EVENT_SUSPEND};
+#endif
dcd_event_handler(&event, true);
USBOTG_FS->INT_FG = USBFS_INT_FG_SUSPEND;
}
@@ -310,7 +447,12 @@ void dcd_edpt0_status_complete(uint8_t rhport, const tusb_control_request_t *req
(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) {
+#if CFG_TUSB_MCU == OPT_MCU_CH583
+ // On CH58x R8_USB_DEV_AD bit 7 is a user general-purpose flag; only bits [6:0] are the address.
+ USBOTG_FS->DEV_ADDR = (uint8_t)((USBOTG_FS->DEV_ADDR & 0x80u) | (request->wValue & 0x7Fu));
+#else
USBOTG_FS->DEV_ADDR = (uint8_t)request->wValue;
+#endif
}
}
@@ -323,10 +465,12 @@ bool dcd_edpt_open(uint8_t rhport, const tusb_desc_endpoint_t *desc_ep) {
data.xfer[ep][dir].max_size = tu_edpt_packet_size(desc_ep);
if (ep != 0) {
+ // Opening clears the toggle to DATA0 (ep_*_ctrl_set writes the toggle bit clear since v has no
+ // R/T_TOG); with manual toggle EP_*_AUTO_TOG is 0 so the ISR owns subsequent toggling.
if (dir == TUSB_DIR_OUT) {
- ep_rx_ctrl_set(ep, USBFS_EP_R_AUTO_TOG | USBFS_EP_R_RES_NAK);
+ ep_rx_ctrl_set(ep, EP_R_AUTO_TOG | USBFS_EP_R_RES_NAK);
} else {
- ep_tx_ctrl_set(ep, USBFS_EP_T_AUTO_TOG | USBFS_EP_T_RES_NAK);
+ ep_tx_ctrl_set(ep, EP_T_AUTO_TOG | USBFS_EP_T_RES_NAK);
}
}
return true;
@@ -341,18 +485,29 @@ bool dcd_edpt_iso_alloc(uint8_t rhport, uint8_t ep_addr, uint16_t largest_packet
(void)rhport;
(void)ep_addr;
(void)largest_packet_size;
+#if CFG_TUSB_MCU == OPT_MCU_CH583
+ // No isochronous support on CH58x: its 8-bit T_LEN caps a packet at 255B and the endpoints use
+ // plain 64-byte buffers, so accepting an iso max_size (up to 1023) would let update_in()/
+ // update_out() run off the end of the buffer into neighbouring ones. Refuse it outright.
+ return false;
+#else
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;
+#endif
}
bool dcd_edpt_iso_activate(uint8_t rhport, const tusb_desc_endpoint_t *desc_ep) {
(void)rhport;
(void)desc_ep;
+#if CFG_TUSB_MCU == OPT_MCU_CH583
+ return false; // CH58x has no isochronous support (see dcd_edpt_iso_alloc)
+#else
return true;
+#endif
}
bool dcd_edpt_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t *buffer, uint16_t total_bytes, bool is_isr) {
@@ -362,12 +517,14 @@ bool dcd_edpt_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t *buffer, uint16_t to
uint8_t dir = tu_edpt_dir(ep_addr);
struct usb_xfer *xfer = &data.xfer[ep][dir];
+ // Keep the IRQ masked across the whole arming sequence: update_in()/ep_rx_set_response() do a
+ // read-modify-write of the (combined) EP control register, which the ISR also RMWs to flip the
+ // manual data toggle; re-enabling before they run lets a transfer IRQ clobber that toggle.
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);
@@ -375,6 +532,7 @@ bool dcd_edpt_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t *buffer, uint16_t to
uint8_t rx_res = data.isochronous[ep] ? USBFS_EP_R_RES_NYET : USBFS_EP_R_RES_ACK;
ep_rx_set_response(ep, rx_res);
}
+ dcd_int_enable(rhport);
return true;
}
@@ -407,10 +565,11 @@ void dcd_edpt_clear_stall(uint8_t rhport, uint8_t ep_addr) {
ep_rx_ctrl_set(0, USBFS_EP_R_RES_ACK);
}
} else {
+ // clear-stall resets the toggle to DATA0 (USB spec); manual-toggle parts then re-sync via ISR
if (dir == TUSB_DIR_OUT) {
- ep_rx_ctrl_set(ep, USBFS_EP_R_AUTO_TOG | USBFS_EP_R_RES_NAK);
+ ep_rx_ctrl_set(ep, EP_R_AUTO_TOG | USBFS_EP_R_RES_NAK);
} else {
- ep_tx_ctrl_set(ep, USBFS_EP_T_AUTO_TOG | USBFS_EP_T_RES_NAK);
+ ep_tx_ctrl_set(ep, EP_T_AUTO_TOG | USBFS_EP_T_RES_NAK);
}
}
}
diff --git a/src/tusb_option.h b/src/tusb_option.h
index 415e083c9..b5457fe8a 100644
--- a/src/tusb_option.h
+++ b/src/tusb_option.h
@@ -195,6 +195,8 @@
#define OPT_MCU_CH32F20X 2210 ///< WCH CH32F20x
#define OPT_MCU_CH32V20X 2220 ///< WCH CH32V20X
#define OPT_MCU_CH32V103 2230 ///< WCH CH32V103
+#define OPT_MCU_CH583 2240 ///< WCH CH583
+#define OPT_MCU_CH582 OPT_MCU_CH583 ///< WCH CH582 (alias, same USB IP as CH583)
// NXP LPC MCX
#define OPT_MCU_MCXN9 2300 ///< NXP MCX N9 Series