| /* |
| * Copyright (c) 2023 Antmicro <www.antmicro.com> |
| * |
| * Based on: |
| * sam0_rtc_timer.c Copyright (c) 2018 omSquare s.r.o. |
| * intel_adsp_timer.c Copyright (c) 2020 Intel Corporation |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #define DT_DRV_COMPAT silabs_gecko_burtc |
| |
| /** |
| * @file |
| * @brief SiLabs Gecko BURTC-based sys_clock driver |
| * |
| */ |
| |
| #include <zephyr/init.h> |
| #include <soc.h> |
| #include <zephyr/drivers/clock_control.h> |
| #include <zephyr/drivers/timer/system_timer.h> |
| #include <zephyr/drivers/pinctrl.h> |
| #include <zephyr/sys_clock.h> |
| #include <zephyr/irq.h> |
| #include <zephyr/spinlock.h> |
| #include <zephyr/logging/log.h> |
| |
| #include "em_device.h" |
| #include "em_cmu.h" |
| #include "em_burtc.h" |
| |
| |
| LOG_MODULE_REGISTER(gecko_burtc_timer); |
| |
| |
| /* Maximum time interval between timer interrupts (in hw_cycles) */ |
| #define MAX_TIMEOUT_CYC (UINT32_MAX >> 1) |
| |
| /* |
| * Mininum time interval between now and IRQ firing that can be scheduled. |
| * The main cause for this is LFSYNC register update, which requires several |
| * LF clk cycles for synchronization. |
| * Seee e.g. "4.2.4.4.4 LFSYNC Registers" in "EFR32xG22 Reference Manual" |
| */ |
| #define MIN_DELAY_CYC (6u) |
| |
| #define TIMER_IRQ (DT_INST_IRQN(0)) |
| |
| #if defined(CONFIG_TEST) |
| /* See tests/kernel/context */ |
| const int32_t z_sys_timer_irq_for_test = TIMER_IRQ; |
| #endif |
| |
| /* With CONFIG_TIMER_READS_ITS_FREQUENCY_AT_RUNTIME, that's where we |
| * should write hw_cycles timer clock frequency upon init |
| */ |
| extern int z_clock_hw_cycles_per_sec; |
| |
| /* Number of hw_cycles clocks per 1 kernel tick */ |
| static uint32_t g_cyc_per_tick; |
| |
| /* MAX_TIMEOUT_CYC expressed as ticks */ |
| static uint32_t g_max_timeout_ticks; |
| |
| /* Value of BURTC counter when the previous kernel tick was announced */ |
| static atomic_t g_last_count; |
| |
| /* Spinlock to sync between Compare ISR and update of Compare register */ |
| static struct k_spinlock g_lock; |
| |
| /* Set to true when timer is initialized */ |
| static bool g_init; |
| |
| static void burtc_isr(const void *arg) |
| { |
| ARG_UNUSED(arg); |
| |
| /* Clear the interrupt */ |
| BURTC_IntClear(BURTC_IF_COMP); |
| |
| uint32_t curr = BURTC_CounterGet(); |
| |
| /* NOTE: this is the only place where g_last_count is modified, |
| * so we don't need to do make the whole read-and-modify atomic, just |
| * writing it behind the memory barrier is enough |
| */ |
| uint32_t prev = atomic_get(&g_last_count); |
| |
| /* How many ticks have we not announced since the last announcement */ |
| uint32_t unannounced = (curr - prev) / g_cyc_per_tick; |
| |
| atomic_set(&g_last_count, prev + unannounced * g_cyc_per_tick); |
| |
| if (!IS_ENABLED(CONFIG_TICKLESS_KERNEL)) { |
| /* Counter value on which announcement should be made */ |
| uint32_t next = prev + g_cyc_per_tick; |
| |
| /* `next` can be too close in the future since we're trying to |
| * announce the very next tick - in that case we skip one and |
| * announce the one after it instead |
| */ |
| if ((next - curr) < MIN_DELAY_CYC) { |
| next += g_cyc_per_tick; |
| } |
| |
| BURTC_CompareSet(0, next); |
| } |
| |
| sys_clock_announce(unannounced); |
| } |
| |
| void sys_clock_set_timeout(int32_t ticks, bool idle) |
| { |
| ARG_UNUSED(idle); |
| |
| if (!IS_ENABLED(CONFIG_TICKLESS_KERNEL)) { |
| return; |
| } |
| |
| /* |
| * calculate 'ticks' value that specifies which tick to announce, |
| * beginning from the closest upcoming one: |
| * 0 - announce upcoming tick itself |
| * 1 - skip upcoming one, but announce the one after it, etc. |
| */ |
| ticks = (ticks == K_TICKS_FOREVER) ? g_max_timeout_ticks : ticks; |
| ticks = CLAMP(ticks - 1, 0, g_max_timeout_ticks); |
| |
| k_spinlock_key_t key = k_spin_lock(&g_lock); |
| |
| uint32_t curr = BURTC_CounterGet(); |
| uint32_t prev = atomic_get(&g_last_count); |
| |
| /* How many ticks have we not announced since the last announcement */ |
| uint32_t unannounced = (curr - prev) / g_cyc_per_tick; |
| |
| /* Which tick to announce (counting from the last announced one) */ |
| uint32_t to_announce = unannounced + ticks + 1; |
| |
| /* Force maximum interval between announcements. If we sit without |
| * announcements for too long, counter will roll over and we'll lose |
| * track of unannounced ticks. |
| */ |
| to_announce = MIN(to_announce, g_max_timeout_ticks); |
| |
| /* Counter value on which announcement should be made */ |
| uint32_t next = prev + to_announce * g_cyc_per_tick; |
| |
| /* `next` can be too close in the future if we're trying to announce |
| * the very next tick - in that case we skip one and announce the one |
| * after it instead |
| */ |
| if ((next - curr) < MIN_DELAY_CYC) { |
| next += g_cyc_per_tick; |
| } |
| |
| BURTC_CompareSet(0, next); |
| k_spin_unlock(&g_lock, key); |
| } |
| |
| uint32_t sys_clock_elapsed(void) |
| { |
| if (!IS_ENABLED(CONFIG_TICKLESS_KERNEL) || !g_init) { |
| return 0; |
| } else { |
| return (BURTC_CounterGet() - g_last_count) / g_cyc_per_tick; |
| } |
| } |
| |
| uint32_t sys_clock_cycle_get_32(void) |
| { |
| /* API note: this function is unrelated to kernel ticks, it returns |
| * a value of some 32-bit hw_cycles counter which counts with |
| * z_clock_hw_cycles_per_sec frequency |
| */ |
| if (!g_init) { |
| return 0; |
| } else { |
| return BURTC_CounterGet(); |
| } |
| } |
| |
| static int burtc_init(void) |
| { |
| uint32_t hw_clock_freq; |
| BURTC_Init_TypeDef init = BURTC_INIT_DEFAULT; |
| |
| /* Enable clock for BURTC CSRs on APB */ |
| CMU_ClockEnable(cmuClock_BURTC, true); |
| |
| /* Calculate timing constants and init BURTC */ |
| hw_clock_freq = CMU_ClockFreqGet(cmuClock_BURTC); |
| z_clock_hw_cycles_per_sec = hw_clock_freq; |
| |
| BUILD_ASSERT(CONFIG_SYS_CLOCK_TICKS_PER_SEC > 0, |
| "Invalid CONFIG_SYS_CLOCK_TICKS_PER_SEC value"); |
| g_cyc_per_tick = hw_clock_freq / CONFIG_SYS_CLOCK_TICKS_PER_SEC; |
| |
| __ASSERT(g_cyc_per_tick >= MIN_DELAY_CYC, |
| "%u cycle-long tick is too short to be scheduled " |
| "(min is %u). Config: SYS_CLOCK_TICKS_PER_SEC is " |
| "%d and timer frequency is %u", |
| g_cyc_per_tick, MIN_DELAY_CYC, CONFIG_SYS_CLOCK_TICKS_PER_SEC, |
| hw_clock_freq); |
| |
| g_max_timeout_ticks = MAX_TIMEOUT_CYC / g_cyc_per_tick; |
| |
| init.clkDiv = 1; |
| init.start = false; |
| BURTC_Init(&init); |
| g_init = true; |
| |
| /* Enable compare match interrupt */ |
| BURTC_IntClear(BURTC_IF_COMP); |
| BURTC_IntEnable(BURTC_IF_COMP); |
| NVIC_ClearPendingIRQ(TIMER_IRQ); |
| IRQ_CONNECT(TIMER_IRQ, DT_INST_IRQ(0, priority), burtc_isr, 0, 0); |
| irq_enable(TIMER_IRQ); |
| |
| /* Start the timer and announce 1 kernel tick */ |
| atomic_set(&g_last_count, 0); |
| BURTC_CompareSet(0, g_cyc_per_tick); |
| |
| BURTC_SyncWait(); |
| BURTC->CNT = 0; |
| BURTC_Start(); |
| |
| return 0; |
| } |
| |
| SYS_INIT(burtc_init, PRE_KERNEL_2, |
| CONFIG_SYSTEM_CLOCK_INIT_PRIORITY); |