diff options
author | Heiko Stuebner <heiko@sntech.de> | 2013-04-04 01:55:10 -0400 |
---|---|---|
committer | Kukjin Kim <kgene.kim@samsung.com> | 2013-04-04 01:56:30 -0400 |
commit | f0774d41da0e607b70e54ecc50aeb6684f54c2b1 (patch) | |
tree | dc188d9b2d30ca34e3ef1d82f347c35062dc3a38 | |
parent | f5a25524508e68ac670b28db9112d3962dca4703 (diff) |
irqchip: s3c24xx: add devicetree support
Add the necessary code to initialize the interrupt controller
thru devicetree data using the irqchip infrastructure.
Signed-off-by: Heiko Stuebner <heiko@sntech.de>
Acked-by: Arnd Bergmann <arnd@arndb.de>
Acked-by: Rob Herring <rob.herring@calxeda.com>
Signed-off-by: Kukjin Kim <kgene.kim@samsung.com>
-rw-r--r-- | Documentation/devicetree/bindings/interrupt-controller/samsung,s3c24xx-irq.txt | 53 | ||||
-rw-r--r-- | drivers/irqchip/irq-s3c24xx.c | 231 |
2 files changed, 278 insertions, 6 deletions
diff --git a/Documentation/devicetree/bindings/interrupt-controller/samsung,s3c24xx-irq.txt b/Documentation/devicetree/bindings/interrupt-controller/samsung,s3c24xx-irq.txt new file mode 100644 index 000000000000..c54c5a9a2a90 --- /dev/null +++ b/Documentation/devicetree/bindings/interrupt-controller/samsung,s3c24xx-irq.txt | |||
@@ -0,0 +1,53 @@ | |||
1 | Samsung S3C24XX Interrupt Controllers | ||
2 | |||
3 | The S3C24XX SoCs contain a custom set of interrupt controllers providing a | ||
4 | varying number of interrupt sources. The set consists of a main- and sub- | ||
5 | controller and on newer SoCs even a second main controller. | ||
6 | |||
7 | Required properties: | ||
8 | - compatible: Compatible property value should be "samsung,s3c2410-irq" | ||
9 | for machines before s3c2416 and "samsung,s3c2416-irq" for s3c2416 and later. | ||
10 | |||
11 | - reg: Physical base address of the controller and length of memory mapped | ||
12 | region. | ||
13 | |||
14 | - interrupt-controller : Identifies the node as an interrupt controller | ||
15 | |||
16 | - #interrupt-cells : Specifies the number of cells needed to encode an | ||
17 | interrupt source. The value shall be 4 and interrupt descriptor shall | ||
18 | have the following format: | ||
19 | <ctrl_num parent_irq ctrl_irq type> | ||
20 | |||
21 | ctrl_num contains the controller to use: | ||
22 | - 0 ... main controller | ||
23 | - 1 ... sub controller | ||
24 | - 2 ... second main controller on s3c2416 and s3c2450 | ||
25 | parent_irq contains the parent bit in the main controller and will be | ||
26 | ignored in main controllers | ||
27 | ctrl_irq contains the interrupt bit of the controller | ||
28 | type contains the trigger type to use | ||
29 | |||
30 | Example: | ||
31 | |||
32 | interrupt-controller@4a000000 { | ||
33 | compatible = "samsung,s3c2410-irq"; | ||
34 | reg = <0x4a000000 0x100>; | ||
35 | interrupt-controller; | ||
36 | #interrupt-cells=<4>; | ||
37 | }; | ||
38 | |||
39 | [...] | ||
40 | |||
41 | serial@50000000 { | ||
42 | compatible = "samsung,s3c2410-uart"; | ||
43 | reg = <0x50000000 0x4000>; | ||
44 | interrupt-parent = <&subintc>; | ||
45 | interrupts = <1 28 0 4>, <1 28 1 4>; | ||
46 | }; | ||
47 | |||
48 | rtc@57000000 { | ||
49 | compatible = "samsung,s3c2410-rtc"; | ||
50 | reg = <0x57000000 0x100>; | ||
51 | interrupt-parent = <&intc>; | ||
52 | interrupts = <0 30 0 3>, <0 8 0 3>; | ||
53 | }; | ||
diff --git a/drivers/irqchip/irq-s3c24xx.c b/drivers/irqchip/irq-s3c24xx.c index 02b82db086d8..5e40b3424df8 100644 --- a/drivers/irqchip/irq-s3c24xx.c +++ b/drivers/irqchip/irq-s3c24xx.c | |||
@@ -25,6 +25,9 @@ | |||
25 | #include <linux/ioport.h> | 25 | #include <linux/ioport.h> |
26 | #include <linux/device.h> | 26 | #include <linux/device.h> |
27 | #include <linux/irqdomain.h> | 27 | #include <linux/irqdomain.h> |
28 | #include <linux/of.h> | ||
29 | #include <linux/of_irq.h> | ||
30 | #include <linux/of_address.h> | ||
28 | 31 | ||
29 | #include <asm/exception.h> | 32 | #include <asm/exception.h> |
30 | #include <asm/mach/irq.h> | 33 | #include <asm/mach/irq.h> |
@@ -36,6 +39,8 @@ | |||
36 | #include <plat/regs-irqtype.h> | 39 | #include <plat/regs-irqtype.h> |
37 | #include <plat/pm.h> | 40 | #include <plat/pm.h> |
38 | 41 | ||
42 | #include "irqchip.h" | ||
43 | |||
39 | #define S3C_IRQTYPE_NONE 0 | 44 | #define S3C_IRQTYPE_NONE 0 |
40 | #define S3C_IRQTYPE_EINT 1 | 45 | #define S3C_IRQTYPE_EINT 1 |
41 | #define S3C_IRQTYPE_EDGE 2 | 46 | #define S3C_IRQTYPE_EDGE 2 |
@@ -94,7 +99,10 @@ static void s3c_irq_mask(struct irq_data *data) | |||
94 | if (parent_intc) { | 99 | if (parent_intc) { |
95 | parent_data = &parent_intc->irqs[irq_data->parent_irq]; | 100 | parent_data = &parent_intc->irqs[irq_data->parent_irq]; |
96 | 101 | ||
97 | /* check to see if we need to mask the parent IRQ */ | 102 | /* check to see if we need to mask the parent IRQ |
103 | * The parent_irq is always in main_intc, so the hwirq | ||
104 | * for find_mapping does not need an offset in any case. | ||
105 | */ | ||
98 | if ((mask & parent_data->sub_bits) == parent_data->sub_bits) { | 106 | if ((mask & parent_data->sub_bits) == parent_data->sub_bits) { |
99 | irqno = irq_find_mapping(parent_intc->domain, | 107 | irqno = irq_find_mapping(parent_intc->domain, |
100 | irq_data->parent_irq); | 108 | irq_data->parent_irq); |
@@ -294,10 +302,18 @@ static void s3c_irq_demux(unsigned int irq, struct irq_desc *desc) | |||
294 | { | 302 | { |
295 | struct irq_chip *chip = irq_desc_get_chip(desc); | 303 | struct irq_chip *chip = irq_desc_get_chip(desc); |
296 | struct s3c_irq_data *irq_data = irq_desc_get_chip_data(desc); | 304 | struct s3c_irq_data *irq_data = irq_desc_get_chip_data(desc); |
305 | struct s3c_irq_intc *intc = irq_data->intc; | ||
297 | struct s3c_irq_intc *sub_intc = irq_data->sub_intc; | 306 | struct s3c_irq_intc *sub_intc = irq_data->sub_intc; |
298 | unsigned long src; | 307 | unsigned long src; |
299 | unsigned long msk; | 308 | unsigned long msk; |
300 | unsigned int n; | 309 | unsigned int n; |
310 | unsigned int offset; | ||
311 | |||
312 | /* we're using individual domains for the non-dt case | ||
313 | * and one big domain for the dt case where the subintc | ||
314 | * starts at hwirq number 32. | ||
315 | */ | ||
316 | offset = (intc->domain->of_node) ? 32 : 0; | ||
301 | 317 | ||
302 | chained_irq_enter(chip, desc); | 318 | chained_irq_enter(chip, desc); |
303 | 319 | ||
@@ -310,14 +326,15 @@ static void s3c_irq_demux(unsigned int irq, struct irq_desc *desc) | |||
310 | while (src) { | 326 | while (src) { |
311 | n = __ffs(src); | 327 | n = __ffs(src); |
312 | src &= ~(1 << n); | 328 | src &= ~(1 << n); |
313 | generic_handle_irq(irq_find_mapping(sub_intc->domain, n)); | 329 | irq = irq_find_mapping(sub_intc->domain, offset + n); |
330 | generic_handle_irq(irq); | ||
314 | } | 331 | } |
315 | 332 | ||
316 | chained_irq_exit(chip, desc); | 333 | chained_irq_exit(chip, desc); |
317 | } | 334 | } |
318 | 335 | ||
319 | static inline int s3c24xx_handle_intc(struct s3c_irq_intc *intc, | 336 | static inline int s3c24xx_handle_intc(struct s3c_irq_intc *intc, |
320 | struct pt_regs *regs) | 337 | struct pt_regs *regs, int intc_offset) |
321 | { | 338 | { |
322 | int pnd; | 339 | int pnd; |
323 | int offset; | 340 | int offset; |
@@ -327,6 +344,10 @@ static inline int s3c24xx_handle_intc(struct s3c_irq_intc *intc, | |||
327 | if (!pnd) | 344 | if (!pnd) |
328 | return false; | 345 | return false; |
329 | 346 | ||
347 | /* non-dt machines use individual domains */ | ||
348 | if (!intc->domain->of_node) | ||
349 | intc_offset = 0; | ||
350 | |||
330 | /* We have a problem that the INTOFFSET register does not always | 351 | /* We have a problem that the INTOFFSET register does not always |
331 | * show one interrupt. Occasionally we get two interrupts through | 352 | * show one interrupt. Occasionally we get two interrupts through |
332 | * the prioritiser, and this causes the INTOFFSET register to show | 353 | * the prioritiser, and this causes the INTOFFSET register to show |
@@ -343,7 +364,7 @@ static inline int s3c24xx_handle_intc(struct s3c_irq_intc *intc, | |||
343 | if (!(pnd & (1 << offset))) | 364 | if (!(pnd & (1 << offset))) |
344 | offset = __ffs(pnd); | 365 | offset = __ffs(pnd); |
345 | 366 | ||
346 | irq = irq_find_mapping(intc->domain, offset); | 367 | irq = irq_find_mapping(intc->domain, intc_offset + offset); |
347 | handle_IRQ(irq, regs); | 368 | handle_IRQ(irq, regs); |
348 | return true; | 369 | return true; |
349 | } | 370 | } |
@@ -352,11 +373,11 @@ asmlinkage void __exception_irq_entry s3c24xx_handle_irq(struct pt_regs *regs) | |||
352 | { | 373 | { |
353 | do { | 374 | do { |
354 | if (likely(s3c_intc[0])) | 375 | if (likely(s3c_intc[0])) |
355 | if (s3c24xx_handle_intc(s3c_intc[0], regs)) | 376 | if (s3c24xx_handle_intc(s3c_intc[0], regs, 0)) |
356 | continue; | 377 | continue; |
357 | 378 | ||
358 | if (s3c_intc[2]) | 379 | if (s3c_intc[2]) |
359 | if (s3c24xx_handle_intc(s3c_intc[2], regs)) | 380 | if (s3c24xx_handle_intc(s3c_intc[2], regs, 64)) |
360 | continue; | 381 | continue; |
361 | 382 | ||
362 | break; | 383 | break; |
@@ -1134,3 +1155,201 @@ void __init s3c2443_init_irq(void) | |||
1134 | s3c_intc[0], 0x4a000018); | 1155 | s3c_intc[0], 0x4a000018); |
1135 | } | 1156 | } |
1136 | #endif | 1157 | #endif |
1158 | |||
1159 | #ifdef CONFIG_OF | ||
1160 | static int s3c24xx_irq_map_of(struct irq_domain *h, unsigned int virq, | ||
1161 | irq_hw_number_t hw) | ||
1162 | { | ||
1163 | unsigned int ctrl_num = hw / 32; | ||
1164 | unsigned int intc_hw = hw % 32; | ||
1165 | struct s3c_irq_intc *intc = s3c_intc[ctrl_num]; | ||
1166 | struct s3c_irq_intc *parent_intc = intc->parent; | ||
1167 | struct s3c_irq_data *irq_data = &intc->irqs[intc_hw]; | ||
1168 | |||
1169 | /* attach controller pointer to irq_data */ | ||
1170 | irq_data->intc = intc; | ||
1171 | irq_data->offset = intc_hw; | ||
1172 | |||
1173 | if (!parent_intc) | ||
1174 | irq_set_chip_and_handler(virq, &s3c_irq_chip, handle_edge_irq); | ||
1175 | else | ||
1176 | irq_set_chip_and_handler(virq, &s3c_irq_level_chip, | ||
1177 | handle_edge_irq); | ||
1178 | |||
1179 | irq_set_chip_data(virq, irq_data); | ||
1180 | |||
1181 | set_irq_flags(virq, IRQF_VALID); | ||
1182 | |||
1183 | return 0; | ||
1184 | } | ||
1185 | |||
1186 | /* Translate our of irq notation | ||
1187 | * format: <ctrl_num ctrl_irq parent_irq type> | ||
1188 | */ | ||
1189 | static int s3c24xx_irq_xlate_of(struct irq_domain *d, struct device_node *n, | ||
1190 | const u32 *intspec, unsigned int intsize, | ||
1191 | irq_hw_number_t *out_hwirq, unsigned int *out_type) | ||
1192 | { | ||
1193 | struct s3c_irq_intc *intc; | ||
1194 | struct s3c_irq_intc *parent_intc; | ||
1195 | struct s3c_irq_data *irq_data; | ||
1196 | struct s3c_irq_data *parent_irq_data; | ||
1197 | int irqno; | ||
1198 | |||
1199 | if (WARN_ON(intsize < 4)) | ||
1200 | return -EINVAL; | ||
1201 | |||
1202 | if (intspec[0] > 2 || !s3c_intc[intspec[0]]) { | ||
1203 | pr_err("controller number %d invalid\n", intspec[0]); | ||
1204 | return -EINVAL; | ||
1205 | } | ||
1206 | intc = s3c_intc[intspec[0]]; | ||
1207 | |||
1208 | *out_hwirq = intspec[0] * 32 + intspec[2]; | ||
1209 | *out_type = intspec[3] & IRQ_TYPE_SENSE_MASK; | ||
1210 | |||
1211 | parent_intc = intc->parent; | ||
1212 | if (parent_intc) { | ||
1213 | irq_data = &intc->irqs[intspec[2]]; | ||
1214 | irq_data->parent_irq = intspec[1]; | ||
1215 | parent_irq_data = &parent_intc->irqs[irq_data->parent_irq]; | ||
1216 | parent_irq_data->sub_intc = intc; | ||
1217 | parent_irq_data->sub_bits |= (1UL << intspec[2]); | ||
1218 | |||
1219 | /* parent_intc is always s3c_intc[0], so no offset */ | ||
1220 | irqno = irq_create_mapping(parent_intc->domain, intspec[1]); | ||
1221 | if (irqno < 0) { | ||
1222 | pr_err("irq: could not map parent interrupt\n"); | ||
1223 | return irqno; | ||
1224 | } | ||
1225 | |||
1226 | irq_set_chained_handler(irqno, s3c_irq_demux); | ||
1227 | } | ||
1228 | |||
1229 | return 0; | ||
1230 | } | ||
1231 | |||
1232 | static struct irq_domain_ops s3c24xx_irq_ops_of = { | ||
1233 | .map = s3c24xx_irq_map_of, | ||
1234 | .xlate = s3c24xx_irq_xlate_of, | ||
1235 | }; | ||
1236 | |||
1237 | struct s3c24xx_irq_of_ctrl { | ||
1238 | char *name; | ||
1239 | unsigned long offset; | ||
1240 | struct s3c_irq_intc **handle; | ||
1241 | struct s3c_irq_intc **parent; | ||
1242 | struct irq_domain_ops *ops; | ||
1243 | }; | ||
1244 | |||
1245 | static int __init s3c_init_intc_of(struct device_node *np, | ||
1246 | struct device_node *interrupt_parent, | ||
1247 | struct s3c24xx_irq_of_ctrl *s3c_ctrl, int num_ctrl) | ||
1248 | { | ||
1249 | struct s3c_irq_intc *intc; | ||
1250 | struct s3c24xx_irq_of_ctrl *ctrl; | ||
1251 | struct irq_domain *domain; | ||
1252 | void __iomem *reg_base; | ||
1253 | int i; | ||
1254 | |||
1255 | reg_base = of_iomap(np, 0); | ||
1256 | if (!reg_base) { | ||
1257 | pr_err("irq-s3c24xx: could not map irq registers\n"); | ||
1258 | return -EINVAL; | ||
1259 | } | ||
1260 | |||
1261 | domain = irq_domain_add_linear(np, num_ctrl * 32, | ||
1262 | &s3c24xx_irq_ops_of, NULL); | ||
1263 | if (!domain) { | ||
1264 | pr_err("irq: could not create irq-domain\n"); | ||
1265 | return -EINVAL; | ||
1266 | } | ||
1267 | |||
1268 | for (i = 0; i < num_ctrl; i++) { | ||
1269 | ctrl = &s3c_ctrl[i]; | ||
1270 | |||
1271 | pr_debug("irq: found controller %s\n", ctrl->name); | ||
1272 | |||
1273 | intc = kzalloc(sizeof(struct s3c_irq_intc), GFP_KERNEL); | ||
1274 | if (!intc) | ||
1275 | return -ENOMEM; | ||
1276 | |||
1277 | intc->domain = domain; | ||
1278 | intc->irqs = kzalloc(sizeof(struct s3c_irq_data) * 32, | ||
1279 | GFP_KERNEL); | ||
1280 | if (!intc->irqs) { | ||
1281 | kfree(intc); | ||
1282 | return -ENOMEM; | ||
1283 | } | ||
1284 | |||
1285 | if (ctrl->parent) { | ||
1286 | intc->reg_pending = reg_base + ctrl->offset; | ||
1287 | intc->reg_mask = reg_base + ctrl->offset + 0x4; | ||
1288 | |||
1289 | if (*(ctrl->parent)) { | ||
1290 | intc->parent = *(ctrl->parent); | ||
1291 | } else { | ||
1292 | pr_warn("irq: parent of %s missing\n", | ||
1293 | ctrl->name); | ||
1294 | kfree(intc->irqs); | ||
1295 | kfree(intc); | ||
1296 | continue; | ||
1297 | } | ||
1298 | } else { | ||
1299 | intc->reg_pending = reg_base + ctrl->offset; | ||
1300 | intc->reg_mask = reg_base + ctrl->offset + 0x08; | ||
1301 | intc->reg_intpnd = reg_base + ctrl->offset + 0x10; | ||
1302 | } | ||
1303 | |||
1304 | s3c24xx_clear_intc(intc); | ||
1305 | s3c_intc[i] = intc; | ||
1306 | } | ||
1307 | |||
1308 | set_handle_irq(s3c24xx_handle_irq); | ||
1309 | |||
1310 | return 0; | ||
1311 | } | ||
1312 | |||
1313 | static struct s3c24xx_irq_of_ctrl s3c2410_ctrl[] = { | ||
1314 | { | ||
1315 | .name = "intc", | ||
1316 | .offset = 0, | ||
1317 | }, { | ||
1318 | .name = "subintc", | ||
1319 | .offset = 0x18, | ||
1320 | .parent = &s3c_intc[0], | ||
1321 | } | ||
1322 | }; | ||
1323 | |||
1324 | int __init s3c2410_init_intc_of(struct device_node *np, | ||
1325 | struct device_node *interrupt_parent, | ||
1326 | struct s3c24xx_irq_of_ctrl *ctrl, int num_ctrl) | ||
1327 | { | ||
1328 | return s3c_init_intc_of(np, interrupt_parent, | ||
1329 | s3c2410_ctrl, ARRAY_SIZE(s3c2410_ctrl)); | ||
1330 | } | ||
1331 | IRQCHIP_DECLARE(s3c2410_irq, "samsung,s3c2410-irq", s3c2410_init_intc_of); | ||
1332 | |||
1333 | static struct s3c24xx_irq_of_ctrl s3c2416_ctrl[] = { | ||
1334 | { | ||
1335 | .name = "intc", | ||
1336 | .offset = 0, | ||
1337 | }, { | ||
1338 | .name = "subintc", | ||
1339 | .offset = 0x18, | ||
1340 | .parent = &s3c_intc[0], | ||
1341 | }, { | ||
1342 | .name = "intc2", | ||
1343 | .offset = 0x40, | ||
1344 | } | ||
1345 | }; | ||
1346 | |||
1347 | int __init s3c2416_init_intc_of(struct device_node *np, | ||
1348 | struct device_node *interrupt_parent, | ||
1349 | struct s3c24xx_irq_of_ctrl *ctrl, int num_ctrl) | ||
1350 | { | ||
1351 | return s3c_init_intc_of(np, interrupt_parent, | ||
1352 | s3c2416_ctrl, ARRAY_SIZE(s3c2416_ctrl)); | ||
1353 | } | ||
1354 | IRQCHIP_DECLARE(s3c2416_irq, "samsung,s3c2416-irq", s3c2416_init_intc_of); | ||
1355 | #endif | ||