blob: db8bffb83f7aead4e525d7e4fb651996e6b82625 [file] [log] [blame]
/*
* 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 <soc.h>
#ifndef CONFIG_SOC_SERIES_MEC1501X
#include <zephyr/drivers/clock_control/mchp_xec_clock_control.h>
#include <zephyr/drivers/interrupt_controller/intc_mchp_xec_ecia.h>
#endif
#include <zephyr/drivers/led.h>
#include <zephyr/drivers/pinctrl.h>
#include <zephyr/device.h>
#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>
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_MEC1501X
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)