// SPDX-License-Identifier: GPL-2.0-or-later /* * ECAP PWM driver * * Copyright (C) 2025 BayLibre, SAS * Author: Sukrut Bellary */ #include #include #include #include #include #include /* 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), };