370 lines
9.0 KiB
C
370 lines
9.0 KiB
C
/*
|
|
* Copyright (c) 2024 Nordic Semiconductor ASA
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#define DT_DRV_COMPAT nordic_rram_controller
|
|
|
|
#include <string.h>
|
|
#include <zephyr/kernel.h>
|
|
#include <zephyr/device.h>
|
|
#include <zephyr/drivers/flash.h>
|
|
#include <zephyr/logging/log.h>
|
|
#include <zephyr/irq.h>
|
|
#include <zephyr/sys/barrier.h>
|
|
#include <hal/nrf_rramc.h>
|
|
|
|
#include <zephyr/../../drivers/flash/soc_flash_nrf.h>
|
|
|
|
/* Note that it is supported to compile this driver for both secure
|
|
* and non-secure images, but non-secure images cannot call
|
|
* nrf_rramc_config_set because NRF_RRAMC_NS does not exist.
|
|
*
|
|
* Instead, when TF-M boots, it will configure RRAMC with this static
|
|
* configuration:
|
|
*
|
|
* nrf_rramc_config_t config = {
|
|
* .mode_write = true,
|
|
* .write_buff_size = WRITE_BUFFER_SIZE
|
|
* };
|
|
*
|
|
* nrf_rramc_ready_next_timeout_t params = {
|
|
* .value = CONFIG_NRF_RRAM_READYNEXT_TIMEOUT_VALUE,
|
|
* .enable = true,
|
|
* };
|
|
*
|
|
* For more details see NCSDK-26982.
|
|
*/
|
|
|
|
LOG_MODULE_REGISTER(flash_nrf_rram, CONFIG_FLASH_LOG_LEVEL);
|
|
|
|
#define RRAM DT_INST(0, soc_nv_flash)
|
|
|
|
#if defined(CONFIG_SOC_SERIES_BSIM_NRFXX)
|
|
#define RRAM_START NRF_RRAM_BASE_ADDR
|
|
#else
|
|
#define RRAM_START DT_REG_ADDR(RRAM)
|
|
#endif
|
|
#define RRAM_SIZE DT_REG_SIZE(RRAM)
|
|
|
|
#define PAGE_SIZE DT_PROP(RRAM, erase_block_size)
|
|
#define PAGE_COUNT ((RRAM_SIZE) / (PAGE_SIZE))
|
|
|
|
#define WRITE_BLOCK_SIZE_FROM_DT DT_PROP(RRAM, write_block_size)
|
|
#define ERASE_VALUE 0xFF
|
|
|
|
#ifdef CONFIG_MULTITHREADING
|
|
static struct k_sem sem_lock;
|
|
#define SYNC_INIT() k_sem_init(&sem_lock, 1, 1)
|
|
#define SYNC_LOCK() k_sem_take(&sem_lock, K_FOREVER)
|
|
#define SYNC_UNLOCK() k_sem_give(&sem_lock)
|
|
#else
|
|
#define SYNC_INIT()
|
|
#define SYNC_LOCK()
|
|
#define SYNC_UNLOCK()
|
|
#endif /* CONFIG_MULTITHREADING */
|
|
|
|
#if CONFIG_NRF_RRAM_WRITE_BUFFER_SIZE > 0
|
|
#define WRITE_BUFFER_ENABLE 1
|
|
#define WRITE_BUFFER_SIZE CONFIG_NRF_RRAM_WRITE_BUFFER_SIZE
|
|
#define WRITE_LINE_SIZE 16 /* In bytes, one line is 128 bits. */
|
|
#define WRITE_BUFFER_MAX_SIZE (WRITE_BUFFER_SIZE * WRITE_LINE_SIZE)
|
|
BUILD_ASSERT((PAGE_SIZE % (WRITE_LINE_SIZE) == 0), "erase-block-size must be a multiple of 16");
|
|
BUILD_ASSERT((WRITE_BLOCK_SIZE_FROM_DT % (WRITE_LINE_SIZE) == 0),
|
|
"if NRF_RRAM_WRITE_BUFFER_SIZE > 0, then write-block-size must be a multiple of 16");
|
|
#else
|
|
#define WRITE_BUFFER_ENABLE 0
|
|
#define WRITE_BUFFER_SIZE 0
|
|
#define WRITE_LINE_SIZE WRITE_BLOCK_SIZE_FROM_DT
|
|
#define WRITE_BUFFER_MAX_SIZE 16 /* In bytes, one line is 128 bits. */
|
|
BUILD_ASSERT((PAGE_SIZE % (WRITE_LINE_SIZE) == 0),
|
|
"erase-block-size must be a multiple of write-block-size");
|
|
#endif
|
|
|
|
#ifndef CONFIG_SOC_FLASH_NRF_RADIO_SYNC_NONE
|
|
|
|
#if (WRITE_BUFFER_SIZE < 2)
|
|
#define FLASH_SLOT_WRITE 500
|
|
#elif (WRITE_BUFFER_SIZE < 4)
|
|
#define FLASH_SLOT_WRITE 1000
|
|
#elif (WRITE_BUFFER_SIZE < 9)
|
|
#define FLASH_SLOT_WRITE 2000
|
|
#elif (WRITE_BUFFER_SIZE < 17)
|
|
#define FLASH_SLOT_WRITE 4000
|
|
#else
|
|
#define FLASH_SLOT_WRITE 8000 /* longest write takes 7107 us */
|
|
#endif
|
|
|
|
static int write_op(void *context); /* instance of flash_op_handler_t */
|
|
static int write_synchronously(off_t addr, const void *data, size_t len);
|
|
|
|
#endif /* !CONFIG_SOC_FLASH_NRF_RADIO_SYNC_NONE */
|
|
|
|
static inline bool is_within_bounds(off_t addr, size_t len, off_t boundary_start,
|
|
size_t boundary_size)
|
|
{
|
|
return (addr >= boundary_start && (addr < (boundary_start + boundary_size)) &&
|
|
(len <= (boundary_start + boundary_size - addr)));
|
|
}
|
|
|
|
#if WRITE_BUFFER_ENABLE
|
|
static void commit_changes(off_t addr, size_t len)
|
|
{
|
|
#if !defined(CONFIG_TRUSTED_EXECUTION_NONSECURE)
|
|
if (nrf_rramc_empty_buffer_check(NRF_RRAMC)) {
|
|
/* The internal write-buffer has been committed to RRAM and is now empty. */
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
if ((len % (WRITE_BUFFER_MAX_SIZE)) == 0) {
|
|
/* Our last operation was buffer size-aligned, so we're done. */
|
|
return;
|
|
}
|
|
|
|
#if !defined(CONFIG_TRUSTED_EXECUTION_NONSECURE)
|
|
ARG_UNUSED(addr);
|
|
|
|
nrf_rramc_task_trigger(NRF_RRAMC, NRF_RRAMC_TASK_COMMIT_WRITEBUF);
|
|
#else
|
|
/*
|
|
* When the commit task is unavailable we need to get creative to
|
|
* ensure this is committed.
|
|
*
|
|
* According to the PS the buffer is committed when "There is a
|
|
* read operation from a 128-bit word line in the buffer that has
|
|
* already been written to".
|
|
*
|
|
* So we read the last byte that has been written to trigger this
|
|
* commit.
|
|
*
|
|
* If this approach proves to be problematic, e.g. for writes to
|
|
* write-only memory, then one would have to rely on
|
|
* READYNEXTTIMEOUT to eventually commit the write.
|
|
*/
|
|
volatile uint8_t dummy_read = *(volatile uint8_t *)(addr + len - 1);
|
|
ARG_UNUSED(dummy_read);
|
|
#endif
|
|
|
|
barrier_dmem_fence_full();
|
|
}
|
|
#endif
|
|
|
|
static void rram_write(off_t addr, const void *data, size_t len)
|
|
{
|
|
#if !defined(CONFIG_TRUSTED_EXECUTION_NONSECURE)
|
|
nrf_rramc_config_t config = {.mode_write = true, .write_buff_size = WRITE_BUFFER_SIZE};
|
|
|
|
nrf_rramc_config_set(NRF_RRAMC, &config);
|
|
#endif
|
|
|
|
if (data) {
|
|
memcpy((void *)addr, data, len);
|
|
} else {
|
|
memset((void *)addr, ERASE_VALUE, len);
|
|
}
|
|
|
|
barrier_dmem_fence_full(); /* Barrier following our last write. */
|
|
|
|
#if WRITE_BUFFER_ENABLE
|
|
commit_changes(addr, len);
|
|
#endif
|
|
|
|
#if !defined(CONFIG_TRUSTED_EXECUTION_NONSECURE)
|
|
config.mode_write = false;
|
|
nrf_rramc_config_set(NRF_RRAMC, &config);
|
|
#endif
|
|
}
|
|
|
|
#ifndef CONFIG_SOC_FLASH_NRF_RADIO_SYNC_NONE
|
|
static void shift_write_context(uint32_t shift, struct flash_context *w_ctx)
|
|
{
|
|
w_ctx->flash_addr += shift;
|
|
|
|
/* NULL data_addr => erase emulation request*/
|
|
if (w_ctx->data_addr) {
|
|
w_ctx->data_addr += shift;
|
|
}
|
|
|
|
w_ctx->len -= shift;
|
|
}
|
|
|
|
static int write_op(void *context)
|
|
{
|
|
struct flash_context *w_ctx = context;
|
|
size_t len;
|
|
|
|
uint32_t i = 0U;
|
|
|
|
if (w_ctx->enable_time_limit) {
|
|
nrf_flash_sync_get_timestamp_begin();
|
|
}
|
|
|
|
while (w_ctx->len > 0) {
|
|
len = (WRITE_BUFFER_MAX_SIZE < w_ctx->len) ? WRITE_BUFFER_MAX_SIZE : w_ctx->len;
|
|
|
|
rram_write(w_ctx->flash_addr, (const void *)w_ctx->data_addr, len);
|
|
|
|
shift_write_context(len, w_ctx);
|
|
|
|
if (w_ctx->len > 0) {
|
|
i++;
|
|
|
|
if (w_ctx->enable_time_limit) {
|
|
if (nrf_flash_sync_check_time_limit(i)) {
|
|
return FLASH_OP_ONGOING;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return FLASH_OP_DONE;
|
|
}
|
|
|
|
static int write_synchronously(off_t addr, const void *data, size_t len)
|
|
{
|
|
struct flash_context context = {
|
|
.data_addr = (uint32_t)data,
|
|
.flash_addr = addr,
|
|
.len = len,
|
|
.enable_time_limit = 1 /* enable time limit */
|
|
};
|
|
|
|
struct flash_op_desc flash_op_desc = {.handler = write_op, .context = &context};
|
|
|
|
nrf_flash_sync_set_context(FLASH_SLOT_WRITE);
|
|
return nrf_flash_sync_exe(&flash_op_desc);
|
|
}
|
|
|
|
#endif /* !CONFIG_SOC_FLASH_NRF_RADIO_SYNC_NONE */
|
|
|
|
static int nrf_write(off_t addr, const void *data, size_t len)
|
|
{
|
|
int ret = 0;
|
|
|
|
if (!is_within_bounds(addr, len, 0, RRAM_SIZE)) {
|
|
return -EINVAL;
|
|
}
|
|
addr += RRAM_START;
|
|
|
|
if (!len) {
|
|
return 0;
|
|
}
|
|
|
|
LOG_DBG("Write: %p:%zu", (void *)addr, len);
|
|
|
|
SYNC_LOCK();
|
|
|
|
#ifndef CONFIG_SOC_FLASH_NRF_RADIO_SYNC_NONE
|
|
if (nrf_flash_sync_is_required()) {
|
|
ret = write_synchronously(addr, data, len);
|
|
} else
|
|
#endif /* !CONFIG_SOC_FLASH_NRF_RADIO_SYNC_NONE */
|
|
{
|
|
rram_write(addr, data, len);
|
|
}
|
|
|
|
SYNC_UNLOCK();
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int nrf_rram_read(const struct device *dev, off_t addr, void *data, size_t len)
|
|
{
|
|
ARG_UNUSED(dev);
|
|
|
|
if (!is_within_bounds(addr, len, 0, RRAM_SIZE)) {
|
|
return -EINVAL;
|
|
}
|
|
addr += RRAM_START;
|
|
|
|
memcpy(data, (void *)addr, len);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int nrf_rram_write(const struct device *dev, off_t addr, const void *data, size_t len)
|
|
{
|
|
ARG_UNUSED(dev);
|
|
|
|
if (data == NULL) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
return nrf_write(addr, data, len);
|
|
}
|
|
|
|
static int nrf_rram_erase(const struct device *dev, off_t addr, size_t len)
|
|
{
|
|
ARG_UNUSED(dev);
|
|
|
|
return nrf_write(addr, NULL, len);
|
|
}
|
|
|
|
static const struct flash_parameters *nrf_rram_get_parameters(const struct device *dev)
|
|
{
|
|
ARG_UNUSED(dev);
|
|
|
|
static const struct flash_parameters parameters = {
|
|
.write_block_size = WRITE_LINE_SIZE,
|
|
.erase_value = ERASE_VALUE,
|
|
.caps = {
|
|
.no_explicit_erase = true,
|
|
},
|
|
};
|
|
|
|
return ¶meters;
|
|
}
|
|
|
|
#if defined(CONFIG_FLASH_PAGE_LAYOUT)
|
|
static void nrf_rram_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_rram_api = {
|
|
.read = nrf_rram_read,
|
|
.write = nrf_rram_write,
|
|
.erase = nrf_rram_erase,
|
|
.get_parameters = nrf_rram_get_parameters,
|
|
#if defined(CONFIG_FLASH_PAGE_LAYOUT)
|
|
.page_layout = nrf_rram_page_layout,
|
|
#endif
|
|
};
|
|
|
|
static int nrf_rram_init(const struct device *dev)
|
|
{
|
|
ARG_UNUSED(dev);
|
|
|
|
SYNC_INIT();
|
|
|
|
#ifndef CONFIG_SOC_FLASH_NRF_RADIO_SYNC_NONE
|
|
nrf_flash_sync_init();
|
|
#endif /* !CONFIG_SOC_FLASH_NRF_RADIO_SYNC_NONE */
|
|
|
|
#if !defined(CONFIG_TRUSTED_EXECUTION_NONSECURE) && CONFIG_NRF_RRAM_READYNEXT_TIMEOUT_VALUE > 0
|
|
nrf_rramc_ready_next_timeout_t params = {
|
|
.value = CONFIG_NRF_RRAM_READYNEXT_TIMEOUT_VALUE,
|
|
.enable = true,
|
|
};
|
|
|
|
nrf_rramc_ready_next_timeout_set(NRF_RRAMC, ¶ms);
|
|
#endif
|
|
|
|
return 0;
|
|
}
|
|
|
|
DEVICE_DT_INST_DEFINE(0, nrf_rram_init, NULL, NULL, NULL, POST_KERNEL, CONFIG_FLASH_INIT_PRIORITY,
|
|
&nrf_rram_api);
|