Commit | Line | Data |
---|---|---|
5f97f7f9 HS |
1 | /* |
2 | * External interrupt handling for AT32AP CPUs | |
3 | * | |
4 | * Copyright (C) 2006 Atmel Corporation | |
5 | * | |
6 | * This program is free software; you can redistribute it and/or modify | |
7 | * it under the terms of the GNU General Public License version 2 as | |
8 | * published by the Free Software Foundation. | |
9 | */ | |
10 | ||
11 | #include <linux/errno.h> | |
12 | #include <linux/init.h> | |
13 | #include <linux/interrupt.h> | |
14 | #include <linux/irq.h> | |
15 | #include <linux/platform_device.h> | |
16 | #include <linux/random.h> | |
17 | ||
18 | #include <asm/io.h> | |
19 | ||
7a5b8059 HS |
20 | /* EIC register offsets */ |
21 | #define EIC_IER 0x0000 | |
22 | #define EIC_IDR 0x0004 | |
23 | #define EIC_IMR 0x0008 | |
24 | #define EIC_ISR 0x000c | |
25 | #define EIC_ICR 0x0010 | |
26 | #define EIC_MODE 0x0014 | |
27 | #define EIC_EDGE 0x0018 | |
28 | #define EIC_LEVEL 0x001c | |
7a5b8059 HS |
29 | #define EIC_NMIC 0x0024 |
30 | ||
7a5b8059 | 31 | /* Bitfields in NMIC */ |
e7ba176b | 32 | #define EIC_NMIC_ENABLE (1 << 0) |
7a5b8059 HS |
33 | |
34 | /* Bit manipulation macros */ | |
35 | #define EIC_BIT(name) \ | |
36 | (1 << EIC_##name##_OFFSET) | |
37 | #define EIC_BF(name,value) \ | |
38 | (((value) & ((1 << EIC_##name##_SIZE) - 1)) \ | |
39 | << EIC_##name##_OFFSET) | |
40 | #define EIC_BFEXT(name,value) \ | |
41 | (((value) >> EIC_##name##_OFFSET) \ | |
42 | & ((1 << EIC_##name##_SIZE) - 1)) | |
43 | #define EIC_BFINS(name,value,old) \ | |
44 | (((old) & ~(((1 << EIC_##name##_SIZE) - 1) \ | |
45 | << EIC_##name##_OFFSET)) \ | |
46 | | EIC_BF(name,value)) | |
47 | ||
48 | /* Register access macros */ | |
49 | #define eic_readl(port,reg) \ | |
50 | __raw_readl((port)->regs + EIC_##reg) | |
51 | #define eic_writel(port,reg,value) \ | |
52 | __raw_writel((value), (port)->regs + EIC_##reg) | |
53 | ||
54 | struct eic { | |
55 | void __iomem *regs; | |
56 | struct irq_chip *chip; | |
57 | unsigned int first_irq; | |
58 | }; | |
5f97f7f9 | 59 | |
e7ba176b HS |
60 | static struct eic *nmi_eic; |
61 | static bool nmi_enabled; | |
62 | ||
7a5b8059 | 63 | static void eic_ack_irq(unsigned int irq) |
5f97f7f9 | 64 | { |
7a5b8059 HS |
65 | struct eic *eic = get_irq_chip_data(irq); |
66 | eic_writel(eic, ICR, 1 << (irq - eic->first_irq)); | |
5f97f7f9 HS |
67 | } |
68 | ||
7a5b8059 | 69 | static void eic_mask_irq(unsigned int irq) |
5f97f7f9 | 70 | { |
7a5b8059 HS |
71 | struct eic *eic = get_irq_chip_data(irq); |
72 | eic_writel(eic, IDR, 1 << (irq - eic->first_irq)); | |
5f97f7f9 HS |
73 | } |
74 | ||
7a5b8059 | 75 | static void eic_mask_ack_irq(unsigned int irq) |
5f97f7f9 | 76 | { |
7a5b8059 HS |
77 | struct eic *eic = get_irq_chip_data(irq); |
78 | eic_writel(eic, ICR, 1 << (irq - eic->first_irq)); | |
79 | eic_writel(eic, IDR, 1 << (irq - eic->first_irq)); | |
5f97f7f9 HS |
80 | } |
81 | ||
7a5b8059 | 82 | static void eic_unmask_irq(unsigned int irq) |
5f97f7f9 | 83 | { |
7a5b8059 HS |
84 | struct eic *eic = get_irq_chip_data(irq); |
85 | eic_writel(eic, IER, 1 << (irq - eic->first_irq)); | |
5f97f7f9 HS |
86 | } |
87 | ||
7a5b8059 | 88 | static int eic_set_irq_type(unsigned int irq, unsigned int flow_type) |
5f97f7f9 | 89 | { |
7a5b8059 | 90 | struct eic *eic = get_irq_chip_data(irq); |
01cb087e | 91 | struct irq_desc *desc; |
7a5b8059 | 92 | unsigned int i = irq - eic->first_irq; |
5f97f7f9 | 93 | u32 mode, edge, level; |
5f97f7f9 HS |
94 | int ret = 0; |
95 | ||
58febc0b | 96 | flow_type &= IRQ_TYPE_SENSE_MASK; |
01cb087e HS |
97 | if (flow_type == IRQ_TYPE_NONE) |
98 | flow_type = IRQ_TYPE_LEVEL_LOW; | |
99 | ||
100 | desc = &irq_desc[irq]; | |
5f97f7f9 | 101 | |
7a5b8059 HS |
102 | mode = eic_readl(eic, MODE); |
103 | edge = eic_readl(eic, EDGE); | |
104 | level = eic_readl(eic, LEVEL); | |
5f97f7f9 HS |
105 | |
106 | switch (flow_type) { | |
107 | case IRQ_TYPE_LEVEL_LOW: | |
108 | mode |= 1 << i; | |
109 | level &= ~(1 << i); | |
110 | break; | |
111 | case IRQ_TYPE_LEVEL_HIGH: | |
112 | mode |= 1 << i; | |
113 | level |= 1 << i; | |
114 | break; | |
115 | case IRQ_TYPE_EDGE_RISING: | |
116 | mode &= ~(1 << i); | |
117 | edge |= 1 << i; | |
118 | break; | |
119 | case IRQ_TYPE_EDGE_FALLING: | |
120 | mode &= ~(1 << i); | |
121 | edge &= ~(1 << i); | |
122 | break; | |
123 | default: | |
124 | ret = -EINVAL; | |
125 | break; | |
126 | } | |
127 | ||
58febc0b | 128 | if (ret == 0) { |
7a5b8059 HS |
129 | eic_writel(eic, MODE, mode); |
130 | eic_writel(eic, EDGE, edge); | |
131 | eic_writel(eic, LEVEL, level); | |
58febc0b | 132 | |
e4f586f2 | 133 | if (flow_type & (IRQ_TYPE_LEVEL_LOW | IRQ_TYPE_LEVEL_HIGH)) { |
58febc0b | 134 | flow_type |= IRQ_LEVEL; |
e4f586f2 DB |
135 | __set_irq_handler_unlocked(irq, handle_level_irq); |
136 | } else | |
137 | __set_irq_handler_unlocked(irq, handle_edge_irq); | |
58febc0b DB |
138 | desc->status &= ~(IRQ_TYPE_SENSE_MASK | IRQ_LEVEL); |
139 | desc->status |= flow_type; | |
140 | } | |
5f97f7f9 | 141 | |
5f97f7f9 HS |
142 | return ret; |
143 | } | |
144 | ||
86298962 | 145 | static struct irq_chip eic_chip = { |
7a5b8059 HS |
146 | .name = "eic", |
147 | .ack = eic_ack_irq, | |
148 | .mask = eic_mask_irq, | |
149 | .mask_ack = eic_mask_ack_irq, | |
150 | .unmask = eic_unmask_irq, | |
151 | .set_type = eic_set_irq_type, | |
5f97f7f9 HS |
152 | }; |
153 | ||
7a5b8059 | 154 | static void demux_eic_irq(unsigned int irq, struct irq_desc *desc) |
5f97f7f9 | 155 | { |
7a5b8059 | 156 | struct eic *eic = desc->handler_data; |
5f97f7f9 | 157 | unsigned long status, pending; |
e4f586f2 | 158 | unsigned int i; |
5f97f7f9 | 159 | |
7a5b8059 HS |
160 | status = eic_readl(eic, ISR); |
161 | pending = status & eic_readl(eic, IMR); | |
5f97f7f9 HS |
162 | |
163 | while (pending) { | |
164 | i = fls(pending) - 1; | |
165 | pending &= ~(1 << i); | |
166 | ||
e4f586f2 | 167 | generic_handle_irq(i + eic->first_irq); |
5f97f7f9 | 168 | } |
5f97f7f9 HS |
169 | } |
170 | ||
e7ba176b HS |
171 | int nmi_enable(void) |
172 | { | |
173 | nmi_enabled = true; | |
174 | ||
175 | if (nmi_eic) | |
176 | eic_writel(nmi_eic, NMIC, EIC_NMIC_ENABLE); | |
177 | ||
178 | return 0; | |
179 | } | |
180 | ||
181 | void nmi_disable(void) | |
182 | { | |
183 | if (nmi_eic) | |
184 | eic_writel(nmi_eic, NMIC, 0); | |
185 | ||
186 | nmi_enabled = false; | |
187 | } | |
188 | ||
7a5b8059 | 189 | static int __init eic_probe(struct platform_device *pdev) |
5f97f7f9 | 190 | { |
7a5b8059 HS |
191 | struct eic *eic; |
192 | struct resource *regs; | |
5f97f7f9 HS |
193 | unsigned int i; |
194 | unsigned int nr_irqs; | |
195 | unsigned int int_irq; | |
7a5b8059 | 196 | int ret; |
5f97f7f9 HS |
197 | u32 pattern; |
198 | ||
7a5b8059 HS |
199 | regs = platform_get_resource(pdev, IORESOURCE_MEM, 0); |
200 | int_irq = platform_get_irq(pdev, 0); | |
201 | if (!regs || !int_irq) { | |
202 | dev_dbg(&pdev->dev, "missing regs and/or irq resource\n"); | |
203 | return -ENXIO; | |
204 | } | |
205 | ||
206 | ret = -ENOMEM; | |
207 | eic = kzalloc(sizeof(struct eic), GFP_KERNEL); | |
208 | if (!eic) { | |
209 | dev_dbg(&pdev->dev, "no memory for eic structure\n"); | |
210 | goto err_kzalloc; | |
211 | } | |
212 | ||
213 | eic->first_irq = EIM_IRQ_BASE + 32 * pdev->id; | |
214 | eic->regs = ioremap(regs->start, regs->end - regs->start + 1); | |
215 | if (!eic->regs) { | |
216 | dev_dbg(&pdev->dev, "failed to map regs\n"); | |
217 | goto err_ioremap; | |
218 | } | |
5f97f7f9 HS |
219 | |
220 | /* | |
221 | * Find out how many interrupt lines that are actually | |
222 | * implemented in hardware. | |
223 | */ | |
7a5b8059 HS |
224 | eic_writel(eic, IDR, ~0UL); |
225 | eic_writel(eic, MODE, ~0UL); | |
226 | pattern = eic_readl(eic, MODE); | |
5f97f7f9 HS |
227 | nr_irqs = fls(pattern); |
228 | ||
d6c49a7a | 229 | /* Trigger on low level unless overridden by driver */ |
7a5b8059 | 230 | eic_writel(eic, EDGE, 0UL); |
d6c49a7a | 231 | eic_writel(eic, LEVEL, 0UL); |
01cb087e | 232 | |
7a5b8059 | 233 | eic->chip = &eic_chip; |
5f97f7f9 HS |
234 | |
235 | for (i = 0; i < nr_irqs; i++) { | |
7a5b8059 | 236 | set_irq_chip_and_handler(eic->first_irq + i, &eic_chip, |
d6c49a7a | 237 | handle_level_irq); |
7a5b8059 | 238 | set_irq_chip_data(eic->first_irq + i, eic); |
5f97f7f9 HS |
239 | } |
240 | ||
7a5b8059 HS |
241 | set_irq_chained_handler(int_irq, demux_eic_irq); |
242 | set_irq_data(int_irq, eic); | |
5f97f7f9 | 243 | |
e7ba176b HS |
244 | if (pdev->id == 0) { |
245 | nmi_eic = eic; | |
246 | if (nmi_enabled) | |
247 | /* | |
248 | * Someone tried to enable NMI before we were | |
249 | * ready. Do it now. | |
250 | */ | |
251 | nmi_enable(); | |
252 | } | |
253 | ||
7a5b8059 HS |
254 | dev_info(&pdev->dev, |
255 | "External Interrupt Controller at 0x%p, IRQ %u\n", | |
256 | eic->regs, int_irq); | |
257 | dev_info(&pdev->dev, | |
258 | "Handling %u external IRQs, starting with IRQ %u\n", | |
259 | nr_irqs, eic->first_irq); | |
5f97f7f9 HS |
260 | |
261 | return 0; | |
7a5b8059 HS |
262 | |
263 | err_ioremap: | |
264 | kfree(eic); | |
265 | err_kzalloc: | |
266 | return ret; | |
267 | } | |
268 | ||
269 | static struct platform_driver eic_driver = { | |
270 | .driver = { | |
271 | .name = "at32_eic", | |
272 | }, | |
273 | }; | |
274 | ||
275 | static int __init eic_init(void) | |
276 | { | |
277 | return platform_driver_probe(&eic_driver, eic_probe); | |
5f97f7f9 | 278 | } |
7a5b8059 | 279 | arch_initcall(eic_init); |