From 1d06d22892828f06e4c00d8d22629eba189188d5 Mon Sep 17 00:00:00 2001 From: Marc Schink Date: Sun, 16 Mar 2025 11:17:25 +0000 Subject: [PATCH] flash/nor: Add nRF54 NVM driver The driver is intentionally not integrated into the 'nRF5' driver in order to serve as a kind of 'test vehicle' for non-flash memory drivers. The driver does not support the UICR but only the main memory region. Change-Id: Ia69527c4e07d40e8805186be8e988c0d74c12041 Signed-off-by: Marc Schink --- doc/openocd.texi | 13 ++ src/flash/nor/Makefile.am | 1 + src/flash/nor/driver.h | 1 + src/flash/nor/drivers.c | 1 + src/flash/nor/nrf54.c | 376 ++++++++++++++++++++++++++++++++++++++ 5 files changed, 392 insertions(+) create mode 100644 src/flash/nor/nrf54.c diff --git a/doc/openocd.texi b/doc/openocd.texi index ebe956789..4e8f3c8b8 100644 --- a/doc/openocd.texi +++ b/doc/openocd.texi @@ -7849,6 +7849,19 @@ code. @end deffn +@deffn {Flash Driver} {nrf54} +This driver supports the nRF54 microcontroller family from Nordic Semiconductor. +These devices use a resistive random access memory (RRAM) and no flash memory technology. +This technology requires no special erase operation, data can be arbitrarily manipulated. + +The UICR memory region is currently not supported by the driver. + +@example +flash bank $_FLASHNAME nrf54 0 0x00000000 0 0 $_TARGETNAME +@end example + +@end deffn + @deffn {Flash Driver} {ocl} This driver is an implementation of the ``on chip flash loader'' protocol proposed by Pavel Chromy. diff --git a/src/flash/nor/Makefile.am b/src/flash/nor/Makefile.am index d95249c2e..7e14607e8 100644 --- a/src/flash/nor/Makefile.am +++ b/src/flash/nor/Makefile.am @@ -53,6 +53,7 @@ NOR_DRIVERS = \ %D%/non_cfi.c \ %D%/npcx.c \ %D%/nrf5.c \ + %D%/nrf54.c \ %D%/numicro.c \ %D%/ocl.c \ %D%/pic32mx.c \ diff --git a/src/flash/nor/driver.h b/src/flash/nor/driver.h index 872e450b4..c836565dd 100644 --- a/src/flash/nor/driver.h +++ b/src/flash/nor/driver.h @@ -283,6 +283,7 @@ extern const struct flash_driver niietcm4_flash; extern const struct flash_driver npcx_flash; extern const struct flash_driver nrf51_flash; extern const struct flash_driver nrf5_flash; +extern const struct flash_driver nrf54_flash; extern const struct flash_driver numicro_flash; extern const struct flash_driver ocl_flash; extern const struct flash_driver pic32mx_flash; diff --git a/src/flash/nor/drivers.c b/src/flash/nor/drivers.c index 19015554b..c718dea4c 100644 --- a/src/flash/nor/drivers.c +++ b/src/flash/nor/drivers.c @@ -62,6 +62,7 @@ static const struct flash_driver * const flash_drivers[] = { &npcx_flash, &nrf51_flash, &nrf5_flash, + &nrf54_flash, &numicro_flash, &ocl_flash, &pic32mx_flash, diff --git a/src/flash/nor/nrf54.c b/src/flash/nor/nrf54.c new file mode 100644 index 000000000..3b90784a3 --- /dev/null +++ b/src/flash/nor/nrf54.c @@ -0,0 +1,376 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "imp.h" +#include +#include +#include +#include +#include +#include + +/* + * Related documents + * + * nRF54L15, nRF54L10, and nRF54L05 Wireless SoCs + * https://docs-be.nordicsemi.com/bundle/ps_nrf54L15/page/pdf/nRF54L15_nRF54L10_nRF54L05_Datasheet_v0.8.pdf + * + * nRF54L Series Production Programming + * https://docs-be.nordicsemi.com/bundle/nan_047/attach/nan_047.pdf + */ + +#define NRF54_FICR_BASE_ADDR 0x00FFC000 + +#define NRF54_FICR_INFO_PART_REG 0x31c +#define NRF54_FICR_INFO_VARIANT_REG 0x320 +#define NRF54_FICR_INFO_PACKAGE_REG 0x324 +#define NRF54_FICR_INFO_RAM_REG 0x328 +#define NRF54_FICR_INFO_RRAM_REG 0x32C + +#define NRF54_RRAM_BASE_ADDR 0x00000000 + +#define NRF54_RRAMC_BASE_ADDR 0x5004B000 + +#define NRF54_RRAMC_REG_CONFIG 0x500 +#define NRF54_RRAMC_REG_READY 0x400 + +#define NRF54_RRAMC_CONFIG_WEN BIT(0) + +struct { + uint32_t id; + const char *name; +} nrf54_part_names[] = { + { 0x00054B15, "nRF54L15" }, + { 0x00054B10, "nRF54L10" }, + { 0x00054B05, "nRF54L05" }, +}; + +struct nrf54_ficr_info { + // Part identification number. + uint32_t part; + /* + * Part variant (V), hardware version (H) and production + * configuration (P) encoded as ASCII:

. + */ + uint32_t variant; + /* + * Package variant (P) encoded as ASCII: <__>, where (_) indicates + * the null character. + */ + uint32_t package; + // SRAM size in KiB. + uint32_t sram_size; + // RRAM size in KiB. + uint32_t rram_size; +}; + +struct nrf54_info { + bool probed; + const char *part_name; + + struct nrf54_ficr_info ficr_info; +}; + +static int nrf54_read_ficr_info(struct target *target, + struct nrf54_ficr_info *info) +{ + int retval = target_read_u32(target, + NRF54_FICR_BASE_ADDR + NRF54_FICR_INFO_PART_REG, &info->part); + if (retval != ERROR_OK) + return retval; + + retval = target_read_u32(target, + NRF54_FICR_BASE_ADDR + NRF54_FICR_INFO_VARIANT_REG, &info->variant); + if (retval != ERROR_OK) + return retval; + + retval = target_read_u32(target, + NRF54_FICR_BASE_ADDR + NRF54_FICR_INFO_PACKAGE_REG, &info->package); + if (retval != ERROR_OK) + return retval; + + retval = target_read_u32(target, + NRF54_FICR_BASE_ADDR + NRF54_FICR_INFO_RAM_REG, &info->sram_size); + if (retval != ERROR_OK) + return retval; + + retval = target_read_u32(target, + NRF54_FICR_BASE_ADDR + NRF54_FICR_INFO_RRAM_REG, &info->rram_size); + if (retval != ERROR_OK) + return retval; + + return ERROR_OK; +} + +static const char *nrf54_get_part_name(uint32_t id) +{ + for (size_t i = 0; i < ARRAY_SIZE(nrf54_part_names); i++) { + if (id == nrf54_part_names[i].id) + return nrf54_part_names[i].name; + } + + return NULL; +} + +static void nrf54_u32_to_str(uint32_t data, char *str) +{ + uint8_t tmp[4]; + + h_u32_to_be(tmp, data); + memcpy(str, tmp, 4); + str[4] = '\0'; +} + +static bool nrf54_get_chip_type_str(const struct nrf54_info *info, + char *buf, size_t size) +{ + char variant[5]; + nrf54_u32_to_str(info->ficr_info.variant, variant); + + char package[5]; + // Note that the first two characters are always null characters. + nrf54_u32_to_str(info->ficr_info.package, package); + + int res = snprintf(buf, size, "%s-%c%c%c%c", + info->part_name, package[2], package[3], variant[0], variant[1]); + + if (res < 0) + return false; + + if ((size_t)res >= size) + return false; + + return true; +} + +static int nrf54_info(struct flash_bank *bank, struct command_invocation *cmd) +{ + struct nrf54_info *info = bank->driver_priv; + + char chip_type[128]; + if (!nrf54_get_chip_type_str(info, chip_type, sizeof(chip_type))) { + LOG_ERROR("Failed to build chip type string"); + return ERROR_FAIL; + } + + command_print_sameline(cmd, "%s %" PRIu32 " KiB RRAM, %" PRIu32 " KiB SRAM", + chip_type, info->ficr_info.rram_size, info->ficr_info.sram_size); + + return ERROR_OK; +} + +static int nrf54_probe(struct flash_bank *bank) +{ + struct target *target = bank->target; + struct nrf54_info *nrf54_info = bank->driver_priv; + struct nrf54_ficr_info *ficr_info = &nrf54_info->ficr_info; + + int retval = nrf54_read_ficr_info(target, ficr_info); + if (retval != ERROR_OK) { + LOG_ERROR("Failed to load FICR from device"); + return retval; + } + + const char *part_name = nrf54_get_part_name(ficr_info->part); + + if (!part_name) { + LOG_ERROR("Unknown device with part number 0x%08" PRIx32, + nrf54_info->ficr_info.part); + return ERROR_FAIL; + } + + nrf54_info->part_name = part_name; + bank->size = ficr_info->rram_size * 1024; + + nrf54_info->probed = true; + + // RRAM has no alignment constraints, see section 4.2.6.2 in the datasheet. + bank->write_start_alignment = 0; + bank->write_end_alignment = 0; + + return ERROR_OK; +} + +static int nrf54_auto_probe(struct flash_bank *bank) +{ + struct nrf54_info *info = bank->driver_priv; + + if (!info->probed) + return nrf54_probe(bank); + + return ERROR_OK; +} + +static int nrf54_wait_until_ready(struct target *target, uint32_t timeout) +{ + const int64_t start_time = timeval_ms(); + + while (true) { + uint32_t ready; + int retval = target_read_u32(target, + NRF54_RRAMC_BASE_ADDR + NRF54_RRAMC_REG_READY, &ready); + + if (retval != ERROR_OK) + return retval; + + /* + * Send a 'keep-alive' signal before checking if the target is ready. + * The target is usually ready immediately, so without this 'keep-alive' + * it would process consecutive write calls without any keep-alive and + * eventually trigger a timeout. + */ + keep_alive(); + + if (ready) + break; + + if ((timeval_ms() - start_time) > timeout) { + LOG_ERROR("Timed out waiting for RRAM"); + return ERROR_FAIL; + } + } + + return ERROR_OK; +} + +static int nrf54_write(struct flash_bank *bank, const uint8_t *buffer, + uint32_t offset, uint32_t count) +{ + struct target *target = bank->target; + + if (target->state != TARGET_HALTED) { + LOG_ERROR("Target not halted"); + return ERROR_TARGET_NOT_HALTED; + } + + // Write buffer size in 128-bit words, we always use the maximum. + const uint32_t write_buffer_size = 32; + + // Unlock RRAM to enables writes and configure the write buffer size. + int retval = target_write_u32(target, + NRF54_RRAMC_BASE_ADDR + NRF54_RRAMC_REG_CONFIG, + (write_buffer_size << 8) | NRF54_RRAMC_CONFIG_WEN); + + if (retval != ERROR_OK) { + LOG_ERROR("Failed to unlock RRAM"); + return retval; + } + + retval = nrf54_wait_until_ready(target, 1000); + if (retval != ERROR_OK) + goto error; + + // See section (4.2.6.2) in the datasheet about writing to RRAM. + const uint32_t buffer_size = (write_buffer_size == 0) ? + 4 : write_buffer_size * 16; + + while (count > 0) { + const uint32_t write_size = MIN(buffer_size, count); + retval = target_write_buffer(target, bank->base + offset, write_size, + buffer); + + if (retval != ERROR_OK) + goto error; + + retval = nrf54_wait_until_ready(target, 1000); + if (retval != ERROR_OK) + goto error; + + offset += write_size; + buffer += write_size; + count -= write_size; + } + + // Check if there is data in the write-buffer that needs to be committed. + uint32_t buf_status; + retval = target_read_u32(target, NRF54_RRAMC_BASE_ADDR + 0x418, &buf_status); + if (retval != ERROR_OK) { + LOG_ERROR("Failed to read write-buffer status"); + goto error; + } + + if (!buf_status) { + LOG_DEBUG("Commit data from write-buffer to RRAM"); + + retval = target_write_u32(target, NRF54_RRAMC_BASE_ADDR + 0x8, 0x1); + if (retval != ERROR_OK) { + LOG_ERROR("Failed to start write-buffer commit task"); + goto error; + } + + retval = nrf54_wait_until_ready(target, 1000); + if (retval != ERROR_OK) { + LOG_ERROR("Timed out during write-buffer commit task"); + goto error; + } + } + + int retval_lock; +error: + // Lock RRAMC and disable writes. + retval_lock = target_write_u32(target, + NRF54_RRAMC_BASE_ADDR + NRF54_RRAMC_REG_CONFIG, 0x0); + + if (retval != ERROR_OK) + return retval; + + if (retval_lock != ERROR_OK) { + LOG_ERROR("Failed to lock RRAMC"); + return retval_lock; + } + + return ERROR_OK; +} + +static int nrf54_erase(struct flash_bank *bank, unsigned int first, + unsigned int last) +{ + return ERROR_FLASH_OPER_UNSUPPORTED; +} + +FLASH_BANK_COMMAND_HANDLER(nrf54_flash_bank_command) +{ + if (bank->base != NRF54_RRAM_BASE_ADDR) { + LOG_ERROR("Bank base address must be %" PRIx32, NRF54_RRAM_BASE_ADDR); + return ERROR_FAIL; + } + + struct nrf54_info *nrf54_info = calloc(1, sizeof(struct nrf54_info)); + + if (!nrf54_info) { + LOG_ERROR("Failed to allocate memory"); + return ERROR_FAIL; + } + + nrf54_info->probed = false; + bank->driver_priv = nrf54_info; + + return ERROR_OK; +} + +static const struct command_registration nrf54_command_handlers[] = { + { + .name = "nrf54", + .mode = COMMAND_ANY, + .help = "nrf54 flash command group", + .usage = "", + }, + COMMAND_REGISTRATION_DONE +}; + +const struct flash_driver nrf54_flash = { + .name = "nrf54", + .commands = nrf54_command_handlers, + .flash_bank_command = nrf54_flash_bank_command, + .info = nrf54_info, + .erase = nrf54_erase, + .write = nrf54_write, + .read = default_flash_read, + .probe = nrf54_probe, + .auto_probe = nrf54_auto_probe, + .erase_check = default_flash_blank_check, + .free_driver_priv = default_flash_free_driver_priv, +};