aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorShenwei Wang <shenwei.wang@freescale.com>2015-08-24 15:04:15 -0400
committerThomas Gleixner <tglx@linutronix.de>2015-08-24 15:49:34 -0400
commite324c4dc4a5991d5b1171f434884a4026345e4b4 (patch)
tree7eafdc7f1b97f201d85e5dd7c67695886c5412cb
parent1a15aaa998dc3b51f7f8b9a820bc7a192a0c2f76 (diff)
irqchip/imx-gpcv2: IMX GPCv2 driver for wakeup sources
IMX7D contains a new version of GPC IP block (GPCv2). It has two major functions: power management and wakeup source management. When the system is in WFI (wait for interrupt) mode, the GPC block will be the first block on the platform to be activated and signaled. In normal wait mode during cpu idle, the system can be woken up by any enabled interrupts. In standby or suspend mode, the system can only be wokem up by the pre-defined wakeup sources. Based-on-patch-by: Anson Huang <b20788@freescale.com> Signed-off-by: Shenwei Wang <shenwei.wang@freescale.com> Cc: <linux-arm-kernel@lists.infradead.org> Cc: <shawn.guo@linaro.org> Cc: <jason@lakedaemon.net> Link: http://lkml.kernel.org/r/1440443055-7291-1-git-send-email-shenwei.wang@freescale.com Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
-rw-r--r--drivers/irqchip/Kconfig6
-rw-r--r--drivers/irqchip/Makefile1
-rw-r--r--drivers/irqchip/irq-imx-gpcv2.c278
3 files changed, 285 insertions, 0 deletions
diff --git a/drivers/irqchip/Kconfig b/drivers/irqchip/Kconfig
index 5b853715f40b..27b52c8729cd 100644
--- a/drivers/irqchip/Kconfig
+++ b/drivers/irqchip/Kconfig
@@ -181,3 +181,9 @@ config RENESAS_H8300H_INTC
181config RENESAS_H8S_INTC 181config RENESAS_H8S_INTC
182 bool 182 bool
183 select IRQ_DOMAIN 183 select IRQ_DOMAIN
184
185config IMX_GPCV2
186 bool
187 select IRQ_DOMAIN
188 help
189 Enables the wakeup IRQs for IMX platforms with GPCv2 block
diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile
index e5e00690b272..bb3048f00e64 100644
--- a/drivers/irqchip/Makefile
+++ b/drivers/irqchip/Makefile
@@ -54,3 +54,4 @@ obj-$(CONFIG_RENESAS_H8300H_INTC) += irq-renesas-h8300h.o
54obj-$(CONFIG_RENESAS_H8S_INTC) += irq-renesas-h8s.o 54obj-$(CONFIG_RENESAS_H8S_INTC) += irq-renesas-h8s.o
55obj-$(CONFIG_ARCH_SA1100) += irq-sa11x0.o 55obj-$(CONFIG_ARCH_SA1100) += irq-sa11x0.o
56obj-$(CONFIG_INGENIC_IRQ) += irq-ingenic.o 56obj-$(CONFIG_INGENIC_IRQ) += irq-ingenic.o
57obj-$(CONFIG_IMX_GPCV2) += irq-imx-gpcv2.o
diff --git a/drivers/irqchip/irq-imx-gpcv2.c b/drivers/irqchip/irq-imx-gpcv2.c
new file mode 100644
index 000000000000..e48d3305456f
--- /dev/null
+++ b/drivers/irqchip/irq-imx-gpcv2.c
@@ -0,0 +1,278 @@
1/*
2 * Copyright (C) 2015 Freescale Semiconductor, Inc.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License version 2 as
6 * published by the Free Software Foundation.
7 */
8
9#include <linux/of_address.h>
10#include <linux/of_irq.h>
11#include <linux/slab.h>
12#include <linux/irqchip.h>
13#include <linux/syscore_ops.h>
14
15#define IMR_NUM 4
16#define GPC_MAX_IRQS (IMR_NUM * 32)
17
18#define GPC_IMR1_CORE0 0x30
19#define GPC_IMR1_CORE1 0x40
20
21struct gpcv2_irqchip_data {
22 struct raw_spinlock rlock;
23 void __iomem *gpc_base;
24 u32 wakeup_sources[IMR_NUM];
25 u32 saved_irq_mask[IMR_NUM];
26 u32 cpu2wakeup;
27};
28
29static struct gpcv2_irqchip_data *imx_gpcv2_instance;
30
31/*
32 * Interface for the low level wakeup code.
33 */
34u32 imx_gpcv2_get_wakeup_source(u32 **sources)
35{
36 if (!imx_gpcv2_instance)
37 return 0;
38
39 if (sources)
40 *sources = imx_gpcv2_instance->wakeup_sources;
41
42 return IMR_NUM;
43}
44
45static int gpcv2_wakeup_source_save(void)
46{
47 struct gpcv2_irqchip_data *cd;
48 void __iomem *reg;
49 int i;
50
51 cd = imx_gpcv2_instance;
52 if (!cd)
53 return 0;
54
55 for (i = 0; i < IMR_NUM; i++) {
56 reg = cd->gpc_base + cd->cpu2wakeup + i * 4;
57 cd->saved_irq_mask[i] = readl_relaxed(reg);
58 writel_relaxed(cd->wakeup_sources[i], reg);
59 }
60
61 return 0;
62}
63
64static void gpcv2_wakeup_source_restore(void)
65{
66 struct gpcv2_irqchip_data *cd;
67 void __iomem *reg;
68 int i;
69
70 cd = imx_gpcv2_instance;
71 if (!cd)
72 return;
73
74 for (i = 0; i < IMR_NUM; i++) {
75 reg = cd->gpc_base + cd->cpu2wakeup + i * 4;
76 writel_relaxed(cd->saved_irq_mask[i], reg);
77 }
78}
79
80static struct syscore_ops imx_gpcv2_syscore_ops = {
81 .suspend = gpcv2_wakeup_source_save,
82 .resume = gpcv2_wakeup_source_restore,
83};
84
85static int imx_gpcv2_irq_set_wake(struct irq_data *d, unsigned int on)
86{
87 struct gpcv2_irqchip_data *cd = d->chip_data;
88 unsigned int idx = d->hwirq / 32;
89 unsigned long flags;
90 void __iomem *reg;
91 u32 mask, val;
92
93 raw_spin_lock_irqsave(&cd->rlock, flags);
94 reg = cd->gpc_base + cd->cpu2wakeup + idx * 4;
95 mask = 1 << d->hwirq % 32;
96 val = cd->wakeup_sources[idx];
97
98 cd->wakeup_sources[idx] = on ? (val & ~mask) : (val | mask);
99 raw_spin_unlock_irqrestore(&cd->rlock, flags);
100
101 /*
102 * Do *not* call into the parent, as the GIC doesn't have any
103 * wake-up facility...
104 */
105
106 return 0;
107}
108
109static void imx_gpcv2_irq_unmask(struct irq_data *d)
110{
111 struct gpcv2_irqchip_data *cd = d->chip_data;
112 void __iomem *reg;
113 u32 val;
114
115 raw_spin_lock(&cd->rlock);
116 reg = cd->gpc_base + cd->cpu2wakeup + d->hwirq / 32 * 4;
117 val = readl_relaxed(reg);
118 val &= ~(1 << d->hwirq % 32);
119 writel_relaxed(val, reg);
120 raw_spin_unlock(&cd->rlock);
121
122 irq_chip_unmask_parent(d);
123}
124
125static void imx_gpcv2_irq_mask(struct irq_data *d)
126{
127 struct gpcv2_irqchip_data *cd = d->chip_data;
128 void __iomem *reg;
129 u32 val;
130
131 raw_spin_lock(&cd->rlock);
132 reg = cd->gpc_base + cd->cpu2wakeup + d->hwirq / 32 * 4;
133 val = readl_relaxed(reg);
134 val |= 1 << (d->hwirq % 32);
135 writel_relaxed(val, reg);
136 raw_spin_unlock(&cd->rlock);
137
138 irq_chip_mask_parent(d);
139}
140
141static struct irq_chip gpcv2_irqchip_data_chip = {
142 .name = "GPCv2",
143 .irq_eoi = irq_chip_eoi_parent,
144 .irq_mask = imx_gpcv2_irq_mask,
145 .irq_unmask = imx_gpcv2_irq_unmask,
146 .irq_set_wake = imx_gpcv2_irq_set_wake,
147 .irq_retrigger = irq_chip_retrigger_hierarchy,
148#ifdef CONFIG_SMP
149 .irq_set_affinity = irq_chip_set_affinity_parent,
150#endif
151};
152
153static int imx_gpcv2_domain_xlate(struct irq_domain *domain,
154 struct device_node *controller,
155 const u32 *intspec,
156 unsigned int intsize,
157 unsigned long *out_hwirq,
158 unsigned int *out_type)
159{
160 /* Shouldn't happen, really... */
161 if (domain->of_node != controller)
162 return -EINVAL;
163
164 /* Not GIC compliant */
165 if (intsize != 3)
166 return -EINVAL;
167
168 /* No PPI should point to this domain */
169 if (intspec[0] != 0)
170 return -EINVAL;
171
172 *out_hwirq = intspec[1];
173 *out_type = intspec[2];
174 return 0;
175}
176
177static int imx_gpcv2_domain_alloc(struct irq_domain *domain,
178 unsigned int irq, unsigned int nr_irqs,
179 void *data)
180{
181 struct of_phandle_args *args = data;
182 struct of_phandle_args parent_args;
183 irq_hw_number_t hwirq;
184 int i;
185
186 /* Not GIC compliant */
187 if (args->args_count != 3)
188 return -EINVAL;
189
190 /* No PPI should point to this domain */
191 if (args->args[0] != 0)
192 return -EINVAL;
193
194 /* Can't deal with this */
195 hwirq = args->args[1];
196 if (hwirq >= GPC_MAX_IRQS)
197 return -EINVAL;
198
199 for (i = 0; i < nr_irqs; i++) {
200 irq_domain_set_hwirq_and_chip(domain, irq + i, hwirq + i,
201 &gpcv2_irqchip_data_chip, domain->host_data);
202 }
203
204 parent_args = *args;
205 parent_args.np = domain->parent->of_node;
206 return irq_domain_alloc_irqs_parent(domain, irq, nr_irqs, &parent_args);
207}
208
209static struct irq_domain_ops gpcv2_irqchip_data_domain_ops = {
210 .xlate = imx_gpcv2_domain_xlate,
211 .alloc = imx_gpcv2_domain_alloc,
212 .free = irq_domain_free_irqs_common,
213};
214
215static int __init imx_gpcv2_irqchip_init(struct device_node *node,
216 struct device_node *parent)
217{
218 struct irq_domain *parent_domain, *domain;
219 struct gpcv2_irqchip_data *cd;
220 int i;
221
222 if (!parent) {
223 pr_err("%s: no parent, giving up\n", node->full_name);
224 return -ENODEV;
225 }
226
227 parent_domain = irq_find_host(parent);
228 if (!parent_domain) {
229 pr_err("%s: unable to get parent domain\n", node->full_name);
230 return -ENXIO;
231 }
232
233 cd = kzalloc(sizeof(struct gpcv2_irqchip_data), GFP_KERNEL);
234 if (!cd) {
235 pr_err("kzalloc failed!\n");
236 return -ENOMEM;
237 }
238
239 cd->gpc_base = of_iomap(node, 0);
240 if (!cd->gpc_base) {
241 pr_err("fsl-gpcv2: unable to map gpc registers\n");
242 kfree(cd);
243 return -ENOMEM;
244 }
245
246 domain = irq_domain_add_hierarchy(parent_domain, 0, GPC_MAX_IRQS,
247 node, &gpcv2_irqchip_data_domain_ops, cd);
248 if (!domain) {
249 iounmap(cd->gpc_base);
250 kfree(cd);
251 return -ENOMEM;
252 }
253 irq_set_default_host(domain);
254
255 /* Initially mask all interrupts */
256 for (i = 0; i < IMR_NUM; i++) {
257 writel_relaxed(~0, cd->gpc_base + GPC_IMR1_CORE0 + i * 4);
258 writel_relaxed(~0, cd->gpc_base + GPC_IMR1_CORE1 + i * 4);
259 cd->wakeup_sources[i] = ~0;
260 }
261
262 /* Let CORE0 as the default CPU to wake up by GPC */
263 cd->cpu2wakeup = GPC_IMR1_CORE0;
264
265 /*
266 * Due to hardware design failure, need to make sure GPR
267 * interrupt(#32) is unmasked during RUN mode to avoid entering
268 * DSM by mistake.
269 */
270 writel_relaxed(~0x1, cd->gpc_base + cd->cpu2wakeup);
271
272 imx_gpcv2_instance = cd;
273 register_syscore_ops(&imx_gpcv2_syscore_ops);
274
275 return 0;
276}
277
278IRQCHIP_DECLARE(imx_gpcv2, "fsl,imx7d-gpc", imx_gpcv2_irqchip_init);