You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
288 lines
8.1 KiB
288 lines
8.1 KiB
/* |
|
* 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 DEVICE_API(led, 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)
|
|
|