From 0797e3c42e8d14e24f4cf8b1e183fab012d74542 Mon Sep 17 00:00:00 2001 From: Grzegorz Swiderski Date: Tue, 5 Mar 2024 14:18:50 +0100 Subject: [PATCH] drivers: flash: Add flash driver for MRAM Basic driver utilizing the flash API for NVM operations on the nRF54H20. Signed-off-by: Grzegorz Swiderski --- drivers/flash/CMakeLists.txt | 1 + drivers/flash/Kconfig | 2 + drivers/flash/Kconfig.nrf_mram | 40 +++++++ drivers/flash/soc_flash_nrf_mram.c | 186 +++++++++++++++++++++++++++++ 4 files changed, 229 insertions(+) create mode 100644 drivers/flash/Kconfig.nrf_mram create mode 100644 drivers/flash/soc_flash_nrf_mram.c diff --git a/drivers/flash/CMakeLists.txt b/drivers/flash/CMakeLists.txt index 4f21b2f65ed..98f24ff123c 100644 --- a/drivers/flash/CMakeLists.txt +++ b/drivers/flash/CMakeLists.txt @@ -129,3 +129,4 @@ zephyr_library_sources_ifdef(CONFIG_FLASH_JESD216 jesd216.c) zephyr_library_sources_ifdef(CONFIG_FLASH_INFINEON_CAT1 flash_ifx_cat1.c) zephyr_library_sources_ifdef(CONFIG_SOC_FLASH_NUMAKER soc_flash_numaker.c) zephyr_library_sources_ifdef(CONFIG_SOC_FLASH_NRF_RRAM soc_flash_nrf_rram.c) +zephyr_library_sources_ifdef(CONFIG_SOC_FLASH_NRF_MRAM soc_flash_nrf_mram.c) diff --git a/drivers/flash/Kconfig b/drivers/flash/Kconfig index 4731a628438..0c24890d44d 100644 --- a/drivers/flash/Kconfig +++ b/drivers/flash/Kconfig @@ -163,4 +163,6 @@ source "drivers/flash/Kconfig.ambiq" source "drivers/flash/Kconfig.nrf_rram" +source "drivers/flash/Kconfig.nrf_mram" + endif # FLASH diff --git a/drivers/flash/Kconfig.nrf_mram b/drivers/flash/Kconfig.nrf_mram new file mode 100644 index 00000000000..79faf2d7dcd --- /dev/null +++ b/drivers/flash/Kconfig.nrf_mram @@ -0,0 +1,40 @@ +# +# Copyright (c) 2024 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: Apache-2.0 +# + +menuconfig SOC_FLASH_NRF_MRAM + bool "Nordic Semiconductor flash driver for MRAM" + default y + depends on DT_HAS_NORDIC_MRAM_ENABLED + select FLASH_HAS_DRIVER_ENABLED + select FLASH_HAS_PAGE_LAYOUT + imply MPU_ALLOW_FLASH_WRITE if ARM_MPU + help + Enables Nordic Semiconductor flash driver for MRAM in direct write mode. + + Note that MRAM words are auto-erased when written to, but writing to a + pre-erased area is faster. Hence, the erase API is not required, but + it can be used to amortize write performance for some use cases. + +if SOC_FLASH_NRF_MRAM + +config SOC_FLASH_NRF_MRAM_ONE_BYTE_WRITE_ACCESS + bool "1-byte write block size" + help + By default, write/erase is done on MRAM word-aligned (16-byte) blocks. + This option enables 1-byte block (unaligned) access for write/erase. + This is supported in hardware, but it requires slightly more code to + perform the operations safely. + +config SOC_FLASH_NRF_MRAM_LAYOUT_PAGE_SIZE + int "Page size to use for FLASH_LAYOUT feature" + default 4096 + help + When CONFIG_FLASH_PAGE_LAYOUT is used this driver will support that API. + The page size must be a multiple of the MRAM word size, unless + CONFIG_SOC_FLASH_NRF_MRAM_ONE_BYTE_WRITE_ACCESS is enabled. + The default value is chosen arbitrarily. + +endif # SOC_FLASH_NRF_MRAM diff --git a/drivers/flash/soc_flash_nrf_mram.c b/drivers/flash/soc_flash_nrf_mram.c new file mode 100644 index 00000000000..3f2a3267383 --- /dev/null +++ b/drivers/flash/soc_flash_nrf_mram.c @@ -0,0 +1,186 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +#include +#include +#include + +LOG_MODULE_REGISTER(flash_nrf_mram, CONFIG_FLASH_LOG_LEVEL); + +#define DT_DRV_COMPAT nordic_mram + +#define MRAM_START DT_INST_REG_ADDR(0) +#define MRAM_SIZE DT_INST_REG_SIZE(0) + +#define MRAM_WORD_SIZE DT_INST_PROP(0, write_block_size) +#define MRAM_WORD_MASK (MRAM_WORD_SIZE - 1) + +#define ERASE_VALUE 0xff + +#if defined(CONFIG_SOC_FLASH_NRF_MRAM_ONE_BYTE_WRITE_ACCESS) +#define WRITE_BLOCK_SIZE 1 +#else +#define WRITE_BLOCK_SIZE MRAM_WORD_SIZE +#endif + +#define PAGE_SIZE CONFIG_SOC_FLASH_NRF_MRAM_LAYOUT_PAGE_SIZE +#define PAGE_COUNT ((MRAM_SIZE) / (PAGE_SIZE)) + +BUILD_ASSERT(MRAM_START > 0, "nordic,mram: start address expected to be non-zero"); +BUILD_ASSERT(IS_POWER_OF_TWO(MRAM_WORD_SIZE), + "nordic,mram: write-block-size expected to be a power of 2"); +BUILD_ASSERT((PAGE_SIZE % WRITE_BLOCK_SIZE) == 0, + "page size expected to be a multiple of write block size"); + +/** + * @param[in,out] offset Relative offset into memory, from the driver API. + * @param[in] len Number of bytes for the intended operation. + * @param[in] must_align Require MRAM word alignment, if applicable. + * + * @return Absolute address in MRAM, or NULL if @p offset or @p len are not + * within bounds or appropriately aligned. + */ +static uintptr_t validate_and_map_addr(off_t offset, size_t len, bool must_align) +{ + if (unlikely(offset < 0 || offset + len > MRAM_SIZE)) { + LOG_ERR("invalid offset: %ld:%zu", offset, len); + return 0; + } + + const uintptr_t addr = MRAM_START + offset; + + if (WRITE_BLOCK_SIZE == MRAM_WORD_SIZE && must_align) { + /* Address and length must both be multiples of MRAM_WORD_SIZE. + * Since it's a power of two, we can check this with a bitmask. + */ + if (unlikely((addr | len) & MRAM_WORD_MASK)) { + LOG_ERR("invalid alignment: %p:%zu", (void *)addr, len); + return 0; + } + } + + return addr; +} + +/** + * @param[in] addr_end Last modified MRAM address (not inclusive). + */ +static void commit_changes(uintptr_t addr_end) +{ + /* Barrier following our last write. */ + barrier_dmem_fence_full(); + + if (WRITE_BLOCK_SIZE == MRAM_WORD_SIZE || (addr_end & MRAM_WORD_MASK) == 0) { + /* Our last operation was MRAM word-aligned, so we're done. */ + return; + } + + /* Get the most significant byte (MSB) of the last MRAM word we were modifying. + * Writing to this byte makes the MRAM controller commit other pending writes to that word. + */ + addr_end |= MRAM_WORD_MASK; + + /* Issue a dummy write, since we didn't have anything to write here. + * Doing this lets us finalize our changes before we exit the driver API. + */ + sys_write8(sys_read8(addr_end), addr_end); +} + +static int nrf_mram_read(const struct device *dev, off_t offset, void *data, size_t len) +{ + ARG_UNUSED(dev); + + const uintptr_t addr = validate_and_map_addr(offset, len, false); + + if (!addr) { + return -EINVAL; + } + + LOG_DBG("read: %p:%zu", (void *)addr, len); + + memcpy(data, (void *)addr, len); + + return 0; +} + +static int nrf_mram_write(const struct device *dev, off_t offset, const void *data, size_t len) +{ + ARG_UNUSED(dev); + + const uintptr_t addr = validate_and_map_addr(offset, len, true); + + if (!addr) { + return -EINVAL; + } + + LOG_DBG("write: %p:%zu", (void *)addr, len); + + memcpy((void *)addr, data, len); + commit_changes(addr + len); + + return 0; +} + +static int nrf_mram_erase(const struct device *dev, off_t offset, size_t size) +{ + ARG_UNUSED(dev); + + const uintptr_t addr = validate_and_map_addr(offset, size, true); + + if (!addr) { + return -EINVAL; + } + + LOG_DBG("erase: %p:%zu", (void *)addr, size); + + memset((void *)addr, ERASE_VALUE, size); + commit_changes(addr + size); + + return 0; +} + +static const struct flash_parameters *nrf_mram_get_parameters(const struct device *dev) +{ + ARG_UNUSED(dev); + + static const struct flash_parameters parameters = { + .write_block_size = WRITE_BLOCK_SIZE, + .erase_value = ERASE_VALUE, + }; + + return ¶meters; +} + +#if defined(CONFIG_FLASH_PAGE_LAYOUT) +static void nrf_mram_page_layout(const struct device *dev, const struct flash_pages_layout **layout, + size_t *layout_size) +{ + ARG_UNUSED(dev); + + static const struct flash_pages_layout pages_layout = { + .pages_count = PAGE_COUNT, + .pages_size = PAGE_SIZE, + }; + + *layout = &pages_layout; + *layout_size = 1; +} +#endif + +static const struct flash_driver_api nrf_mram_api = { + .read = nrf_mram_read, + .write = nrf_mram_write, + .erase = nrf_mram_erase, + .get_parameters = nrf_mram_get_parameters, +#if defined(CONFIG_FLASH_PAGE_LAYOUT) + .page_layout = nrf_mram_page_layout, +#endif +}; + +DEVICE_DT_INST_DEFINE(0, NULL, NULL, NULL, NULL, POST_KERNEL, CONFIG_FLASH_INIT_PRIORITY, + &nrf_mram_api);