// SPDX-License-Identifier: GPL-2.0-only /* * EN751221 Interrupt Controller Driver. * * The EcoNet EN751221 Interrupt Controller is a simple interrupt controller * designed for the MIPS 34Kc MT SMP processor with 2 VPEs. Each interrupt can * be routed to either VPE but not both, so to support per-CPU interrupts, a * secondary IRQ number is allocated to control masking/unmasking on VPE#1. In * this driver, these are called "shadow interrupts". The assignment of shadow * interrupts is defined by the SoC integrator when wiring the interrupt lines, * so they are configurable in the device tree. * * If an interrupt (say 30) needs per-CPU capability, the SoC integrator * allocates another IRQ number (say 29) to be its shadow. The device tree * reflects this by adding the pair <30 29> to the "econet,shadow-interrupts" * property. * * When VPE#1 requests IRQ 30, the driver manipulates the mask bit for IRQ 29, * telling the hardware to mask VPE#1's view of IRQ 30. * * Copyright (C) 2025 Caleb James DeLisle */ #include #include #include #include #include #include #include #include #define IRQ_COUNT 40 #define NOT_PERCPU 0xff #define IS_SHADOW 0xfe #define REG_MASK0 0x04 #define REG_MASK1 0x50 #define REG_PENDING0 0x08 #define REG_PENDING1 0x54 /** * @membase: Base address of the interrupt controller registers * @interrupt_shadows: Array of all interrupts, for each value, * - NOT_PERCPU: This interrupt is not per-cpu, so it has no shadow * - IS_SHADOW: This interrupt is a shadow of another per-cpu interrupt * - else: This is a per-cpu interrupt whose shadow is the value */ static struct { void __iomem *membase; u8 interrupt_shadows[IRQ_COUNT]; } econet_intc __ro_after_init; static DEFINE_RAW_SPINLOCK(irq_lock); /* IRQs must be disabled */ static void econet_wreg(u32 reg, u32 val, u32 mask) { u32 v; guard(raw_spinlock)(&irq_lock); v = ioread32(econet_intc.membase + reg); v &= ~mask; v |= val & mask; iowrite32(v, econet_intc.membase + reg); } /* IRQs must be disabled */ static void econet_chmask(u32 hwirq, bool unmask) { u32 reg, mask; u8 shadow; /* * If the IRQ is a shadow, it should never be manipulated directly. * It should only be masked/unmasked as a result of the "real" per-cpu * irq being manipulated by a thread running on VPE#1. * If it is per-cpu (has a shadow), and we're on VPE#1, the shadow is what we mask. * This is single processor only, so smp_processor_id() never exceeds 1. */ shadow = econet_intc.interrupt_shadows[hwirq]; if (WARN_ON_ONCE(shadow == IS_SHADOW)) return; else if (shadow != NOT_PERCPU && smp_processor_id() == 1) hwirq = shadow; if (hwirq >= 32) { reg = REG_MASK1; mask = BIT(hwirq - 32); } else { reg = REG_MASK0; mask = BIT(hwirq); } econet_wreg(reg, unmask ? mask : 0, mask); } /* IRQs must be disabled */ static void econet_intc_mask(struct irq_data *d) { econet_chmask(d->hwirq, false); } /* IRQs must be disabled */ static void econet_intc_unmask(struct irq_data *d) { econet_chmask(d->hwirq, true); } static void econet_mask_all(void) { /* IRQs are generally disabled during init, but guarding here makes it non-obligatory. */ guard(irqsave)(); econet_wreg(REG_MASK0, 0, ~0); econet_wreg(REG_MASK1, 0, ~0); } static void econet_intc_handle_pending(struct irq_domain *d, u32 pending, u32 offset) { int hwirq; while (pending) { hwirq = fls(pending) - 1; generic_handle_domain_irq(d, hwirq + offset); pending &= ~BIT(hwirq); } } static void econet_intc_from_parent(struct irq_desc *desc) { struct irq_chip *chip = irq_desc_get_chip(desc); struct irq_domain *domain; u32 pending0, pending1; chained_irq_enter(chip, desc); pending0 = ioread32(econet_intc.membase + REG_PENDING0); pending1 = ioread32(econet_intc.membase + REG_PENDING1); if (unlikely(!(pending0 | pending1))) { spurious_interrupt(); } else { domain = irq_desc_get_handler_data(desc); econet_intc_handle_pending(domain, pending0, 0); econet_intc_handle_pending(domain, pending1, 32); } chained_irq_exit(chip, desc); } static const struct irq_chip econet_irq_chip; static int econet_intc_map(struct irq_domain *d, u32 irq, irq_hw_number_t hwirq) { int ret; if (hwirq >= IRQ_COUNT) { pr_err("%s: hwirq %lu out of range\n", __func__, hwirq); return -EINVAL; } else if (econet_intc.interrupt_shadows[hwirq] == IS_SHADOW) { pr_err("%s: can't map hwirq %lu, it is a shadow interrupt\n", __func__, hwirq); return -EINVAL; } if (econet_intc.interrupt_shadows[hwirq] == NOT_PERCPU) { irq_set_chip_and_handler(irq, &econet_irq_chip, handle_level_irq); } else { irq_set_chip_and_handler(irq, &econet_irq_chip, handle_percpu_devid_irq); ret = irq_set_percpu_devid(irq); if (ret) pr_warn("%s: Failed irq_set_percpu_devid for %u: %d\n", d->name, irq, ret); } irq_set_chip_data(irq, NULL); return 0; } static const struct irq_chip econet_irq_chip = { .name = "en751221-intc", .irq_unmask = econet_intc_unmask, .irq_mask = econet_intc_mask, .irq_mask_ack = econet_intc_mask, }; static const struct irq_domain_ops econet_domain_ops = { .xlate = irq_domain_xlate_onecell, .map = econet_intc_map }; static int __init get_shadow_interrupts(struct device_node *node) { const char *field = "econet,shadow-interrupts"; int num_shadows; num_shadows = of_property_count_u32_elems(node, field); memset(econet_intc.interrupt_shadows, NOT_PERCPU, sizeof(econet_intc.interrupt_shadows)); if (num_shadows <= 0) { return 0; } else if (num_shadows % 2) { pr_err("%pOF: %s count is odd, ignoring\n", node, field); return 0; } u32 *shadows __free(kfree) = kmalloc_array(num_shadows, sizeof(u32), GFP_KERNEL); if (!shadows) return -ENOMEM; if (of_property_read_u32_array(node, field, shadows, num_shadows)) { pr_err("%pOF: Failed to read %s\n", node, field); return -EINVAL; } for (int i = 0; i < num_shadows; i += 2) { u32 shadow = shadows[i + 1]; u32 target = shadows[i]; if (shadow > IRQ_COUNT) { pr_err("%pOF: %s[%d] shadow(%d) out of range\n", node, field, i + 1, shadow); continue; } if (target >= IRQ_COUNT) { pr_err("%pOF: %s[%d] target(%d) out of range\n", node, field, i, target); continue; } if (econet_intc.interrupt_shadows[target] != NOT_PERCPU) { pr_err("%pOF: %s[%d] target(%d) already has a shadow\n", node, field, i, target); continue; } if (econet_intc.interrupt_shadows[shadow] != NOT_PERCPU) { pr_err("%pOF: %s[%d] shadow(%d) already has a target\n", node, field, i + 1, shadow); continue; } econet_intc.interrupt_shadows[target] = shadow; econet_intc.interrupt_shadows[shadow] = IS_SHADOW; } return 0; } static int __init econet_intc_of_init(struct device_node *node, struct device_node *parent) { struct irq_domain *domain; struct resource res; int ret, irq; ret = get_shadow_interrupts(node); if (ret) return ret; irq = irq_of_parse_and_map(node, 0); if (!irq) { pr_err("%pOF: DT: Failed to get IRQ from 'interrupts'\n", node); return -EINVAL; } if (of_address_to_resource(node, 0, &res)) { pr_err("%pOF: DT: Failed to get 'reg'\n", node); ret = -EINVAL; goto err_dispose_mapping; } if (!request_mem_region(res.start, resource_size(&res), res.name)) { pr_err("%pOF: Failed to request memory\n", node); ret = -EBUSY; goto err_dispose_mapping; } econet_intc.membase = ioremap(res.start, resource_size(&res)); if (!econet_intc.membase) { pr_err("%pOF: Failed to remap membase\n", node); ret = -ENOMEM; goto err_release; } econet_mask_all(); domain = irq_domain_create_linear(of_fwnode_handle(node), IRQ_COUNT, &econet_domain_ops, NULL); if (!domain) { pr_err("%pOF: Failed to add irqdomain\n", node); ret = -ENOMEM; goto err_unmap; } irq_set_chained_handler_and_data(irq, econet_intc_from_parent, domain); return 0; err_unmap: iounmap(econet_intc.membase); err_release: release_mem_region(res.start, resource_size(&res)); err_dispose_mapping: irq_dispose_mapping(irq); return ret; } IRQCHIP_DECLARE(econet_en751221_intc, "econet,en751221-intc", econet_intc_of_init);