blob: c060e883bf358cd68a1dc056347baec9280a1bdf [file] [log] [blame]
/*
* Copyright (c) 2020 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <errno.h>
#include <zephyr/kernel.h>
#include <zephyr/device.h>
#include <zephyr/init.h>
#include <soc.h>
#include "soc_flash_nrf.h"
#include <zephyr/sys/__assert.h>
#include <zephyr/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
/* 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 inline int _ticker_stop(uint8_t inst_idx, uint8_t u_id, uint8_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(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;
uint8_t instance_index;
uint8_t ticker_id;
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) {
ll_timeslice_ticker_id_get(&instance_index, &ticker_id);
/* Stop the time slot ticker */
_ticker_stop(instance_index, 0, ticker_id);
_ticker_sync_context.result = (rc == FLASH_OP_DONE) ? 0 : rc;
/* notify thread that data is available */
k_sem_give(&sem_sync);
}
}
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;
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 */
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 (err != TICKER_STATUS_SUCCESS && err != TICKER_STATUS_BUSY) {
_ticker_sync_context.result = 0;
/* 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(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;
int result;
uint32_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 */
/* 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 (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 = _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;
}