Commit | Line | Data |
---|---|---|
d7fbc6ca MR |
1 | /* |
2 | * Allwinner A1X SoCs IRQ chip driver. | |
3 | * | |
4 | * Copyright (C) 2012 Maxime Ripard | |
5 | * | |
6 | * Maxime Ripard <maxime.ripard@free-electrons.com> | |
7 | * | |
8 | * Based on code from | |
9 | * Allwinner Technology Co., Ltd. <www.allwinnertech.com> | |
10 | * Benn Huang <benn@allwinnertech.com> | |
11 | * | |
12 | * This file is licensed under the terms of the GNU General Public | |
13 | * License version 2. This program is licensed "as is" without any | |
14 | * warranty of any kind, whether express or implied. | |
15 | */ | |
16 | ||
17 | #include <linux/io.h> | |
18 | #include <linux/irq.h> | |
41a83e06 | 19 | #include <linux/irqchip.h> |
d7fbc6ca MR |
20 | #include <linux/of.h> |
21 | #include <linux/of_address.h> | |
22 | #include <linux/of_irq.h> | |
23 | ||
24 | #include <asm/exception.h> | |
d7fbc6ca | 25 | |
d7fbc6ca MR |
26 | #define SUN4I_IRQ_VECTOR_REG 0x00 |
27 | #define SUN4I_IRQ_PROTECTION_REG 0x08 | |
28 | #define SUN4I_IRQ_NMI_CTRL_REG 0x0c | |
29 | #define SUN4I_IRQ_PENDING_REG(x) (0x10 + 0x4 * x) | |
30 | #define SUN4I_IRQ_FIQ_PENDING_REG(x) (0x20 + 0x4 * x) | |
d4fc2ea0 MK |
31 | #define SUN4I_IRQ_ENABLE_REG(data, x) ((data)->enable_reg_offset + 0x4 * x) |
32 | #define SUN4I_IRQ_MASK_REG(data, x) ((data)->mask_reg_offset + 0x4 * x) | |
33 | #define SUN4I_IRQ_ENABLE_REG_OFFSET 0x40 | |
34 | #define SUN4I_IRQ_MASK_REG_OFFSET 0x50 | |
b0c4b9f3 MK |
35 | #define SUNIV_IRQ_ENABLE_REG_OFFSET 0x20 |
36 | #define SUNIV_IRQ_MASK_REG_OFFSET 0x30 | |
d7fbc6ca | 37 | |
177304cf MK |
38 | struct sun4i_irq_chip_data { |
39 | void __iomem *irq_base; | |
40 | struct irq_domain *irq_domain; | |
d4fc2ea0 MK |
41 | u32 enable_reg_offset; |
42 | u32 mask_reg_offset; | |
177304cf MK |
43 | }; |
44 | ||
45 | static struct sun4i_irq_chip_data *irq_ic_data; | |
d7fbc6ca | 46 | |
8783dd3a | 47 | static void __exception_irq_entry sun4i_handle_irq(struct pt_regs *regs); |
d7fbc6ca | 48 | |
baaecfa7 | 49 | static void sun4i_irq_ack(struct irq_data *irqd) |
d7fbc6ca MR |
50 | { |
51 | unsigned int irq = irqd_to_hwirq(irqd); | |
d7fbc6ca | 52 | |
915b78ce HG |
53 | if (irq != 0) |
54 | return; /* Only IRQ 0 / the ENMI needs to be acked */ | |
55 | ||
177304cf | 56 | writel(BIT(0), irq_ic_data->irq_base + SUN4I_IRQ_PENDING_REG(0)); |
d7fbc6ca MR |
57 | } |
58 | ||
59 | static void sun4i_irq_mask(struct irq_data *irqd) | |
60 | { | |
61 | unsigned int irq = irqd_to_hwirq(irqd); | |
62 | unsigned int irq_off = irq % 32; | |
63 | int reg = irq / 32; | |
64 | u32 val; | |
65 | ||
d4fc2ea0 MK |
66 | val = readl(irq_ic_data->irq_base + |
67 | SUN4I_IRQ_ENABLE_REG(irq_ic_data, reg)); | |
d7fbc6ca | 68 | writel(val & ~(1 << irq_off), |
d4fc2ea0 | 69 | irq_ic_data->irq_base + SUN4I_IRQ_ENABLE_REG(irq_ic_data, reg)); |
d7fbc6ca MR |
70 | } |
71 | ||
72 | static void sun4i_irq_unmask(struct irq_data *irqd) | |
73 | { | |
74 | unsigned int irq = irqd_to_hwirq(irqd); | |
75 | unsigned int irq_off = irq % 32; | |
76 | int reg = irq / 32; | |
77 | u32 val; | |
78 | ||
d4fc2ea0 MK |
79 | val = readl(irq_ic_data->irq_base + |
80 | SUN4I_IRQ_ENABLE_REG(irq_ic_data, reg)); | |
d7fbc6ca | 81 | writel(val | (1 << irq_off), |
d4fc2ea0 | 82 | irq_ic_data->irq_base + SUN4I_IRQ_ENABLE_REG(irq_ic_data, reg)); |
d7fbc6ca MR |
83 | } |
84 | ||
85 | static struct irq_chip sun4i_irq_chip = { | |
e9df9e22 HG |
86 | .name = "sun4i_irq", |
87 | .irq_eoi = sun4i_irq_ack, | |
88 | .irq_mask = sun4i_irq_mask, | |
89 | .irq_unmask = sun4i_irq_unmask, | |
90 | .flags = IRQCHIP_EOI_THREADED | IRQCHIP_EOI_IF_HANDLED, | |
91 | }; | |
92 | ||
d7fbc6ca MR |
93 | static int sun4i_irq_map(struct irq_domain *d, unsigned int virq, |
94 | irq_hw_number_t hw) | |
95 | { | |
915b78ce | 96 | irq_set_chip_and_handler(virq, &sun4i_irq_chip, handle_fasteoi_irq); |
d17cab44 | 97 | irq_set_probe(virq); |
d7fbc6ca MR |
98 | |
99 | return 0; | |
100 | } | |
101 | ||
96009736 | 102 | static const struct irq_domain_ops sun4i_irq_ops = { |
d7fbc6ca MR |
103 | .map = sun4i_irq_map, |
104 | .xlate = irq_domain_xlate_onecell, | |
105 | }; | |
106 | ||
107 | static int __init sun4i_of_init(struct device_node *node, | |
108 | struct device_node *parent) | |
109 | { | |
177304cf MK |
110 | irq_ic_data->irq_base = of_iomap(node, 0); |
111 | if (!irq_ic_data->irq_base) | |
e81f54c6 RH |
112 | panic("%pOF: unable to map IC registers\n", |
113 | node); | |
d7fbc6ca MR |
114 | |
115 | /* Disable all interrupts */ | |
d4fc2ea0 MK |
116 | writel(0, irq_ic_data->irq_base + SUN4I_IRQ_ENABLE_REG(irq_ic_data, 0)); |
117 | writel(0, irq_ic_data->irq_base + SUN4I_IRQ_ENABLE_REG(irq_ic_data, 1)); | |
118 | writel(0, irq_ic_data->irq_base + SUN4I_IRQ_ENABLE_REG(irq_ic_data, 2)); | |
d7fbc6ca | 119 | |
649ff46e | 120 | /* Unmask all the interrupts, ENABLE_REG(x) is used for masking */ |
d4fc2ea0 MK |
121 | writel(0, irq_ic_data->irq_base + SUN4I_IRQ_MASK_REG(irq_ic_data, 0)); |
122 | writel(0, irq_ic_data->irq_base + SUN4I_IRQ_MASK_REG(irq_ic_data, 1)); | |
123 | writel(0, irq_ic_data->irq_base + SUN4I_IRQ_MASK_REG(irq_ic_data, 2)); | |
d7fbc6ca MR |
124 | |
125 | /* Clear all the pending interrupts */ | |
177304cf MK |
126 | writel(0xffffffff, irq_ic_data->irq_base + SUN4I_IRQ_PENDING_REG(0)); |
127 | writel(0xffffffff, irq_ic_data->irq_base + SUN4I_IRQ_PENDING_REG(1)); | |
128 | writel(0xffffffff, irq_ic_data->irq_base + SUN4I_IRQ_PENDING_REG(2)); | |
d7fbc6ca MR |
129 | |
130 | /* Enable protection mode */ | |
177304cf | 131 | writel(0x01, irq_ic_data->irq_base + SUN4I_IRQ_PROTECTION_REG); |
d7fbc6ca MR |
132 | |
133 | /* Configure the external interrupt source type */ | |
177304cf | 134 | writel(0x00, irq_ic_data->irq_base + SUN4I_IRQ_NMI_CTRL_REG); |
d7fbc6ca | 135 | |
177304cf | 136 | irq_ic_data->irq_domain = irq_domain_add_linear(node, 3 * 32, |
d7fbc6ca | 137 | &sun4i_irq_ops, NULL); |
177304cf | 138 | if (!irq_ic_data->irq_domain) |
e81f54c6 | 139 | panic("%pOF: unable to create IRQ domain\n", node); |
d7fbc6ca MR |
140 | |
141 | set_handle_irq(sun4i_handle_irq); | |
142 | ||
143 | return 0; | |
144 | } | |
b0c4b9f3 MK |
145 | |
146 | static int __init sun4i_ic_of_init(struct device_node *node, | |
147 | struct device_node *parent) | |
148 | { | |
149 | irq_ic_data = kzalloc(sizeof(struct sun4i_irq_chip_data), GFP_KERNEL); | |
150 | if (!irq_ic_data) { | |
151 | pr_err("kzalloc failed!\n"); | |
152 | return -ENOMEM; | |
153 | } | |
154 | ||
155 | irq_ic_data->enable_reg_offset = SUN4I_IRQ_ENABLE_REG_OFFSET; | |
156 | irq_ic_data->mask_reg_offset = SUN4I_IRQ_MASK_REG_OFFSET; | |
157 | ||
158 | return sun4i_of_init(node, parent); | |
159 | } | |
160 | ||
161 | IRQCHIP_DECLARE(allwinner_sun4i_ic, "allwinner,sun4i-a10-ic", sun4i_ic_of_init); | |
162 | ||
163 | static int __init suniv_ic_of_init(struct device_node *node, | |
164 | struct device_node *parent) | |
165 | { | |
166 | irq_ic_data = kzalloc(sizeof(struct sun4i_irq_chip_data), GFP_KERNEL); | |
167 | if (!irq_ic_data) { | |
168 | pr_err("kzalloc failed!\n"); | |
169 | return -ENOMEM; | |
170 | } | |
171 | ||
172 | irq_ic_data->enable_reg_offset = SUNIV_IRQ_ENABLE_REG_OFFSET; | |
173 | irq_ic_data->mask_reg_offset = SUNIV_IRQ_MASK_REG_OFFSET; | |
174 | ||
175 | return sun4i_of_init(node, parent); | |
176 | } | |
177 | ||
178 | IRQCHIP_DECLARE(allwinner_sunvi_ic, "allwinner,suniv-f1c100s-ic", | |
179 | suniv_ic_of_init); | |
d7fbc6ca | 180 | |
8783dd3a | 181 | static void __exception_irq_entry sun4i_handle_irq(struct pt_regs *regs) |
d7fbc6ca | 182 | { |
21d06d91 | 183 | u32 hwirq; |
d7fbc6ca | 184 | |
56af0416 HG |
185 | /* |
186 | * hwirq == 0 can mean one of 3 things: | |
187 | * 1) no more irqs pending | |
188 | * 2) irq 0 pending | |
189 | * 3) spurious irq | |
190 | * So if we immediately get a reading of 0, check the irq-pending reg | |
191 | * to differentiate between 2 and 3. We only do this once to avoid | |
192 | * the extra check in the common case of 1 hapening after having | |
193 | * read the vector-reg once. | |
194 | */ | |
177304cf | 195 | hwirq = readl(irq_ic_data->irq_base + SUN4I_IRQ_VECTOR_REG) >> 2; |
56af0416 | 196 | if (hwirq == 0 && |
177304cf MK |
197 | !(readl(irq_ic_data->irq_base + SUN4I_IRQ_PENDING_REG(0)) & |
198 | BIT(0))) | |
56af0416 HG |
199 | return; |
200 | ||
201 | do { | |
177304cf MK |
202 | handle_domain_irq(irq_ic_data->irq_domain, hwirq, regs); |
203 | hwirq = readl(irq_ic_data->irq_base + | |
204 | SUN4I_IRQ_VECTOR_REG) >> 2; | |
56af0416 | 205 | } while (hwirq != 0); |
d7fbc6ca | 206 | } |