blob: 405fa1496a5a7ec6f08bfad6f5646e65545f387b [file] [log] [blame]
/*
* Copyright (c) 2024 Silicon Laboratories Inc.
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <errno.h>
#include <stdint.h>
#include <stdbool.h>
#include <zephyr/init.h>
#include <zephyr/device.h>
#include <zephyr/kernel.h>
#include <zephyr/sys_clock.h>
#include <zephyr/drivers/timer/system_timer.h>
#include <zephyr/logging/log.h>
#include <sl_sleeptimer.h>
LOG_MODULE_REGISTER(silabs_sleeptimer_timer);
/* Maximum time interval between timer interrupts (in hw_cycles) */
#define MAX_TIMEOUT_CYC (UINT32_MAX >> 1)
#define MIN_DELAY_CYC (4U)
#define DT_RTC DT_COMPAT_GET_ANY_STATUS_OKAY(silabs_gecko_stimer)
/* Ensure interrupt names don't expand to register interface struct pointers */
#undef RTCC
/* With CONFIG_TIMER_READS_ITS_FREQUENCY_AT_RUNTIME, this global variable holds the clock frequency,
* and must be written by the driver at init.
*/
extern int z_clock_hw_cycles_per_sec;
/* Global timer state */
struct sleeptimer_timer_data {
uint32_t cyc_per_tick; /* Number of hw_cycles per 1 kernel tick */
uint32_t max_timeout_ticks; /* MAX_TIMEOUT_CYC expressed as ticks */
atomic_t last_count; /* Value of counter when the previous tick was announced */
struct k_spinlock lock; /* Spinlock to sync between ISR and updating the timeout */
bool initialized; /* Set to true when timer is initialized */
sl_sleeptimer_timer_handle_t handle; /* Timer handle for system timer */
};
static struct sleeptimer_timer_data g_sleeptimer_timer_data = {0};
static void sleeptimer_cb(sl_sleeptimer_timer_handle_t *handle, void *data)
{
ARG_UNUSED(handle);
struct sleeptimer_timer_data *timer = data;
uint32_t curr = sl_sleeptimer_get_tick_count();
uint32_t prev = atomic_get(&timer->last_count);
uint32_t pending = curr - prev;
/* Number of unannounced ticks since the last announcement */
uint32_t unannounced = pending / timer->cyc_per_tick;
atomic_set(&timer->last_count, prev + unannounced * timer->cyc_per_tick);
sys_clock_announce(unannounced);
}
static void sleeptimer_clock_set_timeout(int32_t ticks, struct sleeptimer_timer_data *timer)
{
if (!IS_ENABLED(CONFIG_TICKLESS_KERNEL)) {
return;
}
ticks = (ticks == K_TICKS_FOREVER) ? timer->max_timeout_ticks : ticks;
ticks = CLAMP(ticks, 0, timer->max_timeout_ticks);
k_spinlock_key_t key = k_spin_lock(&timer->lock);
uint32_t curr = sl_sleeptimer_get_tick_count();
uint32_t prev = atomic_get(&timer->last_count);
uint32_t pending = curr - prev;
uint32_t next = ticks * timer->cyc_per_tick;
/* Next timeout is N ticks in the future, minus the current progress
* towards the timeout. If we are behind, set the timeout to the first
* possible upcoming tick.
*/
while (next < (pending + MIN_DELAY_CYC)) {
next += timer->cyc_per_tick;
}
next -= pending;
sl_sleeptimer_restart_timer(&timer->handle, next, sleeptimer_cb, timer, 0, 0);
k_spin_unlock(&timer->lock, key);
}
static uint32_t sleeptimer_clock_elapsed(struct sleeptimer_timer_data *timer)
{
if (!IS_ENABLED(CONFIG_TICKLESS_KERNEL) || !timer->initialized) {
/* No unannounced ticks can have elapsed if not in tickless mode */
return 0;
} else {
return (sl_sleeptimer_get_tick_count() - atomic_get(&timer->last_count)) /
timer->cyc_per_tick;
}
}
void sys_clock_set_timeout(int32_t ticks, bool idle)
{
ARG_UNUSED(idle);
sleeptimer_clock_set_timeout(ticks, &g_sleeptimer_timer_data);
}
uint32_t sys_clock_elapsed(void)
{
return sleeptimer_clock_elapsed(&g_sleeptimer_timer_data);
}
uint32_t sys_clock_cycle_get_32(void)
{
return g_sleeptimer_timer_data.initialized ? sl_sleeptimer_get_tick_count() : 0;
}
static int sleeptimer_init(void)
{
sl_status_t status = SL_STATUS_OK;
struct sleeptimer_timer_data *timer = &g_sleeptimer_timer_data;
IRQ_CONNECT(DT_IRQ(DT_RTC, irq), DT_IRQ(DT_RTC, priority),
CONCAT(DT_STRING_UPPER_TOKEN_BY_IDX(DT_RTC, interrupt_names, 0), _IRQHandler),
0, 0);
sl_sleeptimer_init();
z_clock_hw_cycles_per_sec = sl_sleeptimer_get_timer_frequency();
BUILD_ASSERT(CONFIG_SYS_CLOCK_TICKS_PER_SEC > 0,
"Invalid CONFIG_SYS_CLOCK_TICKS_PER_SEC value");
timer->cyc_per_tick = z_clock_hw_cycles_per_sec / CONFIG_SYS_CLOCK_TICKS_PER_SEC;
__ASSERT(timer->cyc_per_tick >= MIN_DELAY_CYC,
"A tick of %u cycles is too short to be scheduled "
"(min is %u). Config: SYS_CLOCK_TICKS_PER_SEC is "
"%d and timer frequency is %u",
timer->cyc_per_tick, MIN_DELAY_CYC, CONFIG_SYS_CLOCK_TICKS_PER_SEC,
z_clock_hw_cycles_per_sec);
timer->max_timeout_ticks = MAX_TIMEOUT_CYC / timer->cyc_per_tick;
timer->initialized = true;
atomic_set(&timer->last_count, sl_sleeptimer_get_tick_count());
/* Start the timer and announce 1 kernel tick */
if (IS_ENABLED(CONFIG_TICKLESS_KERNEL)) {
status = sl_sleeptimer_start_timer(&timer->handle, timer->cyc_per_tick,
sleeptimer_cb, timer, 0, 0);
} else {
status = sl_sleeptimer_start_periodic_timer(&timer->handle, timer->cyc_per_tick,
sleeptimer_cb, timer, 0, 0);
}
if (status != SL_STATUS_OK) {
return -ENODEV;
}
return 0;
}
SYS_INIT(sleeptimer_init, PRE_KERNEL_2, CONFIG_SYSTEM_CLOCK_INIT_PRIORITY);