diff options
author | Yash Shah <yash.shah@sifive.com> | 2019-06-11 01:44:44 -0400 |
---|---|---|
committer | Thierry Reding <thierry.reding@gmail.com> | 2019-06-25 08:48:12 -0400 |
commit | 9e37a53eb05114b663ded9f1f2f69c7fec6c5913 (patch) | |
tree | d3989f9db69bb62688bf8e50113d6739696c18db | |
parent | daa78cc3408e7145d99462d51c5f4c6b0cb2af9c (diff) |
pwm: sifive: Add a driver for SiFive SoC PWM
Adds a PWM driver for PWM chip present in SiFive's HiFive Unleashed SoC.
Signed-off-by: Wesley W. Terpstra <wesley@sifive.com>
[Atish: Various fixes and code cleanup]
Signed-off-by: Atish Patra <atish.patra@wdc.com>
Signed-off-by: Yash Shah <yash.shah@sifive.com>
Reviewed-by: Uwe Kleine-König <u.kleine-koenig@pengutronix.de>
Signed-off-by: Thierry Reding <thierry.reding@gmail.com>
-rw-r--r-- | drivers/pwm/Kconfig | 11 | ||||
-rw-r--r-- | drivers/pwm/Makefile | 1 | ||||
-rw-r--r-- | drivers/pwm/pwm-sifive.c | 339 |
3 files changed, 351 insertions, 0 deletions
diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig index 1311b54089be..f7eacac0d713 100644 --- a/drivers/pwm/Kconfig +++ b/drivers/pwm/Kconfig | |||
@@ -400,6 +400,17 @@ config PWM_SAMSUNG | |||
400 | To compile this driver as a module, choose M here: the module | 400 | To compile this driver as a module, choose M here: the module |
401 | will be called pwm-samsung. | 401 | will be called pwm-samsung. |
402 | 402 | ||
403 | config PWM_SIFIVE | ||
404 | tristate "SiFive PWM support" | ||
405 | depends on OF | ||
406 | depends on COMMON_CLK | ||
407 | depends on RISCV || COMPILE_TEST | ||
408 | help | ||
409 | Generic PWM framework driver for SiFive SoCs. | ||
410 | |||
411 | To compile this driver as a module, choose M here: the module | ||
412 | will be called pwm-sifive. | ||
413 | |||
403 | config PWM_SPEAR | 414 | config PWM_SPEAR |
404 | tristate "STMicroelectronics SPEAr PWM support" | 415 | tristate "STMicroelectronics SPEAr PWM support" |
405 | depends on PLAT_SPEAR | 416 | depends on PLAT_SPEAR |
diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile index c368599d36c0..76b555b51887 100644 --- a/drivers/pwm/Makefile +++ b/drivers/pwm/Makefile | |||
@@ -39,6 +39,7 @@ obj-$(CONFIG_PWM_RCAR) += pwm-rcar.o | |||
39 | obj-$(CONFIG_PWM_RENESAS_TPU) += pwm-renesas-tpu.o | 39 | obj-$(CONFIG_PWM_RENESAS_TPU) += pwm-renesas-tpu.o |
40 | obj-$(CONFIG_PWM_ROCKCHIP) += pwm-rockchip.o | 40 | obj-$(CONFIG_PWM_ROCKCHIP) += pwm-rockchip.o |
41 | obj-$(CONFIG_PWM_SAMSUNG) += pwm-samsung.o | 41 | obj-$(CONFIG_PWM_SAMSUNG) += pwm-samsung.o |
42 | obj-$(CONFIG_PWM_SIFIVE) += pwm-sifive.o | ||
42 | obj-$(CONFIG_PWM_SPEAR) += pwm-spear.o | 43 | obj-$(CONFIG_PWM_SPEAR) += pwm-spear.o |
43 | obj-$(CONFIG_PWM_STI) += pwm-sti.o | 44 | obj-$(CONFIG_PWM_STI) += pwm-sti.o |
44 | obj-$(CONFIG_PWM_STM32) += pwm-stm32.o | 45 | obj-$(CONFIG_PWM_STM32) += pwm-stm32.o |
diff --git a/drivers/pwm/pwm-sifive.c b/drivers/pwm/pwm-sifive.c new file mode 100644 index 000000000000..a7c107f19e66 --- /dev/null +++ b/drivers/pwm/pwm-sifive.c | |||
@@ -0,0 +1,339 @@ | |||
1 | // SPDX-License-Identifier: GPL-2.0 | ||
2 | /* | ||
3 | * Copyright (C) 2017-2018 SiFive | ||
4 | * For SiFive's PWM IP block documentation please refer Chapter 14 of | ||
5 | * Reference Manual : https://static.dev.sifive.com/FU540-C000-v1.0.pdf | ||
6 | * | ||
7 | * Limitations: | ||
8 | * - When changing both duty cycle and period, we cannot prevent in | ||
9 | * software that the output might produce a period with mixed | ||
10 | * settings (new period length and old duty cycle). | ||
11 | * - The hardware cannot generate a 100% duty cycle. | ||
12 | * - The hardware generates only inverted output. | ||
13 | */ | ||
14 | #include <linux/clk.h> | ||
15 | #include <linux/io.h> | ||
16 | #include <linux/module.h> | ||
17 | #include <linux/platform_device.h> | ||
18 | #include <linux/pwm.h> | ||
19 | #include <linux/slab.h> | ||
20 | #include <linux/bitfield.h> | ||
21 | |||
22 | /* Register offsets */ | ||
23 | #define PWM_SIFIVE_PWMCFG 0x0 | ||
24 | #define PWM_SIFIVE_PWMCOUNT 0x8 | ||
25 | #define PWM_SIFIVE_PWMS 0x10 | ||
26 | #define PWM_SIFIVE_PWMCMP0 0x20 | ||
27 | |||
28 | /* PWMCFG fields */ | ||
29 | #define PWM_SIFIVE_PWMCFG_SCALE GENMASK(3, 0) | ||
30 | #define PWM_SIFIVE_PWMCFG_STICKY BIT(8) | ||
31 | #define PWM_SIFIVE_PWMCFG_ZERO_CMP BIT(9) | ||
32 | #define PWM_SIFIVE_PWMCFG_DEGLITCH BIT(10) | ||
33 | #define PWM_SIFIVE_PWMCFG_EN_ALWAYS BIT(12) | ||
34 | #define PWM_SIFIVE_PWMCFG_EN_ONCE BIT(13) | ||
35 | #define PWM_SIFIVE_PWMCFG_CENTER BIT(16) | ||
36 | #define PWM_SIFIVE_PWMCFG_GANG BIT(24) | ||
37 | #define PWM_SIFIVE_PWMCFG_IP BIT(28) | ||
38 | |||
39 | /* PWM_SIFIVE_SIZE_PWMCMP is used to calculate offset for pwmcmpX registers */ | ||
40 | #define PWM_SIFIVE_SIZE_PWMCMP 4 | ||
41 | #define PWM_SIFIVE_CMPWIDTH 16 | ||
42 | #define PWM_SIFIVE_DEFAULT_PERIOD 10000000 | ||
43 | |||
44 | struct pwm_sifive_ddata { | ||
45 | struct pwm_chip chip; | ||
46 | struct mutex lock; /* lock to protect user_count */ | ||
47 | struct notifier_block notifier; | ||
48 | struct clk *clk; | ||
49 | void __iomem *regs; | ||
50 | unsigned int real_period; | ||
51 | unsigned int approx_period; | ||
52 | int user_count; | ||
53 | }; | ||
54 | |||
55 | static inline | ||
56 | struct pwm_sifive_ddata *pwm_sifive_chip_to_ddata(struct pwm_chip *c) | ||
57 | { | ||
58 | return container_of(c, struct pwm_sifive_ddata, chip); | ||
59 | } | ||
60 | |||
61 | static int pwm_sifive_request(struct pwm_chip *chip, struct pwm_device *pwm) | ||
62 | { | ||
63 | struct pwm_sifive_ddata *ddata = pwm_sifive_chip_to_ddata(chip); | ||
64 | |||
65 | mutex_lock(&ddata->lock); | ||
66 | ddata->user_count++; | ||
67 | mutex_unlock(&ddata->lock); | ||
68 | |||
69 | return 0; | ||
70 | } | ||
71 | |||
72 | static void pwm_sifive_free(struct pwm_chip *chip, struct pwm_device *pwm) | ||
73 | { | ||
74 | struct pwm_sifive_ddata *ddata = pwm_sifive_chip_to_ddata(chip); | ||
75 | |||
76 | mutex_lock(&ddata->lock); | ||
77 | ddata->user_count--; | ||
78 | mutex_unlock(&ddata->lock); | ||
79 | } | ||
80 | |||
81 | static void pwm_sifive_update_clock(struct pwm_sifive_ddata *ddata, | ||
82 | unsigned long rate) | ||
83 | { | ||
84 | unsigned long long num; | ||
85 | unsigned long scale_pow; | ||
86 | int scale; | ||
87 | u32 val; | ||
88 | /* | ||
89 | * The PWM unit is used with pwmzerocmp=0, so the only way to modify the | ||
90 | * period length is using pwmscale which provides the number of bits the | ||
91 | * counter is shifted before being feed to the comparators. A period | ||
92 | * lasts (1 << (PWM_SIFIVE_CMPWIDTH + pwmscale)) clock ticks. | ||
93 | * (1 << (PWM_SIFIVE_CMPWIDTH + scale)) * 10^9/rate = period | ||
94 | */ | ||
95 | scale_pow = div64_ul(ddata->approx_period * (u64)rate, NSEC_PER_SEC); | ||
96 | scale = clamp(ilog2(scale_pow) - PWM_SIFIVE_CMPWIDTH, 0, 0xf); | ||
97 | |||
98 | val = PWM_SIFIVE_PWMCFG_EN_ALWAYS | | ||
99 | FIELD_PREP(PWM_SIFIVE_PWMCFG_SCALE, scale); | ||
100 | writel(val, ddata->regs + PWM_SIFIVE_PWMCFG); | ||
101 | |||
102 | /* As scale <= 15 the shift operation cannot overflow. */ | ||
103 | num = (unsigned long long)NSEC_PER_SEC << (PWM_SIFIVE_CMPWIDTH + scale); | ||
104 | ddata->real_period = div64_ul(num, rate); | ||
105 | dev_dbg(ddata->chip.dev, | ||
106 | "New real_period = %u ns\n", ddata->real_period); | ||
107 | } | ||
108 | |||
109 | static void pwm_sifive_get_state(struct pwm_chip *chip, struct pwm_device *pwm, | ||
110 | struct pwm_state *state) | ||
111 | { | ||
112 | struct pwm_sifive_ddata *ddata = pwm_sifive_chip_to_ddata(chip); | ||
113 | u32 duty, val; | ||
114 | |||
115 | duty = readl(ddata->regs + PWM_SIFIVE_PWMCMP0 + | ||
116 | pwm->hwpwm * PWM_SIFIVE_SIZE_PWMCMP); | ||
117 | |||
118 | state->enabled = duty > 0; | ||
119 | |||
120 | val = readl(ddata->regs + PWM_SIFIVE_PWMCFG); | ||
121 | if (!(val & PWM_SIFIVE_PWMCFG_EN_ALWAYS)) | ||
122 | state->enabled = false; | ||
123 | |||
124 | state->period = ddata->real_period; | ||
125 | state->duty_cycle = | ||
126 | (u64)duty * ddata->real_period >> PWM_SIFIVE_CMPWIDTH; | ||
127 | state->polarity = PWM_POLARITY_INVERSED; | ||
128 | } | ||
129 | |||
130 | static int pwm_sifive_enable(struct pwm_chip *chip, bool enable) | ||
131 | { | ||
132 | struct pwm_sifive_ddata *ddata = pwm_sifive_chip_to_ddata(chip); | ||
133 | int ret; | ||
134 | |||
135 | if (enable) { | ||
136 | ret = clk_enable(ddata->clk); | ||
137 | if (ret) { | ||
138 | dev_err(ddata->chip.dev, "Enable clk failed\n"); | ||
139 | return ret; | ||
140 | } | ||
141 | } | ||
142 | |||
143 | if (!enable) | ||
144 | clk_disable(ddata->clk); | ||
145 | |||
146 | return 0; | ||
147 | } | ||
148 | |||
149 | static int pwm_sifive_apply(struct pwm_chip *chip, struct pwm_device *pwm, | ||
150 | struct pwm_state *state) | ||
151 | { | ||
152 | struct pwm_sifive_ddata *ddata = pwm_sifive_chip_to_ddata(chip); | ||
153 | struct pwm_state cur_state; | ||
154 | unsigned int duty_cycle; | ||
155 | unsigned long long num; | ||
156 | bool enabled; | ||
157 | int ret = 0; | ||
158 | u32 frac; | ||
159 | |||
160 | if (state->polarity != PWM_POLARITY_INVERSED) | ||
161 | return -EINVAL; | ||
162 | |||
163 | ret = clk_enable(ddata->clk); | ||
164 | if (ret) { | ||
165 | dev_err(ddata->chip.dev, "Enable clk failed\n"); | ||
166 | return ret; | ||
167 | } | ||
168 | |||
169 | mutex_lock(&ddata->lock); | ||
170 | cur_state = pwm->state; | ||
171 | enabled = cur_state.enabled; | ||
172 | |||
173 | duty_cycle = state->duty_cycle; | ||
174 | if (!state->enabled) | ||
175 | duty_cycle = 0; | ||
176 | |||
177 | /* | ||
178 | * The problem of output producing mixed setting as mentioned at top, | ||
179 | * occurs here. To minimize the window for this problem, we are | ||
180 | * calculating the register values first and then writing them | ||
181 | * consecutively | ||
182 | */ | ||
183 | num = (u64)duty_cycle * (1U << PWM_SIFIVE_CMPWIDTH); | ||
184 | frac = DIV_ROUND_CLOSEST_ULL(num, state->period); | ||
185 | /* The hardware cannot generate a 100% duty cycle */ | ||
186 | frac = min(frac, (1U << PWM_SIFIVE_CMPWIDTH) - 1); | ||
187 | |||
188 | if (state->period != ddata->approx_period) { | ||
189 | if (ddata->user_count != 1) { | ||
190 | ret = -EBUSY; | ||
191 | goto exit; | ||
192 | } | ||
193 | ddata->approx_period = state->period; | ||
194 | pwm_sifive_update_clock(ddata, clk_get_rate(ddata->clk)); | ||
195 | } | ||
196 | |||
197 | writel(frac, ddata->regs + PWM_SIFIVE_PWMCMP0 + | ||
198 | pwm->hwpwm * PWM_SIFIVE_SIZE_PWMCMP); | ||
199 | |||
200 | if (state->enabled != enabled) | ||
201 | pwm_sifive_enable(chip, state->enabled); | ||
202 | |||
203 | exit: | ||
204 | clk_disable(ddata->clk); | ||
205 | mutex_unlock(&ddata->lock); | ||
206 | return ret; | ||
207 | } | ||
208 | |||
209 | static const struct pwm_ops pwm_sifive_ops = { | ||
210 | .request = pwm_sifive_request, | ||
211 | .free = pwm_sifive_free, | ||
212 | .get_state = pwm_sifive_get_state, | ||
213 | .apply = pwm_sifive_apply, | ||
214 | .owner = THIS_MODULE, | ||
215 | }; | ||
216 | |||
217 | static int pwm_sifive_clock_notifier(struct notifier_block *nb, | ||
218 | unsigned long event, void *data) | ||
219 | { | ||
220 | struct clk_notifier_data *ndata = data; | ||
221 | struct pwm_sifive_ddata *ddata = | ||
222 | container_of(nb, struct pwm_sifive_ddata, notifier); | ||
223 | |||
224 | if (event == POST_RATE_CHANGE) | ||
225 | pwm_sifive_update_clock(ddata, ndata->new_rate); | ||
226 | |||
227 | return NOTIFY_OK; | ||
228 | } | ||
229 | |||
230 | static int pwm_sifive_probe(struct platform_device *pdev) | ||
231 | { | ||
232 | struct device *dev = &pdev->dev; | ||
233 | struct pwm_sifive_ddata *ddata; | ||
234 | struct pwm_chip *chip; | ||
235 | struct resource *res; | ||
236 | int ret; | ||
237 | |||
238 | ddata = devm_kzalloc(dev, sizeof(*ddata), GFP_KERNEL); | ||
239 | if (!ddata) | ||
240 | return -ENOMEM; | ||
241 | |||
242 | mutex_init(&ddata->lock); | ||
243 | chip = &ddata->chip; | ||
244 | chip->dev = dev; | ||
245 | chip->ops = &pwm_sifive_ops; | ||
246 | chip->of_xlate = of_pwm_xlate_with_flags; | ||
247 | chip->of_pwm_n_cells = 3; | ||
248 | chip->base = -1; | ||
249 | chip->npwm = 4; | ||
250 | |||
251 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | ||
252 | ddata->regs = devm_ioremap_resource(dev, res); | ||
253 | if (IS_ERR(ddata->regs)) { | ||
254 | dev_err(dev, "Unable to map IO resources\n"); | ||
255 | return PTR_ERR(ddata->regs); | ||
256 | } | ||
257 | |||
258 | ddata->clk = devm_clk_get(dev, NULL); | ||
259 | if (IS_ERR(ddata->clk)) { | ||
260 | if (PTR_ERR(ddata->clk) != -EPROBE_DEFER) | ||
261 | dev_err(dev, "Unable to find controller clock\n"); | ||
262 | return PTR_ERR(ddata->clk); | ||
263 | } | ||
264 | |||
265 | ret = clk_prepare_enable(ddata->clk); | ||
266 | if (ret) { | ||
267 | dev_err(dev, "failed to enable clock for pwm: %d\n", ret); | ||
268 | return ret; | ||
269 | } | ||
270 | |||
271 | /* Watch for changes to underlying clock frequency */ | ||
272 | ddata->notifier.notifier_call = pwm_sifive_clock_notifier; | ||
273 | ret = clk_notifier_register(ddata->clk, &ddata->notifier); | ||
274 | if (ret) { | ||
275 | dev_err(dev, "failed to register clock notifier: %d\n", ret); | ||
276 | goto disable_clk; | ||
277 | } | ||
278 | |||
279 | ret = pwmchip_add(chip); | ||
280 | if (ret < 0) { | ||
281 | dev_err(dev, "cannot register PWM: %d\n", ret); | ||
282 | goto unregister_clk; | ||
283 | } | ||
284 | |||
285 | platform_set_drvdata(pdev, ddata); | ||
286 | dev_dbg(dev, "SiFive PWM chip registered %d PWMs\n", chip->npwm); | ||
287 | |||
288 | return 0; | ||
289 | |||
290 | unregister_clk: | ||
291 | clk_notifier_unregister(ddata->clk, &ddata->notifier); | ||
292 | disable_clk: | ||
293 | clk_disable_unprepare(ddata->clk); | ||
294 | |||
295 | return ret; | ||
296 | } | ||
297 | |||
298 | static int pwm_sifive_remove(struct platform_device *dev) | ||
299 | { | ||
300 | struct pwm_sifive_ddata *ddata = platform_get_drvdata(dev); | ||
301 | bool is_enabled = false; | ||
302 | struct pwm_device *pwm; | ||
303 | int ret, ch; | ||
304 | |||
305 | for (ch = 0; ch < ddata->chip.npwm; ch++) { | ||
306 | pwm = &ddata->chip.pwms[ch]; | ||
307 | if (pwm->state.enabled) { | ||
308 | is_enabled = true; | ||
309 | break; | ||
310 | } | ||
311 | } | ||
312 | if (is_enabled) | ||
313 | clk_disable(ddata->clk); | ||
314 | |||
315 | clk_disable_unprepare(ddata->clk); | ||
316 | ret = pwmchip_remove(&ddata->chip); | ||
317 | clk_notifier_unregister(ddata->clk, &ddata->notifier); | ||
318 | |||
319 | return ret; | ||
320 | } | ||
321 | |||
322 | static const struct of_device_id pwm_sifive_of_match[] = { | ||
323 | { .compatible = "sifive,pwm0" }, | ||
324 | {}, | ||
325 | }; | ||
326 | MODULE_DEVICE_TABLE(of, pwm_sifive_of_match); | ||
327 | |||
328 | static struct platform_driver pwm_sifive_driver = { | ||
329 | .probe = pwm_sifive_probe, | ||
330 | .remove = pwm_sifive_remove, | ||
331 | .driver = { | ||
332 | .name = "pwm-sifive", | ||
333 | .of_match_table = pwm_sifive_of_match, | ||
334 | }, | ||
335 | }; | ||
336 | module_platform_driver(pwm_sifive_driver); | ||
337 | |||
338 | MODULE_DESCRIPTION("SiFive PWM driver"); | ||
339 | MODULE_LICENSE("GPL v2"); | ||