Commit | Line | Data |
---|---|---|
5f97f7f9 | 1 | /* |
aa8e87ca | 2 | * Copyright (C) 2006, 2008 Atmel Corporation |
5f97f7f9 HS |
3 | * |
4 | * This program is free software; you can redistribute it and/or modify | |
5 | * it under the terms of the GNU General Public License version 2 as | |
6 | * published by the Free Software Foundation. | |
7 | */ | |
8 | ||
9 | #include <linux/clk.h> | |
10 | #include <linux/err.h> | |
11 | #include <linux/init.h> | |
12 | #include <linux/interrupt.h> | |
13 | #include <linux/irq.h> | |
14 | #include <linux/platform_device.h> | |
aa8e87ca | 15 | #include <linux/sysdev.h> |
5f97f7f9 HS |
16 | |
17 | #include <asm/io.h> | |
18 | ||
19 | #include "intc.h" | |
20 | ||
21 | struct intc { | |
aa8e87ca HS |
22 | void __iomem *regs; |
23 | struct irq_chip chip; | |
24 | struct sys_device sysdev; | |
02a00cf6 HS |
25 | #ifdef CONFIG_PM |
26 | unsigned long suspend_ipr; | |
27 | unsigned long saved_ipr[64]; | |
28 | #endif | |
5f97f7f9 HS |
29 | }; |
30 | ||
31 | extern struct platform_device at32_intc0_device; | |
32 | ||
33 | /* | |
34 | * TODO: We may be able to implement mask/unmask by setting IxM flags | |
35 | * in the status register. | |
36 | */ | |
3972f691 | 37 | static void intc_mask_irq(struct irq_data *d) |
5f97f7f9 HS |
38 | { |
39 | ||
40 | } | |
41 | ||
3972f691 | 42 | static void intc_unmask_irq(struct irq_data *d) |
5f97f7f9 HS |
43 | { |
44 | ||
45 | } | |
46 | ||
47 | static struct intc intc0 = { | |
48 | .chip = { | |
49 | .name = "intc", | |
3972f691 TG |
50 | .irq_mask = intc_mask_irq, |
51 | .irq_unmask = intc_unmask_irq, | |
5f97f7f9 HS |
52 | }, |
53 | }; | |
54 | ||
55 | /* | |
56 | * All interrupts go via intc at some point. | |
57 | */ | |
58 | asmlinkage void do_IRQ(int level, struct pt_regs *regs) | |
59 | { | |
4e0fadfc | 60 | struct pt_regs *old_regs; |
5f97f7f9 HS |
61 | unsigned int irq; |
62 | unsigned long status_reg; | |
63 | ||
64 | local_irq_disable(); | |
65 | ||
4e0fadfc HS |
66 | old_regs = set_irq_regs(regs); |
67 | ||
5f97f7f9 HS |
68 | irq_enter(); |
69 | ||
70 | irq = intc_readl(&intc0, INTCAUSE0 - 4 * level); | |
3972f691 | 71 | generic_handle_irq(irq); |
5f97f7f9 HS |
72 | |
73 | /* | |
74 | * Clear all interrupt level masks so that we may handle | |
75 | * interrupts during softirq processing. If this is a nested | |
76 | * interrupt, interrupts must stay globally disabled until we | |
77 | * return. | |
78 | */ | |
79 | status_reg = sysreg_read(SR); | |
80 | status_reg &= ~(SYSREG_BIT(I0M) | SYSREG_BIT(I1M) | |
81 | | SYSREG_BIT(I2M) | SYSREG_BIT(I3M)); | |
82 | sysreg_write(SR, status_reg); | |
83 | ||
84 | irq_exit(); | |
4e0fadfc HS |
85 | |
86 | set_irq_regs(old_regs); | |
5f97f7f9 HS |
87 | } |
88 | ||
89 | void __init init_IRQ(void) | |
90 | { | |
91 | extern void _evba(void); | |
92 | extern void irq_level0(void); | |
93 | struct resource *regs; | |
94 | struct clk *pclk; | |
95 | unsigned int i; | |
96 | u32 offset, readback; | |
97 | ||
98 | regs = platform_get_resource(&at32_intc0_device, IORESOURCE_MEM, 0); | |
99 | if (!regs) { | |
100 | printk(KERN_EMERG "intc: no mmio resource defined\n"); | |
101 | goto fail; | |
102 | } | |
103 | pclk = clk_get(&at32_intc0_device.dev, "pclk"); | |
104 | if (IS_ERR(pclk)) { | |
105 | printk(KERN_EMERG "intc: no clock defined\n"); | |
106 | goto fail; | |
107 | } | |
108 | ||
109 | clk_enable(pclk); | |
110 | ||
111 | intc0.regs = ioremap(regs->start, regs->end - regs->start + 1); | |
112 | if (!intc0.regs) { | |
113 | printk(KERN_EMERG "intc: failed to map registers (0x%08lx)\n", | |
114 | (unsigned long)regs->start); | |
115 | goto fail; | |
116 | } | |
117 | ||
118 | /* | |
119 | * Initialize all interrupts to level 0 (lowest priority). The | |
120 | * priority level may be changed by calling | |
121 | * irq_set_priority(). | |
122 | * | |
123 | */ | |
124 | offset = (unsigned long)&irq_level0 - (unsigned long)&_evba; | |
125 | for (i = 0; i < NR_INTERNAL_IRQS; i++) { | |
126 | intc_writel(&intc0, INTPR0 + 4 * i, offset); | |
127 | readback = intc_readl(&intc0, INTPR0 + 4 * i); | |
128 | if (readback == offset) | |
d75f1bfd | 129 | irq_set_chip_and_handler(i, &intc0.chip, |
5f97f7f9 HS |
130 | handle_simple_irq); |
131 | } | |
132 | ||
133 | /* Unmask all interrupt levels */ | |
134 | sysreg_write(SR, (sysreg_read(SR) | |
135 | & ~(SR_I3M | SR_I2M | SR_I1M | SR_I0M))); | |
136 | ||
137 | return; | |
138 | ||
139 | fail: | |
140 | panic("Interrupt controller initialization failed!\n"); | |
141 | } | |
142 | ||
02a00cf6 HS |
143 | #ifdef CONFIG_PM |
144 | void intc_set_suspend_handler(unsigned long offset) | |
145 | { | |
146 | intc0.suspend_ipr = offset; | |
147 | } | |
148 | ||
149 | static int intc_suspend(struct sys_device *sdev, pm_message_t state) | |
150 | { | |
151 | struct intc *intc = container_of(sdev, struct intc, sysdev); | |
152 | int i; | |
153 | ||
154 | if (unlikely(!irqs_disabled())) { | |
155 | pr_err("intc_suspend: called with interrupts enabled\n"); | |
156 | return -EINVAL; | |
157 | } | |
158 | ||
159 | if (unlikely(!intc->suspend_ipr)) { | |
160 | pr_err("intc_suspend: suspend_ipr not initialized\n"); | |
161 | return -EINVAL; | |
162 | } | |
163 | ||
164 | for (i = 0; i < 64; i++) { | |
165 | intc->saved_ipr[i] = intc_readl(intc, INTPR0 + 4 * i); | |
166 | intc_writel(intc, INTPR0 + 4 * i, intc->suspend_ipr); | |
167 | } | |
168 | ||
169 | return 0; | |
170 | } | |
171 | ||
172 | static int intc_resume(struct sys_device *sdev) | |
173 | { | |
174 | struct intc *intc = container_of(sdev, struct intc, sysdev); | |
175 | int i; | |
176 | ||
177 | WARN_ON(!irqs_disabled()); | |
178 | ||
179 | for (i = 0; i < 64; i++) | |
180 | intc_writel(intc, INTPR0 + 4 * i, intc->saved_ipr[i]); | |
181 | ||
182 | return 0; | |
183 | } | |
184 | #else | |
185 | #define intc_suspend NULL | |
186 | #define intc_resume NULL | |
187 | #endif | |
188 | ||
aa8e87ca | 189 | static struct sysdev_class intc_class = { |
02a00cf6 HS |
190 | .name = "intc", |
191 | .suspend = intc_suspend, | |
192 | .resume = intc_resume, | |
aa8e87ca HS |
193 | }; |
194 | ||
195 | static int __init intc_init_sysdev(void) | |
196 | { | |
197 | int ret; | |
198 | ||
199 | ret = sysdev_class_register(&intc_class); | |
200 | if (ret) | |
201 | return ret; | |
202 | ||
203 | intc0.sysdev.id = 0; | |
204 | intc0.sysdev.cls = &intc_class; | |
205 | ret = sysdev_register(&intc0.sysdev); | |
206 | ||
207 | return ret; | |
208 | } | |
209 | device_initcall(intc_init_sysdev); | |
210 | ||
597702ae | 211 | unsigned long intc_get_pending(unsigned int group) |
69562118 HS |
212 | { |
213 | return intc_readl(&intc0, INTREQ0 + 4 * group); | |
214 | } | |
597702ae | 215 | EXPORT_SYMBOL_GPL(intc_get_pending); |