On 3/11/25 15:30, Cheick Traore wrote: > Add driver to support pwm on STM32MP1X SoCs. The PWM signal is generated > using a multifuntion timer which provide a pwm feature. Clock rate and > addresses are retrieved from the multifunction timer driver. > > Signed-off-by: Cheick Traore <cheick.tra...@foss.st.com> > --- > > Changes in v2: > - add the check of duty_ns and period_ns parameters in pwm-stm32 driver > > drivers/pwm/Kconfig | 8 ++ > drivers/pwm/Makefile | 1 + > drivers/pwm/pwm-stm32.c | 205 ++++++++++++++++++++++++++++++++++++++++ > 3 files changed, 214 insertions(+) > create mode 100644 drivers/pwm/pwm-stm32.c
Hi Cheick, Reviewed-by: Fabrice Gasnier <fabrice.gasn...@foss.st.com> Thanks, Best Regards, Fabrice > > diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig > index 6e79868d0ef..de312656746 100644 > --- a/drivers/pwm/Kconfig > +++ b/drivers/pwm/Kconfig > @@ -105,6 +105,14 @@ config PWM_TEGRA > 32KHz clock is supported by the driver but the duty cycle is > configurable. > > +config PWM_STM32 > + bool "Enable support for STM32 PWM" > + depends on DM_PWM && MFD_STM32_TIMERS > + help > + This enables PWM driver for STMicroelectronics STM32 pulse width > + modulation. It uses STM32 timer devices that can have up to 4 output > + channels, with complementary outputs and configurable polarity. > + > config PWM_SUNXI > bool "Enable support for the Allwinner Sunxi PWM" > depends on DM_PWM > diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile > index e4d10c8dc3e..76305b93bc9 100644 > --- a/drivers/pwm/Makefile > +++ b/drivers/pwm/Makefile > @@ -22,5 +22,6 @@ obj-$(CONFIG_PWM_ROCKCHIP) += rk_pwm.o > obj-$(CONFIG_PWM_SANDBOX) += sandbox_pwm.o > obj-$(CONFIG_PWM_SIFIVE) += pwm-sifive.o > obj-$(CONFIG_PWM_TEGRA) += tegra_pwm.o > +obj-$(CONFIG_PWM_STM32) += pwm-stm32.o > obj-$(CONFIG_PWM_SUNXI) += sunxi_pwm.o > obj-$(CONFIG_PWM_TI_EHRPWM) += pwm-ti-ehrpwm.o > diff --git a/drivers/pwm/pwm-stm32.c b/drivers/pwm/pwm-stm32.c > new file mode 100644 > index 00000000000..5fa649b5903 > --- /dev/null > +++ b/drivers/pwm/pwm-stm32.c > @@ -0,0 +1,205 @@ > +// SPDX-License-Identifier: GPL-2.0-only > +/* > + * Copyright (C) 2025, STMicroelectronics - All Rights Reserved > + * Author: Cheick Traore <cheick.tra...@foss.st.com> > + * > + * Originally based on the Linux kernel v6.10 drivers/pwm/pwm-stm32.c. > + */ > + > +#include <div64.h> > +#include <dm.h> > +#include <pwm.h> > +#include <asm/io.h> > +#include <asm/arch/timers.h> > +#include <dm/device_compat.h> > +#include <linux/time.h> > + > +#define CCMR_CHANNEL_SHIFT 8 > +#define CCMR_CHANNEL_MASK 0xFF > + > +struct stm32_pwm_priv { > + bool have_complementary_output; > + bool invert_polarity; > +}; > + > +static u32 active_channels(struct stm32_timers_plat *plat) > +{ > + return readl(plat->base + TIM_CCER) & TIM_CCER_CCXE; > +} > + > +static int stm32_pwm_set_config(struct udevice *dev, uint channel, > + uint period_ns, uint duty_ns) > +{ > + struct stm32_timers_plat *plat = dev_get_plat(dev_get_parent(dev)); > + struct stm32_timers_priv *priv = dev_get_priv(dev_get_parent(dev)); > + unsigned long long prd, div, dty; > + unsigned int prescaler = 0; > + u32 ccmr, mask, shift; > + > + if (duty_ns > period_ns) > + return -EINVAL; > + > + /* > + * Period and prescaler values depends on clock rate > + * First we need to find the minimal value for prescaler such that > + * > + * period_ns * clkrate > + * ------------------------------ < max_arr + 1 > + * NSEC_PER_SEC * (prescaler + 1) > + * > + * This equation is equivalent to > + * > + * period_ns * clkrate > + * ---------------------------- < prescaler + 1 > + * NSEC_PER_SEC * (max_arr + 1) > + * > + * Using integer division and knowing that the right hand side is > + * integer, this is further equivalent to > + * > + * (period_ns * clkrate) // (NSEC_PER_SEC * (max_arr + 1)) ≤ prescaler > + */ > + > + div = (unsigned long long)priv->rate * period_ns; > + do_div(div, NSEC_PER_SEC); > + prd = div; > + > + do_div(div, priv->max_arr + 1); > + prescaler = div; > + if (prescaler > MAX_TIM_PSC) > + return -EINVAL; > + > + do_div(prd, prescaler + 1); > + if (!prd) > + return -EINVAL; > + > + /* > + * All channels share the same prescaler and counter so when two > + * channels are active at the same time we can't change them > + */ > + if (active_channels(plat) & ~(1 << channel * 4)) { > + u32 psc, arr; > + > + psc = readl(plat->base + TIM_PSC); > + arr = readl(plat->base + TIM_ARR); > + if (psc != prescaler || arr != prd - 1) > + return -EBUSY; > + } > + > + writel(prescaler, plat->base + TIM_PSC); > + writel(prd - 1, plat->base + TIM_ARR); > + setbits_le32(plat->base + TIM_CR1, TIM_CR1_ARPE); > + > + /* Calculate the duty cycles */ > + dty = prd * duty_ns; > + do_div(dty, period_ns); > + > + writel(dty, plat->base + TIM_CCRx(channel + 1)); > + > + /* Configure output mode */ > + shift = (channel & 0x1) * CCMR_CHANNEL_SHIFT; > + ccmr = (TIM_CCMR_PE | TIM_CCMR_M1) << shift; > + mask = CCMR_CHANNEL_MASK << shift; > + if (channel < 2) > + clrsetbits_le32(plat->base + TIM_CCMR1, mask, ccmr); > + else > + clrsetbits_le32(plat->base + TIM_CCMR2, mask, ccmr); > + > + setbits_le32(plat->base + TIM_BDTR, TIM_BDTR_MOE); > + > + return 0; > +} > + > +static int stm32_pwm_set_enable(struct udevice *dev, uint channel, > + bool enable) > +{ > + struct stm32_timers_plat *plat = dev_get_plat(dev_get_parent(dev)); > + struct stm32_pwm_priv *priv = dev_get_priv(dev); > + u32 mask; > + > + /* Enable channel */ > + mask = TIM_CCER_CC1E << (channel * 4); > + if (priv->have_complementary_output) > + mask |= TIM_CCER_CC1NE << (channel * 4); > + > + if (enable) { > + setbits_le32(plat->base + TIM_CCER, mask); > + /* Make sure that registers are updated */ > + setbits_le32(plat->base + TIM_EGR, TIM_EGR_UG); > + /* Enable controller */ > + setbits_le32(plat->base + TIM_CR1, TIM_CR1_CEN); > + } else { > + clrbits_le32(plat->base + TIM_CCER, mask); > + /* When all channels are disabled, we can disable the > controller */ > + if (!active_channels(plat)) > + clrbits_le32(plat->base + TIM_CR1, TIM_CR1_CEN); > + } > + > + return 0; > +} > + > +static int stm32_pwm_set_invert(struct udevice *dev, uint channel, > + bool polarity) > +{ > + struct stm32_timers_plat *plat = dev_get_plat(dev_get_parent(dev)); > + struct stm32_pwm_priv *priv = dev_get_priv(dev); > + u32 mask; > + > + mask = TIM_CCER_CC1P << (channel * 4); > + if (priv->have_complementary_output) > + mask |= TIM_CCER_CC1NP << (channel * 4); > + > + clrsetbits_le32(plat->base + TIM_CCER, mask, polarity ? mask : 0); > + > + return 0; > +} > + > +static void stm32_pwm_detect_complementary(struct udevice *dev) > +{ > + struct stm32_timers_plat *plat = dev_get_plat(dev_get_parent(dev)); > + struct stm32_pwm_priv *priv = dev_get_priv(dev); > + u32 ccer; > + > + /* > + * If complementary bit doesn't exist writing 1 will have no > + * effect so we can detect it. > + */ > + setbits_le32(plat->base + TIM_CCER, TIM_CCER_CC1NE); > + ccer = readl(plat->base + TIM_CCER); > + clrbits_le32(plat->base + TIM_CCER, TIM_CCER_CC1NE); > + > + priv->have_complementary_output = (ccer != 0); > +} > + > +static int stm32_pwm_probe(struct udevice *dev) > +{ > + struct stm32_timers_priv *timer = dev_get_priv(dev_get_parent(dev)); > + > + if (timer->rate > 1000000000) { > + dev_err(dev, "Clock freq too high (%lu)\n", timer->rate); > + return -EINVAL; > + } > + > + stm32_pwm_detect_complementary(dev); > + > + return 0; > +} > + > +static const struct pwm_ops stm32_pwm_ops = { > + .set_config = stm32_pwm_set_config, > + .set_enable = stm32_pwm_set_enable, > + .set_invert = stm32_pwm_set_invert, > +}; > + > +static const struct udevice_id stm32_pwm_ids[] = { > + { .compatible = "st,stm32-pwm" }, > + { } > +}; > + > +U_BOOT_DRIVER(stm32_pwm) = { > + .name = "stm32_pwm", > + .id = UCLASS_PWM, > + .of_match = stm32_pwm_ids, > + .ops = &stm32_pwm_ops, > + .probe = stm32_pwm_probe, > + .priv_auto = sizeof(struct stm32_pwm_priv), > +};