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.
354 lines
12 KiB
354 lines
12 KiB
/* |
|
* Copyright (c) 2021, NXP |
|
* |
|
* SPDX-License-Identifier: Apache-2.0 |
|
*/ |
|
|
|
#define DT_DRV_COMPAT nxp_gpt_hw_timer |
|
|
|
#include <zephyr/device.h> |
|
#include <zephyr/drivers/timer/system_timer.h> |
|
#include <fsl_gpt.h> |
|
#include <zephyr/sys_clock.h> |
|
#include <zephyr/spinlock.h> |
|
#include <zephyr/sys/time_units.h> |
|
#include <zephyr/irq.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. |
|
* |
|
*/ |
|
void mcux_imx_gpt_isr(const void *arg) |
|
{ |
|
ARG_UNUSED(arg); |
|
|
|
/* 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 |
|
} |
|
|
|
/* 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; |
|
if (reload_value == ticks * CYC_PER_TICK) { |
|
/* We are on a tick boundary. Since we subtracted from |
|
* 'ticks' earlier, we need to add one tick worth of |
|
* cycles to announce to the kernel at the right time. |
|
*/ |
|
reload_value += CYC_PER_TICK; |
|
} |
|
/* 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_CONNECT(DT_IRQN(GPT_INST), DT_IRQ(GPT_INST, priority), |
|
mcux_imx_gpt_isr, NULL, 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);
|
|
|