1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Copyright (c) 2011 Samsung Electronics Co., Ltd. |
4 | * http://www.samsung.com/ |
5 | * |
6 | * samsung - Common hr-timer support (s3c and s5p) |
7 | */ |
8 | |
9 | #include <linux/interrupt.h> |
10 | #include <linux/irq.h> |
11 | #include <linux/err.h> |
12 | #include <linux/clk.h> |
13 | #include <linux/clockchips.h> |
14 | #include <linux/list.h> |
15 | #include <linux/module.h> |
16 | #include <linux/of.h> |
17 | #include <linux/of_address.h> |
18 | #include <linux/of_irq.h> |
19 | #include <linux/platform_device.h> |
20 | #include <linux/slab.h> |
21 | #include <linux/sched_clock.h> |
22 | |
23 | #include <clocksource/samsung_pwm.h> |
24 | |
25 | /* |
26 | * Clocksource driver |
27 | */ |
28 | |
29 | #define REG_TCFG0 0x00 |
30 | #define REG_TCFG1 0x04 |
31 | #define REG_TCON 0x08 |
32 | #define REG_TINT_CSTAT 0x44 |
33 | |
34 | #define REG_TCNTB(chan) (0x0c + 12 * (chan)) |
35 | #define REG_TCMPB(chan) (0x10 + 12 * (chan)) |
36 | |
37 | #define TCFG0_PRESCALER_MASK 0xff |
38 | #define TCFG0_PRESCALER1_SHIFT 8 |
39 | |
40 | #define TCFG1_SHIFT(x) ((x) * 4) |
41 | #define TCFG1_MUX_MASK 0xf |
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. |
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) (1 << (4 * (chan) + 0)) |
52 | #define TCON_MANUALUPDATE(chan) (1 << (4 * (chan) + 1)) |
53 | #define TCON_INVERT(chan) (1 << (4 * (chan) + 2)) |
54 | #define _TCON_AUTORELOAD(chan) (1 << (4 * (chan) + 3)) |
55 | #define _TCON_AUTORELOAD4(chan) (1 << (4 * (chan) + 2)) |
56 | #define TCON_AUTORELOAD(chan) \ |
57 | ((chan < 5) ? _TCON_AUTORELOAD(chan) : _TCON_AUTORELOAD4(chan)) |
58 | |
59 | DEFINE_SPINLOCK(samsung_pwm_lock); |
60 | EXPORT_SYMBOL(samsung_pwm_lock); |
61 | |
62 | struct samsung_pwm_clocksource { |
63 | void __iomem *base; |
64 | const void __iomem *source_reg; |
65 | unsigned int irq[SAMSUNG_PWM_NUM]; |
66 | struct samsung_pwm_variant variant; |
67 | |
68 | struct clk *timerclk; |
69 | |
70 | unsigned int event_id; |
71 | unsigned int source_id; |
72 | unsigned int tcnt_max; |
73 | unsigned int tscaler_div; |
74 | unsigned int tdiv; |
75 | |
76 | unsigned long clock_count_per_tick; |
77 | }; |
78 | |
79 | static struct samsung_pwm_clocksource pwm; |
80 | |
81 | static void samsung_timer_set_prescale(unsigned int channel, u16 prescale) |
82 | { |
83 | unsigned long flags; |
84 | u8 shift = 0; |
85 | u32 reg; |
86 | |
87 | if (channel >= 2) |
88 | shift = TCFG0_PRESCALER1_SHIFT; |
89 | |
90 | spin_lock_irqsave(&samsung_pwm_lock, flags); |
91 | |
92 | reg = readl(addr: pwm.base + REG_TCFG0); |
93 | reg &= ~(TCFG0_PRESCALER_MASK << shift); |
94 | reg |= (prescale - 1) << shift; |
95 | writel(val: reg, addr: pwm.base + REG_TCFG0); |
96 | |
97 | spin_unlock_irqrestore(lock: &samsung_pwm_lock, flags); |
98 | } |
99 | |
100 | static void samsung_timer_set_divisor(unsigned int channel, u8 divisor) |
101 | { |
102 | u8 shift = TCFG1_SHIFT(channel); |
103 | unsigned long flags; |
104 | u32 reg; |
105 | u8 bits; |
106 | |
107 | bits = (fls(x: divisor) - 1) - pwm.variant.div_base; |
108 | |
109 | spin_lock_irqsave(&samsung_pwm_lock, flags); |
110 | |
111 | reg = readl(addr: pwm.base + REG_TCFG1); |
112 | reg &= ~(TCFG1_MUX_MASK << shift); |
113 | reg |= bits << shift; |
114 | writel(val: reg, addr: pwm.base + REG_TCFG1); |
115 | |
116 | spin_unlock_irqrestore(lock: &samsung_pwm_lock, flags); |
117 | } |
118 | |
119 | static void samsung_time_stop(unsigned int channel) |
120 | { |
121 | unsigned long tcon; |
122 | unsigned long flags; |
123 | |
124 | if (channel > 0) |
125 | ++channel; |
126 | |
127 | spin_lock_irqsave(&samsung_pwm_lock, flags); |
128 | |
129 | tcon = readl_relaxed(pwm.base + REG_TCON); |
130 | tcon &= ~TCON_START(channel); |
131 | writel_relaxed(tcon, pwm.base + REG_TCON); |
132 | |
133 | spin_unlock_irqrestore(lock: &samsung_pwm_lock, flags); |
134 | } |
135 | |
136 | static void samsung_time_setup(unsigned int channel, unsigned long tcnt) |
137 | { |
138 | unsigned long tcon; |
139 | unsigned long flags; |
140 | unsigned int tcon_chan = channel; |
141 | |
142 | if (tcon_chan > 0) |
143 | ++tcon_chan; |
144 | |
145 | spin_lock_irqsave(&samsung_pwm_lock, flags); |
146 | |
147 | tcon = readl_relaxed(pwm.base + REG_TCON); |
148 | |
149 | tcon &= ~(TCON_START(tcon_chan) | TCON_AUTORELOAD(tcon_chan)); |
150 | tcon |= TCON_MANUALUPDATE(tcon_chan); |
151 | |
152 | writel_relaxed(tcnt, pwm.base + REG_TCNTB(channel)); |
153 | writel_relaxed(tcnt, pwm.base + REG_TCMPB(channel)); |
154 | writel_relaxed(tcon, pwm.base + REG_TCON); |
155 | |
156 | spin_unlock_irqrestore(lock: &samsung_pwm_lock, flags); |
157 | } |
158 | |
159 | static void samsung_time_start(unsigned int channel, bool periodic) |
160 | { |
161 | unsigned long tcon; |
162 | unsigned long flags; |
163 | |
164 | if (channel > 0) |
165 | ++channel; |
166 | |
167 | spin_lock_irqsave(&samsung_pwm_lock, flags); |
168 | |
169 | tcon = readl_relaxed(pwm.base + REG_TCON); |
170 | |
171 | tcon &= ~TCON_MANUALUPDATE(channel); |
172 | tcon |= TCON_START(channel); |
173 | |
174 | if (periodic) |
175 | tcon |= TCON_AUTORELOAD(channel); |
176 | else |
177 | tcon &= ~TCON_AUTORELOAD(channel); |
178 | |
179 | writel_relaxed(tcon, pwm.base + REG_TCON); |
180 | |
181 | spin_unlock_irqrestore(lock: &samsung_pwm_lock, flags); |
182 | } |
183 | |
184 | static int samsung_set_next_event(unsigned long cycles, |
185 | struct clock_event_device *evt) |
186 | { |
187 | /* |
188 | * This check is needed to account for internal rounding |
189 | * errors inside clockevents core, which might result in |
190 | * passing cycles = 0, which in turn would not generate any |
191 | * timer interrupt and hang the system. |
192 | * |
193 | * Another solution would be to set up the clockevent device |
194 | * with min_delta = 2, but this would unnecessarily increase |
195 | * the minimum sleep period. |
196 | */ |
197 | if (!cycles) |
198 | cycles = 1; |
199 | |
200 | samsung_time_setup(channel: pwm.event_id, tcnt: cycles); |
201 | samsung_time_start(channel: pwm.event_id, periodic: false); |
202 | |
203 | return 0; |
204 | } |
205 | |
206 | static int samsung_shutdown(struct clock_event_device *evt) |
207 | { |
208 | samsung_time_stop(channel: pwm.event_id); |
209 | return 0; |
210 | } |
211 | |
212 | static int samsung_set_periodic(struct clock_event_device *evt) |
213 | { |
214 | samsung_time_stop(channel: pwm.event_id); |
215 | samsung_time_setup(channel: pwm.event_id, tcnt: pwm.clock_count_per_tick - 1); |
216 | samsung_time_start(channel: pwm.event_id, periodic: true); |
217 | return 0; |
218 | } |
219 | |
220 | static void samsung_clockevent_resume(struct clock_event_device *cev) |
221 | { |
222 | samsung_timer_set_prescale(channel: pwm.event_id, prescale: pwm.tscaler_div); |
223 | samsung_timer_set_divisor(channel: pwm.event_id, divisor: pwm.tdiv); |
224 | |
225 | if (pwm.variant.has_tint_cstat) { |
226 | u32 mask = (1 << pwm.event_id); |
227 | |
228 | writel(val: mask | (mask << 5), addr: pwm.base + REG_TINT_CSTAT); |
229 | } |
230 | } |
231 | |
232 | static struct clock_event_device time_event_device = { |
233 | .name = "samsung_event_timer" , |
234 | .features = CLOCK_EVT_FEAT_PERIODIC | |
235 | CLOCK_EVT_FEAT_ONESHOT, |
236 | .rating = 200, |
237 | .set_next_event = samsung_set_next_event, |
238 | .set_state_shutdown = samsung_shutdown, |
239 | .set_state_periodic = samsung_set_periodic, |
240 | .set_state_oneshot = samsung_shutdown, |
241 | .tick_resume = samsung_shutdown, |
242 | .resume = samsung_clockevent_resume, |
243 | }; |
244 | |
245 | static irqreturn_t samsung_clock_event_isr(int irq, void *dev_id) |
246 | { |
247 | struct clock_event_device *evt = dev_id; |
248 | |
249 | if (pwm.variant.has_tint_cstat) { |
250 | u32 mask = (1 << pwm.event_id); |
251 | |
252 | writel(val: mask | (mask << 5), addr: pwm.base + REG_TINT_CSTAT); |
253 | } |
254 | |
255 | evt->event_handler(evt); |
256 | |
257 | return IRQ_HANDLED; |
258 | } |
259 | |
260 | static void __init samsung_clockevent_init(void) |
261 | { |
262 | unsigned long pclk; |
263 | unsigned long clock_rate; |
264 | unsigned int irq_number; |
265 | |
266 | pclk = clk_get_rate(clk: pwm.timerclk); |
267 | |
268 | samsung_timer_set_prescale(channel: pwm.event_id, prescale: pwm.tscaler_div); |
269 | samsung_timer_set_divisor(channel: pwm.event_id, divisor: pwm.tdiv); |
270 | |
271 | clock_rate = pclk / (pwm.tscaler_div * pwm.tdiv); |
272 | pwm.clock_count_per_tick = clock_rate / HZ; |
273 | |
274 | time_event_device.cpumask = cpumask_of(0); |
275 | clockevents_config_and_register(dev: &time_event_device, |
276 | freq: clock_rate, min_delta: 1, max_delta: pwm.tcnt_max); |
277 | |
278 | irq_number = pwm.irq[pwm.event_id]; |
279 | if (request_irq(irq: irq_number, handler: samsung_clock_event_isr, |
280 | IRQF_TIMER | IRQF_IRQPOLL, name: "samsung_time_irq" , |
281 | dev: &time_event_device)) |
282 | pr_err("%s: request_irq() failed\n" , "samsung_time_irq" ); |
283 | |
284 | if (pwm.variant.has_tint_cstat) { |
285 | u32 mask = (1 << pwm.event_id); |
286 | |
287 | writel(val: mask | (mask << 5), addr: pwm.base + REG_TINT_CSTAT); |
288 | } |
289 | } |
290 | |
291 | static void samsung_clocksource_suspend(struct clocksource *cs) |
292 | { |
293 | samsung_time_stop(channel: pwm.source_id); |
294 | } |
295 | |
296 | static void samsung_clocksource_resume(struct clocksource *cs) |
297 | { |
298 | samsung_timer_set_prescale(channel: pwm.source_id, prescale: pwm.tscaler_div); |
299 | samsung_timer_set_divisor(channel: pwm.source_id, divisor: pwm.tdiv); |
300 | |
301 | samsung_time_setup(channel: pwm.source_id, tcnt: pwm.tcnt_max); |
302 | samsung_time_start(channel: pwm.source_id, periodic: true); |
303 | } |
304 | |
305 | static u64 notrace samsung_clocksource_read(struct clocksource *c) |
306 | { |
307 | return ~readl_relaxed(pwm.source_reg); |
308 | } |
309 | |
310 | static struct clocksource samsung_clocksource = { |
311 | .name = "samsung_clocksource_timer" , |
312 | .rating = 250, |
313 | .read = samsung_clocksource_read, |
314 | .suspend = samsung_clocksource_suspend, |
315 | .resume = samsung_clocksource_resume, |
316 | .flags = CLOCK_SOURCE_IS_CONTINUOUS, |
317 | }; |
318 | |
319 | /* |
320 | * Override the global weak sched_clock symbol with this |
321 | * local implementation which uses the clocksource to get some |
322 | * better resolution when scheduling the kernel. We accept that |
323 | * this wraps around for now, since it is just a relative time |
324 | * stamp. (Inspired by U300 implementation.) |
325 | */ |
326 | static u64 notrace samsung_read_sched_clock(void) |
327 | { |
328 | return samsung_clocksource_read(NULL); |
329 | } |
330 | |
331 | static int __init samsung_clocksource_init(void) |
332 | { |
333 | unsigned long pclk; |
334 | unsigned long clock_rate; |
335 | |
336 | pclk = clk_get_rate(clk: pwm.timerclk); |
337 | |
338 | samsung_timer_set_prescale(channel: pwm.source_id, prescale: pwm.tscaler_div); |
339 | samsung_timer_set_divisor(channel: pwm.source_id, divisor: pwm.tdiv); |
340 | |
341 | clock_rate = pclk / (pwm.tscaler_div * pwm.tdiv); |
342 | |
343 | samsung_time_setup(channel: pwm.source_id, tcnt: pwm.tcnt_max); |
344 | samsung_time_start(channel: pwm.source_id, periodic: true); |
345 | |
346 | if (pwm.source_id == 4) |
347 | pwm.source_reg = pwm.base + 0x40; |
348 | else |
349 | pwm.source_reg = pwm.base + pwm.source_id * 0x0c + 0x14; |
350 | |
351 | sched_clock_register(read: samsung_read_sched_clock, |
352 | bits: pwm.variant.bits, rate: clock_rate); |
353 | |
354 | samsung_clocksource.mask = CLOCKSOURCE_MASK(pwm.variant.bits); |
355 | return clocksource_register_hz(cs: &samsung_clocksource, hz: clock_rate); |
356 | } |
357 | |
358 | static void __init samsung_timer_resources(void) |
359 | { |
360 | clk_prepare_enable(clk: pwm.timerclk); |
361 | |
362 | pwm.tcnt_max = (1UL << pwm.variant.bits) - 1; |
363 | if (pwm.variant.bits == 16) { |
364 | pwm.tscaler_div = 25; |
365 | pwm.tdiv = 2; |
366 | } else { |
367 | pwm.tscaler_div = 2; |
368 | pwm.tdiv = 1; |
369 | } |
370 | } |
371 | |
372 | /* |
373 | * PWM master driver |
374 | */ |
375 | static int __init _samsung_pwm_clocksource_init(void) |
376 | { |
377 | u8 mask; |
378 | int channel; |
379 | |
380 | mask = ~pwm.variant.output_mask & ((1 << SAMSUNG_PWM_NUM) - 1); |
381 | channel = fls(x: mask) - 1; |
382 | if (channel < 0) { |
383 | pr_crit("failed to find PWM channel for clocksource\n" ); |
384 | return -EINVAL; |
385 | } |
386 | pwm.source_id = channel; |
387 | |
388 | mask &= ~(1 << channel); |
389 | channel = fls(x: mask) - 1; |
390 | if (channel < 0) { |
391 | pr_crit("failed to find PWM channel for clock event\n" ); |
392 | return -EINVAL; |
393 | } |
394 | pwm.event_id = channel; |
395 | |
396 | samsung_timer_resources(); |
397 | samsung_clockevent_init(); |
398 | |
399 | return samsung_clocksource_init(); |
400 | } |
401 | |
402 | void __init samsung_pwm_clocksource_init(void __iomem *base, |
403 | unsigned int *irqs, |
404 | const struct samsung_pwm_variant *variant) |
405 | { |
406 | pwm.base = base; |
407 | memcpy(&pwm.variant, variant, sizeof(pwm.variant)); |
408 | memcpy(pwm.irq, irqs, SAMSUNG_PWM_NUM * sizeof(*irqs)); |
409 | |
410 | pwm.timerclk = clk_get(NULL, id: "timers" ); |
411 | if (IS_ERR(ptr: pwm.timerclk)) |
412 | panic(fmt: "failed to get timers clock for timer" ); |
413 | |
414 | _samsung_pwm_clocksource_init(); |
415 | } |
416 | |
417 | #ifdef CONFIG_TIMER_OF |
418 | static int __init samsung_pwm_alloc(struct device_node *np, |
419 | const struct samsung_pwm_variant *variant) |
420 | { |
421 | struct property *prop; |
422 | const __be32 *cur; |
423 | u32 val; |
424 | int i, ret; |
425 | |
426 | memcpy(&pwm.variant, variant, sizeof(pwm.variant)); |
427 | for (i = 0; i < SAMSUNG_PWM_NUM; ++i) |
428 | pwm.irq[i] = irq_of_parse_and_map(node: np, index: i); |
429 | |
430 | of_property_for_each_u32(np, "samsung,pwm-outputs" , prop, cur, val) { |
431 | if (val >= SAMSUNG_PWM_NUM) { |
432 | pr_warn("%s: invalid channel index in samsung,pwm-outputs property\n" , __func__); |
433 | continue; |
434 | } |
435 | pwm.variant.output_mask |= 1 << val; |
436 | } |
437 | |
438 | pwm.base = of_iomap(node: np, index: 0); |
439 | if (!pwm.base) { |
440 | pr_err("%s: failed to map PWM registers\n" , __func__); |
441 | return -ENXIO; |
442 | } |
443 | |
444 | pwm.timerclk = of_clk_get_by_name(np, name: "timers" ); |
445 | if (IS_ERR(ptr: pwm.timerclk)) { |
446 | pr_crit("failed to get timers clock for timer\n" ); |
447 | ret = PTR_ERR(ptr: pwm.timerclk); |
448 | goto err_clk; |
449 | } |
450 | |
451 | ret = _samsung_pwm_clocksource_init(); |
452 | if (ret) |
453 | goto err_clocksource; |
454 | |
455 | return 0; |
456 | |
457 | err_clocksource: |
458 | clk_put(clk: pwm.timerclk); |
459 | pwm.timerclk = NULL; |
460 | err_clk: |
461 | iounmap(addr: pwm.base); |
462 | pwm.base = NULL; |
463 | |
464 | return ret; |
465 | } |
466 | |
467 | static const struct samsung_pwm_variant s3c24xx_variant = { |
468 | .bits = 16, |
469 | .div_base = 1, |
470 | .has_tint_cstat = false, |
471 | .tclk_mask = (1 << 4), |
472 | }; |
473 | |
474 | static int __init s3c2410_pwm_clocksource_init(struct device_node *np) |
475 | { |
476 | return samsung_pwm_alloc(np, variant: &s3c24xx_variant); |
477 | } |
478 | TIMER_OF_DECLARE(s3c2410_pwm, "samsung,s3c2410-pwm" , s3c2410_pwm_clocksource_init); |
479 | |
480 | static const struct samsung_pwm_variant s3c64xx_variant = { |
481 | .bits = 32, |
482 | .div_base = 0, |
483 | .has_tint_cstat = true, |
484 | .tclk_mask = (1 << 7) | (1 << 6) | (1 << 5), |
485 | }; |
486 | |
487 | static int __init s3c64xx_pwm_clocksource_init(struct device_node *np) |
488 | { |
489 | return samsung_pwm_alloc(np, variant: &s3c64xx_variant); |
490 | } |
491 | TIMER_OF_DECLARE(s3c6400_pwm, "samsung,s3c6400-pwm" , s3c64xx_pwm_clocksource_init); |
492 | |
493 | static const struct samsung_pwm_variant s5p64x0_variant = { |
494 | .bits = 32, |
495 | .div_base = 0, |
496 | .has_tint_cstat = true, |
497 | .tclk_mask = 0, |
498 | }; |
499 | |
500 | static int __init s5p64x0_pwm_clocksource_init(struct device_node *np) |
501 | { |
502 | return samsung_pwm_alloc(np, variant: &s5p64x0_variant); |
503 | } |
504 | TIMER_OF_DECLARE(s5p6440_pwm, "samsung,s5p6440-pwm" , s5p64x0_pwm_clocksource_init); |
505 | |
506 | static const struct samsung_pwm_variant s5p_variant = { |
507 | .bits = 32, |
508 | .div_base = 0, |
509 | .has_tint_cstat = true, |
510 | .tclk_mask = (1 << 5), |
511 | }; |
512 | |
513 | static int __init s5p_pwm_clocksource_init(struct device_node *np) |
514 | { |
515 | return samsung_pwm_alloc(np, variant: &s5p_variant); |
516 | } |
517 | TIMER_OF_DECLARE(s5pc100_pwm, "samsung,s5pc100-pwm" , s5p_pwm_clocksource_init); |
518 | #endif |
519 | |