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