/* * Copyright (c) 2022 Microchip Technology Inc. * * SPDX-License-Identifier: Apache-2.0 */ #define DT_DRV_COMPAT microchip_xec_bbled /** * @file * @brief Microchip Breathing-Blinking LED controller */ #include #ifndef CONFIG_SOC_SERIES_MEC15XX #include #include #endif #include #include #include #include #include LOG_MODULE_REGISTER(led_xec, CONFIG_LED_LOG_LEVEL); /* Same BBLED hardware block in MEC15xx and MEC172x families * Config register */ #define XEC_BBLED_CFG_MSK 0x1ffffu #define XEC_BBLED_CFG_MODE_POS 0 #define XEC_BBLED_CFG_MODE_MSK 0x3u #define XEC_BBLED_CFG_MODE_OFF 0 #define XEC_BBLED_CFG_MODE_BREATHING 0x1u #define XEC_BBLED_CFG_MODE_PWM 0x2u #define XEC_BBLED_CFG_MODE_ALWAYS_ON 0x3u #define XEC_BBLED_CFG_CLK_SRC_48M_POS 2 #define XEC_BBLED_CFG_EN_UPDATE_POS 6 #define XEC_BBLED_CFG_RST_PWM_POS 7 #define XEC_BBLED_CFG_WDT_RLD_POS 8 #define XEC_BBLED_CFG_WDT_RLD_MSK0 0xffu #define XEC_BBLED_CFG_WDT_RLD_MSK 0xff00u #define XEC_BBLED_CFG_WDT_RLD_DFLT 0x1400u /* Limits register */ #define XEC_BBLED_LIM_MSK 0xffffu #define XEC_BBLED_LIM_MIN_POS 0 #define XEC_BBLED_LIM_MIN_MSK 0xffu #define XEC_BBLED_LIM_MAX_POS 8 #define XEC_BBLED_LIM_MAX_MSK 0xff00u /* Delay register */ #define XEC_BBLED_DLY_MSK 0xffffffu #define XEC_BBLED_DLY_LO_POS 0 #define XEC_BBLED_DLY_LO_MSK 0xfffu #define XEC_BBLED_DLY_HI_POS 12 #define XEC_BBLED_DLY_HI_MSK 0xfff000u /* Update step size and update interval registers implement * eight 4-bit fields numbered 0 to 7 */ #define XEC_BBLED_UPD_SSI_POS(n) ((uint32_t)(n) * 4u) #define XEC_BBLED_UPD_SSI0_MSK(n) ((uint32_t)0xfu << XEC_BBLED_UPD_SSI_POS(n)) /* Output delay register: b[7:0] is delay in clock source units */ #define XEC_BBLED_OUT_DLY_MSK 0xffu /* Delay.Lo register field */ #define XEC_BBLED_MAX_PRESCALER 4095u /* Blink mode source frequency is 32768 Hz */ #define XEC_BBLED_BLINK_CLK_SRC_HZ 32768u /* Fblink = 32768 / (256 * (prescaler+1)) * prescaler is 12 bit. * Maximum Fblink = 128 Hz or 7.8125 ms * Minimum Fblink = 32.25 mHz or 32000 ms */ #define XEC_BBLED_BLINK_PERIOD_MAX_MS 32000u #define XEC_BBLED_BLINK_PERIOD_MIN_MS 8u struct xec_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; }; struct xec_bbled_config { struct xec_bbled_regs * const regs; const struct pinctrl_dev_config *pcfg; uint8_t pcr_id; uint8_t pcr_pos; }; /* delay_on and delay_off are in milliseconds * (prescale+1) = (32768 * Tblink_ms) / (256 * 1000) * requires caller to limit delay_on and delay_off based * on BBLED 32KHz minimum/maximum values. */ static uint32_t calc_blink_32k_prescaler(uint32_t delay_on, uint32_t delay_off) { uint32_t temp = ((delay_on + delay_off) * XEC_BBLED_BLINK_CLK_SRC_HZ) / (256U * 1000U); uint32_t prescaler = 0; if (temp) { temp--; if (temp > XEC_BBLED_MAX_PRESCALER) { prescaler = XEC_BBLED_MAX_PRESCALER; } else { prescaler = (uint32_t)temp; } } return prescaler; } /* return duty cycle scaled to [0, 255] * caller must insure delay_on and delay_off are in hardware range. */ static uint32_t calc_blink_duty_cycle(uint32_t delay_on, uint32_t delay_off) { return (256U * delay_on) / (delay_on + delay_off); } /* Enable HW blinking of the LED. * delay_on = on time in milliseconds * delay_off = off time in milliseconds * BBLED blinking mode uses an 8-bit accumulator and an 8-bit duty cycle * register. The duty cycle register is programmed once and the * accumulator is used as an 8-bit up counter. * The counter uses the 32768 Hz clock and is pre-scaled by the delay * counter. Maximum blink rate is 128Hz to 32.25 mHz (7.8 ms to 32 seconds). * 8-bit duty cycle values: 0x00 = full off, 0xff = full on. * Fblink = 32768 / ((prescale + 1) * 256) * HiWidth (seconds) = (1/Fblink) * (duty_cycle / 256) * LoWidth (seconds) = (1/Fblink) * ((1 - duty_cycle) / 256) * duty_cycle in [0, 1]. Register value for duty cycle is * scaled to [0, 255]. * prescale is delay register low delay field, bits[11:0] * duty_cycle is limits register minimum field, bits[7:0] */ static int xec_bbled_blink(const struct device *dev, uint32_t led, uint32_t delay_on, uint32_t delay_off) { const struct xec_bbled_config * const config = dev->config; struct xec_bbled_regs * const regs = config->regs; uint32_t period, prescaler, dcs; if (led) { return -EINVAL; } /* insure period will not overflow uin32_t */ if ((delay_on > XEC_BBLED_BLINK_PERIOD_MAX_MS) || (delay_off > XEC_BBLED_BLINK_PERIOD_MAX_MS)) { return -EINVAL; } period = delay_on + delay_off; if ((period < XEC_BBLED_BLINK_PERIOD_MIN_MS) || (period > XEC_BBLED_BLINK_PERIOD_MAX_MS)) { return -EINVAL; } prescaler = calc_blink_32k_prescaler(delay_on, delay_off); dcs = calc_blink_duty_cycle(delay_on, delay_off); regs->config = (regs->config & ~(XEC_BBLED_CFG_MODE_MSK)) | XEC_BBLED_CFG_MODE_OFF; regs->delay = (regs->delay & ~(XEC_BBLED_DLY_LO_MSK)) | (prescaler & XEC_BBLED_DLY_LO_MSK); regs->limits = (regs->limits & ~(XEC_BBLED_LIM_MIN_MSK)) | (dcs & XEC_BBLED_LIM_MIN_MSK); regs->config = (regs->config & ~(XEC_BBLED_CFG_MODE_MSK)) | XEC_BBLED_CFG_MODE_PWM; regs->config |= BIT(XEC_BBLED_CFG_EN_UPDATE_POS); return 0; } static int xec_bbled_on(const struct device *dev, uint32_t led) { const struct xec_bbled_config * const config = dev->config; struct xec_bbled_regs * const regs = config->regs; if (led) { return -EINVAL; } regs->config = (regs->config & ~(XEC_BBLED_CFG_MODE_MSK)) | XEC_BBLED_CFG_MODE_ALWAYS_ON; return 0; } static int xec_bbled_off(const struct device *dev, uint32_t led) { const struct xec_bbled_config * const config = dev->config; struct xec_bbled_regs * const regs = config->regs; if (led) { return -EINVAL; } regs->config = (regs->config & ~(XEC_BBLED_CFG_MODE_MSK)) | XEC_BBLED_CFG_MODE_OFF; return 0; } #ifdef CONFIG_SOC_SERIES_MEC15XX static inline void xec_bbled_slp_en_clr(const struct device *dev) { const struct xec_bbled_config * const cfg = dev->config; enum pcr_id pcr_val = PCR_MAX_ID; switch (cfg->pcr_pos) { case MCHP_PCR3_LED0_POS: pcr_val = PCR_LED0; break; case MCHP_PCR3_LED1_POS: pcr_val = PCR_LED1; break; case MCHP_PCR3_LED2_POS: pcr_val = PCR_LED2; break; default: return; } mchp_pcr_periph_slp_ctrl(pcr_val, 0); } #else static inline void xec_bbled_slp_en_clr(const struct device *dev) { const struct xec_bbled_config * const cfg = dev->config; z_mchp_xec_pcr_periph_sleep(cfg->pcr_id, cfg->pcr_pos, 0); } #endif static int xec_bbled_init(const struct device *dev) { const struct xec_bbled_config * const config = dev->config; struct xec_bbled_regs * const regs = config->regs; int ret; xec_bbled_slp_en_clr(dev); /* soft reset, disable BBLED WDT, set clock source to default (32KHz domain) */ regs->config |= BIT(XEC_BBLED_CFG_RST_PWM_POS); regs->config = XEC_BBLED_CFG_MODE_OFF; ret = pinctrl_apply_state(config->pcfg, PINCTRL_STATE_DEFAULT); if (ret != 0) { LOG_ERR("XEC BBLED pinctrl setup failed (%d)", ret); } return ret; } static const struct led_driver_api xec_bbled_api = { .on = xec_bbled_on, .off = xec_bbled_off, .blink = xec_bbled_blink, }; #define XEC_BBLED_PINCTRL_DEF(i) PINCTRL_DT_INST_DEFINE(i) #define XEC_BBLED_CONFIG(i) \ static struct xec_bbled_config xec_bbled_config_##i = { \ .regs = (struct xec_bbled_regs * const)DT_INST_REG_ADDR(i), \ .pcfg = PINCTRL_DT_INST_DEV_CONFIG_GET(i), \ .pcr_id = (uint8_t)DT_INST_PROP_BY_IDX(i, pcrs, 0), \ .pcr_pos = (uint8_t)DT_INST_PROP_BY_IDX(i, pcrs, 1), \ } #define XEC_BBLED_DEVICE(i) \ \ XEC_BBLED_PINCTRL_DEF(i); \ \ XEC_BBLED_CONFIG(i); \ \ DEVICE_DT_INST_DEFINE(i, &xec_bbled_init, NULL, \ NULL, &xec_bbled_config_##i, \ POST_KERNEL, CONFIG_LED_INIT_PRIORITY, \ &xec_bbled_api); DT_INST_FOREACH_STATUS_OKAY(XEC_BBLED_DEVICE)