mirror of
https://github.com/hathach/tinyusb.git
synced 2026-08-18 11:02:16 +00:00
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) <noreply@anthropic.com>
This commit is contained in:
5
hw/bsp/ch58x/boards/ch582m_evt/board.cmake
Normal file
5
hw/bsp/ch58x/boards/ch582m_evt/board.cmake
Normal file
@ -0,0 +1,5 @@
|
||||
set(LD_FLASH_SIZE 448K)
|
||||
set(LD_RAM_SIZE 32K)
|
||||
|
||||
function(update_board TARGET)
|
||||
endfunction()
|
||||
61
hw/bsp/ch58x/boards/ch582m_evt/board.h
Normal file
61
hw/bsp/ch58x/boards/ch582m_evt/board.h
Normal file
@ -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
|
||||
3
hw/bsp/ch58x/boards/ch582m_evt/board.mk
Normal file
3
hw/bsp/ch58x/boards/ch582m_evt/board.mk
Normal file
@ -0,0 +1,3 @@
|
||||
LDFLAGS += \
|
||||
-Wl,--defsym=__FLASH_SIZE=448K \
|
||||
-Wl,--defsym=__RAM_SIZE=32K \
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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 \
|
||||
|
||||
@ -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
|
||||
|
||||
//--------------------------------------------------------------------+
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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 */
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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 */
|
||||
@ -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 */
|
||||
Reference in New Issue
Block a user