blob: b6aca76eff624149892cb9bd1fa01db35a26b883 [file] [log] [blame]
/*
* Copyright (c) 2018 Intel Corporation
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <drivers/system_timer.h>
#include <sys_clock.h>
#include <spinlock.h>
#include <arch/arm/cortex_m/cmsis.h>
void z_ExcExit(void);
/* Minimum cycles in the future to try to program. */
#define MIN_DELAY 512
#define COUNTER_MAX 0x00ffffff
#define TIMER_STOPPED 0xff000000
#define CYC_PER_TICK (CONFIG_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)
#define TICKLESS (IS_ENABLED(CONFIG_TICKLESS_KERNEL) && \
!IS_ENABLED(CONFIG_QEMU_TICKLESS_WORKAROUND))
static struct k_spinlock lock;
static u32_t last_load;
static u32_t cycle_count;
static u32_t announced_cycles;
static volatile u32_t ctrl_cache; /* overflow bit clears on read! */
static u32_t elapsed(void)
{
u32_t val, ov;
do {
val = SysTick->VAL & COUNTER_MAX;
ctrl_cache |= SysTick->CTRL;
} while (SysTick->VAL > val);
ov = (ctrl_cache & SysTick_CTRL_COUNTFLAG_Msk) ? last_load : 0;
return (last_load - val) + ov;
}
/* 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;
ctrl_cache = SysTick->CTRL; /* Reset overflow flag */
ctrl_cache = 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;
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);
SysTick->LOAD = last_load;
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;
}