/* * SPDX-FileCopyrightText: Copyright (c) 2019 Nathan Conrad * SPDX-FileCopyrightText: Copyright (c) 2024, Ha Thach (tinyusb.org) * SPDX-FileCopyrightText: Copyright (c) 2025, HiFiPhile (Zixun LI) * SPDX-License-Identifier: MIT * * This file is part of the TinyUSB stack. */ #ifndef TUSB_FSDEV_COMMON_H #define TUSB_FSDEV_COMMON_H #ifdef __cplusplus extern "C" { #endif #include "common/tusb_common.h" #if CFG_TUD_ENABLED #include "device/dcd.h" #endif #if CFG_TUH_ENABLED #include "host/hcd.h" #endif //--------------------------------------------------------------------+ // FSDEV Register Bit Definitions // Vendor-independent definitions with U_ prefix to avoid conflicts. // Based on the common USB FSDEV IP block register layout. // Lower 16 bits are shared across all variants (STM32, CH32, AT32). // Upper 16 bits (DRD extensions) only exist on 32-bit DRD MCUs. //--------------------------------------------------------------------+ // EPnR / CHEPnR - Endpoint/Channel Register // DTOG and STAT bits are toggle-on-write-1. CTR bits are clear-on-write-0. // // 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 // CTR_RX DTOG_RX STAT_RX[1:0] SETUP EP_TYPE[1:0] KIND CTR_TX DTOG_TX STAT_TX[1:0] EA[3:0] // // DRD 32-bit only (C0, G0, H5, U0, U5): // 31:27 26 25 24 23 22 21 20 19 18 17 16 // Rsvd ERR_RX ERR_TX LSEP NAK DEVADDR[6:0] #define U_EP_CTR_RX 0x8000u #define U_EP_DTOG_RX 0x4000u #define U_EPRX_STAT 0x3000u #define U_EP_SETUP 0x0800u #define U_EP_T_FIELD 0x0600u #define U_EP_KIND 0x0100u #define U_EP_CTR_TX 0x0080u #define U_EP_DTOG_TX 0x0040u #define U_EPTX_STAT 0x0030u #define U_EPADDR_FIELD 0x000Fu // DRD 32-bit upper bits #define U_EP_ERRRX 0x04000000u #define U_EP_ERRTX 0x02000000u #define U_EP_LSEP 0x01000000u #define U_EP_NAK 0x00800000u #define U_EP_DEVADDR 0x007F0000u #define U_EP_DEVADDR_Pos 16u // Endpoint types (EP_TYPE field values) #define U_EP_BULK 0x0000u #define U_EP_CONTROL 0x0200u #define U_EP_ISOCHRONOUS 0x0400u #define U_EP_INTERRUPT 0x0600u #define U_EP_TYPE_MASK (U_EP_T_FIELD) // EP register mask components (non-toggle bits preserved during read-modify-write) // Excludes DTOG_RX, STAT_RX, DTOG_TX, STAT_TX (toggle-on-write-1) #define U_EPREG_MASK_16 (U_EP_CTR_RX | U_EP_SETUP | U_EP_T_FIELD | U_EP_KIND | U_EP_CTR_TX | U_EPADDR_FIELD) #define U_EPREG_MASK_32 (U_EP_ERRRX | U_EP_ERRTX | U_EP_LSEP | U_EP_NAK | U_EP_DEVADDR | U_EPREG_MASK_16) // EP register mask selection based on bus width #ifdef CFG_TUSB_FSDEV_32BIT #define U_EPREG_MASK U_EPREG_MASK_32 #else #define U_EPREG_MASK U_EPREG_MASK_16 #endif #define U_EPKIND_MASK ((uint32_t)(~U_EP_KIND) & U_EPREG_MASK) #define U_EPTX_DTOGMASK (U_EPTX_STAT | U_EPREG_MASK) #define U_EPRX_DTOGMASK (U_EPRX_STAT | U_EPREG_MASK) // Bit positions #define U_EPTX_STAT_Pos 4u #define U_EP_DTOG_TX_Pos 6u #define U_EP_CTR_TX_Pos 7u // Data toggle helpers #define U_EPTX_DTOG1 0x0010u #define U_EPTX_DTOG2 0x0020u #define U_EPRX_DTOG1 0x1000u #define U_EPRX_DTOG2 0x2000u // CNTR - Control Register // 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 // CTRM PMAOVRM ERRM WKUPM SUSPM RESETM SOFM ESOFM Rsvd Rsvd Rsvd RESUME FSUSP LPMODE PDWN FRES // // DRD 32-bit only: // 31 30:16 // HOST Rsvd #define U_CNTR_CTRM 0x8000u #define U_CNTR_PMAOVRM 0x4000u #define U_CNTR_ERRM 0x2000u #define U_CNTR_WKUPM 0x1000u #define U_CNTR_SUSPM 0x0800u #define U_CNTR_RESETM 0x0400u #define U_CNTR_SOFM 0x0200u #define U_CNTR_ESOFM 0x0100u #define U_CNTR_RESUME 0x0010u #define U_CNTR_FSUSP 0x0008u #define U_CNTR_LPMODE 0x0004u #define U_CNTR_PDWN 0x0002u #define U_CNTR_FRES 0x0001u #define U_CNTR_HOST 0x80000000u // DRD: enable host mode #define U_CNTR_DCON 0x0400u // DRD host: same bit as RESETM // ISTR - Interrupt Status Register // 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 // CTR PMAOVR ERR WKUP SUSP RESET SOF ESOF Rsvd Rsvd Rsvd DIR EP_ID[3:0] // // DRD 32-bit only: // 31 30 29 28:16 // Rsvd LS_DCONN DCON_STAT Rsvd #define U_ISTR_CTR 0x8000u #define U_ISTR_PMAOVR 0x4000u #define U_ISTR_ERR 0x2000u #define U_ISTR_WKUP 0x1000u #define U_ISTR_SUSP 0x0800u #define U_ISTR_RESET 0x0400u #define U_ISTR_SOF 0x0200u #define U_ISTR_ESOF 0x0100u #define U_ISTR_DIR 0x0010u #define U_ISTR_EP_ID 0x000Fu #define U_ISTR_LS_DCONN 0x40000000u // DRD: low-speed device connected #define U_ISTR_DCON_STAT 0x20000000u // DRD: device connection status #define U_ISTR_DCON 0x0400u // DRD host: same bit as RESET // FNR - Frame Number Register (read-only) // 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 // RXDP RXDM LCK[2:0] FN[10:0] #define U_FNR_RXDP 0x8000u #define U_FNR_RXDM 0x4000u #define U_FNR_FN 0x07FFu // DADDR - Device Address Register // 15:8 7 6 5 4 3 2 1 0 // Rsvd EF ADD[6:0] #define U_DADDR_EF 0x80u // LPMCSR - LPM Control and Status Register // Supported: STM32 F0, L0, L4, G0, G4, C0, H5, U0, WB. Not on: F1, F3, AT32, CH32. // 15:8 7 6 5 4 3 2 1 0 // Rsvd BESL[3:0] Rsvd REMWAKE Rsvd LPMACK LMPEN #define U_LPMCSR_LMPEN 0x0001u #define U_LPMCSR_LPMACK 0x0002u #define U_LPMCSR_REMWAKE 0x0008u #define U_LPMCSR_BESL 0x00F0u // BCDR - Battery Charging Detector Register // Supported: STM32 F0, L0, L4, G0, G4, C0, H5, U0, WB. Not on: F1, F3, AT32, CH32. // 15 14:8 7 6 5 4 3 2 1 0 // DPPU Rsvd PS2DET SDET PDET DCDET SDEN PDEN DCDEN BCDEN #define U_BCDR_BCDEN 0x0001u #define U_BCDR_DCDEN 0x0002u #define U_BCDR_PDEN 0x0004u #define U_BCDR_SDEN 0x0008u #define U_BCDR_DCDET 0x0010u #define U_BCDR_PDET 0x0020u #define U_BCDR_SDET 0x0040u #define U_BCDR_PS2DET 0x0080u #define U_BCDR_DPPU 0x8000u // Channel status (DRD host mode, reuses STAT_TX/STAT_RX bit positions) #define U_CH_TX_STTX 0x0030u #define U_CH_TX_ACK_SBUF 0x0000u #define U_CH_TX_STALL 0x0010u #define U_CH_TX_NAK 0x0020u #define U_CH_RX_STRX 0x3000u #define U_CH_RX_ACK_SBUF 0x0000u #define U_CH_RX_STALL 0x1000u #define U_CH_RX_NAK 0x2000u #define U_CH_RX_VALID 0x3000u //--------------------------------------------------------------------+ // Registers Typedef //--------------------------------------------------------------------+ // hardware limit endpoint #define FSDEV_EP_COUNT 8 // The fsdev_bus_t type can be used for both register and PMA access necessities #ifdef CFG_TUSB_FSDEV_32BIT typedef uint32_t fsdev_bus_t; #else typedef uint16_t fsdev_bus_t; #endif // volatile 32-bit aligned #define _va32 volatile TU_ATTR_ALIGNED(4) typedef struct { struct { _va32 fsdev_bus_t reg; } ep[FSDEV_EP_COUNT]; _va32 uint32_t RESERVED7[8]; // Reserved _va32 fsdev_bus_t CNTR; // 40: Control register _va32 fsdev_bus_t ISTR; // 44: Interrupt status register _va32 fsdev_bus_t FNR; // 48: Frame number register _va32 fsdev_bus_t DADDR; // 4C: Device address register _va32 fsdev_bus_t BTABLE; // 50: Buffer Table address register _va32 fsdev_bus_t LPMCSR; // 54: LPM Control and Status (not on F1, F3, AT32, CH32) _va32 fsdev_bus_t BCDR; // 58: Battery Charging Detector (not on F1, F3, AT32, CH32) } fsdev_regs_t; TU_VERIFY_STATIC(offsetof(fsdev_regs_t, CNTR) == 0x40, "Wrong offset"); TU_VERIFY_STATIC(sizeof(fsdev_regs_t) == 0x5C, "Size is not correct"); #define FSDEV_REG ((fsdev_regs_t *)FSDEV_REG_BASE) //--------------------------------------------------------------------+ // BTable and PMA Access //--------------------------------------------------------------------+ // If sharing with CAN, one can set this to be non-zero to give CAN space where it wants it // Both of these MUST be a multiple of 2, and are in byte units. #ifndef FSDEV_BTABLE_BASE #define FSDEV_BTABLE_BASE 0U #endif TU_VERIFY_STATIC((FSDEV_BTABLE_BASE & 0x7) == 0, "BTABLE base must be aligned to 8 bytes"); #define FSDEV_ADDR_DATA_RATIO (CFG_TUSB_FIFO_HWFIFO_ADDR_STRIDE/CFG_TUSB_FIFO_HWFIFO_DATA_STRIDE) // Need alignment when access address is 32 bit but data is only 16-bit #if FSDEV_ADDR_DATA_RATIO == 2 #define fsdev_addr_data_align TU_ATTR_ALIGNED(4) #else #define fsdev_addr_data_align #endif enum { BTABLE_BUF_TX = 0, BTABLE_BUF_RX = 1 }; // Buffer Table is located in Packet Memory Area (PMA) and therefore its address access is forced to either // 16-bit or 32-bit depending on CFG_TUSB_FSDEV_32BIT. // 0: TX (IN), 1: RX (OUT) typedef union { // data is strictly 16-bit access (address could be 32-bit aligned) struct { volatile fsdev_addr_data_align uint16_t addr; volatile fsdev_addr_data_align uint16_t count; } ep16[FSDEV_EP_COUNT][2]; // strictly 32-bit access struct { volatile uint32_t count_addr; } ep32[FSDEV_EP_COUNT][2]; } fsdev_btable_t; TU_VERIFY_STATIC(sizeof(fsdev_btable_t) == FSDEV_EP_COUNT * 8 * FSDEV_ADDR_DATA_RATIO, "size is not correct"); TU_VERIFY_STATIC(FSDEV_BTABLE_BASE + FSDEV_EP_COUNT * 8 <= CFG_TUSB_FSDEV_PMA_SIZE, "BTABLE does not fit in PMA RAM"); #define FSDEV_BTABLE ((volatile fsdev_btable_t *)(FSDEV_PMA_BASE + FSDEV_ADDR_DATA_RATIO * FSDEV_BTABLE_BASE)) typedef struct { volatile fsdev_addr_data_align fsdev_bus_t value; } fsdev_pma_buf_t; #define PMA_BUF_AT(_addr) ((fsdev_pma_buf_t *)(FSDEV_PMA_BASE + FSDEV_ADDR_DATA_RATIO * (_addr))) //--------------------------------------------------------------------+ // Vendor-specific includes //--------------------------------------------------------------------+ #if defined(TUP_USBIP_FSDEV_STM32) #include "fsdev_stm32.h" #elif defined(TUP_USBIP_FSDEV_CH32) #include "fsdev_ch32.h" #elif defined(TUP_USBIP_FSDEV_AT32) #include "fsdev_at32.h" #else #error "Unknown USB IP" #endif //--------------------------------------------------------------------+ // Endpoint Helper // - CTR is write 0 to clear // - DTOG and STAT are write 1 to toggle //--------------------------------------------------------------------+ typedef enum { EP_STAT_DISABLED = 0, EP_STAT_STALL = 1, EP_STAT_NAK = 2, EP_STAT_VALID = 3 } ep_stat_t; #define EP_STAT_MASK(_dir) (3u << (U_EPTX_STAT_Pos + ((_dir) == TUSB_DIR_IN ? 0 : 8))) #define EP_DTOG_MASK(_dir) (1u << (U_EP_DTOG_TX_Pos + ((_dir) == TUSB_DIR_IN ? 0 : 8))) #define CH_STAT_MASK(_dir) (3u << (U_EPTX_STAT_Pos + ((_dir) == TUSB_DIR_IN ? 8 : 0))) #define CH_DTOG_MASK(_dir) (1u << (U_EP_DTOG_TX_Pos + ((_dir) == TUSB_DIR_IN ? 8 : 0))) TU_ATTR_ALWAYS_INLINE static inline uint32_t ep_read(uint32_t ep_id) { return FSDEV_REG->ep[ep_id].reg; } TU_ATTR_ALWAYS_INLINE static inline void ep_write(uint32_t ep_id, uint32_t value, bool need_exclusive) { if (need_exclusive) { fsdev_int_disable(0); } FSDEV_REG->ep[ep_id].reg = (fsdev_bus_t)value; if (need_exclusive) { fsdev_int_enable(0); } } TU_ATTR_ALWAYS_INLINE static inline void ep_write_clear_ctr(uint32_t ep_id, tusb_dir_t dir) { uint32_t reg = FSDEV_REG->ep[ep_id].reg; reg |= U_EP_CTR_TX | U_EP_CTR_RX; reg &= U_EPREG_MASK; reg &= ~(1u << (U_EP_CTR_TX_Pos + (dir == TUSB_DIR_IN ? 0u : 8u))); ep_write(ep_id, reg, false); } TU_ATTR_ALWAYS_INLINE static inline void ep_change_status(uint32_t *reg, tusb_dir_t dir, ep_stat_t state) { *reg ^= (state << (U_EPTX_STAT_Pos + (dir == TUSB_DIR_IN ? 0 : 8))); } TU_ATTR_ALWAYS_INLINE static inline void ep_change_dtog(uint32_t *reg, tusb_dir_t dir, uint8_t state) { *reg ^= (state << (U_EP_DTOG_TX_Pos + (dir == TUSB_DIR_IN ? 0 : 8))); } TU_ATTR_ALWAYS_INLINE static inline bool ep_is_iso(uint32_t reg) { return (reg & U_EP_TYPE_MASK) == U_EP_ISOCHRONOUS; } //--------------------------------------------------------------------+ // Channel Helper // - Direction is opposite to endpoint direction //--------------------------------------------------------------------+ TU_ATTR_ALWAYS_INLINE static inline uint32_t ch_read(uint32_t ch_id) { return ep_read(ch_id); } TU_ATTR_ALWAYS_INLINE static inline void ch_write(uint32_t ch_id, uint32_t value, bool need_exclusive) { ep_write(ch_id, value, need_exclusive); } TU_ATTR_ALWAYS_INLINE static inline void ch_write_clear_ctr(uint32_t ch_id, tusb_dir_t dir) { uint32_t reg = FSDEV_REG->ep[ch_id].reg; reg |= U_EP_CTR_TX | U_EP_CTR_RX; reg &= U_EPREG_MASK; reg &= ~(1u << (U_EP_CTR_TX_Pos + (dir == TUSB_DIR_IN ? 8u : 0u))); ep_write(ch_id, reg, false); } TU_ATTR_ALWAYS_INLINE static inline void ch_change_status(uint32_t *reg, tusb_dir_t dir, ep_stat_t state) { *reg ^= (state << (U_EPTX_STAT_Pos + (dir == TUSB_DIR_IN ? 8 : 0))); } TU_ATTR_ALWAYS_INLINE static inline void ch_change_dtog(uint32_t *reg, tusb_dir_t dir, uint8_t state) { *reg ^= (state << (U_EP_DTOG_TX_Pos + (dir == TUSB_DIR_IN ? 8 : 0))); } //--------------------------------------------------------------------+ // BTable Helper //--------------------------------------------------------------------+ TU_ATTR_ALWAYS_INLINE static inline uint32_t btable_get_addr(uint32_t ep_id, uint8_t buf_id) { #ifdef CFG_TUSB_FSDEV_32BIT return FSDEV_BTABLE->ep32[ep_id][buf_id].count_addr & 0x0000FFFFu; #else return FSDEV_BTABLE->ep16[ep_id][buf_id].addr; #endif } TU_ATTR_ALWAYS_INLINE static inline void btable_set_addr(uint32_t ep_id, uint8_t buf_id, uint16_t addr) { #ifdef CFG_TUSB_FSDEV_32BIT uint32_t count_addr = FSDEV_BTABLE->ep32[ep_id][buf_id].count_addr; count_addr = (count_addr & 0xFFFF0000u) | (addr & 0x0000FFFCu); FSDEV_BTABLE->ep32[ep_id][buf_id].count_addr = count_addr; #else FSDEV_BTABLE->ep16[ep_id][buf_id].addr = addr; #endif } TU_ATTR_ALWAYS_INLINE static inline uint16_t btable_get_count(uint32_t ep_id, uint8_t buf_id) { uint16_t count; #ifdef CFG_TUSB_FSDEV_32BIT count = (FSDEV_BTABLE->ep32[ep_id][buf_id].count_addr >> 16); #else count = FSDEV_BTABLE->ep16[ep_id][buf_id].count; #endif return count & 0x3FFU; } TU_ATTR_ALWAYS_INLINE static inline void btable_set_count(uint32_t ep_id, uint8_t buf_id, uint16_t byte_count) { #ifdef CFG_TUSB_FSDEV_32BIT uint32_t count_addr = FSDEV_BTABLE->ep32[ep_id][buf_id].count_addr; count_addr = (count_addr & ~0x03FF0000u) | ((byte_count & 0x3FFu) << 16); FSDEV_BTABLE->ep32[ep_id][buf_id].count_addr = count_addr; #else uint16_t cnt = FSDEV_BTABLE->ep16[ep_id][buf_id].count; cnt = (cnt & ~0x3FFU) | (byte_count & 0x3FFU); FSDEV_BTABLE->ep16[ep_id][buf_id].count = cnt; #endif } // Reset the USB Core void fsdev_core_reset(void); // De-initialize the USB Core void fsdev_deinit(void); // Aligned buffer size according to hardware uint16_t pma_align_buffer_size(uint16_t size, uint8_t *blsize, uint8_t *num_block); // Set RX buffer size void btable_set_rx_bufsize(uint32_t ep_id, uint8_t buf_id, uint16_t wCount); #ifdef __cplusplus } #endif #endif /* TUSB_FSDEV_COMMON_H */