/* * Copyright (c) 2023 Antmicro * * 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 #include #include #include #include #include #include #include #include #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); /* Configure BURTC LF clocksource according to Kconfig */ #if defined(CONFIG_CMU_BURTCCLK_LFXO) CMU_ClockSelectSet(cmuClock_BURTC, cmuSelect_LFXO); #elif defined(CONFIG_CMU_BURTCCLK_LFRCO) CMU_ClockSelectSet(cmuClock_BURTC, cmuSelect_LFRCO); #elif defined(CONFIG_CMU_BURTCCLK_ULFRCO) CMU_ClockSelectSet(cmuClock_BURTC, cmuSelect_ULFRCO); #else #error "Unsupported BURTC clock specified" #endif /* 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);