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 | ||
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[] __initdata = { | |
38d34c31 BB |
232 | /* Main clock */ |
233 | { | |
234 | .compatible = "atmel,at91rm9200-clk-main", | |
235 | .data = of_at91rm9200_clk_main_setup, | |
236 | }, | |
0ad6125b BB |
237 | { /*sentinel*/ } |
238 | }; | |
239 | ||
240 | static void __init of_at91_pmc_setup(struct device_node *np, | |
241 | const struct at91_pmc_caps *caps) | |
242 | { | |
243 | struct at91_pmc *pmc; | |
244 | struct device_node *childnp; | |
245 | void (*clk_setup)(struct device_node *, struct at91_pmc *); | |
246 | const struct of_device_id *clk_id; | |
247 | void __iomem *regbase = of_iomap(np, 0); | |
248 | int virq; | |
249 | ||
250 | if (!regbase) | |
251 | return; | |
252 | ||
253 | virq = irq_of_parse_and_map(np, 0); | |
254 | if (!virq) | |
255 | return; | |
256 | ||
257 | pmc = at91_pmc_init(np, regbase, virq, caps); | |
258 | if (!pmc) | |
259 | return; | |
260 | for_each_child_of_node(np, childnp) { | |
261 | clk_id = of_match_node(pmc_clk_ids, childnp); | |
262 | if (!clk_id) | |
263 | continue; | |
264 | clk_setup = clk_id->data; | |
265 | clk_setup(childnp, pmc); | |
266 | } | |
267 | } | |
268 | ||
269 | static void __init of_at91rm9200_pmc_setup(struct device_node *np) | |
270 | { | |
271 | of_at91_pmc_setup(np, &at91rm9200_caps); | |
272 | } | |
273 | CLK_OF_DECLARE(at91rm9200_clk_pmc, "atmel,at91rm9200-pmc", | |
274 | of_at91rm9200_pmc_setup); | |
275 | ||
276 | static void __init of_at91sam9260_pmc_setup(struct device_node *np) | |
277 | { | |
278 | of_at91_pmc_setup(np, &at91sam9260_caps); | |
279 | } | |
280 | CLK_OF_DECLARE(at91sam9260_clk_pmc, "atmel,at91sam9260-pmc", | |
281 | of_at91sam9260_pmc_setup); | |
282 | ||
283 | static void __init of_at91sam9g45_pmc_setup(struct device_node *np) | |
284 | { | |
285 | of_at91_pmc_setup(np, &at91sam9g45_caps); | |
286 | } | |
287 | CLK_OF_DECLARE(at91sam9g45_clk_pmc, "atmel,at91sam9g45-pmc", | |
288 | of_at91sam9g45_pmc_setup); | |
289 | ||
290 | static void __init of_at91sam9n12_pmc_setup(struct device_node *np) | |
291 | { | |
292 | of_at91_pmc_setup(np, &at91sam9n12_caps); | |
293 | } | |
294 | CLK_OF_DECLARE(at91sam9n12_clk_pmc, "atmel,at91sam9n12-pmc", | |
295 | of_at91sam9n12_pmc_setup); | |
296 | ||
297 | static void __init of_at91sam9x5_pmc_setup(struct device_node *np) | |
298 | { | |
299 | of_at91_pmc_setup(np, &at91sam9x5_caps); | |
300 | } | |
301 | CLK_OF_DECLARE(at91sam9x5_clk_pmc, "atmel,at91sam9x5-pmc", | |
302 | of_at91sam9x5_pmc_setup); | |
303 | ||
304 | static void __init of_sama5d3_pmc_setup(struct device_node *np) | |
305 | { | |
306 | of_at91_pmc_setup(np, &sama5d3_caps); | |
307 | } | |
308 | CLK_OF_DECLARE(sama5d3_clk_pmc, "atmel,sama5d3-pmc", | |
309 | of_sama5d3_pmc_setup); |