Merge branch 'next' of git://git.kernel.org/pub/scm/linux/kernel/git/djbw/async_tx
[pandora-kernel.git] / arch / arm / mach-s5pc100 / irq-gpio.c
1 /*
2  * arch/arm/mach-s5pc100/irq-gpio.c
3  *
4  * Copyright (C) 2009 Samsung Electronics
5  *
6  * S5PC100 - Interrupt handling for IRQ_GPIO${group}(x)
7  *
8  * This program is free software; you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License version 2 as
10  * published by the Free Software Foundation.
11  */
12
13 #include <linux/kernel.h>
14 #include <linux/interrupt.h>
15 #include <linux/irq.h>
16 #include <linux/io.h>
17 #include <linux/gpio.h>
18
19 #include <mach/map.h>
20 #include <plat/gpio-cfg.h>
21
22 #define S5P_GPIOREG(x)          (S5P_VA_GPIO + (x))
23
24 #define CON_OFFSET                      0x700
25 #define MASK_OFFSET                     0x900
26 #define PEND_OFFSET                     0xA00
27 #define CON_OFFSET_2                    0xE00
28 #define MASK_OFFSET_2                   0xF00
29 #define PEND_OFFSET_2                   0xF40
30
31 #define GPIOINT_LEVEL_LOW               0x0
32 #define GPIOINT_LEVEL_HIGH              0x1
33 #define GPIOINT_EDGE_FALLING            0x2
34 #define GPIOINT_EDGE_RISING             0x3
35 #define GPIOINT_EDGE_BOTH               0x4
36
37 static int group_to_con_offset(int group)
38 {
39         return group << 2;
40 }
41
42 static int group_to_mask_offset(int group)
43 {
44         return group << 2;
45 }
46
47 static int group_to_pend_offset(int group)
48 {
49         return group << 2;
50 }
51
52 static int s5pc100_get_start(unsigned int group)
53 {
54         switch (group) {
55         case 0: return S5PC100_GPIO_A0_START;
56         case 1: return S5PC100_GPIO_A1_START;
57         case 2: return S5PC100_GPIO_B_START;
58         case 3: return S5PC100_GPIO_C_START;
59         case 4: return S5PC100_GPIO_D_START;
60         case 5: return S5PC100_GPIO_E0_START;
61         case 6: return S5PC100_GPIO_E1_START;
62         case 7: return S5PC100_GPIO_F0_START;
63         case 8: return S5PC100_GPIO_F1_START;
64         case 9: return S5PC100_GPIO_F2_START;
65         case 10: return S5PC100_GPIO_F3_START;
66         case 11: return S5PC100_GPIO_G0_START;
67         case 12: return S5PC100_GPIO_G1_START;
68         case 13: return S5PC100_GPIO_G2_START;
69         case 14: return S5PC100_GPIO_G3_START;
70         case 15: return S5PC100_GPIO_I_START;
71         case 16: return S5PC100_GPIO_J0_START;
72         case 17: return S5PC100_GPIO_J1_START;
73         case 18: return S5PC100_GPIO_J2_START;
74         case 19: return S5PC100_GPIO_J3_START;
75         case 20: return S5PC100_GPIO_J4_START;
76         default:
77                 BUG();
78         }
79
80         return -EINVAL;
81 }
82
83 static int s5pc100_get_group(unsigned int irq)
84 {
85         irq -= S3C_IRQ_GPIO(0);
86
87         switch (irq) {
88         case S5PC100_GPIO_A0_START ... S5PC100_GPIO_A1_START - 1:
89                 return 0;
90         case S5PC100_GPIO_A1_START ... S5PC100_GPIO_B_START - 1:
91                 return 1;
92         case S5PC100_GPIO_B_START ... S5PC100_GPIO_C_START - 1:
93                 return 2;
94         case S5PC100_GPIO_C_START ... S5PC100_GPIO_D_START - 1:
95                 return 3;
96         case S5PC100_GPIO_D_START ... S5PC100_GPIO_E0_START - 1:
97                 return 4;
98         case S5PC100_GPIO_E0_START ... S5PC100_GPIO_E1_START - 1:
99                 return 5;
100         case S5PC100_GPIO_E1_START ... S5PC100_GPIO_F0_START - 1:
101                 return 6;
102         case S5PC100_GPIO_F0_START ... S5PC100_GPIO_F1_START - 1:
103                 return 7;
104         case S5PC100_GPIO_F1_START ... S5PC100_GPIO_F2_START - 1:
105                 return 8;
106         case S5PC100_GPIO_F2_START ... S5PC100_GPIO_F3_START - 1:
107                 return 9;
108         case S5PC100_GPIO_F3_START ... S5PC100_GPIO_G0_START - 1:
109                 return 10;
110         case S5PC100_GPIO_G0_START ... S5PC100_GPIO_G1_START - 1:
111                 return 11;
112         case S5PC100_GPIO_G1_START ... S5PC100_GPIO_G2_START - 1:
113                 return 12;
114         case S5PC100_GPIO_G2_START ... S5PC100_GPIO_G3_START - 1:
115                 return 13;
116         case S5PC100_GPIO_G3_START ... S5PC100_GPIO_H0_START - 1:
117                 return 14;
118         case S5PC100_GPIO_I_START ... S5PC100_GPIO_J0_START - 1:
119                 return 15;
120         case S5PC100_GPIO_J0_START ... S5PC100_GPIO_J1_START - 1:
121                 return 16;
122         case S5PC100_GPIO_J1_START ... S5PC100_GPIO_J2_START - 1:
123                 return 17;
124         case S5PC100_GPIO_J2_START ... S5PC100_GPIO_J3_START - 1:
125                 return 18;
126         case S5PC100_GPIO_J3_START ... S5PC100_GPIO_J4_START - 1:
127                 return 19;
128         case S5PC100_GPIO_J4_START ... S5PC100_GPIO_K0_START - 1:
129                 return 20;
130         default:
131                 BUG();
132         }
133
134         return -EINVAL;
135 }
136
137 static int s5pc100_get_offset(unsigned int irq)
138 {
139         struct gpio_chip *chip = get_irq_data(irq);
140         return irq - S3C_IRQ_GPIO(chip->base);
141 }
142
143 static void s5pc100_gpioint_ack(unsigned int irq)
144 {
145         int group, offset, pend_offset;
146         unsigned int value;
147
148         group = s5pc100_get_group(irq);
149         offset = s5pc100_get_offset(irq);
150         pend_offset = group_to_pend_offset(group);
151
152         value = __raw_readl(S5P_GPIOREG(PEND_OFFSET) + pend_offset);
153         value |= 1 << offset;
154         __raw_writel(value, S5P_GPIOREG(PEND_OFFSET) + pend_offset);
155 }
156
157 static void s5pc100_gpioint_mask(unsigned int irq)
158 {
159         int group, offset, mask_offset;
160         unsigned int value;
161
162         group = s5pc100_get_group(irq);
163         offset = s5pc100_get_offset(irq);
164         mask_offset = group_to_mask_offset(group);
165
166         value = __raw_readl(S5P_GPIOREG(MASK_OFFSET) + mask_offset);
167         value |= 1 << offset;
168         __raw_writel(value, S5P_GPIOREG(MASK_OFFSET) + mask_offset);
169 }
170
171 static void s5pc100_gpioint_unmask(unsigned int irq)
172 {
173         int group, offset, mask_offset;
174         unsigned int value;
175
176         group = s5pc100_get_group(irq);
177         offset = s5pc100_get_offset(irq);
178         mask_offset = group_to_mask_offset(group);
179
180         value = __raw_readl(S5P_GPIOREG(MASK_OFFSET) + mask_offset);
181         value &= ~(1 << offset);
182         __raw_writel(value, S5P_GPIOREG(MASK_OFFSET) + mask_offset);
183 }
184
185 static void s5pc100_gpioint_mask_ack(unsigned int irq)
186 {
187         s5pc100_gpioint_mask(irq);
188         s5pc100_gpioint_ack(irq);
189 }
190
191 static int s5pc100_gpioint_set_type(unsigned int irq, unsigned int type)
192 {
193         int group, offset, con_offset;
194         unsigned int value;
195
196         group = s5pc100_get_group(irq);
197         offset = s5pc100_get_offset(irq);
198         con_offset = group_to_con_offset(group);
199
200         switch (type) {
201         case IRQ_TYPE_NONE:
202                 printk(KERN_WARNING "No irq type\n");
203                 return -EINVAL;
204         case IRQ_TYPE_EDGE_RISING:
205                 type = GPIOINT_EDGE_RISING;
206                 break;
207         case IRQ_TYPE_EDGE_FALLING:
208                 type = GPIOINT_EDGE_FALLING;
209                 break;
210         case IRQ_TYPE_EDGE_BOTH:
211                 type = GPIOINT_EDGE_BOTH;
212                 break;
213         case IRQ_TYPE_LEVEL_HIGH:
214                 type = GPIOINT_LEVEL_HIGH;
215                 break;
216         case IRQ_TYPE_LEVEL_LOW:
217                 type = GPIOINT_LEVEL_LOW;
218                 break;
219         default:
220                 BUG();
221         }
222
223
224         value = __raw_readl(S5P_GPIOREG(CON_OFFSET) + con_offset);
225         value &= ~(0xf << (offset * 0x4));
226         value |= (type << (offset * 0x4));
227         __raw_writel(value, S5P_GPIOREG(CON_OFFSET) + con_offset);
228
229         return 0;
230 }
231
232 struct irq_chip s5pc100_gpioint = {
233         .name           = "GPIO",
234         .ack            = s5pc100_gpioint_ack,
235         .mask           = s5pc100_gpioint_mask,
236         .mask_ack       = s5pc100_gpioint_mask_ack,
237         .unmask         = s5pc100_gpioint_unmask,
238         .set_type       = s5pc100_gpioint_set_type,
239 };
240
241 void s5pc100_irq_gpioint_handler(unsigned int irq, struct irq_desc *desc)
242 {
243         int group, offset, pend_offset, mask_offset;
244         int real_irq, group_end;
245         unsigned int pend, mask;
246
247         group_end = 21;
248
249         for (group = 0; group < group_end; group++) {
250                 pend_offset = group_to_pend_offset(group);
251                 pend = __raw_readl(S5P_GPIOREG(PEND_OFFSET) + pend_offset);
252                 if (!pend)
253                         continue;
254
255                 mask_offset = group_to_mask_offset(group);
256                 mask = __raw_readl(S5P_GPIOREG(MASK_OFFSET) + mask_offset);
257                 pend &= ~mask;
258
259                 for (offset = 0; offset < 8; offset++) {
260                         if (pend & (1 << offset)) {
261                                 real_irq = s5pc100_get_start(group) + offset;
262                                 generic_handle_irq(S3C_IRQ_GPIO(real_irq));
263                         }
264                 }
265         }
266 }