mirror of
https://github.com/zephyrproject-rtos/zephyr
synced 2025-09-12 00:31:56 +00:00
On some SoCs the frequency of the system clock is obtained at run time as the exact configuration of the hardware is not known at compile time. On such platforms using CONFIG_SYS_CLOCK_HW_CYCLES_PER_SEC define directly introduces timing errors. This commit replaces CONFIG_SYS_CLOCK_HW_CYCLES_PER_SEC by the call to inline function sys_clock_hw_cycles_per_sec() which always returns correct frequency of the system clock. Signed-off-by: Piotr Zięcik <piotr.ziecik@nordicsemi.no>
187 lines
5.2 KiB
C
187 lines
5.2 KiB
C
/*
|
|
* Copyright (c) 2018 Intel Corporation
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
#include <drivers/timer/system_timer.h>
|
|
#include <sys_clock.h>
|
|
#include <spinlock.h>
|
|
#include <arch/arm/cortex_m/cmsis.h>
|
|
|
|
void z_ExcExit(void);
|
|
|
|
#define COUNTER_MAX 0x00ffffff
|
|
#define TIMER_STOPPED 0xff000000
|
|
|
|
#define CYC_PER_TICK (sys_clock_hw_cycles_per_sec() \
|
|
/ CONFIG_SYS_CLOCK_TICKS_PER_SEC)
|
|
#define MAX_TICKS ((COUNTER_MAX / CYC_PER_TICK) - 1)
|
|
#define MAX_CYCLES (MAX_TICKS * CYC_PER_TICK)
|
|
|
|
/* Minimum cycles in the future to try to program. Note that this is
|
|
* NOT simply "enough cycles to get the counter read and reprogrammed
|
|
* reliably" -- it becomes the minimum value of the LOAD register, and
|
|
* thus reflects how much time we can reliably see expire between
|
|
* calls to elapsed() to read the COUNTFLAG bit. So it needs to be
|
|
* set to be larger than the maximum time the interrupt might be
|
|
* masked. Choosing a fraction of a tick is probably a good enough
|
|
* default, with an absolute minimum of 1k cyc.
|
|
*/
|
|
#define MIN_DELAY MAX(1024, (CYC_PER_TICK/16))
|
|
|
|
#define TICKLESS (IS_ENABLED(CONFIG_TICKLESS_KERNEL) && \
|
|
!IS_ENABLED(CONFIG_QEMU_TICKLESS_WORKAROUND))
|
|
|
|
/* VAL value above which we assume that a subsequent COUNTFLAG
|
|
* overflow seen in CTRL is real and not an artifact of wraparound
|
|
* timing.
|
|
*/
|
|
#define VAL_ABOUT_TO_WRAP 8
|
|
|
|
static struct k_spinlock lock;
|
|
|
|
static u32_t last_load;
|
|
|
|
static u32_t cycle_count;
|
|
|
|
static u32_t announced_cycles;
|
|
|
|
static volatile u32_t overflow_cyc;
|
|
|
|
static u32_t elapsed(void)
|
|
{
|
|
u32_t val, ctrl1, ctrl2;
|
|
|
|
/* SysTick is infuriatingly racy. The counter wraps at zero
|
|
* automatically, setting a 1 in the COUNTFLAG bit of the CTRL
|
|
* register when it does. But reading the control register
|
|
* automatically resets that bit, so we need to save it for
|
|
* future calls. And ordering is critical and race-prone: if
|
|
* we read CTRL first, then it is possible for VAL to wrap
|
|
* after that read but before we read VAL and we'll miss the
|
|
* overflow. If we read VAL first, then it can wrap after we
|
|
* read it and we'll see an "extra" overflow in CTRL. And we
|
|
* want to handle multiple overflows, so we effectively must
|
|
* read CTRL first otherwise there will be no way to detect
|
|
* the double-overflow if called at the end of a cycle. There
|
|
* is no safe algorithm here, so we split the difference by
|
|
* reading CTRL twice, suppressing the second overflow bit if
|
|
* VAL was "about to overflow".
|
|
*/
|
|
ctrl1 = SysTick->CTRL;
|
|
val = SysTick->VAL & COUNTER_MAX;
|
|
ctrl2 = SysTick->CTRL;
|
|
|
|
overflow_cyc += (ctrl1 & SysTick_CTRL_COUNTFLAG_Msk) ? last_load : 0;
|
|
if (val > VAL_ABOUT_TO_WRAP) {
|
|
int wrap = ctrl2 & SysTick_CTRL_COUNTFLAG_Msk;
|
|
|
|
overflow_cyc += (wrap != 0) ? last_load : 0;
|
|
}
|
|
|
|
return (last_load - val) + overflow_cyc;
|
|
}
|
|
|
|
/* Callout out of platform assembly, not hooked via IRQ_CONNECT... */
|
|
void z_clock_isr(void *arg)
|
|
{
|
|
ARG_UNUSED(arg);
|
|
u32_t dticks;
|
|
|
|
cycle_count += last_load;
|
|
dticks = (cycle_count - announced_cycles) / CYC_PER_TICK;
|
|
announced_cycles += dticks * CYC_PER_TICK;
|
|
|
|
overflow_cyc = SysTick->CTRL; /* Reset overflow flag */
|
|
overflow_cyc = 0U;
|
|
|
|
z_clock_announce(TICKLESS ? dticks : 1);
|
|
z_ExcExit();
|
|
}
|
|
|
|
int z_clock_driver_init(struct device *device)
|
|
{
|
|
NVIC_SetPriority(SysTick_IRQn, _IRQ_PRIO_OFFSET);
|
|
last_load = CYC_PER_TICK - 1;
|
|
overflow_cyc = 0U;
|
|
SysTick->LOAD = last_load;
|
|
SysTick->VAL = 0; /* resets timer to last_load */
|
|
SysTick->CTRL |= (SysTick_CTRL_ENABLE_Msk |
|
|
SysTick_CTRL_TICKINT_Msk |
|
|
SysTick_CTRL_CLKSOURCE_Msk);
|
|
return 0;
|
|
}
|
|
|
|
void z_clock_set_timeout(s32_t ticks, bool idle)
|
|
{
|
|
/* Fast CPUs and a 24 bit counter mean that even idle systems
|
|
* need to wake up multiple times per second. If the kernel
|
|
* allows us to miss tick announcements in idle, then shut off
|
|
* the counter. (Note: we can assume if idle==true that
|
|
* interrupts are already disabled)
|
|
*/
|
|
if (IS_ENABLED(CONFIG_TICKLESS_IDLE) && idle && ticks == K_FOREVER) {
|
|
SysTick->CTRL &= ~SysTick_CTRL_ENABLE_Msk;
|
|
last_load = TIMER_STOPPED;
|
|
return;
|
|
}
|
|
|
|
#if defined(CONFIG_TICKLESS_KERNEL) && !defined(CONFIG_QEMU_TICKLESS_WORKAROUND)
|
|
u32_t delay;
|
|
|
|
ticks = MIN(MAX_TICKS, MAX(ticks - 1, 0));
|
|
|
|
/* Desired delay in the future */
|
|
delay = (ticks == 0) ? MIN_DELAY : ticks * CYC_PER_TICK;
|
|
|
|
k_spinlock_key_t key = k_spin_lock(&lock);
|
|
|
|
cycle_count += elapsed();
|
|
|
|
/* Round delay up to next tick boundary */
|
|
delay = delay + (cycle_count - announced_cycles);
|
|
delay = ((delay + CYC_PER_TICK - 1) / CYC_PER_TICK) * CYC_PER_TICK;
|
|
last_load = delay - (cycle_count - announced_cycles);
|
|
|
|
overflow_cyc = 0U;
|
|
SysTick->LOAD = last_load - 1;
|
|
SysTick->VAL = 0; /* resets timer to last_load */
|
|
|
|
k_spin_unlock(&lock, key);
|
|
#endif
|
|
}
|
|
|
|
u32_t z_clock_elapsed(void)
|
|
{
|
|
if (!TICKLESS) {
|
|
return 0;
|
|
}
|
|
|
|
k_spinlock_key_t key = k_spin_lock(&lock);
|
|
u32_t cyc = elapsed() + cycle_count - announced_cycles;
|
|
|
|
k_spin_unlock(&lock, key);
|
|
return cyc / CYC_PER_TICK;
|
|
}
|
|
|
|
u32_t z_timer_cycle_get_32(void)
|
|
{
|
|
k_spinlock_key_t key = k_spin_lock(&lock);
|
|
u32_t ret = elapsed() + cycle_count;
|
|
|
|
k_spin_unlock(&lock, key);
|
|
return ret;
|
|
}
|
|
|
|
void z_clock_idle_exit(void)
|
|
{
|
|
if (last_load == TIMER_STOPPED) {
|
|
SysTick->CTRL |= SysTick_CTRL_ENABLE_Msk;
|
|
}
|
|
}
|
|
|
|
void sys_clock_disable(void)
|
|
{
|
|
SysTick->CTRL &= ~SysTick_CTRL_ENABLE_Msk;
|
|
}
|