1 /*
2 * Copyright (c) 2019 Intel Corporation
3 * Copyright (c) 2022 Microchip Technololgy Inc.
4 *
5 * SPDX-License-Identifier: Apache-2.0
6 */
7
8 #define DT_DRV_COMPAT microchip_xec_pwm
9
10 #include <errno.h>
11 #include <stdlib.h>
12 #include <stdint.h>
13
14 #include <zephyr/device.h>
15 #include <zephyr/drivers/pwm.h>
16 #ifdef CONFIG_SOC_SERIES_MEC172X
17 #include <zephyr/drivers/clock_control/mchp_xec_clock_control.h>
18 #include <zephyr/drivers/interrupt_controller/intc_mchp_xec_ecia.h>
19 #endif
20 #include <zephyr/drivers/pinctrl.h>
21 #include <zephyr/logging/log.h>
22 #include <zephyr/pm/device.h>
23
24 #include <soc.h>
25
26 LOG_MODULE_REGISTER(pwm_mchp_xec, CONFIG_PWM_LOG_LEVEL);
27
28 /* Minimal on/off are 1 & 1 both are incremented, so 4.
29 * 0 cannot be set (used for full low/high output) so a
30 * combination of on_off of 2 is not possible.
31 */
32 #define XEC_PWM_LOWEST_ON_OFF 4U
33 /* Maximal on/off are UINT16_T, both are incremented.
34 * Multiplied by the highest divider: 16
35 */
36 #define XEC_PWM_HIGHEST_ON_OFF (2U * (UINT16_MAX + 1U) * 16U)
37
38 #define XEC_PWM_MIN_HIGH_CLK_FREQ \
39 (MCHP_PWM_INPUT_FREQ_HI / XEC_PWM_HIGHEST_ON_OFF)
40 #define XEC_PWM_MAX_LOW_CLK_FREQ \
41 (MCHP_PWM_INPUT_FREQ_LO / XEC_PWM_LOWEST_ON_OFF)
42 /* Precision factor for frequency calculation
43 * To mitigate frequency comparision up to the first digit after 0.
44 */
45 #define XEC_PWM_FREQ_PF 10U
46 /* Precision factor for DC calculation
47 * To avoid losing some digits after 0.
48 */
49 #define XEC_PWM_DC_PF 100000U
50 /* Lowest reachable frequency */
51 #define XEC_PWM_FREQ_LIMIT 1 /* 0.1hz * XEC_PWM_FREQ_PF */
52
53 struct pwm_xec_config {
54 struct pwm_regs * const regs;
55 uint8_t pcr_idx;
56 uint8_t pcr_pos;
57 const struct pinctrl_dev_config *pcfg;
58 };
59
60 struct xec_params {
61 uint32_t on;
62 uint32_t off;
63 uint8_t div;
64 };
65
66 struct pwm_xec_data {
67 uint32_t config;
68 };
69
70 #define NUM_DIV_ELEMS 16
71
72 static const uint32_t max_freq_high_on_div[NUM_DIV_ELEMS] = {
73 48000000,
74 24000000,
75 16000000,
76 12000000,
77 9600000,
78 8000000,
79 6857142,
80 6000000,
81 5333333,
82 4800000,
83 4363636,
84 4000000,
85 3692307,
86 3428571,
87 3200000,
88 3000000,
89 };
90
91 static const uint32_t max_freq_low_on_div[NUM_DIV_ELEMS] = {
92 100000,
93 50000,
94 33333,
95 25000,
96 20000,
97 16666,
98 14285,
99 12500,
100 11111,
101 10000,
102 9090,
103 8333,
104 7692,
105 7142,
106 6666,
107 6250,
108 };
109
xec_compute_frequency(uint32_t clk,uint32_t on,uint32_t off)110 static uint32_t xec_compute_frequency(uint32_t clk, uint32_t on, uint32_t off)
111 {
112 return ((clk * XEC_PWM_FREQ_PF)/((on + 1) + (off + 1)));
113 }
114
xec_select_div(uint32_t freq,const uint32_t max_freq[16])115 static uint16_t xec_select_div(uint32_t freq, const uint32_t max_freq[16])
116 {
117 uint8_t i;
118
119 if (freq >= max_freq[3]) {
120 return 0;
121 }
122
123 freq *= XEC_PWM_LOWEST_ON_OFF;
124
125 for (i = 0; i < 15; i++) {
126 if (freq >= max_freq[i]) {
127 break;
128 }
129 }
130
131 return i;
132 }
133
xec_compute_on_off(uint32_t freq,uint32_t dc,uint32_t clk,uint32_t * on,uint32_t * off)134 static void xec_compute_on_off(uint32_t freq, uint32_t dc, uint32_t clk,
135 uint32_t *on, uint32_t *off)
136 {
137 uint64_t on_off;
138
139 on_off = (clk * 10) / freq;
140
141 *on = ((on_off * dc) / XEC_PWM_DC_PF) - 1;
142 *off = on_off - *on - 2;
143 }
144
xec_compute_dc(uint32_t on,uint32_t off)145 static uint32_t xec_compute_dc(uint32_t on, uint32_t off)
146 {
147 int dc = (on + 1) + (off + 1);
148
149 /* Make calculation in uint64_t since XEC_PWM_DC_PF is large */
150 dc = (((uint64_t)(on + 1) * XEC_PWM_DC_PF) / dc);
151
152 return (uint32_t)dc;
153 }
154
xec_compare_div_on_off(uint32_t target_freq,uint32_t dc,const uint32_t max_freq[16],uint8_t div_a,uint8_t div_b,uint32_t * on_a,uint32_t * off_a)155 static uint16_t xec_compare_div_on_off(uint32_t target_freq, uint32_t dc,
156 const uint32_t max_freq[16],
157 uint8_t div_a, uint8_t div_b,
158 uint32_t *on_a, uint32_t *off_a)
159 {
160 uint32_t freq_a, freq_b, on_b, off_b;
161
162 xec_compute_on_off(target_freq, dc, max_freq[div_a],
163 on_a, off_a);
164
165 freq_a = xec_compute_frequency(max_freq[div_a], *on_a, *off_a);
166
167 xec_compute_on_off(target_freq, dc, max_freq[div_b],
168 &on_b, &off_b);
169
170 freq_b = xec_compute_frequency(max_freq[div_b], on_b, off_b);
171
172 if ((target_freq - freq_a) < (target_freq - freq_b)) {
173 if ((*on_a <= UINT16_MAX) && (*off_a <= UINT16_MAX)) {
174 return div_a;
175 }
176 }
177
178 if ((on_b <= UINT16_MAX) && (off_b <= UINT16_MAX)) {
179 *on_a = on_b;
180 *off_a = off_b;
181
182 return div_b;
183 }
184
185 return div_a;
186 }
187
xec_select_best_div_on_off(uint32_t target_freq,uint32_t dc,const uint32_t max_freq[16],uint32_t * on,uint32_t * off)188 static uint8_t xec_select_best_div_on_off(uint32_t target_freq, uint32_t dc,
189 const uint32_t max_freq[16],
190 uint32_t *on, uint32_t *off)
191 {
192 int div_comp;
193 uint8_t div;
194
195 div = xec_select_div(target_freq, max_freq);
196
197 for (div_comp = (int)div - 1; div_comp >= 0; div_comp--) {
198 div = xec_compare_div_on_off(target_freq, dc, max_freq,
199 div, div_comp, on, off);
200 }
201
202 return div;
203 }
204
xec_compare_params(uint32_t target_freq,struct xec_params * hc_params,struct xec_params * lc_params)205 static struct xec_params *xec_compare_params(uint32_t target_freq,
206 struct xec_params *hc_params,
207 struct xec_params *lc_params)
208 {
209 struct xec_params *params;
210 uint32_t freq_h = 0;
211 uint32_t freq_l = 0;
212
213 if (hc_params->div < NUM_DIV_ELEMS) {
214 freq_h = xec_compute_frequency(
215 max_freq_high_on_div[hc_params->div],
216 hc_params->on,
217 hc_params->off);
218 }
219
220 if (lc_params->div < NUM_DIV_ELEMS) {
221 freq_l = xec_compute_frequency(
222 max_freq_low_on_div[lc_params->div],
223 lc_params->on,
224 lc_params->off);
225 }
226
227 if (abs((int)target_freq - (int)freq_h) <
228 abs((int)target_freq - (int)freq_l)) {
229 params = hc_params;
230 } else {
231 params = lc_params;
232 }
233
234 LOG_DBG("\tFrequency (x%u): %u", XEC_PWM_FREQ_PF, freq_h);
235 LOG_DBG("\tOn %s clock, ON %u OFF %u DIV %u",
236 params == hc_params ? "High" : "Low",
237 params->on, params->off, params->div);
238
239 return params;
240 }
241
xec_compute_and_set_parameters(const struct device * dev,uint32_t target_freq,uint32_t on,uint32_t off)242 static void xec_compute_and_set_parameters(const struct device *dev,
243 uint32_t target_freq,
244 uint32_t on, uint32_t off)
245 {
246 const struct pwm_xec_config * const cfg = dev->config;
247 struct pwm_regs * const regs = cfg->regs;
248 bool compute_high, compute_low;
249 struct xec_params hc_params;
250 struct xec_params lc_params;
251 struct xec_params *params;
252 uint32_t dc, cfgval;
253
254 dc = xec_compute_dc(on, off);
255
256 compute_high = (target_freq >= XEC_PWM_MIN_HIGH_CLK_FREQ);
257 compute_low = (target_freq <= XEC_PWM_MAX_LOW_CLK_FREQ);
258
259 LOG_DBG("Target freq (x%u): %u and DC %u per-cent",
260 XEC_PWM_FREQ_PF, target_freq, (dc / 1000));
261
262 if (compute_high) {
263 if (!compute_low
264 && (on <= UINT16_MAX)
265 && (off <= UINT16_MAX)) {
266 hc_params.on = on;
267 hc_params.off = off;
268 hc_params.div = 0;
269 lc_params.div = UINT8_MAX;
270
271 goto done;
272 }
273
274 hc_params.div = xec_select_best_div_on_off(
275 target_freq, dc,
276 max_freq_high_on_div,
277 &hc_params.on,
278 &hc_params.off);
279 LOG_DBG("Best div high: %u (on/off: %u/%u)",
280 hc_params.div, hc_params.on, hc_params.off);
281 } else {
282 hc_params.div = UINT8_MAX;
283 }
284
285 if (compute_low) {
286 lc_params.div = xec_select_best_div_on_off(
287 target_freq, dc,
288 max_freq_low_on_div,
289 &lc_params.on,
290 &lc_params.off);
291 LOG_DBG("Best div low: %u (on/off: %u/%u)",
292 lc_params.div, lc_params.on, lc_params.off);
293 } else {
294 lc_params.div = UINT8_MAX;
295 }
296 done:
297 regs->CONFIG &= ~MCHP_PWM_CFG_ENABLE;
298
299 cfgval = regs->CONFIG;
300
301 params = xec_compare_params(target_freq, &hc_params, &lc_params);
302 if (params == &hc_params) {
303 cfgval &= ~MCHP_PWM_CFG_CLK_SEL_100K;
304 } else {
305 cfgval |= MCHP_PWM_CFG_CLK_SEL_100K;
306 }
307
308 regs->COUNT_ON = params->on;
309 regs->COUNT_OFF = params->off;
310 cfgval &= ~MCHP_PWM_CFG_CLK_PRE_DIV(0xF);
311 cfgval |= MCHP_PWM_CFG_CLK_PRE_DIV(params->div);
312 cfgval |= MCHP_PWM_CFG_ENABLE;
313
314 regs->CONFIG = cfgval;
315 }
316
pwm_xec_set_cycles(const struct device * dev,uint32_t channel,uint32_t period_cycles,uint32_t pulse_cycles,pwm_flags_t flags)317 static int pwm_xec_set_cycles(const struct device *dev, uint32_t channel,
318 uint32_t period_cycles, uint32_t pulse_cycles,
319 pwm_flags_t flags)
320 {
321 const struct pwm_xec_config * const cfg = dev->config;
322 struct pwm_regs * const regs = cfg->regs;
323 uint32_t target_freq;
324 uint32_t on, off;
325
326 if (channel > 0) {
327 return -EIO;
328 }
329
330 if (flags & PWM_POLARITY_INVERTED)
331 regs->CONFIG |= MCHP_PWM_CFG_ON_POL_LO;
332
333 on = pulse_cycles;
334 off = period_cycles - pulse_cycles;
335
336 target_freq = xec_compute_frequency(MCHP_PWM_INPUT_FREQ_HI, on, off);
337 if (target_freq < XEC_PWM_FREQ_LIMIT) {
338 LOG_DBG("Target frequency below limit");
339 return -EINVAL;
340 }
341
342 if ((pulse_cycles == 0U) && (period_cycles == 0U)) {
343 regs->CONFIG &= ~MCHP_PWM_CFG_ENABLE;
344 } else if ((pulse_cycles == 0U) && (period_cycles > 0U)) {
345 regs->COUNT_ON = 0;
346 regs->COUNT_OFF = 1;
347 } else if ((pulse_cycles > 0U) && (period_cycles == 0U)) {
348 regs->COUNT_ON = 1;
349 regs->COUNT_OFF = 0;
350 } else {
351 xec_compute_and_set_parameters(dev, target_freq, on, off);
352 }
353
354 return 0;
355 }
356
pwm_xec_get_cycles_per_sec(const struct device * dev,uint32_t channel,uint64_t * cycles)357 static int pwm_xec_get_cycles_per_sec(const struct device *dev,
358 uint32_t channel, uint64_t *cycles)
359 {
360 ARG_UNUSED(dev);
361
362 if (channel > 0) {
363 return -EIO;
364 }
365
366 if (cycles) {
367 /* User does not have to know about lowest clock,
368 * the driver will select the most relevant one.
369 */
370 *cycles = MCHP_PWM_INPUT_FREQ_HI;
371 }
372
373 return 0;
374 }
375
376 #ifdef CONFIG_PM_DEVICE
pwm_xec_pm_action(const struct device * dev,enum pm_device_action action)377 static int pwm_xec_pm_action(const struct device *dev, enum pm_device_action action)
378 {
379 const struct pwm_xec_config *const devcfg = dev->config;
380 struct pwm_regs * const regs = devcfg->regs;
381 struct pwm_xec_data * const data = dev->data;
382 int ret = 0;
383
384 switch (action) {
385 case PM_DEVICE_ACTION_RESUME:
386 ret = pinctrl_apply_state(devcfg->pcfg, PINCTRL_STATE_DEFAULT);
387 if (ret != 0) {
388 LOG_ERR("XEC PWM pinctrl setup failed (%d)", ret);
389 }
390
391 /* Turn on PWM only if it is ON before sleep */
392 if ((data->config & MCHP_PWM_CFG_ENABLE) == MCHP_PWM_CFG_ENABLE) {
393
394 regs->CONFIG |= MCHP_PWM_CFG_ENABLE;
395
396 data->config &= (~MCHP_PWM_CFG_ENABLE);
397 }
398 break;
399 case PM_DEVICE_ACTION_SUSPEND:
400 if ((regs->CONFIG & MCHP_PWM_CFG_ENABLE) == MCHP_PWM_CFG_ENABLE) {
401 /* Do copy first, then clear mode. */
402 data->config = regs->CONFIG;
403
404 regs->CONFIG &= ~(MCHP_PWM_CFG_ENABLE);
405 }
406
407 ret = pinctrl_apply_state(devcfg->pcfg, PINCTRL_STATE_SLEEP);
408 /* pinctrl-1 does not exist. */
409 if (ret == -ENOENT) {
410 ret = 0;
411 }
412 break;
413 default:
414 ret = -ENOTSUP;
415 }
416 return ret;
417 }
418 #endif /* CONFIG_PM_DEVICE */
419
420 static const struct pwm_driver_api pwm_xec_driver_api = {
421 .set_cycles = pwm_xec_set_cycles,
422 .get_cycles_per_sec = pwm_xec_get_cycles_per_sec,
423 };
424
pwm_xec_init(const struct device * dev)425 static int pwm_xec_init(const struct device *dev)
426 {
427 const struct pwm_xec_config * const cfg = dev->config;
428 int ret = pinctrl_apply_state(cfg->pcfg, PINCTRL_STATE_DEFAULT);
429
430 if (ret != 0) {
431 LOG_ERR("XEC PWM pinctrl init failed (%d)", ret);
432 return ret;
433 }
434
435 return 0;
436 }
437
438 #define XEC_PWM_CONFIG(inst) \
439 static struct pwm_xec_config pwm_xec_config_##inst = { \
440 .regs = (struct pwm_regs * const)DT_INST_REG_ADDR(inst), \
441 .pcr_idx = (uint8_t)DT_INST_PROP_BY_IDX(inst, pcrs, 0), \
442 .pcr_pos = (uint8_t)DT_INST_PROP_BY_IDX(inst, pcrs, 1), \
443 .pcfg = PINCTRL_DT_INST_DEV_CONFIG_GET(inst), \
444 };
445
446 #define XEC_PWM_DEVICE_INIT(index) \
447 \
448 static struct pwm_xec_data pwm_xec_data_##index; \
449 \
450 PINCTRL_DT_INST_DEFINE(index); \
451 \
452 XEC_PWM_CONFIG(index); \
453 \
454 PM_DEVICE_DT_INST_DEFINE(index, pwm_xec_pm_action); \
455 \
456 DEVICE_DT_INST_DEFINE(index, &pwm_xec_init, \
457 PM_DEVICE_DT_INST_GET(index), \
458 &pwm_xec_data_##index, \
459 &pwm_xec_config_##index, POST_KERNEL, \
460 CONFIG_PWM_INIT_PRIORITY, \
461 &pwm_xec_driver_api);
462
463 DT_INST_FOREACH_STATUS_OKAY(XEC_PWM_DEVICE_INIT)
464