diff options
| author | alt-0191 <[email protected]> | 2026-02-26 20:46:39 +0800 |
|---|---|---|
| committer | alt-0191 <[email protected]> | 2026-03-18 15:23:28 +0800 |
| commit | 6b0d20d70dbe0462fc89a92ab0c0ca04a9c625d0 (patch) | |
| tree | c3106c92bfa1be6f199c59f4bc2470f41b84dfee /src | |
| parent | 8f7f286ae347ec1adbfd0888d65fb1aeb263b7d2 (diff) | |
add WCH CH58x (CH582/CH583) BSP and USB FS device/host driver
Diffstat (limited to 'src')
| -rw-r--r-- | src/common/tusb_mcu.h | 14 | ||||
| -rw-r--r-- | src/portable/wch/ch58x_usbfs_reg.h | 299 | ||||
| -rw-r--r-- | src/portable/wch/dcd_ch58x_usbfs.c | 583 | ||||
| -rw-r--r-- | src/portable/wch/hcd_ch58x_usbfs.c | 712 | ||||
| -rw-r--r-- | src/tusb_option.h | 1 |
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 |
