blob: e3b5c17f5e64eba3f9d90ea7e2eea2e2849fd85f [file] [log] [blame]
/* Copyright (c) 2017, Nordic Semiconductor ASA
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. Neither the name of Nordic Semiconductor ASA nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*/
/**
* @file
* This file implements the nrf 802.15.4 radio arbiter for softdevice.
*
* This arbiter should be used when 802.15.4 works concurrently with SoftDevice's radio stack.
*
*/
#include "nrf_raal_softdevice.h"
#include <assert.h>
#include <stdbool.h>
#include <string.h>
#include <nrf_raal_api.h>
#include <nrf_drv_radio802154_debug.h>
#include <nrf_drv_clock.h>
#include <softdevice.h>
/**@brief Enable Request and End on timeslot safety interrupt. */
#define ENABLE_REQUEST_AND_END_ON_TIMESLOT_END 0
/**@brief Maximum jitter relative to the start time of START and TIMER0 (safety margin) events . */
#define TIMER_TO_SIGNAL_JITTER_US NRF_RADIO_START_JITTER_US + 6
/**@brief Timer compare channel definitions. */
#define TIMER_CC_EXTEND 0
#define TIMER_CC_EXTEND_INTENSET TIMER_INTENSET_COMPARE0_Msk
#define TIMER_CC_EXTEND_INTENCLR TIMER_INTENCLR_COMPARE0_Pos
#define TIMER_CC_MARGIN 1
#define TIMER_CC_MARGIN_INTENSET TIMER_INTENSET_COMPARE1_Msk
#define TIMER_CC_MARGIN_INTENCLR TIMER_INTENCLR_COMPARE1_Pos
#define TIMER_CC_CAPTURE 2
/**@brief Defines number of microseconds in one second. */
#define US_PER_S 1000000
/**@brief Ceil division helper */
#define DIVIDE_AND_CEIL(A, B) (((A) + (B) - 1) / (B))
/**@brief Defines pending events. */
typedef enum
{
PENDING_EVENT_NONE = 0,
PENDING_EVENT_STARTED,
PENDING_EVENT_ENDED
} pending_events_t;
/**@brief Request parameters. */
static nrf_radio_request_t m_request;
/**@brief Return parameter for SD radio signal handler. */
static nrf_radio_signal_callback_return_param_t m_ret_param;
/**@brief Current configuration of the RAAL. */
static nrf_raal_softdevice_cfg_t m_config;
/**@brief Current timeslot length. */
static uint16_t m_timeslot_length;
/**@brief Defines if RAAL is in continous mode. */
static volatile bool m_continuous = false;
/**@brief Defines if RAAL is currently in a timeslot. */
static volatile bool m_in_timeslot = false;
/**@brief Current iteration process number. */
static volatile uint16_t m_alloc_iters;
/**@brief Defines if module has been initialized. */
static bool m_initialize = false;
/**@breif Defines if Radio Driver entered critical section. */
static volatile bool m_in_critical_section = false;
/**@brief Defines current pending event. */
static volatile pending_events_t m_pending_event = PENDING_EVENT_NONE;
/**@brief Defines RTC0 counter value on timeslot begin. */
static uint32_t m_start_rtc_ticks = 0;
/**@brief External Interrupt from RADIO. */
extern void RADIO_IRQHandler(void);
/**@brief Initialize timeslot internal variables. */
static inline void timeslot_data_init(void)
{
m_alloc_iters = 0;
m_timeslot_length = m_config.timeslot_length;
}
/**@brief Get actual time. */
static inline uint32_t timer_time_get(void)
{
NRF_TIMER0->TASKS_CAPTURE[TIMER_CC_CAPTURE] = 1;
return NRF_TIMER0->CC[TIMER_CC_CAPTURE];
}
/**@brief Check if margin is already reached. */
static inline bool timer_is_margin_reached(void)
{
return NRF_TIMER0->EVENTS_COMPARE[TIMER_CC_MARGIN];
}
/**@brief Enter timeslot critical section. */
static inline void timeslot_critical_section_enter(void)
{
NVIC_DisableIRQ(TIMER0_IRQn);
__DSB();
__ISB();
}
/**@brief Exit timeslot critical section. */
static inline void timeslot_critical_section_exit(void)
{
NVIC_EnableIRQ(TIMER0_IRQn);
}
static inline void timeslot_started_notify(void)
{
if (m_in_timeslot && m_continuous)
{
nrf_raal_timeslot_started();
}
}
static inline void timeslot_ended_notify(void)
{
if (!m_in_timeslot && m_continuous)
{
nrf_raal_timeslot_ended();
}
}
/**@brief Calculate maximal crystal drift. */
static inline uint32_t rtc_drift_calculate(uint32_t timeslot_length)
{
return DIVIDE_AND_CEIL(((uint64_t)timeslot_length * m_config.lf_clk_accuracy_ppm), US_PER_S);
}
/**@brief Prepare earliest timeslot request. */
static void timeslot_request_prepare(void)
{
memset(&m_request, 0, sizeof(m_request));
m_request.request_type = NRF_RADIO_REQ_TYPE_EARLIEST;
m_request.params.earliest.hfclk = NRF_RADIO_HFCLK_CFG_NO_GUARANTEE;
m_request.params.earliest.priority = NRF_RADIO_PRIORITY_NORMAL;
m_request.params.earliest.length_us = m_timeslot_length;
m_request.params.earliest.timeout_us = m_config.timeslot_timeout;
}
/**@brief Request earliest timeslot. */
static void timeslot_request(void)
{
assert(!m_in_timeslot);
timeslot_request_prepare();
sd_radio_request(&m_request);
}
/**@brief Set timer on timeslot started. */
static void timer_start(void)
{
NRF_TIMER0->TASKS_STOP = 1;
NRF_TIMER0->TASKS_CLEAR = 1;
NRF_TIMER0->BITMODE = TIMER_BITMODE_BITMODE_32Bit;
NRF_TIMER0->INTENSET = TIMER_CC_MARGIN_INTENSET;
NRF_TIMER0->CC[TIMER_CC_EXTEND] = 0;
NRF_TIMER0->CC[TIMER_CC_MARGIN] = m_timeslot_length - m_config.timeslot_safe_margin;
NRF_TIMER0->TASKS_START = 1;
NVIC_EnableIRQ(TIMER0_IRQn);
}
/**@brief Reset timer. */
static void timer_reset(void)
{
NRF_TIMER0->TASKS_STOP = 1;
NRF_TIMER0->EVENTS_COMPARE[TIMER_CC_EXTEND] = 0;
NRF_TIMER0->EVENTS_COMPARE[TIMER_CC_MARGIN] = 0;
NVIC_ClearPendingIRQ(TIMER0_IRQn);
}
/**@brief Set timer on extend event. */
static void timer_extend(void)
{
NVIC_ClearPendingIRQ(TIMER0_IRQn);
NRF_TIMER0->INTENSET = TIMER_CC_MARGIN_INTENSET;
NRF_TIMER0->CC[TIMER_CC_MARGIN] += m_timeslot_length;
if (!m_alloc_iters)
{
NRF_TIMER0->INTENSET = TIMER_CC_EXTEND_INTENSET;
NRF_TIMER0->CC[TIMER_CC_EXTEND] += m_timeslot_length;
}
}
/**@brief Eliminate timers jitters. */
static void timer_jitter_adjust(void)
{
// Adjust TIMER0 and RTC0 clocks drifts.
uint32_t timer_ticks = timer_time_get();
uint64_t rtc_ticks = NRF_RTC0->COUNTER;
if (rtc_ticks > m_start_rtc_ticks)
{
rtc_ticks -= m_start_rtc_ticks;
}
else
{
// Overflow detected.
rtc_ticks = RTC_COUNTER_COUNTER_Msk - m_start_rtc_ticks + rtc_ticks;
}
// Adjust RTC0 ticks to TIMER0 resolution. RTC0 works with 32768kHz so first
// multiply with 10^6 (microseconds) and divide by 32768Hz (2^15) to get microseconds.
rtc_ticks = DIVIDE_AND_CEIL((rtc_ticks * US_PER_S), 32768);
// Check if we are still in time.
uint32_t cc_margin = NRF_TIMER0->CC[TIMER_CC_MARGIN];
assert(cc_margin > rtc_ticks + rtc_drift_calculate(cc_margin));
if (rtc_ticks > timer_ticks)
{
NRF_TIMER0->CC[TIMER_CC_MARGIN] -= (rtc_ticks - timer_ticks);
}
else
{
NRF_TIMER0->CC[TIMER_CC_MARGIN] += (timer_ticks - rtc_ticks);
}
// Add safety drift time.
NRF_TIMER0->CC[TIMER_CC_MARGIN] -= rtc_drift_calculate(2 * m_config.timeslot_length) +
TIMER_TO_SIGNAL_JITTER_US;
}
/**@brief Decrease timeslot length. */
static void timeslot_decrease_length(void)
{
m_alloc_iters++;
m_timeslot_length = m_timeslot_length >> 1;
}
/**@brief Extend timeslot. */
static void timeslot_extend(void)
{
if (m_alloc_iters < m_config.timeslot_alloc_iters)
{
timeslot_decrease_length();
// Try to extend right after start.
m_ret_param.callback_action = NRF_RADIO_SIGNAL_CALLBACK_ACTION_EXTEND;
m_ret_param.params.extend.length_us = m_timeslot_length;
nrf_drv_radio802154_pin_set(PIN_DBG_TIMESLOT_EXTEND_REQ);
}
else
{
timer_jitter_adjust();
}
}
static void timer_irq_handle(void)
{
// Safe margin exceeded.
if (NRF_TIMER0->EVENTS_COMPARE[TIMER_CC_MARGIN])
{
nrf_drv_radio802154_pin_clr(PIN_DBG_TIMESLOT_ACTIVE);
nrf_drv_radio802154_log(EVENT_TRACE_ENTER, FUNCTION_RAAL_SIG_EVENT_MARGIN);
m_in_timeslot = false;
if (m_in_critical_section)
{
assert(m_pending_event != PENDING_EVENT_ENDED);
if (m_pending_event == PENDING_EVENT_STARTED)
{
m_pending_event = PENDING_EVENT_NONE;
}
else
{
m_pending_event = PENDING_EVENT_ENDED;
}
}
else
{
timeslot_ended_notify();
}
// Ignore any other events.
timer_reset();
#if (ENABLE_REQUEST_AND_END_ON_TIMESLOT_END == 1)
timeslot_data_init();
timeslot_request_prepare();
m_ret_param.callback_action = NRF_RADIO_SIGNAL_CALLBACK_ACTION_REQUEST_AND_END;
m_ret_param.params.request.p_next = &m_request;
#else
// Return and wait for NRF_EVT_RADIO_SESSION_IDLE event.
m_ret_param.callback_action = NRF_RADIO_SIGNAL_CALLBACK_ACTION_NONE;
#endif
nrf_drv_radio802154_log(EVENT_TRACE_EXIT, FUNCTION_RAAL_SIG_EVENT_MARGIN);
}
// Extension margin exceeded.
else if (NRF_TIMER0->EVENTS_COMPARE[TIMER_CC_EXTEND])
{
nrf_drv_radio802154_log(EVENT_TRACE_ENTER, FUNCTION_RAAL_SIG_EVENT_EXTEND);
NRF_TIMER0->INTENCLR = (1 << TIMER_CC_EXTEND_INTENCLR);
NRF_TIMER0->EVENTS_COMPARE[TIMER_CC_EXTEND] = 0;
if (m_continuous &&
NRF_TIMER0->CC[TIMER_CC_EXTEND] + m_config.timeslot_length < m_config.timeslot_max_length)
{
nrf_drv_radio802154_pin_set(PIN_DBG_TIMESLOT_EXTEND_REQ);
m_ret_param.callback_action = NRF_RADIO_SIGNAL_CALLBACK_ACTION_EXTEND;
m_ret_param.params.extend.length_us = m_config.timeslot_length;
}
else
{
timer_jitter_adjust();
m_ret_param.callback_action = NRF_RADIO_SIGNAL_CALLBACK_ACTION_NONE;
}
nrf_drv_radio802154_log(EVENT_TRACE_EXIT, FUNCTION_RAAL_SIG_EVENT_EXTEND);
}
else
{
// Should not happen.
assert(false);
}
}
/**@brief Signal handler. */
static nrf_radio_signal_callback_return_param_t *signal_handler(uint8_t signal_type)
{
nrf_drv_radio802154_log(EVENT_TRACE_ENTER, FUNCTION_RAAL_SIG_HANDLER);
// Default response.
m_ret_param.callback_action = NRF_RADIO_SIGNAL_CALLBACK_ACTION_NONE;
if (!m_continuous)
{
nrf_drv_radio802154_pin_clr(PIN_DBG_TIMESLOT_ACTIVE);
nrf_drv_radio802154_log(EVENT_TRACE_ENTER, FUNCTION_RAAL_SIG_EVENT_ENDED);
m_pending_event = PENDING_EVENT_NONE;
m_in_timeslot = false;
// TODO: Change to NRF_RADIO_SIGNAL_CALLBACK_ACTION_END (KRKNWK-937)
m_ret_param.callback_action = NRF_RADIO_SIGNAL_CALLBACK_ACTION_NONE;
timer_reset();
nrf_drv_radio802154_log(EVENT_TRACE_EXIT, FUNCTION_RAAL_SIG_EVENT_ENDED);
nrf_drv_radio802154_log(EVENT_TRACE_EXIT, FUNCTION_RAAL_SIG_HANDLER);
return &m_ret_param;
}
switch (signal_type)
{
case NRF_RADIO_CALLBACK_SIGNAL_TYPE_START: /**< This signal indicates the start of the radio timeslot. */
{
nrf_drv_radio802154_pin_set(PIN_DBG_TIMESLOT_ACTIVE);
nrf_drv_radio802154_log(EVENT_TRACE_ENTER, FUNCTION_RAAL_SIG_EVENT_START);
// Ensure HFCLK is running before start is issued.
assert(NRF_CLOCK->HFCLKSTAT == (CLOCK_HFCLKSTAT_SRC_Msk | CLOCK_HFCLKSTAT_STATE_Msk));
m_start_rtc_ticks = NRF_RTC0->COUNTER;
m_in_timeslot = true;
m_alloc_iters = 0;
m_timeslot_length = m_timeslot_length;
timer_start();
if (m_in_critical_section)
{
assert(m_pending_event != PENDING_EVENT_STARTED);
if (m_pending_event == PENDING_EVENT_ENDED)
{
m_pending_event = PENDING_EVENT_NONE;
}
else
{
m_pending_event = PENDING_EVENT_STARTED;
}
}
else
{
timeslot_started_notify();
}
// Try to extend right after start.
m_ret_param.callback_action = NRF_RADIO_SIGNAL_CALLBACK_ACTION_EXTEND;
m_ret_param.params.extend.length_us = m_timeslot_length;
nrf_drv_radio802154_log(EVENT_TRACE_EXIT, FUNCTION_RAAL_SIG_EVENT_START);
nrf_drv_radio802154_pin_set(PIN_DBG_TIMESLOT_EXTEND_REQ);
break;
}
case NRF_RADIO_CALLBACK_SIGNAL_TYPE_TIMER0: /**< This signal indicates the NRF_TIMER0 interrupt. */
timer_irq_handle();
break;
case NRF_RADIO_CALLBACK_SIGNAL_TYPE_RADIO: /**< This signal indicates the NRF_RADIO interrupt. */
nrf_drv_radio802154_pin_set(PIN_DBG_TIMESLOT_RADIO_IRQ);
nrf_drv_radio802154_log(EVENT_TRACE_ENTER, FUNCTION_RAAL_SIG_EVENT_RADIO);
if (m_in_timeslot)
{
if (!timer_is_margin_reached())
{
RADIO_IRQHandler();
}
else
{
// Handle margin exceeded event.
timer_irq_handle();
}
}
nrf_drv_radio802154_log(EVENT_TRACE_EXIT, FUNCTION_RAAL_SIG_EVENT_RADIO);
nrf_drv_radio802154_pin_clr(PIN_DBG_TIMESLOT_RADIO_IRQ);
break;
case NRF_RADIO_CALLBACK_SIGNAL_TYPE_EXTEND_FAILED: /**< This signal indicates extend action failed. */
nrf_drv_radio802154_pin_clr(PIN_DBG_TIMESLOT_EXTEND_REQ);
nrf_drv_radio802154_pin_tgl(PIN_DBG_TIMESLOT_FAILED);
nrf_drv_radio802154_log(EVENT_TRACE_ENTER, FUNCTION_RAAL_SIG_EVENT_EXCEED_FAIL);
timeslot_extend();
nrf_drv_radio802154_log(EVENT_TRACE_EXIT, FUNCTION_RAAL_SIG_EVENT_EXCEED_FAIL);
break;
case NRF_RADIO_CALLBACK_SIGNAL_TYPE_EXTEND_SUCCEEDED: /**< This signal indicates extend action succeeded. */
nrf_drv_radio802154_pin_clr(PIN_DBG_TIMESLOT_EXTEND_REQ);
nrf_drv_radio802154_log(EVENT_TRACE_ENTER, FUNCTION_RAAL_SIG_EVENT_EXCEED_SUCCESS);
timer_extend();
if (m_alloc_iters != 0)
{
timeslot_extend();
}
nrf_drv_radio802154_log(EVENT_TRACE_EXIT, FUNCTION_RAAL_SIG_EVENT_EXCEED_SUCCESS);
break;
default:
break;
}
nrf_drv_radio802154_log(EVENT_TRACE_EXIT, FUNCTION_RAAL_SIG_HANDLER);
return &m_ret_param;
}
void nrf_raal_softdevice_soc_evt_handler(uint32_t evt_id)
{
switch (evt_id)
{
case NRF_EVT_HFCLKSTARTED:
if (m_continuous)
{
timeslot_request();
}
break;
case NRF_EVT_RADIO_BLOCKED:
case NRF_EVT_RADIO_CANCELED:
{
nrf_drv_radio802154_pin_tgl(PIN_DBG_TIMESLOT_BLOCKED);
assert(!m_in_timeslot);
if (m_continuous)
{
if (m_alloc_iters < m_config.timeslot_alloc_iters)
{
timeslot_decrease_length();
}
timeslot_request();
}
break;
}
case NRF_EVT_RADIO_SIGNAL_CALLBACK_INVALID_RETURN:
assert(false);
break;
case NRF_EVT_RADIO_SESSION_IDLE:
if (m_continuous)
{
nrf_drv_radio802154_pin_tgl(PIN_DBG_TIMESLOT_SESSION_IDLE);
timeslot_data_init();
timeslot_request();
}
break;
case NRF_EVT_RADIO_SESSION_CLOSED:
break;
default:
break;
}
}
void nrf_raal_softdevice_config(const nrf_raal_softdevice_cfg_t * p_cfg)
{
assert(m_initialize);
assert(!m_continuous);
assert(p_cfg);
m_config = *p_cfg;
}
void nrf_raal_init(void)
{
assert(!m_initialize);
m_continuous = false;
m_in_timeslot = false;
m_config.lf_clk_accuracy_ppm = NRF_RAAL_DEFAULT_LF_CLK_ACCURACY_PPM;
m_config.timeslot_length = NRF_RAAL_TIMESLOT_DEFAULT_LENGTH;
m_config.timeslot_alloc_iters = NRF_RAAL_TIMESLOT_DEFAULT_ALLOC_ITERS;
m_config.timeslot_safe_margin = NRF_RAAL_TIMESLOT_DEFAULT_SAFE_MARGIN;
m_config.timeslot_max_length = NRF_RAAL_TIMESLOT_DEFAULT_MAX_LENGTH;
m_config.timeslot_timeout = NRF_RAAL_TIMESLOT_DEFAULT_TIMEOUT;
assert(sd_radio_session_open(signal_handler) == NRF_SUCCESS);
m_initialize = true;
}
void nrf_raal_uninit(void)
{
assert(m_initialize);
assert(sd_radio_session_close() == NRF_SUCCESS);
m_continuous = false;
m_in_timeslot = false;
nrf_drv_radio802154_pin_clr(PIN_DBG_TIMESLOT_ACTIVE);
}
void nrf_raal_continuous_mode_enter(void)
{
nrf_drv_radio802154_log(EVENT_TRACE_ENTER, FUNCTION_RAAL_CONTINUOUS_ENTER);
assert(m_initialize);
assert(!m_continuous);
m_alloc_iters = 0;
m_timeslot_length = m_config.timeslot_length;
m_continuous = true;
nrf_drv_clock_hfclk_request(NULL);
if (NRF_CLOCK->HFCLKSTAT == (CLOCK_HFCLKSTAT_SRC_Msk | CLOCK_HFCLKSTAT_STATE_Msk))
{
timeslot_request();
}
nrf_drv_radio802154_log(EVENT_TRACE_EXIT, FUNCTION_RAAL_CONTINUOUS_ENTER);
}
void nrf_raal_continuous_mode_exit(void)
{
nrf_drv_radio802154_log(EVENT_TRACE_ENTER, FUNCTION_RAAL_CONTINUOUS_EXIT);
assert(m_initialize);
assert(m_continuous);
m_continuous = false;
// Emulate signal interrupt to inform SD about end of m_continuous mode.
if (m_in_timeslot)
{
NVIC_SetPendingIRQ(TIMER0_IRQn);
}
nrf_drv_clock_hfclk_release();
nrf_drv_radio802154_log(EVENT_TRACE_EXIT, FUNCTION_RAAL_CONTINUOUS_EXIT);
}
bool nrf_raal_timeslot_request(uint32_t length_us)
{
if (!m_continuous || !m_in_timeslot)
{
return false;
}
return timer_time_get() + length_us < NRF_TIMER0->CC[TIMER_CC_MARGIN];
}
uint32_t nrf_raal_timeslot_us_left_get(void)
{
if (!m_continuous || !m_in_timeslot)
{
return 0;
}
return NRF_TIMER0->CC[TIMER_CC_MARGIN] - timer_time_get();
}
void nrf_raal_critical_section_enter(void)
{
nrf_drv_radio802154_log(EVENT_TRACE_ENTER, FUNCTION_RAAL_CRIT_SECT_ENTER);
assert(!m_in_critical_section);
m_in_critical_section = true;
nrf_drv_radio802154_log(EVENT_TRACE_EXIT, FUNCTION_RAAL_CRIT_SECT_ENTER);
}
void nrf_raal_critical_section_exit(void)
{
nrf_drv_radio802154_log(EVENT_TRACE_ENTER, FUNCTION_RAAL_CRIT_SECT_EXIT);
timeslot_critical_section_enter();
assert(m_in_critical_section);
m_in_critical_section = false;
switch (m_pending_event)
{
case PENDING_EVENT_STARTED:
timeslot_started_notify();
break;
case PENDING_EVENT_ENDED:
timeslot_ended_notify();
break;
default:
break;
}
m_pending_event = PENDING_EVENT_NONE;
timeslot_critical_section_exit();
nrf_drv_radio802154_log(EVENT_TRACE_EXIT, FUNCTION_RAAL_CRIT_SECT_EXIT);
}