59f9dfae58
GPT timer must continue running in low power modes, as it is the system wakeup source. Set configuration to ensure peripheral will not stop running in low power modes. Signed-off-by: Daniel DeGrasse <daniel.degrasse@nxp.com>
353 lines
11 KiB
C
353 lines
11 KiB
C
/*
|
|
* Copyright (c) 2021, NXP
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#define DT_DRV_COMPAT nxp_gpt_hw_timer
|
|
|
|
#include <device.h>
|
|
#include <drivers/timer/system_timer.h>
|
|
#include <fsl_gpt.h>
|
|
#include <sys_clock.h>
|
|
#include <spinlock.h>
|
|
#include <sys/time_units.h>
|
|
|
|
|
|
|
|
/* GPT is a 32 bit counter, but we use a lower value to avoid integer overflow */
|
|
#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 GPT counter flag 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 4 cyc (keep in mind the
|
|
* counter freq is only 32k).
|
|
*/
|
|
#define MIN_DELAY MAX(4, (CYC_PER_TICK/16))
|
|
|
|
/* Use the first device defined with GPT HW timer compatible string */
|
|
#define GPT_INST DT_INST(0, DT_DRV_COMPAT)
|
|
|
|
static GPT_Type *base; /* GPT timer base address */
|
|
/*
|
|
* Stores the current number of cycles the system has had announced to it.
|
|
* must be a multiple of CYC_PER_TICK.
|
|
*/
|
|
static volatile uint32_t announced_cycles;
|
|
|
|
/*
|
|
* Stores the amount of elapsed cycles. Updated in mcux_gpt_isr(), and
|
|
* sys_clock_set_timeout(). At an arbitrary point in time, the current number of
|
|
* elapsed HW cycles is calculated as cycle_count + elapsed()
|
|
*/
|
|
static volatile uint32_t cycle_count;
|
|
|
|
/*
|
|
* Stores the elapsed hardware cycles due to the GPT wrapping. The GPT wrap
|
|
* will trigger an interrupt, but if the timer wraps while interrupts are
|
|
* disabled this variable will record the overflow value.
|
|
*
|
|
* Each time cycle_count is updated with this value, overflow cycles should be
|
|
* reset to 0.
|
|
*/
|
|
static volatile uint32_t wrapped_cycles;
|
|
|
|
/*
|
|
* Stores the last value loaded to the GPT. This can also be queried from the
|
|
* hardware, but storing it locally lets the compiler attempt to optimize access.
|
|
*/
|
|
static uint32_t last_load;
|
|
|
|
/*
|
|
* Used by sys_clock_set_timeout to determine if it was called from an ISR.
|
|
*/
|
|
static volatile bool gpt_isr_active;
|
|
|
|
/* GPT timer base address */
|
|
static GPT_Type *base;
|
|
|
|
/* Lock on shared variables */
|
|
static struct k_spinlock lock;
|
|
|
|
/**
|
|
* This function calculates the amount of hardware cycles that have elapsed
|
|
* since the last time the absolute hardware cycles counter was updated.
|
|
* 'cycle_count' will be updated in the ISR, or if the counter capture value is
|
|
* changed in sys_clock_set_timeout().
|
|
*
|
|
* The primary purpose of this function is to aid in catching the edge case
|
|
* where the timer wraps around while ISRs are disabled, and ensure the calling
|
|
* function still correctly reports the timer's state.
|
|
*
|
|
* In order to store state if a wrap occurs, the function will update the
|
|
* 'wrapped_cycles' variable so that the GPT ISR can use it.
|
|
*
|
|
* Prerequisites:
|
|
* - When the GPT capture value is programmed, 'wrapped_cycles' must be zeroed
|
|
* - ISR must clear 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 not able to be interrupted by the GPT ISR
|
|
*/
|
|
static uint32_t elapsed(void)
|
|
{
|
|
/* Read the GPT twice, and read the GPT status flags */
|
|
uint32_t read1 = GPT_GetCurrentTimerCount(base);
|
|
uint32_t status_flags = GPT_GetStatusFlags(base, kGPT_OutputCompare1Flag);
|
|
/* Clear status flag */
|
|
GPT_ClearStatusFlags(base, kGPT_OutputCompare1Flag);
|
|
uint32_t read2 = GPT_GetCurrentTimerCount(base);
|
|
|
|
/*
|
|
* The counter wraps to zero at the output compare value ('last load').
|
|
* Therefore, if read1 > read2, the counter wrapped. If the status flag
|
|
* is set the counter also will have wrapped.
|
|
*
|
|
* Otherwise, the counter has not wrapped.
|
|
*/
|
|
if (status_flags || (read1 > read2)) {
|
|
/* A wrap occurred. We need to update 'wrapped_cycles' */
|
|
wrapped_cycles += last_load;
|
|
/* We know there was a wrap, but it may not have been cleared. */
|
|
GPT_ClearStatusFlags(base, kGPT_OutputCompare1Flag);
|
|
}
|
|
|
|
/* Calculate the cycles since the ISR last fired (the ISR updates 'cycle_count') */
|
|
return read2 + wrapped_cycles;
|
|
}
|
|
|
|
|
|
/* Interrupt fires every time GPT timer reaches set value.
|
|
* GPT timer will reset to 0x0.
|
|
*
|
|
* Note: we use a direct ISR for latency concerns.
|
|
*/
|
|
ISR_DIRECT_DECLARE(mcux_imx_gpt_isr)
|
|
{
|
|
/* Note: we do not call PM hooks in this function,
|
|
* GPT timer does not need PM
|
|
*/
|
|
ISR_DIRECT_HEADER();
|
|
/* Update the value of 'wrapped_cycles' */
|
|
elapsed();
|
|
|
|
/* Update the total number of cycles elapsed */
|
|
cycle_count += wrapped_cycles;
|
|
wrapped_cycles = 0;
|
|
|
|
#if defined(CONFIG_TICKLESS_KERNEL)
|
|
uint32_t tick_delta;
|
|
|
|
tick_delta = (cycle_count - announced_cycles) / CYC_PER_TICK;
|
|
announced_cycles += tick_delta * CYC_PER_TICK;
|
|
/* Announce the number of elapsed ticks.
|
|
*
|
|
* Note that by the definition of the way that the kernel uses
|
|
* sys_clock_set_timeout, we should change the GPT counter value here to
|
|
* occur on a tick boundary. However, the kernel will call
|
|
* sys_clock_set_timeout within the call to sys_clock_announce, so we
|
|
* don't have to worry about that.
|
|
*/
|
|
gpt_isr_active = true;
|
|
sys_clock_announce(tick_delta);
|
|
#else
|
|
/* If system is tickful, interrupt will fire again at next tick */
|
|
sys_clock_announce(1);
|
|
#endif
|
|
ISR_DIRECT_FOOTER(1);
|
|
return 1;
|
|
}
|
|
|
|
/* Next needed call to sys_clock_announce will not be until the specified number
|
|
* of ticks from the current time have elapsed. Note that this timeout value is
|
|
* persistent, ie if the kernel sets the timeout to 2 ticks this driver must
|
|
* announce ticks to the kernel every 2 ticks until told otherwise
|
|
*/
|
|
void sys_clock_set_timeout(int32_t ticks, bool idle)
|
|
{
|
|
#if defined(CONFIG_TICKLESS_KERNEL)
|
|
k_spinlock_key_t key;
|
|
uint32_t reload_value, pending_cycles, unannounced_cycles, read1, read2;
|
|
/* Save prior load value (to check for wrap at end of function) */
|
|
uint32_t old_load = last_load;
|
|
|
|
if ((ticks == K_TICKS_FOREVER) && idle) {
|
|
/* GPT timer no longer needed. Stop it. */
|
|
GPT_StopTimer(base);
|
|
last_load = TIMER_STOPPED;
|
|
return;
|
|
}
|
|
ticks = (ticks == K_TICKS_FOREVER) ? MAX_TICKS : ticks;
|
|
/* Clamp ticks */
|
|
ticks = CLAMP(ticks - 1, 0, (int32_t)MAX_TICKS);
|
|
|
|
key = k_spin_lock(&lock);
|
|
|
|
/* Update the wrapped cycles value if required */
|
|
pending_cycles = elapsed();
|
|
/* Get first read as soon as possible */
|
|
read1 = GPT_GetCurrentTimerCount(base);
|
|
|
|
/* Update cycle count and reset wrapped cycles */
|
|
cycle_count += pending_cycles;
|
|
wrapped_cycles = 0U;
|
|
|
|
|
|
unannounced_cycles = cycle_count - announced_cycles;
|
|
|
|
if ((int32_t)unannounced_cycles < 0) {
|
|
/* Announcement has not occurred for more than half the 32 bit counter
|
|
* value, since new timeouts keep being set. Force an announcement
|
|
*/
|
|
reload_value = MIN_DELAY;
|
|
} else {
|
|
reload_value = ticks * CYC_PER_TICK;
|
|
/* Round reload value up to a tick boundary */
|
|
reload_value += unannounced_cycles;
|
|
reload_value =
|
|
((reload_value + CYC_PER_TICK - 1) / CYC_PER_TICK) * CYC_PER_TICK;
|
|
reload_value -= unannounced_cycles;
|
|
/* Clamp reload value */
|
|
reload_value = CLAMP(reload_value, MIN_DELAY, MAX_CYCLES);
|
|
}
|
|
/* Set reload value (will also reset GPT timer) */
|
|
read2 = GPT_GetCurrentTimerCount(base);
|
|
/* The below checks correspond to the following:
|
|
* GPT timer is at zero ticks
|
|
* No requirement to force an announcement to the kernel
|
|
* called from GPT ISR (pending cycles might be zero in this case)
|
|
* kernel wants an announcement sooner than we currently will announce
|
|
*/
|
|
if ((pending_cycles != 0) ||
|
|
((int32_t)unannounced_cycles < 0) ||
|
|
gpt_isr_active ||
|
|
(reload_value < last_load)) {
|
|
/*
|
|
* In cases where sys_clock_set_timeout is repeatedly called by the
|
|
* kernel outside of the context of sys_clock_annouce, the GPT timer
|
|
* may be reset before it can "tick" upwards. This prevents progress
|
|
* from occurring in the kernel. These checks ensure that the GPT timer
|
|
* gets a chance to tick before being reset.
|
|
*/
|
|
last_load = reload_value;
|
|
GPT_SetOutputCompareValue(base, kGPT_OutputCompare_Channel1, last_load - 1);
|
|
while (GPT_GetCurrentTimerCount(base) != 0) {
|
|
/* Since GPT timer frequency is much slower than system clock, we must
|
|
* wait for GPT timer to reset here.
|
|
*
|
|
* If the GPT timer is switched to a faster clock, this block must
|
|
* be removed, as the timer will count past zero before we can read it.
|
|
*/
|
|
}
|
|
}
|
|
/* Reset ISR flag */
|
|
gpt_isr_active = false;
|
|
|
|
/* read1 and read2 are used to 'time' this function, so we can keep
|
|
* the cycle count accurate.
|
|
|
|
* Strictly speaking, we should check the counter interrupt flag here fo
|
|
* wraparound, but if the GPT output compare value we just set has wrapped,
|
|
* we would erroneously catch that wrap here.
|
|
*/
|
|
if (read1 > read2) {
|
|
/* Timer wrapped while in this function. Update cycle count */
|
|
cycle_count += ((old_load - read1) + read2);
|
|
} else {
|
|
cycle_count += (read2 - read1);
|
|
}
|
|
k_spin_unlock(&lock, key);
|
|
#endif
|
|
}
|
|
|
|
/* Get the number of ticks since the last call to sys_clock_announce() */
|
|
uint32_t sys_clock_elapsed(void)
|
|
{
|
|
#if defined(CONFIG_TICKLESS_KERNEL)
|
|
uint32_t cyc;
|
|
k_spinlock_key_t key = k_spin_lock(&lock);
|
|
|
|
cyc = elapsed() + cycle_count - announced_cycles;
|
|
k_spin_unlock(&lock, key);
|
|
return cyc / CYC_PER_TICK;
|
|
#else
|
|
/* 0 ticks will always have elapsed */
|
|
return 0;
|
|
#endif
|
|
}
|
|
|
|
/* Get the number of elapsed hardware cycles of the clock */
|
|
uint32_t sys_clock_cycle_get_32(void)
|
|
{
|
|
uint32_t ret;
|
|
k_spinlock_key_t key = k_spin_lock(&lock);
|
|
|
|
ret = elapsed() + cycle_count;
|
|
k_spin_unlock(&lock, key);
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* @brief Initialize system timer driver
|
|
*
|
|
* Enable the hw timer, setting its tick period, and setup its interrupt
|
|
*/
|
|
int sys_clock_driver_init(const struct device *dev)
|
|
{
|
|
gpt_config_t gpt_config;
|
|
|
|
ARG_UNUSED(dev);
|
|
/* Configure ISR. Use instance 0 of the GPT timer */
|
|
IRQ_DIRECT_CONNECT(DT_IRQN(GPT_INST), DT_IRQ(GPT_INST, priority),
|
|
mcux_imx_gpt_isr, 0);
|
|
|
|
base = (GPT_Type *)DT_REG_ADDR(GPT_INST);
|
|
|
|
GPT_GetDefaultConfig(&gpt_config);
|
|
/* Enable GPT timer to run in SOC low power states */
|
|
gpt_config.enableRunInStop = true;
|
|
gpt_config.enableRunInWait = true;
|
|
gpt_config.enableRunInDoze = true;
|
|
/* Use 32KHz clock frequency */
|
|
gpt_config.clockSource = kGPT_ClockSource_LowFreq;
|
|
gpt_config.enableFreeRun = false; /* Set GPT to reset mode */
|
|
|
|
/* Initialize the GPT timer in reset mode, and enable the relevant interrupts */
|
|
GPT_Init(base, &gpt_config);
|
|
|
|
last_load = CYC_PER_TICK;
|
|
wrapped_cycles = 0U;
|
|
/* Set initial trigger value to one tick worth of cycles */
|
|
GPT_SetOutputCompareValue(base, kGPT_OutputCompare_Channel1, last_load - 1);
|
|
while (GPT_GetCurrentTimerCount(base)) {
|
|
/* Wait for timer count to clear.
|
|
* Writes to the GPT output compare register occur after 1 cycle of
|
|
* wait state
|
|
*/
|
|
}
|
|
/* Enable GPT interrupt */
|
|
GPT_EnableInterrupts(base, kGPT_OutputCompare1InterruptEnable);
|
|
/* Enable IRQ */
|
|
irq_enable(DT_IRQN(GPT_INST));
|
|
/* Start timer */
|
|
GPT_StartTimer(base);
|
|
|
|
return 0;
|
|
}
|
|
|
|
SYS_INIT(sys_clock_driver_init, PRE_KERNEL_2,
|
|
CONFIG_SYSTEM_CLOCK_INIT_PRIORITY);
|