Skip to content

Commit 8cb17b5

Browse files
vzapolskiyMarc Zyngier
authored andcommitted
irqchip: Add LPC32xx interrupt controller driver
The change adds improved support of NXP LPC32xx MIC, SIC1 and SIC2 interrupt controllers. This is a list of new features in comparison to the legacy driver: * irq types are taken from device tree settings, no more need to hardcode them, * old driver is based on irq_domain_add_legacy, which causes problems with handling MIC hardware interrupt 0 produced by SIC1, * there is one driver for MIC, SIC1 and SIC2, no more need to handle them separately, e.g. have two separate handlers for SIC1 and SIC2, * the driver does not have any dependencies on hardcoded register offsets, * the driver is much simpler for maintenance, * SPARSE_IRQS option is supported. Legacy LPC32xx interrupt controller driver was broken since commit 76ba59f ("genirq: Add irq_domain-aware core IRQ handler"), which requires a private interrupt handler, otherwise any SIC1 generated interrupt (mapped to MIC hwirq 0) breaks the kernel with the message "unexpected IRQ trap at vector 00". The change disables compilation of a legacy driver found at arch/arm/mach-lpc32xx/irq.c, the file will be removed in a separate commit. Fixes: 76ba59f ("genirq: Add irq_domain-aware core IRQ handler") Tested-by: Sylvain Lemieux <[email protected]> Signed-off-by: Vladimir Zapolskiy <[email protected]> Signed-off-by: Marc Zyngier <[email protected]>
1 parent f86c4fb commit 8cb17b5

File tree

4 files changed

+241
-1
lines changed

4 files changed

+241
-1
lines changed

arch/arm/Kconfig

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -531,6 +531,8 @@ config ARCH_LPC32XX
531531
select COMMON_CLK
532532
select CPU_ARM926T
533533
select GENERIC_CLOCKEVENTS
534+
select MULTI_IRQ_HANDLER
535+
select SPARSE_IRQ
534536
select USE_OF
535537
help
536538
Support for the NXP LPC32XX family of processors

arch/arm/mach-lpc32xx/phy3250.c

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,6 @@ static const char *const lpc32xx_dt_compat[] __initconst = {
206206
DT_MACHINE_START(LPC32XX_DT, "LPC32XX SoC (Flattened Device Tree)")
207207
.atag_offset = 0x100,
208208
.map_io = lpc32xx_map_io,
209-
.init_irq = lpc32xx_init_irq,
210209
.init_machine = lpc3250_machine_init,
211210
.dt_compat = lpc32xx_dt_compat,
212211
MACHINE_END

drivers/irqchip/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ obj-$(CONFIG_ARCH_BCM2835) += irq-bcm2835.o
77
obj-$(CONFIG_ARCH_BCM2835) += irq-bcm2836.o
88
obj-$(CONFIG_ARCH_EXYNOS) += exynos-combiner.o
99
obj-$(CONFIG_ARCH_HIP04) += irq-hip04.o
10+
obj-$(CONFIG_ARCH_LPC32XX) += irq-lpc32xx.o
1011
obj-$(CONFIG_ARCH_MMP) += irq-mmp.o
1112
obj-$(CONFIG_IRQ_MXS) += irq-mxs.o
1213
obj-$(CONFIG_ARCH_TEGRA) += irq-tegra.o

drivers/irqchip/irq-lpc32xx.c

Lines changed: 238 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,238 @@
1+
/*
2+
* Copyright 2015-2016 Vladimir Zapolskiy <[email protected]>
3+
*
4+
* The code contained herein is licensed under the GNU General Public
5+
* License. You may obtain a copy of the GNU General Public License
6+
* Version 2 or later at the following locations:
7+
*
8+
* http://www.opensource.org/licenses/gpl-license.html
9+
* http://www.gnu.org/copyleft/gpl.html
10+
*/
11+
12+
#define pr_fmt(fmt) "%s: " fmt, __func__
13+
14+
#include <linux/io.h>
15+
#include <linux/irqchip.h>
16+
#include <linux/irqchip/chained_irq.h>
17+
#include <linux/of_address.h>
18+
#include <linux/of_irq.h>
19+
#include <linux/of_platform.h>
20+
#include <linux/slab.h>
21+
#include <asm/exception.h>
22+
23+
#define LPC32XX_INTC_MASK 0x00
24+
#define LPC32XX_INTC_RAW 0x04
25+
#define LPC32XX_INTC_STAT 0x08
26+
#define LPC32XX_INTC_POL 0x0C
27+
#define LPC32XX_INTC_TYPE 0x10
28+
#define LPC32XX_INTC_FIQ 0x14
29+
30+
#define NR_LPC32XX_IC_IRQS 32
31+
32+
struct lpc32xx_irq_chip {
33+
void __iomem *base;
34+
struct irq_domain *domain;
35+
struct irq_chip chip;
36+
};
37+
38+
static struct lpc32xx_irq_chip *lpc32xx_mic_irqc;
39+
40+
static inline u32 lpc32xx_ic_read(struct lpc32xx_irq_chip *ic, u32 reg)
41+
{
42+
return readl_relaxed(ic->base + reg);
43+
}
44+
45+
static inline void lpc32xx_ic_write(struct lpc32xx_irq_chip *ic,
46+
u32 reg, u32 val)
47+
{
48+
writel_relaxed(val, ic->base + reg);
49+
}
50+
51+
static void lpc32xx_irq_mask(struct irq_data *d)
52+
{
53+
struct lpc32xx_irq_chip *ic = irq_data_get_irq_chip_data(d);
54+
u32 val, mask = BIT(d->hwirq);
55+
56+
val = lpc32xx_ic_read(ic, LPC32XX_INTC_MASK) & ~mask;
57+
lpc32xx_ic_write(ic, LPC32XX_INTC_MASK, val);
58+
}
59+
60+
static void lpc32xx_irq_unmask(struct irq_data *d)
61+
{
62+
struct lpc32xx_irq_chip *ic = irq_data_get_irq_chip_data(d);
63+
u32 val, mask = BIT(d->hwirq);
64+
65+
val = lpc32xx_ic_read(ic, LPC32XX_INTC_MASK) | mask;
66+
lpc32xx_ic_write(ic, LPC32XX_INTC_MASK, val);
67+
}
68+
69+
static void lpc32xx_irq_ack(struct irq_data *d)
70+
{
71+
struct lpc32xx_irq_chip *ic = irq_data_get_irq_chip_data(d);
72+
u32 mask = BIT(d->hwirq);
73+
74+
lpc32xx_ic_write(ic, LPC32XX_INTC_RAW, mask);
75+
}
76+
77+
static int lpc32xx_irq_set_type(struct irq_data *d, unsigned int type)
78+
{
79+
struct lpc32xx_irq_chip *ic = irq_data_get_irq_chip_data(d);
80+
u32 val, mask = BIT(d->hwirq);
81+
bool high, edge;
82+
83+
switch (type) {
84+
case IRQ_TYPE_EDGE_RISING:
85+
edge = true;
86+
high = true;
87+
break;
88+
case IRQ_TYPE_EDGE_FALLING:
89+
edge = true;
90+
high = false;
91+
break;
92+
case IRQ_TYPE_LEVEL_HIGH:
93+
edge = false;
94+
high = true;
95+
break;
96+
case IRQ_TYPE_LEVEL_LOW:
97+
edge = false;
98+
high = false;
99+
break;
100+
default:
101+
pr_info("unsupported irq type %d\n", type);
102+
return -EINVAL;
103+
}
104+
105+
irqd_set_trigger_type(d, type);
106+
107+
val = lpc32xx_ic_read(ic, LPC32XX_INTC_POL);
108+
if (high)
109+
val |= mask;
110+
else
111+
val &= ~mask;
112+
lpc32xx_ic_write(ic, LPC32XX_INTC_POL, val);
113+
114+
val = lpc32xx_ic_read(ic, LPC32XX_INTC_TYPE);
115+
if (edge) {
116+
val |= mask;
117+
irq_set_handler_locked(d, handle_edge_irq);
118+
} else {
119+
val &= ~mask;
120+
irq_set_handler_locked(d, handle_level_irq);
121+
}
122+
lpc32xx_ic_write(ic, LPC32XX_INTC_TYPE, val);
123+
124+
return 0;
125+
}
126+
127+
static void __exception_irq_entry lpc32xx_handle_irq(struct pt_regs *regs)
128+
{
129+
struct lpc32xx_irq_chip *ic = lpc32xx_mic_irqc;
130+
u32 hwirq = lpc32xx_ic_read(ic, LPC32XX_INTC_STAT), irq;
131+
132+
while (hwirq) {
133+
irq = __ffs(hwirq);
134+
hwirq &= ~BIT(irq);
135+
handle_domain_irq(lpc32xx_mic_irqc->domain, irq, regs);
136+
}
137+
}
138+
139+
static void lpc32xx_sic_handler(struct irq_desc *desc)
140+
{
141+
struct lpc32xx_irq_chip *ic = irq_desc_get_handler_data(desc);
142+
struct irq_chip *chip = irq_desc_get_chip(desc);
143+
u32 hwirq = lpc32xx_ic_read(ic, LPC32XX_INTC_STAT), irq;
144+
145+
chained_irq_enter(chip, desc);
146+
147+
while (hwirq) {
148+
irq = __ffs(hwirq);
149+
hwirq &= ~BIT(irq);
150+
generic_handle_irq(irq_find_mapping(ic->domain, irq));
151+
}
152+
153+
chained_irq_exit(chip, desc);
154+
}
155+
156+
static int lpc32xx_irq_domain_map(struct irq_domain *id, unsigned int virq,
157+
irq_hw_number_t hw)
158+
{
159+
struct lpc32xx_irq_chip *ic = id->host_data;
160+
161+
irq_set_chip_data(virq, ic);
162+
irq_set_chip_and_handler(virq, &ic->chip, handle_level_irq);
163+
irq_set_status_flags(virq, IRQ_LEVEL);
164+
irq_set_noprobe(virq);
165+
166+
return 0;
167+
}
168+
169+
static void lpc32xx_irq_domain_unmap(struct irq_domain *id, unsigned int virq)
170+
{
171+
irq_set_chip_and_handler(virq, NULL, NULL);
172+
}
173+
174+
static const struct irq_domain_ops lpc32xx_irq_domain_ops = {
175+
.map = lpc32xx_irq_domain_map,
176+
.unmap = lpc32xx_irq_domain_unmap,
177+
.xlate = irq_domain_xlate_twocell,
178+
};
179+
180+
static int __init lpc32xx_of_ic_init(struct device_node *node,
181+
struct device_node *parent)
182+
{
183+
struct lpc32xx_irq_chip *irqc;
184+
bool is_mic = of_device_is_compatible(node, "nxp,lpc3220-mic");
185+
const __be32 *reg = of_get_property(node, "reg", NULL);
186+
u32 parent_irq, i, addr = reg ? be32_to_cpu(*reg) : 0;
187+
188+
irqc = kzalloc(sizeof(*irqc), GFP_KERNEL);
189+
if (!irqc)
190+
return -ENOMEM;
191+
192+
irqc->base = of_iomap(node, 0);
193+
if (!irqc->base) {
194+
pr_err("%s: unable to map registers\n", node->full_name);
195+
kfree(irqc);
196+
return -EINVAL;
197+
}
198+
199+
irqc->chip.irq_ack = lpc32xx_irq_ack;
200+
irqc->chip.irq_mask = lpc32xx_irq_mask;
201+
irqc->chip.irq_unmask = lpc32xx_irq_unmask;
202+
irqc->chip.irq_set_type = lpc32xx_irq_set_type;
203+
if (is_mic)
204+
irqc->chip.name = kasprintf(GFP_KERNEL, "%08x.mic", addr);
205+
else
206+
irqc->chip.name = kasprintf(GFP_KERNEL, "%08x.sic", addr);
207+
208+
irqc->domain = irq_domain_add_linear(node, NR_LPC32XX_IC_IRQS,
209+
&lpc32xx_irq_domain_ops, irqc);
210+
if (!irqc->domain) {
211+
pr_err("unable to add irq domain\n");
212+
iounmap(irqc->base);
213+
kfree(irqc->chip.name);
214+
kfree(irqc);
215+
return -ENODEV;
216+
}
217+
218+
if (is_mic) {
219+
lpc32xx_mic_irqc = irqc;
220+
set_handle_irq(lpc32xx_handle_irq);
221+
} else {
222+
for (i = 0; i < of_irq_count(node); i++) {
223+
parent_irq = irq_of_parse_and_map(node, i);
224+
if (parent_irq)
225+
irq_set_chained_handler_and_data(parent_irq,
226+
lpc32xx_sic_handler, irqc);
227+
}
228+
}
229+
230+
lpc32xx_ic_write(irqc, LPC32XX_INTC_MASK, 0x00);
231+
lpc32xx_ic_write(irqc, LPC32XX_INTC_POL, 0x00);
232+
lpc32xx_ic_write(irqc, LPC32XX_INTC_TYPE, 0x00);
233+
234+
return 0;
235+
}
236+
237+
IRQCHIP_DECLARE(nxp_lpc32xx_mic, "nxp,lpc3220-mic", lpc32xx_of_ic_init);
238+
IRQCHIP_DECLARE(nxp_lpc32xx_sic, "nxp,lpc3220-sic", lpc32xx_of_ic_init);

0 commit comments

Comments
 (0)