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