xref: /openbmc/linux/arch/arm/mach-imx/gpc.c (revision 1ac731c529cd4d6adbce134754b51ff7d822b145)
1fcaf2036SThomas Gleixner // SPDX-License-Identifier: GPL-2.0-or-later
29fbbe689SShawn Guo /*
3263475d4SAnson Huang  * Copyright 2011-2013 Freescale Semiconductor, Inc.
49fbbe689SShawn Guo  * Copyright 2011 Linaro Ltd.
59fbbe689SShawn Guo  */
69fbbe689SShawn Guo 
79fbbe689SShawn Guo #include <linux/io.h>
89fbbe689SShawn Guo #include <linux/irq.h>
90cc09e85SMarc Zyngier #include <linux/irqchip.h>
109fbbe689SShawn Guo #include <linux/of.h>
119fbbe689SShawn Guo #include <linux/of_address.h>
129fbbe689SShawn Guo #include <linux/of_irq.h>
136384a04bSAnson Huang 
149a67a6fdSFabio Estevam #include "common.h"
1500eb60a8SPhilipp Zabel #include "hardware.h"
169fbbe689SShawn Guo 
17c791bbbfSAnson Huang #define GPC_CNTR		0x0
189fbbe689SShawn Guo #define GPC_IMR1		0x008
199fbbe689SShawn Guo #define GPC_PGC_CPU_PDN		0x2a0
2005136f08SAnson Huang #define GPC_PGC_CPU_PUPSCR	0x2a4
2105136f08SAnson Huang #define GPC_PGC_CPU_PDNSCR	0x2a8
2205136f08SAnson Huang #define GPC_PGC_SW2ISO_SHIFT	0x8
2305136f08SAnson Huang #define GPC_PGC_SW_SHIFT	0x0
249fbbe689SShawn Guo 
25c791bbbfSAnson Huang #define GPC_CNTR_L2_PGE_SHIFT	22
26c791bbbfSAnson Huang 
279fbbe689SShawn Guo #define IMR_NUM			4
28b923ff6aSMarc Zyngier #define GPC_MAX_IRQS		(IMR_NUM * 32)
299fbbe689SShawn Guo 
309fbbe689SShawn Guo static void __iomem *gpc_base;
319fbbe689SShawn Guo static u32 gpc_wake_irqs[IMR_NUM];
329fbbe689SShawn Guo static u32 gpc_saved_imrs[IMR_NUM];
339fbbe689SShawn Guo 
imx_gpc_set_arm_power_up_timing(u32 sw2iso,u32 sw)3405136f08SAnson Huang void imx_gpc_set_arm_power_up_timing(u32 sw2iso, u32 sw)
3505136f08SAnson Huang {
3605136f08SAnson Huang 	writel_relaxed((sw2iso << GPC_PGC_SW2ISO_SHIFT) |
3705136f08SAnson Huang 		(sw << GPC_PGC_SW_SHIFT), gpc_base + GPC_PGC_CPU_PUPSCR);
3805136f08SAnson Huang }
3905136f08SAnson Huang 
imx_gpc_set_arm_power_down_timing(u32 sw2iso,u32 sw)4005136f08SAnson Huang void imx_gpc_set_arm_power_down_timing(u32 sw2iso, u32 sw)
4105136f08SAnson Huang {
4205136f08SAnson Huang 	writel_relaxed((sw2iso << GPC_PGC_SW2ISO_SHIFT) |
4305136f08SAnson Huang 		(sw << GPC_PGC_SW_SHIFT), gpc_base + GPC_PGC_CPU_PDNSCR);
4405136f08SAnson Huang }
4505136f08SAnson Huang 
imx_gpc_set_arm_power_in_lpm(bool power_off)4605136f08SAnson Huang void imx_gpc_set_arm_power_in_lpm(bool power_off)
4705136f08SAnson Huang {
4805136f08SAnson Huang 	writel_relaxed(power_off, gpc_base + GPC_PGC_CPU_PDN);
4905136f08SAnson Huang }
5005136f08SAnson Huang 
imx_gpc_set_l2_mem_power_in_lpm(bool power_off)51c791bbbfSAnson Huang void imx_gpc_set_l2_mem_power_in_lpm(bool power_off)
52c791bbbfSAnson Huang {
53c791bbbfSAnson Huang 	u32 val;
54c791bbbfSAnson Huang 
55c791bbbfSAnson Huang 	val = readl_relaxed(gpc_base + GPC_CNTR);
56c791bbbfSAnson Huang 	val &= ~(1 << GPC_CNTR_L2_PGE_SHIFT);
57c791bbbfSAnson Huang 	if (power_off)
58c791bbbfSAnson Huang 		val |= 1 << GPC_CNTR_L2_PGE_SHIFT;
59c791bbbfSAnson Huang 	writel_relaxed(val, gpc_base + GPC_CNTR);
60c791bbbfSAnson Huang }
61c791bbbfSAnson Huang 
imx_gpc_pre_suspend(bool arm_power_off)6280c0ecdcSAnson Huang void imx_gpc_pre_suspend(bool arm_power_off)
639fbbe689SShawn Guo {
649fbbe689SShawn Guo 	void __iomem *reg_imr1 = gpc_base + GPC_IMR1;
659fbbe689SShawn Guo 	int i;
669fbbe689SShawn Guo 
679fbbe689SShawn Guo 	/* Tell GPC to power off ARM core when suspend */
6880c0ecdcSAnson Huang 	if (arm_power_off)
6905136f08SAnson Huang 		imx_gpc_set_arm_power_in_lpm(arm_power_off);
709fbbe689SShawn Guo 
719fbbe689SShawn Guo 	for (i = 0; i < IMR_NUM; i++) {
729fbbe689SShawn Guo 		gpc_saved_imrs[i] = readl_relaxed(reg_imr1 + i * 4);
739fbbe689SShawn Guo 		writel_relaxed(~gpc_wake_irqs[i], reg_imr1 + i * 4);
749fbbe689SShawn Guo 	}
759fbbe689SShawn Guo }
769fbbe689SShawn Guo 
imx_gpc_post_resume(void)779fbbe689SShawn Guo void imx_gpc_post_resume(void)
789fbbe689SShawn Guo {
799fbbe689SShawn Guo 	void __iomem *reg_imr1 = gpc_base + GPC_IMR1;
809fbbe689SShawn Guo 	int i;
819fbbe689SShawn Guo 
829fbbe689SShawn Guo 	/* Keep ARM core powered on for other low-power modes */
8305136f08SAnson Huang 	imx_gpc_set_arm_power_in_lpm(false);
849fbbe689SShawn Guo 
859fbbe689SShawn Guo 	for (i = 0; i < IMR_NUM; i++)
869fbbe689SShawn Guo 		writel_relaxed(gpc_saved_imrs[i], reg_imr1 + i * 4);
879fbbe689SShawn Guo }
889fbbe689SShawn Guo 
imx_gpc_irq_set_wake(struct irq_data * d,unsigned int on)899fbbe689SShawn Guo static int imx_gpc_irq_set_wake(struct irq_data *d, unsigned int on)
909fbbe689SShawn Guo {
91b923ff6aSMarc Zyngier 	unsigned int idx = d->hwirq / 32;
929fbbe689SShawn Guo 	u32 mask;
939fbbe689SShawn Guo 
94e2fd06f6SMarc Zyngier 	mask = 1 << d->hwirq % 32;
959fbbe689SShawn Guo 	gpc_wake_irqs[idx] = on ? gpc_wake_irqs[idx] | mask :
969fbbe689SShawn Guo 				  gpc_wake_irqs[idx] & ~mask;
979fbbe689SShawn Guo 
98b923ff6aSMarc Zyngier 	/*
99b923ff6aSMarc Zyngier 	 * Do *not* call into the parent, as the GIC doesn't have any
100b923ff6aSMarc Zyngier 	 * wake-up facility...
101b923ff6aSMarc Zyngier 	 */
1029fbbe689SShawn Guo 	return 0;
1039fbbe689SShawn Guo }
1049fbbe689SShawn Guo 
imx_gpc_mask_all(void)105263475d4SAnson Huang void imx_gpc_mask_all(void)
106263475d4SAnson Huang {
107263475d4SAnson Huang 	void __iomem *reg_imr1 = gpc_base + GPC_IMR1;
108263475d4SAnson Huang 	int i;
109263475d4SAnson Huang 
110263475d4SAnson Huang 	for (i = 0; i < IMR_NUM; i++) {
111263475d4SAnson Huang 		gpc_saved_imrs[i] = readl_relaxed(reg_imr1 + i * 4);
112263475d4SAnson Huang 		writel_relaxed(~0, reg_imr1 + i * 4);
113263475d4SAnson Huang 	}
114263475d4SAnson Huang }
115263475d4SAnson Huang 
imx_gpc_restore_all(void)116263475d4SAnson Huang void imx_gpc_restore_all(void)
117263475d4SAnson Huang {
118263475d4SAnson Huang 	void __iomem *reg_imr1 = gpc_base + GPC_IMR1;
119263475d4SAnson Huang 	int i;
120263475d4SAnson Huang 
121263475d4SAnson Huang 	for (i = 0; i < IMR_NUM; i++)
122263475d4SAnson Huang 		writel_relaxed(gpc_saved_imrs[i], reg_imr1 + i * 4);
123263475d4SAnson Huang }
124263475d4SAnson Huang 
imx_gpc_hwirq_unmask(unsigned int hwirq)12565bb688aSMarc Zyngier void imx_gpc_hwirq_unmask(unsigned int hwirq)
1269fbbe689SShawn Guo {
1279fbbe689SShawn Guo 	void __iomem *reg;
1289fbbe689SShawn Guo 	u32 val;
1299fbbe689SShawn Guo 
130b923ff6aSMarc Zyngier 	reg = gpc_base + GPC_IMR1 + hwirq / 32 * 4;
1319fbbe689SShawn Guo 	val = readl_relaxed(reg);
13265bb688aSMarc Zyngier 	val &= ~(1 << hwirq % 32);
1339fbbe689SShawn Guo 	writel_relaxed(val, reg);
1349fbbe689SShawn Guo }
1359fbbe689SShawn Guo 
imx_gpc_hwirq_mask(unsigned int hwirq)13665bb688aSMarc Zyngier void imx_gpc_hwirq_mask(unsigned int hwirq)
1379fbbe689SShawn Guo {
1389fbbe689SShawn Guo 	void __iomem *reg;
1399fbbe689SShawn Guo 	u32 val;
1409fbbe689SShawn Guo 
141b923ff6aSMarc Zyngier 	reg = gpc_base + GPC_IMR1 + hwirq / 32 * 4;
14265bb688aSMarc Zyngier 	val = readl_relaxed(reg);
14365bb688aSMarc Zyngier 	val |= 1 << (hwirq % 32);
14465bb688aSMarc Zyngier 	writel_relaxed(val, reg);
14565bb688aSMarc Zyngier }
14665bb688aSMarc Zyngier 
imx_gpc_irq_unmask(struct irq_data * d)14765bb688aSMarc Zyngier static void imx_gpc_irq_unmask(struct irq_data *d)
14865bb688aSMarc Zyngier {
14965bb688aSMarc Zyngier 	imx_gpc_hwirq_unmask(d->hwirq);
150b923ff6aSMarc Zyngier 	irq_chip_unmask_parent(d);
15165bb688aSMarc Zyngier }
15265bb688aSMarc Zyngier 
imx_gpc_irq_mask(struct irq_data * d)15365bb688aSMarc Zyngier static void imx_gpc_irq_mask(struct irq_data *d)
15465bb688aSMarc Zyngier {
15565bb688aSMarc Zyngier 	imx_gpc_hwirq_mask(d->hwirq);
156b923ff6aSMarc Zyngier 	irq_chip_mask_parent(d);
1579fbbe689SShawn Guo }
1589fbbe689SShawn Guo 
159b923ff6aSMarc Zyngier static struct irq_chip imx_gpc_chip = {
160b923ff6aSMarc Zyngier 	.name			= "GPC",
161b923ff6aSMarc Zyngier 	.irq_eoi		= irq_chip_eoi_parent,
162b923ff6aSMarc Zyngier 	.irq_mask		= imx_gpc_irq_mask,
163b923ff6aSMarc Zyngier 	.irq_unmask		= imx_gpc_irq_unmask,
164b923ff6aSMarc Zyngier 	.irq_retrigger		= irq_chip_retrigger_hierarchy,
165b923ff6aSMarc Zyngier 	.irq_set_wake		= imx_gpc_irq_set_wake,
1664699ccbfSAnson Huang 	.irq_set_type           = irq_chip_set_type_parent,
167e33b6752SMarc Zyngier #ifdef CONFIG_SMP
168e33b6752SMarc Zyngier 	.irq_set_affinity	= irq_chip_set_affinity_parent,
169e33b6752SMarc Zyngier #endif
170b923ff6aSMarc Zyngier };
171b923ff6aSMarc Zyngier 
imx_gpc_domain_translate(struct irq_domain * d,struct irq_fwspec * fwspec,unsigned long * hwirq,unsigned int * type)172f833f57fSMarc Zyngier static int imx_gpc_domain_translate(struct irq_domain *d,
173f833f57fSMarc Zyngier 				    struct irq_fwspec *fwspec,
174f833f57fSMarc Zyngier 				    unsigned long *hwirq,
175f833f57fSMarc Zyngier 				    unsigned int *type)
1769fbbe689SShawn Guo {
177f833f57fSMarc Zyngier 	if (is_of_node(fwspec->fwnode)) {
178f833f57fSMarc Zyngier 		if (fwspec->param_count != 3)
179f833f57fSMarc Zyngier 			return -EINVAL;
180b923ff6aSMarc Zyngier 
181f833f57fSMarc Zyngier 		/* No PPI should point to this domain */
182f833f57fSMarc Zyngier 		if (fwspec->param[0] != 0)
183f833f57fSMarc Zyngier 			return -EINVAL;
184f833f57fSMarc Zyngier 
185f833f57fSMarc Zyngier 		*hwirq = fwspec->param[1];
186f833f57fSMarc Zyngier 		*type = fwspec->param[2];
187b923ff6aSMarc Zyngier 		return 0;
188b923ff6aSMarc Zyngier 	}
189b923ff6aSMarc Zyngier 
190f833f57fSMarc Zyngier 	return -EINVAL;
191f833f57fSMarc Zyngier }
192f833f57fSMarc Zyngier 
imx_gpc_domain_alloc(struct irq_domain * domain,unsigned int irq,unsigned int nr_irqs,void * data)193b923ff6aSMarc Zyngier static int imx_gpc_domain_alloc(struct irq_domain *domain,
194b923ff6aSMarc Zyngier 				  unsigned int irq,
195b923ff6aSMarc Zyngier 				  unsigned int nr_irqs, void *data)
196b923ff6aSMarc Zyngier {
197f833f57fSMarc Zyngier 	struct irq_fwspec *fwspec = data;
198f833f57fSMarc Zyngier 	struct irq_fwspec parent_fwspec;
199b923ff6aSMarc Zyngier 	irq_hw_number_t hwirq;
200485863b8SShawn Guo 	int i;
2019fbbe689SShawn Guo 
202f833f57fSMarc Zyngier 	if (fwspec->param_count != 3)
203b923ff6aSMarc Zyngier 		return -EINVAL;	/* Not GIC compliant */
204f833f57fSMarc Zyngier 	if (fwspec->param[0] != 0)
205b923ff6aSMarc Zyngier 		return -EINVAL;	/* No PPI should point to this domain */
206b923ff6aSMarc Zyngier 
207f833f57fSMarc Zyngier 	hwirq = fwspec->param[1];
208b923ff6aSMarc Zyngier 	if (hwirq >= GPC_MAX_IRQS)
209b923ff6aSMarc Zyngier 		return -EINVAL;	/* Can't deal with this */
210b923ff6aSMarc Zyngier 
211b923ff6aSMarc Zyngier 	for (i = 0; i < nr_irqs; i++)
212b923ff6aSMarc Zyngier 		irq_domain_set_hwirq_and_chip(domain, irq + i, hwirq + i,
213b923ff6aSMarc Zyngier 					      &imx_gpc_chip, NULL);
214b923ff6aSMarc Zyngier 
215f833f57fSMarc Zyngier 	parent_fwspec = *fwspec;
216f833f57fSMarc Zyngier 	parent_fwspec.fwnode = domain->parent->fwnode;
217f833f57fSMarc Zyngier 	return irq_domain_alloc_irqs_parent(domain, irq, nr_irqs,
218f833f57fSMarc Zyngier 					    &parent_fwspec);
219b923ff6aSMarc Zyngier }
220b923ff6aSMarc Zyngier 
2219b589a83SKrzysztof Kozlowski static const struct irq_domain_ops imx_gpc_domain_ops = {
222f833f57fSMarc Zyngier 	.translate	= imx_gpc_domain_translate,
223b923ff6aSMarc Zyngier 	.alloc		= imx_gpc_domain_alloc,
224b923ff6aSMarc Zyngier 	.free		= irq_domain_free_irqs_common,
225b923ff6aSMarc Zyngier };
226b923ff6aSMarc Zyngier 
imx_gpc_init(struct device_node * node,struct device_node * parent)227b923ff6aSMarc Zyngier static int __init imx_gpc_init(struct device_node *node,
228b923ff6aSMarc Zyngier 			       struct device_node *parent)
229b923ff6aSMarc Zyngier {
230b923ff6aSMarc Zyngier 	struct irq_domain *parent_domain, *domain;
231b923ff6aSMarc Zyngier 	int i;
232b923ff6aSMarc Zyngier 
233b923ff6aSMarc Zyngier 	if (!parent) {
234a8e65e06SRob Herring 		pr_err("%pOF: no parent, giving up\n", node);
235b923ff6aSMarc Zyngier 		return -ENODEV;
236b923ff6aSMarc Zyngier 	}
237b923ff6aSMarc Zyngier 
238b923ff6aSMarc Zyngier 	parent_domain = irq_find_host(parent);
239b923ff6aSMarc Zyngier 	if (!parent_domain) {
240a8e65e06SRob Herring 		pr_err("%pOF: unable to obtain parent domain\n", node);
241b923ff6aSMarc Zyngier 		return -ENXIO;
242b923ff6aSMarc Zyngier 	}
243b923ff6aSMarc Zyngier 
244b923ff6aSMarc Zyngier 	gpc_base = of_iomap(node, 0);
245b923ff6aSMarc Zyngier 	if (WARN_ON(!gpc_base))
246b923ff6aSMarc Zyngier 	        return -ENOMEM;
247b923ff6aSMarc Zyngier 
248b923ff6aSMarc Zyngier 	domain = irq_domain_add_hierarchy(parent_domain, 0, GPC_MAX_IRQS,
249b923ff6aSMarc Zyngier 					  node, &imx_gpc_domain_ops,
250b923ff6aSMarc Zyngier 					  NULL);
251b923ff6aSMarc Zyngier 	if (!domain) {
252b923ff6aSMarc Zyngier 		iounmap(gpc_base);
253b923ff6aSMarc Zyngier 		return -ENOMEM;
254b923ff6aSMarc Zyngier 	}
2559fbbe689SShawn Guo 
256485863b8SShawn Guo 	/* Initially mask all interrupts */
257485863b8SShawn Guo 	for (i = 0; i < IMR_NUM; i++)
258485863b8SShawn Guo 		writel_relaxed(~0, gpc_base + GPC_IMR1 + i * 4);
259485863b8SShawn Guo 
260255c0397SPhilipp Zabel 	/*
261255c0397SPhilipp Zabel 	 * Clear the OF_POPULATED flag set in of_irq_init so that
262255c0397SPhilipp Zabel 	 * later the GPC power domain driver will not be skipped.
263255c0397SPhilipp Zabel 	 */
264255c0397SPhilipp Zabel 	of_node_clear_flag(node, OF_POPULATED);
265255c0397SPhilipp Zabel 
266b923ff6aSMarc Zyngier 	return 0;
2679fbbe689SShawn Guo }
2680cc09e85SMarc Zyngier IRQCHIP_DECLARE(imx_gpc, "fsl,imx6q-gpc", imx_gpc_init);
269b923ff6aSMarc Zyngier 
imx_gpc_check_dt(void)27014517564SMarc Zyngier void __init imx_gpc_check_dt(void)
27114517564SMarc Zyngier {
27214517564SMarc Zyngier 	struct device_node *np;
27314517564SMarc Zyngier 
27414517564SMarc Zyngier 	np = of_find_compatible_node(NULL, NULL, "fsl,imx6q-gpc");
275634a6037SLucas Stach 	if (WARN_ON(!np))
276634a6037SLucas Stach 		return;
277634a6037SLucas Stach 
278*614bef7eSRob Herring 	if (WARN_ON(!of_property_read_bool(np, "interrupt-controller"))) {
279634a6037SLucas Stach 		pr_warn("Outdated DT detected, suspend/resume will NOT work\n");
280634a6037SLucas Stach 
281634a6037SLucas Stach 		/* map GPC, so that at least CPUidle and WARs keep working */
282634a6037SLucas Stach 		gpc_base = of_iomap(np, 0);
283634a6037SLucas Stach 	}
2844a4fb661SAnson Huang 	of_node_put(np);
28514517564SMarc Zyngier }
286