Merge branch 'syscore' of git://git.kernel.org/pub/scm/linux/kernel/git/rafael/suspen...
[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(struct irq_data *d)
80 {
81         unsigned int irq = d->irq - IRQ_INTERNAL_BASE;
82         u32 mask;
83
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(struct irq_data *d)
90 {
91         unsigned int irq = d->irq - IRQ_INTERNAL_BASE;
92         u32 mask;
93
94         mask = bcm_perf_readl(PERF_IRQMASK_REG);
95         mask |= (1 << irq);
96         bcm_perf_writel(mask, PERF_IRQMASK_REG);
97 }
98
99 /*
100  * external IRQs operations: mask/unmask and clear on PERF external
101  * irq control register.
102  */
103 static void bcm63xx_external_irq_mask(struct irq_data *d)
104 {
105         unsigned int irq = d->irq - IRQ_EXT_BASE;
106         u32 reg;
107
108         reg = bcm_perf_readl(PERF_EXTIRQ_CFG_REG);
109         reg &= ~EXTIRQ_CFG_MASK(irq);
110         bcm_perf_writel(reg, PERF_EXTIRQ_CFG_REG);
111 }
112
113 static void bcm63xx_external_irq_unmask(struct irq_data *d)
114 {
115         unsigned int irq = d->irq - IRQ_EXT_BASE;
116         u32 reg;
117
118         reg = bcm_perf_readl(PERF_EXTIRQ_CFG_REG);
119         reg |= EXTIRQ_CFG_MASK(irq);
120         bcm_perf_writel(reg, PERF_EXTIRQ_CFG_REG);
121 }
122
123 static void bcm63xx_external_irq_clear(struct irq_data *d)
124 {
125         unsigned int irq = d->irq - IRQ_EXT_BASE;
126         u32 reg;
127
128         reg = bcm_perf_readl(PERF_EXTIRQ_CFG_REG);
129         reg |= EXTIRQ_CFG_CLEAR(irq);
130         bcm_perf_writel(reg, PERF_EXTIRQ_CFG_REG);
131 }
132
133 static unsigned int bcm63xx_external_irq_startup(struct irq_data *d)
134 {
135         set_c0_status(0x100 << (d->irq - IRQ_MIPS_BASE));
136         irq_enable_hazard();
137         bcm63xx_external_irq_unmask(d);
138         return 0;
139 }
140
141 static void bcm63xx_external_irq_shutdown(struct irq_data *d)
142 {
143         bcm63xx_external_irq_mask(d);
144         clear_c0_status(0x100 << (d->irq - IRQ_MIPS_BASE));
145         irq_disable_hazard();
146 }
147
148 static int bcm63xx_external_irq_set_type(struct irq_data *d,
149                                          unsigned int flow_type)
150 {
151         unsigned int irq = d->irq - IRQ_EXT_BASE;
152         u32 reg;
153
154         flow_type &= IRQ_TYPE_SENSE_MASK;
155
156         if (flow_type == IRQ_TYPE_NONE)
157                 flow_type = IRQ_TYPE_LEVEL_LOW;
158
159         reg = bcm_perf_readl(PERF_EXTIRQ_CFG_REG);
160         switch (flow_type) {
161         case IRQ_TYPE_EDGE_BOTH:
162                 reg &= ~EXTIRQ_CFG_LEVELSENSE(irq);
163                 reg |= EXTIRQ_CFG_BOTHEDGE(irq);
164                 break;
165
166         case IRQ_TYPE_EDGE_RISING:
167                 reg &= ~EXTIRQ_CFG_LEVELSENSE(irq);
168                 reg |= EXTIRQ_CFG_SENSE(irq);
169                 reg &= ~EXTIRQ_CFG_BOTHEDGE(irq);
170                 break;
171
172         case IRQ_TYPE_EDGE_FALLING:
173                 reg &= ~EXTIRQ_CFG_LEVELSENSE(irq);
174                 reg &= ~EXTIRQ_CFG_SENSE(irq);
175                 reg &= ~EXTIRQ_CFG_BOTHEDGE(irq);
176                 break;
177
178         case IRQ_TYPE_LEVEL_HIGH:
179                 reg |= EXTIRQ_CFG_LEVELSENSE(irq);
180                 reg |= EXTIRQ_CFG_SENSE(irq);
181                 break;
182
183         case IRQ_TYPE_LEVEL_LOW:
184                 reg |= EXTIRQ_CFG_LEVELSENSE(irq);
185                 reg &= ~EXTIRQ_CFG_SENSE(irq);
186                 break;
187
188         default:
189                 printk(KERN_ERR "bogus flow type combination given !\n");
190                 return -EINVAL;
191         }
192         bcm_perf_writel(reg, PERF_EXTIRQ_CFG_REG);
193
194         irqd_set_trigger_type(d, flow_type);
195         if (flow_type & (IRQ_TYPE_LEVEL_LOW | IRQ_TYPE_LEVEL_HIGH))
196                 __irq_set_handler_locked(d->irq, handle_level_irq);
197         else
198                 __irq_set_handler_locked(d->irq, handle_edge_irq);
199
200         return IRQ_SET_MASK_OK_NOCOPY;
201 }
202
203 static struct irq_chip bcm63xx_internal_irq_chip = {
204         .name           = "bcm63xx_ipic",
205         .irq_mask       = bcm63xx_internal_irq_mask,
206         .irq_unmask     = bcm63xx_internal_irq_unmask,
207 };
208
209 static struct irq_chip bcm63xx_external_irq_chip = {
210         .name           = "bcm63xx_epic",
211         .irq_startup    = bcm63xx_external_irq_startup,
212         .irq_shutdown   = bcm63xx_external_irq_shutdown,
213
214         .irq_ack        = bcm63xx_external_irq_clear,
215
216         .irq_mask       = bcm63xx_external_irq_mask,
217         .irq_unmask     = bcm63xx_external_irq_unmask,
218
219         .irq_set_type   = bcm63xx_external_irq_set_type,
220 };
221
222 static struct irqaction cpu_ip2_cascade_action = {
223         .handler        = no_action,
224         .name           = "cascade_ip2",
225 };
226
227 void __init arch_init_irq(void)
228 {
229         int i;
230
231         mips_cpu_irq_init();
232         for (i = IRQ_INTERNAL_BASE; i < NR_IRQS; ++i)
233                 set_irq_chip_and_handler(i, &bcm63xx_internal_irq_chip,
234                                          handle_level_irq);
235
236         for (i = IRQ_EXT_BASE; i < IRQ_EXT_BASE + 4; ++i)
237                 set_irq_chip_and_handler(i, &bcm63xx_external_irq_chip,
238                                          handle_edge_irq);
239
240         setup_irq(IRQ_MIPS_BASE + 2, &cpu_ip2_cascade_action);
241 }