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.
232 lines
6.1 KiB
232 lines
6.1 KiB
/* |
|
* 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/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 unsigned 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);
|
|
|