diff options
author | Talel Shenhar <talel@amazon.com> | 2019-06-10 04:34:43 -0400 |
---|---|---|
committer | Marc Zyngier <marc.zyngier@arm.com> | 2019-07-03 04:19:11 -0400 |
commit | 1eb77c3bcdb70f2501f419b3da45b19acaf01072 (patch) | |
tree | 7edfbadb265ac6c597f22527f9e34a4f99ca5f1e /drivers/irqchip | |
parent | 74b5150cde45ab29ee332a3d1e770c00431f7993 (diff) |
irqchip/al-fic: Introduce Amazon's Annapurna Labs Fabric Interrupt Controller Driver
The Amazon's Annapurna Labs Fabric Interrupt Controller has 32 inputs.
A FIC (Fabric Interrupt Controller) may be cascaded into another FIC or
directly to the main CPU Interrupt Controller (e.g. GIC).
Signed-off-by: Talel Shenhar <talel@amazon.com>
Signed-off-by: Marc Zyngier <marc.zyngier@arm.com>
Diffstat (limited to 'drivers/irqchip')
-rw-r--r-- | drivers/irqchip/Kconfig | 8 | ||||
-rw-r--r-- | drivers/irqchip/Makefile | 1 | ||||
-rw-r--r-- | drivers/irqchip/irq-al-fic.c | 278 |
3 files changed, 287 insertions, 0 deletions
diff --git a/drivers/irqchip/Kconfig b/drivers/irqchip/Kconfig index b2391d994da2..5a780f01ddf1 100644 --- a/drivers/irqchip/Kconfig +++ b/drivers/irqchip/Kconfig | |||
@@ -88,6 +88,14 @@ config ALPINE_MSI | |||
88 | select PCI_MSI | 88 | select PCI_MSI |
89 | select GENERIC_IRQ_CHIP | 89 | select GENERIC_IRQ_CHIP |
90 | 90 | ||
91 | config AL_FIC | ||
92 | bool "Amazon's Annapurna Labs Fabric Interrupt Controller" | ||
93 | depends on OF || COMPILE_TEST | ||
94 | select GENERIC_IRQ_CHIP | ||
95 | select IRQ_DOMAIN | ||
96 | help | ||
97 | Support Amazon's Annapurna Labs Fabric Interrupt Controller. | ||
98 | |||
91 | config ATMEL_AIC_IRQ | 99 | config ATMEL_AIC_IRQ |
92 | bool | 100 | bool |
93 | select GENERIC_IRQ_CHIP | 101 | select GENERIC_IRQ_CHIP |
diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile index c629a8ab76b2..8d0fcec6ab23 100644 --- a/drivers/irqchip/Makefile +++ b/drivers/irqchip/Makefile | |||
@@ -1,6 +1,7 @@ | |||
1 | # SPDX-License-Identifier: GPL-2.0 | 1 | # SPDX-License-Identifier: GPL-2.0 |
2 | obj-$(CONFIG_IRQCHIP) += irqchip.o | 2 | obj-$(CONFIG_IRQCHIP) += irqchip.o |
3 | 3 | ||
4 | obj-$(CONFIG_AL_FIC) += irq-al-fic.o | ||
4 | obj-$(CONFIG_ALPINE_MSI) += irq-alpine-msi.o | 5 | obj-$(CONFIG_ALPINE_MSI) += irq-alpine-msi.o |
5 | obj-$(CONFIG_ATH79) += irq-ath79-cpu.o | 6 | obj-$(CONFIG_ATH79) += irq-ath79-cpu.o |
6 | obj-$(CONFIG_ATH79) += irq-ath79-misc.o | 7 | obj-$(CONFIG_ATH79) += irq-ath79-misc.o |
diff --git a/drivers/irqchip/irq-al-fic.c b/drivers/irqchip/irq-al-fic.c new file mode 100644 index 000000000000..1a57cee3efab --- /dev/null +++ b/drivers/irqchip/irq-al-fic.c | |||
@@ -0,0 +1,278 @@ | |||
1 | // SPDX-License-Identifier: GPL-2.0 | ||
2 | /* | ||
3 | * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. | ||
4 | */ | ||
5 | |||
6 | #include <linux/bitfield.h> | ||
7 | #include <linux/irq.h> | ||
8 | #include <linux/irqchip.h> | ||
9 | #include <linux/irqchip/chained_irq.h> | ||
10 | #include <linux/irqdomain.h> | ||
11 | #include <linux/module.h> | ||
12 | #include <linux/of.h> | ||
13 | #include <linux/of_address.h> | ||
14 | #include <linux/of_irq.h> | ||
15 | |||
16 | /* FIC Registers */ | ||
17 | #define AL_FIC_CAUSE 0x00 | ||
18 | #define AL_FIC_MASK 0x10 | ||
19 | #define AL_FIC_CONTROL 0x28 | ||
20 | |||
21 | #define CONTROL_TRIGGER_RISING BIT(3) | ||
22 | #define CONTROL_MASK_MSI_X BIT(5) | ||
23 | |||
24 | #define NR_FIC_IRQS 32 | ||
25 | |||
26 | MODULE_AUTHOR("Talel Shenhar"); | ||
27 | MODULE_DESCRIPTION("Amazon's Annapurna Labs Interrupt Controller Driver"); | ||
28 | MODULE_LICENSE("GPL v2"); | ||
29 | |||
30 | enum al_fic_state { | ||
31 | AL_FIC_UNCONFIGURED = 0, | ||
32 | AL_FIC_CONFIGURED_LEVEL, | ||
33 | AL_FIC_CONFIGURED_RISING_EDGE, | ||
34 | }; | ||
35 | |||
36 | struct al_fic { | ||
37 | void __iomem *base; | ||
38 | struct irq_domain *domain; | ||
39 | const char *name; | ||
40 | unsigned int parent_irq; | ||
41 | enum al_fic_state state; | ||
42 | }; | ||
43 | |||
44 | static void al_fic_set_trigger(struct al_fic *fic, | ||
45 | struct irq_chip_generic *gc, | ||
46 | enum al_fic_state new_state) | ||
47 | { | ||
48 | irq_flow_handler_t handler; | ||
49 | u32 control = readl_relaxed(fic->base + AL_FIC_CONTROL); | ||
50 | |||
51 | if (new_state == AL_FIC_CONFIGURED_LEVEL) { | ||
52 | handler = handle_level_irq; | ||
53 | control &= ~CONTROL_TRIGGER_RISING; | ||
54 | } else { | ||
55 | handler = handle_edge_irq; | ||
56 | control |= CONTROL_TRIGGER_RISING; | ||
57 | } | ||
58 | gc->chip_types->handler = handler; | ||
59 | fic->state = new_state; | ||
60 | writel_relaxed(control, fic->base + AL_FIC_CONTROL); | ||
61 | } | ||
62 | |||
63 | static int al_fic_irq_set_type(struct irq_data *data, unsigned int flow_type) | ||
64 | { | ||
65 | struct irq_chip_generic *gc = irq_data_get_irq_chip_data(data); | ||
66 | struct al_fic *fic = gc->private; | ||
67 | enum al_fic_state new_state; | ||
68 | int ret = 0; | ||
69 | |||
70 | irq_gc_lock(gc); | ||
71 | |||
72 | if (((flow_type & IRQ_TYPE_SENSE_MASK) != IRQ_TYPE_LEVEL_HIGH) && | ||
73 | ((flow_type & IRQ_TYPE_SENSE_MASK) != IRQ_TYPE_EDGE_RISING)) { | ||
74 | pr_debug("fic doesn't support flow type %d\n", flow_type); | ||
75 | ret = -EINVAL; | ||
76 | goto err; | ||
77 | } | ||
78 | |||
79 | new_state = (flow_type & IRQ_TYPE_LEVEL_HIGH) ? | ||
80 | AL_FIC_CONFIGURED_LEVEL : AL_FIC_CONFIGURED_RISING_EDGE; | ||
81 | |||
82 | /* | ||
83 | * A given FIC instance can be either all level or all edge triggered. | ||
84 | * This is generally fixed depending on what pieces of HW it's wired up | ||
85 | * to. | ||
86 | * | ||
87 | * We configure it based on the sensitivity of the first source | ||
88 | * being setup, and reject any subsequent attempt at configuring it in a | ||
89 | * different way. | ||
90 | */ | ||
91 | if (fic->state == AL_FIC_UNCONFIGURED) { | ||
92 | al_fic_set_trigger(fic, gc, new_state); | ||
93 | } else if (fic->state != new_state) { | ||
94 | pr_debug("fic %s state already configured to %d\n", | ||
95 | fic->name, fic->state); | ||
96 | ret = -EINVAL; | ||
97 | goto err; | ||
98 | } | ||
99 | |||
100 | err: | ||
101 | irq_gc_unlock(gc); | ||
102 | |||
103 | return ret; | ||
104 | } | ||
105 | |||
106 | static void al_fic_irq_handler(struct irq_desc *desc) | ||
107 | { | ||
108 | struct al_fic *fic = irq_desc_get_handler_data(desc); | ||
109 | struct irq_domain *domain = fic->domain; | ||
110 | struct irq_chip *irqchip = irq_desc_get_chip(desc); | ||
111 | struct irq_chip_generic *gc = irq_get_domain_generic_chip(domain, 0); | ||
112 | unsigned long pending; | ||
113 | unsigned int irq; | ||
114 | u32 hwirq; | ||
115 | |||
116 | chained_irq_enter(irqchip, desc); | ||
117 | |||
118 | pending = readl_relaxed(fic->base + AL_FIC_CAUSE); | ||
119 | pending &= ~gc->mask_cache; | ||
120 | |||
121 | for_each_set_bit(hwirq, &pending, NR_FIC_IRQS) { | ||
122 | irq = irq_find_mapping(domain, hwirq); | ||
123 | generic_handle_irq(irq); | ||
124 | } | ||
125 | |||
126 | chained_irq_exit(irqchip, desc); | ||
127 | } | ||
128 | |||
129 | static int al_fic_register(struct device_node *node, | ||
130 | struct al_fic *fic) | ||
131 | { | ||
132 | struct irq_chip_generic *gc; | ||
133 | int ret; | ||
134 | |||
135 | fic->domain = irq_domain_add_linear(node, | ||
136 | NR_FIC_IRQS, | ||
137 | &irq_generic_chip_ops, | ||
138 | fic); | ||
139 | if (!fic->domain) { | ||
140 | pr_err("fail to add irq domain\n"); | ||
141 | return -ENOMEM; | ||
142 | } | ||
143 | |||
144 | ret = irq_alloc_domain_generic_chips(fic->domain, | ||
145 | NR_FIC_IRQS, | ||
146 | 1, fic->name, | ||
147 | handle_level_irq, | ||
148 | 0, 0, IRQ_GC_INIT_MASK_CACHE); | ||
149 | if (ret) { | ||
150 | pr_err("fail to allocate generic chip (%d)\n", ret); | ||
151 | goto err_domain_remove; | ||
152 | } | ||
153 | |||
154 | gc = irq_get_domain_generic_chip(fic->domain, 0); | ||
155 | gc->reg_base = fic->base; | ||
156 | gc->chip_types->regs.mask = AL_FIC_MASK; | ||
157 | gc->chip_types->regs.ack = AL_FIC_CAUSE; | ||
158 | gc->chip_types->chip.irq_mask = irq_gc_mask_set_bit; | ||
159 | gc->chip_types->chip.irq_unmask = irq_gc_mask_clr_bit; | ||
160 | gc->chip_types->chip.irq_ack = irq_gc_ack_clr_bit; | ||
161 | gc->chip_types->chip.irq_set_type = al_fic_irq_set_type; | ||
162 | gc->chip_types->chip.flags = IRQCHIP_SKIP_SET_WAKE; | ||
163 | gc->private = fic; | ||
164 | |||
165 | irq_set_chained_handler_and_data(fic->parent_irq, | ||
166 | al_fic_irq_handler, | ||
167 | fic); | ||
168 | return 0; | ||
169 | |||
170 | err_domain_remove: | ||
171 | irq_domain_remove(fic->domain); | ||
172 | |||
173 | return ret; | ||
174 | } | ||
175 | |||
176 | /* | ||
177 | * al_fic_wire_init() - initialize and configure fic in wire mode | ||
178 | * @of_node: optional pointer to interrupt controller's device tree node. | ||
179 | * @base: mmio to fic register | ||
180 | * @name: name of the fic | ||
181 | * @parent_irq: interrupt of parent | ||
182 | * | ||
183 | * This API will configure the fic hardware to to work in wire mode. | ||
184 | * In wire mode, fic hardware is generating a wire ("wired") interrupt. | ||
185 | * Interrupt can be generated based on positive edge or level - configuration is | ||
186 | * to be determined based on connected hardware to this fic. | ||
187 | */ | ||
188 | static struct al_fic *al_fic_wire_init(struct device_node *node, | ||
189 | void __iomem *base, | ||
190 | const char *name, | ||
191 | unsigned int parent_irq) | ||
192 | { | ||
193 | struct al_fic *fic; | ||
194 | int ret; | ||
195 | u32 control = CONTROL_MASK_MSI_X; | ||
196 | |||
197 | fic = kzalloc(sizeof(*fic), GFP_KERNEL); | ||
198 | if (!fic) | ||
199 | return ERR_PTR(-ENOMEM); | ||
200 | |||
201 | fic->base = base; | ||
202 | fic->parent_irq = parent_irq; | ||
203 | fic->name = name; | ||
204 | |||
205 | /* mask out all interrupts */ | ||
206 | writel_relaxed(0xFFFFFFFF, fic->base + AL_FIC_MASK); | ||
207 | |||
208 | /* clear any pending interrupt */ | ||
209 | writel_relaxed(0, fic->base + AL_FIC_CAUSE); | ||
210 | |||
211 | writel_relaxed(control, fic->base + AL_FIC_CONTROL); | ||
212 | |||
213 | ret = al_fic_register(node, fic); | ||
214 | if (ret) { | ||
215 | pr_err("fail to register irqchip\n"); | ||
216 | goto err_free; | ||
217 | } | ||
218 | |||
219 | pr_debug("%s initialized successfully in Legacy mode (parent-irq=%u)\n", | ||
220 | fic->name, parent_irq); | ||
221 | |||
222 | return fic; | ||
223 | |||
224 | err_free: | ||
225 | kfree(fic); | ||
226 | return ERR_PTR(ret); | ||
227 | } | ||
228 | |||
229 | static int __init al_fic_init_dt(struct device_node *node, | ||
230 | struct device_node *parent) | ||
231 | { | ||
232 | int ret; | ||
233 | void __iomem *base; | ||
234 | unsigned int parent_irq; | ||
235 | struct al_fic *fic; | ||
236 | |||
237 | if (!parent) { | ||
238 | pr_err("%s: unsupported - device require a parent\n", | ||
239 | node->name); | ||
240 | return -EINVAL; | ||
241 | } | ||
242 | |||
243 | base = of_iomap(node, 0); | ||
244 | if (!base) { | ||
245 | pr_err("%s: fail to map memory\n", node->name); | ||
246 | return -ENOMEM; | ||
247 | } | ||
248 | |||
249 | parent_irq = irq_of_parse_and_map(node, 0); | ||
250 | if (!parent_irq) { | ||
251 | pr_err("%s: fail to map irq\n", node->name); | ||
252 | ret = -EINVAL; | ||
253 | goto err_unmap; | ||
254 | } | ||
255 | |||
256 | fic = al_fic_wire_init(node, | ||
257 | base, | ||
258 | node->name, | ||
259 | parent_irq); | ||
260 | if (IS_ERR(fic)) { | ||
261 | pr_err("%s: fail to initialize irqchip (%lu)\n", | ||
262 | node->name, | ||
263 | PTR_ERR(fic)); | ||
264 | ret = PTR_ERR(fic); | ||
265 | goto err_irq_dispose; | ||
266 | } | ||
267 | |||
268 | return 0; | ||
269 | |||
270 | err_irq_dispose: | ||
271 | irq_dispose_mapping(parent_irq); | ||
272 | err_unmap: | ||
273 | iounmap(base); | ||
274 | |||
275 | return ret; | ||
276 | } | ||
277 | |||
278 | IRQCHIP_DECLARE(al_fic, "amazon,al-fic", al_fic_init_dt); | ||