Skip to content

Commit

Permalink
modules: nrfxlib: nrf_802154: RTC0 used for HW Task
Browse files Browse the repository at this point in the history
Instead of using an RTC1 channel for hardware tasks triggering
used by 802.15.4, an RTC0 channel is used. This frees an RTC1
CC channel, allowing it to be used by Zephyr for other purposes.

Signed-off-by: Artur Hadasz <[email protected]>
  • Loading branch information
ahasztag authored and rlubos committed Sep 25, 2023
1 parent 404d6a1 commit 9b18cb3
Showing 1 changed file with 145 additions and 33 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,28 @@
#include <zephyr/kernel.h>

#include <zephyr/drivers/timer/nrf_rtc_timer.h>
#include <zephyr/sys/barrier.h>
#include <hal/nrf_rtc.h>
#include <helpers/nrfx_gppi.h>

#include "platform/nrf_802154_clock.h"
#include "nrf_802154_sl_utils.h"

#define RTC_CHAN_INVALID (-1)
#if defined(CONFIG_SOC_NRF5340_CPUNET)

BUILD_ASSERT(CONFIG_MPSL);

#define HW_TASK_RTC NRF_RTC0
#define HW_TASK_RTC_CHAN (3)

#define RTC_COUNTER_BIT_WIDTH 24U
#define RTC_COUNTER_SPAN BIT(RTC_COUNTER_BIT_WIDTH)
#define RTC_COUNTER_MAX (RTC_COUNTER_SPAN - 1U)
#define RTC_COUNTER_HALF_SPAN (RTC_COUNTER_SPAN / 2U)

#define RTC_MIN_CYCLES_FROM_NOW 3
#endif

struct timer_desc {
z_nrf_rtc_timer_compare_handler_t handler;
Expand Down Expand Up @@ -98,40 +114,152 @@ static void timer_start_at(struct timer_desc *timer, uint64_t target_time)
nrf_802154_sl_mcu_critical_exit(state);
}

#if defined(CONFIG_SOC_NRF5340_CPUNET)
static inline uint32_t rtc_counter_diff(uint32_t a, uint32_t b)
{
return (a - b) & RTC_COUNTER_MAX;
}
#endif

static bool hw_task_state_set(enum hw_task_state_type expected_state,
enum hw_task_state_type new_state)
{
return atomic_cas(&m_hw_task.state, expected_state, new_state);
}

static void cc_bind_to_ppi(int32_t cc_num, uint32_t ppi_num)
static inline uint32_t hw_task_rtc_get_compare_evt_address(int32_t cc_num)
{
#if defined(CONFIG_SOC_NRF5340_CPUNET)
return nrf_rtc_event_address_get(HW_TASK_RTC, nrf_rtc_compare_event_get(cc_num));
#else
return z_nrf_rtc_timer_compare_evt_address_get(cc_num);
#endif
}

static void hw_task_rtc_cc_bind_to_ppi(int32_t cc_num, uint32_t ppi_num)
{
uint32_t event_address = z_nrf_rtc_timer_compare_evt_address_get(cc_num);
uint32_t event_address = hw_task_rtc_get_compare_evt_address(cc_num);

nrfx_gppi_event_endpoint_setup(ppi_num, event_address);
}

static void cc_unbind(int32_t cc_num, uint32_t ppi_num)
static void hw_task_rtc_cc_unbind(int32_t cc_num, uint32_t ppi_num)
{
if (ppi_num != NRF_802154_SL_HW_TASK_PPI_INVALID) {
uint32_t event_address = z_nrf_rtc_timer_compare_evt_address_get(cc_num);
uint32_t event_address = hw_task_rtc_get_compare_evt_address(cc_num);

nrfx_gppi_event_endpoint_clear(ppi_num, event_address);
}
}

static bool cc_event_check(int32_t cc_num)
static bool hw_task_rtc_cc_event_check(int32_t cc_num)
{
uint32_t event_address = z_nrf_rtc_timer_compare_evt_address_get(cc_num);
uint32_t event_address = hw_task_rtc_get_compare_evt_address(cc_num);

return *(volatile uint32_t *)event_address;
}

static void hw_task_rtc_timer_abort(void)
{
#if defined(CONFIG_SOC_NRF5340_CPUNET)
/* No interrupt disabling/clearing is needed, as HW task does not use interrupts*/
nrf_rtc_event_disable(HW_TASK_RTC, NRF_RTC_CHANNEL_INT_MASK(m_hw_task.chan));
nrf_rtc_event_clear(HW_TASK_RTC, NRF_RTC_CHANNEL_EVENT_ADDR(m_hw_task.chan));
#else
z_nrf_rtc_timer_abort(m_hw_task.chan);
#endif
}

#if defined(CONFIG_SOC_NRF5340_CPUNET)
static uint32_t zephyr_rtc_ticks_to_hw_task_rtc_counter(uint64_t ticks)
{
uint64_t zephyr_rtc_counter_now = 0;
uint32_t hw_task_rtc_counter_now = 0;
uint32_t hw_task_rtc_counter_now_2 = 0;

/**
* The RTC0 counter is read twice - once before and once after reading RTC1.
* If the two reads are different, the procedure is repeated.
* This is to make sure that no RTC tick occurred between reading RTC0 and
* RTC1 and so the calculated difference between the timers is correct.
*/
do {
hw_task_rtc_counter_now = nrf_rtc_counter_get(HW_TASK_RTC);
zephyr_rtc_counter_now = z_nrf_rtc_timer_read();
barrier_dmem_fence_full();
hw_task_rtc_counter_now_2 = nrf_rtc_counter_get(HW_TASK_RTC);

} while (hw_task_rtc_counter_now != hw_task_rtc_counter_now_2);

/* This function can only be called in close proximity of ticks */
assert(ticks >= zephyr_rtc_counter_now
|| zephyr_rtc_counter_now - ticks < RTC_COUNTER_HALF_SPAN);
assert(ticks <= zephyr_rtc_counter_now
|| ticks - zephyr_rtc_counter_now < RTC_COUNTER_HALF_SPAN);

uint32_t diff = rtc_counter_diff(hw_task_rtc_counter_now,
(uint32_t) zephyr_rtc_counter_now % RTC_COUNTER_SPAN);

return (uint32_t) ((ticks + diff) % RTC_COUNTER_SPAN);
}

static int hw_task_rtc_counter_compare_set(uint32_t cc_value)
{
uint32_t current_counter_value = nrf_rtc_counter_get(HW_TASK_RTC);

/* As the HW tasks are setup only shortly before they are triggered,
* it is certain that the required fire time is not further away
* than one half of the RTC counter span.
* If it is it means that the trigger time is already in the past.
*/
if (rtc_counter_diff(cc_value, current_counter_value) > RTC_COUNTER_HALF_SPAN) {
return -EINVAL;
}

nrf_rtc_event_disable(HW_TASK_RTC, NRF_RTC_CHANNEL_INT_MASK(m_hw_task.chan));
nrf_rtc_event_clear(HW_TASK_RTC, NRF_RTC_CHANNEL_EVENT_ADDR(m_hw_task.chan));

nrf_rtc_cc_set(HW_TASK_RTC, m_hw_task.chan, cc_value);

nrf_rtc_event_enable(HW_TASK_RTC, NRF_RTC_CHANNEL_INT_MASK(m_hw_task.chan));

current_counter_value = nrf_rtc_counter_get(HW_TASK_RTC);

if (rtc_counter_diff(cc_value, current_counter_value + RTC_MIN_CYCLES_FROM_NOW)
> (RTC_COUNTER_HALF_SPAN - RTC_MIN_CYCLES_FROM_NOW)) {
nrf_rtc_event_disable(HW_TASK_RTC, NRF_RTC_CHANNEL_INT_MASK(m_hw_task.chan));
nrf_rtc_event_clear(HW_TASK_RTC, NRF_RTC_CHANNEL_EVENT_ADDR(m_hw_task.chan));
return -EINVAL;
}

return 0;
}
#endif

static int hw_task_rtc_timer_set(uint64_t fire_lpticks)
{
#if defined(CONFIG_SOC_NRF5340_CPUNET)
uint32_t hw_task_rtc_counter_ticks = zephyr_rtc_ticks_to_hw_task_rtc_counter(fire_lpticks);

return hw_task_rtc_counter_compare_set(hw_task_rtc_counter_ticks);
#else
return z_nrf_rtc_timer_exact_set(m_hw_task.chan, fire_lpticks, NULL, NULL);
#endif
}

void nrf_802154_platform_sl_lp_timer_init(void)
{
m_in_critical_section = false;
m_hw_task.state = HW_TASK_STATE_IDLE;
m_hw_task.chan = RTC_CHAN_INVALID;
#if defined(CONFIG_SOC_NRF5340_CPUNET)
m_hw_task.chan = HW_TASK_RTC_CHAN;
#else
m_hw_task.chan = z_nrf_rtc_timer_chan_alloc();
if (m_hw_task.chan < 0) {
assert(false);
return;
}
#endif
m_hw_task.ppi = NRF_802154_SL_HW_TASK_PPI_INVALID;
m_timer.handler = timer_handler;
m_sync_timer.handler = sync_timer_handler;
Expand Down Expand Up @@ -159,7 +287,9 @@ void nrf_802154_platform_sl_lp_timer_deinit(void)
z_nrf_rtc_timer_chan_free(m_sync_timer.chan);

if (m_hw_task.chan != RTC_CHAN_INVALID) {
#if !defined(CONFIG_SOC_NRF5340_CPUNET)
z_nrf_rtc_timer_chan_free(m_hw_task.chan);
#endif
m_hw_task.chan = RTC_CHAN_INVALID;
}
}
Expand Down Expand Up @@ -270,25 +400,7 @@ nrf_802154_sl_lptimer_platform_result_t nrf_802154_platform_sl_lptimer_hw_task_p
return NRF_802154_SL_LPTIMER_PLATFORM_NO_RESOURCES;
}

/* Allocate the channel if not already done. */
if (m_hw_task.chan == RTC_CHAN_INVALID) {
/* The channel allocation cannot take place during module
* initialization, because for nRF53 it would run
* out of resources - some other module temporarily needs
* an rtc channel to initialize. For this reason, this is where
* the allocation is made.
* Once a channel has been successfully allocated, it will be held
* until nrf_802154_platform_sl_lp_timer_deinit is called.
*/
m_hw_task.chan = z_nrf_rtc_timer_chan_alloc();
if (m_hw_task.chan < 0) {
m_hw_task.chan = RTC_CHAN_INVALID;
hw_task_state_set(HW_TASK_STATE_SETTING_UP, HW_TASK_STATE_IDLE);
return NRF_802154_SL_LPTIMER_PLATFORM_NO_RESOURCES;
}
}

if (z_nrf_rtc_timer_exact_set(m_hw_task.chan, fire_lpticks, NULL, NULL) != 0) {
if (hw_task_rtc_timer_set(fire_lpticks) != 0) {
hw_task_state_set(HW_TASK_STATE_SETTING_UP, HW_TASK_STATE_IDLE);
uint64_t now = z_nrf_rtc_timer_read();

Expand All @@ -299,17 +411,17 @@ nrf_802154_sl_lptimer_platform_result_t nrf_802154_platform_sl_lptimer_hw_task_p
return NRF_802154_SL_LPTIMER_PLATFORM_TOO_LATE;
}

evt_address = z_nrf_rtc_timer_compare_evt_address_get(m_hw_task.chan);
evt_address = hw_task_rtc_get_compare_evt_address(m_hw_task.chan);

nrf_802154_sl_mcu_critical_enter(mcu_cs_state);

/* For triggering to take place a safe margin is 2 lpticks from `now`. */
if ((z_nrf_rtc_timer_read() + 2) > fire_lpticks) {
/* it is too late */
nrf_802154_sl_mcu_critical_exit(mcu_cs_state);
cc_unbind(m_hw_task.chan, ppi_channel);
hw_task_rtc_cc_unbind(m_hw_task.chan, ppi_channel);
m_hw_task.ppi = NRF_802154_SL_HW_TASK_PPI_INVALID;
z_nrf_rtc_timer_abort(m_hw_task.chan);
hw_task_rtc_timer_abort();
hw_task_state_set(HW_TASK_STATE_SETTING_UP, HW_TASK_STATE_IDLE);
return NRF_802154_SL_LPTIMER_PLATFORM_TOO_LATE;
}
Expand All @@ -330,9 +442,9 @@ nrf_802154_sl_lptimer_platform_result_t nrf_802154_platform_sl_lptimer_hw_task_c
return NRF_802154_SL_LPTIMER_PLATFORM_WRONG_STATE;
}

z_nrf_rtc_timer_abort(m_hw_task.chan);
hw_task_rtc_timer_abort();

cc_unbind(m_hw_task.chan, m_hw_task.ppi);
hw_task_rtc_cc_unbind(m_hw_task.chan, m_hw_task.ppi);
m_hw_task.ppi = NRF_802154_SL_HW_TASK_PPI_INVALID;

hw_task_state_set(HW_TASK_STATE_CLEANING, HW_TASK_STATE_IDLE);
Expand All @@ -353,10 +465,10 @@ nrf_802154_sl_lptimer_platform_result_t nrf_802154_platform_sl_lptimer_hw_task_u

nrf_802154_sl_mcu_critical_enter(mcu_cs_state);

cc_bind_to_ppi(m_hw_task.chan, ppi_channel);
hw_task_rtc_cc_bind_to_ppi(m_hw_task.chan, ppi_channel);
m_hw_task.ppi = ppi_channel;

cc_triggered = cc_event_check(m_hw_task.chan);
cc_triggered = hw_task_rtc_cc_event_check(m_hw_task.chan);
if (z_nrf_rtc_timer_read() >= m_hw_task.fire_lpticks) {
cc_triggered = true;
}
Expand Down

0 comments on commit 9b18cb3

Please sign in to comment.