| /* |
| * 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 |
| * 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: |
| manufacturer: Nordic Semiconductor |
| */ |
| |
| #include "bsp/board_api.h" |
| #include "board.h" |
| |
| // Suppress warning caused by mcu driver |
| #ifdef __GNUC__ |
| #pragma GCC diagnostic push |
| #pragma GCC diagnostic ignored "-Wcast-qual" |
| #pragma GCC diagnostic ignored "-Wcast-align" |
| #pragma GCC diagnostic ignored "-Wunused-parameter" |
| #pragma GCC diagnostic ignored "-Wunused-variable" |
| #pragma GCC diagnostic ignored "-Wundef" |
| #pragma GCC diagnostic ignored "-Wredundant-decls" |
| #endif |
| |
| #include "nrfx.h" |
| #include "hal/nrf_gpio.h" |
| #include "nrfx_gpiote.h" |
| #if !defined(NRF54H20_XXAA) && !defined(NRF54LM20A_ENGA_XXAA) |
| #include "nrfx_power.h" |
| #endif |
| #include "nrfx_uarte.h" |
| #include "nrfx_spim.h" |
| |
| #ifdef SOFTDEVICE_PRESENT |
| #include "nrf_sdm.h" |
| #include "nrf_soc.h" |
| #endif |
| |
| #ifdef __GNUC__ |
| #pragma GCC diagnostic pop |
| #endif |
| |
| // example only supports nrfx v3 for code simplicity |
| #if !(defined(NRFX_CONFIG_API_VER_MAJOR) && NRFX_CONFIG_API_VER_MAJOR >= 3) && \ |
| !(85301 >= (10000*MDK_MAJOR_VERSION + 100*MDK_MINOR_VERSION + MDK_MICRO_VERSION)) |
| #error "Example requires nrfx v3.0.0 or later" |
| #endif |
| |
| |
| /*------------------------------------------------------------------*/ |
| /* MACRO TYPEDEF CONSTANT ENUM |
| *------------------------------------------------------------------*/ |
| |
| // Value is chosen to be as same as NRFX_POWER_USB_EVT_* in nrfx_power.h |
| enum { |
| USB_EVT_DETECTED = 0, |
| USB_EVT_REMOVED = 1, |
| USB_EVT_READY = 2 |
| }; |
| |
| // Forward USB interrupt events to TinyUSB IRQ Handler |
| #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 |
| #ifdef NRF5340_XXAA |
| #define LFCLK_SRC_RC CLOCK_LFCLKSRC_SRC_LFRC |
| #define VBUSDETECT_Msk USBREG_USBREGSTATUS_VBUSDETECT_Msk |
| #define OUTPUTRDY_Msk USBREG_USBREGSTATUS_OUTPUTRDY_Msk |
| #define GPIOTE_IRQn GPIOTE1_IRQn |
| #else |
| #define LFCLK_SRC_RC CLOCK_LFCLKSRC_SRC_RC |
| #define VBUSDETECT_Msk POWER_USBREGSTATUS_VBUSDETECT_Msk |
| #define OUTPUTRDY_Msk POWER_USBREGSTATUS_OUTPUTRDY_Msk |
| #endif |
| |
| #if CFG_TUSB_OS != OPT_OS_ZEPHYR |
| static nrfx_uarte_t _uart_id = NRFX_UARTE_INSTANCE(0); |
| #endif |
| |
| void USBD_IRQHandler(void) { |
| tusb_int_handler(0, true); |
| } |
| #endif |
| |
| //--------------------------------------------------------------------+ |
| // UART RX ring buffer and event handler |
| //--------------------------------------------------------------------+ |
| #if CFG_TUSB_OS != OPT_OS_ZEPHYR |
| |
| #define UART_RX_BUFSIZE 64 |
| |
| static uint8_t rx_fifo[UART_RX_BUFSIZE]; |
| static volatile uint16_t rx_fifo_head = 0; |
| static volatile uint16_t rx_fifo_tail = 0; |
| |
| static uint8_t rx_dma_buf[1]; // 1-byte DMA target for continuous RX |
| |
| static void uart_event_handler(nrfx_uarte_event_t const* p_event, void* p_context) { |
| (void) p_context; |
| if (p_event->type == NRFX_UARTE_EVT_RX_DONE) { |
| for (size_t i = 0; i < p_event->data.rx.length; i++) { |
| uint16_t next_head = (rx_fifo_head + 1) & (UART_RX_BUFSIZE - 1); |
| if (next_head != rx_fifo_tail) { |
| rx_fifo[rx_fifo_head] = p_event->data.rx.p_buffer[i]; |
| rx_fifo_head = next_head; |
| } |
| } |
| // re-arm 1-byte RX |
| nrfx_uarte_rx(&_uart_id, rx_dma_buf, sizeof(rx_dma_buf)); |
| } |
| } |
| |
| #endif |
| |
| // tinyusb function that handles power event (detected, ready, removed) |
| // 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) && !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); |
| } |
| #endif |
| |
| //------------- Host using MAX2341E -------------// |
| #if CFG_TUH_ENABLED && defined(CFG_TUH_MAX3421) && CFG_TUH_MAX3421 |
| static void max3421_init(void); |
| static nrfx_spim_t _spi = NRFX_SPIM_INSTANCE(1); |
| static nrfx_gpiote_t _gpiote = NRFX_GPIOTE_INSTANCE(0); |
| #endif |
| |
| //--------------------------------------------------------------------+ |
| // |
| //--------------------------------------------------------------------+ |
| void board_init(void) { |
| #if defined(TRACE_ETM) && defined(NRF5340_XXAA) |
| // SystemInit (ENABLE_TRACE) sets the TAD trace port to 64 MHz, which is |
| // marginal through the DK's switch stubs - 16 MHz streams reliably (matches the validated nrf52840dk) and is |
| // ample bandwidth for the 64 MHz core |
| NRF_TAD_S->TRACEPORTSPEED = TAD_TRACEPORTSPEED_TRACEPORTSPEED_16MHz; |
| #endif |
| #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; |
| |
| // Use Internal OSC to compatible with all boards |
| NRF_CLOCK->LFCLKSRC = LFCLK_SRC_RC; |
| NRF_CLOCK->TASKS_LFCLKSTART = 1UL; |
| #endif |
| |
| // LED |
| nrf_gpio_cfg_output(LED_PIN); |
| board_led_write(false); |
| |
| // Button |
| nrf_gpio_cfg_input(BUTTON_PIN, NRF_GPIO_PIN_PULLUP); |
| |
| #if CFG_TUSB_OS == OPT_OS_NONE |
| // 1ms tick timer |
| SysTick_Config(SystemCoreClock / 1000); |
| |
| #elif CFG_TUSB_OS == OPT_OS_ZEPHYR |
| #ifdef CONFIG_HAS_HW_NRF_USBREG |
| // IRQ_CONNECT(USBREGULATOR_IRQn, DT_IRQ(DT_INST(0, nordic_nrf_clock), priority), nrfx_isr, nrfx_usbreg_irq_handler, 0); |
| // irq_enable(USBREGULATOR_IRQn); |
| #endif |
| |
| /* USB device controller access from devicetree */ |
| #define DT_DRV_COMPAT nordic_nrf_usbd |
| IRQ_CONNECT(DT_INST_IRQN(0), DT_INST_IRQ(0, priority), nrfx_isr, USBD_IRQHandler, 0); |
| irq_enable(DT_INST_IRQN(0)); |
| #endif |
| |
| #if CFG_TUSB_OS != OPT_OS_ZEPHYR |
| // UART |
| static uint8_t uart_tx_cache[64]; |
| nrfx_uarte_config_t uart_cfg = { |
| .txd_pin = UART_TX_PIN, |
| .rxd_pin = UART_RX_PIN, |
| .rts_pin = NRF_UARTE_PSEL_DISCONNECTED, |
| .cts_pin = NRF_UARTE_PSEL_DISCONNECTED, |
| .p_context = NULL, |
| .tx_cache = { .p_buffer = uart_tx_cache, .length = sizeof(uart_tx_cache) }, |
| .baudrate = NRF_UARTE_BAUDRATE_115200, // CFG_BOARD_UART_BAUDRATE |
| .interrupt_priority = 7, |
| .config = { |
| .hwfc = NRF_UARTE_HWFC_DISABLED, |
| .parity = NRF_UARTE_PARITY_EXCLUDED, |
| } |
| }; |
| |
| nrfx_uarte_init(&_uart_id, &uart_cfg, uart_event_handler); |
| // start continuous 1-byte RX |
| nrfx_uarte_rx(&_uart_id, rx_dma_buf, sizeof(rx_dma_buf)); |
| #endif |
| |
| //------------- USB -------------// |
| #if CFG_TUD_ENABLED |
| |
| #if defined(NRF54LM20A_ENGA_XXAA) |
| // 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 |
| |
| // Priorities 0, 1, 4 (nRF52) are reserved for SoftDevice |
| // 2 is highest for application |
| NVIC_SetPriority(USBD_IRQn, 2); |
| |
| #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; |
| |
| #ifdef SOFTDEVICE_PRESENT |
| uint8_t sd_en = false; |
| sd_softdevice_is_enabled(&sd_en); |
| |
| if ( sd_en ) { |
| sd_power_usbdetected_enable(true); |
| sd_power_usbpwrrdy_enable(true); |
| sd_power_usbremoved_enable(true); |
| |
| sd_power_usbregstatus_get(&usb_reg); |
| }else |
| #endif |
| { |
| // Power module init |
| const nrfx_power_config_t pwr_cfg = {0}; |
| nrfx_power_init(&pwr_cfg); |
| |
| // Register tusb function as USB power handler |
| // cause cast-function-type warning |
| const nrfx_power_usbevt_config_t config = {.handler = power_event_handler}; |
| nrfx_power_usbevt_init(&config); |
| nrfx_power_usbevt_enable(); |
| |
| // USB power may already be ready at this time -> no event generated |
| // We need to invoke the handler based on the status initially |
| #ifdef NRF5340_XXAA |
| usb_reg = NRF_USBREGULATOR->USBREGSTATUS; |
| #else |
| usb_reg = NRF_POWER->USBREGSTATUS; |
| #endif |
| } |
| |
| if ( usb_reg & VBUSDETECT_Msk ) { |
| tusb_hal_nrf_power_event(USB_EVT_DETECTED); |
| } |
| if ( usb_reg & OUTPUTRDY_Msk ) { |
| tusb_hal_nrf_power_event(USB_EVT_READY); |
| } |
| #endif |
| #endif |
| |
| #if CFG_TUH_ENABLED && defined(CFG_TUH_MAX3421) && CFG_TUH_MAX3421 |
| max3421_init(); |
| #endif |
| |
| } |
| |
| //--------------------------------------------------------------------+ |
| // Board porting API |
| //--------------------------------------------------------------------+ |
| void board_led_write(bool state) { |
| nrf_gpio_pin_write(LED_PIN, state ? LED_STATE_ON : (1 - LED_STATE_ON)); |
| } |
| |
| uint32_t board_button_read(void) { |
| return BUTTON_STATE_ACTIVE == nrf_gpio_pin_read(BUTTON_PIN); |
| } |
| |
| size_t board_get_unique_id(uint8_t id[], size_t max_len) { |
| (void) max_len; |
| |
| #if defined(NRF54H20_XXAA) |
| uintptr_t did_addr = (uintptr_t) NRF_FICR->BLE.ADDR; |
| #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; |
| #endif |
| |
| const uint8_t* device_id = (const uint8_t*) did_addr; |
| for(uint8_t i=0; i<8; i++) { |
| id[i] = device_id[i]; |
| } |
| return 8; |
| } |
| |
| int board_uart_read(uint8_t* buf, int len) { |
| #if CFG_TUSB_OS == OPT_OS_ZEPHYR |
| (void) buf; |
| (void) len; |
| return -1; |
| #else |
| int count = 0; |
| while (count < len && rx_fifo_tail != rx_fifo_head) { |
| buf[count++] = rx_fifo[rx_fifo_tail]; |
| rx_fifo_tail = (rx_fifo_tail + 1) & (UART_RX_BUFSIZE - 1); |
| } |
| return count; |
| #endif |
| } |
| |
| int board_uart_write(void const* buf, int len) { |
| #if CFG_TUSB_OS == OPT_OS_ZEPHYR |
| (void) buf; |
| return len; |
| #else |
| if (nrfx_uarte_tx_in_progress(&_uart_id)) { |
| return 0; |
| } |
| nrfx_err_t err = nrfx_uarte_tx(&_uart_id, (uint8_t const*) buf, (size_t) len, 0); |
| return (NRFX_SUCCESS == err) ? len : 0; |
| #endif |
| } |
| |
| #if CFG_TUSB_OS == OPT_OS_NONE |
| volatile uint32_t system_ticks = 0; |
| |
| void SysTick_Handler(void) { |
| system_ticks++; |
| } |
| |
| uint32_t tusb_time_millis_api(void) { |
| return system_ticks; |
| } |
| #endif |
| |
| #ifndef __ICCARM__ |
| // Implement _start() since we use linker flag '-nostartfiles'. |
| // Requires defined __STARTUP_CLEAR_BSS, |
| extern int main(void); |
| TU_ATTR_UNUSED void _start(void) { |
| // called by startup code |
| main(); |
| while (1) {} |
| } |
| #endif |
| |
| //--------------------------------------------------------------------+ |
| // Softdevice running |
| //--------------------------------------------------------------------+ |
| #ifdef SOFTDEVICE_PRESENT |
| // process SOC event from SD |
| uint32_t proc_soc(void) { |
| uint32_t soc_evt; |
| uint32_t err = sd_evt_get(&soc_evt); |
| |
| if (NRF_SUCCESS == err) |
| { |
| /*------------- usb power event handler -------------*/ |
| int32_t usbevt = (soc_evt == NRF_EVT_POWER_USB_DETECTED ) ? NRFX_POWER_USB_EVT_DETECTED: |
| (soc_evt == NRF_EVT_POWER_USB_POWER_READY) ? NRFX_POWER_USB_EVT_READY : |
| (soc_evt == NRF_EVT_POWER_USB_REMOVED ) ? NRFX_POWER_USB_EVT_REMOVED : -1; |
| |
| if ( usbevt >= 0) tusb_hal_nrf_power_event(usbevt); |
| } |
| |
| return err; |
| } |
| |
| uint32_t proc_ble(void) { |
| // do nothing with ble |
| return NRF_ERROR_NOT_FOUND; |
| } |
| |
| void SD_EVT_IRQHandler(void) { |
| // process BLE and SOC until there is no more events |
| while( (NRF_ERROR_NOT_FOUND != proc_ble()) || (NRF_ERROR_NOT_FOUND != proc_soc()) ) { |
| } |
| } |
| |
| void nrf_error_cb(uint32_t id, uint32_t pc, uint32_t info) { |
| (void) id; |
| (void) pc; |
| (void) info; |
| } |
| #endif |
| |
| //--------------------------------------------------------------------+ |
| // API: SPI transfer with MAX3421E, must be implemented by application |
| //--------------------------------------------------------------------+ |
| #if CFG_TUH_ENABLED && defined(CFG_TUH_MAX3421) && CFG_TUH_MAX3421 |
| void max3421_int_handler(nrfx_gpiote_pin_t pin, nrfx_gpiote_trigger_t action, void* p_context) { |
| (void) p_context; |
| if (action != NRFX_GPIOTE_TRIGGER_HITOLO) { |
| return; |
| } |
| if (pin != MAX3421_INTR_PIN) { |
| return; |
| } |
| |
| tusb_int_handler(1, true); |
| } |
| |
| static void max3421_init(void) { |
| // Somehow pca10056/95 is not working probably due to signal incompatible (1.8V 3v3) with MAC3421E !? |
| |
| // manually manage CS |
| nrf_gpio_cfg_output(MAX3421_CS_PIN); |
| nrf_gpio_pin_write(MAX3421_CS_PIN, 1); |
| |
| // USB host using max3421e usb controller via SPI |
| nrfx_spim_config_t cfg = { |
| .sck_pin = MAX3421_SCK_PIN, |
| .mosi_pin = MAX3421_MOSI_PIN, |
| .miso_pin = MAX3421_MISO_PIN, |
| .ss_pin = NRF_SPIM_PIN_NOT_CONNECTED, |
| .frequency = 4000000u, |
| .ss_active_high = false, |
| .irq_priority = 3, |
| .orc = 0xFF, |
| // default setting 4 Mhz, Mode 0, MSB first |
| .mode = NRF_SPIM_MODE_0, |
| .bit_order = NRF_SPIM_BIT_ORDER_MSB_FIRST, |
| .miso_pull = NRF_GPIO_PIN_NOPULL, |
| }; |
| |
| // no handler --> blocking |
| TU_ASSERT(NRFX_SUCCESS == nrfx_spim_init(&_spi, &cfg, NULL, NULL), ); |
| |
| // max3421e interrupt pin |
| nrf_gpio_pin_pull_t intr_pull = NRF_GPIO_PIN_PULLUP; |
| nrfx_gpiote_trigger_config_t intr_trigger = { |
| .trigger = NRFX_GPIOTE_TRIGGER_HITOLO, |
| .p_in_channel = NULL, // sensing mechanism |
| }; |
| nrfx_gpiote_handler_config_t intr_handler = { |
| .handler = max3421_int_handler, |
| .p_context = NULL, |
| }; |
| nrfx_gpiote_input_pin_config_t intr_config = { |
| .p_pull_config = &intr_pull, |
| .p_trigger_config = &intr_trigger, |
| .p_handler_config = &intr_handler, |
| }; |
| |
| nrfx_gpiote_init(&_gpiote, 1); |
| NVIC_SetPriority(GPIOTE_IRQn, 2); |
| |
| nrfx_gpiote_input_configure(&_gpiote, MAX3421_INTR_PIN, &intr_config); |
| nrfx_gpiote_trigger_enable(&_gpiote, MAX3421_INTR_PIN, true); |
| } |
| |
| // API to enable/disable MAX3421 INTR pin interrupt |
| void tuh_max3421_int_api(uint8_t rhport, bool enabled) { |
| (void) rhport; |
| |
| // use NVIC_Enable/Disable instead since nrfx_gpiote_trigger_enable/disable clear pending and can miss interrupt |
| // when disabled and re-enabled. |
| if (enabled) { |
| NVIC_EnableIRQ(GPIOTE_IRQn); |
| } else { |
| NVIC_DisableIRQ(GPIOTE_IRQn); |
| } |
| } |
| |
| // API to control MAX3421 SPI CS |
| void tuh_max3421_spi_cs_api(uint8_t rhport, bool active) { |
| (void) rhport; |
| nrf_gpio_pin_write(MAX3421_CS_PIN, active ? 0 : 1); |
| } |
| |
| // API to transfer data with MAX3421 SPI |
| // Either tx_buf or rx_buf can be NULL, which means transfer is write or read only |
| bool tuh_max3421_spi_xfer_api(uint8_t rhport, uint8_t const* tx_buf, uint8_t* rx_buf, size_t xfer_bytes) { |
| (void) rhport; |
| |
| nrfx_spim_xfer_desc_t xfer = { |
| .p_tx_buffer = tx_buf, |
| .tx_length = tx_buf ? xfer_bytes : 0, |
| .p_rx_buffer = rx_buf, |
| .rx_length = rx_buf ? xfer_bytes : 0, |
| }; |
| |
| return nrfx_spim_xfer(&_spi, &xfer, 0) == NRFX_SUCCESS; |
| } |
| |
| #endif |