zephyr/drivers/timer/silabs_sleeptimer_timer.c
Aksel Skauge Mellbye da6ddc92cd drivers: timer: silabs: Add sleeptimer timer driver
Add OS timer implementation making use of the Sleeptimer HAL.
Sleeptimer integrates tightly with the Silabs Power Manager HAL,
and must be used as the OS timer to achieve optimal power consumption
when using the radio.

Signed-off-by: Aksel Skauge Mellbye <aksel.mellbye@silabs.com>
2024-10-24 17:51:01 +02:00

167 lines
5.0 KiB
C

/*
* 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);