Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

drivers: pwm: bbled: bbled-pwm driver to compatible with led api #61748

Merged
merged 2 commits into from
Mar 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
221 changes: 80 additions & 141 deletions drivers/pwm/pwm_mchp_xec_bbled.c
Original file line number Diff line number Diff line change
Expand Up @@ -24,20 +24,23 @@

LOG_MODULE_REGISTER(pwmbbled_mchp_xec, CONFIG_PWM_LOG_LEVEL);

#define XEC_PWM_BBLED_MAX_FREQ_DIV 256U

/* We will choose frequency from Device Tree */
#define XEC_PWM_BBLED_INPUT_FREQ_HI 48000000
#define XEC_PWM_BBLED_INPUT_FREQ_LO 32768

#define XEC_PWM_BBLED_MAX_FREQ_DIV 256U
albertofloyd marked this conversation as resolved.
Show resolved Hide resolved
#define XEC_PWM_BBLED_MIN_FREQ_DIV (256U * 4066U)

/* Maximum frequency BBLED-PWM can generate is scaled by
* 256 * (LD+1) where LD is in [0, 4065].
/* Hardware blink mode equation is Fpwm = Fin / (256 * (LD + 1))
* The maximum Fpwm is actually Fin / 256
* LD in [0, 4095]
*/
#define XEC_PWM_BBLED_MAX_PWM_FREQ_AHB_CLK \
(XEC_PWM_BBLED_INPUT_FREQ_HI / XEC_PWM_BBLED_MAX_FREQ_DIV)
#define XEC_PWM_BBLED_MAX_PWM_FREQ_32K_CLK \
(XEC_PWM_BBLED_INPUT_FREQ_LO / XEC_PWM_BBLED_MAX_FREQ_DIV)
#define XEC_PWM_BBLED_MAX_PWM_FREQ_HI (XEC_PWM_BBLED_INPUT_FREQ_HI / \
XEC_PWM_BBLED_MAX_FREQ_DIV)
#define XEC_PWM_BBLED_MAX_PWM_FREQ_LO (XEC_PWM_BBLED_INPUT_FREQ_LO / \
XEC_PWM_BBLED_MAX_FREQ_DIV)
#define XEC_PWM_BBLED_LD_MAX 4095
#define XEC_PWM_BBLED_DC_MIN 1u /* 0 full off */
#define XEC_PWM_BBLED_DC_MAX 254u /* 255 is full on */

/* BBLED PWM mode uses the duty cycle to set the PWM frequency:
* Fpwm = Fclock / (256 * (LD + 1)) OR
Expand Down Expand Up @@ -98,12 +101,10 @@ LOG_MODULE_REGISTER(pwmbbled_mchp_xec, CONFIG_PWM_LOG_LEVEL);

/* DT enum values */
#define XEC_PWM_BBLED_CLKSEL_32K 0
#define XEC_PWM_BBLED_CLKSEL_PCR_SLOW 1
#define XEC_PWM_BBLED_CLKSEL_AHB_48M 2
#define XEC_PWM_BBLED_CLKSEL_AHB_48M 1

#define XEC_PWM_BBLED_CLKSEL_0 XEC_PWM_BBLED_CLKSEL_32K
#define XEC_PWM_BBLED_CLKSEL_1 XEC_PWM_BBLED_CLKSEL_PCR_SLOW
#define XEC_PWM_BBLED_CLKSEL_2 XEC_PWM_BBLED_CLKSEL_AHB_48M
#define XEC_PWM_BBLED_CLKSEL_1 XEC_PWM_BBLED_CLKSEL_AHB_48M


struct bbled_regs {
Expand Down Expand Up @@ -133,52 +134,6 @@ struct bbled_xec_data {
uint32_t config;
};

/* Compute BBLED PWM delay factor to produce requested frequency.
* Fpwm = Fclk / (256 * (LD+1)) where Fclk is 48MHz or 32KHz and
* LD is a 12-bit value in [0, 4096].
* We expect 256 <= pulse_cycles <= (256 * 4096)
* period_cycles = (period * cycles_per_sec) / NSEC_PER_SEC;
* period_cycles = (Tpwm * Fclk) = Fclk / Fpwm
* period_cycles = Fclk * (256 * (LD+1)) / Fclk = (256 * (LD+1))
* (LD+1) = period_cycles / 256
*/
static uint32_t xec_pwmbb_compute_ld(const struct device *dev, uint32_t period_cycles)
{
uint32_t ld = 0;

ld = period_cycles / 256U;
if (ld > 0) {
if (ld > 4096U) {
ld = 4096U;
}
ld--;
}

return ld;
}

/* BBLED-PWM duty cycle set in 8-bit MINIMUM field of BBLED LIMITS register.
* Limits.Minimum == 0 (alwyas off, output driven low)
* == 255 (always on, output driven high)
* 1 <= Limits.Minimum <= 254 duty cycle
*/
static uint32_t xec_pwmbb_compute_dc(uint32_t period_cycles, uint32_t pulse_cycles)
{
uint32_t dc;

if (pulse_cycles >= period_cycles) {
return 255U; /* always on */
}

if (period_cycles < 256U) {
return 0; /* always off */
}

dc = (256U * pulse_cycles) / period_cycles;

return dc;
}

/* Issue: two separate registers must be updated.
* LIMITS.MIN = duty cycle = [1, 254]
* LIMITS register update takes effect immediately.
Expand All @@ -193,14 +148,14 @@ static void xec_pwmbb_progam_pwm(const struct device *dev, uint32_t ld, uint32_t
struct bbled_regs * const regs = cfg->regs;
uint32_t val;

val = regs->delay & ~(XEC_PWM_BBLED_DLY_LO_MSK);
val |= ((ld << XEC_PWM_BBLED_DLY_LO_POS) & XEC_PWM_BBLED_DLY_LO_MSK);
regs->delay = val;

val = regs->limits & ~(XEC_PWM_BBLED_LIM_MIN_MSK);
val |= ((dc << XEC_PWM_BBLED_LIM_MIN_POS) & XEC_PWM_BBLED_LIM_MIN_MSK);
regs->limits = val;

val = regs->delay & ~(XEC_PWM_BBLED_DLY_LO_MSK);
val |= ((ld << XEC_PWM_BBLED_DLY_LO_POS) & XEC_PWM_BBLED_DLY_LO_MSK);
regs->delay = val;

/* transfer new delay value from holding register */
regs->config |= BIT(XEC_PWM_BBLED_CFG_EN_UPDATE_POS);

Expand All @@ -209,117 +164,101 @@ static void xec_pwmbb_progam_pwm(const struct device *dev, uint32_t ld, uint32_t
regs->config = val;
}

/* API implementation: Set the period and pulse width for a single PWM.
* channel must be 0 as each PWM instance implements one output.
* period in clock cycles of currently configured clock.
* pulse width in clock cycles of currently configured clock.
* flags b[7:0] defined by zephyr. b[15:8] can be SoC specific.
* Bit[0] = 1 inverted, bits[7:1] specify capture features not implemented in XEC PWM.
* Note: macro PWM_MSEC() and others convert from other units to nanoseconds.
* BBLED output state is Active High. If Active low is required the GPIO pin invert
* bit must be set. The XEC PWM block also defaults to Active High but it has a bit
* to select Active Low.
* PWM API exposes this function as pwm_set_cycles and has wrapper API defined in
* pwm.h, named pwm_set which:
* Calls pwm_get_cycles_per_second to get current maximum HW frequency as cycles_per_sec
* Computes period_cycles = (period * cycles_per_sec) / NSEC_PER_SEC
* pulse_cycles = (pulse * cycles_per_sec) / NSEC_PER_SEC
* Call pwm_set_cycles passing period_cycles and pulse_cycles.
*
* BBLED PWM input frequency is 32KHz (POR default) or 48MHz selected by device tree
* at application build time.
* BBLED Fpwm = Fin / (256 * (LD + 1)) where LD = [0, 4095]
* This equation tells use the maximum number of cycles of Fin the hardware can
* generate is 256 whereas the mininum number of cycles is 256 * 4096.
*
* Fin = 32KHz
* Fpwm-min = 32768 / (256 * 4096) = 31.25 mHz = 31250000 nHz = 0x01DC_D650 nHz
* Fpwm-max = 32768 / 256 = 128 Hz = 128e9 nHz = 0x1D_CD65_0000 nHz
* Tpwm-min = 32e9 ns = 0x0007_7359_4000 ns
* Tpmw-max = 7812500 ns = 0x0077_3594 ns
*
* Fin = 48MHz
* Fpwm-min = 48e6 / (256 * 4096) = 45.7763 Hz = 45776367188 nHz = 0x000A_A87B_EE53 nHz
* Fpwm-max = 48e6 / 256 = 187500 = 1.875e14 = 0xAA87_BEE5_3800 nHz
* Tpwm-min = 5334 ns = 0x14D6 ns
* Tpwm-max = 21845333 ns = 0x014D_5555 ns
/* API implementation: Get the clock rate (cycles per second) for a single PWM output.
* BBLED in PWM mode (same as blink mode) PWM frequency = Source Frequency / (256 * (LP + 1))
* where Source Frequency is either 48 MHz or 32768 Hz and LP is the 12-bit low delay
* field of the DELAY register. We return the maximum PWM frequency which is configured
* hardware input frequency (32K or 48M) divided by 256.
*/
static int pwm_bbled_xec_check_cycles(uint32_t period_cycles, uint32_t pulse_cycles)
static int pwm_bbled_xec_get_cycles_per_sec(const struct device *dev,
uint32_t channel, uint64_t *cycles)
{
if ((period_cycles < 256U) || (period_cycles > (4096U * 256U))) {
return -EINVAL;
const struct pwm_bbled_xec_config * const cfg = dev->config;
struct bbled_regs * const regs = cfg->regs;

if (channel > 0) {
return -EIO;
}

if ((pulse_cycles < 256U) || (pulse_cycles > (4096U * 256U))) {
return -EINVAL;
if (cycles) {
if (regs->config & BIT(XEC_PWM_BBLED_CFG_CLK_SRC_48M_POS)) {
*cycles = XEC_PWM_BBLED_MAX_PWM_FREQ_HI; /* 187,500 Hz */
} else {
*cycles = XEC_PWM_BBLED_MAX_PWM_FREQ_LO; /* 128 Hz */
}
}

return 0;
}

/* API PWM set cycles:
* pulse == 0 -> pin should be constant inactive level
* pulse >= period -> pin should be constant active level
* hardware PWM (blink) mode: Fpwm = Fin_actual / (LD + 1)
* Fin_actual = XEC_PWM_BBLED_MAX_PWM_FREQ_HI or XEC_PWM_BBLED_MAX_PWM_FREQ_LO.
* period cycles and pulse cycles both zero is OFF
* pulse cycles == 0 is OFF
* pulse cycles > 0 and period cycles == 0 is OFF
* otherwise
* compute duty cycle = 256 * (pulse_cycles / period_cycles).
* compute (LD + 1) = Fin_actual / Fpwm
* program LD into bits[11:0] of Delay register
* program duty cycle info bits[7:0] of Limits register
* NOTE: flags parameter is currently used for pin invert and PWM capture.
* The BBLED HW does not support pin invert or PWM capture.
* NOTE 2: Pin invert is possible by using the MCHP function invert feature
* of the GPIO pin. This property can be set using PINCTRL at build time.
*/
static int pwm_bbled_xec_set_cycles(const struct device *dev, uint32_t channel,
uint32_t period_cycles, uint32_t pulse_cycles,
pwm_flags_t flags)
{
const struct pwm_bbled_xec_config * const cfg = dev->config;
struct bbled_regs * const regs = cfg->regs;
uint32_t dc, ld;
int ret;

if (channel > 0) {
LOG_ERR("Invalid channel: %u", channel);
return -EIO;
}

if (flags) {
/* PWM polarity not supported (yet?) */
return -ENOTSUP;
}

if ((pulse_cycles == 0U) && (period_cycles == 0U)) { /* Controller off, clocks gated */
LOG_DBG("period_cycles = %u pulse_cycles = %u", period_cycles, pulse_cycles);

if (pulse_cycles == 0u) {
/* drive pin to inactive state */
regs->config = (regs->config & ~XEC_PWM_BBLED_CFG_MODE_MSK)
| XEC_PWM_BBLED_CFG_MODE_OFF;
} else if ((pulse_cycles == 0U) && (period_cycles > 0U)) {
/* PWM mode: Limits minimum duty cycle == 0 -> LED output is fully OFF */
regs->limits &= ~XEC_PWM_BBLED_LIM_MIN_MSK;
} else if ((pulse_cycles > 0U) && (period_cycles == 0U)) {
/* PWM mode: Limits minimum duty cycle == full value -> LED output is fully ON */
regs->limits |= XEC_PWM_BBLED_LIM_MIN_MSK;
regs->delay &= ~(XEC_PWM_BBLED_DLY_LO_MSK);
} else if (pulse_cycles >= period_cycles) {
/* drive pin to active state */
regs->config = (regs->config & ~XEC_PWM_BBLED_CFG_MODE_MSK)
| XEC_PWM_BBLED_CFG_MODE_ALWAYS_ON;
regs->limits &= ~XEC_PWM_BBLED_LIM_MIN_MSK;
regs->delay &= ~(XEC_PWM_BBLED_DLY_LO_MSK);
} else {
ret = pwm_bbled_xec_check_cycles(period_cycles, pulse_cycles);
if (ret) {
LOG_DBG("Target frequency out of range");
return ret;
ld = period_cycles;
if (ld) {
ld--;
if (ld > XEC_PWM_BBLED_LD_MAX) {
ld = XEC_PWM_BBLED_LD_MAX;
}
}

ld = xec_pwmbb_compute_ld(dev, period_cycles);
dc = xec_pwmbb_compute_dc(period_cycles, pulse_cycles);
xec_pwmbb_progam_pwm(dev, ld, dc);
}

return 0;
}

/* API implementation: Get the clock rate (cycles per second) for a single PWM output.
* BBLED in PWM mode (same as blink mode) PWM frequency = Source Frequency / (256 * (LP + 1))
* where Source Frequency is either 48 MHz or 32768 Hz and LP is the 12-bit low delay
* field of the DELAY register.
*/
static int pwm_bbled_xec_get_cycles_per_sec(const struct device *dev,
uint32_t channel, uint64_t *cycles)
{
const struct pwm_bbled_xec_config * const cfg = dev->config;
struct bbled_regs * const regs = cfg->regs;
dc = ((XEC_PWM_BBLED_DC_MAX + 1) * pulse_cycles / period_cycles);
if (dc < XEC_PWM_BBLED_DC_MIN) {
dc = XEC_PWM_BBLED_DC_MIN;
} else if (dc > XEC_PWM_BBLED_DC_MAX) {
dc = XEC_PWM_BBLED_DC_MAX;
}

if (channel > 0) {
return -EIO;
}
LOG_DBG("Program: ld = 0x%0x dc = 0x%0x", ld, dc);

if (cycles) {
if (regs->config & BIT(XEC_PWM_BBLED_CFG_CLK_SRC_48M_POS)) {
*cycles = XEC_PWM_BBLED_INPUT_FREQ_HI;
} else {
*cycles = XEC_PWM_BBLED_INPUT_FREQ_LO;
}
xec_pwmbb_progam_pwm(dev, ld, dc);
}

return 0;
Expand Down
15 changes: 15 additions & 0 deletions dts/arm/microchip/mec152x/mec152xhsz-pinctrl.dtsi
Original file line number Diff line number Diff line change
Expand Up @@ -1349,4 +1349,19 @@
low-power-enable;
};

/* BBLED */
led0_gpio156_sleep: led0_gpio156_sleep {
pinmux = < MCHP_XEC_PINMUX(0156, MCHP_AF1) >;
low-power-enable;
};

led1_gpio157_sleep: led1_gpio157_sleep {
pinmux = < MCHP_XEC_PINMUX(0157, MCHP_AF1) >;
low-power-enable;
};

led2_gpio153_sleep: led2_gpio153_sleep {
pinmux = < MCHP_XEC_PINMUX(0153, MCHP_AF1) >;
low-power-enable;
};
};
22 changes: 22 additions & 0 deletions dts/arm/microchip/mec172x/mec172xnsz-pinctrl.dtsi
Original file line number Diff line number Diff line change
Expand Up @@ -1344,4 +1344,26 @@
pinmux = < MCHP_XEC_PINMUX(0140, MCHP_AF3) >;
low-power-enable;
};

/* BBLED */
led0_gpio156_sleep: led0_gpio156_sleep {
pinmux = < MCHP_XEC_PINMUX(0156, MCHP_AF1) >;
low-power-enable;
};

led1_gpio157_sleep: led1_gpio157_sleep {
pinmux = < MCHP_XEC_PINMUX(0157, MCHP_AF1) >;
low-power-enable;
};

led2_gpio153_sleep: led2_gpio153_sleep {
pinmux = < MCHP_XEC_PINMUX(0153, MCHP_AF1) >;
low-power-enable;
};

led3_gpio035_sleep: led3_gpio035_sleep {
pinmux = < MCHP_XEC_PINMUX(035, MCHP_AF4) >;
low-power-enable;
};

};
7 changes: 3 additions & 4 deletions dts/bindings/pwm/microchip,xec-pwmbbled.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,9 @@ properties:
description: |
Clock source selection: 32 KHz is available in deep sleep.
- PWM_BBLED_CLK_AHB: Clock source is the PLL based AHB clock
- PWM_BBLED_CLK_SLOW: Clock source is the PLL based PCR slow clock
- PWM_BBLED_CLK_32K: Clock source is the 32KHz domain
enum:
- "PWM_BBLED_CLK_32K"
- "PWM_BBLED_CLK_SLOW"
- "PWM_BBLED_CLK_48M"

pinctrl-0:
Expand All @@ -44,7 +42,7 @@ properties:
required: true

"#pwm-cells":
const: 2
const: 3

enable-low-power-32k:
type: boolean
Expand All @@ -55,9 +53,10 @@ properties:
When BBLED enter into Suspend state, 48MHz clock will be switched off by
PCR(Power, Clock and Reset) block. But 32KHz Core clock will be available to BBLED.
There may be a product requirement, either to blink (or) not blink LED in Suspend state.
Flag "enable-low-power-32k" shall be used along with 32KHz clock to blink (or) not blink
Property "enable-low-power-32k" shall be used along with 32KHz clock to blink (or) not blink
the LED in Suspend state.

pwm-cells:
- channel
- period
- flags
Loading
Loading