Add initial board support for nRF54LM20 DK

This commit is contained in:
gab-k
2026-03-27 18:34:04 +01:00
committed by hathach
parent 7c3f5979ff
commit a39e9cdf2c
12 changed files with 238 additions and 16 deletions

View File

@ -221,6 +221,7 @@ nrf52840dk Nordic nRF52840DK nrf ht
nrf52840dongle Nordic nRF52840 Dongle nrf https://www.nordicsemi.com/Software-and-Tools/Development-Kits/nRF52840-Dongle
nrf5340dk Nordic nRF5340 DK nrf https://www.nordicsemi.com/Software-and-Tools/Development-Kits/nRF5340-DK
nrf54h20dk Nordic nRF54H20 DK nrf https://www.nordicsemi.com/Software-and-Tools/Development-Kits/nRF5340-DK
nrf54lm20dk Nordic nRF54LM20 DK nrf https://www.nordicsemi.com/Products/Development-hardware/nRF54LM20-DK
=========================== ===================================== ======== ============================================================================== ======
Raspberry Pi

View File

@ -470,6 +470,10 @@
"name": "nrf54h20dk",
"inherits": "default"
},
{
"name": "nrf54lm20dk",
"inherits": "default"
},
{
"name": "nutiny_nuc126v",
"inherits": "default"
@ -1563,6 +1567,11 @@
"description": "Build preset for the nrf54h20dk board",
"configurePreset": "nrf54h20dk"
},
{
"name": "nrf54lm20dk",
"description": "Build preset for the nrf54lm20dk board",
"configurePreset": "nrf54lm20dk"
},
{
"name": "nutiny_nuc126v",
"description": "Build preset for the nutiny_nuc126v board",
@ -3711,6 +3720,19 @@
}
]
},
{
"name": "nrf54lm20dk",
"steps": [
{
"type": "configure",
"name": "nrf54lm20dk"
},
{
"type": "build",
"name": "nrf54lm20dk"
}
]
},
{
"name": "nutiny_nuc126v",
"steps": [

View File

@ -0,0 +1,11 @@
set(MCU_VARIANT nrf54lm20a_enga)
set(JLINK_DEVICE NRF54LM20A_M33)
function(update_board TARGET)
target_compile_definitions(${TARGET} PUBLIC
CFG_EXAMPLE_VIDEO_READONLY
)
target_sources(${TARGET} PRIVATE
# ${NRFX_PATH}/drivers/src/nrfx_usbreg.c
)
endfunction()

View File

@ -0,0 +1,58 @@
/*
* The MIT License (MIT)
*
* Copyright (c) 2020, Ha Thach (tinyusb.org)
* Copyright (c) 2026, Gabriel Koppenstein
*
* 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.
*/
/* metadata:
name: Nordic nRF54LM20 DK
url: https://www.nordicsemi.com/Products/Development-hardware/nRF54LM20-DK
*/
#ifndef BOARD_H_
#define BOARD_H_
#ifdef __cplusplus
extern "C" {
#endif
#define _PINNUM(port, pin) ((port)*32 + (pin))
// LED0 active high (MOSFET-driven)
#define LED_PIN _PINNUM(1, 22)
#define LED_STATE_ON 1
// Button0 active low
#define BUTTON_PIN _PINNUM(1, 26)
#define BUTTON_STATE_ACTIVE 0
// UART20 (VCOM via debugger)
#define UART_TX_PIN _PINNUM(1, 16)
#define UART_RX_PIN _PINNUM(1, 17)
#ifdef __cplusplus
}
#endif
#endif /* BOARD_H_ */

View File

@ -0,0 +1,6 @@
MCU_VARIANT = nrf54lm20a_enga
CFLAGS += -DNRF54LM20A_ENGA_XXAA
# flash using jlink
JLINK_DEVICE = NRF54LM20A_M33
flash: flash-jlink

View File

@ -2,6 +2,7 @@
* The MIT License (MIT)
*
* Copyright (c) 2019 Ha Thach (tinyusb.org)
* Copyright (c) 2026, Gabriel Koppenstein
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@ -45,7 +46,7 @@
#include "nrfx.h"
#include "hal/nrf_gpio.h"
#include "nrfx_gpiote.h"
#if !defined(NRF54H20_XXAA)
#if !defined(NRF54H20_XXAA) && !defined(NRF54LM20A_ENGA_XXAA)
#include "nrfx_power.h"
#endif
#include "nrfx_uarte.h"
@ -79,13 +80,17 @@ enum {
};
// Forward USB interrupt events to TinyUSB IRQ Handler
#if defined(NRF54H20_XXAA)
#if defined(NRF54H20_XXAA) || defined(NRF54LM20A_ENGA_XXAA)
#define USBD_IRQn USBHS_IRQn
void USBHS_IRQHandler(void) {
tusb_int_handler(0, true);
}
#if defined(NRF54LM20A_ENGA_XXAA)
static nrfx_uarte_t _uart_id = NRFX_UARTE_INSTANCE(20);
#else
static nrfx_uarte_t _uart_id = NRFX_UARTE_INSTANCE(120);
#endif
#else
@ -114,7 +119,7 @@ void USBD_IRQHandler(void) {
// We must call it within SD's SOC event handler, or set it as power event handler if SD is not enabled.
extern void tusb_hal_nrf_power_event(uint32_t event);
#if !defined(NRF54H20_XXAA)
#if !defined(NRF54H20_XXAA) && !defined(NRF54LM20A_ENGA_XXAA)
// nrf power callback, could be unused if SD is enabled or usb is disabled (board_test example)
TU_ATTR_UNUSED static void power_event_handler(nrfx_power_usb_evt_t event) {
tusb_hal_nrf_power_event((uint32_t) event);
@ -133,7 +138,7 @@ static nrfx_gpiote_t _gpiote = NRFX_GPIOTE_INSTANCE(0);
//--------------------------------------------------------------------+
void board_init(void) {
#if !defined(NRF54H20_XXAA)
#if !defined(NRF54H20_XXAA) && !defined(NRF54LM20A_ENGA_XXAA)
// stop LF clock just in case we jump from application without reset
NRF_CLOCK->TASKS_LFCLKSTOP = 1UL;
@ -186,11 +191,63 @@ void board_init(void) {
//------------- USB -------------//
#if CFG_TUD_ENABLED
#if defined(NRF54LM20A_ENGA_XXAA)
// Start the USB voltage regulator
NRF_VREGUSB->TASKS_START = VREGUSB_TASKS_START_TASKS_START_Trigger;
// Request HFXO crystal clock for PCLK24M (required by USBHS core)
NRF_CLOCK->TASKS_XO24MSTART = CLOCK_TASKS_XO24MSTART_TASKS_XO24MSTART_Trigger;
while (!NRF_CLOCK->EVENTS_XO24MSTARTED) {}
NRF_CLOCK->EVENTS_XO24MSTARTED = 0;
#endif
#if defined(NRF54H20_XXAA)
// Enable the USBHS wrapper (core + PHY) before any DWC2 register access
NRF_USBHS->ENABLE = (USBHS_ENABLE_PHY_Enabled << USBHS_ENABLE_PHY_Pos) |
(USBHS_ENABLE_CORE_Enabled << USBHS_ENABLE_CORE_Pos);
NRF_USBHS->TASKS_START = USBHS_TASKS_START_TASKS_START_Trigger;
// Brief delay for PHY PLL lock and core power-up
for (volatile int i = 0; i < 1000; i++) {}
#endif
#if defined(NRF54LM20A_ENGA_XXAA)
// 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_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_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
// Priorities 0, 1, 4 (nRF52) are reserved for SoftDevice
// 2 is highest for application
NVIC_SetPriority(USBD_IRQn, 2);
#if !defined(NRF54H20_XXAA)
#if !defined(NRF54H20_XXAA) && !defined(NRF54LM20A_ENGA_XXAA)
// USB power may already be ready at this time -> no event generated
// We need to invoke the handler based on the status initially
uint32_t usb_reg;
@ -258,7 +315,7 @@ size_t board_get_unique_id(uint8_t id[], size_t max_len) {
#if defined(NRF54H20_XXAA)
uintptr_t did_addr = (uintptr_t) NRF_FICR->BLE.ADDR;
#elif defined(NRF5340_XXAA)
#elif defined(NRF54LM20A_ENGA_XXAA) || defined(NRF5340_XXAA)
uintptr_t did_addr = (uintptr_t) NRF_FICR->INFO.DEVICEID;
#else
uintptr_t did_addr = (uintptr_t) NRF_FICR->DEVICEID;

View File

@ -10,15 +10,19 @@ if (NOT board_cmake_included)
endif ()
# toolchain set up
if (MCU_VARIANT STREQUAL nrf5340 OR MCU_VARIANT STREQUAL nrf54h20)
if (MCU_VARIANT STREQUAL nrf5340 OR MCU_VARIANT STREQUAL nrf54h20 OR MCU_VARIANT STREQUAL nrf54lm20a_enga)
set(CMAKE_SYSTEM_CPU cortex-m33 CACHE INTERNAL "System Processor")
set(JLINK_DEVICE ${MCU_VARIANT}_xxaa_app)
if (NOT DEFINED JLINK_DEVICE)
set(JLINK_DEVICE ${MCU_VARIANT}_xxaa_app)
endif ()
else ()
set(CMAKE_SYSTEM_CPU cortex-m4 CACHE INTERNAL "System Processor")
set(JLINK_DEVICE ${MCU_VARIANT}_xxaa)
if (NOT DEFINED JLINK_DEVICE)
set(JLINK_DEVICE ${MCU_VARIANT}_xxaa)
endif ()
endif ()
if (MCU_VARIANT STREQUAL "nrf54h20")
if (MCU_VARIANT STREQUAL "nrf54h20" OR MCU_VARIANT STREQUAL "nrf54lm20a_enga")
set(FAMILY_MCUS NRF54 CACHE INTERNAL "")
else ()
set(FAMILY_MCUS NRF5X CACHE INTERNAL "")
@ -29,7 +33,10 @@ set(CMAKE_TOOLCHAIN_FILE ${TOP}/examples/build_system/cmake/toolchain/arm_${TOOL
#------------------------------------
# Startup & Linker script
#------------------------------------
if (MCU_VARIANT STREQUAL nrf54h20)
if (MCU_VARIANT STREQUAL nrf54lm20a_enga)
set(LD_FILE_GNU_DEFAULT ${CMAKE_CURRENT_LIST_DIR}/linker/${MCU_VARIANT}_xxaa_application.ld)
set(STARTUP_FILE_GNU ${NRFX_PATH}/mdk/gcc_startup_${MCU_VARIANT}_application.S)
elseif (MCU_VARIANT STREQUAL nrf54h20)
set(LD_FILE_GNU_DEFAULT ${CMAKE_CURRENT_LIST_DIR}/linker/${MCU_VARIANT}_xxaa_application.ld)
set(STARTUP_FILE_GNU ${NRFX_PATH}/mdk/gcc_startup_${MCU_VARIANT}_application.S)
elseif (MCU_VARIANT STREQUAL nrf5340)
@ -52,13 +59,20 @@ function(family_add_board BOARD_TARGET)
add_library(${BOARD_TARGET} STATIC
${NRFX_PATH}/helpers/nrfx_flag32_allocator.c
${NRFX_PATH}/drivers/src/nrfx_gpiote.c
${NRFX_PATH}/drivers/src/nrfx_power.c
${NRFX_PATH}/drivers/src/nrfx_spim.c
${NRFX_PATH}/drivers/src/nrfx_uarte.c
${NRFX_PATH}/soc/nrfx_atomic.c
)
if (MCU_VARIANT STREQUAL nrf54h20)
if (NOT FAMILY_MCUS STREQUAL "NRF54")
target_sources(${BOARD_TARGET} PRIVATE ${NRFX_PATH}/drivers/src/nrfx_power.c)
endif ()
if (MCU_VARIANT STREQUAL nrf54lm20a_enga)
target_sources(${BOARD_TARGET} PRIVATE
${NRFX_PATH}/mdk/system_nrf54l.c
)
elseif (MCU_VARIANT STREQUAL nrf54h20)
target_sources(${BOARD_TARGET} PRIVATE
${NRFX_PATH}/mdk/system_nrf54h.c
)

View File

@ -4,6 +4,15 @@ NRFX_PATH = hw/mcu/nordic/nrfx
include $(TOP)/$(BOARD_PATH)/board.mk
ifeq (${MCU_VARIANT},nrf54lm20a_enga)
CPU_CORE = cortex-m33
CFLAGS += -DCFG_TUSB_MCU=OPT_MCU_NRF54
LD_FILE_DEFAULT = ${FAMILY_PATH}/linker/${MCU_VARIANT}_xxaa_application.ld
SRC_C += ${NRFX_PATH}/mdk/system_nrf54l.c
SRC_S += ${NRFX_PATH}/mdk/gcc_startup_$(MCU_VARIANT)_application.S
JLINK_DEVICE ?= $(MCU_VARIANT)_xxaa_app
else
ifeq (${MCU_VARIANT},nrf54h20)
CPU_CORE = cortex-m33
CFLAGS += -DCFG_TUSB_MCU=OPT_MCU_NRF54
@ -32,6 +41,7 @@ else
JLINK_DEVICE ?= $(MCU_VARIANT)_xxaa
endif
endif
endif
CFLAGS += \
-DNRF_APPLICATION \
@ -70,11 +80,14 @@ SRC_C += \
src/portable/synopsys/dwc2/hcd_dwc2.c \
${NRFX_PATH}/helpers/nrfx_flag32_allocator.c \
${NRFX_PATH}/drivers/src/nrfx_gpiote.c \
${NRFX_PATH}/drivers/src/nrfx_power.c \
${NRFX_PATH}/drivers/src/nrfx_spim.c \
${NRFX_PATH}/drivers/src/nrfx_uarte.c \
${NRFX_PATH}/soc/nrfx_atomic.c
ifeq (,$(findstring OPT_MCU_NRF54,$(CFLAGS)))
SRC_C += ${NRFX_PATH}/drivers/src/nrfx_power.c
endif
INC += \
$(TOP)/$(BOARD_PATH) \
$(TOP)/$(FAMILY_PATH)/nrfx_config \

View File

@ -0,0 +1,13 @@
/* Linker script to configure memory regions. */
SEARCH_DIR(.)
/*GROUP(-lgcc -lc) not compatible with clang*/
MEMORY
{
FLASH (rx) : ORIGIN = 0x00000000, LENGTH = 0x1FD000 /* Inside global RRAM0 */
RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 0x40000
RAM1 (rwx) : ORIGIN = 0x20040000, LENGTH = 0x40000
}
INCLUDE "nrf_common.ld"

View File

@ -62,6 +62,9 @@
#if defined(NRF54H20_XXAA)
#define NRFX_UARTE120_ENABLED 1
#elif defined(NRF54LM20A_ENGA_XXAA)
#define NRFX_UARTE20_ENABLED 1
#else
#define NRFX_POWER_ENABLED 1

View File

@ -166,6 +166,7 @@
#define TUP_USBIP_DWC2
#define TUP_USBIP_DWC2_NRF
#define TUP_DCD_ENDPOINT_MAX 16
#define TUP_RHPORT_HIGHSPEED 1
#define CFG_TUH_DWC2_DMA_ENABLE_DEFAULT 0
//--------------------------------------------------------------------+

View File

@ -2,6 +2,7 @@
* The MIT License (MIT)
*
* Copyright (c) 2025 Ha Thach (tinyusb.org)
* Copyright (c) 2026, Gabriel Koppenstein
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@ -30,14 +31,25 @@
#define DWC2_EP_MAX 16
// Use the auto-resolving peripheral pointer (respects TrustZone secure/non-secure mapping)
#if defined(NRF54LM20A_ENGA_XXAA)
#define _DWC2_NRF_REG_BASE ((uintptr_t) NRF_USBHSCORE)
#else
#define _DWC2_NRF_REG_BASE ((uintptr_t) NRF_USBHSCORE0)
#endif
static const dwc2_controller_t _dwc2_controller[] = {
{ .reg_base = NRF_USBHSCORE0_NS_BASE, .irqnum = USBHS_IRQn, .ep_count = 16, .ep_fifo_size = 12288 },
{ .reg_base = _DWC2_NRF_REG_BASE, .irqnum = USBHS_IRQn, .ep_count = 16, .ep_fifo_size = 12160 },
};
TU_ATTR_ALWAYS_INLINE static inline void dwc2_int_set(uint8_t rhport, tusb_role_t role, bool enabled) {
(void) rhport;
(void) role;
(void) enabled;
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)
@ -64,4 +76,15 @@ TU_ATTR_ALWAYS_INLINE static inline void dwc2_phy_update(dwc2_regs_t* dwc2, uint
(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