/* * Copyright (c) 2020 Nordic Semiconductor ASA * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include #include #include #include #include #include "hal/ticker.h" #include "ticker/ticker.h" #include "ll.h" #include "soc_flash_nrf.h" #define FLASH_RADIO_ABORT_DELAY_US 1500 #define FLASH_RADIO_WORK_DELAY_US 200 /* delay needed for start execution-window */ #define FLASH_SYNC_SWITCHING_TIME (FLASH_RADIO_ABORT_DELAY_US +\ FLASH_RADIO_WORK_DELAY_US) struct ticker_sync_context { uint32_t interval; /* timeslot interval. */ uint32_t slot; /* timeslot length. */ uint32_t ticks_begin; /* timeslot begin timestamp */ int result; }; static struct ticker_sync_context _ticker_sync_context; /* semaphore for synchronization of flash operations */ static struct k_sem sem_sync; static void ticker_stop_work_cb(uint32_t status, void *param) { __ASSERT((status == TICKER_STATUS_SUCCESS || status == TICKER_STATUS_FAILURE), "Failed to stop work ticker, ticker job busy.\n"); /* notify thread that data is available */ k_sem_give(&sem_sync); } static void ticker_stop_prepare_cb(uint32_t status, void *param) { uint8_t instance_index; uint8_t ticker_id; uint32_t ret; __ASSERT(status == TICKER_STATUS_SUCCESS, "Failed to stop prepare ticker.\n"); /* Get the ticker instance and ticker id for flash operations */ ll_timeslice_ticker_id_get(&instance_index, &ticker_id); /* Stop the work ticker, from ULL_LOW context */ ret = ticker_stop(instance_index, 2U, (ticker_id + 1U), ticker_stop_work_cb, NULL); __ASSERT((ret == TICKER_STATUS_SUCCESS || ret == TICKER_STATUS_BUSY), "Failed to request the work ticker to stop.\n"); } static void time_slot_callback_work(uint32_t ticks_at_expire, uint32_t ticks_drift, uint32_t remainder, uint16_t lazy, uint8_t force, void *context) { struct flash_op_desc *op_desc; int rc; __ASSERT(ll_radio_state_is_idle(), "Radio is on during flash operation.\n"); op_desc = context; rc = op_desc->handler(op_desc->context); if (rc != FLASH_OP_ONGOING) { uint8_t instance_index; uint8_t ticker_id; uint32_t ret; /* Get the ticker instance and ticker id for flash operations */ ll_timeslice_ticker_id_get(&instance_index, &ticker_id); /* Stop the prepare ticker, from ULL_HIGH context */ ret = ticker_stop(instance_index, 1U, ticker_id, ticker_stop_prepare_cb, NULL); __ASSERT((ret == TICKER_STATUS_SUCCESS || ret == TICKER_STATUS_BUSY), "Failed to stop ticker.\n"); _ticker_sync_context.result = (rc == FLASH_OP_DONE) ? 0 : rc; } } static void time_slot_delay(uint32_t ticks_at_expire, uint32_t ticks_delay, ticker_timeout_func callback, void *context) { uint8_t instance_index; uint8_t ticker_id; uint32_t ret; /* Get the ticker instance and ticker id for flash operations */ 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. */ ret = ticker_start(instance_index, /* Radio instance ticker */ 1, /* user id for link layer ULL_HIGH */ /* (MAYFLY_CALL_ID_WORKER) */ (ticker_id + 1), /* 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 (ret != TICKER_STATUS_SUCCESS && ret != TICKER_STATUS_BUSY) { /* Failed to enqueue the ticker start operation request */ _ticker_sync_context.result = 0; /* Abort flash prepare ticker, from ULL_HIGH context */ ret = ticker_stop(instance_index, 1U, ticker_id, ticker_stop_prepare_cb, NULL); __ASSERT((ret == TICKER_STATUS_SUCCESS || ret == TICKER_STATUS_BUSY), "Failed to stop ticker.\n"); } } static void time_slot_callback_abort(uint32_t ticks_at_expire, uint32_t ticks_drift, uint32_t remainder, uint16_t lazy, uint8_t force, 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(uint32_t ticks_at_expire, uint32_t ticks_drift, uint32_t remainder, uint16_t lazy, uint8_t force, void *context) { #if defined(CONFIG_BT_CTLR_LOW_LAT) time_slot_callback_abort(ticks_at_expire, ticks_drift, remainder, lazy, force, context); #else /* !CONFIG_BT_CTLR_LOW_LAT */ time_slot_delay(ticks_at_expire, HAL_TICKER_US_TO_TICKS(FLASH_RADIO_ABORT_DELAY_US), time_slot_callback_abort, context); #endif /* CONFIG_BT_CTLR_LOW_LAT */ } int nrf_flash_sync_init(void) { k_sem_init(&sem_sync, 0, 1); return 0; } void nrf_flash_sync_set_context(uint32_t duration) { /* FLASH_SYNC_SWITCHING_TIME is delay which is always added by * the slot calling mechanism */ _ticker_sync_context.interval = duration - FLASH_SYNC_SWITCHING_TIME; _ticker_sync_context.slot = duration; } int nrf_flash_sync_exe(struct flash_op_desc *op_desc) { uint8_t instance_index; uint8_t ticker_id; uint32_t ret; int result; /* Get the ticker instance and ticker id for flash operations */ ll_timeslice_ticker_id_get(&instance_index, &ticker_id); /* Start periodic flash operation prepare time slots */ ret = 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( _ticker_sync_context.interval), /* period remainder */ HAL_TICKER_REMAINDER(_ticker_sync_context.interval), 0, /* lazy, voluntary skips */ HAL_TICKER_US_TO_TICKS(_ticker_sync_context.slot), time_slot_callback_prepare, op_desc, NULL, /* no op callback */ NULL); if (ret != TICKER_STATUS_SUCCESS && ret != TICKER_STATUS_BUSY) { /* Failed to enqueue the ticker start operation request */ result = -ECANCELED; } else if (k_sem_take(&sem_sync, K_MSEC(FLASH_TIMEOUT_MS)) != 0) { /* Stop any scheduled jobs, from thread context */ ret = ticker_stop(instance_index, 3U, ticker_id, NULL, NULL); __ASSERT((ret == TICKER_STATUS_SUCCESS || ret == TICKER_STATUS_BUSY), "Failed to stop ticker.\n"); /* wait for operation's complete overrun*/ result = -ETIMEDOUT; } else { result = _ticker_sync_context.result; } return result; } bool nrf_flash_sync_is_required(void) { return ticker_is_initialized(0); } void nrf_flash_sync_get_timestamp_begin(void) { _ticker_sync_context.ticks_begin = ticker_ticks_now_get(); } bool nrf_flash_sync_check_time_limit(uint32_t iteration) { uint32_t ticks_diff; ticks_diff = ticker_ticks_diff_get(ticker_ticks_now_get(), _ticker_sync_context.ticks_begin); if (ticks_diff + ticks_diff/iteration > HAL_TICKER_US_TO_TICKS(_ticker_sync_context.slot)) { return true; } return false; }