/* * SPDX-FileCopyrightText: Copyright (c) 2025 Ha Thach (tinyusb.org) * SPDX-License-Identifier: MIT * * This file is part of the TinyUSB stack. * * Modification * - Gabriel Koppenstein add nRF54LM20 support */ #ifndef TUSB_DWC2_NRF_H #define TUSB_DWC2_NRF_H // NRF is device only without OTG support #include "nrf.h" #ifdef __GNUC__ #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wcast-align" #endif #include #ifdef __GNUC__ #pragma GCC diagnostic pop #endif #define DWC2_EP_MAX 16 // Use the auto-resolving peripheral pointer (respects TrustZone secure/non-secure mapping) #if defined(NRF54LM20A_ENGA_XXAA) #define DWC2_REG_BASE ((uintptr_t)NRF_USBHSCORE) #else #define DWC2_REG_BASE ((uintptr_t)NRF_USBHSCORE0) #endif static const dwc2_controller_t _dwc2_controller[] = { {.reg_base = DWC2_REG_BASE, .irqnum = USBHS_IRQn, .ep_count = 16, .otg_dfifo_depth = 3072}, }; // MCU specific to enable dwc2 clock/power before any access to register TU_ATTR_ALWAYS_INLINE static inline void dwc2_clock_init(uint8_t rhport, tusb_role_t role) { (void) rhport; (void) role; #if defined(NRF54LM20A_ENGA_XXAA) // Start the USB voltage regulator NRF_VREGUSB->TASKS_START = VREGUSB_TASKS_START_TASKS_START_Trigger; // Based on Zephyr usbhs_enable_core() in drivers/usb/udc/udc_dwc2_vendor_quirks.h // Step 1: Power up core only (PHY not yet) NRF_USBHS->ENABLE = USBHS_ENABLE_CORE_Msk; // Step 2: Override ID=Device (bit 31), and temporarily override VBUSVALID NRF_USBHS->PHY.OVERRIDEVALUES = (USBHS_PHY_OVERRIDEVALUES_ID_Device << USBHS_PHY_OVERRIDEVALUES_ID_Pos); NRF_USBHS->PHY.INPUTOVERRIDE = USBHS_PHY_INPUTOVERRIDE_ID_Msk | USBHS_PHY_INPUTOVERRIDE_VBUSVALID_Msk; // Step 3: Release PHY power-on reset by enabling PHY NRF_USBHS->ENABLE = USBHS_ENABLE_PHY_Msk | USBHS_ENABLE_CORE_Msk; // Step 4: Wait 45us for PHY clock to start nrfx_coredep_delay_us(45); // Step 5: Release DWC2 reset NRF_USBHS->TASKS_START = USBHS_TASKS_START_TASKS_START_Trigger; // Step 6: Wait for clock to start to avoid hang on too early register read nrfx_coredep_delay_us(2); // Step 7: Clear VBUSVALID override (keep ID=Device override) // DWC2 is now in Non-Driving opmode; D+ pull-up will activate when DWC2 clears DCTL SftDiscon NRF_USBHS->PHY.INPUTOVERRIDE = USBHS_PHY_INPUTOVERRIDE_ID_Msk; // Barrier: USBHS wrapper (0x5005A000) and USBHSCORE (0x50020000) are separate // peripheral blocks. Ensure the ENABLE/TASKS_START writes have propagated from // the Cortex-M33 write buffer to hardware before anyone reads DWC2 core regs. __DSB(); #endif } TU_ATTR_ALWAYS_INLINE static inline void dwc2_int_set(uint8_t rhport, tusb_role_t role, bool enabled) { (void)rhport; (void)role; if (enabled) { NVIC_EnableIRQ(USBHS_IRQn); } else { NVIC_DisableIRQ(USBHS_IRQn); } } #define dwc2_dcd_int_enable(_rhport) dwc2_int_set(_rhport, TUSB_ROLE_DEVICE, true) #define dwc2_dcd_int_disable(_rhport) dwc2_int_set(_rhport, TUSB_ROLE_DEVICE, false) TU_ATTR_ALWAYS_INLINE static inline void dwc2_remote_wakeup_delay(void) { } // MCU specific PHY init, called BEFORE core reset TU_ATTR_ALWAYS_INLINE static inline void dwc2_phy_init(dwc2_regs_t *dwc2, uint8_t hs_phy_type) { (void)dwc2; (void)hs_phy_type; } // MCU specific PHY deinit, disable PHY power TU_ATTR_ALWAYS_INLINE static inline void dwc2_phy_deinit(dwc2_regs_t *dwc2, uint8_t hs_phy_type) { (void)dwc2; (void)hs_phy_type; } // MCU specific PHY update, it is called AFTER init() and core reset TU_ATTR_ALWAYS_INLINE static inline void dwc2_phy_update(dwc2_regs_t *dwc2, uint8_t hs_phy_type) { (void)dwc2; (void)hs_phy_type; } // nRF54 Cortex-M33 has no D-cache, provide no-op stubs for DMA mode TU_ATTR_ALWAYS_INLINE static inline bool dwc2_dcache_clean(const void *addr, uint32_t data_size) { (void)addr; (void)data_size; return true; } TU_ATTR_ALWAYS_INLINE static inline bool dwc2_dcache_invalidate(const void *addr, uint32_t data_size) { (void)addr; (void)data_size; return true; } TU_ATTR_ALWAYS_INLINE static inline bool dwc2_dcache_clean_invalidate(const void *addr, uint32_t data_size) { (void)addr; (void)data_size; return true; } #endif