[adm5120] add support for GPIO IRQs
[openwrt.git] / target / linux / adm5120 / files / arch / mips / adm5120 / irq.c
1 /*
2  *  $Id$
3  *
4  *  ADM5120 specific interrupt handlers
5  *
6  *  Copyright (C) 2007 Gabor Juhos <juhosg at openwrt.org>
7  *  Copyright (C) 2007 OpenWrt.org
8  *
9  *  This program is free software; you can redistribute it and/or
10  *  modify it under the terms of the GNU General Public License
11  *  as published by the Free Software Foundation; either version 2
12  *  of the License, or (at your option) any later version.
13  *
14  *  This program is distributed in the hope that it will be useful,
15  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
16  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  *  GNU General Public License for more details.
18  *
19  *  You should have received a copy of the GNU General Public License
20  *  along with this program; if not, write to the
21  *  Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
22  *  Boston, MA  02110-1301, USA.
23  *
24  */
25
26 #include <linux/init.h>
27 #include <linux/kernel.h>
28 #include <linux/version.h>
29 #include <linux/interrupt.h>
30 #include <linux/ioport.h>
31
32 #include <linux/io.h>
33 #include <asm/irq.h>
34 #include <asm/irq_cpu.h>
35 #include <asm/mipsregs.h>
36 #include <asm/bitops.h>
37
38 #include <adm5120_defs.h>
39 #include <adm5120_irq.h>
40
41 #define INTC_WRITE(reg, val)    __raw_writel((val), \
42         (void __iomem *)(KSEG1ADDR(ADM5120_INTC_BASE) + reg))
43
44 #define INTC_READ(reg)          __raw_readl(\
45         (void __iomem *)(KSEG1ADDR(ADM5120_INTC_BASE) + reg))
46
47 static void adm5120_intc_irq_unmask(unsigned int irq);
48 static void adm5120_intc_irq_mask(unsigned int irq);
49 static int  adm5120_intc_irq_set_type(unsigned int irq, unsigned int flow_type);
50
51 static struct irq_chip adm5120_intc_irq_chip = {
52         .name           = "INTC",
53         .unmask         = adm5120_intc_irq_unmask,
54         .mask           = adm5120_intc_irq_mask,
55         .mask_ack       = adm5120_intc_irq_mask,
56         .set_type       = adm5120_intc_irq_set_type
57 };
58
59 static struct irqaction adm5120_intc_irq_action = {
60         .handler        = no_action,
61         .name           = "cascade [INTC]"
62 };
63
64 static void adm5120_intc_irq_unmask(unsigned int irq)
65 {
66         irq -= ADM5120_INTC_IRQ_BASE;
67         INTC_WRITE(INTC_REG_IRQ_ENABLE, 1 << irq);
68 }
69
70 static void adm5120_intc_irq_mask(unsigned int irq)
71 {
72         irq -= ADM5120_INTC_IRQ_BASE;
73         INTC_WRITE(INTC_REG_IRQ_DISABLE, 1 << irq);
74 }
75
76 static int adm5120_intc_irq_set_type(unsigned int irq, unsigned int flow_type)
77 {
78         /* TODO: not yet tested */
79
80         unsigned int sense;
81         unsigned long mode;
82         int err = 0;
83
84         sense = flow_type & (IRQ_TYPE_SENSE_MASK);
85         switch (sense) {
86         case IRQ_TYPE_NONE:
87         case IRQ_TYPE_LEVEL_HIGH:
88                 break;
89         case IRQ_TYPE_LEVEL_LOW:
90                 switch (irq) {
91                 case ADM5120_IRQ_GPIO2:
92                 case ADM5120_IRQ_GPIO4:
93                         break;
94                 default:
95                         err = -EINVAL;
96                         break;
97                 }
98                 break;
99         default:
100                 err = -EINVAL;
101                 break;
102         }
103
104         if (err)
105                 return err;
106
107         switch (irq) {
108         case ADM5120_IRQ_GPIO2:
109         case ADM5120_IRQ_GPIO4:
110                 mode = INTC_READ(INTC_REG_INT_MODE);
111                 if (sense == IRQ_TYPE_LEVEL_LOW)
112                         mode |= (1 << (irq-ADM5120_INTC_IRQ_BASE));
113                 else
114                         mode &= (1 << (irq-ADM5120_INTC_IRQ_BASE));
115
116                 INTC_WRITE(INTC_REG_INT_MODE, mode);
117                 /* fallthrough */
118         default:
119                 irq_desc[irq].status &= ~IRQ_TYPE_SENSE_MASK;
120                 irq_desc[irq].status |= sense;
121                 break;
122         }
123
124         return 0;
125 }
126
127 static void adm5120_intc_irq_dispatch(void)
128 {
129         unsigned long status;
130         int irq;
131
132         /* dispatch only one IRQ at a time */
133         status = INTC_READ(INTC_REG_IRQ_STATUS) & INTC_INT_ALL;
134
135         if (status) {
136                 irq = ADM5120_INTC_IRQ_BASE+fls(status)-1;
137                 do_IRQ(irq);
138         } else
139                 spurious_interrupt();
140 }
141
142 asmlinkage void plat_irq_dispatch(void)
143 {
144         unsigned long pending;
145
146         pending = read_c0_status() & read_c0_cause() & ST0_IM;
147
148         if (pending & STATUSF_IP7)
149                 do_IRQ(ADM5120_IRQ_COUNTER);
150         else if (pending & STATUSF_IP2)
151                 adm5120_intc_irq_dispatch();
152         else
153                 spurious_interrupt();
154 }
155
156 #define INTC_IRQ_STATUS (IRQ_LEVEL | IRQ_TYPE_LEVEL_HIGH | IRQ_DISABLED)
157
158 static void __init adm5120_intc_irq_init(int base)
159 {
160         int i;
161
162         /* disable all interrupts */
163         INTC_WRITE(INTC_REG_IRQ_DISABLE, INTC_INT_ALL);
164
165         /* setup all interrupts to generate IRQ instead of FIQ */
166         INTC_WRITE(INTC_REG_INT_MODE, 0);
167
168         /* set active level for all external interrupts to HIGH */
169         INTC_WRITE(INTC_REG_INT_LEVEL, 0);
170
171         /* disable usage of the TEST_SOURCE register */
172         INTC_WRITE(INTC_REG_IRQ_SOURCE_SELECT, 0);
173
174         for (i = ADM5120_INTC_IRQ_BASE;
175                 i <= ADM5120_INTC_IRQ_BASE+INTC_IRQ_LAST;
176                 i++) {
177                 irq_desc[i].status = INTC_IRQ_STATUS;
178                 set_irq_chip_and_handler(i, &adm5120_intc_irq_chip,
179                         handle_level_irq);
180         }
181
182         setup_irq(ADM5120_IRQ_INTC, &adm5120_intc_irq_action);
183 }
184
185 void __init arch_init_irq(void) {
186         mips_cpu_irq_init();
187         adm5120_intc_irq_init(ADM5120_INTC_IRQ_BASE);
188 }