Skip to content

Commit

Permalink
drivers: flash: Add flash driver for MRAM
Browse files Browse the repository at this point in the history
Basic driver utilizing the flash API for NVM operations on the nRF54H20.

Signed-off-by: Grzegorz Swiderski <grzegorz.swiderski@nordicsemi.no>
  • Loading branch information
57300 committed Mar 5, 2024
1 parent 0e2a25b commit 0797e3c
Show file tree
Hide file tree
Showing 4 changed files with 229 additions and 0 deletions.
1 change: 1 addition & 0 deletions drivers/flash/CMakeLists.txt
Expand Up @@ -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)
2 changes: 2 additions & 0 deletions drivers/flash/Kconfig
Expand Up @@ -163,4 +163,6 @@ source "drivers/flash/Kconfig.ambiq"

source "drivers/flash/Kconfig.nrf_rram"

source "drivers/flash/Kconfig.nrf_mram"

endif # FLASH
40 changes: 40 additions & 0 deletions 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
186 changes: 186 additions & 0 deletions drivers/flash/soc_flash_nrf_mram.c
@@ -0,0 +1,186 @@
/*
* Copyright (c) 2024 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/

#include <string.h>

#include <zephyr/drivers/flash.h>
#include <zephyr/logging/log.h>
#include <zephyr/sys/barrier.h>

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 &parameters;
}

#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);

0 comments on commit 0797e3c

Please sign in to comment.