aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/sh/intc/virq.c
diff options
context:
space:
mode:
authorPaul Mundt <lethal@linux-sh.org>2010-10-05 09:10:30 -0400
committerPaul Mundt <lethal@linux-sh.org>2010-10-05 09:10:30 -0400
commit2be6bb0c79c7fbda3425b65ee51c558bbaf4cf91 (patch)
treedb0dafd7e7f83945edc2c50c358a3d81fca960c3 /drivers/sh/intc/virq.c
parentd74310d3b18aabbb7d0549ea9e3fd3259c1dce00 (diff)
sh: intc: Split up the INTC code.
This splits up the sh intc core in to something more vaguely resembling a subsystem. Most of the functionality was alread fairly well compartmentalized, and there were only a handful of interdependencies that needed to be resolved in the process. This also serves as future-proofing for the genirq and sparseirq rework, which will make some of the split out functionality wholly generic, allowing things to be killed off in place with minimal migration pain. Signed-off-by: Paul Mundt <lethal@linux-sh.org>
Diffstat (limited to 'drivers/sh/intc/virq.c')
-rw-r--r--drivers/sh/intc/virq.c255
1 files changed, 255 insertions, 0 deletions
diff --git a/drivers/sh/intc/virq.c b/drivers/sh/intc/virq.c
new file mode 100644
index 00000000000..643dfd4d205
--- /dev/null
+++ b/drivers/sh/intc/virq.c
@@ -0,0 +1,255 @@
1/*
2 * Support for virtual IRQ subgroups.
3 *
4 * Copyright (C) 2010 Paul Mundt
5 *
6 * This file is subject to the terms and conditions of the GNU General Public
7 * License. See the file "COPYING" in the main directory of this archive
8 * for more details.
9 */
10#define pr_fmt(fmt) "intc: " fmt
11
12#include <linux/slab.h>
13#include <linux/irq.h>
14#include <linux/list.h>
15#include <linux/radix-tree.h>
16#include <linux/spinlock.h>
17#include "internals.h"
18
19static struct intc_map_entry intc_irq_xlate[NR_IRQS];
20
21struct intc_virq_list {
22 unsigned int irq;
23 struct intc_virq_list *next;
24};
25
26#define for_each_virq(entry, head) \
27 for (entry = head; entry; entry = entry->next)
28
29/*
30 * Tags for the radix tree
31 */
32#define INTC_TAG_VIRQ_NEEDS_ALLOC 0
33
34void intc_irq_xlate_set(unsigned int irq, intc_enum id, struct intc_desc_int *d)
35{
36 unsigned long flags;
37
38 raw_spin_lock_irqsave(&intc_big_lock, flags);
39 intc_irq_xlate[irq].enum_id = id;
40 intc_irq_xlate[irq].desc = d;
41 raw_spin_unlock_irqrestore(&intc_big_lock, flags);
42}
43
44struct intc_map_entry *intc_irq_xlate_get(unsigned int irq)
45{
46 return intc_irq_xlate + irq;
47}
48
49int intc_irq_lookup(const char *chipname, intc_enum enum_id)
50{
51 struct intc_map_entry *ptr;
52 struct intc_desc_int *d;
53 int irq = -1;
54
55 list_for_each_entry(d, &intc_list, list) {
56 int tagged;
57
58 if (strcmp(d->chip.name, chipname) != 0)
59 continue;
60
61 /*
62 * Catch early lookups for subgroup VIRQs that have not
63 * yet been allocated an IRQ. This already includes a
64 * fast-path out if the tree is untagged, so there is no
65 * need to explicitly test the root tree.
66 */
67 tagged = radix_tree_tag_get(&d->tree, enum_id,
68 INTC_TAG_VIRQ_NEEDS_ALLOC);
69 if (unlikely(tagged))
70 break;
71
72 ptr = radix_tree_lookup(&d->tree, enum_id);
73 if (ptr) {
74 irq = ptr - intc_irq_xlate;
75 break;
76 }
77 }
78
79 return irq;
80}
81EXPORT_SYMBOL_GPL(intc_irq_lookup);
82
83static int add_virq_to_pirq(unsigned int irq, unsigned int virq)
84{
85 struct intc_virq_list **last, *entry;
86 struct irq_desc *desc = irq_to_desc(irq);
87
88 /* scan for duplicates */
89 last = (struct intc_virq_list **)&desc->handler_data;
90 for_each_virq(entry, desc->handler_data) {
91 if (entry->irq == virq)
92 return 0;
93 last = &entry->next;
94 }
95
96 entry = kzalloc(sizeof(struct intc_virq_list), GFP_ATOMIC);
97 if (!entry) {
98 pr_err("can't allocate VIRQ mapping for %d\n", virq);
99 return -ENOMEM;
100 }
101
102 entry->irq = virq;
103
104 *last = entry;
105
106 return 0;
107}
108
109static void intc_virq_handler(unsigned int irq, struct irq_desc *desc)
110{
111 struct intc_virq_list *entry, *vlist = get_irq_data(irq);
112 struct intc_desc_int *d = get_intc_desc(irq);
113
114 desc->chip->mask_ack(irq);
115
116 for_each_virq(entry, vlist) {
117 unsigned long addr, handle;
118
119 handle = (unsigned long)get_irq_data(entry->irq);
120 addr = INTC_REG(d, _INTC_ADDR_E(handle), 0);
121
122 if (intc_reg_fns[_INTC_FN(handle)](addr, handle, 0))
123 generic_handle_irq(entry->irq);
124 }
125
126 desc->chip->unmask(irq);
127}
128
129static unsigned long __init intc_subgroup_data(struct intc_subgroup *subgroup,
130 struct intc_desc_int *d,
131 unsigned int index)
132{
133 unsigned int fn = REG_FN_TEST_BASE + (subgroup->reg_width >> 3) - 1;
134
135 return _INTC_MK(fn, MODE_ENABLE_REG, intc_get_reg(d, subgroup->reg),
136 0, 1, (subgroup->reg_width - 1) - index);
137}
138
139static void __init intc_subgroup_init_one(struct intc_desc *desc,
140 struct intc_desc_int *d,
141 struct intc_subgroup *subgroup)
142{
143 struct intc_map_entry *mapped;
144 unsigned int pirq;
145 unsigned long flags;
146 int i;
147
148 mapped = radix_tree_lookup(&d->tree, subgroup->parent_id);
149 if (!mapped) {
150 WARN_ON(1);
151 return;
152 }
153
154 pirq = mapped - intc_irq_xlate;
155
156 raw_spin_lock_irqsave(&d->lock, flags);
157
158 for (i = 0; i < ARRAY_SIZE(subgroup->enum_ids); i++) {
159 struct intc_subgroup_entry *entry;
160 int err;
161
162 if (!subgroup->enum_ids[i])
163 continue;
164
165 entry = kmalloc(sizeof(*entry), GFP_NOWAIT);
166 if (!entry)
167 break;
168
169 entry->pirq = pirq;
170 entry->enum_id = subgroup->enum_ids[i];
171 entry->handle = intc_subgroup_data(subgroup, d, i);
172
173 err = radix_tree_insert(&d->tree, entry->enum_id, entry);
174 if (unlikely(err < 0))
175 break;
176
177 radix_tree_tag_set(&d->tree, entry->enum_id,
178 INTC_TAG_VIRQ_NEEDS_ALLOC);
179 }
180
181 raw_spin_unlock_irqrestore(&d->lock, flags);
182}
183
184void __init intc_subgroup_init(struct intc_desc *desc, struct intc_desc_int *d)
185{
186 int i;
187
188 if (!desc->hw.subgroups)
189 return;
190
191 for (i = 0; i < desc->hw.nr_subgroups; i++)
192 intc_subgroup_init_one(desc, d, desc->hw.subgroups + i);
193}
194
195static void __init intc_subgroup_map(struct intc_desc_int *d)
196{
197 struct intc_subgroup_entry *entries[32];
198 unsigned long flags;
199 unsigned int nr_found;
200 int i;
201
202 raw_spin_lock_irqsave(&d->lock, flags);
203
204restart:
205 nr_found = radix_tree_gang_lookup_tag_slot(&d->tree,
206 (void ***)entries, 0, ARRAY_SIZE(entries),
207 INTC_TAG_VIRQ_NEEDS_ALLOC);
208
209 for (i = 0; i < nr_found; i++) {
210 struct intc_subgroup_entry *entry;
211 int irq;
212
213 entry = radix_tree_deref_slot((void **)entries[i]);
214 if (unlikely(!entry))
215 continue;
216 if (unlikely(entry == RADIX_TREE_RETRY))
217 goto restart;
218
219 irq = create_irq();
220 if (unlikely(irq < 0)) {
221 pr_err("no more free IRQs, bailing..\n");
222 break;
223 }
224
225 pr_info("Setting up a chained VIRQ from %d -> %d\n",
226 irq, entry->pirq);
227
228 intc_irq_xlate_set(irq, entry->enum_id, d);
229
230 set_irq_chip_and_handler_name(irq, get_irq_chip(entry->pirq),
231 handle_simple_irq, "virq");
232 set_irq_chip_data(irq, get_irq_chip_data(entry->pirq));
233
234 set_irq_data(irq, (void *)entry->handle);
235
236 set_irq_chained_handler(entry->pirq, intc_virq_handler);
237 add_virq_to_pirq(entry->pirq, irq);
238
239 radix_tree_tag_clear(&d->tree, entry->enum_id,
240 INTC_TAG_VIRQ_NEEDS_ALLOC);
241 radix_tree_replace_slot((void **)entries[i],
242 &intc_irq_xlate[irq]);
243 }
244
245 raw_spin_unlock_irqrestore(&d->lock, flags);
246}
247
248void __init intc_finalize(void)
249{
250 struct intc_desc_int *d;
251
252 list_for_each_entry(d, &intc_list, list)
253 if (radix_tree_tagged(&d->tree, INTC_TAG_VIRQ_NEEDS_ALLOC))
254 intc_subgroup_map(d);
255}