/* * Copyright (c) 2024 Silicon Laboratories Inc. * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include #include #include #include #include #include #include #include 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);