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