diff --git a/os/common/startup/ARMCMx/compilers/GCC/mk/startup_ch579m.mk b/os/common/startup/ARMCMx/compilers/GCC/mk/startup_ch579m.mk new file mode 100644 index 0000000000..4f249ef84f --- /dev/null +++ b/os/common/startup/ARMCMx/compilers/GCC/mk/startup_ch579m.mk @@ -0,0 +1,30 @@ +# CH579M startup files (ARM Cortex-M0, ARMv6-M). +# +# Uses the generic ChibiOS ARMv6-M startup (crt0_v6m.S + vectors.S) +# with WCH's CH579M CMSIS device header for the vector table layout. +# +# The system_ch579m.c provides SystemInit() which is called from crt0; +# our hal_lld_init() then does the PLL switch in __early_init(). + +STARTUPSRC = $(CHIBIOS)/os/common/startup/ARMCMx/compilers/GCC/crt1.c + +STARTUPASM = $(CHIBIOS)/os/common/startup/ARMCMx/compilers/GCC/crt0_v6m.S \ + $(CHIBIOS)/os/common/startup/ARMCMx/compilers/GCC/vectors.S + +STARTUPINC = $(CHIBIOS)/os/common/portability/GCC \ + $(CHIBIOS)/os/common/startup/ARMCMx/compilers/GCC \ + $(CHIBIOS)/os/common/startup/ARMCMx/compilers/GCC/ld \ + $(CHIBIOS)/os/common/ext/ARM/CMSIS/Core/Include \ + $(CHIBIOS_CONTRIB)/os/common/startup/ARMCMx/devices/CH579 \ + $(CHIBIOS_CONTRIB)/os/hal/ports/WCH/CH579 \ + $(KEYBOARD_PATH_1)/ld + +STARTUPLD = $(CHIBIOS)/os/common/startup/ARMCMx/compilers/GCC/ld +STARTUPLD_CONTRIB = $(KEYBOARD_PATH_1)/ld + +LDFLAGS += -L$(KEYBOARD_PATH_1)/ld + +# Shared variables +ALLXASMSRC += $(STARTUPASM) +ALLCSRC += $(STARTUPSRC) +ALLINC += $(STARTUPINC) diff --git a/os/common/startup/ARMCMx/devices/CH579/cmparams.h b/os/common/startup/ARMCMx/devices/CH579/cmparams.h new file mode 100644 index 0000000000..3752365d04 --- /dev/null +++ b/os/common/startup/ARMCMx/devices/CH579/cmparams.h @@ -0,0 +1,48 @@ +/* + * ChibiOS - CH579M Cortex-M0 CMSIS parameters + * + * CH579M interrupt vector count: 26 external IRQs from the datasheet, + * rounded up to the next multiple of 8 → 32. + */ + +#ifndef CMPARAMS_H +#define CMPARAMS_H + +/** @brief Cortex core model: 0 = Cortex-M0. */ +#define CORTEX_MODEL 0 + +/** @brief No FPU on Cortex-M0. */ +#define CORTEX_HAS_FPU 0 + +/** + * @brief Number of bits in priority masks. + * @note Cortex-M0 only implements the top 2 bits of the priority field. + */ +#define CORTEX_PRIORITY_BITS 2 + +/** + * @brief Number of interrupt vectors. + * @note Does NOT include the 16 system vectors. Must be a multiple of 8. + * CH579M has 26 external IRQs → rounded to 32. + */ +#define CORTEX_NUM_VECTORS 32 + +#if !defined(_FROM_ASM_) + +#include "board.h" + +/* Pull in the device register header solely to verify parameters match. */ +#include "CH579.h" + +#if CORTEX_MODEL != __CORTEX_M +#error "CMSIS __CORTEX_M mismatch — verify CH579.h defines __CORTEX_M 0" +#endif + +#if CORTEX_PRIORITY_BITS != __NVIC_PRIO_BITS +#error \ + "CMSIS __NVIC_PRIO_BITS mismatch — verify CH579.h defines __NVIC_PRIO_BITS 2" +#endif + +#endif /* !defined(_FROM_ASM_) */ + +#endif /* CMPARAMS_H */ diff --git a/os/hal/ports/WCH/CH579/CH579.h b/os/hal/ports/WCH/CH579/CH579.h new file mode 100644 index 0000000000..7a325fea29 --- /dev/null +++ b/os/hal/ports/WCH/CH579/CH579.h @@ -0,0 +1,362 @@ +#ifndef CH579_H +#define CH579_H + +#include + +/* ── CMSIS Cortex-M core parameters (required by ChibiOS ARMv6-M port) ── */ +#define __CORTEX_M (0U) /* Cortex-M0 */ +#define __NVIC_PRIO_BITS 2 /* CM0: only top 2 bits of priority used */ +#define __CM0_REV 0 /* Core revision */ +#define __MPU_PRESENT 0 /* No MPU */ +#define __VTOR_PRESENT 0 /* No VTOR on basic CM0 */ +#define __FPU_PRESENT 0 /* No FPU */ + +typedef enum { + /* ── Cortex-M0 Exceptions ── */ + NonMaskableInt_IRQn = -14, + HardFault_IRQn = -13, + SVCall_IRQn = -5, + PendSV_IRQn = -2, + SysTick_IRQn = -1, + /* ── CH579M Specific Interrupts ── */ + TMR0_IRQn = 0, + USB_IRQn = 6, /* vector address 0x0058 (datasheet Table 3-1) */ +} IRQn_Type; + +#include "core_cm0.h" + +/* ── Safe-access unlock (required before writing RWA-protected regs) ── */ +/* Address 0x40001040 per datasheet Table 4-1. + * Window is valid for ~16 system clock cycles after writing 0xA8. */ +#define R8_SAFE_ACCESS_SIG (*((volatile uint8_t *)0x40001040)) +#define SAFE_ACCESS_OPEN() \ + do { \ + R8_SAFE_ACCESS_SIG = 0x57; \ + R8_SAFE_ACCESS_SIG = 0xA8; \ + } while (0) +#define SAFE_ACCESS_CLOSE() \ + do { \ + R8_SAFE_ACCESS_SIG = 0x00; \ + } while (0) + +/* ── System clock (safe-access protected) ───────────────────────── */ +/* R16_CLK_SYS_CFG at 0x40001008 (datasheet Table 6-1). + * Low byte (0x40001008): bits[7:6]=RB_CLK_SYS_MOD, bits[4:0]=RB_CLK_PLL_DIV + * High byte (0x40001009): bit1=RB_CLK_OSC32M_XT (0=HSI, 1=external crystal) + * + * For 48 MHz: Fpll=480 MHz, CLK_PLL_DIV=10 → Fsys=48 MHz. + * Write 0x4A to low byte: (0b01<<6)|(10) = 0x40|0x0A = 0x4A */ +#define R8_CLK_PLL_DIV (*((volatile uint8_t *)0x40001008)) /* low byte */ +#define R8_CLK_SYS_CFG_H (*((volatile uint8_t *)0x40001009)) /* high byte */ +#define RB_CLK_SYS_MOD (3 << 6) +#define CLK_SYS_MOD_PLL (1 << 6) /* 0b01 = PLL source (datasheet §6.2) */ +#define RB_CLK_OSC32M_XT (1 << 1) /* select external XT32M as CK32M source */ + +/* ── HF clock power control (safe-access protected, 0x4000100A) ─── */ +#define R8_HFCK_PWR_CTRL (*((volatile uint8_t *)0x4000100A)) +#define RB_CLK_PLL_PON (1 << 4) /* power on PLL (off at reset) */ +#define RB_CLK_XT32M_PON (1 << 2) /* power on external 32 MHz oscillator */ + +/* ── External 32 MHz crystal tuning (safe-access protected, 0x4000104E) ─── */ +/* Reset value 0x31 = C_LOAD=011b (16 pF) | I_BIAS=01b (rated current). + * + * bits [6:4] RB_XT32M_C_LOAD: built-in load capacitor. + * Capacitance = RB_XT32M_C_LOAD * 2 + 10 pF. 000b=10pF … 111b=24pF. + * Use _10PF when the PCB has external load capacitors on the crystal pins + * (otherwise total load = internal + external, shifting oscillation frequency). + * Use _24PF when crystal pins connect directly to chip with no external caps. + * + * bits [1:0] RB_XT32M_I_BIAS: oscillator bias current. + * 00=75 % 01=rated (reset) 10=125 % 11=150 % */ +#define R8_XT32M_TUNE (*((volatile uint8_t *)0x4000104E)) +#define RB_XT32M_C_LOAD_MASK (0x7 << 4) +#define RB_XT32M_C_LOAD_10PF (0x0 << 4) /* 000b → 10 pF (use with external load caps) */ +#define RB_XT32M_C_LOAD_16PF (0x3 << 4) /* 011b → 16 pF (reset default) */ +#define RB_XT32M_C_LOAD_24PF (0x7 << 4) /* 111b → 24 pF (no external load caps) */ +#define RB_XT32M_I_BIAS_MASK (0x3) +#define RB_XT32M_I_BIAS_75 (0x0) /* 75% rated */ +#define RB_XT32M_I_BIAS_100 (0x1) /* 100% rated (reset default) */ +#define RB_XT32M_I_BIAS_125 (0x2) /* 125% rated */ +#define RB_XT32M_I_BIAS_150 (0x3) /* 150% rated */ + +/* ── PLL lock status (read-only, 0x40001053) ────────────────────── */ +#define R8_PLL_CONFIG (*((volatile uint8_t *)0x40001053)) +#define RB_PLL_LOCKED (1 << 7) /* RO: 1 when PLL is locked */ + +/* ── Sleep / clock-gate control (safe-access protected) ─────────── */ +/* bit=1 gates the clock OFF; clear to enable (datasheet Table 5-1). */ +#define R8_SLP_CLK_OFF0 (*((volatile uint8_t *)0x4000100C)) /* UART/TMR gates */ +#define R8_SLP_CLK_OFF1 (*((volatile uint8_t *)0x4000100D)) +#define RB_SLP_CLK_USB (1 << 4) /* USB clock gate in OFF1 (0=on at reset) */ + +/* ── GPIO ────────────────────────────────────────────────────────── */ +typedef struct { + volatile uint32_t DIR; /* direction: 1=output */ + volatile uint32_t PIN; /* input value */ + volatile uint32_t OUT; /* output latch */ + volatile uint32_t CLR; /* atomic bit clear */ + volatile uint32_t PU; /* pull-up enable */ + volatile uint32_t PD_DRV; /* pull-down / drive strength */ +} GPIO_TypeDef; + +#define GPIOA_BASE 0x400010A0UL +#define GPIOB_BASE 0x400010C0UL + +#define GPIOA ((GPIO_TypeDef *)GPIOA_BASE) +#define GPIOB ((GPIO_TypeDef *)GPIOB_BASE) + +/* Datasheet register names (CH579DS1, GPIO chapter / register table). */ +#define R32_PA_DIR (*((volatile uint32_t *)0x400010A0)) +#define R32_PA_PIN (*((volatile uint32_t *)0x400010A4)) +#define R32_PA_OUT (*((volatile uint32_t *)0x400010A8)) +#define R32_PA_CLR (*((volatile uint32_t *)0x400010AC)) +#define R32_PA_PU (*((volatile uint32_t *)0x400010B0)) +#define R32_PA_PD_DRV (*((volatile uint32_t *)0x400010B4)) + +#define R32_PB_DIR (*((volatile uint32_t *)0x400010C0)) +#define R32_PB_PIN (*((volatile uint32_t *)0x400010C4)) +#define R32_PB_OUT (*((volatile uint32_t *)0x400010C8)) +#define R32_PB_CLR (*((volatile uint32_t *)0x400010CC)) +#define R32_PB_PU (*((volatile uint32_t *)0x400010D0)) +#define R32_PB_PD_DRV (*((volatile uint32_t *)0x400010D4)) + +#define GPIOA_VALID_PINS_MASK 0x0000FFFFUL +#define GPIOB_VALID_PINS_MASK 0x00FFFFFFUL + +#define PA0 0U +#define PA1 1U +#define PA2 2U +#define PA3 3U +#define PA4 4U +#define PA5 5U +#define PA6 6U +#define PA7 7U +#define PA8 8U +#define PA9 9U +#define PA10 10U +#define PA11 11U +#define PA12 12U +#define PA13 13U +#define PA14 14U +#define PA15 15U + +#define PB0 0U +#define PB1 1U +#define PB2 2U +#define PB3 3U +#define PB4 4U +#define PB5 5U +#define PB6 6U +#define PB7 7U +#define PB8 8U +#define PB9 9U +#define PB10 10U +#define PB11 11U +#define PB12 12U +#define PB13 13U +#define PB14 14U +#define PB15 15U +#define PB16 16U +#define PB17 17U +#define PB18 18U +#define PB19 19U +#define PB20 20U +#define PB21 21U +#define PB22 22U +#define PB23 23U + +#define PA0_MASK (1UL << 0) +#define PA1_MASK (1UL << 1) +#define PA2_MASK (1UL << 2) +#define PA3_MASK (1UL << 3) +#define PA4_MASK (1UL << 4) +#define PA5_MASK (1UL << 5) +#define PA6_MASK (1UL << 6) +#define PA7_MASK (1UL << 7) +#define PA8_MASK (1UL << 8) +#define PA9_MASK (1UL << 9) +#define PA10_MASK (1UL << 10) +#define PA11_MASK (1UL << 11) +#define PA12_MASK (1UL << 12) +#define PA13_MASK (1UL << 13) +#define PA14_MASK (1UL << 14) +#define PA15_MASK (1UL << 15) + +#define PB0_MASK (1UL << 0) +#define PB1_MASK (1UL << 1) +#define PB2_MASK (1UL << 2) +#define PB3_MASK (1UL << 3) +#define PB4_MASK (1UL << 4) +#define PB5_MASK (1UL << 5) +#define PB6_MASK (1UL << 6) +#define PB7_MASK (1UL << 7) +#define PB8_MASK (1UL << 8) +#define PB9_MASK (1UL << 9) +#define PB10_MASK (1UL << 10) +#define PB11_MASK (1UL << 11) +#define PB12_MASK (1UL << 12) +#define PB13_MASK (1UL << 13) +#define PB14_MASK (1UL << 14) +#define PB15_MASK (1UL << 15) +#define PB16_MASK (1UL << 16) +#define PB17_MASK (1UL << 17) +#define PB18_MASK (1UL << 18) +#define PB19_MASK (1UL << 19) +#define PB20_MASK (1UL << 20) +#define PB21_MASK (1UL << 21) +#define PB22_MASK (1UL << 22) +#define PB23_MASK (1UL << 23) + +#define LINE_PA0 PAL_LINE(IOPORTA, PA0) +#define LINE_PA1 PAL_LINE(IOPORTA, PA1) +#define LINE_PA2 PAL_LINE(IOPORTA, PA2) +#define LINE_PA3 PAL_LINE(IOPORTA, PA3) +#define LINE_PA4 PAL_LINE(IOPORTA, PA4) +#define LINE_PA5 PAL_LINE(IOPORTA, PA5) +#define LINE_PA6 PAL_LINE(IOPORTA, PA6) +#define LINE_PA7 PAL_LINE(IOPORTA, PA7) +#define LINE_PA8 PAL_LINE(IOPORTA, PA8) +#define LINE_PA9 PAL_LINE(IOPORTA, PA9) +#define LINE_PA10 PAL_LINE(IOPORTA, PA10) +#define LINE_PA11 PAL_LINE(IOPORTA, PA11) +#define LINE_PA12 PAL_LINE(IOPORTA, PA12) +#define LINE_PA13 PAL_LINE(IOPORTA, PA13) +#define LINE_PA14 PAL_LINE(IOPORTA, PA14) +#define LINE_PA15 PAL_LINE(IOPORTA, PA15) + +#define LINE_PB0 PAL_LINE(IOPORTB, PB0) +#define LINE_PB1 PAL_LINE(IOPORTB, PB1) +#define LINE_PB2 PAL_LINE(IOPORTB, PB2) +#define LINE_PB3 PAL_LINE(IOPORTB, PB3) +#define LINE_PB4 PAL_LINE(IOPORTB, PB4) +#define LINE_PB5 PAL_LINE(IOPORTB, PB5) +#define LINE_PB6 PAL_LINE(IOPORTB, PB6) +#define LINE_PB7 PAL_LINE(IOPORTB, PB7) +#define LINE_PB8 PAL_LINE(IOPORTB, PB8) +#define LINE_PB9 PAL_LINE(IOPORTB, PB9) +#define LINE_PB10 PAL_LINE(IOPORTB, PB10) +#define LINE_PB11 PAL_LINE(IOPORTB, PB11) +#define LINE_PB12 PAL_LINE(IOPORTB, PB12) +#define LINE_PB13 PAL_LINE(IOPORTB, PB13) +#define LINE_PB14 PAL_LINE(IOPORTB, PB14) +#define LINE_PB15 PAL_LINE(IOPORTB, PB15) +#define LINE_PB16 PAL_LINE(IOPORTB, PB16) +#define LINE_PB17 PAL_LINE(IOPORTB, PB17) +#define LINE_PB18 PAL_LINE(IOPORTB, PB18) +#define LINE_PB19 PAL_LINE(IOPORTB, PB19) +#define LINE_PB20 PAL_LINE(IOPORTB, PB20) +#define LINE_PB21 PAL_LINE(IOPORTB, PB21) +#define LINE_PB22 PAL_LINE(IOPORTB, PB22) +#define LINE_PB23 PAL_LINE(IOPORTB, PB23) + +/* ── USB Device (base 0x40008000) ───────────────────────────────── */ +/* + * bMaxPacketSize0 = 8 confirmed from lsusb dump of stock firmware. + * Endpoint buffer RAM: 0x40008800 (512 bytes shared). + * + * Datasheet Table 17-1 / 17-2: + * R8_USB_CTRL (0x40008000): global USB control + * R8_UDEV_CTRL (0x40008001): physical port control — must set RB_UD_PORT_EN + * + * To assert D+ pull-up (announce FS device to host): + * 1. R8_USB_CTRL = RB_UC_DEV_PU_EN | RB_UC_INT_BUSY (0x20 | 0x08) + * 2. R16_PIN_ANALOG_IE |= RB_PIN_USB_IE (enable USB pin I/O) + * 3. R8_UDEV_CTRL = RB_UD_PD_DIS | RB_UD_PORT_EN (0x80 | 0x01) + */ +#define R8_USB_CTRL (*((volatile uint8_t *)0x40008000)) +#define R8_UDEV_CTRL (*((volatile uint8_t *)0x40008001)) /* device physical port */ +#define R8_USB_INT_EN (*((volatile uint8_t *)0x40008002)) +#define R8_USB_DEV_AD (*((volatile uint8_t *)0x40008003)) +#define R8_USB_MIS_ST (*((volatile uint8_t *)0x40008004)) +#define R8_USB_INT_FG (*((volatile uint8_t *)0x40008006)) +#define R8_USB_INT_ST (*((volatile uint8_t *)0x40008007)) +#define R8_USB_RX_LEN (*((volatile uint8_t *)0x40008008)) + +/* R8_USB_CTRL bits (datasheet Table 17-1) */ +#define RB_UC_HOST_MODE (1 << 7) /* 1=host mode, 0=device mode */ +#define RB_UC_DEV_PU_EN (1 << 5) /* device mode + internal 1.5k D+ pull-up */ +#define RB_UC_INT_BUSY (1 << 3) /* NAK while CPU busy */ +#define RB_UC_RESET_SIE (1 << 2) +#define RB_UC_CLR_ALL (1 << 1) +#define RB_UC_DMA_EN (1 << 0) + +/* R8_UDEV_CTRL bits (datasheet Table 17-2) */ +#define RB_UD_PD_DIS (1 << 7) /* disable internal D+/D- pull-down */ +#define RB_UD_PORT_EN (1 << 0) /* enable USB physical port I/O */ + +/* R16_PIN_ANALOG_IE (0x4000101A): enable USB pin analog I/O */ +#define R16_PIN_ANALOG_IE (*((volatile uint16_t *)0x4000101A)) +#define RB_PIN_USB_IE (1 << 7) /* bit 7 of the 16-bit register = 0x0080 */ + +/* R8_USB_MIS_ST bits (read-only status, datasheet Table 17-1, p.76) + * Read this register when RB_UIF_SUSPEND fires to distinguish suspend from wakeup. */ +#define RB_UMS_SUSPEND (1 << 2) /* 1 = bus currently suspended, 0 = bus active (wakeup) */ + +/* R8_USB_INT_FG / R8_USB_INT_EN bits (datasheet Table 17-1, p.77) + * INT_FG reset = 0x20 (bit5 RB_U_SIE_FREE set). */ +#define RB_UIF_BUS_RST (1 << 0) /* device: bus reset event */ +#define RB_UIF_TRANSFER (1 << 1) /* transfer complete */ +#define RB_UIF_SUSPEND (1 << 2) /* bus suspend / wake */ + +/* R8_USB_INT_ST bits (datasheet p.77-78) */ +#define MASK_UIS_TOKEN (3 << 4) /* PID token field */ +#define UIS_TOKEN_OUT (0 << 4) /* OUT token */ +#define UIS_TOKEN_SOF (1 << 4) /* SOF token */ +#define UIS_TOKEN_IN (2 << 4) /* IN token */ +#define UIS_TOKEN_SETUP (3 << 4) /* SETUP token */ +#define MASK_UIS_ENDP (0x0F) /* endpoint number field */ + +/* ── Endpoint mode registers (datasheet Table 17-2) ─────────────── */ +#define R8_UEP4_1_MOD (*((volatile uint8_t *)0x4000800c)) +#define R8_UEP2_3_MOD (*((volatile uint8_t *)0x4000800d)) +#define RB_UEP1_TX_EN (1 << 6) /* enable EP1 IN (TX) */ +#define RB_UEP1_RX_EN (1 << 7) /* enable EP1 OUT (RX) */ +#define RB_UEP2_TX_EN (1 << 2) /* enable EP2 IN (TX) */ +#define RB_UEP2_RX_EN (1 << 3) /* enable EP2 OUT (RX) */ + +/* ── Endpoint DMA base-address registers (must be set even without DMA) ── */ +/* Stores lower 16 bits of the endpoint SRAM address. + * SIE uses these regardless of RB_UC_DMA_EN to locate buffer in SRAM. */ +#define R16_UEP0_DMA (*((volatile uint16_t *)0x40008010)) +#define R16_UEP1_DMA (*((volatile uint16_t *)0x40008014)) +#define R16_UEP2_DMA (*((volatile uint16_t *)0x40008018)) + +/* ── Endpoint TX-length and control registers (datasheet Table 17-2) ── */ +/* Register order per endpoint: T_LEN at base+0, CTRL at base+2, stride 4. */ +#define R8_UEP0_T_LEN (*((volatile uint8_t *)0x40008020)) +#define R8_UEP0_CTRL (*((volatile uint8_t *)0x40008022)) +#define R8_UEP1_T_LEN (*((volatile uint8_t *)0x40008024)) +#define R8_UEP1_CTRL (*((volatile uint8_t *)0x40008026)) +#define R8_UEP2_T_LEN (*((volatile uint8_t *)0x40008028)) +#define R8_UEP2_CTRL (*((volatile uint8_t *)0x4000802a)) + +/* R8_UEPn_CTRL bits (datasheet Table 17-2, p.88): + * bit 7 = RB_UEP_R_TOG: RX expected toggle: 1=Expect DATA1, 0=Expect DATA0 + * bit 6 = RB_UEP_T_TOG: TX data toggle: 1=Send DATA1, 0=Send DATA0 + * bit 5 = Reserved + * bit 4 = RB_UEP_AUTO_TOG: Auto-flip T_TOG/R_TOG on success (EP1/2/3 only, NOT EP0) + * bits [3:2] = MASK_UEP_R_RES: OUT response + * bits [1:0] = MASK_UEP_T_RES: IN response */ +#define RB_UEP_R_TOG 0x80 /* RX toggle bit: 1=expect DATA1 */ +#define RB_UEP_T_TOG 0x40 /* TX toggle bit: 1=send DATA1 */ +#define RB_UEP_AUTO_TOG 0x10 /* Auto-flip T/R_TOG (EP1/2/3 only) */ +#define MASK_UEP_T_RES 0x03 +#define MASK_UEP_R_RES 0x0C +#define UEP_T_RES_ACK 0x00 /* DATA0/1 ready, respond ACK */ +#define UEP_T_RES_NAK 0x02 /* respond NAK */ +#define UEP_T_RES_STALL 0x03 /* respond STALL */ +#define UEP_R_RES_ACK 0x00 /* respond ACK (bits [3:2]) */ +#define UEP_R_RES_NAK 0x08 /* respond NAK (bits [3:2]) */ +#define UEP_R_RES_STALL 0x0C /* respond STALL */ + +/* ── Endpoint buffer RAM ─────────────────────────────────────────── */ +/* Layout confirmed from WCH EVT reference: + * EP0: 8-byte shared buf at base+0x00 + * EP1: 64-byte IN buf at base+0x40 + * EP2: 4-byte IN buf at base+0x80 */ +#define USB_BUF_BASE 0x40008800UL +#define EP0_BUF ((uint8_t *)(USB_BUF_BASE + 0x00)) /* 8 bytes */ +#define EP1_IN_BUF ((uint8_t *)(USB_BUF_BASE + 0x40)) /* 64 bytes */ +#define EP2_IN_BUF ((uint8_t *)(USB_BUF_BASE + 0x80)) /* 4 bytes */ + +#endif /* CH579_H */ diff --git a/os/hal/ports/WCH/CH579/hal_lld.c b/os/hal/ports/WCH/CH579/hal_lld.c new file mode 100644 index 0000000000..c13944aed8 --- /dev/null +++ b/os/hal/ports/WCH/CH579/hal_lld.c @@ -0,0 +1,52 @@ +#include "hal.h" + +void hal_lld_init(void) { + /* Step 1: Configure the external 32 MHz crystal oscillator (XT32M). + * + * R8_XT32M_TUNE (reset 0x31): + * C_LOAD = 000b (10 pF) — PCB has external load capacitors C1 and C37 on + * the crystal pins, so internal caps are set to minimum to avoid + * over-loading the crystal (internal + external stacking shifts freq). + * I_BIAS = 10b (125% rated) — slightly above nominal for reliable startup. + * + * Must be written before XT32M is selected as CK32M source. R8_HFCK_PWR_CTRL + * resets to 0x0C which already has RB_CLK_XT32M_PON set, so the crystal + * oscillator is already being powered; writing this register early ensures the + * load cap and bias are correct from the moment of first oscillation. */ + SAFE_ACCESS_OPEN(); + R8_XT32M_TUNE = RB_XT32M_C_LOAD_10PF | RB_XT32M_I_BIAS_125; /* 0x02 */ + SAFE_ACCESS_CLOSE(); + + /* Step 2: Wait for the 32 MHz crystal to start up and stabilise. + * Crystal startup time is typically 1–5 ms. Using 100 000 NOP iterations + * is conservative (≥ 5 ms even at 6.4 MHz startup clock). */ + for (volatile uint32_t i = 0; i < 100000; i++) { __NOP(); } + + /* Step 3: Switch CK32M source from internal RC to external crystal, then + * power on the PLL. Both writes go in the same safe-access window to keep + * the sequence atomic. + * + * RB_CLK_OSC32M_XT (bit1 of R8_CLK_SYS_CFG_H) must be set BEFORE enabling + * the PLL so the PLL locks onto the accurate external crystal (±50 ppm) + * rather than the internal RC oscillator (±2% = ±20 000 ppm, out-of-spec + * for USB which requires ±2 500 ppm). */ + SAFE_ACCESS_OPEN(); + R8_CLK_SYS_CFG_H = RB_CLK_OSC32M_XT; /* CK32M ← external crystal (other bits reserved, reset=0) */ + SAFE_ACCESS_CLOSE(); + SAFE_ACCESS_OPEN(); + R8_HFCK_PWR_CTRL = 0x1C; /* XT32M_PON | bit3 | PLL_PON */ + SAFE_ACCESS_CLOSE(); + + /* Step 4: Wait for PLL to stabilise. + * Datasheet: lock time < 100 µs. 2 000 NOP iterations ≥ 300 µs. */ + for (volatile uint32_t i = 0; i < 2000; i++) { __NOP(); } + + /* Step 5: Switch HCLK to CK32M direct (CLK_SYS_MOD=0b10 → 32 MHz). + * The USB peripheral derives its 48 MHz independently from the PLL, + * so HCLK need not be 48 MHz. bits[7:6]=10 selects CK32M direct mode; + * bits[4:0] are ignored in this mode. */ + SAFE_ACCESS_OPEN(); + R8_CLK_PLL_DIV = 0x80; + __NOP(); __NOP(); + SAFE_ACCESS_CLOSE(); +} \ No newline at end of file diff --git a/os/hal/ports/WCH/CH579/hal_lld.h b/os/hal/ports/WCH/CH579/hal_lld.h new file mode 100644 index 0000000000..530435b37f --- /dev/null +++ b/os/hal/ports/WCH/CH579/hal_lld.h @@ -0,0 +1,68 @@ +/** + * @file hal_lld.h + * @brief CH579M HAL low-level driver header — clock init. + * + * @addtogroup HAL + * @{ + */ + +#ifndef _HAL_LLD_H_ +#define _HAL_LLD_H_ + +/*===========================================================================*/ +/* Driver constants. */ +/*===========================================================================*/ + +/** + * @name Platform identification + * @{ + */ +#define PLATFORM_NAME "WCH CH579M" +/** @} */ + +/*===========================================================================*/ +/* Driver pre-compile time settings. */ +/*===========================================================================*/ + +/** + * @brief System core clock after PLL — 48 MHz for USB. + */ +#ifndef CH579_SYSCLK +#define CH579_SYSCLK 48000000UL +#endif + +/*===========================================================================*/ +/* Derived constants and error checks. */ +/*===========================================================================*/ + +/*===========================================================================*/ +/* Driver data structures and types. */ +/*===========================================================================*/ + +/*===========================================================================*/ +/* Driver macros. */ +/*===========================================================================*/ + +/** + * @brief Returns the frequency of a clock point in Hz. + */ +#define hal_lld_get_clock_point(clkpt) CH579_SYSCLK + +/*===========================================================================*/ +/* External declarations. */ +/*===========================================================================*/ + +#include "CH579.h" +#include "nvic.h" + +#ifdef __cplusplus +extern "C" { +#endif +void hal_lld_init(void); +#ifdef __cplusplus +} +#endif + +#endif /* _HAL_LLD_H_ */ + +/** @} */ diff --git a/os/hal/ports/WCH/CH579/hal_pal_lld.c b/os/hal/ports/WCH/CH579/hal_pal_lld.c new file mode 100644 index 0000000000..7c0491c9e1 --- /dev/null +++ b/os/hal/ports/WCH/CH579/hal_pal_lld.c @@ -0,0 +1,83 @@ +/** + * @file hal_pal_lld.c + * @brief CH579M PAL (GPIO) low level driver. + * + * @addtogroup PAL + * @{ + */ + +#include "hal.h" + +#if HAL_USE_PAL || defined(__DOXYGEN__) + +/*===========================================================================*/ +/* Driver local definitions. */ +/*===========================================================================*/ + +/*===========================================================================*/ +/* Driver exported functions. */ +/*===========================================================================*/ + +/** + * @brief Pads group mode setup. + * @details This function programs a group of pads belonging to a port + * to the specified PAL mode. + * + * @param[in] port the port identifier + * @param[in] mask the group mask (bit N → pad N) + * @param[in] mode the mode — one of PAL_MODE_INPUT, + * PAL_MODE_INPUT_PULLUP, PAL_MODE_INPUT_PULLDOWN, + * PAL_MODE_OUTPUT_PUSHPULL + * + * @notapi + */ +/* ChibiOS calls pal_lld_setgroupmode with 4 args: (port, mask, offset, mode). + * offset is always 0 for single-pad calls; we accept and ignore it. + * + * CH579 GPIO control registers (DIR, PU, PD_DRV) are fully readable as 32-bit + * values — the original "write-only / bus fault" comment was incorrect; reads + * are confirmed working via the WCH EVT SDK (CH57x_gpio.c uses |= / &=). + * We therefore use direct read-modify-write and protect each register triplet + * with a critical section so that concurrent calls (e.g. from interrupt context) + * cannot interleave partial updates. */ +void _pal_lld_setgroupmode(ioportid_t port, ioportmask_t mask, + uint32_t offset, iomode_t mode) { + (void)offset; + + syssts_t sts = osalSysGetStatusAndLockX(); + + switch (mode) { + case PAL_MODE_INPUT: + port->DIR &= ~mask; + port->PU &= ~mask; + port->PD_DRV &= ~mask; + break; + + case PAL_MODE_INPUT_PULLUP: + port->DIR &= ~mask; + port->PU |= mask; + port->PD_DRV &= ~mask; + break; + + case PAL_MODE_INPUT_PULLDOWN: + port->DIR &= ~mask; + port->PU &= ~mask; + port->PD_DRV |= mask; + break; + + case PAL_MODE_OUTPUT_PUSHPULL: + port->DIR |= mask; + port->PU &= ~mask; + port->PD_DRV &= ~mask; + break; + + default: + break; + } + + osalSysRestoreStatusX(sts); +} + +#endif /* HAL_USE_PAL */ + +/** @} */ diff --git a/os/hal/ports/WCH/CH579/hal_pal_lld.h b/os/hal/ports/WCH/CH579/hal_pal_lld.h new file mode 100644 index 0000000000..0f578e911a --- /dev/null +++ b/os/hal/ports/WCH/CH579/hal_pal_lld.h @@ -0,0 +1,192 @@ +/** + * @file hal_pal_lld.h + * @brief CH579M PAL (GPIO) low level driver header. + * + * @addtogroup PAL + * @{ + */ + +#ifndef HAL_PAL_LLD_H +#define HAL_PAL_LLD_H + +#include "CH579.h" + +/*===========================================================================*/ +/* Unsupported modes. */ +/*===========================================================================*/ + +#undef PAL_MODE_RESET +#undef PAL_MODE_UNCONNECTED +#undef PAL_MODE_INPUT +#undef PAL_MODE_INPUT_PULLUP +#undef PAL_MODE_INPUT_PULLDOWN +#undef PAL_MODE_INPUT_ANALOG +#undef PAL_MODE_OUTPUT_PUSHPULL +#undef PAL_MODE_OUTPUT_OPENDRAIN + +/*===========================================================================*/ +/* Driver constants. */ +/*===========================================================================*/ + +/** + * @name PAL port/pad type aliases + * @{ + */ +typedef GPIO_TypeDef *ioportid_t; +typedef uint32_t ioportmask_t; +typedef uint32_t iopadid_t; +typedef uint32_t iomode_t; + +/** @} */ + +/** + * @name PAL mode constants (CH579M subset) + * @{ + */ +#define PAL_MODE_INPUT 0U /**< Floating input (DIR=0, PU=0, PD=0) */ +#define PAL_MODE_INPUT_PULLUP 1U /**< Input with pull-up */ +#define PAL_MODE_INPUT_PULLDOWN 2U /**< Input with pull-down */ +#define PAL_MODE_OUTPUT_PUSHPULL 3U /**< Push-pull output */ +/** @} */ + +/*===========================================================================*/ +/* I/O Ports Identifiers. */ +/*===========================================================================*/ + +#define IOPORTA GPIOA +#define IOPORTB GPIOB + +/*===========================================================================*/ +/* Driver pre-compile time settings. */ +/*===========================================================================*/ + +/*===========================================================================*/ +/* Derived constants and error checks. */ +/*===========================================================================*/ + +/*===========================================================================*/ +/* Driver data structures and types. */ +/*===========================================================================*/ + +/** + * @brief Type of digital I/O port sized unsigned type. + */ +typedef uint32_t ioline_t; + +/** + * @brief PAL line identifier. + */ +#define PAL_IOPORTS_WIDTH 5 +#define PAL_LINE(port, pad) ((ioline_t)((uint32_t)(port)) | ((uint32_t)(pad))) + +#define PAL_PORT(line) \ + ((ioportid_t)((line) & ~((1U << PAL_IOPORTS_WIDTH) - 1U))) +#define PAL_PAD(line) ((uint32_t)((line) & ((1U << PAL_IOPORTS_WIDTH) - 1U))) + +/*===========================================================================*/ +/* Driver macros. */ +/*===========================================================================*/ + +/** + * @brief Type of a PAL configuration. + */ +typedef struct { + uint32_t dummy; /* Empty configuration for now */ +} PALConfig; + +/** + * @brief Low level PAL subsystem initialization. + */ +#define pal_lld_init(config) \ + (void)(config) /* GPIO regs default to input on reset */ + +/** + * @brief Reads the physical I/O port state. + */ +#define pal_lld_readport(port) ((ioportmask_t)((port)->PIN)) + +/** + * @brief Reads the output latch state. + */ +#define pal_lld_readlatch(port) ((ioportmask_t)((port)->OUT)) + +/** + * @brief Writes a value to the whole physical I/O port. + */ +#define pal_lld_writeport(port, bits) ((port)->OUT = (bits)) + +/** + * @brief Sets masked bits in a port. + */ +#define pal_lld_setport(port, bits) ((port)->OUT |= (bits)) + +/** + * @brief Clears masked bits in a port (uses atomic CLR register). + */ +#define pal_lld_clearport(port, bits) ((port)->CLR = (bits)) + +/** + * @brief Toggles masked bits in a port. + */ +#define pal_lld_toggleport(port, bits) ((port)->OUT ^= (bits)) + +/** + * @brief Reads the logical state of an I/O pad. + */ +#define pal_lld_readpad(port, pad) (((port)->PIN >> (pad)) & 1U) + +/** + * @brief Writes the logical state of an output pad. + */ +#define pal_lld_writepad(port, pad, bit) \ + do { \ + if (bit) \ + (port)->OUT |= (1UL << (pad)); \ + else \ + (port)->CLR = (1UL << (pad)); \ + } while (0) + +/** + * @brief Sets a pad logical level to @p PAL_HIGH. + */ +#define pal_lld_setpad(port, pad) ((port)->OUT |= (1UL << (pad))) + +/** + * @brief Clears a pad logical level to @p PAL_LOW. + */ +#define pal_lld_clearpad(port, pad) ((port)->CLR = (1UL << (pad))) + +/** + * @brief Toggles a pad logical level. + */ +#define pal_lld_togglepad(port, pad) ((port)->OUT ^= (1UL << (pad))) + +/** + * @brief Returns the pad group mode. + */ +#define pal_lld_getgroupmode(port, mask, mode) /* not implemented */ + +/*===========================================================================*/ +/* External declarations. */ +/*===========================================================================*/ + +#if !defined(__DOXYGEN__) +extern const PALConfig pal_default_config; +#endif + +#ifdef __cplusplus +extern "C" { +#endif +void _pal_lld_setgroupmode(ioportid_t port, ioportmask_t mask, uint32_t offset, iomode_t mode); +#ifdef __cplusplus +} +#endif + +/* Define the macro so hal_pal.h's #if !defined(pal_lld_setgroupmode) sees + * our implementation and does NOT fall back to the no-op stub. */ +#define pal_lld_setgroupmode(port, mask, offset, mode) \ + _pal_lld_setgroupmode((port), (mask), (offset), (mode)) + +#endif /* HAL_PAL_LLD_H */ + +/** @} */ diff --git a/os/hal/ports/WCH/CH579/hal_st_lld.c b/os/hal/ports/WCH/CH579/hal_st_lld.c new file mode 100644 index 0000000000..0f2cb9a298 --- /dev/null +++ b/os/hal/ports/WCH/CH579/hal_st_lld.c @@ -0,0 +1,62 @@ +/** + * @file hal_st_lld.c + * @brief CH579M ST subsystem low level driver. + * @details ARM SysTick in periodic mode. QMK sets OSAL_ST_MODE_PERIODIC + * and OSAL_ST_FREQUENCY = 1000 (1 kHz tick) by default. + * + * @addtogroup ST + * @{ + */ + +#include "hal.h" + +#if (OSAL_ST_MODE != OSAL_ST_MODE_NONE) || defined(__DOXYGEN__) + +/*===========================================================================*/ +/* Driver local definitions. */ +/*===========================================================================*/ + +#define SYSTICK_CK CH579_SYSCLK + +/*===========================================================================*/ +/* Driver interrupt handlers. */ +/*===========================================================================*/ + +/** + * @brief SysTick IRQ — drives the OSAL tick. + */ +OSAL_IRQ_HANDLER(SysTick_Handler) { + OSAL_IRQ_PROLOGUE(); + + osalSysLockFromISR(); + osalOsTimerHandlerI(); + osalSysUnlockFromISR(); + + OSAL_IRQ_EPILOGUE(); +} + +/*===========================================================================*/ +/* Driver exported functions. */ +/*===========================================================================*/ + +/** + * @brief Initialises the SysTick timer for periodic tick generation. + * @note Called by halInit() via st_lld_init(). + * + * @notapi + */ +void st_lld_init(void) { + /* Configure SysTick for periodic interrupts at OSAL_ST_FREQUENCY. */ + SysTick->LOAD = (SYSTICK_CK / OSAL_ST_FREQUENCY) - 1UL; + SysTick->VAL = 0UL; + SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk | /* processor clock */ + SysTick_CTRL_TICKINT_Msk | /* enable exception */ + SysTick_CTRL_ENABLE_Msk; /* start counting */ + + /* Set SysTick interrupt priority. */ + nvicSetSystemHandlerPriority(HANDLER_SYSTICK, CH579_ST_IRQ_PRIORITY); +} + +#endif /* OSAL_ST_MODE != OSAL_ST_MODE_NONE */ + +/** @} */ diff --git a/os/hal/ports/WCH/CH579/hal_st_lld.h b/os/hal/ports/WCH/CH579/hal_st_lld.h new file mode 100644 index 0000000000..d42cb83201 --- /dev/null +++ b/os/hal/ports/WCH/CH579/hal_st_lld.h @@ -0,0 +1,83 @@ +/** + * @file hal_st_lld.h + * @brief CH579M ST subsystem low level driver header. + * @details Uses the ARM Cortex-M0 SysTick peripheral directly. + * Periodic mode: SysTick fires at OSAL_ST_FREQUENCY. + * Freerunning mode: not supported (CM0 SysTick is 24-bit countdown + * only; use OSAL_ST_MODE_PERIODIC which QMK defaults to anyway). + * + * @addtogroup ST + * @{ + */ + +#ifndef HAL_ST_LLD_H +#define HAL_ST_LLD_H + +/*===========================================================================*/ +/* Driver constants. */ +/*===========================================================================*/ + +/*===========================================================================*/ +/* Driver pre-compile time settings. */ +/*===========================================================================*/ + +/** + * @brief SysTick IRQ priority. + */ +#if !defined(CH579_ST_IRQ_PRIORITY) || defined(__DOXYGEN__) +#define CH579_ST_IRQ_PRIORITY 8 +#endif + +/*===========================================================================*/ +/* Derived constants and error checks. */ +/*===========================================================================*/ + +#if OSAL_ST_MODE == OSAL_ST_MODE_FREERUNNING +#error \ + "CH579M hal_st_lld does not support OSAL_ST_MODE_FREERUNNING; use PERIODIC" +#endif + +#if CH579_SYSCLK % OSAL_ST_FREQUENCY != 0 +#error "OSAL_ST_FREQUENCY is not an integer divisor of CH579_SYSCLK" +#endif + +#if (CH579_SYSCLK / OSAL_ST_FREQUENCY) - 1 > 0xFFFFFFUL +#error "OSAL_ST_FREQUENCY too low — SysTick reload value exceeds 24 bits" +#endif + +/*===========================================================================*/ +/* Driver data structures and types. */ +/*===========================================================================*/ + +/*===========================================================================*/ +/* Driver macros. */ +/*===========================================================================*/ + +/*===========================================================================*/ +/* External declarations. */ +/*===========================================================================*/ + +#ifdef __cplusplus +extern "C" { +#endif +void st_lld_init(void); +#ifdef __cplusplus +} +#endif + +/*===========================================================================*/ +/* Driver inline functions (freerunning stubs — unused in periodic mode). */ +/*===========================================================================*/ + +#if (OSAL_ST_MODE == OSAL_ST_MODE_FREERUNNING) || defined(__DOXYGEN__) +static inline systime_t st_lld_get_counter(void) { return 0; } +static inline void st_lld_stop_alarm(void) {} +static inline void st_lld_start_alarm(systime_t t) { (void)t; } +static inline void st_lld_set_alarm(systime_t t) { (void)t; } +static inline systime_t st_lld_get_alarm(void) { return 0; } +static inline bool st_lld_is_alarm_active(void) { return false; } +#endif + +#endif /* HAL_ST_LLD_H */ + +/** @} */ diff --git a/os/hal/ports/WCH/CH579/hal_usb_lld.c b/os/hal/ports/WCH/CH579/hal_usb_lld.c new file mode 100644 index 0000000000..c8dbffab6b --- /dev/null +++ b/os/hal/ports/WCH/CH579/hal_usb_lld.c @@ -0,0 +1,541 @@ +/** + * @file hal_usb_lld.c + * @brief CH579M USB Full-Speed device low level driver. + * + * @details This is a minimally-complete driver that lets ChibiOS USB HAL + * run and QMK boot. The register-level USB sequences follow the + * WCH CH579EVT/EXAM/USB/Device/DevHID/ reference code and the + * CH579DS1 datasheet Chapter 17. + * + * Endpoint mapping (confirmed from lsusb of stock firmware): + * EP0 → 8-byte control (bMaxPacketSize0 = 8) + * EP1 → 64-byte IN (keyboard HID reports) + * EP2 → 4-byte IN (consumer/encoder HID, optional) + * + * Buffer layout (512-byte RAM at 0x40008800): + * 0x00 – 0x07 EP0 shared buf (8 bytes) + * 0x40 – 0x7F EP1 IN buf (64 bytes) + * 0x80 – 0x83 EP2 IN buf (4 bytes) + * + * @addtogroup USB + * @{ + */ + +#include "hal.h" +#include + +#if HAL_USE_USB || defined(__DOXYGEN__) + +/*===========================================================================*/ +/* Driver exported variables. */ +/*===========================================================================*/ + +/** + * @brief USB1 driver. + */ +USBDriver USBD1; + + +/*===========================================================================*/ +/* Driver local variables. */ +/*===========================================================================*/ + +/** + * @brief Endpoint buffer RAM (in normal SRAM 0x20000000+). + * + * @details WCH CH579 USB DMA engine accesses normal SRAM via R16_UEPn_DMA. + * R16_UEPn_DMA stores the lower 16 bits of the SRAM buffer address; + * the SIE reconstructs the full 32-bit address by prepending the SRAM + * base (0x20000000). Buffers must therefore live in normal SRAM — + * NOT at 0x40008800 (USB peripheral register space) which the DMA + * engine cannot reach via the SRAM bus. + * + * Buffer sizes match the hardware endpoint max-packet limits: + * EP0: 8 bytes (bMaxPacketSize0 = 8, confirmed from stock lsusb) + * EP1: 64 bytes IN (keyboard HID) + * EP2: 4 bytes IN (consumer/encoder HID, optional) + */ +static uint8_t ep0_buf[8] __attribute__((aligned(4))); +static uint8_t ep1_in_buf[64] __attribute__((aligned(4))); +static uint8_t ep2_in_buf[32] __attribute__((aligned(4))); + +/** + * @brief EP0 TX data toggle tracker. + * + * @details The CH579 USB SIE does NOT auto-toggle EP0 — firmware must manage + * RB_UEP_T_TOG (bit6) in R8_UEP0_CTRL manually. + * USB spec: first IN after SETUP must be DATA1 (toggle=1). + * Reset to 1 at every SETUP so each new control transfer starts correctly. + */ +static uint8_t ep0_tx_tog; + +/** + * @brief Static EP0 endpoint state (IN + OUT share the same 8-byte buffer). + */ +static struct { + USBInEndpointState in; + USBOutEndpointState out; +} ep0_state; + +/** + * @brief Static EP0 endpoint configuration. + * + * @details ChibiOS _usb_reset() clears epc[0] = NULL on every bus reset. + * QMK's event callback never re-initialises EP0 via usbInitEndpointI, + * so usb_lld_reset() must restore epc[0] from this static config — + * identical to the RP2040 LLD approach. + */ +static const USBEndpointConfig ep0config = { + .ep_mode = USB_EP_MODE_TYPE_CTRL, + .setup_cb = _usb_ep0setup, + .in_cb = _usb_ep0in, + .out_cb = _usb_ep0out, + .in_maxsize = USB_EP0_PACKET_SIZE, + .out_maxsize = USB_EP0_PACKET_SIZE, + .in_state = &ep0_state.in, + .out_state = &ep0_state.out, + .ep_bufoffset = 0, +}; + +/*===========================================================================*/ +/* Driver interrupt handlers. */ +/*===========================================================================*/ + +/** + * @brief USB interrupt handler. + * @note USB_IRQn = 6 → vector address 0x0058 → ChibiOS slot Vector58. + * Formula: hex(0x40 + IRQn×4) = hex(0x40+24) = 0x58. + * The handler MUST use this exact name or it lands in the default + * trap and locks up the CPU on the first USB event. + */ +OSAL_IRQ_HANDLER(Vector58) { + OSAL_IRQ_PROLOGUE(); + + uint8_t int_fg = R8_USB_INT_FG; + + if (int_fg & RB_UIF_TRANSFER) { + uint8_t token = R8_USB_INT_ST & MASK_UIS_TOKEN; + uint8_t ep_num = R8_USB_INT_ST & MASK_UIS_ENDP; + + switch (token) { + case UIS_TOKEN_SETUP: + /* Copy SETUP packet from EP0 buffer into driver setup[8] array. + * Reset ep0_tx_tog=1: first IN of each control transfer must be DATA1. */ + memcpy(USBD1.setup, ep0_buf, 8); + ep0_tx_tog = 1; + _usb_ep0setup(&USBD1, 0); + break; + case UIS_TOKEN_IN: + if (ep_num == 0) { + /* ChibiOS _usb_ep0in() must be called only when the ENTIRE EP0 IN + * transfer is complete. For multi-packet transfers (e.g. 18-byte + * device descriptor over 8-byte EP0), advance txcnt and re-arm the + * endpoint for the next chunk; only notify the framework when the last + * byte has been acknowledged. + * + * We compute the bytes just ACKed from (txsize − txcnt) clamped to + * the EP0 packet size rather than reading R8_UEP0_T_LEN. This avoids + * any ambiguity about whether the SIE clears or modifies that register + * after the transaction completes. */ + const USBEndpointConfig *epc0 = USBD1.epc[0]; + if (epc0 != NULL && epc0->in_state != NULL) { + USBInEndpointState *isp = epc0->in_state; + size_t remaining = isp->txsize - isp->txcnt; + if (remaining > USB_EP0_PACKET_SIZE) + remaining = USB_EP0_PACKET_SIZE; + isp->txcnt += remaining; + if (isp->txcnt < isp->txsize) { + /* More data remaining — usb_lld_start_in stages + arms next chunk. */ + usb_lld_start_in(&USBD1, 0); + break; /* do NOT call _usb_ep0in yet */ + } + } + _usb_ep0in(&USBD1, 0); + } else { + /* Guard: epc[ep_num] may be NULL before SET_CONFIGURATION completes. */ + if (USBD1.epc[ep_num] == NULL) break; + if (ep_num == 1) { + /* NAK immediately so the SIE does not retransmit the same buffer on + * the next host poll. _usb_isr_invoke_in_cb notifies QMK that the + * transfer completed; QMK will call usb_lld_start_in (which re-arms + * with ACK) only when it has a new report to send. */ + R8_UEP1_CTRL = (R8_UEP1_CTRL & ~MASK_UEP_T_RES) | UEP_T_RES_NAK; + } + _usb_isr_invoke_in_cb(&USBD1, ep_num); + } + break; + case UIS_TOKEN_OUT: + if (ep_num == 0) { + _usb_ep0out(&USBD1, 0); + } else { + _usb_isr_invoke_out_cb(&USBD1, ep_num); + } + break; + default: + break; + } + R8_USB_INT_FG = RB_UIF_TRANSFER; /* W1C */ + } + + if (int_fg & RB_UIF_BUS_RST) { + R8_USB_INT_FG = RB_UIF_BUS_RST; /* W1C before _usb_reset which re-enables */ + _usb_reset(&USBD1); + } + + if (int_fg & RB_UIF_SUSPEND) { + R8_USB_INT_FG = RB_UIF_SUSPEND; /* W1C — suspend handling not implemented */ + } + + OSAL_IRQ_EPILOGUE(); +} + +/*===========================================================================*/ +/* Driver exported functions. */ +/*===========================================================================*/ + +/** + * @brief Low level USB driver global init. + */ +void usb_lld_init(void) { usbObjectInit(&USBD1); } + +/** + * @brief Activates the USB peripheral, enabling D+ pull-up. + */ +void usb_lld_start(USBDriver *usbp) { + if (usbp->state == USB_STOP) { + /* Arm EP0 config now so SETUP packets are handled even if the host sends + * one before we receive the first BUS_RST event. usbStart() sets + * epc[0]=NULL; usb_lld_reset() also restores it, but that fires only on + * BUS_RST which may not arrive before the first SETUP on some hosts. */ + usbp->epc[0] = &ep0config; + + /* Reset SIE and clear all pending flags (reset value of R8_USB_CTRL is + * already 0x06 = RESET_SIE|CLR_ALL, but be explicit on warm start). */ + R8_USB_CTRL = RB_UC_RESET_SIE | RB_UC_CLR_ALL; + for (volatile int i = 0; i < 10; i++) { __NOP(); } + R8_USB_CTRL = 0x00; + + /* Point SIE at endpoint buffers in normal SRAM. + * R16_UEPn_DMA stores the lower 16 bits of the SRAM address; the DMA + * engine prepends the SRAM base (0x20000000) internally. Buffers must + * be in 0x20000000–0x2000FFFF for the lower-16-bit truncation to work. */ + R16_UEP0_DMA = (uint16_t)(uint32_t)ep0_buf; + R16_UEP1_DMA = (uint16_t)(uint32_t)ep1_in_buf; + R16_UEP2_DMA = (uint16_t)(uint32_t)ep2_in_buf; + + /* Enable EP1 IN (keyboard reports) and EP2 IN (consumer reports). + * R8_UEP4_1_MOD bit6 = RB_UEP1_TX_EN; R8_UEP2_3_MOD bit2 = RB_UEP2_TX_EN */ + R8_UEP4_1_MOD = RB_UEP1_TX_EN; + R8_UEP2_3_MOD = RB_UEP2_TX_EN; + + /* Enable USB device mode + internal 1.5k D+ pull-up. + * RB_UC_DEV_PU_EN (bit 5): device mode + internal D+ pull-up. + * RB_UC_INT_BUSY (bit 3): SIE auto-NAKs all tokens while any bit in + * R8_USB_INT_FG is set, serialising token handling. Required for EP0 + * correctness: prevents the SIE from serving a new IN token with stale + * data before the ISR has processed the previous SETUP and staged new data. + * RB_UC_DMA_EN: Required for SIE to use R16_UEPn_DMA registers to + * locate endpoint buffers in SRAM. Without this bit the + * SIE ignores the DMA address registers entirely. */ + R8_USB_CTRL = RB_UC_DEV_PU_EN | RB_UC_INT_BUSY | RB_UC_DMA_EN; + + /* Enable USB analog pin I/O (PB10=UD-, PB11=UD+). */ + R16_PIN_ANALOG_IE |= RB_PIN_USB_IE; + + /* Enable physical port; disable internal D+/D- pull-downs. */ + R8_UDEV_CTRL = RB_UD_PD_DIS | RB_UD_PORT_EN; + + /* Enable transfer, bus-reset, and suspend interrupts. */ + R8_USB_INT_EN = RB_UIF_TRANSFER | RB_UIF_BUS_RST | RB_UIF_SUSPEND; + + nvicEnableVector(USB_IRQn, CH579_USB_IRQ_PRIORITY); + } +} + +/** + * @brief Deactivates the USB peripheral. + */ +void usb_lld_stop(USBDriver *usbp) { + if (usbp->state != USB_STOP) { + nvicDisableVector(USB_IRQn); + R8_UDEV_CTRL = 0x00; + R8_USB_CTRL = 0x00; + R8_USB_INT_EN = 0x00; + } +} + +/** + * @brief USB low-level reset — hardware only, called by ChibiOS _usb_reset(). + * + * @details _usb_reset() (hal_usb.c) clears ChibiOS state and fires + * USB_EVENT_RESET, then calls usb_lld_reset() for the hardware + * portion. This function must NOT call _usb_reset() — that would + * cause infinite recursion. + */ +void usb_lld_reset(USBDriver *usbp) { + /* Restore EP0 config — _usb_reset() cleared epc[0]=NULL and QMK never + * calls usbInitEndpointI for EP0, so we must re-arm it here. */ + usbp->epc[0] = &ep0config; + + R8_USB_DEV_AD = 0x00; /* address 0 */ + + /* EP0: ACK OUT/SETUP, NAK IN. Manage T_TOG manually. */ + ep0_tx_tog = 1; /* first IN of next control transfer must be DATA1 */ + R8_UEP0_T_LEN = 0; + R8_UEP0_CTRL = UEP_R_RES_ACK | UEP_T_RES_NAK; + + /* EP1/EP2: NAK IN, T_TOG=0. AUTO_TOG enabled by usb_lld_init_endpoint(). */ + R8_UEP1_T_LEN = 0; + R8_UEP1_CTRL = UEP_T_RES_NAK; + + R8_UEP2_T_LEN = 0; + R8_UEP2_CTRL = UEP_T_RES_NAK; +} + +/** + * @brief Sets the device USB address. + */ +void usb_lld_set_address(USBDriver *usbp) { + R8_USB_DEV_AD = (uint8_t)usbp->address; +} + +/** + * @brief Enables an endpoint. + */ +void usb_lld_init_endpoint(USBDriver *usbp, usbep_t ep) { + (void)usbp; + switch (ep) { + case 0: + ep0_tx_tog = 1; /* first IN of next SETUP will be DATA1 */ + R8_UEP0_T_LEN = 0; + R8_UEP0_CTRL = UEP_R_RES_ACK | UEP_T_RES_NAK; + break; + case 1: + /* AUTO_TOG (bit4): hardware flips T_TOG after each successful IN. + * T_TOG starts at 0 → first report is DATA0, then DATA1, alternating. + * Pre-arm with an 8-byte idle report so the very first IN token from the + * host gets an ACK immediately — no dependency on matrix scan timing. */ + memset(ep1_in_buf, 0, 8); + R8_UEP1_T_LEN = 8; + R8_UEP1_CTRL = RB_UEP_AUTO_TOG | UEP_T_RES_ACK; + R8_UEP4_1_MOD |= RB_UEP1_TX_EN; + break; + case 2: + R8_UEP2_T_LEN = 0; + R8_UEP2_CTRL = RB_UEP_AUTO_TOG | UEP_T_RES_NAK; + R8_UEP2_3_MOD |= RB_UEP2_TX_EN; + break; + default: + break; + } +} + +/** + * @brief Disables all non-zero endpoints. + */ +void usb_lld_disable_endpoints(USBDriver *usbp) { + (void)usbp; + R8_UEP4_1_MOD &= ~RB_UEP1_TX_EN; + R8_UEP2_3_MOD &= ~RB_UEP2_TX_EN; + R8_UEP1_CTRL = UEP_T_RES_NAK; + R8_UEP2_CTRL = UEP_T_RES_NAK; +} + +/** + * @brief Returns the IN endpoint status. + */ +usbepstatus_t usb_lld_get_status_in(USBDriver *usbp, usbep_t ep) { + (void)usbp; + switch (ep) { + case 1: + return (R8_UEP1_CTRL & MASK_UEP_T_RES) == UEP_T_RES_STALL + ? EP_STATUS_STALLED : EP_STATUS_ACTIVE; + case 2: + return (R8_UEP2_CTRL & MASK_UEP_T_RES) == UEP_T_RES_STALL + ? EP_STATUS_STALLED : EP_STATUS_ACTIVE; + default: + return EP_STATUS_DISABLED; + } +} + +/** + * @brief Returns the OUT endpoint status. + */ +usbepstatus_t usb_lld_get_status_out(USBDriver *usbp, usbep_t ep) { + (void)usbp; + (void)ep; + return EP_STATUS_DISABLED; +} + +/** + * @brief Copies the received SETUP payload into buf. + */ +void usb_lld_read_setup(USBDriver *usbp, usbep_t ep, uint8_t *buf) { + (void)ep; + memcpy(buf, usbp->setup, 8); +} + +/** + * @brief Prepares for a data receive on an OUT endpoint. + */ +void usb_lld_prepare_receive(USBDriver *usbp, usbep_t ep) { + (void)usbp; + if (ep == 0) { + R8_UEP0_CTRL = (R8_UEP0_CTRL & ~MASK_UEP_R_RES) | UEP_R_RES_ACK; + } +} + +/** + * @brief Prepares an IN transfer (stages data into the endpoint buffer). + */ +void usb_lld_prepare_transmit(USBDriver *usbp, usbep_t ep) { + USBInEndpointState *isp = usbp->epc[ep]->in_state; + size_t n = isp->txsize - isp->txcnt; + + switch (ep) { + case 0: + if (n > USB_EP0_PACKET_SIZE) n = USB_EP0_PACKET_SIZE; + if (n > 0) memcpy(ep0_buf, isp->txbuf + isp->txcnt, n); + R8_UEP0_T_LEN = (uint8_t)n; + break; + case 1: + if (n > 64) n = 64; + memcpy(ep1_in_buf, isp->txbuf + isp->txcnt, n); + R8_UEP1_T_LEN = (uint8_t)n; + break; + case 2: + if (n > 32) n = 32; + memcpy(ep2_in_buf, isp->txbuf + isp->txcnt, n); + R8_UEP2_T_LEN = (uint8_t)n; + break; + default: + break; + } +} + +/** + * @brief Starts an IN transfer: stages the next chunk then arms the endpoint. + * + * @details ChibiOS calls only usb_lld_start_in (never usb_lld_prepare_transmit) + * from usbStartTransmitI, so this function must both load the EP buffer + * and flip T_RES to ACK. + * + * EP0: firmware manages T_TOG manually (bit6). ep0_tx_tog tracks the + * next DATA PID. Initialised to 1 (DATA1) at each SETUP so the first + * IN is always DATA1, then alternates each packet. + * EP1/2: AUTO_TOG (bit4) handles toggle automatically. + */ +void usb_lld_start_in(USBDriver *usbp, usbep_t ep) { + /* Stage data into the EP buffer first. */ + usb_lld_prepare_transmit(usbp, ep); + + switch (ep) { + case 0: { + /* Set T_TOG from ep0_tx_tog, then pre-flip for next call. */ + uint8_t ctrl = R8_UEP0_CTRL & ~(MASK_UEP_T_RES | RB_UEP_T_TOG); + ctrl |= UEP_T_RES_ACK; + if (ep0_tx_tog) ctrl |= RB_UEP_T_TOG; + ep0_tx_tog ^= 1u; + R8_UEP0_CTRL = ctrl; + break; + } + case 1: + R8_UEP1_CTRL = (R8_UEP1_CTRL & ~MASK_UEP_T_RES) | UEP_T_RES_ACK; + break; + case 2: + R8_UEP2_CTRL = (R8_UEP2_CTRL & ~MASK_UEP_T_RES) | UEP_T_RES_ACK; + break; + default: + break; + } +} + +/** + * @brief Starts an OUT transfer (flip R_RES from NAK to ACK, set R_TOG for EP0). + * + * @details After every SETUP (DATA0), the next OUT token from the host uses + * DATA1 — either the STATUS OUT for D→H transfers, or the first data + * packet for H→D transfers. Set RB_UEP_R_TOG (bit7) so the SIE + * accepts DATA1; if R_TOG doesn't match the host's PID, the SIE ACKs + * but RB_UIS_TOG_OK will be clear and firmware should discard the data. + */ +void usb_lld_start_out(USBDriver *usbp, usbep_t ep) { + (void)usbp; + if (ep == 0) { + /* Clear both R_RES and T_RES fields plus R_TOG. + * Setting T_RES=NAK here prevents a stale ACK from a previous IN + * transaction triggering a spurious IN response during the OUT phase. */ + R8_UEP0_CTRL = (R8_UEP0_CTRL & ~(MASK_UEP_R_RES | MASK_UEP_T_RES | RB_UEP_R_TOG)) + | UEP_R_RES_ACK | UEP_T_RES_NAK | RB_UEP_R_TOG; + } +} + +/** + * @brief Stalls an IN endpoint. + */ +void usb_lld_stall_in(USBDriver *usbp, usbep_t ep) { + (void)usbp; + switch (ep) { + case 0: + R8_UEP0_CTRL = (R8_UEP0_CTRL & ~MASK_UEP_T_RES) | UEP_T_RES_STALL; + break; + case 1: + R8_UEP1_CTRL = (R8_UEP1_CTRL & ~MASK_UEP_T_RES) | UEP_T_RES_STALL; + break; + case 2: + R8_UEP2_CTRL = (R8_UEP2_CTRL & ~MASK_UEP_T_RES) | UEP_T_RES_STALL; + break; + default: + break; + } +} + +/** + * @brief Stalls an OUT endpoint. + */ +void usb_lld_stall_out(USBDriver *usbp, usbep_t ep) { + (void)usbp; + if (ep == 0) { + R8_UEP0_CTRL = (R8_UEP0_CTRL & ~MASK_UEP_R_RES) | UEP_R_RES_STALL; + } +} + +/** + * @brief Clears an IN endpoint stall. + * + * @details On STALL clear, the data toggle must restart from DATA0 per USB spec. + * EP0: reset ep0_tx_tog; T_TOG cleared via full CTRL rewrite. + * EP1/2: force T_TOG=0 (bit6=0), keep AUTO_TOG (bit4=1). + */ +void usb_lld_clear_in(USBDriver *usbp, usbep_t ep) { + (void)usbp; + switch (ep) { + case 0: + ep0_tx_tog = 1; /* next control transfer starts from DATA1 */ + R8_UEP0_CTRL = UEP_R_RES_ACK | UEP_T_RES_NAK; /* T_TOG=0 */ + break; + case 1: + /* T_TOG=0 (bit6), AUTO_TOG on (bit4), T_RES=NAK */ + R8_UEP1_CTRL = RB_UEP_AUTO_TOG | UEP_T_RES_NAK; + break; + case 2: + R8_UEP2_CTRL = RB_UEP_AUTO_TOG | UEP_T_RES_NAK; + break; + default: + break; + } +} + +/** + * @brief Clears an OUT endpoint stall. + */ +void usb_lld_clear_out(USBDriver *usbp, usbep_t ep) { + (void)usbp; + if (ep == 0) { + R8_UEP0_CTRL = (R8_UEP0_CTRL & ~MASK_UEP_R_RES) | UEP_R_RES_ACK; + } +} + +#endif /* HAL_USE_USB */ + +/** @} */ diff --git a/os/hal/ports/WCH/CH579/hal_usb_lld.h b/os/hal/ports/WCH/CH579/hal_usb_lld.h new file mode 100644 index 0000000000..eefe593979 --- /dev/null +++ b/os/hal/ports/WCH/CH579/hal_usb_lld.h @@ -0,0 +1,354 @@ +/** + * @file hal_usb_lld.h + * @brief CH579M USB Full-Speed device low level driver header. + * + * @details The CH579M USB controller has: + * - EP0: 8-byte control endpoint (bMaxPacketSize0 = 8, confirmed from lsusb) + * - EP1: 64-byte IN (keyboard HID reports) + * - EP2: 4-byte IN (consumer/media HID, for encoder keys) + * - Endpoint buffer RAM at 0x40008800 (512 bytes shared) + * + * @addtogroup USB + * @{ + */ + +#ifndef HAL_USB_LLD_H +#define HAL_USB_LLD_H + +#include "CH579.h" + +#if HAL_USE_USB || defined(__DOXYGEN__) + +/*===========================================================================*/ +/* Driver constants. */ +/*===========================================================================*/ + +/** + * @brief Maximum number of endpoints. + * @details EP0 + EP1 + EP2 = 3 in practice; reserve 8 for the ChibiOS API. + */ +#define USB_MAX_ENDPOINTS 7 + +/** + * @brief EP0 packet size (hardware fixed at 8 bytes on CH579M). + */ +#define USB_EP0_PACKET_SIZE 8 + +/** + * @brief Address is applied AFTER the STATUS IN for SET_ADDRESS is ACKed. + * + * @details R8_USB_DEV_AD takes effect immediately when written. Using EARLY + * mode would cause the SIE to change its address before sending the + * STATUS IN, so the host's IN token (to the old address) would be + * ignored → "Device not responding to setup address". Use LATE mode + * so the address is updated via the ep0endcb callback only after the + * STATUS IN is successfully ACKed. + */ +#define USB_SET_ADDRESS_MODE USB_LATE_SET_ADDRESS + +/** + * @brief Status stage handled entirely in software by usbStartTransmitI / + * usbStartReceiveI (USB_EP0_STATUS_STAGE_SW = 0 is the default when + * this macro is left undefined, but we make it explicit here). + */ +#define USB_EP0_STATUS_STAGE USB_EP0_STATUS_STAGE_SW + +#define USB_SUPPORTS_HSIC FALSE + +/*===========================================================================*/ +/* Driver pre-compile time settings. */ +/*===========================================================================*/ + +/** + * @brief USB1 driver enable switch. + */ +#if !defined(CH579_USB_USE_USB1) || defined(__DOXYGEN__) +#define CH579_USB_USE_USB1 TRUE +#endif + +/** + * @brief USB interrupt priority. + */ +#if !defined(CH579_USB_IRQ_PRIORITY) || defined(__DOXYGEN__) +#define CH579_USB_IRQ_PRIORITY 5 +#endif + +/*===========================================================================*/ +/* Derived constants and error checks. */ +/*===========================================================================*/ + +#if !CH579_USB_USE_USB1 +#error "CH579M has only one USB peripheral" +#endif + +/*===========================================================================*/ +/* Driver data structures and types. */ +/*===========================================================================*/ + +/** + * @brief Type of an IN endpoint state structure. + */ +typedef struct { + /** + * @brief Pointer to the next buffer to be transmitted. + */ + const uint8_t *txbuf; + /** + * @brief Transmit transfer size. + */ + size_t txsize; + /** + * @brief Transmitted bytes so far. + */ + size_t txcnt; +#if (USB_USE_WAIT == TRUE) || defined(__DOXYGEN__) + /** + * @brief Waiting thread. + */ + thread_reference_t thread; +#endif + /* End of the mandatory fields.*/ +} USBInEndpointState; + +/** + * @brief Type of an OUT endpoint state structure. + */ +typedef struct { + /** + * @brief Receive buffer pointer. + */ + uint8_t *rxbuf; + /** + * @brief Receive transfer size. + */ + size_t rxsize; + /** + * @brief Received bytes so far. + */ + size_t rxcnt; +#if (USB_USE_WAIT == TRUE) || defined(__DOXYGEN__) + /** + * @brief Waiting thread. + */ + thread_reference_t thread; +#endif + /* End of the mandatory fields.*/ +} USBOutEndpointState; + +/** + * @brief Type of an USB endpoint configuration structure. + */ +typedef struct { + /** + * @brief Type and mode of the endpoint. + */ + uint32_t ep_mode; + /** + * @brief Setup packet callback (EP0 only). + */ + usbepcallback_t setup_cb; + /** + * @brief IN endpoint notification callback. + */ + usbepcallback_t in_cb; + /** + * @brief OUT endpoint notification callback. + */ + usbepcallback_t out_cb; + /** + * @brief IN endpoint maximum packet size. + */ + uint16_t in_maxsize; + /** + * @brief OUT endpoint maximum packet size. + */ + uint16_t out_maxsize; + /** + * @brief IN endpoint state. + */ + USBInEndpointState *in_state; + /** + * @brief OUT endpoint state. + */ + USBOutEndpointState *out_state; + /* End of the mandatory fields. */ + /** + * @brief EP buffer offset in endpoint RAM (relative to USB_BUF_BASE). + */ + uint16_t ep_bufoffset; +} USBEndpointConfig; + +/** + * @brief USB driver specific endpoint fields initializer. + */ +#define usb_lld_endpoint_fields 0 + +/** + * @brief Type of an USB driver configuration structure. + */ +typedef struct { + /** + * @brief USB events callback. Receives USB_EVENT_* events. + */ + usbeventcb_t event_cb; + /** + * @brief Device GET_DESCRIPTOR request callback. + */ + usbgetdescriptor_t get_descriptor_cb; + /** + * @brief Requests hook callback. + */ + usbreqhandler_t requests_hook_cb; + /** + * @brief Start Of Frame callback. + */ + usbcallback_t sof_cb; +} USBConfig; + +/** + * @brief Structure representing an USB driver. + */ +struct USBDriver { + /** + * @brief Driver state. + */ + usbstate_t state; + /** + * @brief Current configuration data. + */ + const USBConfig *config; + /** + * @brief Bit map of the transmitting IN endpoints. + */ + uint16_t transmitting; + /** + * @brief Bit map of the receiving OUT endpoints. + */ + uint16_t receiving; + /** + * @brief Pointer to the next address in the packet memory. + */ + uint8_t *pmnext; + /** + * @brief Endpoint 0 state. + */ + usbep0state_t ep0state; + /** + * @brief Next position in the buffer for endpoint 0. + */ + uint8_t *ep0next; + /** + * @brief Number of bytes yet to be transferred on endpoint 0. + */ + size_t ep0n; + /** + * @brief Endpoint 0 end callback. + */ + usbcallback_t ep0endcb; + /** + * @brief Next position in the buffer of the next packets. + */ + uint8_t setup[8]; + /** + * @brief Current USB device status. + */ + uint16_t status; + /** + * @brief Assigned USB address. + */ + uint8_t address; + /** + * @brief Current USB device configuration. + */ + uint8_t configuration; + /** + * @brief State of the driver when a suspend happened. + */ + usbstate_t saved_state; + /** + * @brief Active IN endpoint descriptors. + */ + const USBEndpointConfig *epc[USB_MAX_ENDPOINTS + 1]; + /** + * @brief Fields available to user, not used by the driver. + */ + void *in_params[USB_MAX_ENDPOINTS]; + void *out_params[USB_MAX_ENDPOINTS]; +}; + +/*===========================================================================*/ +/* Driver macros. */ +/*===========================================================================*/ + +/** + * @brief Returns the current frame number. + */ +#define usb_lld_get_frame_number(usbp) (0) + +/** + * @brief Returns the exact size of a receive transaction. + */ +#define usb_lld_get_transaction_size(usbp, ep) \ + ((usbp)->epc[ep]->out_state->rxcnt) + +/** + * @brief Connects the USB device. + */ +#ifndef usb_lld_connect_bus +#define usb_lld_connect_bus(usbp) +#endif + +/** + * @brief Disconnect the USB device. + */ +#ifndef usb_lld_disconnect_bus +#define usb_lld_disconnect_bus(usbp) +#endif + +/** + * @brief Wake up the host. + */ +#ifndef usb_lld_wakeup_host +#define usb_lld_wakeup_host(usbp) \ + do { \ + } while (false) +#endif + +/*===========================================================================*/ +/* External declarations. */ +/*===========================================================================*/ + +#if CH579_USB_USE_USB1 && !defined(__DOXYGEN__) +extern USBDriver USBD1; +#endif + +#ifdef __cplusplus +extern "C" { +#endif +void usb_lld_init(void); +void usb_lld_start(USBDriver *usbp); +void usb_lld_stop(USBDriver *usbp); +void usb_lld_reset(USBDriver *usbp); +void usb_lld_set_address(USBDriver *usbp); +void usb_lld_init_endpoint(USBDriver *usbp, usbep_t ep); +void usb_lld_disable_endpoints(USBDriver *usbp); +usbepstatus_t usb_lld_get_status_in(USBDriver *usbp, usbep_t ep); +usbepstatus_t usb_lld_get_status_out(USBDriver *usbp, usbep_t ep); +void usb_lld_read_setup(USBDriver *usbp, usbep_t ep, uint8_t *buf); +void usb_lld_prepare_receive(USBDriver *usbp, usbep_t ep); +void usb_lld_prepare_transmit(USBDriver *usbp, usbep_t ep); +void usb_lld_start_in(USBDriver *usbp, usbep_t ep); +void usb_lld_start_out(USBDriver *usbp, usbep_t ep); +void usb_lld_stall_in(USBDriver *usbp, usbep_t ep); +void usb_lld_stall_out(USBDriver *usbp, usbep_t ep); +void usb_lld_clear_in(USBDriver *usbp, usbep_t ep); +void usb_lld_clear_out(USBDriver *usbp, usbep_t ep); +#ifdef __cplusplus +} +#endif + +#endif /* HAL_USE_USB */ + +#endif /* HAL_USB_LLD_H */ + +/** @} */ diff --git a/os/hal/ports/WCH/CH579/platform.mk b/os/hal/ports/WCH/CH579/platform.mk new file mode 100644 index 0000000000..eec487add2 --- /dev/null +++ b/os/hal/ports/WCH/CH579/platform.mk @@ -0,0 +1,13 @@ +PLATFORMSRC_CONTRIB := \ + $(CHIBIOS)/os/hal/ports/common/ARMCMx/nvic.c \ + $(CHIBIOS_CONTRIB)/os/hal/ports/WCH/CH579/hal_lld.c \ + $(CHIBIOS_CONTRIB)/os/hal/ports/WCH/CH579/hal_pal_lld.c \ + $(CHIBIOS_CONTRIB)/os/hal/ports/WCH/CH579/hal_st_lld.c \ + $(CHIBIOS_CONTRIB)/os/hal/ports/WCH/CH579/hal_usb_lld.c + +PLATFORMINC_CONTRIB := \ + $(CHIBIOS_CONTRIB)/os/hal/ports/WCH/CH579 \ + $(CHIBIOS)/os/hal/ports/common/ARMCMx + +ALLCSRC += $(PLATFORMSRC_CONTRIB) +ALLINC += $(PLATFORMINC_CONTRIB)