diff options
author | Tomasz Figa <tomasz.figa@gmail.com> | 2013-04-05 20:40:36 -0400 |
---|---|---|
committer | Tomasz Figa <tomasz.figa@gmail.com> | 2013-08-12 15:53:22 -0400 |
commit | 11ad39ede24ee42909d58dc95031d96da46e33bd (patch) | |
tree | 3de054343ca1712338b7a2526d1bac460e0f6cb2 | |
parent | 615c19e1607be1586aa2848712770423288c4f0e (diff) |
pwm: Add new pwm-samsung driver
This patch introduces new Samsung PWM driver, which is completely
rewritten to be multiplatform- and DeviceTree-aware.
In addition, remaining problems of old driver are fixed, such as:
- proper handling of hardware variants,
- synchronization on SMP systems,
- handling of boundary parameter values,
- hardware sharing with PWM clocksource driver,
- undefined state of PWM output after stopping PWM channel.
Signed-off-by: Tomasz Figa <tomasz.figa@gmail.com>
Reviewed-by: Sylwester Nawrocki <s.nawrocki@samsung.com>
Tested-by: Heiko Stuebner <heiko@sntech.de>
Tested-by: Mark Brown <broonie@linaro.org>
Tested-by: Sylwester Nawrocki <sylvester.nawrocki@gmail.com>
Acked-by: Arnd Bergmann <arnd@arndb.de>
Acked-by: Thierry Reding <thierry.reding@gmail.com>
-rw-r--r-- | drivers/pwm/Makefile | 1 | ||||
-rw-r--r-- | drivers/pwm/pwm-samsung.c | 618 | ||||
-rw-r--r-- | include/clocksource/samsung_pwm.h | 7 |
3 files changed, 626 insertions, 0 deletions
diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile index aa94807b7d61..86a57713b721 100644 --- a/drivers/pwm/Makefile +++ b/drivers/pwm/Makefile | |||
@@ -12,6 +12,7 @@ obj-$(CONFIG_PWM_PUV3) += pwm-puv3.o | |||
12 | obj-$(CONFIG_PWM_PXA) += pwm-pxa.o | 12 | obj-$(CONFIG_PWM_PXA) += pwm-pxa.o |
13 | obj-$(CONFIG_PWM_RENESAS_TPU) += pwm-renesas-tpu.o | 13 | obj-$(CONFIG_PWM_RENESAS_TPU) += pwm-renesas-tpu.o |
14 | obj-$(CONFIG_PWM_SAMSUNG) += pwm-samsung-legacy.o | 14 | obj-$(CONFIG_PWM_SAMSUNG) += pwm-samsung-legacy.o |
15 | obj-$(CONFIG_PWM_SAMSUNG) += pwm-samsung.o | ||
15 | obj-$(CONFIG_PWM_SPEAR) += pwm-spear.o | 16 | obj-$(CONFIG_PWM_SPEAR) += pwm-spear.o |
16 | obj-$(CONFIG_PWM_TEGRA) += pwm-tegra.o | 17 | obj-$(CONFIG_PWM_TEGRA) += pwm-tegra.o |
17 | obj-$(CONFIG_PWM_TIECAP) += pwm-tiecap.o | 18 | obj-$(CONFIG_PWM_TIECAP) += pwm-tiecap.o |
diff --git a/drivers/pwm/pwm-samsung.c b/drivers/pwm/pwm-samsung.c new file mode 100644 index 000000000000..fcc8b9adde9f --- /dev/null +++ b/drivers/pwm/pwm-samsung.c | |||
@@ -0,0 +1,618 @@ | |||
1 | /* | ||
2 | * Copyright (c) 2007 Ben Dooks | ||
3 | * Copyright (c) 2008 Simtec Electronics | ||
4 | * Ben Dooks <ben@simtec.co.uk>, <ben-linux@fluff.org> | ||
5 | * Copyright (c) 2013 Tomasz Figa <tomasz.figa@gmail.com> | ||
6 | * | ||
7 | * PWM driver for Samsung SoCs | ||
8 | * | ||
9 | * This program is free software; you can redistribute it and/or modify | ||
10 | * it under the terms of the GNU General Public License as published by | ||
11 | * the Free Software Foundation; either version 2 of the License. | ||
12 | */ | ||
13 | |||
14 | #include <linux/bitops.h> | ||
15 | #include <linux/clk.h> | ||
16 | #include <linux/export.h> | ||
17 | #include <linux/err.h> | ||
18 | #include <linux/io.h> | ||
19 | #include <linux/kernel.h> | ||
20 | #include <linux/module.h> | ||
21 | #include <linux/platform_device.h> | ||
22 | #include <linux/pwm.h> | ||
23 | #include <linux/slab.h> | ||
24 | #include <linux/spinlock.h> | ||
25 | #include <linux/time.h> | ||
26 | |||
27 | /* For struct samsung_timer_variant and samsung_pwm_lock. */ | ||
28 | #include <clocksource/samsung_pwm.h> | ||
29 | |||
30 | #define REG_TCFG0 0x00 | ||
31 | #define REG_TCFG1 0x04 | ||
32 | #define REG_TCON 0x08 | ||
33 | |||
34 | #define REG_TCNTB(chan) (0x0c + ((chan) * 0xc)) | ||
35 | #define REG_TCMPB(chan) (0x10 + ((chan) * 0xc)) | ||
36 | |||
37 | #define TCFG0_PRESCALER_MASK 0xff | ||
38 | #define TCFG0_PRESCALER1_SHIFT 8 | ||
39 | |||
40 | #define TCFG1_MUX_MASK 0xf | ||
41 | #define TCFG1_SHIFT(chan) (4 * (chan)) | ||
42 | |||
43 | /* | ||
44 | * Each channel occupies 4 bits in TCON register, but there is a gap of 4 | ||
45 | * bits (one channel) after channel 0, so channels have different numbering | ||
46 | * when accessing TCON register. See to_tcon_channel() function. | ||
47 | * | ||
48 | * In addition, the location of autoreload bit for channel 4 (TCON channel 5) | ||
49 | * in its set of bits is 2 as opposed to 3 for other channels. | ||
50 | */ | ||
51 | #define TCON_START(chan) BIT(4 * (chan) + 0) | ||
52 | #define TCON_MANUALUPDATE(chan) BIT(4 * (chan) + 1) | ||
53 | #define TCON_INVERT(chan) BIT(4 * (chan) + 2) | ||
54 | #define _TCON_AUTORELOAD(chan) BIT(4 * (chan) + 3) | ||
55 | #define _TCON_AUTORELOAD4(chan) BIT(4 * (chan) + 2) | ||
56 | #define TCON_AUTORELOAD(chan) \ | ||
57 | ((chan < 5) ? _TCON_AUTORELOAD(chan) : _TCON_AUTORELOAD4(chan)) | ||
58 | |||
59 | /** | ||
60 | * struct samsung_pwm_channel - private data of PWM channel | ||
61 | * @period_ns: current period in nanoseconds programmed to the hardware | ||
62 | * @duty_ns: current duty time in nanoseconds programmed to the hardware | ||
63 | * @tin_ns: time of one timer tick in nanoseconds with current timer rate | ||
64 | */ | ||
65 | struct samsung_pwm_channel { | ||
66 | u32 period_ns; | ||
67 | u32 duty_ns; | ||
68 | u32 tin_ns; | ||
69 | }; | ||
70 | |||
71 | /** | ||
72 | * struct samsung_pwm_chip - private data of PWM chip | ||
73 | * @chip: generic PWM chip | ||
74 | * @variant: local copy of hardware variant data | ||
75 | * @inverter_mask: inverter status for all channels - one bit per channel | ||
76 | * @base: base address of mapped PWM registers | ||
77 | * @base_clk: base clock used to drive the timers | ||
78 | * @tclk0: external clock 0 (can be ERR_PTR if not present) | ||
79 | * @tclk1: external clock 1 (can be ERR_PTR if not present) | ||
80 | */ | ||
81 | struct samsung_pwm_chip { | ||
82 | struct pwm_chip chip; | ||
83 | struct samsung_pwm_variant variant; | ||
84 | u8 inverter_mask; | ||
85 | |||
86 | void __iomem *base; | ||
87 | struct clk *base_clk; | ||
88 | struct clk *tclk0; | ||
89 | struct clk *tclk1; | ||
90 | }; | ||
91 | |||
92 | #ifndef CONFIG_CLKSRC_SAMSUNG_PWM | ||
93 | /* | ||
94 | * PWM block is shared between pwm-samsung and samsung_pwm_timer drivers | ||
95 | * and some registers need access synchronization. If both drivers are | ||
96 | * compiled in, the spinlock is defined in the clocksource driver, | ||
97 | * otherwise following definition is used. | ||
98 | * | ||
99 | * Currently we do not need any more complex synchronization method | ||
100 | * because all the supported SoCs contain only one instance of the PWM | ||
101 | * IP. Should this change, both drivers will need to be modified to | ||
102 | * properly synchronize accesses to particular instances. | ||
103 | */ | ||
104 | static DEFINE_SPINLOCK(samsung_pwm_lock); | ||
105 | #endif | ||
106 | |||
107 | static inline | ||
108 | struct samsung_pwm_chip *to_samsung_pwm_chip(struct pwm_chip *chip) | ||
109 | { | ||
110 | return container_of(chip, struct samsung_pwm_chip, chip); | ||
111 | } | ||
112 | |||
113 | static inline unsigned int to_tcon_channel(unsigned int channel) | ||
114 | { | ||
115 | /* TCON register has a gap of 4 bits (1 channel) after channel 0 */ | ||
116 | return (channel == 0) ? 0 : (channel + 1); | ||
117 | } | ||
118 | |||
119 | static void pwm_samsung_set_divisor(struct samsung_pwm_chip *pwm, | ||
120 | unsigned int channel, u8 divisor) | ||
121 | { | ||
122 | u8 shift = TCFG1_SHIFT(channel); | ||
123 | unsigned long flags; | ||
124 | u32 reg; | ||
125 | u8 bits; | ||
126 | |||
127 | bits = (fls(divisor) - 1) - pwm->variant.div_base; | ||
128 | |||
129 | spin_lock_irqsave(&samsung_pwm_lock, flags); | ||
130 | |||
131 | reg = readl(pwm->base + REG_TCFG1); | ||
132 | reg &= ~(TCFG1_MUX_MASK << shift); | ||
133 | reg |= bits << shift; | ||
134 | writel(reg, pwm->base + REG_TCFG1); | ||
135 | |||
136 | spin_unlock_irqrestore(&samsung_pwm_lock, flags); | ||
137 | } | ||
138 | |||
139 | static int pwm_samsung_is_tdiv(struct samsung_pwm_chip *chip, unsigned int chan) | ||
140 | { | ||
141 | struct samsung_pwm_variant *variant = &chip->variant; | ||
142 | u32 reg; | ||
143 | |||
144 | reg = readl(chip->base + REG_TCFG1); | ||
145 | reg >>= TCFG1_SHIFT(chan); | ||
146 | reg &= TCFG1_MUX_MASK; | ||
147 | |||
148 | return (BIT(reg) & variant->tclk_mask) == 0; | ||
149 | } | ||
150 | |||
151 | static unsigned long pwm_samsung_get_tin_rate(struct samsung_pwm_chip *chip, | ||
152 | unsigned int chan) | ||
153 | { | ||
154 | unsigned long rate; | ||
155 | u32 reg; | ||
156 | |||
157 | rate = clk_get_rate(chip->base_clk); | ||
158 | |||
159 | reg = readl(chip->base + REG_TCFG0); | ||
160 | if (chan >= 2) | ||
161 | reg >>= TCFG0_PRESCALER1_SHIFT; | ||
162 | reg &= TCFG0_PRESCALER_MASK; | ||
163 | |||
164 | return rate / (reg + 1); | ||
165 | } | ||
166 | |||
167 | static unsigned long pwm_samsung_calc_tin(struct samsung_pwm_chip *chip, | ||
168 | unsigned int chan, unsigned long freq) | ||
169 | { | ||
170 | struct samsung_pwm_variant *variant = &chip->variant; | ||
171 | unsigned long rate; | ||
172 | struct clk *clk; | ||
173 | u8 div; | ||
174 | |||
175 | if (!pwm_samsung_is_tdiv(chip, chan)) { | ||
176 | clk = (chan < 2) ? chip->tclk0 : chip->tclk1; | ||
177 | if (!IS_ERR(clk)) { | ||
178 | rate = clk_get_rate(clk); | ||
179 | if (rate) | ||
180 | return rate; | ||
181 | } | ||
182 | |||
183 | dev_warn(chip->chip.dev, | ||
184 | "tclk of PWM %d is inoperational, using tdiv\n", chan); | ||
185 | } | ||
186 | |||
187 | rate = pwm_samsung_get_tin_rate(chip, chan); | ||
188 | dev_dbg(chip->chip.dev, "tin parent at %lu\n", rate); | ||
189 | |||
190 | /* | ||
191 | * Compare minimum PWM frequency that can be achieved with possible | ||
192 | * divider settings and choose the lowest divisor that can generate | ||
193 | * frequencies lower than requested. | ||
194 | */ | ||
195 | for (div = variant->div_base; div < 4; ++div) | ||
196 | if ((rate >> (variant->bits + div)) < freq) | ||
197 | break; | ||
198 | |||
199 | pwm_samsung_set_divisor(chip, chan, BIT(div)); | ||
200 | |||
201 | return rate >> div; | ||
202 | } | ||
203 | |||
204 | static int pwm_samsung_request(struct pwm_chip *chip, struct pwm_device *pwm) | ||
205 | { | ||
206 | struct samsung_pwm_chip *our_chip = to_samsung_pwm_chip(chip); | ||
207 | struct samsung_pwm_channel *our_chan; | ||
208 | |||
209 | if (!(our_chip->variant.output_mask & BIT(pwm->hwpwm))) { | ||
210 | dev_warn(chip->dev, | ||
211 | "tried to request PWM channel %d without output\n", | ||
212 | pwm->hwpwm); | ||
213 | return -EINVAL; | ||
214 | } | ||
215 | |||
216 | our_chan = devm_kzalloc(chip->dev, sizeof(*our_chan), GFP_KERNEL); | ||
217 | if (!our_chan) | ||
218 | return -ENOMEM; | ||
219 | |||
220 | pwm_set_chip_data(pwm, our_chan); | ||
221 | |||
222 | return 0; | ||
223 | } | ||
224 | |||
225 | static void pwm_samsung_free(struct pwm_chip *chip, struct pwm_device *pwm) | ||
226 | { | ||
227 | pwm_set_chip_data(pwm, NULL); | ||
228 | devm_kfree(chip->dev, pwm_get_chip_data(pwm)); | ||
229 | } | ||
230 | |||
231 | static int pwm_samsung_enable(struct pwm_chip *chip, struct pwm_device *pwm) | ||
232 | { | ||
233 | struct samsung_pwm_chip *our_chip = to_samsung_pwm_chip(chip); | ||
234 | unsigned int tcon_chan = to_tcon_channel(pwm->hwpwm); | ||
235 | unsigned long flags; | ||
236 | u32 tcon; | ||
237 | |||
238 | spin_lock_irqsave(&samsung_pwm_lock, flags); | ||
239 | |||
240 | tcon = readl(our_chip->base + REG_TCON); | ||
241 | |||
242 | tcon &= ~TCON_START(tcon_chan); | ||
243 | tcon |= TCON_MANUALUPDATE(tcon_chan); | ||
244 | writel(tcon, our_chip->base + REG_TCON); | ||
245 | |||
246 | tcon &= ~TCON_MANUALUPDATE(tcon_chan); | ||
247 | tcon |= TCON_START(tcon_chan) | TCON_AUTORELOAD(tcon_chan); | ||
248 | writel(tcon, our_chip->base + REG_TCON); | ||
249 | |||
250 | spin_unlock_irqrestore(&samsung_pwm_lock, flags); | ||
251 | |||
252 | return 0; | ||
253 | } | ||
254 | |||
255 | static void pwm_samsung_disable(struct pwm_chip *chip, struct pwm_device *pwm) | ||
256 | { | ||
257 | struct samsung_pwm_chip *our_chip = to_samsung_pwm_chip(chip); | ||
258 | unsigned int tcon_chan = to_tcon_channel(pwm->hwpwm); | ||
259 | unsigned long flags; | ||
260 | u32 tcon; | ||
261 | |||
262 | spin_lock_irqsave(&samsung_pwm_lock, flags); | ||
263 | |||
264 | tcon = readl(our_chip->base + REG_TCON); | ||
265 | tcon &= ~TCON_AUTORELOAD(tcon_chan); | ||
266 | writel(tcon, our_chip->base + REG_TCON); | ||
267 | |||
268 | spin_unlock_irqrestore(&samsung_pwm_lock, flags); | ||
269 | } | ||
270 | |||
271 | static int pwm_samsung_config(struct pwm_chip *chip, struct pwm_device *pwm, | ||
272 | int duty_ns, int period_ns) | ||
273 | { | ||
274 | struct samsung_pwm_chip *our_chip = to_samsung_pwm_chip(chip); | ||
275 | struct samsung_pwm_channel *chan = pwm_get_chip_data(pwm); | ||
276 | u32 tin_ns = chan->tin_ns, tcnt, tcmp; | ||
277 | |||
278 | /* | ||
279 | * We currently avoid using 64bit arithmetic by using the | ||
280 | * fact that anything faster than 1Hz is easily representable | ||
281 | * by 32bits. | ||
282 | */ | ||
283 | if (period_ns > NSEC_PER_SEC) | ||
284 | return -ERANGE; | ||
285 | |||
286 | if (period_ns == chan->period_ns && duty_ns == chan->duty_ns) | ||
287 | return 0; | ||
288 | |||
289 | tcnt = readl(our_chip->base + REG_TCNTB(pwm->hwpwm)); | ||
290 | |||
291 | /* We need tick count for calculation, not last tick. */ | ||
292 | ++tcnt; | ||
293 | |||
294 | /* Check to see if we are changing the clock rate of the PWM. */ | ||
295 | if (chan->period_ns != period_ns) { | ||
296 | unsigned long tin_rate; | ||
297 | u32 period; | ||
298 | |||
299 | period = NSEC_PER_SEC / period_ns; | ||
300 | |||
301 | dev_dbg(our_chip->chip.dev, "duty_ns=%d, period_ns=%d (%u)\n", | ||
302 | duty_ns, period_ns, period); | ||
303 | |||
304 | tin_rate = pwm_samsung_calc_tin(our_chip, pwm->hwpwm, period); | ||
305 | |||
306 | dev_dbg(our_chip->chip.dev, "tin_rate=%lu\n", tin_rate); | ||
307 | |||
308 | tin_ns = NSEC_PER_SEC / tin_rate; | ||
309 | tcnt = period_ns / tin_ns; | ||
310 | } | ||
311 | |||
312 | /* Period is too short. */ | ||
313 | if (tcnt <= 1) | ||
314 | return -ERANGE; | ||
315 | |||
316 | /* Note that counters count down. */ | ||
317 | tcmp = duty_ns / tin_ns; | ||
318 | |||
319 | /* 0% duty is not available */ | ||
320 | if (!tcmp) | ||
321 | ++tcmp; | ||
322 | |||
323 | tcmp = tcnt - tcmp; | ||
324 | |||
325 | /* Decrement to get tick numbers, instead of tick counts. */ | ||
326 | --tcnt; | ||
327 | /* -1UL will give 100% duty. */ | ||
328 | --tcmp; | ||
329 | |||
330 | dev_dbg(our_chip->chip.dev, | ||
331 | "tin_ns=%u, tcmp=%u/%u\n", tin_ns, tcmp, tcnt); | ||
332 | |||
333 | /* Update PWM registers. */ | ||
334 | writel(tcnt, our_chip->base + REG_TCNTB(pwm->hwpwm)); | ||
335 | writel(tcmp, our_chip->base + REG_TCMPB(pwm->hwpwm)); | ||
336 | |||
337 | if (test_bit(PWMF_ENABLED, &pwm->flags)) | ||
338 | pwm_samsung_enable(chip, pwm); | ||
339 | |||
340 | chan->period_ns = period_ns; | ||
341 | chan->tin_ns = tin_ns; | ||
342 | chan->duty_ns = duty_ns; | ||
343 | |||
344 | return 0; | ||
345 | } | ||
346 | |||
347 | static void pwm_samsung_set_invert(struct samsung_pwm_chip *chip, | ||
348 | unsigned int channel, bool invert) | ||
349 | { | ||
350 | unsigned int tcon_chan = to_tcon_channel(channel); | ||
351 | unsigned long flags; | ||
352 | u32 tcon; | ||
353 | |||
354 | spin_lock_irqsave(&samsung_pwm_lock, flags); | ||
355 | |||
356 | tcon = readl(chip->base + REG_TCON); | ||
357 | |||
358 | if (invert) { | ||
359 | chip->inverter_mask |= BIT(channel); | ||
360 | tcon |= TCON_INVERT(tcon_chan); | ||
361 | } else { | ||
362 | chip->inverter_mask &= ~BIT(channel); | ||
363 | tcon &= ~TCON_INVERT(tcon_chan); | ||
364 | } | ||
365 | |||
366 | writel(tcon, chip->base + REG_TCON); | ||
367 | |||
368 | spin_unlock_irqrestore(&samsung_pwm_lock, flags); | ||
369 | } | ||
370 | |||
371 | static int pwm_samsung_set_polarity(struct pwm_chip *chip, | ||
372 | struct pwm_device *pwm, | ||
373 | enum pwm_polarity polarity) | ||
374 | { | ||
375 | struct samsung_pwm_chip *our_chip = to_samsung_pwm_chip(chip); | ||
376 | bool invert = (polarity == PWM_POLARITY_NORMAL); | ||
377 | |||
378 | /* Inverted means normal in the hardware. */ | ||
379 | pwm_samsung_set_invert(our_chip, pwm->hwpwm, invert); | ||
380 | |||
381 | return 0; | ||
382 | } | ||
383 | |||
384 | static const struct pwm_ops pwm_samsung_ops = { | ||
385 | .request = pwm_samsung_request, | ||
386 | .free = pwm_samsung_free, | ||
387 | .enable = pwm_samsung_enable, | ||
388 | .disable = pwm_samsung_disable, | ||
389 | .config = pwm_samsung_config, | ||
390 | .set_polarity = pwm_samsung_set_polarity, | ||
391 | .owner = THIS_MODULE, | ||
392 | }; | ||
393 | |||
394 | #ifdef CONFIG_OF | ||
395 | static const struct samsung_pwm_variant s3c24xx_variant = { | ||
396 | .bits = 16, | ||
397 | .div_base = 1, | ||
398 | .has_tint_cstat = false, | ||
399 | .tclk_mask = BIT(4), | ||
400 | }; | ||
401 | |||
402 | static const struct samsung_pwm_variant s3c64xx_variant = { | ||
403 | .bits = 32, | ||
404 | .div_base = 0, | ||
405 | .has_tint_cstat = true, | ||
406 | .tclk_mask = BIT(7) | BIT(6) | BIT(5), | ||
407 | }; | ||
408 | |||
409 | static const struct samsung_pwm_variant s5p64x0_variant = { | ||
410 | .bits = 32, | ||
411 | .div_base = 0, | ||
412 | .has_tint_cstat = true, | ||
413 | .tclk_mask = 0, | ||
414 | }; | ||
415 | |||
416 | static const struct samsung_pwm_variant s5pc100_variant = { | ||
417 | .bits = 32, | ||
418 | .div_base = 0, | ||
419 | .has_tint_cstat = true, | ||
420 | .tclk_mask = BIT(5), | ||
421 | }; | ||
422 | |||
423 | static const struct of_device_id samsung_pwm_matches[] = { | ||
424 | { .compatible = "samsung,s3c2410-pwm", .data = &s3c24xx_variant }, | ||
425 | { .compatible = "samsung,s3c6400-pwm", .data = &s3c64xx_variant }, | ||
426 | { .compatible = "samsung,s5p6440-pwm", .data = &s5p64x0_variant }, | ||
427 | { .compatible = "samsung,s5pc100-pwm", .data = &s5pc100_variant }, | ||
428 | { .compatible = "samsung,exynos4210-pwm", .data = &s5p64x0_variant }, | ||
429 | {}, | ||
430 | }; | ||
431 | |||
432 | static int pwm_samsung_parse_dt(struct samsung_pwm_chip *chip) | ||
433 | { | ||
434 | struct device_node *np = chip->chip.dev->of_node; | ||
435 | const struct of_device_id *match; | ||
436 | struct property *prop; | ||
437 | const __be32 *cur; | ||
438 | u32 val; | ||
439 | |||
440 | match = of_match_node(samsung_pwm_matches, np); | ||
441 | if (!match) | ||
442 | return -ENODEV; | ||
443 | |||
444 | memcpy(&chip->variant, match->data, sizeof(chip->variant)); | ||
445 | |||
446 | of_property_for_each_u32(np, "samsung,pwm-outputs", prop, cur, val) { | ||
447 | if (val >= SAMSUNG_PWM_NUM) { | ||
448 | dev_err(chip->chip.dev, | ||
449 | "%s: invalid channel index in samsung,pwm-outputs property\n", | ||
450 | __func__); | ||
451 | continue; | ||
452 | } | ||
453 | chip->variant.output_mask |= BIT(val); | ||
454 | } | ||
455 | |||
456 | return 0; | ||
457 | } | ||
458 | #else | ||
459 | static int pwm_samsung_parse_dt(struct samsung_pwm_chip *chip) | ||
460 | { | ||
461 | return -ENODEV; | ||
462 | } | ||
463 | #endif | ||
464 | |||
465 | static int pwm_samsung_probe(struct platform_device *pdev) | ||
466 | { | ||
467 | struct device *dev = &pdev->dev; | ||
468 | struct samsung_pwm_chip *chip; | ||
469 | struct resource *res; | ||
470 | unsigned int chan; | ||
471 | int ret; | ||
472 | |||
473 | chip = devm_kzalloc(&pdev->dev, sizeof(*chip), GFP_KERNEL); | ||
474 | if (chip == NULL) | ||
475 | return -ENOMEM; | ||
476 | |||
477 | chip->chip.dev = &pdev->dev; | ||
478 | chip->chip.ops = &pwm_samsung_ops; | ||
479 | chip->chip.base = -1; | ||
480 | chip->chip.npwm = SAMSUNG_PWM_NUM; | ||
481 | chip->inverter_mask = BIT(SAMSUNG_PWM_NUM) - 1; | ||
482 | |||
483 | if (IS_ENABLED(CONFIG_OF) && pdev->dev.of_node) { | ||
484 | ret = pwm_samsung_parse_dt(chip); | ||
485 | if (ret) | ||
486 | return ret; | ||
487 | |||
488 | chip->chip.of_xlate = of_pwm_xlate_with_flags; | ||
489 | chip->chip.of_pwm_n_cells = 3; | ||
490 | } else { | ||
491 | if (!pdev->dev.platform_data) { | ||
492 | dev_err(&pdev->dev, "no platform data specified\n"); | ||
493 | return -EINVAL; | ||
494 | } | ||
495 | |||
496 | memcpy(&chip->variant, pdev->dev.platform_data, | ||
497 | sizeof(chip->variant)); | ||
498 | } | ||
499 | |||
500 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | ||
501 | chip->base = devm_ioremap_resource(&pdev->dev, res); | ||
502 | if (IS_ERR(chip->base)) | ||
503 | return PTR_ERR(chip->base); | ||
504 | |||
505 | chip->base_clk = devm_clk_get(&pdev->dev, "timers"); | ||
506 | if (IS_ERR(chip->base_clk)) { | ||
507 | dev_err(dev, "failed to get timer base clk\n"); | ||
508 | return PTR_ERR(chip->base_clk); | ||
509 | } | ||
510 | |||
511 | ret = clk_prepare_enable(chip->base_clk); | ||
512 | if (ret < 0) { | ||
513 | dev_err(dev, "failed to enable base clock\n"); | ||
514 | return ret; | ||
515 | } | ||
516 | |||
517 | for (chan = 0; chan < SAMSUNG_PWM_NUM; ++chan) | ||
518 | if (chip->variant.output_mask & BIT(chan)) | ||
519 | pwm_samsung_set_invert(chip, chan, true); | ||
520 | |||
521 | /* Following clocks are optional. */ | ||
522 | chip->tclk0 = devm_clk_get(&pdev->dev, "pwm-tclk0"); | ||
523 | chip->tclk1 = devm_clk_get(&pdev->dev, "pwm-tclk1"); | ||
524 | |||
525 | platform_set_drvdata(pdev, chip); | ||
526 | |||
527 | ret = pwmchip_add(&chip->chip); | ||
528 | if (ret < 0) { | ||
529 | dev_err(dev, "failed to register PWM chip\n"); | ||
530 | clk_disable_unprepare(chip->base_clk); | ||
531 | return ret; | ||
532 | } | ||
533 | |||
534 | dev_dbg(dev, "base_clk at %lu, tclk0 at %lu, tclk1 at %lu\n", | ||
535 | clk_get_rate(chip->base_clk), | ||
536 | !IS_ERR(chip->tclk0) ? clk_get_rate(chip->tclk0) : 0, | ||
537 | !IS_ERR(chip->tclk1) ? clk_get_rate(chip->tclk1) : 0); | ||
538 | |||
539 | return 0; | ||
540 | } | ||
541 | |||
542 | static int pwm_samsung_remove(struct platform_device *pdev) | ||
543 | { | ||
544 | struct samsung_pwm_chip *chip = platform_get_drvdata(pdev); | ||
545 | int ret; | ||
546 | |||
547 | ret = pwmchip_remove(&chip->chip); | ||
548 | if (ret < 0) | ||
549 | return ret; | ||
550 | |||
551 | clk_disable_unprepare(chip->base_clk); | ||
552 | |||
553 | return 0; | ||
554 | } | ||
555 | |||
556 | #ifdef CONFIG_PM_SLEEP | ||
557 | static int pwm_samsung_suspend(struct device *dev) | ||
558 | { | ||
559 | struct samsung_pwm_chip *chip = dev_get_drvdata(dev); | ||
560 | unsigned int i; | ||
561 | |||
562 | /* | ||
563 | * No one preserves these values during suspend so reset them. | ||
564 | * Otherwise driver leaves PWM unconfigured if same values are | ||
565 | * passed to pwm_config() next time. | ||
566 | */ | ||
567 | for (i = 0; i < SAMSUNG_PWM_NUM; ++i) { | ||
568 | struct pwm_device *pwm = &chip->chip.pwms[i]; | ||
569 | struct samsung_pwm_channel *chan = pwm_get_chip_data(pwm); | ||
570 | |||
571 | if (!chan) | ||
572 | continue; | ||
573 | |||
574 | chan->period_ns = 0; | ||
575 | chan->duty_ns = 0; | ||
576 | } | ||
577 | |||
578 | return 0; | ||
579 | } | ||
580 | |||
581 | static int pwm_samsung_resume(struct device *dev) | ||
582 | { | ||
583 | struct samsung_pwm_chip *chip = dev_get_drvdata(dev); | ||
584 | unsigned int chan; | ||
585 | |||
586 | /* | ||
587 | * Inverter setting must be preserved across suspend/resume | ||
588 | * as nobody really seems to configure it more than once. | ||
589 | */ | ||
590 | for (chan = 0; chan < SAMSUNG_PWM_NUM; ++chan) { | ||
591 | if (chip->variant.output_mask & BIT(chan)) | ||
592 | pwm_samsung_set_invert(chip, chan, | ||
593 | chip->inverter_mask & BIT(chan)); | ||
594 | } | ||
595 | |||
596 | return 0; | ||
597 | } | ||
598 | #endif | ||
599 | |||
600 | static const struct dev_pm_ops pwm_samsung_pm_ops = { | ||
601 | SET_SYSTEM_SLEEP_PM_OPS(pwm_samsung_suspend, pwm_samsung_resume) | ||
602 | }; | ||
603 | |||
604 | static struct platform_driver pwm_samsung_driver = { | ||
605 | .driver = { | ||
606 | .name = "samsung-pwm", | ||
607 | .owner = THIS_MODULE, | ||
608 | .pm = &pwm_samsung_pm_ops, | ||
609 | .of_match_table = of_match_ptr(samsung_pwm_matches), | ||
610 | }, | ||
611 | .probe = pwm_samsung_probe, | ||
612 | .remove = pwm_samsung_remove, | ||
613 | }; | ||
614 | module_platform_driver(pwm_samsung_driver); | ||
615 | |||
616 | MODULE_LICENSE("GPL"); | ||
617 | MODULE_AUTHOR("Tomasz Figa <tomasz.figa@gmail.com>"); | ||
618 | MODULE_ALIAS("platform:samsung-pwm"); | ||
diff --git a/include/clocksource/samsung_pwm.h b/include/clocksource/samsung_pwm.h index 5c449c8199e9..0c7d48b8b396 100644 --- a/include/clocksource/samsung_pwm.h +++ b/include/clocksource/samsung_pwm.h | |||
@@ -20,7 +20,14 @@ | |||
20 | 20 | ||
21 | #define SAMSUNG_PWM_NUM 5 | 21 | #define SAMSUNG_PWM_NUM 5 |
22 | 22 | ||
23 | /* | ||
24 | * Following declaration must be in an ifdef due to this symbol being static | ||
25 | * in pwm-samsung driver if the clocksource driver is not compiled in and the | ||
26 | * spinlock is not shared between both drivers. | ||
27 | */ | ||
28 | #ifdef CONFIG_CLKSRC_SAMSUNG_PWM | ||
23 | extern spinlock_t samsung_pwm_lock; | 29 | extern spinlock_t samsung_pwm_lock; |
30 | #endif | ||
24 | 31 | ||
25 | struct samsung_pwm_variant { | 32 | struct samsung_pwm_variant { |
26 | u8 bits; | 33 | u8 bits; |