22 * The MIT License (MIT)
33 *
44 * Copyright (c) 2019 Ha Thach (tinyusb.org)
5+ * Copyright (c) 2026, Gabriel Koppenstein
56 *
67 * Permission is hereby granted, free of charge, to any person obtaining a copy
78 * of this software and associated documentation files (the "Software"), to deal
4546#include "nrfx.h"
4647#include "hal/nrf_gpio.h"
4748#include "nrfx_gpiote.h"
48- #if !defined(NRF54H20_XXAA )
49+ #if !defined(NRF54H20_XXAA ) && !defined( NRF54LM20A_ENGA_XXAA )
4950#include "nrfx_power.h"
5051#endif
5152#include "nrfx_uarte.h"
@@ -79,13 +80,17 @@ enum {
7980};
8081
8182// Forward USB interrupt events to TinyUSB IRQ Handler
82- #if defined(NRF54H20_XXAA )
83+ #if defined(NRF54H20_XXAA ) || defined( NRF54LM20A_ENGA_XXAA )
8384#define USBD_IRQn USBHS_IRQn
8485void USBHS_IRQHandler (void ) {
8586 tusb_int_handler (0 , true);
8687}
8788
89+ #if defined(NRF54LM20A_ENGA_XXAA )
90+ static nrfx_uarte_t _uart_id = NRFX_UARTE_INSTANCE (20 );
91+ #else
8892static nrfx_uarte_t _uart_id = NRFX_UARTE_INSTANCE (120 );
93+ #endif
8994
9095#else
9196
@@ -114,7 +119,7 @@ void USBD_IRQHandler(void) {
114119// We must call it within SD's SOC event handler, or set it as power event handler if SD is not enabled.
115120extern void tusb_hal_nrf_power_event (uint32_t event );
116121
117- #if !defined(NRF54H20_XXAA )
122+ #if !defined(NRF54H20_XXAA ) && !defined( NRF54LM20A_ENGA_XXAA )
118123// nrf power callback, could be unused if SD is enabled or usb is disabled (board_test example)
119124TU_ATTR_UNUSED static void power_event_handler (nrfx_power_usb_evt_t event ) {
120125 tusb_hal_nrf_power_event ((uint32_t ) event );
@@ -133,7 +138,7 @@ static nrfx_gpiote_t _gpiote = NRFX_GPIOTE_INSTANCE(0);
133138//--------------------------------------------------------------------+
134139
135140void board_init (void ) {
136- #if !defined(NRF54H20_XXAA )
141+ #if !defined(NRF54H20_XXAA ) && !defined( NRF54LM20A_ENGA_XXAA )
137142 // stop LF clock just in case we jump from application without reset
138143 NRF_CLOCK -> TASKS_LFCLKSTOP = 1UL ;
139144
@@ -186,11 +191,63 @@ void board_init(void) {
186191
187192 //------------- USB -------------//
188193#if CFG_TUD_ENABLED
194+
195+ #if defined(NRF54LM20A_ENGA_XXAA )
196+ // Start the USB voltage regulator
197+ NRF_VREGUSB -> TASKS_START = VREGUSB_TASKS_START_TASKS_START_Trigger ;
198+
199+ // Request HFXO crystal clock for PCLK24M (required by USBHS core)
200+ NRF_CLOCK -> TASKS_XO24MSTART = CLOCK_TASKS_XO24MSTART_TASKS_XO24MSTART_Trigger ;
201+ while (!NRF_CLOCK -> EVENTS_XO24MSTARTED ) {}
202+ NRF_CLOCK -> EVENTS_XO24MSTARTED = 0 ;
203+ #endif
204+
205+ #if defined(NRF54H20_XXAA )
206+ // Enable the USBHS wrapper (core + PHY) before any DWC2 register access
207+ NRF_USBHS -> ENABLE = (USBHS_ENABLE_PHY_Enabled << USBHS_ENABLE_PHY_Pos ) |
208+ (USBHS_ENABLE_CORE_Enabled << USBHS_ENABLE_CORE_Pos );
209+ NRF_USBHS -> TASKS_START = USBHS_TASKS_START_TASKS_START_Trigger ;
210+ // Brief delay for PHY PLL lock and core power-up
211+ for (volatile int i = 0 ; i < 1000 ; i ++ ) {}
212+ #endif
213+
214+ #if defined(NRF54LM20A_ENGA_XXAA )
215+ // Based on Zephyr usbhs_enable_core() in drivers/usb/udc/udc_dwc2_vendor_quirks.h
216+ // Step 1: Power up core only (PHY not yet)
217+ NRF_USBHS -> ENABLE = USBHS_ENABLE_CORE_Msk ;
218+
219+ // Step 2: Override ID=Device (bit 31), and temporarily override VBUSVALID
220+ NRF_USBHS -> PHY .OVERRIDEVALUES = (USBHS_PHY_OVERRIDEVALUES_ID_Device << USBHS_PHY_OVERRIDEVALUES_ID_Pos );
221+ NRF_USBHS -> PHY .INPUTOVERRIDE = USBHS_PHY_INPUTOVERRIDE_ID_Msk | USBHS_PHY_INPUTOVERRIDE_VBUSVALID_Msk ;
222+
223+ // Step 3: Release PHY power-on reset by enabling PHY
224+ NRF_USBHS -> ENABLE = USBHS_ENABLE_PHY_Msk | USBHS_ENABLE_CORE_Msk ;
225+
226+ // Step 4: Wait 45us for PHY clock to start
227+ NRFX_DELAY_US (45 );
228+
229+ // Step 5: Release DWC2 reset
230+ NRF_USBHS -> TASKS_START = USBHS_TASKS_START_TASKS_START_Trigger ;
231+
232+ // Step 6: Wait for clock to start to avoid hang on too early register read
233+ NRFX_DELAY_US (2 );
234+
235+ // Step 7: Clear VBUSVALID override (keep ID=Device override)
236+ // DWC2 is now in Non-Driving opmode; D+ pull-up will activate when DWC2 clears DCTL SftDiscon
237+ NRF_USBHS -> PHY .INPUTOVERRIDE = USBHS_PHY_INPUTOVERRIDE_ID_Msk ;
238+
239+ // Barrier: USBHS wrapper (0x5005A000) and USBHSCORE (0x50020000) are separate
240+ // peripheral blocks. Ensure the ENABLE/TASKS_START writes have propagated from
241+ // the Cortex-M33 write buffer to hardware before anyone reads DWC2 core regs.
242+ __DSB ();
243+
244+ #endif
245+
189246 // Priorities 0, 1, 4 (nRF52) are reserved for SoftDevice
190247 // 2 is highest for application
191248 NVIC_SetPriority (USBD_IRQn , 2 );
192249
193- #if !defined(NRF54H20_XXAA )
250+ #if !defined(NRF54H20_XXAA ) && !defined( NRF54LM20A_ENGA_XXAA )
194251 // USB power may already be ready at this time -> no event generated
195252 // We need to invoke the handler based on the status initially
196253 uint32_t usb_reg ;
@@ -258,7 +315,7 @@ size_t board_get_unique_id(uint8_t id[], size_t max_len) {
258315
259316#if defined(NRF54H20_XXAA )
260317 uintptr_t did_addr = (uintptr_t ) NRF_FICR -> BLE .ADDR ;
261- #elif defined(NRF5340_XXAA )
318+ #elif defined(NRF54LM20A_ENGA_XXAA ) || defined( NRF5340_XXAA )
262319 uintptr_t did_addr = (uintptr_t ) NRF_FICR -> INFO .DEVICEID ;
263320#else
264321 uintptr_t did_addr = (uintptr_t ) NRF_FICR -> DEVICEID ;
0 commit comments