Commit f02147dd authored by Marc Zyngier's avatar Marc Zyngier
Browse files

irqchip/armada-370-xp: Configure IPIs as standard interrupts



To introduce IPIs as standard interrupts to the Armada 370-XP
driver, let's allocate a completely separate irqdomain and
irqchip combo that lives parallel to the "standard" one.

This effectively should be modelled as a chained interrupt
controller, but the code is in such a state that it is
pretty hard to shoehorn, as it would require the rewrite
of the MSI layer as well.

Signed-off-by: default avatarMarc Zyngier <maz@kernel.org>
parent a2df12c5
Loading
Loading
Loading
Loading
+178 −84
Original line number Diff line number Diff line
@@ -310,7 +310,134 @@ static inline int armada_370_xp_msi_init(struct device_node *node,
}
#endif

static void armada_xp_mpic_perf_init(void)
{
	unsigned long cpuid = cpu_logical_map(smp_processor_id());

	/* Enable Performance Counter Overflow interrupts */
	writel(ARMADA_370_XP_INT_CAUSE_PERF(cpuid),
	       per_cpu_int_base + ARMADA_370_XP_INT_FABRIC_MASK_OFFS);
}

#ifdef CONFIG_SMP
static struct irq_domain *ipi_domain;

static void armada_370_xp_ipi_mask(struct irq_data *d)
{
	u32 reg;
	reg = readl(per_cpu_int_base + ARMADA_370_XP_IN_DRBEL_MSK_OFFS);
	reg &= ~BIT(d->hwirq);
	writel(reg, per_cpu_int_base + ARMADA_370_XP_IN_DRBEL_MSK_OFFS);
}

static void armada_370_xp_ipi_unmask(struct irq_data *d)
{
	u32 reg;
	reg = readl(per_cpu_int_base + ARMADA_370_XP_IN_DRBEL_MSK_OFFS);
	reg |= BIT(d->hwirq);
	writel(reg, per_cpu_int_base + ARMADA_370_XP_IN_DRBEL_MSK_OFFS);
}

static void armada_370_xp_ipi_send_mask(struct irq_data *d,
					const struct cpumask *mask)
{
	unsigned long map = 0;
	int cpu;

	/* Convert our logical CPU mask into a physical one. */
	for_each_cpu(cpu, mask)
		map |= 1 << cpu_logical_map(cpu);

	/*
	 * Ensure that stores to Normal memory are visible to the
	 * other CPUs before issuing the IPI.
	 */
	dsb();

	/* submit softirq */
	writel((map << 8) | d->hwirq, main_int_base +
		ARMADA_370_XP_SW_TRIG_INT_OFFS);
}

static void armada_370_xp_ipi_eoi(struct irq_data *d)
{
	writel(~BIT(d->hwirq), per_cpu_int_base + ARMADA_370_XP_IN_DRBEL_CAUSE_OFFS);
}

static struct irq_chip ipi_irqchip = {
	.name		= "IPI",
	.irq_mask	= armada_370_xp_ipi_mask,
	.irq_unmask	= armada_370_xp_ipi_unmask,
	.irq_eoi	= armada_370_xp_ipi_eoi,
	.ipi_send_mask	= armada_370_xp_ipi_send_mask,
};

static int armada_370_xp_ipi_alloc(struct irq_domain *d,
					 unsigned int virq,
					 unsigned int nr_irqs, void *args)
{
	int i;

	for (i = 0; i < nr_irqs; i++) {
		irq_set_percpu_devid(virq + i);
		irq_domain_set_info(d, virq + i, i, &ipi_irqchip,
				    d->host_data,
				    handle_percpu_devid_fasteoi_ipi,
				    NULL, NULL);
	}

	return 0;
}

static void armada_370_xp_ipi_free(struct irq_domain *d,
					 unsigned int virq,
					 unsigned int nr_irqs)
{
	/* Not freeing IPIs */
}

static const struct irq_domain_ops ipi_domain_ops = {
	.alloc	= armada_370_xp_ipi_alloc,
	.free	= armada_370_xp_ipi_free,
};

static void ipi_resume(void)
{
	int i;

	for (i = 0; i < IPI_DOORBELL_END; i++) {
		int irq;

		irq = irq_find_mapping(ipi_domain, i);
		if (irq <= 0)
			continue;
		if (irq_percpu_is_enabled(irq)) {
			struct irq_data *d;
			d = irq_domain_get_irq_data(ipi_domain, irq);
			armada_370_xp_ipi_unmask(d);
		}
	}
}

static __init void armada_xp_ipi_init(struct device_node *node)
{
	int base_ipi;

	ipi_domain = irq_domain_create_linear(of_node_to_fwnode(node),
					      IPI_DOORBELL_END,
					      &ipi_domain_ops, NULL);
	if (WARN_ON(!ipi_domain))
		return;

	irq_domain_update_bus_token(ipi_domain, DOMAIN_BUS_IPI);
	base_ipi = __irq_domain_alloc_irqs(ipi_domain, -1, IPI_DOORBELL_END,
					   NUMA_NO_NODE, NULL, false, NULL);
	if (WARN_ON(!base_ipi))
		return;

	set_smp_ipi_range(base_ipi, IPI_DOORBELL_END);
}

static DEFINE_RAW_SPINLOCK(irq_controller_lock);

static int armada_xp_set_affinity(struct irq_data *d,
@@ -334,43 +461,6 @@ static int armada_xp_set_affinity(struct irq_data *d,

	return IRQ_SET_MASK_OK;
}
#endif

static struct irq_chip armada_370_xp_irq_chip = {
	.name		= "MPIC",
	.irq_mask       = armada_370_xp_irq_mask,
	.irq_mask_ack   = armada_370_xp_irq_mask,
	.irq_unmask     = armada_370_xp_irq_unmask,
#ifdef CONFIG_SMP
	.irq_set_affinity = armada_xp_set_affinity,
#endif
	.flags		= IRQCHIP_SKIP_SET_WAKE | IRQCHIP_MASK_ON_SUSPEND,
};

static int armada_370_xp_mpic_irq_map(struct irq_domain *h,
				      unsigned int virq, irq_hw_number_t hw)
{
	armada_370_xp_irq_mask(irq_get_irq_data(virq));
	if (!is_percpu_irq(hw))
		writel(hw, per_cpu_int_base +
			ARMADA_370_XP_INT_CLEAR_MASK_OFFS);
	else
		writel(hw, main_int_base + ARMADA_370_XP_INT_SET_ENABLE_OFFS);
	irq_set_status_flags(virq, IRQ_LEVEL);

	if (is_percpu_irq(hw)) {
		irq_set_percpu_devid(virq);
		irq_set_chip_and_handler(virq, &armada_370_xp_irq_chip,
					handle_percpu_devid_irq);
	} else {
		irq_set_chip_and_handler(virq, &armada_370_xp_irq_chip,
					handle_level_irq);
		irqd_set_single_target(irq_desc_get_irq_data(irq_to_desc(virq)));
	}
	irq_set_probe(virq);

	return 0;
}

static void armada_xp_mpic_smp_cpu_init(void)
{
@@ -383,48 +473,16 @@ static void armada_xp_mpic_smp_cpu_init(void)
	for (i = 0; i < nr_irqs; i++)
		writel(i, per_cpu_int_base + ARMADA_370_XP_INT_SET_MASK_OFFS);

	/* Disable all IPIs */
	writel(0, per_cpu_int_base + ARMADA_370_XP_IN_DRBEL_MSK_OFFS);

	/* Clear pending IPIs */
	writel(0, per_cpu_int_base + ARMADA_370_XP_IN_DRBEL_CAUSE_OFFS);

	/* Enable first 8 IPIs */
	writel(IPI_DOORBELL_MASK, per_cpu_int_base +
		ARMADA_370_XP_IN_DRBEL_MSK_OFFS);

	/* Unmask IPI interrupt */
	writel(0, per_cpu_int_base + ARMADA_370_XP_INT_CLEAR_MASK_OFFS);
}

static void armada_xp_mpic_perf_init(void)
{
	unsigned long cpuid = cpu_logical_map(smp_processor_id());

	/* Enable Performance Counter Overflow interrupts */
	writel(ARMADA_370_XP_INT_CAUSE_PERF(cpuid),
	       per_cpu_int_base + ARMADA_370_XP_INT_FABRIC_MASK_OFFS);
}

#ifdef CONFIG_SMP
static void armada_mpic_send_doorbell(const struct cpumask *mask,
				      unsigned int irq)
{
	int cpu;
	unsigned long map = 0;

	/* Convert our logical CPU mask into a physical one. */
	for_each_cpu(cpu, mask)
		map |= 1 << cpu_logical_map(cpu);

	/*
	 * Ensure that stores to Normal memory are visible to the
	 * other CPUs before issuing the IPI.
	 */
	dsb();

	/* submit softirq */
	writel((map << 8) | irq, main_int_base +
		ARMADA_370_XP_SW_TRIG_INT_OFFS);
}

static void armada_xp_mpic_reenable_percpu(void)
{
	unsigned int irq;
@@ -445,6 +503,8 @@ static void armada_xp_mpic_reenable_percpu(void)

		armada_370_xp_irq_unmask(data);
	}

	ipi_resume();
}

static int armada_xp_mpic_starting_cpu(unsigned int cpu)
@@ -462,8 +522,47 @@ static int mpic_cascaded_starting_cpu(unsigned int cpu)
	enable_percpu_irq(parent_irq, IRQ_TYPE_NONE);
	return 0;
}
#else
static void armada_xp_mpic_smp_cpu_init(void) {}
static void ipi_resume(void) {}
#endif

static struct irq_chip armada_370_xp_irq_chip = {
	.name		= "MPIC",
	.irq_mask       = armada_370_xp_irq_mask,
	.irq_mask_ack   = armada_370_xp_irq_mask,
	.irq_unmask     = armada_370_xp_irq_unmask,
#ifdef CONFIG_SMP
	.irq_set_affinity = armada_xp_set_affinity,
#endif
	.flags		= IRQCHIP_SKIP_SET_WAKE | IRQCHIP_MASK_ON_SUSPEND,
};

static int armada_370_xp_mpic_irq_map(struct irq_domain *h,
				      unsigned int virq, irq_hw_number_t hw)
{
	armada_370_xp_irq_mask(irq_get_irq_data(virq));
	if (!is_percpu_irq(hw))
		writel(hw, per_cpu_int_base +
			ARMADA_370_XP_INT_CLEAR_MASK_OFFS);
	else
		writel(hw, main_int_base + ARMADA_370_XP_INT_SET_ENABLE_OFFS);
	irq_set_status_flags(virq, IRQ_LEVEL);

	if (is_percpu_irq(hw)) {
		irq_set_percpu_devid(virq);
		irq_set_chip_and_handler(virq, &armada_370_xp_irq_chip,
					handle_percpu_devid_irq);
	} else {
		irq_set_chip_and_handler(virq, &armada_370_xp_irq_chip,
					handle_level_irq);
		irqd_set_single_target(irq_desc_get_irq_data(irq_to_desc(virq)));
	}
	irq_set_probe(virq);

	return 0;
}

static const struct irq_domain_ops armada_370_xp_mpic_irq_ops = {
	.map = armada_370_xp_mpic_irq_map,
	.xlate = irq_domain_xlate_onecell,
@@ -562,22 +661,15 @@ armada_370_xp_handle_irq(struct pt_regs *regs)
#ifdef CONFIG_SMP
		/* IPI Handling */
		if (irqnr == 0) {
			u32 ipimask, ipinr;
			unsigned long ipimask;
			int ipi;

			ipimask = readl_relaxed(per_cpu_int_base +
						ARMADA_370_XP_IN_DRBEL_CAUSE_OFFS)
				& IPI_DOORBELL_MASK;

			writel(~ipimask, per_cpu_int_base +
				ARMADA_370_XP_IN_DRBEL_CAUSE_OFFS);

			/* Handle all pending doorbells */
			for (ipinr = IPI_DOORBELL_START;
			     ipinr < IPI_DOORBELL_END; ipinr++) {
				if (ipimask & (0x1 << ipinr))
					handle_IPI(ipinr, regs);
			}
			continue;
			for_each_set_bit(ipi, &ipimask, IPI_DOORBELL_END)
				handle_domain_irq(ipi_domain, ipi, regs);
		}
#endif

@@ -636,6 +728,8 @@ static void armada_370_xp_mpic_resume(void)
		writel(0, per_cpu_int_base + ARMADA_370_XP_INT_CLEAR_MASK_OFFS);
	if (doorbell_mask_reg & PCI_MSI_DOORBELL_MASK)
		writel(1, per_cpu_int_base + ARMADA_370_XP_INT_CLEAR_MASK_OFFS);

	ipi_resume();
}

static struct syscore_ops armada_370_xp_mpic_syscore_ops = {
@@ -691,7 +785,7 @@ static int __init armada_370_xp_mpic_of_init(struct device_node *node,
		irq_set_default_host(armada_370_xp_mpic_domain);
		set_handle_irq(armada_370_xp_handle_irq);
#ifdef CONFIG_SMP
		set_smp_cross_call(armada_mpic_send_doorbell);
		armada_xp_ipi_init(node);
		cpuhp_setup_state_nocalls(CPUHP_AP_IRQ_ARMADA_XP_STARTING,
					  "irqchip/armada/ipi:starting",
					  armada_xp_mpic_starting_cpu, NULL);