|
| 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