[PATCH 2/6] irqchip/armada-370-xp: Implement SoC Error interrupts

Pali Rohár
2022-05-06T13:41:10Z

MPIC IRQ 4 is used as SoC Error Summary interrupt and provides access to
another hierarchy of SoC Error interrupts. Implement a new IRQ chip and
domain for accessing this IRQ hierarchy.

Signed-off-by: Pali Rohár <pali@kernel.org>
---
drivers/irqchip/irq-armada-370-xp.c | 213 +++++++++++++++++++++++++++-
1 file changed, 210 insertions(+), 3 deletions(-)

diff --git a/drivers/irqchip/irq-armada-370-xp.c b/drivers/irqchip/irq-armada-370-xp.c
index ebd76ea1c69b..71578b65f5c8 100644
--- a/drivers/irqchip/irq-armada-370-xp.c
+++ b/drivers/irqchip/irq-armada-370-xp.c
@@ -117,6 +117,8 @@
/* Registers relative to main_int_base */
#define ARMADA_370_XP_INT_CONTROL (0x00)
#define ARMADA_370_XP_SW_TRIG_INT_OFFS (0x04)
+#define ARMADA_370_XP_INT_SOC_ERR_0_CAUSE_OFFS (0x20)
+#define ARMADA_370_XP_INT_SOC_ERR_1_CAUSE_OFFS (0x24)
#define ARMADA_370_XP_INT_SET_ENABLE_OFFS (0x30)
#define ARMADA_370_XP_INT_CLEAR_ENABLE_OFFS (0x34)
#define ARMADA_370_XP_INT_SOURCE_CTL(irq) (0x100 + irq*4)
@@ -130,6 +132,8 @@
#define ARMADA_370_XP_CPU_INTACK_OFFS (0x44)
#define ARMADA_370_XP_INT_SET_MASK_OFFS (0x48)
#define ARMADA_370_XP_INT_CLEAR_MASK_OFFS (0x4C)
+#define ARMADA_370_XP_INT_SOC_ERR_0_MASK_OFF (0x50)
+#define ARMADA_370_XP_INT_SOC_ERR_1_MASK_OFF (0x54)
#define ARMADA_370_XP_INT_FABRIC_MASK_OFFS (0x54)
#define ARMADA_370_XP_INT_CAUSE_PERF(cpu) (1 << cpu)

@@ -146,6 +150,8 @@
static void __iomem *per_cpu_int_base;
static void __iomem *main_int_base;
static struct irq_domain *armada_370_xp_mpic_domain;
+static struct irq_domain *armada_370_xp_soc_err_domain;
+static unsigned int soc_err_irq_num_regs;
static u32 doorbell_mask_reg;
static int parent_irq;
#ifdef CONFIG_PCI_MSI
@@ -156,6 +162,8 @@ static DEFINE_MUTEX(msi_used_lock);
static phys_addr_t msi_doorbell_addr;
#endif

+static void armada_370_xp_soc_err_irq_unmask(struct irq_data *d);
+
static inline bool is_percpu_irq(irq_hw_number_t irq)
{
if (irq <= ARMADA_370_XP_MAX_PER_CPU_IRQS)
@@ -509,6 +517,27 @@ static void armada_xp_mpic_reenable_percpu(void)
armada_370_xp_irq_unmask(data);
}

+ /* Re-enable per-CPU SoC Error interrupts that were enabled before suspend */
+ for (irq = 0; irq < soc_err_irq_num_regs * 32; irq++) {
+ struct irq_data *data;
+ int virq;
+
+ virq = irq_linear_revmap(armada_370_xp_soc_err_domain, irq);
+ if (virq == 0)
+ continue;
+
+ data = irq_get_irq_data(virq);
+
+ if (!irq_percpu_is_enabled(virq))
+ continue;
+
+ armada_370_xp_soc_err_irq_unmask(data);
+ }
+
+ /* Unmask summary SoC Error Interrupt */
+ if (soc_err_irq_num_regs > 0)
+ writel(4, per_cpu_int_base + ARMADA_370_XP_INT_CLEAR_MASK_OFFS);
+
ipi_resume();
}

@@ -546,8 +575,8 @@ static struct irq_chip armada_370_xp_irq_chip = {
static int armada_370_xp_mpic_irq_map(struct irq_domain *h,
unsigned int virq, irq_hw_number_t hw)
{
- /* IRQs 0 and 1 cannot be mapped, they are handled internally */
- if (hw <= 1)
+ /* IRQs 0, 1 and 4 cannot be mapped, they are handled internally */
+ if (hw <= 1 || hw == 4)
return -EINVAL;

armada_370_xp_irq_mask(irq_get_irq_data(virq));
@@ -577,6 +606,99 @@ static const struct irq_domain_ops armada_370_xp_mpic_irq_ops = {
.xlate = irq_domain_xlate_onecell,
};

+static DEFINE_RAW_SPINLOCK(armada_370_xp_soc_err_lock);
+
+static void armada_370_xp_soc_err_irq_mask(struct irq_data *d)
+{
+ irq_hw_number_t hwirq = irqd_to_hwirq(d);
+ u32 reg, mask;
+
+ reg = hwirq >= 32 ? ARMADA_370_XP_INT_SOC_ERR_1_MASK_OFF
+ : ARMADA_370_XP_INT_SOC_ERR_0_MASK_OFF;
+
+ raw_spin_lock(&armada_370_xp_soc_err_lock);
+ mask = readl(per_cpu_int_base + reg);
+ mask &= ~BIT(hwirq % 32);
+ writel(mask, per_cpu_int_base + reg);
+ raw_spin_unlock(&armada_370_xp_soc_err_lock);
+}
+
+static void armada_370_xp_soc_err_irq_unmask(struct irq_data *d)
+{
+ irq_hw_number_t hwirq = irqd_to_hwirq(d);
+ u32 reg, mask;
+
+ reg = hwirq >= 32 ? ARMADA_370_XP_INT_SOC_ERR_1_MASK_OFF
+ : ARMADA_370_XP_INT_SOC_ERR_0_MASK_OFF;
+
+ raw_spin_lock(&armada_370_xp_soc_err_lock);
+ mask = readl(per_cpu_int_base + reg);
+ mask |= BIT(hwirq % 32);
+ writel(mask, per_cpu_int_base + reg);
+ raw_spin_unlock(&armada_370_xp_soc_err_lock);
+}
+
+static int armada_370_xp_soc_err_irq_mask_on_cpu(void *par)
+{
+ struct irq_data *d = par;
+ armada_370_xp_soc_err_irq_mask(d);
+ return 0;
+}
+
+static int armada_370_xp_soc_err_irq_unmask_on_cpu(void *par)
+{
+ struct irq_data *d = par;
+ armada_370_xp_soc_err_irq_unmask(d);
+ return 0;
+}
+
+static int armada_xp_soc_err_irq_set_affinity(struct irq_data *d,
+ const struct cpumask *mask,
+ bool force)
+{
+ unsigned int cpu;
+
+ cpus_read_lock();
+
+ /* First disable IRQ on all cores */
+ for_each_online_cpu(cpu)
+ smp_call_on_cpu(cpu, armada_370_xp_soc_err_irq_mask_on_cpu, d, true);
+
+ /* Select a single core from the affinity mask which is online */
+ cpu = cpumask_any_and(mask, cpu_online_mask);
+ smp_call_on_cpu(cpu, armada_370_xp_soc_err_irq_unmask_on_cpu, d, true);
+
+ cpus_read_unlock();
+
+ irq_data_update_effective_affinity(d, cpumask_of(cpu));
+
+ return IRQ_SET_MASK_OK;
+}
+
+static struct irq_chip armada_370_xp_soc_err_irq_chip = {
+ .name = "MPIC SOC",
+ .irq_mask = armada_370_xp_soc_err_irq_mask,
+ .irq_unmask = armada_370_xp_soc_err_irq_unmask,
+ .irq_set_affinity = armada_xp_soc_err_irq_set_affinity,
+};
+
+static int armada_370_xp_soc_err_irq_map(struct irq_domain *h,
+ unsigned int virq, irq_hw_number_t hw)
+{
+ armada_370_xp_soc_err_irq_mask(irq_get_irq_data(virq));
+ irq_set_status_flags(virq, IRQ_LEVEL);
+ irq_set_percpu_devid(virq);
+ irq_set_chip_and_handler(virq, &armada_370_xp_soc_err_irq_chip,
+ handle_percpu_devid_irq);
+ irq_set_probe(virq);
+ return 0;
+}
+
+static const struct irq_domain_ops armada_370_xp_soc_err_irq_ops = {
+ .map = armada_370_xp_soc_err_irq_map,
+ .xlate = irq_domain_xlate_onecell,
+};
+
#ifdef CONFIG_PCI_MSI
static void armada_370_xp_handle_msi_irq(struct pt_regs *regs, bool is_chained)
{
@@ -605,6 +727,32 @@ static void armada_370_xp_handle_msi_irq(struct pt_regs *regs, bool is_chained)
static void armada_370_xp_handle_msi_irq(struct pt_regs *r, bool b) {}
#endif

+static void armada_370_xp_handle_soc_err_irq(void)
+{
+ unsigned long status, bit;
+ u32 mask, cause;
+
+ if (soc_err_irq_num_regs < 1)
+ return;
+
+ mask = readl(per_cpu_int_base + ARMADA_370_XP_INT_SOC_ERR_0_MASK_OFF);
+ cause = readl(main_int_base + ARMADA_370_XP_INT_SOC_ERR_0_CAUSE_OFFS);
+ status = cause & mask;
+
+ for_each_set_bit(bit, &status, 32)
+ generic_handle_domain_irq(armada_370_xp_soc_err_domain, bit);
+
+ if (soc_err_irq_num_regs < 2)
+ return;
+
+ mask = readl(per_cpu_int_base + ARMADA_370_XP_INT_SOC_ERR_1_MASK_OFF);
+ cause = readl(main_int_base + ARMADA_370_XP_INT_SOC_ERR_1_CAUSE_OFFS);
+ status = cause & mask;
+
+ for_each_set_bit(bit, &status, 32)
+ generic_handle_domain_irq(armada_370_xp_soc_err_domain, bit + 32);
+}
+
static void armada_370_xp_mpic_handle_cascade_irq(struct irq_desc *desc)
{
struct irq_chip *chip = irq_desc_get_chip(desc);
@@ -630,6 +778,11 @@ static void armada_370_xp_mpic_handle_cascade_irq(struct irq_desc *desc)
continue;
}

+ if (irqn == 4) {
+ armada_370_xp_handle_soc_err_irq();
+ continue;
+ }
+
generic_handle_domain_irq(armada_370_xp_mpic_domain, irqn);
}

@@ -649,7 +802,7 @@ armada_370_xp_handle_irq(struct pt_regs *regs)
if (irqnr > 1022)
break;

- if (irqnr > 1) {
+ if (irqnr > 1 && irqnr != 4) {
generic_handle_domain_irq(armada_370_xp_mpic_domain,
irqnr);
continue;
@@ -659,6 +812,10 @@ armada_370_xp_handle_irq(struct pt_regs *regs)
if (irqnr == 1)
armada_370_xp_handle_msi_irq(regs, false);

+ /* SoC Error handling */
+ if (irqnr == 4)
+ armada_370_xp_handle_soc_err_irq();
+
#ifdef CONFIG_SMP
/* IPI Handling */
if (irqnr == 0) {
@@ -722,6 +879,26 @@ static void armada_370_xp_mpic_resume(void)
}
}

+ /* Re-enable per-CPU SoC Error interrupts */
+ for (irq = 0; irq < soc_err_irq_num_regs * 32; irq++) {
+ struct irq_data *data;
+ int virq;
+
+ virq = irq_linear_revmap(armada_370_xp_soc_err_domain, irq);
+ if (virq == 0)
+ continue;
+
+ data = irq_get_irq_data(virq);
+
+ /*
+ * Re-enable on the current CPU,
+ * armada_xp_mpic_reenable_percpu() will take
+ * care of secondary CPUs when they come up.
+ */
+ if (irq_percpu_is_enabled(virq))
+ armada_370_xp_soc_err_irq_unmask(data);
+ }
+
/* Reconfigure doorbells for IPIs and MSIs */
writel(doorbell_mask_reg,
per_cpu_int_base + ARMADA_370_XP_IN_DRBEL_MSK_OFFS);
@@ -730,6 +907,10 @@ static void armada_370_xp_mpic_resume(void)
if (doorbell_mask_reg & PCI_MSI_DOORBELL_MASK)
writel(1, per_cpu_int_base + ARMADA_370_XP_INT_CLEAR_MASK_OFFS);

+ /* Unmask summary SoC Error Interrupt */
+ if (soc_err_irq_num_regs > 0)
+ writel(4, per_cpu_int_base + ARMADA_370_XP_INT_CLEAR_MASK_OFFS);
+
ipi_resume();
}

@@ -742,6 +923,7 @@ static int __init armada_370_xp_mpic_of_init(struct device_node *node,
struct device_node *parent)
{
struct resource main_int_res, per_cpu_int_res;
+ struct device_node *soc_err_node;
int nr_irqs, i;
u32 control;

@@ -775,12 +957,37 @@ static int __init armada_370_xp_mpic_of_init(struct device_node *node,
BUG_ON(!armada_370_xp_mpic_domain);
irq_domain_update_bus_token(armada_370_xp_mpic_domain, DOMAIN_BUS_WIRED);

+ soc_err_node = of_get_next_child(node, NULL);
+ if (!soc_err_node) {
+ pr_warn("Missing SoC Error Interrupt Controller node\n");
+ pr_warn("Extended interrupts are not supported\n");
+ } else {
+ pr_info("Registering MPIC SoC Error Interrupt Controller\n");
+ /*
+ * Armada 370 and XP have only 32 SoC Error IRQs in one register
+ * and other Armada platforms have 64 IRQs in two registers.
+ */
+ soc_err_irq_num_regs =
+ of_machine_is_compatible("marvell,armada-370-xp") ? 1 : 2;
+ armada_370_xp_soc_err_domain =
+ irq_domain_add_hierarchy(armada_370_xp_mpic_domain, 0,
+ soc_err_irq_num_regs * 32,
+ soc_err_node,
+ &armada_370_xp_soc_err_irq_ops,
+ NULL);
+ BUG_ON(!armada_370_xp_soc_err_domain);
+ }
+
/* Setup for the boot CPU */
armada_xp_mpic_perf_init();
armada_xp_mpic_smp_cpu_init();

armada_370_xp_msi_init(node, main_int_res.start);

+ /* Unmask summary SoC Error Interrupt */
+ if (soc_err_irq_num_regs > 0)
+ writel(4, per_cpu_int_base + ARMADA_370_XP_INT_CLEAR_MASK_OFFS);
+
parent_irq = irq_of_parse_and_map(node, 0);
if (parent_irq <= 0) {
irq_set_default_host(armada_370_xp_mpic_domain);
--
2.20.1