1 /*
2  * Copyright (c) 2021, NXP
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 #define DT_DRV_COMPAT nxp_gpt_hw_timer
8 
9 #include <zephyr/init.h>
10 #include <zephyr/drivers/timer/system_timer.h>
11 #include <fsl_gpt.h>
12 #include <zephyr/sys_clock.h>
13 #include <zephyr/spinlock.h>
14 #include <zephyr/sys/time_units.h>
15 #include <zephyr/irq.h>
16 
17 
18 /*
19  * By limiting counter to 30 bits, we ensure that
20  * timeout calculations will never overflow in sys_clock_set_timeout
21  */
22 #define COUNTER_MAX 0x3fffffff
23 
24 #define CYC_PER_TICK (sys_clock_hw_cycles_per_sec() \
25 				  / CONFIG_SYS_CLOCK_TICKS_PER_SEC)
26 
27 #define MAX_TICKS ((COUNTER_MAX / CYC_PER_TICK) - 1)
28 #define MAX_CYCLES (MAX_TICKS * CYC_PER_TICK)
29 
30 /* Use the first device defined with GPT HW timer compatible string */
31 #define GPT_INST DT_INST(0, DT_DRV_COMPAT)
32 
33 /*
34  * Stores the current number of cycles the system has had announced to it,
35  * since the last rollover of the free running counter.
36  */
37 static uint32_t announced_cycles;
38 
39 /*
40  * Stores the number of cycles that have elapsed due to counter rollover.
41  * this value is updated in the GPT isr, and is used to keep the value
42  * returned by sys_clock_cycle_get_32 accurate after a timer rollover.
43  */
44 static uint32_t rollover_cycles;
45 
46 /* GPT timer base address */
47 static GPT_Type *base;
48 
49 /* Lock on shared variables */
50 static struct k_spinlock lock;
51 
52 /* Helper function to set GPT compare value, so we don't set a compare point in past */
gpt_set_safe(uint32_t next)53 static void gpt_set_safe(uint32_t next)
54 {
55 
56 	uint32_t now;
57 
58 	next = MIN(MAX_CYCLES, next);
59 	GPT_SetOutputCompareValue(base, kGPT_OutputCompare_Channel2, next - 1);
60 	now = GPT_GetCurrentTimerCount(base);
61 
62 	/* GPT fires interrupt at next counter cycle after a compare point is
63 	 * hit, so we should bump the compare point if 1 cycle or less exists
64 	 * between now and compare point.
65 	 *
66 	 * We will exit this loop if next==MAX_CYCLES, as we already
67 	 * have a rollover interrupt set up for that point, so we
68 	 * no longer need to keep bumping the compare point.
69 	 */
70 	if (unlikely(((int32_t)(next - now)) <= 1)) {
71 		uint32_t bump = 1;
72 
73 		do {
74 			next = now + bump;
75 			bump *= 2;
76 			next = MIN(MAX_CYCLES, next);
77 			GPT_SetOutputCompareValue(base,
78 					kGPT_OutputCompare_Channel2, next - 1);
79 			now = GPT_GetCurrentTimerCount(base);
80 		} while ((((int32_t)(next - now)) <= 1) && (next < MAX_CYCLES));
81 	}
82 }
83 
84 /* Interrupt fires every time GPT reaches the current capture value */
mcux_imx_gpt_isr(const void * arg)85 void mcux_imx_gpt_isr(const void *arg)
86 {
87 	ARG_UNUSED(arg);
88 	k_spinlock_key_t key;
89 	uint32_t tick_delta = 0, now, status;
90 
91 	key = k_spin_lock(&lock);
92 	if (IS_ENABLED(CONFIG_TICKLESS_KERNEL)) {
93 		/* Get current timer count */
94 		now = GPT_GetCurrentTimerCount(base);
95 		status = GPT_GetStatusFlags(base,
96 			kGPT_OutputCompare2Flag | kGPT_OutputCompare1Flag);
97 		/* Clear GPT capture interrupts */
98 		GPT_ClearStatusFlags(base, status);
99 		if (status & kGPT_OutputCompare1Flag) {
100 			/* Counter has just rolled over. We should
101 			 * reset the announced cycles counter, and record the
102 			 * cycles that remained before rollover.
103 			 *
104 			 * Since rollover occurs on a tick boundary, we don't
105 			 * need to worry about losing time here due to rounding.
106 			 */
107 			tick_delta += (MAX_CYCLES - announced_cycles) / CYC_PER_TICK;
108 			announced_cycles = 0U;
109 			/* Update count of rolled over cycles */
110 			rollover_cycles += MAX_CYCLES;
111 		}
112 		if (status & kGPT_OutputCompare2Flag) {
113 			/* Normal counter interrupt. Get delta since last announcement */
114 			tick_delta += (now - announced_cycles) / CYC_PER_TICK;
115 			announced_cycles += (((now - announced_cycles) / CYC_PER_TICK) *
116 					CYC_PER_TICK);
117 		}
118 	} else {
119 		GPT_ClearStatusFlags(base, kGPT_OutputCompare1Flag);
120 		/* Update count of rolled over cycles */
121 		rollover_cycles += CYC_PER_TICK;
122 	}
123 
124 	/* Announce progress to the kernel */
125 	k_spin_unlock(&lock, key);
126 	sys_clock_announce(IS_ENABLED(CONFIG_TICKLESS_KERNEL) ? tick_delta : 1);
127 }
128 
129 /*
130  * Next needed call to sys_clock_announce will not be until the specified number
131  * of ticks from the current time have elapsed.
132  */
sys_clock_set_timeout(int32_t ticks,bool idle)133 void sys_clock_set_timeout(int32_t ticks, bool idle)
134 {
135 	if (!IS_ENABLED(CONFIG_TICKLESS_KERNEL)) {
136 		/* Not supported on tickful kernels */
137 		return;
138 	}
139 	k_spinlock_key_t key;
140 	uint32_t next, adj, now;
141 
142 	ticks = (ticks == K_TICKS_FOREVER) ? MAX_TICKS : ticks;
143 	/* Clamp ticks. We subtract one since we round up to next tick */
144 	ticks = CLAMP((ticks - 1), 0, (int32_t)MAX_TICKS);
145 
146 	key = k_spin_lock(&lock);
147 
148 	/* Read current timer value */
149 	now = GPT_GetCurrentTimerCount(base);
150 
151 	/* Adjustment value, used to ensure next capture is on tick boundary */
152 	adj = (now - announced_cycles) + (CYC_PER_TICK - 1);
153 
154 	next = ticks * CYC_PER_TICK;
155 	/*
156 	 * The following section rounds the capture value up to the next tick
157 	 * boundary
158 	 */
159 	next += adj;
160 	next = (next / CYC_PER_TICK) * CYC_PER_TICK;
161 	next += announced_cycles;
162 	/* Set GPT output value */
163 	gpt_set_safe(next);
164 	k_spin_unlock(&lock, key);
165 }
166 
167 /* Get the number of ticks since the last call to sys_clock_announce() */
sys_clock_elapsed(void)168 uint32_t sys_clock_elapsed(void)
169 {
170 	if (!IS_ENABLED(CONFIG_TICKLESS_KERNEL)) {
171 		/* Always return 0 for tickful kernel system */
172 		return 0;
173 	}
174 
175 	k_spinlock_key_t key = k_spin_lock(&lock);
176 	uint32_t cyc = GPT_GetCurrentTimerCount(base);
177 
178 	cyc -= announced_cycles;
179 	k_spin_unlock(&lock, key);
180 	return cyc / CYC_PER_TICK;
181 }
182 
183 /* Get the number of elapsed hardware cycles of the clock */
sys_clock_cycle_get_32(void)184 uint32_t sys_clock_cycle_get_32(void)
185 {
186 	return rollover_cycles + GPT_GetCurrentTimerCount(base);
187 }
188 
189 /*
190  * @brief Initialize system timer driver
191  *
192  * Enable the hw timer, setting its tick period, and setup its interrupt
193  */
sys_clock_driver_init(void)194 int sys_clock_driver_init(void)
195 {
196 	gpt_config_t gpt_config;
197 
198 	/* Configure ISR. Use instance 0 of the GPT timer */
199 	IRQ_CONNECT(DT_IRQN(GPT_INST), DT_IRQ(GPT_INST, priority),
200 		    mcux_imx_gpt_isr, NULL, 0);
201 
202 	base = (GPT_Type *)DT_REG_ADDR(GPT_INST);
203 
204 	GPT_GetDefaultConfig(&gpt_config);
205 	/* Enable GPT timer to run in SOC low power states */
206 	gpt_config.enableRunInStop = true;
207 	gpt_config.enableRunInWait = true;
208 	gpt_config.enableRunInDoze = true;
209 	/* Use 32KHz clock frequency */
210 	gpt_config.clockSource = kGPT_ClockSource_LowFreq;
211 	/* We use reset mode, but reset at MAX ticks- see comment below */
212 	gpt_config.enableFreeRun = false;
213 
214 	/* Initialize the GPT timer, and enable the relevant interrupts */
215 	GPT_Init(base, &gpt_config);
216 	announced_cycles = 0U;
217 	rollover_cycles = 0U;
218 
219 	if (IS_ENABLED(CONFIG_TICKLESS_KERNEL)) {
220 		/*
221 		 * Set GPT capture value 1 to MAX_CYCLES, and use GPT capture
222 		 * value 2 as the source for GPT interrupts. This way, we can
223 		 * use the counter as a free running timer, but it will roll
224 		 * over on a tick boundary.
225 		 */
226 		GPT_SetOutputCompareValue(base, kGPT_OutputCompare_Channel1,
227 			MAX_CYCLES - 1);
228 
229 		/* Set initial trigger value to one tick worth of cycles */
230 		GPT_SetOutputCompareValue(base, kGPT_OutputCompare_Channel2,
231 			CYC_PER_TICK - 1);
232 		/* Enable GPT interrupts for timer match, and reset at capture value 1 */
233 		GPT_EnableInterrupts(base, kGPT_OutputCompare1InterruptEnable |
234 					kGPT_OutputCompare2InterruptEnable);
235 	} else {
236 		/* For a tickful kernel, just roll the counter over every tick */
237 		GPT_SetOutputCompareValue(base, kGPT_OutputCompare_Channel1,
238 			CYC_PER_TICK - 1);
239 		GPT_EnableInterrupts(base, kGPT_OutputCompare1InterruptEnable);
240 	}
241 	/* Enable IRQ */
242 	irq_enable(DT_IRQN(GPT_INST));
243 	/* Start timer */
244 	GPT_StartTimer(base);
245 
246 	return 0;
247 }
248 
249 SYS_INIT(sys_clock_driver_init, PRE_KERNEL_2,
250 	 CONFIG_SYSTEM_CLOCK_INIT_PRIORITY);
251