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