Skip to content

Commit 8af0f03

Browse files
FelixWang47831jhedberg
authored andcommitted
drivers: pwm: enable pwm capture for qtmr driver
Supported mode: single capture, continus capture, pulse capture, period capture. Signed-off-by: Felix Wang <[email protected]>
1 parent cfd2e10 commit 8af0f03

File tree

2 files changed

+280
-13
lines changed

2 files changed

+280
-13
lines changed

drivers/pwm/Kconfig.mcux_qtmr

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Copyright 2024 NXP
1+
# Copyright 2024-2025 NXP
22
# SPDX-License-Identifier: Apache-2.0
33

44
config PWM_MCUX_QTMR
@@ -9,3 +9,23 @@ config PWM_MCUX_QTMR
99
select PINCTRL
1010
help
1111
Enable QTMR based pwm driver.
12+
13+
config PWM_CAPTURE_MCUX_QTMR_FILTER_PERIOD
14+
int "MCUX QMTR PWM Input Filter Sample Period"
15+
depends on PWM_MCUX_QTMR && PWM_CAPTURE
16+
range 0 255
17+
default 0
18+
help
19+
Indicates the sampling period (in IP bus clock cycles) of
20+
the TMR input signals. If the value is 0x00 (default), then
21+
the input filter is bypassed.
22+
23+
config PWM_CAPTURE_MCUX_QTMR_FILTER_COUNT
24+
int "MCUX QMTR PWM Input Filter Sample Count"
25+
depends on PWM_MCUX_QTMR && PWM_CAPTURE
26+
range 0 7
27+
default 0
28+
help
29+
Indicates the number of consecutive samples that must agree prior to the
30+
input filter accepting an input transition. The real number of samples is
31+
(value +3). For example, a value of 0x0 indicates 3 samples.

drivers/pwm/pwm_mcux_qtmr.c

Lines changed: 259 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2024, NXP
2+
* Copyright (c) 2024-2025 NXP
33
*
44
* SPDX-License-Identifier: Apache-2.0
55
*/
@@ -8,6 +8,7 @@
88

99
#include <errno.h>
1010
#include <zephyr/drivers/pwm.h>
11+
#include <zephyr/irq.h>
1112
#include <fsl_qtmr.h>
1213
#include <fsl_clock.h>
1314
#include <zephyr/drivers/pinctrl.h>
@@ -26,10 +27,29 @@ struct pwm_mcux_qtmr_config {
2627
const struct pinctrl_dev_config *pincfg;
2728
const struct device *clock_dev;
2829
clock_control_subsys_t clock_subsys;
30+
#ifdef CONFIG_PWM_CAPTURE
31+
void (*irq_config_func)(const struct device *dev);
32+
#endif /* CONFIG_PWM_CAPTURE */
2933
};
3034

35+
#ifdef CONFIG_PWM_CAPTURE
36+
struct pwm_mcux_qtmr_capture_data {
37+
pwm_capture_callback_handler_t callback;
38+
void *user_data;
39+
uint32_t overflow_count;
40+
uint32_t channel;
41+
bool continuous : 1;
42+
bool overflowed : 1;
43+
bool pulse_capture: 1;
44+
bool first_edge_captured : 1;
45+
};
46+
#endif /* CONFIG_PWM_CAPTURE */
47+
3148
struct pwm_mcux_qtmr_data {
3249
struct k_mutex lock;
50+
#ifdef CONFIG_PWM_CAPTURE
51+
struct pwm_mcux_qtmr_capture_data capture;
52+
#endif /* CONFIG_PWM_CAPTURE */
3353
};
3454

3555
static int mcux_qtmr_pwm_set_cycles(const struct device *dev, uint32_t channel,
@@ -124,6 +144,198 @@ static int mcux_qtmr_pwm_get_cycles_per_sec(const struct device *dev, uint32_t c
124144

125145
return 0;
126146
}
147+
#ifdef CONFIG_PWM_CAPTURE
148+
static inline bool mcux_qtmr_channel_is_active(const struct device *dev, uint32_t channel)
149+
{
150+
const struct pwm_mcux_qtmr_config *config = dev->config;
151+
152+
return (config->base->CHANNEL[channel].CTRL & TMR_CTRL_CM_MASK) != 0U;
153+
}
154+
155+
static int mcux_qtmr_configure_capture(const struct device *dev,
156+
uint32_t channel, pwm_flags_t flags,
157+
pwm_capture_callback_handler_t cb,
158+
void *user_data)
159+
{
160+
const struct pwm_mcux_qtmr_config *config = dev->config;
161+
struct pwm_mcux_qtmr_data *data = dev->data;
162+
bool inverted = (flags & PWM_POLARITY_MASK) == PWM_POLARITY_INVERTED;
163+
164+
if (channel >= CHANNEL_COUNT) {
165+
LOG_ERR("invalid channel %d", channel);
166+
return -EINVAL;
167+
}
168+
169+
if (mcux_qtmr_channel_is_active(dev, channel)) {
170+
LOG_ERR("pwm capture in progress");
171+
return -EBUSY;
172+
}
173+
174+
if (!(flags & PWM_CAPTURE_TYPE_MASK)) {
175+
LOG_ERR("No capture type specified");
176+
return -EINVAL;
177+
}
178+
179+
if ((flags & PWM_CAPTURE_TYPE_MASK) == PWM_CAPTURE_TYPE_BOTH) {
180+
LOG_ERR("Cannot capture both period and pulse width");
181+
return -ENOTSUP;
182+
}
183+
184+
data->capture.callback = cb;
185+
data->capture.user_data = user_data;
186+
data->capture.channel = channel;
187+
data->capture.continuous =
188+
(flags & PWM_CAPTURE_MODE_MASK) == PWM_CAPTURE_MODE_CONTINUOUS;
189+
190+
if (flags & PWM_CAPTURE_TYPE_PERIOD) {
191+
data->capture.pulse_capture = false;
192+
/* set reloadOnCapture to true to reload counter when capture event happends,
193+
* so only second caputre value is needed when calculating ticks
194+
*/
195+
QTMR_SetupInputCapture(config->base,
196+
(qtmr_channel_selection_t)channel,
197+
(qtmr_input_source_t)channel,
198+
inverted, true, kQTMR_RisingEdge);
199+
} else {
200+
data->capture.pulse_capture = true;
201+
QTMR_SetupInputCapture(config->base,
202+
(qtmr_channel_selection_t)channel,
203+
(qtmr_input_source_t)channel,
204+
inverted, true, kQTMR_RisingAndFallingEdge);
205+
}
206+
QTMR_EnableInterrupts(config->base, channel,
207+
kQTMR_EdgeInterruptEnable | kQTMR_OverflowInterruptEnable);
208+
209+
return 0;
210+
}
211+
212+
static int mcux_qtmr_enable_capture(const struct device *dev, uint32_t channel)
213+
{
214+
const struct pwm_mcux_qtmr_config *config = dev->config;
215+
struct pwm_mcux_qtmr_data *data = dev->data;
216+
217+
if (channel >= CHANNEL_COUNT) {
218+
LOG_ERR("invalid channel %d", channel);
219+
return -EINVAL;
220+
}
221+
222+
if (!data->capture.callback) {
223+
LOG_ERR("PWM capture not configured");
224+
return -EINVAL;
225+
}
226+
227+
if (mcux_qtmr_channel_is_active(dev, channel)) {
228+
LOG_ERR("PWM capture already enabled");
229+
return -EBUSY;
230+
}
231+
232+
data->capture.overflowed = false;
233+
data->capture.first_edge_captured = false;
234+
data->capture.overflow_count = 0;
235+
QTMR_StartTimer(config->base, channel, kQTMR_PriSrcRiseEdge);
236+
237+
return 0;
238+
}
239+
240+
static int mcux_qtmr_disable_capture(const struct device *dev, uint32_t channel)
241+
{
242+
const struct pwm_mcux_qtmr_config *config = dev->config;
243+
244+
if (channel >= CHANNEL_COUNT) {
245+
LOG_ERR("invalid channel %d", channel);
246+
return -EINVAL;
247+
}
248+
249+
QTMR_StopTimer(config->base, channel);
250+
return 0;
251+
}
252+
253+
static int mcux_qtmr_calc_ticks(uint32_t overflows, uint32_t capture,
254+
uint32_t *result)
255+
{
256+
uint32_t pulse;
257+
258+
/* Calculate cycles from overflow counter */
259+
if (u32_mul_overflow(overflows, 0xFFFFU, &pulse)) {
260+
return -ERANGE;
261+
}
262+
263+
/* Add pulse width */
264+
if (u32_add_overflow(pulse, capture, &pulse)) {
265+
return -ERANGE;
266+
}
267+
268+
*result = pulse;
269+
270+
return 0;
271+
}
272+
273+
static void mcux_qtmr_isr(const struct device *dev)
274+
{
275+
const struct pwm_mcux_qtmr_config *config = dev->config;
276+
struct pwm_mcux_qtmr_data *data = dev->data;
277+
uint32_t ticks = 0;
278+
uint32_t timeCapt = 0;
279+
int err = 0;
280+
uint32_t flags;
281+
282+
flags = QTMR_GetStatus(config->base, data->capture.channel);
283+
QTMR_ClearStatusFlags(config->base, data->capture.channel, flags);
284+
285+
if ((flags & kQTMR_OverflowFlag) != 0U) {
286+
data->capture.overflowed |= u32_add_overflow(1,
287+
data->capture.overflow_count, &data->capture.overflow_count);
288+
}
289+
290+
if ((flags & kQTMR_EdgeFlag) == 0U) {
291+
return;
292+
}
293+
294+
if (!data->capture.first_edge_captured) {
295+
data->capture.first_edge_captured = true;
296+
data->capture.overflow_count = 0;
297+
data->capture.overflowed = false;
298+
return;
299+
}
300+
301+
if (data->capture.overflowed) {
302+
err = -ERANGE;
303+
} else {
304+
/* calculate ticks from second capture */
305+
timeCapt = config->base->CHANNEL[data->capture.channel].CAPT;
306+
err = mcux_qtmr_calc_ticks(data->capture.overflow_count, timeCapt, &ticks);
307+
}
308+
309+
if (data->capture.pulse_capture) {
310+
data->capture.callback(dev, data->capture.channel,
311+
0, ticks, err, data->capture.user_data);
312+
} else {
313+
data->capture.callback(dev, data->capture.channel,
314+
ticks, 0, err, data->capture.user_data);
315+
}
316+
317+
/* Prepare for next capture */
318+
data->capture.overflowed = false;
319+
data->capture.overflow_count = 0;
320+
321+
if (data->capture.continuous) {
322+
if (data->capture.pulse_capture) {
323+
data->capture.first_edge_captured = false;
324+
} else {
325+
/* No action required. In continuous period capture mode,
326+
* first edge of next period captureis second edge of this
327+
* capture (this edge)
328+
*/
329+
}
330+
} else {
331+
/* Stop timer and disable interrupts for single capture*/
332+
data->capture.first_edge_captured = false;
333+
QTMR_DisableInterrupts(config->base, data->capture.channel,
334+
kQTMR_EdgeInterruptEnable | kQTMR_OverflowInterruptEnable);
335+
QTMR_StopTimer(config->base, data->capture.channel);
336+
}
337+
}
338+
#endif /* CONFIG_PWM_CAPTURE */
127339

128340
static int mcux_qtmr_pwm_init(const struct device *dev)
129341
{
@@ -142,6 +354,13 @@ static int mcux_qtmr_pwm_init(const struct device *dev)
142354
QTMR_GetDefaultConfig(&qtmr_config);
143355
qtmr_config.primarySource = kQTMR_ClockDivide_1 + (31 - __builtin_clz(config->prescaler));
144356

357+
#ifdef CONFIG_PWM_CAPTURE
358+
config->irq_config_func(dev);
359+
360+
qtmr_config.faultFilterCount = CONFIG_PWM_CAPTURE_MCUX_QTMR_FILTER_COUNT;
361+
qtmr_config.faultFilterPeriod = CONFIG_PWM_CAPTURE_MCUX_QTMR_FILTER_PERIOD;
362+
#endif /* CONFIG_PWM_CAPTURE */
363+
145364
for (int i = 0; i < CHANNEL_COUNT; i++) {
146365
QTMR_Init(config->base, i, &qtmr_config);
147366
}
@@ -152,22 +371,50 @@ static int mcux_qtmr_pwm_init(const struct device *dev)
152371
static DEVICE_API(pwm, pwm_mcux_qtmr_driver_api) = {
153372
.set_cycles = mcux_qtmr_pwm_set_cycles,
154373
.get_cycles_per_sec = mcux_qtmr_pwm_get_cycles_per_sec,
374+
#ifdef CONFIG_PWM_CAPTURE
375+
.configure_capture = mcux_qtmr_configure_capture,
376+
.enable_capture = mcux_qtmr_enable_capture,
377+
.disable_capture = mcux_qtmr_disable_capture,
378+
#endif
155379
};
156380

157-
#define PWM_MCUX_QTMR_DEVICE_INIT(n) \
158-
PINCTRL_DT_INST_DEFINE(n); \
159-
static struct pwm_mcux_qtmr_data pwm_mcux_qtmr_data_##n; \
160-
\
161-
static const struct pwm_mcux_qtmr_config pwm_mcux_qtmr_config_##n = { \
381+
#ifdef CONFIG_PWM_CAPTURE
382+
#define QTMR_CONFIG_FUNC(n) \
383+
static void mcux_qtmr_config_func_##n(const struct device *dev) \
384+
{ \
385+
IRQ_CONNECT(DT_INST_IRQN(n), DT_INST_IRQ(n, priority), \
386+
mcux_qtmr_isr, DEVICE_DT_INST_GET(n), 0); \
387+
irq_enable(DT_INST_IRQN(n)); \
388+
}
389+
#define QTMR_CFG_CAPTURE_INIT(n) \
390+
.irq_config_func = mcux_qtmr_config_func_##n
391+
#define QTMR_INIT_CFG(n) QTMR_DECLARE_CFG(n, QTMR_CFG_CAPTURE_INIT(n))
392+
#else /* !CONFIG_PWM_CAPTURE */
393+
#define QTMR_CONFIG_FUNC(n)
394+
#define QTMR_CFG_CAPTURE_INIT
395+
#define QTMR_INIT_CFG(n) QTMR_DECLARE_CFG(n, QTMR_CFG_CAPTURE_INIT)
396+
#endif /* !CONFIG_PWM_CAPTURE */
397+
398+
#define QTMR_DECLARE_CFG(n, CAPTURE_INIT) \
399+
static const struct pwm_mcux_qtmr_config pwm_mcux_qtmr_config_##n = { \
162400
.base = (TMR_Type *)DT_INST_REG_ADDR(n), \
163401
.prescaler = DT_INST_PROP(n, prescaler), \
164402
.pincfg = PINCTRL_DT_INST_DEV_CONFIG_GET(n), \
165403
.clock_dev = DEVICE_DT_GET(DT_INST_CLOCKS_CTLR(n)), \
166404
.clock_subsys = (clock_control_subsys_t)DT_INST_CLOCKS_CELL(n, name), \
167-
}; \
168-
\
169-
DEVICE_DT_INST_DEFINE(n, mcux_qtmr_pwm_init, NULL, &pwm_mcux_qtmr_data_##n, \
170-
&pwm_mcux_qtmr_config_##n, POST_KERNEL, CONFIG_PWM_INIT_PRIORITY, \
171-
&pwm_mcux_qtmr_driver_api);
405+
CAPTURE_INIT \
406+
}
172407

173-
DT_INST_FOREACH_STATUS_OKAY(PWM_MCUX_QTMR_DEVICE_INIT)
408+
#define QTMR_DEVICE(n) \
409+
PINCTRL_DT_INST_DEFINE(n); \
410+
static const struct pwm_mcux_qtmr_config pwm_mcux_qtmr_config_##n; \
411+
static struct pwm_mcux_qtmr_data pwm_mcux_qtmr_data_##n; \
412+
DEVICE_DT_INST_DEFINE(n, &mcux_qtmr_pwm_init, NULL, \
413+
&pwm_mcux_qtmr_data_##n, \
414+
&pwm_mcux_qtmr_config_##n, \
415+
POST_KERNEL, CONFIG_PWM_INIT_PRIORITY, \
416+
&pwm_mcux_qtmr_driver_api); \
417+
QTMR_CONFIG_FUNC(n) \
418+
QTMR_INIT_CFG(n);
419+
420+
DT_INST_FOREACH_STATUS_OKAY(QTMR_DEVICE)

0 commit comments

Comments
 (0)