1 /*
2 * Copyright (c) 2022 Microchip Technololgy Inc.
3 *
4 * SPDX-License-Identifier: Apache-2.0
5 */
6
7 #define DT_DRV_COMPAT microchip_xec_pwmbbled
8
9 #include <errno.h>
10 #include <stdlib.h>
11 #include <stdint.h>
12
13 #include <zephyr/device.h>
14 #include <zephyr/drivers/pwm.h>
15 #ifdef CONFIG_SOC_SERIES_MEC172X
16 #include <zephyr/drivers/clock_control/mchp_xec_clock_control.h>
17 #include <zephyr/drivers/interrupt_controller/intc_mchp_xec_ecia.h>
18 #endif
19 #include <zephyr/drivers/pinctrl.h>
20 #include <zephyr/logging/log.h>
21 #include <zephyr/pm/device.h>
22
23 #include <soc.h>
24
25 LOG_MODULE_REGISTER(pwmbbled_mchp_xec, CONFIG_PWM_LOG_LEVEL);
26
27 #define XEC_PWM_BBLED_MAX_FREQ_DIV 256U
28
29 /* We will choose frequency from Device Tree */
30 #define XEC_PWM_BBLED_INPUT_FREQ_HI 48000000
31 #define XEC_PWM_BBLED_INPUT_FREQ_LO 32768
32
33 /* Hardware blink mode equation is Fpwm = Fin / (256 * (LD + 1))
34 * The maximum Fpwm is actually Fin / 256
35 * LD in [0, 4095]
36 */
37 #define XEC_PWM_BBLED_MAX_PWM_FREQ_HI (XEC_PWM_BBLED_INPUT_FREQ_HI / \
38 XEC_PWM_BBLED_MAX_FREQ_DIV)
39 #define XEC_PWM_BBLED_MAX_PWM_FREQ_LO (XEC_PWM_BBLED_INPUT_FREQ_LO / \
40 XEC_PWM_BBLED_MAX_FREQ_DIV)
41 #define XEC_PWM_BBLED_LD_MAX 4095
42 #define XEC_PWM_BBLED_DC_MIN 1u /* 0 full off */
43 #define XEC_PWM_BBLED_DC_MAX 254u /* 255 is full on */
44
45 /* BBLED PWM mode uses the duty cycle to set the PWM frequency:
46 * Fpwm = Fclock / (256 * (LD + 1)) OR
47 * Tpwm = (256 * (LD + 1)) / Fclock
48 * Fclock is 48MHz or 32KHz
49 * LD = Delay register, LOW_DELAY field: bits[11:0]
50 * Pulse_ON_width = (1/Fpwm) * (duty_cycle/256) seconds
51 * Puse_OFF_width = (1/Fpwm) * (256 - duty_cycle) seconds
52 * where duty_cycle is an 8-bit value 0 to 255.
53 * Prescale is derived from DELAY register LOW_DELAY 12-bit field
54 * Duty cycle is derived from LIMITS register MINIMUM 8-bit field
55 *
56 * Fc in Hz, Tp in seconds
57 * Fc / Fp = 256 * (LD+1)
58 * Tp / Tc = 256 * (LD+1)
59 *
60 * API passes pulse period and pulse width in nanoseconds.
61 * BBLED PWM mode duty cycle specified by 8-bit MIN field of the LIMITS register
62 * MIN=0 is OFF, pin driven low
63 * MIN=255 is ON, pin driven high
64 */
65
66 /* Same BBLED hardware block in MEC15xx and MEC172x families
67 * Config register
68 */
69 #define XEC_PWM_BBLED_CFG_MSK 0x1ffffu
70 #define XEC_PWM_BBLED_CFG_MODE_POS 0
71 #define XEC_PWM_BBLED_CFG_MODE_MSK 0x3u
72 #define XEC_PWM_BBLED_CFG_MODE_OFF 0
73 #define XEC_PWM_BBLED_CFG_MODE_PWM 0x2u
74 #define XEC_PWM_BBLED_CFG_MODE_ALWAYS_ON 0x3u
75 #define XEC_PWM_BBLED_CFG_CLK_SRC_48M_POS 2
76 #define XEC_PWM_BBLED_CFG_EN_UPDATE_POS 6
77 #define XEC_PWM_BBLED_CFG_RST_PWM_POS 7
78 #define XEC_PWM_BBLED_CFG_WDT_RLD_POS 8
79 #define XEC_PWM_BBLED_CFG_WDT_RLD_MSK0 0xffu
80 #define XEC_PWM_BBLED_CFG_WDT_RLD_MSK 0xff00u
81 #define XEC_PWM_BBLED_CFG_WDT_RLD_DFLT 0x1400u
82
83 /* Limits register */
84 #define XEC_PWM_BBLED_LIM_MSK 0xffffu
85 #define XEC_PWM_BBLED_LIM_MIN_POS 0
86 #define XEC_PWM_BBLED_LIM_MIN_MSK 0xffu
87 #define XEC_PWM_BBLED_LIM_MAX_POS 8
88 #define XEC_PWM_BBLED_LIM_MAX_MSK 0xff00u
89
90 /* Delay register */
91 #define XEC_PWM_BBLED_DLY_MSK 0xffffffu
92 #define XEC_PWM_BBLED_DLY_LO_POS 0
93 #define XEC_PWM_BBLED_DLY_LO_MSK 0xfffu
94 #define XEC_PWM_BBLED_DLY_HI_POS 12
95 #define XEC_PWM_BBLED_DLY_HI_MSK 0xfff000u
96
97 /* Output delay in clocks for initial enable and enable on resume from sleep
98 * Clocks are either 48MHz or 32KHz selected in CONFIG register.
99 */
100 #define XEC_PWM_BBLED_OUT_DLY_MSK 0xffu
101
102 /* DT enum values */
103 #define XEC_PWM_BBLED_CLKSEL_32K 0
104 #define XEC_PWM_BBLED_CLKSEL_AHB_48M 1
105
106 #define XEC_PWM_BBLED_CLKSEL_0 XEC_PWM_BBLED_CLKSEL_32K
107 #define XEC_PWM_BBLED_CLKSEL_1 XEC_PWM_BBLED_CLKSEL_AHB_48M
108
109
110 struct bbled_regs {
111 volatile uint32_t config;
112 volatile uint32_t limits;
113 volatile uint32_t delay;
114 volatile uint32_t update_step_size;
115 volatile uint32_t update_interval;
116 volatile uint32_t output_delay;
117 };
118
119 #define XEC_PWM_BBLED_CLK_SEL_48M 0
120 #define XEC_PWM_BBLED_CLK_SEL_32K 1
121
122 struct pwm_bbled_xec_config {
123 struct bbled_regs * const regs;
124 const struct pinctrl_dev_config *pcfg;
125 uint8_t girq;
126 uint8_t girq_pos;
127 uint8_t pcr_idx;
128 uint8_t pcr_pos;
129 uint8_t clk_sel;
130 bool enable_low_power_32K;
131 };
132
133 struct bbled_xec_data {
134 uint32_t config;
135 };
136
137 /* Issue: two separate registers must be updated.
138 * LIMITS.MIN = duty cycle = [1, 254]
139 * LIMITS register update takes effect immediately.
140 * DELAY.LO = pre-scaler = [0, 4095]
141 * Writing DELAY stores value in an internal holding register.
142 * Writing bit[6]=1 causes HW to update DELAY at the beginning of
143 * the next HW PWM period.
144 */
xec_pwmbb_progam_pwm(const struct device * dev,uint32_t ld,uint32_t dc)145 static void xec_pwmbb_progam_pwm(const struct device *dev, uint32_t ld, uint32_t dc)
146 {
147 const struct pwm_bbled_xec_config * const cfg = dev->config;
148 struct bbled_regs * const regs = cfg->regs;
149 uint32_t val;
150
151 val = regs->limits & ~(XEC_PWM_BBLED_LIM_MIN_MSK);
152 val |= ((dc << XEC_PWM_BBLED_LIM_MIN_POS) & XEC_PWM_BBLED_LIM_MIN_MSK);
153 regs->limits = val;
154
155 val = regs->delay & ~(XEC_PWM_BBLED_DLY_LO_MSK);
156 val |= ((ld << XEC_PWM_BBLED_DLY_LO_POS) & XEC_PWM_BBLED_DLY_LO_MSK);
157 regs->delay = val;
158
159 /* transfer new delay value from holding register */
160 regs->config |= BIT(XEC_PWM_BBLED_CFG_EN_UPDATE_POS);
161
162 val = regs->config & ~(XEC_PWM_BBLED_CFG_MODE_MSK);
163 val |= XEC_PWM_BBLED_CFG_MODE_PWM;
164 regs->config = val;
165 }
166
167 /* API implementation: Get the clock rate (cycles per second) for a single PWM output.
168 * BBLED in PWM mode (same as blink mode) PWM frequency = Source Frequency / (256 * (LP + 1))
169 * where Source Frequency is either 48 MHz or 32768 Hz and LP is the 12-bit low delay
170 * field of the DELAY register. We return the maximum PWM frequency which is configured
171 * hardware input frequency (32K or 48M) divided by 256.
172 */
pwm_bbled_xec_get_cycles_per_sec(const struct device * dev,uint32_t channel,uint64_t * cycles)173 static int pwm_bbled_xec_get_cycles_per_sec(const struct device *dev,
174 uint32_t channel, uint64_t *cycles)
175 {
176 const struct pwm_bbled_xec_config * const cfg = dev->config;
177 struct bbled_regs * const regs = cfg->regs;
178
179 if (channel > 0) {
180 return -EIO;
181 }
182
183 if (cycles) {
184 if (regs->config & BIT(XEC_PWM_BBLED_CFG_CLK_SRC_48M_POS)) {
185 *cycles = XEC_PWM_BBLED_MAX_PWM_FREQ_HI; /* 187,500 Hz */
186 } else {
187 *cycles = XEC_PWM_BBLED_MAX_PWM_FREQ_LO; /* 128 Hz */
188 }
189 }
190
191 return 0;
192 }
193
194 /* API PWM set cycles:
195 * pulse == 0 -> pin should be constant inactive level
196 * pulse >= period -> pin should be constant active level
197 * hardware PWM (blink) mode: Fpwm = Fin_actual / (LD + 1)
198 * Fin_actual = XEC_PWM_BBLED_MAX_PWM_FREQ_HI or XEC_PWM_BBLED_MAX_PWM_FREQ_LO.
199 * period cycles and pulse cycles both zero is OFF
200 * pulse cycles == 0 is OFF
201 * pulse cycles > 0 and period cycles == 0 is OFF
202 * otherwise
203 * compute duty cycle = 256 * (pulse_cycles / period_cycles).
204 * compute (LD + 1) = Fin_actual / Fpwm
205 * program LD into bits[11:0] of Delay register
206 * program duty cycle info bits[7:0] of Limits register
207 * NOTE: flags parameter is currently used for pin invert and PWM capture.
208 * The BBLED HW does not support pin invert or PWM capture.
209 * NOTE 2: Pin invert is possible by using the MCHP function invert feature
210 * of the GPIO pin. This property can be set using PINCTRL at build time.
211 */
pwm_bbled_xec_set_cycles(const struct device * dev,uint32_t channel,uint32_t period_cycles,uint32_t pulse_cycles,pwm_flags_t flags)212 static int pwm_bbled_xec_set_cycles(const struct device *dev, uint32_t channel,
213 uint32_t period_cycles, uint32_t pulse_cycles,
214 pwm_flags_t flags)
215 {
216 const struct pwm_bbled_xec_config * const cfg = dev->config;
217 struct bbled_regs * const regs = cfg->regs;
218 uint32_t dc, ld;
219
220 if (channel > 0) {
221 LOG_ERR("Invalid channel: %u", channel);
222 return -EIO;
223 }
224
225 if (flags) {
226 return -ENOTSUP;
227 }
228
229 LOG_DBG("period_cycles = %u pulse_cycles = %u", period_cycles, pulse_cycles);
230
231 if (pulse_cycles == 0u) {
232 /* drive pin to inactive state */
233 regs->config = (regs->config & ~XEC_PWM_BBLED_CFG_MODE_MSK)
234 | XEC_PWM_BBLED_CFG_MODE_OFF;
235 regs->limits &= ~XEC_PWM_BBLED_LIM_MIN_MSK;
236 regs->delay &= ~(XEC_PWM_BBLED_DLY_LO_MSK);
237 } else if (pulse_cycles >= period_cycles) {
238 /* drive pin to active state */
239 regs->config = (regs->config & ~XEC_PWM_BBLED_CFG_MODE_MSK)
240 | XEC_PWM_BBLED_CFG_MODE_ALWAYS_ON;
241 regs->limits &= ~XEC_PWM_BBLED_LIM_MIN_MSK;
242 regs->delay &= ~(XEC_PWM_BBLED_DLY_LO_MSK);
243 } else {
244 ld = period_cycles;
245 if (ld) {
246 ld--;
247 if (ld > XEC_PWM_BBLED_LD_MAX) {
248 ld = XEC_PWM_BBLED_LD_MAX;
249 }
250 }
251
252 dc = ((XEC_PWM_BBLED_DC_MAX + 1) * pulse_cycles / period_cycles);
253 if (dc < XEC_PWM_BBLED_DC_MIN) {
254 dc = XEC_PWM_BBLED_DC_MIN;
255 } else if (dc > XEC_PWM_BBLED_DC_MAX) {
256 dc = XEC_PWM_BBLED_DC_MAX;
257 }
258
259 LOG_DBG("Program: ld = 0x%0x dc = 0x%0x", ld, dc);
260
261 xec_pwmbb_progam_pwm(dev, ld, dc);
262 }
263
264 return 0;
265 }
266
267
268 #ifdef CONFIG_PM_DEVICE
pwm_bbled_xec_pm_action(const struct device * dev,enum pm_device_action action)269 static int pwm_bbled_xec_pm_action(const struct device *dev, enum pm_device_action action)
270 {
271 const struct pwm_bbled_xec_config *const devcfg = dev->config;
272 struct bbled_regs * const regs = devcfg->regs;
273 struct bbled_xec_data * const data = dev->data;
274 int ret = 0;
275
276 /* 32K core clock is not gated by PCR in sleep, so BBLED can blink the LED even
277 * in sleep, if it is configured to use 32K clock. If we want to control it
278 * we shall use flag "enable_low_power_32K".
279 * This flag dont have effect on 48M clock. Since it is gated by PCR in sleep, BBLED
280 * will not get clock during sleep.
281 */
282 if ((!devcfg->enable_low_power_32K) &&
283 (!(regs->config & BIT(XEC_PWM_BBLED_CFG_CLK_SRC_48M_POS)))) {
284 return ret;
285 }
286
287 switch (action) {
288 case PM_DEVICE_ACTION_RESUME:
289 ret = pinctrl_apply_state(devcfg->pcfg, PINCTRL_STATE_DEFAULT);
290 if (ret != 0) {
291 LOG_ERR("XEC BBLED pinctrl setup failed (%d)", ret);
292 }
293
294 /* Turn on BBLED only if it is ON before sleep */
295 if ((data->config & XEC_PWM_BBLED_CFG_MODE_MSK) != XEC_PWM_BBLED_CFG_MODE_OFF) {
296
297 regs->config |= (data->config & XEC_PWM_BBLED_CFG_MODE_MSK);
298 regs->config |= BIT(XEC_PWM_BBLED_CFG_EN_UPDATE_POS);
299
300 data->config = XEC_PWM_BBLED_CFG_MODE_OFF;
301 }
302 break;
303 case PM_DEVICE_ACTION_SUSPEND:
304 if ((regs->config & XEC_PWM_BBLED_CFG_MODE_MSK) != XEC_PWM_BBLED_CFG_MODE_OFF) {
305 /* Do copy first, then clear mode. */
306 data->config = regs->config;
307
308 regs->config &= ~(XEC_PWM_BBLED_CFG_MODE_MSK);
309 }
310
311 ret = pinctrl_apply_state(devcfg->pcfg, PINCTRL_STATE_SLEEP);
312 /* pinctrl-1 does not exist. */
313 if (ret == -ENOENT) {
314 ret = 0;
315 }
316 break;
317 default:
318 ret = -ENOTSUP;
319 }
320 return ret;
321 }
322 #endif /* CONFIG_PM_DEVICE */
323
324 static DEVICE_API(pwm, pwm_bbled_xec_driver_api) = {
325 .set_cycles = pwm_bbled_xec_set_cycles,
326 .get_cycles_per_sec = pwm_bbled_xec_get_cycles_per_sec,
327 };
328
pwm_bbled_xec_init(const struct device * dev)329 static int pwm_bbled_xec_init(const struct device *dev)
330 {
331 const struct pwm_bbled_xec_config * const cfg = dev->config;
332 struct bbled_regs * const regs = cfg->regs;
333 int ret = pinctrl_apply_state(cfg->pcfg, PINCTRL_STATE_DEFAULT);
334
335 if (ret != 0) {
336 LOG_ERR("XEC PWM-BBLED pinctrl init failed (%d)", ret);
337 return ret;
338 }
339
340 /* BBLED PWM WDT is enabled by default. Disable it and select 32KHz */
341 regs->config = BIT(XEC_PWM_BBLED_CFG_RST_PWM_POS);
342 regs->config = 0U;
343 if (cfg->clk_sel == XEC_PWM_BBLED_CLKSEL_AHB_48M) {
344 regs->config |= BIT(XEC_PWM_BBLED_CFG_CLK_SRC_48M_POS);
345 }
346
347 return 0;
348 }
349
350 #define XEC_PWM_BBLED_CLKSEL(n) \
351 COND_CODE_1(DT_INST_NODE_HAS_PROP(n, clock_select), \
352 (DT_INST_ENUM_IDX(n, clock_select)), (0))
353
354 #define XEC_PWM_BBLED_CONFIG(inst) \
355 static struct pwm_bbled_xec_config pwm_bbled_xec_config_##inst = { \
356 .regs = (struct bbled_regs * const)DT_INST_REG_ADDR(inst), \
357 .girq = (uint8_t)(DT_INST_PROP_BY_IDX(0, girqs, 0)), \
358 .girq_pos = (uint8_t)(DT_INST_PROP_BY_IDX(0, girqs, 1)), \
359 .pcr_idx = (uint8_t)DT_INST_PROP_BY_IDX(inst, pcrs, 0), \
360 .pcr_pos = (uint8_t)DT_INST_PROP_BY_IDX(inst, pcrs, 1), \
361 .clk_sel = UTIL_CAT(XEC_PWM_BBLED_CLKSEL_, XEC_PWM_BBLED_CLKSEL(inst)), \
362 .enable_low_power_32K = DT_INST_PROP(inst, enable_low_power_32k),\
363 .pcfg = PINCTRL_DT_INST_DEV_CONFIG_GET(inst), \
364 };
365
366 #define XEC_PWM_BBLED_DEVICE_INIT(index) \
367 \
368 static struct bbled_xec_data bbled_xec_data_##index; \
369 \
370 PINCTRL_DT_INST_DEFINE(index); \
371 \
372 XEC_PWM_BBLED_CONFIG(index); \
373 \
374 PM_DEVICE_DT_INST_DEFINE(index, pwm_bbled_xec_pm_action); \
375 \
376 DEVICE_DT_INST_DEFINE(index, &pwm_bbled_xec_init, \
377 PM_DEVICE_DT_INST_GET(index), \
378 &bbled_xec_data_##index, \
379 &pwm_bbled_xec_config_##index, POST_KERNEL, \
380 CONFIG_PWM_INIT_PRIORITY, \
381 &pwm_bbled_xec_driver_api);
382
383 DT_INST_FOREACH_STATUS_OKAY(XEC_PWM_BBLED_DEVICE_INIT)
384