diff options
author | Andrew Lunn <andrew@lunn.ch> | 2013-11-07 05:35:29 -0500 |
---|---|---|
committer | Jason Cooper <jason@lakedaemon.net> | 2013-11-26 10:04:53 -0500 |
commit | 40b367d95fb3d60fc1edb9ba8f6ef52272e48936 (patch) | |
tree | 7d4060a9c5001fa7c4d31e189ade110411ce05d5 | |
parent | 6ce4eac1f600b34f2f7f58f9cd8f0503d79e42ae (diff) |
irqchip: irq-dove: Add PMU interrupt controller.
Dove has a Power Management Unit with its own interrupt
controller. This is chained on the main interrupt controller. Add a
driver, making use of generic chip where possible.
Signed-off-by: Andrew Lunn <andrew@lunn.ch>
Tested-by: Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com>
cc: devicetree@vger.kernel.org
cc: pawel.moll@arm.com
cc: mark.rutland@arm.com
cc: swarren@wwwdotorg.org
cc: ian.campbell@citrix.com
Signed-off-by: Jason Cooper <jason@lakedaemon.net>
-rw-r--r-- | Documentation/devicetree/bindings/interrupt-controller/marvell,dove-pmu-intc.txt | 17 | ||||
-rw-r--r-- | drivers/irqchip/Makefile | 1 | ||||
-rw-r--r-- | drivers/irqchip/irq-dove.c | 126 |
3 files changed, 144 insertions, 0 deletions
diff --git a/Documentation/devicetree/bindings/interrupt-controller/marvell,dove-pmu-intc.txt b/Documentation/devicetree/bindings/interrupt-controller/marvell,dove-pmu-intc.txt new file mode 100644 index 000000000000..1feb5825d372 --- /dev/null +++ b/Documentation/devicetree/bindings/interrupt-controller/marvell,dove-pmu-intc.txt | |||
@@ -0,0 +1,17 @@ | |||
1 | Marvell Dove Power Management Unit interrupt controller | ||
2 | |||
3 | Required properties: | ||
4 | - compatible: shall be "marvell,dove-pmu-intc" | ||
5 | - reg: base address of PMU interrupt registers starting with CAUSE register | ||
6 | - interrupts: PMU interrupt of the main interrupt controller | ||
7 | - interrupt-controller: identifies the node as an interrupt controller | ||
8 | - #interrupt-cells: number of cells to encode an interrupt source, shall be 1 | ||
9 | |||
10 | Example: | ||
11 | pmu_intc: pmu-interrupt-ctrl@d0050 { | ||
12 | compatible = "marvell,dove-pmu-intc"; | ||
13 | interrupt-controller; | ||
14 | #interrupt-cells = <1>; | ||
15 | reg = <0xd0050 0x8>; | ||
16 | interrupts = <33>; | ||
17 | }; | ||
diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile index c60b9010b152..f743006ce7ad 100644 --- a/drivers/irqchip/Makefile +++ b/drivers/irqchip/Makefile | |||
@@ -1,6 +1,7 @@ | |||
1 | obj-$(CONFIG_IRQCHIP) += irqchip.o | 1 | obj-$(CONFIG_IRQCHIP) += irqchip.o |
2 | 2 | ||
3 | obj-$(CONFIG_ARCH_BCM2835) += irq-bcm2835.o | 3 | obj-$(CONFIG_ARCH_BCM2835) += irq-bcm2835.o |
4 | obj-$(CONFIG_ARCH_DOVE) += irq-dove.o | ||
4 | obj-$(CONFIG_ARCH_EXYNOS) += exynos-combiner.o | 5 | obj-$(CONFIG_ARCH_EXYNOS) += exynos-combiner.o |
5 | obj-$(CONFIG_ARCH_MMP) += irq-mmp.o | 6 | obj-$(CONFIG_ARCH_MMP) += irq-mmp.o |
6 | obj-$(CONFIG_ARCH_MVEBU) += irq-armada-370-xp.o | 7 | obj-$(CONFIG_ARCH_MVEBU) += irq-armada-370-xp.o |
diff --git a/drivers/irqchip/irq-dove.c b/drivers/irqchip/irq-dove.c new file mode 100644 index 000000000000..788acd89848a --- /dev/null +++ b/drivers/irqchip/irq-dove.c | |||
@@ -0,0 +1,126 @@ | |||
1 | /* | ||
2 | * Marvell Dove SoCs PMU IRQ chip driver. | ||
3 | * | ||
4 | * Andrew Lunn <andrew@lunn.ch> | ||
5 | * | ||
6 | * This file is licensed under the terms of the GNU General Public | ||
7 | * License version 2. This program is licensed "as is" without any | ||
8 | * warranty of any kind, whether express or implied. | ||
9 | */ | ||
10 | |||
11 | #include <linux/io.h> | ||
12 | #include <linux/irq.h> | ||
13 | #include <linux/of.h> | ||
14 | #include <linux/of_address.h> | ||
15 | #include <linux/of_irq.h> | ||
16 | #include <asm/exception.h> | ||
17 | #include <asm/mach/irq.h> | ||
18 | |||
19 | #include "irqchip.h" | ||
20 | |||
21 | #define DOVE_PMU_IRQ_CAUSE 0x00 | ||
22 | #define DOVE_PMU_IRQ_MASK 0x04 | ||
23 | |||
24 | static void dove_pmu_irq_handler(unsigned int irq, struct irq_desc *desc) | ||
25 | { | ||
26 | struct irq_domain *d = irq_get_handler_data(irq); | ||
27 | struct irq_chip_generic *gc = irq_get_domain_generic_chip(d, 0); | ||
28 | u32 stat = readl_relaxed(gc->reg_base + DOVE_PMU_IRQ_CAUSE) & | ||
29 | gc->mask_cache; | ||
30 | |||
31 | while (stat) { | ||
32 | u32 hwirq = ffs(stat) - 1; | ||
33 | |||
34 | generic_handle_irq(irq_find_mapping(d, gc->irq_base + hwirq)); | ||
35 | stat &= ~(1 << hwirq); | ||
36 | } | ||
37 | } | ||
38 | |||
39 | static void pmu_irq_ack(struct irq_data *d) | ||
40 | { | ||
41 | struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d); | ||
42 | struct irq_chip_type *ct = irq_data_get_chip_type(d); | ||
43 | u32 mask = ~d->mask; | ||
44 | |||
45 | /* | ||
46 | * The PMU mask register is not RW0C: it is RW. This means that | ||
47 | * the bits take whatever value is written to them; if you write | ||
48 | * a '1', you will set the interrupt. | ||
49 | * | ||
50 | * Unfortunately this means there is NO race free way to clear | ||
51 | * these interrupts. | ||
52 | * | ||
53 | * So, let's structure the code so that the window is as small as | ||
54 | * possible. | ||
55 | */ | ||
56 | irq_gc_lock(gc); | ||
57 | mask &= irq_reg_readl(gc->reg_base + ct->regs.ack); | ||
58 | irq_reg_writel(mask, gc->reg_base + ct->regs.ack); | ||
59 | irq_gc_unlock(gc); | ||
60 | } | ||
61 | |||
62 | static int __init dove_pmu_irq_init(struct device_node *np, | ||
63 | struct device_node *parent) | ||
64 | { | ||
65 | unsigned int clr = IRQ_NOREQUEST | IRQ_NOPROBE | IRQ_NOAUTOEN; | ||
66 | struct resource r; | ||
67 | struct irq_domain *domain; | ||
68 | struct irq_chip_generic *gc; | ||
69 | int ret, irq, nrirqs = 7; | ||
70 | |||
71 | domain = irq_domain_add_linear(np, nrirqs, | ||
72 | &irq_generic_chip_ops, NULL); | ||
73 | if (!domain) { | ||
74 | pr_err("%s: unable to add irq domain\n", np->name); | ||
75 | return -ENOMEM; | ||
76 | } | ||
77 | |||
78 | ret = irq_alloc_domain_generic_chips(domain, nrirqs, 1, np->name, | ||
79 | handle_level_irq, clr, 0, IRQ_GC_INIT_MASK_CACHE); | ||
80 | if (ret) { | ||
81 | pr_err("%s: unable to alloc irq domain gc\n", np->name); | ||
82 | return ret; | ||
83 | } | ||
84 | |||
85 | ret = of_address_to_resource(np, 0, &r); | ||
86 | if (ret) { | ||
87 | pr_err("%s: unable to get resource\n", np->name); | ||
88 | return ret; | ||
89 | } | ||
90 | |||
91 | if (!request_mem_region(r.start, resource_size(&r), np->name)) { | ||
92 | pr_err("%s: unable to request mem region\n", np->name); | ||
93 | return -ENOMEM; | ||
94 | } | ||
95 | |||
96 | /* Map the parent interrupt for the chained handler */ | ||
97 | irq = irq_of_parse_and_map(np, 0); | ||
98 | if (irq <= 0) { | ||
99 | pr_err("%s: unable to parse irq\n", np->name); | ||
100 | return -EINVAL; | ||
101 | } | ||
102 | |||
103 | gc = irq_get_domain_generic_chip(domain, 0); | ||
104 | gc->reg_base = ioremap(r.start, resource_size(&r)); | ||
105 | if (!gc->reg_base) { | ||
106 | pr_err("%s: unable to map resource\n", np->name); | ||
107 | return -ENOMEM; | ||
108 | } | ||
109 | |||
110 | gc->chip_types[0].regs.ack = DOVE_PMU_IRQ_CAUSE; | ||
111 | gc->chip_types[0].regs.mask = DOVE_PMU_IRQ_MASK; | ||
112 | gc->chip_types[0].chip.irq_ack = pmu_irq_ack; | ||
113 | gc->chip_types[0].chip.irq_mask = irq_gc_mask_clr_bit; | ||
114 | gc->chip_types[0].chip.irq_unmask = irq_gc_mask_set_bit; | ||
115 | |||
116 | /* mask and clear all interrupts */ | ||
117 | writel(0, gc->reg_base + DOVE_PMU_IRQ_MASK); | ||
118 | writel(0, gc->reg_base + DOVE_PMU_IRQ_CAUSE); | ||
119 | |||
120 | irq_set_handler_data(irq, domain); | ||
121 | irq_set_chained_handler(irq, dove_pmu_irq_handler); | ||
122 | |||
123 | return 0; | ||
124 | } | ||
125 | IRQCHIP_DECLARE(dove_pmu_intc, | ||
126 | "marvell,dove-pmu-intc", dove_pmu_irq_init); | ||