On 3/6/25 15:19, Patrice CHOTARD wrote:
> 
> 
> On 3/6/25 11:56, 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>
>> ---
>>
>>  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),
>> +};
> 
> 
> Reviewed-by: Patrice Chotard <patrice.chot...@foss.st.com>
> 
> Thanks
> Patrice
Applied to u-boot-stm32/next

Thanks
Patrice

Reply via email to