1 /*
2  * Copyright (c) 2021, NXP
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 
8 #define DT_DRV_COMPAT nxp_sctimer_pwm
9 
10 #include <errno.h>
11 #include <zephyr/drivers/pwm.h>
12 #include <fsl_sctimer.h>
13 #include <fsl_clock.h>
14 #include <zephyr/drivers/pinctrl.h>
15 #include <zephyr/drivers/clock_control.h>
16 
17 #include <zephyr/logging/log.h>
18 
19 LOG_MODULE_REGISTER(pwm_mcux_sctimer, CONFIG_PWM_LOG_LEVEL);
20 
21 #define CHANNEL_COUNT FSL_FEATURE_SCT_NUMBER_OF_OUTPUTS
22 
23 /* Constant identifying that no event number has been set */
24 #define EVENT_NOT_SET FSL_FEATURE_SCT_NUMBER_OF_EVENTS
25 
26 struct pwm_mcux_sctimer_config {
27 	SCT_Type *base;
28 	uint32_t prescale;
29 	const struct pinctrl_dev_config *pincfg;
30 	const struct device *clock_dev;
31 	clock_control_subsys_t clock_subsys;
32 };
33 
34 struct pwm_mcux_sctimer_data {
35 	uint32_t event_number[CHANNEL_COUNT];
36 	sctimer_pwm_signal_param_t channel[CHANNEL_COUNT];
37 	uint32_t match_period;
38 	uint32_t configured_chan;
39 };
40 
41 /* Helper to setup channel that has not previously been configured for PWM */
mcux_sctimer_new_channel(const struct device * dev,uint32_t channel,uint32_t period_cycles,uint32_t duty_cycle)42 static int mcux_sctimer_new_channel(const struct device *dev,
43 				    uint32_t channel, uint32_t period_cycles,
44 				    uint32_t duty_cycle)
45 {
46 	const struct pwm_mcux_sctimer_config *config = dev->config;
47 	struct pwm_mcux_sctimer_data *data = dev->data;
48 	uint32_t clock_freq;
49 	uint32_t pwm_freq;
50 
51 	data->match_period = period_cycles;
52 
53 	if (clock_control_get_rate(config->clock_dev, config->clock_subsys,
54 				&clock_freq)) {
55 		return -EINVAL;
56 	}
57 
58 	pwm_freq = (clock_freq / config->prescale) / period_cycles;
59 
60 	if (pwm_freq == 0) {
61 		LOG_ERR("Could not set up pwm_freq=%d", pwm_freq);
62 		return -EINVAL;
63 	}
64 
65 	SCTIMER_StopTimer(config->base, kSCTIMER_Counter_U);
66 
67 	LOG_DBG("SETUP dutycycle to %u\n", duty_cycle);
68 	data->channel[channel].dutyCyclePercent = duty_cycle;
69 	if (SCTIMER_SetupPwm(config->base, &data->channel[channel],
70 			     kSCTIMER_EdgeAlignedPwm, pwm_freq,
71 			     clock_freq, &data->event_number[channel]) == kStatus_Fail) {
72 		LOG_ERR("Could not set up pwm");
73 		return -ENOTSUP;
74 	}
75 
76 	SCTIMER_StartTimer(config->base, kSCTIMER_Counter_U);
77 	data->configured_chan++;
78 	return 0;
79 }
80 
mcux_sctimer_pwm_set_cycles(const struct device * dev,uint32_t channel,uint32_t period_cycles,uint32_t pulse_cycles,pwm_flags_t flags)81 static int mcux_sctimer_pwm_set_cycles(const struct device *dev,
82 				       uint32_t channel, uint32_t period_cycles,
83 				       uint32_t pulse_cycles, pwm_flags_t flags)
84 {
85 	const struct pwm_mcux_sctimer_config *config = dev->config;
86 	struct pwm_mcux_sctimer_data *data = dev->data;
87 	uint8_t duty_cycle;
88 	int ret;
89 
90 	if (channel >= CHANNEL_COUNT) {
91 		LOG_ERR("Invalid channel");
92 		return -EINVAL;
93 	}
94 
95 	if (period_cycles == 0) {
96 		LOG_ERR("Channel can not be set to inactive level");
97 		return -ENOTSUP;
98 	}
99 
100 	if ((flags & PWM_POLARITY_INVERTED) == 0) {
101 		data->channel[channel].level = kSCTIMER_HighTrue;
102 	} else {
103 		data->channel[channel].level = kSCTIMER_LowTrue;
104 	}
105 
106 	duty_cycle = 100 * pulse_cycles / period_cycles;
107 
108 	if (duty_cycle == 0 && data->configured_chan == 1) {
109 		/* Only one channel is active. We can turn off the SCTimer
110 		 * global counter.
111 		 */
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 
124 		return 0;
125 	}
126 
127 	/* SCTimer has some unique restrictions when operation as a PWM output.
128 	 * The peripheral is based around a single counter, with a block of
129 	 * match registers that can trigger corresponding events. When used
130 	 * as a PWM peripheral, MCUX SDK sets up the SCTimer as follows:
131 	 * - one match register is used to set PWM output high, and reset
132 	 *   SCtimer counter. This sets the PWM period
133 	 * - one match register is used to set PWM output low. This sets the
134 	 *   pulse length
135 	 *
136 	 * This means that when configured, multiple channels must have the
137 	 * same PWM period, since they all share the same SCTimer counter.
138 	 */
139 	if (period_cycles != data->match_period &&
140 	    data->event_number[channel] == EVENT_NOT_SET &&
141 	    data->match_period == 0U) {
142 		/* No PWM signals have been configured. We can set up the first
143 		 * PWM output using the MCUX SDK.
144 		 */
145 		ret = mcux_sctimer_new_channel(dev, channel, period_cycles,
146 					       duty_cycle);
147 		if (ret < 0) {
148 			return ret;
149 		}
150 	} else if (data->event_number[channel] == EVENT_NOT_SET) {
151 		/* We have already configured a PWM signal, but this channel
152 		 * has not been setup. We can only support this channel
153 		 * if the period matches that of other PWM signals.
154 		 */
155 		if (period_cycles != data->match_period) {
156 			LOG_ERR("Only one PWM period is supported between "
157 				"multiple channels");
158 			return -ENOTSUP;
159 		}
160 		/* Setup PWM output using MCUX SDK */
161 		ret = mcux_sctimer_new_channel(dev, channel, period_cycles,
162 					       duty_cycle);
163 	} else if (period_cycles != data->match_period) {
164 		uint32_t period_event = data->event_number[channel];
165 		/* We are reconfiguring the period of a configured channel
166 		 * MCUX SDK does not provide support for this feature, and
167 		 * we cannot do this safely if multiple channels are setup.
168 		 */
169 		if (data->configured_chan != 1) {
170 			LOG_ERR("Cannot change PWM period when multiple "
171 				"channels active");
172 			return -ENOTSUP;
173 		}
174 
175 		/* To make this change, we can simply set the MATCHREL
176 		 * registers for the period match, and the next match
177 		 * (which the SDK will setup as the pulse match event)
178 		 */
179 		SCTIMER_StopTimer(config->base, kSCTIMER_Counter_U);
180 		config->base->MATCHREL[period_event] = period_cycles - 1U;
181 		config->base->MATCHREL[period_event + 1] = pulse_cycles - 1U;
182 		SCTIMER_StartTimer(config->base, kSCTIMER_Counter_U);
183 		data->match_period = period_cycles;
184 	} else {
185 		/* Only duty cycle needs to be updated */
186 		SCTIMER_UpdatePwmDutycycle(config->base, channel, duty_cycle,
187 					   data->event_number[channel]);
188 	}
189 
190 	return 0;
191 }
192 
mcux_sctimer_pwm_get_cycles_per_sec(const struct device * dev,uint32_t channel,uint64_t * cycles)193 static int mcux_sctimer_pwm_get_cycles_per_sec(const struct device *dev,
194 					       uint32_t channel,
195 					       uint64_t *cycles)
196 {
197 	const struct pwm_mcux_sctimer_config *config = dev->config;
198 	uint32_t clock_freq;
199 
200 	if (clock_control_get_rate(config->clock_dev, config->clock_subsys,
201 				&clock_freq)) {
202 		return -EINVAL;
203 	}
204 
205 	*cycles = clock_freq / config->prescale;
206 
207 	return 0;
208 }
209 
mcux_sctimer_pwm_init(const struct device * dev)210 static int mcux_sctimer_pwm_init(const struct device *dev)
211 {
212 	const struct pwm_mcux_sctimer_config *config = dev->config;
213 	struct pwm_mcux_sctimer_data *data = dev->data;
214 	sctimer_config_t pwm_config;
215 	status_t status;
216 	int i;
217 	int err;
218 
219 	err = pinctrl_apply_state(config->pincfg, PINCTRL_STATE_DEFAULT);
220 	if (err) {
221 		return err;
222 	}
223 
224 	SCTIMER_GetDefaultConfig(&pwm_config);
225 
226 	pwm_config.prescale_l = config->prescale - 1;
227 
228 	status = SCTIMER_Init(config->base, &pwm_config);
229 	if (status != kStatus_Success) {
230 		LOG_ERR("Unable to init PWM");
231 		return -EIO;
232 	}
233 
234 	for (i = 0; i < CHANNEL_COUNT; i++) {
235 		data->channel[i].output = i;
236 		data->channel[i].level = kSCTIMER_HighTrue;
237 		data->channel[i].dutyCyclePercent = 0;
238 		data->event_number[i] = EVENT_NOT_SET;
239 	}
240 	data->match_period = 0;
241 	data->configured_chan = 0;
242 
243 	return 0;
244 }
245 
246 static const struct pwm_driver_api pwm_mcux_sctimer_driver_api = {
247 	.set_cycles = mcux_sctimer_pwm_set_cycles,
248 	.get_cycles_per_sec = mcux_sctimer_pwm_get_cycles_per_sec,
249 };
250 
251 #define PWM_MCUX_SCTIMER_DEVICE_INIT_MCUX(n)						\
252 	PINCTRL_DT_INST_DEFINE(n);							\
253 	static struct pwm_mcux_sctimer_data pwm_mcux_sctimer_data_##n;			\
254 											\
255 	static const struct pwm_mcux_sctimer_config pwm_mcux_sctimer_config_##n = {	\
256 		.base = (SCT_Type *)DT_INST_REG_ADDR(n),				\
257 		.prescale = DT_INST_PROP(n, prescaler),					\
258 		.pincfg = PINCTRL_DT_INST_DEV_CONFIG_GET(n),				\
259 		.clock_dev = DEVICE_DT_GET(DT_INST_CLOCKS_CTLR(n)),		\
260 		.clock_subsys = (clock_control_subsys_t)DT_INST_CLOCKS_CELL(n, name),\
261 	};										\
262 											\
263 	DEVICE_DT_INST_DEFINE(n,							\
264 			      mcux_sctimer_pwm_init,					\
265 			      NULL,							\
266 			      &pwm_mcux_sctimer_data_##n,				\
267 			      &pwm_mcux_sctimer_config_##n,				\
268 			      POST_KERNEL, CONFIG_PWM_INIT_PRIORITY,			\
269 			      &pwm_mcux_sctimer_driver_api);
270 
271 DT_INST_FOREACH_STATUS_OKAY(PWM_MCUX_SCTIMER_DEVICE_INIT_MCUX)
272