Merge branch 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/gerg/m68knommu
[pandora-kernel.git] / arch / mips / bcm63xx / irq.c
1 /*
2  * This file is subject to the terms and conditions of the GNU General Public
3  * License.  See the file "COPYING" in the main directory of this archive
4  * for more details.
5  *
6  * Copyright (C) 2008 Maxime Bizon <mbizon@freebox.fr>
7  * Copyright (C) 2008 Nicolas Schichan <nschichan@freebox.fr>
8  */
9
10 #include <linux/kernel.h>
11 #include <linux/init.h>
12 #include <linux/interrupt.h>
13 #include <linux/module.h>
14 #include <linux/irq.h>
15 #include <asm/irq_cpu.h>
16 #include <asm/mipsregs.h>
17 #include <bcm63xx_cpu.h>
18 #include <bcm63xx_regs.h>
19 #include <bcm63xx_io.h>
20 #include <bcm63xx_irq.h>
21
22 /*
23  * dispatch internal devices IRQ (uart, enet, watchdog, ...). do not
24  * prioritize any interrupt relatively to another. the static counter
25  * will resume the loop where it ended the last time we left this
26  * function.
27  */
28 static void bcm63xx_irq_dispatch_internal(void)
29 {
30         u32 pending;
31         static int i;
32
33         pending = bcm_perf_readl(PERF_IRQMASK_REG) &
34                 bcm_perf_readl(PERF_IRQSTAT_REG);
35
36         if (!pending)
37                 return ;
38
39         while (1) {
40                 int to_call = i;
41
42                 i = (i + 1) & 0x1f;
43                 if (pending & (1 << to_call)) {
44                         do_IRQ(to_call + IRQ_INTERNAL_BASE);
45                         break;
46                 }
47         }
48 }
49
50 asmlinkage void plat_irq_dispatch(void)
51 {
52         u32 cause;
53
54         do {
55                 cause = read_c0_cause() & read_c0_status() & ST0_IM;
56
57                 if (!cause)
58                         break;
59
60                 if (cause & CAUSEF_IP7)
61                         do_IRQ(7);
62                 if (cause & CAUSEF_IP2)
63                         bcm63xx_irq_dispatch_internal();
64                 if (cause & CAUSEF_IP3)
65                         do_IRQ(IRQ_EXT_0);
66                 if (cause & CAUSEF_IP4)
67                         do_IRQ(IRQ_EXT_1);
68                 if (cause & CAUSEF_IP5)
69                         do_IRQ(IRQ_EXT_2);
70                 if (cause & CAUSEF_IP6)
71                         do_IRQ(IRQ_EXT_3);
72         } while (1);
73 }
74
75 /*
76  * internal IRQs operations: only mask/unmask on PERF irq mask
77  * register.
78  */
79 static inline void bcm63xx_internal_irq_mask(unsigned int irq)
80 {
81         u32 mask;
82
83         irq -= IRQ_INTERNAL_BASE;
84         mask = bcm_perf_readl(PERF_IRQMASK_REG);
85         mask &= ~(1 << irq);
86         bcm_perf_writel(mask, PERF_IRQMASK_REG);
87 }
88
89 static void bcm63xx_internal_irq_unmask(unsigned int irq)
90 {
91         u32 mask;
92
93         irq -= IRQ_INTERNAL_BASE;
94         mask = bcm_perf_readl(PERF_IRQMASK_REG);
95         mask |= (1 << irq);
96         bcm_perf_writel(mask, PERF_IRQMASK_REG);
97 }
98
99 static unsigned int bcm63xx_internal_irq_startup(unsigned int irq)
100 {
101         bcm63xx_internal_irq_unmask(irq);
102         return 0;
103 }
104
105 /*
106  * external IRQs operations: mask/unmask and clear on PERF external
107  * irq control register.
108  */
109 static void bcm63xx_external_irq_mask(unsigned int irq)
110 {
111         u32 reg;
112
113         irq -= IRQ_EXT_BASE;
114         reg = bcm_perf_readl(PERF_EXTIRQ_CFG_REG);
115         reg &= ~EXTIRQ_CFG_MASK(irq);
116         bcm_perf_writel(reg, PERF_EXTIRQ_CFG_REG);
117 }
118
119 static void bcm63xx_external_irq_unmask(unsigned int irq)
120 {
121         u32 reg;
122
123         irq -= IRQ_EXT_BASE;
124         reg = bcm_perf_readl(PERF_EXTIRQ_CFG_REG);
125         reg |= EXTIRQ_CFG_MASK(irq);
126         bcm_perf_writel(reg, PERF_EXTIRQ_CFG_REG);
127 }
128
129 static void bcm63xx_external_irq_clear(unsigned int irq)
130 {
131         u32 reg;
132
133         irq -= IRQ_EXT_BASE;
134         reg = bcm_perf_readl(PERF_EXTIRQ_CFG_REG);
135         reg |= EXTIRQ_CFG_CLEAR(irq);
136         bcm_perf_writel(reg, PERF_EXTIRQ_CFG_REG);
137 }
138
139 static unsigned int bcm63xx_external_irq_startup(unsigned int irq)
140 {
141         set_c0_status(0x100 << (irq - IRQ_MIPS_BASE));
142         irq_enable_hazard();
143         bcm63xx_external_irq_unmask(irq);
144         return 0;
145 }
146
147 static void bcm63xx_external_irq_shutdown(unsigned int irq)
148 {
149         bcm63xx_external_irq_mask(irq);
150         clear_c0_status(0x100 << (irq - IRQ_MIPS_BASE));
151         irq_disable_hazard();
152 }
153
154 static int bcm63xx_external_irq_set_type(unsigned int irq,
155                                          unsigned int flow_type)
156 {
157         u32 reg;
158         struct irq_desc *desc = irq_desc + irq;
159
160         irq -= IRQ_EXT_BASE;
161
162         flow_type &= IRQ_TYPE_SENSE_MASK;
163
164         if (flow_type == IRQ_TYPE_NONE)
165                 flow_type = IRQ_TYPE_LEVEL_LOW;
166
167         reg = bcm_perf_readl(PERF_EXTIRQ_CFG_REG);
168         switch (flow_type) {
169         case IRQ_TYPE_EDGE_BOTH:
170                 reg &= ~EXTIRQ_CFG_LEVELSENSE(irq);
171                 reg |= EXTIRQ_CFG_BOTHEDGE(irq);
172                 break;
173
174         case IRQ_TYPE_EDGE_RISING:
175                 reg &= ~EXTIRQ_CFG_LEVELSENSE(irq);
176                 reg |= EXTIRQ_CFG_SENSE(irq);
177                 reg &= ~EXTIRQ_CFG_BOTHEDGE(irq);
178                 break;
179
180         case IRQ_TYPE_EDGE_FALLING:
181                 reg &= ~EXTIRQ_CFG_LEVELSENSE(irq);
182                 reg &= ~EXTIRQ_CFG_SENSE(irq);
183                 reg &= ~EXTIRQ_CFG_BOTHEDGE(irq);
184                 break;
185
186         case IRQ_TYPE_LEVEL_HIGH:
187                 reg |= EXTIRQ_CFG_LEVELSENSE(irq);
188                 reg |= EXTIRQ_CFG_SENSE(irq);
189                 break;
190
191         case IRQ_TYPE_LEVEL_LOW:
192                 reg |= EXTIRQ_CFG_LEVELSENSE(irq);
193                 reg &= ~EXTIRQ_CFG_SENSE(irq);
194                 break;
195
196         default:
197                 printk(KERN_ERR "bogus flow type combination given !\n");
198                 return -EINVAL;
199         }
200         bcm_perf_writel(reg, PERF_EXTIRQ_CFG_REG);
201
202         if (flow_type & (IRQ_TYPE_LEVEL_LOW | IRQ_TYPE_LEVEL_HIGH))  {
203                 desc->status |= IRQ_LEVEL;
204                 desc->handle_irq = handle_level_irq;
205         } else {
206                 desc->handle_irq = handle_edge_irq;
207         }
208
209         return 0;
210 }
211
212 static struct irq_chip bcm63xx_internal_irq_chip = {
213         .name           = "bcm63xx_ipic",
214         .startup        = bcm63xx_internal_irq_startup,
215         .shutdown       = bcm63xx_internal_irq_mask,
216
217         .mask           = bcm63xx_internal_irq_mask,
218         .mask_ack       = bcm63xx_internal_irq_mask,
219         .unmask         = bcm63xx_internal_irq_unmask,
220 };
221
222 static struct irq_chip bcm63xx_external_irq_chip = {
223         .name           = "bcm63xx_epic",
224         .startup        = bcm63xx_external_irq_startup,
225         .shutdown       = bcm63xx_external_irq_shutdown,
226
227         .ack            = bcm63xx_external_irq_clear,
228
229         .mask           = bcm63xx_external_irq_mask,
230         .unmask         = bcm63xx_external_irq_unmask,
231
232         .set_type       = bcm63xx_external_irq_set_type,
233 };
234
235 static struct irqaction cpu_ip2_cascade_action = {
236         .handler        = no_action,
237         .name           = "cascade_ip2",
238 };
239
240 void __init arch_init_irq(void)
241 {
242         int i;
243
244         mips_cpu_irq_init();
245         for (i = IRQ_INTERNAL_BASE; i < NR_IRQS; ++i)
246                 set_irq_chip_and_handler(i, &bcm63xx_internal_irq_chip,
247                                          handle_level_irq);
248
249         for (i = IRQ_EXT_BASE; i < IRQ_EXT_BASE + 4; ++i)
250                 set_irq_chip_and_handler(i, &bcm63xx_external_irq_chip,
251                                          handle_edge_irq);
252
253         setup_irq(IRQ_MIPS_BASE + 2, &cpu_ip2_cascade_action);
254 }