/* * Copyright (c) 2018 Intel Corporation * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include #include #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)) static struct k_spinlock lock; static uint32_t last_load; /* * This local variable holds the amount of SysTick HW cycles elapsed * and it is updated in z_clock_isr() and z_clock_set_timeout(). * * Note: * At an arbitrary point in time the "current" value of the SysTick * HW timer is calculated as: * * t = cycle_counter + elapsed(); */ static uint32_t cycle_count; /* * This local variable holds the amount of elapsed SysTick HW cycles * that have been announced to the kernel. */ static uint32_t announced_cycles; /* * This local variable holds the amount of elapsed HW cycles due to * SysTick timer wraps ('overflows') and is used in the calculation * in elapsed() function, as well as in the updates to cycle_count. * * Note: * Each time cycle_count is updated with the value from overflow_cyc, * the overflow_cyc must be reset to zero. */ static volatile uint32_t overflow_cyc; /* This internal function calculates the amount of HW cycles that have * elapsed since the last time the absolute HW cycles counter has been * updated. 'cycle_count' may be updated either by the ISR, or when we * re-program the SysTick.LOAD register, in z_clock_set_timeout(). * * Additionally, the function updates the 'overflow_cyc' counter, that * holds the amount of elapsed HW cycles due to (possibly) multiple * timer wraps (overflows). * * Prerequisites: * - reprogramming of SysTick.LOAD must be clearing the SysTick.COUNTER * register and the 'overflow_cyc' counter. * - ISR must be clearing the 'overflow_cyc' counter. * - no more than one counter-wrap has occurred between * - the timer reset or the last time the function was called * - and until the current call of the function is completed. * - the function is invoked with interrupts disabled. */ static uint32_t elapsed(void) { uint32_t val1 = SysTick->VAL; /* A */ uint32_t ctrl = SysTick->CTRL; /* B */ uint32_t val2 = SysTick->VAL; /* C */ /* SysTick behavior: The counter wraps at zero automatically, * setting the COUNTFLAG field of the CTRL register when it * does. Reading the control register automatically clears * that field. * * If the count wrapped... * 1) Before A then COUNTFLAG will be set and val1 >= val2 * 2) Between A and B then COUNTFLAG will be set and val1 < val2 * 3) Between B and C then COUNTFLAG will be clear and val1 < val2 * 4) After C we'll see it next time * * So the count in val2 is post-wrap and last_load needs to be * added if and only if COUNTFLAG is set or val1 < val2. */ if ((ctrl & SysTick_CTRL_COUNTFLAG_Msk) || (val1 < val2)) { overflow_cyc += last_load; /* We know there was a wrap, but we might not have * seen it in CTRL, so clear it. */ (void)SysTick->CTRL; } return (last_load - val2) + overflow_cyc; } /* Callout out of platform assembly, not hooked via IRQ_CONNECT... */ void z_clock_isr(void *arg) { ARG_UNUSED(arg); uint32_t dticks; /* Update overflow_cyc and clear COUNTFLAG by invoking elapsed() */ elapsed(); /* Increment the amount of HW cycles elapsed (complete counter * cycles) and announce the progress to the kernel. */ cycle_count += overflow_cyc; overflow_cyc = 0; if (TICKLESS) { /* In TICKLESS mode, the SysTick.LOAD is re-programmed * in z_clock_set_timeout(), followed by resetting of * the counter (VAL = 0). * * If a timer wrap occurs right when we re-program LOAD, * the ISR is triggered immediately after z_clock_set_timeout() * returns; in that case we shall not increment the cycle_count * because the value has been updated before LOAD re-program. * * We can assess if this is the case by inspecting COUNTFLAG. */ dticks = (cycle_count - announced_cycles) / CYC_PER_TICK; announced_cycles += dticks * CYC_PER_TICK; z_clock_announce(dticks); } else { z_clock_announce(1); } z_arm_int_exit(); } 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(int32_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_TICKS_FOREVER) { SysTick->CTRL &= ~SysTick_CTRL_ENABLE_Msk; last_load = TIMER_STOPPED; return; } #if defined(CONFIG_TICKLESS_KERNEL) uint32_t delay; ticks = (ticks == K_TICKS_FOREVER) ? MAX_TICKS : ticks; ticks = MAX(MIN(ticks - 1, (int32_t)MAX_TICKS), 0); k_spinlock_key_t key = k_spin_lock(&lock); uint32_t pending = elapsed(); cycle_count += pending; overflow_cyc = 0U; uint32_t unannounced = cycle_count - announced_cycles; if ((int32_t)unannounced < 0) { /* We haven't announced for more than half the 32-bit * wrap duration, because new timeouts keep being set * before the existing one fires. Force an announce * to avoid loss of a wrap event, making sure the * delay is at least the minimum delay possible. */ last_load = MIN_DELAY; } else { /* Desired delay in the future */ delay = ticks * CYC_PER_TICK; /* Round delay up to next tick boundary */ delay += unannounced; delay = ((delay + CYC_PER_TICK - 1) / CYC_PER_TICK) * CYC_PER_TICK; delay -= unannounced; delay = MAX(delay, MIN_DELAY); if (delay > MAX_CYCLES) { last_load = MAX_CYCLES; } else { last_load = delay; } } SysTick->LOAD = last_load - 1; SysTick->VAL = 0; /* resets timer to last_load */ k_spin_unlock(&lock, key); #endif } uint32_t z_clock_elapsed(void) { if (!TICKLESS) { return 0; } k_spinlock_key_t key = k_spin_lock(&lock); uint32_t cyc = elapsed() + cycle_count - announced_cycles; k_spin_unlock(&lock, key); return cyc / CYC_PER_TICK; } uint32_t z_timer_cycle_get_32(void) { k_spinlock_key_t key = k_spin_lock(&lock); uint32_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; }