blob: fb9c859caeb89dbc85a05215a8f5b3d2ba39402f [file] [log] [blame]
/*
* Copyright (c) 2024 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/kernel.h>
#include <zephyr/device.h>
#include <zephyr/irq.h>
#if defined(CONFIG_CLOCK_CONTROL_NRF)
#include <zephyr/drivers/clock_control/nrf_clock_control.h>
#endif
#include <zephyr/drivers/timer/system_timer.h>
#include <zephyr/drivers/timer/nrf_grtc_timer.h>
#include <nrfx_grtc.h>
#include <zephyr/sys/math_extras.h>
#define GRTC_NODE DT_NODELABEL(grtc)
/* Ensure that GRTC properties in devicetree are defined correctly. */
#if !DT_NODE_HAS_PROP(GRTC_NODE, owned_channels)
#error GRTC owned-channels DT property is not defined
#endif
#define OWNED_CHANNELS_MASK NRFX_CONFIG_MASK_DT(GRTC_NODE, owned_channels)
#define CHILD_OWNED_CHANNELS_MASK NRFX_CONFIG_MASK_DT(GRTC_NODE, child_owned_channels)
#if ((OWNED_CHANNELS_MASK | CHILD_OWNED_CHANNELS_MASK) != OWNED_CHANNELS_MASK)
#error GRTC child-owned-channels DT property must be a subset of owned-channels
#endif
#define CHAN_COUNT NRFX_GRTC_CONFIG_NUM_OF_CC_CHANNELS
#define EXT_CHAN_COUNT (CHAN_COUNT - 1)
#ifndef GRTC_SYSCOUNTERL_VALUE_Msk
#define GRTC_SYSCOUNTERL_VALUE_Msk GRTC_SYSCOUNTER_SYSCOUNTERL_VALUE_Msk
#endif
#ifndef GRTC_SYSCOUNTERH_VALUE_Msk
#define GRTC_SYSCOUNTERH_VALUE_Msk GRTC_SYSCOUNTER_SYSCOUNTERH_VALUE_Msk
#endif
#define MAX_CC_LATCH_WAIT_TIME_US 77
#define CYC_PER_TICK \
((uint64_t)sys_clock_hw_cycles_per_sec() / (uint64_t)CONFIG_SYS_CLOCK_TICKS_PER_SEC)
#define COUNTER_SPAN (GRTC_SYSCOUNTERL_VALUE_Msk | ((uint64_t)GRTC_SYSCOUNTERH_VALUE_Msk << 32))
#define MAX_TICKS \
(((COUNTER_SPAN / CYC_PER_TICK) > INT_MAX) ? INT_MAX : (COUNTER_SPAN / CYC_PER_TICK))
#define MAX_CYCLES (MAX_TICKS * CYC_PER_TICK)
#define LFCLK_FREQUENCY_HZ 32768
#if defined(CONFIG_TEST)
const int32_t z_sys_timer_irq_for_test = DT_IRQN(GRTC_NODE);
#endif
static void sys_clock_timeout_handler(int32_t id, uint64_t cc_val, void *p_context);
static struct k_spinlock lock;
static uint64_t last_count; /* Time (SYSCOUNTER value) @last sys_clock_announce() */
static atomic_t int_mask;
static uint8_t ext_channels_allocated;
static nrfx_grtc_channel_t system_clock_channel_data = {
.handler = sys_clock_timeout_handler,
.p_context = NULL,
.channel = (uint8_t)-1,
};
#define IS_CHANNEL_ALLOWED_ASSERT(chan) \
__ASSERT_NO_MSG((NRFX_GRTC_CONFIG_ALLOWED_CC_CHANNELS_MASK & (1UL << (chan))) && \
((chan) != system_clock_channel_data.channel))
static inline uint64_t counter_sub(uint64_t a, uint64_t b)
{
return (a - b);
}
static inline uint64_t counter(void)
{
uint64_t now;
nrfx_grtc_syscounter_get(&now);
return now;
}
static inline int get_comparator(uint32_t chan, uint64_t *cc)
{
nrfx_err_t result;
result = nrfx_grtc_syscounter_cc_value_read(chan, cc);
if (result != NRFX_SUCCESS) {
if (result != NRFX_ERROR_INVALID_PARAM) {
return -EAGAIN;
}
return -EPERM;
}
return 0;
}
/*
* Program a new callback <value> microseconds in the future
*/
static void system_timeout_set_relative(uint64_t value)
{
if (value <= NRF_GRTC_SYSCOUNTER_CCADD_MASK) {
nrfx_grtc_syscounter_cc_relative_set(&system_clock_channel_data, value, true,
NRFX_GRTC_CC_RELATIVE_SYSCOUNTER);
} else {
nrfx_grtc_syscounter_cc_absolute_set(&system_clock_channel_data, value + counter(),
true);
}
}
/*
* Program a new callback in the absolute time given by <value>
*/
static void system_timeout_set_abs(uint64_t value)
{
nrfx_grtc_syscounter_cc_absolute_set(&system_clock_channel_data, value,
true);
}
static bool compare_int_lock(int32_t chan)
{
atomic_val_t prev = atomic_and(&int_mask, ~BIT(chan));
nrfx_grtc_syscounter_cc_int_disable(chan);
return prev & BIT(chan);
}
static void compare_int_unlock(int32_t chan, bool key)
{
if (key) {
atomic_or(&int_mask, BIT(chan));
nrfx_grtc_syscounter_cc_int_enable(chan);
}
}
static void sys_clock_timeout_handler(int32_t id, uint64_t cc_val, void *p_context)
{
ARG_UNUSED(id);
ARG_UNUSED(p_context);
uint64_t dticks;
uint64_t now = counter();
if (unlikely(now < cc_val)) {
return;
}
dticks = counter_sub(cc_val, last_count) / CYC_PER_TICK;
last_count += dticks * CYC_PER_TICK;
if (!IS_ENABLED(CONFIG_TICKLESS_KERNEL)) {
/* protection is not needed because we are in the GRTC interrupt
* so it won't get preempted by the interrupt.
*/
system_timeout_set_abs(last_count + CYC_PER_TICK);
}
sys_clock_announce((int32_t)dticks);
}
int32_t z_nrf_grtc_timer_chan_alloc(void)
{
uint8_t chan;
nrfx_err_t err_code;
/* Prevent allocating all available channels - one must be left for system purposes. */
if (ext_channels_allocated >= EXT_CHAN_COUNT) {
return -ENOMEM;
}
err_code = nrfx_grtc_channel_alloc(&chan);
if (err_code != NRFX_SUCCESS) {
return -ENOMEM;
}
ext_channels_allocated++;
return (int32_t)chan;
}
void z_nrf_grtc_timer_chan_free(int32_t chan)
{
IS_CHANNEL_ALLOWED_ASSERT(chan);
nrfx_err_t err_code = nrfx_grtc_channel_free(chan);
if (err_code == NRFX_SUCCESS) {
ext_channels_allocated--;
}
}
bool z_nrf_grtc_timer_compare_evt_check(int32_t chan)
{
IS_CHANNEL_ALLOWED_ASSERT(chan);
uint32_t event_address = nrfx_grtc_event_compare_address_get(chan);
return *(volatile uint32_t *)event_address != 0;
}
uint32_t z_nrf_grtc_timer_compare_evt_address_get(int32_t chan)
{
IS_CHANNEL_ALLOWED_ASSERT(chan);
return nrfx_grtc_event_compare_address_get(chan);
}
uint32_t z_nrf_grtc_timer_capture_task_address_get(int32_t chan)
{
IS_CHANNEL_ALLOWED_ASSERT(chan);
return nrfx_grtc_capture_task_address_get(chan);
}
uint64_t z_nrf_grtc_timer_read(void)
{
return counter();
}
bool z_nrf_grtc_timer_compare_int_lock(int32_t chan)
{
IS_CHANNEL_ALLOWED_ASSERT(chan);
return compare_int_lock(chan);
}
void z_nrf_grtc_timer_compare_int_unlock(int32_t chan, bool key)
{
IS_CHANNEL_ALLOWED_ASSERT(chan);
compare_int_unlock(chan, key);
}
int z_nrf_grtc_timer_compare_read(int32_t chan, uint64_t *val)
{
IS_CHANNEL_ALLOWED_ASSERT(chan);
return get_comparator(chan, val);
}
static int compare_set_nolocks(int32_t chan, uint64_t target_time,
z_nrf_grtc_timer_compare_handler_t handler, void *user_data)
{
nrfx_err_t result;
__ASSERT_NO_MSG(target_time < COUNTER_SPAN);
nrfx_grtc_channel_t user_channel_data = {
.handler = handler,
.p_context = user_data,
.channel = chan,
};
result = nrfx_grtc_syscounter_cc_absolute_set(&user_channel_data, target_time, true);
if (result != NRFX_SUCCESS) {
return -EPERM;
}
return 0;
}
static int compare_set(int32_t chan, uint64_t target_time,
z_nrf_grtc_timer_compare_handler_t handler, void *user_data)
{
bool key = compare_int_lock(chan);
int ret = compare_set_nolocks(chan, target_time, handler, user_data);
compare_int_unlock(chan, key);
return ret;
}
int z_nrf_grtc_timer_set(int32_t chan, uint64_t target_time,
z_nrf_grtc_timer_compare_handler_t handler, void *user_data)
{
IS_CHANNEL_ALLOWED_ASSERT(chan);
return compare_set(chan, target_time, (nrfx_grtc_cc_handler_t)handler, user_data);
}
void z_nrf_grtc_timer_abort(int32_t chan)
{
IS_CHANNEL_ALLOWED_ASSERT(chan);
bool key = compare_int_lock(chan);
(void)nrfx_grtc_syscounter_cc_disable(chan);
compare_int_unlock(chan, key);
}
uint64_t z_nrf_grtc_timer_get_ticks(k_timeout_t t)
{
uint64_t curr_time;
int64_t curr_tick;
int64_t result;
int64_t abs_ticks;
int64_t grtc_ticks;
curr_time = counter();
curr_tick = sys_clock_tick_get();
grtc_ticks = t.ticks * CYC_PER_TICK;
abs_ticks = Z_TICK_ABS(t.ticks);
if (abs_ticks < 0) {
/* relative timeout */
return (grtc_ticks > (int64_t)COUNTER_SPAN) ?
-EINVAL : (curr_time + grtc_ticks);
}
/* absolute timeout */
result = (abs_ticks - curr_tick) * CYC_PER_TICK;
if (result > (int64_t)COUNTER_SPAN) {
return -EINVAL;
}
return curr_time + result;
}
int z_nrf_grtc_timer_capture_prepare(int32_t chan)
{
nrfx_grtc_channel_t user_channel_data = {
.handler = NULL,
.p_context = NULL,
.channel = chan,
};
nrfx_err_t result;
IS_CHANNEL_ALLOWED_ASSERT(chan);
/* Set the CC value to mark channel as not triggered and also to enable it
* (makes CCEN=1). COUNTER_SPAN is used so as not to fire an event unnecessarily
* - it can be assumed that such a large value will never be reached.
*/
result = nrfx_grtc_syscounter_cc_absolute_set(&user_channel_data, COUNTER_SPAN, false);
if (result != NRFX_SUCCESS) {
return -EPERM;
}
return 0;
}
int z_nrf_grtc_timer_capture_read(int32_t chan, uint64_t *captured_time)
{
/* TODO: The implementation should probably go to nrfx_grtc and this
* should be just a wrapper for some nrfx_grtc_syscounter_capture_read.
*/
uint64_t capt_time;
nrfx_err_t result;
IS_CHANNEL_ALLOWED_ASSERT(chan);
/* TODO: Use `nrfy_grtc_sys_counter_enable_check` when available (NRFX-2480) */
if (NRF_GRTC->CC[chan].CCEN == GRTC_CC_CCEN_ACTIVE_Enable) {
/* If the channel is enabled (.CCEN), it means that there was no capture
* triggering event.
*/
return -EBUSY;
}
result = nrfx_grtc_syscounter_cc_value_read(chan, &capt_time);
if (result != NRFX_SUCCESS) {
return -EPERM;
}
__ASSERT_NO_MSG(capt_time < COUNTER_SPAN);
*captured_time = capt_time;
return 0;
}
#if defined(CONFIG_POWEROFF) && defined(CONFIG_NRF_GRTC_START_SYSCOUNTER)
int z_nrf_grtc_wakeup_prepare(uint64_t wake_time_us)
{
nrfx_err_t err_code;
static uint8_t systemoff_channel;
uint64_t now = counter();
nrfx_grtc_sleep_config_t sleep_cfg;
/* Minimum time that ensures valid execution of system-off procedure. */
uint32_t minimum_latency_us;
uint32_t chan;
int ret;
nrfx_grtc_sleep_configuration_get(&sleep_cfg);
minimum_latency_us = (sleep_cfg.waketime + sleep_cfg.timeout) *
USEC_PER_SEC / LFCLK_FREQUENCY_HZ +
CONFIG_NRF_GRTC_SYSCOUNTER_SLEEP_MINIMUM_LATENCY;
sleep_cfg.auto_mode = false;
nrfx_grtc_sleep_configure(&sleep_cfg);
if (minimum_latency_us > wake_time_us) {
return -EINVAL;
}
k_spinlock_key_t key = k_spin_lock(&lock);
err_code = nrfx_grtc_channel_alloc(&systemoff_channel);
if (err_code != NRFX_SUCCESS) {
k_spin_unlock(&lock, key);
return -ENOMEM;
}
(void)nrfx_grtc_syscounter_cc_int_disable(systemoff_channel);
ret = compare_set(systemoff_channel,
now + wake_time_us * sys_clock_hw_cycles_per_sec() / USEC_PER_SEC, NULL,
NULL);
if (ret < 0) {
k_spin_unlock(&lock, key);
return ret;
}
for (uint32_t grtc_chan_mask = NRFX_GRTC_CONFIG_ALLOWED_CC_CHANNELS_MASK;
grtc_chan_mask > 0; grtc_chan_mask &= ~BIT(chan)) {
/* Clear all GRTC channels except the systemoff_channel. */
chan = u32_count_trailing_zeros(grtc_chan_mask);
if (chan != systemoff_channel) {
nrfx_grtc_syscounter_cc_disable(chan);
}
}
/* Make sure that wake_time_us was not triggered yet. */
if (nrfx_grtc_syscounter_compare_event_check(systemoff_channel)) {
k_spin_unlock(&lock, key);
return -EINVAL;
}
/* This mechanism ensures that stored CC value is latched. */
uint32_t wait_time =
nrfy_grtc_timeout_get(NRF_GRTC) * CONFIG_SYS_CLOCK_HW_CYCLES_PER_SEC / 32768 +
MAX_CC_LATCH_WAIT_TIME_US;
k_busy_wait(wait_time);
#if DT_NODE_HAS_STATUS_OKAY(DT_NODELABEL(lfxo)) && NRF_GRTC_HAS_CLKSEL
nrfx_grtc_clock_source_set(NRF_GRTC_CLKSEL_LFXO);
#endif
k_spin_unlock(&lock, key);
return 0;
}
#endif /* CONFIG_POWEROFF */
uint32_t sys_clock_cycle_get_32(void)
{
k_spinlock_key_t key = k_spin_lock(&lock);
uint32_t ret = (uint32_t)counter();
k_spin_unlock(&lock, key);
return ret;
}
uint64_t sys_clock_cycle_get_64(void)
{
k_spinlock_key_t key = k_spin_lock(&lock);
uint64_t ret = counter();
k_spin_unlock(&lock, key);
return ret;
}
uint32_t sys_clock_elapsed(void)
{
if (!IS_ENABLED(CONFIG_TICKLESS_KERNEL)) {
return 0;
}
return (uint32_t)(counter_sub(counter(), last_count) / CYC_PER_TICK);
}
static int sys_clock_driver_init(void)
{
nrfx_err_t err_code;
IRQ_CONNECT(DT_IRQN(GRTC_NODE), DT_IRQ(GRTC_NODE, priority), nrfx_isr,
nrfx_grtc_irq_handler, 0);
err_code = nrfx_grtc_init(0);
if (err_code != NRFX_SUCCESS) {
return -EPERM;
}
#if defined(CONFIG_NRF_GRTC_START_SYSCOUNTER)
err_code = nrfx_grtc_syscounter_start(true, &system_clock_channel_data.channel);
if (err_code != NRFX_SUCCESS) {
return err_code == NRFX_ERROR_NO_MEM ? -ENOMEM : -EPERM;
}
#else
err_code = nrfx_grtc_channel_alloc(&system_clock_channel_data.channel);
if (err_code != NRFX_SUCCESS) {
return -ENOMEM;
}
#endif /* CONFIG_NRF_GRTC_START_SYSCOUNTER */
int_mask = NRFX_GRTC_CONFIG_ALLOWED_CC_CHANNELS_MASK;
if (!IS_ENABLED(CONFIG_TICKLESS_KERNEL)) {
system_timeout_set_relative(CYC_PER_TICK);
}
#if defined(CONFIG_CLOCK_CONTROL_NRF)
static const enum nrf_lfclk_start_mode mode =
IS_ENABLED(CONFIG_SYSTEM_CLOCK_NO_WAIT)
? CLOCK_CONTROL_NRF_LF_START_NOWAIT
: (IS_ENABLED(CONFIG_SYSTEM_CLOCK_WAIT_FOR_AVAILABILITY)
? CLOCK_CONTROL_NRF_LF_START_AVAILABLE
: CLOCK_CONTROL_NRF_LF_START_STABLE);
z_nrf_clock_control_lf_on(mode);
#endif
#if defined(CONFIG_NRF_GRTC_TIMER_CLOCK_MANAGEMENT) && \
DT_NODE_HAS_STATUS_OKAY(DT_NODELABEL(lfxo)) && NRF_GRTC_HAS_CLKSEL
/* Switch to LFXO as the low-frequency clock source. */
nrfx_grtc_clock_source_set(NRF_GRTC_CLKSEL_LFXO);
#endif
#if defined(CONFIG_NRF_GRTC_ALWAYS_ON)
nrfx_grtc_active_request_set(true);
#endif
return 0;
}
void sys_clock_set_timeout(int32_t ticks, bool idle)
{
ARG_UNUSED(idle);
if (!IS_ENABLED(CONFIG_TICKLESS_KERNEL)) {
return;
}
ticks = (ticks == K_TICKS_FOREVER) ? MAX_TICKS : MIN(MAX_TICKS, MAX(ticks, 0));
uint64_t delta_time = ticks * CYC_PER_TICK;
uint64_t target_time = counter() + delta_time;
/* Rounded down target_time to the tick boundary
* (but not less than one tick after the last)
*/
target_time = MAX((target_time - last_count)/CYC_PER_TICK, 1)*CYC_PER_TICK + last_count;
system_timeout_set_abs(target_time);
}
#if defined(CONFIG_NRF_GRTC_TIMER_APP_DEFINED_INIT)
int nrf_grtc_timer_clock_driver_init(void)
{
return sys_clock_driver_init();
}
#else
SYS_INIT(sys_clock_driver_init, PRE_KERNEL_2, CONFIG_SYSTEM_CLOCK_INIT_PRIORITY);
#endif