diff options
author | Kevin Cernekee <cernekee@gmail.com> | 2014-11-07 01:44:26 -0500 |
---|---|---|
committer | Jason Cooper <jason@lakedaemon.net> | 2014-11-08 23:03:13 -0500 |
commit | c76acf4dffa3232711b5364d7a29746df590f3db (patch) | |
tree | 1477f55905d95991574d12a46eb05b17ec2af156 | |
parent | 05b8ce8260b069b0d59516711e2795758f203556 (diff) |
irqchip: bcm7120-l2: Extend driver to support 64+ bit controllers
Most implementations of the bcm7120-l2 controller only have a single
32-bit enable word + 32-bit status word. But some instances have added
more enable/status pairs in order to support 64+ IRQs (which are all
ORed into one parent IRQ input). Make the following changes to allow
the driver to support this:
- Extend DT bindings so that multiple words can be specified for the
reg property, various masks, etc.
- Add loops to the probe/handle functions to deal with each word
separately
- Allocate 1 generic-chip for every 32 IRQs, so we can still use the
clr/set helper functions
- Update the documentation
This uses one domain per bcm7120-l2 DT node. If the DT node defines
multiple enable/status pairs (i.e. >=64 IRQs) then the driver will
create a single IRQ domain with 2+ generic chips. Multiple generic chips
are required because the generic-chip code can only handle one
enable/status register pair per instance.
Signed-off-by: Kevin Cernekee <cernekee@gmail.com>
Acked-by: Arnd Bergmann <arnd@arndb.de>
Link: https://lkml.kernel.org/r/1415342669-30640-12-git-send-email-cernekee@gmail.com
Signed-off-by: Jason Cooper <jason@lakedaemon.net>
-rw-r--r-- | Documentation/devicetree/bindings/interrupt-controller/brcm,bcm7120-l2-intc.txt | 26 | ||||
-rw-r--r-- | drivers/irqchip/irq-bcm7120-l2.c | 144 |
2 files changed, 113 insertions, 57 deletions
diff --git a/Documentation/devicetree/bindings/interrupt-controller/brcm,bcm7120-l2-intc.txt b/Documentation/devicetree/bindings/interrupt-controller/brcm,bcm7120-l2-intc.txt index ff812a8a82bc..bae1f2187226 100644 --- a/Documentation/devicetree/bindings/interrupt-controller/brcm,bcm7120-l2-intc.txt +++ b/Documentation/devicetree/bindings/interrupt-controller/brcm,bcm7120-l2-intc.txt | |||
@@ -13,7 +13,12 @@ Such an interrupt controller has the following hardware design: | |||
13 | or if they will output an interrupt signal at this 2nd level interrupt | 13 | or if they will output an interrupt signal at this 2nd level interrupt |
14 | controller, in particular for UARTs | 14 | controller, in particular for UARTs |
15 | 15 | ||
16 | - not all 32-bits within the interrupt controller actually map to an interrupt | 16 | - typically has one 32-bit enable word and one 32-bit status word, but on |
17 | some hardware may have more than one enable/status pair | ||
18 | |||
19 | - no atomic set/clear operations | ||
20 | |||
21 | - not all bits within the interrupt controller actually map to an interrupt | ||
17 | 22 | ||
18 | The typical hardware layout for this controller is represented below: | 23 | The typical hardware layout for this controller is represented below: |
19 | 24 | ||
@@ -48,7 +53,9 @@ The typical hardware layout for this controller is represented below: | |||
48 | Required properties: | 53 | Required properties: |
49 | 54 | ||
50 | - compatible: should be "brcm,bcm7120-l2-intc" | 55 | - compatible: should be "brcm,bcm7120-l2-intc" |
51 | - reg: specifies the base physical address and size of the registers | 56 | - reg: specifies the base physical address and size of the registers; |
57 | multiple pairs may be specified, with the first pair handling IRQ offsets | ||
58 | 0..31 and the second pair handling 32..63 | ||
52 | - interrupt-controller: identifies the node as an interrupt controller | 59 | - interrupt-controller: identifies the node as an interrupt controller |
53 | - #interrupt-cells: specifies the number of cells needed to encode an interrupt | 60 | - #interrupt-cells: specifies the number of cells needed to encode an interrupt |
54 | source, should be 1. | 61 | source, should be 1. |
@@ -59,18 +66,21 @@ Required properties: | |||
59 | - brcm,int-map-mask: 32-bits bit mask describing how many and which interrupts | 66 | - brcm,int-map-mask: 32-bits bit mask describing how many and which interrupts |
60 | are wired to this 2nd level interrupt controller, and how they match their | 67 | are wired to this 2nd level interrupt controller, and how they match their |
61 | respective interrupt parents. Should match exactly the number of interrupts | 68 | respective interrupt parents. Should match exactly the number of interrupts |
62 | specified in the 'interrupts' property. | 69 | specified in the 'interrupts' property, multiplied by the number of |
70 | enable/status register pairs implemented by this controller. For | ||
71 | multiple parent IRQs with multiple enable/status words, this looks like: | ||
72 | <irq0_w0 irq0_w1 irq1_w0 irq1_w1 ...> | ||
63 | 73 | ||
64 | Optional properties: | 74 | Optional properties: |
65 | 75 | ||
66 | - brcm,irq-can-wake: if present, this means the L2 controller can be used as a | 76 | - brcm,irq-can-wake: if present, this means the L2 controller can be used as a |
67 | wakeup source for system suspend/resume. | 77 | wakeup source for system suspend/resume. |
68 | 78 | ||
69 | - brcm,int-fwd-mask: if present, a 32-bits bit mask to configure for the | 79 | - brcm,int-fwd-mask: if present, a bit mask to configure the interrupts which |
70 | interrupts which have a mux gate, typically UARTs. Setting these bits will | 80 | have a mux gate, typically UARTs. Setting these bits will make their |
71 | make their respective interrupts outputs bypass this 2nd level interrupt | 81 | respective interrupt outputs bypass this 2nd level interrupt controller |
72 | controller completely, it completely transparent for the interrupt controller | 82 | completely; it is completely transparent for the interrupt controller |
73 | parent | 83 | parent. This should have one 32-bit word per enable/status pair. |
74 | 84 | ||
75 | Example: | 85 | Example: |
76 | 86 | ||
diff --git a/drivers/irqchip/irq-bcm7120-l2.c b/drivers/irqchip/irq-bcm7120-l2.c index 984112112042..ef4d32cf267f 100644 --- a/drivers/irqchip/irq-bcm7120-l2.c +++ b/drivers/irqchip/irq-bcm7120-l2.c | |||
@@ -23,6 +23,7 @@ | |||
23 | #include <linux/io.h> | 23 | #include <linux/io.h> |
24 | #include <linux/irqdomain.h> | 24 | #include <linux/irqdomain.h> |
25 | #include <linux/reboot.h> | 25 | #include <linux/reboot.h> |
26 | #include <linux/bitops.h> | ||
26 | #include <linux/irqchip/chained_irq.h> | 27 | #include <linux/irqchip/chained_irq.h> |
27 | 28 | ||
28 | #include "irqchip.h" | 29 | #include "irqchip.h" |
@@ -31,27 +32,42 @@ | |||
31 | #define IRQEN 0x00 | 32 | #define IRQEN 0x00 |
32 | #define IRQSTAT 0x04 | 33 | #define IRQSTAT 0x04 |
33 | 34 | ||
35 | #define MAX_WORDS 4 | ||
36 | #define IRQS_PER_WORD 32 | ||
37 | |||
34 | struct bcm7120_l2_intc_data { | 38 | struct bcm7120_l2_intc_data { |
35 | void __iomem *base; | 39 | unsigned int n_words; |
40 | void __iomem *base[MAX_WORDS]; | ||
36 | struct irq_domain *domain; | 41 | struct irq_domain *domain; |
37 | bool can_wake; | 42 | bool can_wake; |
38 | u32 irq_fwd_mask; | 43 | u32 irq_fwd_mask[MAX_WORDS]; |
39 | u32 irq_map_mask; | 44 | u32 irq_map_mask[MAX_WORDS]; |
40 | }; | 45 | }; |
41 | 46 | ||
42 | static void bcm7120_l2_intc_irq_handle(unsigned int irq, struct irq_desc *desc) | 47 | static void bcm7120_l2_intc_irq_handle(unsigned int irq, struct irq_desc *desc) |
43 | { | 48 | { |
44 | struct bcm7120_l2_intc_data *b = irq_desc_get_handler_data(desc); | 49 | struct bcm7120_l2_intc_data *b = irq_desc_get_handler_data(desc); |
45 | struct irq_chip *chip = irq_desc_get_chip(desc); | 50 | struct irq_chip *chip = irq_desc_get_chip(desc); |
46 | u32 status; | 51 | unsigned int idx; |
47 | 52 | ||
48 | chained_irq_enter(chip, desc); | 53 | chained_irq_enter(chip, desc); |
49 | 54 | ||
50 | status = __raw_readl(b->base + IRQSTAT); | 55 | for (idx = 0; idx < b->n_words; idx++) { |
51 | while (status) { | 56 | int base = idx * IRQS_PER_WORD; |
52 | irq = ffs(status) - 1; | 57 | struct irq_chip_generic *gc = |
53 | status &= ~(1 << irq); | 58 | irq_get_domain_generic_chip(b->domain, base); |
54 | generic_handle_irq(irq_find_mapping(b->domain, irq)); | 59 | unsigned long pending; |
60 | int hwirq; | ||
61 | |||
62 | irq_gc_lock(gc); | ||
63 | pending = __raw_readl(b->base[idx] + IRQSTAT) & | ||
64 | gc->mask_cache; | ||
65 | irq_gc_unlock(gc); | ||
66 | |||
67 | for_each_set_bit(hwirq, &pending, IRQS_PER_WORD) { | ||
68 | generic_handle_irq(irq_find_mapping(b->domain, | ||
69 | base + hwirq)); | ||
70 | } | ||
55 | } | 71 | } |
56 | 72 | ||
57 | chained_irq_exit(chip, desc); | 73 | chained_irq_exit(chip, desc); |
@@ -65,7 +81,7 @@ static void bcm7120_l2_intc_suspend(struct irq_data *d) | |||
65 | irq_gc_lock(gc); | 81 | irq_gc_lock(gc); |
66 | if (b->can_wake) { | 82 | if (b->can_wake) { |
67 | __raw_writel(gc->mask_cache | gc->wake_active, | 83 | __raw_writel(gc->mask_cache | gc->wake_active, |
68 | b->base + IRQEN); | 84 | gc->reg_base + IRQEN); |
69 | } | 85 | } |
70 | irq_gc_unlock(gc); | 86 | irq_gc_unlock(gc); |
71 | } | 87 | } |
@@ -76,7 +92,7 @@ static void bcm7120_l2_intc_resume(struct irq_data *d) | |||
76 | 92 | ||
77 | /* Restore the saved mask */ | 93 | /* Restore the saved mask */ |
78 | irq_gc_lock(gc); | 94 | irq_gc_lock(gc); |
79 | __raw_writel(gc->mask_cache, b->base + IRQEN); | 95 | __raw_writel(gc->mask_cache, gc->reg_base + IRQEN); |
80 | irq_gc_unlock(gc); | 96 | irq_gc_unlock(gc); |
81 | } | 97 | } |
82 | 98 | ||
@@ -85,6 +101,7 @@ static int bcm7120_l2_intc_init_one(struct device_node *dn, | |||
85 | int irq, const __be32 *map_mask) | 101 | int irq, const __be32 *map_mask) |
86 | { | 102 | { |
87 | int parent_irq; | 103 | int parent_irq; |
104 | unsigned int idx; | ||
88 | 105 | ||
89 | parent_irq = irq_of_parse_and_map(dn, irq); | 106 | parent_irq = irq_of_parse_and_map(dn, irq); |
90 | if (parent_irq < 0) { | 107 | if (parent_irq < 0) { |
@@ -92,7 +109,12 @@ static int bcm7120_l2_intc_init_one(struct device_node *dn, | |||
92 | return parent_irq; | 109 | return parent_irq; |
93 | } | 110 | } |
94 | 111 | ||
95 | data->irq_map_mask |= be32_to_cpup(map_mask + irq); | 112 | /* For multiple parent IRQs with multiple words, this looks like: |
113 | * <irq0_w0 irq0_w1 irq1_w0 irq1_w1 ...> | ||
114 | */ | ||
115 | for (idx = 0; idx < data->n_words; idx++) | ||
116 | data->irq_map_mask[idx] |= | ||
117 | be32_to_cpup(map_mask + irq * data->n_words + idx); | ||
96 | 118 | ||
97 | irq_set_handler_data(parent_irq, data); | 119 | irq_set_handler_data(parent_irq, data); |
98 | irq_set_chained_handler(parent_irq, bcm7120_l2_intc_irq_handle); | 120 | irq_set_chained_handler(parent_irq, bcm7120_l2_intc_irq_handle); |
@@ -109,26 +131,41 @@ int __init bcm7120_l2_intc_of_init(struct device_node *dn, | |||
109 | struct irq_chip_type *ct; | 131 | struct irq_chip_type *ct; |
110 | const __be32 *map_mask; | 132 | const __be32 *map_mask; |
111 | int num_parent_irqs; | 133 | int num_parent_irqs; |
112 | int ret = 0, len, irq; | 134 | int ret = 0, len; |
135 | unsigned int idx, irq; | ||
113 | 136 | ||
114 | data = kzalloc(sizeof(*data), GFP_KERNEL); | 137 | data = kzalloc(sizeof(*data), GFP_KERNEL); |
115 | if (!data) | 138 | if (!data) |
116 | return -ENOMEM; | 139 | return -ENOMEM; |
117 | 140 | ||
118 | data->base = of_iomap(dn, 0); | 141 | for (idx = 0; idx < MAX_WORDS; idx++) { |
119 | if (!data->base) { | 142 | data->base[idx] = of_iomap(dn, idx); |
143 | if (!data->base[idx]) | ||
144 | break; | ||
145 | data->n_words = idx + 1; | ||
146 | } | ||
147 | if (!data->n_words) { | ||
120 | pr_err("failed to remap intc L2 registers\n"); | 148 | pr_err("failed to remap intc L2 registers\n"); |
121 | ret = -ENOMEM; | 149 | ret = -ENOMEM; |
122 | goto out_free; | 150 | goto out_unmap; |
123 | } | 151 | } |
124 | 152 | ||
125 | if (of_property_read_u32(dn, "brcm,int-fwd-mask", &data->irq_fwd_mask)) | 153 | /* Enable all interrupts specified in the interrupt forward mask; |
126 | data->irq_fwd_mask = 0; | 154 | * disable all others. If the property doesn't exist (-EINVAL), |
127 | 155 | * assume all zeroes. | |
128 | /* Enable all interrupt specified in the interrupt forward mask and have | ||
129 | * the other disabled | ||
130 | */ | 156 | */ |
131 | __raw_writel(data->irq_fwd_mask, data->base + IRQEN); | 157 | ret = of_property_read_u32_array(dn, "brcm,int-fwd-mask", |
158 | data->irq_fwd_mask, data->n_words); | ||
159 | if (ret == 0 || ret == -EINVAL) { | ||
160 | for (idx = 0; idx < data->n_words; idx++) | ||
161 | __raw_writel(data->irq_fwd_mask[idx], | ||
162 | data->base[idx] + IRQEN); | ||
163 | } else { | ||
164 | /* property exists but has the wrong number of words */ | ||
165 | pr_err("invalid int-fwd-mask property\n"); | ||
166 | ret = -EINVAL; | ||
167 | goto out_unmap; | ||
168 | } | ||
132 | 169 | ||
133 | num_parent_irqs = of_irq_count(dn); | 170 | num_parent_irqs = of_irq_count(dn); |
134 | if (num_parent_irqs <= 0) { | 171 | if (num_parent_irqs <= 0) { |
@@ -138,7 +175,8 @@ int __init bcm7120_l2_intc_of_init(struct device_node *dn, | |||
138 | } | 175 | } |
139 | 176 | ||
140 | map_mask = of_get_property(dn, "brcm,int-map-mask", &len); | 177 | map_mask = of_get_property(dn, "brcm,int-map-mask", &len); |
141 | if (!map_mask || (len != (sizeof(*map_mask) * num_parent_irqs))) { | 178 | if (!map_mask || |
179 | (len != (sizeof(*map_mask) * num_parent_irqs * data->n_words))) { | ||
142 | pr_err("invalid brcm,int-map-mask property\n"); | 180 | pr_err("invalid brcm,int-map-mask property\n"); |
143 | ret = -EINVAL; | 181 | ret = -EINVAL; |
144 | goto out_unmap; | 182 | goto out_unmap; |
@@ -150,14 +188,14 @@ int __init bcm7120_l2_intc_of_init(struct device_node *dn, | |||
150 | goto out_unmap; | 188 | goto out_unmap; |
151 | } | 189 | } |
152 | 190 | ||
153 | data->domain = irq_domain_add_linear(dn, 32, | 191 | data->domain = irq_domain_add_linear(dn, IRQS_PER_WORD * data->n_words, |
154 | &irq_generic_chip_ops, NULL); | 192 | &irq_generic_chip_ops, NULL); |
155 | if (!data->domain) { | 193 | if (!data->domain) { |
156 | ret = -ENOMEM; | 194 | ret = -ENOMEM; |
157 | goto out_unmap; | 195 | goto out_unmap; |
158 | } | 196 | } |
159 | 197 | ||
160 | ret = irq_alloc_domain_generic_chips(data->domain, 32, 1, | 198 | ret = irq_alloc_domain_generic_chips(data->domain, IRQS_PER_WORD, 1, |
161 | dn->full_name, handle_level_irq, clr, 0, | 199 | dn->full_name, handle_level_irq, clr, 0, |
162 | IRQ_GC_INIT_MASK_CACHE); | 200 | IRQ_GC_INIT_MASK_CACHE); |
163 | if (ret) { | 201 | if (ret) { |
@@ -165,39 +203,47 @@ int __init bcm7120_l2_intc_of_init(struct device_node *dn, | |||
165 | goto out_free_domain; | 203 | goto out_free_domain; |
166 | } | 204 | } |
167 | 205 | ||
168 | gc = irq_get_domain_generic_chip(data->domain, 0); | 206 | if (of_property_read_bool(dn, "brcm,irq-can-wake")) |
169 | gc->unused = 0xffffffff & ~data->irq_map_mask; | ||
170 | gc->reg_base = data->base; | ||
171 | gc->private = data; | ||
172 | ct = gc->chip_types; | ||
173 | |||
174 | ct->regs.mask = IRQEN; | ||
175 | ct->chip.irq_mask = irq_gc_mask_clr_bit; | ||
176 | ct->chip.irq_unmask = irq_gc_mask_set_bit; | ||
177 | ct->chip.irq_ack = irq_gc_noop; | ||
178 | ct->chip.irq_suspend = bcm7120_l2_intc_suspend; | ||
179 | ct->chip.irq_resume = bcm7120_l2_intc_resume; | ||
180 | |||
181 | if (of_property_read_bool(dn, "brcm,irq-can-wake")) { | ||
182 | data->can_wake = true; | 207 | data->can_wake = true; |
183 | /* This IRQ chip can wake the system, set all relevant child | 208 | |
184 | * interupts in wake_enabled mask | 209 | for (idx = 0; idx < data->n_words; idx++) { |
185 | */ | 210 | irq = idx * IRQS_PER_WORD; |
186 | gc->wake_enabled = 0xffffffff; | 211 | gc = irq_get_domain_generic_chip(data->domain, irq); |
187 | gc->wake_enabled &= ~gc->unused; | 212 | |
188 | ct->chip.irq_set_wake = irq_gc_set_wake; | 213 | gc->unused = 0xffffffff & ~data->irq_map_mask[idx]; |
214 | gc->reg_base = data->base[idx]; | ||
215 | gc->private = data; | ||
216 | ct = gc->chip_types; | ||
217 | |||
218 | ct->regs.mask = IRQEN; | ||
219 | ct->chip.irq_mask = irq_gc_mask_clr_bit; | ||
220 | ct->chip.irq_unmask = irq_gc_mask_set_bit; | ||
221 | ct->chip.irq_ack = irq_gc_noop; | ||
222 | ct->chip.irq_suspend = bcm7120_l2_intc_suspend; | ||
223 | ct->chip.irq_resume = bcm7120_l2_intc_resume; | ||
224 | |||
225 | if (data->can_wake) { | ||
226 | /* This IRQ chip can wake the system, set all | ||
227 | * relevant child interupts in wake_enabled mask | ||
228 | */ | ||
229 | gc->wake_enabled = 0xffffffff; | ||
230 | gc->wake_enabled &= ~gc->unused; | ||
231 | ct->chip.irq_set_wake = irq_gc_set_wake; | ||
232 | } | ||
189 | } | 233 | } |
190 | 234 | ||
191 | pr_info("registered BCM7120 L2 intc (mem: 0x%p, parent IRQ(s): %d)\n", | 235 | pr_info("registered BCM7120 L2 intc (mem: 0x%p, parent IRQ(s): %d)\n", |
192 | data->base, num_parent_irqs); | 236 | data->base[0], num_parent_irqs); |
193 | 237 | ||
194 | return 0; | 238 | return 0; |
195 | 239 | ||
196 | out_free_domain: | 240 | out_free_domain: |
197 | irq_domain_remove(data->domain); | 241 | irq_domain_remove(data->domain); |
198 | out_unmap: | 242 | out_unmap: |
199 | iounmap(data->base); | 243 | for (idx = 0; idx < MAX_WORDS; idx++) { |
200 | out_free: | 244 | if (data->base[idx]) |
245 | iounmap(data->base[idx]); | ||
246 | } | ||
201 | kfree(data); | 247 | kfree(data); |
202 | return ret; | 248 | return ret; |
203 | } | 249 | } |