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>
---

 drivers/pwm/Kconfig     |   8 ++
 drivers/pwm/Makefile    |   1 +
 drivers/pwm/pwm-stm32.c | 202 ++++++++++++++++++++++++++++++++++++++++
 3 files changed, 211 insertions(+)
 create mode 100644 drivers/pwm/pwm-stm32.c

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..6c47df893be
--- /dev/null
+++ b/drivers/pwm/pwm-stm32.c
@@ -0,0 +1,202 @@
+// 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;
+
+       /*
+        * 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),
+};
-- 
2.34.1

Reply via email to