1 /*
2 * Copyright (c) 2022 KT-Elektronik, Klaucke und Partner GmbH
3 * SPDX-License-Identifier: Apache-2.0
4 */
5
6 /**
7 * @file
8 *
9 * Counter driver for the Quad Timer through the MCUxpresso SDK. Based mainly on counter_mcux_gpt.c
10 *
11 * Each quad timer module has four channels (0-3) that can operate independently, but the Zephyr
12 * counter-API does not support starting or stopping different channels independently. Hence, each
13 * channel is represented as an independent counter device.
14 */
15
16 #include <zephyr/drivers/counter.h>
17 #include <zephyr/drivers/clock_control.h>
18 #include <zephyr/irq.h>
19 #include <fsl_qtmr.h>
20 #include <zephyr/logging/log.h>
21 #include <zephyr/sys/barrier.h>
22
23 LOG_MODULE_REGISTER(mcux_qtmr, CONFIG_COUNTER_LOG_LEVEL);
24
25 struct mcux_qtmr_config {
26 /* info must be first element */
27 struct counter_config_info info;
28 const struct device *clock_dev;
29 clock_control_subsys_t clock_subsys;
30 TMR_Type *base;
31 clock_name_t clock_source;
32 qtmr_channel_selection_t channel;
33 qtmr_config_t qtmr_config;
34 qtmr_counting_mode_t mode;
35 };
36
37 struct mcux_qtmr_data {
38 counter_alarm_callback_t alarm_callback;
39 counter_top_callback_t top_callback;
40 void *alarm_user_data;
41 void *top_user_data;
42 qtmr_status_flags_t interrupt_mask;
43 uint32_t freq;
44 };
45
46 /* Only one interrupt per QTMR module. Each of which has four timers. */
47 #define DT_DRV_COMPAT nxp_imx_qtmr
48
49 /**
50 * @brief ISR for a specific timer channel
51 *
52 * @param dev timer channel device
53 */
mcux_qtmr_timer_handler(const struct device * dev,uint32_t status)54 void mcux_qtmr_timer_handler(const struct device *dev, uint32_t status)
55 {
56 const struct mcux_qtmr_config *config = dev->config;
57 struct mcux_qtmr_data *data = dev->data;
58 uint32_t current = QTMR_GetCurrentTimerCount(config->base, config->channel);
59
60 QTMR_ClearStatusFlags(config->base, config->channel, status);
61 barrier_dsync_fence_full();
62
63 if ((status & kQTMR_Compare1Flag) && data->alarm_callback) {
64 QTMR_DisableInterrupts(config->base, config->channel,
65 kQTMR_Compare1InterruptEnable);
66 data->interrupt_mask &= ~kQTMR_Compare1InterruptEnable;
67 counter_alarm_callback_t alarm_cb = data->alarm_callback;
68
69 data->alarm_callback = NULL;
70 alarm_cb(dev, config->channel, current, data->alarm_user_data);
71 }
72
73 if ((status & kQTMR_OverflowFlag) && data->top_callback) {
74 data->top_callback(dev, data->top_user_data);
75 }
76 }
77
78 /**
79 * @brief ISR for the QTMR
80 *
81 * @param timers array containing the counter devices for each channel of the timer module
82 */
mcux_qtmr_isr(const struct device * timers[])83 static void mcux_qtmr_isr(const struct device *timers[])
84 {
85 /* the interrupt can be triggered by any of the four channels of the QTMR. Check status
86 * of all channels and trigger the ISR for the channel(s) that has/have triggered the
87 * interrupt.
88 */
89 for (qtmr_channel_selection_t ch = kQTMR_Channel_0; ch <= kQTMR_Channel_3 ; ch++) {
90 if (timers[ch] != NULL) {
91 const struct mcux_qtmr_config *config = timers[ch]->config;
92 struct mcux_qtmr_data *data = timers[ch]->data;
93
94 uint32_t channel_status = QTMR_GetStatus(config->base, ch);
95
96 if ((channel_status & data->interrupt_mask) != 0) {
97 mcux_qtmr_timer_handler(timers[ch], channel_status);
98 }
99 }
100 }
101 }
102
103 #define INIT_TIMER(node_id) [DT_PROP(node_id, channel)] = DEVICE_DT_GET(node_id),
104
105 #define QTMR_DEVICE_INIT_MCUX(n) \
106 static const struct device *const timers_##n[4] = { \
107 DT_FOREACH_CHILD_STATUS_OKAY(DT_DRV_INST(n), INIT_TIMER) \
108 }; \
109 static int init_irq_##n(void) \
110 { \
111 IRQ_CONNECT(DT_INST_IRQN(n), DT_INST_IRQ(n, priority), mcux_qtmr_isr, \
112 timers_##n, 0); \
113 irq_enable(DT_INST_IRQN(n)); \
114 return 0; \
115 } \
116 \
117 SYS_INIT(init_irq_##n, POST_KERNEL, CONFIG_COUNTER_INIT_PRIORITY);
118
DT_INST_FOREACH_STATUS_OKAY(QTMR_DEVICE_INIT_MCUX)119 DT_INST_FOREACH_STATUS_OKAY(QTMR_DEVICE_INIT_MCUX)
120
121 #undef DT_DRV_COMPAT
122 #define DT_DRV_COMPAT nxp_imx_tmr
123
124 static int mcux_qtmr_start(const struct device *dev)
125 {
126 const struct mcux_qtmr_config *config = dev->config;
127
128 QTMR_StartTimer(config->base, config->channel, config->mode);
129
130 return 0;
131 }
132
mcux_qtmr_stop(const struct device * dev)133 static int mcux_qtmr_stop(const struct device *dev)
134 {
135 const struct mcux_qtmr_config *config = dev->config;
136
137 QTMR_StopTimer(config->base, config->channel);
138
139 return 0;
140 }
141
mcux_qtmr_get_value(const struct device * dev,uint32_t * ticks)142 static int mcux_qtmr_get_value(const struct device *dev, uint32_t *ticks)
143 {
144 const struct mcux_qtmr_config *config = dev->config;
145
146 *ticks = QTMR_GetCurrentTimerCount(config->base, config->channel);
147 return 0;
148 }
149
mcux_qtmr_set_alarm(const struct device * dev,uint8_t chan_id,const struct counter_alarm_cfg * alarm_cfg)150 static int mcux_qtmr_set_alarm(const struct device *dev, uint8_t chan_id,
151 const struct counter_alarm_cfg *alarm_cfg)
152 {
153 const struct mcux_qtmr_config *config = dev->config;
154 struct mcux_qtmr_data *data = dev->data;
155 uint32_t current;
156 uint32_t ticks;
157
158 if (chan_id != 0) {
159 LOG_ERR("Invalid channel id");
160 return -EINVAL;
161 }
162
163 if (data->alarm_callback) {
164 return -EBUSY;
165 }
166
167 data->alarm_callback = alarm_cfg->callback;
168 data->alarm_user_data = alarm_cfg->user_data;
169
170 current = QTMR_GetCurrentTimerCount(config->base, config->channel);
171 ticks = alarm_cfg->ticks;
172
173 if ((alarm_cfg->flags & COUNTER_ALARM_CFG_ABSOLUTE) == 0) {
174 ticks += current;
175 }
176
177 /* this timer always counts up. */
178 config->base->CHANNEL[config->channel].COMP1 = ticks;
179
180 data->interrupt_mask |= kQTMR_Compare1InterruptEnable;
181 QTMR_EnableInterrupts(config->base, config->channel, data->interrupt_mask);
182
183 return 0;
184 }
185
mcux_qtmr_cancel_alarm(const struct device * dev,uint8_t chan_id)186 static int mcux_qtmr_cancel_alarm(const struct device *dev, uint8_t chan_id)
187 {
188 const struct mcux_qtmr_config *config = dev->config;
189 struct mcux_qtmr_data *data = dev->data;
190
191 if (chan_id != 0) {
192 LOG_ERR("Invalid channel id");
193 return -EINVAL;
194 }
195
196 QTMR_DisableInterrupts(config->base, config->channel, data->interrupt_mask);
197 data->interrupt_mask &= ~kQTMR_Compare1InterruptEnable;
198 data->alarm_callback = NULL;
199
200 return 0;
201 }
202
mcux_qtmr_get_pending_int(const struct device * dev)203 static uint32_t mcux_qtmr_get_pending_int(const struct device *dev)
204 {
205 const struct mcux_qtmr_config *config = dev->config;
206
207 return QTMR_GetStatus(config->base, config->channel);
208 }
209
mcux_qtmr_set_top_value(const struct device * dev,const struct counter_top_cfg * cfg)210 static int mcux_qtmr_set_top_value(const struct device *dev,
211 const struct counter_top_cfg *cfg)
212 {
213 const struct mcux_qtmr_config *config = dev->config;
214 struct mcux_qtmr_data *data = dev->data;
215
216 if (cfg->ticks != config->info.max_top_value) {
217 LOG_ERR("Wrap can only be set to 0x%x",
218 config->info.max_top_value);
219 return -ENOTSUP;
220 }
221
222 if ((cfg->flags & COUNTER_TOP_CFG_DONT_RESET) == 0) {
223 if ((config->base->CHANNEL[config->channel].CTRL & TMR_CTRL_DIR_MASK) != 0U) {
224 /* counting down, reset to UINT16MAX */
225 config->base->CHANNEL[config->channel].CNTR = UINT16_MAX;
226 } else {
227 /* counting up, reset to 0 */
228 config->base->CHANNEL[config->channel].CNTR = 0;
229 }
230 }
231
232 if (cfg->callback != NULL) {
233 data->top_callback = cfg->callback;
234 data->top_user_data = cfg->user_data;
235
236 data->interrupt_mask |= kQTMR_OverflowInterruptEnable;
237 QTMR_EnableInterrupts(config->base, config->channel, kQTMR_OverflowInterruptEnable);
238 }
239
240 return 0;
241 }
242
mcux_qtmr_get_top_value(const struct device * dev)243 static uint32_t mcux_qtmr_get_top_value(const struct device *dev)
244 {
245 const struct mcux_qtmr_config *config = dev->config;
246
247 return config->info.max_top_value;
248 }
249
mcux_qtmr_get_freq(const struct device * dev)250 static uint32_t mcux_qtmr_get_freq(const struct device *dev)
251 {
252 struct mcux_qtmr_data *data = dev->data;
253
254 return data->freq;
255 }
256
257 /**
258 * @brief look up table for dividers when using internal clock sources kQTMR_ClockDivide_1 to
259 * kQTMR_ClockDivide_128
260 */
261 static const uint8_t qtmr_primary_source_divider[] = {1, 2, 4, 8, 16, 32, 64, 128};
262
mcux_qtmr_init(const struct device * dev)263 static int mcux_qtmr_init(const struct device *dev)
264 {
265 const struct mcux_qtmr_config *config = dev->config;
266 struct mcux_qtmr_data *data = dev->data;
267
268 if (config->qtmr_config.primarySource < kQTMR_ClockDivide_1) {
269 /* for external sources, use the value from the dts (if given) */
270 data->freq = config->info.freq;
271 } else {
272 /* bus clock with divider */
273 if (!device_is_ready(config->clock_dev)) {
274 LOG_ERR("clock control device not ready");
275 return -ENODEV;
276 }
277
278 if (clock_control_get_rate(config->clock_dev, config->clock_subsys,
279 &data->freq)) {
280 return -EINVAL;
281 }
282
283 data->freq /= qtmr_primary_source_divider[config->qtmr_config.primarySource -
284 kQTMR_ClockDivide_1];
285 }
286
287 QTMR_Init(config->base, config->channel, &config->qtmr_config);
288
289 return 0;
290 }
291
292 static const struct counter_driver_api mcux_qtmr_driver_api = {
293 .start = mcux_qtmr_start,
294 .stop = mcux_qtmr_stop,
295 .get_value = mcux_qtmr_get_value,
296 .set_alarm = mcux_qtmr_set_alarm,
297 .cancel_alarm = mcux_qtmr_cancel_alarm,
298 .set_top_value = mcux_qtmr_set_top_value,
299 .get_pending_int = mcux_qtmr_get_pending_int,
300 .get_top_value = mcux_qtmr_get_top_value,
301 .get_freq = mcux_qtmr_get_freq,
302 };
303
304 #define TMR_DEVICE_INIT_MCUX(n) \
305 static struct mcux_qtmr_data mcux_qtmr_data_ ## n; \
306 \
307 static const struct mcux_qtmr_config mcux_qtmr_config_ ## n = { \
308 .base = (void *)DT_REG_ADDR(DT_INST_PARENT(n)), \
309 .clock_dev = DEVICE_DT_GET(DT_CLOCKS_CTLR(DT_INST_PARENT(n))), \
310 .clock_subsys = \
311 (clock_control_subsys_t)DT_CLOCKS_CELL(DT_INST_PARENT(n), name), \
312 .info = { \
313 .max_top_value = UINT16_MAX, \
314 .freq = DT_INST_PROP_OR(n, freq, 0), \
315 .channels = 1, \
316 .flags = COUNTER_CONFIG_INFO_COUNT_UP, \
317 }, \
318 .channel = DT_INST_PROP(n, channel), \
319 .qtmr_config = { \
320 .debugMode = kQTMR_RunNormalInDebug, \
321 .enableExternalForce = false, \
322 .enableMasterMode = false, \
323 .faultFilterCount = DT_INST_PROP_OR(n, filter_count, 0), \
324 .faultFilterPeriod = DT_INST_PROP_OR(n, filter_count, 0), \
325 .primarySource = DT_INST_ENUM_IDX(n, primary_source), \
326 .secondarySource = DT_INST_ENUM_IDX_OR(n, secondary_source, 0), \
327 }, \
328 .mode = DT_INST_ENUM_IDX(n, mode), \
329 }; \
330 \
331 DEVICE_DT_INST_DEFINE(n, \
332 mcux_qtmr_init, \
333 NULL, \
334 &mcux_qtmr_data_ ## n, \
335 &mcux_qtmr_config_ ## n, \
336 POST_KERNEL, \
337 CONFIG_COUNTER_INIT_PRIORITY, \
338 &mcux_qtmr_driver_api); \
339
340 DT_INST_FOREACH_STATUS_OKAY(TMR_DEVICE_INIT_MCUX)
341