1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * PWM device driver for SUNPLUS SP7021 SoC |
4 | * |
5 | * Links: |
6 | * Reference Manual: |
7 | * https://sunplus-tibbo.atlassian.net/wiki/spaces/doc/overview |
8 | * |
9 | * Reference Manual(PWM module): |
10 | * https://sunplus.atlassian.net/wiki/spaces/doc/pages/461144198/12.+Pulse+Width+Modulation+PWM |
11 | * |
12 | * Limitations: |
13 | * - Only supports normal polarity. |
14 | * - It output low when PWM channel disabled. |
15 | * - When the parameters change, current running period will not be completed |
16 | * and run new settings immediately. |
17 | * - In .apply() PWM output need to write register FREQ and DUTY. When first write FREQ |
18 | * done and not yet write DUTY, it has short timing gap use new FREQ and old DUTY. |
19 | * |
20 | * Author: Hammer Hsieh <hammerh0314@gmail.com> |
21 | */ |
22 | #include <linux/bitfield.h> |
23 | #include <linux/clk.h> |
24 | #include <linux/io.h> |
25 | #include <linux/kernel.h> |
26 | #include <linux/mod_devicetable.h> |
27 | #include <linux/module.h> |
28 | #include <linux/platform_device.h> |
29 | #include <linux/pwm.h> |
30 | |
31 | #define SP7021_PWM_MODE0 0x000 |
32 | #define SP7021_PWM_MODE0_PWMEN(ch) BIT(ch) |
33 | #define SP7021_PWM_MODE0_BYPASS(ch) BIT(8 + (ch)) |
34 | #define SP7021_PWM_MODE1 0x004 |
35 | #define SP7021_PWM_MODE1_CNT_EN(ch) BIT(ch) |
36 | #define SP7021_PWM_FREQ(ch) (0x008 + 4 * (ch)) |
37 | #define SP7021_PWM_FREQ_MAX GENMASK(15, 0) |
38 | #define SP7021_PWM_DUTY(ch) (0x018 + 4 * (ch)) |
39 | #define SP7021_PWM_DUTY_DD_SEL(ch) FIELD_PREP(GENMASK(9, 8), ch) |
40 | #define SP7021_PWM_DUTY_MAX GENMASK(7, 0) |
41 | #define SP7021_PWM_DUTY_MASK SP7021_PWM_DUTY_MAX |
42 | #define SP7021_PWM_FREQ_SCALER 256 |
43 | #define SP7021_PWM_NUM 4 |
44 | |
45 | struct sunplus_pwm { |
46 | void __iomem *base; |
47 | struct clk *clk; |
48 | }; |
49 | |
50 | static inline struct sunplus_pwm *to_sunplus_pwm(struct pwm_chip *chip) |
51 | { |
52 | return pwmchip_get_drvdata(chip); |
53 | } |
54 | |
55 | static int sunplus_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm, |
56 | const struct pwm_state *state) |
57 | { |
58 | struct sunplus_pwm *priv = to_sunplus_pwm(chip); |
59 | u32 dd_freq, duty, mode0, mode1; |
60 | u64 clk_rate; |
61 | |
62 | if (state->polarity != pwm->state.polarity) |
63 | return -EINVAL; |
64 | |
65 | if (!state->enabled) { |
66 | /* disable pwm channel output */ |
67 | mode0 = readl(addr: priv->base + SP7021_PWM_MODE0); |
68 | mode0 &= ~SP7021_PWM_MODE0_PWMEN(pwm->hwpwm); |
69 | writel(val: mode0, addr: priv->base + SP7021_PWM_MODE0); |
70 | /* disable pwm channel clk source */ |
71 | mode1 = readl(addr: priv->base + SP7021_PWM_MODE1); |
72 | mode1 &= ~SP7021_PWM_MODE1_CNT_EN(pwm->hwpwm); |
73 | writel(val: mode1, addr: priv->base + SP7021_PWM_MODE1); |
74 | return 0; |
75 | } |
76 | |
77 | clk_rate = clk_get_rate(clk: priv->clk); |
78 | |
79 | /* |
80 | * The following calculations might overflow if clk is bigger |
81 | * than 256 GHz. In practise it's 202.5MHz, so this limitation |
82 | * is only theoretic. |
83 | */ |
84 | if (clk_rate > (u64)SP7021_PWM_FREQ_SCALER * NSEC_PER_SEC) |
85 | return -EINVAL; |
86 | |
87 | /* |
88 | * With clk_rate limited above we have dd_freq <= state->period, |
89 | * so this cannot overflow. |
90 | */ |
91 | dd_freq = mul_u64_u64_div_u64(a: clk_rate, mul: state->period, div: (u64)SP7021_PWM_FREQ_SCALER |
92 | * NSEC_PER_SEC); |
93 | |
94 | if (dd_freq == 0) |
95 | return -EINVAL; |
96 | |
97 | if (dd_freq > SP7021_PWM_FREQ_MAX) |
98 | dd_freq = SP7021_PWM_FREQ_MAX; |
99 | |
100 | writel(val: dd_freq, addr: priv->base + SP7021_PWM_FREQ(pwm->hwpwm)); |
101 | |
102 | /* cal and set pwm duty */ |
103 | mode0 = readl(addr: priv->base + SP7021_PWM_MODE0); |
104 | mode0 |= SP7021_PWM_MODE0_PWMEN(pwm->hwpwm); |
105 | mode1 = readl(addr: priv->base + SP7021_PWM_MODE1); |
106 | mode1 |= SP7021_PWM_MODE1_CNT_EN(pwm->hwpwm); |
107 | if (state->duty_cycle == state->period) { |
108 | /* PWM channel output = high */ |
109 | mode0 |= SP7021_PWM_MODE0_BYPASS(pwm->hwpwm); |
110 | duty = SP7021_PWM_DUTY_DD_SEL(pwm->hwpwm) | SP7021_PWM_DUTY_MAX; |
111 | } else { |
112 | mode0 &= ~SP7021_PWM_MODE0_BYPASS(pwm->hwpwm); |
113 | /* |
114 | * duty_ns <= period_ns 27 bits, clk_rate 28 bits, won't overflow. |
115 | */ |
116 | duty = mul_u64_u64_div_u64(a: state->duty_cycle, mul: clk_rate, |
117 | div: (u64)dd_freq * NSEC_PER_SEC); |
118 | duty = SP7021_PWM_DUTY_DD_SEL(pwm->hwpwm) | duty; |
119 | } |
120 | writel(val: duty, addr: priv->base + SP7021_PWM_DUTY(pwm->hwpwm)); |
121 | writel(val: mode1, addr: priv->base + SP7021_PWM_MODE1); |
122 | writel(val: mode0, addr: priv->base + SP7021_PWM_MODE0); |
123 | |
124 | return 0; |
125 | } |
126 | |
127 | static int sunplus_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm, |
128 | struct pwm_state *state) |
129 | { |
130 | struct sunplus_pwm *priv = to_sunplus_pwm(chip); |
131 | u32 mode0, dd_freq, duty; |
132 | u64 clk_rate; |
133 | |
134 | mode0 = readl(addr: priv->base + SP7021_PWM_MODE0); |
135 | |
136 | if (mode0 & BIT(pwm->hwpwm)) { |
137 | clk_rate = clk_get_rate(clk: priv->clk); |
138 | dd_freq = readl(addr: priv->base + SP7021_PWM_FREQ(pwm->hwpwm)); |
139 | duty = readl(addr: priv->base + SP7021_PWM_DUTY(pwm->hwpwm)); |
140 | duty = FIELD_GET(SP7021_PWM_DUTY_MASK, duty); |
141 | /* |
142 | * dd_freq 16 bits, SP7021_PWM_FREQ_SCALER 8 bits |
143 | * NSEC_PER_SEC 30 bits, won't overflow. |
144 | */ |
145 | state->period = DIV64_U64_ROUND_UP((u64)dd_freq * (u64)SP7021_PWM_FREQ_SCALER |
146 | * NSEC_PER_SEC, clk_rate); |
147 | /* |
148 | * dd_freq 16 bits, duty 8 bits, NSEC_PER_SEC 30 bits, won't overflow. |
149 | */ |
150 | state->duty_cycle = DIV64_U64_ROUND_UP((u64)dd_freq * (u64)duty * NSEC_PER_SEC, |
151 | clk_rate); |
152 | state->enabled = true; |
153 | } else { |
154 | state->enabled = false; |
155 | } |
156 | |
157 | state->polarity = PWM_POLARITY_NORMAL; |
158 | |
159 | return 0; |
160 | } |
161 | |
162 | static const struct pwm_ops sunplus_pwm_ops = { |
163 | .apply = sunplus_pwm_apply, |
164 | .get_state = sunplus_pwm_get_state, |
165 | }; |
166 | |
167 | static void sunplus_pwm_clk_release(void *data) |
168 | { |
169 | struct clk *clk = data; |
170 | |
171 | clk_disable_unprepare(clk); |
172 | } |
173 | |
174 | static int sunplus_pwm_probe(struct platform_device *pdev) |
175 | { |
176 | struct device *dev = &pdev->dev; |
177 | struct pwm_chip *chip; |
178 | struct sunplus_pwm *priv; |
179 | int ret; |
180 | |
181 | chip = devm_pwmchip_alloc(parent: dev, SP7021_PWM_NUM, sizeof_priv: sizeof(*priv)); |
182 | if (IS_ERR(ptr: chip)) |
183 | return PTR_ERR(ptr: chip); |
184 | priv = to_sunplus_pwm(chip); |
185 | |
186 | priv->base = devm_platform_ioremap_resource(pdev, index: 0); |
187 | if (IS_ERR(ptr: priv->base)) |
188 | return PTR_ERR(ptr: priv->base); |
189 | |
190 | priv->clk = devm_clk_get(dev, NULL); |
191 | if (IS_ERR(ptr: priv->clk)) |
192 | return dev_err_probe(dev, err: PTR_ERR(ptr: priv->clk), |
193 | fmt: "get pwm clock failed\n" ); |
194 | |
195 | ret = clk_prepare_enable(clk: priv->clk); |
196 | if (ret < 0) { |
197 | dev_err(dev, "failed to enable clock: %d\n" , ret); |
198 | return ret; |
199 | } |
200 | |
201 | ret = devm_add_action_or_reset(dev, sunplus_pwm_clk_release, priv->clk); |
202 | if (ret < 0) { |
203 | dev_err(dev, "failed to release clock: %d\n" , ret); |
204 | return ret; |
205 | } |
206 | |
207 | chip->ops = &sunplus_pwm_ops; |
208 | |
209 | ret = devm_pwmchip_add(dev, chip); |
210 | if (ret < 0) |
211 | return dev_err_probe(dev, err: ret, fmt: "Cannot register sunplus PWM\n" ); |
212 | |
213 | return 0; |
214 | } |
215 | |
216 | static const struct of_device_id sunplus_pwm_of_match[] = { |
217 | { .compatible = "sunplus,sp7021-pwm" , }, |
218 | {} |
219 | }; |
220 | MODULE_DEVICE_TABLE(of, sunplus_pwm_of_match); |
221 | |
222 | static struct platform_driver sunplus_pwm_driver = { |
223 | .probe = sunplus_pwm_probe, |
224 | .driver = { |
225 | .name = "sunplus-pwm" , |
226 | .of_match_table = sunplus_pwm_of_match, |
227 | }, |
228 | }; |
229 | module_platform_driver(sunplus_pwm_driver); |
230 | |
231 | MODULE_DESCRIPTION("Sunplus SoC PWM Driver" ); |
232 | MODULE_AUTHOR("Hammer Hsieh <hammerh0314@gmail.com>" ); |
233 | MODULE_LICENSE("GPL" ); |
234 | |