Loading...
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 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), }; |