Skip to content

Commit b78d8c1

Browse files
drivers: pwm: add driver for the NXP Kinetis Pulse Width Timer (PWT)
Add PWM capture driver for the NXP Kinetis Pulse Width Timer (PWT). Signed-off-by: Henrik Brix Andersen <[email protected]>
1 parent 4718225 commit b78d8c1

File tree

6 files changed

+365
-0
lines changed

6 files changed

+365
-0
lines changed

drivers/pwm/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ zephyr_library_sources_ifdef(CONFIG_PWM_MCUX_TPM pwm_mcux_tpm.c)
2020
zephyr_library_sources_ifdef(CONFIG_PWM_SAM0_TCC pwm_sam0_tcc.c)
2121
zephyr_library_sources_ifdef(CONFIG_PWM_NPCX pwm_npcx.c)
2222
zephyr_library_sources_ifdef(CONFIG_PWM_XLNX_AXI_TIMER pwm_xlnx_axi_timer.c)
23+
zephyr_library_sources_ifdef(CONFIG_PWM_MCUX_PWT pwm_mcux_pwt.c)
2324

2425
zephyr_library_sources_ifdef(CONFIG_USERSPACE pwm_handlers.c)
2526
zephyr_library_sources_ifdef(CONFIG_PWM_CAPTURE pwm_capture.c)

drivers/pwm/Kconfig

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,4 +63,6 @@ source "drivers/pwm/Kconfig.npcx"
6363

6464
source "drivers/pwm/Kconfig.xlnx"
6565

66+
source "drivers/pwm/Kconfig.mcux_pwt"
67+
6668
endif # PWM

drivers/pwm/Kconfig.mcux_pwt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# Copyright 2021 Vestas Wind Systems A/S
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
# MCUX PWT PWM
5+
6+
config PWM_MCUX_PWT
7+
bool "MCUX PWT PWM capture driver"
8+
depends on HAS_MCUX_PWT && CLOCK_CONTROL && PWM_CAPTURE
9+
help
10+
Enable the MCUX Pulse Width Timer (PWT) PWM capture driver.

drivers/pwm/pwm_mcux_pwt.c

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

soc/arm/nxp_kinetis/ke1xf/Kconfig.defconfig.series

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,10 @@ config PWM_MCUX_FTM
5050
default y
5151
depends on PWM
5252

53+
config PWM_MCUX_PWT
54+
default y
55+
depends on PWM_CAPTURE
56+
5357
config PINMUX_MCUX
5458
default y
5559
depends on PINMUX

soc/arm/nxp_kinetis/ke1xf/Kconfig.series

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,5 +29,6 @@ config SOC_SERIES_KINETIS_KE1XF
2929
select HAS_MCUX_DAC32
3030
select HAS_MCUX_EDMA
3131
select HAS_MCUX_ACMP
32+
select HAS_MCUX_PWT
3233
help
3334
Enable support for Kinetis KE1xF MCU series

0 commit comments

Comments
 (0)