Commit | Line | Data |
---|---|---|
5ecafc12 ZY |
1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* | |
3 | * Ingenic XBurst SoCs SYSOST clocks driver | |
4 | * Copyright (c) 2020 周琰杰 (Zhou Yanjie) <zhouyanjie@wanyeetech.com> | |
5 | */ | |
6 | ||
3b87265d | 7 | #include <linux/bitfield.h> |
5ecafc12 ZY |
8 | #include <linux/bitops.h> |
9 | #include <linux/clk.h> | |
10 | #include <linux/clk-provider.h> | |
11 | #include <linux/clockchips.h> | |
12 | #include <linux/clocksource.h> | |
13 | #include <linux/interrupt.h> | |
14 | #include <linux/mfd/syscon.h> | |
15 | #include <linux/of_address.h> | |
16 | #include <linux/of_irq.h> | |
17 | #include <linux/sched_clock.h> | |
18 | #include <linux/slab.h> | |
19 | #include <linux/syscore_ops.h> | |
20 | ||
21 | #include <dt-bindings/clock/ingenic,sysost.h> | |
22 | ||
23 | /* OST register offsets */ | |
24 | #define OST_REG_OSTCCR 0x00 | |
25 | #define OST_REG_OSTCR 0x08 | |
26 | #define OST_REG_OSTFR 0x0c | |
27 | #define OST_REG_OSTMR 0x10 | |
28 | #define OST_REG_OST1DFR 0x14 | |
29 | #define OST_REG_OST1CNT 0x18 | |
30 | #define OST_REG_OST2CNTL 0x20 | |
31 | #define OST_REG_OSTCNT2HBUF 0x24 | |
32 | #define OST_REG_OSTESR 0x34 | |
33 | #define OST_REG_OSTECR 0x38 | |
34 | ||
35 | /* bits within the OSTCCR register */ | |
36 | #define OSTCCR_PRESCALE1_MASK 0x3 | |
37 | #define OSTCCR_PRESCALE2_MASK 0xc | |
5ecafc12 ZY |
38 | |
39 | /* bits within the OSTCR register */ | |
40 | #define OSTCR_OST1CLR BIT(0) | |
41 | #define OSTCR_OST2CLR BIT(1) | |
42 | ||
43 | /* bits within the OSTFR register */ | |
44 | #define OSTFR_FFLAG BIT(0) | |
45 | ||
46 | /* bits within the OSTMR register */ | |
47 | #define OSTMR_FMASK BIT(0) | |
48 | ||
49 | /* bits within the OSTESR register */ | |
50 | #define OSTESR_OST1ENS BIT(0) | |
51 | #define OSTESR_OST2ENS BIT(1) | |
52 | ||
53 | /* bits within the OSTECR register */ | |
54 | #define OSTECR_OST1ENC BIT(0) | |
55 | #define OSTECR_OST2ENC BIT(1) | |
56 | ||
57 | struct ingenic_soc_info { | |
58 | unsigned int num_channels; | |
59 | }; | |
60 | ||
61 | struct ingenic_ost_clk_info { | |
62 | struct clk_init_data init_data; | |
63 | u8 ostccr_reg; | |
64 | }; | |
65 | ||
66 | struct ingenic_ost_clk { | |
67 | struct clk_hw hw; | |
68 | unsigned int idx; | |
69 | struct ingenic_ost *ost; | |
70 | const struct ingenic_ost_clk_info *info; | |
71 | }; | |
72 | ||
73 | struct ingenic_ost { | |
74 | void __iomem *base; | |
75 | const struct ingenic_soc_info *soc_info; | |
76 | struct clk *clk, *percpu_timer_clk, *global_timer_clk; | |
77 | struct clock_event_device cevt; | |
78 | struct clocksource cs; | |
79 | char name[20]; | |
80 | ||
81 | struct clk_hw_onecell_data *clocks; | |
82 | }; | |
83 | ||
84 | static struct ingenic_ost *ingenic_ost; | |
85 | ||
86 | static inline struct ingenic_ost_clk *to_ost_clk(struct clk_hw *hw) | |
87 | { | |
88 | return container_of(hw, struct ingenic_ost_clk, hw); | |
89 | } | |
90 | ||
91 | static unsigned long ingenic_ost_percpu_timer_recalc_rate(struct clk_hw *hw, | |
92 | unsigned long parent_rate) | |
93 | { | |
94 | struct ingenic_ost_clk *ost_clk = to_ost_clk(hw); | |
95 | const struct ingenic_ost_clk_info *info = ost_clk->info; | |
96 | unsigned int prescale; | |
97 | ||
98 | prescale = readl(ost_clk->ost->base + info->ostccr_reg); | |
99 | ||
3b87265d | 100 | prescale = FIELD_GET(OSTCCR_PRESCALE1_MASK, prescale); |
5ecafc12 ZY |
101 | |
102 | return parent_rate >> (prescale * 2); | |
103 | } | |
104 | ||
105 | static unsigned long ingenic_ost_global_timer_recalc_rate(struct clk_hw *hw, | |
106 | unsigned long parent_rate) | |
107 | { | |
108 | struct ingenic_ost_clk *ost_clk = to_ost_clk(hw); | |
109 | const struct ingenic_ost_clk_info *info = ost_clk->info; | |
110 | unsigned int prescale; | |
111 | ||
112 | prescale = readl(ost_clk->ost->base + info->ostccr_reg); | |
113 | ||
3b87265d | 114 | prescale = FIELD_GET(OSTCCR_PRESCALE2_MASK, prescale); |
5ecafc12 ZY |
115 | |
116 | return parent_rate >> (prescale * 2); | |
117 | } | |
118 | ||
119 | static u8 ingenic_ost_get_prescale(unsigned long rate, unsigned long req_rate) | |
120 | { | |
121 | u8 prescale; | |
122 | ||
123 | for (prescale = 0; prescale < 2; prescale++) | |
124 | if ((rate >> (prescale * 2)) <= req_rate) | |
125 | return prescale; | |
126 | ||
127 | return 2; /* /16 divider */ | |
128 | } | |
129 | ||
130 | static long ingenic_ost_round_rate(struct clk_hw *hw, unsigned long req_rate, | |
131 | unsigned long *parent_rate) | |
132 | { | |
133 | unsigned long rate = *parent_rate; | |
134 | u8 prescale; | |
135 | ||
136 | if (req_rate > rate) | |
137 | return rate; | |
138 | ||
139 | prescale = ingenic_ost_get_prescale(rate, req_rate); | |
140 | ||
141 | return rate >> (prescale * 2); | |
142 | } | |
143 | ||
144 | static int ingenic_ost_percpu_timer_set_rate(struct clk_hw *hw, unsigned long req_rate, | |
145 | unsigned long parent_rate) | |
146 | { | |
147 | struct ingenic_ost_clk *ost_clk = to_ost_clk(hw); | |
148 | const struct ingenic_ost_clk_info *info = ost_clk->info; | |
149 | u8 prescale = ingenic_ost_get_prescale(parent_rate, req_rate); | |
150 | int val; | |
151 | ||
152 | val = readl(ost_clk->ost->base + info->ostccr_reg); | |
3b87265d ZY |
153 | val &= ~OSTCCR_PRESCALE1_MASK; |
154 | val |= FIELD_PREP(OSTCCR_PRESCALE1_MASK, prescale); | |
5ecafc12 ZY |
155 | writel(val, ost_clk->ost->base + info->ostccr_reg); |
156 | ||
157 | return 0; | |
158 | } | |
159 | ||
160 | static int ingenic_ost_global_timer_set_rate(struct clk_hw *hw, unsigned long req_rate, | |
161 | unsigned long parent_rate) | |
162 | { | |
163 | struct ingenic_ost_clk *ost_clk = to_ost_clk(hw); | |
164 | const struct ingenic_ost_clk_info *info = ost_clk->info; | |
165 | u8 prescale = ingenic_ost_get_prescale(parent_rate, req_rate); | |
166 | int val; | |
167 | ||
168 | val = readl(ost_clk->ost->base + info->ostccr_reg); | |
3b87265d ZY |
169 | val &= ~OSTCCR_PRESCALE2_MASK; |
170 | val |= FIELD_PREP(OSTCCR_PRESCALE2_MASK, prescale); | |
5ecafc12 ZY |
171 | writel(val, ost_clk->ost->base + info->ostccr_reg); |
172 | ||
173 | return 0; | |
174 | } | |
175 | ||
176 | static const struct clk_ops ingenic_ost_percpu_timer_ops = { | |
177 | .recalc_rate = ingenic_ost_percpu_timer_recalc_rate, | |
178 | .round_rate = ingenic_ost_round_rate, | |
179 | .set_rate = ingenic_ost_percpu_timer_set_rate, | |
180 | }; | |
181 | ||
182 | static const struct clk_ops ingenic_ost_global_timer_ops = { | |
183 | .recalc_rate = ingenic_ost_global_timer_recalc_rate, | |
184 | .round_rate = ingenic_ost_round_rate, | |
185 | .set_rate = ingenic_ost_global_timer_set_rate, | |
186 | }; | |
187 | ||
188 | static const char * const ingenic_ost_clk_parents[] = { "ext" }; | |
189 | ||
870a6e15 | 190 | static const struct ingenic_ost_clk_info x1000_ost_clk_info[] = { |
5ecafc12 ZY |
191 | [OST_CLK_PERCPU_TIMER] = { |
192 | .init_data = { | |
193 | .name = "percpu timer", | |
194 | .parent_names = ingenic_ost_clk_parents, | |
195 | .num_parents = ARRAY_SIZE(ingenic_ost_clk_parents), | |
196 | .ops = &ingenic_ost_percpu_timer_ops, | |
197 | .flags = CLK_SET_RATE_UNGATE, | |
198 | }, | |
199 | .ostccr_reg = OST_REG_OSTCCR, | |
200 | }, | |
201 | ||
202 | [OST_CLK_GLOBAL_TIMER] = { | |
203 | .init_data = { | |
204 | .name = "global timer", | |
205 | .parent_names = ingenic_ost_clk_parents, | |
206 | .num_parents = ARRAY_SIZE(ingenic_ost_clk_parents), | |
207 | .ops = &ingenic_ost_global_timer_ops, | |
208 | .flags = CLK_SET_RATE_UNGATE, | |
209 | }, | |
210 | .ostccr_reg = OST_REG_OSTCCR, | |
211 | }, | |
212 | }; | |
213 | ||
214 | static u64 notrace ingenic_ost_global_timer_read_cntl(void) | |
215 | { | |
216 | struct ingenic_ost *ost = ingenic_ost; | |
217 | unsigned int count; | |
218 | ||
219 | count = readl(ost->base + OST_REG_OST2CNTL); | |
220 | ||
221 | return count; | |
222 | } | |
223 | ||
224 | static u64 notrace ingenic_ost_clocksource_read(struct clocksource *cs) | |
225 | { | |
226 | return ingenic_ost_global_timer_read_cntl(); | |
227 | } | |
228 | ||
229 | static inline struct ingenic_ost *to_ingenic_ost(struct clock_event_device *evt) | |
230 | { | |
231 | return container_of(evt, struct ingenic_ost, cevt); | |
232 | } | |
233 | ||
234 | static int ingenic_ost_cevt_set_state_shutdown(struct clock_event_device *evt) | |
235 | { | |
236 | struct ingenic_ost *ost = to_ingenic_ost(evt); | |
237 | ||
238 | writel(OSTECR_OST1ENC, ost->base + OST_REG_OSTECR); | |
239 | ||
240 | return 0; | |
241 | } | |
242 | ||
243 | static int ingenic_ost_cevt_set_next(unsigned long next, | |
244 | struct clock_event_device *evt) | |
245 | { | |
246 | struct ingenic_ost *ost = to_ingenic_ost(evt); | |
247 | ||
248 | writel((u32)~OSTFR_FFLAG, ost->base + OST_REG_OSTFR); | |
249 | writel(next, ost->base + OST_REG_OST1DFR); | |
250 | writel(OSTCR_OST1CLR, ost->base + OST_REG_OSTCR); | |
251 | writel(OSTESR_OST1ENS, ost->base + OST_REG_OSTESR); | |
252 | writel((u32)~OSTMR_FMASK, ost->base + OST_REG_OSTMR); | |
253 | ||
254 | return 0; | |
255 | } | |
256 | ||
257 | static irqreturn_t ingenic_ost_cevt_cb(int irq, void *dev_id) | |
258 | { | |
259 | struct clock_event_device *evt = dev_id; | |
260 | struct ingenic_ost *ost = to_ingenic_ost(evt); | |
261 | ||
262 | writel(OSTECR_OST1ENC, ost->base + OST_REG_OSTECR); | |
263 | ||
264 | if (evt->event_handler) | |
265 | evt->event_handler(evt); | |
266 | ||
267 | return IRQ_HANDLED; | |
268 | } | |
269 | ||
270 | static int __init ingenic_ost_register_clock(struct ingenic_ost *ost, | |
271 | unsigned int idx, const struct ingenic_ost_clk_info *info, | |
272 | struct clk_hw_onecell_data *clocks) | |
273 | { | |
274 | struct ingenic_ost_clk *ost_clk; | |
275 | int val, err; | |
276 | ||
277 | ost_clk = kzalloc(sizeof(*ost_clk), GFP_KERNEL); | |
278 | if (!ost_clk) | |
279 | return -ENOMEM; | |
280 | ||
281 | ost_clk->hw.init = &info->init_data; | |
282 | ost_clk->idx = idx; | |
283 | ost_clk->info = info; | |
284 | ost_clk->ost = ost; | |
285 | ||
286 | /* Reset clock divider */ | |
287 | val = readl(ost->base + info->ostccr_reg); | |
288 | val &= ~(OSTCCR_PRESCALE1_MASK | OSTCCR_PRESCALE2_MASK); | |
289 | writel(val, ost->base + info->ostccr_reg); | |
290 | ||
291 | err = clk_hw_register(NULL, &ost_clk->hw); | |
292 | if (err) { | |
293 | kfree(ost_clk); | |
294 | return err; | |
295 | } | |
296 | ||
297 | clocks->hws[idx] = &ost_clk->hw; | |
298 | ||
299 | return 0; | |
300 | } | |
301 | ||
302 | static struct clk * __init ingenic_ost_get_clock(struct device_node *np, int id) | |
303 | { | |
304 | struct of_phandle_args args; | |
305 | ||
306 | args.np = np; | |
307 | args.args_count = 1; | |
308 | args.args[0] = id; | |
309 | ||
310 | return of_clk_get_from_provider(&args); | |
311 | } | |
312 | ||
313 | static int __init ingenic_ost_percpu_timer_init(struct device_node *np, | |
314 | struct ingenic_ost *ost) | |
315 | { | |
316 | unsigned int timer_virq, channel = OST_CLK_PERCPU_TIMER; | |
317 | unsigned long rate; | |
318 | int err; | |
319 | ||
320 | ost->percpu_timer_clk = ingenic_ost_get_clock(np, channel); | |
321 | if (IS_ERR(ost->percpu_timer_clk)) | |
322 | return PTR_ERR(ost->percpu_timer_clk); | |
323 | ||
324 | err = clk_prepare_enable(ost->percpu_timer_clk); | |
325 | if (err) | |
326 | goto err_clk_put; | |
327 | ||
328 | rate = clk_get_rate(ost->percpu_timer_clk); | |
329 | if (!rate) { | |
330 | err = -EINVAL; | |
331 | goto err_clk_disable; | |
332 | } | |
333 | ||
334 | timer_virq = of_irq_get(np, 0); | |
335 | if (!timer_virq) { | |
336 | err = -EINVAL; | |
337 | goto err_clk_disable; | |
338 | } | |
339 | ||
340 | snprintf(ost->name, sizeof(ost->name), "OST percpu timer"); | |
341 | ||
342 | err = request_irq(timer_virq, ingenic_ost_cevt_cb, IRQF_TIMER, | |
343 | ost->name, &ost->cevt); | |
344 | if (err) | |
345 | goto err_irq_dispose_mapping; | |
346 | ||
347 | ost->cevt.cpumask = cpumask_of(smp_processor_id()); | |
348 | ost->cevt.features = CLOCK_EVT_FEAT_ONESHOT; | |
349 | ost->cevt.name = ost->name; | |
350 | ost->cevt.rating = 400; | |
351 | ost->cevt.set_state_shutdown = ingenic_ost_cevt_set_state_shutdown; | |
352 | ost->cevt.set_next_event = ingenic_ost_cevt_set_next; | |
353 | ||
354 | clockevents_config_and_register(&ost->cevt, rate, 4, 0xffffffff); | |
355 | ||
356 | return 0; | |
357 | ||
358 | err_irq_dispose_mapping: | |
359 | irq_dispose_mapping(timer_virq); | |
360 | err_clk_disable: | |
361 | clk_disable_unprepare(ost->percpu_timer_clk); | |
362 | err_clk_put: | |
363 | clk_put(ost->percpu_timer_clk); | |
364 | return err; | |
365 | } | |
366 | ||
367 | static int __init ingenic_ost_global_timer_init(struct device_node *np, | |
368 | struct ingenic_ost *ost) | |
369 | { | |
370 | unsigned int channel = OST_CLK_GLOBAL_TIMER; | |
371 | struct clocksource *cs = &ost->cs; | |
372 | unsigned long rate; | |
373 | int err; | |
374 | ||
375 | ost->global_timer_clk = ingenic_ost_get_clock(np, channel); | |
376 | if (IS_ERR(ost->global_timer_clk)) | |
377 | return PTR_ERR(ost->global_timer_clk); | |
378 | ||
379 | err = clk_prepare_enable(ost->global_timer_clk); | |
380 | if (err) | |
381 | goto err_clk_put; | |
382 | ||
383 | rate = clk_get_rate(ost->global_timer_clk); | |
384 | if (!rate) { | |
385 | err = -EINVAL; | |
386 | goto err_clk_disable; | |
387 | } | |
388 | ||
389 | /* Clear counter CNT registers */ | |
390 | writel(OSTCR_OST2CLR, ost->base + OST_REG_OSTCR); | |
391 | ||
392 | /* Enable OST channel */ | |
393 | writel(OSTESR_OST2ENS, ost->base + OST_REG_OSTESR); | |
394 | ||
395 | cs->name = "ingenic-ost"; | |
396 | cs->rating = 400; | |
397 | cs->flags = CLOCK_SOURCE_IS_CONTINUOUS; | |
398 | cs->mask = CLOCKSOURCE_MASK(32); | |
399 | cs->read = ingenic_ost_clocksource_read; | |
400 | ||
401 | err = clocksource_register_hz(cs, rate); | |
402 | if (err) | |
403 | goto err_clk_disable; | |
404 | ||
405 | return 0; | |
406 | ||
407 | err_clk_disable: | |
408 | clk_disable_unprepare(ost->global_timer_clk); | |
409 | err_clk_put: | |
410 | clk_put(ost->global_timer_clk); | |
411 | return err; | |
412 | } | |
413 | ||
414 | static const struct ingenic_soc_info x1000_soc_info = { | |
415 | .num_channels = 2, | |
416 | }; | |
417 | ||
870a6e15 ZY |
418 | static const struct of_device_id __maybe_unused ingenic_ost_of_matches[] __initconst = { |
419 | { .compatible = "ingenic,x1000-ost", .data = &x1000_soc_info }, | |
5ecafc12 ZY |
420 | { /* sentinel */ } |
421 | }; | |
422 | ||
423 | static int __init ingenic_ost_probe(struct device_node *np) | |
424 | { | |
870a6e15 | 425 | const struct of_device_id *id = of_match_node(ingenic_ost_of_matches, np); |
5ecafc12 ZY |
426 | struct ingenic_ost *ost; |
427 | unsigned int i; | |
428 | int ret; | |
429 | ||
430 | ost = kzalloc(sizeof(*ost), GFP_KERNEL); | |
431 | if (!ost) | |
432 | return -ENOMEM; | |
433 | ||
434 | ost->base = of_io_request_and_map(np, 0, of_node_full_name(np)); | |
435 | if (IS_ERR(ost->base)) { | |
436 | pr_err("%s: Failed to map OST registers\n", __func__); | |
437 | ret = PTR_ERR(ost->base); | |
438 | goto err_free_ost; | |
439 | } | |
440 | ||
441 | ost->clk = of_clk_get_by_name(np, "ost"); | |
442 | if (IS_ERR(ost->clk)) { | |
443 | ret = PTR_ERR(ost->clk); | |
444 | pr_crit("%s: Cannot get OST clock\n", __func__); | |
445 | goto err_free_ost; | |
446 | } | |
447 | ||
448 | ret = clk_prepare_enable(ost->clk); | |
449 | if (ret) { | |
450 | pr_crit("%s: Unable to enable OST clock\n", __func__); | |
451 | goto err_put_clk; | |
452 | } | |
453 | ||
454 | ost->soc_info = id->data; | |
455 | ||
456 | ost->clocks = kzalloc(struct_size(ost->clocks, hws, ost->soc_info->num_channels), | |
457 | GFP_KERNEL); | |
458 | if (!ost->clocks) { | |
459 | ret = -ENOMEM; | |
460 | goto err_clk_disable; | |
461 | } | |
462 | ||
463 | ost->clocks->num = ost->soc_info->num_channels; | |
464 | ||
465 | for (i = 0; i < ost->clocks->num; i++) { | |
870a6e15 | 466 | ret = ingenic_ost_register_clock(ost, i, &x1000_ost_clk_info[i], ost->clocks); |
5ecafc12 ZY |
467 | if (ret) { |
468 | pr_crit("%s: Cannot register clock %d\n", __func__, i); | |
469 | goto err_unregister_ost_clocks; | |
470 | } | |
471 | } | |
472 | ||
473 | ret = of_clk_add_hw_provider(np, of_clk_hw_onecell_get, ost->clocks); | |
474 | if (ret) { | |
475 | pr_crit("%s: Cannot add OF clock provider\n", __func__); | |
476 | goto err_unregister_ost_clocks; | |
477 | } | |
478 | ||
479 | ingenic_ost = ost; | |
480 | ||
481 | return 0; | |
482 | ||
483 | err_unregister_ost_clocks: | |
484 | for (i = 0; i < ost->clocks->num; i++) | |
485 | if (ost->clocks->hws[i]) | |
486 | clk_hw_unregister(ost->clocks->hws[i]); | |
487 | kfree(ost->clocks); | |
488 | err_clk_disable: | |
489 | clk_disable_unprepare(ost->clk); | |
490 | err_put_clk: | |
491 | clk_put(ost->clk); | |
492 | err_free_ost: | |
493 | kfree(ost); | |
494 | return ret; | |
495 | } | |
496 | ||
497 | static int __init ingenic_ost_init(struct device_node *np) | |
498 | { | |
499 | struct ingenic_ost *ost; | |
500 | unsigned long rate; | |
501 | int ret; | |
502 | ||
503 | ret = ingenic_ost_probe(np); | |
504 | if (ret) { | |
505 | pr_crit("%s: Failed to initialize OST clocks: %d\n", __func__, ret); | |
506 | return ret; | |
507 | } | |
508 | ||
509 | of_node_clear_flag(np, OF_POPULATED); | |
510 | ||
511 | ost = ingenic_ost; | |
512 | if (IS_ERR(ost)) | |
513 | return PTR_ERR(ost); | |
514 | ||
515 | ret = ingenic_ost_global_timer_init(np, ost); | |
516 | if (ret) { | |
517 | pr_crit("%s: Unable to init global timer: %x\n", __func__, ret); | |
518 | goto err_free_ingenic_ost; | |
519 | } | |
520 | ||
521 | ret = ingenic_ost_percpu_timer_init(np, ost); | |
522 | if (ret) | |
523 | goto err_ost_global_timer_cleanup; | |
524 | ||
525 | /* Register the sched_clock at the end as there's no way to undo it */ | |
526 | rate = clk_get_rate(ost->global_timer_clk); | |
527 | sched_clock_register(ingenic_ost_global_timer_read_cntl, 32, rate); | |
528 | ||
529 | return 0; | |
530 | ||
531 | err_ost_global_timer_cleanup: | |
532 | clocksource_unregister(&ost->cs); | |
533 | clk_disable_unprepare(ost->global_timer_clk); | |
534 | clk_put(ost->global_timer_clk); | |
535 | err_free_ingenic_ost: | |
536 | kfree(ost); | |
537 | return ret; | |
538 | } | |
539 | ||
540 | TIMER_OF_DECLARE(x1000_ost, "ingenic,x1000-ost", ingenic_ost_init); |