553 lines
13 KiB
C
553 lines
13 KiB
C
/*
|
|
* Copyright (c) 2017 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 <flash.h>
|
|
#include <string.h>
|
|
|
|
#if defined(CONFIG_SOC_FLASH_NRF5_RADIO_SYNC)
|
|
#include <misc/__assert.h>
|
|
#include <bluetooth/hci.h>
|
|
#include "controller/ticker/ticker.h"
|
|
#include "controller/include/ll.h"
|
|
|
|
#define FLASH_SLOT FLASH_PAGE_ERASE_MAX_TIME_US
|
|
#define FLASH_INTERVAL FLASH_SLOT
|
|
#define FLASH_RADIO_ABORT_DELAY_US 500
|
|
#define FLASH_TIMEOUT_MS ((FLASH_PAGE_ERASE_MAX_TIME_US)\
|
|
* (FLASH_PAGE_MAX_CNT) / 1000)
|
|
#endif /* CONFIG_SOC_FLASH_NRF5_RADIO_SYNC */
|
|
|
|
#define FLASH_OP_DONE (0) /* 0 for compliance with the driver API. */
|
|
#define FLASH_OP_ONGOING (-1)
|
|
|
|
struct erase_context {
|
|
u32_t addr; /* Address off the 1st page to erase */
|
|
u32_t size; /* Size off area to erase [B] */
|
|
#if defined(CONFIG_SOC_FLASH_NRF5_RADIO_SYNC)
|
|
u8_t enable_time_limit; /* execution limited to timeslot */
|
|
#endif /* CONFIG_SOC_FLASH_NRF5_RADIO_SYNC */
|
|
}; /*< Context type for f. @ref erase_op */
|
|
|
|
struct write_context {
|
|
u32_t data_addr;
|
|
u32_t flash_addr; /* Address off the 1st page to erase */
|
|
u32_t len; /* Size off data to write [B] */
|
|
#if defined(CONFIG_SOC_FLASH_NRF5_RADIO_SYNC)
|
|
u8_t enable_time_limit; /* execution limited to timeslot*/
|
|
#endif /* CONFIG_SOC_FLASH_NRF5_RADIO_SYNC */
|
|
}; /*< Context type for f. @ref write_op */
|
|
|
|
#if defined(CONFIG_SOC_FLASH_NRF5_RADIO_SYNC)
|
|
typedef int (*flash_op_handler_t) (void *context);
|
|
|
|
struct flash_op_desc {
|
|
flash_op_handler_t handler;
|
|
void *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_NRF5_RADIO_SYNC */
|
|
|
|
/* semaphore for locking flash resources (tickers) */
|
|
static struct k_sem sem_lock;
|
|
|
|
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_addr_valid(off_t addr, size_t len)
|
|
{
|
|
if (addr + len > NRF_FICR->CODEPAGESIZE * NRF_FICR->CODESIZE ||
|
|
addr < 0) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static void nvmc_wait_ready(void)
|
|
{
|
|
while (NRF_NVMC->READY == NVMC_READY_READY_Busy) {
|
|
;
|
|
}
|
|
}
|
|
|
|
static int flash_nrf5_read(struct device *dev, off_t addr,
|
|
void *data, size_t len)
|
|
{
|
|
if (!is_addr_valid(addr, len)) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (!len) {
|
|
return 0;
|
|
}
|
|
|
|
memcpy(data, (void *)addr, len);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int flash_nrf5_write(struct device *dev, off_t addr,
|
|
const void *data, size_t len)
|
|
{
|
|
int ret;
|
|
|
|
if (!is_addr_valid(addr, len)) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (!len) {
|
|
return 0;
|
|
}
|
|
|
|
k_sem_take(&sem_lock, K_FOREVER);
|
|
|
|
#if defined(CONFIG_SOC_FLASH_NRF5_RADIO_SYNC)
|
|
if (ticker_is_initialized(0)) {
|
|
ret = write_in_timeslice(addr, data, len);
|
|
} else
|
|
#endif /* CONFIG_SOC_FLASH_NRF5_RADIO_SYNC */
|
|
{
|
|
ret = write(addr, data, len);
|
|
}
|
|
|
|
k_sem_give(&sem_lock);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int flash_nrf5_erase(struct device *dev, off_t addr, size_t size)
|
|
{
|
|
u32_t pg_size = NRF_FICR->CODEPAGESIZE;
|
|
u32_t n_pages = size / pg_size;
|
|
int ret;
|
|
|
|
/* Erase can only be done per page */
|
|
if (((addr % pg_size) != 0) || ((size % pg_size) != 0)) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (!is_addr_valid(addr, size)) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (!n_pages) {
|
|
return 0;
|
|
}
|
|
|
|
k_sem_take(&sem_lock, K_FOREVER);
|
|
|
|
#if defined(CONFIG_SOC_FLASH_NRF5_RADIO_SYNC)
|
|
if (ticker_is_initialized(0)) {
|
|
ret = erase_in_timeslice(addr, size);
|
|
} else
|
|
#endif /* CONFIG_SOC_FLASH_NRF5_RADIO_SYNC */
|
|
{
|
|
ret = erase(addr, size);
|
|
}
|
|
|
|
k_sem_give(&sem_lock);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int flash_nrf5_write_protection(struct device *dev, bool enable)
|
|
{
|
|
k_sem_take(&sem_lock, K_FOREVER);
|
|
|
|
if (enable) {
|
|
NRF_NVMC->CONFIG = NVMC_CONFIG_WEN_Ren << NVMC_CONFIG_WEN_Pos;
|
|
} else {
|
|
NRF_NVMC->CONFIG = NVMC_CONFIG_WEN_Wen << NVMC_CONFIG_WEN_Pos;
|
|
}
|
|
nvmc_wait_ready();
|
|
|
|
k_sem_give(&sem_lock);
|
|
|
|
return 0;
|
|
}
|
|
|
|
#if defined(CONFIG_FLASH_PAGE_LAYOUT)
|
|
static struct flash_pages_layout dev_layout;
|
|
|
|
static void flash_nrf5_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_nrf5_api = {
|
|
.read = flash_nrf5_read,
|
|
.write = flash_nrf5_write,
|
|
.erase = flash_nrf5_erase,
|
|
.write_protection = flash_nrf5_write_protection,
|
|
#if defined(CONFIG_FLASH_PAGE_LAYOUT)
|
|
.page_layout = flash_nrf5_pages_layout,
|
|
#endif
|
|
.write_block_size = 1,
|
|
};
|
|
|
|
static int nrf5_flash_init(struct device *dev)
|
|
{
|
|
dev->driver_api = &flash_nrf5_api;
|
|
|
|
k_sem_init(&sem_lock, 1, 1);
|
|
|
|
#if defined(CONFIG_SOC_FLASH_NRF5_RADIO_SYNC)
|
|
k_sem_init(&sem_sync, 0, 1);
|
|
#endif /* CONFIG_SOC_FLASH_NRF5_RADIO_SYNC */
|
|
|
|
#if defined(CONFIG_FLASH_PAGE_LAYOUT)
|
|
dev_layout.pages_count = NRF_FICR->CODESIZE;
|
|
dev_layout.pages_size = NRF_FICR->CODEPAGESIZE;
|
|
#endif
|
|
|
|
return 0;
|
|
}
|
|
|
|
DEVICE_INIT(nrf5_flash, CONFIG_SOC_FLASH_NRF5_DEV_NAME, nrf5_flash_init,
|
|
NULL, NULL, POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEVICE);
|
|
|
|
#if defined(CONFIG_SOC_FLASH_NRF5_RADIO_SYNC)
|
|
|
|
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;
|
|
int result;
|
|
|
|
__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 */
|
|
result = ticker_stop(instance_index,
|
|
0,
|
|
ticker_id,
|
|
NULL,
|
|
NULL);
|
|
|
|
if (result != TICKER_STATUS_SUCCESS &&
|
|
result != TICKER_STATUS_BUSY) {
|
|
__ASSERT(0, "Failed to stop ticker.\n");
|
|
}
|
|
|
|
((struct flash_op_desc *)context)->result = 0;
|
|
|
|
/* notify thread that data is available */
|
|
k_sem_give(&sem_sync);
|
|
}
|
|
}
|
|
|
|
static void time_slot_callback_helper(u32_t ticks_at_expire, u32_t remainder,
|
|
u16_t lazy, void *context)
|
|
{
|
|
u8_t instance_index;
|
|
u8_t ticker_id;
|
|
int err;
|
|
|
|
ll_radio_state_abort();
|
|
|
|
ll_timeslice_ticker_id_get(&instance_index, &ticker_id);
|
|
|
|
/* start a secondary one-shot ticker after ~ 500 us, */
|
|
/* this will let any radio role to gracefully release the Radio h/w */
|
|
|
|
err = ticker_start(instance_index, /* Radio instance ticker */
|
|
0, /* user_id */
|
|
0, /* ticker_id */
|
|
ticks_at_expire, /* current tick */
|
|
TICKER_US_TO_TICKS(FLASH_RADIO_ABORT_DELAY_US),
|
|
0, /* periodic (on-shot) */
|
|
0, /* per. remaind. (on-shot) */
|
|
0, /* lazy, voluntary skips */
|
|
0,
|
|
time_slot_callback_work, /* handler for executing */
|
|
/* the flash operation */
|
|
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 */
|
|
err = ticker_stop(instance_index, 0, ticker_id, NULL, NULL);
|
|
if (err != TICKER_STATUS_SUCCESS &&
|
|
err != TICKER_STATUS_BUSY) {
|
|
__ASSERT(0, "Failed to stop ticker.\n");
|
|
}
|
|
|
|
/* notify thread that data is available */
|
|
k_sem_give(&sem_sync);
|
|
}
|
|
}
|
|
|
|
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;
|
|
|
|
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 */
|
|
TICKER_US_TO_TICKS(FLASH_INTERVAL), /* periodic */
|
|
TICKER_REMAINDER(FLASH_INTERVAL), /* per. remaind.*/
|
|
0, /* lazy, voluntary skips */
|
|
TICKER_US_TO_TICKS(FLASH_SLOT),
|
|
time_slot_callback_helper,
|
|
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) {
|
|
/* 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 erase_context context = {
|
|
.addr = addr,
|
|
.size = size,
|
|
.enable_time_limit = 1 /* enable time limit */
|
|
};
|
|
|
|
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 write_context context = {
|
|
.data_addr = (u32_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
|
|
};
|
|
|
|
return work_in_time_slice(&flash_op_desc);
|
|
}
|
|
|
|
#endif /* CONFIG_SOC_FLASH_NRF5_RADIO_SYNC */
|
|
|
|
static int erase_op(void *context)
|
|
{
|
|
u32_t prev_nvmc_cfg = NRF_NVMC->CONFIG;
|
|
u32_t pg_size = NRF_FICR->CODEPAGESIZE;
|
|
struct erase_context *e_ctx = context;
|
|
|
|
#if defined(CONFIG_SOC_FLASH_NRF5_RADIO_SYNC)
|
|
u32_t ticks_begin = 0;
|
|
u32_t ticks_diff;
|
|
u32_t i = 0;
|
|
|
|
if (e_ctx->enable_time_limit) {
|
|
ticks_begin = ticker_ticks_now_get();
|
|
}
|
|
#endif /* CONFIG_SOC_FLASH_NRF5_RADIO_SYNC */
|
|
|
|
/* Erase uses a specific configuration register */
|
|
NRF_NVMC->CONFIG = NVMC_CONFIG_WEN_Een << NVMC_CONFIG_WEN_Pos;
|
|
nvmc_wait_ready();
|
|
|
|
do {
|
|
NRF_NVMC->ERASEPAGE = e_ctx->addr;
|
|
nvmc_wait_ready();
|
|
|
|
e_ctx->size -= pg_size;
|
|
e_ctx->addr += pg_size;
|
|
|
|
#if defined(CONFIG_SOC_FLASH_NRF5_RADIO_SYNC)
|
|
i++;
|
|
|
|
if (e_ctx->enable_time_limit) {
|
|
ticks_diff = ticker_ticks_now_get() - ticks_begin;
|
|
if (ticks_diff + ticks_diff/i > FLASH_SLOT) {
|
|
break;
|
|
}
|
|
}
|
|
#endif /* CONFIG_SOC_FLASH_NRF5_RADIO_SYNC */
|
|
|
|
} while (e_ctx->size > 0);
|
|
|
|
NRF_NVMC->CONFIG = prev_nvmc_cfg;
|
|
nvmc_wait_ready();
|
|
|
|
return (e_ctx->size > 0) ? FLASH_OP_ONGOING : FLASH_OP_DONE;
|
|
}
|
|
|
|
static void shift_write_context(u32_t shift, struct write_context *w_ctx)
|
|
{
|
|
w_ctx->flash_addr += shift;
|
|
w_ctx->data_addr += shift;
|
|
w_ctx->len -= shift;
|
|
}
|
|
|
|
static int write_op(void *context)
|
|
{
|
|
struct write_context *w_ctx = context;
|
|
u32_t addr_word;
|
|
u32_t tmp_word;
|
|
u32_t count;
|
|
|
|
#if defined(CONFIG_SOC_FLASH_NRF5_RADIO_SYNC)
|
|
u32_t ticks_begin = 0;
|
|
u32_t ticks_diff;
|
|
u32_t i = 1;
|
|
|
|
if (w_ctx->enable_time_limit) {
|
|
ticks_begin = ticker_ticks_now_get();
|
|
}
|
|
#endif /* CONFIG_SOC_FLASH_NRF5_RADIO_SYNC */
|
|
|
|
/* Start with a word-aligned address and handle the offset */
|
|
addr_word = (u32_t)w_ctx->flash_addr & ~0x3;
|
|
|
|
/* If not aligned, read first word, update and write it back */
|
|
if (!is_aligned_32(w_ctx->flash_addr)) {
|
|
tmp_word = *(u32_t *)(addr_word);
|
|
|
|
count = sizeof(u32_t) - (w_ctx->flash_addr & 0x3);
|
|
if (count > w_ctx->len) {
|
|
count = w_ctx->len;
|
|
}
|
|
|
|
memcpy((u8_t *)&tmp_word + (w_ctx->flash_addr & 0x3),
|
|
(void *)w_ctx->data_addr,
|
|
count);
|
|
nvmc_wait_ready();
|
|
*(u32_t *)addr_word = tmp_word;
|
|
|
|
shift_write_context(count, w_ctx);
|
|
|
|
#if defined(CONFIG_SOC_FLASH_NRF5_RADIO_SYNC)
|
|
if (w_ctx->enable_time_limit) {
|
|
ticks_diff = ticker_ticks_now_get() - ticks_begin;
|
|
if (2 * ticks_diff > FLASH_SLOT) {
|
|
nvmc_wait_ready();
|
|
return FLASH_OP_ONGOING;
|
|
}
|
|
}
|
|
#endif /* CONFIG_SOC_FLASH_NRF5_RADIO_SYNC */
|
|
}
|
|
|
|
/* Write all the 4-byte aligned data */
|
|
while (w_ctx->len >= sizeof(u32_t)) {
|
|
nvmc_wait_ready();
|
|
*(u32_t *)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_NRF5_RADIO_SYNC)
|
|
i++;
|
|
|
|
if (w_ctx->enable_time_limit) {
|
|
ticks_diff = ticker_ticks_now_get() - ticks_begin;
|
|
|
|
if (ticks_diff + ticks_diff/i > FLASH_SLOT) {
|
|
nvmc_wait_ready();
|
|
return FLASH_OP_ONGOING;
|
|
}
|
|
}
|
|
#endif /* CONFIG_SOC_FLASH_NRF5_RADIO_SYNC */
|
|
}
|
|
|
|
/* Write remaining data */
|
|
if (w_ctx->len) {
|
|
tmp_word = *(u32_t *)(w_ctx->flash_addr);
|
|
memcpy((u8_t *)&tmp_word, (void *)w_ctx->data_addr, w_ctx->len);
|
|
nvmc_wait_ready();
|
|
*(u32_t *)w_ctx->flash_addr = tmp_word;
|
|
|
|
shift_write_context(w_ctx->len, w_ctx);
|
|
}
|
|
|
|
nvmc_wait_ready();
|
|
|
|
return FLASH_OP_DONE;
|
|
}
|
|
|
|
static int erase(u32_t addr, u32_t size)
|
|
{
|
|
struct erase_context context = {
|
|
.addr = addr,
|
|
.size = size,
|
|
#if defined(CONFIG_SOC_FLASH_NRF5_RADIO_SYNC)
|
|
.enable_time_limit = 0 /* disable time limit */
|
|
#endif /* CONFIG_SOC_FLASH_NRF5_RADIO_SYNC */
|
|
};
|
|
|
|
return erase_op(&context);
|
|
}
|
|
|
|
static int write(off_t addr, const void *data, size_t len)
|
|
{
|
|
struct write_context context = {
|
|
.data_addr = (u32_t) data,
|
|
.flash_addr = addr,
|
|
.len = len,
|
|
#if defined(CONFIG_SOC_FLASH_NRF5_RADIO_SYNC)
|
|
.enable_time_limit = 0 /* disable time limit */
|
|
#endif /* CONFIG_SOC_FLASH_NRF5_RADIO_SYNC */
|
|
};
|
|
|
|
return write_op(&context);
|
|
}
|