diff options
| author | hathach <[email protected]> | 2026-07-12 04:59:21 +0700 |
|---|---|---|
| committer | hathach <[email protected]> | 2026-07-12 04:59:21 +0700 |
| commit | fbdbcd092e9234a82e10c8e673b63c28b46cb007 (patch) | |
| tree | 4bb82abcbc086ab4a12517dc83da7c5acc1f9012 | |
| parent | 242377411afe78367efc01994241a6fda5bbdc00 (diff) | |
ch32h417: add USB3 SuperSpeed dcd with runtime USB2 fallback
Second stage: the USBSS SuperSpeed device driver (SPEED=super now links). The
LINK layer is the same IP as the CH569 but the endpoint engine is the H417's
reworked chain-DMA design (hardware SEQ_AUTO/ERDY_AUTO, persistent RB_EP_*_HALT,
per-chain completion). The RCC/PHY/LINK bring-up and EP0 control sequences are
transcribed from the WCH EVT USBSS device demo; data endpoints arm one chain per
packet (burst 1) for correctness-first bring-up, matching the CH569 port's
initial path - raise CFG_TUD_WCH_USB30_MAX_BURST once the hardware validates.
Buffers may live anywhere in the shared SRAM (all DMA-reachable), so no bounce
machinery is needed.
- src/portable/wch/dcd_ch32h417_usb30.c (+ ch32h417_usb30_reg.h): full dcd with
a STATE_CHG-driven LTSSM handler, LMP PORT_CAP/PORT_CFG exchange, EP0 DPH/ERDY
flow with software sequence, chain-armed data endpoints, per-EP HALT stall, and
a TIM12-backstopped runtime USB2 fallback that hands rhport 0 to ch32h417_usb2_*
when SuperSpeed training fails (host has no SS port).
- examples/device/*/skip.txt: add mcu:CH32H417 to the FreeRTOS + audio/video
examples (no FreeRTOSConfig for this family yet), mirroring mcu:CH569.
Build-verified: all 20 device examples build for SPEED=super, and cdc_msc for
SPEED=high, clean under -Werror (riscv-none-elf-gcc 13.2). Hardware bring-up at
5 Gbps (usbtest battery, throughput) is pending rig availability.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01QWhrtHjbSVMKcY68vxBwBm
| -rw-r--r-- | examples/device/audio_4_channel_mic/skip.txt | 1 | ||||
| -rw-r--r-- | examples/device/audio_4_channel_mic_freertos/skip.txt | 1 | ||||
| -rw-r--r-- | examples/device/audio_test/skip.txt | 1 | ||||
| -rw-r--r-- | examples/device/audio_test_freertos/skip.txt | 1 | ||||
| -rw-r--r-- | examples/device/audio_test_multi_rate/skip.txt | 1 | ||||
| -rw-r--r-- | examples/device/cdc_msc_freertos/skip.txt | 1 | ||||
| -rw-r--r-- | examples/device/cdc_uac2/skip.txt | 1 | ||||
| -rw-r--r-- | examples/device/hid_composite_freertos/skip.txt | 1 | ||||
| -rw-r--r-- | examples/device/midi_test_freertos/skip.txt | 1 | ||||
| -rw-r--r-- | examples/device/uac2_headset/skip.txt | 1 | ||||
| -rw-r--r-- | examples/device/uac2_speaker_fb/skip.txt | 1 | ||||
| -rw-r--r-- | examples/device/video_capture/skip.txt | 1 | ||||
| -rw-r--r-- | examples/device/video_capture_2ch/skip.txt | 1 | ||||
| -rw-r--r-- | src/portable/wch/ch32h417_usb30_reg.h | 76 | ||||
| -rw-r--r-- | src/portable/wch/dcd_ch32h417_usb30.c | 655 |
15 files changed, 737 insertions, 7 deletions
diff --git a/examples/device/audio_4_channel_mic/skip.txt b/examples/device/audio_4_channel_mic/skip.txt index 6d4f5a48e..d4736aee9 100644 --- a/examples/device/audio_4_channel_mic/skip.txt +++ b/examples/device/audio_4_channel_mic/skip.txt @@ -5,3 +5,4 @@ family:broadcom_64bit family:espressif mcu:CH583 mcu:CH569 +mcu:CH32H417 diff --git a/examples/device/audio_4_channel_mic_freertos/skip.txt b/examples/device/audio_4_channel_mic_freertos/skip.txt index 9725814bb..5f52307d0 100644 --- a/examples/device/audio_4_channel_mic_freertos/skip.txt +++ b/examples/device/audio_4_channel_mic_freertos/skip.txt @@ -21,3 +21,4 @@ family:broadcom_64bit family:nuc121_125 family:hpmicro mcu:CH569 +mcu:CH32H417 diff --git a/examples/device/audio_test/skip.txt b/examples/device/audio_test/skip.txt index 15c8461d2..786b167a7 100644 --- a/examples/device/audio_test/skip.txt +++ b/examples/device/audio_test/skip.txt @@ -4,3 +4,4 @@ mcu:SAMG family:espressif mcu:CH583 mcu:CH569 +mcu:CH32H417 diff --git a/examples/device/audio_test_freertos/skip.txt b/examples/device/audio_test_freertos/skip.txt index a88b265c2..ed5669c43 100644 --- a/examples/device/audio_test_freertos/skip.txt +++ b/examples/device/audio_test_freertos/skip.txt @@ -19,3 +19,4 @@ board:stm32l0538disco family:nuc121_125 family:hpmicro mcu:CH569 +mcu:CH32H417 diff --git a/examples/device/audio_test_multi_rate/skip.txt b/examples/device/audio_test_multi_rate/skip.txt index 15c8461d2..786b167a7 100644 --- a/examples/device/audio_test_multi_rate/skip.txt +++ b/examples/device/audio_test_multi_rate/skip.txt @@ -4,3 +4,4 @@ mcu:SAMG family:espressif mcu:CH583 mcu:CH569 +mcu:CH32H417 diff --git a/examples/device/cdc_msc_freertos/skip.txt b/examples/device/cdc_msc_freertos/skip.txt index dda94cb65..33ae88a17 100644 --- a/examples/device/cdc_msc_freertos/skip.txt +++ b/examples/device/cdc_msc_freertos/skip.txt @@ -19,3 +19,4 @@ family:broadcom_64bit family:nuc121_125 family:hpmicro mcu:CH569 +mcu:CH32H417 diff --git a/examples/device/cdc_uac2/skip.txt b/examples/device/cdc_uac2/skip.txt index 946bae864..0908c778b 100644 --- a/examples/device/cdc_uac2/skip.txt +++ b/examples/device/cdc_uac2/skip.txt @@ -8,3 +8,4 @@ board:stm32l052dap52 family:espressif mcu:CH583 mcu:CH569 +mcu:CH32H417 diff --git a/examples/device/hid_composite_freertos/skip.txt b/examples/device/hid_composite_freertos/skip.txt index bc2d90149..9e2f51a04 100644 --- a/examples/device/hid_composite_freertos/skip.txt +++ b/examples/device/hid_composite_freertos/skip.txt @@ -17,3 +17,4 @@ family:broadcom_32bit family:broadcom_64bit family:hpmicro mcu:CH569 +mcu:CH32H417 diff --git a/examples/device/midi_test_freertos/skip.txt b/examples/device/midi_test_freertos/skip.txt index bc2d90149..9e2f51a04 100644 --- a/examples/device/midi_test_freertos/skip.txt +++ b/examples/device/midi_test_freertos/skip.txt @@ -17,3 +17,4 @@ family:broadcom_32bit family:broadcom_64bit family:hpmicro mcu:CH569 +mcu:CH32H417 diff --git a/examples/device/uac2_headset/skip.txt b/examples/device/uac2_headset/skip.txt index 946bae864..0908c778b 100644 --- a/examples/device/uac2_headset/skip.txt +++ b/examples/device/uac2_headset/skip.txt @@ -8,3 +8,4 @@ board:stm32l052dap52 family:espressif mcu:CH583 mcu:CH569 +mcu:CH32H417 diff --git a/examples/device/uac2_speaker_fb/skip.txt b/examples/device/uac2_speaker_fb/skip.txt index 30ddf4e18..c6109d61d 100644 --- a/examples/device/uac2_speaker_fb/skip.txt +++ b/examples/device/uac2_speaker_fb/skip.txt @@ -8,3 +8,4 @@ board:stm32l052dap52 family:broadcom_64bit mcu:CH583 mcu:CH569 +mcu:CH32H417 diff --git a/examples/device/video_capture/skip.txt b/examples/device/video_capture/skip.txt index 9bb1410a8..90a01f757 100644 --- a/examples/device/video_capture/skip.txt +++ b/examples/device/video_capture/skip.txt @@ -5,3 +5,4 @@ mcu:MSP430x5xx mcu:NUC121 mcu:SAMD11 mcu:CH569 +mcu:CH32H417 diff --git a/examples/device/video_capture_2ch/skip.txt b/examples/device/video_capture_2ch/skip.txt index 7260ec2aa..c3b4e0880 100644 --- a/examples/device/video_capture_2ch/skip.txt +++ b/examples/device/video_capture_2ch/skip.txt @@ -18,3 +18,4 @@ board:ek_tm4c123gxl board:uno_r4 board:ra4m1_ek mcu:CH569 +mcu:CH32H417 diff --git a/src/portable/wch/ch32h417_usb30_reg.h b/src/portable/wch/ch32h417_usb30_reg.h new file mode 100644 index 000000000..168359ca4 --- /dev/null +++ b/src/portable/wch/ch32h417_usb30_reg.h @@ -0,0 +1,76 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2026 Ha Thach (tinyusb.org) + * + * 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 USB_CH32H417_USB30_REG_H +#define USB_CH32H417_USB30_REG_H + +// USBSSD_TypeDef (@0x40034000), LINK_*/USBSS_* bit macros come from the SDK. The LINK-layer bit +// names/semantics are documented in CH32H417RM-EN chapter 27 (the same LINK IP is used, less its +// register documentation, by the CH569). +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wstrict-prototypes" +#include "ch32h417.h" +#include "ch32h417_usb.h" +#include "ch32h417_tim.h" // TIM12 fallback backstop timer (TIM_IT_Update, TIM_CEN) +#pragma GCC diagnostic pop + +// PHY configuration back-door (undocumented; values transcribed from the WCH EVT USBSS demo) +#define USBSS_PHY_CFG_CR (*(volatile uint32_t*)0x400341F8) +#define USBSS_PHY_CFG_DAT (*(volatile uint32_t*)0x400341FC) +#define USBSS_PHY_MISC_REG (*(volatile uint32_t*)0x5003C018) + +// Link Management Packet (LMP) subtype / header fields (device = upstream port) +#define LMP_HP 0 +#define LMP_SUBTYPE_MASK (0xFu << 5) +#define LMP_SET_LINK_FUNC (0x1u << 5) +#define LMP_U2_INACT_TOUT (0x2u << 5) +#define LMP_PORT_CAP (0x4u << 5) +#define LMP_PORT_CFG (0x5u << 5) +#define LMP_PORT_CFG_RES (0x6u << 5) +#define LMP_LINK_SPEED (1u << 9) +#define LMP_NUM_HP_BUF (4u << 0) +#define LMP_UP_STREAM (2u << 16) + +// Offset sanity checks against the SDK struct +TU_VERIFY_STATIC(offsetof(USBSSD_TypeDef, USB_CONTROL) == 0x70, "USB_CONTROL"); +TU_VERIFY_STATIC(offsetof(USBSSD_TypeDef, USB_STATUS) == 0x74, "USB_STATUS"); +TU_VERIFY_STATIC(offsetof(USBSSD_TypeDef, UEP0_TX_CTRL) == 0x84, "UEP0_TX_CTRL"); +TU_VERIFY_STATIC(offsetof(USBSSD_TypeDef, EP1_TX) == 0xC0, "EP1_TX bank"); + +// The per-endpoint TX and RX register banks alternate in the struct (EP1_TX, EP1_RX, EP2_TX, +// EP2_RX, ...), each 16 bytes, so EPn's bank is 2 * (n-1) entries past EP1's. +TU_ATTR_ALWAYS_INLINE static inline volatile USBSS_EP_TX_TypeDef* usbss_ep_tx(uint8_t ep_num) { + return (volatile USBSS_EP_TX_TypeDef*)&USBSSD->EP1_TX + 2u * (ep_num - 1u); +} +TU_ATTR_ALWAYS_INLINE static inline volatile USBSS_EP_RX_TypeDef* usbss_ep_rx(uint8_t ep_num) { + return (volatile USBSS_EP_RX_TypeDef*)&USBSSD->EP1_RX + 2u * (ep_num - 1u); +} + +// USB_STATUS fields: which endpoint/direction raised the transfer interrupt +#define USBSS_STATUS_EP_NUM(st) (((st) & USBSS_EP_ID_MASK) >> 8) +#define USBSS_STATUS_EP_IN(st) (((st) & USBSS_EP_DIR_MASK) != 0) + +#endif diff --git a/src/portable/wch/dcd_ch32h417_usb30.c b/src/portable/wch/dcd_ch32h417_usb30.c index c730e33a4..d355dda7d 100644 --- a/src/portable/wch/dcd_ch32h417_usb30.c +++ b/src/portable/wch/dcd_ch32h417_usb30.c @@ -26,13 +26,19 @@ // USB3.0 SuperSpeed device driver for the WCH CH32H417/CH32H416 USBSS controller // (@0x40034000), documented in CH32H417RM-EN chapter 27. The LINK layer is the same IP as the -// CH569 (see dcd_ch56x_usb30.c) but the endpoint engine is a reworked chain-DMA design: -// hardware sequence numbers and ERDY, per-endpoint halt (RB_EP_TX/RX_HALT), per-chain -// completion flags. Software drives the LTSSM through the USBSS_LINK interrupt. +// CH569 (dcd_ch56x_usb30.c) but the endpoint engine is a reworked chain-DMA design with +// hardware sequence numbers and ERDY (SEQ_AUTO / ERDY_AUTO), per-endpoint persistent halt +// (RB_EP_TX/RX_HALT), and per-chain completion flags. Software drives the LTSSM through the +// USBSS_LINK interrupt. The init/PHY/LINK/EP0 register sequences are transcribed from the WCH +// EVT USBSS device demo; the data-endpoint engine arms one chain per packet (burst 1) for +// correctness-first bring-up, matching the CH569 port's initial path - raise the burst once the +// hardware validates. Buffers may live anywhere in the shared SRAM (all DMA-reachable). // -// With CFG_TUD_WCH_USB30_FALLBACK this dcd owns both controllers: TIM12 times SuperSpeed -// training and hands rhport 0 to the USB2 driver (ch32h417_usb2_* in dcd_ch32h417_usbhs.c) -// when the host has no SuperSpeed port. +// With CFG_TUD_WCH_USB30_FALLBACK this dcd owns both controllers: it counts failed SuperSpeed +// training attempts (LINK DISABLE/INACTIVE) with a TIM12 backstop and hands rhport 0 to the USB2 +// driver (ch32h417_usb2_* in dcd_ch32h417_usbhs.c) when the host has no SuperSpeed port. +// +// NOTE: compile-verified; full hardware bring-up (usbtest at 5 Gbps) is pending. #include "tusb_option.h" @@ -40,7 +46,642 @@ defined(CFG_TUD_WCH_USBIP_USB30) && CFG_TUD_WCH_USBIP_USB30 == 1 #include "device/dcd.h" +#include "dcd_ch32h417.h" +#include "ch32h417_usb30_reg.h" + +#define EP_MAX 8 +#define EP0_MAX_SIZE 512 + +#ifndef CFG_TUD_WCH_USB30_MAX_BURST +#define CFG_TUD_WCH_USB30_MAX_BURST 1 +#endif + +typedef struct { + uint8_t *buffer; + uint16_t total_len; + uint16_t queued_len; + uint16_t max_size; + bool is_iso; + bool valid; +} xfer_ctl_t; + +#define XFER_CTL_BASE(_ep, _dir) (&xfer_status[_ep][_dir]) +static xfer_ctl_t xfer_status[EP_MAX][2]; + +// EP0 buffer in shared SRAM (DMA-reachable); SETUP lands here. +TU_ATTR_ALIGNED(16) static uint8_t ep0_buffer[EP0_MAX_SIZE]; +static uint8_t ep0_tx_seq; // EP0 IN sequence number [4:0] +static uint8_t pending_addr; +static bool pending_addr_valid; + +//--------------------------------------------------------------------+ +// Runtime USB2 fallback state +//--------------------------------------------------------------------+ +#if CFG_TUD_WCH_USB30_FALLBACK +enum { FB_USB3_TRAINING, FB_USB3_UP, FB_USB2_ACTIVE }; +static uint8_t fb_state; +static uint8_t fb_fail_count; +#define FB_FAIL_LIMIT 3 +#endif + +//--------------------------------------------------------------------+ +// PHY / clock / link bring-up (transcribed from the WCH EVT USBSS demo) +//--------------------------------------------------------------------+ + +static void usbss_rcc_init(bool enable) { + if (enable) { + RCC->CTLR |= (uint32_t)RCC_USBSS_PLLON; + while ((RCC->CTLR & (uint32_t)RCC_USBSS_PLLRDY) != (uint32_t)RCC_USBSS_PLLRDY) {} + RCC_HBPeriphClockCmd(RCC_HBPeriph_USBSS, ENABLE); + RCC_PIPECmd(ENABLE); + RCC_UTMIcmd(ENABLE); + } else { + RCC_HBPeriphClockCmd(RCC_HBPeriph_USBSS, DISABLE); + RCC_UTMIcmd(DISABLE); + RCC_PIPECmd(DISABLE); + RCC->CTLR &= ~(uint32_t)RCC_USBSS_PLLON; + } +} + +static uint32_t usbss_phy_cfg(uint8_t addr, uint16_t data) { + USBSS_PHY_CFG_CR = (1u << 23) | ((uint32_t)addr << 16) | data; + USBSS_PHY_CFG_DAT = 0x01; + return USBSS_PHY_CFG_DAT; +} + +static void usbss_cfg_mod(void) { + usbss_phy_cfg(0x03, 0x7C12); + usbss_phy_cfg(0x0D, 0x79AA); + usbss_phy_cfg(0x15, 0x4430); + usbss_phy_cfg(0x13, 0x0010); + USBSS_PHY_MISC_REG = 0xB0054000; +} + +static void ep_chain_init(void) { + // EP0 buffer for SETUP/data + USBSSD->UEP0_TX_CTRL = 0; + USBSSD->UEP0_RX_CTRL = 0; + USBSSD->UEP0_TX_DMA = (uint32_t)(uintptr_t)ep0_buffer; + USBSSD->UEP0_RX_DMA = (uint32_t)(uintptr_t)ep0_buffer; +} + +static void usbss_device_init(bool enable) { + if (enable) { + usbss_rcc_init(true); + + USBSSD->LINK_CFG = LINK_RX_EQ_EN | LINK_TX_DEEMPH_3_5DB | LINK_PHY_RESET; + USBSSD->LINK_CTRL = LINK_P2_MODE | LINK_GO_DISABLED; + USBSSD->LINK_CFG = LINK_RX_EQ_EN | LINK_TX_DEEMPH_3_5DB | LINK_LTSSM_MODE | LINK_TOUT_MODE; + USBSSD->LINK_LPM_CR |= LINK_LPM_EN; + USBSSD->LINK_CFG |= LINK_RX_TERM_EN; + USBSSD->LINK_INT_CTRL = LINK_IE_TX_LMP | LINK_IE_RX_LMP | LINK_IE_RX_LMP_TOUT | LINK_IE_STATE_CHG | + LINK_IE_WARM_RST | LINK_IE_TERM_PRES; + USBSSD->LINK_CTRL = LINK_P2_MODE; + USBSSD->LINK_U1_WKUP_TMR = 120; + USBSSD->LINK_U1_WKUP_FILTER = 50; + USBSSD->LINK_U2_WKUP_FILTER = 0; + USBSSD->LINK_U3_WKUP_FILTER = 0; + + USBSSD->USB_CONTROL |= USBSS_FORCE_RST; + USBSSD->USB_STATUS = USBSS_UIF_TRANSFER; + USBSSD->USB_CONTROL = USBSS_UIE_TRANSFER | USBSS_UDIE_SETUP | USBSS_UDIE_STATUS | USBSS_DMA_EN | USBSS_SETUP_FLOW; + + usbss_cfg_mod(); + + USBSSD->UEP_TX_EN = 0; + USBSSD->UEP_RX_EN = 0; + ep_chain_init(); + + NVIC_EnableIRQ(USBSS_IRQn); + NVIC_EnableIRQ(USBSS_LINK_IRQn); + } else { + NVIC_DisableIRQ(USBSS_LINK_IRQn); + NVIC_DisableIRQ(USBSS_IRQn); + USBSSD->USB_CONTROL = USBSS_FORCE_RST; + USBSSD->LINK_CFG |= LINK_PHY_RESET | U3_LINK_RESET; + for (volatile int i = 0; i < 2000; i++) {} + USBSSD->USB_CONTROL &= ~USBSS_FORCE_RST; + USBSSD->LINK_CFG &= ~(LINK_PHY_RESET | U3_LINK_RESET); + usbss_rcc_init(false); + } +} + +// re-init the endpoint engine after a warm/hot reset (keeps the link up) +static void usbss_reset_init(void) { + USBSSD->USB_CONTROL |= USBSS_FORCE_RST; + USBSSD->USB_STATUS = USBSS_UIF_TRANSFER; + USBSSD->USB_CONTROL = USBSS_UIE_TRANSFER | USBSS_UDIE_SETUP | USBSS_UDIE_STATUS | USBSS_DMA_EN | USBSS_SETUP_FLOW; + ep_chain_init(); +} + +//--------------------------------------------------------------------+ +// LINK layer (LTSSM) - state-change driven, transcribed from the demo +//--------------------------------------------------------------------+ + +static void handle_link_irq(uint8_t rhport) { + uint32_t link_int = USBSSD->LINK_INT_FLAG; + uint32_t link_state = USBSSD->LINK_STATUS & LINK_STATE_MASK; + + if (link_int & LINK_IF_STATE_CHG) { + USBSSD->LINK_INT_FLAG = LINK_IF_STATE_CHG; + + switch (link_state) { + case LINK_STATE_DISABLE: + USBSSD->LINK_CTRL &= ~LINK_GO_DISABLED; +#if CFG_TUD_WCH_USB30_FALLBACK + if (fb_state == FB_USB3_TRAINING && ++fb_fail_count >= FB_FAIL_LIMIT) { + // host has no SuperSpeed port: bring up USB2 on the same rhport + usbss_device_init(false); + fb_state = FB_USB2_ACTIVE; + ch32h417_usb2_init(rhport); + ch32h417_usb2_int_enable(); + } +#endif + break; + + case LINK_STATE_HOTRST: + usbss_reset_init(); + dcd_event_bus_reset(rhport, TUSB_SPEED_SUPER, true); + USBSSD->LINK_CTRL &= ~LINK_HOT_RESET; + break; + + case LINK_STATE_U3: + dcd_event_bus_signal(rhport, DCD_EVENT_SUSPEND, true); + break; + + case LINK_STATE_U0: + case LINK_STATE_RECOVERY: + case LINK_STATE_POLLING: + case LINK_STATE_RXDET: + default: + break; + } + } else if (link_int & LINK_IF_TERM_PRES) { + USBSSD->LINK_INT_FLAG = LINK_IF_TERM_PRES; + } else if (link_int & LINK_IF_RX_LMP_TOUT) { + USBSSD->LINK_INT_FLAG = LINK_IF_RX_LMP_TOUT; + USBSSD->LINK_CTRL |= LINK_GO_DISABLED; + USBSSD->LINK_CTRL |= LINK_GO_RX_DET; + } else if (link_int & LINK_IF_TX_LMP) { + USBSSD->LINK_INT_FLAG = LINK_IF_TX_LMP; + USBSSD->LINK_LMP_TX_DATA0 = LMP_LINK_SPEED | LMP_PORT_CAP | LMP_HP; + USBSSD->LINK_LMP_TX_DATA1 = LMP_UP_STREAM | LMP_NUM_HP_BUF; + USBSSD->LINK_LMP_TX_DATA2 = 0; + } else if (link_int & LINK_IF_RX_LMP) { + USBSSD->LINK_INT_FLAG = LINK_IF_RX_LMP; + uint32_t rx0 = USBSSD->LINK_LMP_RX_DATA0; + if ((rx0 & LMP_SUBTYPE_MASK) == LMP_PORT_CFG) { + // upstream port received Port Configuration: reply Port Config Response = SS training done + USBSSD->LINK_LMP_TX_DATA0 = LMP_LINK_SPEED | LMP_PORT_CFG_RES | LMP_HP; + USBSSD->LINK_LMP_TX_DATA1 = 0; + USBSSD->LINK_LMP_TX_DATA2 = 0; + USBSSD->LINK_LMP_PORT_CAP |= LINK_LMP_TX_CAP_VLD; +#if CFG_TUD_WCH_USB30_FALLBACK + fb_state = FB_USB3_UP; + fb_fail_count = 0; +#endif + dcd_event_bus_reset(rhport, TUSB_SPEED_SUPER, true); + } else if ((rx0 & LMP_SUBTYPE_MASK) == LMP_U2_INACT_TOUT) { + USBSSD->LINK_U2_INACT_TIMER = (rx0 >> 9) & 0xFF; + } + } else if (link_int & LINK_IF_WARM_RST) { + USBSSD->LINK_INT_FLAG = LINK_IF_WARM_RST; + if (USBSSD->LINK_STATUS & LINK_RX_WARM_RST) { + usbss_reset_init(); + USBSSD->LINK_CTRL |= LINK_GO_DISABLED; + __NOP(); __NOP(); __NOP(); __NOP(); + USBSSD->LINK_CTRL &= ~LINK_GO_DISABLED; + dcd_event_bus_reset(rhport, TUSB_SPEED_SUPER, true); + } + } +} + +//--------------------------------------------------------------------+ +// EP0 control +//--------------------------------------------------------------------+ + +static void ep0_arm_in(uint16_t len) { + USBSSD->UEP0_TX_CTRL = USBSS_EP0_TX_DPH | len | ((uint32_t)ep0_tx_seq << 16); + USBSSD->UEP0_TX_CTRL |= USBSS_EP0_TX_ERDY; + ep0_tx_seq = (ep0_tx_seq + 1) & 0x1F; +} + +static void ep0_arm_out(void) { + USBSSD->UEP0_RX_CTRL = USBSS_EP0_RX_ERDY | USBSS_EP0_RX_ACK; +} + +static void handle_setup(uint8_t rhport) { + ep0_tx_seq = 0; + pending_addr_valid = false; + xfer_status[0][TUSB_DIR_IN].valid = false; + xfer_status[0][TUSB_DIR_OUT].valid = false; + dcd_event_setup_received(rhport, ep0_buffer, true); +} + +static void handle_ep0_in(uint8_t rhport) { + xfer_ctl_t *xfer = XFER_CTL_BASE(0, TUSB_DIR_IN); + if (!xfer->valid) { + return; + } + uint16_t remaining = xfer->total_len - xfer->queued_len; + if (remaining == 0) { + xfer->valid = false; + dcd_event_xfer_complete(rhport, 0x80, xfer->queued_len, XFER_RESULT_SUCCESS, true); + return; + } + uint16_t len = TU_MIN(remaining, (uint16_t)EP0_MAX_SIZE); + memcpy(ep0_buffer, &xfer->buffer[xfer->queued_len], len); + xfer->queued_len += len; + ep0_arm_in(len); +} + +static void handle_ep0_out(uint8_t rhport) { + xfer_ctl_t *xfer = XFER_CTL_BASE(0, TUSB_DIR_OUT); + if (!xfer->valid) { + return; + } + uint16_t rx_len = USBSSD->UEP0_RX_CTRL & USBSS_EP0_RX_LEN_MASK; + uint16_t remaining = xfer->total_len - xfer->queued_len; + uint16_t len = TU_MIN(rx_len, remaining); + memcpy(&xfer->buffer[xfer->queued_len], ep0_buffer, len); + xfer->queued_len += len; + + if (xfer->queued_len >= xfer->total_len || len < EP0_MAX_SIZE) { + xfer->valid = false; + dcd_event_xfer_complete(rhport, 0x00, xfer->queued_len, XFER_RESULT_SUCCESS, true); + } else { + ep0_arm_out(); + } +} + +//--------------------------------------------------------------------+ +// Data endpoints - single chain armed per packet (burst 1) +//--------------------------------------------------------------------+ + +static void queue_in_packet(uint8_t ep_num, xfer_ctl_t *xfer) { + uint16_t remaining = xfer->total_len - xfer->queued_len; + uint16_t tx_len = TU_MIN(remaining, xfer->max_size); + volatile USBSS_EP_TX_TypeDef *tx = usbss_ep_tx(ep_num); + + tx->UEP_TX_DMA = (uint32_t)(uintptr_t)&xfer->buffer[xfer->queued_len]; + tx->UEP_TX_DMA_OFS = xfer->max_size; + tx->UEP_TX_CHAIN_LEN = tx_len; + tx->UEP_TX_CHAIN_EXP_NUMP = 1; // arms the chain + xfer->queued_len += tx_len; +} + +static void queue_out_packet(uint8_t ep_num, xfer_ctl_t *xfer) { + volatile USBSS_EP_RX_TypeDef *rx = usbss_ep_rx(ep_num); + rx->UEP_RX_DMA = (uint32_t)(uintptr_t)&xfer->buffer[xfer->queued_len]; + rx->UEP_RX_DMA_OFS = xfer->max_size; + rx->UEP_RX_CHAIN_MAX_NUMP = 1; // arms the chain +} + +static void handle_ep_in(uint8_t rhport, uint8_t ep_num) { + xfer_ctl_t *xfer = XFER_CTL_BASE(ep_num, TUSB_DIR_IN); + volatile USBSS_EP_TX_TypeDef *tx = usbss_ep_tx(ep_num); + tx->UEP_TX_CHAIN_ST |= USBSS_EP_TX_CHAIN_IF; // release completed chain + + if (!xfer->valid) { + return; + } + if (xfer->queued_len >= xfer->total_len) { + xfer->valid = false; + dcd_event_xfer_complete(rhport, ep_num | TUSB_DIR_IN_MASK, xfer->queued_len, XFER_RESULT_SUCCESS, true); + } else { + queue_in_packet(ep_num, xfer); + } +} -// implementation lands with the SPEED=super bring-up stage +static void handle_ep_out(uint8_t rhport, uint8_t ep_num) { + xfer_ctl_t *xfer = XFER_CTL_BASE(ep_num, TUSB_DIR_OUT); + volatile USBSS_EP_RX_TypeDef *rx = usbss_ep_rx(ep_num); + uint16_t rx_len = rx->UEP_RX_CHAIN_LEN; + rx->UEP_RX_CHAIN_ST |= USBSS_EP_RX_CHAIN_IF; // release completed chain + + if (!xfer->valid) { + return; + } + uint16_t remaining = xfer->total_len - xfer->queued_len; + uint16_t len = TU_MIN(rx_len, TU_MIN(remaining, xfer->max_size)); + xfer->queued_len += len; + + if (xfer->queued_len >= xfer->total_len || len < xfer->max_size) { + xfer->valid = false; + dcd_event_xfer_complete(rhport, ep_num, xfer->queued_len, XFER_RESULT_SUCCESS, true); + } else { + queue_out_packet(ep_num, xfer); + } +} + +//--------------------------------------------------------------------+ +// USB (transfer) interrupt dispatch +//--------------------------------------------------------------------+ + +static void handle_usb_irq(uint8_t rhport) { + uint32_t status = USBSSD->USB_STATUS; + + if ((status & USBSS_UDIF_SETUP) && !(status & USBSS_UDIF_STATUS)) { + USBSSD->USB_STATUS = USBSS_UDIF_SETUP; + handle_setup(rhport); + } else if (status & USBSS_UDIF_STATUS) { + USBSSD->USB_STATUS = USBSS_UDIF_STATUS; + if (pending_addr_valid) { + USBSSD->USB_CONTROL = (USBSSD->USB_CONTROL & 0x00FFFFFF) | ((uint32_t)pending_addr << 24); + pending_addr_valid = false; + } + USBSSD->UEP0_TX_CTRL = 0; + USBSSD->UEP0_RX_CTRL = 0; + } else if (status & USBSS_UIF_TRANSFER) { + uint8_t ep_num = USBSS_STATUS_EP_NUM(status); + bool is_in = USBSS_STATUS_EP_IN(status); + if (ep_num == 0) { + if (is_in) { handle_ep0_in(rhport); } else { handle_ep0_out(rhport); } + } else if (is_in) { + handle_ep_in(rhport, ep_num); + } else { + handle_ep_out(rhport, ep_num); + } + } +} + +//--------------------------------------------------------------------+ +// TIM12 fallback backstop timer (0.5 s), direct-register +//--------------------------------------------------------------------+ +#if CFG_TUD_WCH_USB30_FALLBACK +static void fallback_timer_start(bool enable) { + if (enable) { + RCC_HB2PeriphClockCmd(RCC_HB2Periph_TIM12, ENABLE); + RCC_ClocksTypeDef clk; + RCC_GetClocksFreq(&clk); + TIM12->PSC = (uint16_t)(clk.HCLK_Frequency / 10000 - 1); + TIM12->ATRLR = 5000 - 1; // 0.5 s + TIM12->CNT = 0; + TIM12->INTFR = 0; + TIM12->DMAINTENR |= TIM_IT_Update; + TIM12->CTLR1 |= TIM_CEN; + NVIC_EnableIRQ(TIM12_IRQn); + } else { + NVIC_DisableIRQ(TIM12_IRQn); + TIM12->CTLR1 &= (uint16_t)~TIM_CEN; + TIM12->DMAINTENR &= (uint16_t)~TIM_IT_Update; + } +} + +static void handle_timer_irq(uint8_t rhport) { + if (!(TIM12->INTFR & TIM_IT_Update)) { + return; + } + TIM12->INTFR = (uint16_t)~TIM_IT_Update; + if (fb_state == FB_USB3_TRAINING && ++fb_fail_count >= FB_FAIL_LIMIT) { + fallback_timer_start(false); + usbss_device_init(false); + fb_state = FB_USB2_ACTIVE; + ch32h417_usb2_init(rhport); + ch32h417_usb2_int_enable(); + } +} +#endif + +//--------------------------------------------------------------------+ +// dcd API +//--------------------------------------------------------------------+ + +bool dcd_init(uint8_t rhport, const tusb_rhport_init_t *rh_init) { + (void)rhport; + (void)rh_init; + memset(&xfer_status, 0, sizeof(xfer_status)); + ep0_tx_seq = 0; + pending_addr_valid = false; + +#if CFG_TUD_WCH_USB30_FALLBACK + fb_state = FB_USB3_TRAINING; + fb_fail_count = 0; +#endif + + usbss_device_init(true); + +#if CFG_TUD_WCH_USB30_FALLBACK + fallback_timer_start(true); +#endif + return true; +} + +void dcd_int_handler(uint8_t rhport) { +#if CFG_TUD_WCH_USB30_FALLBACK + if (fb_state == FB_USB2_ACTIVE) { + ch32h417_usb2_int_handler(rhport); + return; + } + handle_timer_irq(rhport); + if (fb_state == FB_USB2_ACTIVE) { + return; + } +#endif + if (USBSSD->LINK_INT_FLAG & USBSSD->LINK_INT_CTRL) { + handle_link_irq(rhport); + } + if (USBSSD->USB_STATUS & (USBSS_UIF_TRANSFER | USBSS_UDIF_SETUP | USBSS_UDIF_STATUS)) { + handle_usb_irq(rhport); + } +} + +void dcd_int_enable(uint8_t rhport) { +#if CFG_TUD_WCH_USB30_FALLBACK + if (fb_state == FB_USB2_ACTIVE) { ch32h417_usb2_int_enable(); return; } +#endif + (void)rhport; + NVIC_EnableIRQ(USBSS_IRQn); + NVIC_EnableIRQ(USBSS_LINK_IRQn); +} + +void dcd_int_disable(uint8_t rhport) { +#if CFG_TUD_WCH_USB30_FALLBACK + if (fb_state == FB_USB2_ACTIVE) { ch32h417_usb2_int_disable(); return; } +#endif + (void)rhport; + NVIC_DisableIRQ(USBSS_IRQn); + NVIC_DisableIRQ(USBSS_LINK_IRQn); +} + +void dcd_set_address(uint8_t rhport, uint8_t dev_addr) { +#if CFG_TUD_WCH_USB30_FALLBACK + if (fb_state == FB_USB2_ACTIVE) { ch32h417_usb2_edpt0_status_complete(rhport, NULL); } +#endif + // apply the address at the status stage (USBSS_UDIF_STATUS), per the SIE + pending_addr = dev_addr; + pending_addr_valid = true; + dcd_edpt_xfer(rhport, 0x80, NULL, 0, false); // ZLP status +} + +void dcd_remote_wakeup(uint8_t rhport) { + (void)rhport; + USBSSD->LINK_CTRL |= LINK_TX_UX_EXIT; +} + +void dcd_sof_enable(uint8_t rhport, bool en) { + (void)rhport; + (void)en; +} + +void dcd_edpt0_status_complete(uint8_t rhport, const tusb_control_request_t *request) { +#if CFG_TUD_WCH_USB30_FALLBACK + if (fb_state == FB_USB2_ACTIVE) { ch32h417_usb2_edpt0_status_complete(rhport, request); return; } +#endif + (void)rhport; + (void)request; +} + +bool dcd_edpt_open(uint8_t rhport, const tusb_desc_endpoint_t *desc_edpt) { +#if CFG_TUD_WCH_USB30_FALLBACK + if (fb_state == FB_USB2_ACTIVE) { return ch32h417_usb2_edpt_open(rhport, desc_edpt); } +#endif + (void)rhport; + const uint8_t ep_num = tu_edpt_number(desc_edpt->bEndpointAddress); + const tusb_dir_t dir = tu_edpt_dir(desc_edpt->bEndpointAddress); + TU_ASSERT(ep_num < EP_MAX); + if (ep_num == 0) { + return true; + } + + xfer_ctl_t *xfer = XFER_CTL_BASE(ep_num, dir); + xfer->max_size = tu_edpt_packet_size(desc_edpt); + xfer->is_iso = (desc_edpt->bmAttributes.xfer == TUSB_XFER_ISOCHRONOUS); + + if (dir == TUSB_DIR_IN) { + volatile USBSS_EP_TX_TypeDef *tx = usbss_ep_tx(ep_num); + tx->UEP_TX_CFG = USBSS_EP_TX_CHAIN_AUTO | USBSS_EP_TX_ERDY_AUTO | USBSS_EP_TX_SEQ_AUTO | + (xfer->is_iso ? USBSS_EP_TX_ISO_MODE : 0); + tx->UEP_TX_CR = (uint8_t)(USBSS_EP_TX_CLR | USBSS_EP_TX_CHAIN_CLR); + tx->UEP_TX_CR = CFG_TUD_WCH_USB30_MAX_BURST; // ERDY_NUMP advertised + USBSSD->UEP_TX_EN |= (uint16_t)(1u << ep_num); + } else { + volatile USBSS_EP_RX_TypeDef *rx = usbss_ep_rx(ep_num); + rx->UEP_RX_CFG = USBSS_EP_RX_CHAIN_AUTO | USBSS_EP_RX_ERDY_AUTO | USBSS_EP_RX_SEQ_AUTO | + (xfer->is_iso ? USBSS_EP_RX_ISO_MODE : 0); + rx->UEP_RX_CR = (uint8_t)(USBSS_EP_RX_CLR | USBSS_EP_RX_CHAIN_CLR); + rx->UEP_RX_CR = CFG_TUD_WCH_USB30_MAX_BURST; + USBSSD->UEP_RX_EN |= (uint16_t)(1u << ep_num); + } + return true; +} + +void dcd_edpt_close(uint8_t rhport, uint8_t ep_addr) { +#if CFG_TUD_WCH_USB30_FALLBACK + if (fb_state == FB_USB2_ACTIVE) { ch32h417_usb2_edpt_close(rhport, ep_addr); return; } +#endif + (void)rhport; + const uint8_t ep_num = tu_edpt_number(ep_addr); + const tusb_dir_t dir = tu_edpt_dir(ep_addr); + if (dir == TUSB_DIR_IN) { + usbss_ep_tx(ep_num)->UEP_TX_CR = (uint8_t)(USBSS_EP_TX_CLR | USBSS_EP_TX_CHAIN_CLR); + USBSSD->UEP_TX_EN &= (uint16_t)~(1u << ep_num); + } else { + usbss_ep_rx(ep_num)->UEP_RX_CR = (uint8_t)(USBSS_EP_RX_CLR | USBSS_EP_RX_CHAIN_CLR); + USBSSD->UEP_RX_EN &= (uint16_t)~(1u << ep_num); + } + XFER_CTL_BASE(ep_num, dir)->valid = false; +} + +void dcd_edpt_close_all(uint8_t rhport) { +#if CFG_TUD_WCH_USB30_FALLBACK + if (fb_state == FB_USB2_ACTIVE) { ch32h417_usb2_edpt_close_all(rhport); return; } +#endif + (void)rhport; + for (uint8_t ep = 1; ep < EP_MAX; ep++) { + usbss_ep_tx(ep)->UEP_TX_CR = (uint8_t)(USBSS_EP_TX_CLR | USBSS_EP_TX_CHAIN_CLR); + usbss_ep_rx(ep)->UEP_RX_CR = (uint8_t)(USBSS_EP_RX_CLR | USBSS_EP_RX_CHAIN_CLR); + xfer_status[ep][0].valid = false; + xfer_status[ep][1].valid = false; + } + USBSSD->UEP_TX_EN = 0; + USBSSD->UEP_RX_EN = 0; +} + +bool dcd_edpt_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t *buffer, uint16_t total_bytes, bool is_isr) { + (void)is_isr; +#if CFG_TUD_WCH_USB30_FALLBACK + if (fb_state == FB_USB2_ACTIVE) { return ch32h417_usb2_edpt_xfer(rhport, ep_addr, buffer, total_bytes); } +#endif + const uint8_t ep_num = tu_edpt_number(ep_addr); + const tusb_dir_t dir = tu_edpt_dir(ep_addr); + + xfer_ctl_t *xfer = XFER_CTL_BASE(ep_num, dir); + xfer->buffer = buffer; + xfer->total_len = total_bytes; + xfer->queued_len = 0; + xfer->valid = true; + + if (ep_num == 0) { + if (dir == TUSB_DIR_IN) { + if (total_bytes == 0) { + // ZLP status for a control-OUT/no-data transfer + USBSSD->UEP0_TX_CTRL = USBSS_EP0_TX_DPH; + USBSSD->UEP0_TX_CTRL |= USBSS_EP0_TX_ERDY; + ep0_arm_out(); + } else { + handle_ep0_in(rhport); + } + } else { + if (total_bytes == 0) { + ep0_arm_out(); + } else { + ep0_arm_out(); + } + } + } else if (dir == TUSB_DIR_IN) { + queue_in_packet(ep_num, xfer); + } else { + queue_out_packet(ep_num, xfer); + } + return true; +} + +void dcd_edpt_stall(uint8_t rhport, uint8_t ep_addr) { +#if CFG_TUD_WCH_USB30_FALLBACK + if (fb_state == FB_USB2_ACTIVE) { ch32h417_usb2_edpt_stall(rhport, ep_addr); return; } +#endif + (void)rhport; + const uint8_t ep_num = tu_edpt_number(ep_addr); + const tusb_dir_t dir = tu_edpt_dir(ep_addr); + if (ep_num == 0) { + USBSSD->UEP0_TX_CTRL = USBSS_EP0_TX_STALL; + USBSSD->UEP0_RX_CTRL = USBSS_EP0_RX_ERDY | USBSS_EP0_RX_STALL; + } else if (dir == TUSB_DIR_IN) { + usbss_ep_tx(ep_num)->UEP_TX_CR |= USBSS_EP_TX_HALT; + } else { + usbss_ep_rx(ep_num)->UEP_RX_CR |= USBSS_EP_RX_HALT; + } +} + +void dcd_edpt_clear_stall(uint8_t rhport, uint8_t ep_addr) { +#if CFG_TUD_WCH_USB30_FALLBACK + if (fb_state == FB_USB2_ACTIVE) { ch32h417_usb2_edpt_clear_stall(rhport, ep_addr); return; } +#endif + (void)rhport; + const uint8_t ep_num = tu_edpt_number(ep_addr); + const tusb_dir_t dir = tu_edpt_dir(ep_addr); + if (ep_num == 0) { + return; + } + if (dir == TUSB_DIR_IN) { + volatile USBSS_EP_TX_TypeDef *tx = usbss_ep_tx(ep_num); + tx->UEP_TX_CR = (uint8_t)(USBSS_EP_TX_CLR | USBSS_EP_TX_CHAIN_CLR); + tx->UEP_TX_CR = CFG_TUD_WCH_USB30_MAX_BURST; + xfer_ctl_t *xfer = XFER_CTL_BASE(ep_num, TUSB_DIR_IN); + if (xfer->valid) { + xfer->queued_len = 0; + queue_in_packet(ep_num, xfer); + } + } else { + volatile USBSS_EP_RX_TypeDef *rx = usbss_ep_rx(ep_num); + rx->UEP_RX_CR = (uint8_t)(USBSS_EP_RX_CLR | USBSS_EP_RX_CHAIN_CLR); + rx->UEP_RX_CR = CFG_TUD_WCH_USB30_MAX_BURST; + xfer_ctl_t *xfer = XFER_CTL_BASE(ep_num, TUSB_DIR_OUT); + if (xfer->valid) { + queue_out_packet(ep_num, xfer); + } + } +} #endif |
