| Author | Tokens | Token Proportion | Commits | Commit Proportion |
|---|---|---|---|---|
| Ryan Chen | 2770 | 100.00% | 2 | 100.00% |
| Total | 2770 | 2 |
// SPDX-License-Identifier: GPL-2.0-only /* * Aspeed AST2700 Interrupt Controller. * * Copyright (C) 2026 ASPEED Technology Inc. */ #include <linux/bitops.h> #include <linux/device.h> #include <linux/err.h> #include <linux/fwnode.h> #include <linux/io.h> #include <linux/irq.h> #include <linux/irqchip.h> #include <linux/irqchip/chained_irq.h> #include <linux/irqdomain.h> #include <linux/kconfig.h> #include <linux/of.h> #include <linux/of_irq.h> #include <linux/overflow.h> #include <linux/property.h> #include <linux/spinlock.h> #include "irq-ast2700.h" #define INT_NUM 480 #define INTM_NUM 50 #define SWINT_NUM 16 #define INTM_BASE (INT_NUM) #define SWINT_BASE (INT_NUM + INTM_NUM) #define INT0_NUM (INT_NUM + INTM_NUM + SWINT_NUM) #define INTC0_IN_NUM 480 #define INTC0_ROUTE_NUM 5 #define INTC0_INTM_NUM 50 #define INTC0_ROUTE_BITS 3 #define GIC_P2P_SPI_END 128 #define INTC0_SWINT_OUT_BASE 144 #define INTC0_SWINT_IER 0x10 #define INTC0_SWINT_ISR 0x14 #define INTC0_INTBANKX_IER 0x1000 #define INTC0_INTBANK_SIZE 0x100 #define INTC0_INTBANK_GROUPS 11 #define INTC0_INTBANKS_PER_GRP 3 #define INTC0_INTMX_IER 0x1b00 #define INTC0_INTMX_ISR 0x1b04 #define INTC0_INTMX_BANK_SIZE 0x10 #define INTC0_INTM_BANK_NUM 3 #define INTC0_IRQS_PER_BANK 32 #define INTM_IRQS_PER_BANK 10 #define INTC0_SEL_BASE 0x200 #define INTC0_SEL_BANK_SIZE 0x4 #define INTC0_SEL_ROUTE_SIZE 0x100 static void aspeed_swint_irq_mask(struct irq_data *data) { struct aspeed_intc0 *intc0 = irq_data_get_irq_chip_data(data); int bit = data->hwirq - SWINT_BASE; u32 ier; guard(raw_spinlock)(&intc0->intc_lock); ier = readl(intc0->base + INTC0_SWINT_IER) & ~BIT(bit); writel(ier, intc0->base + INTC0_SWINT_IER); irq_chip_mask_parent(data); } static void aspeed_swint_irq_unmask(struct irq_data *data) { struct aspeed_intc0 *intc0 = irq_data_get_irq_chip_data(data); int bit = data->hwirq - SWINT_BASE; u32 ier; guard(raw_spinlock)(&intc0->intc_lock); ier = readl(intc0->base + INTC0_SWINT_IER) | BIT(bit); writel(ier, intc0->base + INTC0_SWINT_IER); irq_chip_unmask_parent(data); } static void aspeed_swint_irq_eoi(struct irq_data *data) { struct aspeed_intc0 *intc0 = irq_data_get_irq_chip_data(data); int bit = data->hwirq - SWINT_BASE; writel(BIT(bit), intc0->base + INTC0_SWINT_ISR); irq_chip_eoi_parent(data); } static struct irq_chip aspeed_swint_chip = { .name = "ast2700-swint", .irq_eoi = aspeed_swint_irq_eoi, .irq_mask = aspeed_swint_irq_mask, .irq_unmask = aspeed_swint_irq_unmask, .irq_set_affinity = irq_chip_set_affinity_parent, .flags = IRQCHIP_SET_TYPE_MASKED, }; static void aspeed_intc0_irq_mask(struct irq_data *data) { struct aspeed_intc0 *intc0 = irq_data_get_irq_chip_data(data); int bank = (data->hwirq - INTM_BASE) / INTM_IRQS_PER_BANK; int bit = (data->hwirq - INTM_BASE) % INTM_IRQS_PER_BANK; u32 ier; guard(raw_spinlock)(&intc0->intc_lock); ier = readl(intc0->base + INTC0_INTMX_IER + bank * INTC0_INTMX_BANK_SIZE) & ~BIT(bit); writel(ier, intc0->base + INTC0_INTMX_IER + bank * INTC0_INTMX_BANK_SIZE); irq_chip_mask_parent(data); } static void aspeed_intc0_irq_unmask(struct irq_data *data) { struct aspeed_intc0 *intc0 = irq_data_get_irq_chip_data(data); int bank = (data->hwirq - INTM_BASE) / INTM_IRQS_PER_BANK; int bit = (data->hwirq - INTM_BASE) % INTM_IRQS_PER_BANK; u32 ier; guard(raw_spinlock)(&intc0->intc_lock); ier = readl(intc0->base + INTC0_INTMX_IER + bank * INTC0_INTMX_BANK_SIZE) | BIT(bit); writel(ier, intc0->base + INTC0_INTMX_IER + bank * INTC0_INTMX_BANK_SIZE); irq_chip_unmask_parent(data); } static void aspeed_intc0_irq_eoi(struct irq_data *data) { struct aspeed_intc0 *intc0 = irq_data_get_irq_chip_data(data); int bank = (data->hwirq - INTM_BASE) / INTM_IRQS_PER_BANK; int bit = (data->hwirq - INTM_BASE) % INTM_IRQS_PER_BANK; writel(BIT(bit), intc0->base + INTC0_INTMX_ISR + bank * INTC0_INTMX_BANK_SIZE); irq_chip_eoi_parent(data); } static struct irq_chip aspeed_intm_chip = { .name = "ast2700-intmerge", .irq_eoi = aspeed_intc0_irq_eoi, .irq_mask = aspeed_intc0_irq_mask, .irq_unmask = aspeed_intc0_irq_unmask, .irq_set_affinity = irq_chip_set_affinity_parent, .flags = IRQCHIP_SET_TYPE_MASKED, }; static struct irq_chip linear_intr_irq_chip = { .name = "ast2700-int", .irq_eoi = irq_chip_eoi_parent, .irq_mask = irq_chip_mask_parent, .irq_unmask = irq_chip_unmask_parent, .irq_set_affinity = irq_chip_set_affinity_parent, .flags = IRQCHIP_SET_TYPE_MASKED, }; static const u32 aspeed_intc0_routes[INTC0_IN_NUM / INTC0_IRQS_PER_BANK][INTC0_ROUTE_NUM] = { { 0, 256, 426, AST2700_INTC_INVALID_ROUTE, AST2700_INTC_INVALID_ROUTE }, { 32, 288, 458, AST2700_INTC_INVALID_ROUTE, AST2700_INTC_INVALID_ROUTE }, { 64, 320, 490, AST2700_INTC_INVALID_ROUTE, AST2700_INTC_INVALID_ROUTE }, { 96, 352, 522, AST2700_INTC_INVALID_ROUTE, AST2700_INTC_INVALID_ROUTE }, { 128, 384, 554, 160, 176 }, { 129, 385, 555, 161, 177 }, { 130, 386, 556, 162, 178 }, { 131, 387, 557, 163, 179 }, { 132, 388, 558, 164, 180 }, { 133, 544, 714, 165, 181 }, { 134, 545, 715, 166, 182 }, { 135, 546, 706, 167, 183 }, { 136, 547, 707, 168, 184 }, { 137, 548, 708, 169, 185 }, { 138, 549, 709, 170, 186 }, }; static const u32 aspeed_intc0_intm_routes[INTC0_INTM_NUM / INTM_IRQS_PER_BANK] = { 192, 416, 586, 208, 224 }; static int resolve_input_from_child_ranges(const struct aspeed_intc0 *intc0, const struct aspeed_intc_interrupt_range *range, u32 outpin, u32 *input) { u32 offset, base; if (!in_range32(outpin, range->start, range->count)) return -ENOENT; if (range->upstream.param_count == 0) return -EINVAL; base = range->upstream.param[ASPEED_INTC_RANGES_BASE]; offset = outpin - range->start; if (check_add_overflow(base, offset, input)) { dev_warn(intc0->dev, "%s: Arithmetic overflow for input derivation: %u + %u\n", __func__, base, offset); return -EINVAL; } return 0; } static int resolve_parent_range_for_output(const struct aspeed_intc0 *intc0, const struct fwnode_handle *parent, u32 output, struct aspeed_intc_interrupt_range *resolved) { for (size_t i = 0; i < intc0->ranges.nranges; i++) { struct aspeed_intc_interrupt_range range = intc0->ranges.ranges[i]; if (!in_range32(output, range.start, range.count)) continue; if (range.upstream.fwnode != parent) continue; if (resolved) { resolved->start = output; resolved->count = 1; resolved->upstream = range.upstream; resolved->upstream.param[ASPEED_INTC_RANGES_COUNT] += output - range.start; } return 0; } return -ENOENT; } static int resolve_parent_route_for_input(const struct aspeed_intc0 *intc0, const struct fwnode_handle *parent, u32 input, struct aspeed_intc_interrupt_range *resolved) { int rc = -ENOENT; u32 c0o; if (input < INT_NUM) { static_assert(INTC0_ROUTE_NUM < INT_MAX, "Broken cast"); for (size_t i = 0; rc == -ENOENT && i < INTC0_ROUTE_NUM; i++) { c0o = aspeed_intc0_routes[input / INTC0_IRQS_PER_BANK][i]; if (c0o == AST2700_INTC_INVALID_ROUTE) continue; if (input < GIC_P2P_SPI_END) c0o += input % INTC0_IRQS_PER_BANK; rc = resolve_parent_range_for_output(intc0, parent, c0o, resolved); if (!rc) return (int)i; } } else if (input < (INT_NUM + INTM_NUM)) { c0o = aspeed_intc0_intm_routes[(input - INT_NUM) / INTM_IRQS_PER_BANK]; c0o += ((input - INT_NUM) % INTM_IRQS_PER_BANK); return resolve_parent_range_for_output(intc0, parent, c0o, resolved); } else if (input < (INT_NUM + INTM_NUM + SWINT_NUM)) { c0o = input - SWINT_BASE + INTC0_SWINT_OUT_BASE; return resolve_parent_range_for_output(intc0, parent, c0o, resolved); } else { return -ENOENT; } return rc; } /** * aspeed_intc0_resolve_route - Determine the necessary interrupt output at intc1 * @c0domain: The pointer to intc0's irq_domain * @nc1outs: The number of valid intc1 outputs available for the input * @c1outs: The array of available intc1 output indices for the input * @nc1ranges: The number of interrupt range entries for intc1 * @c1ranges: The array of configured intc1 interrupt ranges * @resolved: The fully resolved range entry after applying the resolution * algorithm * * Returns: The intc1 route index associated with the intc1 output identified in * @resolved on success. Otherwise, a negative errno value. * * The AST2700 interrupt architecture allows any peripheral interrupt source * to be routed to one of up to four processors running in the SoC. A processor * binding a driver for a peripheral that requests an interrupt is (without * further design and effort) the destination for the requested interrupt. * * Routing a peripheral interrupt to its destination processor requires * coordination between INTC0 on the CPU die and one or more INTC1 instances. * At least one INTC1 instance exists in the SoC on the IO-die, however up * to two more instances may be integrated via LTPI (LVDS Tunneling Protocol * & Interface). * * Between the multiple destinations, various route constraints, and the * devicetree binding design, some information that's needed at INTC1 instances * to route inbound interrupts correctly to the destination processor is only * available at INTC0. * * aspeed_intc0_resolve_route() is to be invoked by INTC1 driver instances to * perform the route resolution. The implementation in INTC0 allows INTC0 to * encapsulate the information used to perform route selection, and provides it * with an opportunity to apply policy as part of the selection process. Such * policy may, for instance, choose to de-prioritise some interrupts destined * for the PSP (Primary Service Processor) GIC. */ int aspeed_intc0_resolve_route(const struct irq_domain *c0domain, size_t nc1outs, const u32 *c1outs, size_t nc1ranges, const struct aspeed_intc_interrupt_range *c1ranges, struct aspeed_intc_interrupt_range *resolved) { struct fwnode_handle *parent_fwnode; struct aspeed_intc0 *intc0; int ret; if (!c0domain || !resolved) return -EINVAL; if (nc1outs > INT_MAX) return -EINVAL; if (nc1outs == 0 || nc1ranges == 0) return -ENOENT; if (!IS_ENABLED(CONFIG_ASPEED_AST2700_INTC_TEST) && !fwnode_device_is_compatible(c0domain->fwnode, "aspeed,ast2700-intc0")) return -ENODEV; intc0 = c0domain->host_data; if (!intc0) return -EINVAL; parent_fwnode = of_fwnode_handle(intc0->parent); for (size_t i = 0; i < nc1outs; i++) { u32 c1o = c1outs[i]; if (c1o == AST2700_INTC_INVALID_ROUTE) continue; for (size_t j = 0; j < nc1ranges; j++) { struct aspeed_intc_interrupt_range c1r = c1ranges[j]; u32 input; /* * Range match for intc1 output pin * * Assume a failed match is still a match for the purpose of testing, * saves a bunch of mess in the test fixtures */ if (!(c0domain == c1r.domain || IS_ENABLED(CONFIG_ASPEED_AST2700_INTC_TEST))) continue; ret = resolve_input_from_child_ranges(intc0, &c1r, c1o, &input); if (ret) continue; /* * INTC1 should never request routes for peripheral interrupt sources * directly attached to INTC0. */ if (input < GIC_P2P_SPI_END) continue; ret = resolve_parent_route_for_input(intc0, parent_fwnode, input, NULL); if (ret < 0) continue; /* Route resolution succeeded */ resolved->start = c1o; resolved->count = 1; resolved->upstream = c1r.upstream; resolved->upstream.param[ASPEED_INTC_RANGES_BASE] = input; /* Cast protected by prior test against nc1outs */ return (int)i; } } return -ENOENT; } static int aspeed_intc0_irq_domain_map(struct irq_domain *domain, unsigned int irq, irq_hw_number_t hwirq) { if (hwirq < GIC_P2P_SPI_END) irq_set_chip_and_handler(irq, &linear_intr_irq_chip, handle_level_irq); else if (hwirq < INTM_BASE) return -EINVAL; else if (hwirq < SWINT_BASE) irq_set_chip_and_handler(irq, &aspeed_intm_chip, handle_level_irq); else if (hwirq < INT0_NUM) irq_set_chip_and_handler(irq, &aspeed_swint_chip, handle_level_irq); else return -EINVAL; irq_set_chip_data(irq, domain->host_data); return 0; } static int aspeed_intc0_irq_domain_translate(struct irq_domain *domain, struct irq_fwspec *fwspec, unsigned long *hwirq, unsigned int *type) { if (fwspec->param_count != 1) return -EINVAL; *hwirq = fwspec->param[0]; *type = IRQ_TYPE_NONE; return 0; } static int aspeed_intc0_irq_domain_alloc(struct irq_domain *domain, unsigned int virq, unsigned int nr_irqs, void *data) { struct aspeed_intc0 *intc0 = domain->host_data; struct aspeed_intc_interrupt_range resolved; struct irq_fwspec *fwspec = data; struct irq_fwspec parent_fwspec; struct irq_chip *chip; unsigned long hwirq; unsigned int type; int ret; ret = aspeed_intc0_irq_domain_translate(domain, fwspec, &hwirq, &type); if (ret) return ret; if (hwirq >= GIC_P2P_SPI_END && hwirq < INT_NUM) return -EINVAL; if (hwirq < INTM_BASE) chip = &linear_intr_irq_chip; else if (hwirq < SWINT_BASE) chip = &aspeed_intm_chip; else chip = &aspeed_swint_chip; ret = resolve_parent_route_for_input(intc0, domain->parent->fwnode, (u32)hwirq, &resolved); if (ret) return ret; parent_fwspec = resolved.upstream; ret = irq_domain_alloc_irqs_parent(domain, virq, nr_irqs, &parent_fwspec); if (ret) return ret; for (int i = 0; i < nr_irqs; ++i, ++hwirq, ++virq) { ret = irq_domain_set_hwirq_and_chip(domain, virq, hwirq, chip, domain->host_data); if (ret) return ret; } return 0; } static int aspeed_intc0_irq_domain_activate(struct irq_domain *domain, struct irq_data *data, bool reserve) { struct aspeed_intc0 *intc0 = irq_data_get_irq_chip_data(data); unsigned long hwirq = data->hwirq; int route, bank, bit; u32 mask; if (hwirq >= INT0_NUM) return -EINVAL; if (in_range32(hwirq, INTM_BASE, INTM_NUM + SWINT_NUM)) return 0; bank = hwirq / INTC0_IRQS_PER_BANK; bit = hwirq % INTC0_IRQS_PER_BANK; mask = BIT(bit); route = resolve_parent_route_for_input(intc0, intc0->local->parent->fwnode, hwirq, NULL); if (route < 0) return route; guard(raw_spinlock)(&intc0->intc_lock); for (int i = 0; i < INTC0_ROUTE_BITS; i++) { void __iomem *sel = intc0->base + INTC0_SEL_BASE + (bank * INTC0_SEL_BANK_SIZE) + (INTC0_SEL_ROUTE_SIZE * i); u32 reg = readl(sel); if (route & BIT(i)) reg |= mask; else reg &= ~mask; writel(reg, sel); if (readl(sel) != reg) return -EACCES; } return 0; } static const struct irq_domain_ops aspeed_intc0_irq_domain_ops = { .translate = aspeed_intc0_irq_domain_translate, .activate = aspeed_intc0_irq_domain_activate, .alloc = aspeed_intc0_irq_domain_alloc, .free = irq_domain_free_irqs_common, .map = aspeed_intc0_irq_domain_map, }; static void aspeed_intc0_disable_swint(struct aspeed_intc0 *intc0) { writel(0, intc0->base + INTC0_SWINT_IER); } static void aspeed_intc0_disable_intbank(struct aspeed_intc0 *intc0) { for (int i = 0; i < INTC0_INTBANK_GROUPS; i++) { for (int j = 0; j < INTC0_INTBANKS_PER_GRP; j++) { u32 base = INTC0_INTBANKX_IER + (INTC0_INTBANK_SIZE * i) + (INTC0_INTMX_BANK_SIZE * j); writel(0, intc0->base + base); } } } static void aspeed_intc0_disable_intm(struct aspeed_intc0 *intc0) { for (int i = 0; i < INTC0_INTM_BANK_NUM; i++) writel(0, intc0->base + INTC0_INTMX_IER + (INTC0_INTMX_BANK_SIZE * i)); } static int aspeed_intc0_probe(struct platform_device *pdev, struct device_node *parent) { struct device_node *node = pdev->dev.of_node; struct irq_domain *parent_domain; struct aspeed_intc0 *intc0; int ret; if (!parent) { pr_err("missing parent interrupt node\n"); return -ENODEV; } intc0 = devm_kzalloc(&pdev->dev, sizeof(*intc0), GFP_KERNEL); if (!intc0) return -ENOMEM; intc0->dev = &pdev->dev; intc0->parent = parent; intc0->base = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(intc0->base)) return PTR_ERR(intc0->base); aspeed_intc0_disable_swint(intc0); aspeed_intc0_disable_intbank(intc0); aspeed_intc0_disable_intm(intc0); raw_spin_lock_init(&intc0->intc_lock); parent_domain = irq_find_host(parent); if (!parent_domain) { pr_err("unable to obtain parent domain\n"); return -ENODEV; } if (!of_device_is_compatible(parent, "arm,gic-v3")) return -ENODEV; intc0->local = irq_domain_create_hierarchy(parent_domain, 0, INT0_NUM, of_fwnode_handle(node), &aspeed_intc0_irq_domain_ops, intc0); if (!intc0->local) return -ENOMEM; ret = aspeed_intc_populate_ranges(&pdev->dev, &intc0->ranges); if (ret < 0) { irq_domain_remove(intc0->local); return ret; } return 0; } IRQCHIP_PLATFORM_DRIVER_BEGIN(ast2700_intc0) IRQCHIP_MATCH("aspeed,ast2700-intc0", aspeed_intc0_probe) IRQCHIP_PLATFORM_DRIVER_END(ast2700_intc0)
Information contained on this website is for historical information purposes only and does not indicate or represent copyright ownership.
Created with Cregit http://github.com/cregit/cregit
Version 2.0-RC1