Commit | Line | Data |
---|---|---|
0ad6125b BB |
1 | /* |
2 | * Copyright (C) 2013 Boris BREZILLON <b.brezillon@overkiz.com> | |
3 | * | |
4 | * This program is free software; you can redistribute it and/or modify | |
5 | * it under the terms of the GNU General Public License as published by | |
6 | * the Free Software Foundation; either version 2 of the License, or | |
7 | * (at your option) any later version. | |
8 | * | |
9 | */ | |
10 | ||
11 | #include <linux/clk-provider.h> | |
12 | #include <linux/clkdev.h> | |
13 | #include <linux/clk/at91_pmc.h> | |
14 | #include <linux/of.h> | |
15 | #include <linux/of_address.h> | |
16 | #include <linux/io.h> | |
17 | #include <linux/interrupt.h> | |
18 | #include <linux/irq.h> | |
19 | #include <linux/irqchip/chained_irq.h> | |
20 | #include <linux/irqdomain.h> | |
21 | #include <linux/of_irq.h> | |
22 | ||
23 | #include <asm/proc-fns.h> | |
24 | ||
25 | #include "pmc.h" | |
26 | ||
27 | void __iomem *at91_pmc_base; | |
28 | EXPORT_SYMBOL_GPL(at91_pmc_base); | |
29 | ||
29ee506d AB |
30 | void at91rm9200_idle(void) |
31 | { | |
32 | /* | |
33 | * Disable the processor clock. The processor will be automatically | |
34 | * re-enabled by an interrupt or by a reset. | |
35 | */ | |
36 | at91_pmc_write(AT91_PMC_SCDR, AT91_PMC_PCK); | |
37 | } | |
38 | ||
0ad6125b BB |
39 | void at91sam9_idle(void) |
40 | { | |
41 | at91_pmc_write(AT91_PMC_SCDR, AT91_PMC_PCK); | |
42 | cpu_do_idle(); | |
43 | } | |
44 | ||
45 | int of_at91_get_clk_range(struct device_node *np, const char *propname, | |
46 | struct clk_range *range) | |
47 | { | |
48 | u32 min, max; | |
49 | int ret; | |
50 | ||
51 | ret = of_property_read_u32_index(np, propname, 0, &min); | |
52 | if (ret) | |
53 | return ret; | |
54 | ||
55 | ret = of_property_read_u32_index(np, propname, 1, &max); | |
56 | if (ret) | |
57 | return ret; | |
58 | ||
59 | if (range) { | |
60 | range->min = min; | |
61 | range->max = max; | |
62 | } | |
63 | ||
64 | return 0; | |
65 | } | |
66 | EXPORT_SYMBOL_GPL(of_at91_get_clk_range); | |
67 | ||
68 | static void pmc_irq_mask(struct irq_data *d) | |
69 | { | |
70 | struct at91_pmc *pmc = irq_data_get_irq_chip_data(d); | |
71 | ||
72 | pmc_write(pmc, AT91_PMC_IDR, 1 << d->hwirq); | |
73 | } | |
74 | ||
75 | static void pmc_irq_unmask(struct irq_data *d) | |
76 | { | |
77 | struct at91_pmc *pmc = irq_data_get_irq_chip_data(d); | |
78 | ||
79 | pmc_write(pmc, AT91_PMC_IER, 1 << d->hwirq); | |
80 | } | |
81 | ||
82 | static int pmc_irq_set_type(struct irq_data *d, unsigned type) | |
83 | { | |
84 | if (type != IRQ_TYPE_LEVEL_HIGH) { | |
85 | pr_warn("PMC: type not supported (support only IRQ_TYPE_LEVEL_HIGH type)\n"); | |
86 | return -EINVAL; | |
87 | } | |
88 | ||
89 | return 0; | |
90 | } | |
91 | ||
947f5b10 BB |
92 | static void pmc_irq_suspend(struct irq_data *d) |
93 | { | |
94 | struct at91_pmc *pmc = irq_data_get_irq_chip_data(d); | |
95 | ||
96 | pmc->imr = pmc_read(pmc, AT91_PMC_IMR); | |
97 | pmc_write(pmc, AT91_PMC_IDR, pmc->imr); | |
98 | } | |
99 | ||
100 | static void pmc_irq_resume(struct irq_data *d) | |
101 | { | |
102 | struct at91_pmc *pmc = irq_data_get_irq_chip_data(d); | |
103 | ||
104 | pmc_write(pmc, AT91_PMC_IER, pmc->imr); | |
105 | } | |
106 | ||
0ad6125b BB |
107 | static struct irq_chip pmc_irq = { |
108 | .name = "PMC", | |
109 | .irq_disable = pmc_irq_mask, | |
110 | .irq_mask = pmc_irq_mask, | |
111 | .irq_unmask = pmc_irq_unmask, | |
112 | .irq_set_type = pmc_irq_set_type, | |
947f5b10 BB |
113 | .irq_suspend = pmc_irq_suspend, |
114 | .irq_resume = pmc_irq_resume, | |
0ad6125b BB |
115 | }; |
116 | ||
117 | static struct lock_class_key pmc_lock_class; | |
118 | ||
119 | static int pmc_irq_map(struct irq_domain *h, unsigned int virq, | |
120 | irq_hw_number_t hw) | |
121 | { | |
122 | struct at91_pmc *pmc = h->host_data; | |
123 | ||
124 | irq_set_lockdep_class(virq, &pmc_lock_class); | |
125 | ||
126 | irq_set_chip_and_handler(virq, &pmc_irq, | |
127 | handle_level_irq); | |
0ad6125b BB |
128 | irq_set_chip_data(virq, pmc); |
129 | ||
130 | return 0; | |
131 | } | |
132 | ||
133 | static int pmc_irq_domain_xlate(struct irq_domain *d, | |
134 | struct device_node *ctrlr, | |
135 | const u32 *intspec, unsigned int intsize, | |
136 | irq_hw_number_t *out_hwirq, | |
137 | unsigned int *out_type) | |
138 | { | |
139 | struct at91_pmc *pmc = d->host_data; | |
140 | const struct at91_pmc_caps *caps = pmc->caps; | |
141 | ||
142 | if (WARN_ON(intsize < 1)) | |
143 | return -EINVAL; | |
144 | ||
145 | *out_hwirq = intspec[0]; | |
146 | ||
147 | if (!(caps->available_irqs & (1 << *out_hwirq))) | |
148 | return -EINVAL; | |
149 | ||
150 | *out_type = IRQ_TYPE_LEVEL_HIGH; | |
151 | ||
152 | return 0; | |
153 | } | |
154 | ||
d6c27678 | 155 | static const struct irq_domain_ops pmc_irq_ops = { |
0ad6125b BB |
156 | .map = pmc_irq_map, |
157 | .xlate = pmc_irq_domain_xlate, | |
158 | }; | |
159 | ||
160 | static irqreturn_t pmc_irq_handler(int irq, void *data) | |
161 | { | |
162 | struct at91_pmc *pmc = (struct at91_pmc *)data; | |
163 | unsigned long sr; | |
164 | int n; | |
165 | ||
166 | sr = pmc_read(pmc, AT91_PMC_SR) & pmc_read(pmc, AT91_PMC_IMR); | |
167 | if (!sr) | |
168 | return IRQ_NONE; | |
169 | ||
170 | for_each_set_bit(n, &sr, BITS_PER_LONG) | |
171 | generic_handle_irq(irq_find_mapping(pmc->irqdomain, n)); | |
172 | ||
173 | return IRQ_HANDLED; | |
174 | } | |
175 | ||
176 | static const struct at91_pmc_caps at91rm9200_caps = { | |
177 | .available_irqs = AT91_PMC_MOSCS | AT91_PMC_LOCKA | AT91_PMC_LOCKB | | |
178 | AT91_PMC_MCKRDY | AT91_PMC_PCK0RDY | | |
179 | AT91_PMC_PCK1RDY | AT91_PMC_PCK2RDY | | |
180 | AT91_PMC_PCK3RDY, | |
181 | }; | |
182 | ||
183 | static const struct at91_pmc_caps at91sam9260_caps = { | |
184 | .available_irqs = AT91_PMC_MOSCS | AT91_PMC_LOCKA | AT91_PMC_LOCKB | | |
185 | AT91_PMC_MCKRDY | AT91_PMC_PCK0RDY | | |
186 | AT91_PMC_PCK1RDY, | |
187 | }; | |
188 | ||
189 | static const struct at91_pmc_caps at91sam9g45_caps = { | |
190 | .available_irqs = AT91_PMC_MOSCS | AT91_PMC_LOCKA | AT91_PMC_MCKRDY | | |
191 | AT91_PMC_LOCKU | AT91_PMC_PCK0RDY | | |
192 | AT91_PMC_PCK1RDY, | |
193 | }; | |
194 | ||
195 | static const struct at91_pmc_caps at91sam9n12_caps = { | |
196 | .available_irqs = AT91_PMC_MOSCS | AT91_PMC_LOCKA | AT91_PMC_LOCKB | | |
197 | AT91_PMC_MCKRDY | AT91_PMC_PCK0RDY | | |
198 | AT91_PMC_PCK1RDY | AT91_PMC_MOSCSELS | | |
199 | AT91_PMC_MOSCRCS | AT91_PMC_CFDEV, | |
200 | }; | |
201 | ||
202 | static const struct at91_pmc_caps at91sam9x5_caps = { | |
203 | .available_irqs = AT91_PMC_MOSCS | AT91_PMC_LOCKA | AT91_PMC_MCKRDY | | |
204 | AT91_PMC_LOCKU | AT91_PMC_PCK0RDY | | |
205 | AT91_PMC_PCK1RDY | AT91_PMC_MOSCSELS | | |
206 | AT91_PMC_MOSCRCS | AT91_PMC_CFDEV, | |
207 | }; | |
208 | ||
a5752e57 NF |
209 | static const struct at91_pmc_caps sama5d2_caps = { |
210 | .available_irqs = AT91_PMC_MOSCS | AT91_PMC_LOCKA | AT91_PMC_MCKRDY | | |
211 | AT91_PMC_LOCKU | AT91_PMC_PCK0RDY | | |
212 | AT91_PMC_PCK1RDY | AT91_PMC_PCK2RDY | | |
213 | AT91_PMC_MOSCSELS | AT91_PMC_MOSCRCS | | |
214 | AT91_PMC_CFDEV | AT91_PMC_GCKRDY, | |
215 | }; | |
216 | ||
0ad6125b BB |
217 | static const struct at91_pmc_caps sama5d3_caps = { |
218 | .available_irqs = AT91_PMC_MOSCS | AT91_PMC_LOCKA | AT91_PMC_MCKRDY | | |
219 | AT91_PMC_LOCKU | AT91_PMC_PCK0RDY | | |
220 | AT91_PMC_PCK1RDY | AT91_PMC_PCK2RDY | | |
221 | AT91_PMC_MOSCSELS | AT91_PMC_MOSCRCS | | |
222 | AT91_PMC_CFDEV, | |
223 | }; | |
224 | ||
225 | static struct at91_pmc *__init at91_pmc_init(struct device_node *np, | |
226 | void __iomem *regbase, int virq, | |
227 | const struct at91_pmc_caps *caps) | |
228 | { | |
229 | struct at91_pmc *pmc; | |
230 | ||
231 | if (!regbase || !virq || !caps) | |
232 | return NULL; | |
233 | ||
234 | at91_pmc_base = regbase; | |
235 | ||
236 | pmc = kzalloc(sizeof(*pmc), GFP_KERNEL); | |
237 | if (!pmc) | |
238 | return NULL; | |
239 | ||
240 | spin_lock_init(&pmc->lock); | |
241 | pmc->regbase = regbase; | |
242 | pmc->virq = virq; | |
243 | pmc->caps = caps; | |
244 | ||
245 | pmc->irqdomain = irq_domain_add_linear(np, 32, &pmc_irq_ops, pmc); | |
246 | ||
247 | if (!pmc->irqdomain) | |
248 | goto out_free_pmc; | |
249 | ||
250 | pmc_write(pmc, AT91_PMC_IDR, 0xffffffff); | |
947f5b10 BB |
251 | if (request_irq(pmc->virq, pmc_irq_handler, |
252 | IRQF_SHARED | IRQF_COND_SUSPEND, "pmc", pmc)) | |
0ad6125b BB |
253 | goto out_remove_irqdomain; |
254 | ||
255 | return pmc; | |
256 | ||
257 | out_remove_irqdomain: | |
258 | irq_domain_remove(pmc->irqdomain); | |
259 | out_free_pmc: | |
260 | kfree(pmc); | |
261 | ||
262 | return NULL; | |
263 | } | |
264 | ||
7736c715 | 265 | static const struct of_device_id pmc_clk_ids[] __initconst = { |
80eded6c BB |
266 | /* Slow oscillator */ |
267 | { | |
268 | .compatible = "atmel,at91sam9260-clk-slow", | |
269 | .data = of_at91sam9260_clk_slow_setup, | |
270 | }, | |
38d34c31 | 271 | /* Main clock */ |
27cb1c20 BB |
272 | { |
273 | .compatible = "atmel,at91rm9200-clk-main-osc", | |
274 | .data = of_at91rm9200_clk_main_osc_setup, | |
275 | }, | |
276 | { | |
277 | .compatible = "atmel,at91sam9x5-clk-main-rc-osc", | |
278 | .data = of_at91sam9x5_clk_main_rc_osc_setup, | |
279 | }, | |
38d34c31 BB |
280 | { |
281 | .compatible = "atmel,at91rm9200-clk-main", | |
282 | .data = of_at91rm9200_clk_main_setup, | |
283 | }, | |
27cb1c20 BB |
284 | { |
285 | .compatible = "atmel,at91sam9x5-clk-main", | |
286 | .data = of_at91sam9x5_clk_main_setup, | |
287 | }, | |
1a748d2b BB |
288 | /* PLL clocks */ |
289 | { | |
290 | .compatible = "atmel,at91rm9200-clk-pll", | |
291 | .data = of_at91rm9200_clk_pll_setup, | |
292 | }, | |
293 | { | |
294 | .compatible = "atmel,at91sam9g45-clk-pll", | |
295 | .data = of_at91sam9g45_clk_pll_setup, | |
296 | }, | |
297 | { | |
298 | .compatible = "atmel,at91sam9g20-clk-pllb", | |
299 | .data = of_at91sam9g20_clk_pllb_setup, | |
300 | }, | |
301 | { | |
302 | .compatible = "atmel,sama5d3-clk-pll", | |
303 | .data = of_sama5d3_clk_pll_setup, | |
304 | }, | |
305 | { | |
306 | .compatible = "atmel,at91sam9x5-clk-plldiv", | |
307 | .data = of_at91sam9x5_clk_plldiv_setup, | |
308 | }, | |
e442d234 BB |
309 | /* Master clock */ |
310 | { | |
311 | .compatible = "atmel,at91rm9200-clk-master", | |
312 | .data = of_at91rm9200_clk_master_setup, | |
313 | }, | |
314 | { | |
315 | .compatible = "atmel,at91sam9x5-clk-master", | |
316 | .data = of_at91sam9x5_clk_master_setup, | |
317 | }, | |
5fba62ea BB |
318 | /* System clocks */ |
319 | { | |
320 | .compatible = "atmel,at91rm9200-clk-system", | |
321 | .data = of_at91rm9200_clk_sys_setup, | |
322 | }, | |
6114067e BB |
323 | /* Peripheral clocks */ |
324 | { | |
325 | .compatible = "atmel,at91rm9200-clk-peripheral", | |
326 | .data = of_at91rm9200_clk_periph_setup, | |
327 | }, | |
328 | { | |
329 | .compatible = "atmel,at91sam9x5-clk-peripheral", | |
330 | .data = of_at91sam9x5_clk_periph_setup, | |
331 | }, | |
1f22f8bb | 332 | /* Programmable clocks */ |
1f22f8bb BB |
333 | { |
334 | .compatible = "atmel,at91rm9200-clk-programmable", | |
335 | .data = of_at91rm9200_clk_prog_setup, | |
336 | }, | |
337 | { | |
338 | .compatible = "atmel,at91sam9g45-clk-programmable", | |
339 | .data = of_at91sam9g45_clk_prog_setup, | |
340 | }, | |
341 | { | |
342 | .compatible = "atmel,at91sam9x5-clk-programmable", | |
343 | .data = of_at91sam9x5_clk_prog_setup, | |
344 | }, | |
f090fb37 BB |
345 | /* UTMI clock */ |
346 | #if defined(CONFIG_HAVE_AT91_UTMI) | |
347 | { | |
348 | .compatible = "atmel,at91sam9x5-clk-utmi", | |
349 | .data = of_at91sam9x5_clk_utmi_setup, | |
350 | }, | |
c84a61d8 BB |
351 | #endif |
352 | /* USB clock */ | |
353 | #if defined(CONFIG_HAVE_AT91_USB_CLK) | |
354 | { | |
355 | .compatible = "atmel,at91rm9200-clk-usb", | |
356 | .data = of_at91rm9200_clk_usb_setup, | |
357 | }, | |
358 | { | |
359 | .compatible = "atmel,at91sam9x5-clk-usb", | |
360 | .data = of_at91sam9x5_clk_usb_setup, | |
361 | }, | |
362 | { | |
363 | .compatible = "atmel,at91sam9n12-clk-usb", | |
364 | .data = of_at91sam9n12_clk_usb_setup, | |
365 | }, | |
a9c0688f BB |
366 | #endif |
367 | /* SMD clock */ | |
368 | #if defined(CONFIG_HAVE_AT91_SMD) | |
369 | { | |
370 | .compatible = "atmel,at91sam9x5-clk-smd", | |
371 | .data = of_at91sam9x5_clk_smd_setup, | |
372 | }, | |
bcc5fd49 AB |
373 | #endif |
374 | #if defined(CONFIG_HAVE_AT91_H32MX) | |
375 | { | |
376 | .compatible = "atmel,sama5d4-clk-h32mx", | |
377 | .data = of_sama5d4_clk_h32mx_setup, | |
378 | }, | |
df70aeef NF |
379 | #endif |
380 | #if defined(CONFIG_HAVE_AT91_GENERATED_CLK) | |
381 | { | |
382 | .compatible = "atmel,sama5d2-clk-generated", | |
383 | .data = of_sama5d2_clk_generated_setup, | |
384 | }, | |
1f22f8bb | 385 | #endif |
0ad6125b BB |
386 | { /*sentinel*/ } |
387 | }; | |
388 | ||
389 | static void __init of_at91_pmc_setup(struct device_node *np, | |
390 | const struct at91_pmc_caps *caps) | |
391 | { | |
392 | struct at91_pmc *pmc; | |
393 | struct device_node *childnp; | |
394 | void (*clk_setup)(struct device_node *, struct at91_pmc *); | |
395 | const struct of_device_id *clk_id; | |
396 | void __iomem *regbase = of_iomap(np, 0); | |
397 | int virq; | |
398 | ||
399 | if (!regbase) | |
400 | return; | |
401 | ||
402 | virq = irq_of_parse_and_map(np, 0); | |
403 | if (!virq) | |
404 | return; | |
405 | ||
406 | pmc = at91_pmc_init(np, regbase, virq, caps); | |
407 | if (!pmc) | |
408 | return; | |
409 | for_each_child_of_node(np, childnp) { | |
410 | clk_id = of_match_node(pmc_clk_ids, childnp); | |
411 | if (!clk_id) | |
412 | continue; | |
413 | clk_setup = clk_id->data; | |
414 | clk_setup(childnp, pmc); | |
415 | } | |
416 | } | |
417 | ||
418 | static void __init of_at91rm9200_pmc_setup(struct device_node *np) | |
419 | { | |
420 | of_at91_pmc_setup(np, &at91rm9200_caps); | |
421 | } | |
422 | CLK_OF_DECLARE(at91rm9200_clk_pmc, "atmel,at91rm9200-pmc", | |
423 | of_at91rm9200_pmc_setup); | |
424 | ||
425 | static void __init of_at91sam9260_pmc_setup(struct device_node *np) | |
426 | { | |
427 | of_at91_pmc_setup(np, &at91sam9260_caps); | |
428 | } | |
429 | CLK_OF_DECLARE(at91sam9260_clk_pmc, "atmel,at91sam9260-pmc", | |
430 | of_at91sam9260_pmc_setup); | |
431 | ||
432 | static void __init of_at91sam9g45_pmc_setup(struct device_node *np) | |
433 | { | |
434 | of_at91_pmc_setup(np, &at91sam9g45_caps); | |
435 | } | |
436 | CLK_OF_DECLARE(at91sam9g45_clk_pmc, "atmel,at91sam9g45-pmc", | |
437 | of_at91sam9g45_pmc_setup); | |
438 | ||
439 | static void __init of_at91sam9n12_pmc_setup(struct device_node *np) | |
440 | { | |
441 | of_at91_pmc_setup(np, &at91sam9n12_caps); | |
442 | } | |
443 | CLK_OF_DECLARE(at91sam9n12_clk_pmc, "atmel,at91sam9n12-pmc", | |
444 | of_at91sam9n12_pmc_setup); | |
445 | ||
446 | static void __init of_at91sam9x5_pmc_setup(struct device_node *np) | |
447 | { | |
448 | of_at91_pmc_setup(np, &at91sam9x5_caps); | |
449 | } | |
450 | CLK_OF_DECLARE(at91sam9x5_clk_pmc, "atmel,at91sam9x5-pmc", | |
451 | of_at91sam9x5_pmc_setup); | |
452 | ||
a5752e57 NF |
453 | static void __init of_sama5d2_pmc_setup(struct device_node *np) |
454 | { | |
455 | of_at91_pmc_setup(np, &sama5d2_caps); | |
456 | } | |
457 | CLK_OF_DECLARE(sama5d2_clk_pmc, "atmel,sama5d2-pmc", | |
458 | of_sama5d2_pmc_setup); | |
459 | ||
0ad6125b BB |
460 | static void __init of_sama5d3_pmc_setup(struct device_node *np) |
461 | { | |
462 | of_at91_pmc_setup(np, &sama5d3_caps); | |
463 | } | |
464 | CLK_OF_DECLARE(sama5d3_clk_pmc, "atmel,sama5d3-pmc", | |
465 | of_sama5d3_pmc_setup); |