diff options
author | Alexandre TORGUE <alexandre.torgue@st.com> | 2016-09-20 12:00:57 -0400 |
---|---|---|
committer | Thomas Gleixner <tglx@linutronix.de> | 2016-09-21 08:13:21 -0400 |
commit | e072041688ca73f125719815fa4b0fd23a45152c (patch) | |
tree | b0d26235f66bbf229c05d478fcecc2a58051bf2e | |
parent | 3027f78bb7243bef28c103507fc857e1471d769d (diff) |
drivers/irqchip: Add STM32 external interrupts support
The STM32 external interrupt controller consists of edge detectors that
generate interrupts requests or wake-up events.
Each line can be independently configured as interrupt or wake-up source,
and triggers either on rising, falling or both edges. Each line can also
be masked independently.
Originally-from: Maxime Coquelin <mcoquelin.stm32@gmail.com>
Signed-off-by: Alexandre TORGUE <alexandre.torgue@st.com>
Cc: Mark Rutland <mark.rutland@arm.com>
Cc: devicetree@vger.kernel.org
Cc: Daniel Thompson <daniel.thompson@linaro.org>
Cc: Jason Cooper <jason@lakedaemon.net>
Cc: arnd@arndb.de
Cc: Marc Zyngier <marc.zyngier@arm.com>
Cc: bruherrera@gmail.com
Cc: Linus Walleij <linus.walleij@linaro.org>
Cc: linux-gpio@vger.kernel.org
Cc: Rob Herring <robh+dt@kernel.org>
Cc: lee.jones@linaro.org
Cc: linux-arm-kernel@lists.infradead.org
Link: http://lkml.kernel.org/r/1474387259-18926-3-git-send-email-alexandre.torgue@st.com
Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
-rw-r--r-- | drivers/irqchip/Kconfig | 4 | ||||
-rw-r--r-- | drivers/irqchip/Makefile | 1 | ||||
-rw-r--r-- | drivers/irqchip/irq-stm32-exti.c | 201 |
3 files changed, 206 insertions, 0 deletions
diff --git a/drivers/irqchip/Kconfig b/drivers/irqchip/Kconfig index 9aeea1d8a579..329c941e46b5 100644 --- a/drivers/irqchip/Kconfig +++ b/drivers/irqchip/Kconfig | |||
@@ -265,3 +265,7 @@ config EZNPS_GIC | |||
265 | select IRQ_DOMAIN | 265 | select IRQ_DOMAIN |
266 | help | 266 | help |
267 | Support the EZchip NPS400 global interrupt controller | 267 | Support the EZchip NPS400 global interrupt controller |
268 | |||
269 | config STM32_EXTI | ||
270 | bool | ||
271 | select IRQ_DOMAIN | ||
diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile index 4c203b6b8163..96383b22cffe 100644 --- a/drivers/irqchip/Makefile +++ b/drivers/irqchip/Makefile | |||
@@ -71,3 +71,4 @@ obj-$(CONFIG_MVEBU_ODMI) += irq-mvebu-odmi.o | |||
71 | obj-$(CONFIG_LS_SCFG_MSI) += irq-ls-scfg-msi.o | 71 | obj-$(CONFIG_LS_SCFG_MSI) += irq-ls-scfg-msi.o |
72 | obj-$(CONFIG_EZNPS_GIC) += irq-eznps.o | 72 | obj-$(CONFIG_EZNPS_GIC) += irq-eznps.o |
73 | obj-$(CONFIG_ARCH_ASPEED) += irq-aspeed-vic.o | 73 | obj-$(CONFIG_ARCH_ASPEED) += irq-aspeed-vic.o |
74 | obj-$(CONFIG_STM32_EXTI) += irq-stm32-exti.o | ||
diff --git a/drivers/irqchip/irq-stm32-exti.c b/drivers/irqchip/irq-stm32-exti.c new file mode 100644 index 000000000000..491568c95aa5 --- /dev/null +++ b/drivers/irqchip/irq-stm32-exti.c | |||
@@ -0,0 +1,201 @@ | |||
1 | /* | ||
2 | * Copyright (C) Maxime Coquelin 2015 | ||
3 | * Author: Maxime Coquelin <mcoquelin.stm32@gmail.com> | ||
4 | * License terms: GNU General Public License (GPL), version 2 | ||
5 | */ | ||
6 | |||
7 | #include <linux/bitops.h> | ||
8 | #include <linux/interrupt.h> | ||
9 | #include <linux/io.h> | ||
10 | #include <linux/irq.h> | ||
11 | #include <linux/irqchip.h> | ||
12 | #include <linux/irqchip/chained_irq.h> | ||
13 | #include <linux/irqdomain.h> | ||
14 | #include <linux/of_address.h> | ||
15 | #include <linux/of_irq.h> | ||
16 | |||
17 | #define EXTI_IMR 0x0 | ||
18 | #define EXTI_EMR 0x4 | ||
19 | #define EXTI_RTSR 0x8 | ||
20 | #define EXTI_FTSR 0xc | ||
21 | #define EXTI_SWIER 0x10 | ||
22 | #define EXTI_PR 0x14 | ||
23 | |||
24 | static void stm32_irq_handler(struct irq_desc *desc) | ||
25 | { | ||
26 | struct irq_domain *domain = irq_desc_get_handler_data(desc); | ||
27 | struct irq_chip_generic *gc = domain->gc->gc[0]; | ||
28 | struct irq_chip *chip = irq_desc_get_chip(desc); | ||
29 | unsigned long pending; | ||
30 | int n; | ||
31 | |||
32 | chained_irq_enter(chip, desc); | ||
33 | |||
34 | while ((pending = irq_reg_readl(gc, EXTI_PR))) { | ||
35 | for_each_set_bit(n, &pending, BITS_PER_LONG) { | ||
36 | generic_handle_irq(irq_find_mapping(domain, n)); | ||
37 | irq_reg_writel(gc, BIT(n), EXTI_PR); | ||
38 | } | ||
39 | } | ||
40 | |||
41 | chained_irq_exit(chip, desc); | ||
42 | } | ||
43 | |||
44 | static int stm32_irq_set_type(struct irq_data *data, unsigned int type) | ||
45 | { | ||
46 | struct irq_chip_generic *gc = irq_data_get_irq_chip_data(data); | ||
47 | int pin = data->hwirq; | ||
48 | u32 rtsr, ftsr; | ||
49 | |||
50 | irq_gc_lock(gc); | ||
51 | |||
52 | rtsr = irq_reg_readl(gc, EXTI_RTSR); | ||
53 | ftsr = irq_reg_readl(gc, EXTI_FTSR); | ||
54 | |||
55 | switch (type) { | ||
56 | case IRQ_TYPE_EDGE_RISING: | ||
57 | rtsr |= BIT(pin); | ||
58 | ftsr &= ~BIT(pin); | ||
59 | break; | ||
60 | case IRQ_TYPE_EDGE_FALLING: | ||
61 | rtsr &= ~BIT(pin); | ||
62 | ftsr |= BIT(pin); | ||
63 | break; | ||
64 | case IRQ_TYPE_EDGE_BOTH: | ||
65 | rtsr |= BIT(pin); | ||
66 | ftsr |= BIT(pin); | ||
67 | break; | ||
68 | default: | ||
69 | irq_gc_unlock(gc); | ||
70 | return -EINVAL; | ||
71 | } | ||
72 | |||
73 | irq_reg_writel(gc, rtsr, EXTI_RTSR); | ||
74 | irq_reg_writel(gc, ftsr, EXTI_FTSR); | ||
75 | |||
76 | irq_gc_unlock(gc); | ||
77 | |||
78 | return 0; | ||
79 | } | ||
80 | |||
81 | static int stm32_irq_set_wake(struct irq_data *data, unsigned int on) | ||
82 | { | ||
83 | struct irq_chip_generic *gc = irq_data_get_irq_chip_data(data); | ||
84 | int pin = data->hwirq; | ||
85 | u32 emr; | ||
86 | |||
87 | irq_gc_lock(gc); | ||
88 | |||
89 | emr = irq_reg_readl(gc, EXTI_EMR); | ||
90 | if (on) | ||
91 | emr |= BIT(pin); | ||
92 | else | ||
93 | emr &= ~BIT(pin); | ||
94 | irq_reg_writel(gc, emr, EXTI_EMR); | ||
95 | |||
96 | irq_gc_unlock(gc); | ||
97 | |||
98 | return 0; | ||
99 | } | ||
100 | |||
101 | static int stm32_exti_alloc(struct irq_domain *d, unsigned int virq, | ||
102 | unsigned int nr_irqs, void *data) | ||
103 | { | ||
104 | struct irq_chip_generic *gc = d->gc->gc[0]; | ||
105 | struct irq_fwspec *fwspec = data; | ||
106 | irq_hw_number_t hwirq; | ||
107 | |||
108 | hwirq = fwspec->param[0]; | ||
109 | |||
110 | irq_map_generic_chip(d, virq, hwirq); | ||
111 | irq_domain_set_info(d, virq, hwirq, &gc->chip_types->chip, gc, | ||
112 | handle_simple_irq, NULL, NULL); | ||
113 | |||
114 | return 0; | ||
115 | } | ||
116 | |||
117 | static void stm32_exti_free(struct irq_domain *d, unsigned int virq, | ||
118 | unsigned int nr_irqs) | ||
119 | { | ||
120 | struct irq_data *data = irq_domain_get_irq_data(d, virq); | ||
121 | |||
122 | irq_domain_reset_irq_data(data); | ||
123 | } | ||
124 | |||
125 | struct irq_domain_ops irq_exti_domain_ops = { | ||
126 | .map = irq_map_generic_chip, | ||
127 | .xlate = irq_domain_xlate_onetwocell, | ||
128 | .alloc = stm32_exti_alloc, | ||
129 | .free = stm32_exti_free, | ||
130 | }; | ||
131 | |||
132 | static int __init stm32_exti_init(struct device_node *node, | ||
133 | struct device_node *parent) | ||
134 | { | ||
135 | unsigned int clr = IRQ_NOREQUEST | IRQ_NOPROBE | IRQ_NOAUTOEN; | ||
136 | int nr_irqs, nr_exti, ret, i; | ||
137 | struct irq_chip_generic *gc; | ||
138 | struct irq_domain *domain; | ||
139 | void *base; | ||
140 | |||
141 | base = of_iomap(node, 0); | ||
142 | if (!base) { | ||
143 | pr_err("%s: Unable to map registers\n", node->full_name); | ||
144 | return -ENOMEM; | ||
145 | } | ||
146 | |||
147 | /* Determine number of irqs supported */ | ||
148 | writel_relaxed(~0UL, base + EXTI_RTSR); | ||
149 | nr_exti = fls(readl_relaxed(base + EXTI_RTSR)); | ||
150 | writel_relaxed(0, base + EXTI_RTSR); | ||
151 | |||
152 | pr_info("%s: %d External IRQs detected\n", node->full_name, nr_exti); | ||
153 | |||
154 | domain = irq_domain_add_linear(node, nr_exti, | ||
155 | &irq_exti_domain_ops, NULL); | ||
156 | if (!domain) { | ||
157 | pr_err("%s: Could not register interrupt domain.\n", | ||
158 | node->name); | ||
159 | ret = -ENOMEM; | ||
160 | goto out_unmap; | ||
161 | } | ||
162 | |||
163 | ret = irq_alloc_domain_generic_chips(domain, nr_exti, 1, "exti", | ||
164 | handle_edge_irq, clr, 0, 0); | ||
165 | if (ret) { | ||
166 | pr_err("%s: Could not allocate generic interrupt chip.\n", | ||
167 | node->full_name); | ||
168 | goto out_free_domain; | ||
169 | } | ||
170 | |||
171 | gc = domain->gc->gc[0]; | ||
172 | gc->reg_base = base; | ||
173 | gc->chip_types->type = IRQ_TYPE_EDGE_BOTH; | ||
174 | gc->chip_types->chip.name = gc->chip_types[0].chip.name; | ||
175 | gc->chip_types->chip.irq_ack = irq_gc_ack_set_bit; | ||
176 | gc->chip_types->chip.irq_mask = irq_gc_mask_clr_bit; | ||
177 | gc->chip_types->chip.irq_unmask = irq_gc_mask_set_bit; | ||
178 | gc->chip_types->chip.irq_set_type = stm32_irq_set_type; | ||
179 | gc->chip_types->chip.irq_set_wake = stm32_irq_set_wake; | ||
180 | gc->chip_types->regs.ack = EXTI_PR; | ||
181 | gc->chip_types->regs.mask = EXTI_IMR; | ||
182 | gc->chip_types->handler = handle_edge_irq; | ||
183 | |||
184 | nr_irqs = of_irq_count(node); | ||
185 | for (i = 0; i < nr_irqs; i++) { | ||
186 | unsigned int irq = irq_of_parse_and_map(node, i); | ||
187 | |||
188 | irq_set_handler_data(irq, domain); | ||
189 | irq_set_chained_handler(irq, stm32_irq_handler); | ||
190 | } | ||
191 | |||
192 | return 0; | ||
193 | |||
194 | out_free_domain: | ||
195 | irq_domain_remove(domain); | ||
196 | out_unmap: | ||
197 | iounmap(base); | ||
198 | return ret; | ||
199 | } | ||
200 | |||
201 | IRQCHIP_DECLARE(stm32_exti, "st,stm32-exti", stm32_exti_init); | ||