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