]> git.ipfire.org Git - thirdparty/u-boot.git/commitdiff
pwm: ti: am33xx: Enable Auxiliary PWM using eCAP
authorSukrut Bellary <sbellary@baylibre.com>
Fri, 30 May 2025 21:22:30 +0000 (14:22 -0700)
committerTom Rini <trini@konsulko.com>
Thu, 12 Jun 2025 22:26:56 +0000 (16:26 -0600)
In am33xx SoC[1], enhanced capture (eCAP) supports auxiliary PWM (APWM).
This series adds the PWM driver support for the APWM feature for eCAP on
AM33xx.

eCAP HW also supports the capture mode. Currently, this driver only
supports APWM.

This is based on the Linux kernel driver -> drivers/pwm/pwm-tiecap.c
Version: v6.12

Tested on AM335x EVM[2].

[1] AM335x TRM - https://www.ti.com/lit/ug/spruh73q/spruh73q.pdf
[2] AM335x EVM - https://www.ti.com/tool/TMDXEVM3358

Signed-off-by: Sukrut Bellary <sbellary@baylibre.com>
drivers/pwm/Kconfig
drivers/pwm/Makefile
drivers/pwm/pwm-tiecap.c [new file with mode: 0644]

index de31265674675de1ff34c977adca280b3cb69052..e4c676d75c2b8813c0584738ca64b69bd1a1198a 100644 (file)
@@ -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.
index 76305b93bc9699f52d42ab9d4446185d4d1c160e..2682c536c6f31f138c59cc832afa231207e078af 100644 (file)
@@ -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-tiecap.c b/drivers/pwm/pwm-tiecap.c
new file mode 100644 (file)
index 0000000..cfd6c87
--- /dev/null
@@ -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),
+};