Skip to content

Commit bcc7499

Browse files
David Ullmanncfriedt
David Ullmann
authored andcommitted
drivers: rt6xx ctimer pwm driver
using ctimer to implement pwm api Signed-off-by: David Ullmann <[email protected]>
1 parent 62aa0fa commit bcc7499

File tree

10 files changed

+334
-2
lines changed

10 files changed

+334
-2
lines changed

boards/arm/mimxrt685_evk/mimxrt685_evk_cm33-pinctrl.dtsi

+9
Original file line numberDiff line numberDiff line change
@@ -220,4 +220,13 @@
220220
drive-strength = "normal";
221221
};
222222
};
223+
224+
pinmux_ctimer2_pwm: pinmux_ctimer2_pwm {
225+
group0 {
226+
pinmux = <CTIMER2_MATCH0_PIO0_14>;
227+
slew-rate = "normal";
228+
drive-strength = "normal";
229+
};
230+
};
231+
223232
};

boards/arm/mimxrt685_evk/mimxrt685_evk_cm33.dts

+1-1
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@
5555
};
5656
};
5757

58-
leds {
58+
leds: leds {
5959
compatible = "gpio-leds";
6060
green_led: led_1 {
6161
gpios = <&gpio0 14 0>;

drivers/clock_control/clock_control_mcux_syscon.c

+1-1
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ static int mcux_lpc_syscon_clock_control_get_subsys_rate(
127127
break;
128128
#endif /* defined(CONFIG_CAN_MCUX_MCAN) */
129129

130-
#if defined(CONFIG_COUNTER_MCUX_CTIMER)
130+
#if defined(CONFIG_COUNTER_MCUX_CTIMER) || defined(CONFIG_PWM_MCUX_CTIMER)
131131
case (MCUX_CTIMER0_CLK + MCUX_CTIMER_CLK_OFFSET):
132132
*rate = CLOCK_GetCTimerClkFreq(0);
133133
break;

drivers/pwm/CMakeLists.txt

+1
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ zephyr_library_sources_ifdef(CONFIG_PWM_BBLED_XEC pwm_mchp_xec_bbled.c)
3636
zephyr_library_sources_ifdef(CONFIG_PWM_INTEL_BLINKY pwm_intel_blinky.c)
3737
zephyr_library_sources_ifdef(CONFIG_PWM_XMC4XXX_CCU4 pwm_xmc4xxx_ccu4.c)
3838
zephyr_library_sources_ifdef(CONFIG_PWM_XMC4XXX_CCU8 pwm_xmc4xxx_ccu8.c)
39+
zephyr_library_sources_ifdef(CONFIG_PWM_MCUX_CTIMER pwm_mcux_ctimer.c)
3940

4041
zephyr_library_sources_ifdef(CONFIG_USERSPACE pwm_handlers.c)
4142
zephyr_library_sources_ifdef(CONFIG_PWM_CAPTURE pwm_capture.c)

drivers/pwm/Kconfig

+2
Original file line numberDiff line numberDiff line change
@@ -93,4 +93,6 @@ source "drivers/pwm/Kconfig.xmc4xxx_ccu4"
9393

9494
source "drivers/pwm/Kconfig.xmc4xxx_ccu8"
9595

96+
source "drivers/pwm/Kconfig.mcux_ctimer"
97+
9698
endif # PWM

drivers/pwm/Kconfig.mcux_ctimer

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# (c) Meta Platforms, Inc. and affiliates.
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
config PWM_MCUX_CTIMER
5+
bool "MCUX CTimer PWM driver"
6+
default y
7+
depends on DT_HAS_NXP_CTIMER_PWM_ENABLED
8+
help
9+
Enable ctimer based pwm driver.

drivers/pwm/pwm_mcux_ctimer.c

+277
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,277 @@
1+
/*
2+
* (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
#define DT_DRV_COMPAT nxp_ctimer_pwm
8+
9+
#include <errno.h>
10+
#include <fsl_ctimer.h>
11+
#include <fsl_clock.h>
12+
#include <zephyr/drivers/pwm.h>
13+
#include <zephyr/drivers/pinctrl.h>
14+
#include <zephyr/drivers/clock_control.h>
15+
#include <zephyr/dt-bindings/clock/mcux_lpc_syscon_clock.h>
16+
17+
#include <zephyr/logging/log.h>
18+
LOG_MODULE_REGISTER(pwm_mcux_ctimer, CONFIG_PWM_LOG_LEVEL);
19+
20+
#define CHANNEL_COUNT kCTIMER_Match_3 + 1
21+
22+
enum pwm_ctimer_channel_role {
23+
PWM_CTIMER_CHANNEL_ROLE_NONE = 0,
24+
PWM_CTIMER_CHANNEL_ROLE_PULSE,
25+
PWM_CTIMER_CHANNEL_ROLE_PERIOD,
26+
};
27+
28+
struct pwm_ctimer_channel_state {
29+
enum pwm_ctimer_channel_role role;
30+
uint32_t cycles;
31+
};
32+
33+
struct pwm_mcux_ctimer_data {
34+
struct pwm_ctimer_channel_state channel_states[CHANNEL_COUNT];
35+
ctimer_match_t current_period_channel;
36+
bool is_period_channel_set;
37+
uint32_t num_active_pulse_chans;
38+
};
39+
40+
struct pwm_mcux_ctimer_config {
41+
CTIMER_Type *base;
42+
uint32_t prescale;
43+
uint32_t period_channel;
44+
const struct device *clock_control;
45+
clock_control_subsys_t clock_id;
46+
const struct pinctrl_dev_config *pincfg;
47+
};
48+
49+
/*
50+
* All pwm signals generated from the same ctimer must have same period. To avoid this, we check
51+
* if the new parameters will NOT change the period for a ctimer with active pulse channels
52+
*/
53+
static bool mcux_ctimer_pwm_is_period_valid(struct pwm_mcux_ctimer_data *data,
54+
uint32_t new_pulse_channel, uint32_t new_period_cycles,
55+
uint32_t current_period_channel)
56+
{
57+
/* if we aren't changing the period, we're ok */
58+
if (data->channel_states[current_period_channel].cycles == new_period_cycles) {
59+
return true;
60+
}
61+
62+
/*
63+
* if we are changing it but there aren't any pulse channels that depend on it, then we're
64+
* ok too
65+
*/
66+
if (data->num_active_pulse_chans == 0) {
67+
return true;
68+
}
69+
70+
if (data->num_active_pulse_chans > 1) {
71+
return false;
72+
}
73+
74+
/*
75+
* there is exactly one pulse channel that depends on existing period and its not the
76+
* one we're changing now
77+
*/
78+
if (data->channel_states[new_pulse_channel].role != PWM_CTIMER_CHANNEL_ROLE_PULSE) {
79+
return false;
80+
}
81+
82+
return true;
83+
}
84+
85+
/*
86+
* Each ctimer channel can either be used as a pulse or period channel. Each channel has a counter.
87+
* The duty cycle is counted by the pulse channel. When the period channel counts down, it resets
88+
* the pulse channel (and all counters in the ctimer instance). The pwm api does not permit us to
89+
* specify a period channel (only pulse channel). So we need to figure out an acceptable period
90+
* channel in the driver (if that's even possible)
91+
*/
92+
static int mcux_ctimer_pwm_select_period_channel(struct pwm_mcux_ctimer_data *data,
93+
uint32_t new_pulse_channel,
94+
uint32_t new_period_cycles,
95+
uint32_t *ret_period_channel)
96+
{
97+
if (data->is_period_channel_set) {
98+
if (!mcux_ctimer_pwm_is_period_valid(data, new_pulse_channel, new_period_cycles,
99+
data->current_period_channel)) {
100+
LOG_ERR("Cannot set channel %u to %u as period channel",
101+
*ret_period_channel, new_period_cycles);
102+
return -EINVAL;
103+
}
104+
105+
*ret_period_channel = data->current_period_channel;
106+
if (new_pulse_channel != *ret_period_channel) {
107+
/* the existing period channel will not conflict with new pulse_channel */
108+
return 0;
109+
}
110+
}
111+
112+
/* we need to find an unused channel to use as period_channel */
113+
*ret_period_channel = new_pulse_channel + 1;
114+
*ret_period_channel %= CHANNEL_COUNT;
115+
while (data->channel_states[*ret_period_channel].role != PWM_CTIMER_CHANNEL_ROLE_NONE) {
116+
if (new_pulse_channel == *ret_period_channel) {
117+
LOG_ERR("no available channel for period counter");
118+
return -EBUSY;
119+
}
120+
(*ret_period_channel)++;
121+
*ret_period_channel %= CHANNEL_COUNT;
122+
}
123+
124+
return 0;
125+
}
126+
127+
static void mcux_ctimer_pwm_update_state(struct pwm_mcux_ctimer_data *data, uint32_t pulse_channel,
128+
uint32_t pulse_cycles, uint32_t period_channel,
129+
uint32_t period_cycles)
130+
{
131+
if (data->channel_states[pulse_channel].role != PWM_CTIMER_CHANNEL_ROLE_PULSE) {
132+
data->num_active_pulse_chans++;
133+
}
134+
135+
data->channel_states[pulse_channel].role = PWM_CTIMER_CHANNEL_ROLE_PULSE;
136+
data->channel_states[pulse_channel].cycles = pulse_cycles;
137+
138+
data->is_period_channel_set = true;
139+
data->current_period_channel = period_channel;
140+
data->channel_states[period_channel].role = PWM_CTIMER_CHANNEL_ROLE_PERIOD;
141+
data->channel_states[period_channel].cycles = period_cycles;
142+
}
143+
144+
static int mcux_ctimer_pwm_set_cycles(const struct device *dev, uint32_t pulse_channel,
145+
uint32_t period_cycles, uint32_t pulse_cycles,
146+
pwm_flags_t flags)
147+
{
148+
const struct pwm_mcux_ctimer_config *config = dev->config;
149+
struct pwm_mcux_ctimer_data *data = dev->data;
150+
uint32_t period_channel = data->current_period_channel;
151+
int ret = 0;
152+
status_t status;
153+
154+
if (pulse_channel >= CHANNEL_COUNT) {
155+
LOG_ERR("Invalid channel %u. muse be less than %u", pulse_channel, CHANNEL_COUNT);
156+
return -EINVAL;
157+
}
158+
159+
if (period_cycles == 0) {
160+
LOG_ERR("Channel can not be set to zero");
161+
return -ENOTSUP;
162+
}
163+
164+
ret = mcux_ctimer_pwm_select_period_channel(data, pulse_channel, period_cycles,
165+
&period_channel);
166+
if (ret != 0) {
167+
LOG_ERR("could not select valid period channel. ret=%d", ret);
168+
return ret;
169+
}
170+
171+
if (flags & PWM_POLARITY_INVERTED) {
172+
if (pulse_cycles == 0) {
173+
/* make pulse cycles greater than period so event never occurs */
174+
pulse_cycles = period_cycles + 1;
175+
} else {
176+
pulse_cycles = period_cycles - pulse_cycles;
177+
}
178+
}
179+
180+
status = CTIMER_SetupPwmPeriod(config->base, period_channel, pulse_channel, period_cycles,
181+
pulse_cycles, false);
182+
if (kStatus_Success != status) {
183+
LOG_ERR("failed setup pwm period. status=%d", status);
184+
return -EIO;
185+
}
186+
mcux_ctimer_pwm_update_state(data, pulse_channel, pulse_cycles, period_channel,
187+
period_cycles);
188+
189+
CTIMER_StartTimer(config->base);
190+
return 0;
191+
}
192+
193+
static int mcux_ctimer_pwm_get_cycles_per_sec(const struct device *dev, uint32_t channel,
194+
uint64_t *cycles)
195+
{
196+
const struct pwm_mcux_ctimer_config *config = dev->config;
197+
int err = 0;
198+
199+
200+
/* clean up upper word of return parameter */
201+
*cycles &= 0xFFFFFFFF;
202+
203+
err = clock_control_get_rate(config->clock_control, config->clock_id, (uint32_t *)cycles);
204+
if (err != 0) {
205+
LOG_ERR("could not get clock rate");
206+
return err;
207+
}
208+
209+
if (config->prescale > 0) {
210+
*cycles /= config->prescale;
211+
}
212+
213+
return err;
214+
}
215+
216+
static int mcux_ctimer_pwm_init(const struct device *dev)
217+
{
218+
const struct pwm_mcux_ctimer_config *config = dev->config;
219+
ctimer_config_t pwm_config;
220+
int err;
221+
222+
err = pinctrl_apply_state(config->pincfg, PINCTRL_STATE_DEFAULT);
223+
if (err) {
224+
return err;
225+
}
226+
227+
if (config->period_channel >= CHANNEL_COUNT) {
228+
LOG_ERR("invalid period_channel: %d. must be less than %d", config->period_channel,
229+
CHANNEL_COUNT);
230+
return -EINVAL;
231+
}
232+
233+
CTIMER_GetDefaultConfig(&pwm_config);
234+
pwm_config.prescale = config->prescale;
235+
236+
CTIMER_Init(config->base, &pwm_config);
237+
return 0;
238+
}
239+
240+
static const struct pwm_driver_api pwm_mcux_ctimer_driver_api = {
241+
.set_cycles = mcux_ctimer_pwm_set_cycles,
242+
.get_cycles_per_sec = mcux_ctimer_pwm_get_cycles_per_sec,
243+
};
244+
245+
#define PWM_MCUX_CTIMER_PINCTRL_DEFINE(n) PINCTRL_DT_INST_DEFINE(n);
246+
#define PWM_MCUX_CTIMER_PINCTRL_INIT(n) .pincfg = PINCTRL_DT_INST_DEV_CONFIG_GET(n),
247+
248+
#define PWM_MCUX_CTIMER_DEVICE_INIT_MCUX(n) \
249+
static struct pwm_mcux_ctimer_data pwm_mcux_ctimer_data_##n = { \
250+
.channel_states = \
251+
{ \
252+
[kCTIMER_Match_0] = {.role = PWM_CTIMER_CHANNEL_ROLE_NONE, \
253+
.cycles = 0}, \
254+
[kCTIMER_Match_1] = {.role = PWM_CTIMER_CHANNEL_ROLE_NONE, \
255+
.cycles = 0}, \
256+
[kCTIMER_Match_2] = {.role = PWM_CTIMER_CHANNEL_ROLE_NONE, \
257+
.cycles = 0}, \
258+
[kCTIMER_Match_3] = {.role = PWM_CTIMER_CHANNEL_ROLE_NONE, \
259+
.cycles = 0}, \
260+
}, \
261+
.current_period_channel = kCTIMER_Match_0, \
262+
.is_period_channel_set = false, \
263+
}; \
264+
PWM_MCUX_CTIMER_PINCTRL_DEFINE(n) \
265+
static const struct pwm_mcux_ctimer_config pwm_mcux_ctimer_config_##n = { \
266+
.base = (CTIMER_Type *)DT_INST_REG_ADDR(n), \
267+
.prescale = DT_INST_PROP(n, prescaler), \
268+
.clock_control = DEVICE_DT_GET(DT_INST_CLOCKS_CTLR(n)), \
269+
.clock_id = (clock_control_subsys_t)(DT_INST_CLOCKS_CELL(n, name) + \
270+
MCUX_CTIMER_CLK_OFFSET), \
271+
PWM_MCUX_CTIMER_PINCTRL_INIT(n)}; \
272+
\
273+
DEVICE_DT_INST_DEFINE(n, mcux_ctimer_pwm_init, NULL, &pwm_mcux_ctimer_data_##n, \
274+
&pwm_mcux_ctimer_config_##n, POST_KERNEL, \
275+
CONFIG_KERNEL_INIT_PRIORITY_DEVICE, &pwm_mcux_ctimer_driver_api);
276+
277+
DT_INST_FOREACH_STATUS_OKAY(PWM_MCUX_CTIMER_DEVICE_INIT_MCUX)

dts/bindings/pwm/nxp,ctimer-pwm.yaml

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# (c) Meta Platforms, Inc. and affiliates.
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
description: NXP CTimer PWM
5+
6+
compatible: "nxp,ctimer-pwm"
7+
8+
include: [pwm-controller.yaml, pinctrl-device.yaml, base.yaml, "nxp,lpc-ctimer.yaml"]
9+
10+
properties:
11+
reg:
12+
required: true
13+
14+
prescaler:
15+
type: int
16+
default: 1
17+
description: prescaling value
18+
19+
clk-source:
20+
type: int
21+
required: true
22+
description: clock to use
23+
24+
"#pwm-cells":
25+
const: 3
26+
27+
pwm-cells:
28+
- channel
29+
- period
30+
- flags

modules/hal_nxp/CMakeLists.txt

+3
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,8 @@ if(CONFIG_HAS_MCUX OR CONFIG_HAS_IMX_HAL OR CONFIG_HAS_S32_HAL)
88
add_subdirectory(${ZEPHYR_CURRENT_MODULE_DIR} hal_nxp)
99
add_subdirectory_ifdef(CONFIG_USB_DEVICE_DRIVER usb)
1010

11+
zephyr_sources_ifdef(CONFIG_PWM_MCUX_CTIMER ${ZEPHYR_CURRENT_MODULE_DIR}/mcux/mcux-sdk/drivers/ctimer/fsl_ctimer.c)
12+
zephyr_include_directories_ifdef(CONFIG_PWM_MCUX_CTIMER
13+
${ZEPHYR_CURRENT_MODULE_DIR}/mcux/mcux-sdk/drivers/ctimer/)
1114
zephyr_include_directories(.)
1215
endif()

soc/arm/nxp_imx/rt6xx/soc.c

+1
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,7 @@ static ALWAYS_INLINE void clock_init(void)
298298
#endif
299299

300300
DT_FOREACH_STATUS_OKAY(nxp_lpc_ctimer, CTIMER_CLOCK_SETUP)
301+
DT_FOREACH_STATUS_OKAY(nxp_ctimer_pwm, CTIMER_CLOCK_SETUP)
301302

302303
#if (DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(i3c0), nxp_mcux_i3c, okay))
303304
CLOCK_AttachClk(kFFRO_to_I3C_CLK);

0 commit comments

Comments
 (0)