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 
is_percpu_irq(irq_hw_number_t irq)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  */
armada_370_xp_irq_mask(struct irq_data * d)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 
armada_370_xp_irq_unmask(struct irq_data * d)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 
armada_370_xp_compose_msi_msg(struct irq_data * data,struct msi_msg * msg)210fcc392d5SThomas Petazzoni static void armada_370_xp_compose_msi_msg(struct irq_data *data, struct msi_msg *msg)
211fcc392d5SThomas Petazzoni {
2128ca61cdeSNathan Rossi 	unsigned int cpu = cpumask_first(irq_data_get_effective_affinity_mask(data));
2138ca61cdeSNathan Rossi 
214fcc392d5SThomas Petazzoni 	msg->address_lo = lower_32_bits(msi_doorbell_addr);
215fcc392d5SThomas Petazzoni 	msg->address_hi = upper_32_bits(msi_doorbell_addr);
2168ca61cdeSNathan Rossi 	msg->data = BIT(cpu + 8) | (data->hwirq + PCI_MSI_DOORBELL_START);
217fcc392d5SThomas Petazzoni }
218fcc392d5SThomas Petazzoni 
armada_370_xp_msi_set_affinity(struct irq_data * irq_data,const struct cpumask * mask,bool force)219fcc392d5SThomas Petazzoni static int armada_370_xp_msi_set_affinity(struct irq_data *irq_data,
220fcc392d5SThomas Petazzoni 					  const struct cpumask *mask, bool force)
221fcc392d5SThomas Petazzoni {
2228ca61cdeSNathan Rossi 	unsigned int cpu;
2238ca61cdeSNathan Rossi 
2248ca61cdeSNathan Rossi 	if (!force)
2258ca61cdeSNathan Rossi 		cpu = cpumask_any_and(mask, cpu_online_mask);
2268ca61cdeSNathan Rossi 	else
2278ca61cdeSNathan Rossi 		cpu = cpumask_first(mask);
2288ca61cdeSNathan Rossi 
2298ca61cdeSNathan Rossi 	if (cpu >= nr_cpu_ids)
230fcc392d5SThomas Petazzoni 		return -EINVAL;
2318ca61cdeSNathan Rossi 
2328ca61cdeSNathan Rossi 	irq_data_update_effective_affinity(irq_data, cpumask_of(cpu));
2338ca61cdeSNathan Rossi 
2348ca61cdeSNathan Rossi 	return IRQ_SET_MASK_OK;
235fcc392d5SThomas Petazzoni }
236fcc392d5SThomas Petazzoni 
237fcc392d5SThomas Petazzoni static struct irq_chip armada_370_xp_msi_bottom_irq_chip = {
238f692a172SThomas Petazzoni 	.name			= "MPIC MSI",
239fcc392d5SThomas Petazzoni 	.irq_compose_msi_msg	= armada_370_xp_compose_msi_msg,
240fcc392d5SThomas Petazzoni 	.irq_set_affinity	= armada_370_xp_msi_set_affinity,
241fcc392d5SThomas Petazzoni };
242fcc392d5SThomas Petazzoni 
armada_370_xp_msi_alloc(struct irq_domain * domain,unsigned int virq,unsigned int nr_irqs,void * args)243fcc392d5SThomas Petazzoni static int armada_370_xp_msi_alloc(struct irq_domain *domain, unsigned int virq,
244fcc392d5SThomas Petazzoni 				   unsigned int nr_irqs, void *args)
24531f614edSThomas Petazzoni {
246a71b9412SThomas Petazzoni 	int hwirq, i;
24731f614edSThomas Petazzoni 
24831f614edSThomas Petazzoni 	mutex_lock(&msi_used_lock);
249d0a55350SPali Rohár 	hwirq = bitmap_find_free_region(msi_used, PCI_MSI_DOORBELL_NR,
250d0a55350SPali Rohár 					order_base_2(nr_irqs));
251fcc392d5SThomas Petazzoni 	mutex_unlock(&msi_used_lock);
252d0a55350SPali Rohár 
253d0a55350SPali Rohár 	if (hwirq < 0)
254fcc392d5SThomas Petazzoni 		return -ENOSPC;
25531f614edSThomas Petazzoni 
256a71b9412SThomas Petazzoni 	for (i = 0; i < nr_irqs; i++) {
257a71b9412SThomas Petazzoni 		irq_domain_set_info(domain, virq + i, hwirq + i,
258a71b9412SThomas Petazzoni 				    &armada_370_xp_msi_bottom_irq_chip,
259fcc392d5SThomas Petazzoni 				    domain->host_data, handle_simple_irq,
260fcc392d5SThomas Petazzoni 				    NULL, NULL);
261a71b9412SThomas Petazzoni 	}
262fcc392d5SThomas Petazzoni 
263ce20eff5SPali Rohár 	return 0;
26431f614edSThomas Petazzoni }
26531f614edSThomas Petazzoni 
armada_370_xp_msi_free(struct irq_domain * domain,unsigned int virq,unsigned int nr_irqs)266fcc392d5SThomas Petazzoni static void armada_370_xp_msi_free(struct irq_domain *domain,
267fcc392d5SThomas Petazzoni 				   unsigned int virq, unsigned int nr_irqs)
26831f614edSThomas Petazzoni {
269fcc392d5SThomas Petazzoni 	struct irq_data *d = irq_domain_get_irq_data(domain, virq);
270fcc392d5SThomas Petazzoni 
27131f614edSThomas Petazzoni 	mutex_lock(&msi_used_lock);
272d0a55350SPali Rohár 	bitmap_release_region(msi_used, d->hwirq, order_base_2(nr_irqs));
27331f614edSThomas Petazzoni 	mutex_unlock(&msi_used_lock);
27431f614edSThomas Petazzoni }
27531f614edSThomas Petazzoni 
276fcc392d5SThomas Petazzoni static const struct irq_domain_ops armada_370_xp_msi_domain_ops = {
277fcc392d5SThomas Petazzoni 	.alloc	= armada_370_xp_msi_alloc,
278fcc392d5SThomas Petazzoni 	.free	= armada_370_xp_msi_free,
27931f614edSThomas Petazzoni };
28031f614edSThomas Petazzoni 
armada_370_xp_msi_reenable_percpu(void)2818ca61cdeSNathan Rossi static void armada_370_xp_msi_reenable_percpu(void)
28231f614edSThomas Petazzoni {
28331f614edSThomas Petazzoni 	u32 reg;
28431f614edSThomas Petazzoni 
2858ca61cdeSNathan Rossi 	/* Enable MSI doorbell mask and combined cpu local interrupt */
2868ca61cdeSNathan Rossi 	reg = readl(per_cpu_int_base + ARMADA_370_XP_IN_DRBEL_MSK_OFFS)
2878ca61cdeSNathan Rossi 		| PCI_MSI_DOORBELL_MASK;
2888ca61cdeSNathan Rossi 	writel(reg, per_cpu_int_base + ARMADA_370_XP_IN_DRBEL_MSK_OFFS);
2898ca61cdeSNathan Rossi 	/* Unmask local doorbell interrupt */
2908ca61cdeSNathan Rossi 	writel(1, per_cpu_int_base + ARMADA_370_XP_INT_CLEAR_MASK_OFFS);
2918ca61cdeSNathan Rossi }
2928ca61cdeSNathan Rossi 
armada_370_xp_msi_init(struct device_node * node,phys_addr_t main_int_phys_base)2938ca61cdeSNathan Rossi static int armada_370_xp_msi_init(struct device_node *node,
2948ca61cdeSNathan Rossi 				  phys_addr_t main_int_phys_base)
2958ca61cdeSNathan Rossi {
29631f614edSThomas Petazzoni 	msi_doorbell_addr = main_int_phys_base +
29731f614edSThomas Petazzoni 		ARMADA_370_XP_SW_TRIG_INT_OFFS;
29831f614edSThomas Petazzoni 
299fcc392d5SThomas Petazzoni 	armada_370_xp_msi_inner_domain =
300fcc392d5SThomas Petazzoni 		irq_domain_add_linear(NULL, PCI_MSI_DOORBELL_NR,
301fcc392d5SThomas Petazzoni 				      &armada_370_xp_msi_domain_ops, NULL);
302fcc392d5SThomas Petazzoni 	if (!armada_370_xp_msi_inner_domain)
30331f614edSThomas Petazzoni 		return -ENOMEM;
30431f614edSThomas Petazzoni 
30531f614edSThomas Petazzoni 	armada_370_xp_msi_domain =
306fcc392d5SThomas Petazzoni 		pci_msi_create_irq_domain(of_node_to_fwnode(node),
307fcc392d5SThomas Petazzoni 					  &armada_370_xp_msi_domain_info,
308fcc392d5SThomas Petazzoni 					  armada_370_xp_msi_inner_domain);
30931f614edSThomas Petazzoni 	if (!armada_370_xp_msi_domain) {
310fcc392d5SThomas Petazzoni 		irq_domain_remove(armada_370_xp_msi_inner_domain);
31131f614edSThomas Petazzoni 		return -ENOMEM;
31231f614edSThomas Petazzoni 	}
31331f614edSThomas Petazzoni 
3148ca61cdeSNathan Rossi 	armada_370_xp_msi_reenable_percpu();
31531f614edSThomas Petazzoni 
31631f614edSThomas Petazzoni 	return 0;
31731f614edSThomas Petazzoni }
31831f614edSThomas Petazzoni #else
armada_370_xp_msi_reenable_percpu(void)3198ca61cdeSNathan Rossi static void armada_370_xp_msi_reenable_percpu(void) {}
3208ca61cdeSNathan Rossi 
armada_370_xp_msi_init(struct device_node * node,phys_addr_t main_int_phys_base)32131f614edSThomas Petazzoni static inline int armada_370_xp_msi_init(struct device_node *node,
32231f614edSThomas Petazzoni 					 phys_addr_t main_int_phys_base)
32331f614edSThomas Petazzoni {
32431f614edSThomas Petazzoni 	return 0;
32531f614edSThomas Petazzoni }
32631f614edSThomas Petazzoni #endif
32731f614edSThomas Petazzoni 
armada_xp_mpic_perf_init(void)328f02147ddSMarc Zyngier static void armada_xp_mpic_perf_init(void)
329f02147ddSMarc Zyngier {
330a3d66a76SPali Rohár 	unsigned long cpuid;
331a3d66a76SPali Rohár 
332a3d66a76SPali Rohár 	/*
333a3d66a76SPali Rohár 	 * This Performance Counter Overflow interrupt is specific for
334a3d66a76SPali Rohár 	 * Armada 370 and XP. It is not available on Armada 375, 38x and 39x.
335a3d66a76SPali Rohár 	 */
336a3d66a76SPali Rohár 	if (!of_machine_is_compatible("marvell,armada-370-xp"))
337a3d66a76SPali Rohár 		return;
338a3d66a76SPali Rohár 
339a3d66a76SPali Rohár 	cpuid = cpu_logical_map(smp_processor_id());
340f02147ddSMarc Zyngier 
341f02147ddSMarc Zyngier 	/* Enable Performance Counter Overflow interrupts */
342f02147ddSMarc Zyngier 	writel(ARMADA_370_XP_INT_CAUSE_PERF(cpuid),
343f02147ddSMarc Zyngier 	       per_cpu_int_base + ARMADA_370_XP_INT_FABRIC_MASK_OFFS);
344f02147ddSMarc Zyngier }
345f02147ddSMarc Zyngier 
3469339d432SThomas Petazzoni #ifdef CONFIG_SMP
347f02147ddSMarc Zyngier static struct irq_domain *ipi_domain;
348f02147ddSMarc Zyngier 
armada_370_xp_ipi_mask(struct irq_data * d)349f02147ddSMarc Zyngier static void armada_370_xp_ipi_mask(struct irq_data *d)
350f02147ddSMarc Zyngier {
351f02147ddSMarc Zyngier 	u32 reg;
352f02147ddSMarc Zyngier 	reg = readl(per_cpu_int_base + ARMADA_370_XP_IN_DRBEL_MSK_OFFS);
353f02147ddSMarc Zyngier 	reg &= ~BIT(d->hwirq);
354f02147ddSMarc Zyngier 	writel(reg, per_cpu_int_base + ARMADA_370_XP_IN_DRBEL_MSK_OFFS);
355f02147ddSMarc Zyngier }
356f02147ddSMarc Zyngier 
armada_370_xp_ipi_unmask(struct irq_data * d)357f02147ddSMarc Zyngier static void armada_370_xp_ipi_unmask(struct irq_data *d)
358f02147ddSMarc Zyngier {
359f02147ddSMarc Zyngier 	u32 reg;
360f02147ddSMarc Zyngier 	reg = readl(per_cpu_int_base + ARMADA_370_XP_IN_DRBEL_MSK_OFFS);
361f02147ddSMarc Zyngier 	reg |= BIT(d->hwirq);
362f02147ddSMarc Zyngier 	writel(reg, per_cpu_int_base + ARMADA_370_XP_IN_DRBEL_MSK_OFFS);
363f02147ddSMarc Zyngier }
364f02147ddSMarc Zyngier 
armada_370_xp_ipi_send_mask(struct irq_data * d,const struct cpumask * mask)365f02147ddSMarc Zyngier static void armada_370_xp_ipi_send_mask(struct irq_data *d,
366f02147ddSMarc Zyngier 					const struct cpumask *mask)
367f02147ddSMarc Zyngier {
368f02147ddSMarc Zyngier 	unsigned long map = 0;
369f02147ddSMarc Zyngier 	int cpu;
370f02147ddSMarc Zyngier 
371f02147ddSMarc Zyngier 	/* Convert our logical CPU mask into a physical one. */
372f02147ddSMarc Zyngier 	for_each_cpu(cpu, mask)
373f02147ddSMarc Zyngier 		map |= 1 << cpu_logical_map(cpu);
374f02147ddSMarc Zyngier 
375f02147ddSMarc Zyngier 	/*
376f02147ddSMarc Zyngier 	 * Ensure that stores to Normal memory are visible to the
377f02147ddSMarc Zyngier 	 * other CPUs before issuing the IPI.
378f02147ddSMarc Zyngier 	 */
379f02147ddSMarc Zyngier 	dsb();
380f02147ddSMarc Zyngier 
381f02147ddSMarc Zyngier 	/* submit softirq */
382f02147ddSMarc Zyngier 	writel((map << 8) | d->hwirq, main_int_base +
383f02147ddSMarc Zyngier 		ARMADA_370_XP_SW_TRIG_INT_OFFS);
384f02147ddSMarc Zyngier }
385f02147ddSMarc Zyngier 
armada_370_xp_ipi_ack(struct irq_data * d)3862a7313dcSMarc Zyngier static void armada_370_xp_ipi_ack(struct irq_data *d)
387f02147ddSMarc Zyngier {
388f02147ddSMarc Zyngier 	writel(~BIT(d->hwirq), per_cpu_int_base + ARMADA_370_XP_IN_DRBEL_CAUSE_OFFS);
389f02147ddSMarc Zyngier }
390f02147ddSMarc Zyngier 
391f02147ddSMarc Zyngier static struct irq_chip ipi_irqchip = {
392f02147ddSMarc Zyngier 	.name		= "IPI",
3932a7313dcSMarc Zyngier 	.irq_ack	= armada_370_xp_ipi_ack,
394f02147ddSMarc Zyngier 	.irq_mask	= armada_370_xp_ipi_mask,
395f02147ddSMarc Zyngier 	.irq_unmask	= armada_370_xp_ipi_unmask,
396f02147ddSMarc Zyngier 	.ipi_send_mask	= armada_370_xp_ipi_send_mask,
397f02147ddSMarc Zyngier };
398f02147ddSMarc Zyngier 
armada_370_xp_ipi_alloc(struct irq_domain * d,unsigned int virq,unsigned int nr_irqs,void * args)399f02147ddSMarc Zyngier static int armada_370_xp_ipi_alloc(struct irq_domain *d,
400f02147ddSMarc Zyngier 					 unsigned int virq,
401f02147ddSMarc Zyngier 					 unsigned int nr_irqs, void *args)
402f02147ddSMarc Zyngier {
403f02147ddSMarc Zyngier 	int i;
404f02147ddSMarc Zyngier 
405f02147ddSMarc Zyngier 	for (i = 0; i < nr_irqs; i++) {
406f02147ddSMarc Zyngier 		irq_set_percpu_devid(virq + i);
407f02147ddSMarc Zyngier 		irq_domain_set_info(d, virq + i, i, &ipi_irqchip,
408f02147ddSMarc Zyngier 				    d->host_data,
409e52e73b7SValentin Schneider 				    handle_percpu_devid_irq,
410f02147ddSMarc Zyngier 				    NULL, NULL);
411f02147ddSMarc Zyngier 	}
412f02147ddSMarc Zyngier 
413f02147ddSMarc Zyngier 	return 0;
414f02147ddSMarc Zyngier }
415f02147ddSMarc Zyngier 
armada_370_xp_ipi_free(struct irq_domain * d,unsigned int virq,unsigned int nr_irqs)416f02147ddSMarc Zyngier static void armada_370_xp_ipi_free(struct irq_domain *d,
417f02147ddSMarc Zyngier 					 unsigned int virq,
418f02147ddSMarc Zyngier 					 unsigned int nr_irqs)
419f02147ddSMarc Zyngier {
420f02147ddSMarc Zyngier 	/* Not freeing IPIs */
421f02147ddSMarc Zyngier }
422f02147ddSMarc Zyngier 
423f02147ddSMarc Zyngier static const struct irq_domain_ops ipi_domain_ops = {
424f02147ddSMarc Zyngier 	.alloc	= armada_370_xp_ipi_alloc,
425f02147ddSMarc Zyngier 	.free	= armada_370_xp_ipi_free,
426f02147ddSMarc Zyngier };
427f02147ddSMarc Zyngier 
ipi_resume(void)428f02147ddSMarc Zyngier static void ipi_resume(void)
429f02147ddSMarc Zyngier {
430f02147ddSMarc Zyngier 	int i;
431f02147ddSMarc Zyngier 
432f02147ddSMarc Zyngier 	for (i = 0; i < IPI_DOORBELL_END; i++) {
433f02147ddSMarc Zyngier 		int irq;
434f02147ddSMarc Zyngier 
435f02147ddSMarc Zyngier 		irq = irq_find_mapping(ipi_domain, i);
436f02147ddSMarc Zyngier 		if (irq <= 0)
437f02147ddSMarc Zyngier 			continue;
438f02147ddSMarc Zyngier 		if (irq_percpu_is_enabled(irq)) {
439f02147ddSMarc Zyngier 			struct irq_data *d;
440f02147ddSMarc Zyngier 			d = irq_domain_get_irq_data(ipi_domain, irq);
441f02147ddSMarc Zyngier 			armada_370_xp_ipi_unmask(d);
442f02147ddSMarc Zyngier 		}
443f02147ddSMarc Zyngier 	}
444f02147ddSMarc Zyngier }
445f02147ddSMarc Zyngier 
armada_xp_ipi_init(struct device_node * node)446f02147ddSMarc Zyngier static __init void armada_xp_ipi_init(struct device_node *node)
447f02147ddSMarc Zyngier {
448f02147ddSMarc Zyngier 	int base_ipi;
449f02147ddSMarc Zyngier 
450f02147ddSMarc Zyngier 	ipi_domain = irq_domain_create_linear(of_node_to_fwnode(node),
451f02147ddSMarc Zyngier 					      IPI_DOORBELL_END,
452f02147ddSMarc Zyngier 					      &ipi_domain_ops, NULL);
453f02147ddSMarc Zyngier 	if (WARN_ON(!ipi_domain))
454f02147ddSMarc Zyngier 		return;
455f02147ddSMarc Zyngier 
456f02147ddSMarc Zyngier 	irq_domain_update_bus_token(ipi_domain, DOMAIN_BUS_IPI);
457*0e2213feSJohan Hovold 	base_ipi = irq_domain_alloc_irqs(ipi_domain, IPI_DOORBELL_END, NUMA_NO_NODE, NULL);
458f02147ddSMarc Zyngier 	if (WARN_ON(!base_ipi))
459f02147ddSMarc Zyngier 		return;
460f02147ddSMarc Zyngier 
461f02147ddSMarc Zyngier 	set_smp_ipi_range(base_ipi, IPI_DOORBELL_END);
462f02147ddSMarc Zyngier }
463f02147ddSMarc Zyngier 
46419e61d41SArnaud Ebalard static DEFINE_RAW_SPINLOCK(irq_controller_lock);
46519e61d41SArnaud Ebalard 
armada_xp_set_affinity(struct irq_data * d,const struct cpumask * mask_val,bool force)4669339d432SThomas Petazzoni static int armada_xp_set_affinity(struct irq_data *d,
4679339d432SThomas Petazzoni 				  const struct cpumask *mask_val, bool force)
4689339d432SThomas Petazzoni {
4699339d432SThomas Petazzoni 	irq_hw_number_t hwirq = irqd_to_hwirq(d);
4708cc3cfc5SThomas Gleixner 	unsigned long reg, mask;
4719339d432SThomas Petazzoni 	int cpu;
4729339d432SThomas Petazzoni 
4738cc3cfc5SThomas Gleixner 	/* Select a single core from the affinity mask which is online */
4748cc3cfc5SThomas Gleixner 	cpu = cpumask_any_and(mask_val, cpu_online_mask);
4758cc3cfc5SThomas Gleixner 	mask = 1UL << cpu_logical_map(cpu);
4769339d432SThomas Petazzoni 
4779339d432SThomas Petazzoni 	raw_spin_lock(&irq_controller_lock);
4789339d432SThomas Petazzoni 	reg = readl(main_int_base + ARMADA_370_XP_INT_SOURCE_CTL(hwirq));
4798cc3cfc5SThomas Gleixner 	reg = (reg & (~ARMADA_370_XP_INT_SOURCE_CPU_MASK)) | mask;
4809339d432SThomas Petazzoni 	writel(reg, main_int_base + ARMADA_370_XP_INT_SOURCE_CTL(hwirq));
4819339d432SThomas Petazzoni 	raw_spin_unlock(&irq_controller_lock);
4829339d432SThomas Petazzoni 
483e31793a3SMarc Zyngier 	irq_data_update_effective_affinity(d, cpumask_of(cpu));
484e31793a3SMarc Zyngier 
4851dacf194SThomas Petazzoni 	return IRQ_SET_MASK_OK;
4869339d432SThomas Petazzoni }
487f02147ddSMarc Zyngier 
armada_xp_mpic_smp_cpu_init(void)488f02147ddSMarc Zyngier static void armada_xp_mpic_smp_cpu_init(void)
489f02147ddSMarc Zyngier {
490f02147ddSMarc Zyngier 	u32 control;
491f02147ddSMarc Zyngier 	int nr_irqs, i;
492f02147ddSMarc Zyngier 
493f02147ddSMarc Zyngier 	control = readl(main_int_base + ARMADA_370_XP_INT_CONTROL);
494f02147ddSMarc Zyngier 	nr_irqs = (control >> 2) & 0x3ff;
495f02147ddSMarc Zyngier 
496f02147ddSMarc Zyngier 	for (i = 0; i < nr_irqs; i++)
497f02147ddSMarc Zyngier 		writel(i, per_cpu_int_base + ARMADA_370_XP_INT_SET_MASK_OFFS);
498f02147ddSMarc Zyngier 
499f02147ddSMarc Zyngier 	/* Disable all IPIs */
500f02147ddSMarc Zyngier 	writel(0, per_cpu_int_base + ARMADA_370_XP_IN_DRBEL_MSK_OFFS);
501f02147ddSMarc Zyngier 
502f02147ddSMarc Zyngier 	/* Clear pending IPIs */
503f02147ddSMarc Zyngier 	writel(0, per_cpu_int_base + ARMADA_370_XP_IN_DRBEL_CAUSE_OFFS);
504f02147ddSMarc Zyngier 
505f02147ddSMarc Zyngier 	/* Unmask IPI interrupt */
506f02147ddSMarc Zyngier 	writel(0, per_cpu_int_base + ARMADA_370_XP_INT_CLEAR_MASK_OFFS);
507f02147ddSMarc Zyngier }
508f02147ddSMarc Zyngier 
armada_xp_mpic_reenable_percpu(void)509f02147ddSMarc Zyngier static void armada_xp_mpic_reenable_percpu(void)
510f02147ddSMarc Zyngier {
511f02147ddSMarc Zyngier 	unsigned int irq;
512f02147ddSMarc Zyngier 
513f02147ddSMarc Zyngier 	/* Re-enable per-CPU interrupts that were enabled before suspend */
514f02147ddSMarc Zyngier 	for (irq = 0; irq < ARMADA_370_XP_MAX_PER_CPU_IRQS; irq++) {
515f02147ddSMarc Zyngier 		struct irq_data *data;
516f02147ddSMarc Zyngier 		int virq;
517f02147ddSMarc Zyngier 
518f02147ddSMarc Zyngier 		virq = irq_linear_revmap(armada_370_xp_mpic_domain, irq);
519f02147ddSMarc Zyngier 		if (virq == 0)
520f02147ddSMarc Zyngier 			continue;
521f02147ddSMarc Zyngier 
522f02147ddSMarc Zyngier 		data = irq_get_irq_data(virq);
523f02147ddSMarc Zyngier 
524f02147ddSMarc Zyngier 		if (!irq_percpu_is_enabled(virq))
525f02147ddSMarc Zyngier 			continue;
526f02147ddSMarc Zyngier 
527f02147ddSMarc Zyngier 		armada_370_xp_irq_unmask(data);
528f02147ddSMarc Zyngier 	}
529f02147ddSMarc Zyngier 
530f02147ddSMarc Zyngier 	ipi_resume();
5318ca61cdeSNathan Rossi 
5328ca61cdeSNathan Rossi 	armada_370_xp_msi_reenable_percpu();
533f02147ddSMarc Zyngier }
534f02147ddSMarc Zyngier 
armada_xp_mpic_starting_cpu(unsigned int cpu)535f02147ddSMarc Zyngier static int armada_xp_mpic_starting_cpu(unsigned int cpu)
536f02147ddSMarc Zyngier {
537f02147ddSMarc Zyngier 	armada_xp_mpic_perf_init();
538f02147ddSMarc Zyngier 	armada_xp_mpic_smp_cpu_init();
539f02147ddSMarc Zyngier 	armada_xp_mpic_reenable_percpu();
540f02147ddSMarc Zyngier 	return 0;
541f02147ddSMarc Zyngier }
542f02147ddSMarc Zyngier 
mpic_cascaded_starting_cpu(unsigned int cpu)543f02147ddSMarc Zyngier static int mpic_cascaded_starting_cpu(unsigned int cpu)
544f02147ddSMarc Zyngier {
545f02147ddSMarc Zyngier 	armada_xp_mpic_perf_init();
546f02147ddSMarc Zyngier 	armada_xp_mpic_reenable_percpu();
547f02147ddSMarc Zyngier 	enable_percpu_irq(parent_irq, IRQ_TYPE_NONE);
548f02147ddSMarc Zyngier 	return 0;
549f02147ddSMarc Zyngier }
550f02147ddSMarc Zyngier #else
armada_xp_mpic_smp_cpu_init(void)551f02147ddSMarc Zyngier static void armada_xp_mpic_smp_cpu_init(void) {}
ipi_resume(void)552f02147ddSMarc Zyngier static void ipi_resume(void) {}
5539339d432SThomas Petazzoni #endif
5549339d432SThomas Petazzoni 
5559339d432SThomas Petazzoni static struct irq_chip armada_370_xp_irq_chip = {
556f692a172SThomas Petazzoni 	.name		= "MPIC",
5579339d432SThomas Petazzoni 	.irq_mask       = armada_370_xp_irq_mask,
5589339d432SThomas Petazzoni 	.irq_mask_ack   = armada_370_xp_irq_mask,
5599339d432SThomas Petazzoni 	.irq_unmask     = armada_370_xp_irq_unmask,
5609339d432SThomas Petazzoni #ifdef CONFIG_SMP
5619339d432SThomas Petazzoni 	.irq_set_affinity = armada_xp_set_affinity,
5629339d432SThomas Petazzoni #endif
5630d8e1d80SGregory CLEMENT 	.flags		= IRQCHIP_SKIP_SET_WAKE | IRQCHIP_MASK_ON_SUSPEND,
5649339d432SThomas Petazzoni };
5659339d432SThomas Petazzoni 
armada_370_xp_mpic_irq_map(struct irq_domain * h,unsigned int virq,irq_hw_number_t hw)5669339d432SThomas Petazzoni static int armada_370_xp_mpic_irq_map(struct irq_domain *h,
5679339d432SThomas Petazzoni 				      unsigned int virq, irq_hw_number_t hw)
5689339d432SThomas Petazzoni {
5699339d432SThomas Petazzoni 	armada_370_xp_irq_mask(irq_get_irq_data(virq));
5702c299de5SEzequiel Garcia 	if (!is_percpu_irq(hw))
5711bf25e78SLinus Torvalds 		writel(hw, per_cpu_int_base +
5721bf25e78SLinus Torvalds 			ARMADA_370_XP_INT_CLEAR_MASK_OFFS);
5731bf25e78SLinus Torvalds 	else
5749339d432SThomas Petazzoni 		writel(hw, main_int_base + ARMADA_370_XP_INT_SET_ENABLE_OFFS);
5759339d432SThomas Petazzoni 	irq_set_status_flags(virq, IRQ_LEVEL);
5769339d432SThomas Petazzoni 
5772c299de5SEzequiel Garcia 	if (is_percpu_irq(hw)) {
5789339d432SThomas Petazzoni 		irq_set_percpu_devid(virq);
5799339d432SThomas Petazzoni 		irq_set_chip_and_handler(virq, &armada_370_xp_irq_chip,
5809339d432SThomas Petazzoni 					handle_percpu_devid_irq);
5819339d432SThomas Petazzoni 	} else {
5829339d432SThomas Petazzoni 		irq_set_chip_and_handler(virq, &armada_370_xp_irq_chip,
5839339d432SThomas Petazzoni 					handle_level_irq);
584e31793a3SMarc Zyngier 		irqd_set_single_target(irq_desc_get_irq_data(irq_to_desc(virq)));
5859339d432SThomas Petazzoni 	}
586d17cab44SRob Herring 	irq_set_probe(virq);
5879339d432SThomas Petazzoni 
5889339d432SThomas Petazzoni 	return 0;
5899339d432SThomas Petazzoni }
5909339d432SThomas Petazzoni 
59196009736SKrzysztof Kozlowski static const struct irq_domain_ops armada_370_xp_mpic_irq_ops = {
5929339d432SThomas Petazzoni 	.map = armada_370_xp_mpic_irq_map,
5939339d432SThomas Petazzoni 	.xlate = irq_domain_xlate_onecell,
5949339d432SThomas Petazzoni };
5959339d432SThomas Petazzoni 
59631f614edSThomas Petazzoni #ifdef CONFIG_PCI_MSI
armada_370_xp_handle_msi_irq(struct pt_regs * regs,bool is_chained)597bc69b8adSEzequiel Garcia static void armada_370_xp_handle_msi_irq(struct pt_regs *regs, bool is_chained)
5989b8cf779SEzequiel Garcia {
59931f614edSThomas Petazzoni 	u32 msimask, msinr;
60031f614edSThomas Petazzoni 
60131f614edSThomas Petazzoni 	msimask = readl_relaxed(per_cpu_int_base +
60231f614edSThomas Petazzoni 				ARMADA_370_XP_IN_DRBEL_CAUSE_OFFS)
60331f614edSThomas Petazzoni 		& PCI_MSI_DOORBELL_MASK;
60431f614edSThomas Petazzoni 
605c7f7bd4aSLior Amsalem 	writel(~msimask, per_cpu_int_base +
60631f614edSThomas Petazzoni 	       ARMADA_370_XP_IN_DRBEL_CAUSE_OFFS);
60731f614edSThomas Petazzoni 
60831f614edSThomas Petazzoni 	for (msinr = PCI_MSI_DOORBELL_START;
60931f614edSThomas Petazzoni 	     msinr < PCI_MSI_DOORBELL_END; msinr++) {
610046a6ee2SMarc Zyngier 		unsigned int irq;
61131f614edSThomas Petazzoni 
61231f614edSThomas Petazzoni 		if (!(msimask & BIT(msinr)))
61331f614edSThomas Petazzoni 			continue;
61431f614edSThomas Petazzoni 
6150636bab6SThomas Petazzoni 		irq = msinr - PCI_MSI_DOORBELL_START;
616046a6ee2SMarc Zyngier 
6170953fb26SMark Rutland 		generic_handle_domain_irq(armada_370_xp_msi_inner_domain, irq);
618e89c6a06SMarc Zyngier 	}
61931f614edSThomas Petazzoni }
6209b8cf779SEzequiel Garcia #else
armada_370_xp_handle_msi_irq(struct pt_regs * r,bool b)621bc69b8adSEzequiel Garcia static void armada_370_xp_handle_msi_irq(struct pt_regs *r, bool b) {}
62231f614edSThomas Petazzoni #endif
62331f614edSThomas Petazzoni 
armada_370_xp_mpic_handle_cascade_irq(struct irq_desc * desc)624bd0b9ac4SThomas Gleixner static void armada_370_xp_mpic_handle_cascade_irq(struct irq_desc *desc)
625bc69b8adSEzequiel Garcia {
6265b29264cSJiang Liu 	struct irq_chip *chip = irq_desc_get_chip(desc);
627758e8366SGrzegorz Jaszczyk 	unsigned long irqmap, irqn, irqsrc, cpuid;
628bc69b8adSEzequiel Garcia 
629bc69b8adSEzequiel Garcia 	chained_irq_enter(chip, desc);
630bc69b8adSEzequiel Garcia 
631bc69b8adSEzequiel Garcia 	irqmap = readl_relaxed(per_cpu_int_base + ARMADA_375_PPI_CAUSE);
632758e8366SGrzegorz Jaszczyk 	cpuid = cpu_logical_map(smp_processor_id());
633bc69b8adSEzequiel Garcia 
634bc69b8adSEzequiel Garcia 	for_each_set_bit(irqn, &irqmap, BITS_PER_LONG) {
635758e8366SGrzegorz Jaszczyk 		irqsrc = readl_relaxed(main_int_base +
636758e8366SGrzegorz Jaszczyk 				       ARMADA_370_XP_INT_SOURCE_CTL(irqn));
637758e8366SGrzegorz Jaszczyk 
638758e8366SGrzegorz Jaszczyk 		/* Check if the interrupt is not masked on current CPU.
639758e8366SGrzegorz Jaszczyk 		 * Test IRQ (0-1) and FIQ (8-9) mask bits.
640758e8366SGrzegorz Jaszczyk 		 */
641758e8366SGrzegorz Jaszczyk 		if (!(irqsrc & ARMADA_370_XP_INT_IRQ_FIQ_MASK(cpuid)))
642758e8366SGrzegorz Jaszczyk 			continue;
643758e8366SGrzegorz Jaszczyk 
644758e8366SGrzegorz Jaszczyk 		if (irqn == 1) {
645758e8366SGrzegorz Jaszczyk 			armada_370_xp_handle_msi_irq(NULL, true);
646758e8366SGrzegorz Jaszczyk 			continue;
647758e8366SGrzegorz Jaszczyk 		}
648758e8366SGrzegorz Jaszczyk 
649046a6ee2SMarc Zyngier 		generic_handle_domain_irq(armada_370_xp_mpic_domain, irqn);
650bc69b8adSEzequiel Garcia 	}
651bc69b8adSEzequiel Garcia 
652bc69b8adSEzequiel Garcia 	chained_irq_exit(chip, desc);
653bc69b8adSEzequiel Garcia }
654bc69b8adSEzequiel Garcia 
6558783dd3aSStephen Boyd static void __exception_irq_entry
armada_370_xp_handle_irq(struct pt_regs * regs)6569b8cf779SEzequiel Garcia armada_370_xp_handle_irq(struct pt_regs *regs)
6579b8cf779SEzequiel Garcia {
6589b8cf779SEzequiel Garcia 	u32 irqstat, irqnr;
6599b8cf779SEzequiel Garcia 
6609b8cf779SEzequiel Garcia 	do {
6619b8cf779SEzequiel Garcia 		irqstat = readl_relaxed(per_cpu_int_base +
6629b8cf779SEzequiel Garcia 					ARMADA_370_XP_CPU_INTACK_OFFS);
6639b8cf779SEzequiel Garcia 		irqnr = irqstat & 0x3FF;
6649b8cf779SEzequiel Garcia 
6659b8cf779SEzequiel Garcia 		if (irqnr > 1022)
6669b8cf779SEzequiel Garcia 			break;
6679b8cf779SEzequiel Garcia 
6689b8cf779SEzequiel Garcia 		if (irqnr > 1) {
6690953fb26SMark Rutland 			generic_handle_domain_irq(armada_370_xp_mpic_domain,
6700953fb26SMark Rutland 						  irqnr);
6719b8cf779SEzequiel Garcia 			continue;
6729b8cf779SEzequiel Garcia 		}
6739b8cf779SEzequiel Garcia 
6749b8cf779SEzequiel Garcia 		/* MSI handling */
6759b8cf779SEzequiel Garcia 		if (irqnr == 1)
676bc69b8adSEzequiel Garcia 			armada_370_xp_handle_msi_irq(regs, false);
6779b8cf779SEzequiel Garcia 
6789339d432SThomas Petazzoni #ifdef CONFIG_SMP
6799339d432SThomas Petazzoni 		/* IPI Handling */
6809339d432SThomas Petazzoni 		if (irqnr == 0) {
681f02147ddSMarc Zyngier 			unsigned long ipimask;
682f02147ddSMarc Zyngier 			int ipi;
6839339d432SThomas Petazzoni 
6849339d432SThomas Petazzoni 			ipimask = readl_relaxed(per_cpu_int_base +
6859339d432SThomas Petazzoni 						ARMADA_370_XP_IN_DRBEL_CAUSE_OFFS)
6865ec69017SThomas Petazzoni 				& IPI_DOORBELL_MASK;
6879339d432SThomas Petazzoni 
688f02147ddSMarc Zyngier 			for_each_set_bit(ipi, &ipimask, IPI_DOORBELL_END)
6890953fb26SMark Rutland 				generic_handle_domain_irq(ipi_domain, ipi);
6909339d432SThomas Petazzoni 		}
6919339d432SThomas Petazzoni #endif
6929339d432SThomas Petazzoni 
6939339d432SThomas Petazzoni 	} while (1);
6949339d432SThomas Petazzoni }
6959339d432SThomas Petazzoni 
armada_370_xp_mpic_suspend(void)6960f077eb5SThomas Petazzoni static int armada_370_xp_mpic_suspend(void)
6970f077eb5SThomas Petazzoni {
6980f077eb5SThomas Petazzoni 	doorbell_mask_reg = readl(per_cpu_int_base +
6990f077eb5SThomas Petazzoni 				  ARMADA_370_XP_IN_DRBEL_MSK_OFFS);
7000f077eb5SThomas Petazzoni 	return 0;
7010f077eb5SThomas Petazzoni }
7020f077eb5SThomas Petazzoni 
armada_370_xp_mpic_resume(void)7030f077eb5SThomas Petazzoni static void armada_370_xp_mpic_resume(void)
7040f077eb5SThomas Petazzoni {
7050f077eb5SThomas Petazzoni 	int nirqs;
7060f077eb5SThomas Petazzoni 	irq_hw_number_t irq;
7070f077eb5SThomas Petazzoni 
7080f077eb5SThomas Petazzoni 	/* Re-enable interrupts */
7090f077eb5SThomas Petazzoni 	nirqs = (readl(main_int_base + ARMADA_370_XP_INT_CONTROL) >> 2) & 0x3ff;
7100f077eb5SThomas Petazzoni 	for (irq = 0; irq < nirqs; irq++) {
7110f077eb5SThomas Petazzoni 		struct irq_data *data;
7120f077eb5SThomas Petazzoni 		int virq;
7130f077eb5SThomas Petazzoni 
7140f077eb5SThomas Petazzoni 		virq = irq_linear_revmap(armada_370_xp_mpic_domain, irq);
7150f077eb5SThomas Petazzoni 		if (virq == 0)
7160f077eb5SThomas Petazzoni 			continue;
7170f077eb5SThomas Petazzoni 
7180fa4ce74SThomas Petazzoni 		data = irq_get_irq_data(virq);
7190fa4ce74SThomas Petazzoni 
7200fa4ce74SThomas Petazzoni 		if (!is_percpu_irq(irq)) {
7210fa4ce74SThomas Petazzoni 			/* Non per-CPU interrupts */
7220f077eb5SThomas Petazzoni 			writel(irq, per_cpu_int_base +
7230f077eb5SThomas Petazzoni 			       ARMADA_370_XP_INT_CLEAR_MASK_OFFS);
7240fa4ce74SThomas Petazzoni 			if (!irqd_irq_disabled(data))
7250fa4ce74SThomas Petazzoni 				armada_370_xp_irq_unmask(data);
7260fa4ce74SThomas Petazzoni 		} else {
7270fa4ce74SThomas Petazzoni 			/* Per-CPU interrupts */
7280f077eb5SThomas Petazzoni 			writel(irq, main_int_base +
7290f077eb5SThomas Petazzoni 			       ARMADA_370_XP_INT_SET_ENABLE_OFFS);
7300f077eb5SThomas Petazzoni 
7310fa4ce74SThomas Petazzoni 			/*
7320fa4ce74SThomas Petazzoni 			 * Re-enable on the current CPU,
7330fa4ce74SThomas Petazzoni 			 * armada_xp_mpic_reenable_percpu() will take
7340fa4ce74SThomas Petazzoni 			 * care of secondary CPUs when they come up.
7350fa4ce74SThomas Petazzoni 			 */
7360fa4ce74SThomas Petazzoni 			if (irq_percpu_is_enabled(virq))
7370f077eb5SThomas Petazzoni 				armada_370_xp_irq_unmask(data);
7380f077eb5SThomas Petazzoni 		}
7390fa4ce74SThomas Petazzoni 	}
7400f077eb5SThomas Petazzoni 
7410f077eb5SThomas Petazzoni 	/* Reconfigure doorbells for IPIs and MSIs */
7420f077eb5SThomas Petazzoni 	writel(doorbell_mask_reg,
7430f077eb5SThomas Petazzoni 	       per_cpu_int_base + ARMADA_370_XP_IN_DRBEL_MSK_OFFS);
7440f077eb5SThomas Petazzoni 	if (doorbell_mask_reg & IPI_DOORBELL_MASK)
7450f077eb5SThomas Petazzoni 		writel(0, per_cpu_int_base + ARMADA_370_XP_INT_CLEAR_MASK_OFFS);
7460f077eb5SThomas Petazzoni 	if (doorbell_mask_reg & PCI_MSI_DOORBELL_MASK)
7470f077eb5SThomas Petazzoni 		writel(1, per_cpu_int_base + ARMADA_370_XP_INT_CLEAR_MASK_OFFS);
748f02147ddSMarc Zyngier 
749f02147ddSMarc Zyngier 	ipi_resume();
7500f077eb5SThomas Petazzoni }
7510f077eb5SThomas Petazzoni 
7526c880902SBen Dooks static struct syscore_ops armada_370_xp_mpic_syscore_ops = {
7530f077eb5SThomas Petazzoni 	.suspend	= armada_370_xp_mpic_suspend,
7540f077eb5SThomas Petazzoni 	.resume		= armada_370_xp_mpic_resume,
7550f077eb5SThomas Petazzoni };
7560f077eb5SThomas Petazzoni 
armada_370_xp_mpic_of_init(struct device_node * node,struct device_node * parent)757b313ada8SThomas Petazzoni static int __init armada_370_xp_mpic_of_init(struct device_node *node,
758b313ada8SThomas Petazzoni 					     struct device_node *parent)
759b313ada8SThomas Petazzoni {
760627dfcc2SThomas Petazzoni 	struct resource main_int_res, per_cpu_int_res;
7615724be84SMaxime Ripard 	int nr_irqs, i;
762b313ada8SThomas Petazzoni 	u32 control;
763b313ada8SThomas Petazzoni 
764627dfcc2SThomas Petazzoni 	BUG_ON(of_address_to_resource(node, 0, &main_int_res));
765627dfcc2SThomas Petazzoni 	BUG_ON(of_address_to_resource(node, 1, &per_cpu_int_res));
766b313ada8SThomas Petazzoni 
767627dfcc2SThomas Petazzoni 	BUG_ON(!request_mem_region(main_int_res.start,
768627dfcc2SThomas Petazzoni 				   resource_size(&main_int_res),
769627dfcc2SThomas Petazzoni 				   node->full_name));
770627dfcc2SThomas Petazzoni 	BUG_ON(!request_mem_region(per_cpu_int_res.start,
771627dfcc2SThomas Petazzoni 				   resource_size(&per_cpu_int_res),
772627dfcc2SThomas Petazzoni 				   node->full_name));
773627dfcc2SThomas Petazzoni 
774627dfcc2SThomas Petazzoni 	main_int_base = ioremap(main_int_res.start,
775627dfcc2SThomas Petazzoni 				resource_size(&main_int_res));
776b313ada8SThomas Petazzoni 	BUG_ON(!main_int_base);
777627dfcc2SThomas Petazzoni 
778627dfcc2SThomas Petazzoni 	per_cpu_int_base = ioremap(per_cpu_int_res.start,
779627dfcc2SThomas Petazzoni 				   resource_size(&per_cpu_int_res));
780b313ada8SThomas Petazzoni 	BUG_ON(!per_cpu_int_base);
781b313ada8SThomas Petazzoni 
782b313ada8SThomas Petazzoni 	control = readl(main_int_base + ARMADA_370_XP_INT_CONTROL);
783b73842b7SThomas Petazzoni 	nr_irqs = (control >> 2) & 0x3ff;
784b73842b7SThomas Petazzoni 
785b73842b7SThomas Petazzoni 	for (i = 0; i < nr_irqs; i++)
786b73842b7SThomas Petazzoni 		writel(i, main_int_base + ARMADA_370_XP_INT_CLEAR_ENABLE_OFFS);
787b313ada8SThomas Petazzoni 
788b313ada8SThomas Petazzoni 	armada_370_xp_mpic_domain =
789b73842b7SThomas Petazzoni 		irq_domain_add_linear(node, nr_irqs,
790b313ada8SThomas Petazzoni 				&armada_370_xp_mpic_irq_ops, NULL);
791627dfcc2SThomas Petazzoni 	BUG_ON(!armada_370_xp_mpic_domain);
79296f0d93aSMarc Zyngier 	irq_domain_update_bus_token(armada_370_xp_mpic_domain, DOMAIN_BUS_WIRED);
793b313ada8SThomas Petazzoni 
794933a24b0SEzequiel Garcia 	/* Setup for the boot CPU */
79528da06dfSMaxime Ripard 	armada_xp_mpic_perf_init();
796b313ada8SThomas Petazzoni 	armada_xp_mpic_smp_cpu_init();
797b313ada8SThomas Petazzoni 
79831f614edSThomas Petazzoni 	armada_370_xp_msi_init(node, main_int_res.start);
79931f614edSThomas Petazzoni 
800bc69b8adSEzequiel Garcia 	parent_irq = irq_of_parse_and_map(node, 0);
801bc69b8adSEzequiel Garcia 	if (parent_irq <= 0) {
802bc69b8adSEzequiel Garcia 		irq_set_default_host(armada_370_xp_mpic_domain);
803b313ada8SThomas Petazzoni 		set_handle_irq(armada_370_xp_handle_irq);
804ef37d337SThomas Petazzoni #ifdef CONFIG_SMP
805f02147ddSMarc Zyngier 		armada_xp_ipi_init(node);
806cb5ff2d2SRichard Cochran 		cpuhp_setup_state_nocalls(CPUHP_AP_IRQ_ARMADA_XP_STARTING,
80773c1b41eSThomas Gleixner 					  "irqchip/armada/ipi:starting",
808cb5ff2d2SRichard Cochran 					  armada_xp_mpic_starting_cpu, NULL);
809ef37d337SThomas Petazzoni #endif
810bc69b8adSEzequiel Garcia 	} else {
8115724be84SMaxime Ripard #ifdef CONFIG_SMP
812008b69e4SThomas Gleixner 		cpuhp_setup_state_nocalls(CPUHP_AP_IRQ_ARMADA_XP_STARTING,
81373c1b41eSThomas Gleixner 					  "irqchip/armada/cascade:starting",
814cb5ff2d2SRichard Cochran 					  mpic_cascaded_starting_cpu, NULL);
8155724be84SMaxime Ripard #endif
816bc69b8adSEzequiel Garcia 		irq_set_chained_handler(parent_irq,
817bc69b8adSEzequiel Garcia 					armada_370_xp_mpic_handle_cascade_irq);
818bc69b8adSEzequiel Garcia 	}
819b313ada8SThomas Petazzoni 
8200f077eb5SThomas Petazzoni 	register_syscore_ops(&armada_370_xp_mpic_syscore_ops);
8210f077eb5SThomas Petazzoni 
822b313ada8SThomas Petazzoni 	return 0;
823b313ada8SThomas Petazzoni }
824b313ada8SThomas Petazzoni 
8259339d432SThomas Petazzoni IRQCHIP_DECLARE(armada_370_xp_mpic, "marvell,mpic", armada_370_xp_mpic_of_init);
826