289 lines
8.1 KiB
C
289 lines
8.1 KiB
C
/*
|
|
* 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_MEC15XX
|
|
#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_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)
|