diff options
Diffstat (limited to 'drivers/sh/intc/virq.c')
-rw-r--r-- | drivers/sh/intc/virq.c | 255 |
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 000000000000..643dfd4d2057 --- /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 | |||
19 | static struct intc_map_entry intc_irq_xlate[NR_IRQS]; | ||
20 | |||
21 | struct 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 | |||
34 | void 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 | |||
44 | struct intc_map_entry *intc_irq_xlate_get(unsigned int irq) | ||
45 | { | ||
46 | return intc_irq_xlate + irq; | ||
47 | } | ||
48 | |||
49 | int 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 | } | ||
81 | EXPORT_SYMBOL_GPL(intc_irq_lookup); | ||
82 | |||
83 | static 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 | |||
109 | static 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 | |||
129 | static 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 | |||
139 | static 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 | |||
184 | void __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 | |||
195 | static 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 | |||
204 | restart: | ||
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 | |||
248 | void __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 | } | ||