You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
166 lines
5.0 KiB
166 lines
5.0 KiB
/* |
|
* 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);
|
|
|