Skip to content

Commit f371482

Browse files
jvasanth1nashif
authored andcommitted
drivers: pwm: Add Microchip XEC BBLED PWM driver
The Microchip XEC (MEC172x and MEC152x) have a breathing-blinking LED (BBLED) block which implements a simple PWM mode. The BBLED PWM frequencies are 32KHz and 48MHz selectable in device tree. Frequency divider is 12-bit resolution from 256 to (256 * 4096). Signed-off-by: Jay Vasanth <[email protected]>
1 parent b1cf745 commit f371482

File tree

4 files changed

+439
-0
lines changed

4 files changed

+439
-0
lines changed

drivers/pwm/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ zephyr_library_sources_ifdef(CONFIG_PWM_RCAR pwm_rcar.c)
2929
zephyr_library_sources_ifdef(CONFIG_PWM_PCA9685 pwm_pca9685.c)
3030
zephyr_library_sources_ifdef(CONFIG_PWM_TEST pwm_test.c)
3131
zephyr_library_sources_ifdef(CONFIG_PWM_RPI_PICO pwm_rpi_pico.c)
32+
zephyr_library_sources_ifdef(CONFIG_PWM_BBLED_XEC pwm_mchp_xec_bbled.c)
3233

3334
zephyr_library_sources_ifdef(CONFIG_USERSPACE pwm_handlers.c)
3435
zephyr_library_sources_ifdef(CONFIG_PWM_CAPTURE pwm_capture.c)

drivers/pwm/Kconfig.xec

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,11 @@ config PWM_XEC
99
depends on DT_HAS_MICROCHIP_XEC_PWM_ENABLED
1010
help
1111
Enable driver to utilize PWM on the Microchip XEC IP block.
12+
13+
config PWM_BBLED_XEC
14+
bool "Microchip XEC PWM-BBLED"
15+
default y
16+
depends on DT_HAS_MICROCHIP_XEC_PWMBBLED_ENABLED
17+
help
18+
Enable driver to utilize the Microchip XEC Breathing-Blinking LED
19+
as a PWM

drivers/pwm/pwm_mchp_xec_bbled.c

Lines changed: 379 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,379 @@
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+
22+
#include <soc.h>
23+
24+
LOG_MODULE_REGISTER(pwmbbled_mchp_xec, CONFIG_PWM_LOG_LEVEL);
25+
26+
/* We will choose frequency from Device Tree */
27+
#define XEC_PWM_BBLED_INPUT_FREQ_HI 48000000
28+
#define XEC_PWM_BBLED_INPUT_FREQ_LO 32768
29+
30+
#define XEC_PWM_BBLED_MAX_FREQ_DIV 256U
31+
#define XEC_PWM_BBLED_MIN_FREQ_DIV (256U * 4066U)
32+
33+
/* Maximum frequency BBLED-PWM can generate is scaled by
34+
* 256 * (LD+1) where LD is in [0, 4065].
35+
*/
36+
#define XEC_PWM_BBLED_MAX_PWM_FREQ_AHB_CLK \
37+
(XEC_PWM_BBLED_INPUT_FREQ_HI / XEC_PWM_BBLED_MAX_FREQ_DIV)
38+
#define XEC_PWM_BBLED_MAX_PWM_FREQ_32K_CLK \
39+
(XEC_PWM_BBLED_INPUT_FREQ_LO / XEC_PWM_BBLED_MAX_FREQ_DIV)
40+
41+
/* BBLED PWM mode uses the duty cycle to set the PWM frequency:
42+
* Fpwm = Fclock / (256 * (LD + 1)) OR
43+
* Tpwm = (256 * (LD + 1)) / Fclock
44+
* Fclock is 48MHz or 32KHz
45+
* LD = Delay register, LOW_DELAY field: bits[11:0]
46+
* Pulse_ON_width = (1/Fpwm) * (duty_cycle/256) seconds
47+
* Puse_OFF_width = (1/Fpwm) * (256 - duty_cycle) seconds
48+
* where duty_cycle is an 8-bit value 0 to 255.
49+
* Prescale is derived from DELAY register LOW_DELAY 12-bit field
50+
* Duty cycle is derived from LIMITS register MINIMUM 8-bit field
51+
*
52+
* Fc in Hz, Tp in seconds
53+
* Fc / Fp = 256 * (LD+1)
54+
* Tp / Tc = 256 * (LD+1)
55+
*
56+
* API passes pulse period and pulse width in nanoseconds.
57+
* BBLED PWM mode duty cycle specified by 8-bit MIN field of the LIMITS register
58+
* MIN=0 is OFF, pin driven low
59+
* MIN=255 is ON, pin driven high
60+
*/
61+
62+
/* Same BBLED hardware block in MEC15xx and MEC172x families
63+
* Config register
64+
*/
65+
#define XEC_PWM_BBLED_CFG_MSK 0x1ffffu
66+
#define XEC_PWM_BBLED_CFG_MODE_POS 0
67+
#define XEC_PWM_BBLED_CFG_MODE_MSK 0x3u
68+
#define XEC_PWM_BBLED_CFG_MODE_OFF 0
69+
#define XEC_PWM_BBLED_CFG_MODE_PWM 0x2u
70+
#define XEC_PWM_BBLED_CFG_MODE_ALWAYS_ON 0x3u
71+
#define XEC_PWM_BBLED_CFG_CLK_SRC_48M_POS 2
72+
#define XEC_PWM_BBLED_CFG_EN_UPDATE_POS 6
73+
#define XEC_PWM_BBLED_CFG_RST_PWM_POS 7
74+
#define XEC_PWM_BBLED_CFG_WDT_RLD_POS 8
75+
#define XEC_PWM_BBLED_CFG_WDT_RLD_MSK0 0xffu
76+
#define XEC_PWM_BBLED_CFG_WDT_RLD_MSK 0xff00u
77+
#define XEC_PWM_BBLED_CFG_WDT_RLD_DFLT 0x1400u
78+
79+
/* Limits register */
80+
#define XEC_PWM_BBLED_LIM_MSK 0xffffu
81+
#define XEC_PWM_BBLED_LIM_MIN_POS 0
82+
#define XEC_PWM_BBLED_LIM_MIN_MSK 0xffu
83+
#define XEC_PWM_BBLED_LIM_MAX_POS 8
84+
#define XEC_PWM_BBLED_LIM_MAX_MSK 0xff00u
85+
86+
/* Delay register */
87+
#define XEC_PWM_BBLED_DLY_MSK 0xffffffu
88+
#define XEC_PWM_BBLED_DLY_LO_POS 0
89+
#define XEC_PWM_BBLED_DLY_LO_MSK 0xfffu
90+
#define XEC_PWM_BBLED_DLY_HI_POS 12
91+
#define XEC_PWM_BBLED_DLY_HI_MSK 0xfff000u
92+
93+
/* Output delay in clocks for initial enable and enable on resume from sleep
94+
* Clocks are either 48MHz or 32KHz selected in CONFIG register.
95+
*/
96+
#define XEC_PWM_BBLED_OUT_DLY_MSK 0xffu
97+
98+
/* DT enum values */
99+
#define XEC_PWM_BBLED_CLKSEL_32K 0
100+
#define XEC_PWM_BBLED_CLKSEL_PCR_SLOW 1
101+
#define XEC_PWM_BBLED_CLKSEL_AHB_48M 2
102+
103+
#define XEC_PWM_BBLED_CLKSEL_0 XEC_PWM_BBLED_CLKSEL_32K
104+
#define XEC_PWM_BBLED_CLKSEL_1 XEC_PWM_BBLED_CLKSEL_PCR_SLOW
105+
#define XEC_PWM_BBLED_CLKSEL_2 XEC_PWM_BBLED_CLKSEL_AHB_48M
106+
107+
struct bbled_regs {
108+
volatile uint32_t config;
109+
volatile uint32_t limits;
110+
volatile uint32_t delay;
111+
volatile uint32_t update_step_size;
112+
volatile uint32_t update_interval;
113+
volatile uint32_t output_delay;
114+
};
115+
116+
#define XEC_PWM_BBLED_CLK_SEL_48M 0
117+
#define XEC_PWM_BBLED_CLK_SEL_32K 1
118+
119+
struct pwm_bbled_xec_config {
120+
struct bbled_regs * const regs;
121+
uint8_t girq;
122+
uint8_t girq_pos;
123+
uint8_t pcr_idx;
124+
uint8_t pcr_pos;
125+
uint8_t clk_sel;
126+
#ifdef CONFIG_PINCTRL
127+
const struct pinctrl_dev_config *pcfg;
128+
#endif
129+
};
130+
131+
/* Compute BBLED PWM delay factor to produce requested frequency.
132+
* Fpwm = Fclk / (256 * (LD+1)) where Fclk is 48MHz or 32KHz and
133+
* LD is a 12-bit value in [0, 4096].
134+
* We expect 256 <= pulse_cycles <= (256 * 4096)
135+
* period_cycles = (period * cycles_per_sec) / NSEC_PER_SEC;
136+
* period_cycles = (Tpwm * Fclk) = Fclk / Fpwm
137+
* period_cycles = Fclk * (256 * (LD+1)) / Fclk = (256 * (LD+1))
138+
* (LD+1) = period_cycles / 256
139+
*/
140+
static uint32_t xec_pwmbb_compute_ld(const struct device *dev, uint32_t period_cycles)
141+
{
142+
uint32_t ld = 0;
143+
144+
ld = period_cycles / 256U;
145+
if (ld > 0) {
146+
if (ld > 4096U) {
147+
ld = 4096U;
148+
}
149+
ld--;
150+
}
151+
152+
return ld;
153+
}
154+
155+
/* BBLED-PWM duty cycle set in 8-bit MINIMUM field of BBLED LIMITS register.
156+
* Limits.Minimum == 0 (alwyas off, output driven low)
157+
* == 255 (always on, output driven high)
158+
* 1 <= Limits.Minimum <= 254 duty cycle
159+
*/
160+
static uint32_t xec_pwmbb_compute_dc(uint32_t period_cycles, uint32_t pulse_cycles)
161+
{
162+
uint32_t dc;
163+
164+
if (pulse_cycles >= period_cycles) {
165+
return 255U; /* always on */
166+
}
167+
168+
if (period_cycles < 256U) {
169+
return 0; /* always off */
170+
}
171+
172+
dc = (256U * pulse_cycles) / period_cycles;
173+
174+
return dc;
175+
}
176+
177+
/* Issue: two separate registers must be updated.
178+
* LIMITS.MIN = duty cycle = [1, 254]
179+
* LIMITS register update takes effect immediately.
180+
* DELAY.LO = pre-scaler = [0, 4095]
181+
* Writing DELAY stores value in an internal holding register.
182+
* Writing bit[6]=1 causes HW to update DELAY at the beginning of
183+
* the next HW PWM period.
184+
*/
185+
static void xec_pwmbb_progam_pwm(const struct device *dev, uint32_t ld, uint32_t dc)
186+
{
187+
const struct pwm_bbled_xec_config * const cfg = dev->config;
188+
struct bbled_regs * const regs = cfg->regs;
189+
uint32_t val;
190+
191+
val = regs->delay & ~(XEC_PWM_BBLED_DLY_LO_MSK);
192+
val |= ((ld << XEC_PWM_BBLED_DLY_LO_POS) & XEC_PWM_BBLED_DLY_LO_MSK);
193+
regs->delay = val;
194+
195+
val = regs->limits & ~(XEC_PWM_BBLED_LIM_MIN_MSK);
196+
val |= ((dc << XEC_PWM_BBLED_LIM_MIN_POS) & XEC_PWM_BBLED_LIM_MIN_MSK);
197+
regs->limits = val;
198+
199+
/* transfer new delay value from holding register */
200+
regs->config |= BIT(XEC_PWM_BBLED_CFG_EN_UPDATE_POS);
201+
202+
val = regs->config & ~(XEC_PWM_BBLED_CFG_MODE_MSK);
203+
val |= XEC_PWM_BBLED_CFG_MODE_PWM;
204+
regs->config = val;
205+
}
206+
207+
/* API implementation: Set the period and pulse width for a single PWM.
208+
* channel must be 0 as each PWM instance implements one output.
209+
* period in clock cycles of currently configured clock.
210+
* pulse width in clock cycles of currently configured clock.
211+
* flags b[7:0] defined by zephyr. b[15:8] can be SoC specific.
212+
* Bit[0] = 1 inverted, bits[7:1] specify capture features not implemented in XEC PWM.
213+
* Note: macro PWM_MSEC() and others convert from other units to nanoseconds.
214+
* BBLED output state is Active High. If Active low is required the GPIO pin invert
215+
* bit must be set. The XEC PWM block also defaults to Active High but it has a bit
216+
* to select Active Low.
217+
* PWM API exposes this function as pwm_set_cycles and has wrapper API defined in
218+
* pwm.h, named pwm_set which:
219+
* Calls pwm_get_cycles_per_second to get current maximum HW frequency as cycles_per_sec
220+
* Computes period_cycles = (period * cycles_per_sec) / NSEC_PER_SEC
221+
* pulse_cycles = (pulse * cycles_per_sec) / NSEC_PER_SEC
222+
* Call pwm_set_cycles passing period_cycles and pulse_cycles.
223+
*
224+
* BBLED PWM input frequency is 32KHz (POR default) or 48MHz selected by device tree
225+
* at application build time.
226+
* BBLED Fpwm = Fin / (256 * (LD + 1)) where LD = [0, 4095]
227+
* This equation tells use the maximum number of cycles of Fin the hardware can
228+
* generate is 256 whereas the mininum number of cycles is 256 * 4096.
229+
*
230+
* Fin = 32KHz
231+
* Fpwm-min = 32768 / (256 * 4096) = 31.25 mHz = 31250000 nHz = 0x01DC_D650 nHz
232+
* Fpwm-max = 32768 / 256 = 128 Hz = 128e9 nHz = 0x1D_CD65_0000 nHz
233+
* Tpwm-min = 32e9 ns = 0x0007_7359_4000 ns
234+
* Tpmw-max = 7812500 ns = 0x0077_3594 ns
235+
*
236+
* Fin = 48MHz
237+
* Fpwm-min = 48e6 / (256 * 4096) = 45.7763 Hz = 45776367188 nHz = 0x000A_A87B_EE53 nHz
238+
* Fpwm-max = 48e6 / 256 = 187500 = 1.875e14 = 0xAA87_BEE5_3800 nHz
239+
* Tpwm-min = 5334 ns = 0x14D6 ns
240+
* Tpwm-max = 21845333 ns = 0x014D_5555 ns
241+
*/
242+
static int pwm_bbled_xec_check_cycles(uint32_t period_cycles, uint32_t pulse_cycles)
243+
{
244+
if ((period_cycles < 256U) || (period_cycles > (4096U * 256U))) {
245+
return -EINVAL;
246+
}
247+
248+
if ((pulse_cycles < 256U) || (pulse_cycles > (4096U * 256U))) {
249+
return -EINVAL;
250+
}
251+
252+
return 0;
253+
}
254+
255+
static int pwm_bbled_xec_set_cycles(const struct device *dev, uint32_t channel,
256+
uint32_t period_cycles, uint32_t pulse_cycles,
257+
pwm_flags_t flags)
258+
{
259+
const struct pwm_bbled_xec_config * const cfg = dev->config;
260+
struct bbled_regs * const regs = cfg->regs;
261+
uint32_t dc, ld;
262+
int ret;
263+
264+
if (channel > 0) {
265+
return -EIO;
266+
}
267+
268+
if (flags) {
269+
/* PWM polarity not supported (yet?) */
270+
return -ENOTSUP;
271+
}
272+
273+
if ((pulse_cycles == 0U) && (period_cycles == 0U)) { /* Controller off, clocks gated */
274+
regs->config = (regs->config & ~XEC_PWM_BBLED_CFG_MODE_MSK)
275+
| XEC_PWM_BBLED_CFG_MODE_OFF;
276+
} else if ((pulse_cycles == 0U) && (period_cycles > 0U)) {
277+
/* PWM mode: Limits minimum duty cycle == 0 -> LED output is fully OFF */
278+
regs->limits &= ~XEC_PWM_BBLED_LIM_MIN_MSK;
279+
} else if ((pulse_cycles > 0U) && (period_cycles == 0U)) {
280+
/* PWM mode: Limits minimum duty cycle == full value -> LED output is fully ON */
281+
regs->limits |= XEC_PWM_BBLED_LIM_MIN_MSK;
282+
} else {
283+
ret = pwm_bbled_xec_check_cycles(period_cycles, pulse_cycles);
284+
if (ret) {
285+
LOG_DBG("Target frequency out of range");
286+
return ret;
287+
}
288+
289+
ld = xec_pwmbb_compute_ld(dev, period_cycles);
290+
dc = xec_pwmbb_compute_dc(period_cycles, pulse_cycles);
291+
xec_pwmbb_progam_pwm(dev, ld, dc);
292+
}
293+
294+
return 0;
295+
}
296+
297+
/* API implementation: Get the clock rate (cycles per second) for a single PWM output.
298+
* BBLED in PWM mode (same as blink mode) PWM frequency = Source Frequency / (256 * (LP + 1))
299+
* where Source Frequency is either 48 MHz or 32768 Hz and LP is the 12-bit low delay
300+
* field of the DELAY register.
301+
*/
302+
static int pwm_bbled_xec_get_cycles_per_sec(const struct device *dev,
303+
uint32_t channel, uint64_t *cycles)
304+
{
305+
const struct pwm_bbled_xec_config * const cfg = dev->config;
306+
struct bbled_regs * const regs = cfg->regs;
307+
308+
if (channel > 0) {
309+
return -EIO;
310+
}
311+
312+
if (cycles) {
313+
if (regs->config & BIT(XEC_PWM_BBLED_CFG_CLK_SRC_48M_POS)) {
314+
*cycles = XEC_PWM_BBLED_INPUT_FREQ_HI;
315+
} else {
316+
*cycles = XEC_PWM_BBLED_INPUT_FREQ_LO;
317+
}
318+
}
319+
320+
return 0;
321+
}
322+
323+
static const struct pwm_driver_api pwm_bbled_xec_driver_api = {
324+
.set_cycles = pwm_bbled_xec_set_cycles,
325+
.get_cycles_per_sec = pwm_bbled_xec_get_cycles_per_sec,
326+
};
327+
328+
static int pwm_bbled_xec_init(const struct device *dev)
329+
{
330+
const struct pwm_bbled_xec_config * const cfg = dev->config;
331+
struct bbled_regs * const regs = cfg->regs;
332+
int ret = pinctrl_apply_state(cfg->pcfg, PINCTRL_STATE_DEFAULT);
333+
334+
if (ret != 0) {
335+
LOG_ERR("XEC PWM-BBLED pinctrl init failed (%d)", ret);
336+
return ret;
337+
}
338+
339+
/* BBLED PWM WDT is enabled by default. Disable it and select 32KHz */
340+
regs->config = BIT(XEC_PWM_BBLED_CFG_RST_PWM_POS);
341+
regs->config = 0U;
342+
if (cfg->clk_sel == XEC_PWM_BBLED_CLKSEL_AHB_48M) {
343+
regs->config |= BIT(XEC_PWM_BBLED_CFG_CLK_SRC_48M_POS);
344+
}
345+
346+
return 0;
347+
}
348+
349+
#define XEC_PWM_BBLED_PINCTRL_DEF(inst) PINCTRL_DT_INST_DEFINE(inst)
350+
351+
#define XEC_PWM_BBLED_CLKSEL(n) \
352+
COND_CODE_1(DT_INST_NODE_HAS_PROP(n, clock_select), \
353+
(DT_INST_ENUM_IDX(n, clock_select)), (0))
354+
355+
#define XEC_PWM_BBLED_CONFIG(inst) \
356+
static struct pwm_bbled_xec_config pwm_bbled_xec_config_##inst = { \
357+
.regs = (struct bbled_regs * const)DT_INST_REG_ADDR(inst), \
358+
.girq = (uint8_t)(DT_INST_PROP_BY_IDX(0, girqs, 0)), \
359+
.girq_pos = (uint8_t)(DT_INST_PROP_BY_IDX(0, girqs, 1)), \
360+
.pcr_idx = (uint8_t)DT_INST_PROP_BY_IDX(inst, pcrs, 0), \
361+
.pcr_pos = (uint8_t)DT_INST_PROP_BY_IDX(inst, pcrs, 1), \
362+
.clk_sel = UTIL_CAT(XEC_PWM_BBLED_CLKSEL_, XEC_PWM_BBLED_CLKSEL(n)), \
363+
.pcfg = PINCTRL_DT_INST_DEV_CONFIG_GET(inst), \
364+
};
365+
366+
#define XEC_PWM_BBLED_DEVICE_INIT(index) \
367+
\
368+
XEC_PWM_BBLED_PINCTRL_DEF(index); \
369+
\
370+
XEC_PWM_BBLED_CONFIG(index); \
371+
\
372+
DEVICE_DT_INST_DEFINE(index, &pwm_bbled_xec_init, \
373+
NULL, \
374+
NULL, \
375+
&pwm_bbled_xec_config_##index, POST_KERNEL, \
376+
CONFIG_PWM_INIT_PRIORITY, \
377+
&pwm_bbled_xec_driver_api);
378+
379+
DT_INST_FOREACH_STATUS_OKAY(XEC_PWM_BBLED_DEVICE_INIT)

0 commit comments

Comments
 (0)