Skip to content

Commit 82afaaa

Browse files
committed
drivers: pwm_mcux_sctimer: Add PM support for low power modes
Enables sleep mode (PM3) in RW61x. Once any channel is configured, PWM driver blocks standby and suspend modes idefinitely since there is no API to disable PWM outputs. Signed-off-by: Alex Rodriguez <[email protected]>
1 parent 5862b64 commit 82afaaa

File tree

2 files changed

+112
-21
lines changed

2 files changed

+112
-21
lines changed

drivers/pwm/Kconfig.mcux_sctimer

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

44
config PWM_MCUX_SCTIMER
@@ -9,3 +9,12 @@ config PWM_MCUX_SCTIMER
99
select PINCTRL
1010
help
1111
Enable sctimer based pwm driver.
12+
13+
config PWM_MCUX_SCT_KEEP_STATE
14+
bool "Retain output level after PWM is disabled"
15+
default y
16+
help
17+
When enabled, the PWM driver will hold the inactive output level and
18+
retain the power management lock even after the PWM signal is
19+
stopped (pulse = 0). This prevents the pin from floating or losing state
20+
in low power modes where the peripheral or pad may be disabled.

drivers/pwm/pwm_mcux_sctimer.c

Lines changed: 102 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2021, NXP
2+
* Copyright 2021, 2025 NXP
33
*
44
* SPDX-License-Identifier: Apache-2.0
55
*/
@@ -16,6 +16,9 @@
1616

1717
#include <zephyr/logging/log.h>
1818

19+
#include <zephyr/pm/policy.h>
20+
#include <zephyr/pm/device.h>
21+
1922
LOG_MODULE_REGISTER(pwm_mcux_sctimer, CONFIG_PWM_LOG_LEVEL);
2023

2124
#define CHANNEL_COUNT FSL_FEATURE_SCT_NUMBER_OF_OUTPUTS
@@ -36,6 +39,11 @@ struct pwm_mcux_sctimer_data {
3639
sctimer_pwm_signal_param_t channel[CHANNEL_COUNT];
3740
uint32_t match_period;
3841
uint32_t configured_chan;
42+
/* This flag keeps track of active/idle channels
43+
* to determine whether to allow low power mode
44+
* or not (see PWM_MCUX_SCTIMER_RETAIN_INACTIVE_STATE).
45+
*/
46+
uint32_t active_chan;
3947
};
4048

4149
/* Helper to setup channel that has not previously been configured for PWM */
@@ -75,9 +83,50 @@ static int mcux_sctimer_new_channel(const struct device *dev,
7583

7684
SCTIMER_StartTimer(config->base, kSCTIMER_Counter_U);
7785
data->configured_chan++;
86+
data->active_chan++;
87+
7888
return 0;
7989
}
8090

91+
static void mcux_sctimer_pwm_update_channel(const struct device *dev, uint32_t channel,
92+
uint8_t duty_cycle)
93+
{
94+
const struct pwm_mcux_sctimer_config *config = dev->config;
95+
struct pwm_mcux_sctimer_data *data = dev->data;
96+
SCT_Type *base = config->base;
97+
98+
SCTIMER_UpdatePwmDutycycle(base, channel, duty_cycle,
99+
data->event_number[channel]);
100+
101+
if (duty_cycle > 0 && data->channel[channel].dutyCyclePercent == 0) {
102+
data->active_chan++;
103+
}
104+
105+
if (duty_cycle == 0 && data->channel[channel].dutyCyclePercent > 0) {
106+
data->active_chan--;
107+
}
108+
109+
data->channel[channel].dutyCyclePercent = duty_cycle;
110+
111+
if (data->active_chan == 0) {
112+
/* No channels active. Stop the sctimer to manually set the output.
113+
* Set the output to inactive state. Output for other channels
114+
* already at the correct level. Just need to update the current one.
115+
*/
116+
SCTIMER_StopTimer(base, kSCTIMER_Counter_U);
117+
118+
if (data->channel[channel].level == kSCTIMER_HighTrue) {
119+
base->OUTPUT &= ~(1UL << channel);
120+
} else {
121+
base->OUTPUT |= (1UL << channel);
122+
}
123+
124+
#ifndef CONFIG_PWM_MCUX_SCT_KEEP_STATE
125+
pm_policy_device_power_lock_put(dev);
126+
#endif
127+
}
128+
}
129+
81130
static int mcux_sctimer_pwm_set_cycles(const struct device *dev,
82131
uint32_t channel, uint32_t period_cycles,
83132
uint32_t pulse_cycles, pwm_flags_t flags)
@@ -106,21 +155,10 @@ static int mcux_sctimer_pwm_set_cycles(const struct device *dev,
106155
duty_cycle = 100 * pulse_cycles / period_cycles;
107156

108157
if (duty_cycle == 0 && data->configured_chan == 1) {
109-
/* Only one channel is active. We can turn off the SCTimer
158+
/* Only one channel is configured. We can turn off the SCTimer
110159
* global counter.
111160
*/
112-
SCT_Type *base = config->base;
113-
114-
/* Stop timer so we can set output directly */
115-
SCTIMER_StopTimer(base, kSCTIMER_Counter_U);
116-
117-
/* Set the output to inactive State */
118-
if (data->channel[channel].level == kSCTIMER_HighTrue) {
119-
base->OUTPUT &= ~(1UL << channel);
120-
} else {
121-
base->OUTPUT |= (1UL << channel);
122-
}
123-
161+
mcux_sctimer_pwm_update_channel(dev, channel, duty_cycle);
124162
return 0;
125163
}
126164

@@ -147,6 +185,10 @@ static int mcux_sctimer_pwm_set_cycles(const struct device *dev,
147185
if (ret < 0) {
148186
return ret;
149187
}
188+
189+
/* Block from losing power while PWM output is enabled. */
190+
pm_policy_device_power_lock_get(dev);
191+
150192
} else if (data->event_number[channel] == EVENT_NOT_SET) {
151193
/* We have already configured a PWM signal, but this channel
152194
* has not been setup. We can only support this channel
@@ -168,7 +210,7 @@ static int mcux_sctimer_pwm_set_cycles(const struct device *dev,
168210
*/
169211
if (data->configured_chan != 1) {
170212
LOG_ERR("Cannot change PWM period when multiple "
171-
"channels active");
213+
"channels configured");
172214
return -ENOTSUP;
173215
}
174216

@@ -183,8 +225,7 @@ static int mcux_sctimer_pwm_set_cycles(const struct device *dev,
183225
data->match_period = period_cycles;
184226
} else {
185227
/* Only duty cycle needs to be updated */
186-
SCTIMER_UpdatePwmDutycycle(config->base, channel, duty_cycle,
187-
data->event_number[channel]);
228+
mcux_sctimer_pwm_update_channel(dev, channel, duty_cycle);
188229
}
189230

190231
return 0;
@@ -207,7 +248,7 @@ static int mcux_sctimer_pwm_get_cycles_per_sec(const struct device *dev,
207248
return 0;
208249
}
209250

210-
static int mcux_sctimer_pwm_init(const struct device *dev)
251+
static int mcux_sctimer_pwm_init_common(const struct device *dev)
211252
{
212253
const struct pwm_mcux_sctimer_config *config = dev->config;
213254
struct pwm_mcux_sctimer_data *data = dev->data;
@@ -239,10 +280,51 @@ static int mcux_sctimer_pwm_init(const struct device *dev)
239280
}
240281
data->match_period = 0;
241282
data->configured_chan = 0;
283+
data->active_chan = 0;
242284

243285
return 0;
244286
}
245287

288+
/* Note: This driver does not save and restore state when entering or exiting
289+
* low power mode due to limitations in the SDK drivers. Users should ensure
290+
* that the required configuration is reapplied after waking up from low power
291+
* states.
292+
*/
293+
static int mcux_sctimer_pwm_driver_pm_action(const struct device *dev,
294+
enum pm_device_action action)
295+
{
296+
const struct pwm_mcux_sctimer_config *config = dev->config;
297+
int err = 0;
298+
299+
switch (action) {
300+
case PM_DEVICE_ACTION_RESUME:
301+
break;
302+
case PM_DEVICE_ACTION_TURN_ON:
303+
err = mcux_sctimer_pwm_init_common(dev);
304+
break;
305+
case PM_DEVICE_ACTION_SUSPEND:
306+
break;
307+
case PM_DEVICE_ACTION_TURN_OFF:
308+
/* Reaching this point implies that all the PWM channels
309+
* are in idle state and the SCTimer counter is disabled.
310+
*/
311+
err = pinctrl_apply_state(config->pincfg, PINCTRL_STATE_SLEEP);
312+
break;
313+
default:
314+
err = -ENOTSUP;
315+
}
316+
317+
return err;
318+
}
319+
320+
static int mcux_sctimer_pwm_init(const struct device *dev)
321+
{
322+
/* The device init is done from the PM_DEVICE_ACTION_TURN_ON in
323+
* the pm callback which is invoked by pm_device_driver_init.
324+
*/
325+
return pm_device_driver_init(dev, mcux_sctimer_pwm_driver_pm_action);
326+
}
327+
246328
static DEVICE_API(pwm, pwm_mcux_sctimer_driver_api) = {
247329
.set_cycles = mcux_sctimer_pwm_set_cycles,
248330
.get_cycles_per_sec = mcux_sctimer_pwm_get_cycles_per_sec,
@@ -259,10 +341,10 @@ static DEVICE_API(pwm, pwm_mcux_sctimer_driver_api) = {
259341
.clock_dev = DEVICE_DT_GET(DT_INST_CLOCKS_CTLR(n)), \
260342
.clock_subsys = (clock_control_subsys_t)DT_INST_CLOCKS_CELL(n, name),\
261343
}; \
262-
\
344+
PM_DEVICE_DT_INST_DEFINE(n, mcux_sctimer_pwm_driver_pm_action); \
263345
DEVICE_DT_INST_DEFINE(n, \
264346
mcux_sctimer_pwm_init, \
265-
NULL, \
347+
PM_DEVICE_DT_INST_GET(n), \
266348
&pwm_mcux_sctimer_data_##n, \
267349
&pwm_mcux_sctimer_config_##n, \
268350
POST_KERNEL, CONFIG_PWM_INIT_PRIORITY, \

0 commit comments

Comments
 (0)