xref: /linux/drivers/irqchip/irq-econet-en751221.c (revision c0f182c979cfead8fff08108a11fbd2fe885dd33)
11902a59cSCaleb James DeLisle // SPDX-License-Identifier: GPL-2.0-only
21902a59cSCaleb James DeLisle /*
31902a59cSCaleb James DeLisle  * EN751221 Interrupt Controller Driver.
41902a59cSCaleb James DeLisle  *
51902a59cSCaleb James DeLisle  * The EcoNet EN751221 Interrupt Controller is a simple interrupt controller
61902a59cSCaleb James DeLisle  * designed for the MIPS 34Kc MT SMP processor with 2 VPEs. Each interrupt can
71902a59cSCaleb James DeLisle  * be routed to either VPE but not both, so to support per-CPU interrupts, a
81902a59cSCaleb James DeLisle  * secondary IRQ number is allocated to control masking/unmasking on VPE#1. In
91902a59cSCaleb James DeLisle  * this driver, these are called "shadow interrupts". The assignment of shadow
101902a59cSCaleb James DeLisle  * interrupts is defined by the SoC integrator when wiring the interrupt lines,
111902a59cSCaleb James DeLisle  * so they are configurable in the device tree.
121902a59cSCaleb James DeLisle  *
131902a59cSCaleb James DeLisle  * If an interrupt (say 30) needs per-CPU capability, the SoC integrator
141902a59cSCaleb James DeLisle  * allocates another IRQ number (say 29) to be its shadow. The device tree
151902a59cSCaleb James DeLisle  * reflects this by adding the pair <30 29> to the "econet,shadow-interrupts"
161902a59cSCaleb James DeLisle  * property.
171902a59cSCaleb James DeLisle  *
181902a59cSCaleb James DeLisle  * When VPE#1 requests IRQ 30, the driver manipulates the mask bit for IRQ 29,
191902a59cSCaleb James DeLisle  * telling the hardware to mask VPE#1's view of IRQ 30.
201902a59cSCaleb James DeLisle  *
211902a59cSCaleb James DeLisle  * Copyright (C) 2025 Caleb James DeLisle <cjd@cjdns.fr>
221902a59cSCaleb James DeLisle  */
231902a59cSCaleb James DeLisle 
241902a59cSCaleb James DeLisle #include <linux/cleanup.h>
251902a59cSCaleb James DeLisle #include <linux/io.h>
261902a59cSCaleb James DeLisle #include <linux/of.h>
271902a59cSCaleb James DeLisle #include <linux/of_address.h>
281902a59cSCaleb James DeLisle #include <linux/of_irq.h>
291902a59cSCaleb James DeLisle #include <linux/irqdomain.h>
301902a59cSCaleb James DeLisle #include <linux/irqchip.h>
311902a59cSCaleb James DeLisle #include <linux/irqchip/chained_irq.h>
321902a59cSCaleb James DeLisle 
331902a59cSCaleb James DeLisle #define IRQ_COUNT		40
341902a59cSCaleb James DeLisle 
351902a59cSCaleb James DeLisle #define NOT_PERCPU		0xff
361902a59cSCaleb James DeLisle #define IS_SHADOW		0xfe
371902a59cSCaleb James DeLisle 
381902a59cSCaleb James DeLisle #define REG_MASK0		0x04
391902a59cSCaleb James DeLisle #define REG_MASK1		0x50
401902a59cSCaleb James DeLisle #define REG_PENDING0		0x08
411902a59cSCaleb James DeLisle #define REG_PENDING1		0x54
421902a59cSCaleb James DeLisle 
431902a59cSCaleb James DeLisle /**
441902a59cSCaleb James DeLisle  * @membase: Base address of the interrupt controller registers
451902a59cSCaleb James DeLisle  * @interrupt_shadows: Array of all interrupts, for each value,
461902a59cSCaleb James DeLisle  *	- NOT_PERCPU: This interrupt is not per-cpu, so it has no shadow
471902a59cSCaleb James DeLisle  *	- IS_SHADOW: This interrupt is a shadow of another per-cpu interrupt
481902a59cSCaleb James DeLisle  *	- else: This is a per-cpu interrupt whose shadow is the value
491902a59cSCaleb James DeLisle  */
501902a59cSCaleb James DeLisle static struct {
511902a59cSCaleb James DeLisle 	void __iomem	*membase;
521902a59cSCaleb James DeLisle 	u8		interrupt_shadows[IRQ_COUNT];
531902a59cSCaleb James DeLisle } econet_intc __ro_after_init;
541902a59cSCaleb James DeLisle 
551902a59cSCaleb James DeLisle static DEFINE_RAW_SPINLOCK(irq_lock);
561902a59cSCaleb James DeLisle 
571902a59cSCaleb James DeLisle /* IRQs must be disabled */
econet_wreg(u32 reg,u32 val,u32 mask)581902a59cSCaleb James DeLisle static void econet_wreg(u32 reg, u32 val, u32 mask)
591902a59cSCaleb James DeLisle {
601902a59cSCaleb James DeLisle 	u32 v;
611902a59cSCaleb James DeLisle 
621902a59cSCaleb James DeLisle 	guard(raw_spinlock)(&irq_lock);
631902a59cSCaleb James DeLisle 
641902a59cSCaleb James DeLisle 	v = ioread32(econet_intc.membase + reg);
651902a59cSCaleb James DeLisle 	v &= ~mask;
661902a59cSCaleb James DeLisle 	v |= val & mask;
671902a59cSCaleb James DeLisle 	iowrite32(v, econet_intc.membase + reg);
681902a59cSCaleb James DeLisle }
691902a59cSCaleb James DeLisle 
701902a59cSCaleb James DeLisle /* IRQs must be disabled */
econet_chmask(u32 hwirq,bool unmask)711902a59cSCaleb James DeLisle static void econet_chmask(u32 hwirq, bool unmask)
721902a59cSCaleb James DeLisle {
731902a59cSCaleb James DeLisle 	u32 reg, mask;
741902a59cSCaleb James DeLisle 	u8 shadow;
751902a59cSCaleb James DeLisle 
761902a59cSCaleb James DeLisle 	/*
771902a59cSCaleb James DeLisle 	 * If the IRQ is a shadow, it should never be manipulated directly.
781902a59cSCaleb James DeLisle 	 * It should only be masked/unmasked as a result of the "real" per-cpu
791902a59cSCaleb James DeLisle 	 * irq being manipulated by a thread running on VPE#1.
801902a59cSCaleb James DeLisle 	 * If it is per-cpu (has a shadow), and we're on VPE#1, the shadow is what we mask.
811902a59cSCaleb James DeLisle 	 * This is single processor only, so smp_processor_id() never exceeds 1.
821902a59cSCaleb James DeLisle 	 */
831902a59cSCaleb James DeLisle 	shadow = econet_intc.interrupt_shadows[hwirq];
841902a59cSCaleb James DeLisle 	if (WARN_ON_ONCE(shadow == IS_SHADOW))
851902a59cSCaleb James DeLisle 		return;
861902a59cSCaleb James DeLisle 	else if (shadow != NOT_PERCPU && smp_processor_id() == 1)
871902a59cSCaleb James DeLisle 		hwirq = shadow;
881902a59cSCaleb James DeLisle 
891902a59cSCaleb James DeLisle 	if (hwirq >= 32) {
901902a59cSCaleb James DeLisle 		reg = REG_MASK1;
911902a59cSCaleb James DeLisle 		mask = BIT(hwirq - 32);
921902a59cSCaleb James DeLisle 	} else {
931902a59cSCaleb James DeLisle 		reg = REG_MASK0;
941902a59cSCaleb James DeLisle 		mask = BIT(hwirq);
951902a59cSCaleb James DeLisle 	}
961902a59cSCaleb James DeLisle 
971902a59cSCaleb James DeLisle 	econet_wreg(reg, unmask ? mask : 0, mask);
981902a59cSCaleb James DeLisle }
991902a59cSCaleb James DeLisle 
1001902a59cSCaleb James DeLisle /* IRQs must be disabled */
econet_intc_mask(struct irq_data * d)1011902a59cSCaleb James DeLisle static void econet_intc_mask(struct irq_data *d)
1021902a59cSCaleb James DeLisle {
1031902a59cSCaleb James DeLisle 	econet_chmask(d->hwirq, false);
1041902a59cSCaleb James DeLisle }
1051902a59cSCaleb James DeLisle 
1061902a59cSCaleb James DeLisle /* IRQs must be disabled */
econet_intc_unmask(struct irq_data * d)1071902a59cSCaleb James DeLisle static void econet_intc_unmask(struct irq_data *d)
1081902a59cSCaleb James DeLisle {
1091902a59cSCaleb James DeLisle 	econet_chmask(d->hwirq, true);
1101902a59cSCaleb James DeLisle }
1111902a59cSCaleb James DeLisle 
econet_mask_all(void)1121902a59cSCaleb James DeLisle static void econet_mask_all(void)
1131902a59cSCaleb James DeLisle {
1141902a59cSCaleb James DeLisle 	/* IRQs are generally disabled during init, but guarding here makes it non-obligatory. */
1151902a59cSCaleb James DeLisle 	guard(irqsave)();
1161902a59cSCaleb James DeLisle 	econet_wreg(REG_MASK0, 0, ~0);
1171902a59cSCaleb James DeLisle 	econet_wreg(REG_MASK1, 0, ~0);
1181902a59cSCaleb James DeLisle }
1191902a59cSCaleb James DeLisle 
econet_intc_handle_pending(struct irq_domain * d,u32 pending,u32 offset)1201902a59cSCaleb James DeLisle static void econet_intc_handle_pending(struct irq_domain *d, u32 pending, u32 offset)
1211902a59cSCaleb James DeLisle {
1221902a59cSCaleb James DeLisle 	int hwirq;
1231902a59cSCaleb James DeLisle 
1241902a59cSCaleb James DeLisle 	while (pending) {
1251902a59cSCaleb James DeLisle 		hwirq = fls(pending) - 1;
1261902a59cSCaleb James DeLisle 		generic_handle_domain_irq(d, hwirq + offset);
1271902a59cSCaleb James DeLisle 		pending &= ~BIT(hwirq);
1281902a59cSCaleb James DeLisle 	}
1291902a59cSCaleb James DeLisle }
1301902a59cSCaleb James DeLisle 
econet_intc_from_parent(struct irq_desc * desc)1311902a59cSCaleb James DeLisle static void econet_intc_from_parent(struct irq_desc *desc)
1321902a59cSCaleb James DeLisle {
1331902a59cSCaleb James DeLisle 	struct irq_chip *chip = irq_desc_get_chip(desc);
1341902a59cSCaleb James DeLisle 	struct irq_domain *domain;
1351902a59cSCaleb James DeLisle 	u32 pending0, pending1;
1361902a59cSCaleb James DeLisle 
1371902a59cSCaleb James DeLisle 	chained_irq_enter(chip, desc);
1381902a59cSCaleb James DeLisle 
1391902a59cSCaleb James DeLisle 	pending0 = ioread32(econet_intc.membase + REG_PENDING0);
1401902a59cSCaleb James DeLisle 	pending1 = ioread32(econet_intc.membase + REG_PENDING1);
1411902a59cSCaleb James DeLisle 
1421902a59cSCaleb James DeLisle 	if (unlikely(!(pending0 | pending1))) {
1431902a59cSCaleb James DeLisle 		spurious_interrupt();
1441902a59cSCaleb James DeLisle 	} else {
1451902a59cSCaleb James DeLisle 		domain = irq_desc_get_handler_data(desc);
1461902a59cSCaleb James DeLisle 		econet_intc_handle_pending(domain, pending0, 0);
1471902a59cSCaleb James DeLisle 		econet_intc_handle_pending(domain, pending1, 32);
1481902a59cSCaleb James DeLisle 	}
1491902a59cSCaleb James DeLisle 
1501902a59cSCaleb James DeLisle 	chained_irq_exit(chip, desc);
1511902a59cSCaleb James DeLisle }
1521902a59cSCaleb James DeLisle 
1531902a59cSCaleb James DeLisle static const struct irq_chip econet_irq_chip;
1541902a59cSCaleb James DeLisle 
econet_intc_map(struct irq_domain * d,u32 irq,irq_hw_number_t hwirq)1551902a59cSCaleb James DeLisle static int econet_intc_map(struct irq_domain *d, u32 irq, irq_hw_number_t hwirq)
1561902a59cSCaleb James DeLisle {
1571902a59cSCaleb James DeLisle 	int ret;
1581902a59cSCaleb James DeLisle 
1591902a59cSCaleb James DeLisle 	if (hwirq >= IRQ_COUNT) {
1601902a59cSCaleb James DeLisle 		pr_err("%s: hwirq %lu out of range\n", __func__, hwirq);
1611902a59cSCaleb James DeLisle 		return -EINVAL;
1621902a59cSCaleb James DeLisle 	} else if (econet_intc.interrupt_shadows[hwirq] == IS_SHADOW) {
1631902a59cSCaleb James DeLisle 		pr_err("%s: can't map hwirq %lu, it is a shadow interrupt\n", __func__, hwirq);
1641902a59cSCaleb James DeLisle 		return -EINVAL;
1651902a59cSCaleb James DeLisle 	}
1661902a59cSCaleb James DeLisle 
1671902a59cSCaleb James DeLisle 	if (econet_intc.interrupt_shadows[hwirq] == NOT_PERCPU) {
1681902a59cSCaleb James DeLisle 		irq_set_chip_and_handler(irq, &econet_irq_chip, handle_level_irq);
1691902a59cSCaleb James DeLisle 	} else {
1701902a59cSCaleb James DeLisle 		irq_set_chip_and_handler(irq, &econet_irq_chip, handle_percpu_devid_irq);
1711902a59cSCaleb James DeLisle 		ret = irq_set_percpu_devid(irq);
1721902a59cSCaleb James DeLisle 		if (ret)
1731902a59cSCaleb James DeLisle 			pr_warn("%s: Failed irq_set_percpu_devid for %u: %d\n", d->name, irq, ret);
1741902a59cSCaleb James DeLisle 	}
1751902a59cSCaleb James DeLisle 
1761902a59cSCaleb James DeLisle 	irq_set_chip_data(irq, NULL);
1771902a59cSCaleb James DeLisle 	return 0;
1781902a59cSCaleb James DeLisle }
1791902a59cSCaleb James DeLisle 
1801902a59cSCaleb James DeLisle static const struct irq_chip econet_irq_chip = {
1811902a59cSCaleb James DeLisle 	.name		= "en751221-intc",
1821902a59cSCaleb James DeLisle 	.irq_unmask	= econet_intc_unmask,
1831902a59cSCaleb James DeLisle 	.irq_mask	= econet_intc_mask,
1841902a59cSCaleb James DeLisle 	.irq_mask_ack	= econet_intc_mask,
1851902a59cSCaleb James DeLisle };
1861902a59cSCaleb James DeLisle 
1871902a59cSCaleb James DeLisle static const struct irq_domain_ops econet_domain_ops = {
1881902a59cSCaleb James DeLisle 	.xlate	= irq_domain_xlate_onecell,
1891902a59cSCaleb James DeLisle 	.map	= econet_intc_map
1901902a59cSCaleb James DeLisle };
1911902a59cSCaleb James DeLisle 
get_shadow_interrupts(struct device_node * node)1921902a59cSCaleb James DeLisle static int __init get_shadow_interrupts(struct device_node *node)
1931902a59cSCaleb James DeLisle {
1941902a59cSCaleb James DeLisle 	const char *field = "econet,shadow-interrupts";
1951902a59cSCaleb James DeLisle 	int num_shadows;
1961902a59cSCaleb James DeLisle 
1971902a59cSCaleb James DeLisle 	num_shadows = of_property_count_u32_elems(node, field);
1981902a59cSCaleb James DeLisle 
1991902a59cSCaleb James DeLisle 	memset(econet_intc.interrupt_shadows, NOT_PERCPU,
2001902a59cSCaleb James DeLisle 	       sizeof(econet_intc.interrupt_shadows));
2011902a59cSCaleb James DeLisle 
2021902a59cSCaleb James DeLisle 	if (num_shadows <= 0) {
2031902a59cSCaleb James DeLisle 		return 0;
2041902a59cSCaleb James DeLisle 	} else if (num_shadows % 2) {
2051902a59cSCaleb James DeLisle 		pr_err("%pOF: %s count is odd, ignoring\n", node, field);
2061902a59cSCaleb James DeLisle 		return 0;
2071902a59cSCaleb James DeLisle 	}
2081902a59cSCaleb James DeLisle 
2091902a59cSCaleb James DeLisle 	u32 *shadows __free(kfree) = kmalloc_array(num_shadows, sizeof(u32), GFP_KERNEL);
2101902a59cSCaleb James DeLisle 	if (!shadows)
2111902a59cSCaleb James DeLisle 		return -ENOMEM;
2121902a59cSCaleb James DeLisle 
2131902a59cSCaleb James DeLisle 	if (of_property_read_u32_array(node, field, shadows, num_shadows)) {
2141902a59cSCaleb James DeLisle 		pr_err("%pOF: Failed to read %s\n", node, field);
2151902a59cSCaleb James DeLisle 		return -EINVAL;
2161902a59cSCaleb James DeLisle 	}
2171902a59cSCaleb James DeLisle 
2181902a59cSCaleb James DeLisle 	for (int i = 0; i < num_shadows; i += 2) {
2191902a59cSCaleb James DeLisle 		u32 shadow = shadows[i + 1];
2201902a59cSCaleb James DeLisle 		u32 target = shadows[i];
2211902a59cSCaleb James DeLisle 
2221902a59cSCaleb James DeLisle 		if (shadow > IRQ_COUNT) {
2231902a59cSCaleb James DeLisle 			pr_err("%pOF: %s[%d] shadow(%d) out of range\n",
2241902a59cSCaleb James DeLisle 			       node, field, i + 1, shadow);
2251902a59cSCaleb James DeLisle 			continue;
2261902a59cSCaleb James DeLisle 		}
2271902a59cSCaleb James DeLisle 
2281902a59cSCaleb James DeLisle 		if (target >= IRQ_COUNT) {
2291902a59cSCaleb James DeLisle 			pr_err("%pOF: %s[%d] target(%d) out of range\n", node, field, i, target);
2301902a59cSCaleb James DeLisle 			continue;
2311902a59cSCaleb James DeLisle 		}
2321902a59cSCaleb James DeLisle 
2331902a59cSCaleb James DeLisle 		if (econet_intc.interrupt_shadows[target] != NOT_PERCPU) {
2341902a59cSCaleb James DeLisle 			pr_err("%pOF: %s[%d] target(%d) already has a shadow\n",
2351902a59cSCaleb James DeLisle 			       node, field, i, target);
2361902a59cSCaleb James DeLisle 			continue;
2371902a59cSCaleb James DeLisle 		}
2381902a59cSCaleb James DeLisle 
2391902a59cSCaleb James DeLisle 		if (econet_intc.interrupt_shadows[shadow] != NOT_PERCPU) {
2401902a59cSCaleb James DeLisle 			pr_err("%pOF: %s[%d] shadow(%d) already has a target\n",
2411902a59cSCaleb James DeLisle 			       node, field, i + 1, shadow);
2421902a59cSCaleb James DeLisle 			continue;
2431902a59cSCaleb James DeLisle 		}
2441902a59cSCaleb James DeLisle 
2451902a59cSCaleb James DeLisle 		econet_intc.interrupt_shadows[target] = shadow;
2461902a59cSCaleb James DeLisle 		econet_intc.interrupt_shadows[shadow] = IS_SHADOW;
2471902a59cSCaleb James DeLisle 	}
2481902a59cSCaleb James DeLisle 
2491902a59cSCaleb James DeLisle 	return 0;
2501902a59cSCaleb James DeLisle }
2511902a59cSCaleb James DeLisle 
econet_intc_of_init(struct device_node * node,struct device_node * parent)2521902a59cSCaleb James DeLisle static int __init econet_intc_of_init(struct device_node *node, struct device_node *parent)
2531902a59cSCaleb James DeLisle {
2541902a59cSCaleb James DeLisle 	struct irq_domain *domain;
2551902a59cSCaleb James DeLisle 	struct resource res;
2561902a59cSCaleb James DeLisle 	int ret, irq;
2571902a59cSCaleb James DeLisle 
2581902a59cSCaleb James DeLisle 	ret = get_shadow_interrupts(node);
2591902a59cSCaleb James DeLisle 	if (ret)
2601902a59cSCaleb James DeLisle 		return ret;
2611902a59cSCaleb James DeLisle 
2621902a59cSCaleb James DeLisle 	irq = irq_of_parse_and_map(node, 0);
2631902a59cSCaleb James DeLisle 	if (!irq) {
2641902a59cSCaleb James DeLisle 		pr_err("%pOF: DT: Failed to get IRQ from 'interrupts'\n", node);
2651902a59cSCaleb James DeLisle 		return -EINVAL;
2661902a59cSCaleb James DeLisle 	}
2671902a59cSCaleb James DeLisle 
2681902a59cSCaleb James DeLisle 	if (of_address_to_resource(node, 0, &res)) {
2691902a59cSCaleb James DeLisle 		pr_err("%pOF: DT: Failed to get 'reg'\n", node);
2701902a59cSCaleb James DeLisle 		ret = -EINVAL;
2711902a59cSCaleb James DeLisle 		goto err_dispose_mapping;
2721902a59cSCaleb James DeLisle 	}
2731902a59cSCaleb James DeLisle 
2741902a59cSCaleb James DeLisle 	if (!request_mem_region(res.start, resource_size(&res), res.name)) {
2751902a59cSCaleb James DeLisle 		pr_err("%pOF: Failed to request memory\n", node);
2761902a59cSCaleb James DeLisle 		ret = -EBUSY;
2771902a59cSCaleb James DeLisle 		goto err_dispose_mapping;
2781902a59cSCaleb James DeLisle 	}
2791902a59cSCaleb James DeLisle 
2801902a59cSCaleb James DeLisle 	econet_intc.membase = ioremap(res.start, resource_size(&res));
2811902a59cSCaleb James DeLisle 	if (!econet_intc.membase) {
2821902a59cSCaleb James DeLisle 		pr_err("%pOF: Failed to remap membase\n", node);
2831902a59cSCaleb James DeLisle 		ret = -ENOMEM;
2841902a59cSCaleb James DeLisle 		goto err_release;
2851902a59cSCaleb James DeLisle 	}
2861902a59cSCaleb James DeLisle 
2871902a59cSCaleb James DeLisle 	econet_mask_all();
2881902a59cSCaleb James DeLisle 
289*96a8cb6dSJiri Slaby (SUSE) 	domain = irq_domain_create_linear(of_fwnode_handle(node), IRQ_COUNT,
29085cf5c63SThomas Gleixner 					  &econet_domain_ops, NULL);
2911902a59cSCaleb James DeLisle 	if (!domain) {
2921902a59cSCaleb James DeLisle 		pr_err("%pOF: Failed to add irqdomain\n", node);
2931902a59cSCaleb James DeLisle 		ret = -ENOMEM;
2941902a59cSCaleb James DeLisle 		goto err_unmap;
2951902a59cSCaleb James DeLisle 	}
2961902a59cSCaleb James DeLisle 
2971902a59cSCaleb James DeLisle 	irq_set_chained_handler_and_data(irq, econet_intc_from_parent, domain);
2981902a59cSCaleb James DeLisle 
2991902a59cSCaleb James DeLisle 	return 0;
3001902a59cSCaleb James DeLisle 
3011902a59cSCaleb James DeLisle err_unmap:
3021902a59cSCaleb James DeLisle 	iounmap(econet_intc.membase);
3031902a59cSCaleb James DeLisle err_release:
3041902a59cSCaleb James DeLisle 	release_mem_region(res.start, resource_size(&res));
3051902a59cSCaleb James DeLisle err_dispose_mapping:
3061902a59cSCaleb James DeLisle 	irq_dispose_mapping(irq);
3071902a59cSCaleb James DeLisle 	return ret;
3081902a59cSCaleb James DeLisle }
3091902a59cSCaleb James DeLisle 
3101902a59cSCaleb James DeLisle IRQCHIP_DECLARE(econet_en751221_intc, "econet,en751221-intc", econet_intc_of_init);
311