lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-ID: <20260205-irqchip-v1-2-b0310e06c087@aspeedtech.com>
Date: Thu, 5 Feb 2026 14:07:20 +0800
From: Ryan Chen <ryan_chen@...eedtech.com>
To: Thomas Gleixner <tglx@...utronix.de>, Rob Herring <robh@...nel.org>,
	Krzysztof Kozlowski <krzk+dt@...nel.org>, Conor Dooley <conor+dt@...nel.org>,
	Joel Stanley <joel@....id.au>, Andrew Jeffery <andrew@...econstruct.com.au>,
	Paul Walmsley <pjw@...nel.org>, Palmer Dabbelt <palmer@...belt.com>, "Albert
 Ou" <aou@...s.berkeley.edu>, Alexandre Ghiti <alex@...ti.fr>
CC: <linux-kernel@...r.kernel.org>, <devicetree@...r.kernel.org>,
	<linux-arm-kernel@...ts.infradead.org>, <linux-aspeed@...ts.ozlabs.org>,
	<linux-riscv@...ts.infradead.org>, Ryan Chen <ryan_chen@...eedtech.com>
Subject: [PATCH 2/4] irqchip/ast2700-intcx: Add AST2700 INTC0/INTC1 support

The AST2700 interrupt fabric is shared by multiple integrated processors
(PSP/SSP/TSP/BootMCU), each with its own interrupt controller and its own
devicetree view of the system. As a result, interrupt routing cannot be
treated as fixed: the valid route for a peripheral interrupt depends on
which processor is consuming it.

The INTC0 driver models this by creating a hierarchical irqdomain under
the upstream interrupt controller selected by the interrupt-parent
property in the devicetree. Information derived from this relationship
is incorporated into the route resolution logic for the controller.

The INTC1 driver implements the banked INTM-fed controller and forwards
interrupts toward INTC0, without embedding assumptions about the final
destination processor.

Signed-off-by: Ryan Chen <ryan_chen@...eedtech.com>
---
 drivers/irqchip/Kconfig                  |  11 +
 drivers/irqchip/Makefile                 |   1 +
 drivers/irqchip/irq-ast2700-intc0-test.c | 474 +++++++++++++++++++
 drivers/irqchip/irq-ast2700-intc0.c      | 770 +++++++++++++++++++++++++++++++
 drivers/irqchip/irq-ast2700-intc1.c      | 345 ++++++++++++++
 drivers/irqchip/irq-ast2700.c            | 105 +++++
 drivers/irqchip/irq-ast2700.h            |  37 ++
 7 files changed, 1743 insertions(+)

diff --git a/drivers/irqchip/Kconfig b/drivers/irqchip/Kconfig
index f334f49c9791..3cc0f3af0e1e 100644
--- a/drivers/irqchip/Kconfig
+++ b/drivers/irqchip/Kconfig
@@ -110,6 +110,17 @@ config AL_FIC
 	help
 	  Support Amazon's Annapurna Labs Fabric Interrupt Controller.
 
+config ASPEED_AST2700_INTC
+	bool "ASPEED AST2700 Interrupt Controller support"
+	depends on OF
+	depends on ARCH_ASPEED || COMPILE_TEST
+	select IRQ_DOMAIN_HIERARCHY
+
+config ASPEED_AST2700_INTC_TEST
+	bool "Tests for the ASPEED AST2700 Interrupt Controller"
+	depends on ASPEED_AST2700_INTC && KUNIT=y
+	default KUNIT_ALL_TESTS
+
 config ATMEL_AIC_IRQ
 	bool
 	select GENERIC_IRQ_CHIP
diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile
index 6a229443efe0..51fdf269e436 100644
--- a/drivers/irqchip/Makefile
+++ b/drivers/irqchip/Makefile
@@ -88,6 +88,7 @@ obj-$(CONFIG_MVEBU_PIC)			+= irq-mvebu-pic.o
 obj-$(CONFIG_MVEBU_SEI)			+= irq-mvebu-sei.o
 obj-$(CONFIG_LS_EXTIRQ)			+= irq-ls-extirq.o
 obj-$(CONFIG_LS_SCFG_MSI)		+= irq-ls-scfg-msi.o
+obj-$(CONFIG_ASPEED_AST2700_INTC)	+= irq-ast2700.o irq-ast2700-intc0.o irq-ast2700-intc1.o
 obj-$(CONFIG_ARCH_ASPEED)		+= irq-aspeed-vic.o irq-aspeed-i2c-ic.o irq-aspeed-scu-ic.o
 obj-$(CONFIG_ARCH_ASPEED)		+= irq-aspeed-intc.o
 obj-$(CONFIG_STM32MP_EXTI)		+= irq-stm32mp-exti.o
diff --git a/drivers/irqchip/irq-ast2700-intc0-test.c b/drivers/irqchip/irq-ast2700-intc0-test.c
new file mode 100644
index 000000000000..d6bc19676b2e
--- /dev/null
+++ b/drivers/irqchip/irq-ast2700-intc0-test.c
@@ -0,0 +1,474 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ *  Copyright (C) 2026 Code Construct
+ */
+#include <kunit/test.h>
+#include "irq-ast2700.h"
+
+static void aspeed_intc0_resolve_route_bad_args(struct kunit *test)
+{
+	static const struct aspeed_intc_interrupt_range c1ranges[] = { 0 };
+	static const aspeed_intc_output_t c1outs[] = { 0 };
+	struct aspeed_intc_interrupt_range resolved;
+	const struct irq_domain c0domain = { 0 };
+
+	int rc;
+
+	rc = aspeed_intc0_resolve_route(NULL, 0, c1outs, 0, c1ranges, NULL);
+	KUNIT_EXPECT_EQ(test, rc, -EINVAL);
+
+	rc = aspeed_intc0_resolve_route(&c0domain, 0, c1outs,
+					ARRAY_SIZE(c1ranges), c1ranges,
+					&resolved);
+	KUNIT_EXPECT_EQ(test, rc, -ENODEV);
+
+	rc = aspeed_intc0_resolve_route(&c0domain, ARRAY_SIZE(c1outs), c1outs,
+					0, c1ranges, &resolved);
+	KUNIT_EXPECT_EQ(test, rc, -ENODEV);
+}
+
+static int
+arm_gicv3_fwnode_read_string_array(const struct fwnode_handle *fwnode_handle,
+				   const char *propname, const char **val,
+				   size_t nval)
+{
+	if (!propname)
+		return -EINVAL;
+
+	if (!val)
+		return 1;
+
+	if (WARN_ON(nval != 1))
+		return -EOVERFLOW;
+
+	*val = "arm,gic-v3";
+	return 1;
+}
+
+static const struct fwnode_operations arm_gicv3_fwnode_ops = {
+	.property_read_string_array = arm_gicv3_fwnode_read_string_array,
+};
+
+static void aspeed_intc_resolve_route_invalid_c0domain(struct kunit *test)
+{
+	struct device_node intc0_node = {
+		.fwnode = { .ops = &arm_gicv3_fwnode_ops },
+	};
+	const struct irq_domain c0domain = { .fwnode = &intc0_node.fwnode };
+	static const struct aspeed_intc_interrupt_range c1ranges[] = { 0 };
+	static const aspeed_intc_output_t c1outs[] = { 0 };
+	struct aspeed_intc_interrupt_range resolved;
+	int rc;
+
+	rc = aspeed_intc0_resolve_route(&c0domain, ARRAY_SIZE(c1outs), c1outs,
+					ARRAY_SIZE(c1ranges), c1ranges,
+					&resolved);
+	KUNIT_EXPECT_NE(test, rc, 0);
+}
+
+static int
+aspeed_intc0_fwnode_read_string_array(const struct fwnode_handle *fwnode_handle,
+				      const char *propname, const char **val,
+				      size_t nval)
+{
+	if (!propname)
+		return -EINVAL;
+
+	if (!val)
+		return 1;
+
+	if (WARN_ON(nval != 1))
+		return -EOVERFLOW;
+
+	*val = "aspeed,ast2700-intc0-ic";
+	return nval;
+}
+
+static const struct fwnode_operations intc0_fwnode_ops = {
+	.property_read_string_array = aspeed_intc0_fwnode_read_string_array,
+};
+
+static void
+aspeed_intc0_resolve_route_c1i1o1c0i1o1_connected(struct kunit *test)
+{
+	struct device_node intc0_node = {
+		.fwnode = { .ops = &intc0_fwnode_ops },
+	};
+	struct aspeed_intc_interrupt_range c1ranges[] = {
+		{
+			.start = 0,
+			.count = 1,
+			.upstream = {
+				.fwnode = &intc0_node.fwnode,
+				.param_count = 1,
+				.param = { 128 }
+			}
+		}
+	};
+	static const aspeed_intc_output_t c1outs[] = { 0 };
+	struct aspeed_intc_interrupt_range resolved;
+	struct aspeed_intc_interrupt_range intc0_ranges[] = {
+		{
+			.start = 128,
+			.count = 1,
+			.upstream = {
+				.fwnode = NULL,
+				.param_count = 0,
+				.param = { 0 },
+			}
+		}
+	};
+	struct aspeed_intc0 intc0 = {
+		.ranges = { .ranges = intc0_ranges, .nranges = ARRAY_SIZE(intc0_ranges), }
+	};
+	const struct irq_domain c0domain = {
+		.host_data = &intc0,
+		.fwnode = &intc0_node.fwnode
+	};
+	int rc;
+
+	rc = aspeed_intc0_resolve_route(&c0domain, ARRAY_SIZE(c1outs), c1outs,
+					ARRAY_SIZE(c1ranges), c1ranges,
+					&resolved);
+	KUNIT_EXPECT_EQ(test, rc, 0);
+	KUNIT_EXPECT_EQ(test, resolved.start, 0);
+	KUNIT_EXPECT_EQ(test, resolved.count, 1);
+	KUNIT_EXPECT_EQ(test, resolved.upstream.param[0], 128);
+}
+
+static void
+aspeed_intc0_resolve_route_c1i1o1c0i1o1_disconnected(struct kunit *test)
+{
+	struct device_node intc0_node = {
+		.fwnode = { .ops = &intc0_fwnode_ops },
+	};
+	struct aspeed_intc_interrupt_range c1ranges[] = {
+		{
+			.start = 0,
+			.count = 1,
+			.upstream = {
+				.fwnode = &intc0_node.fwnode,
+				.param_count = 1,
+				.param = { 128 }
+			}
+		}
+	};
+	static const aspeed_intc_output_t c1outs[] = { 0 };
+	struct aspeed_intc_interrupt_range resolved;
+	struct aspeed_intc_interrupt_range intc0_ranges[] = {
+		{
+			.start = 129,
+			.count = 1,
+			.upstream = {
+				.fwnode = NULL,
+				.param_count = 0,
+				.param = { 0 },
+			}
+		}
+	};
+	struct aspeed_intc0 intc0 = {
+		.ranges = {
+			.ranges = intc0_ranges,
+			.nranges = ARRAY_SIZE(intc0_ranges),
+		}
+	};
+	const struct irq_domain c0domain = {
+		.host_data = &intc0,
+		.fwnode = &intc0_node.fwnode
+	};
+	int rc;
+
+	rc = aspeed_intc0_resolve_route(&c0domain, ARRAY_SIZE(c1outs), c1outs,
+					ARRAY_SIZE(c1ranges), c1ranges,
+					&resolved);
+	KUNIT_EXPECT_NE(test, rc, 0);
+}
+
+static void aspeed_intc0_resolve_route_c1i1o1mc0i1o1(struct kunit *test)
+{
+	struct device_node intc0_node = {
+		.fwnode = { .ops = &intc0_fwnode_ops },
+	};
+	struct aspeed_intc_interrupt_range c1ranges[] = {
+		{
+			.start = 0,
+			.count = 1,
+			.upstream = {
+				.fwnode = &intc0_node.fwnode,
+				.param_count = 1,
+				.param = { 480 }
+			}
+		}
+	};
+	static const aspeed_intc_output_t c1outs[] = { 0 };
+	struct aspeed_intc_interrupt_range resolved;
+	struct aspeed_intc_interrupt_range intc0_ranges[] = {
+		{
+			.start = 192,
+			.count = 1,
+			.upstream = {
+				.fwnode = NULL,
+				.param_count = 0,
+				.param = { 0 },
+			}
+		}
+	};
+	struct aspeed_intc0 intc0 = {
+		.ranges = {
+			.ranges = intc0_ranges,
+			.nranges = ARRAY_SIZE(intc0_ranges),
+		}
+	};
+	const struct irq_domain c0domain = {
+		.host_data = &intc0,
+		.fwnode = &intc0_node.fwnode
+	};
+	int rc;
+
+	rc = aspeed_intc0_resolve_route(&c0domain, ARRAY_SIZE(c1outs), c1outs,
+					ARRAY_SIZE(c1ranges), c1ranges,
+					&resolved);
+	KUNIT_EXPECT_EQ(test, rc, 0);
+	KUNIT_EXPECT_EQ(test, resolved.start, 0);
+	KUNIT_EXPECT_EQ(test, resolved.count, 1);
+	KUNIT_EXPECT_EQ(test, resolved.upstream.param[0], 480);
+}
+
+static void aspeed_intc0_resolve_route_c1i2o2mc0i1o1(struct kunit *test)
+{
+	struct device_node intc0_node = {
+		.fwnode = { .ops = &intc0_fwnode_ops },
+	};
+	struct aspeed_intc_interrupt_range c1ranges[] = {
+		{
+			.start = 0,
+			.count = 1,
+			.upstream = {
+				.fwnode = &intc0_node.fwnode,
+				.param_count = 1,
+				.param = { 480 }
+			}
+		},
+		{
+			.start = 1,
+			.count = 1,
+			.upstream = {
+				.fwnode = &intc0_node.fwnode,
+				.param_count = 1,
+				.param = { 510 }
+			}
+		}
+	};
+	static const aspeed_intc_output_t c1outs[] = { 1 };
+	struct aspeed_intc_interrupt_range resolved;
+	struct aspeed_intc_interrupt_range intc0_ranges[] = {
+		{
+			.start = 208,
+			.count = 1,
+			.upstream = {
+				.fwnode = NULL,
+				.param_count = 0,
+				.param = { 0 },
+			}
+		}
+	};
+	struct aspeed_intc0 intc0 = {
+		.ranges = {
+			.ranges = intc0_ranges,
+			.nranges = ARRAY_SIZE(intc0_ranges),
+		}
+	};
+	const struct irq_domain c0domain = {
+		.host_data = &intc0,
+		.fwnode = &intc0_node.fwnode
+	};
+	int rc;
+
+	rc = aspeed_intc0_resolve_route(&c0domain, ARRAY_SIZE(c1outs), c1outs,
+					ARRAY_SIZE(c1ranges), c1ranges,
+					&resolved);
+	KUNIT_EXPECT_EQ(test, rc, 0);
+	KUNIT_EXPECT_EQ(test, resolved.start, 1);
+	KUNIT_EXPECT_EQ(test, resolved.count, 1);
+	KUNIT_EXPECT_EQ(test, resolved.upstream.param[0], 510);
+}
+
+static void aspeed_intc0_resolve_route_c1i1o1mc0i2o1(struct kunit *test)
+{
+	struct device_node intc0_node = {
+		.fwnode = { .ops = &intc0_fwnode_ops },
+	};
+	struct aspeed_intc_interrupt_range c1ranges[] = {
+		{
+			.start = 0,
+			.count = 1,
+			.upstream = {
+				.fwnode = &intc0_node.fwnode,
+				.param_count = 1,
+				.param = { 510 }
+			}
+		},
+	};
+	static const aspeed_intc_output_t c1outs[] = { 0 };
+	struct aspeed_intc_interrupt_range resolved;
+	struct aspeed_intc_interrupt_range intc0_ranges[] = {
+		{
+			.start = 192,
+			.count = 1,
+			.upstream = {
+				.fwnode = NULL,
+				.param_count = 0,
+				.param = {0},
+			}
+		},
+		{
+			.start = 208,
+			.count = 1,
+			.upstream = {
+				.fwnode = NULL,
+				.param_count = 0,
+				.param = {0},
+			}
+		}
+	};
+	struct aspeed_intc0 intc0 = {
+		.ranges = {
+			.ranges = intc0_ranges,
+			.nranges = ARRAY_SIZE(intc0_ranges),
+		}
+	};
+	const struct irq_domain c0domain = {
+		.host_data = &intc0,
+		.fwnode = &intc0_node.fwnode
+	};
+	int rc;
+
+	rc = aspeed_intc0_resolve_route(&c0domain, ARRAY_SIZE(c1outs), c1outs,
+					ARRAY_SIZE(c1ranges), c1ranges,
+					&resolved);
+	KUNIT_EXPECT_EQ(test, rc, 0);
+	KUNIT_EXPECT_EQ(test, resolved.start, 0);
+	KUNIT_EXPECT_EQ(test, resolved.count, 1);
+	KUNIT_EXPECT_EQ(test, resolved.upstream.param[0], 510);
+}
+
+static void aspeed_intc0_resolve_route_c1i1o2mc0i1o1_invalid(struct kunit *test)
+{
+	struct device_node intc0_node = {
+		.fwnode = { .ops = &intc0_fwnode_ops },
+	};
+	struct aspeed_intc_interrupt_range c1ranges[] = {
+		{
+			.start = 0,
+			.count = 1,
+			.upstream = {
+				.fwnode = &intc0_node.fwnode,
+				.param_count = 1,
+				.param = { 480 }
+			}
+		}
+	};
+	static const aspeed_intc_output_t c1outs[] = {
+		AST2700_INTC_INVALID_ROUTE, 0
+	};
+	struct aspeed_intc_interrupt_range resolved;
+	struct aspeed_intc_interrupt_range intc0_ranges[] = {
+		{
+			.start = 192,
+			.count = 1,
+			.upstream = {
+				.fwnode = NULL,
+				.param_count = 0,
+				.param = { 0 },
+			}
+		}
+	};
+	struct aspeed_intc0 intc0 = {
+		.ranges = {
+			.ranges = intc0_ranges,
+			.nranges = ARRAY_SIZE(intc0_ranges),
+		}
+	};
+	const struct irq_domain c0domain = {
+		.host_data = &intc0,
+		.fwnode = &intc0_node.fwnode
+	};
+	int rc;
+
+	rc = aspeed_intc0_resolve_route(&c0domain, ARRAY_SIZE(c1outs), c1outs,
+					ARRAY_SIZE(c1ranges), c1ranges,
+					&resolved);
+	KUNIT_EXPECT_EQ(test, rc, 1);
+	KUNIT_EXPECT_EQ(test, resolved.start, 0);
+	KUNIT_EXPECT_EQ(test, resolved.count, 1);
+	KUNIT_EXPECT_EQ(test, resolved.upstream.param[0], 480);
+}
+
+static void
+aspeed_intc0_resolve_route_c1i1o1mc0i1o1_bad_range_upstream(struct kunit *test)
+{
+	struct device_node intc0_node = {
+		.fwnode = { .ops = &intc0_fwnode_ops },
+	};
+	struct aspeed_intc_interrupt_range c1ranges[] = {
+		{
+			.start = 0,
+			.count = 1,
+			.upstream = {
+				.fwnode = &intc0_node.fwnode,
+				.param_count = 0,
+				.param = { 0 }
+			}
+		}
+	};
+	static const aspeed_intc_output_t c1outs[] = { 0 };
+	struct aspeed_intc_interrupt_range resolved;
+	struct aspeed_intc_interrupt_range intc0_ranges[] = {
+		{
+			.start = 0,
+			.count = 0,
+			.upstream = {
+				.fwnode = NULL,
+				.param_count = 0,
+				.param = { 0 },
+			}
+		}
+	};
+	struct aspeed_intc0 intc0 = {
+		.ranges = {
+			.ranges = intc0_ranges,
+			.nranges = ARRAY_SIZE(intc0_ranges),
+		}
+	};
+	const struct irq_domain c0domain = {
+		.host_data = &intc0,
+		.fwnode = &intc0_node.fwnode
+	};
+	int rc;
+
+	rc = aspeed_intc0_resolve_route(&c0domain, ARRAY_SIZE(c1outs), c1outs,
+					ARRAY_SIZE(c1ranges), c1ranges,
+					&resolved);
+	KUNIT_EXPECT_NE(test, rc, 0);
+}
+
+static struct kunit_case ast2700_intc0_test_cases[] = {
+	KUNIT_CASE(aspeed_intc0_resolve_route_bad_args),
+	KUNIT_CASE(aspeed_intc_resolve_route_invalid_c0domain),
+	KUNIT_CASE(aspeed_intc0_resolve_route_c1i1o1c0i1o1_connected),
+	KUNIT_CASE(aspeed_intc0_resolve_route_c1i1o1c0i1o1_disconnected),
+	KUNIT_CASE(aspeed_intc0_resolve_route_c1i1o1mc0i1o1),
+	KUNIT_CASE(aspeed_intc0_resolve_route_c1i2o2mc0i1o1),
+	KUNIT_CASE(aspeed_intc0_resolve_route_c1i1o1mc0i2o1),
+	KUNIT_CASE(aspeed_intc0_resolve_route_c1i1o2mc0i1o1_invalid),
+	KUNIT_CASE(aspeed_intc0_resolve_route_c1i1o1mc0i1o1_bad_range_upstream),
+	{},
+};
+
+static struct kunit_suite ast2700_intc0_test_suite = {
+	.name = "ast2700-intc0",
+	.test_cases = ast2700_intc0_test_cases,
+};
+kunit_test_suite(ast2700_intc0_test_suite);
+
+MODULE_LICENSE("GPL");
diff --git a/drivers/irqchip/irq-ast2700-intc0.c b/drivers/irqchip/irq-ast2700-intc0.c
new file mode 100644
index 000000000000..722457dd2e18
--- /dev/null
+++ b/drivers/irqchip/irq-ast2700-intc0.c
@@ -0,0 +1,770 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ *  Aspeed Interrupt Controller.
+ *
+ *  Copyright (C) 2023 ASPEED Technology Inc.
+ */
+
+#include <asm-generic/errno.h>
+#include <linux/bitops.h>
+#include <linux/device.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/property.h>
+#include <linux/spinlock.h>
+
+#include <dt-bindings/interrupt-controller/arm-gic.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 GIC_P2P_SPI_END 128
+
+#define INTC0_SWINT_IER 0x10
+#define INTC0_SWINT_ISR 0x14
+#define INTC0_INTBANKX_IER 0x1000
+#define INTC0_INTBANK_GROUPS 11
+#define INTC0_INTBANKS_PER_GRP 3
+#define INTC0_INTMX_IER 0x1b00
+#define INTC0_INTMX_ISR 0x1b04
+#define INTC0_INTM_BANK_NUM 3
+#define INTM_IRQS_PER_BANK 10
+
+struct aspeed_intc0 {
+	struct device *dev;
+	void __iomem *base;
+	raw_spinlock_t intc_lock;
+	struct irq_domain *local;
+	struct device_node *parent;
+	struct aspeed_intc_interrupt_ranges ranges;
+};
+
+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;
+	unsigned int mask;
+
+	guard(raw_spinlock_irqsave)(&intc0->intc_lock);
+	mask = readl(intc0->base + INTC0_SWINT_IER) & ~BIT(bit);
+	writel(mask, 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;
+	unsigned int unmask;
+
+	guard(raw_spinlock_irqsave)(&intc0->intc_lock);
+	unmask = readl(intc0->base + INTC0_SWINT_IER) | BIT(bit);
+	writel(unmask, 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;
+	unsigned int mask;
+
+	guard(raw_spinlock_irqsave)(&intc0->intc_lock);
+	mask = readl(intc0->base + INTC0_INTMX_IER + bank * 0x10) & ~BIT(bit);
+	writel(mask, intc0->base + INTC0_INTMX_IER + bank * 0x10);
+	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;
+	unsigned int unmask;
+
+	guard(raw_spinlock_irqsave)(&intc0->intc_lock);
+	unmask = readl(intc0->base + INTC0_INTMX_IER + bank * 0x10) | BIT(bit);
+	writel(unmask, intc0->base + INTC0_INTMX_IER + bank * 0x10);
+	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 * 0x10);
+	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,
+};
+
+#define INTC0_IN_NUM 480
+#define INTC0_ROUTE_NUM 5
+
+static const aspeed_intc_output_t aspeed_intc0_routes[INTC0_IN_NUM / 32][INTC0_ROUTE_NUM] = {
+	[0] = {
+		[0b000] = 0,
+		[0b001] = 256,
+		[0b010] = 426,
+		[0b011] = AST2700_INTC_INVALID_ROUTE,
+		[0b100] = AST2700_INTC_INVALID_ROUTE,
+	},
+	[1] = {
+		[0b000] = 32,
+		[0b001] = 288,
+		[0b010] = 458,
+		[0b011] = AST2700_INTC_INVALID_ROUTE,
+		[0b100] = AST2700_INTC_INVALID_ROUTE,
+	},
+	[2] = {
+		[0b000] = 64,
+		[0b001] = 320,
+		[0b010] = 490,
+		[0b011] = AST2700_INTC_INVALID_ROUTE,
+		[0b100] = AST2700_INTC_INVALID_ROUTE,
+	},
+	[3] = {
+		[0b000] = 96,
+		[0b001] = 352,
+		[0b010] = 522,
+		[0b011] = AST2700_INTC_INVALID_ROUTE,
+		[0b100] = AST2700_INTC_INVALID_ROUTE,
+	},
+	[4] = {
+		[0b000] = 128,
+		[0b001] = 384,
+		[0b010] = 554,
+		[0b011] = 160,
+		[0b100] = 176,
+	},
+	[5] = {
+		[0b000] = 129,
+		[0b001] = 385,
+		[0b010] = 555,
+		[0b011] = 161,
+		[0b100] = 177,
+	},
+	[6] = {
+		[0b000] = 130,
+		[0b001] = 386,
+		[0b010] = 556,
+		[0b011] = 162,
+		[0b100] = 178,
+	},
+	[7] = {
+		[0b000] = 131,
+		[0b001] = 387,
+		[0b010] = 557,
+		[0b011] = 163,
+		[0b100] = 179,
+	},
+	[8] = {
+		[0b000] = 132,
+		[0b001] = 388,
+		[0b010] = 558,
+		[0b011] = 164,
+		[0b100] = 180,
+
+	},
+	[9] = {
+		[0b000] = 133,
+		[0b001] = 544,
+		[0b010] = 714,
+		[0b011] = 165,
+		[0b100] = 181,
+	},
+	[10] = {
+		[0b000] = 134,
+		[0b001] = 545,
+		[0b010] = 715,
+		[0b011] = 166,
+		[0b100] = 182,
+	},
+	[11] = {
+		[0b000] = 135,
+		[0b001] = 546,
+		[0b010] = 706,
+		[0b011] = 167,
+		[0b100] = 183,
+	},
+	[12] = {
+		[0b000] = 136,
+		[0b001] = 547,
+		[0b010] = 707,
+		[0b011] = 168,
+		[0b100] = 184,
+
+	},
+	[13] = {
+		[0b000] = 137,
+		[0b001] = 548,
+		[0b010] = 708,
+		[0b011] = 169,
+		[0b100] = 185,
+
+	},
+	[14] = {
+		[0b000] = 138,
+		[0b001] = 549,
+		[0b010] = 709,
+		[0b011] = 170,
+		[0b100] = 186,
+	},
+};
+
+#define INTC0_INTM_NUM 50
+
+static const aspeed_intc_output_t
+	aspeed_intc0_intm_routes[INTC0_INTM_NUM / 10] = {
+		[0] = 192, /* INTM00 ~ INTM09 */
+		[1] = 416, /* INTM10 ~ INTM19 */
+		[2] = 586, /* INTM20 ~ INTM29 */
+		[3] = 208, /* INTM30 ~ INTM39 */
+		[4] = 224, /* INTM40 ~ INTM49 */
+	};
+
+static bool range_contains_element(u32 start, u32 count, u32 value)
+{
+	if (WARN_ON_ONCE((U32_MAX - count) < start))
+		return false;
+
+	return value >= start && value < start + count;
+}
+
+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;
+	u32 base;
+
+	if (!range_contains_element(range->start, range->count, outpin))
+		return -ENOENT;
+
+	if (range->upstream.param_count == 0)
+		return -EINVAL;
+
+	base = range->upstream.param[0];
+	offset = outpin - range->start;
+	if ((U32_MAX - offset) < base) {
+		dev_warn(intc0->dev,
+			 "%s: Arithmetic overflow for input derivation: %u + %u\n",
+			 __func__, base, offset);
+		return -EINVAL;
+	}
+
+	*input = base + offset;
+	return 0;
+}
+
+static bool 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];
+
+		dev_dbg(intc0->dev,
+			"%s: Inspecting candidate parent range %zu starting at %u for %u\n",
+			__func__, i, range.start, range.count);
+		if (!range_contains_element(range.start, range.count, output)) {
+			dev_dbg(intc0->dev, "%s: Output %u not in range [%u, %u)\n",
+				__func__, output, range.start, range.start + range.count);
+			continue;
+		}
+
+		if (range.upstream.fwnode != parent) {
+			dev_dbg(intc0->dev, "%s: Parent mismatch for range %zu\n", __func__, i);
+			continue;
+		}
+
+		dev_dbg(intc0->dev, "%s: Parent range %zu matched for output %u\n",
+			__func__, i, output);
+
+		if (resolved) {
+			resolved->start = output;
+			resolved->count = 1;
+			resolved->upstream = range.upstream;
+			resolved->upstream.param[1] += output - range.start;
+		}
+
+		return true;
+	}
+
+	return false;
+}
+
+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)
+{
+	aspeed_intc_output_t c0o;
+	int rc = -ENOENT;
+
+	if (input < INT_NUM) {
+		bool found;
+
+		dev_dbg(intc0->dev, "%s: Resolving parent route for linear input %u\n",
+			__func__, input);
+		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 / 32][i];
+			if (c0o == AST2700_INTC_INVALID_ROUTE)
+				continue;
+
+			if (input < GIC_P2P_SPI_END)
+				c0o += input % 32;
+
+			found = resolve_parent_range_for_output(intc0, parent, c0o, resolved);
+			rc = found ? (int)i : -ENOENT;
+		}
+	} else if (input < (INT_NUM + INTM_NUM)) {
+		bool found;
+
+		dev_dbg(intc0->dev, "%s: Resolving parent route for merged input %u\n",
+			__func__, input);
+		c0o = aspeed_intc0_intm_routes[(input - INT_NUM) / INTM_IRQS_PER_BANK];
+		c0o += ((input - INT_NUM) % INTM_IRQS_PER_BANK);
+
+		found = resolve_parent_range_for_output(intc0, parent, c0o, resolved);
+		rc = found ? 0 : -ENOENT;
+	} else if (input < (INT_NUM + INTM_NUM + SWINT_NUM)) {
+		bool found;
+
+		dev_dbg(intc0->dev, "%s: Resolving parent route for merged input %u\n",
+			__func__, input);
+		c0o = input - SWINT_BASE + 144;
+		found = resolve_parent_range_for_output(intc0, parent, c0o, resolved);
+		rc = found ? 0 : -ENOENT;
+	} else {
+		dev_dbg(intc0->dev, "%s: Invalid input: %u\n", __func__, input);
+		return -ENOENT;
+	}
+
+	if (rc < 0) {
+		dev_dbg(intc0->dev,
+			"%s: Failed to resolve INTC0 parent route for input %u: %d\n",
+			__func__, input, rc);
+	} else {
+		dev_dbg(intc0->dev,
+			"%s: Resolved INTC0 input %u route to parent via %d\n",
+			__func__, input, rc);
+	}
+
+	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 aspeed_intc_output_t c1outs[static nc1outs],
+			       size_t nc1ranges,
+			       const struct aspeed_intc_interrupt_range c1ranges[static nc1ranges],
+			       struct aspeed_intc_interrupt_range *resolved)
+{
+	struct aspeed_intc0 *intc0;
+	struct fwnode_handle *parent_fwnode;
+	int ret;
+
+	if (!c0domain || !resolved)
+		return -EINVAL;
+
+	if (nc1outs > INT_MAX)
+		return -EINVAL;
+
+	if (nc1outs == 0 || nc1ranges == 0)
+		return -ENODEV;
+
+	if (!fwnode_device_is_compatible(c0domain->fwnode,
+					 "aspeed,ast2700-intc0-ic"))
+		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++) {
+		aspeed_intc_output_t c1o = c1outs[i];
+
+		if (c1o == AST2700_INTC_INVALID_ROUTE) {
+			dev_dbg(intc0->dev, "%s: Invalid output at route index %zu\n",
+				__func__, i);
+			continue;
+		}
+
+		dev_dbg(intc0->dev, "%s: Have output %u for route index %zu\n",
+			__func__, c1o, i);
+
+		for (size_t j = 0; j < nc1ranges; j++) {
+			struct aspeed_intc_interrupt_range c1r = c1ranges[j];
+			u32 input;
+
+			dev_dbg(intc0->dev,
+				"%s: Inspecting candidate range %zu starting at %u for %u\n",
+				__func__, j, c1r.start, c1r.count);
+
+			/*
+			 * 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 == irq_find_matching_fwspec(&c1r.upstream,
+								   c0domain->bus_token) ||
+			      IS_ENABLED(CONFIG_ASPEED_AST2700_INTC_TEST))) {
+				dev_dbg(intc0->dev, "%s: Parent mismatch for candidate range %zu\n",
+					__func__, j);
+				continue;
+			}
+
+			ret = resolve_input_from_child_ranges(intc0, &c1r, c1o, &input);
+			if (ret) {
+				if (ret == -ENOENT)
+					dev_dbg(intc0->dev,
+						"%s: Output %u not in candidate range %zu starting at %u for %u\n",
+						__func__, c1o, j, c1r.start, c1r.count);
+				continue;
+			}
+			dev_dbg(intc0->dev,
+				"%s: Resolved INTC0 input to %u using candidate range %zu: [%u, %u)\n",
+				__func__, input, j, c1r.start, c1r.start + c1r.count);
+
+			/*
+			 * INTC1 should never request routes for peripheral interrupt sources
+			 * directly attached to INTC0.
+			 */
+			if (input < GIC_P2P_SPI_END) {
+				dev_dbg(intc0->dev,
+					"%s: Invalid range specification at index %zu routed INTC1 output to unreachable INTC0 input\n",
+					__func__, j);
+				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[0] = input;
+			dev_dbg(intc0->dev,
+				"%s: Route resolution selected INTC1 output %u via index %zu\n",
+				__func__, c1o, i);
+			/* Cast protected by prior test against nc1outs */
+			return (int)i;
+		}
+	}
+
+	ret = -EHOSTUNREACH;
+	return ret;
+}
+EXPORT_SYMBOL_GPL(aspeed_intc0_resolve_route);
+
+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);
+
+	if (data->hwirq < INT_NUM) {
+		int bank = data->hwirq / 32;
+		int bit = data->hwirq % 32;
+		u32 mask = BIT(bit);
+		int route;
+
+		route = resolve_parent_route_for_input(intc0,
+						       intc0->local->parent->fwnode,
+						       data->hwirq, NULL);
+		if (route < 0)
+			return route;
+
+		guard(raw_spinlock_irqsave)(&intc0->intc_lock);
+		for (int i = 0; i < 3; i++) {
+			void __iomem *sel = intc0->base + 0x200 + bank * 4 + 0x100 * i;
+			u32 reg = readl(sel);
+
+			if (route & BIT(i))
+				reg |= mask;
+			else
+				reg &= ~mask;
+
+			writel(reg, sel);
+			if (readl(sel) != reg)
+				return -EACCES;
+		}
+	} else if (data->hwirq < INT0_NUM) {
+		return 0;
+	} else {
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static const struct irq_domain_ops aspeed_intc0_ic_irq_domain_ops = {
+	.translate = aspeed_intc0_irq_domain_translate,
+	.alloc = aspeed_intc0_irq_domain_alloc,
+	.free = irq_domain_free_irqs_common,
+	.map = aspeed_intc0_irq_domain_map,
+	.activate = aspeed_intc0_irq_domain_activate,
+};
+
+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)
+{
+	int i, j;
+
+	for (i = 0; i < INTC0_INTBANK_GROUPS; i++) {
+		for (j = 0; j < INTC0_INTBANKS_PER_GRP; j++) {
+			u32 base = INTC0_INTBANKX_IER + (0x100 * i) + (0x10 * j);
+
+			writel(0, intc0->base + base);
+		}
+	}
+}
+
+static void aspeed_intc0_disable_intm(struct aspeed_intc0 *intc0)
+{
+	int i;
+
+	for (i = 0; i < INTC0_INTM_BANK_NUM; i++)
+		writel(0, intc0->base + INTC0_INTMX_IER + (0x10 * i));
+}
+
+static int aspeed_intc0_ic_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_ic_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-ic", aspeed_intc0_ic_probe)
+IRQCHIP_PLATFORM_DRIVER_END(ast2700_intc0)
+
+#ifdef CONFIG_ASPEED_AST2700_INTC_TEST
+#include "irq-ast2700-intc0-test.c"
+#endif
diff --git a/drivers/irqchip/irq-ast2700-intc1.c b/drivers/irqchip/irq-ast2700-intc1.c
new file mode 100644
index 000000000000..51371cc57c5c
--- /dev/null
+++ b/drivers/irqchip/irq-ast2700-intc1.c
@@ -0,0 +1,345 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ *  Aspeed AST2700 Interrupt Controller.
+ *
+ *  Copyright (C) 2023 ASPEED Technology Inc.
+ */
+
+#include "linux/dev_printk.h"
+#include "linux/device/devres.h"
+#include "linux/property.h"
+#include <linux/bitops.h>
+#include <linux/device.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/of.h>
+#include <linux/of_irq.h>
+#include <linux/spinlock.h>
+
+#include "irq-ast2700.h"
+
+#define INTC1_IER 0x100
+#define INTC1_ISR 0x104
+#define INTC1_IRQS_PER_BANK 32
+#define INTC1_BANK_NUM 6
+#define INTC1_ROUTE_NUM 7
+
+struct aspeed_intc1 {
+	struct device *dev;
+	void __iomem *base;
+	raw_spinlock_t intc_lock;
+	struct irq_domain *local;
+	struct irq_domain *upstream;
+	struct aspeed_intc_interrupt_ranges ranges;
+};
+
+static void aspeed_intc1_disable_int(struct aspeed_intc1 *intc1)
+{
+	for (int i = 0; i < INTC1_BANK_NUM; i++)
+		writel(0x0, intc1->base + INTC1_IER + (0x10 * i));
+}
+
+static void aspeed_intc1_ic_irq_handler(struct irq_desc *desc)
+{
+	struct aspeed_intc1 *intc1 = irq_desc_get_handler_data(desc);
+	struct irq_chip *chip = irq_desc_get_chip(desc);
+	unsigned long bit, status;
+
+	chained_irq_enter(chip, desc);
+
+	for (int bank = 0; bank < INTC1_BANK_NUM; bank++) {
+		status = readl(intc1->base + INTC1_ISR + (0x10 * bank));
+		if (!status)
+			continue;
+
+		for_each_set_bit(bit, &status, INTC1_IRQS_PER_BANK) {
+			generic_handle_domain_irq(intc1->local,
+						  (bank * INTC1_IRQS_PER_BANK) +
+							  bit);
+			writel(BIT(bit),
+			       intc1->base + INTC1_ISR + (0x10 * bank));
+		}
+	}
+
+	chained_irq_exit(chip, desc);
+}
+
+static void aspeed_intc1_irq_mask(struct irq_data *data)
+{
+	struct aspeed_intc1 *intc1 = irq_data_get_irq_chip_data(data);
+	int bank = data->hwirq / INTC1_IRQS_PER_BANK;
+	int bit = data->hwirq % INTC1_IRQS_PER_BANK;
+	unsigned int mask;
+
+	guard(raw_spinlock_irqsave)(&intc1->intc_lock);
+	mask = readl(intc1->base + INTC1_IER + (0x10 * bank)) & ~BIT(bit);
+	writel(mask, intc1->base + INTC1_IER + (0x10 * bank));
+}
+
+static void aspeed_intc1_irq_unmask(struct irq_data *data)
+{
+	struct aspeed_intc1 *intc1 = irq_data_get_irq_chip_data(data);
+	int bank = data->hwirq / INTC1_IRQS_PER_BANK;
+	int bit = data->hwirq % INTC1_IRQS_PER_BANK;
+	unsigned int unmask;
+
+	guard(raw_spinlock_irqsave)(&intc1->intc_lock);
+	unmask = readl(intc1->base + INTC1_IER + (0x10 * bank)) | BIT(bit);
+	writel(unmask, intc1->base + INTC1_IER + (0x10 * bank));
+}
+
+static struct irq_chip aspeed_intc_chip = {
+	.name = "ASPEED INTC1",
+	.irq_mask = aspeed_intc1_irq_mask,
+	.irq_unmask = aspeed_intc1_irq_unmask,
+};
+
+static int aspeed_intc1_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_LEVEL_HIGH;
+	return 0;
+}
+
+static int aspeed_intc1_ic_map_irq_domain(struct irq_domain *domain,
+					  unsigned int irq,
+					  irq_hw_number_t hwirq)
+{
+	irq_domain_set_info(domain, irq, hwirq, &aspeed_intc_chip,
+			    domain->host_data, handle_level_irq, NULL, NULL);
+	return 0;
+}
+
+#define INTC1_IN_NUM 192
+
+/*
+ * In-bound interrupts are progressively merged into one out-bound interrupt in
+ * groups of 32. Apply this fact to compress the route table in corresponding
+ * groups of 32.
+ */
+static const aspeed_intc_output_t aspeed_intc1_routes[INTC1_IN_NUM / 32][INTC1_ROUTE_NUM] = {
+	[0] = {
+		[0b000] = 0,
+		[0b001] = AST2700_INTC_INVALID_ROUTE, /* path not verified */
+		[0b010] = 10,
+		[0b011] = 20,
+		[0b100] = 30,
+		[0b101] = 40,
+		[0b110] = 50,
+	},
+	[1] = {
+		[0b000] = 1,
+		[0b001] = AST2700_INTC_INVALID_ROUTE,
+		[0b010] = 11,
+		[0b011] = 21,
+		[0b100] = 31,
+		[0b101] = 41,
+		[0b110] = 50,
+	},
+	[2] = {
+		[0b000] = 2,
+		[0b001] = AST2700_INTC_INVALID_ROUTE,
+		[0b010] = 12,
+		[0b011] = 22,
+		[0b100] = 32,
+		[0b101] = 42,
+		[0b110] = 50,
+	},
+	[3] = {
+		[0b000] = 3,
+		[0b001] = AST2700_INTC_INVALID_ROUTE,
+		[0b010] = 13,
+		[0b011] = 23,
+		[0b100] = 33,
+		[0b101] = 43,
+		[0b110] = 50,
+	},
+	[4] = {
+		[0b000] = 4,
+		[0b001] = AST2700_INTC_INVALID_ROUTE,
+		[0b010] = 14,
+		[0b011] = 24,
+		[0b100] = 34,
+		[0b101] = 44,
+		[0b110] = 50,
+	},
+	[5] = {
+		[0b000] = 5,
+		[0b001] = AST2700_INTC_INVALID_ROUTE,
+		[0b010] = 15,
+		[0b011] = 25,
+		[0b100] = 35,
+		[0b101] = 45,
+		[0b110] = 50,
+	},
+};
+
+#define INTC1_BOOTMCU_ROUTE 0b110
+
+static int aspeed_intc1_parent_is_bootmcu(const struct irq_domain *upstream)
+{
+	if (!upstream || !upstream->fwnode)
+		return 0;
+
+	return fwnode_device_is_compatible(upstream->fwnode, "riscv,aplic");
+}
+
+static int aspeed_intc1_irq_domain_activate(struct irq_domain *domain,
+					    struct irq_data *data, bool reserve)
+{
+	struct aspeed_intc1 *intc1 = irq_data_get_irq_chip_data(data);
+	int bank = data->hwirq / INTC1_IRQS_PER_BANK;
+	struct aspeed_intc_interrupt_range resolved;
+	int bit = data->hwirq % INTC1_IRQS_PER_BANK;
+	u32 mask = BIT(bit);
+	int rc;
+
+	if (WARN_ON_ONCE((data->hwirq >> 5) >= ARRAY_SIZE(aspeed_intc1_routes)))
+		return -EINVAL;
+
+	dev_dbg(intc1->dev, "Activation request for hwirq %lu in domain %s\n",
+		data->hwirq, domain->name);
+
+	/*
+	 * outpin may be an error if the upstream is the BootMCU APLIC node, or
+	 * anything except a valid intc0 driver instance
+	 */
+	rc = aspeed_intc0_resolve_route(intc1->upstream, INTC1_ROUTE_NUM,
+					aspeed_intc1_routes[data->hwirq >> 5],
+					intc1->ranges.nranges,
+					intc1->ranges.ranges, &resolved);
+	if (rc < 0) {
+		if (!aspeed_intc1_parent_is_bootmcu(intc1->upstream)) {
+			dev_warn(intc1->dev,
+				 "Failed to resolve interrupt route for hwirq %lu in domain %s\n",
+				 data->hwirq, domain->name);
+			return rc;
+		}
+		rc = INTC1_BOOTMCU_ROUTE;
+	}
+
+	guard(raw_spinlock_irqsave)(&intc1->intc_lock);
+	/* Route selector uses 3 bits across the selector registers. */
+	for (int i = 0; i < 3; i++) {
+		void __iomem *sel = intc1->base + 0x80 + bank * 4 + 0x20 * i;
+		u32 reg = readl(sel);
+
+		if (rc & BIT(i))
+			reg |= mask;
+		else
+			reg &= ~mask;
+
+		writel(reg, sel);
+		if (readl(sel) != reg)
+			return -EACCES;
+	}
+
+	dev_dbg(intc1->dev,
+		"Routed hwirq %lu in domain %s to output %u via route %d\n",
+		data->hwirq, domain->name, resolved.start, rc);
+
+	return 0;
+}
+
+static const struct irq_domain_ops aspeed_intc1_ic_irq_domain_ops = {
+	.map = aspeed_intc1_ic_map_irq_domain,
+	.translate = aspeed_intc1_irq_domain_translate,
+	.activate = aspeed_intc1_irq_domain_activate,
+};
+
+static void aspeed_intc1_request_interrupts(struct aspeed_intc1 *intc1)
+{
+	unsigned int i;
+
+	for (i = 0; i < intc1->ranges.nranges; i++) {
+		struct aspeed_intc_interrupt_range *r =
+			&intc1->ranges.ranges[i];
+
+		if (intc1->upstream !=
+		    irq_find_matching_fwspec(&r->upstream,
+					     intc1->upstream->bus_token))
+			continue;
+
+		for (u32 k = 0; k < r->count; k++) {
+			struct of_phandle_args parent_irq;
+			int irq;
+
+			parent_irq.np = to_of_node(r->upstream.fwnode);
+			parent_irq.args_count = 1;
+			parent_irq.args[0] =
+				intc1->ranges.ranges[i].upstream.param[0] + k;
+
+			irq = irq_create_of_mapping(&parent_irq);
+			if (!irq)
+				continue;
+
+			irq_set_chained_handler_and_data(irq,
+							 aspeed_intc1_ic_irq_handler, intc1);
+			dev_dbg(intc1->dev, "Mapped irq %d\n", parent_irq.args[0]);
+		}
+	}
+}
+
+static int aspeed_intc1_ic_probe(struct platform_device *pdev,
+				 struct device_node *parent)
+{
+	struct device_node *node = pdev->dev.of_node;
+	struct aspeed_intc1 *intc1;
+	struct irq_domain *host;
+	int ret;
+
+	if (!parent) {
+		dev_err(&pdev->dev, "missing parent interrupt node\n");
+		return -ENODEV;
+	}
+
+	if (!of_device_is_compatible(parent, "aspeed,ast2700-intc0-ic"))
+		return -ENODEV;
+
+	host = irq_find_host(parent);
+	if (!host)
+		return -ENODEV;
+
+	intc1 = devm_kzalloc(&pdev->dev, sizeof(*intc1), GFP_KERNEL);
+	if (!intc1)
+		return -ENOMEM;
+
+	intc1->dev = &pdev->dev;
+	intc1->upstream = host;
+	intc1->base = devm_platform_ioremap_resource(pdev, 0);
+	if (IS_ERR(intc1->base))
+		return PTR_ERR(intc1->base);
+
+	aspeed_intc1_disable_int(intc1);
+
+	raw_spin_lock_init(&intc1->intc_lock);
+
+	intc1->local = irq_domain_create_linear(of_fwnode_handle(node),
+						INTC1_BANK_NUM * INTC1_IRQS_PER_BANK,
+						&aspeed_intc1_ic_irq_domain_ops, intc1);
+	if (!intc1->local)
+		return -ENOMEM;
+
+	ret = aspeed_intc_populate_ranges(&pdev->dev, &intc1->ranges);
+	if (ret < 0) {
+		irq_domain_remove(intc1->local);
+		return ret;
+	}
+
+	aspeed_intc1_request_interrupts(intc1);
+
+	return 0;
+}
+
+IRQCHIP_PLATFORM_DRIVER_BEGIN(ast2700_intc1)
+IRQCHIP_MATCH("aspeed,ast2700-intc1-ic", aspeed_intc1_ic_probe)
+IRQCHIP_PLATFORM_DRIVER_END(ast2700_intc1)
diff --git a/drivers/irqchip/irq-ast2700.c b/drivers/irqchip/irq-ast2700.c
new file mode 100644
index 000000000000..269d743207ae
--- /dev/null
+++ b/drivers/irqchip/irq-ast2700.c
@@ -0,0 +1,105 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ *  Aspeed Interrupt Controller.
+ *
+ *  Copyright (C) 2023 ASPEED Technology Inc.
+ */
+#include "irq-ast2700.h"
+
+#include <linux/dev_printk.h>
+#include <linux/device/devres.h>
+
+int aspeed_intc_populate_ranges(struct device *dev,
+				struct aspeed_intc_interrupt_ranges *ranges)
+{
+	struct aspeed_intc_interrupt_range *arr;
+	const __be32 *pvs, *pve;
+	struct device_node *dn;
+	int len;
+
+	if (!dev || !ranges)
+		return -EINVAL;
+
+	dn = dev->of_node;
+
+	pvs = of_get_property(dn, "aspeed,interrupt-ranges", &len);
+	if (!pvs)
+		return -EINVAL;
+
+	if (len % sizeof(__be32))
+		return -EINVAL;
+
+	/* Over-estimate the range entry count for now */
+	ranges->ranges = devm_kmalloc_array(dev, (len / (3 * sizeof(__be32))),
+					    sizeof(*ranges->ranges),
+					    GFP_KERNEL);
+	if (!ranges->ranges)
+		return -ENOMEM;
+
+	pve = pvs + (len / sizeof(__be32));
+	for (unsigned int i = 0; pve - pvs >= 3; i++) {
+		struct aspeed_intc_interrupt_range *r;
+		struct device_node *target;
+		u32 target_cells;
+
+		target = of_find_node_by_phandle(be32_to_cpu(pvs[2]));
+		if (!target)
+			return -EINVAL;
+
+		if (of_property_read_u32(target, "#interrupt-cells",
+					 &target_cells)) {
+			of_node_put(target);
+			return -EINVAL;
+		}
+
+		if (!target_cells || target_cells > IRQ_DOMAIN_IRQ_SPEC_PARAMS) {
+			of_node_put(target);
+			return -EINVAL;
+		}
+
+		if (pve - pvs < 3 + target_cells) {
+			of_node_put(target);
+			return -EINVAL;
+		}
+
+		r = &ranges->ranges[i];
+		r->start = be32_to_cpu(pvs[0]);
+		r->count = be32_to_cpu(pvs[1]);
+
+		{
+			struct of_phandle_args args = {
+				.np = target,
+				.args_count = target_cells,
+			};
+
+			for (u32 j = 0; j < target_cells; j++)
+				args.args[j] = be32_to_cpu(pvs[3 + j]);
+
+			of_phandle_args_to_fwspec(target, args.args,
+						  args.args_count,
+						  &r->upstream);
+		}
+
+		if (target_cells >= 1)
+			dev_dbg(dev,
+				"Mapped %u outputs from %u to %u on parent %s",
+				r->count, r->start, r->upstream.param[0], target->full_name);
+		else
+			dev_dbg(dev,
+				"Registered interrupt range from %u for count %u\n",
+				r->start, r->count);
+
+		of_node_put(target);
+		pvs += 3 + target_cells;
+		ranges->nranges++;
+	}
+
+	/* Re-fit the range array now we know the entry count */
+	arr = devm_krealloc_array(dev, ranges->ranges, ranges->nranges,
+				  sizeof(*ranges->ranges), GFP_KERNEL);
+	if (!arr)
+		return -ENOMEM;
+	ranges->ranges = arr;
+
+	return 0;
+}
diff --git a/drivers/irqchip/irq-ast2700.h b/drivers/irqchip/irq-ast2700.h
new file mode 100644
index 000000000000..56dbb1c88b11
--- /dev/null
+++ b/drivers/irqchip/irq-ast2700.h
@@ -0,0 +1,37 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ *  Aspeed AST2700 Interrupt Controller.
+ *
+ *  Copyright (C) 2023 ASPEED Technology Inc.
+ */
+#ifndef DRIVERS_IRQCHIP_AST2700
+#define DRIVERS_IRQCHIP_AST2700
+
+#include <linux/device.h>
+#include <linux/irqdomain.h>
+
+#define AST2700_INTC_INVALID_ROUTE (~0U)
+
+struct aspeed_intc_interrupt_range {
+	u32 start;
+	u32 count;
+	struct irq_fwspec upstream;
+};
+
+struct aspeed_intc_interrupt_ranges {
+	struct aspeed_intc_interrupt_range *ranges;
+	unsigned int nranges;
+};
+
+int aspeed_intc_populate_ranges(struct device *dev,
+				struct aspeed_intc_interrupt_ranges *ranges);
+
+typedef u32 aspeed_intc_output_t;
+
+int aspeed_intc0_resolve_route(
+	const struct irq_domain *c0domain, size_t nc1outs,
+	const aspeed_intc_output_t c1outs[static nc1outs], size_t nc1ranges,
+	const struct aspeed_intc_interrupt_range c1ranges[static nc1ranges],
+	struct aspeed_intc_interrupt_range *resolved);
+
+#endif

-- 
2.34.1


Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ