1 /*
2 * Copyright (c) 2021 Vestas Wind Systems A/S
3 * Copyright 2024 NXP
4 *
5 * SPDX-License-Identifier: Apache-2.0
6 */
7
8 #define DT_DRV_COMPAT nxp_kinetis_pwt
9
10 #include <zephyr/drivers/clock_control.h>
11 #include <errno.h>
12 #include <zephyr/drivers/pwm.h>
13 #include <zephyr/irq.h>
14 #include <soc.h>
15 #include <fsl_pwt.h>
16 #include <fsl_clock.h>
17 #include <zephyr/drivers/pinctrl.h>
18
19 #include <zephyr/logging/log.h>
20
21 LOG_MODULE_REGISTER(pwm_mcux_pwt, CONFIG_PWM_LOG_LEVEL);
22
23 /* Number of PWT input ports */
24 #define PWT_INPUTS 4U
25
26 struct mcux_pwt_config {
27 PWT_Type *base;
28 const struct device *clock_dev;
29 clock_control_subsys_t clock_subsys;
30 pwt_clock_source_t pwt_clock_source;
31 pwt_clock_prescale_t prescale;
32 void (*irq_config_func)(const struct device *dev);
33 const struct pinctrl_dev_config *pincfg;
34 };
35
36 struct mcux_pwt_data {
37 uint32_t clock_freq;
38 uint32_t period_cycles;
39 uint32_t high_overflows;
40 uint32_t low_overflows;
41 pwm_capture_callback_handler_t callback;
42 void *user_data;
43 pwt_config_t pwt_config;
44 bool continuous : 1;
45 bool inverted : 1;
46 bool overflowed : 1;
47 };
48
mcux_pwt_is_active(const struct device * dev)49 static inline bool mcux_pwt_is_active(const struct device *dev)
50 {
51 const struct mcux_pwt_config *config = dev->config;
52
53 return !!(config->base->CS & PWT_CS_PWTEN_MASK);
54 }
55
mcux_pwt_set_cycles(const struct device * dev,uint32_t channel,uint32_t period_cycles,uint32_t pulse_cycles,pwm_flags_t flags)56 static int mcux_pwt_set_cycles(const struct device *dev, uint32_t channel,
57 uint32_t period_cycles, uint32_t pulse_cycles,
58 pwm_flags_t flags)
59 {
60 ARG_UNUSED(dev);
61 ARG_UNUSED(channel);
62 ARG_UNUSED(period_cycles);
63 ARG_UNUSED(pulse_cycles);
64 ARG_UNUSED(flags);
65
66 LOG_ERR("pwt only supports pwm capture");
67
68 return -ENOTSUP;
69 }
70
mcux_pwt_configure_capture(const struct device * dev,uint32_t channel,pwm_flags_t flags,pwm_capture_callback_handler_t cb,void * user_data)71 static int mcux_pwt_configure_capture(const struct device *dev,
72 uint32_t channel, pwm_flags_t flags,
73 pwm_capture_callback_handler_t cb,
74 void *user_data)
75 {
76 const struct mcux_pwt_config *config = dev->config;
77 struct mcux_pwt_data *data = dev->data;
78
79 if (channel >= PWT_INPUTS) {
80 LOG_ERR("invalid channel %d", channel);
81 return -EINVAL;
82 }
83
84 if (mcux_pwt_is_active(dev)) {
85 LOG_ERR("pwm capture in progress");
86 return -EBUSY;
87 }
88
89 #if defined(CONFIG_SOC_SERIES_KE1XZ)
90 if ((flags & PWM_CAPTURE_TYPE_MASK) == PWM_CAPTURE_TYPE_BOTH) {
91 LOG_ERR("Cannot capture both period and pulse width");
92 return -ENOTSUP;
93 }
94
95 if (((flags & PWM_CAPTURE_TYPE_MASK) == PWM_CAPTURE_TYPE_PERIOD) &&
96 ((flags & PWM_POLARITY_MASK) == PWM_POLARITY_NORMAL)) {
97 LOG_ERR("Cannot capture period in normal polarity (active-high pulse)");
98 return -ENOTSUP;
99 }
100 #endif
101
102 data->callback = cb;
103 data->user_data = user_data;
104
105 data->pwt_config.inputSelect = channel;
106
107 data->continuous =
108 (flags & PWM_CAPTURE_MODE_MASK) == PWM_CAPTURE_MODE_CONTINUOUS;
109 data->inverted =
110 (flags & PWM_POLARITY_MASK) == PWM_POLARITY_INVERTED;
111
112 PWT_Init(config->base, &data->pwt_config);
113 PWT_EnableInterrupts(config->base,
114 kPWT_PulseWidthReadyInterruptEnable |
115 kPWT_CounterOverflowInterruptEnable);
116
117 return 0;
118 }
119
mcux_pwt_enable_capture(const struct device * dev,uint32_t channel)120 static int mcux_pwt_enable_capture(const struct device *dev, uint32_t channel)
121 {
122 const struct mcux_pwt_config *config = dev->config;
123 struct mcux_pwt_data *data = dev->data;
124
125 if (channel >= PWT_INPUTS) {
126 LOG_ERR("invalid channel %d", channel);
127 return -EINVAL;
128 }
129
130 if (!data->callback) {
131 LOG_ERR("PWM capture not configured");
132 return -EINVAL;
133 }
134
135 if (mcux_pwt_is_active(dev)) {
136 LOG_ERR("PWM capture already enabled");
137 return -EBUSY;
138 }
139
140 data->overflowed = false;
141 data->high_overflows = 0;
142 data->low_overflows = 0;
143 PWT_StartTimer(config->base);
144
145 return 0;
146 }
147
mcux_pwt_disable_capture(const struct device * dev,uint32_t channel)148 static int mcux_pwt_disable_capture(const struct device *dev, uint32_t channel)
149 {
150 const struct mcux_pwt_config *config = dev->config;
151
152 if (channel >= PWT_INPUTS) {
153 LOG_ERR("invalid channel %d", channel);
154 return -EINVAL;
155 }
156
157 PWT_StopTimer(config->base);
158
159 return 0;
160 }
161
mcux_pwt_calc_period(uint16_t ppw,uint16_t npw,uint32_t high_overflows,uint32_t low_overflows,uint32_t * result)162 static int mcux_pwt_calc_period(uint16_t ppw, uint16_t npw,
163 uint32_t high_overflows,
164 uint32_t low_overflows,
165 uint32_t *result)
166 {
167 uint32_t period;
168
169 /* Calculate sum of overflow counters */
170 if (u32_add_overflow(high_overflows, low_overflows, &period)) {
171 return -ERANGE;
172 }
173
174 /* Calculate cycles from sum of overflow counters */
175 if (u32_mul_overflow(period, 0xFFFFU, &period)) {
176 return -ERANGE;
177 }
178
179 /* Add positive pulse width */
180 if (u32_add_overflow(period, ppw, &period)) {
181 return -ERANGE;
182 }
183
184 /* Add negative pulse width */
185 if (u32_add_overflow(period, npw, &period)) {
186 return -ERANGE;
187 }
188
189 *result = period;
190
191 return 0;
192 }
193
mcux_pwt_calc_pulse(uint16_t pw,uint32_t overflows,uint32_t * result)194 static int mcux_pwt_calc_pulse(uint16_t pw, uint32_t overflows,
195 uint32_t *result)
196 {
197 uint32_t pulse;
198
199 /* Calculate cycles from overflow counter */
200 if (u32_mul_overflow(overflows, 0xFFFFU, &pulse)) {
201 return -ERANGE;
202 }
203
204 /* Add pulse width */
205 if (u32_add_overflow(pulse, pw, &pulse)) {
206 return -ERANGE;
207 }
208
209 *result = pulse;
210
211 return 0;
212 }
213
mcux_pwt_isr(const struct device * dev)214 static void mcux_pwt_isr(const struct device *dev)
215 {
216 const struct mcux_pwt_config *config = dev->config;
217 struct mcux_pwt_data *data = dev->data;
218 uint32_t period = 0;
219 uint32_t pulse = 0;
220 uint32_t flags;
221 uint16_t ppw;
222 uint16_t npw;
223 int err;
224
225 flags = PWT_GetStatusFlags(config->base);
226
227 if (flags & kPWT_CounterOverflowFlag) {
228 if (config->base->CR & PWT_CR_LVL_MASK) {
229 data->overflowed |= u32_add_overflow(1,
230 data->high_overflows, &data->high_overflows);
231 } else {
232 data->overflowed |= u32_add_overflow(1,
233 data->low_overflows, &data->low_overflows);
234 }
235
236 PWT_ClearStatusFlags(config->base, kPWT_CounterOverflowFlag);
237 }
238
239 if (flags & kPWT_PulseWidthValidFlag) {
240 ppw = PWT_ReadPositivePulseWidth(config->base);
241 npw = PWT_ReadNegativePulseWidth(config->base);
242
243 if (!data->continuous) {
244 PWT_StopTimer(config->base);
245 }
246
247 if (data->inverted) {
248 err = mcux_pwt_calc_pulse(npw, data->low_overflows,
249 &pulse);
250 } else {
251 err = mcux_pwt_calc_pulse(ppw, data->high_overflows,
252 &pulse);
253 }
254
255 if (err == 0) {
256 err = mcux_pwt_calc_period(ppw, npw,
257 data->high_overflows,
258 data->low_overflows,
259 &period);
260 }
261
262 if (data->overflowed) {
263 err = -ERANGE;
264 }
265
266 LOG_DBG("period = %d, pulse = %d, err = %d", period, pulse,
267 err);
268
269 if (data->callback) {
270 data->callback(dev, data->pwt_config.inputSelect,
271 period, pulse, err, data->user_data);
272 }
273
274 data->overflowed = false;
275 data->high_overflows = 0;
276 data->low_overflows = 0;
277 PWT_ClearStatusFlags(config->base, kPWT_PulseWidthValidFlag);
278 }
279 }
280
mcux_pwt_get_cycles_per_sec(const struct device * dev,uint32_t channel,uint64_t * cycles)281 static int mcux_pwt_get_cycles_per_sec(const struct device *dev,
282 uint32_t channel, uint64_t *cycles)
283 {
284 const struct mcux_pwt_config *config = dev->config;
285 struct mcux_pwt_data *data = dev->data;
286
287 ARG_UNUSED(channel);
288
289 *cycles = data->clock_freq >> config->prescale;
290
291 return 0;
292 }
293
mcux_pwt_init(const struct device * dev)294 static int mcux_pwt_init(const struct device *dev)
295 {
296 const struct mcux_pwt_config *config = dev->config;
297 struct mcux_pwt_data *data = dev->data;
298 pwt_config_t *pwt_config = &data->pwt_config;
299 int err;
300
301 if (!device_is_ready(config->clock_dev)) {
302 LOG_ERR("clock control device not ready");
303 return -ENODEV;
304 }
305
306 if (clock_control_get_rate(config->clock_dev, config->clock_subsys,
307 &data->clock_freq)) {
308 LOG_ERR("could not get clock frequency");
309 return -EINVAL;
310 }
311
312 PWT_GetDefaultConfig(pwt_config);
313 pwt_config->clockSource = config->pwt_clock_source;
314 pwt_config->prescale = config->prescale;
315 pwt_config->enableFirstCounterLoad = true;
316 PWT_Init(config->base, pwt_config);
317
318 err = pinctrl_apply_state(config->pincfg, PINCTRL_STATE_DEFAULT);
319 if (err) {
320 return err;
321 }
322
323 config->irq_config_func(dev);
324
325 return 0;
326 }
327
328 static DEVICE_API(pwm, mcux_pwt_driver_api) = {
329 .set_cycles = mcux_pwt_set_cycles,
330 .get_cycles_per_sec = mcux_pwt_get_cycles_per_sec,
331 .configure_capture = mcux_pwt_configure_capture,
332 .enable_capture = mcux_pwt_enable_capture,
333 .disable_capture = mcux_pwt_disable_capture,
334 };
335
336 #define TO_PWT_PRESCALE_DIVIDE(val) _DO_CONCAT(kPWT_Prescale_Divide_, val)
337
338 #define PWT_DEVICE(n) \
339 static void mcux_pwt_config_func_##n(const struct device *dev); \
340 \
341 PINCTRL_DT_INST_DEFINE(n); \
342 \
343 static const struct mcux_pwt_config mcux_pwt_config_##n = { \
344 .base = (PWT_Type *)DT_INST_REG_ADDR(n), \
345 .clock_dev = DEVICE_DT_GET(DT_INST_CLOCKS_CTLR(n)), \
346 .clock_subsys = \
347 (clock_control_subsys_t)DT_INST_CLOCKS_CELL(n, name), \
348 .pwt_clock_source = kPWT_BusClock, \
349 .prescale = \
350 TO_PWT_PRESCALE_DIVIDE(DT_INST_PROP(n, prescaler)), \
351 .irq_config_func = mcux_pwt_config_func_##n, \
352 .pincfg = PINCTRL_DT_INST_DEV_CONFIG_GET(n), \
353 }; \
354 \
355 static struct mcux_pwt_data mcux_pwt_data_##n; \
356 \
357 DEVICE_DT_INST_DEFINE(n, &mcux_pwt_init, \
358 NULL, &mcux_pwt_data_##n, \
359 &mcux_pwt_config_##n, \
360 POST_KERNEL, \
361 CONFIG_PWM_INIT_PRIORITY, \
362 &mcux_pwt_driver_api); \
363 \
364 static void mcux_pwt_config_func_##n(const struct device *dev) \
365 { \
366 IRQ_CONNECT(DT_INST_IRQN(n), DT_INST_IRQ(n, priority), \
367 mcux_pwt_isr, DEVICE_DT_INST_GET(n), 0); \
368 irq_enable(DT_INST_IRQN(n)); \
369 }
370
371 DT_INST_FOREACH_STATUS_OKAY(PWT_DEVICE)
372