diff options
Diffstat (limited to 'drivers/clk/at91/pmc.c')
-rw-r--r-- | drivers/clk/at91/pmc.c | 395 |
1 files changed, 395 insertions, 0 deletions
diff --git a/drivers/clk/at91/pmc.c b/drivers/clk/at91/pmc.c new file mode 100644 index 000000000000..6a61477a57e0 --- /dev/null +++ b/drivers/clk/at91/pmc.c | |||
@@ -0,0 +1,395 @@ | |||
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 | |||
30 | void at91sam9_idle(void) | ||
31 | { | ||
32 | at91_pmc_write(AT91_PMC_SCDR, AT91_PMC_PCK); | ||
33 | cpu_do_idle(); | ||
34 | } | ||
35 | |||
36 | int of_at91_get_clk_range(struct device_node *np, const char *propname, | ||
37 | struct clk_range *range) | ||
38 | { | ||
39 | u32 min, max; | ||
40 | int ret; | ||
41 | |||
42 | ret = of_property_read_u32_index(np, propname, 0, &min); | ||
43 | if (ret) | ||
44 | return ret; | ||
45 | |||
46 | ret = of_property_read_u32_index(np, propname, 1, &max); | ||
47 | if (ret) | ||
48 | return ret; | ||
49 | |||
50 | if (range) { | ||
51 | range->min = min; | ||
52 | range->max = max; | ||
53 | } | ||
54 | |||
55 | return 0; | ||
56 | } | ||
57 | EXPORT_SYMBOL_GPL(of_at91_get_clk_range); | ||
58 | |||
59 | static void pmc_irq_mask(struct irq_data *d) | ||
60 | { | ||
61 | struct at91_pmc *pmc = irq_data_get_irq_chip_data(d); | ||
62 | |||
63 | pmc_write(pmc, AT91_PMC_IDR, 1 << d->hwirq); | ||
64 | } | ||
65 | |||
66 | static void pmc_irq_unmask(struct irq_data *d) | ||
67 | { | ||
68 | struct at91_pmc *pmc = irq_data_get_irq_chip_data(d); | ||
69 | |||
70 | pmc_write(pmc, AT91_PMC_IER, 1 << d->hwirq); | ||
71 | } | ||
72 | |||
73 | static int pmc_irq_set_type(struct irq_data *d, unsigned type) | ||
74 | { | ||
75 | if (type != IRQ_TYPE_LEVEL_HIGH) { | ||
76 | pr_warn("PMC: type not supported (support only IRQ_TYPE_LEVEL_HIGH type)\n"); | ||
77 | return -EINVAL; | ||
78 | } | ||
79 | |||
80 | return 0; | ||
81 | } | ||
82 | |||
83 | static struct irq_chip pmc_irq = { | ||
84 | .name = "PMC", | ||
85 | .irq_disable = pmc_irq_mask, | ||
86 | .irq_mask = pmc_irq_mask, | ||
87 | .irq_unmask = pmc_irq_unmask, | ||
88 | .irq_set_type = pmc_irq_set_type, | ||
89 | }; | ||
90 | |||
91 | static struct lock_class_key pmc_lock_class; | ||
92 | |||
93 | static int pmc_irq_map(struct irq_domain *h, unsigned int virq, | ||
94 | irq_hw_number_t hw) | ||
95 | { | ||
96 | struct at91_pmc *pmc = h->host_data; | ||
97 | |||
98 | irq_set_lockdep_class(virq, &pmc_lock_class); | ||
99 | |||
100 | irq_set_chip_and_handler(virq, &pmc_irq, | ||
101 | handle_level_irq); | ||
102 | set_irq_flags(virq, IRQF_VALID); | ||
103 | irq_set_chip_data(virq, pmc); | ||
104 | |||
105 | return 0; | ||
106 | } | ||
107 | |||
108 | static int pmc_irq_domain_xlate(struct irq_domain *d, | ||
109 | struct device_node *ctrlr, | ||
110 | const u32 *intspec, unsigned int intsize, | ||
111 | irq_hw_number_t *out_hwirq, | ||
112 | unsigned int *out_type) | ||
113 | { | ||
114 | struct at91_pmc *pmc = d->host_data; | ||
115 | const struct at91_pmc_caps *caps = pmc->caps; | ||
116 | |||
117 | if (WARN_ON(intsize < 1)) | ||
118 | return -EINVAL; | ||
119 | |||
120 | *out_hwirq = intspec[0]; | ||
121 | |||
122 | if (!(caps->available_irqs & (1 << *out_hwirq))) | ||
123 | return -EINVAL; | ||
124 | |||
125 | *out_type = IRQ_TYPE_LEVEL_HIGH; | ||
126 | |||
127 | return 0; | ||
128 | } | ||
129 | |||
130 | static struct irq_domain_ops pmc_irq_ops = { | ||
131 | .map = pmc_irq_map, | ||
132 | .xlate = pmc_irq_domain_xlate, | ||
133 | }; | ||
134 | |||
135 | static irqreturn_t pmc_irq_handler(int irq, void *data) | ||
136 | { | ||
137 | struct at91_pmc *pmc = (struct at91_pmc *)data; | ||
138 | unsigned long sr; | ||
139 | int n; | ||
140 | |||
141 | sr = pmc_read(pmc, AT91_PMC_SR) & pmc_read(pmc, AT91_PMC_IMR); | ||
142 | if (!sr) | ||
143 | return IRQ_NONE; | ||
144 | |||
145 | for_each_set_bit(n, &sr, BITS_PER_LONG) | ||
146 | generic_handle_irq(irq_find_mapping(pmc->irqdomain, n)); | ||
147 | |||
148 | return IRQ_HANDLED; | ||
149 | } | ||
150 | |||
151 | static const struct at91_pmc_caps at91rm9200_caps = { | ||
152 | .available_irqs = AT91_PMC_MOSCS | AT91_PMC_LOCKA | AT91_PMC_LOCKB | | ||
153 | AT91_PMC_MCKRDY | AT91_PMC_PCK0RDY | | ||
154 | AT91_PMC_PCK1RDY | AT91_PMC_PCK2RDY | | ||
155 | AT91_PMC_PCK3RDY, | ||
156 | }; | ||
157 | |||
158 | static const struct at91_pmc_caps at91sam9260_caps = { | ||
159 | .available_irqs = AT91_PMC_MOSCS | AT91_PMC_LOCKA | AT91_PMC_LOCKB | | ||
160 | AT91_PMC_MCKRDY | AT91_PMC_PCK0RDY | | ||
161 | AT91_PMC_PCK1RDY, | ||
162 | }; | ||
163 | |||
164 | static const struct at91_pmc_caps at91sam9g45_caps = { | ||
165 | .available_irqs = AT91_PMC_MOSCS | AT91_PMC_LOCKA | AT91_PMC_MCKRDY | | ||
166 | AT91_PMC_LOCKU | AT91_PMC_PCK0RDY | | ||
167 | AT91_PMC_PCK1RDY, | ||
168 | }; | ||
169 | |||
170 | static const struct at91_pmc_caps at91sam9n12_caps = { | ||
171 | .available_irqs = AT91_PMC_MOSCS | AT91_PMC_LOCKA | AT91_PMC_LOCKB | | ||
172 | AT91_PMC_MCKRDY | AT91_PMC_PCK0RDY | | ||
173 | AT91_PMC_PCK1RDY | AT91_PMC_MOSCSELS | | ||
174 | AT91_PMC_MOSCRCS | AT91_PMC_CFDEV, | ||
175 | }; | ||
176 | |||
177 | static const struct at91_pmc_caps at91sam9x5_caps = { | ||
178 | .available_irqs = AT91_PMC_MOSCS | AT91_PMC_LOCKA | AT91_PMC_MCKRDY | | ||
179 | AT91_PMC_LOCKU | AT91_PMC_PCK0RDY | | ||
180 | AT91_PMC_PCK1RDY | AT91_PMC_MOSCSELS | | ||
181 | AT91_PMC_MOSCRCS | AT91_PMC_CFDEV, | ||
182 | }; | ||
183 | |||
184 | static const struct at91_pmc_caps sama5d3_caps = { | ||
185 | .available_irqs = AT91_PMC_MOSCS | AT91_PMC_LOCKA | AT91_PMC_MCKRDY | | ||
186 | AT91_PMC_LOCKU | AT91_PMC_PCK0RDY | | ||
187 | AT91_PMC_PCK1RDY | AT91_PMC_PCK2RDY | | ||
188 | AT91_PMC_MOSCSELS | AT91_PMC_MOSCRCS | | ||
189 | AT91_PMC_CFDEV, | ||
190 | }; | ||
191 | |||
192 | static struct at91_pmc *__init at91_pmc_init(struct device_node *np, | ||
193 | void __iomem *regbase, int virq, | ||
194 | const struct at91_pmc_caps *caps) | ||
195 | { | ||
196 | struct at91_pmc *pmc; | ||
197 | |||
198 | if (!regbase || !virq || !caps) | ||
199 | return NULL; | ||
200 | |||
201 | at91_pmc_base = regbase; | ||
202 | |||
203 | pmc = kzalloc(sizeof(*pmc), GFP_KERNEL); | ||
204 | if (!pmc) | ||
205 | return NULL; | ||
206 | |||
207 | spin_lock_init(&pmc->lock); | ||
208 | pmc->regbase = regbase; | ||
209 | pmc->virq = virq; | ||
210 | pmc->caps = caps; | ||
211 | |||
212 | pmc->irqdomain = irq_domain_add_linear(np, 32, &pmc_irq_ops, pmc); | ||
213 | |||
214 | if (!pmc->irqdomain) | ||
215 | goto out_free_pmc; | ||
216 | |||
217 | pmc_write(pmc, AT91_PMC_IDR, 0xffffffff); | ||
218 | if (request_irq(pmc->virq, pmc_irq_handler, IRQF_SHARED, "pmc", pmc)) | ||
219 | goto out_remove_irqdomain; | ||
220 | |||
221 | return pmc; | ||
222 | |||
223 | out_remove_irqdomain: | ||
224 | irq_domain_remove(pmc->irqdomain); | ||
225 | out_free_pmc: | ||
226 | kfree(pmc); | ||
227 | |||
228 | return NULL; | ||
229 | } | ||
230 | |||
231 | static const struct of_device_id pmc_clk_ids[] __initconst = { | ||
232 | /* Main clock */ | ||
233 | { | ||
234 | .compatible = "atmel,at91rm9200-clk-main", | ||
235 | .data = of_at91rm9200_clk_main_setup, | ||
236 | }, | ||
237 | /* PLL clocks */ | ||
238 | { | ||
239 | .compatible = "atmel,at91rm9200-clk-pll", | ||
240 | .data = of_at91rm9200_clk_pll_setup, | ||
241 | }, | ||
242 | { | ||
243 | .compatible = "atmel,at91sam9g45-clk-pll", | ||
244 | .data = of_at91sam9g45_clk_pll_setup, | ||
245 | }, | ||
246 | { | ||
247 | .compatible = "atmel,at91sam9g20-clk-pllb", | ||
248 | .data = of_at91sam9g20_clk_pllb_setup, | ||
249 | }, | ||
250 | { | ||
251 | .compatible = "atmel,sama5d3-clk-pll", | ||
252 | .data = of_sama5d3_clk_pll_setup, | ||
253 | }, | ||
254 | { | ||
255 | .compatible = "atmel,at91sam9x5-clk-plldiv", | ||
256 | .data = of_at91sam9x5_clk_plldiv_setup, | ||
257 | }, | ||
258 | /* Master clock */ | ||
259 | { | ||
260 | .compatible = "atmel,at91rm9200-clk-master", | ||
261 | .data = of_at91rm9200_clk_master_setup, | ||
262 | }, | ||
263 | { | ||
264 | .compatible = "atmel,at91sam9x5-clk-master", | ||
265 | .data = of_at91sam9x5_clk_master_setup, | ||
266 | }, | ||
267 | /* System clocks */ | ||
268 | { | ||
269 | .compatible = "atmel,at91rm9200-clk-system", | ||
270 | .data = of_at91rm9200_clk_sys_setup, | ||
271 | }, | ||
272 | /* Peripheral clocks */ | ||
273 | { | ||
274 | .compatible = "atmel,at91rm9200-clk-peripheral", | ||
275 | .data = of_at91rm9200_clk_periph_setup, | ||
276 | }, | ||
277 | { | ||
278 | .compatible = "atmel,at91sam9x5-clk-peripheral", | ||
279 | .data = of_at91sam9x5_clk_periph_setup, | ||
280 | }, | ||
281 | /* Programmable clocks */ | ||
282 | { | ||
283 | .compatible = "atmel,at91rm9200-clk-programmable", | ||
284 | .data = of_at91rm9200_clk_prog_setup, | ||
285 | }, | ||
286 | { | ||
287 | .compatible = "atmel,at91sam9g45-clk-programmable", | ||
288 | .data = of_at91sam9g45_clk_prog_setup, | ||
289 | }, | ||
290 | { | ||
291 | .compatible = "atmel,at91sam9x5-clk-programmable", | ||
292 | .data = of_at91sam9x5_clk_prog_setup, | ||
293 | }, | ||
294 | /* UTMI clock */ | ||
295 | #if defined(CONFIG_HAVE_AT91_UTMI) | ||
296 | { | ||
297 | .compatible = "atmel,at91sam9x5-clk-utmi", | ||
298 | .data = of_at91sam9x5_clk_utmi_setup, | ||
299 | }, | ||
300 | #endif | ||
301 | /* USB clock */ | ||
302 | #if defined(CONFIG_HAVE_AT91_USB_CLK) | ||
303 | { | ||
304 | .compatible = "atmel,at91rm9200-clk-usb", | ||
305 | .data = of_at91rm9200_clk_usb_setup, | ||
306 | }, | ||
307 | { | ||
308 | .compatible = "atmel,at91sam9x5-clk-usb", | ||
309 | .data = of_at91sam9x5_clk_usb_setup, | ||
310 | }, | ||
311 | { | ||
312 | .compatible = "atmel,at91sam9n12-clk-usb", | ||
313 | .data = of_at91sam9n12_clk_usb_setup, | ||
314 | }, | ||
315 | #endif | ||
316 | /* SMD clock */ | ||
317 | #if defined(CONFIG_HAVE_AT91_SMD) | ||
318 | { | ||
319 | .compatible = "atmel,at91sam9x5-clk-smd", | ||
320 | .data = of_at91sam9x5_clk_smd_setup, | ||
321 | }, | ||
322 | #endif | ||
323 | { /*sentinel*/ } | ||
324 | }; | ||
325 | |||
326 | static void __init of_at91_pmc_setup(struct device_node *np, | ||
327 | const struct at91_pmc_caps *caps) | ||
328 | { | ||
329 | struct at91_pmc *pmc; | ||
330 | struct device_node *childnp; | ||
331 | void (*clk_setup)(struct device_node *, struct at91_pmc *); | ||
332 | const struct of_device_id *clk_id; | ||
333 | void __iomem *regbase = of_iomap(np, 0); | ||
334 | int virq; | ||
335 | |||
336 | if (!regbase) | ||
337 | return; | ||
338 | |||
339 | virq = irq_of_parse_and_map(np, 0); | ||
340 | if (!virq) | ||
341 | return; | ||
342 | |||
343 | pmc = at91_pmc_init(np, regbase, virq, caps); | ||
344 | if (!pmc) | ||
345 | return; | ||
346 | for_each_child_of_node(np, childnp) { | ||
347 | clk_id = of_match_node(pmc_clk_ids, childnp); | ||
348 | if (!clk_id) | ||
349 | continue; | ||
350 | clk_setup = clk_id->data; | ||
351 | clk_setup(childnp, pmc); | ||
352 | } | ||
353 | } | ||
354 | |||
355 | static void __init of_at91rm9200_pmc_setup(struct device_node *np) | ||
356 | { | ||
357 | of_at91_pmc_setup(np, &at91rm9200_caps); | ||
358 | } | ||
359 | CLK_OF_DECLARE(at91rm9200_clk_pmc, "atmel,at91rm9200-pmc", | ||
360 | of_at91rm9200_pmc_setup); | ||
361 | |||
362 | static void __init of_at91sam9260_pmc_setup(struct device_node *np) | ||
363 | { | ||
364 | of_at91_pmc_setup(np, &at91sam9260_caps); | ||
365 | } | ||
366 | CLK_OF_DECLARE(at91sam9260_clk_pmc, "atmel,at91sam9260-pmc", | ||
367 | of_at91sam9260_pmc_setup); | ||
368 | |||
369 | static void __init of_at91sam9g45_pmc_setup(struct device_node *np) | ||
370 | { | ||
371 | of_at91_pmc_setup(np, &at91sam9g45_caps); | ||
372 | } | ||
373 | CLK_OF_DECLARE(at91sam9g45_clk_pmc, "atmel,at91sam9g45-pmc", | ||
374 | of_at91sam9g45_pmc_setup); | ||
375 | |||
376 | static void __init of_at91sam9n12_pmc_setup(struct device_node *np) | ||
377 | { | ||
378 | of_at91_pmc_setup(np, &at91sam9n12_caps); | ||
379 | } | ||
380 | CLK_OF_DECLARE(at91sam9n12_clk_pmc, "atmel,at91sam9n12-pmc", | ||
381 | of_at91sam9n12_pmc_setup); | ||
382 | |||
383 | static void __init of_at91sam9x5_pmc_setup(struct device_node *np) | ||
384 | { | ||
385 | of_at91_pmc_setup(np, &at91sam9x5_caps); | ||
386 | } | ||
387 | CLK_OF_DECLARE(at91sam9x5_clk_pmc, "atmel,at91sam9x5-pmc", | ||
388 | of_at91sam9x5_pmc_setup); | ||
389 | |||
390 | static void __init of_sama5d3_pmc_setup(struct device_node *np) | ||
391 | { | ||
392 | of_at91_pmc_setup(np, &sama5d3_caps); | ||
393 | } | ||
394 | CLK_OF_DECLARE(sama5d3_clk_pmc, "atmel,sama5d3-pmc", | ||
395 | of_sama5d3_pmc_setup); | ||