From fa56e9ee2bfed13da236045fe59c856594318577 Mon Sep 17 00:00:00 2001 From: Daniel DeGrasse Date: Wed, 13 Oct 2021 17:28:05 -0500 Subject: [PATCH] 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 --- drivers/timer/CMakeLists.txt | 1 + drivers/timer/Kconfig | 1 + drivers/timer/Kconfig.mcux_gpt | 15 + drivers/timer/mcux_gpt_timer.c | 348 ++++++++++++++++++++ dts/arm/nxp/nxp_rt.dtsi | 18 +- dts/arm/nxp/nxp_rt1010.dtsi | 3 +- dts/arm/nxp/nxp_rt1015.dtsi | 4 - dts/arm/nxp/nxp_rt1020.dtsi | 4 - dts/arm/nxp/nxp_rt11xx.dtsi | 20 +- soc/arm/nxp_imx/rt/Kconfig.defconfig.series | 7 + 10 files changed, 400 insertions(+), 21 deletions(-) create mode 100644 drivers/timer/Kconfig.mcux_gpt create mode 100644 drivers/timer/mcux_gpt_timer.c diff --git a/drivers/timer/CMakeLists.txt b/drivers/timer/CMakeLists.txt index 1fc4d5d32c7..2707390b25e 100644 --- a/drivers/timer/CMakeLists.txt +++ b/drivers/timer/CMakeLists.txt @@ -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) diff --git a/drivers/timer/Kconfig b/drivers/timer/Kconfig index eb183511626..184a5c79b95 100644 --- a/drivers/timer/Kconfig +++ b/drivers/timer/Kconfig @@ -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" diff --git a/drivers/timer/Kconfig.mcux_gpt b/drivers/timer/Kconfig.mcux_gpt new file mode 100644 index 00000000000..1e9aadd2e67 --- /dev/null +++ b/drivers/timer/Kconfig.mcux_gpt @@ -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. diff --git a/drivers/timer/mcux_gpt_timer.c b/drivers/timer/mcux_gpt_timer.c new file mode 100644 index 00000000000..050d33769fb --- /dev/null +++ b/drivers/timer/mcux_gpt_timer.c @@ -0,0 +1,348 @@ +/* + * Copyright (c) 2021, NXP + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#define DT_DRV_COMPAT nxp_gpt_hw_timer + +#include +#include +#include +#include +#include +#include + + + +/* 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); diff --git a/dts/arm/nxp/nxp_rt.dtsi b/dts/arm/nxp/nxp_rt.dtsi index 905938c6491..a0c6dc3c3d7 100644 --- a/dts/arm/nxp/nxp_rt.dtsi +++ b/dts/arm/nxp/nxp_rt.dtsi @@ -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"; +}; diff --git a/dts/arm/nxp/nxp_rt1010.dtsi b/dts/arm/nxp/nxp_rt1010.dtsi index 15453c71ce0..27cbf5f3047 100644 --- a/dts/arm/nxp/nxp_rt1010.dtsi +++ b/dts/arm/nxp/nxp_rt1010.dtsi @@ -30,9 +30,8 @@ interrupts = <73 0>; }; -&gpt1 { +&gpt_hw_timer { interrupts = <30 0>; - gptfreq = <12500000>; }; &gpt2 { diff --git a/dts/arm/nxp/nxp_rt1015.dtsi b/dts/arm/nxp/nxp_rt1015.dtsi index 827fd965ad5..e266addcb36 100644 --- a/dts/arm/nxp/nxp_rt1015.dtsi +++ b/dts/arm/nxp/nxp_rt1015.dtsi @@ -22,10 +22,6 @@ reg = <0x20200000 DT_SIZE_K(64)>; }; -&gpt1 { - gptfreq = <12500000>; -}; - &gpt2 { gptfreq = <12500000>; }; diff --git a/dts/arm/nxp/nxp_rt1020.dtsi b/dts/arm/nxp/nxp_rt1020.dtsi index a85537b4d0d..1d322e09e68 100644 --- a/dts/arm/nxp/nxp_rt1020.dtsi +++ b/dts/arm/nxp/nxp_rt1020.dtsi @@ -22,10 +22,6 @@ reg = <0x20200000 DT_SIZE_K(128)>; }; -&gpt1 { - gptfreq = <12500000>; -}; - &gpt2 { gptfreq = <12500000>; }; diff --git a/dts/arm/nxp/nxp_rt11xx.dtsi b/dts/arm/nxp/nxp_rt11xx.dtsi index a47628da27d..aac0acf2e95 100644 --- a/dts/arm/nxp/nxp_rt11xx.dtsi +++ b/dts/arm/nxp/nxp_rt11xx.dtsi @@ -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"; +}; diff --git a/soc/arm/nxp_imx/rt/Kconfig.defconfig.series b/soc/arm/nxp_imx/rt/Kconfig.defconfig.series index 3ce0c3be86b..b191de14756 100644 --- a/soc/arm/nxp_imx/rt/Kconfig.defconfig.series +++ b/soc/arm/nxp_imx/rt/Kconfig.defconfig.series @@ -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