forked from nrfconnect/sdk-zephyr
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
drivers: flash: Add flash driver for MRAM
Basic driver utilizing the flash API for NVM operations on the nRF54H20. Signed-off-by: Grzegorz Swiderski <grzegorz.swiderski@nordicsemi.no>
- Loading branch information
Showing
4 changed files
with
229 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 ¶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); |