blob: cfd6c871b5740f8edc2701cfafc3489efded9d3a [file] [log] [blame]
Sukrut Bellary10053db2025-05-30 14:22:30 -07001// SPDX-License-Identifier: GPL-2.0-or-later
2/*
3 * ECAP PWM driver
4 *
5 * Copyright (C) 2025 BayLibre, SAS
6 * Author: Sukrut Bellary <sbellary@baylibre.com>
7 */
8
9#include <clk.h>
10#include <div64.h>
11#include <dm.h>
12#include <dm/device_compat.h>
13#include <pwm.h>
14#include <asm/io.h>
15
16/* eCAP module registers */
17#define ECAP_PWM_CAP1 0x08
18#define ECAP_PWM_CAP2 0x0C
19#define ECAP_PWM_CAP3 0x10
20#define ECAP_PWM_CAP4 0x14
21
22#define ECAP_PWM_ECCTL2 0x2A
23#define ECAP_PWM_ECCTL2_APWM_POL_LOW BIT(10)
24#define ECAP_PWM_ECCTL2_APWM_MODE BIT(9)
25#define ECAP_PWM_ECCTL2_TSCTR_FREERUN BIT(4)
26#define ECAP_PWM_ECCTL2_SYNC_SEL_DISA (BIT(7) | BIT(6))
27
28#define NSEC_PER_SEC 1000000000L
29
30enum tiecap_pwm_polarity {
31 TIECAP_PWM_POLARITY_NORMAL,
32 TIECAP_PWM_POLARITY_INVERSED
33};
34
35enum tiecap_pwm_state {
36 TIECAP_APWM_DISABLED,
37 TIECAP_APWM_ENABLED
38};
39
40struct tiecap_pwm_priv {
41 fdt_addr_t regs;
42 u32 clk_rate;
43 enum tiecap_pwm_state pwm_state;
44};
45
46static int tiecap_pwm_set_config(struct udevice *dev, uint channel,
47 uint period_ns, uint duty_ns)
48{
49 struct tiecap_pwm_priv *priv = dev_get_priv(dev);
50 u32 period_cycles, duty_cycles;
51 unsigned long long c;
52 u16 value;
53
54 c = priv->clk_rate;
55 c = c * period_ns;
56 do_div(c, NSEC_PER_SEC);
57 period_cycles = (u32)c;
58
59 if (period_cycles < 1) {
60 period_cycles = 1;
61 duty_cycles = 1;
62 } else {
63 c = priv->clk_rate;
64 c = c * duty_ns;
65 do_div(c, NSEC_PER_SEC);
66 duty_cycles = (u32)c;
67 }
68
69 value = readw(priv->regs + ECAP_PWM_ECCTL2);
70
71 /* Configure APWM mode & disable sync option */
72 value |= ECAP_PWM_ECCTL2_APWM_MODE | ECAP_PWM_ECCTL2_SYNC_SEL_DISA;
73
74 writew(value, priv->regs + ECAP_PWM_ECCTL2);
75
76 if (priv->pwm_state == TIECAP_APWM_DISABLED) {
77 /* Update active registers */
78 writel(duty_cycles, priv->regs + ECAP_PWM_CAP2);
79 writel(period_cycles, priv->regs + ECAP_PWM_CAP1);
80 } else {
81 /* Update shadow registers to configure period and
82 * compare values. This helps current pwm period to
83 * complete on reconfiguring.
84 */
85 writel(duty_cycles, priv->regs + ECAP_PWM_CAP4);
86 writel(period_cycles, priv->regs + ECAP_PWM_CAP3);
87 }
88
89 return 0;
90}
91
92static int tiecap_pwm_set_enable(struct udevice *dev, uint channel, bool enable)
93{
94 struct tiecap_pwm_priv *priv = dev_get_priv(dev);
95 u16 value;
96
97 value = readw(priv->regs + ECAP_PWM_ECCTL2);
98
99 if (enable) {
100 /*
101 * Enable 'Free run Time stamp counter mode' to start counter
102 * and 'APWM mode' to enable APWM output
103 */
104 value |= ECAP_PWM_ECCTL2_TSCTR_FREERUN | ECAP_PWM_ECCTL2_APWM_MODE;
105 priv->pwm_state = TIECAP_APWM_ENABLED;
106 } else {
107 /* Disable 'Free run Time stamp counter mode' to stop counter
108 * and 'APWM mode' to put APWM output to low
109 */
110 value &= ~(ECAP_PWM_ECCTL2_TSCTR_FREERUN | ECAP_PWM_ECCTL2_APWM_MODE);
111 priv->pwm_state = TIECAP_APWM_DISABLED;
112 }
113
114 writew(value, priv->regs + ECAP_PWM_ECCTL2);
115
116 return 0;
117}
118
119static int tiecap_pwm_set_invert(struct udevice *dev, uint channel,
120 bool polarity)
121{
122 struct tiecap_pwm_priv *priv = dev_get_priv(dev);
123 u16 value;
124
125 value = readw(priv->regs + ECAP_PWM_ECCTL2);
126
127 if (polarity == TIECAP_PWM_POLARITY_INVERSED)
128 /* Duty cycle defines LOW period of PWM */
129 value |= ECAP_PWM_ECCTL2_APWM_POL_LOW;
130 else
131 /* Duty cycle defines HIGH period of PWM */
132 value &= ~ECAP_PWM_ECCTL2_APWM_POL_LOW;
133
134 writew(value, priv->regs + ECAP_PWM_ECCTL2);
135
136 return 0;
137}
138
139static int tiecap_pwm_of_to_plat(struct udevice *dev)
140{
141 struct tiecap_pwm_priv *priv = dev_get_priv(dev);
142
143 priv->regs = dev_read_addr(dev);
144 if (priv->regs == FDT_ADDR_T_NONE) {
145 dev_err(dev, "invalid address\n");
146 return -EINVAL;
147 }
148
149 dev_dbg(dev, "regs=0x%08x\n", priv->regs);
150
151 return 0;
152}
153
154static int tiecap_pwm_probe(struct udevice *dev)
155{
156 struct tiecap_pwm_priv *priv = dev_get_priv(dev);
157 struct clk clk;
158 int err;
159
160 err = clk_get_by_name(dev, "fck", &clk);
161 if (err) {
162 dev_err(dev, "failed to get clock\n");
163 return err;
164 }
165
166 priv->clk_rate = clk_get_rate(&clk);
167 if (IS_ERR_VALUE(priv->clk_rate) || !priv->clk_rate) {
168 dev_err(dev, "failed to get clock rate\n");
169 if (IS_ERR_VALUE(priv->clk_rate))
170 return priv->clk_rate;
171
172 return -EINVAL;
173 }
174
175 return 0;
176}
177
178static const struct pwm_ops tiecap_pwm_ops = {
179 .set_config = tiecap_pwm_set_config,
180 .set_enable = tiecap_pwm_set_enable,
181 .set_invert = tiecap_pwm_set_invert,
182};
183
184static const struct udevice_id tiecap_pwm_ids[] = {
185 { .compatible = "ti,am3352-ecap" },
186 { .compatible = "ti,am33xx-ecap" },
187 { }
188};
189
190U_BOOT_DRIVER(tiecap_pwm) = {
191 .name = "tiecap_pwm",
192 .id = UCLASS_PWM,
193 .of_match = tiecap_pwm_ids,
194 .ops = &tiecap_pwm_ops,
195 .probe = tiecap_pwm_probe,
196 .of_to_plat = tiecap_pwm_of_to_plat,
197 .priv_auto = sizeof(struct tiecap_pwm_priv),
198};