]>
Commit | Line | Data |
---|---|---|
5698c50d JH |
1 | /* |
2 | * Meta internal (HWSTATMETA) interrupt code. | |
3 | * | |
4 | * Copyright (C) 2011-2012 Imagination Technologies Ltd. | |
5 | * | |
6 | * This code is based on the code in SoC/common/irq.c and SoC/comet/irq.c | |
7 | * The code base could be generalised/merged as a lot of the functionality is | |
8 | * similar. Until this is done, we try to keep the code simple here. | |
9 | */ | |
10 | ||
11 | #include <linux/interrupt.h> | |
12 | #include <linux/io.h> | |
13 | #include <linux/irqdomain.h> | |
14 | ||
15 | #include <asm/irq.h> | |
16 | #include <asm/hwthread.h> | |
17 | ||
18 | #define PERF0VECINT 0x04820580 | |
19 | #define PERF1VECINT 0x04820588 | |
20 | #define PERF0TRIG_OFFSET 16 | |
21 | #define PERF1TRIG_OFFSET 17 | |
22 | ||
23 | /** | |
24 | * struct metag_internal_irq_priv - private meta internal interrupt data | |
25 | * @domain: IRQ domain for all internal Meta IRQs (HWSTATMETA) | |
26 | * @unmasked: Record of unmasked IRQs | |
27 | */ | |
28 | struct metag_internal_irq_priv { | |
29 | struct irq_domain *domain; | |
30 | ||
31 | unsigned long unmasked; | |
32 | }; | |
33 | ||
34 | /* Private data for the one and only internal interrupt controller */ | |
35 | static struct metag_internal_irq_priv metag_internal_irq_priv; | |
36 | ||
37 | static unsigned int metag_internal_irq_startup(struct irq_data *data); | |
38 | static void metag_internal_irq_shutdown(struct irq_data *data); | |
39 | static void metag_internal_irq_ack(struct irq_data *data); | |
40 | static void metag_internal_irq_mask(struct irq_data *data); | |
41 | static void metag_internal_irq_unmask(struct irq_data *data); | |
42 | #ifdef CONFIG_SMP | |
43 | static int metag_internal_irq_set_affinity(struct irq_data *data, | |
44 | const struct cpumask *cpumask, bool force); | |
45 | #endif | |
46 | ||
47 | static struct irq_chip internal_irq_edge_chip = { | |
48 | .name = "HWSTATMETA-IRQ", | |
49 | .irq_startup = metag_internal_irq_startup, | |
50 | .irq_shutdown = metag_internal_irq_shutdown, | |
51 | .irq_ack = metag_internal_irq_ack, | |
52 | .irq_mask = metag_internal_irq_mask, | |
53 | .irq_unmask = metag_internal_irq_unmask, | |
54 | #ifdef CONFIG_SMP | |
55 | .irq_set_affinity = metag_internal_irq_set_affinity, | |
56 | #endif | |
57 | }; | |
58 | ||
59 | /* | |
60 | * metag_hwvec_addr - get the address of *VECINT regs of irq | |
61 | * | |
62 | * This function is a table of supported triggers on HWSTATMETA | |
63 | * Could do with a structure, but better keep it simple. Changes | |
64 | * in this code should be rare. | |
65 | */ | |
66 | static inline void __iomem *metag_hwvec_addr(irq_hw_number_t hw) | |
67 | { | |
68 | void __iomem *addr; | |
69 | ||
70 | switch (hw) { | |
71 | case PERF0TRIG_OFFSET: | |
72 | addr = (void __iomem *)PERF0VECINT; | |
73 | break; | |
74 | case PERF1TRIG_OFFSET: | |
75 | addr = (void __iomem *)PERF1VECINT; | |
76 | break; | |
77 | default: | |
78 | addr = NULL; | |
79 | break; | |
80 | } | |
81 | return addr; | |
82 | } | |
83 | ||
84 | /* | |
85 | * metag_internal_startup - setup an internal irq | |
86 | * @irq: the irq to startup | |
87 | * | |
88 | * Multiplex interrupts for @irq onto TR1. Clear any pending | |
89 | * interrupts. | |
90 | */ | |
91 | static unsigned int metag_internal_irq_startup(struct irq_data *data) | |
92 | { | |
93 | /* Clear (toggle) the bit in HWSTATMETA for our interrupt. */ | |
94 | metag_internal_irq_ack(data); | |
95 | ||
96 | /* Enable the interrupt by unmasking it */ | |
97 | metag_internal_irq_unmask(data); | |
98 | ||
99 | return 0; | |
100 | } | |
101 | ||
102 | /* | |
103 | * metag_internal_irq_shutdown - turn off the irq | |
104 | * @irq: the irq number to turn off | |
105 | * | |
106 | * Mask @irq and clear any pending interrupts. | |
107 | * Stop muxing @irq onto TR1. | |
108 | */ | |
109 | static void metag_internal_irq_shutdown(struct irq_data *data) | |
110 | { | |
111 | /* Disable the IRQ at the core by masking it. */ | |
112 | metag_internal_irq_mask(data); | |
113 | ||
114 | /* Clear (toggle) the bit in HWSTATMETA for our interrupt. */ | |
115 | metag_internal_irq_ack(data); | |
116 | } | |
117 | ||
118 | /* | |
119 | * metag_internal_irq_ack - acknowledge irq | |
120 | * @irq: the irq to ack | |
121 | */ | |
122 | static void metag_internal_irq_ack(struct irq_data *data) | |
123 | { | |
124 | irq_hw_number_t hw = data->hwirq; | |
125 | unsigned int bit = 1 << hw; | |
126 | ||
127 | if (metag_in32(HWSTATMETA) & bit) | |
128 | metag_out32(bit, HWSTATMETA); | |
129 | } | |
130 | ||
131 | /** | |
132 | * metag_internal_irq_mask() - mask an internal irq by unvectoring | |
133 | * @data: data for the internal irq to mask | |
134 | * | |
135 | * HWSTATMETA has no mask register. Instead the IRQ is unvectored from the core | |
136 | * and retriggered if necessary later. | |
137 | */ | |
138 | static void metag_internal_irq_mask(struct irq_data *data) | |
139 | { | |
140 | struct metag_internal_irq_priv *priv = &metag_internal_irq_priv; | |
141 | irq_hw_number_t hw = data->hwirq; | |
142 | void __iomem *vec_addr = metag_hwvec_addr(hw); | |
143 | ||
144 | clear_bit(hw, &priv->unmasked); | |
145 | ||
146 | /* there is no interrupt mask, so unvector the interrupt */ | |
147 | metag_out32(0, vec_addr); | |
148 | } | |
149 | ||
150 | /** | |
151 | * meta_intc_unmask_edge_irq_nomask() - unmask an edge irq by revectoring | |
152 | * @data: data for the internal irq to unmask | |
153 | * | |
154 | * HWSTATMETA has no mask register. Instead the IRQ is revectored back to the | |
155 | * core and retriggered if necessary. | |
156 | */ | |
157 | static void metag_internal_irq_unmask(struct irq_data *data) | |
158 | { | |
159 | struct metag_internal_irq_priv *priv = &metag_internal_irq_priv; | |
160 | irq_hw_number_t hw = data->hwirq; | |
161 | unsigned int bit = 1 << hw; | |
162 | void __iomem *vec_addr = metag_hwvec_addr(hw); | |
163 | unsigned int thread = hard_processor_id(); | |
164 | ||
165 | set_bit(hw, &priv->unmasked); | |
166 | ||
167 | /* there is no interrupt mask, so revector the interrupt */ | |
168 | metag_out32(TBI_TRIG_VEC(TBID_SIGNUM_TR1(thread)), vec_addr); | |
169 | ||
170 | /* | |
171 | * Re-trigger interrupt | |
172 | * | |
173 | * Writing a 1 toggles, and a 0->1 transition triggers. We only | |
174 | * retrigger if the status bit is already set, which means we | |
175 | * need to clear it first. Retriggering is fundamentally racy | |
176 | * because if the interrupt fires again after we clear it we | |
177 | * could end up clearing it again and the interrupt handler | |
178 | * thinking it hasn't fired. Therefore we need to keep trying to | |
179 | * retrigger until the bit is set. | |
180 | */ | |
181 | if (metag_in32(HWSTATMETA) & bit) { | |
182 | metag_out32(bit, HWSTATMETA); | |
183 | while (!(metag_in32(HWSTATMETA) & bit)) | |
184 | metag_out32(bit, HWSTATMETA); | |
185 | } | |
186 | } | |
187 | ||
188 | #ifdef CONFIG_SMP | |
189 | /* | |
190 | * metag_internal_irq_set_affinity - set the affinity for an interrupt | |
191 | */ | |
192 | static int metag_internal_irq_set_affinity(struct irq_data *data, | |
193 | const struct cpumask *cpumask, bool force) | |
194 | { | |
195 | unsigned int cpu, thread; | |
196 | irq_hw_number_t hw = data->hwirq; | |
197 | /* | |
198 | * Wire up this interrupt from *VECINT to the Meta core. | |
199 | * | |
200 | * Note that we can't wire up *VECINT to interrupt more than | |
201 | * one cpu (the interrupt code doesn't support it), so we just | |
202 | * pick the first cpu we find in 'cpumask'. | |
203 | */ | |
f229006e | 204 | cpu = cpumask_any_and(cpumask, cpu_online_mask); |
5698c50d JH |
205 | thread = cpu_2_hwthread_id[cpu]; |
206 | ||
207 | metag_out32(TBI_TRIG_VEC(TBID_SIGNUM_TR1(thread)), | |
208 | metag_hwvec_addr(hw)); | |
209 | ||
210 | return 0; | |
211 | } | |
212 | #endif | |
213 | ||
214 | /* | |
215 | * metag_internal_irq_demux - irq de-multiplexer | |
216 | * @irq: the interrupt number | |
217 | * @desc: the interrupt description structure for this irq | |
218 | * | |
219 | * The cpu receives an interrupt on TR1 when an interrupt has | |
220 | * occurred. It is this function's job to demux this irq and | |
221 | * figure out exactly which trigger needs servicing. | |
222 | */ | |
223 | static void metag_internal_irq_demux(unsigned int irq, struct irq_desc *desc) | |
224 | { | |
225 | struct metag_internal_irq_priv *priv = irq_desc_get_handler_data(desc); | |
226 | irq_hw_number_t hw; | |
227 | unsigned int irq_no; | |
228 | u32 status; | |
229 | ||
230 | recalculate: | |
231 | status = metag_in32(HWSTATMETA) & priv->unmasked; | |
232 | ||
233 | for (hw = 0; status != 0; status >>= 1, ++hw) { | |
234 | if (status & 0x1) { | |
235 | /* | |
236 | * Map the hardware IRQ number to a virtual Linux IRQ | |
237 | * number. | |
238 | */ | |
239 | irq_no = irq_linear_revmap(priv->domain, hw); | |
240 | ||
241 | /* | |
242 | * Only fire off interrupts that are | |
243 | * registered to be handled by the kernel. | |
244 | * Other interrupts are probably being | |
245 | * handled by other Meta hardware threads. | |
246 | */ | |
247 | generic_handle_irq(irq_no); | |
248 | ||
249 | /* | |
250 | * The handler may have re-enabled interrupts | |
251 | * which could have caused a nested invocation | |
252 | * of this code and make the copy of the | |
253 | * status register we are using invalid. | |
254 | */ | |
255 | goto recalculate; | |
256 | } | |
257 | } | |
258 | } | |
259 | ||
260 | /** | |
261 | * internal_irq_map() - Map an internal meta IRQ to a virtual IRQ number. | |
262 | * @hw: Number of the internal IRQ. Must be in range. | |
263 | * | |
264 | * Returns: The virtual IRQ number of the Meta internal IRQ specified by | |
265 | * @hw. | |
266 | */ | |
267 | int internal_irq_map(unsigned int hw) | |
268 | { | |
269 | struct metag_internal_irq_priv *priv = &metag_internal_irq_priv; | |
270 | if (!priv->domain) | |
271 | return -ENODEV; | |
272 | return irq_create_mapping(priv->domain, hw); | |
273 | } | |
274 | ||
275 | /** | |
276 | * metag_internal_irq_init_cpu - regsister with the Meta cpu | |
277 | * @cpu: the CPU to register on | |
278 | * | |
279 | * Configure @cpu's TR1 irq so that we can demux irqs. | |
280 | */ | |
281 | static void metag_internal_irq_init_cpu(struct metag_internal_irq_priv *priv, | |
282 | int cpu) | |
283 | { | |
284 | unsigned int thread = cpu_2_hwthread_id[cpu]; | |
285 | unsigned int signum = TBID_SIGNUM_TR1(thread); | |
286 | int irq = tbisig_map(signum); | |
287 | ||
288 | /* Register the multiplexed IRQ handler */ | |
289 | irq_set_handler_data(irq, priv); | |
290 | irq_set_chained_handler(irq, metag_internal_irq_demux); | |
291 | irq_set_irq_type(irq, IRQ_TYPE_LEVEL_LOW); | |
292 | } | |
293 | ||
294 | /** | |
295 | * metag_internal_intc_map() - map an internal irq | |
296 | * @d: irq domain of internal trigger block | |
297 | * @irq: virtual irq number | |
298 | * @hw: hardware irq number within internal trigger block | |
299 | * | |
300 | * This sets up a virtual irq for a specified hardware interrupt. The irq chip | |
301 | * and handler is configured. | |
302 | */ | |
303 | static int metag_internal_intc_map(struct irq_domain *d, unsigned int irq, | |
304 | irq_hw_number_t hw) | |
305 | { | |
306 | /* only register interrupt if it is mapped */ | |
307 | if (!metag_hwvec_addr(hw)) | |
308 | return -EINVAL; | |
309 | ||
310 | irq_set_chip_and_handler(irq, &internal_irq_edge_chip, | |
311 | handle_edge_irq); | |
312 | return 0; | |
313 | } | |
314 | ||
315 | static const struct irq_domain_ops metag_internal_intc_domain_ops = { | |
316 | .map = metag_internal_intc_map, | |
317 | }; | |
318 | ||
319 | /** | |
320 | * metag_internal_irq_register - register internal IRQs | |
321 | * | |
322 | * Register the irq chip and handler function for all internal IRQs | |
323 | */ | |
324 | int __init init_internal_IRQ(void) | |
325 | { | |
326 | struct metag_internal_irq_priv *priv = &metag_internal_irq_priv; | |
327 | unsigned int cpu; | |
328 | ||
329 | /* Set up an IRQ domain */ | |
330 | priv->domain = irq_domain_add_linear(NULL, 32, | |
331 | &metag_internal_intc_domain_ops, | |
332 | priv); | |
333 | if (unlikely(!priv->domain)) { | |
334 | pr_err("meta-internal-intc: cannot add IRQ domain\n"); | |
335 | return -ENOMEM; | |
336 | } | |
337 | ||
338 | /* Setup TR1 for all cpus. */ | |
339 | for_each_possible_cpu(cpu) | |
340 | metag_internal_irq_init_cpu(priv, cpu); | |
341 | ||
342 | return 0; | |
343 | }; |