| /* |
| * Copyright (c) 2021, NXP |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #define DT_DRV_COMPAT nxp_gpt_hw_timer |
| |
| #include <zephyr/init.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> |
| |
| |
| /* |
| * By limiting counter to 30 bits, we ensure that |
| * timeout calculations will never overflow in sys_clock_set_timeout |
| */ |
| #define COUNTER_MAX 0x3fffffff |
| |
| #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) |
| |
| /* Use the first device defined with GPT HW timer compatible string */ |
| #define GPT_INST DT_INST(0, DT_DRV_COMPAT) |
| |
| /* |
| * Stores the current number of cycles the system has had announced to it, |
| * since the last rollover of the free running counter. |
| */ |
| static uint32_t announced_cycles; |
| |
| /* |
| * Stores the number of cycles that have elapsed due to counter rollover. |
| * this value is updated in the GPT isr, and is used to keep the value |
| * returned by sys_clock_cycle_get_32 accurate after a timer rollover. |
| */ |
| static uint32_t rollover_cycles; |
| |
| /* GPT timer base address */ |
| static GPT_Type *base; |
| |
| /* Lock on shared variables */ |
| static struct k_spinlock lock; |
| |
| /* Helper function to set GPT compare value, so we don't set a compare point in past */ |
| static void gpt_set_safe(uint32_t next) |
| { |
| |
| uint32_t now; |
| |
| next = MIN(MAX_CYCLES, next); |
| GPT_SetOutputCompareValue(base, kGPT_OutputCompare_Channel2, next - 1); |
| now = GPT_GetCurrentTimerCount(base); |
| |
| /* GPT fires interrupt at next counter cycle after a compare point is |
| * hit, so we should bump the compare point if 1 cycle or less exists |
| * between now and compare point. |
| * |
| * We will exit this loop if next==MAX_CYCLES, as we already |
| * have a rollover interrupt set up for that point, so we |
| * no longer need to keep bumping the compare point. |
| */ |
| if (unlikely(((int32_t)(next - now)) <= 1)) { |
| uint32_t bump = 1; |
| |
| do { |
| next = now + bump; |
| bump *= 2; |
| next = MIN(MAX_CYCLES, next); |
| GPT_SetOutputCompareValue(base, |
| kGPT_OutputCompare_Channel2, next - 1); |
| now = GPT_GetCurrentTimerCount(base); |
| } while ((((int32_t)(next - now)) <= 1) && (next < MAX_CYCLES)); |
| } |
| } |
| |
| /* Interrupt fires every time GPT reaches the current capture value */ |
| void mcux_imx_gpt_isr(const void *arg) |
| { |
| ARG_UNUSED(arg); |
| k_spinlock_key_t key; |
| uint32_t tick_delta = 0, now, status; |
| |
| key = k_spin_lock(&lock); |
| if (IS_ENABLED(CONFIG_TICKLESS_KERNEL)) { |
| /* Get current timer count */ |
| now = GPT_GetCurrentTimerCount(base); |
| status = GPT_GetStatusFlags(base, |
| kGPT_OutputCompare2Flag | kGPT_OutputCompare1Flag); |
| /* Clear GPT capture interrupts */ |
| GPT_ClearStatusFlags(base, status); |
| if (status & kGPT_OutputCompare1Flag) { |
| /* Counter has just rolled over. We should |
| * reset the announced cycles counter, and record the |
| * cycles that remained before rollover. |
| * |
| * Since rollover occurs on a tick boundary, we don't |
| * need to worry about losing time here due to rounding. |
| */ |
| tick_delta += (MAX_CYCLES - announced_cycles) / CYC_PER_TICK; |
| announced_cycles = 0U; |
| /* Update count of rolled over cycles */ |
| rollover_cycles += MAX_CYCLES; |
| } |
| if (status & kGPT_OutputCompare2Flag) { |
| /* Normal counter interrupt. Get delta since last announcement */ |
| tick_delta += (now - announced_cycles) / CYC_PER_TICK; |
| announced_cycles += (((now - announced_cycles) / CYC_PER_TICK) * |
| CYC_PER_TICK); |
| } |
| } else { |
| GPT_ClearStatusFlags(base, kGPT_OutputCompare1Flag); |
| /* Update count of rolled over cycles */ |
| rollover_cycles += CYC_PER_TICK; |
| } |
| |
| /* Announce progress to the kernel */ |
| k_spin_unlock(&lock, key); |
| sys_clock_announce(IS_ENABLED(CONFIG_TICKLESS_KERNEL) ? tick_delta : 1); |
| } |
| |
| /* |
| * Next needed call to sys_clock_announce will not be until the specified number |
| * of ticks from the current time have elapsed. |
| */ |
| void sys_clock_set_timeout(int32_t ticks, bool idle) |
| { |
| if (!IS_ENABLED(CONFIG_TICKLESS_KERNEL)) { |
| /* Not supported on tickful kernels */ |
| return; |
| } |
| k_spinlock_key_t key; |
| uint32_t next, adj, now; |
| |
| ticks = (ticks == K_TICKS_FOREVER) ? MAX_TICKS : ticks; |
| /* Clamp ticks. We subtract one since we round up to next tick */ |
| ticks = CLAMP((ticks - 1), 0, (int32_t)MAX_TICKS); |
| |
| key = k_spin_lock(&lock); |
| |
| /* Read current timer value */ |
| now = GPT_GetCurrentTimerCount(base); |
| |
| /* Adjustment value, used to ensure next capture is on tick boundary */ |
| adj = (now - announced_cycles) + (CYC_PER_TICK - 1); |
| |
| next = ticks * CYC_PER_TICK; |
| /* |
| * The following section rounds the capture value up to the next tick |
| * boundary |
| */ |
| next += adj; |
| next = (next / CYC_PER_TICK) * CYC_PER_TICK; |
| next += announced_cycles; |
| /* Set GPT output value */ |
| gpt_set_safe(next); |
| k_spin_unlock(&lock, key); |
| } |
| |
| /* Get the number of ticks since the last call to sys_clock_announce() */ |
| uint32_t sys_clock_elapsed(void) |
| { |
| if (!IS_ENABLED(CONFIG_TICKLESS_KERNEL)) { |
| /* Always return 0 for tickful kernel system */ |
| return 0; |
| } |
| |
| k_spinlock_key_t key = k_spin_lock(&lock); |
| uint32_t cyc = GPT_GetCurrentTimerCount(base); |
| |
| cyc -= announced_cycles; |
| k_spin_unlock(&lock, key); |
| return cyc / CYC_PER_TICK; |
| } |
| |
| /* Get the number of elapsed hardware cycles of the clock */ |
| uint32_t sys_clock_cycle_get_32(void) |
| { |
| return rollover_cycles + GPT_GetCurrentTimerCount(base); |
| } |
| |
| /* |
| * @brief Initialize system timer driver |
| * |
| * Enable the hw timer, setting its tick period, and setup its interrupt |
| */ |
| int sys_clock_driver_init(void) |
| { |
| gpt_config_t gpt_config; |
| |
| /* 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; |
| /* We use reset mode, but reset at MAX ticks- see comment below */ |
| gpt_config.enableFreeRun = false; |
| |
| /* Initialize the GPT timer, and enable the relevant interrupts */ |
| GPT_Init(base, &gpt_config); |
| announced_cycles = 0U; |
| rollover_cycles = 0U; |
| |
| if (IS_ENABLED(CONFIG_TICKLESS_KERNEL)) { |
| /* |
| * Set GPT capture value 1 to MAX_CYCLES, and use GPT capture |
| * value 2 as the source for GPT interrupts. This way, we can |
| * use the counter as a free running timer, but it will roll |
| * over on a tick boundary. |
| */ |
| GPT_SetOutputCompareValue(base, kGPT_OutputCompare_Channel1, |
| MAX_CYCLES - 1); |
| |
| /* Set initial trigger value to one tick worth of cycles */ |
| GPT_SetOutputCompareValue(base, kGPT_OutputCompare_Channel2, |
| CYC_PER_TICK - 1); |
| /* Enable GPT interrupts for timer match, and reset at capture value 1 */ |
| GPT_EnableInterrupts(base, kGPT_OutputCompare1InterruptEnable | |
| kGPT_OutputCompare2InterruptEnable); |
| } else { |
| /* For a tickful kernel, just roll the counter over every tick */ |
| GPT_SetOutputCompareValue(base, kGPT_OutputCompare_Channel1, |
| CYC_PER_TICK - 1); |
| 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); |