diff options
Diffstat (limited to 'drivers/pwm')
-rw-r--r-- | drivers/pwm/Kconfig | 6 | ||||
-rw-r--r-- | drivers/pwm/Makefile | 1 | ||||
-rw-r--r-- | drivers/pwm/pwm-ti-ehrpwm.c | 2 | ||||
-rw-r--r-- | drivers/pwm/pwm-tiecap.c | 198 |
4 files changed, 206 insertions, 1 deletions
diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig index de312656746..e4c676d75c2 100644 --- a/drivers/pwm/Kconfig +++ b/drivers/pwm/Kconfig @@ -126,3 +126,9 @@ config PWM_TI_EHRPWM default y help PWM driver support for the EHRPWM controller found on TI SOCs. + +config PWM_TI_ECAP + bool "Enable support for ECAP PWM" + depends on DM_PWM && ARCH_OMAP2PLUS + help + PWM driver support for the ECAP controller found on TI SOCs. diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile index 76305b93bc9..2682c536c6f 100644 --- a/drivers/pwm/Makefile +++ b/drivers/pwm/Makefile @@ -25,3 +25,4 @@ 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 +obj-$(CONFIG_PWM_TI_ECAP) += pwm-tiecap.o diff --git a/drivers/pwm/pwm-ti-ehrpwm.c b/drivers/pwm/pwm-ti-ehrpwm.c index 563109ef0f8..135ea3b4321 100644 --- a/drivers/pwm/pwm-ti-ehrpwm.c +++ b/drivers/pwm/pwm-ti-ehrpwm.c @@ -399,7 +399,7 @@ static int ti_ehrpwm_of_to_plat(struct udevice *dev) return -EINVAL; } - dev_dbg(dev, "regs=0x%08lx\n", priv->regs); + dev_dbg(dev, "regs=0x%08x\n", priv->regs); return 0; } diff --git a/drivers/pwm/pwm-tiecap.c b/drivers/pwm/pwm-tiecap.c new file mode 100644 index 00000000000..cfd6c871b57 --- /dev/null +++ b/drivers/pwm/pwm-tiecap.c @@ -0,0 +1,198 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * ECAP PWM driver + * + * Copyright (C) 2025 BayLibre, SAS + * Author: Sukrut Bellary <sbellary@baylibre.com> + */ + +#include <clk.h> +#include <div64.h> +#include <dm.h> +#include <dm/device_compat.h> +#include <pwm.h> +#include <asm/io.h> + +/* eCAP module registers */ +#define ECAP_PWM_CAP1 0x08 +#define ECAP_PWM_CAP2 0x0C +#define ECAP_PWM_CAP3 0x10 +#define ECAP_PWM_CAP4 0x14 + +#define ECAP_PWM_ECCTL2 0x2A +#define ECAP_PWM_ECCTL2_APWM_POL_LOW BIT(10) +#define ECAP_PWM_ECCTL2_APWM_MODE BIT(9) +#define ECAP_PWM_ECCTL2_TSCTR_FREERUN BIT(4) +#define ECAP_PWM_ECCTL2_SYNC_SEL_DISA (BIT(7) | BIT(6)) + +#define NSEC_PER_SEC 1000000000L + +enum tiecap_pwm_polarity { + TIECAP_PWM_POLARITY_NORMAL, + TIECAP_PWM_POLARITY_INVERSED +}; + +enum tiecap_pwm_state { + TIECAP_APWM_DISABLED, + TIECAP_APWM_ENABLED +}; + +struct tiecap_pwm_priv { + fdt_addr_t regs; + u32 clk_rate; + enum tiecap_pwm_state pwm_state; +}; + +static int tiecap_pwm_set_config(struct udevice *dev, uint channel, + uint period_ns, uint duty_ns) +{ + struct tiecap_pwm_priv *priv = dev_get_priv(dev); + u32 period_cycles, duty_cycles; + unsigned long long c; + u16 value; + + c = priv->clk_rate; + c = c * period_ns; + do_div(c, NSEC_PER_SEC); + period_cycles = (u32)c; + + if (period_cycles < 1) { + period_cycles = 1; + duty_cycles = 1; + } else { + c = priv->clk_rate; + c = c * duty_ns; + do_div(c, NSEC_PER_SEC); + duty_cycles = (u32)c; + } + + value = readw(priv->regs + ECAP_PWM_ECCTL2); + + /* Configure APWM mode & disable sync option */ + value |= ECAP_PWM_ECCTL2_APWM_MODE | ECAP_PWM_ECCTL2_SYNC_SEL_DISA; + + writew(value, priv->regs + ECAP_PWM_ECCTL2); + + if (priv->pwm_state == TIECAP_APWM_DISABLED) { + /* Update active registers */ + writel(duty_cycles, priv->regs + ECAP_PWM_CAP2); + writel(period_cycles, priv->regs + ECAP_PWM_CAP1); + } else { + /* Update shadow registers to configure period and + * compare values. This helps current pwm period to + * complete on reconfiguring. + */ + writel(duty_cycles, priv->regs + ECAP_PWM_CAP4); + writel(period_cycles, priv->regs + ECAP_PWM_CAP3); + } + + return 0; +} + +static int tiecap_pwm_set_enable(struct udevice *dev, uint channel, bool enable) +{ + struct tiecap_pwm_priv *priv = dev_get_priv(dev); + u16 value; + + value = readw(priv->regs + ECAP_PWM_ECCTL2); + + if (enable) { + /* + * Enable 'Free run Time stamp counter mode' to start counter + * and 'APWM mode' to enable APWM output + */ + value |= ECAP_PWM_ECCTL2_TSCTR_FREERUN | ECAP_PWM_ECCTL2_APWM_MODE; + priv->pwm_state = TIECAP_APWM_ENABLED; + } else { + /* Disable 'Free run Time stamp counter mode' to stop counter + * and 'APWM mode' to put APWM output to low + */ + value &= ~(ECAP_PWM_ECCTL2_TSCTR_FREERUN | ECAP_PWM_ECCTL2_APWM_MODE); + priv->pwm_state = TIECAP_APWM_DISABLED; + } + + writew(value, priv->regs + ECAP_PWM_ECCTL2); + + return 0; +} + +static int tiecap_pwm_set_invert(struct udevice *dev, uint channel, + bool polarity) +{ + struct tiecap_pwm_priv *priv = dev_get_priv(dev); + u16 value; + + value = readw(priv->regs + ECAP_PWM_ECCTL2); + + if (polarity == TIECAP_PWM_POLARITY_INVERSED) + /* Duty cycle defines LOW period of PWM */ + value |= ECAP_PWM_ECCTL2_APWM_POL_LOW; + else + /* Duty cycle defines HIGH period of PWM */ + value &= ~ECAP_PWM_ECCTL2_APWM_POL_LOW; + + writew(value, priv->regs + ECAP_PWM_ECCTL2); + + return 0; +} + +static int tiecap_pwm_of_to_plat(struct udevice *dev) +{ + struct tiecap_pwm_priv *priv = dev_get_priv(dev); + + priv->regs = dev_read_addr(dev); + if (priv->regs == FDT_ADDR_T_NONE) { + dev_err(dev, "invalid address\n"); + return -EINVAL; + } + + dev_dbg(dev, "regs=0x%08x\n", priv->regs); + + return 0; +} + +static int tiecap_pwm_probe(struct udevice *dev) +{ + struct tiecap_pwm_priv *priv = dev_get_priv(dev); + struct clk clk; + int err; + + err = clk_get_by_name(dev, "fck", &clk); + if (err) { + dev_err(dev, "failed to get clock\n"); + return err; + } + + priv->clk_rate = clk_get_rate(&clk); + if (IS_ERR_VALUE(priv->clk_rate) || !priv->clk_rate) { + dev_err(dev, "failed to get clock rate\n"); + if (IS_ERR_VALUE(priv->clk_rate)) + return priv->clk_rate; + + return -EINVAL; + } + + return 0; +} + +static const struct pwm_ops tiecap_pwm_ops = { + .set_config = tiecap_pwm_set_config, + .set_enable = tiecap_pwm_set_enable, + .set_invert = tiecap_pwm_set_invert, +}; + +static const struct udevice_id tiecap_pwm_ids[] = { + { .compatible = "ti,am3352-ecap" }, + { .compatible = "ti,am33xx-ecap" }, + { } +}; + +U_BOOT_DRIVER(tiecap_pwm) = { + .name = "tiecap_pwm", + .id = UCLASS_PWM, + .of_match = tiecap_pwm_ids, + .ops = &tiecap_pwm_ops, + .probe = tiecap_pwm_probe, + .of_to_plat = tiecap_pwm_of_to_plat, + .priv_auto = sizeof(struct tiecap_pwm_priv), +}; |