u-boot/drivers/pwm/pwm-stm32.c
Cheick Traore be5523d382 pwm: stm32: add driver to support pwm with timer
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.traore@foss.st.com>
Reviewed-by: Fabrice Gasnier <fabrice.gasnier@foss.st.com>
Reviewed-by: Patrice Chotard <patrice.chotard@foss.st.com>
2025-03-12 16:39:39 +01:00

205 lines
5.2 KiB
C

// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2025, STMicroelectronics - All Rights Reserved
* Author: Cheick Traore <cheick.traore@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),
};