19339d432SThomas Petazzoni /*
29339d432SThomas Petazzoni  * Marvell Armada 370 and Armada XP SoC IRQ handling
39339d432SThomas Petazzoni  *
49339d432SThomas Petazzoni  * Copyright (C) 2012 Marvell
59339d432SThomas Petazzoni  *
69339d432SThomas Petazzoni  * Lior Amsalem <alior@marvell.com>
79339d432SThomas Petazzoni  * Gregory CLEMENT <gregory.clement@free-electrons.com>
89339d432SThomas Petazzoni  * Thomas Petazzoni <thomas.petazzoni@free-electrons.com>
99339d432SThomas Petazzoni  * Ben Dooks <ben.dooks@codethink.co.uk>
109339d432SThomas Petazzoni  *
119339d432SThomas Petazzoni  * This file is licensed under the terms of the GNU General Public
129339d432SThomas Petazzoni  * License version 2.  This program is licensed "as is" without any
139339d432SThomas Petazzoni  * warranty of any kind, whether express or implied.
149339d432SThomas Petazzoni  */
159339d432SThomas Petazzoni 
169339d432SThomas Petazzoni #include <linux/kernel.h>
179339d432SThomas Petazzoni #include <linux/module.h>
189339d432SThomas Petazzoni #include <linux/init.h>
199339d432SThomas Petazzoni #include <linux/irq.h>
209339d432SThomas Petazzoni #include <linux/interrupt.h>
2141a83e06SJoel Porquet #include <linux/irqchip.h>
22bc69b8adSEzequiel Garcia #include <linux/irqchip/chained_irq.h>
23d7df84b3SThomas Petazzoni #include <linux/cpu.h>
249339d432SThomas Petazzoni #include <linux/io.h>
259339d432SThomas Petazzoni #include <linux/of_address.h>
269339d432SThomas Petazzoni #include <linux/of_irq.h>
2731f614edSThomas Petazzoni #include <linux/of_pci.h>
289339d432SThomas Petazzoni #include <linux/irqdomain.h>
2931f614edSThomas Petazzoni #include <linux/slab.h>
300f077eb5SThomas Petazzoni #include <linux/syscore_ops.h>
3131f614edSThomas Petazzoni #include <linux/msi.h>
329339d432SThomas Petazzoni #include <asm/mach/arch.h>
339339d432SThomas Petazzoni #include <asm/exception.h>
349339d432SThomas Petazzoni #include <asm/smp_plat.h>
359339d432SThomas Petazzoni #include <asm/mach/irq.h>
369339d432SThomas Petazzoni 
37054ea4ceSThomas Petazzoni /*
38054ea4ceSThomas Petazzoni  * Overall diagram of the Armada XP interrupt controller:
39054ea4ceSThomas Petazzoni  *
40054ea4ceSThomas Petazzoni  *    To CPU 0                 To CPU 1
41054ea4ceSThomas Petazzoni  *
42054ea4ceSThomas Petazzoni  *       /\                       /\
43054ea4ceSThomas Petazzoni  *       ||                       ||
44054ea4ceSThomas Petazzoni  * +---------------+     +---------------+
45054ea4ceSThomas Petazzoni  * |               |	 |               |
46054ea4ceSThomas Petazzoni  * |    per-CPU    |	 |    per-CPU    |
47054ea4ceSThomas Petazzoni  * |  mask/unmask  |	 |  mask/unmask  |
48054ea4ceSThomas Petazzoni  * |     CPU0      |	 |     CPU1      |
49054ea4ceSThomas Petazzoni  * |               |	 |               |
50054ea4ceSThomas Petazzoni  * +---------------+	 +---------------+
51054ea4ceSThomas Petazzoni  *        /\                       /\
52054ea4ceSThomas Petazzoni  *        ||                       ||
53054ea4ceSThomas Petazzoni  *        \\_______________________//
54054ea4ceSThomas Petazzoni  *                     ||
55054ea4ceSThomas Petazzoni  *            +-------------------+
56054ea4ceSThomas Petazzoni  *            |                   |
57054ea4ceSThomas Petazzoni  *            | Global interrupt  |
58054ea4ceSThomas Petazzoni  *            |    mask/unmask    |
59054ea4ceSThomas Petazzoni  *            |                   |
60054ea4ceSThomas Petazzoni  *            +-------------------+
61054ea4ceSThomas Petazzoni  *                     /\
62054ea4ceSThomas Petazzoni  *                     ||
63054ea4ceSThomas Petazzoni  *               interrupt from
64054ea4ceSThomas Petazzoni  *                   device
65054ea4ceSThomas Petazzoni  *
66054ea4ceSThomas Petazzoni  * The "global interrupt mask/unmask" is modified using the
67054ea4ceSThomas Petazzoni  * ARMADA_370_XP_INT_SET_ENABLE_OFFS and
68054ea4ceSThomas Petazzoni  * ARMADA_370_XP_INT_CLEAR_ENABLE_OFFS registers, which are relative
69054ea4ceSThomas Petazzoni  * to "main_int_base".
70054ea4ceSThomas Petazzoni  *
71054ea4ceSThomas Petazzoni  * The "per-CPU mask/unmask" is modified using the
72054ea4ceSThomas Petazzoni  * ARMADA_370_XP_INT_SET_MASK_OFFS and
73054ea4ceSThomas Petazzoni  * ARMADA_370_XP_INT_CLEAR_MASK_OFFS registers, which are relative to
74054ea4ceSThomas Petazzoni  * "per_cpu_int_base". This base address points to a special address,
75054ea4ceSThomas Petazzoni  * which automatically accesses the registers of the current CPU.
76054ea4ceSThomas Petazzoni  *
77054ea4ceSThomas Petazzoni  * The per-CPU mask/unmask can also be adjusted using the global
78054ea4ceSThomas Petazzoni  * per-interrupt ARMADA_370_XP_INT_SOURCE_CTL register, which we use
79054ea4ceSThomas Petazzoni  * to configure interrupt affinity.
80054ea4ceSThomas Petazzoni  *
81054ea4ceSThomas Petazzoni  * Due to this model, all interrupts need to be mask/unmasked at two
82054ea4ceSThomas Petazzoni  * different levels: at the global level and at the per-CPU level.
83054ea4ceSThomas Petazzoni  *
84054ea4ceSThomas Petazzoni  * This driver takes the following approach to deal with this:
85054ea4ceSThomas Petazzoni  *
86054ea4ceSThomas Petazzoni  *  - For global interrupts:
87054ea4ceSThomas Petazzoni  *
88054ea4ceSThomas Petazzoni  *    At ->map() time, a global interrupt is unmasked at the per-CPU
89054ea4ceSThomas Petazzoni  *    mask/unmask level. It is therefore unmasked at this level for
90054ea4ceSThomas Petazzoni  *    the current CPU, running the ->map() code. This allows to have
91054ea4ceSThomas Petazzoni  *    the interrupt unmasked at this level in non-SMP
92054ea4ceSThomas Petazzoni  *    configurations. In SMP configurations, the ->set_affinity()
93054ea4ceSThomas Petazzoni  *    callback is called, which using the
94054ea4ceSThomas Petazzoni  *    ARMADA_370_XP_INT_SOURCE_CTL() readjusts the per-CPU mask/unmask
95054ea4ceSThomas Petazzoni  *    for the interrupt.
96054ea4ceSThomas Petazzoni  *
97054ea4ceSThomas Petazzoni  *    The ->mask() and ->unmask() operations only mask/unmask the
98054ea4ceSThomas Petazzoni  *    interrupt at the "global" level.
99054ea4ceSThomas Petazzoni  *
100054ea4ceSThomas Petazzoni  *    So, a global interrupt is enabled at the per-CPU level as soon
101054ea4ceSThomas Petazzoni  *    as it is mapped. At run time, the masking/unmasking takes place
102054ea4ceSThomas Petazzoni  *    at the global level.
103054ea4ceSThomas Petazzoni  *
104054ea4ceSThomas Petazzoni  *  - For per-CPU interrupts
105054ea4ceSThomas Petazzoni  *
106054ea4ceSThomas Petazzoni  *    At ->map() time, a per-CPU interrupt is unmasked at the global
107054ea4ceSThomas Petazzoni  *    mask/unmask level.
108054ea4ceSThomas Petazzoni  *
109054ea4ceSThomas Petazzoni  *    The ->mask() and ->unmask() operations mask/unmask the interrupt
110054ea4ceSThomas Petazzoni  *    at the per-CPU level.
111054ea4ceSThomas Petazzoni  *
112054ea4ceSThomas Petazzoni  *    So, a per-CPU interrupt is enabled at the global level as soon
113054ea4ceSThomas Petazzoni  *    as it is mapped. At run time, the masking/unmasking takes place
114054ea4ceSThomas Petazzoni  *    at the per-CPU level.
115054ea4ceSThomas Petazzoni  */
1169339d432SThomas Petazzoni 
1179a234c9cSThomas Petazzoni /* Registers relative to main_int_base */
1189339d432SThomas Petazzoni #define ARMADA_370_XP_INT_CONTROL		(0x00)
1199a234c9cSThomas Petazzoni #define ARMADA_370_XP_SW_TRIG_INT_OFFS		(0x04)
1209339d432SThomas Petazzoni #define ARMADA_370_XP_INT_SET_ENABLE_OFFS	(0x30)
1219339d432SThomas Petazzoni #define ARMADA_370_XP_INT_CLEAR_ENABLE_OFFS	(0x34)
1229339d432SThomas Petazzoni #define ARMADA_370_XP_INT_SOURCE_CTL(irq)	(0x100 + irq*4)
1238cc3cfc5SThomas Gleixner #define ARMADA_370_XP_INT_SOURCE_CPU_MASK	0xF
124758e8366SGrzegorz Jaszczyk #define ARMADA_370_XP_INT_IRQ_FIQ_MASK(cpuid)	((BIT(0) | BIT(8)) << cpuid)
1259339d432SThomas Petazzoni 
1269a234c9cSThomas Petazzoni /* Registers relative to per_cpu_int_base */
1279a234c9cSThomas Petazzoni #define ARMADA_370_XP_IN_DRBEL_CAUSE_OFFS	(0x08)
1289a234c9cSThomas Petazzoni #define ARMADA_370_XP_IN_DRBEL_MSK_OFFS		(0x0c)
129bc69b8adSEzequiel Garcia #define ARMADA_375_PPI_CAUSE			(0x10)
1309a234c9cSThomas Petazzoni #define ARMADA_370_XP_CPU_INTACK_OFFS		(0x44)
1319a234c9cSThomas Petazzoni #define ARMADA_370_XP_INT_SET_MASK_OFFS		(0x48)
1329a234c9cSThomas Petazzoni #define ARMADA_370_XP_INT_CLEAR_MASK_OFFS	(0x4C)
1339a234c9cSThomas Petazzoni #define ARMADA_370_XP_INT_FABRIC_MASK_OFFS	(0x54)
1349a234c9cSThomas Petazzoni #define ARMADA_370_XP_INT_CAUSE_PERF(cpu)	(1 << cpu)
1359339d432SThomas Petazzoni 
1369339d432SThomas Petazzoni #define ARMADA_370_XP_MAX_PER_CPU_IRQS		(28)
1379339d432SThomas Petazzoni 
1385ec69017SThomas Petazzoni #define IPI_DOORBELL_START                      (0)
1395ec69017SThomas Petazzoni #define IPI_DOORBELL_END                        (8)
1405ec69017SThomas Petazzoni #define IPI_DOORBELL_MASK                       0xFF
14131f614edSThomas Petazzoni #define PCI_MSI_DOORBELL_START                  (16)
14231f614edSThomas Petazzoni #define PCI_MSI_DOORBELL_NR                     (16)
14331f614edSThomas Petazzoni #define PCI_MSI_DOORBELL_END                    (32)
14431f614edSThomas Petazzoni #define PCI_MSI_DOORBELL_MASK                   0xFFFF0000
1459339d432SThomas Petazzoni 
1469339d432SThomas Petazzoni static void __iomem *per_cpu_int_base;
1479339d432SThomas Petazzoni static void __iomem *main_int_base;
1489339d432SThomas Petazzoni static struct irq_domain *armada_370_xp_mpic_domain;
1490f077eb5SThomas Petazzoni static u32 doorbell_mask_reg;
1505724be84SMaxime Ripard static int parent_irq;
15131f614edSThomas Petazzoni #ifdef CONFIG_PCI_MSI
15231f614edSThomas Petazzoni static struct irq_domain *armada_370_xp_msi_domain;
153fcc392d5SThomas Petazzoni static struct irq_domain *armada_370_xp_msi_inner_domain;
15431f614edSThomas Petazzoni static DECLARE_BITMAP(msi_used, PCI_MSI_DOORBELL_NR);
15531f614edSThomas Petazzoni static DEFINE_MUTEX(msi_used_lock);
15631f614edSThomas Petazzoni static phys_addr_t msi_doorbell_addr;
15731f614edSThomas Petazzoni #endif
1589339d432SThomas Petazzoni 
1592c299de5SEzequiel Garcia static inline bool is_percpu_irq(irq_hw_number_t irq)
1602c299de5SEzequiel Garcia {
161080481f9SMaxime Ripard 	if (irq <= ARMADA_370_XP_MAX_PER_CPU_IRQS)
1622c299de5SEzequiel Garcia 		return true;
163080481f9SMaxime Ripard 
1642c299de5SEzequiel Garcia 	return false;
1652c299de5SEzequiel Garcia }
1662c299de5SEzequiel Garcia 
1679339d432SThomas Petazzoni /*
1689339d432SThomas Petazzoni  * In SMP mode:
1699339d432SThomas Petazzoni  * For shared global interrupts, mask/unmask global enable bit
1701bf25e78SLinus Torvalds  * For CPU interrupts, mask/unmask the calling CPU's bit
1719339d432SThomas Petazzoni  */
1729339d432SThomas Petazzoni static void armada_370_xp_irq_mask(struct irq_data *d)
1739339d432SThomas Petazzoni {
1749339d432SThomas Petazzoni 	irq_hw_number_t hwirq = irqd_to_hwirq(d);
1759339d432SThomas Petazzoni 
1762c299de5SEzequiel Garcia 	if (!is_percpu_irq(hwirq))
1779339d432SThomas Petazzoni 		writel(hwirq, main_int_base +
1789339d432SThomas Petazzoni 				ARMADA_370_XP_INT_CLEAR_ENABLE_OFFS);
1799339d432SThomas Petazzoni 	else
1809339d432SThomas Petazzoni 		writel(hwirq, per_cpu_int_base +
1819339d432SThomas Petazzoni 				ARMADA_370_XP_INT_SET_MASK_OFFS);
1829339d432SThomas Petazzoni }
1839339d432SThomas Petazzoni 
1849339d432SThomas Petazzoni static void armada_370_xp_irq_unmask(struct irq_data *d)
1859339d432SThomas Petazzoni {
1869339d432SThomas Petazzoni 	irq_hw_number_t hwirq = irqd_to_hwirq(d);
1879339d432SThomas Petazzoni 
1882c299de5SEzequiel Garcia 	if (!is_percpu_irq(hwirq))
1899339d432SThomas Petazzoni 		writel(hwirq, main_int_base +
1909339d432SThomas Petazzoni 				ARMADA_370_XP_INT_SET_ENABLE_OFFS);
1919339d432SThomas Petazzoni 	else
1929339d432SThomas Petazzoni 		writel(hwirq, per_cpu_int_base +
1939339d432SThomas Petazzoni 				ARMADA_370_XP_INT_CLEAR_MASK_OFFS);
1949339d432SThomas Petazzoni }
1959339d432SThomas Petazzoni 
19631f614edSThomas Petazzoni #ifdef CONFIG_PCI_MSI
19731f614edSThomas Petazzoni 
198fcc392d5SThomas Petazzoni static struct irq_chip armada_370_xp_msi_irq_chip = {
199f692a172SThomas Petazzoni 	.name = "MPIC MSI",
200fcc392d5SThomas Petazzoni 	.irq_mask = pci_msi_mask_irq,
201fcc392d5SThomas Petazzoni 	.irq_unmask = pci_msi_unmask_irq,
202fcc392d5SThomas Petazzoni };
203fcc392d5SThomas Petazzoni 
204fcc392d5SThomas Petazzoni static struct msi_domain_info armada_370_xp_msi_domain_info = {
205a71b9412SThomas Petazzoni 	.flags	= (MSI_FLAG_USE_DEF_DOM_OPS | MSI_FLAG_USE_DEF_CHIP_OPS |
206319ec8b3SStefan Roese 		   MSI_FLAG_MULTI_PCI_MSI | MSI_FLAG_PCI_MSIX),
207fcc392d5SThomas Petazzoni 	.chip	= &armada_370_xp_msi_irq_chip,
208fcc392d5SThomas Petazzoni };
209fcc392d5SThomas Petazzoni 
210fcc392d5SThomas Petazzoni static void armada_370_xp_compose_msi_msg(struct irq_data *data, struct msi_msg *msg)
211fcc392d5SThomas Petazzoni {
212fcc392d5SThomas Petazzoni 	msg->address_lo = lower_32_bits(msi_doorbell_addr);
213fcc392d5SThomas Petazzoni 	msg->address_hi = upper_32_bits(msi_doorbell_addr);
214fcc392d5SThomas Petazzoni 	msg->data = 0xf00 | (data->hwirq + PCI_MSI_DOORBELL_START);
215fcc392d5SThomas Petazzoni }
216fcc392d5SThomas Petazzoni 
217fcc392d5SThomas Petazzoni static int armada_370_xp_msi_set_affinity(struct irq_data *irq_data,
218fcc392d5SThomas Petazzoni 					  const struct cpumask *mask, bool force)
219fcc392d5SThomas Petazzoni {
220fcc392d5SThomas Petazzoni 	 return -EINVAL;
221fcc392d5SThomas Petazzoni }
222fcc392d5SThomas Petazzoni 
223fcc392d5SThomas Petazzoni static struct irq_chip armada_370_xp_msi_bottom_irq_chip = {
224f692a172SThomas Petazzoni 	.name			= "MPIC MSI",
225fcc392d5SThomas Petazzoni 	.irq_compose_msi_msg	= armada_370_xp_compose_msi_msg,
226fcc392d5SThomas Petazzoni 	.irq_set_affinity	= armada_370_xp_msi_set_affinity,
227fcc392d5SThomas Petazzoni };
228fcc392d5SThomas Petazzoni 
229fcc392d5SThomas Petazzoni static int armada_370_xp_msi_alloc(struct irq_domain *domain, unsigned int virq,
230fcc392d5SThomas Petazzoni 				   unsigned int nr_irqs, void *args)
23131f614edSThomas Petazzoni {
232a71b9412SThomas Petazzoni 	int hwirq, i;
23331f614edSThomas Petazzoni 
23431f614edSThomas Petazzoni 	mutex_lock(&msi_used_lock);
235a71b9412SThomas Petazzoni 
236a71b9412SThomas Petazzoni 	hwirq = bitmap_find_next_zero_area(msi_used, PCI_MSI_DOORBELL_NR,
237a71b9412SThomas Petazzoni 					   0, nr_irqs, 0);
238fcc392d5SThomas Petazzoni 	if (hwirq >= PCI_MSI_DOORBELL_NR) {
239fcc392d5SThomas Petazzoni 		mutex_unlock(&msi_used_lock);
240fcc392d5SThomas Petazzoni 		return -ENOSPC;
241fcc392d5SThomas Petazzoni 	}
242fcc392d5SThomas Petazzoni 
243a71b9412SThomas Petazzoni 	bitmap_set(msi_used, hwirq, nr_irqs);
24431f614edSThomas Petazzoni 	mutex_unlock(&msi_used_lock);
24531f614edSThomas Petazzoni 
246a71b9412SThomas Petazzoni 	for (i = 0; i < nr_irqs; i++) {
247a71b9412SThomas Petazzoni 		irq_domain_set_info(domain, virq + i, hwirq + i,
248a71b9412SThomas Petazzoni 				    &armada_370_xp_msi_bottom_irq_chip,
249fcc392d5SThomas Petazzoni 				    domain->host_data, handle_simple_irq,
250fcc392d5SThomas Petazzoni 				    NULL, NULL);
251a71b9412SThomas Petazzoni 	}
252fcc392d5SThomas Petazzoni 
25331f614edSThomas Petazzoni 	return hwirq;
25431f614edSThomas Petazzoni }
25531f614edSThomas Petazzoni 
256fcc392d5SThomas Petazzoni static void armada_370_xp_msi_free(struct irq_domain *domain,
257fcc392d5SThomas Petazzoni 				   unsigned int virq, unsigned int nr_irqs)
25831f614edSThomas Petazzoni {
259fcc392d5SThomas Petazzoni 	struct irq_data *d = irq_domain_get_irq_data(domain, virq);
260fcc392d5SThomas Petazzoni 
26131f614edSThomas Petazzoni 	mutex_lock(&msi_used_lock);
262a71b9412SThomas Petazzoni 	bitmap_clear(msi_used, d->hwirq, nr_irqs);
26331f614edSThomas Petazzoni 	mutex_unlock(&msi_used_lock);
26431f614edSThomas Petazzoni }
26531f614edSThomas Petazzoni 
266fcc392d5SThomas Petazzoni static const struct irq_domain_ops armada_370_xp_msi_domain_ops = {
267fcc392d5SThomas Petazzoni 	.alloc	= armada_370_xp_msi_alloc,
268fcc392d5SThomas Petazzoni 	.free	= armada_370_xp_msi_free,
26931f614edSThomas Petazzoni };
27031f614edSThomas Petazzoni 
27131f614edSThomas Petazzoni static int armada_370_xp_msi_init(struct device_node *node,
27231f614edSThomas Petazzoni 				  phys_addr_t main_int_phys_base)
27331f614edSThomas Petazzoni {
27431f614edSThomas Petazzoni 	u32 reg;
27531f614edSThomas Petazzoni 
27631f614edSThomas Petazzoni 	msi_doorbell_addr = main_int_phys_base +
27731f614edSThomas Petazzoni 		ARMADA_370_XP_SW_TRIG_INT_OFFS;
27831f614edSThomas Petazzoni 
279fcc392d5SThomas Petazzoni 	armada_370_xp_msi_inner_domain =
280fcc392d5SThomas Petazzoni 		irq_domain_add_linear(NULL, PCI_MSI_DOORBELL_NR,
281fcc392d5SThomas Petazzoni 				      &armada_370_xp_msi_domain_ops, NULL);
282fcc392d5SThomas Petazzoni 	if (!armada_370_xp_msi_inner_domain)
28331f614edSThomas Petazzoni 		return -ENOMEM;
28431f614edSThomas Petazzoni 
28531f614edSThomas Petazzoni 	armada_370_xp_msi_domain =
286fcc392d5SThomas Petazzoni 		pci_msi_create_irq_domain(of_node_to_fwnode(node),
287fcc392d5SThomas Petazzoni 					  &armada_370_xp_msi_domain_info,
288fcc392d5SThomas Petazzoni 					  armada_370_xp_msi_inner_domain);
28931f614edSThomas Petazzoni 	if (!armada_370_xp_msi_domain) {
290fcc392d5SThomas Petazzoni 		irq_domain_remove(armada_370_xp_msi_inner_domain);
29131f614edSThomas Petazzoni 		return -ENOMEM;
29231f614edSThomas Petazzoni 	}
29331f614edSThomas Petazzoni 
29431f614edSThomas Petazzoni 	reg = readl(per_cpu_int_base + ARMADA_370_XP_IN_DRBEL_MSK_OFFS)
29531f614edSThomas Petazzoni 		| PCI_MSI_DOORBELL_MASK;
29631f614edSThomas Petazzoni 
29731f614edSThomas Petazzoni 	writel(reg, per_cpu_int_base +
29831f614edSThomas Petazzoni 	       ARMADA_370_XP_IN_DRBEL_MSK_OFFS);
29931f614edSThomas Petazzoni 
30031f614edSThomas Petazzoni 	/* Unmask IPI interrupt */
30131f614edSThomas Petazzoni 	writel(1, per_cpu_int_base + ARMADA_370_XP_INT_CLEAR_MASK_OFFS);
30231f614edSThomas Petazzoni 
30331f614edSThomas Petazzoni 	return 0;
30431f614edSThomas Petazzoni }
30531f614edSThomas Petazzoni #else
30631f614edSThomas Petazzoni static inline int armada_370_xp_msi_init(struct device_node *node,
30731f614edSThomas Petazzoni 					 phys_addr_t main_int_phys_base)
30831f614edSThomas Petazzoni {
30931f614edSThomas Petazzoni 	return 0;
31031f614edSThomas Petazzoni }
31131f614edSThomas Petazzoni #endif
31231f614edSThomas Petazzoni 
313f02147ddSMarc Zyngier static void armada_xp_mpic_perf_init(void)
314f02147ddSMarc Zyngier {
315f02147ddSMarc Zyngier 	unsigned long cpuid = cpu_logical_map(smp_processor_id());
316f02147ddSMarc Zyngier 
317f02147ddSMarc Zyngier 	/* Enable Performance Counter Overflow interrupts */
318f02147ddSMarc Zyngier 	writel(ARMADA_370_XP_INT_CAUSE_PERF(cpuid),
319f02147ddSMarc Zyngier 	       per_cpu_int_base + ARMADA_370_XP_INT_FABRIC_MASK_OFFS);
320f02147ddSMarc Zyngier }
321f02147ddSMarc Zyngier 
3229339d432SThomas Petazzoni #ifdef CONFIG_SMP
323f02147ddSMarc Zyngier static struct irq_domain *ipi_domain;
324f02147ddSMarc Zyngier 
325f02147ddSMarc Zyngier static void armada_370_xp_ipi_mask(struct irq_data *d)
326f02147ddSMarc Zyngier {
327f02147ddSMarc Zyngier 	u32 reg;
328f02147ddSMarc Zyngier 	reg = readl(per_cpu_int_base + ARMADA_370_XP_IN_DRBEL_MSK_OFFS);
329f02147ddSMarc Zyngier 	reg &= ~BIT(d->hwirq);
330f02147ddSMarc Zyngier 	writel(reg, per_cpu_int_base + ARMADA_370_XP_IN_DRBEL_MSK_OFFS);
331f02147ddSMarc Zyngier }
332f02147ddSMarc Zyngier 
333f02147ddSMarc Zyngier static void armada_370_xp_ipi_unmask(struct irq_data *d)
334f02147ddSMarc Zyngier {
335f02147ddSMarc Zyngier 	u32 reg;
336f02147ddSMarc Zyngier 	reg = readl(per_cpu_int_base + ARMADA_370_XP_IN_DRBEL_MSK_OFFS);
337f02147ddSMarc Zyngier 	reg |= BIT(d->hwirq);
338f02147ddSMarc Zyngier 	writel(reg, per_cpu_int_base + ARMADA_370_XP_IN_DRBEL_MSK_OFFS);
339f02147ddSMarc Zyngier }
340f02147ddSMarc Zyngier 
341f02147ddSMarc Zyngier static void armada_370_xp_ipi_send_mask(struct irq_data *d,
342f02147ddSMarc Zyngier 					const struct cpumask *mask)
343f02147ddSMarc Zyngier {
344f02147ddSMarc Zyngier 	unsigned long map = 0;
345f02147ddSMarc Zyngier 	int cpu;
346f02147ddSMarc Zyngier 
347f02147ddSMarc Zyngier 	/* Convert our logical CPU mask into a physical one. */
348f02147ddSMarc Zyngier 	for_each_cpu(cpu, mask)
349f02147ddSMarc Zyngier 		map |= 1 << cpu_logical_map(cpu);
350f02147ddSMarc Zyngier 
351f02147ddSMarc Zyngier 	/*
352f02147ddSMarc Zyngier 	 * Ensure that stores to Normal memory are visible to the
353f02147ddSMarc Zyngier 	 * other CPUs before issuing the IPI.
354f02147ddSMarc Zyngier 	 */
355f02147ddSMarc Zyngier 	dsb();
356f02147ddSMarc Zyngier 
357f02147ddSMarc Zyngier 	/* submit softirq */
358f02147ddSMarc Zyngier 	writel((map << 8) | d->hwirq, main_int_base +
359f02147ddSMarc Zyngier 		ARMADA_370_XP_SW_TRIG_INT_OFFS);
360f02147ddSMarc Zyngier }
361f02147ddSMarc Zyngier 
362f02147ddSMarc Zyngier static void armada_370_xp_ipi_eoi(struct irq_data *d)
363f02147ddSMarc Zyngier {
364f02147ddSMarc Zyngier 	writel(~BIT(d->hwirq), per_cpu_int_base + ARMADA_370_XP_IN_DRBEL_CAUSE_OFFS);
365f02147ddSMarc Zyngier }
366f02147ddSMarc Zyngier 
367f02147ddSMarc Zyngier static struct irq_chip ipi_irqchip = {
368f02147ddSMarc Zyngier 	.name		= "IPI",
369f02147ddSMarc Zyngier 	.irq_mask	= armada_370_xp_ipi_mask,
370f02147ddSMarc Zyngier 	.irq_unmask	= armada_370_xp_ipi_unmask,
371f02147ddSMarc Zyngier 	.irq_eoi	= armada_370_xp_ipi_eoi,
372f02147ddSMarc Zyngier 	.ipi_send_mask	= armada_370_xp_ipi_send_mask,
373f02147ddSMarc Zyngier };
374f02147ddSMarc Zyngier 
375f02147ddSMarc Zyngier static int armada_370_xp_ipi_alloc(struct irq_domain *d,
376f02147ddSMarc Zyngier 					 unsigned int virq,
377f02147ddSMarc Zyngier 					 unsigned int nr_irqs, void *args)
378f02147ddSMarc Zyngier {
379f02147ddSMarc Zyngier 	int i;
380f02147ddSMarc Zyngier 
381f02147ddSMarc Zyngier 	for (i = 0; i < nr_irqs; i++) {
382f02147ddSMarc Zyngier 		irq_set_percpu_devid(virq + i);
383f02147ddSMarc Zyngier 		irq_domain_set_info(d, virq + i, i, &ipi_irqchip,
384f02147ddSMarc Zyngier 				    d->host_data,
385f02147ddSMarc Zyngier 				    handle_percpu_devid_fasteoi_ipi,
386f02147ddSMarc Zyngier 				    NULL, NULL);
387f02147ddSMarc Zyngier 	}
388f02147ddSMarc Zyngier 
389f02147ddSMarc Zyngier 	return 0;
390f02147ddSMarc Zyngier }
391f02147ddSMarc Zyngier 
392f02147ddSMarc Zyngier static void armada_370_xp_ipi_free(struct irq_domain *d,
393f02147ddSMarc Zyngier 					 unsigned int virq,
394f02147ddSMarc Zyngier 					 unsigned int nr_irqs)
395f02147ddSMarc Zyngier {
396f02147ddSMarc Zyngier 	/* Not freeing IPIs */
397f02147ddSMarc Zyngier }
398f02147ddSMarc Zyngier 
399f02147ddSMarc Zyngier static const struct irq_domain_ops ipi_domain_ops = {
400f02147ddSMarc Zyngier 	.alloc	= armada_370_xp_ipi_alloc,
401f02147ddSMarc Zyngier 	.free	= armada_370_xp_ipi_free,
402f02147ddSMarc Zyngier };
403f02147ddSMarc Zyngier 
404f02147ddSMarc Zyngier static void ipi_resume(void)
405f02147ddSMarc Zyngier {
406f02147ddSMarc Zyngier 	int i;
407f02147ddSMarc Zyngier 
408f02147ddSMarc Zyngier 	for (i = 0; i < IPI_DOORBELL_END; i++) {
409f02147ddSMarc Zyngier 		int irq;
410f02147ddSMarc Zyngier 
411f02147ddSMarc Zyngier 		irq = irq_find_mapping(ipi_domain, i);
412f02147ddSMarc Zyngier 		if (irq <= 0)
413f02147ddSMarc Zyngier 			continue;
414f02147ddSMarc Zyngier 		if (irq_percpu_is_enabled(irq)) {
415f02147ddSMarc Zyngier 			struct irq_data *d;
416f02147ddSMarc Zyngier 			d = irq_domain_get_irq_data(ipi_domain, irq);
417f02147ddSMarc Zyngier 			armada_370_xp_ipi_unmask(d);
418f02147ddSMarc Zyngier 		}
419f02147ddSMarc Zyngier 	}
420f02147ddSMarc Zyngier }
421f02147ddSMarc Zyngier 
422f02147ddSMarc Zyngier static __init void armada_xp_ipi_init(struct device_node *node)
423f02147ddSMarc Zyngier {
424f02147ddSMarc Zyngier 	int base_ipi;
425f02147ddSMarc Zyngier 
426f02147ddSMarc Zyngier 	ipi_domain = irq_domain_create_linear(of_node_to_fwnode(node),
427f02147ddSMarc Zyngier 					      IPI_DOORBELL_END,
428f02147ddSMarc Zyngier 					      &ipi_domain_ops, NULL);
429f02147ddSMarc Zyngier 	if (WARN_ON(!ipi_domain))
430f02147ddSMarc Zyngier 		return;
431f02147ddSMarc Zyngier 
432f02147ddSMarc Zyngier 	irq_domain_update_bus_token(ipi_domain, DOMAIN_BUS_IPI);
433f02147ddSMarc Zyngier 	base_ipi = __irq_domain_alloc_irqs(ipi_domain, -1, IPI_DOORBELL_END,
434f02147ddSMarc Zyngier 					   NUMA_NO_NODE, NULL, false, NULL);
435f02147ddSMarc Zyngier 	if (WARN_ON(!base_ipi))
436f02147ddSMarc Zyngier 		return;
437f02147ddSMarc Zyngier 
438f02147ddSMarc Zyngier 	set_smp_ipi_range(base_ipi, IPI_DOORBELL_END);
439f02147ddSMarc Zyngier }
440f02147ddSMarc Zyngier 
44119e61d41SArnaud Ebalard static DEFINE_RAW_SPINLOCK(irq_controller_lock);
44219e61d41SArnaud Ebalard 
4439339d432SThomas Petazzoni static int armada_xp_set_affinity(struct irq_data *d,
4449339d432SThomas Petazzoni 				  const struct cpumask *mask_val, bool force)
4459339d432SThomas Petazzoni {
4469339d432SThomas Petazzoni 	irq_hw_number_t hwirq = irqd_to_hwirq(d);
4478cc3cfc5SThomas Gleixner 	unsigned long reg, mask;
4489339d432SThomas Petazzoni 	int cpu;
4499339d432SThomas Petazzoni 
4508cc3cfc5SThomas Gleixner 	/* Select a single core from the affinity mask which is online */
4518cc3cfc5SThomas Gleixner 	cpu = cpumask_any_and(mask_val, cpu_online_mask);
4528cc3cfc5SThomas Gleixner 	mask = 1UL << cpu_logical_map(cpu);
4539339d432SThomas Petazzoni 
4549339d432SThomas Petazzoni 	raw_spin_lock(&irq_controller_lock);
4559339d432SThomas Petazzoni 	reg = readl(main_int_base + ARMADA_370_XP_INT_SOURCE_CTL(hwirq));
4568cc3cfc5SThomas Gleixner 	reg = (reg & (~ARMADA_370_XP_INT_SOURCE_CPU_MASK)) | mask;
4579339d432SThomas Petazzoni 	writel(reg, main_int_base + ARMADA_370_XP_INT_SOURCE_CTL(hwirq));
4589339d432SThomas Petazzoni 	raw_spin_unlock(&irq_controller_lock);
4599339d432SThomas Petazzoni 
460e31793a3SMarc Zyngier 	irq_data_update_effective_affinity(d, cpumask_of(cpu));
461e31793a3SMarc Zyngier 
4621dacf194SThomas Petazzoni 	return IRQ_SET_MASK_OK;
4639339d432SThomas Petazzoni }
464f02147ddSMarc Zyngier 
465f02147ddSMarc Zyngier static void armada_xp_mpic_smp_cpu_init(void)
466f02147ddSMarc Zyngier {
467f02147ddSMarc Zyngier 	u32 control;
468f02147ddSMarc Zyngier 	int nr_irqs, i;
469f02147ddSMarc Zyngier 
470f02147ddSMarc Zyngier 	control = readl(main_int_base + ARMADA_370_XP_INT_CONTROL);
471f02147ddSMarc Zyngier 	nr_irqs = (control >> 2) & 0x3ff;
472f02147ddSMarc Zyngier 
473f02147ddSMarc Zyngier 	for (i = 0; i < nr_irqs; i++)
474f02147ddSMarc Zyngier 		writel(i, per_cpu_int_base + ARMADA_370_XP_INT_SET_MASK_OFFS);
475f02147ddSMarc Zyngier 
476f02147ddSMarc Zyngier 	/* Disable all IPIs */
477f02147ddSMarc Zyngier 	writel(0, per_cpu_int_base + ARMADA_370_XP_IN_DRBEL_MSK_OFFS);
478f02147ddSMarc Zyngier 
479f02147ddSMarc Zyngier 	/* Clear pending IPIs */
480f02147ddSMarc Zyngier 	writel(0, per_cpu_int_base + ARMADA_370_XP_IN_DRBEL_CAUSE_OFFS);
481f02147ddSMarc Zyngier 
482f02147ddSMarc Zyngier 	/* Unmask IPI interrupt */
483f02147ddSMarc Zyngier 	writel(0, per_cpu_int_base + ARMADA_370_XP_INT_CLEAR_MASK_OFFS);
484f02147ddSMarc Zyngier }
485f02147ddSMarc Zyngier 
486f02147ddSMarc Zyngier static void armada_xp_mpic_reenable_percpu(void)
487f02147ddSMarc Zyngier {
488f02147ddSMarc Zyngier 	unsigned int irq;
489f02147ddSMarc Zyngier 
490f02147ddSMarc Zyngier 	/* Re-enable per-CPU interrupts that were enabled before suspend */
491f02147ddSMarc Zyngier 	for (irq = 0; irq < ARMADA_370_XP_MAX_PER_CPU_IRQS; irq++) {
492f02147ddSMarc Zyngier 		struct irq_data *data;
493f02147ddSMarc Zyngier 		int virq;
494f02147ddSMarc Zyngier 
495f02147ddSMarc Zyngier 		virq = irq_linear_revmap(armada_370_xp_mpic_domain, irq);
496f02147ddSMarc Zyngier 		if (virq == 0)
497f02147ddSMarc Zyngier 			continue;
498f02147ddSMarc Zyngier 
499f02147ddSMarc Zyngier 		data = irq_get_irq_data(virq);
500f02147ddSMarc Zyngier 
501f02147ddSMarc Zyngier 		if (!irq_percpu_is_enabled(virq))
502f02147ddSMarc Zyngier 			continue;
503f02147ddSMarc Zyngier 
504f02147ddSMarc Zyngier 		armada_370_xp_irq_unmask(data);
505f02147ddSMarc Zyngier 	}
506f02147ddSMarc Zyngier 
507f02147ddSMarc Zyngier 	ipi_resume();
508f02147ddSMarc Zyngier }
509f02147ddSMarc Zyngier 
510f02147ddSMarc Zyngier static int armada_xp_mpic_starting_cpu(unsigned int cpu)
511f02147ddSMarc Zyngier {
512f02147ddSMarc Zyngier 	armada_xp_mpic_perf_init();
513f02147ddSMarc Zyngier 	armada_xp_mpic_smp_cpu_init();
514f02147ddSMarc Zyngier 	armada_xp_mpic_reenable_percpu();
515f02147ddSMarc Zyngier 	return 0;
516f02147ddSMarc Zyngier }
517f02147ddSMarc Zyngier 
518f02147ddSMarc Zyngier static int mpic_cascaded_starting_cpu(unsigned int cpu)
519f02147ddSMarc Zyngier {
520f02147ddSMarc Zyngier 	armada_xp_mpic_perf_init();
521f02147ddSMarc Zyngier 	armada_xp_mpic_reenable_percpu();
522f02147ddSMarc Zyngier 	enable_percpu_irq(parent_irq, IRQ_TYPE_NONE);
523f02147ddSMarc Zyngier 	return 0;
524f02147ddSMarc Zyngier }
525f02147ddSMarc Zyngier #else
526f02147ddSMarc Zyngier static void armada_xp_mpic_smp_cpu_init(void) {}
527f02147ddSMarc Zyngier static void ipi_resume(void) {}
5289339d432SThomas Petazzoni #endif
5299339d432SThomas Petazzoni 
5309339d432SThomas Petazzoni static struct irq_chip armada_370_xp_irq_chip = {
531f692a172SThomas Petazzoni 	.name		= "MPIC",
5329339d432SThomas Petazzoni 	.irq_mask       = armada_370_xp_irq_mask,
5339339d432SThomas Petazzoni 	.irq_mask_ack   = armada_370_xp_irq_mask,
5349339d432SThomas Petazzoni 	.irq_unmask     = armada_370_xp_irq_unmask,
5359339d432SThomas Petazzoni #ifdef CONFIG_SMP
5369339d432SThomas Petazzoni 	.irq_set_affinity = armada_xp_set_affinity,
5379339d432SThomas Petazzoni #endif
5380d8e1d80SGregory CLEMENT 	.flags		= IRQCHIP_SKIP_SET_WAKE | IRQCHIP_MASK_ON_SUSPEND,
5399339d432SThomas Petazzoni };
5409339d432SThomas Petazzoni 
5419339d432SThomas Petazzoni static int armada_370_xp_mpic_irq_map(struct irq_domain *h,
5429339d432SThomas Petazzoni 				      unsigned int virq, irq_hw_number_t hw)
5439339d432SThomas Petazzoni {
5449339d432SThomas Petazzoni 	armada_370_xp_irq_mask(irq_get_irq_data(virq));
5452c299de5SEzequiel Garcia 	if (!is_percpu_irq(hw))
5461bf25e78SLinus Torvalds 		writel(hw, per_cpu_int_base +
5471bf25e78SLinus Torvalds 			ARMADA_370_XP_INT_CLEAR_MASK_OFFS);
5481bf25e78SLinus Torvalds 	else
5499339d432SThomas Petazzoni 		writel(hw, main_int_base + ARMADA_370_XP_INT_SET_ENABLE_OFFS);
5509339d432SThomas Petazzoni 	irq_set_status_flags(virq, IRQ_LEVEL);
5519339d432SThomas Petazzoni 
5522c299de5SEzequiel Garcia 	if (is_percpu_irq(hw)) {
5539339d432SThomas Petazzoni 		irq_set_percpu_devid(virq);
5549339d432SThomas Petazzoni 		irq_set_chip_and_handler(virq, &armada_370_xp_irq_chip,
5559339d432SThomas Petazzoni 					handle_percpu_devid_irq);
5569339d432SThomas Petazzoni 	} else {
5579339d432SThomas Petazzoni 		irq_set_chip_and_handler(virq, &armada_370_xp_irq_chip,
5589339d432SThomas Petazzoni 					handle_level_irq);
559e31793a3SMarc Zyngier 		irqd_set_single_target(irq_desc_get_irq_data(irq_to_desc(virq)));
5609339d432SThomas Petazzoni 	}
561d17cab44SRob Herring 	irq_set_probe(virq);
5629339d432SThomas Petazzoni 
5639339d432SThomas Petazzoni 	return 0;
5649339d432SThomas Petazzoni }
5659339d432SThomas Petazzoni 
56696009736SKrzysztof Kozlowski static const struct irq_domain_ops armada_370_xp_mpic_irq_ops = {
5679339d432SThomas Petazzoni 	.map = armada_370_xp_mpic_irq_map,
5689339d432SThomas Petazzoni 	.xlate = irq_domain_xlate_onecell,
5699339d432SThomas Petazzoni };
5709339d432SThomas Petazzoni 
57131f614edSThomas Petazzoni #ifdef CONFIG_PCI_MSI
572bc69b8adSEzequiel Garcia static void armada_370_xp_handle_msi_irq(struct pt_regs *regs, bool is_chained)
5739b8cf779SEzequiel Garcia {
57431f614edSThomas Petazzoni 	u32 msimask, msinr;
57531f614edSThomas Petazzoni 
57631f614edSThomas Petazzoni 	msimask = readl_relaxed(per_cpu_int_base +
57731f614edSThomas Petazzoni 				ARMADA_370_XP_IN_DRBEL_CAUSE_OFFS)
57831f614edSThomas Petazzoni 		& PCI_MSI_DOORBELL_MASK;
57931f614edSThomas Petazzoni 
580c7f7bd4aSLior Amsalem 	writel(~msimask, per_cpu_int_base +
58131f614edSThomas Petazzoni 	       ARMADA_370_XP_IN_DRBEL_CAUSE_OFFS);
58231f614edSThomas Petazzoni 
58331f614edSThomas Petazzoni 	for (msinr = PCI_MSI_DOORBELL_START;
58431f614edSThomas Petazzoni 	     msinr < PCI_MSI_DOORBELL_END; msinr++) {
58531f614edSThomas Petazzoni 		int irq;
58631f614edSThomas Petazzoni 
58731f614edSThomas Petazzoni 		if (!(msimask & BIT(msinr)))
58831f614edSThomas Petazzoni 			continue;
58931f614edSThomas Petazzoni 
590e89c6a06SMarc Zyngier 		if (is_chained) {
591fcc392d5SThomas Petazzoni 			irq = irq_find_mapping(armada_370_xp_msi_inner_domain,
5920636bab6SThomas Petazzoni 					       msinr - PCI_MSI_DOORBELL_START);
593bc69b8adSEzequiel Garcia 			generic_handle_irq(irq);
594e89c6a06SMarc Zyngier 		} else {
5950636bab6SThomas Petazzoni 			irq = msinr - PCI_MSI_DOORBELL_START;
596fcc392d5SThomas Petazzoni 			handle_domain_irq(armada_370_xp_msi_inner_domain,
597e89c6a06SMarc Zyngier 					  irq, regs);
598e89c6a06SMarc Zyngier 		}
59931f614edSThomas Petazzoni 	}
60031f614edSThomas Petazzoni }
6019b8cf779SEzequiel Garcia #else
602bc69b8adSEzequiel Garcia static void armada_370_xp_handle_msi_irq(struct pt_regs *r, bool b) {}
60331f614edSThomas Petazzoni #endif
60431f614edSThomas Petazzoni 
605bd0b9ac4SThomas Gleixner static void armada_370_xp_mpic_handle_cascade_irq(struct irq_desc *desc)
606bc69b8adSEzequiel Garcia {
6075b29264cSJiang Liu 	struct irq_chip *chip = irq_desc_get_chip(desc);
608758e8366SGrzegorz Jaszczyk 	unsigned long irqmap, irqn, irqsrc, cpuid;
609bc69b8adSEzequiel Garcia 	unsigned int cascade_irq;
610bc69b8adSEzequiel Garcia 
611bc69b8adSEzequiel Garcia 	chained_irq_enter(chip, desc);
612bc69b8adSEzequiel Garcia 
613bc69b8adSEzequiel Garcia 	irqmap = readl_relaxed(per_cpu_int_base + ARMADA_375_PPI_CAUSE);
614758e8366SGrzegorz Jaszczyk 	cpuid = cpu_logical_map(smp_processor_id());
615bc69b8adSEzequiel Garcia 
616bc69b8adSEzequiel Garcia 	for_each_set_bit(irqn, &irqmap, BITS_PER_LONG) {
617758e8366SGrzegorz Jaszczyk 		irqsrc = readl_relaxed(main_int_base +
618758e8366SGrzegorz Jaszczyk 				       ARMADA_370_XP_INT_SOURCE_CTL(irqn));
619758e8366SGrzegorz Jaszczyk 
620758e8366SGrzegorz Jaszczyk 		/* Check if the interrupt is not masked on current CPU.
621758e8366SGrzegorz Jaszczyk 		 * Test IRQ (0-1) and FIQ (8-9) mask bits.
622758e8366SGrzegorz Jaszczyk 		 */
623758e8366SGrzegorz Jaszczyk 		if (!(irqsrc & ARMADA_370_XP_INT_IRQ_FIQ_MASK(cpuid)))
624758e8366SGrzegorz Jaszczyk 			continue;
625758e8366SGrzegorz Jaszczyk 
626758e8366SGrzegorz Jaszczyk 		if (irqn == 1) {
627758e8366SGrzegorz Jaszczyk 			armada_370_xp_handle_msi_irq(NULL, true);
628758e8366SGrzegorz Jaszczyk 			continue;
629758e8366SGrzegorz Jaszczyk 		}
630758e8366SGrzegorz Jaszczyk 
631bc69b8adSEzequiel Garcia 		cascade_irq = irq_find_mapping(armada_370_xp_mpic_domain, irqn);
632bc69b8adSEzequiel Garcia 		generic_handle_irq(cascade_irq);
633bc69b8adSEzequiel Garcia 	}
634bc69b8adSEzequiel Garcia 
635bc69b8adSEzequiel Garcia 	chained_irq_exit(chip, desc);
636bc69b8adSEzequiel Garcia }
637bc69b8adSEzequiel Garcia 
6388783dd3aSStephen Boyd static void __exception_irq_entry
6399b8cf779SEzequiel Garcia armada_370_xp_handle_irq(struct pt_regs *regs)
6409b8cf779SEzequiel Garcia {
6419b8cf779SEzequiel Garcia 	u32 irqstat, irqnr;
6429b8cf779SEzequiel Garcia 
6439b8cf779SEzequiel Garcia 	do {
6449b8cf779SEzequiel Garcia 		irqstat = readl_relaxed(per_cpu_int_base +
6459b8cf779SEzequiel Garcia 					ARMADA_370_XP_CPU_INTACK_OFFS);
6469b8cf779SEzequiel Garcia 		irqnr = irqstat & 0x3FF;
6479b8cf779SEzequiel Garcia 
6489b8cf779SEzequiel Garcia 		if (irqnr > 1022)
6499b8cf779SEzequiel Garcia 			break;
6509b8cf779SEzequiel Garcia 
6519b8cf779SEzequiel Garcia 		if (irqnr > 1) {
652e89c6a06SMarc Zyngier 			handle_domain_irq(armada_370_xp_mpic_domain,
653e89c6a06SMarc Zyngier 					  irqnr, regs);
6549b8cf779SEzequiel Garcia 			continue;
6559b8cf779SEzequiel Garcia 		}
6569b8cf779SEzequiel Garcia 
6579b8cf779SEzequiel Garcia 		/* MSI handling */
6589b8cf779SEzequiel Garcia 		if (irqnr == 1)
659bc69b8adSEzequiel Garcia 			armada_370_xp_handle_msi_irq(regs, false);
6609b8cf779SEzequiel Garcia 
6619339d432SThomas Petazzoni #ifdef CONFIG_SMP
6629339d432SThomas Petazzoni 		/* IPI Handling */
6639339d432SThomas Petazzoni 		if (irqnr == 0) {
664f02147ddSMarc Zyngier 			unsigned long ipimask;
665f02147ddSMarc Zyngier 			int ipi;
6669339d432SThomas Petazzoni 
6679339d432SThomas Petazzoni 			ipimask = readl_relaxed(per_cpu_int_base +
6689339d432SThomas Petazzoni 						ARMADA_370_XP_IN_DRBEL_CAUSE_OFFS)
6695ec69017SThomas Petazzoni 				& IPI_DOORBELL_MASK;
6709339d432SThomas Petazzoni 
671f02147ddSMarc Zyngier 			for_each_set_bit(ipi, &ipimask, IPI_DOORBELL_END)
672f02147ddSMarc Zyngier 				handle_domain_irq(ipi_domain, ipi, regs);
6739339d432SThomas Petazzoni 		}
6749339d432SThomas Petazzoni #endif
6759339d432SThomas Petazzoni 
6769339d432SThomas Petazzoni 	} while (1);
6779339d432SThomas Petazzoni }
6789339d432SThomas Petazzoni 
6790f077eb5SThomas Petazzoni static int armada_370_xp_mpic_suspend(void)
6800f077eb5SThomas Petazzoni {
6810f077eb5SThomas Petazzoni 	doorbell_mask_reg = readl(per_cpu_int_base +
6820f077eb5SThomas Petazzoni 				  ARMADA_370_XP_IN_DRBEL_MSK_OFFS);
6830f077eb5SThomas Petazzoni 	return 0;
6840f077eb5SThomas Petazzoni }
6850f077eb5SThomas Petazzoni 
6860f077eb5SThomas Petazzoni static void armada_370_xp_mpic_resume(void)
6870f077eb5SThomas Petazzoni {
6880f077eb5SThomas Petazzoni 	int nirqs;
6890f077eb5SThomas Petazzoni 	irq_hw_number_t irq;
6900f077eb5SThomas Petazzoni 
6910f077eb5SThomas Petazzoni 	/* Re-enable interrupts */
6920f077eb5SThomas Petazzoni 	nirqs = (readl(main_int_base + ARMADA_370_XP_INT_CONTROL) >> 2) & 0x3ff;
6930f077eb5SThomas Petazzoni 	for (irq = 0; irq < nirqs; irq++) {
6940f077eb5SThomas Petazzoni 		struct irq_data *data;
6950f077eb5SThomas Petazzoni 		int virq;
6960f077eb5SThomas Petazzoni 
6970f077eb5SThomas Petazzoni 		virq = irq_linear_revmap(armada_370_xp_mpic_domain, irq);
6980f077eb5SThomas Petazzoni 		if (virq == 0)
6990f077eb5SThomas Petazzoni 			continue;
7000f077eb5SThomas Petazzoni 
7010fa4ce74SThomas Petazzoni 		data = irq_get_irq_data(virq);
7020fa4ce74SThomas Petazzoni 
7030fa4ce74SThomas Petazzoni 		if (!is_percpu_irq(irq)) {
7040fa4ce74SThomas Petazzoni 			/* Non per-CPU interrupts */
7050f077eb5SThomas Petazzoni 			writel(irq, per_cpu_int_base +
7060f077eb5SThomas Petazzoni 			       ARMADA_370_XP_INT_CLEAR_MASK_OFFS);
7070fa4ce74SThomas Petazzoni 			if (!irqd_irq_disabled(data))
7080fa4ce74SThomas Petazzoni 				armada_370_xp_irq_unmask(data);
7090fa4ce74SThomas Petazzoni 		} else {
7100fa4ce74SThomas Petazzoni 			/* Per-CPU interrupts */
7110f077eb5SThomas Petazzoni 			writel(irq, main_int_base +
7120f077eb5SThomas Petazzoni 			       ARMADA_370_XP_INT_SET_ENABLE_OFFS);
7130f077eb5SThomas Petazzoni 
7140fa4ce74SThomas Petazzoni 			/*
7150fa4ce74SThomas Petazzoni 			 * Re-enable on the current CPU,
7160fa4ce74SThomas Petazzoni 			 * armada_xp_mpic_reenable_percpu() will take
7170fa4ce74SThomas Petazzoni 			 * care of secondary CPUs when they come up.
7180fa4ce74SThomas Petazzoni 			 */
7190fa4ce74SThomas Petazzoni 			if (irq_percpu_is_enabled(virq))
7200f077eb5SThomas Petazzoni 				armada_370_xp_irq_unmask(data);
7210f077eb5SThomas Petazzoni 		}
7220fa4ce74SThomas Petazzoni 	}
7230f077eb5SThomas Petazzoni 
7240f077eb5SThomas Petazzoni 	/* Reconfigure doorbells for IPIs and MSIs */
7250f077eb5SThomas Petazzoni 	writel(doorbell_mask_reg,
7260f077eb5SThomas Petazzoni 	       per_cpu_int_base + ARMADA_370_XP_IN_DRBEL_MSK_OFFS);
7270f077eb5SThomas Petazzoni 	if (doorbell_mask_reg & IPI_DOORBELL_MASK)
7280f077eb5SThomas Petazzoni 		writel(0, per_cpu_int_base + ARMADA_370_XP_INT_CLEAR_MASK_OFFS);
7290f077eb5SThomas Petazzoni 	if (doorbell_mask_reg & PCI_MSI_DOORBELL_MASK)
7300f077eb5SThomas Petazzoni 		writel(1, per_cpu_int_base + ARMADA_370_XP_INT_CLEAR_MASK_OFFS);
731f02147ddSMarc Zyngier 
732f02147ddSMarc Zyngier 	ipi_resume();
7330f077eb5SThomas Petazzoni }
7340f077eb5SThomas Petazzoni 
7356c880902SBen Dooks static struct syscore_ops armada_370_xp_mpic_syscore_ops = {
7360f077eb5SThomas Petazzoni 	.suspend	= armada_370_xp_mpic_suspend,
7370f077eb5SThomas Petazzoni 	.resume		= armada_370_xp_mpic_resume,
7380f077eb5SThomas Petazzoni };
7390f077eb5SThomas Petazzoni 
740b313ada8SThomas Petazzoni static int __init armada_370_xp_mpic_of_init(struct device_node *node,
741b313ada8SThomas Petazzoni 					     struct device_node *parent)
742b313ada8SThomas Petazzoni {
743627dfcc2SThomas Petazzoni 	struct resource main_int_res, per_cpu_int_res;
7445724be84SMaxime Ripard 	int nr_irqs, i;
745b313ada8SThomas Petazzoni 	u32 control;
746b313ada8SThomas Petazzoni 
747627dfcc2SThomas Petazzoni 	BUG_ON(of_address_to_resource(node, 0, &main_int_res));
748627dfcc2SThomas Petazzoni 	BUG_ON(of_address_to_resource(node, 1, &per_cpu_int_res));
749b313ada8SThomas Petazzoni 
750627dfcc2SThomas Petazzoni 	BUG_ON(!request_mem_region(main_int_res.start,
751627dfcc2SThomas Petazzoni 				   resource_size(&main_int_res),
752627dfcc2SThomas Petazzoni 				   node->full_name));
753627dfcc2SThomas Petazzoni 	BUG_ON(!request_mem_region(per_cpu_int_res.start,
754627dfcc2SThomas Petazzoni 				   resource_size(&per_cpu_int_res),
755627dfcc2SThomas Petazzoni 				   node->full_name));
756627dfcc2SThomas Petazzoni 
757627dfcc2SThomas Petazzoni 	main_int_base = ioremap(main_int_res.start,
758627dfcc2SThomas Petazzoni 				resource_size(&main_int_res));
759b313ada8SThomas Petazzoni 	BUG_ON(!main_int_base);
760627dfcc2SThomas Petazzoni 
761627dfcc2SThomas Petazzoni 	per_cpu_int_base = ioremap(per_cpu_int_res.start,
762627dfcc2SThomas Petazzoni 				   resource_size(&per_cpu_int_res));
763b313ada8SThomas Petazzoni 	BUG_ON(!per_cpu_int_base);
764b313ada8SThomas Petazzoni 
765b313ada8SThomas Petazzoni 	control = readl(main_int_base + ARMADA_370_XP_INT_CONTROL);
766b73842b7SThomas Petazzoni 	nr_irqs = (control >> 2) & 0x3ff;
767b73842b7SThomas Petazzoni 
768b73842b7SThomas Petazzoni 	for (i = 0; i < nr_irqs; i++)
769b73842b7SThomas Petazzoni 		writel(i, main_int_base + ARMADA_370_XP_INT_CLEAR_ENABLE_OFFS);
770b313ada8SThomas Petazzoni 
771b313ada8SThomas Petazzoni 	armada_370_xp_mpic_domain =
772b73842b7SThomas Petazzoni 		irq_domain_add_linear(node, nr_irqs,
773b313ada8SThomas Petazzoni 				&armada_370_xp_mpic_irq_ops, NULL);
774627dfcc2SThomas Petazzoni 	BUG_ON(!armada_370_xp_mpic_domain);
77596f0d93aSMarc Zyngier 	irq_domain_update_bus_token(armada_370_xp_mpic_domain, DOMAIN_BUS_WIRED);
776b313ada8SThomas Petazzoni 
777933a24b0SEzequiel Garcia 	/* Setup for the boot CPU */
77828da06dfSMaxime Ripard 	armada_xp_mpic_perf_init();
779b313ada8SThomas Petazzoni 	armada_xp_mpic_smp_cpu_init();
780b313ada8SThomas Petazzoni 
78131f614edSThomas Petazzoni 	armada_370_xp_msi_init(node, main_int_res.start);
78231f614edSThomas Petazzoni 
783bc69b8adSEzequiel Garcia 	parent_irq = irq_of_parse_and_map(node, 0);
784bc69b8adSEzequiel Garcia 	if (parent_irq <= 0) {
785bc69b8adSEzequiel Garcia 		irq_set_default_host(armada_370_xp_mpic_domain);
786b313ada8SThomas Petazzoni 		set_handle_irq(armada_370_xp_handle_irq);
787ef37d337SThomas Petazzoni #ifdef CONFIG_SMP
788f02147ddSMarc Zyngier 		armada_xp_ipi_init(node);
789cb5ff2d2SRichard Cochran 		cpuhp_setup_state_nocalls(CPUHP_AP_IRQ_ARMADA_XP_STARTING,
79073c1b41eSThomas Gleixner 					  "irqchip/armada/ipi:starting",
791cb5ff2d2SRichard Cochran 					  armada_xp_mpic_starting_cpu, NULL);
792ef37d337SThomas Petazzoni #endif
793bc69b8adSEzequiel Garcia 	} else {
7945724be84SMaxime Ripard #ifdef CONFIG_SMP
795008b69e4SThomas Gleixner 		cpuhp_setup_state_nocalls(CPUHP_AP_IRQ_ARMADA_XP_STARTING,
79673c1b41eSThomas Gleixner 					  "irqchip/armada/cascade:starting",
797cb5ff2d2SRichard Cochran 					  mpic_cascaded_starting_cpu, NULL);
7985724be84SMaxime Ripard #endif
799bc69b8adSEzequiel Garcia 		irq_set_chained_handler(parent_irq,
800bc69b8adSEzequiel Garcia 					armada_370_xp_mpic_handle_cascade_irq);
801bc69b8adSEzequiel Garcia 	}
802b313ada8SThomas Petazzoni 
8030f077eb5SThomas Petazzoni 	register_syscore_ops(&armada_370_xp_mpic_syscore_ops);
8040f077eb5SThomas Petazzoni 
805b313ada8SThomas Petazzoni 	return 0;
806b313ada8SThomas Petazzoni }
807b313ada8SThomas Petazzoni 
8089339d432SThomas Petazzoni IRQCHIP_DECLARE(armada_370_xp_mpic, "marvell,mpic", armada_370_xp_mpic_of_init);
809