/* * Copyright (c) 2017 Nordic Semiconductor ASA * Copyright (c) 2016 Linaro Limited * Copyright (c) 2016 Intel Corporation * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include #include #include #include #include #if defined(CONFIG_SOC_FLASH_NRF5_RADIO_SYNC) #include #include #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); }