]>
Commit | Line | Data |
---|---|---|
9fbbe689 | 1 | /* |
263475d4 | 2 | * Copyright 2011-2013 Freescale Semiconductor, Inc. |
9fbbe689 SG |
3 | * Copyright 2011 Linaro Ltd. |
4 | * | |
5 | * The code contained herein is licensed under the GNU General Public | |
6 | * License. You may obtain a copy of the GNU General Public License | |
7 | * Version 2 or later at the following locations: | |
8 | * | |
9 | * http://www.opensource.org/licenses/gpl-license.html | |
10 | * http://www.gnu.org/copyleft/gpl.html | |
11 | */ | |
12 | ||
13 | #include <linux/io.h> | |
14 | #include <linux/irq.h> | |
0cc09e85 | 15 | #include <linux/irqchip.h> |
9fbbe689 SG |
16 | #include <linux/of.h> |
17 | #include <linux/of_address.h> | |
18 | #include <linux/of_irq.h> | |
520f7bd7 | 19 | #include <linux/irqchip/arm-gic.h> |
9a67a6fd | 20 | #include "common.h" |
00eb60a8 | 21 | #include "hardware.h" |
9fbbe689 SG |
22 | |
23 | #define GPC_IMR1 0x008 | |
24 | #define GPC_PGC_CPU_PDN 0x2a0 | |
05136f08 AH |
25 | #define GPC_PGC_CPU_PUPSCR 0x2a4 |
26 | #define GPC_PGC_CPU_PDNSCR 0x2a8 | |
27 | #define GPC_PGC_SW2ISO_SHIFT 0x8 | |
28 | #define GPC_PGC_SW_SHIFT 0x0 | |
9fbbe689 SG |
29 | |
30 | #define IMR_NUM 4 | |
b923ff6a | 31 | #define GPC_MAX_IRQS (IMR_NUM * 32) |
9fbbe689 SG |
32 | |
33 | static void __iomem *gpc_base; | |
34 | static u32 gpc_wake_irqs[IMR_NUM]; | |
35 | static u32 gpc_saved_imrs[IMR_NUM]; | |
36 | ||
05136f08 AH |
37 | void imx_gpc_set_arm_power_up_timing(u32 sw2iso, u32 sw) |
38 | { | |
39 | writel_relaxed((sw2iso << GPC_PGC_SW2ISO_SHIFT) | | |
40 | (sw << GPC_PGC_SW_SHIFT), gpc_base + GPC_PGC_CPU_PUPSCR); | |
41 | } | |
42 | ||
43 | void imx_gpc_set_arm_power_down_timing(u32 sw2iso, u32 sw) | |
44 | { | |
45 | writel_relaxed((sw2iso << GPC_PGC_SW2ISO_SHIFT) | | |
46 | (sw << GPC_PGC_SW_SHIFT), gpc_base + GPC_PGC_CPU_PDNSCR); | |
47 | } | |
48 | ||
49 | void imx_gpc_set_arm_power_in_lpm(bool power_off) | |
50 | { | |
51 | writel_relaxed(power_off, gpc_base + GPC_PGC_CPU_PDN); | |
52 | } | |
53 | ||
80c0ecdc | 54 | void imx_gpc_pre_suspend(bool arm_power_off) |
9fbbe689 SG |
55 | { |
56 | void __iomem *reg_imr1 = gpc_base + GPC_IMR1; | |
57 | int i; | |
58 | ||
59 | /* Tell GPC to power off ARM core when suspend */ | |
80c0ecdc | 60 | if (arm_power_off) |
05136f08 | 61 | imx_gpc_set_arm_power_in_lpm(arm_power_off); |
9fbbe689 SG |
62 | |
63 | for (i = 0; i < IMR_NUM; i++) { | |
64 | gpc_saved_imrs[i] = readl_relaxed(reg_imr1 + i * 4); | |
65 | writel_relaxed(~gpc_wake_irqs[i], reg_imr1 + i * 4); | |
66 | } | |
67 | } | |
68 | ||
69 | void imx_gpc_post_resume(void) | |
70 | { | |
71 | void __iomem *reg_imr1 = gpc_base + GPC_IMR1; | |
72 | int i; | |
73 | ||
74 | /* Keep ARM core powered on for other low-power modes */ | |
05136f08 | 75 | imx_gpc_set_arm_power_in_lpm(false); |
9fbbe689 SG |
76 | |
77 | for (i = 0; i < IMR_NUM; i++) | |
78 | writel_relaxed(gpc_saved_imrs[i], reg_imr1 + i * 4); | |
79 | } | |
80 | ||
81 | static int imx_gpc_irq_set_wake(struct irq_data *d, unsigned int on) | |
82 | { | |
b923ff6a | 83 | unsigned int idx = d->hwirq / 32; |
9fbbe689 SG |
84 | u32 mask; |
85 | ||
e2fd06f6 | 86 | mask = 1 << d->hwirq % 32; |
9fbbe689 SG |
87 | gpc_wake_irqs[idx] = on ? gpc_wake_irqs[idx] | mask : |
88 | gpc_wake_irqs[idx] & ~mask; | |
89 | ||
b923ff6a MZ |
90 | /* |
91 | * Do *not* call into the parent, as the GIC doesn't have any | |
92 | * wake-up facility... | |
93 | */ | |
9fbbe689 SG |
94 | return 0; |
95 | } | |
96 | ||
263475d4 AH |
97 | void imx_gpc_mask_all(void) |
98 | { | |
99 | void __iomem *reg_imr1 = gpc_base + GPC_IMR1; | |
100 | int i; | |
101 | ||
102 | for (i = 0; i < IMR_NUM; i++) { | |
103 | gpc_saved_imrs[i] = readl_relaxed(reg_imr1 + i * 4); | |
104 | writel_relaxed(~0, reg_imr1 + i * 4); | |
105 | } | |
106 | ||
107 | } | |
108 | ||
109 | void imx_gpc_restore_all(void) | |
110 | { | |
111 | void __iomem *reg_imr1 = gpc_base + GPC_IMR1; | |
112 | int i; | |
113 | ||
114 | for (i = 0; i < IMR_NUM; i++) | |
115 | writel_relaxed(gpc_saved_imrs[i], reg_imr1 + i * 4); | |
116 | } | |
117 | ||
65bb688a | 118 | void imx_gpc_hwirq_unmask(unsigned int hwirq) |
9fbbe689 SG |
119 | { |
120 | void __iomem *reg; | |
121 | u32 val; | |
122 | ||
b923ff6a | 123 | reg = gpc_base + GPC_IMR1 + hwirq / 32 * 4; |
9fbbe689 | 124 | val = readl_relaxed(reg); |
65bb688a | 125 | val &= ~(1 << hwirq % 32); |
9fbbe689 SG |
126 | writel_relaxed(val, reg); |
127 | } | |
128 | ||
65bb688a | 129 | void imx_gpc_hwirq_mask(unsigned int hwirq) |
9fbbe689 SG |
130 | { |
131 | void __iomem *reg; | |
132 | u32 val; | |
133 | ||
b923ff6a | 134 | reg = gpc_base + GPC_IMR1 + hwirq / 32 * 4; |
65bb688a MZ |
135 | val = readl_relaxed(reg); |
136 | val |= 1 << (hwirq % 32); | |
137 | writel_relaxed(val, reg); | |
138 | } | |
139 | ||
140 | static void imx_gpc_irq_unmask(struct irq_data *d) | |
141 | { | |
65bb688a | 142 | imx_gpc_hwirq_unmask(d->hwirq); |
b923ff6a | 143 | irq_chip_unmask_parent(d); |
65bb688a MZ |
144 | } |
145 | ||
146 | static void imx_gpc_irq_mask(struct irq_data *d) | |
147 | { | |
65bb688a | 148 | imx_gpc_hwirq_mask(d->hwirq); |
b923ff6a | 149 | irq_chip_mask_parent(d); |
9fbbe689 SG |
150 | } |
151 | ||
b923ff6a | 152 | static struct irq_chip imx_gpc_chip = { |
e33b6752 MZ |
153 | .name = "GPC", |
154 | .irq_eoi = irq_chip_eoi_parent, | |
155 | .irq_mask = imx_gpc_irq_mask, | |
156 | .irq_unmask = imx_gpc_irq_unmask, | |
157 | .irq_retrigger = irq_chip_retrigger_hierarchy, | |
158 | .irq_set_wake = imx_gpc_irq_set_wake, | |
4699ccbf | 159 | .irq_set_type = irq_chip_set_type_parent, |
e33b6752 MZ |
160 | #ifdef CONFIG_SMP |
161 | .irq_set_affinity = irq_chip_set_affinity_parent, | |
162 | #endif | |
b923ff6a MZ |
163 | }; |
164 | ||
f833f57f MZ |
165 | static int imx_gpc_domain_translate(struct irq_domain *d, |
166 | struct irq_fwspec *fwspec, | |
167 | unsigned long *hwirq, | |
168 | unsigned int *type) | |
9fbbe689 | 169 | { |
f833f57f MZ |
170 | if (is_of_node(fwspec->fwnode)) { |
171 | if (fwspec->param_count != 3) | |
172 | return -EINVAL; | |
b923ff6a | 173 | |
f833f57f MZ |
174 | /* No PPI should point to this domain */ |
175 | if (fwspec->param[0] != 0) | |
176 | return -EINVAL; | |
177 | ||
178 | *hwirq = fwspec->param[1]; | |
179 | *type = fwspec->param[2]; | |
180 | return 0; | |
181 | } | |
182 | ||
183 | return -EINVAL; | |
b923ff6a MZ |
184 | } |
185 | ||
186 | static int imx_gpc_domain_alloc(struct irq_domain *domain, | |
187 | unsigned int irq, | |
188 | unsigned int nr_irqs, void *data) | |
189 | { | |
f833f57f MZ |
190 | struct irq_fwspec *fwspec = data; |
191 | struct irq_fwspec parent_fwspec; | |
b923ff6a | 192 | irq_hw_number_t hwirq; |
485863b8 | 193 | int i; |
9fbbe689 | 194 | |
f833f57f | 195 | if (fwspec->param_count != 3) |
b923ff6a | 196 | return -EINVAL; /* Not GIC compliant */ |
f833f57f | 197 | if (fwspec->param[0] != 0) |
b923ff6a MZ |
198 | return -EINVAL; /* No PPI should point to this domain */ |
199 | ||
f833f57f | 200 | hwirq = fwspec->param[1]; |
b923ff6a MZ |
201 | if (hwirq >= GPC_MAX_IRQS) |
202 | return -EINVAL; /* Can't deal with this */ | |
203 | ||
204 | for (i = 0; i < nr_irqs; i++) | |
205 | irq_domain_set_hwirq_and_chip(domain, irq + i, hwirq + i, | |
206 | &imx_gpc_chip, NULL); | |
207 | ||
f833f57f MZ |
208 | parent_fwspec = *fwspec; |
209 | parent_fwspec.fwnode = domain->parent->fwnode; | |
210 | return irq_domain_alloc_irqs_parent(domain, irq, nr_irqs, | |
211 | &parent_fwspec); | |
b923ff6a MZ |
212 | } |
213 | ||
9b589a83 | 214 | static const struct irq_domain_ops imx_gpc_domain_ops = { |
f833f57f MZ |
215 | .translate = imx_gpc_domain_translate, |
216 | .alloc = imx_gpc_domain_alloc, | |
217 | .free = irq_domain_free_irqs_common, | |
b923ff6a MZ |
218 | }; |
219 | ||
220 | static int __init imx_gpc_init(struct device_node *node, | |
221 | struct device_node *parent) | |
222 | { | |
223 | struct irq_domain *parent_domain, *domain; | |
224 | int i; | |
225 | ||
226 | if (!parent) { | |
227 | pr_err("%s: no parent, giving up\n", node->full_name); | |
228 | return -ENODEV; | |
229 | } | |
230 | ||
231 | parent_domain = irq_find_host(parent); | |
232 | if (!parent_domain) { | |
233 | pr_err("%s: unable to obtain parent domain\n", node->full_name); | |
234 | return -ENXIO; | |
235 | } | |
236 | ||
237 | gpc_base = of_iomap(node, 0); | |
238 | if (WARN_ON(!gpc_base)) | |
239 | return -ENOMEM; | |
240 | ||
241 | domain = irq_domain_add_hierarchy(parent_domain, 0, GPC_MAX_IRQS, | |
242 | node, &imx_gpc_domain_ops, | |
243 | NULL); | |
244 | if (!domain) { | |
245 | iounmap(gpc_base); | |
246 | return -ENOMEM; | |
247 | } | |
9fbbe689 | 248 | |
485863b8 SG |
249 | /* Initially mask all interrupts */ |
250 | for (i = 0; i < IMR_NUM; i++) | |
251 | writel_relaxed(~0, gpc_base + GPC_IMR1 + i * 4); | |
252 | ||
255c0397 PZ |
253 | /* |
254 | * Clear the OF_POPULATED flag set in of_irq_init so that | |
255 | * later the GPC power domain driver will not be skipped. | |
256 | */ | |
257 | of_node_clear_flag(node, OF_POPULATED); | |
258 | ||
b923ff6a | 259 | return 0; |
9fbbe689 | 260 | } |
0cc09e85 | 261 | IRQCHIP_DECLARE(imx_gpc, "fsl,imx6q-gpc", imx_gpc_init); |
b923ff6a | 262 | |
14517564 MZ |
263 | void __init imx_gpc_check_dt(void) |
264 | { | |
265 | struct device_node *np; | |
266 | ||
267 | np = of_find_compatible_node(NULL, NULL, "fsl,imx6q-gpc"); | |
634a6037 LS |
268 | if (WARN_ON(!np)) |
269 | return; | |
270 | ||
271 | if (WARN_ON(!of_find_property(np, "interrupt-controller", NULL))) { | |
272 | pr_warn("Outdated DT detected, suspend/resume will NOT work\n"); | |
273 | ||
274 | /* map GPC, so that at least CPUidle and WARs keep working */ | |
275 | gpc_base = of_iomap(np, 0); | |
276 | } | |
14517564 | 277 | } |