diff options
| author | hathach <[email protected]> | 2026-06-18 17:59:30 +0700 |
|---|---|---|
| committer | hathach <[email protected]> | 2026-06-18 17:59:30 +0700 |
| commit | ea5b8d677f22f4bd1574f864fd0487dfd329c090 (patch) | |
| tree | c73641e9dee6992bdff0f12edf471427fed840a2 | |
| parent | 953abfb3931c67b834838c7fe0b31834cb0d42d6 (diff) | |
dcd/ch58x: drive CH582/583 with shared dcd_ch32_usbfs.c
Replace PR #3515's separate dcd_ch58x_usbfs.c / hcd_ch58x_usbfs.c with the
shared WCH USBFS device driver (combined per-endpoint control, like CH32V103),
adding two CH58x-specific behaviors guarded so CH32V103/V20x/V307 are unchanged:
- CH32_USBFS_EP_MANUAL_TOG: CH58x's hardware AUTO_TOG does not stay in sync, so
the ISR toggles DATA0/DATA1 manually and discards toggle-mismatched OUT
packets. Fixes multi-packet bulk-IN (e.g. MSC READ10) that otherwise hung.
- CH32_USBFS_EP4_SHARES_EP0: EP4 has no DMA register and overlays EP0's region
(EP0[0:63] + EP4 OUT[64:127] + EP4 IN[128:191]); add a 192-byte shared buffer
and buffer-pointer helpers (transparent for the other parts). Fixes
cdc_dual_ports (Port1 is on EP4).
Add the ch582m_evt board. Device only on USB0 (rhport 0): the shared
hcd_ch32_usbfs.c is CH32V20x-specific and cannot drive CH58x, so host / USB2
(rhport 1) is left commented out in the BSP for easy re-add.
Verified on ch582m_evt via local HIL: all device examples pass.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
| -rw-r--r-- | hw/bsp/ch58x/boards/ch582m_evt/board.cmake | 5 | ||||
| -rw-r--r-- | hw/bsp/ch58x/boards/ch582m_evt/board.h | 61 | ||||
| -rw-r--r-- | hw/bsp/ch58x/boards/ch582m_evt/board.mk | 3 | ||||
| -rw-r--r-- | hw/bsp/ch58x/boards/yd-ch582m/board.h | 10 | ||||
| -rw-r--r-- | hw/bsp/ch58x/ch58x_it.h | 2 | ||||
| -rw-r--r-- | hw/bsp/ch58x/family.c | 27 | ||||
| -rw-r--r-- | hw/bsp/ch58x/family.cmake | 3 | ||||
| -rw-r--r-- | hw/bsp/ch58x/family.mk | 3 | ||||
| -rw-r--r-- | src/common/tusb_mcu.h | 11 | ||||
| -rw-r--r-- | src/portable/wch/ch32_usbfs_reg.h | 48 | ||||
| -rw-r--r-- | src/portable/wch/ch58x_usbfs_reg.h | 291 | ||||
| -rw-r--r-- | src/portable/wch/dcd_ch32_usbfs.c | 125 | ||||
| -rw-r--r-- | src/portable/wch/dcd_ch58x_usbfs.c | 636 | ||||
| -rw-r--r-- | src/portable/wch/hcd_ch58x_usbfs.c | 721 |
14 files changed, 244 insertions, 1702 deletions
diff --git a/hw/bsp/ch58x/boards/ch582m_evt/board.cmake b/hw/bsp/ch58x/boards/ch582m_evt/board.cmake new file mode 100644 index 000000000..4129c4550 --- /dev/null +++ b/hw/bsp/ch58x/boards/ch582m_evt/board.cmake @@ -0,0 +1,5 @@ +set(LD_FLASH_SIZE 448K) +set(LD_RAM_SIZE 32K) + +function(update_board TARGET) +endfunction() diff --git a/hw/bsp/ch58x/boards/ch582m_evt/board.h b/hw/bsp/ch58x/boards/ch582m_evt/board.h new file mode 100644 index 000000000..c3483bf17 --- /dev/null +++ b/hw/bsp/ch58x/boards/ch582m_evt/board.h @@ -0,0 +1,61 @@ +/* + * 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. + */ + +/* metadata: + name: CH582M-EVT evaluation board + url: https://www.wch-ic.com/products/CH582.html +*/ + +#ifndef BOARD_H_ +#define BOARD_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +// LED: PB4 on CH582M-EVT +#define LED_PIN GPIO_Pin_4 +#define LED_STATE_ON 0 + +// Directly reuse BOOT pin as user button +#define BUTTON_PIN GPIO_Pin_22 +#define BUTTON_STATE_ACTIVE 0 + +// UART: UART1 TX=PA9, RX=PA8 +#define CFG_BOARD_UART_BAUDRATE 115200 + +// Device only on USB1 (rhport 0). CH58x host / USB2 is not supported by the shared usbfs dcd; +// BOARD_TUH_RHPORT is kept commented out to ease re-adding host later. +#ifndef BOARD_TUD_RHPORT +#define BOARD_TUD_RHPORT 0 +#endif +// #ifndef BOARD_TUH_RHPORT +// #define BOARD_TUH_RHPORT 1 +// #endif + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/hw/bsp/ch58x/boards/ch582m_evt/board.mk b/hw/bsp/ch58x/boards/ch582m_evt/board.mk new file mode 100644 index 000000000..a13979799 --- /dev/null +++ b/hw/bsp/ch58x/boards/ch582m_evt/board.mk @@ -0,0 +1,3 @@ +LDFLAGS += \ + -Wl,--defsym=__FLASH_SIZE=448K \ + -Wl,--defsym=__RAM_SIZE=32K \ diff --git a/hw/bsp/ch58x/boards/yd-ch582m/board.h b/hw/bsp/ch58x/boards/yd-ch582m/board.h index a14ffd198..0da5747bc 100644 --- a/hw/bsp/ch58x/boards/yd-ch582m/board.h +++ b/hw/bsp/ch58x/boards/yd-ch582m/board.h @@ -45,14 +45,14 @@ extern "C" { // UART: UART1 TX=PA9, RX=PA8 #define CFG_BOARD_UART_BAUDRATE 115200 -// Dual-port: USB1 (rhport 0) = Device, USB2 (rhport 1) = Host -// Swap these two if you want the opposite assignment +// Device only on USB1 (rhport 0). CH58x host / USB2 is not supported by the shared usbfs dcd; +// BOARD_TUH_RHPORT is kept commented out to ease re-adding host later. #ifndef BOARD_TUD_RHPORT #define BOARD_TUD_RHPORT 0 #endif -#ifndef BOARD_TUH_RHPORT -#define BOARD_TUH_RHPORT 1 -#endif +// #ifndef BOARD_TUH_RHPORT +// #define BOARD_TUH_RHPORT 1 +// #endif #ifdef __cplusplus } diff --git a/hw/bsp/ch58x/ch58x_it.h b/hw/bsp/ch58x/ch58x_it.h index 18ea52bc9..3e050344d 100644 --- a/hw/bsp/ch58x/ch58x_it.h +++ b/hw/bsp/ch58x/ch58x_it.h @@ -36,7 +36,7 @@ extern "C" { void NMI_Handler(void); void HardFault_Handler(void); void USB_IRQHandler(void); -void USB2_IRQHandler(void); +// void USB2_IRQHandler(void); // host on USB2 (rhport 1) — re-add together with the host driver void SysTick_Handler(void); #ifdef __cplusplus diff --git a/hw/bsp/ch58x/family.c b/hw/bsp/ch58x/family.c index 3b1382e61..8e5f2826b 100644 --- a/hw/bsp/ch58x/family.c +++ b/hw/bsp/ch58x/family.c @@ -50,13 +50,16 @@ // Forward USB interrupt events to TinyUSB IRQ Handler //--------------------------------------------------------------------+ +// Device only: the shared dcd_ch32_usbfs.c drives USB0 (rhport 0). CH58x's second controller +// (USB2 / rhport 1) was driven by the now-removed ch58x host driver; its handler is kept +// commented out below to ease re-adding host support. __INTERRUPT __HIGH_CODE void USB_IRQHandler(void) { tusb_int_handler(0, true); } -__INTERRUPT __HIGH_CODE void USB2_IRQHandler(void) { - tusb_int_handler(1, true); -} +// __INTERRUPT __HIGH_CODE void USB2_IRQHandler(void) { +// tusb_int_handler(1, true); +// } //--------------------------------------------------------------------+ // SysTick @@ -111,16 +114,12 @@ void board_init(void) { #endif #endif - // USB pin enable: enable analog function for USB1 and USB2 D+/D- - R16_PIN_ANALOG_IE |= RB_PIN_USB_IE | RB_PIN_USB2_IE; - - // D+ pull-up is only needed for the device-role port + // Device only on USB0 (rhport 0): enable analog function for USB1 D+/D- and assert its D+ + // pull-up. The shared dcd_ch32_usbfs.c drives USB0; CH58x host / USB2 (rhport 1) is unsupported + // here — to re-add host, also OR in RB_PIN_USB2_IE below. + R16_PIN_ANALOG_IE |= RB_PIN_USB_IE; // | RB_PIN_USB2_IE (re-add for host on USB2) #if CFG_TUD_ENABLED - #if BOARD_TUD_RHPORT == 0 - R16_PIN_ANALOG_IE |= RB_PIN_USB_DP_PU; - #else - R16_PIN_ANALOG_IE |= RB_PIN_USB2_DP_PU; - #endif + R16_PIN_ANALOG_IE |= RB_PIN_USB_DP_PU; #endif // Keep USB clock active during sleep @@ -164,6 +163,10 @@ uint32_t board_button_read(void) { #endif } +// Note: CH58x exposes no memory-mapped unique-ID register (unlike ch32v10x/v20x at +// 0x1FFFF7E8), and the SDK's GET_UNIQUE_ID() is a BootROM stub not present in +// libISP583.a. So board_get_unique_id() falls back to the fixed default in board.c. + int board_uart_read(uint8_t* buf, int len) { (void) buf; (void) len; diff --git a/hw/bsp/ch58x/family.cmake b/hw/bsp/ch58x/family.cmake index b4835cb41..cbe1ffe72 100644 --- a/hw/bsp/ch58x/family.cmake +++ b/hw/bsp/ch58x/family.cmake @@ -77,8 +77,7 @@ function(family_configure_example TARGET RTOS) ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/family.c ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/debug_uart.c ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/../board.c - ${TOP}/src/portable/wch/dcd_ch58x_usbfs.c - ${TOP}/src/portable/wch/hcd_ch58x_usbfs.c + ${TOP}/src/portable/wch/dcd_ch32_usbfs.c ${STARTUP_FILE_${CMAKE_C_COMPILER_ID}} ) target_include_directories(${TARGET} PUBLIC diff --git a/hw/bsp/ch58x/family.mk b/hw/bsp/ch58x/family.mk index 71c2cada6..2ef90a8c3 100644 --- a/hw/bsp/ch58x/family.mk +++ b/hw/bsp/ch58x/family.mk @@ -33,8 +33,7 @@ LDFLAGS_GCC += \ LIBS += $(TOP)/$(SDK_SRC_DIR)/StdPeriphDriver/libISP583.a SRC_C += \ - src/portable/wch/dcd_ch58x_usbfs.c \ - src/portable/wch/hcd_ch58x_usbfs.c \ + src/portable/wch/dcd_ch32_usbfs.c \ $(SDK_SRC_DIR)/StdPeriphDriver/CH58x_gpio.c \ $(SDK_SRC_DIR)/StdPeriphDriver/CH58x_clk.c \ $(SDK_SRC_DIR)/StdPeriphDriver/CH58x_uart1.c \ diff --git a/src/common/tusb_mcu.h b/src/common/tusb_mcu.h index 8408d3ae0..f2a2d92a5 100644 --- a/src/common/tusb_mcu.h +++ b/src/common/tusb_mcu.h @@ -660,17 +660,16 @@ #endif #elif TU_CHECK_MCU(OPT_MCU_CH58X) - // CH582/583 has 2 independent USBFS controllers with merged EP registers, FS only. - #define TUP_USBIP_WCH_CH58X + // 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 - #ifndef CFG_TUH_WCH_USBIP_USBFS - #define CFG_TUH_WCH_USBIP_USBFS 1 - #endif - #define TUP_DCD_ENDPOINT_MAX 8 //--------------------------------------------------------------------+ diff --git a/src/portable/wch/ch32_usbfs_reg.h b/src/portable/wch/ch32_usbfs_reg.h index 7ffdc6cef..0d61e183c 100644 --- a/src/portable/wch/ch32_usbfs_reg.h +++ b/src/portable/wch/ch32_usbfs_reg.h @@ -130,6 +130,53 @@ #elif CFG_TUSB_MCU == OPT_MCU_CH32V307 #include <ch32v30x.h> #define USBHD_IRQn OTG_FS_IRQn +#elif CFG_TUSB_MCU == OPT_MCU_CH58X + #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. EP registers are accessed via the CH58X macros below (not the struct). + #define CH58X_USBFS_BASE 0x40008000u + typedef struct { + __IO uint8_t BASE_CTRL; // 0x00 + __IO uint8_t UDEV_CTRL; // 0x01 + __IO uint8_t INT_EN; // 0x02 + __IO uint8_t DEV_ADDR; // 0x03 + __IO uint8_t Reserve0; // 0x04 + __IO uint8_t MIS_ST; // 0x05 + __IO uint8_t INT_FG; // 0x06 + __IO uint8_t INT_ST; // 0x07 + __IO uint8_t RX_LEN; // 0x08 (8-bit on CH58X) + __IO uint8_t Reserve1[3]; // 0x09..0x0B + __IO uint8_t UEP4_1_MOD; // 0x0C + __IO uint8_t UEP2_3_MOD; // 0x0D + __IO uint8_t UEP567_MOD; // 0x0E + } USBOTG_FS_TypeDef; + #define USBOTG_FS ((USBOTG_FS_TypeDef *) CH58X_USBFS_BASE) + + #define CH32_USBFS_EP_CTRL_COMBINED 1 + #define CH32_USBFS_EP_REGS_CUSTOM 1 // EP register macros provided here, not by the driver + // 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 + + // EP register access. EP0-4: T_LEN @ +0x20+ep*4, CTRL @ +0x22+ep*4. EP5-7 split: T_LEN @ + // +0x64, CTRL @ +0x66. DMA: EP0-3 @ +0x10+ep*4, EP5-7 @ +0x54; EP4 shares EP0's buffer + // (no own DMA reg) so its slot points at a reserved word. + #define EP_TX_LEN(ep) (*(volatile uint8_t *)(CH58X_USBFS_BASE + ((ep) <= 4u ? 0x20u + (ep)*4u : 0x64u + ((ep)-5u)*4u))) + #define EP_CTRL(ep) (*(volatile uint8_t *)(CH58X_USBFS_BASE + ((ep) <= 4u ? 0x22u + (ep)*4u : 0x66u + ((ep)-5u)*4u))) + #define EP_DMA(ep) (*(volatile uint16_t *)(CH58X_USBFS_BASE + ((ep) <= 3u ? 0x10u + (ep)*4u : (ep) == 4u ? 0x40u : 0x54u + ((ep)-5u)*4u))) #endif #ifdef __GNUC__ @@ -170,6 +217,7 @@ // 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/ch58x_usbfs_reg.h b/src/portable/wch/ch58x_usbfs_reg.h deleted file mode 100644 index 67b47e0b6..000000000 --- a/src/portable/wch/ch58x_usbfs_reg.h +++ /dev/null @@ -1,291 +0,0 @@ -/* - * 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 register accessors -//--------------------------------------------------------------------+ - -// EP DMA is 16-bit (only low 16 bits of RAM address, high bits implied 0x2000), EP4 shares EP0's DMA register (no independent DMA), so ep==4 maps to base+0x10 -#define CH58X_EP_DMA(base, ep) (*(volatile uint16_t *)((base) + ((ep) <= 3 ? (0x10u + (ep)*4u) : ((ep) == 4 ? 0x10u : (0x54u + ((ep)-5u)*4u))))) -#define CH58X_EP_TLEN(base, ep) (*(volatile uint8_t *)((base) + ((ep) <= 4 ? (0x20u + (ep)*4u) : (0x64u + ((ep)-5u)*4u)))) -#define CH58X_EP_CTRL(base, ep) (*(volatile uint8_t *)((base) + ((ep) <= 4 ? (0x22u + (ep)*4u) : (0x66u + ((ep)-5u)*4u)))) - -//--------------------------------------------------------------------+ -// 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_TOG_OK 0x40 // toggle match flag (device and host) -#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/5/6/7, not EP0/EP4) -// 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 - -//--------------------------------------------------------------------+ -// 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 - -//--------------------------------------------------------------------+ -// PFIC (Platform-level Fast Interrupt Controller) -//--------------------------------------------------------------------+ -#define CH58X_PFIC_IENR ((volatile uint32_t *)0xE000E100) // interrupt enable -#define CH58X_PFIC_IRER ((volatile uint32_t *)0xE000E180) // interrupt reset (disable) - -#define CH58X_USB_IRQn 22 -#define CH58X_USB2_IRQn 23 - -#endif /* CH58X_USBFS_REG_H */ diff --git a/src/portable/wch/dcd_ch32_usbfs.c b/src/portable/wch/dcd_ch32_usbfs.c index dae31da91..7beb91e16 100644 --- a/src/portable/wch/dcd_ch32_usbfs.c +++ b/src/portable/wch/dcd_ch32_usbfs.c @@ -35,17 +35,23 @@ /* private defines */ #define EP_MAX (8) + // Struct-based EP register access (uniform layout). Some parts (e.g. CH58X) have a different + // register map and define EP_DMA/EP_TX_LEN/EP_CTRL themselves in ch32_usbfs_reg.h. + #ifndef CH32_USBFS_EP_REGS_CUSTOM #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 +92,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; @@ -100,6 +117,12 @@ static struct { bool isochronous[EP_MAX]; struct usb_xfer xfer[EP_MAX][2]; TU_ATTR_ALIGNED(4) uint8_t buffer[EP_MAX][2][64]; +#ifdef CH32_USBFS_EP4_SHARES_EP0 + // CH58X: EP0 and EP4 share one DMA region (EP4 has no DMA register). Layout: + // EP0 [0:63] (half-duplex, OUT+IN) + EP4 OUT [64:127] + EP4 IN [128:191]. buffer[0]/buffer[4] + // are left unused for this part. + TU_ATTR_ALIGNED(4) uint8_t ep0_ep4_buffer[3 * 64]; +#endif TU_ATTR_ALIGNED(4) struct { // OUT transfers >64 bytes will overwrite queued IN data! uint8_t out[64]; @@ -108,19 +131,50 @@ static struct { } ep3_buffer; } 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, EP4 overlays EP0's region (see ep0_ep4_buffer above). +static inline uint32_t ep_dma_addr(uint8_t ep) { +#ifdef CH32_USBFS_EP4_SHARES_EP0 + if (ep == 0) { return (uint32_t) &data.ep0_ep4_buffer[0]; } +#endif + if (ep == 3) { return (uint32_t) &data.ep3_buffer.out[0]; } + return (uint32_t) &data.buffer[ep][0]; +} +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]; } +#endif + if (ep == 3) { return data.ep3_buffer.out; } + return data.buffer[ep][TUSB_DIR_OUT]; +} +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]; } +#endif + 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]; +} +// 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); - } + memcpy(ep_in_buf(ep), xfer->buffer, len); xfer->buffer += len; xfer->len -= len; xfer->processed_len += len; @@ -150,11 +204,7 @@ 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); - } + memcpy(xfer->buffer, ep_out_buf(ep), len); xfer->buffer += len; xfer->len -= len; xfer->processed_len += len; @@ -176,12 +226,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 +244,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 +253,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; +#ifdef CH32_USBFS_EP_REGS_CUSTOM + // 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 +273,31 @@ 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: { +#ifdef CH32_USBFS_EP_MANUAL_TOG + // Manual toggle: drop OUT packets whose data toggle doesn't match (host retransmit), + // otherwise flip the expected RX toggle for the next packet. EP0 is driven by the + // SETUP/status flow below, so its toggle is left to that path. + if (ep != 0) { + if (!(int_st & USBFS_INT_ST_TOG_OK)) { break; } + 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; @@ -237,11 +306,12 @@ void dcd_int_handler(uint8_t rhport) { ep_tx_ctrl_set(0, USBFS_EP_T_RES_NAK); data.ep0_tog = true; - 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; } @@ -323,10 +393,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; @@ -407,10 +479,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/portable/wch/dcd_ch58x_usbfs.c b/src/portable/wch/dcd_ch58x_usbfs.c deleted file mode 100644 index c6f10b801..000000000 --- a/src/portable/wch/dcd_ch58x_usbfs.c +++ /dev/null @@ -1,636 +0,0 @@ -/* - * 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 -//--------------------------------------------------------------------+ -typedef struct { - bool valid; - uint8_t* buffer; - size_t len; - size_t processed_len; - size_t max_size; -} xfer_ctl_t; - -typedef struct { - uint32_t usb_base; - bool ep0_tog; - volatile uint8_t setup_pending; // SETUP arrived while EP0 completion pending in queue - volatile bool ep0_completion_pending; // EP0 XFER_COMPLETE in event queue, not yet processed - uint8_t pending_addr; // Address to set in ISR after Set Address status ZLP - bool isochronous[EP_MAX][2]; // [ep][dir] - xfer_ctl_t 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 -//--------------------------------------------------------------------+ -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; - } -} - -// DMA base pointer (EP4 shares with EP0) -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; - } -} - -// AUTO_TOG is not used (EP1-3/5-7 support it). When clear-stall resets T_TOG/ -// R_TOG to DATA0, the hardware internal toggle doesn't sync, causing mismatch -// and bus resets. Use manual toggle (EP_CTRL ^= TOG) in ISR instead. - -//--------------------------------------------------------------------+ -// Private transfer helpers -//--------------------------------------------------------------------+ -static void update_in(uint8_t rhport, uint8_t ep, bool force, bool in_isr) { - dcd_data_t* d = &_dcd_data[rhport]; - uint32_t base = d->usb_base; - xfer_ctl_t* 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][TUSB_DIR_IN]) { - 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); - if (ep == 0) d->ep0_completion_pending = true; - dcd_event_xfer_complete(rhport, ep | TUSB_DIR_IN_MASK, - xfer->processed_len, XFER_RESULT_SUCCESS, in_isr); - } - } -} - -static void update_out(uint8_t rhport, uint8_t ep, size_t rx_len, bool in_isr) { - dcd_data_t* d = &_dcd_data[rhport]; - uint32_t base = d->usb_base; - xfer_ctl_t* 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; - // NAK to prevent hardware from accepting next OUT before a new xfer is queued - ep_set_rx_response(base, ep, CH58X_EP_R_RES_NAK); - if (ep == 0) d->ep0_completion_pending = true; - dcd_event_xfer_complete(rhport, ep, xfer->processed_len, - XFER_RESULT_SUCCESS, in_isr); - } else if (ep == 0) { - // EP0 multi-packet: ensure ACK for next packet - 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; - d->setup_pending = 0; - d->ep0_completion_pending = false; - d->pending_addr = 0; - - // Reset USB control register first (SDK pattern) - USB_CTRL(base) = 0x00; - - // 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 + manual 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_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 + manual 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); - - // Process regular token before SETUP to avoid losing it - if (token != CH58X_PID_SETUP) { - switch (token) { - case CH58X_PID_OUT: { - uint8_t rx_len = USB_RX_LEN(base); - if (ep == 0) { - // EP0: manual toggle, always process - EP_CTRL(base, 0) ^= CH58X_EP_R_TOG; - update_out(rhport, 0, rx_len, true); - } else if (int_st & CH58X_UIS_TOG_OK) { - // Toggle OK: manual toggle and process - EP_CTRL(base, ep) ^= CH58X_EP_R_TOG; - update_out(rhport, ep, rx_len, true); - } - // else: toggle mismatch, discard - break; - } - - case CH58X_PID_IN: { - // Apply pending Set Address immediately after status ZLP - if (ep == 0 && d->pending_addr) { - USB_DEV_AD(base) = (USB_DEV_AD(base) & CH58X_UDA_GP_BIT) | - (d->pending_addr & CH58X_USB_ADDR_MASK); - d->pending_addr = 0; - } - if (ep != 0) { - // Manual toggle for all non-EP0 endpoints - EP_CTRL(base, ep) ^= CH58X_EP_T_TOG; - } - update_in(rhport, ep, false, true); - break; - } - - default: - break; - } - USB_INT_FG(base) = CH58X_UIF_TRANSFER; - } - - // SETUP_ACT is checked separately — it persists even if token field changed - if (int_st & CH58X_UIS_SETUP_ACT) { - // Reset toggles to DATA1, NAK both directions until stack is ready - EP_CTRL(base, 0) = CH58X_EP_R_TOG | CH58X_EP_T_TOG | - CH58X_EP_R_RES_NAK | CH58X_EP_T_RES_NAK; - d->ep0_tog = true; - - d->pending_addr = 0; - - // Mark stale EP0 completion so dcd_edpt_xfer can skip it - d->setup_pending = d->ep0_completion_pending ? 1 : 0; - d->ep0_completion_pending = false; - d->xfer[0][TUSB_DIR_OUT].valid = false; - d->xfer[0][TUSB_DIR_IN].valid = false; - - dcd_event_setup_received(rhport, ep_out_buffer(d, 0), true); - USB_INT_FG(base) = CH58X_UIF_TRANSFER; - } - } - - // Process bus reset: reset all endpoints immediately (matching WCH SDK pattern) - if (status & CH58X_UIF_BUS_RST) { - d->ep0_tog = true; - d->setup_pending = 0; - d->ep0_completion_pending = false; - d->pending_addr = 0; - - // Reset EP0: ACK for RX (ready for SETUP), NAK for TX - EP_CTRL(base, 0) = CH58X_EP_R_RES_ACK | CH58X_EP_T_RES_NAK; - EP_TLEN(base, 0) = 0; - d->xfer[0][TUSB_DIR_OUT].max_size = EP_BUF_SIZE; - d->xfer[0][TUSB_DIR_IN].max_size = EP_BUF_SIZE; - - // Reset EP1-7: NAK both directions, invalidate pending transfers - 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; - EP_CTRL(base, ep) = CH58X_EP_R_RES_NAK | CH58X_EP_T_RES_NAK; - EP_TLEN(base, ep) = 0; - } - - USB_DEV_AD(base) = 0x00; - - tusb_speed_t speed = (USB_CTRL(base) & CH58X_UC_LOW_SPEED) ? - TUSB_SPEED_LOW : TUSB_SPEED_FULL; - dcd_event_bus_reset(rhport, speed, true); - - USB_INT_FG(base) = CH58X_UIF_BUS_RST; - } - - // Process suspend/resume - if (status & CH58X_UIF_SUSPEND) { - dcd_event_bus_signal(rhport, - (USB_MIS_ST(base) & CH58X_UMS_SUSPEND) ? DCD_EVENT_SUSPEND : DCD_EVENT_RESUME, - true); - USB_INT_FG(base) = CH58X_UIF_SUSPEND; - } -} - -void dcd_int_enable(uint8_t rhport) { - uint8_t irqn = (rhport == 0) ? CH58X_USB_IRQn : CH58X_USB2_IRQn; - CH58X_PFIC_IENR[irqn / 32] = (1u << (irqn % 32)); -} - -void dcd_int_disable(uint8_t rhport) { - uint8_t irqn = (rhport == 0) ? CH58X_USB_IRQn : CH58X_USB2_IRQn; - CH58X_PFIC_IRER[irqn / 32] = (1u << (irqn % 32)); - __asm volatile ("fence.i"); -} - -void dcd_set_address(uint8_t rhport, uint8_t dev_addr) { - // Defer to ISR: apply address right after status ZLP is ACK'd - _dcd_data[rhport].pending_addr = dev_addr; - dcd_edpt_xfer(rhport, 0x80, NULL, 0, false); -} - -void dcd_remote_wakeup(uint8_t rhport) { - (void) rhport; - // TODO: not supported -} - -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) { - (void) rhport; - (void) en; -} - -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) { - // Safety net: re-apply address in case ISR path was skipped - USB_DEV_AD(base) = (USB_DEV_AD(base) & CH58X_UDA_GP_BIT) | - ((uint8_t)request->wValue & CH58X_USB_ADDR_MASK); - } - - dcd_int_disable(rhport); - d->ep0_completion_pending = false; - if (d->setup_pending) { - // SETUP already arrived — don't override its NAK with ACK - d->setup_pending = 0; - } else { - ep_set_both_response(base, 0, CH58X_EP_T_RES_NAK, CH58X_EP_R_RES_ACK); - } - dcd_int_enable(rhport); -} - -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][dir] = (desc_ep->bmAttributes.xfer == TUSB_XFER_ISOCHRONOUS); - uint16_t max_size = tu_edpt_packet_size(desc_ep); - if (max_size > EP_BUF_SIZE) { - max_size = EP_BUF_SIZE; - } - d->xfer[ep][dir].max_size = max_size; - - if (ep != 0) { - dcd_int_disable(rhport); - uint8_t ctrl = EP_CTRL(base, ep); - if (dir == TUSB_DIR_OUT) { - // Clear RX toggle to DATA0 per USB spec - ctrl &= ~CH58X_EP_R_TOG; - if (d->isochronous[ep][TUSB_DIR_OUT]) { - 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; - } - } else { - // Clear TX toggle to DATA0 per USB spec - ctrl &= ~CH58X_EP_T_TOG; - EP_TLEN(base, ep) = 0; - ctrl = (ctrl & ~CH58X_EP_T_RES_MASK) | CH58X_EP_T_RES_NAK; - } - 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][TUSB_DIR_OUT] = false; - d->isochronous[ep][TUSB_DIR_IN] = 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) { - uint8_t ep = tu_edpt_number(ep_addr); - uint8_t dir = tu_edpt_dir(ep_addr); - - dcd_data_t* d = &_dcd_data[rhport]; - xfer_ctl_t* xfer = &d->xfer[ep][dir]; - - dcd_int_disable(rhport); - - // Skip stale EP0 arm if a new SETUP has already arrived - if (ep == 0) { - d->ep0_completion_pending = false; - if (d->setup_pending) { - d->setup_pending = 0; - dcd_int_enable(rhport); - return true; - } - } - - xfer->valid = true; - xfer->buffer = buffer; - xfer->len = total_bytes; - xfer->processed_len = 0; - - if (dir == TUSB_DIR_IN) { - update_in(rhport, ep, true, is_isr); - } else { - if (d->isochronous[ep][TUSB_DIR_OUT]) { - 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); - } - } - dcd_int_enable(rhport); - 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 deleted file mode 100644 index 37dd08539..000000000 --- a/src/portable/wch/hcd_ch58x_usbfs.c +++ /dev/null @@ -1,721 +0,0 @@ -/* - * 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; - -// One transfer state per root port (indexed by rhport). -static volatile hcd_xfer_t _current_xfer[2] = {}; - -//--------------------------------------------------------------------+ -// Per-port state -//--------------------------------------------------------------------+ -typedef struct { - uint32_t usb_base; - bool int_enabled; - bool int_state_before_reset; -} 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[rhport].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; - - // Find which rhport this endpoint belongs to by checking both ports - for (uint8_t rp = 0; rp < 2; rp++) { - if (_current_xfer[rp].nak_pending) { - _current_xfer[rp].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(rp, dev_addr, ep_addr, buf, buflen); - } - break; - } - } -} - -//--------------------------------------------------------------------+ -// 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 only on first init to avoid wiping state of the other rhport - static bool _first_init = true; - if (_first_init) { - tu_memclr(_edpt_list, sizeof(_edpt_list)); - _first_init = false; - } - _current_xfer[rhport] = (const hcd_xfer_t){0}; - - _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) { - uint8_t irqn = (rhport == 0) ? CH58X_USB_IRQn : CH58X_USB2_IRQn; - CH58X_PFIC_IENR[irqn / 32] = (1u << (irqn % 32)); - _port_data[rhport].int_enabled = true; -} - -void hcd_int_disable(uint8_t rhport) { - uint8_t irqn = (rhport == 0) ? CH58X_USB_IRQn : CH58X_USB2_IRQn; - CH58X_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; - if (CH58X_USB_MIS_ST(base) & CH58X_UMS_DM_LEVEL) { - return TUSB_SPEED_LOW; - } - return TUSB_SPEED_FULL; -} - -void hcd_port_reset(uint8_t rhport) { - uint32_t base = _port_data[rhport].usb_base; - - LOG_CH58X_HCD("hcd_port_reset()\r\n"); - - _port_data[rhport].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 (_port_data[rhport].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 - uint32_t t0 = tusb_time_millis_api(); - while (_current_xfer[rhport].is_busy) { - if (tusb_time_millis_api() - t0 > 200) { - _current_xfer[rhport].is_busy = false; - break; - } - } - - 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 (with 200ms timeout to avoid deadlock on disconnect) - uint32_t t0 = tusb_time_millis_api(); - while (_current_xfer[rhport].is_busy) { - if (tusb_time_millis_api() - t0 > 200) { - _current_xfer[rhport].is_busy = false; - return false; - } - } - _current_xfer[rhport].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 = rhport; - _current_xfer[rhport].dev_addr = dev_addr; - _current_xfer[rhport].ep_addr = ep_addr; - _current_xfer[rhport].buffer = buffer; - _current_xfer[rhport].bufferlen = buflen; - _current_xfer[rhport].start_ms = tusb_time_millis_api(); - _current_xfer[rhport].xferred_len = 0; - _current_xfer[rhport].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 - uint32_t t0 = tusb_time_millis_api(); - while (_current_xfer[rhport].is_busy) { - if (tusb_time_millis_api() - t0 > 200) { - _current_xfer[rhport].is_busy = false; - return false; - } - } - _current_xfer[rhport].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 = rhport; - _current_xfer[rhport].dev_addr = dev_addr; - _current_xfer[rhport].ep_addr = 0x00; // SETUP always targets EP0 OUT - _current_xfer[rhport].start_ms = tusb_time_millis_api(); - _current_xfer[rhport].buffer = _tx_buf[rhport]; - _current_xfer[rhport].bufferlen = 8; - _current_xfer[rhport].xferred_len = 0; - _current_xfer[rhport].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) { - (void) rhport; - - LOG_CH58X_HCD("hcd_edpt_clear_stall(dev=0x%02x, ep=0x%02x)\r\n", dev_addr, ep_addr); - hcd_edpt_t* edpt = _get_edpt(dev_addr, ep_addr); - if (edpt != NULL) { - edpt->data_toggle = 0; - } - - 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 { - // Stop any ongoing hardware transfer before reporting removal - CH58X_UH_EP_PID(base) = 0x00; - _current_xfer[rhport].is_busy = false; - _current_xfer[rhport].nak_pending = false; - 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 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[rhport].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[rhport].bufferlen -= tx_len; - _current_xfer[rhport].xferred_len += tx_len; - - if (_current_xfer[rhport].bufferlen == 0) { - LOG_CH58X_HCD("OUT/SETUP complete, %d bytes\r\n", _current_xfer[rhport].xferred_len); - _current_xfer[rhport].is_busy = false; - hcd_event_xfer_complete(dev_addr, ep_addr, - _current_xfer[rhport].xferred_len, XFER_RESULT_SUCCESS, in_isr); - } else { - // Multi-packet OUT: send next chunk - _current_xfer[rhport].buffer += tx_len; - uint16_t copylen = TU_MIN(edpt->max_packet_size, _current_xfer[rhport].bufferlen); - memcpy(_tx_buf[rhport], _current_xfer[rhport].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[rhport].xferred_len += rx_len; - uint16_t xferred = _current_xfer[rhport].xferred_len; - - if (rx_len > 0 && _current_xfer[rhport].buffer != NULL) { - memcpy(_current_xfer[rhport].buffer, _rx_buf[rhport], rx_len); - _current_xfer[rhport].buffer += rx_len; - } - - if ((rx_len < edpt->max_packet_size) || (xferred == _current_xfer[rhport].bufferlen)) { - // Short packet or transfer complete - LOG_CH58X_HCD("IN complete, %d bytes\r\n", xferred); - _current_xfer[rhport].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[rhport].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[rhport].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"); - // For interrupt endpoints, treat NAK as a successful 0-byte poll so - // that upper layers can reschedule based on the endpoint interval. - if (edpt->xfer_type == TUSB_XFER_INTERRUPT) { - _current_xfer[rhport].is_busy = false; - hcd_event_xfer_complete(dev_addr, ep_addr, 0, XFER_RESULT_SUCCESS, in_isr); - } else { - // For non-interrupt endpoints, schedule retry via deferred callback. - _current_xfer[rhport].is_busy = false; - _current_xfer[rhport].nak_pending = true; - - edpt->is_nak_pending = true; - edpt->buflen = _current_xfer[rhport].bufferlen; - edpt->buf = _current_xfer[rhport].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[rhport].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[rhport].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 */ |
