mirror of
https://github.com/u-boot/u-boot.git
synced 2025-04-19 19:34:35 +00:00

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>
205 lines
5.2 KiB
C
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),
|
|
};
|