drivers: mcux_gpt_timer: Added GPT timer for HW clock
Added a driver to enable the GPT timer on RT1xxx parts to be used instead of systick as a clock source. The timer is set to run in reset mode, and uses the low frequency 32kHz oscillator for power savings Signed-off-by: Daniel DeGrasse <daniel.degrasse@nxp.com>
This commit is contained in:
parent
0dfc2ea103
commit
fa56e9ee2b
10 changed files with 400 additions and 21 deletions
|
@ -18,6 +18,7 @@ zephyr_library_sources_ifdef(CONFIG_LITEX_TIMER litex_timer.c)
|
|||
zephyr_library_sources_ifdef(CONFIG_MCHP_XEC_RTOS_TIMER mchp_xec_rtos_timer.c)
|
||||
zephyr_library_sources_ifdef(CONFIG_MCUX_LPTMR_TIMER mcux_lptmr_timer.c)
|
||||
zephyr_library_sources_ifdef(CONFIG_MCUX_OS_TIMER mcux_os_timer.c)
|
||||
zephyr_library_sources_ifdef(CONFIG_MCUX_GPT_TIMER mcux_gpt_timer.c)
|
||||
zephyr_library_sources_ifdef(CONFIG_NATIVE_POSIX_TIMER native_posix_timer.c)
|
||||
zephyr_library_sources_ifdef(CONFIG_NPCX_ITIM_TIMER npcx_itim_timer.c)
|
||||
zephyr_library_sources_ifdef(CONFIG_NRF_RTC_TIMER nrf_rtc_timer.c)
|
||||
|
|
|
@ -67,6 +67,7 @@ source "drivers/timer/Kconfig.ite_it8xxx2"
|
|||
source "drivers/timer/Kconfig.leon_gptimer"
|
||||
source "drivers/timer/Kconfig.litex"
|
||||
source "drivers/timer/Kconfig.mchp_xec_rtos"
|
||||
source "drivers/timer/Kconfig.mcux_gpt"
|
||||
source "drivers/timer/Kconfig.mcux_lptmr"
|
||||
source "drivers/timer/Kconfig.mcux_os"
|
||||
source "drivers/timer/Kconfig.native_posix"
|
||||
|
|
15
drivers/timer/Kconfig.mcux_gpt
Normal file
15
drivers/timer/Kconfig.mcux_gpt
Normal file
|
@ -0,0 +1,15 @@
|
|||
# Copyright (c) 2021 NXP
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
DT_COMPAT_NXP_GPT_TIMER := nxp,gpt-hw-timer
|
||||
|
||||
config MCUX_GPT_TIMER
|
||||
bool "MCUX GPT Event timer"
|
||||
depends on HAS_MCUX_GPT
|
||||
default $(dt_compat_enabled,$(DT_COMPAT_NXP_GPT_TIMER))
|
||||
select TICKLESS_CAPABLE
|
||||
help
|
||||
This module implements a kernel device driver for the NXP GPT timer,
|
||||
and provides the standard "system clock driver" interfaces. It uses the
|
||||
first GPT peripheral defined in the system, which can no longer be used
|
||||
for the GPT counter driver.
|
348
drivers/timer/mcux_gpt_timer.c
Normal file
348
drivers/timer/mcux_gpt_timer.c
Normal file
|
@ -0,0 +1,348 @@
|
|||
/*
|
||||
* 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);
|
||||
/* 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);
|
|
@ -100,13 +100,13 @@
|
|||
#size-cells = <1>;
|
||||
};
|
||||
|
||||
gpt1: gpt@401ec000 {
|
||||
compatible = "nxp,imx-gpt";
|
||||
/* GPT1 is used for the hardware timer, not as a standard counter */
|
||||
gpt_hw_timer: gpt@401ec000 {
|
||||
compatible = "nxp,gpt-hw-timer";
|
||||
reg = <0x401ec000 0x4000>;
|
||||
interrupts = <100 0>;
|
||||
gptfreq = <25000000>;
|
||||
clocks = <&ccm IMX_CCM_GPT_CLK 0x6C 20>;
|
||||
label = "GPT1";
|
||||
label = "GPT_HW_TIMER";
|
||||
status = "disabled";
|
||||
};
|
||||
|
||||
gpt2: gpt@401f0000 {
|
||||
|
@ -823,3 +823,11 @@
|
|||
&nvic {
|
||||
arm,num-irq-priority-bits = <4>;
|
||||
};
|
||||
|
||||
&systick {
|
||||
/*
|
||||
* RT10xx relies by default on the GPT Timer for system clock
|
||||
* implementation, so the SysTick node should not be enabled.
|
||||
*/
|
||||
status = "disabled";
|
||||
};
|
||||
|
|
|
@ -30,9 +30,8 @@
|
|||
interrupts = <73 0>;
|
||||
};
|
||||
|
||||
&gpt1 {
|
||||
&gpt_hw_timer {
|
||||
interrupts = <30 0>;
|
||||
gptfreq = <12500000>;
|
||||
};
|
||||
|
||||
&gpt2 {
|
||||
|
|
|
@ -22,10 +22,6 @@
|
|||
reg = <0x20200000 DT_SIZE_K(64)>;
|
||||
};
|
||||
|
||||
&gpt1 {
|
||||
gptfreq = <12500000>;
|
||||
};
|
||||
|
||||
&gpt2 {
|
||||
gptfreq = <12500000>;
|
||||
};
|
||||
|
|
|
@ -22,10 +22,6 @@
|
|||
reg = <0x20200000 DT_SIZE_K(128)>;
|
||||
};
|
||||
|
||||
&gpt1 {
|
||||
gptfreq = <12500000>;
|
||||
};
|
||||
|
||||
&gpt2 {
|
||||
gptfreq = <12500000>;
|
||||
};
|
||||
|
|
|
@ -75,13 +75,13 @@
|
|||
#size-cells = <1>;
|
||||
};
|
||||
|
||||
gpt1: gpt@400ec000 {
|
||||
compatible = "nxp,imx-gpt";
|
||||
reg = <0x400ec000 0x4000>;
|
||||
/* GPT1 is used for the hardware timer, not as a standard counter */
|
||||
gpt_hw_timer: gpt@401ec000 {
|
||||
compatible = "nxp,gpt-hw-timer";
|
||||
reg = <0x401ec000 0x4000>;
|
||||
interrupts = <119 0>;
|
||||
gptfreq = <24000000>;
|
||||
clocks = <&ccm IMX_CCM_GPT_CLK 0x40 0>;
|
||||
label = "GPT1";
|
||||
label = "GPT_HW_TIMER";
|
||||
status = "disabled";
|
||||
};
|
||||
|
||||
gpt2: gpt@400f0000 {
|
||||
|
@ -823,3 +823,11 @@
|
|||
&nvic {
|
||||
arm,num-irq-priority-bits = <4>;
|
||||
};
|
||||
|
||||
&systick {
|
||||
/*
|
||||
* RT11xx relies by default on the GPT Timer for system clock
|
||||
* implementation, so the SysTick node should not be enabled.
|
||||
*/
|
||||
status = "disabled";
|
||||
};
|
||||
|
|
|
@ -69,6 +69,13 @@ endchoice
|
|||
|
||||
endif
|
||||
|
||||
# set the tick per sec as a divider of the GPT clock source
|
||||
config SYS_CLOCK_TICKS_PER_SEC
|
||||
default 4096 if MCUX_GPT_TIMER
|
||||
|
||||
config SYS_CLOCK_HW_CYCLES_PER_SEC
|
||||
default 32768 if MCUX_GPT_TIMER
|
||||
|
||||
if COUNTER
|
||||
|
||||
config COUNTER_MCUX_GPT
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue