blob: fb877d4a6d57318383f11faa3bf61872bcf61ba9 [file] [log] [blame]
/*
* Copyright (c) 2017-2018 Nordic Semiconductor ASA
* Copyright (c) 2016 Linaro Limited
* Copyright (c) 2016 Intel Corporation
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <errno.h>
#include <kernel.h>
#include <device.h>
#include <init.h>
#include <soc.h>
#include <drivers/flash.h>
#include <string.h>
#include <nrfx_nvmc.h>
#if DT_NODE_HAS_STATUS(DT_INST(0, nordic_nrf51_flash_controller), okay)
#define DT_DRV_COMPAT nordic_nrf51_flash_controller
#elif DT_NODE_HAS_STATUS(DT_INST(0, nordic_nrf52_flash_controller), okay)
#define DT_DRV_COMPAT nordic_nrf52_flash_controller
#elif DT_NODE_HAS_STATUS(DT_INST(0, nordic_nrf53_flash_controller), okay)
#define DT_DRV_COMPAT nordic_nrf53_flash_controller
#elif DT_NODE_HAS_STATUS(DT_INST(0, nordic_nrf91_flash_controller), okay)
#define DT_DRV_COMPAT nordic_nrf91_flash_controller
#else
#error No matching compatible for soc_flash_nrf.c
#endif
#define SOC_NV_FLASH_NODE DT_INST(0, soc_nv_flash)
#if defined(CONFIG_SOC_FLASH_NRF_RADIO_SYNC)
#include <sys/__assert.h>
#include <bluetooth/hci.h>
#include "controller/hal/ticker.h"
#include "controller/ticker/ticker.h"
#include "controller/include/ll.h"
#define FLASH_RADIO_ABORT_DELAY_US 1500
#define FLASH_RADIO_WORK_DELAY_US 200
#define FLASH_INTERVAL_ERASE (FLASH_RADIO_ABORT_DELAY_US + \
FLASH_RADIO_WORK_DELAY_US + \
FLASH_SLOT_ERASE)
#define FLASH_SLOT_WRITE (FLASH_INTERVAL_WRITE - \
FLASH_RADIO_ABORT_DELAY_US - \
FLASH_RADIO_WORK_DELAY_US)
#define FLASH_INTERVAL_WRITE 7500
#if defined(CONFIG_SOC_FLASH_NRF_PARTIAL_ERASE)
/* The timeout is multiplied by 1.5 because switching tasks may take
* significant portion of time.
*/
#define FLASH_TIMEOUT_MS ((FLASH_PAGE_ERASE_MAX_TIME_US) * \
(FLASH_PAGE_MAX_CNT) / 1000 * 15 / 10)
#define FLASH_SLOT_ERASE (MAX(CONFIG_SOC_FLASH_NRF_PARTIAL_ERASE_MS * 1000, \
7500))
#else
#define FLASH_TIMEOUT_MS ((FLASH_PAGE_ERASE_MAX_TIME_US) * \
(FLASH_PAGE_MAX_CNT) / 1000)
#define FLASH_SLOT_ERASE FLASH_PAGE_ERASE_MAX_TIME_US
#endif /* CONFIG_SOC_FLASH_NRF_PARTIAL_ERASE */
#endif /* CONFIG_SOC_FLASH_NRF_RADIO_SYNC */
#define FLASH_OP_DONE (0) /* 0 for compliance with the driver API. */
#define FLASH_OP_ONGOING (-1)
struct flash_context {
u32_t data_addr; /* Address of data to write. */
u32_t flash_addr; /* Address of flash to write or erase. */
u32_t len; /* Size off data to write or erase [B]. */
#if defined(CONFIG_SOC_FLASH_NRF_RADIO_SYNC)
u8_t enable_time_limit; /* execution limited to timeslot. */
u32_t interval; /* timeslot interval. */
u32_t slot; /* timeslot length. */
#endif /* CONFIG_SOC_FLASH_NRF_RADIO_SYNC */
#if defined(CONFIG_SOC_FLASH_NRF_PARTIAL_ERASE)
u32_t flash_addr_next;
#endif /* CONFIG_SOC_FLASH_NRF_PARTIAL_ERASE */
}; /*< Context type for f. @ref write_op @ref erase_op */
#if defined(CONFIG_SOC_FLASH_NRF_RADIO_SYNC)
typedef int (*flash_op_handler_t) (void *context);
struct flash_op_desc {
flash_op_handler_t handler;
struct flash_context *context; /* [in,out] */
int result;
};
/* semaphore for synchronization of flash operations */
static struct k_sem sem_sync;
static int write_op(void *context); /* instance of flash_op_handler_t */
static int write_in_timeslice(off_t addr, const void *data, size_t len);
static int erase_op(void *context); /* instance of flash_op_handler_t */
static int erase_in_timeslice(u32_t addr, u32_t size);
#endif /* CONFIG_SOC_FLASH_NRF_RADIO_SYNC */
#if defined(CONFIG_MULTITHREADING)
/* semaphore for locking flash resources (tickers) */
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
static int write(off_t addr, const void *data, size_t len);
static int erase(u32_t addr, u32_t size);
static inline bool is_aligned_32(u32_t data)
{
return (data & 0x3) ? false : true;
}
static inline bool is_regular_addr_valid(off_t addr, size_t len)
{
size_t flash_size = nrfx_nvmc_flash_size_get();
if (addr >= flash_size ||
addr < 0 ||
len > flash_size ||
(addr) + len > flash_size) {
return false;
}
return true;
}
static inline bool is_uicr_addr_valid(off_t addr, size_t len)
{
#ifdef CONFIG_SOC_FLASH_NRF_UICR
if (addr >= (off_t)NRF_UICR + sizeof(*NRF_UICR) ||
addr < (off_t)NRF_UICR ||
len > sizeof(*NRF_UICR) ||
addr + len > (off_t)NRF_UICR + sizeof(*NRF_UICR)) {
return false;
}
return true;
#else
return false;
#endif /* CONFIG_SOC_FLASH_NRF_UICR */
}
static void nvmc_wait_ready(void)
{
while (!nrfx_nvmc_write_done_check()) {
}
}
static int flash_nrf_read(struct device *dev, off_t addr,
void *data, size_t len)
{
if (is_regular_addr_valid(addr, len)) {
addr += DT_REG_ADDR(SOC_NV_FLASH_NODE);
} else if (!is_uicr_addr_valid(addr, len)) {
return -EINVAL;
}
if (!len) {
return 0;
}
memcpy(data, (void *)addr, len);
return 0;
}
static int flash_nrf_write(struct device *dev, off_t addr,
const void *data, size_t len)
{
int ret;
if (is_regular_addr_valid(addr, len)) {
addr += DT_REG_ADDR(SOC_NV_FLASH_NODE);
} else if (!is_uicr_addr_valid(addr, len)) {
return -EINVAL;
}
#if !IS_ENABLED(CONFIG_SOC_FLASH_NRF_EMULATE_ONE_BYTE_WRITE_ACCESS)
if (!is_aligned_32(addr) || (len % sizeof(u32_t))) {
return -EINVAL;
}
#endif
if (!len) {
return 0;
}
SYNC_LOCK();
#if defined(CONFIG_SOC_FLASH_NRF_RADIO_SYNC)
if (ticker_is_initialized(0)) {
ret = write_in_timeslice(addr, data, len);
} else
#endif /* CONFIG_SOC_FLASH_NRF_RADIO_SYNC */
{
ret = write(addr, data, len);
}
SYNC_UNLOCK();
return ret;
}
static int flash_nrf_erase(struct device *dev, off_t addr, size_t size)
{
u32_t pg_size = nrfx_nvmc_flash_page_size_get();
u32_t n_pages = size / pg_size;
int ret;
if (is_regular_addr_valid(addr, size)) {
/* Erase can only be done per page */
if (((addr % pg_size) != 0) || ((size % pg_size) != 0)) {
return -EINVAL;
}
if (!n_pages) {
return 0;
}
addr += DT_REG_ADDR(SOC_NV_FLASH_NODE);
#ifdef CONFIG_SOC_FLASH_NRF_UICR
} else if (addr != (off_t)NRF_UICR || size != sizeof(*NRF_UICR)) {
return -EINVAL;
}
#else
} else {
return -EINVAL;
}
#endif /* CONFIG_SOC_FLASH_NRF_UICR */
SYNC_LOCK();
#if defined(CONFIG_SOC_FLASH_NRF_RADIO_SYNC)
if (ticker_is_initialized(0)) {
ret = erase_in_timeslice(addr, size);
} else
#endif /* CONFIG_SOC_FLASH_NRF_RADIO_SYNC */
{
ret = erase(addr, size);
}
SYNC_UNLOCK();
return ret;
}
static int flash_nrf_write_protection(struct device *dev, bool enable)
{
return 0;
}
#if defined(CONFIG_FLASH_PAGE_LAYOUT)
static struct flash_pages_layout dev_layout;
static void flash_nrf_pages_layout(struct device *dev,
const struct flash_pages_layout **layout,
size_t *layout_size)
{
*layout = &dev_layout;
*layout_size = 1;
}
#endif /* CONFIG_FLASH_PAGE_LAYOUT */
static const struct flash_driver_api flash_nrf_api = {
.read = flash_nrf_read,
.write = flash_nrf_write,
.erase = flash_nrf_erase,
.write_protection = flash_nrf_write_protection,
#if defined(CONFIG_FLASH_PAGE_LAYOUT)
.page_layout = flash_nrf_pages_layout,
#endif
#if IS_ENABLED(CONFIG_SOC_FLASH_NRF_EMULATE_ONE_BYTE_WRITE_ACCESS)
.write_block_size = 1,
#else
.write_block_size = 4,
#endif
};
static int nrf_flash_init(struct device *dev)
{
SYNC_INIT();
#if defined(CONFIG_SOC_FLASH_NRF_RADIO_SYNC)
k_sem_init(&sem_sync, 0, 1);
#endif /* CONFIG_SOC_FLASH_NRF_RADIO_SYNC */
#if defined(CONFIG_FLASH_PAGE_LAYOUT)
dev_layout.pages_count = nrfx_nvmc_flash_page_count_get();
dev_layout.pages_size = nrfx_nvmc_flash_page_size_get();
#endif
return 0;
}
DEVICE_AND_API_INIT(nrf_flash, DT_INST_LABEL(0), nrf_flash_init,
NULL, NULL, POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEVICE,
&flash_nrf_api);
#if defined(CONFIG_SOC_FLASH_NRF_RADIO_SYNC)
static inline int _ticker_stop(u8_t inst_idx, u8_t u_id, u8_t tic_id)
{
int ret = ticker_stop(inst_idx, u_id, tic_id, NULL, NULL);
if (ret != TICKER_STATUS_SUCCESS &&
ret != TICKER_STATUS_BUSY) {
__ASSERT(0, "Failed to stop ticker.\n");
}
return ret;
}
static void time_slot_callback_work(u32_t ticks_at_expire, u32_t remainder,
u16_t lazy, void *context)
{
struct flash_op_desc *op_desc;
u8_t instance_index;
u8_t ticker_id;
__ASSERT(ll_radio_state_is_idle(),
"Radio is on during flash operation.\n");
op_desc = context;
if (op_desc->handler(op_desc->context) == FLASH_OP_DONE) {
ll_timeslice_ticker_id_get(&instance_index, &ticker_id);
/* Stop the time slot ticker */
_ticker_stop(instance_index, 0, ticker_id);
((struct flash_op_desc *)context)->result = 0;
/* notify thread that data is available */
k_sem_give(&sem_sync);
}
}
static void time_slot_delay(u32_t ticks_at_expire, u32_t ticks_delay,
ticker_timeout_func callback, void *context)
{
u8_t instance_index;
u8_t ticker_id;
int err;
ll_timeslice_ticker_id_get(&instance_index, &ticker_id);
/* start a secondary one-shot ticker after ticks_delay,
* this will let any radio role to gracefully abort and release the
* Radio h/w.
*/
err = ticker_start(instance_index, /* Radio instance ticker */
0, /* user_id */
0, /* ticker_id */
ticks_at_expire, /* current tick */
ticks_delay, /* one-shot delayed timeout */
0, /* periodic timeout */
0, /* periodic remainder */
0, /* lazy, voluntary skips */
0,
callback, /* handler for executing radio abort or */
/* flash work */
context, /* the context for the flash operation */
NULL, /* no op callback */
NULL);
if (err != TICKER_STATUS_SUCCESS && err != TICKER_STATUS_BUSY) {
((struct flash_op_desc *)context)->result = -ECANCELED;
/* abort flash timeslots */
_ticker_stop(instance_index, 0, ticker_id);
/* notify thread that data is available */
k_sem_give(&sem_sync);
}
}
static void time_slot_callback_abort(u32_t ticks_at_expire, u32_t remainder,
u16_t lazy, void *context)
{
ll_radio_state_abort();
time_slot_delay(ticks_at_expire,
HAL_TICKER_US_TO_TICKS(FLASH_RADIO_WORK_DELAY_US),
time_slot_callback_work,
context);
}
static void time_slot_callback_prepare(u32_t ticks_at_expire, u32_t remainder,
u16_t lazy, void *context)
{
time_slot_delay(ticks_at_expire,
HAL_TICKER_US_TO_TICKS(FLASH_RADIO_ABORT_DELAY_US),
time_slot_callback_abort,
context);
}
static int work_in_time_slice(struct flash_op_desc *p_flash_op_desc)
{
u8_t instance_index;
u8_t ticker_id;
int result;
u32_t err;
struct flash_context *context = p_flash_op_desc->context;
ll_timeslice_ticker_id_get(&instance_index, &ticker_id);
err = ticker_start(instance_index,
3, /* user id for thread mode */
/* (MAYFLY_CALL_ID_PROGRAM) */
ticker_id, /* flash ticker id */
ticker_ticks_now_get(), /* current tick */
0, /* first int. immediately */
/* period */
HAL_TICKER_US_TO_TICKS(context->interval),
/* period remainder */
HAL_TICKER_REMAINDER(context->interval),
0, /* lazy, voluntary skips */
HAL_TICKER_US_TO_TICKS(context->slot),
time_slot_callback_prepare,
p_flash_op_desc,
NULL, /* no op callback */
NULL);
if (err != TICKER_STATUS_SUCCESS && err != TICKER_STATUS_BUSY) {
result = -ECANCELED;
} else if (k_sem_take(&sem_sync, K_MSEC(FLASH_TIMEOUT_MS)) != 0) {
/* Stop any scheduled jobs */
_ticker_stop(instance_index, 3, ticker_id);
/* wait for operation's complete overrun*/
result = -ETIMEDOUT;
} else {
result = p_flash_op_desc->result;
}
return result;
}
static int erase_in_timeslice(u32_t addr, u32_t size)
{
struct flash_context context = {
.flash_addr = addr,
.len = size,
.enable_time_limit = 1, /* enable time limit */
.interval = FLASH_INTERVAL_ERASE,
.slot = FLASH_SLOT_ERASE,
#if defined(CONFIG_SOC_FLASH_NRF_PARTIAL_ERASE)
.flash_addr_next = addr
#endif
};
struct flash_op_desc flash_op_desc = {
.handler = erase_op,
.context = &context
};
return work_in_time_slice(&flash_op_desc);
}
static int write_in_timeslice(off_t addr, const void *data, size_t len)
{
struct flash_context context = {
.data_addr = (u32_t) data,
.flash_addr = addr,
.len = len,
.enable_time_limit = 1, /* enable time limit */
.interval = FLASH_INTERVAL_WRITE,
.slot = FLASH_SLOT_WRITE
};
struct flash_op_desc flash_op_desc = {
.handler = write_op,
.context = &context
};
return work_in_time_slice(&flash_op_desc);
}
#endif /* CONFIG_SOC_FLASH_NRF_RADIO_SYNC */
static int erase_op(void *context)
{
u32_t pg_size = nrfx_nvmc_flash_page_size_get();
struct flash_context *e_ctx = context;
#if defined(CONFIG_SOC_FLASH_NRF_RADIO_SYNC)
u32_t ticks_begin = 0U;
u32_t ticks_diff;
u32_t i = 0U;
if (e_ctx->enable_time_limit) {
ticks_begin = ticker_ticks_now_get();
}
#endif /* CONFIG_SOC_FLASH_NRF_RADIO_SYNC */
#ifdef CONFIG_SOC_FLASH_NRF_UICR
if (e_ctx->flash_addr == (off_t)NRF_UICR) {
(void)nrfx_nvmc_uicr_erase();
return FLASH_OP_DONE;
}
#endif
do {
#if defined(CONFIG_SOC_FLASH_NRF_PARTIAL_ERASE)
if (e_ctx->flash_addr == e_ctx->flash_addr_next) {
nrfx_nvmc_page_partial_erase_init(e_ctx->flash_addr,
CONFIG_SOC_FLASH_NRF_PARTIAL_ERASE_MS);
e_ctx->flash_addr_next += pg_size;
}
if (nrfx_nvmc_page_partial_erase_continue()) {
e_ctx->len -= pg_size;
e_ctx->flash_addr += pg_size;
}
#else
(void)nrfx_nvmc_page_erase(e_ctx->flash_addr);
e_ctx->len -= pg_size;
e_ctx->flash_addr += pg_size;
#endif /* CONFIG_SOC_FLASH_NRF_PARTIAL_ERASE */
#if defined(CONFIG_SOC_FLASH_NRF_RADIO_SYNC)
i++;
if (e_ctx->enable_time_limit) {
ticks_diff =
ticker_ticks_diff_get(ticker_ticks_now_get(),
ticks_begin);
if (ticks_diff + ticks_diff/i >
HAL_TICKER_US_TO_TICKS(e_ctx->slot)) {
break;
}
}
#endif /* CONFIG_SOC_FLASH_NRF_RADIO_SYNC */
} while (e_ctx->len > 0);
return (e_ctx->len > 0) ? FLASH_OP_ONGOING : FLASH_OP_DONE;
}
static void shift_write_context(u32_t shift, struct flash_context *w_ctx)
{
w_ctx->flash_addr += shift;
w_ctx->data_addr += shift;
w_ctx->len -= shift;
}
static int write_op(void *context)
{
struct flash_context *w_ctx = context;
#if defined(CONFIG_SOC_FLASH_NRF_RADIO_SYNC)
u32_t ticks_begin = 0U;
u32_t ticks_diff;
u32_t i = 1U;
if (w_ctx->enable_time_limit) {
ticks_begin = ticker_ticks_now_get();
}
#endif /* CONFIG_SOC_FLASH_NRF_RADIO_SYNC */
#if IS_ENABLED(CONFIG_SOC_FLASH_NRF_EMULATE_ONE_BYTE_WRITE_ACCESS)
/* If not aligned, write unaligned beginning */
if (!is_aligned_32(w_ctx->flash_addr)) {
u32_t count = sizeof(u32_t) - (w_ctx->flash_addr & 0x3);
if (count > w_ctx->len) {
count = w_ctx->len;
}
nrfx_nvmc_bytes_write(w_ctx->flash_addr,
(const void *)w_ctx->data_addr,
count);
shift_write_context(count, w_ctx);
#if defined(CONFIG_SOC_FLASH_NRF_RADIO_SYNC)
if (w_ctx->enable_time_limit) {
ticks_diff =
ticker_ticks_diff_get(ticker_ticks_now_get(),
ticks_begin);
if (ticks_diff * 2U >
HAL_TICKER_US_TO_TICKS(w_ctx->slot)) {
nvmc_wait_ready();
return FLASH_OP_ONGOING;
}
}
#endif /* CONFIG_SOC_FLASH_NRF_RADIO_SYNC */
}
#endif /* CONFIG_SOC_FLASH_NRF_EMULATE_ONE_BYTE_WRITE_ACCESS */
/* Write all the 4-byte aligned data */
while (w_ctx->len >= sizeof(u32_t)) {
nrfx_nvmc_word_write(w_ctx->flash_addr,
UNALIGNED_GET((u32_t *)w_ctx->data_addr));
shift_write_context(sizeof(u32_t), w_ctx);
#if defined(CONFIG_SOC_FLASH_NRF_RADIO_SYNC)
i++;
if (w_ctx->enable_time_limit) {
ticks_diff =
ticker_ticks_diff_get(ticker_ticks_now_get(),
ticks_begin);
if (ticks_diff + ticks_diff/i >
HAL_TICKER_US_TO_TICKS(w_ctx->slot)) {
nvmc_wait_ready();
return FLASH_OP_ONGOING;
}
}
#endif /* CONFIG_SOC_FLASH_NRF_RADIO_SYNC */
}
#if IS_ENABLED(CONFIG_SOC_FLASH_NRF_EMULATE_ONE_BYTE_WRITE_ACCESS)
/* Write remaining unaligned data */
if (w_ctx->len) {
nrfx_nvmc_bytes_write(w_ctx->flash_addr,
(const void *)w_ctx->data_addr,
w_ctx->len);
shift_write_context(w_ctx->len, w_ctx);
}
#endif /* CONFIG_SOC_FLASH_NRF_EMULATE_ONE_BYTE_WRITE_ACCESS */
nvmc_wait_ready();
return FLASH_OP_DONE;
}
static int erase(u32_t addr, u32_t size)
{
struct flash_context context = {
.flash_addr = addr,
.len = size,
#if defined(CONFIG_SOC_FLASH_NRF_RADIO_SYNC)
.enable_time_limit = 0, /* disable time limit */
#endif /* CONFIG_SOC_FLASH_NRF_RADIO_SYNC */
#if defined(CONFIG_SOC_FLASH_NRF_PARTIAL_ERASE)
.flash_addr_next = addr
#endif
};
return erase_op(&context);
}
static int write(off_t addr, const void *data, size_t len)
{
struct flash_context context = {
.data_addr = (u32_t) data,
.flash_addr = addr,
.len = len,
#if defined(CONFIG_SOC_FLASH_NRF_RADIO_SYNC)
.enable_time_limit = 0 /* disable time limit */
#endif /* CONFIG_SOC_FLASH_NRF_RADIO_SYNC */
};
return write_op(&context);
}