18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * Copyright 2001 MontaVista Software Inc.
48c2ecf20Sopenharmony_ci * Author: Jun Sun, jsun@mvista.com or jsun@junsun.net
58c2ecf20Sopenharmony_ci *
68c2ecf20Sopenharmony_ci * Copyright (C) 2001 Ralf Baechle
78c2ecf20Sopenharmony_ci * Copyright (C) 2005  MIPS Technologies, Inc.	All rights reserved.
88c2ecf20Sopenharmony_ci *	Author: Maciej W. Rozycki <macro@mips.com>
98c2ecf20Sopenharmony_ci *
108c2ecf20Sopenharmony_ci * This file define the irq handler for MIPS CPU interrupts.
118c2ecf20Sopenharmony_ci */
128c2ecf20Sopenharmony_ci
138c2ecf20Sopenharmony_ci/*
148c2ecf20Sopenharmony_ci * Almost all MIPS CPUs define 8 interrupt sources.  They are typically
158c2ecf20Sopenharmony_ci * level triggered (i.e., cannot be cleared from CPU; must be cleared from
168c2ecf20Sopenharmony_ci * device).
178c2ecf20Sopenharmony_ci *
188c2ecf20Sopenharmony_ci * The first two are software interrupts (i.e. not exposed as pins) which
198c2ecf20Sopenharmony_ci * may be used for IPIs in multi-threaded single-core systems.
208c2ecf20Sopenharmony_ci *
218c2ecf20Sopenharmony_ci * The last one is usually the CPU timer interrupt if the counter register
228c2ecf20Sopenharmony_ci * is present, or for old CPUs with an external FPU by convention it's the
238c2ecf20Sopenharmony_ci * FPU exception interrupt.
248c2ecf20Sopenharmony_ci */
258c2ecf20Sopenharmony_ci#include <linux/init.h>
268c2ecf20Sopenharmony_ci#include <linux/interrupt.h>
278c2ecf20Sopenharmony_ci#include <linux/kernel.h>
288c2ecf20Sopenharmony_ci#include <linux/irq.h>
298c2ecf20Sopenharmony_ci#include <linux/irqchip.h>
308c2ecf20Sopenharmony_ci#include <linux/irqdomain.h>
318c2ecf20Sopenharmony_ci
328c2ecf20Sopenharmony_ci#include <asm/irq_cpu.h>
338c2ecf20Sopenharmony_ci#include <asm/mipsregs.h>
348c2ecf20Sopenharmony_ci#include <asm/mipsmtregs.h>
358c2ecf20Sopenharmony_ci#include <asm/setup.h>
368c2ecf20Sopenharmony_ci
378c2ecf20Sopenharmony_cistatic struct irq_domain *irq_domain;
388c2ecf20Sopenharmony_cistatic struct irq_domain *ipi_domain;
398c2ecf20Sopenharmony_ci
408c2ecf20Sopenharmony_cistatic inline void unmask_mips_irq(struct irq_data *d)
418c2ecf20Sopenharmony_ci{
428c2ecf20Sopenharmony_ci	set_c0_status(IE_SW0 << d->hwirq);
438c2ecf20Sopenharmony_ci	irq_enable_hazard();
448c2ecf20Sopenharmony_ci}
458c2ecf20Sopenharmony_ci
468c2ecf20Sopenharmony_cistatic inline void mask_mips_irq(struct irq_data *d)
478c2ecf20Sopenharmony_ci{
488c2ecf20Sopenharmony_ci	clear_c0_status(IE_SW0 << d->hwirq);
498c2ecf20Sopenharmony_ci	irq_disable_hazard();
508c2ecf20Sopenharmony_ci}
518c2ecf20Sopenharmony_ci
528c2ecf20Sopenharmony_cistatic struct irq_chip mips_cpu_irq_controller = {
538c2ecf20Sopenharmony_ci	.name		= "MIPS",
548c2ecf20Sopenharmony_ci	.irq_ack	= mask_mips_irq,
558c2ecf20Sopenharmony_ci	.irq_mask	= mask_mips_irq,
568c2ecf20Sopenharmony_ci	.irq_mask_ack	= mask_mips_irq,
578c2ecf20Sopenharmony_ci	.irq_unmask	= unmask_mips_irq,
588c2ecf20Sopenharmony_ci	.irq_eoi	= unmask_mips_irq,
598c2ecf20Sopenharmony_ci	.irq_disable	= mask_mips_irq,
608c2ecf20Sopenharmony_ci	.irq_enable	= unmask_mips_irq,
618c2ecf20Sopenharmony_ci};
628c2ecf20Sopenharmony_ci
638c2ecf20Sopenharmony_ci/*
648c2ecf20Sopenharmony_ci * Basically the same as above but taking care of all the MT stuff
658c2ecf20Sopenharmony_ci */
668c2ecf20Sopenharmony_ci
678c2ecf20Sopenharmony_cistatic unsigned int mips_mt_cpu_irq_startup(struct irq_data *d)
688c2ecf20Sopenharmony_ci{
698c2ecf20Sopenharmony_ci	unsigned int vpflags = dvpe();
708c2ecf20Sopenharmony_ci
718c2ecf20Sopenharmony_ci	clear_c0_cause(C_SW0 << d->hwirq);
728c2ecf20Sopenharmony_ci	evpe(vpflags);
738c2ecf20Sopenharmony_ci	unmask_mips_irq(d);
748c2ecf20Sopenharmony_ci	return 0;
758c2ecf20Sopenharmony_ci}
768c2ecf20Sopenharmony_ci
778c2ecf20Sopenharmony_ci/*
788c2ecf20Sopenharmony_ci * While we ack the interrupt interrupts are disabled and thus we don't need
798c2ecf20Sopenharmony_ci * to deal with concurrency issues.  Same for mips_cpu_irq_end.
808c2ecf20Sopenharmony_ci */
818c2ecf20Sopenharmony_cistatic void mips_mt_cpu_irq_ack(struct irq_data *d)
828c2ecf20Sopenharmony_ci{
838c2ecf20Sopenharmony_ci	unsigned int vpflags = dvpe();
848c2ecf20Sopenharmony_ci	clear_c0_cause(C_SW0 << d->hwirq);
858c2ecf20Sopenharmony_ci	evpe(vpflags);
868c2ecf20Sopenharmony_ci	mask_mips_irq(d);
878c2ecf20Sopenharmony_ci}
888c2ecf20Sopenharmony_ci
898c2ecf20Sopenharmony_ci#ifdef CONFIG_GENERIC_IRQ_IPI
908c2ecf20Sopenharmony_ci
918c2ecf20Sopenharmony_cistatic void mips_mt_send_ipi(struct irq_data *d, unsigned int cpu)
928c2ecf20Sopenharmony_ci{
938c2ecf20Sopenharmony_ci	irq_hw_number_t hwirq = irqd_to_hwirq(d);
948c2ecf20Sopenharmony_ci	unsigned long flags;
958c2ecf20Sopenharmony_ci	int vpflags;
968c2ecf20Sopenharmony_ci
978c2ecf20Sopenharmony_ci	local_irq_save(flags);
988c2ecf20Sopenharmony_ci
998c2ecf20Sopenharmony_ci	/* We can only send IPIs to VPEs within the local core */
1008c2ecf20Sopenharmony_ci	WARN_ON(!cpus_are_siblings(smp_processor_id(), cpu));
1018c2ecf20Sopenharmony_ci
1028c2ecf20Sopenharmony_ci	vpflags = dvpe();
1038c2ecf20Sopenharmony_ci	settc(cpu_vpe_id(&cpu_data[cpu]));
1048c2ecf20Sopenharmony_ci	write_vpe_c0_cause(read_vpe_c0_cause() | (C_SW0 << hwirq));
1058c2ecf20Sopenharmony_ci	evpe(vpflags);
1068c2ecf20Sopenharmony_ci
1078c2ecf20Sopenharmony_ci	local_irq_restore(flags);
1088c2ecf20Sopenharmony_ci}
1098c2ecf20Sopenharmony_ci
1108c2ecf20Sopenharmony_ci#endif /* CONFIG_GENERIC_IRQ_IPI */
1118c2ecf20Sopenharmony_ci
1128c2ecf20Sopenharmony_cistatic struct irq_chip mips_mt_cpu_irq_controller = {
1138c2ecf20Sopenharmony_ci	.name		= "MIPS",
1148c2ecf20Sopenharmony_ci	.irq_startup	= mips_mt_cpu_irq_startup,
1158c2ecf20Sopenharmony_ci	.irq_ack	= mips_mt_cpu_irq_ack,
1168c2ecf20Sopenharmony_ci	.irq_mask	= mask_mips_irq,
1178c2ecf20Sopenharmony_ci	.irq_mask_ack	= mips_mt_cpu_irq_ack,
1188c2ecf20Sopenharmony_ci	.irq_unmask	= unmask_mips_irq,
1198c2ecf20Sopenharmony_ci	.irq_eoi	= unmask_mips_irq,
1208c2ecf20Sopenharmony_ci	.irq_disable	= mask_mips_irq,
1218c2ecf20Sopenharmony_ci	.irq_enable	= unmask_mips_irq,
1228c2ecf20Sopenharmony_ci#ifdef CONFIG_GENERIC_IRQ_IPI
1238c2ecf20Sopenharmony_ci	.ipi_send_single = mips_mt_send_ipi,
1248c2ecf20Sopenharmony_ci#endif
1258c2ecf20Sopenharmony_ci};
1268c2ecf20Sopenharmony_ci
1278c2ecf20Sopenharmony_ciasmlinkage void __weak plat_irq_dispatch(void)
1288c2ecf20Sopenharmony_ci{
1298c2ecf20Sopenharmony_ci	unsigned long pending = read_c0_cause() & read_c0_status() & ST0_IM;
1308c2ecf20Sopenharmony_ci	unsigned int virq;
1318c2ecf20Sopenharmony_ci	int irq;
1328c2ecf20Sopenharmony_ci
1338c2ecf20Sopenharmony_ci	if (!pending) {
1348c2ecf20Sopenharmony_ci		spurious_interrupt();
1358c2ecf20Sopenharmony_ci		return;
1368c2ecf20Sopenharmony_ci	}
1378c2ecf20Sopenharmony_ci
1388c2ecf20Sopenharmony_ci	pending >>= CAUSEB_IP;
1398c2ecf20Sopenharmony_ci	while (pending) {
1408c2ecf20Sopenharmony_ci		irq = fls(pending) - 1;
1418c2ecf20Sopenharmony_ci		if (IS_ENABLED(CONFIG_GENERIC_IRQ_IPI) && irq < 2)
1428c2ecf20Sopenharmony_ci			virq = irq_linear_revmap(ipi_domain, irq);
1438c2ecf20Sopenharmony_ci		else
1448c2ecf20Sopenharmony_ci			virq = irq_linear_revmap(irq_domain, irq);
1458c2ecf20Sopenharmony_ci		do_IRQ(virq);
1468c2ecf20Sopenharmony_ci		pending &= ~BIT(irq);
1478c2ecf20Sopenharmony_ci	}
1488c2ecf20Sopenharmony_ci}
1498c2ecf20Sopenharmony_ci
1508c2ecf20Sopenharmony_cistatic int mips_cpu_intc_map(struct irq_domain *d, unsigned int irq,
1518c2ecf20Sopenharmony_ci			     irq_hw_number_t hw)
1528c2ecf20Sopenharmony_ci{
1538c2ecf20Sopenharmony_ci	struct irq_chip *chip;
1548c2ecf20Sopenharmony_ci
1558c2ecf20Sopenharmony_ci	if (hw < 2 && cpu_has_mipsmt) {
1568c2ecf20Sopenharmony_ci		/* Software interrupts are used for MT/CMT IPI */
1578c2ecf20Sopenharmony_ci		chip = &mips_mt_cpu_irq_controller;
1588c2ecf20Sopenharmony_ci	} else {
1598c2ecf20Sopenharmony_ci		chip = &mips_cpu_irq_controller;
1608c2ecf20Sopenharmony_ci	}
1618c2ecf20Sopenharmony_ci
1628c2ecf20Sopenharmony_ci	if (cpu_has_vint)
1638c2ecf20Sopenharmony_ci		set_vi_handler(hw, plat_irq_dispatch);
1648c2ecf20Sopenharmony_ci
1658c2ecf20Sopenharmony_ci	irq_set_chip_and_handler(irq, chip, handle_percpu_irq);
1668c2ecf20Sopenharmony_ci
1678c2ecf20Sopenharmony_ci	return 0;
1688c2ecf20Sopenharmony_ci}
1698c2ecf20Sopenharmony_ci
1708c2ecf20Sopenharmony_cistatic const struct irq_domain_ops mips_cpu_intc_irq_domain_ops = {
1718c2ecf20Sopenharmony_ci	.map = mips_cpu_intc_map,
1728c2ecf20Sopenharmony_ci	.xlate = irq_domain_xlate_onecell,
1738c2ecf20Sopenharmony_ci};
1748c2ecf20Sopenharmony_ci
1758c2ecf20Sopenharmony_ci#ifdef CONFIG_GENERIC_IRQ_IPI
1768c2ecf20Sopenharmony_ci
1778c2ecf20Sopenharmony_cistruct cpu_ipi_domain_state {
1788c2ecf20Sopenharmony_ci	DECLARE_BITMAP(allocated, 2);
1798c2ecf20Sopenharmony_ci};
1808c2ecf20Sopenharmony_ci
1818c2ecf20Sopenharmony_cistatic int mips_cpu_ipi_alloc(struct irq_domain *domain, unsigned int virq,
1828c2ecf20Sopenharmony_ci			      unsigned int nr_irqs, void *arg)
1838c2ecf20Sopenharmony_ci{
1848c2ecf20Sopenharmony_ci	struct cpu_ipi_domain_state *state = domain->host_data;
1858c2ecf20Sopenharmony_ci	unsigned int i, hwirq;
1868c2ecf20Sopenharmony_ci	int ret;
1878c2ecf20Sopenharmony_ci
1888c2ecf20Sopenharmony_ci	for (i = 0; i < nr_irqs; i++) {
1898c2ecf20Sopenharmony_ci		hwirq = find_first_zero_bit(state->allocated, 2);
1908c2ecf20Sopenharmony_ci		if (hwirq == 2)
1918c2ecf20Sopenharmony_ci			return -EBUSY;
1928c2ecf20Sopenharmony_ci		bitmap_set(state->allocated, hwirq, 1);
1938c2ecf20Sopenharmony_ci
1948c2ecf20Sopenharmony_ci		ret = irq_domain_set_hwirq_and_chip(domain, virq + i, hwirq,
1958c2ecf20Sopenharmony_ci						    &mips_mt_cpu_irq_controller,
1968c2ecf20Sopenharmony_ci						    NULL);
1978c2ecf20Sopenharmony_ci		if (ret)
1988c2ecf20Sopenharmony_ci			return ret;
1998c2ecf20Sopenharmony_ci
2008c2ecf20Sopenharmony_ci		ret = irq_domain_set_hwirq_and_chip(domain->parent, virq + i, hwirq,
2018c2ecf20Sopenharmony_ci						    &mips_mt_cpu_irq_controller,
2028c2ecf20Sopenharmony_ci						    NULL);
2038c2ecf20Sopenharmony_ci
2048c2ecf20Sopenharmony_ci		if (ret)
2058c2ecf20Sopenharmony_ci			return ret;
2068c2ecf20Sopenharmony_ci
2078c2ecf20Sopenharmony_ci		ret = irq_set_irq_type(virq + i, IRQ_TYPE_LEVEL_HIGH);
2088c2ecf20Sopenharmony_ci		if (ret)
2098c2ecf20Sopenharmony_ci			return ret;
2108c2ecf20Sopenharmony_ci	}
2118c2ecf20Sopenharmony_ci
2128c2ecf20Sopenharmony_ci	return 0;
2138c2ecf20Sopenharmony_ci}
2148c2ecf20Sopenharmony_ci
2158c2ecf20Sopenharmony_cistatic int mips_cpu_ipi_match(struct irq_domain *d, struct device_node *node,
2168c2ecf20Sopenharmony_ci			      enum irq_domain_bus_token bus_token)
2178c2ecf20Sopenharmony_ci{
2188c2ecf20Sopenharmony_ci	bool is_ipi;
2198c2ecf20Sopenharmony_ci
2208c2ecf20Sopenharmony_ci	switch (bus_token) {
2218c2ecf20Sopenharmony_ci	case DOMAIN_BUS_IPI:
2228c2ecf20Sopenharmony_ci		is_ipi = d->bus_token == bus_token;
2238c2ecf20Sopenharmony_ci		return (!node || (to_of_node(d->fwnode) == node)) && is_ipi;
2248c2ecf20Sopenharmony_ci	default:
2258c2ecf20Sopenharmony_ci		return 0;
2268c2ecf20Sopenharmony_ci	}
2278c2ecf20Sopenharmony_ci}
2288c2ecf20Sopenharmony_ci
2298c2ecf20Sopenharmony_cistatic const struct irq_domain_ops mips_cpu_ipi_chip_ops = {
2308c2ecf20Sopenharmony_ci	.alloc	= mips_cpu_ipi_alloc,
2318c2ecf20Sopenharmony_ci	.match	= mips_cpu_ipi_match,
2328c2ecf20Sopenharmony_ci};
2338c2ecf20Sopenharmony_ci
2348c2ecf20Sopenharmony_cistatic void mips_cpu_register_ipi_domain(struct device_node *of_node)
2358c2ecf20Sopenharmony_ci{
2368c2ecf20Sopenharmony_ci	struct cpu_ipi_domain_state *ipi_domain_state;
2378c2ecf20Sopenharmony_ci
2388c2ecf20Sopenharmony_ci	ipi_domain_state = kzalloc(sizeof(*ipi_domain_state), GFP_KERNEL);
2398c2ecf20Sopenharmony_ci	ipi_domain = irq_domain_add_hierarchy(irq_domain,
2408c2ecf20Sopenharmony_ci					      IRQ_DOMAIN_FLAG_IPI_SINGLE,
2418c2ecf20Sopenharmony_ci					      2, of_node,
2428c2ecf20Sopenharmony_ci					      &mips_cpu_ipi_chip_ops,
2438c2ecf20Sopenharmony_ci					      ipi_domain_state);
2448c2ecf20Sopenharmony_ci	if (!ipi_domain)
2458c2ecf20Sopenharmony_ci		panic("Failed to add MIPS CPU IPI domain");
2468c2ecf20Sopenharmony_ci	irq_domain_update_bus_token(ipi_domain, DOMAIN_BUS_IPI);
2478c2ecf20Sopenharmony_ci}
2488c2ecf20Sopenharmony_ci
2498c2ecf20Sopenharmony_ci#else /* !CONFIG_GENERIC_IRQ_IPI */
2508c2ecf20Sopenharmony_ci
2518c2ecf20Sopenharmony_cistatic inline void mips_cpu_register_ipi_domain(struct device_node *of_node) {}
2528c2ecf20Sopenharmony_ci
2538c2ecf20Sopenharmony_ci#endif /* !CONFIG_GENERIC_IRQ_IPI */
2548c2ecf20Sopenharmony_ci
2558c2ecf20Sopenharmony_cistatic void __init __mips_cpu_irq_init(struct device_node *of_node)
2568c2ecf20Sopenharmony_ci{
2578c2ecf20Sopenharmony_ci	/* Mask interrupts. */
2588c2ecf20Sopenharmony_ci	clear_c0_status(ST0_IM);
2598c2ecf20Sopenharmony_ci	clear_c0_cause(CAUSEF_IP);
2608c2ecf20Sopenharmony_ci
2618c2ecf20Sopenharmony_ci	irq_domain = irq_domain_add_legacy(of_node, 8, MIPS_CPU_IRQ_BASE, 0,
2628c2ecf20Sopenharmony_ci					   &mips_cpu_intc_irq_domain_ops,
2638c2ecf20Sopenharmony_ci					   NULL);
2648c2ecf20Sopenharmony_ci	if (!irq_domain)
2658c2ecf20Sopenharmony_ci		panic("Failed to add irqdomain for MIPS CPU");
2668c2ecf20Sopenharmony_ci
2678c2ecf20Sopenharmony_ci	/*
2688c2ecf20Sopenharmony_ci	 * Only proceed to register the software interrupt IPI implementation
2698c2ecf20Sopenharmony_ci	 * for CPUs which implement the MIPS MT (multi-threading) ASE.
2708c2ecf20Sopenharmony_ci	 */
2718c2ecf20Sopenharmony_ci	if (cpu_has_mipsmt)
2728c2ecf20Sopenharmony_ci		mips_cpu_register_ipi_domain(of_node);
2738c2ecf20Sopenharmony_ci}
2748c2ecf20Sopenharmony_ci
2758c2ecf20Sopenharmony_civoid __init mips_cpu_irq_init(void)
2768c2ecf20Sopenharmony_ci{
2778c2ecf20Sopenharmony_ci	__mips_cpu_irq_init(NULL);
2788c2ecf20Sopenharmony_ci}
2798c2ecf20Sopenharmony_ci
2808c2ecf20Sopenharmony_ciint __init mips_cpu_irq_of_init(struct device_node *of_node,
2818c2ecf20Sopenharmony_ci				struct device_node *parent)
2828c2ecf20Sopenharmony_ci{
2838c2ecf20Sopenharmony_ci	__mips_cpu_irq_init(of_node);
2848c2ecf20Sopenharmony_ci	return 0;
2858c2ecf20Sopenharmony_ci}
2868c2ecf20Sopenharmony_ciIRQCHIP_DECLARE(cpu_intc, "mti,cpu-interrupt-controller", mips_cpu_irq_of_init);
287