From nobody Sun Feb 8 06:22:42 2026 Received: from TWMBX01.aspeed.com (mail.aspeedtech.com [211.20.114.72]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id A33E9334695; Thu, 5 Feb 2026 06:07:39 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=211.20.114.72 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1770271660; cv=none; b=iEsS4hbf/6Ufxc1uKz+57NPO82eqaoHU+2xEEGDmVAfAgkRyooG9h+vHZG8/xj5RQQdy5DVVAzL79S/pERIU/9ebjJebK7b7fvMudwhtA42fn/632c+jA3wnUSWY7ye1MDVvg3NkldWXTPUwo/YANbJ0E5YJiCJaQIG7tWpE8rs= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1770271660; c=relaxed/simple; bh=0fuUkS4W8giyVzoGhPYy+wRAWNG8NtfTogigJsfUK9c=; h=From:Date:Subject:MIME-Version:Content-Type:Message-ID:References: In-Reply-To:To:CC; b=G/Mo1D/BG+n0bWbfe1W7WlOI/uZ/Aq3+D6/4IC/0yGaJ+Hf4sBefKyhbqRpK1tfvdm3IbWvUsRwhYjcsplgyo7mhaX46xWQE1UmeTMV29Y1W9B4CjFodzg85IkcJ0LalFO0mO4wFQ2hTdfjLR9xbb0dGyyTv6xayrL4K7jdAh8k= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=aspeedtech.com; spf=pass smtp.mailfrom=aspeedtech.com; arc=none smtp.client-ip=211.20.114.72 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=aspeedtech.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=aspeedtech.com Received: from TWMBX01.aspeed.com (192.168.0.62) by TWMBX01.aspeed.com (192.168.0.62) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.2.1748.10; Thu, 5 Feb 2026 14:07:30 +0800 Received: from [127.0.1.1] (192.168.10.13) by TWMBX01.aspeed.com (192.168.0.62) with Microsoft SMTP Server id 15.2.1748.10 via Frontend Transport; Thu, 5 Feb 2026 14:07:30 +0800 From: Ryan Chen Date: Thu, 5 Feb 2026 14:07:19 +0800 Subject: [PATCH 1/4] dt-bindings: interrupt-controller: aspeed: Add ASPEED AST2700 INTC0/INTC1 Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-ID: <20260205-irqchip-v1-1-b0310e06c087@aspeedtech.com> References: <20260205-irqchip-v1-0-b0310e06c087@aspeedtech.com> In-Reply-To: <20260205-irqchip-v1-0-b0310e06c087@aspeedtech.com> To: Thomas Gleixner , Rob Herring , Krzysztof Kozlowski , Conor Dooley , Joel Stanley , Andrew Jeffery , Paul Walmsley , Palmer Dabbelt , "Albert Ou" , Alexandre Ghiti CC: , , , , , Ryan Chen X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=ed25519-sha256; t=1770271649; l=8810; i=ryan_chen@aspeedtech.com; s=20251126; h=from:subject:message-id; bh=0fuUkS4W8giyVzoGhPYy+wRAWNG8NtfTogigJsfUK9c=; b=3IwrnqYWYgIrMWXMHNIWcw656dwRDf0J5V3NaL1guqyVO7NWgFMdeAqEnTcbhy5nNjxwyLEPM bqGGDrDj1qIAp/Ql18MaWlvlo351E46K+ELVATkc/0jJyCZYQpaS6wM X-Developer-Key: i=ryan_chen@aspeedtech.com; a=ed25519; pk=Xe73xY6tcnkuRjjbVAB/oU30KdB3FvG4nuJuILj7ZVc= INTC0 is used to assert GIC if interrupt in INTC1 asserted. INTC1 is used to assert INTC0 if interrupt of modules asserted. Signed-off-by: Ryan Chen --- .../aspeed,ast2700-interrupt.yaml | 207 +++++++++++++++++= ++++ 1 file changed, 207 insertions(+) diff --git a/Documentation/devicetree/bindings/interrupt-controller/aspeed,= ast2700-interrupt.yaml b/Documentation/devicetree/bindings/interrupt-contro= ller/aspeed,ast2700-interrupt.yaml new file mode 100644 index 000000000000..8a27e1e667a1 --- /dev/null +++ b/Documentation/devicetree/bindings/interrupt-controller/aspeed,ast2700= -interrupt.yaml @@ -0,0 +1,207 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/interrupt-controller/aspeed,ast2700-int= errupt.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: ASPEED AST2700 Interrupt Controllers (INTC0/INTC1) + +description: | + The ASPEED AST2700 SoC integrates two interrupt controller designs: + + - INTC0: Primary controller that routes interrupt sources to upstream, + processor-specific interrupt controllers + + - INTC1: Secondary controller whose interrupt outputs feed into + INTC0 + + The SoC contains four processors to which interrupts can be routed: + + - PSP: Primary Service Processor (Cortex-A35) + - SSP: Secondary Service Processor (Cortex-M4) + - TSP: Tertiary Sevice Processor (Cortex-M4) + - BMCU: Boot MCU (a RISC-V microcontroller) + + The following diagram illustrates the overall architecture of the + ASPEED AST2700 interrupt controllers: + + +-----------+ +-----------+ + | INTC0 | | INTC1(0)| + +-----------+ +-----------+ + | Router | +-------+ | Router | + | out int | + SOC0 + | out int | + +-----------+ | 0 0 <---+ INTx + | INTM | +-------+ + |PSP GIC <-|---+ . . | +Modules+ | . . <-----+ SOC1 + + +-----------+ | . . | +-------+ | . . | + INTx + + +-----------+ | . . | | . . | +Modules+ + |SSP NVIC <-|---+ . . <----------------+ . . | +-------+ + +-----------+ | . . | | . . | + +-----------+ | . . <-------- | . . | + |TSP NVIC <-|---+ . . | | ----+ . . | + +-----------+ | . . | | | | O P | + | . . | | | +-----------+ + | . . <---- | -------------------- + | . . | | | +-----------+ | + | M N | | ---------+ INTC1(1) | | + +-----------+ | +-----------+ | + | . | + | +-----------+ | + -------------+ INTC1(N) | | + +-----------+ | + +--------------+ | + + BMCU APLIC <-+--------------------------------------------- + +--------------+ + + INTC0 supports: + - 128 local peripheral interrupt inputs + - Fan-in from up to three INTC1 instances via banked interrupt lines (= INTM) + - Local peripheral interrupt outputs + - Merged interrupt outputs + - Software interrupt outputs (SWINT) + - Configurable interrupt routes targeting the PSP, SSP, and TSP + + INTC1 supports: + - 192 local peripheral interrupt inputs + - Banked interrupt outputs (INTM, 5 x 6 banks x 32 interrupts per bank) + - Configurable interrupt routes targeting the PSP, SSP, TSP, and BMCU + + One INTC1 instance is always present, on the SoC's IO die. A further two + instances may be attached to the SoC's one INTC0 instance via LTPI (LVDS + Tunneling Protocol & Interface). + + Interrupt numbering model + ------------------------- + The binding uses a controller-local numbering model. Peripheral device + nodes use the INTCx local interrupt number (hwirq) in their 'interrupts'= or + 'interrupts-extended' properties. + + For AST2700, INTC0 exposes the following (inclusive) input ranges: + + - 000..479: Independent interrupts + - 480..489: INTM0-INTM9 + - 490..499: INTM10-INTM19 + - 500..509: INTM20-INTM29 + - 510..519: INTM30-INTM39 + - 520..529: INTM40-INTM49 + + INTC0's (inclusive) output ranges are as follows: + + - 000..127: 1:1 local peripheral interrupt output to PSP + - 144..151: Software interrupts from the SSP output to PSP + - 152..159: Software interrupts from the TSP output to PSP + - 192..201: INTM0-INTM9 banked outputs to PSP + - 208..217: INTM30-INTM39 banked outputs to PSP + - 224..233: INTM40-INTM49 banked outputs to PSP + - 256..383: 1:1 local peripheral interrupt output to SSP + - 384..393: INTM10-INTM19 banked outputs to SSP + - 400..407: Software interrupts from the PSP output to SSP + - 408..415: Software interrupts from the TSP output to SSP + - 426..553: 1:1 local peripheral interrupt output to TSP + - 554..563: INTM20-INTM29 banked outputs to TSP + - 570..577: Software interrupts from the PSP output to TSP + - 578..585: Software interrupts from the SSP output to TSP + + Inputs and outputs for INTC1 instances are context-dependent. However, f= or the + first instance of INTC1, the (inclusive) output ranges are: + + - 00..05: INTM0-INTM5 + - 10..15: INTM10-INTM15 + - 20..25: INTM20-INTM25 + - 30..35: INTM30-INTM35 + - 40..45: INTM40-INTM45 + - 50..50: BootMCU + +maintainers: + - ryan_chen@aspeedtech.com + - andrew@codeconstruct.com.au + +properties: + compatible: + enum: + - aspeed,ast2700-intc0 + - aspeed,ast2700-intc1 + + reg: + maxItems: 1 + + interrupt-controller: true + + '#interrupt-cells': + const: 1 + description: | + Single cell encoding the INTC local interrupt number (hwirq). + + aspeed,interrupt-ranges: + description: | + Describes how ranges of controller output pins are routed to a parent + interrupt controller. + + Each range entry is encoded as: + + + + where: + - out: First controller interrupt output index in the range. + - count: Number of consecutive controller interrupt outputs and = parent + interrupt inputs in this range. + - phandle: Phandle to the parent interrupt controller node. + - parent-specifier: Interrupt specifier, as defined by the parent + interrupt controller binding. + $ref: /schemas/types.yaml#/definitions/uint32-array + minItems: 3 + items: + description: Range descriptors with a parent interrupt specifier. + +required: + - compatible + - reg + - interrupt-controller + - '#interrupt-cells' + - aspeed,interrupt-ranges + +additionalProperties: false + +examples: + - | + #include + + intc0: interrupt-controller@12100000 { + compatible =3D "aspeed,ast2700-intc0-ic"; + reg =3D <0x12100000 0x3b00>; + interrupt-parent =3D <&gic>; + interrupt-controller; + #interrupt-cells =3D <1>; + + aspeed,interrupt-ranges =3D + <0 128 &gic GIC_SPI 0 IRQ_TYPE_LEVEL_HIGH>, + <144 8 &gic GIC_SPI 144 IRQ_TYPE_LEVEL_HIGH>, + <152 8 &gic GIC_SPI 152 IRQ_TYPE_LEVEL_HIGH>, + <192 10 &gic GIC_SPI 192 IRQ_TYPE_LEVEL_HIGH>, + <208 10 &gic GIC_SPI 208 IRQ_TYPE_LEVEL_HIGH>, + <224 10 &gic GIC_SPI 224 IRQ_TYPE_LEVEL_HIGH>, + <256 128 &ssp_nvic 0 0 >, + <384 10 &ssp_nvic 160 0 >, + <400 8 &ssp_nvic 144 0 >, + <408 8 &ssp_nvic 152 0 >, + <426 128 &tsp_nvic 0 0 >, + <554 10 &tsp_nvic 160 0 >, + <570 8 &tsp_nvic 144 0 >, + <578 8 &tsp_nvic 152 0 >; + }; + + - | + intc1: interrupt-controller@14c18000 { + compatible =3D "aspeed,ast2700-intc1-ic"; + reg =3D <0x14c18000 0x400>; + interrupt-parent =3D <&intc0>; + interrupt-controller; + #interrupt-cells =3D <1>; + + aspeed,interrupt-ranges =3D + <0 6 &intc0 480>, + <10 6 &intc0 490>, + <20 6 &intc0 500>, + <30 6 &intc0 510>, + <40 6 &intc0 520>, + <50 1 &bootmcu_plic 0>; + }; --=20 2.34.1 From nobody Sun Feb 8 06:22:42 2026 Received: from TWMBX01.aspeed.com (mail.aspeedtech.com [211.20.114.72]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 9BFF4334C34; Thu, 5 Feb 2026 06:07:40 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=211.20.114.72 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1770271661; cv=none; b=VIUNDnhbW9uo/6FugNonNyJZzgLHowIHMd0lxyr/SmNoOB1ChwJPdCQgU/XfRp5xAIhC/PztNuxII3gD6FJLbZx+sYlY4cYzk9ch0JCV8rsHO6DoldlzJF0cuGNleyRugQ6XyU+fUT63sAnVwyXfAYtBhKVI/tJTe0nZI5TZiUw= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1770271661; c=relaxed/simple; bh=Y1PlQKvcgT3RIgs+8Ja6zwrgkj1JB+npFm1kMrOX7KQ=; h=From:Date:Subject:MIME-Version:Content-Type:Message-ID:References: In-Reply-To:To:CC; b=Q1E1+UWNUWlRVtvhtRgSaMH1vA6AVZusSKgfOD2L8RR1J9+h5AyxBr8vZWo1ddcCyCjBo/gv736gQDUlJcK5+DLZXu/QyBY9u5kiNOo6WY8waYS1wLz/JXd6kp0T9pLJdAHnLFBo1bJsS6UsGuUnkTQ2TRLugS9+hZeWYtQqZVw= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=aspeedtech.com; spf=pass smtp.mailfrom=aspeedtech.com; arc=none smtp.client-ip=211.20.114.72 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=aspeedtech.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=aspeedtech.com Received: from TWMBX01.aspeed.com (192.168.0.62) by TWMBX01.aspeed.com (192.168.0.62) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.2.1748.10; Thu, 5 Feb 2026 14:07:30 +0800 Received: from [127.0.1.1] (192.168.10.13) by TWMBX01.aspeed.com (192.168.0.62) with Microsoft SMTP Server id 15.2.1748.10 via Frontend Transport; Thu, 5 Feb 2026 14:07:30 +0800 From: Ryan Chen Date: Thu, 5 Feb 2026 14:07:20 +0800 Subject: [PATCH 2/4] irqchip/ast2700-intcx: Add AST2700 INTC0/INTC1 support Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-ID: <20260205-irqchip-v1-2-b0310e06c087@aspeedtech.com> References: <20260205-irqchip-v1-0-b0310e06c087@aspeedtech.com> In-Reply-To: <20260205-irqchip-v1-0-b0310e06c087@aspeedtech.com> To: Thomas Gleixner , Rob Herring , Krzysztof Kozlowski , Conor Dooley , Joel Stanley , Andrew Jeffery , Paul Walmsley , Palmer Dabbelt , "Albert Ou" , Alexandre Ghiti CC: , , , , , Ryan Chen X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=ed25519-sha256; t=1770271649; l=50884; i=ryan_chen@aspeedtech.com; s=20251126; h=from:subject:message-id; bh=Y1PlQKvcgT3RIgs+8Ja6zwrgkj1JB+npFm1kMrOX7KQ=; b=VQM7mi3jHh25LNWdvnrcXf3iSrd43hA9a2bHVfEulpxscLt4iAx9eJNV4Nh72rUxcoXgpgGmw g1E40SZemYcDm8oV3AXmhf6Tr7xBX3gsS4wDO5UMcpBq3IvnAcsSvKB X-Developer-Key: i=ryan_chen@aspeedtech.com; a=ed25519; pk=Xe73xY6tcnkuRjjbVAB/oU30KdB3FvG4nuJuILj7ZVc= 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 --- 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. =20 +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=3Dy + 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) +=3D irq-mvebu-pic.o obj-$(CONFIG_MVEBU_SEI) +=3D irq-mvebu-sei.o obj-$(CONFIG_LS_EXTIRQ) +=3D irq-ls-extirq.o obj-$(CONFIG_LS_SCFG_MSI) +=3D irq-ls-scfg-msi.o +obj-$(CONFIG_ASPEED_AST2700_INTC) +=3D irq-ast2700.o irq-ast2700-intc0.o i= rq-ast2700-intc1.o obj-$(CONFIG_ARCH_ASPEED) +=3D irq-aspeed-vic.o irq-aspeed-i2c-ic.o irq-a= speed-scu-ic.o obj-$(CONFIG_ARCH_ASPEED) +=3D irq-aspeed-intc.o obj-$(CONFIG_STM32MP_EXTI) +=3D 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 +#include "irq-ast2700.h" + +static void aspeed_intc0_resolve_route_bad_args(struct kunit *test) +{ + static const struct aspeed_intc_interrupt_range c1ranges[] =3D { 0 }; + static const aspeed_intc_output_t c1outs[] =3D { 0 }; + struct aspeed_intc_interrupt_range resolved; + const struct irq_domain c0domain =3D { 0 }; + + int rc; + + rc =3D aspeed_intc0_resolve_route(NULL, 0, c1outs, 0, c1ranges, NULL); + KUNIT_EXPECT_EQ(test, rc, -EINVAL); + + rc =3D aspeed_intc0_resolve_route(&c0domain, 0, c1outs, + ARRAY_SIZE(c1ranges), c1ranges, + &resolved); + KUNIT_EXPECT_EQ(test, rc, -ENODEV); + + rc =3D 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_hand= le, + const char *propname, const char **val, + size_t nval) +{ + if (!propname) + return -EINVAL; + + if (!val) + return 1; + + if (WARN_ON(nval !=3D 1)) + return -EOVERFLOW; + + *val =3D "arm,gic-v3"; + return 1; +} + +static const struct fwnode_operations arm_gicv3_fwnode_ops =3D { + .property_read_string_array =3D arm_gicv3_fwnode_read_string_array, +}; + +static void aspeed_intc_resolve_route_invalid_c0domain(struct kunit *test) +{ + struct device_node intc0_node =3D { + .fwnode =3D { .ops =3D &arm_gicv3_fwnode_ops }, + }; + const struct irq_domain c0domain =3D { .fwnode =3D &intc0_node.fwnode }; + static const struct aspeed_intc_interrupt_range c1ranges[] =3D { 0 }; + static const aspeed_intc_output_t c1outs[] =3D { 0 }; + struct aspeed_intc_interrupt_range resolved; + int rc; + + rc =3D 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_h= andle, + const char *propname, const char **val, + size_t nval) +{ + if (!propname) + return -EINVAL; + + if (!val) + return 1; + + if (WARN_ON(nval !=3D 1)) + return -EOVERFLOW; + + *val =3D "aspeed,ast2700-intc0-ic"; + return nval; +} + +static const struct fwnode_operations intc0_fwnode_ops =3D { + .property_read_string_array =3D aspeed_intc0_fwnode_read_string_array, +}; + +static void +aspeed_intc0_resolve_route_c1i1o1c0i1o1_connected(struct kunit *test) +{ + struct device_node intc0_node =3D { + .fwnode =3D { .ops =3D &intc0_fwnode_ops }, + }; + struct aspeed_intc_interrupt_range c1ranges[] =3D { + { + .start =3D 0, + .count =3D 1, + .upstream =3D { + .fwnode =3D &intc0_node.fwnode, + .param_count =3D 1, + .param =3D { 128 } + } + } + }; + static const aspeed_intc_output_t c1outs[] =3D { 0 }; + struct aspeed_intc_interrupt_range resolved; + struct aspeed_intc_interrupt_range intc0_ranges[] =3D { + { + .start =3D 128, + .count =3D 1, + .upstream =3D { + .fwnode =3D NULL, + .param_count =3D 0, + .param =3D { 0 }, + } + } + }; + struct aspeed_intc0 intc0 =3D { + .ranges =3D { .ranges =3D intc0_ranges, .nranges =3D ARRAY_SIZE(intc0_ra= nges), } + }; + const struct irq_domain c0domain =3D { + .host_data =3D &intc0, + .fwnode =3D &intc0_node.fwnode + }; + int rc; + + rc =3D 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 =3D { + .fwnode =3D { .ops =3D &intc0_fwnode_ops }, + }; + struct aspeed_intc_interrupt_range c1ranges[] =3D { + { + .start =3D 0, + .count =3D 1, + .upstream =3D { + .fwnode =3D &intc0_node.fwnode, + .param_count =3D 1, + .param =3D { 128 } + } + } + }; + static const aspeed_intc_output_t c1outs[] =3D { 0 }; + struct aspeed_intc_interrupt_range resolved; + struct aspeed_intc_interrupt_range intc0_ranges[] =3D { + { + .start =3D 129, + .count =3D 1, + .upstream =3D { + .fwnode =3D NULL, + .param_count =3D 0, + .param =3D { 0 }, + } + } + }; + struct aspeed_intc0 intc0 =3D { + .ranges =3D { + .ranges =3D intc0_ranges, + .nranges =3D ARRAY_SIZE(intc0_ranges), + } + }; + const struct irq_domain c0domain =3D { + .host_data =3D &intc0, + .fwnode =3D &intc0_node.fwnode + }; + int rc; + + rc =3D 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 =3D { + .fwnode =3D { .ops =3D &intc0_fwnode_ops }, + }; + struct aspeed_intc_interrupt_range c1ranges[] =3D { + { + .start =3D 0, + .count =3D 1, + .upstream =3D { + .fwnode =3D &intc0_node.fwnode, + .param_count =3D 1, + .param =3D { 480 } + } + } + }; + static const aspeed_intc_output_t c1outs[] =3D { 0 }; + struct aspeed_intc_interrupt_range resolved; + struct aspeed_intc_interrupt_range intc0_ranges[] =3D { + { + .start =3D 192, + .count =3D 1, + .upstream =3D { + .fwnode =3D NULL, + .param_count =3D 0, + .param =3D { 0 }, + } + } + }; + struct aspeed_intc0 intc0 =3D { + .ranges =3D { + .ranges =3D intc0_ranges, + .nranges =3D ARRAY_SIZE(intc0_ranges), + } + }; + const struct irq_domain c0domain =3D { + .host_data =3D &intc0, + .fwnode =3D &intc0_node.fwnode + }; + int rc; + + rc =3D 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 =3D { + .fwnode =3D { .ops =3D &intc0_fwnode_ops }, + }; + struct aspeed_intc_interrupt_range c1ranges[] =3D { + { + .start =3D 0, + .count =3D 1, + .upstream =3D { + .fwnode =3D &intc0_node.fwnode, + .param_count =3D 1, + .param =3D { 480 } + } + }, + { + .start =3D 1, + .count =3D 1, + .upstream =3D { + .fwnode =3D &intc0_node.fwnode, + .param_count =3D 1, + .param =3D { 510 } + } + } + }; + static const aspeed_intc_output_t c1outs[] =3D { 1 }; + struct aspeed_intc_interrupt_range resolved; + struct aspeed_intc_interrupt_range intc0_ranges[] =3D { + { + .start =3D 208, + .count =3D 1, + .upstream =3D { + .fwnode =3D NULL, + .param_count =3D 0, + .param =3D { 0 }, + } + } + }; + struct aspeed_intc0 intc0 =3D { + .ranges =3D { + .ranges =3D intc0_ranges, + .nranges =3D ARRAY_SIZE(intc0_ranges), + } + }; + const struct irq_domain c0domain =3D { + .host_data =3D &intc0, + .fwnode =3D &intc0_node.fwnode + }; + int rc; + + rc =3D 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 =3D { + .fwnode =3D { .ops =3D &intc0_fwnode_ops }, + }; + struct aspeed_intc_interrupt_range c1ranges[] =3D { + { + .start =3D 0, + .count =3D 1, + .upstream =3D { + .fwnode =3D &intc0_node.fwnode, + .param_count =3D 1, + .param =3D { 510 } + } + }, + }; + static const aspeed_intc_output_t c1outs[] =3D { 0 }; + struct aspeed_intc_interrupt_range resolved; + struct aspeed_intc_interrupt_range intc0_ranges[] =3D { + { + .start =3D 192, + .count =3D 1, + .upstream =3D { + .fwnode =3D NULL, + .param_count =3D 0, + .param =3D {0}, + } + }, + { + .start =3D 208, + .count =3D 1, + .upstream =3D { + .fwnode =3D NULL, + .param_count =3D 0, + .param =3D {0}, + } + } + }; + struct aspeed_intc0 intc0 =3D { + .ranges =3D { + .ranges =3D intc0_ranges, + .nranges =3D ARRAY_SIZE(intc0_ranges), + } + }; + const struct irq_domain c0domain =3D { + .host_data =3D &intc0, + .fwnode =3D &intc0_node.fwnode + }; + int rc; + + rc =3D 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 =3D { + .fwnode =3D { .ops =3D &intc0_fwnode_ops }, + }; + struct aspeed_intc_interrupt_range c1ranges[] =3D { + { + .start =3D 0, + .count =3D 1, + .upstream =3D { + .fwnode =3D &intc0_node.fwnode, + .param_count =3D 1, + .param =3D { 480 } + } + } + }; + static const aspeed_intc_output_t c1outs[] =3D { + AST2700_INTC_INVALID_ROUTE, 0 + }; + struct aspeed_intc_interrupt_range resolved; + struct aspeed_intc_interrupt_range intc0_ranges[] =3D { + { + .start =3D 192, + .count =3D 1, + .upstream =3D { + .fwnode =3D NULL, + .param_count =3D 0, + .param =3D { 0 }, + } + } + }; + struct aspeed_intc0 intc0 =3D { + .ranges =3D { + .ranges =3D intc0_ranges, + .nranges =3D ARRAY_SIZE(intc0_ranges), + } + }; + const struct irq_domain c0domain =3D { + .host_data =3D &intc0, + .fwnode =3D &intc0_node.fwnode + }; + int rc; + + rc =3D 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 =3D { + .fwnode =3D { .ops =3D &intc0_fwnode_ops }, + }; + struct aspeed_intc_interrupt_range c1ranges[] =3D { + { + .start =3D 0, + .count =3D 1, + .upstream =3D { + .fwnode =3D &intc0_node.fwnode, + .param_count =3D 0, + .param =3D { 0 } + } + } + }; + static const aspeed_intc_output_t c1outs[] =3D { 0 }; + struct aspeed_intc_interrupt_range resolved; + struct aspeed_intc_interrupt_range intc0_ranges[] =3D { + { + .start =3D 0, + .count =3D 0, + .upstream =3D { + .fwnode =3D NULL, + .param_count =3D 0, + .param =3D { 0 }, + } + } + }; + struct aspeed_intc0 intc0 =3D { + .ranges =3D { + .ranges =3D intc0_ranges, + .nranges =3D ARRAY_SIZE(intc0_ranges), + } + }; + const struct irq_domain c0domain =3D { + .host_data =3D &intc0, + .fwnode =3D &intc0_node.fwnode + }; + int rc; + + rc =3D 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[] =3D { + 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 =3D { + .name =3D "ast2700-intc0", + .test_cases =3D 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-ast2= 700-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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#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 =3D irq_data_get_irq_chip_data(data); + int bit =3D data->hwirq - SWINT_BASE; + unsigned int mask; + + guard(raw_spinlock_irqsave)(&intc0->intc_lock); + mask =3D 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 =3D irq_data_get_irq_chip_data(data); + int bit =3D data->hwirq - SWINT_BASE; + unsigned int unmask; + + guard(raw_spinlock_irqsave)(&intc0->intc_lock); + unmask =3D 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 =3D irq_data_get_irq_chip_data(data); + int bit =3D data->hwirq - SWINT_BASE; + + writel(BIT(bit), intc0->base + INTC0_SWINT_ISR); + irq_chip_eoi_parent(data); +} + +static struct irq_chip aspeed_swint_chip =3D { + .name =3D "ast2700-swint", + .irq_eoi =3D aspeed_swint_irq_eoi, + .irq_mask =3D aspeed_swint_irq_mask, + .irq_unmask =3D aspeed_swint_irq_unmask, + .irq_set_affinity =3D irq_chip_set_affinity_parent, + .flags =3D IRQCHIP_SET_TYPE_MASKED, +}; + +static void aspeed_intc0_irq_mask(struct irq_data *data) +{ + struct aspeed_intc0 *intc0 =3D irq_data_get_irq_chip_data(data); + int bank =3D (data->hwirq - INTM_BASE) / INTM_IRQS_PER_BANK; + int bit =3D (data->hwirq - INTM_BASE) % INTM_IRQS_PER_BANK; + unsigned int mask; + + guard(raw_spinlock_irqsave)(&intc0->intc_lock); + mask =3D 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 =3D irq_data_get_irq_chip_data(data); + int bank =3D (data->hwirq - INTM_BASE) / INTM_IRQS_PER_BANK; + int bit =3D (data->hwirq - INTM_BASE) % INTM_IRQS_PER_BANK; + unsigned int unmask; + + guard(raw_spinlock_irqsave)(&intc0->intc_lock); + unmask =3D 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 =3D irq_data_get_irq_chip_data(data); + int bank =3D (data->hwirq - INTM_BASE) / INTM_IRQS_PER_BANK; + int bit =3D (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 =3D { + .name =3D "ast2700-intmerge", + .irq_eoi =3D aspeed_intc0_irq_eoi, + .irq_mask =3D aspeed_intc0_irq_mask, + .irq_unmask =3D aspeed_intc0_irq_unmask, + .irq_set_affinity =3D irq_chip_set_affinity_parent, + .flags =3D IRQCHIP_SET_TYPE_MASKED, +}; + +static struct irq_chip linear_intr_irq_chip =3D { + .name =3D "ast2700-int", + .irq_eoi =3D irq_chip_eoi_parent, + .irq_mask =3D irq_chip_mask_parent, + .irq_unmask =3D irq_chip_unmask_parent, + .irq_set_affinity =3D irq_chip_set_affinity_parent, + .flags =3D 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][I= NTC0_ROUTE_NUM] =3D { + [0] =3D { + [0b000] =3D 0, + [0b001] =3D 256, + [0b010] =3D 426, + [0b011] =3D AST2700_INTC_INVALID_ROUTE, + [0b100] =3D AST2700_INTC_INVALID_ROUTE, + }, + [1] =3D { + [0b000] =3D 32, + [0b001] =3D 288, + [0b010] =3D 458, + [0b011] =3D AST2700_INTC_INVALID_ROUTE, + [0b100] =3D AST2700_INTC_INVALID_ROUTE, + }, + [2] =3D { + [0b000] =3D 64, + [0b001] =3D 320, + [0b010] =3D 490, + [0b011] =3D AST2700_INTC_INVALID_ROUTE, + [0b100] =3D AST2700_INTC_INVALID_ROUTE, + }, + [3] =3D { + [0b000] =3D 96, + [0b001] =3D 352, + [0b010] =3D 522, + [0b011] =3D AST2700_INTC_INVALID_ROUTE, + [0b100] =3D AST2700_INTC_INVALID_ROUTE, + }, + [4] =3D { + [0b000] =3D 128, + [0b001] =3D 384, + [0b010] =3D 554, + [0b011] =3D 160, + [0b100] =3D 176, + }, + [5] =3D { + [0b000] =3D 129, + [0b001] =3D 385, + [0b010] =3D 555, + [0b011] =3D 161, + [0b100] =3D 177, + }, + [6] =3D { + [0b000] =3D 130, + [0b001] =3D 386, + [0b010] =3D 556, + [0b011] =3D 162, + [0b100] =3D 178, + }, + [7] =3D { + [0b000] =3D 131, + [0b001] =3D 387, + [0b010] =3D 557, + [0b011] =3D 163, + [0b100] =3D 179, + }, + [8] =3D { + [0b000] =3D 132, + [0b001] =3D 388, + [0b010] =3D 558, + [0b011] =3D 164, + [0b100] =3D 180, + + }, + [9] =3D { + [0b000] =3D 133, + [0b001] =3D 544, + [0b010] =3D 714, + [0b011] =3D 165, + [0b100] =3D 181, + }, + [10] =3D { + [0b000] =3D 134, + [0b001] =3D 545, + [0b010] =3D 715, + [0b011] =3D 166, + [0b100] =3D 182, + }, + [11] =3D { + [0b000] =3D 135, + [0b001] =3D 546, + [0b010] =3D 706, + [0b011] =3D 167, + [0b100] =3D 183, + }, + [12] =3D { + [0b000] =3D 136, + [0b001] =3D 547, + [0b010] =3D 707, + [0b011] =3D 168, + [0b100] =3D 184, + + }, + [13] =3D { + [0b000] =3D 137, + [0b001] =3D 548, + [0b010] =3D 708, + [0b011] =3D 169, + [0b100] =3D 185, + + }, + [14] =3D { + [0b000] =3D 138, + [0b001] =3D 549, + [0b010] =3D 709, + [0b011] =3D 170, + [0b100] =3D 186, + }, +}; + +#define INTC0_INTM_NUM 50 + +static const aspeed_intc_output_t + aspeed_intc0_intm_routes[INTC0_INTM_NUM / 10] =3D { + [0] =3D 192, /* INTM00 ~ INTM09 */ + [1] =3D 416, /* INTM10 ~ INTM19 */ + [2] =3D 586, /* INTM20 ~ INTM29 */ + [3] =3D 208, /* INTM30 ~ INTM39 */ + [4] =3D 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 >=3D 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 =3D=3D 0) + return -EINVAL; + + base =3D range->upstream.param[0]; + offset =3D 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 =3D base + offset; + return 0; +} + +static bool resolve_parent_range_for_output(const struct aspeed_intc0 *int= c0, + const struct fwnode_handle *parent, + u32 output, + struct aspeed_intc_interrupt_range *resolved) +{ + for (size_t i =3D 0; i < intc0->ranges.nranges; i++) { + struct aspeed_intc_interrupt_range range =3D + 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 !=3D 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 =3D output; + resolved->count =3D 1; + resolved->upstream =3D range.upstream; + resolved->upstream.param[1] +=3D 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 =3D -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 =3D 0; rc =3D=3D -ENOENT && i < INTC0_ROUTE_NUM; i++) { + c0o =3D aspeed_intc0_routes[input / 32][i]; + if (c0o =3D=3D AST2700_INTC_INVALID_ROUTE) + continue; + + if (input < GIC_P2P_SPI_END) + c0o +=3D input % 32; + + found =3D resolve_parent_range_for_output(intc0, parent, c0o, resolved); + rc =3D 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 =3D aspeed_intc0_intm_routes[(input - INT_NUM) / INTM_IRQS_PER_BANK]; + c0o +=3D ((input - INT_NUM) % INTM_IRQS_PER_BANK); + + found =3D resolve_parent_range_for_output(intc0, parent, c0o, resolved); + rc =3D 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 =3D input - SWINT_BASE + 144; + found =3D resolve_parent_range_for_output(intc0, parent, c0o, resolved); + rc =3D 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 a= t 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 identif= ied in + * @resolved on success. Otherwise, a negative errno value. + * + * The AST2700 interrupt architecture allows any peripheral interrupt sour= ce + * to be routed to one of up to four processors running in the SoC. A proc= essor + * 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 instanc= es. + * 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 Protoc= ol + * & Interface). + * + * Between the multiple destinations, various route constraints, and the + * devicetree binding design, some information that's needed at INTC1 inst= ances + * to route inbound interrupts correctly to the destination processor is o= nly + * 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 provid= es it + * with an opportunity to apply policy as part of the selection process. S= uch + * policy may, for instance, choose to de-prioritise some interrupts desti= ned + * for the PSP (Primary Service Processor) GIC. + */ +int aspeed_intc0_resolve_route(const struct irq_domain *c0domain, size_t n= c1outs, + const aspeed_intc_output_t c1outs[static nc1outs], + size_t nc1ranges, + const struct aspeed_intc_interrupt_range c1ranges[static nc1rang= es], + 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 =3D=3D 0 || nc1ranges =3D=3D 0) + return -ENODEV; + + if (!fwnode_device_is_compatible(c0domain->fwnode, + "aspeed,ast2700-intc0-ic")) + return -ENODEV; + + intc0 =3D c0domain->host_data; + if (!intc0) + return -EINVAL; + + parent_fwnode =3D of_fwnode_handle(intc0->parent); + + for (size_t i =3D 0; i < nc1outs; i++) { + aspeed_intc_output_t c1o =3D c1outs[i]; + + if (c1o =3D=3D 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 =3D 0; j < nc1ranges; j++) { + struct aspeed_intc_interrupt_range c1r =3D 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 =3D=3D 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 =3D resolve_input_from_child_ranges(intc0, &c1r, c1o, &input); + if (ret) { + if (ret =3D=3D -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 =3D resolve_parent_route_for_input(intc0, parent_fwnode, input, NUL= L); + if (ret < 0) + continue; + + /* Route resolution succeeded */ + resolved->start =3D c1o; + resolved->count =3D 1; + resolved->upstream =3D c1r.upstream; + resolved->upstream.param[0] =3D 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 =3D -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 !=3D 1) + return -EINVAL; + + *hwirq =3D fwspec->param[0]; + *type =3D 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 =3D domain->host_data; + struct aspeed_intc_interrupt_range resolved; + struct irq_fwspec *fwspec =3D data; + struct irq_fwspec parent_fwspec; + struct irq_chip *chip; + unsigned long hwirq; + unsigned int type; + int ret; + + ret =3D aspeed_intc0_irq_domain_translate(domain, fwspec, &hwirq, &type); + if (ret) + return ret; + + if (hwirq >=3D GIC_P2P_SPI_END && hwirq < INT_NUM) + return -EINVAL; + + if (hwirq < INTM_BASE) + chip =3D &linear_intr_irq_chip; + else if (hwirq < SWINT_BASE) + chip =3D &aspeed_intm_chip; + else + chip =3D &aspeed_swint_chip; + + ret =3D resolve_parent_route_for_input(intc0, domain->parent->fwnode, + (u32)hwirq, &resolved); + if (ret) + return ret; + + parent_fwspec =3D resolved.upstream; + ret =3D irq_domain_alloc_irqs_parent(domain, virq, nr_irqs, + &parent_fwspec); + if (ret) + return ret; + + for (int i =3D 0; i < nr_irqs; ++i, ++hwirq, ++virq) { + ret =3D 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 =3D irq_data_get_irq_chip_data(data); + + if (data->hwirq < INT_NUM) { + int bank =3D data->hwirq / 32; + int bit =3D data->hwirq % 32; + u32 mask =3D BIT(bit); + int route; + + route =3D 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 =3D 0; i < 3; i++) { + void __iomem *sel =3D intc0->base + 0x200 + bank * 4 + 0x100 * i; + u32 reg =3D readl(sel); + + if (route & BIT(i)) + reg |=3D mask; + else + reg &=3D ~mask; + + writel(reg, sel); + if (readl(sel) !=3D 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 =3D { + .translate =3D aspeed_intc0_irq_domain_translate, + .alloc =3D aspeed_intc0_irq_domain_alloc, + .free =3D irq_domain_free_irqs_common, + .map =3D aspeed_intc0_irq_domain_map, + .activate =3D 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 =3D 0; i < INTC0_INTBANK_GROUPS; i++) { + for (j =3D 0; j < INTC0_INTBANKS_PER_GRP; j++) { + u32 base =3D 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 =3D 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 =3D 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 =3D devm_kzalloc(&pdev->dev, sizeof(*intc0), GFP_KERNEL); + if (!intc0) + return -ENOMEM; + + intc0->dev =3D &pdev->dev; + intc0->parent =3D parent; + intc0->base =3D 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 =3D 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 =3D 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 =3D 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-ast2= 700-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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 =3D 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 =3D irq_desc_get_handler_data(desc); + struct irq_chip *chip =3D irq_desc_get_chip(desc); + unsigned long bit, status; + + chained_irq_enter(chip, desc); + + for (int bank =3D 0; bank < INTC1_BANK_NUM; bank++) { + status =3D 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 =3D irq_data_get_irq_chip_data(data); + int bank =3D data->hwirq / INTC1_IRQS_PER_BANK; + int bit =3D data->hwirq % INTC1_IRQS_PER_BANK; + unsigned int mask; + + guard(raw_spinlock_irqsave)(&intc1->intc_lock); + mask =3D 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 =3D irq_data_get_irq_chip_data(data); + int bank =3D data->hwirq / INTC1_IRQS_PER_BANK; + int bit =3D data->hwirq % INTC1_IRQS_PER_BANK; + unsigned int unmask; + + guard(raw_spinlock_irqsave)(&intc1->intc_lock); + unmask =3D readl(intc1->base + INTC1_IER + (0x10 * bank)) | BIT(bit); + writel(unmask, intc1->base + INTC1_IER + (0x10 * bank)); +} + +static struct irq_chip aspeed_intc_chip =3D { + .name =3D "ASPEED INTC1", + .irq_mask =3D aspeed_intc1_irq_mask, + .irq_unmask =3D 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 !=3D 1) + return -EINVAL; + + *hwirq =3D fwspec->param[0]; + *type =3D 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 interru= pt in + * groups of 32. Apply this fact to compress the route table in correspond= ing + * groups of 32. + */ +static const aspeed_intc_output_t aspeed_intc1_routes[INTC1_IN_NUM / 32][I= NTC1_ROUTE_NUM] =3D { + [0] =3D { + [0b000] =3D 0, + [0b001] =3D AST2700_INTC_INVALID_ROUTE, /* path not verified */ + [0b010] =3D 10, + [0b011] =3D 20, + [0b100] =3D 30, + [0b101] =3D 40, + [0b110] =3D 50, + }, + [1] =3D { + [0b000] =3D 1, + [0b001] =3D AST2700_INTC_INVALID_ROUTE, + [0b010] =3D 11, + [0b011] =3D 21, + [0b100] =3D 31, + [0b101] =3D 41, + [0b110] =3D 50, + }, + [2] =3D { + [0b000] =3D 2, + [0b001] =3D AST2700_INTC_INVALID_ROUTE, + [0b010] =3D 12, + [0b011] =3D 22, + [0b100] =3D 32, + [0b101] =3D 42, + [0b110] =3D 50, + }, + [3] =3D { + [0b000] =3D 3, + [0b001] =3D AST2700_INTC_INVALID_ROUTE, + [0b010] =3D 13, + [0b011] =3D 23, + [0b100] =3D 33, + [0b101] =3D 43, + [0b110] =3D 50, + }, + [4] =3D { + [0b000] =3D 4, + [0b001] =3D AST2700_INTC_INVALID_ROUTE, + [0b010] =3D 14, + [0b011] =3D 24, + [0b100] =3D 34, + [0b101] =3D 44, + [0b110] =3D 50, + }, + [5] =3D { + [0b000] =3D 5, + [0b001] =3D AST2700_INTC_INVALID_ROUTE, + [0b010] =3D 15, + [0b011] =3D 25, + [0b100] =3D 35, + [0b101] =3D 45, + [0b110] =3D 50, + }, +}; + +#define INTC1_BOOTMCU_ROUTE 0b110 + +static int aspeed_intc1_parent_is_bootmcu(const struct irq_domain *upstrea= m) +{ + 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 =3D irq_data_get_irq_chip_data(data); + int bank =3D data->hwirq / INTC1_IRQS_PER_BANK; + struct aspeed_intc_interrupt_range resolved; + int bit =3D data->hwirq % INTC1_IRQS_PER_BANK; + u32 mask =3D BIT(bit); + int rc; + + if (WARN_ON_ONCE((data->hwirq >> 5) >=3D 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 =3D 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 =3D INTC1_BOOTMCU_ROUTE; + } + + guard(raw_spinlock_irqsave)(&intc1->intc_lock); + /* Route selector uses 3 bits across the selector registers. */ + for (int i =3D 0; i < 3; i++) { + void __iomem *sel =3D intc1->base + 0x80 + bank * 4 + 0x20 * i; + u32 reg =3D readl(sel); + + if (rc & BIT(i)) + reg |=3D mask; + else + reg &=3D ~mask; + + writel(reg, sel); + if (readl(sel) !=3D 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 =3D { + .map =3D aspeed_intc1_ic_map_irq_domain, + .translate =3D aspeed_intc1_irq_domain_translate, + .activate =3D aspeed_intc1_irq_domain_activate, +}; + +static void aspeed_intc1_request_interrupts(struct aspeed_intc1 *intc1) +{ + unsigned int i; + + for (i =3D 0; i < intc1->ranges.nranges; i++) { + struct aspeed_intc_interrupt_range *r =3D + &intc1->ranges.ranges[i]; + + if (intc1->upstream !=3D + irq_find_matching_fwspec(&r->upstream, + intc1->upstream->bus_token)) + continue; + + for (u32 k =3D 0; k < r->count; k++) { + struct of_phandle_args parent_irq; + int irq; + + parent_irq.np =3D to_of_node(r->upstream.fwnode); + parent_irq.args_count =3D 1; + parent_irq.args[0] =3D + intc1->ranges.ranges[i].upstream.param[0] + k; + + irq =3D 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 =3D 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 =3D irq_find_host(parent); + if (!host) + return -ENODEV; + + intc1 =3D devm_kzalloc(&pdev->dev, sizeof(*intc1), GFP_KERNEL); + if (!intc1) + return -ENOMEM; + + intc1->dev =3D &pdev->dev; + intc1->upstream =3D host; + intc1->base =3D 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 =3D 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 =3D 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 +#include + +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 =3D dev->of_node; + + pvs =3D 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 =3D devm_kmalloc_array(dev, (len / (3 * sizeof(__be32))), + sizeof(*ranges->ranges), + GFP_KERNEL); + if (!ranges->ranges) + return -ENOMEM; + + pve =3D pvs + (len / sizeof(__be32)); + for (unsigned int i =3D 0; pve - pvs >=3D 3; i++) { + struct aspeed_intc_interrupt_range *r; + struct device_node *target; + u32 target_cells; + + target =3D 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 =3D &ranges->ranges[i]; + r->start =3D be32_to_cpu(pvs[0]); + r->count =3D be32_to_cpu(pvs[1]); + + { + struct of_phandle_args args =3D { + .np =3D target, + .args_count =3D target_cells, + }; + + for (u32 j =3D 0; j < target_cells; j++) + args.args[j] =3D be32_to_cpu(pvs[3 + j]); + + of_phandle_args_to_fwspec(target, args.args, + args.args_count, + &r->upstream); + } + + if (target_cells >=3D 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 +=3D 3 + target_cells; + ranges->nranges++; + } + + /* Re-fit the range array now we know the entry count */ + arr =3D devm_krealloc_array(dev, ranges->ranges, ranges->nranges, + sizeof(*ranges->ranges), GFP_KERNEL); + if (!arr) + return -ENOMEM; + ranges->ranges =3D 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 +#include + +#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 --=20 2.34.1 From nobody Sun Feb 8 06:22:42 2026 Received: from TWMBX01.aspeed.com (mail.aspeedtech.com [211.20.114.72]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 9D90C3346BD; Thu, 5 Feb 2026 06:07:41 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=211.20.114.72 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1770271662; cv=none; b=qJlpxdJzjhUtuxgm7hKTYQXFSr1rWUzvQmr/C7F7wxGMb21yqxjR12ChnA3ZnZ2HelJ5v3sR8KmaY32dR/hhU3gkGod7nJMCgFEW4ym4s4U8Z8S9Nl1VuDqpz1izv7sRp6zMqjhcAbQz8DoMPQL9L8DzbJHbBgmi0XC3SFSP1ss= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1770271662; c=relaxed/simple; bh=/czyJlFUz+m3+HZmKZTbaU3DI1sOmd48ORlq8dtx26A=; h=From:Date:Subject:MIME-Version:Content-Type:Message-ID:References: In-Reply-To:To:CC; b=uYmXEM0DrW/LUB7vWINQxGvsOuDlH7/o2CKiFemljVUuI7PCQsr729UrXPAdSptAoPG3rllydO9vjgHvbM5e7fkPVX7fSd2fO8rWMPxLnGcTRQGxYH4jCcT8hso3aALI6gHGT+CAaN8WyFNPs+Bt1MybXj1nL6h+rD3i3NC7av4= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=aspeedtech.com; spf=pass smtp.mailfrom=aspeedtech.com; arc=none smtp.client-ip=211.20.114.72 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=aspeedtech.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=aspeedtech.com Received: from TWMBX01.aspeed.com (192.168.0.62) by TWMBX01.aspeed.com (192.168.0.62) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.2.1748.10; Thu, 5 Feb 2026 14:07:30 +0800 Received: from [127.0.1.1] (192.168.10.13) by TWMBX01.aspeed.com (192.168.0.62) with Microsoft SMTP Server id 15.2.1748.10 via Frontend Transport; Thu, 5 Feb 2026 14:07:30 +0800 From: Ryan Chen Date: Thu, 5 Feb 2026 14:07:21 +0800 Subject: [PATCH 3/4] irqchip/aspeed: Remove legacy AST2700 interrupt controller driver Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-ID: <20260205-irqchip-v1-3-b0310e06c087@aspeedtech.com> References: <20260205-irqchip-v1-0-b0310e06c087@aspeedtech.com> In-Reply-To: <20260205-irqchip-v1-0-b0310e06c087@aspeedtech.com> To: Thomas Gleixner , Rob Herring , Krzysztof Kozlowski , Conor Dooley , Joel Stanley , Andrew Jeffery , Paul Walmsley , Palmer Dabbelt , "Albert Ou" , Alexandre Ghiti CC: , , , , , Ryan Chen X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=ed25519-sha256; t=1770271649; l=5554; i=ryan_chen@aspeedtech.com; s=20251126; h=from:subject:message-id; bh=/czyJlFUz+m3+HZmKZTbaU3DI1sOmd48ORlq8dtx26A=; b=aJadvqQVyHVtwWconhPq6ddtSwD01LZGhovAH0kIteVf2af1Re1ccgCw1Uthb3ijsZLlSwz/9 NHRDXaSH5MFC+wATQXcP5WsnF0d3X5f8qH93gCvuMs672x0nTSo6NcY X-Developer-Key: i=ryan_chen@aspeedtech.com; a=ed25519; pk=Xe73xY6tcnkuRjjbVAB/oU30KdB3FvG4nuJuILj7ZVc= The legacy driver was designed around a PSP-centric interrupt model and cannot describe the full routing and protection capabilities of the AST2700 interrupt architecture. This driver is superseded by the new AST2700 INTC0/INTC1 drivers, which provide a unified, block-level description suitable for all integrated processors. There are no known upstream users of the legacy aspeed,ast2700-intc-ic compatible. Signed-off-by: Ryan Chen --- drivers/irqchip/Makefile | 1 - drivers/irqchip/irq-aspeed-intc.c | 139 ----------------------------------= ---- 2 files changed, 140 deletions(-) diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile index 51fdf269e436..ed547e90ae89 100644 --- a/drivers/irqchip/Makefile +++ b/drivers/irqchip/Makefile @@ -90,7 +90,6 @@ obj-$(CONFIG_LS_EXTIRQ) +=3D irq-ls-extirq.o obj-$(CONFIG_LS_SCFG_MSI) +=3D irq-ls-scfg-msi.o obj-$(CONFIG_ASPEED_AST2700_INTC) +=3D irq-ast2700.o irq-ast2700-intc0.o i= rq-ast2700-intc1.o obj-$(CONFIG_ARCH_ASPEED) +=3D irq-aspeed-vic.o irq-aspeed-i2c-ic.o irq-a= speed-scu-ic.o -obj-$(CONFIG_ARCH_ASPEED) +=3D irq-aspeed-intc.o obj-$(CONFIG_STM32MP_EXTI) +=3D irq-stm32mp-exti.o obj-$(CONFIG_STM32_EXTI) +=3D irq-stm32-exti.o obj-$(CONFIG_QCOM_IRQ_COMBINER) +=3D qcom-irq-combiner.o diff --git a/drivers/irqchip/irq-aspeed-intc.c b/drivers/irqchip/irq-aspeed= -intc.c deleted file mode 100644 index 8330221799a0..000000000000 --- a/drivers/irqchip/irq-aspeed-intc.c +++ /dev/null @@ -1,139 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* - * Aspeed Interrupt Controller. - * - * Copyright (C) 2023 ASPEED Technology Inc. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define INTC_INT_ENABLE_REG 0x00 -#define INTC_INT_STATUS_REG 0x04 -#define INTC_IRQS_PER_WORD 32 - -struct aspeed_intc_ic { - void __iomem *base; - raw_spinlock_t gic_lock; - raw_spinlock_t intc_lock; - struct irq_domain *irq_domain; -}; - -static void aspeed_intc_ic_irq_handler(struct irq_desc *desc) -{ - struct aspeed_intc_ic *intc_ic =3D irq_desc_get_handler_data(desc); - struct irq_chip *chip =3D irq_desc_get_chip(desc); - - chained_irq_enter(chip, desc); - - scoped_guard(raw_spinlock, &intc_ic->gic_lock) { - unsigned long bit, status; - - status =3D readl(intc_ic->base + INTC_INT_STATUS_REG); - for_each_set_bit(bit, &status, INTC_IRQS_PER_WORD) { - generic_handle_domain_irq(intc_ic->irq_domain, bit); - writel(BIT(bit), intc_ic->base + INTC_INT_STATUS_REG); - } - } - - chained_irq_exit(chip, desc); -} - -static void aspeed_intc_irq_mask(struct irq_data *data) -{ - struct aspeed_intc_ic *intc_ic =3D irq_data_get_irq_chip_data(data); - unsigned int mask =3D readl(intc_ic->base + INTC_INT_ENABLE_REG) & ~BIT(d= ata->hwirq); - - guard(raw_spinlock)(&intc_ic->intc_lock); - writel(mask, intc_ic->base + INTC_INT_ENABLE_REG); -} - -static void aspeed_intc_irq_unmask(struct irq_data *data) -{ - struct aspeed_intc_ic *intc_ic =3D irq_data_get_irq_chip_data(data); - unsigned int unmask =3D readl(intc_ic->base + INTC_INT_ENABLE_REG) | BIT(= data->hwirq); - - guard(raw_spinlock)(&intc_ic->intc_lock); - writel(unmask, intc_ic->base + INTC_INT_ENABLE_REG); -} - -static struct irq_chip aspeed_intc_chip =3D { - .name =3D "ASPEED INTC", - .irq_mask =3D aspeed_intc_irq_mask, - .irq_unmask =3D aspeed_intc_irq_unmask, -}; - -static int aspeed_intc_ic_map_irq_domain(struct irq_domain *domain, unsign= ed int irq, - irq_hw_number_t hwirq) -{ - irq_set_chip_and_handler(irq, &aspeed_intc_chip, handle_level_irq); - irq_set_chip_data(irq, domain->host_data); - - return 0; -} - -static const struct irq_domain_ops aspeed_intc_ic_irq_domain_ops =3D { - .map =3D aspeed_intc_ic_map_irq_domain, -}; - -static int __init aspeed_intc_ic_of_init(struct device_node *node, - struct device_node *parent) -{ - struct aspeed_intc_ic *intc_ic; - int irq, i, ret =3D 0; - - intc_ic =3D kzalloc(sizeof(*intc_ic), GFP_KERNEL); - if (!intc_ic) - return -ENOMEM; - - intc_ic->base =3D of_iomap(node, 0); - if (!intc_ic->base) { - pr_err("Failed to iomap intc_ic base\n"); - ret =3D -ENOMEM; - goto err_free_ic; - } - writel(0xffffffff, intc_ic->base + INTC_INT_STATUS_REG); - writel(0x0, intc_ic->base + INTC_INT_ENABLE_REG); - - intc_ic->irq_domain =3D irq_domain_create_linear(of_fwnode_handle(node), = INTC_IRQS_PER_WORD, - &aspeed_intc_ic_irq_domain_ops, intc_ic); - if (!intc_ic->irq_domain) { - ret =3D -ENOMEM; - goto err_iounmap; - } - - raw_spin_lock_init(&intc_ic->gic_lock); - raw_spin_lock_init(&intc_ic->intc_lock); - - /* Check all the irq numbers valid. If not, unmaps all the base and frees= the data. */ - for (i =3D 0; i < of_irq_count(node); i++) { - irq =3D irq_of_parse_and_map(node, i); - if (!irq) { - pr_err("Failed to get irq number\n"); - ret =3D -EINVAL; - goto err_iounmap; - } - } - - for (i =3D 0; i < of_irq_count(node); i++) { - irq =3D irq_of_parse_and_map(node, i); - irq_set_chained_handler_and_data(irq, aspeed_intc_ic_irq_handler, intc_i= c); - } - - return 0; - -err_iounmap: - iounmap(intc_ic->base); -err_free_ic: - kfree(intc_ic); - return ret; -} - -IRQCHIP_DECLARE(ast2700_intc_ic, "aspeed,ast2700-intc-ic", aspeed_intc_ic_= of_init); --=20 2.34.1 From nobody Sun Feb 8 06:22:42 2026 Received: from TWMBX01.aspeed.com (mail.aspeedtech.com [211.20.114.72]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 30E6133506F; Thu, 5 Feb 2026 06:07:42 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=211.20.114.72 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1770271662; cv=none; b=eoXNp3irbBs+MMNguIZM5036vFzBfS11PpJBRyz7KAaTiLI6NwcO15YKySXhaauNsPtHPEiEHnG4CXUACLmYsFznoY3Y553nF1MMn3nkZT/+wt1Be9tPe/DlZwKo/90uch3Ebu0n23t+8mTth5+FEbODFmG1+ZXK3QEBgCb+AR0= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1770271662; c=relaxed/simple; bh=94kybmSK3L5D9FwcDBryTTemjApRuHICkM61YUdO5vs=; h=From:Date:Subject:MIME-Version:Content-Type:Message-ID:References: In-Reply-To:To:CC; b=WBw4gArtJqKUKNetg/0G7XIa041VL59Fh1fVkhJ65xK1phUEBWEAldS0Fl4iBCtMtOpSgZg0eB4PSpSTi6dz9Vz0+58CBJ9GK2LcYXvqMtf7Uqco0smm9NxMnenNLdE2wWtRQlGw8LDbBymUIfDtk/Ju9ako6ZSI/tMmFIAMZKQ= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=aspeedtech.com; spf=pass smtp.mailfrom=aspeedtech.com; arc=none smtp.client-ip=211.20.114.72 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=aspeedtech.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=aspeedtech.com Received: from TWMBX01.aspeed.com (192.168.0.62) by TWMBX01.aspeed.com (192.168.0.62) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.2.1748.10; Thu, 5 Feb 2026 14:07:30 +0800 Received: from [127.0.1.1] (192.168.10.13) by TWMBX01.aspeed.com (192.168.0.62) with Microsoft SMTP Server id 15.2.1748.10 via Frontend Transport; Thu, 5 Feb 2026 14:07:30 +0800 From: Ryan Chen Date: Thu, 5 Feb 2026 14:07:22 +0800 Subject: [PATCH 4/4] dt-bindings: interrupt-controller: aspeed: Remove legacy AST2700 interrupt binding Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-ID: <20260205-irqchip-v1-4-b0310e06c087@aspeedtech.com> References: <20260205-irqchip-v1-0-b0310e06c087@aspeedtech.com> In-Reply-To: <20260205-irqchip-v1-0-b0310e06c087@aspeedtech.com> To: Thomas Gleixner , Rob Herring , Krzysztof Kozlowski , Conor Dooley , Joel Stanley , Andrew Jeffery , Paul Walmsley , Palmer Dabbelt , "Albert Ou" , Alexandre Ghiti CC: , , , , , Ryan Chen X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=ed25519-sha256; t=1770271649; l=4143; i=ryan_chen@aspeedtech.com; s=20251126; h=from:subject:message-id; bh=94kybmSK3L5D9FwcDBryTTemjApRuHICkM61YUdO5vs=; b=W0brkznpcQsf8wSaCgi65FA86/dT8/rbmt2Kul/YxEM7azEc3tuqfRQbbcJukMHzbV6QsMC9e SkcxUdArKffAJKnTsjEsFA3BtWotZjQjgtH2mmdDNTtaW7iPEV8rUzN X-Developer-Key: i=ryan_chen@aspeedtech.com; a=ed25519; pk=Xe73xY6tcnkuRjjbVAB/oU30KdB3FvG4nuJuILj7ZVc= Remove the legacy AST2700 interrupt controller Devicetree binding. The legacy binding was limited to a PSP-centric view of the interrupt architecture and cannot describe interrupt routing and protection for the full AST2700 system. It is superseded by the new ASPEED AST2700 INTC0/INTC1 binding, which describes the interrupt controllers at the block-function level. There are no known upstream users of the removed binding. Signed-off-by: Ryan Chen --- .../interrupt-controller/aspeed,ast2700-intc.yaml | 90 ------------------= ---- 1 file changed, 90 deletions(-) diff --git a/Documentation/devicetree/bindings/interrupt-controller/aspeed,= ast2700-intc.yaml b/Documentation/devicetree/bindings/interrupt-controller/= aspeed,ast2700-intc.yaml deleted file mode 100644 index 258d21fe6e35..000000000000 --- a/Documentation/devicetree/bindings/interrupt-controller/aspeed,ast2700= -intc.yaml +++ /dev/null @@ -1,90 +0,0 @@ -# SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause -%YAML 1.2 ---- -$id: http://devicetree.org/schemas/interrupt-controller/aspeed,ast2700-int= c.yaml# -$schema: http://devicetree.org/meta-schemas/core.yaml# - -title: Aspeed AST2700 Interrupt Controller - -description: - This interrupt controller hardware is second level interrupt controller = that - is hooked to a parent interrupt controller. It's useful to combine multi= ple - interrupt sources into 1 interrupt to parent interrupt controller. - -maintainers: - - Kevin Chen - -properties: - compatible: - enum: - - aspeed,ast2700-intc-ic - - reg: - maxItems: 1 - - interrupt-controller: true - - '#interrupt-cells': - const: 1 - description: - The first cell is the IRQ number, the second cell is the trigger - type as defined in interrupt.txt in this directory. - - interrupts: - minItems: 1 - maxItems: 10 - description: | - Depend to which INTC0 or INTC1 used. - INTC0 and INTC1 are two kinds of interrupt controller with enable an= d raw - status registers for use. - INTC0 is used to assert GIC if interrupt in INTC1 asserted. - INTC1 is used to assert INTC0 if interrupt of modules asserted. - +-----+ +-------+ +---------+---module0 - | GIC |---| INTC0 |--+--| INTC1_0 |---module2 - | | | | | | |---... - +-----+ +-------+ | +---------+---module31 - | - | +---------+---module0 - +---| INTC1_1 |---module2 - | | |---... - | +---------+---module31 - ... - | +---------+---module0 - +---| INTC1_5 |---module2 - | |---... - +---------+---module31 - -required: - - compatible - - reg - - interrupt-controller - - '#interrupt-cells' - - interrupts - -additionalProperties: false - -examples: - - | - #include - - bus { - #address-cells =3D <2>; - #size-cells =3D <2>; - - interrupt-controller@12101b00 { - compatible =3D "aspeed,ast2700-intc-ic"; - reg =3D <0 0x12101b00 0 0x10>; - #interrupt-cells =3D <1>; - interrupt-controller; - interrupts =3D , - , - , - , - , - , - , - , - , - ; - }; - }; --=20 2.34.1