[GIT pull] irq/drivers for v7.2-rc1
From: Thomas Gleixner
Date: Sat Jun 13 2026 - 17:26:11 EST
Linus,
please pull the latest irq/drivers branch from:
git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip.git irq-drivers-2026-06-13
up to: a1a35c09241f: irqchip/irq-realtek-rtl: Add multicore support
Interrupt chip driver changes:
- Replace the support for the AST2700-A0 early silicon with a proper
driver for the final A2 production silicon
- Rename and rework the StarFive JH8100 interrupt controller for the new
JHB100 SoC as JH8100 was discontinued before production.
- Add support for Amlogic A9 SoCs to the meson-gpio interrupt controller
- Expand the Econet interrupt controller driver to support MIPS 34Kc
Vectored External Interrupt Controller mode.
- Prevent a NULL pointer dereference in the GICv4 code as the vLPI code
blindly assumes that the ITS was populated. Add the missing sanity check.
- Add support for software triggered and for error interrupts to the
Renesas RZ/T2H driver.
- Add interrupt redirection support for the loongarch architecture.
- Add multicore support to the Realtek RTL interrupt driver
- The usual updates, enhancements and fixes all over the place
Thanks,
tglx
------------------>
Caleb James DeLisle (2):
dt-bindings: interrupt-controller: econet: Add CPU interrupt mapping
irqchip/econet-en751221: Support MIPS 34Kc VEIC mode
Changhuang Liang (4):
dt-bindings: interrupt-controller: Repurpose binding for unreleased jh8100 for jhb100
irqchip/starfive: Rename jh8100 to jhb100
irqchip/starfive: Use devm_ interfaces to simplify resource release
irqchip/starfive: Implement irq_set_type() and irq_ack() callbacks
Chen Ni (1):
irqchip/starfive: Fix error check for devm_platform_ioremap_resource()
Cosmin Tanislav (2):
irqchip/renesas-rzt2h: Add software-triggered interrupts support
irqchip/renesas-rzt2h: Add error interrupts support
Hans Zhang (1):
irqchip/gic-v3-its: Use FIELD_MODIFY()
Krzysztof Kozlowski (1):
irqchip/qcom: Unify user-visible "Qualcomm" name
Marek Szyprowski (1):
irqchip/exynos-combiner: Remove useless spinlock
Markus Stockhausen (2):
irqchip/irq-realtek-rtl: Add/simplify register helpers
irqchip/irq-realtek-rtl: Add multicore support
Mason Huo (1):
irqchip/starfive: Increase the interrupt source number up to 64
Mostafa Saleh (1):
irqchip/gic-v4: Don't advertise VLPIs if no ITS is probed
Mukesh Ojha (4):
irqchip/qcom-pdc: Split __pdc_enable_intr() into per-version helpers
irqchip/qcom-pdc: Tighten ioremap clamp to single DRV region size
irqchip/qcom-pdc: Add PDC_VERSION() macro to describe version register fields
irqchip/qcom-pdc: Use FIELD_GET() to extract bank index and bit position
Ryan Chen (4):
dt-bindings: interrupt-controller: Describe AST2700-A2 hardware instead of A0
irqchip/ast2700-intc: Add AST2700-A2 support
irqchip/ast2700-intc: Add KUnit tests for route resolution
irqchip/aspeed-intc: Remove AST2700-A0 support
Thomas Huth (1):
irqchip/gic: Replace __ASSEMBLY__ with __ASSEMBLER__
Tianyang Zhang (4):
Docs/LoongArch: Add advanced extended IRQ model
irqchip/loongarch-avec: Prepare for interrupt redirection support
irqchip/loongarch-avec: Return IRQ_SET_MASK_OK_DONE when keep affinity
irqchip/loongarch-ir: Add IR (interrupt redirection) irqchip support
Xianwei Zhao (3):
irqchip/meson-gpio: Use the correct register in meson_s4_gpio_irq_set_type()
dt-bindings: interrupt-controller: Add support for Amlogic A9 SoCs
irqchip/meson-gpio: Add support for Amlogic A9 SoCs
Documentation/arch/loongarch/irq-chip-model.rst | 35 ++
.../amlogic,meson-gpio-intc.yaml | 21 +-
.../interrupt-controller/aspeed,ast2700-intc.yaml | 90 ----
.../aspeed,ast2700-interrupt.yaml | 188 +++++++
.../interrupt-controller/econet,en751221-intc.yaml | 20 +
...,jh8100-intc.yaml => starfive,jhb100-intc.yaml} | 20 +-
.../zh_CN/arch/loongarch/irq-chip-model.rst | 34 ++
MAINTAINERS | 6 +-
arch/arm/include/asm/arch_gicv3.h | 4 +-
drivers/irqchip/.kunitconfig | 5 +
drivers/irqchip/Kconfig | 35 +-
drivers/irqchip/Makefile | 7 +-
drivers/irqchip/exynos-combiner.c | 4 -
drivers/irqchip/irq-aspeed-intc.c | 139 -----
drivers/irqchip/irq-ast2700-intc0-test.c | 473 +++++++++++++++++
drivers/irqchip/irq-ast2700-intc0.c | 582 +++++++++++++++++++++
drivers/irqchip/irq-ast2700-intc1.c | 280 ++++++++++
drivers/irqchip/irq-ast2700.c | 107 ++++
drivers/irqchip/irq-ast2700.h | 48 ++
drivers/irqchip/irq-econet-en751221.c | 186 ++++++-
drivers/irqchip/irq-gic-v3-its.c | 13 +-
drivers/irqchip/irq-loongarch-avec.c | 20 +-
drivers/irqchip/irq-loongarch-ir.c | 537 +++++++++++++++++++
drivers/irqchip/irq-loongson.h | 15 +
drivers/irqchip/irq-meson-gpio.c | 77 +++
drivers/irqchip/irq-realtek-rtl.c | 106 ++--
drivers/irqchip/irq-renesas-rzt2h.c | 268 +++++++++-
drivers/irqchip/irq-starfive-jh8100-intc.c | 207 --------
drivers/irqchip/irq-starfive-jhb100-intc.c | 254 +++++++++
drivers/irqchip/qcom-pdc.c | 63 ++-
include/linux/irqchip/arm-gic-v3.h | 2 +-
include/linux/irqchip/arm-gic.h | 4 +-
32 files changed, 3277 insertions(+), 573 deletions(-)
delete mode 100644 Documentation/devicetree/bindings/interrupt-controller/aspeed,ast2700-intc.yaml
create mode 100644 Documentation/devicetree/bindings/interrupt-controller/aspeed,ast2700-interrupt.yaml
rename Documentation/devicetree/bindings/interrupt-controller/{starfive,jh8100-intc.yaml => starfive,jhb100-intc.yaml} (68%)
create mode 100644 drivers/irqchip/.kunitconfig
delete mode 100644 drivers/irqchip/irq-aspeed-intc.c
create mode 100644 drivers/irqchip/irq-ast2700-intc0-test.c
create mode 100644 drivers/irqchip/irq-ast2700-intc0.c
create mode 100644 drivers/irqchip/irq-ast2700-intc1.c
create mode 100644 drivers/irqchip/irq-ast2700.c
create mode 100644 drivers/irqchip/irq-ast2700.h
create mode 100644 drivers/irqchip/irq-loongarch-ir.c
delete mode 100644 drivers/irqchip/irq-starfive-jh8100-intc.c
create mode 100644 drivers/irqchip/irq-starfive-jhb100-intc.c
diff --git a/Documentation/arch/loongarch/irq-chip-model.rst b/Documentation/arch/loongarch/irq-chip-model.rst
index 8f5c3345109e..774d40dc6a7e 100644
--- a/Documentation/arch/loongarch/irq-chip-model.rst
+++ b/Documentation/arch/loongarch/irq-chip-model.rst
@@ -181,6 +181,41 @@ go to PCH-PIC/PCH-LPC and gathered by EIOINTC, and then go to CPUINTC directly::
| Devices |
+---------+
+Advanced Extended IRQ model (with redirection)
+==============================================
+
+In this model, IPI (Inter-Processor Interrupt) and CPU Local Timer interrupt go
+to CPUINTC directly, CPU UARTS interrupts go to LIOINTC, PCH-MSI interrupts go
+to REDIRECT for remapping it to AVECINTC, and then go to CPUINTC directly, while
+all other devices interrupts go to PCH-PIC/PCH-LPC and gathered by EIOINTC, and
+then go to CPUINTC directly::
+
+ +-----+ +-----------------------+ +-------+
+ | IPI | --> | CPUINTC | <-- | Timer |
+ +-----+ +-----------------------+ +-------+
+ ^ ^ ^
+ | | |
+ | +----------+ |
+ +---------+ | AVECINTC | +---------+ +-------+
+ | EIOINTC | +----------+ | LIOINTC | <-- | UARTs |
+ +---------+ | REDIRECT | +---------+ +-------+
+ ^ +----------+
+ | ^
+ | |
+ +---------+ +---------+
+ | PCH-PIC | | PCH-MSI |
+ +---------+ +---------+
+ ^ ^ ^
+ | | |
+ +---------+ +---------+ +---------+
+ | Devices | | PCH-LPC | | Devices |
+ +---------+ +---------+ +---------+
+ ^
+ |
+ +---------+
+ | Devices |
+ +---------+
+
ACPI-related definitions
========================
diff --git a/Documentation/devicetree/bindings/interrupt-controller/amlogic,meson-gpio-intc.yaml b/Documentation/devicetree/bindings/interrupt-controller/amlogic,meson-gpio-intc.yaml
index d0fad930de9d..d26671913e89 100644
--- a/Documentation/devicetree/bindings/interrupt-controller/amlogic,meson-gpio-intc.yaml
+++ b/Documentation/devicetree/bindings/interrupt-controller/amlogic,meson-gpio-intc.yaml
@@ -38,6 +38,8 @@ properties:
- amlogic,a4-gpio-intc
- amlogic,a4-gpio-ao-intc
- amlogic,a5-gpio-intc
+ - amlogic,a9-gpio-intc
+ - amlogic,a9-gpio-ao-intc
- amlogic,c3-gpio-intc
- amlogic,s6-gpio-intc
- amlogic,s7-gpio-intc
@@ -56,7 +58,7 @@ properties:
amlogic,channel-interrupts:
description: Array with the upstream hwirq numbers
minItems: 2
- maxItems: 12
+ maxItems: 20
$ref: /schemas/types.yaml#/definitions/uint32-array
required:
@@ -76,9 +78,20 @@ then:
amlogic,channel-interrupts:
maxItems: 2
else:
- properties:
- amlogic,channel-interrupts:
- minItems: 8
+ if:
+ properties:
+ compatible:
+ contains:
+ const: amlogic,a9-gpio-ao-intc
+ then:
+ properties:
+ amlogic,channel-interrupts:
+ minItems: 20
+ else:
+ properties:
+ amlogic,channel-interrupts:
+ minItems: 8
+ maxItems: 12
additionalProperties: false
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-intc.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 multiple
- interrupt sources into 1 interrupt to parent interrupt controller.
-
-maintainers:
- - Kevin Chen <kevin_chen@xxxxxxxxxxxxxx>
-
-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 and 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 <dt-bindings/interrupt-controller/arm-gic.h>
-
- bus {
- #address-cells = <2>;
- #size-cells = <2>;
-
- interrupt-controller@12101b00 {
- compatible = "aspeed,ast2700-intc-ic";
- reg = <0 0x12101b00 0 0x10>;
- #interrupt-cells = <1>;
- interrupt-controller;
- interrupts = <GIC_SPI 192 IRQ_TYPE_LEVEL_HIGH>,
- <GIC_SPI 193 IRQ_TYPE_LEVEL_HIGH>,
- <GIC_SPI 194 IRQ_TYPE_LEVEL_HIGH>,
- <GIC_SPI 195 IRQ_TYPE_LEVEL_HIGH>,
- <GIC_SPI 196 IRQ_TYPE_LEVEL_HIGH>,
- <GIC_SPI 197 IRQ_TYPE_LEVEL_HIGH>,
- <GIC_SPI 198 IRQ_TYPE_LEVEL_HIGH>,
- <GIC_SPI 199 IRQ_TYPE_LEVEL_HIGH>,
- <GIC_SPI 200 IRQ_TYPE_LEVEL_HIGH>,
- <GIC_SPI 201 IRQ_TYPE_LEVEL_HIGH>;
- };
- };
diff --git a/Documentation/devicetree/bindings/interrupt-controller/aspeed,ast2700-interrupt.yaml b/Documentation/devicetree/bindings/interrupt-controller/aspeed,ast2700-interrupt.yaml
new file mode 100644
index 000000000000..a62f0fd2435b
--- /dev/null
+++ b/Documentation/devicetree/bindings/interrupt-controller/aspeed,ast2700-interrupt.yaml
@@ -0,0 +1,188 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/interrupt-controller/aspeed,ast2700-interrupt.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 Service 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 | +Peripheral + | out int |
+ +-----------+ | 0 0 <-+Controllers+ | INTM | +-----------+
+ |PSP GIC <-|---+ . . | +-----------+ | . . <-+Peripheral +
+ +-----------+ | . . | | . . | +Controllers+
+ +-----------+ | . . | | . . | +-----------+
+ |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, for 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 <ryan_chen@xxxxxxxxxxxxxx>
+ - Andrew Jeffery <andrew@xxxxxxxxxxxxxxxxxxxx>
+
+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:
+
+ <out count phandle parent-specifier...>
+
+ 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 <dt-bindings/interrupt-controller/arm-gic.h>
+
+ interrupt-controller@12100000 {
+ compatible = "aspeed,ast2700-intc0";
+ reg = <0x12100000 0x3b00>;
+ interrupt-parent = <&gic>;
+ interrupt-controller;
+ #interrupt-cells = <1>;
+
+ aspeed,interrupt-ranges =
+ <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>;
+ };
diff --git a/Documentation/devicetree/bindings/interrupt-controller/econet,en751221-intc.yaml b/Documentation/devicetree/bindings/interrupt-controller/econet,en751221-intc.yaml
index 5536319c49c3..44c09785e6bb 100644
--- a/Documentation/devicetree/bindings/interrupt-controller/econet,en751221-intc.yaml
+++ b/Documentation/devicetree/bindings/interrupt-controller/econet,en751221-intc.yaml
@@ -52,6 +52,25 @@ properties:
- description: primary per-CPU IRQ
- description: shadow IRQ number
+ econet,cpu-interrupt-map:
+ $ref: /schemas/types.yaml#/definitions/uint32-matrix
+ description:
+ When running in VEIC mode, the hardware re-routes interrupts from the
+ CPU interrupt controller core to the "external" interrupt controller
+ (this device). It then prioritizes them and sends them back to the CPU
+ along with its own interrupts. The CPU hardware handles interrupts using
+ a special dispatch table (the normal interrupt handler is not invoked).
+ In this interrupt controller, the CPU interrupts are renumbered as they
+ are merged with this controller's own hardware interrupts.
+
+ This is the inverse of an interrupt-map, mapping which interrupts from
+ this controller must be routed back to the CPU interrupt domain for
+ correct handling there.
+ items:
+ items:
+ - description: The interrupt number as received in this controller
+ - description: The interrupt number to be dispatched on the CPU intc
+
required:
- compatible
- reg
@@ -74,5 +93,6 @@ examples:
interrupts = <2>;
econet,shadow-interrupts = <7 2>, <8 3>, <13 12>, <30 29>;
+ econet,cpu-interrupt-map = <7 0>, <8 1>;
};
...
diff --git a/Documentation/devicetree/bindings/interrupt-controller/starfive,jh8100-intc.yaml b/Documentation/devicetree/bindings/interrupt-controller/starfive,jhb100-intc.yaml
similarity index 68%
rename from Documentation/devicetree/bindings/interrupt-controller/starfive,jh8100-intc.yaml
rename to Documentation/devicetree/bindings/interrupt-controller/starfive,jhb100-intc.yaml
index ada5788602d6..d8a0a3862ae2 100644
--- a/Documentation/devicetree/bindings/interrupt-controller/starfive,jh8100-intc.yaml
+++ b/Documentation/devicetree/bindings/interrupt-controller/starfive,jhb100-intc.yaml
@@ -1,13 +1,13 @@
# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
%YAML 1.2
---
-$id: http://devicetree.org/schemas/interrupt-controller/starfive,jh8100-intc.yaml#
+$id: http://devicetree.org/schemas/interrupt-controller/starfive,jhb100-intc.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
title: StarFive External Interrupt Controller
description:
- StarFive SoC JH8100 contain a external interrupt controller. It can be used
+ StarFive SoC JHB100 contain a external interrupt controller. It can be used
to handle high-level input interrupt signals. It also send the output
interrupt signal to RISC-V PLIC.
@@ -16,19 +16,11 @@ maintainers:
properties:
compatible:
- const: starfive,jh8100-intc
+ const: starfive,jhb100-intc
reg:
maxItems: 1
- clocks:
- description: APB clock for the interrupt controller
- maxItems: 1
-
- resets:
- description: APB reset for the interrupt controller
- maxItems: 1
-
interrupts:
maxItems: 1
@@ -40,8 +32,6 @@ properties:
required:
- compatible
- reg
- - clocks
- - resets
- interrupts
- interrupt-controller
- "#interrupt-cells"
@@ -51,10 +41,8 @@ additionalProperties: false
examples:
- |
interrupt-controller@12260000 {
- compatible = "starfive,jh8100-intc";
+ compatible = "starfive,jhb100-intc";
reg = <0x12260000 0x10000>;
- clocks = <&syscrg_ne 76>;
- resets = <&syscrg_ne 13>;
interrupts = <45>;
interrupt-controller;
#interrupt-cells = <1>;
diff --git a/Documentation/translations/zh_CN/arch/loongarch/irq-chip-model.rst b/Documentation/translations/zh_CN/arch/loongarch/irq-chip-model.rst
index d4ff80de47b6..87b58aee92e1 100644
--- a/Documentation/translations/zh_CN/arch/loongarch/irq-chip-model.rst
+++ b/Documentation/translations/zh_CN/arch/loongarch/irq-chip-model.rst
@@ -174,6 +174,40 @@ CPU串口(UARTs)中断发送到LIOINTC,PCH-MSI中断发送到AVECINTC,
| Devices |
+---------+
+高级扩展IRQ模型 (带重定向)
+==========================
+
+在这种模型里面,IPI(Inter-Processor Interrupt)和CPU本地时钟中断直接发送到CPUINTC,
+CPU串口(UARTs)中断发送到LIOINTC,PCH-MSI中断首先发送到REDIRECT模块,完成重定向后发
+送到AVECINTC,而后通过AVECINTC直接送达CPUINTC,而其他所有设备的中断则分别发送到所连
+接的PCH-PIC/PCH-LPC,然后由EIOINTC统一收集,再直接到达CPUINTC::
+
+ +-----+ +-----------------------+ +-------+
+ | IPI | --> | CPUINTC | <-- | Timer |
+ +-----+ +-----------------------+ +-------+
+ ^ ^ ^
+ | | |
+ | +----------+ |
+ +---------+ | AVECINTC | +---------+ +-------+
+ | EIOINTC | +----------+ | LIOINTC | <-- | UARTs |
+ +---------+ | REDIRECT | +---------+ +-------+
+ ^ +----------+
+ | ^
+ | |
+ +---------+ +---------+
+ | PCH-PIC | | PCH-MSI |
+ +---------+ +---------+
+ ^ ^ ^
+ | | |
+ +---------+ +---------+ +---------+
+ | Devices | | PCH-LPC | | Devices |
+ +---------+ +---------+ +---------+
+ ^
+ |
+ +---------+
+ | Devices |
+ +---------+
+
ACPI相关的定义
==============
diff --git a/MAINTAINERS b/MAINTAINERS
index 2fb1c75afd16..73af7d7788ef 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -25533,11 +25533,11 @@ F: Documentation/devicetree/bindings/phy/starfive,jh7110-usb-phy.yaml
F: drivers/phy/starfive/phy-jh7110-pcie.c
F: drivers/phy/starfive/phy-jh7110-usb.c
-STARFIVE JH8100 EXTERNAL INTERRUPT CONTROLLER DRIVER
+STARFIVE JHB100 EXTERNAL INTERRUPT CONTROLLER DRIVER
M: Changhuang Liang <changhuang.liang@xxxxxxxxxxxxxxxx>
S: Supported
-F: Documentation/devicetree/bindings/interrupt-controller/starfive,jh8100-intc.yaml
-F: drivers/irqchip/irq-starfive-jh8100-intc.c
+F: Documentation/devicetree/bindings/interrupt-controller/starfive,jhb100-intc.yaml
+F: drivers/irqchip/irq-starfive-jhb100-intc.c
STATIC BRANCH/CALL
M: Peter Zijlstra <peterz@xxxxxxxxxxxxx>
diff --git a/arch/arm/include/asm/arch_gicv3.h b/arch/arm/include/asm/arch_gicv3.h
index 311e83038bdb..847590df7551 100644
--- a/arch/arm/include/asm/arch_gicv3.h
+++ b/arch/arm/include/asm/arch_gicv3.h
@@ -7,7 +7,7 @@
#ifndef __ASM_ARCH_GICV3_H
#define __ASM_ARCH_GICV3_H
-#ifndef __ASSEMBLY__
+#ifndef __ASSEMBLER__
#include <linux/io.h>
#include <linux/io-64-nonatomic-lo-hi.h>
@@ -257,5 +257,5 @@ static inline bool gic_has_relaxed_pmr_sync(void)
return false;
}
-#endif /* !__ASSEMBLY__ */
+#endif /* !__ASSEMBLER__ */
#endif /* !__ASM_ARCH_GICV3_H */
diff --git a/drivers/irqchip/.kunitconfig b/drivers/irqchip/.kunitconfig
new file mode 100644
index 000000000000..00a12703f635
--- /dev/null
+++ b/drivers/irqchip/.kunitconfig
@@ -0,0 +1,5 @@
+CONFIG_KUNIT=y
+CONFIG_OF=y
+CONFIG_COMPILE_TEST=y
+CONFIG_ASPEED_AST2700_INTC=y
+CONFIG_ASPEED_AST2700_INTC_TEST=y
diff --git a/drivers/irqchip/Kconfig b/drivers/irqchip/Kconfig
index e755a2a05209..fdf27cf529fc 100644
--- a/drivers/irqchip/Kconfig
+++ b/drivers/irqchip/Kconfig
@@ -110,6 +110,29 @@ 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
+ help
+ Enable support for the ASPEED AST2700 interrupt controller.
+ This driver handles interrupt, routing and merged interrupt
+ sources to upstream parent interrupt controllers.
+
+ If unsure, say N.
+
+config ASPEED_AST2700_INTC_TEST
+ bool "Tests for the ASPEED AST2700 Interrupt Controller"
+ depends on ASPEED_AST2700_INTC && KUNIT=y
+ default KUNIT_ALL_TESTS
+ help
+ Enable KUnit tests for AST2700 INTC route resolution.
+ The tests exercise error handling and route selection paths.
+ This option is intended for test builds.
+
+ If unsure, say N.
+
config ATMEL_AIC_IRQ
bool
select GENERIC_IRQ_CHIP
@@ -476,7 +499,7 @@ config STM32_EXTI
select GENERIC_IRQ_CHIP
config QCOM_IRQ_COMBINER
- bool "QCOM IRQ combiner support"
+ bool "Qualcomm IRQ combiner support"
depends on ARCH_QCOM && ACPI
select IRQ_DOMAIN_HIERARCHY
help
@@ -509,7 +532,7 @@ config GOLDFISH_PIC
for Goldfish based virtual platforms.
config QCOM_PDC
- tristate "QCOM PDC"
+ tristate "Qualcomm PDC"
depends on ARCH_QCOM
select IRQ_DOMAIN_HIERARCHY
help
@@ -517,7 +540,7 @@ config QCOM_PDC
IRQs for Qualcomm Technologies Inc (QTI) mobile chips.
config QCOM_MPM
- tristate "QCOM MPM"
+ tristate "Qualcomm MPM"
depends on ARCH_QCOM
depends on MAILBOX
select IRQ_DOMAIN_HIERARCHY
@@ -654,13 +677,13 @@ config SIFIVE_PLIC
select IRQ_DOMAIN_HIERARCHY
select GENERIC_IRQ_EFFECTIVE_AFF_MASK if SMP
-config STARFIVE_JH8100_INTC
- bool "StarFive JH8100 External Interrupt Controller"
+config STARFIVE_JHB100_INTC
+ bool "StarFive JHB100 External Interrupt Controller"
depends on ARCH_STARFIVE || COMPILE_TEST
default ARCH_STARFIVE
select IRQ_DOMAIN_HIERARCHY
help
- This enables support for the INTC chip found in StarFive JH8100
+ This enables support for the INTC chip found in StarFive JHB100
SoC.
If you don't know what to do here, say Y.
diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile
index 26aa3b6ec99f..72cdcc9caa16 100644
--- a/drivers/irqchip/Makefile
+++ b/drivers/irqchip/Makefile
@@ -89,8 +89,9 @@ 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_ASPEED_AST2700_INTC_TEST) += irq-ast2700-intc0-test.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
obj-$(CONFIG_STM32_EXTI) += irq-stm32-exti.o
obj-$(CONFIG_QCOM_IRQ_COMBINER) += qcom-irq-combiner.o
@@ -108,7 +109,7 @@ obj-$(CONFIG_RISCV_APLIC_MSI) += irq-riscv-aplic-msi.o
obj-$(CONFIG_RISCV_IMSIC) += irq-riscv-imsic-state.o irq-riscv-imsic-early.o irq-riscv-imsic-platform.o
obj-$(CONFIG_RISCV_RPMI_SYSMSI) += irq-riscv-rpmi-sysmsi.o
obj-$(CONFIG_SIFIVE_PLIC) += irq-sifive-plic.o
-obj-$(CONFIG_STARFIVE_JH8100_INTC) += irq-starfive-jh8100-intc.o
+obj-$(CONFIG_STARFIVE_JHB100_INTC) += irq-starfive-jhb100-intc.o
obj-$(CONFIG_ACLINT_SSWI) += irq-aclint-sswi.o
obj-$(CONFIG_IMX_IRQSTEER) += irq-imx-irqsteer.o
obj-$(CONFIG_IMX_INTMUX) += irq-imx-intmux.o
@@ -119,7 +120,7 @@ obj-$(CONFIG_LS1X_IRQ) += irq-ls1x.o
obj-$(CONFIG_TI_SCI_INTR_IRQCHIP) += irq-ti-sci-intr.o
obj-$(CONFIG_TI_SCI_INTA_IRQCHIP) += irq-ti-sci-inta.o
obj-$(CONFIG_TI_PRUSS_INTC) += irq-pruss-intc.o
-obj-$(CONFIG_IRQ_LOONGARCH_CPU) += irq-loongarch-cpu.o irq-loongarch-avec.o
+obj-$(CONFIG_IRQ_LOONGARCH_CPU) += irq-loongarch-cpu.o irq-loongarch-avec.o irq-loongarch-ir.o
obj-$(CONFIG_LOONGSON_LIOINTC) += irq-loongson-liointc.o
obj-$(CONFIG_LOONGSON_EIOINTC) += irq-loongson-eiointc.o
obj-$(CONFIG_LOONGSON_HTPIC) += irq-loongson-htpic.o
diff --git a/drivers/irqchip/exynos-combiner.c b/drivers/irqchip/exynos-combiner.c
index 03cafcc5c835..d9d408cb4711 100644
--- a/drivers/irqchip/exynos-combiner.c
+++ b/drivers/irqchip/exynos-combiner.c
@@ -24,8 +24,6 @@
#define IRQ_IN_COMBINER 8
-static DEFINE_RAW_SPINLOCK(irq_controller_lock);
-
struct combiner_chip_data {
unsigned int hwirq_offset;
unsigned int irq_mask;
@@ -72,9 +70,7 @@ static void combiner_handle_cascade_irq(struct irq_desc *desc)
chained_irq_enter(chip, desc);
- raw_spin_lock(&irq_controller_lock);
status = readl_relaxed(chip_data->base + COMBINER_INT_STATUS);
- raw_spin_unlock(&irq_controller_lock);
status &= chip_data->irq_mask;
if (status == 0)
diff --git a/drivers/irqchip/irq-aspeed-intc.c b/drivers/irqchip/irq-aspeed-intc.c
deleted file mode 100644
index 4fb0dd8349da..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 <linux/bitops.h>
-#include <linux/irq.h>
-#include <linux/irqchip.h>
-#include <linux/irqchip/chained_irq.h>
-#include <linux/irqdomain.h>
-#include <linux/of_address.h>
-#include <linux/of_irq.h>
-#include <linux/io.h>
-#include <linux/spinlock.h>
-
-#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 = irq_desc_get_handler_data(desc);
- struct irq_chip *chip = irq_desc_get_chip(desc);
-
- chained_irq_enter(chip, desc);
-
- scoped_guard(raw_spinlock, &intc_ic->gic_lock) {
- unsigned long bit, status;
-
- status = 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 = irq_data_get_irq_chip_data(data);
- unsigned int mask = readl(intc_ic->base + INTC_INT_ENABLE_REG) & ~BIT(data->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 = irq_data_get_irq_chip_data(data);
- unsigned int unmask = 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 = {
- .name = "ASPEED INTC",
- .irq_mask = aspeed_intc_irq_mask,
- .irq_unmask = aspeed_intc_irq_unmask,
-};
-
-static int aspeed_intc_ic_map_irq_domain(struct irq_domain *domain, unsigned 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 = {
- .map = 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 = 0;
-
- intc_ic = kzalloc_obj(*intc_ic);
- if (!intc_ic)
- return -ENOMEM;
-
- intc_ic->base = of_iomap(node, 0);
- if (!intc_ic->base) {
- pr_err("Failed to iomap intc_ic base\n");
- ret = -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 = 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 = -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 = 0; i < of_irq_count(node); i++) {
- irq = irq_of_parse_and_map(node, i);
- if (!irq) {
- pr_err("Failed to get irq number\n");
- ret = -EINVAL;
- goto err_iounmap;
- }
- }
-
- for (i = 0; i < of_irq_count(node); i++) {
- irq = irq_of_parse_and_map(node, i);
- irq_set_chained_handler_and_data(irq, aspeed_intc_ic_irq_handler, intc_ic);
- }
-
- 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);
diff --git a/drivers/irqchip/irq-ast2700-intc0-test.c b/drivers/irqchip/irq-ast2700-intc0-test.c
new file mode 100644
index 000000000000..d49784509ac7
--- /dev/null
+++ b/drivers/irqchip/irq-ast2700-intc0-test.c
@@ -0,0 +1,473 @@
+// 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 u32 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, -ENOENT);
+
+ rc = aspeed_intc0_resolve_route(&c0domain, ARRAY_SIZE(c1outs), c1outs,
+ 0, c1ranges, &resolved);
+ KUNIT_EXPECT_EQ(test, rc, -ENOENT);
+}
+
+static int gicv3_fwnode_read_string_array(const struct fwnode_handle *fwnode,
+ 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 = 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 u32 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";
+ 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 u32 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 u32 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 u32 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 u32 c1outs[] = { 1 };
+ struct aspeed_intc_interrupt_range resolved;
+ static 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 u32 c1outs[] = { 0 };
+ struct aspeed_intc_interrupt_range resolved;
+ static 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 u32 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 u32 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..14b8b88f1179
--- /dev/null
+++ b/drivers/irqchip/irq-ast2700-intc0.c
@@ -0,0 +1,582 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Aspeed AST2700 Interrupt Controller.
+ *
+ * Copyright (C) 2026 ASPEED Technology Inc.
+ */
+
+#include <linux/bitops.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/fwnode.h>
+#include <linux/io.h>
+#include <linux/irq.h>
+#include <linux/irqchip.h>
+#include <linux/irqchip/chained_irq.h>
+#include <linux/irqdomain.h>
+#include <linux/kconfig.h>
+#include <linux/of.h>
+#include <linux/of_irq.h>
+#include <linux/overflow.h>
+#include <linux/property.h>
+#include <linux/spinlock.h>
+
+#include "irq-ast2700.h"
+
+#define INT_NUM 480
+#define INTM_NUM 50
+#define SWINT_NUM 16
+
+#define INTM_BASE (INT_NUM)
+#define SWINT_BASE (INT_NUM + INTM_NUM)
+#define INT0_NUM (INT_NUM + INTM_NUM + SWINT_NUM)
+
+#define INTC0_IN_NUM 480
+#define INTC0_ROUTE_NUM 5
+#define INTC0_INTM_NUM 50
+#define INTC0_ROUTE_BITS 3
+
+#define GIC_P2P_SPI_END 128
+#define INTC0_SWINT_OUT_BASE 144
+
+#define INTC0_SWINT_IER 0x10
+#define INTC0_SWINT_ISR 0x14
+#define INTC0_INTBANKX_IER 0x1000
+#define INTC0_INTBANK_SIZE 0x100
+#define INTC0_INTBANK_GROUPS 11
+#define INTC0_INTBANKS_PER_GRP 3
+#define INTC0_INTMX_IER 0x1b00
+#define INTC0_INTMX_ISR 0x1b04
+#define INTC0_INTMX_BANK_SIZE 0x10
+#define INTC0_INTM_BANK_NUM 3
+#define INTC0_IRQS_PER_BANK 32
+#define INTM_IRQS_PER_BANK 10
+#define INTC0_SEL_BASE 0x200
+#define INTC0_SEL_BANK_SIZE 0x4
+#define INTC0_SEL_ROUTE_SIZE 0x100
+
+static void aspeed_swint_irq_mask(struct irq_data *data)
+{
+ struct aspeed_intc0 *intc0 = irq_data_get_irq_chip_data(data);
+ int bit = data->hwirq - SWINT_BASE;
+ u32 ier;
+
+ guard(raw_spinlock)(&intc0->intc_lock);
+ ier = readl(intc0->base + INTC0_SWINT_IER) & ~BIT(bit);
+ writel(ier, intc0->base + INTC0_SWINT_IER);
+ irq_chip_mask_parent(data);
+}
+
+static void aspeed_swint_irq_unmask(struct irq_data *data)
+{
+ struct aspeed_intc0 *intc0 = irq_data_get_irq_chip_data(data);
+ int bit = data->hwirq - SWINT_BASE;
+ u32 ier;
+
+ guard(raw_spinlock)(&intc0->intc_lock);
+ ier = readl(intc0->base + INTC0_SWINT_IER) | BIT(bit);
+ writel(ier, intc0->base + INTC0_SWINT_IER);
+ irq_chip_unmask_parent(data);
+}
+
+static void aspeed_swint_irq_eoi(struct irq_data *data)
+{
+ struct aspeed_intc0 *intc0 = irq_data_get_irq_chip_data(data);
+ int bit = data->hwirq - SWINT_BASE;
+
+ writel(BIT(bit), intc0->base + INTC0_SWINT_ISR);
+ irq_chip_eoi_parent(data);
+}
+
+static struct irq_chip aspeed_swint_chip = {
+ .name = "ast2700-swint",
+ .irq_eoi = aspeed_swint_irq_eoi,
+ .irq_mask = aspeed_swint_irq_mask,
+ .irq_unmask = aspeed_swint_irq_unmask,
+ .irq_set_affinity = irq_chip_set_affinity_parent,
+ .flags = IRQCHIP_SET_TYPE_MASKED,
+};
+
+static void aspeed_intc0_irq_mask(struct irq_data *data)
+{
+ struct aspeed_intc0 *intc0 = irq_data_get_irq_chip_data(data);
+ int bank = (data->hwirq - INTM_BASE) / INTM_IRQS_PER_BANK;
+ int bit = (data->hwirq - INTM_BASE) % INTM_IRQS_PER_BANK;
+ u32 ier;
+
+ guard(raw_spinlock)(&intc0->intc_lock);
+ ier = readl(intc0->base + INTC0_INTMX_IER + bank * INTC0_INTMX_BANK_SIZE) & ~BIT(bit);
+ writel(ier, intc0->base + INTC0_INTMX_IER + bank * INTC0_INTMX_BANK_SIZE);
+ irq_chip_mask_parent(data);
+}
+
+static void aspeed_intc0_irq_unmask(struct irq_data *data)
+{
+ struct aspeed_intc0 *intc0 = irq_data_get_irq_chip_data(data);
+ int bank = (data->hwirq - INTM_BASE) / INTM_IRQS_PER_BANK;
+ int bit = (data->hwirq - INTM_BASE) % INTM_IRQS_PER_BANK;
+ u32 ier;
+
+ guard(raw_spinlock)(&intc0->intc_lock);
+ ier = readl(intc0->base + INTC0_INTMX_IER + bank * INTC0_INTMX_BANK_SIZE) | BIT(bit);
+ writel(ier, intc0->base + INTC0_INTMX_IER + bank * INTC0_INTMX_BANK_SIZE);
+ irq_chip_unmask_parent(data);
+}
+
+static void aspeed_intc0_irq_eoi(struct irq_data *data)
+{
+ struct aspeed_intc0 *intc0 = irq_data_get_irq_chip_data(data);
+ int bank = (data->hwirq - INTM_BASE) / INTM_IRQS_PER_BANK;
+ int bit = (data->hwirq - INTM_BASE) % INTM_IRQS_PER_BANK;
+
+ writel(BIT(bit), intc0->base + INTC0_INTMX_ISR + bank * INTC0_INTMX_BANK_SIZE);
+ irq_chip_eoi_parent(data);
+}
+
+static struct irq_chip aspeed_intm_chip = {
+ .name = "ast2700-intmerge",
+ .irq_eoi = aspeed_intc0_irq_eoi,
+ .irq_mask = aspeed_intc0_irq_mask,
+ .irq_unmask = aspeed_intc0_irq_unmask,
+ .irq_set_affinity = irq_chip_set_affinity_parent,
+ .flags = IRQCHIP_SET_TYPE_MASKED,
+};
+
+static struct irq_chip linear_intr_irq_chip = {
+ .name = "ast2700-int",
+ .irq_eoi = irq_chip_eoi_parent,
+ .irq_mask = irq_chip_mask_parent,
+ .irq_unmask = irq_chip_unmask_parent,
+ .irq_set_affinity = irq_chip_set_affinity_parent,
+ .flags = IRQCHIP_SET_TYPE_MASKED,
+};
+
+static const u32 aspeed_intc0_routes[INTC0_IN_NUM / INTC0_IRQS_PER_BANK][INTC0_ROUTE_NUM] = {
+ { 0, 256, 426, AST2700_INTC_INVALID_ROUTE, AST2700_INTC_INVALID_ROUTE },
+ { 32, 288, 458, AST2700_INTC_INVALID_ROUTE, AST2700_INTC_INVALID_ROUTE },
+ { 64, 320, 490, AST2700_INTC_INVALID_ROUTE, AST2700_INTC_INVALID_ROUTE },
+ { 96, 352, 522, AST2700_INTC_INVALID_ROUTE, AST2700_INTC_INVALID_ROUTE },
+ { 128, 384, 554, 160, 176 },
+ { 129, 385, 555, 161, 177 },
+ { 130, 386, 556, 162, 178 },
+ { 131, 387, 557, 163, 179 },
+ { 132, 388, 558, 164, 180 },
+ { 133, 544, 714, 165, 181 },
+ { 134, 545, 715, 166, 182 },
+ { 135, 546, 706, 167, 183 },
+ { 136, 547, 707, 168, 184 },
+ { 137, 548, 708, 169, 185 },
+ { 138, 549, 709, 170, 186 },
+};
+
+static const u32 aspeed_intc0_intm_routes[INTC0_INTM_NUM / INTM_IRQS_PER_BANK] = {
+ 192, 416, 586, 208, 224
+};
+
+static int resolve_input_from_child_ranges(const struct aspeed_intc0 *intc0,
+ const struct aspeed_intc_interrupt_range *range,
+ u32 outpin, u32 *input)
+{
+ u32 offset, base;
+
+ if (!in_range32(outpin, range->start, range->count))
+ return -ENOENT;
+
+ if (range->upstream.param_count == 0)
+ return -EINVAL;
+
+ base = range->upstream.param[ASPEED_INTC_RANGES_BASE];
+ offset = outpin - range->start;
+ if (check_add_overflow(base, offset, input)) {
+ dev_warn(intc0->dev, "%s: Arithmetic overflow for input derivation: %u + %u\n",
+ __func__, base, offset);
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static int resolve_parent_range_for_output(const struct aspeed_intc0 *intc0,
+ const struct fwnode_handle *parent, u32 output,
+ struct aspeed_intc_interrupt_range *resolved)
+{
+ for (size_t i = 0; i < intc0->ranges.nranges; i++) {
+ struct aspeed_intc_interrupt_range range = intc0->ranges.ranges[i];
+
+ if (!in_range32(output, range.start, range.count))
+ continue;
+
+ if (range.upstream.fwnode != parent)
+ continue;
+
+ if (resolved) {
+ resolved->start = output;
+ resolved->count = 1;
+ resolved->upstream = range.upstream;
+ resolved->upstream.param[ASPEED_INTC_RANGES_COUNT] +=
+ output - range.start;
+ }
+
+ return 0;
+ }
+
+ return -ENOENT;
+}
+
+static int resolve_parent_route_for_input(const struct aspeed_intc0 *intc0,
+ const struct fwnode_handle *parent, u32 input,
+ struct aspeed_intc_interrupt_range *resolved)
+{
+ int rc = -ENOENT;
+ u32 c0o;
+
+ if (input < INT_NUM) {
+ static_assert(INTC0_ROUTE_NUM < INT_MAX, "Broken cast");
+ for (size_t i = 0; rc == -ENOENT && i < INTC0_ROUTE_NUM; i++) {
+ c0o = aspeed_intc0_routes[input / INTC0_IRQS_PER_BANK][i];
+ if (c0o == AST2700_INTC_INVALID_ROUTE)
+ continue;
+
+ if (input < GIC_P2P_SPI_END)
+ c0o += input % INTC0_IRQS_PER_BANK;
+
+ rc = resolve_parent_range_for_output(intc0, parent, c0o, resolved);
+ if (!rc)
+ return (int)i;
+ }
+ } else if (input < (INT_NUM + INTM_NUM)) {
+ c0o = aspeed_intc0_intm_routes[(input - INT_NUM) / INTM_IRQS_PER_BANK];
+ c0o += ((input - INT_NUM) % INTM_IRQS_PER_BANK);
+ return resolve_parent_range_for_output(intc0, parent, c0o, resolved);
+ } else if (input < (INT_NUM + INTM_NUM + SWINT_NUM)) {
+ c0o = input - SWINT_BASE + INTC0_SWINT_OUT_BASE;
+ return resolve_parent_range_for_output(intc0, parent, c0o, resolved);
+ } else {
+ return -ENOENT;
+ }
+
+ return rc;
+}
+
+/**
+ * aspeed_intc0_resolve_route - Determine the necessary interrupt output at intc1
+ * @c0domain: The pointer to intc0's irq_domain
+ * @nc1outs: The number of valid intc1 outputs available for the input
+ * @c1outs: The array of available intc1 output indices for the input
+ * @nc1ranges: The number of interrupt range entries for intc1
+ * @c1ranges: The array of configured intc1 interrupt ranges
+ * @resolved: The fully resolved range entry after applying the resolution
+ * algorithm
+ *
+ * Returns: The intc1 route index associated with the intc1 output identified in
+ * @resolved on success. Otherwise, a negative errno value.
+ *
+ * The AST2700 interrupt architecture allows any peripheral interrupt source
+ * to be routed to one of up to four processors running in the SoC. A processor
+ * binding a driver for a peripheral that requests an interrupt is (without
+ * further design and effort) the destination for the requested interrupt.
+ *
+ * Routing a peripheral interrupt to its destination processor requires
+ * coordination between INTC0 on the CPU die and one or more INTC1 instances.
+ * At least one INTC1 instance exists in the SoC on the IO-die, however up
+ * to two more instances may be integrated via LTPI (LVDS Tunneling Protocol
+ * & Interface).
+ *
+ * Between the multiple destinations, various route constraints, and the
+ * devicetree binding design, some information that's needed at INTC1 instances
+ * to route inbound interrupts correctly to the destination processor is only
+ * available at INTC0.
+ *
+ * aspeed_intc0_resolve_route() is to be invoked by INTC1 driver instances to
+ * perform the route resolution. The implementation in INTC0 allows INTC0 to
+ * encapsulate the information used to perform route selection, and provides it
+ * with an opportunity to apply policy as part of the selection process. Such
+ * policy may, for instance, choose to de-prioritise some interrupts destined
+ * for the PSP (Primary Service Processor) GIC.
+ */
+int aspeed_intc0_resolve_route(const struct irq_domain *c0domain, size_t nc1outs,
+ const u32 *c1outs, size_t nc1ranges,
+ const struct aspeed_intc_interrupt_range *c1ranges,
+ struct aspeed_intc_interrupt_range *resolved)
+{
+ struct fwnode_handle *parent_fwnode;
+ struct aspeed_intc0 *intc0;
+ int ret;
+
+ if (!c0domain || !resolved)
+ return -EINVAL;
+
+ if (nc1outs > INT_MAX)
+ return -EINVAL;
+
+ if (nc1outs == 0 || nc1ranges == 0)
+ return -ENOENT;
+
+ if (!IS_ENABLED(CONFIG_ASPEED_AST2700_INTC_TEST) &&
+ !fwnode_device_is_compatible(c0domain->fwnode, "aspeed,ast2700-intc0"))
+ return -ENODEV;
+
+ intc0 = c0domain->host_data;
+ if (!intc0)
+ return -EINVAL;
+
+ parent_fwnode = of_fwnode_handle(intc0->parent);
+
+ for (size_t i = 0; i < nc1outs; i++) {
+ u32 c1o = c1outs[i];
+
+ if (c1o == AST2700_INTC_INVALID_ROUTE)
+ continue;
+
+ for (size_t j = 0; j < nc1ranges; j++) {
+ struct aspeed_intc_interrupt_range c1r = c1ranges[j];
+ u32 input;
+
+ /*
+ * Range match for intc1 output pin
+ *
+ * Assume a failed match is still a match for the purpose of testing,
+ * saves a bunch of mess in the test fixtures
+ */
+ if (!(c0domain == c1r.domain ||
+ IS_ENABLED(CONFIG_ASPEED_AST2700_INTC_TEST)))
+ continue;
+
+ ret = resolve_input_from_child_ranges(intc0, &c1r, c1o, &input);
+ if (ret)
+ continue;
+
+ /*
+ * INTC1 should never request routes for peripheral interrupt sources
+ * directly attached to INTC0.
+ */
+ if (input < GIC_P2P_SPI_END)
+ continue;
+
+ ret = resolve_parent_route_for_input(intc0, parent_fwnode, input, NULL);
+ if (ret < 0)
+ continue;
+
+ /* Route resolution succeeded */
+ resolved->start = c1o;
+ resolved->count = 1;
+ resolved->upstream = c1r.upstream;
+ resolved->upstream.param[ASPEED_INTC_RANGES_BASE] = input;
+ /* Cast protected by prior test against nc1outs */
+ return (int)i;
+ }
+ }
+
+ return -ENOENT;
+}
+
+static int aspeed_intc0_irq_domain_map(struct irq_domain *domain,
+ unsigned int irq, irq_hw_number_t hwirq)
+{
+ if (hwirq < GIC_P2P_SPI_END)
+ irq_set_chip_and_handler(irq, &linear_intr_irq_chip, handle_level_irq);
+ else if (hwirq < INTM_BASE)
+ return -EINVAL;
+ else if (hwirq < SWINT_BASE)
+ irq_set_chip_and_handler(irq, &aspeed_intm_chip, handle_level_irq);
+ else if (hwirq < INT0_NUM)
+ irq_set_chip_and_handler(irq, &aspeed_swint_chip, handle_level_irq);
+ else
+ return -EINVAL;
+
+ irq_set_chip_data(irq, domain->host_data);
+ return 0;
+}
+
+static int aspeed_intc0_irq_domain_translate(struct irq_domain *domain,
+ struct irq_fwspec *fwspec,
+ unsigned long *hwirq,
+ unsigned int *type)
+{
+ if (fwspec->param_count != 1)
+ return -EINVAL;
+
+ *hwirq = fwspec->param[0];
+ *type = IRQ_TYPE_NONE;
+ return 0;
+}
+
+static int aspeed_intc0_irq_domain_alloc(struct irq_domain *domain,
+ unsigned int virq,
+ unsigned int nr_irqs, void *data)
+{
+ struct aspeed_intc0 *intc0 = domain->host_data;
+ struct aspeed_intc_interrupt_range resolved;
+ struct irq_fwspec *fwspec = data;
+ struct irq_fwspec parent_fwspec;
+ struct irq_chip *chip;
+ unsigned long hwirq;
+ unsigned int type;
+ int ret;
+
+ ret = aspeed_intc0_irq_domain_translate(domain, fwspec, &hwirq, &type);
+ if (ret)
+ return ret;
+
+ if (hwirq >= GIC_P2P_SPI_END && hwirq < INT_NUM)
+ return -EINVAL;
+
+ if (hwirq < INTM_BASE)
+ chip = &linear_intr_irq_chip;
+ else if (hwirq < SWINT_BASE)
+ chip = &aspeed_intm_chip;
+ else
+ chip = &aspeed_swint_chip;
+
+ ret = resolve_parent_route_for_input(intc0, domain->parent->fwnode,
+ (u32)hwirq, &resolved);
+ if (ret)
+ return ret;
+
+ parent_fwspec = resolved.upstream;
+ ret = irq_domain_alloc_irqs_parent(domain, virq, nr_irqs,
+ &parent_fwspec);
+ if (ret)
+ return ret;
+
+ for (int i = 0; i < nr_irqs; ++i, ++hwirq, ++virq) {
+ ret = irq_domain_set_hwirq_and_chip(domain, virq, hwirq, chip,
+ domain->host_data);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static int aspeed_intc0_irq_domain_activate(struct irq_domain *domain,
+ struct irq_data *data, bool reserve)
+{
+ struct aspeed_intc0 *intc0 = irq_data_get_irq_chip_data(data);
+ unsigned long hwirq = data->hwirq;
+ int route, bank, bit;
+ u32 mask;
+
+ if (hwirq >= INT0_NUM)
+ return -EINVAL;
+
+ if (in_range32(hwirq, INTM_BASE, INTM_NUM + SWINT_NUM))
+ return 0;
+
+ bank = hwirq / INTC0_IRQS_PER_BANK;
+ bit = hwirq % INTC0_IRQS_PER_BANK;
+ mask = BIT(bit);
+
+ route = resolve_parent_route_for_input(intc0, intc0->local->parent->fwnode,
+ hwirq, NULL);
+ if (route < 0)
+ return route;
+
+ guard(raw_spinlock)(&intc0->intc_lock);
+ for (int i = 0; i < INTC0_ROUTE_BITS; i++) {
+ void __iomem *sel = intc0->base + INTC0_SEL_BASE +
+ (bank * INTC0_SEL_BANK_SIZE) +
+ (INTC0_SEL_ROUTE_SIZE * i);
+ u32 reg = readl(sel);
+
+ if (route & BIT(i))
+ reg |= mask;
+ else
+ reg &= ~mask;
+
+ writel(reg, sel);
+ if (readl(sel) != reg)
+ return -EACCES;
+ }
+
+ return 0;
+}
+
+static const struct irq_domain_ops aspeed_intc0_irq_domain_ops = {
+ .translate = aspeed_intc0_irq_domain_translate,
+ .activate = aspeed_intc0_irq_domain_activate,
+ .alloc = aspeed_intc0_irq_domain_alloc,
+ .free = irq_domain_free_irqs_common,
+ .map = aspeed_intc0_irq_domain_map,
+};
+
+static void aspeed_intc0_disable_swint(struct aspeed_intc0 *intc0)
+{
+ writel(0, intc0->base + INTC0_SWINT_IER);
+}
+
+static void aspeed_intc0_disable_intbank(struct aspeed_intc0 *intc0)
+{
+ for (int i = 0; i < INTC0_INTBANK_GROUPS; i++) {
+ for (int j = 0; j < INTC0_INTBANKS_PER_GRP; j++) {
+ u32 base = INTC0_INTBANKX_IER +
+ (INTC0_INTBANK_SIZE * i) +
+ (INTC0_INTMX_BANK_SIZE * j);
+
+ writel(0, intc0->base + base);
+ }
+ }
+}
+
+static void aspeed_intc0_disable_intm(struct aspeed_intc0 *intc0)
+{
+ for (int i = 0; i < INTC0_INTM_BANK_NUM; i++)
+ writel(0, intc0->base + INTC0_INTMX_IER + (INTC0_INTMX_BANK_SIZE * i));
+}
+
+static int aspeed_intc0_probe(struct platform_device *pdev,
+ struct device_node *parent)
+{
+ struct device_node *node = pdev->dev.of_node;
+ struct irq_domain *parent_domain;
+ struct aspeed_intc0 *intc0;
+ int ret;
+
+ if (!parent) {
+ pr_err("missing parent interrupt node\n");
+ return -ENODEV;
+ }
+
+ intc0 = devm_kzalloc(&pdev->dev, sizeof(*intc0), GFP_KERNEL);
+ if (!intc0)
+ return -ENOMEM;
+
+ intc0->dev = &pdev->dev;
+ intc0->parent = parent;
+ intc0->base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(intc0->base))
+ return PTR_ERR(intc0->base);
+
+ aspeed_intc0_disable_swint(intc0);
+ aspeed_intc0_disable_intbank(intc0);
+ aspeed_intc0_disable_intm(intc0);
+
+ raw_spin_lock_init(&intc0->intc_lock);
+
+ parent_domain = irq_find_host(parent);
+ if (!parent_domain) {
+ pr_err("unable to obtain parent domain\n");
+ return -ENODEV;
+ }
+
+ if (!of_device_is_compatible(parent, "arm,gic-v3"))
+ return -ENODEV;
+
+ intc0->local = irq_domain_create_hierarchy(parent_domain, 0, INT0_NUM,
+ of_fwnode_handle(node),
+ &aspeed_intc0_irq_domain_ops,
+ intc0);
+ if (!intc0->local)
+ return -ENOMEM;
+
+ ret = aspeed_intc_populate_ranges(&pdev->dev, &intc0->ranges);
+ if (ret < 0) {
+ irq_domain_remove(intc0->local);
+ return ret;
+ }
+
+ return 0;
+}
+
+IRQCHIP_PLATFORM_DRIVER_BEGIN(ast2700_intc0)
+IRQCHIP_MATCH("aspeed,ast2700-intc0", aspeed_intc0_probe)
+IRQCHIP_PLATFORM_DRIVER_END(ast2700_intc0)
diff --git a/drivers/irqchip/irq-ast2700-intc1.c b/drivers/irqchip/irq-ast2700-intc1.c
new file mode 100644
index 000000000000..59e8f0d5ddcd
--- /dev/null
+++ b/drivers/irqchip/irq-ast2700-intc1.c
@@ -0,0 +1,280 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Aspeed AST2700 Interrupt Controller.
+ *
+ * Copyright (C) 2026 ASPEED Technology Inc.
+ */
+
+#include <linux/bitops.h>
+#include <linux/device.h>
+#include <linux/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_BANK_SIZE 0x10
+#define INTC1_SEL_BASE 0x80
+#define INTC1_SEL_BANK_SIZE 0x4
+#define INTC1_SEL_ROUTE_SIZE 0x20
+#define INTC1_IRQS_PER_BANK 32
+#define INTC1_BANK_NUM 6
+#define INTC1_ROUTE_NUM 7
+#define INTC1_IN_NUM 192
+#define INTC1_BOOTMCU_ROUTE 6
+#define INTC1_ROUTE_SELECTOR_BITS 3
+#define INTC1_ROUTE_IRQS_PER_GROUP 32
+#define INTC1_ROUTE_SHIFT 5
+
+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(0, intc1->base + INTC1_IER + (INTC1_BANK_SIZE * i));
+}
+
+static void aspeed_intc1_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 + (INTC1_BANK_SIZE * 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 + (INTC1_BANK_SIZE * 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;
+ u32 ier;
+
+ guard(raw_spinlock)(&intc1->intc_lock);
+ ier = readl(intc1->base + INTC1_IER + (INTC1_BANK_SIZE * bank)) & ~BIT(bit);
+ writel(ier, intc1->base + INTC1_IER + (INTC1_BANK_SIZE * 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;
+ u32 ier;
+
+ guard(raw_spinlock)(&intc1->intc_lock);
+ ier = readl(intc1->base + INTC1_IER + (INTC1_BANK_SIZE * bank)) | BIT(bit);
+ writel(ier, intc1->base + INTC1_IER + (INTC1_BANK_SIZE * 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_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;
+}
+
+/*
+ * 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 u32
+aspeed_intc1_routes[INTC1_IN_NUM / INTC1_ROUTE_IRQS_PER_GROUP][INTC1_ROUTE_NUM] = {
+ { 0, AST2700_INTC_INVALID_ROUTE, 10, 20, 30, 40, 50 },
+ { 1, AST2700_INTC_INVALID_ROUTE, 11, 21, 31, 41, 50 },
+ { 2, AST2700_INTC_INVALID_ROUTE, 12, 22, 32, 42, 50 },
+ { 3, AST2700_INTC_INVALID_ROUTE, 13, 23, 33, 43, 50 },
+ { 4, AST2700_INTC_INVALID_ROUTE, 14, 24, 34, 44, 50 },
+ { 5, AST2700_INTC_INVALID_ROUTE, 15, 25, 35, 45, 50 },
+};
+
+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);
+ struct aspeed_intc_interrupt_range resolved;
+ int rc, bank, bit;
+ u32 mask;
+
+ if (WARN_ON_ONCE((data->hwirq >> INTC1_ROUTE_SHIFT) >= ARRAY_SIZE(aspeed_intc1_routes)))
+ return -EINVAL;
+
+ /*
+ * 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 >> INTC1_ROUTE_SHIFT],
+ intc1->ranges.nranges,
+ intc1->ranges.ranges, &resolved);
+ if (rc < 0) {
+ if (!fwnode_device_is_compatible(intc1->upstream->fwnode, "riscv,aplic")) {
+ 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;
+ }
+
+ bank = data->hwirq / INTC1_IRQS_PER_BANK;
+ bit = data->hwirq % INTC1_IRQS_PER_BANK;
+ mask = BIT(bit);
+
+ guard(raw_spinlock)(&intc1->intc_lock);
+ for (int i = 0; i < INTC1_ROUTE_SELECTOR_BITS; i++) {
+ void __iomem *sel = intc1->base + INTC1_SEL_BASE +
+ (bank * INTC1_SEL_BANK_SIZE) +
+ (INTC1_SEL_ROUTE_SIZE * i);
+ u32 reg = readl(sel);
+
+ if (rc & BIT(i))
+ reg |= mask;
+ else
+ reg &= ~mask;
+
+ writel(reg, sel);
+ if (readl(sel) != reg)
+ return -EACCES;
+ }
+
+ return 0;
+}
+
+static const struct irq_domain_ops aspeed_intc1_irq_domain_ops = {
+ .map = aspeed_intc1_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)
+{
+ for (unsigned int i = 0; i < intc1->ranges.nranges; i++) {
+ struct aspeed_intc_interrupt_range *r =
+ &intc1->ranges.ranges[i];
+
+ if (intc1->upstream != r->domain)
+ 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[ASPEED_INTC_RANGES_BASE] + k;
+
+ irq = irq_create_of_mapping(&parent_irq);
+ if (!irq)
+ continue;
+
+ irq_set_chained_handler_and_data(irq,
+ aspeed_intc1_irq_handler, intc1);
+ }
+ }
+}
+
+static int aspeed_intc1_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"))
+ 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_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", aspeed_intc1_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..1e4c4a624dbf
--- /dev/null
+++ b/drivers/irqchip/irq-ast2700.c
@@ -0,0 +1,107 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Aspeed AST2700 Interrupt Controller.
+ *
+ * Copyright (C) 2026 ASPEED Technology Inc.
+ */
+#include "irq-ast2700.h"
+
+#define ASPEED_INTC_RANGE_FIXED_CELLS 3U
+#define ASPEED_INTC_RANGE_OFF_START 0U
+#define ASPEED_INTC_RANGE_OFF_COUNT 1U
+#define ASPEED_INTC_RANGE_OFF_PHANDLE 2U
+
+/**
+ * aspeed_intc_populate_ranges
+ * @dev: Device owning the interrupt controller node.
+ * @ranges: Destination for parsed range descriptors.
+ *
+ * Return: 0 on success, negative errno on error.
+ */
+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 / (ASPEED_INTC_RANGE_FIXED_CELLS * sizeof(__be32)),
+ sizeof(*ranges->ranges),
+ GFP_KERNEL);
+ if (!ranges->ranges)
+ return -ENOMEM;
+
+ pve = pvs + (len / sizeof(__be32));
+ for (unsigned int i = 0; pve - pvs >= ASPEED_INTC_RANGE_FIXED_CELLS; i++) {
+ struct aspeed_intc_interrupt_range *r;
+ struct device_node *target;
+ u32 target_cells;
+
+ target = of_find_node_by_phandle(be32_to_cpu(pvs[ASPEED_INTC_RANGE_OFF_PHANDLE]));
+ 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 < ASPEED_INTC_RANGE_FIXED_CELLS + target_cells) {
+ of_node_put(target);
+ return -EINVAL;
+ }
+
+ r = &ranges->ranges[i];
+ r->start = be32_to_cpu(pvs[ASPEED_INTC_RANGE_OFF_START]);
+ r->count = be32_to_cpu(pvs[ASPEED_INTC_RANGE_OFF_COUNT]);
+
+ {
+ 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[ASPEED_INTC_RANGE_FIXED_CELLS + j]);
+
+ of_phandle_args_to_fwspec(target, args.args,
+ args.args_count,
+ &r->upstream);
+ }
+
+ of_node_put(target);
+ r->domain = irq_find_matching_fwspec(&r->upstream, DOMAIN_BUS_ANY);
+ pvs += ASPEED_INTC_RANGE_FIXED_CELLS + 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..318296638445
--- /dev/null
+++ b/drivers/irqchip/irq-ast2700.h
@@ -0,0 +1,48 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Aspeed AST2700 Interrupt Controller.
+ *
+ * Copyright (C) 2026 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)
+#define ASPEED_INTC_RANGES_BASE 0U
+#define ASPEED_INTC_RANGES_COUNT 1U
+
+struct aspeed_intc_interrupt_range {
+ u32 start;
+ u32 count;
+ struct irq_fwspec upstream;
+ struct irq_domain *domain;
+};
+
+struct aspeed_intc_interrupt_ranges {
+ struct aspeed_intc_interrupt_range *ranges;
+ unsigned int nranges;
+};
+
+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;
+};
+
+int aspeed_intc_populate_ranges(struct device *dev,
+ struct aspeed_intc_interrupt_ranges *ranges);
+
+int aspeed_intc0_resolve_route(const struct irq_domain *c0domain,
+ size_t nc1outs,
+ const u32 *c1outs,
+ size_t nc1ranges,
+ const struct aspeed_intc_interrupt_range *c1ranges,
+ struct aspeed_intc_interrupt_range *resolved);
+
+#endif
diff --git a/drivers/irqchip/irq-econet-en751221.c b/drivers/irqchip/irq-econet-en751221.c
index d83d5eb12795..2ca5d901866f 100644
--- a/drivers/irqchip/irq-econet-en751221.c
+++ b/drivers/irqchip/irq-econet-en751221.c
@@ -30,6 +30,8 @@
#include <linux/irqchip.h>
#include <linux/irqchip/chained_irq.h>
+#include <asm/setup.h>
+
#define IRQ_COUNT 40
#define NOT_PERCPU 0xff
@@ -41,15 +43,19 @@
#define REG_PENDING1 0x54
/**
- * @membase: Base address of the interrupt controller registers
- * @interrupt_shadows: Array of all interrupts, for each value,
- * - NOT_PERCPU: This interrupt is not per-cpu, so it has no shadow
- * - IS_SHADOW: This interrupt is a shadow of another per-cpu interrupt
- * - else: This is a per-cpu interrupt whose shadow is the value
+ * @membase: Base address of the interrupt controller registers
+ * @domain: The irq_domain for direct dispatch
+ * @ipi_domain: The irq_domain for inter-process dispatch
+ * @interrupt_shadows: Array of all interrupts, for each value,
+ * - NOT_PERCPU: This interrupt is not per-cpu, so it has no shadow
+ * - IS_SHADOW: This interrupt is a shadow of another per-cpu interrupt
+ * - else: This is a per-cpu interrupt whose shadow is the value
*/
static struct {
- void __iomem *membase;
- u8 interrupt_shadows[IRQ_COUNT];
+ void __iomem *membase;
+ struct irq_domain *domain;
+ struct irq_domain *ipi_domain;
+ u8 interrupt_shadows[IRQ_COUNT];
} econet_intc __ro_after_init;
static DEFINE_RAW_SPINLOCK(irq_lock);
@@ -150,6 +156,56 @@ static void econet_intc_from_parent(struct irq_desc *desc)
chained_irq_exit(chip, desc);
}
+/*
+ * When in VEIC mode, the CPU jumps to a handler in the vector table.
+ * The only way to know which interrupt is being triggered is from the vector table offset that
+ * has been jumped to. Reading REG_PENDING(0|1) will tell you which interrupts are currently
+ * pending in the intc, but that will not tell you which one the intc wants you to process
+ * right now. And if you are not processing the exact interrupt that the intc wants you to be
+ * processing, you might be on the wrong VPE. You can't tell which VPE any given REG_PENDING
+ * interrupt is intended for (shadow IRQ numbers are for masking only, they never flag as
+ * pending).
+ *
+ * Consequently, this little ritual of generating n handler functions and registering one per
+ * interrupt is unavoidable.
+ */
+#define X(irq) \
+ static void econet_irq_dispatch ## irq (void) \
+ { \
+ do_domain_IRQ(econet_intc.domain, irq); \
+ }
+
+ X(0) X(1) X(2) X(3) X(4) X(5) X(6) X(7) X(8) X(9)
+X(10) X(11) X(12) X(13) X(14) X(15) X(16) X(17) X(18) X(19)
+X(20) X(21) X(22) X(23) X(24) X(25) X(26) X(27) X(28) X(29)
+X(30) X(31) X(32) X(33) X(34) X(35) X(36) X(37) X(38) X(39)
+
+#undef X
+#define X(irq) econet_irq_dispatch ## irq,
+
+static void (* const econet_irq_dispatchers[])(void) = {
+ X(0) X(1) X(2) X(3) X(4) X(5) X(6) X(7) X(8) X(9)
+ X(10) X(11) X(12) X(13) X(14) X(15) X(16) X(17) X(18) X(19)
+ X(20) X(21) X(22) X(23) X(24) X(25) X(26) X(27) X(28) X(29)
+ X(30) X(31) X(32) X(33) X(34) X(35) X(36) X(37) X(38) X(39)
+};
+
+/* Likewise, we do the same for the 2 IPI IRQs so that we can route them back */
+static void econet_cpu_dispatch0(void)
+{
+ do_domain_IRQ(econet_intc.ipi_domain, 0);
+}
+
+static void econet_cpu_dispatch1(void)
+{
+ do_domain_IRQ(econet_intc.ipi_domain, 1);
+}
+
+static void (* const econet_cpu_dispatchers[])(void) = {
+ econet_cpu_dispatch0,
+ econet_cpu_dispatch1,
+};
+
static const struct irq_chip econet_irq_chip;
static int econet_intc_map(struct irq_domain *d, u32 irq, irq_hw_number_t hwirq)
@@ -174,6 +230,10 @@ static int econet_intc_map(struct irq_domain *d, u32 irq, irq_hw_number_t hwirq)
}
irq_set_chip_data(irq, NULL);
+
+ if (cpu_has_veic)
+ set_vi_handler(hwirq + 1, econet_irq_dispatchers[hwirq]);
+
return 0;
}
@@ -249,6 +309,100 @@ static int __init get_shadow_interrupts(struct device_node *node)
return 0;
}
+/**
+ * econet_cpu_init() - configure routing of CPU interrupts to the correct domain.
+ * @node: The devicetree node of this interrupt controller.
+ *
+ * Interrupts that originate from the CPU are unconditionally unmasked here and are re-routed back
+ * to the IPI irq_domain in the CPU intc. Masking still takes place but the CPU intc is in charge
+ * of it, using the mask bits of the c0_status register.
+ *
+ * Note that because IP2 ... IP7 are repurposed as Interrupt Priority Level, only the two IPI
+ * interrupts are actually supported.
+ */
+static int __init econet_cpu_init(struct device_node *node)
+{
+ const char *field = "econet,cpu-interrupt-map";
+ struct device_node *parent_intc;
+ int map_size;
+ u32 mask;
+
+ map_size = of_property_count_u32_elems(node, field);
+
+ if (map_size <= 0) {
+ return 0;
+ } else if (map_size % 2) {
+ pr_err("%pOF: %s count is odd, ignoring\n", node, field);
+ return 0;
+ }
+
+ u32 *maps __free(kfree) = kmalloc_array(map_size, sizeof(u32), GFP_KERNEL);
+ if (!maps)
+ return -ENOMEM;
+
+ if (of_property_read_u32_array(node, field, maps, map_size)) {
+ pr_err("%pOF: Failed to read %s\n", node, field);
+ return -EINVAL;
+ }
+
+ /* Validation */
+ for (int i = 0; i < map_size; i += 2) {
+ u32 receive = maps[i];
+ u32 dispatch = maps[i + 1];
+ u8 shadow;
+
+ if (receive >= IRQ_COUNT) {
+ pr_err("%pOF: Entry %d:%d in %s (%u) is out of bounds\n",
+ node, i, 0, field, receive);
+ return -EINVAL;
+ }
+
+ shadow = econet_intc.interrupt_shadows[receive];
+ if (shadow != NOT_PERCPU && shadow >= IRQ_COUNT) {
+ pr_err("%pOF: Entry %d:%d in %s (%u) has invalid shadow (%d)\n",
+ node, i, 0, field, receive, shadow);
+ return -EINVAL;
+ }
+
+ if (dispatch >= ARRAY_SIZE(econet_cpu_dispatchers)) {
+ pr_err("%pOF: Entry %d:%d in %s (%u) is out of bounds only IPI interrupts are supported\n",
+ node, i, 1, field, dispatch);
+ return -EINVAL;
+ }
+ }
+
+ parent_intc = of_irq_find_parent(node);
+ if (!parent_intc) {
+ pr_err("%pOF: Failed to find parent %s\n", node, "IRQ device");
+ return -ENODEV;
+ }
+
+ econet_intc.ipi_domain = irq_find_matching_host(parent_intc, DOMAIN_BUS_IPI);
+ if (!econet_intc.ipi_domain) {
+ pr_err("%pOF: Failed to find parent %s\n", node, "IPI domain");
+ return -ENODEV;
+ }
+
+ mask = 0;
+ for (int i = 0; i < map_size; i += 2) {
+ u32 receive = maps[i];
+ u32 dispatch = maps[i + 1];
+ u8 shadow;
+
+ set_vi_handler(receive + 1, econet_cpu_dispatchers[dispatch]);
+
+ mask |= BIT(receive);
+
+ shadow = econet_intc.interrupt_shadows[receive];
+ if (shadow != NOT_PERCPU)
+ mask |= BIT(shadow);
+ }
+
+ econet_wreg(REG_MASK0, mask, mask);
+
+ return 0;
+}
+
static int __init econet_intc_of_init(struct device_node *node, struct device_node *parent)
{
struct irq_domain *domain;
@@ -294,7 +448,23 @@ static int __init econet_intc_of_init(struct device_node *node, struct device_no
goto err_unmap;
}
- irq_set_chained_handler_and_data(irq, econet_intc_from_parent, domain);
+ /*
+ * 34K Manual (MD00534) Section 6.3.1.3 rev 1.13 page 136:
+ * In VEIC mode, IP2 ... IP7 are repurposed as Interrupt Priority Level. The controller
+ * will filter incoming interrupts whose priority is lower than the IPL number. Therefore
+ * we must not set any of these bits. We avoid setting IP2 by not actually chaining this
+ * intc to the CPU intc.
+ */
+ if (cpu_has_veic) {
+ ret = econet_cpu_init(node);
+
+ if (ret)
+ return ret;
+ } else {
+ irq_set_chained_handler_and_data(irq, econet_intc_from_parent, domain);
+ }
+
+ econet_intc.domain = domain;
return 0;
diff --git a/drivers/irqchip/irq-gic-v3-its.c b/drivers/irqchip/irq-gic-v3-its.c
index 291d7668cc8d..b57d81ad33a0 100644
--- a/drivers/irqchip/irq-gic-v3-its.c
+++ b/drivers/irqchip/irq-gic-v3-its.c
@@ -4784,8 +4784,7 @@ static bool __maybe_unused its_enable_quirk_cavium_22375(void *data)
struct its_node *its = data;
/* erratum 22375: only alloc 8MB table size (20 bits) */
- its->typer &= ~GITS_TYPER_DEVBITS;
- its->typer |= FIELD_PREP(GITS_TYPER_DEVBITS, 20 - 1);
+ FIELD_MODIFY(GITS_TYPER_DEVBITS, &its->typer, 20 - 1);
its->flags |= ITS_FLAGS_WORKAROUND_CAVIUM_22375;
return true;
@@ -4805,8 +4804,7 @@ static bool __maybe_unused its_enable_quirk_qdf2400_e0065(void *data)
struct its_node *its = data;
/* On QDF2400, the size of the ITE is 16Bytes */
- its->typer &= ~GITS_TYPER_ITT_ENTRY_SIZE;
- its->typer |= FIELD_PREP(GITS_TYPER_ITT_ENTRY_SIZE, 16 - 1);
+ FIELD_MODIFY(GITS_TYPER_ITT_ENTRY_SIZE, &its->typer, 16 - 1);
return true;
}
@@ -4840,10 +4838,8 @@ static bool __maybe_unused its_enable_quirk_socionext_synquacer(void *data)
its->get_msi_base = its_irq_get_msi_base_pre_its;
ids = ilog2(pre_its_window[1]) - 2;
- if (device_ids(its) > ids) {
- its->typer &= ~GITS_TYPER_DEVBITS;
- its->typer |= FIELD_PREP(GITS_TYPER_DEVBITS, ids - 1);
- }
+ if (device_ids(its) > ids)
+ FIELD_MODIFY(GITS_TYPER_DEVBITS, &its->typer, ids - 1);
/* the pre-ITS breaks isolation, so disable MSI remapping */
its->msi_domain_flags &= ~IRQ_DOMAIN_FLAG_ISOLATED_MSI;
@@ -5837,6 +5833,7 @@ int __init its_init(struct fwnode_handle *handle, struct rdists *rdists,
its_acpi_probe();
if (list_empty(&its_nodes)) {
+ rdists->has_vlpis = false;
pr_warn("ITS: No ITS available, not enabling LPIs\n");
return -ENXIO;
}
diff --git a/drivers/irqchip/irq-loongarch-avec.c b/drivers/irqchip/irq-loongarch-avec.c
index 758262fd5bd6..53d7d23af9bb 100644
--- a/drivers/irqchip/irq-loongarch-avec.c
+++ b/drivers/irqchip/irq-loongarch-avec.c
@@ -24,7 +24,6 @@
#define VECTORS_PER_REG 64
#define IRR_VECTOR_MASK 0xffUL
#define IRR_INVALID_MASK 0x80000000UL
-#define AVEC_MSG_OFFSET 0x100000
#ifdef CONFIG_SMP
struct pending_list {
@@ -47,15 +46,6 @@ struct avecintc_chip {
static struct avecintc_chip loongarch_avec;
-struct avecintc_data {
- struct list_head entry;
- unsigned int cpu;
- unsigned int vec;
- unsigned int prev_cpu;
- unsigned int prev_vec;
- unsigned int moving;
-};
-
static inline void avecintc_enable(void)
{
#ifdef CONFIG_MACH_LOONGSON64
@@ -87,7 +77,7 @@ static inline void pending_list_init(int cpu)
INIT_LIST_HEAD(&plist->head);
}
-static void avecintc_sync(struct avecintc_data *adata)
+void avecintc_sync(struct avecintc_data *adata)
{
struct pending_list *plist;
@@ -111,7 +101,7 @@ static int avecintc_set_affinity(struct irq_data *data, const struct cpumask *de
return -EBUSY;
if (cpu_online(adata->cpu) && cpumask_test_cpu(adata->cpu, dest))
- return 0;
+ return IRQ_SET_MASK_OK_DONE;
cpumask_and(&intersect_mask, dest, cpu_online_mask);
@@ -123,7 +113,8 @@ static int avecintc_set_affinity(struct irq_data *data, const struct cpumask *de
adata->cpu = cpu;
adata->vec = vector;
per_cpu_ptr(irq_map, adata->cpu)[adata->vec] = irq_data_to_desc(data);
- avecintc_sync(adata);
+ if (!cpu_has_redirectint)
+ avecintc_sync(adata);
}
irq_data_update_effective_affinity(data, cpumask_of(cpu));
@@ -415,6 +406,9 @@ static int __init pch_msi_parse_madt(union acpi_subtable_headers *header,
static inline int __init acpi_cascade_irqdomain_init(void)
{
+ if (cpu_has_redirectint)
+ return redirect_acpi_init(loongarch_avec.domain);
+
return acpi_table_parse_madt(ACPI_MADT_TYPE_MSI_PIC, pch_msi_parse_madt, 1);
}
diff --git a/drivers/irqchip/irq-loongarch-ir.c b/drivers/irqchip/irq-loongarch-ir.c
new file mode 100644
index 000000000000..21c649a89a70
--- /dev/null
+++ b/drivers/irqchip/irq-loongarch-ir.c
@@ -0,0 +1,537 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2024-2026 Loongson Technologies, Inc.
+ */
+#define pr_fmt(fmt) "redirect: " fmt
+
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/io-64-nonatomic-lo-hi.h>
+#include <linux/irq.h>
+#include <linux/irqchip.h>
+#include <linux/irqchip/irq-msi-lib.h>
+#include <linux/irqdomain.h>
+#include <linux/kernel.h>
+#include <linux/msi.h>
+#include <linux/spinlock.h>
+
+#include <asm/irq.h>
+#include <asm/loongarch.h>
+#include <asm/loongson.h>
+#include <asm/numa.h>
+#include <asm/setup.h>
+
+#include "irq-loongson.h"
+
+#define LOONGARCH_IOCSR_REDIRECT_CFG 0x15e0
+#define LOONGARCH_IOCSR_REDIRECT_TBR 0x15e8 /* IRT BASE REG */
+#define LOONGARCH_IOCSR_REDIRECT_CQB 0x15f0 /* IRT CACHE QUEUE BASE */
+#define LOONGARCH_IOCSR_REDIRECT_CQH 0x15f8 /* IRT CACHE QUEUE HEAD, 32bit */
+#define LOONGARCH_IOCSR_REDIRECT_CQT 0x15fc /* IRT CACHE QUEUE TAIL, 32bit */
+
+#define CQB_ADDR_MASK GENMASK_U64(47, 12)
+#define CQB_SIZE_MASK 0xf
+
+#define GPID_ADDR_MASK GENMASK_U64(47, 6)
+#define GPID_ADDR_SHIFT 6
+
+#define INVALID_INDEX 0
+#define CFG_DISABLE_IDLE 2
+
+#define MAX_IR_ENGINES 16
+
+struct redirect_entry {
+ struct {
+ u64 valid : 1,
+ res1 : 5,
+ gpid : 42,
+ res2 : 8,
+ vector : 8;
+ } lo;
+ u64 hi;
+};
+
+#define IRD_ENTRY_SIZE sizeof(struct redirect_entry)
+#define IRD_ENTRIES SZ_64K
+#define IRD_TABLE_PAGE_ORDER get_order(IRD_ENTRIES * IRD_ENTRY_SIZE)
+
+struct redirect_cmd {
+ union {
+ u64 cmd_info;
+ struct {
+ u64 res1 : 4,
+ type : 1,
+ need_notice : 1,
+ pad1 : 2,
+ index : 16,
+ pad2 : 40;
+ } index;
+ };
+ u64 notice_addr;
+};
+
+#define IRD_CMD_SIZE sizeof(struct redirect_cmd)
+#define INV_QUEUE_SIZE SZ_4K
+#define INV_QUEUE_PAGE_ORDER get_order(INV_QUEUE_SIZE * IRD_CMD_SIZE)
+
+struct redirect_gpid {
+ u64 pir[4]; /* Pending interrupt requested */
+ u8 en : 1, /* Doorbell */
+ res1 : 7;
+ u8 irqnum;
+ u16 res2;
+ u32 dstcpu;
+ u32 rsvd[6];
+};
+
+struct redirect_table {
+ struct redirect_entry *table;
+ unsigned long *bitmap;
+ raw_spinlock_t lock;
+};
+
+struct redirect_queue {
+ struct redirect_cmd *cmd_base;
+ int head;
+ int tail;
+ raw_spinlock_t lock;
+};
+
+struct redirect_desc {
+ struct redirect_table ird_table;
+ struct redirect_queue inv_queue;
+ int node;
+};
+
+struct redirect_item {
+ int index;
+ struct redirect_desc *irde;
+ struct redirect_gpid *gpid;
+};
+
+static struct irq_domain *redirect_domain;
+static struct redirect_desc redirect_descs[MAX_IR_ENGINES];
+
+static phys_addr_t msi_base_addr;
+static phys_addr_t redirect_reg_base = LOONGSON_REG_BASE;
+
+#ifdef CONFIG_32BIT
+
+#define REDIRECT_REG(reg, node) \
+ ((void __iomem *)(IO_BASE | redirect_reg_base | (reg)))
+
+#else
+
+#define REDIRECT_REG(reg, node) \
+ ((void __iomem *)(IO_BASE | redirect_reg_base | (u64)(node) << NODE_ADDRSPACE_SHIFT | (reg)))
+
+#endif
+
+static inline u32 redirect_read_reg32(u32 node, u32 reg)
+{
+ return readl(REDIRECT_REG(reg, node));
+}
+
+static inline void redirect_write_reg32(u32 node, u32 val, u32 reg)
+{
+ writel(val, REDIRECT_REG(reg, node));
+}
+
+static inline void redirect_write_reg64(u32 node, u64 val, u32 reg)
+{
+ writeq(val, REDIRECT_REG(reg, node));
+}
+
+static inline struct redirect_entry *item_get_entry(struct redirect_item *item)
+{
+ return item->irde->ird_table.table + item->index;
+}
+
+static inline bool invalid_queue_is_full(int node, u32 *tail)
+{
+ u32 head = redirect_read_reg32(node, LOONGARCH_IOCSR_REDIRECT_CQH);
+
+ *tail = redirect_read_reg32(node, LOONGARCH_IOCSR_REDIRECT_CQT);
+
+ return head == ((*tail + 1) % INV_QUEUE_SIZE);
+}
+
+static void invalid_enqueue(struct redirect_item *item, struct redirect_cmd *cmd)
+{
+ struct redirect_queue *inv_queue = &item->irde->inv_queue;
+ u32 tail;
+
+ guard(raw_spinlock_irqsave)(&inv_queue->lock);
+
+ while (invalid_queue_is_full(item->irde->node, &tail))
+ cpu_relax();
+
+ memcpy(&inv_queue->cmd_base[tail], cmd, sizeof(*cmd));
+
+ redirect_write_reg32(item->irde->node, (tail + 1) % INV_QUEUE_SIZE, LOONGARCH_IOCSR_REDIRECT_CQT);
+}
+
+static void irde_invalidate_entry(struct redirect_item *item)
+{
+ struct redirect_cmd cmd;
+ u64 raddr = 0;
+
+ cmd.cmd_info = 0;
+ cmd.index.type = INVALID_INDEX;
+ cmd.index.need_notice = 1;
+ cmd.index.index = item->index;
+ cmd.notice_addr = (u64)(__pa(&raddr));
+
+ invalid_enqueue(item, &cmd);
+
+ /*
+ * The CPU needs to wait here for cmd to complete, and it determines this
+ * by checking whether the invalidation queue has already written a valid value
+ * to cmd.notice_addr.
+ */
+ while (!raddr)
+ cpu_relax();
+}
+
+static inline struct avecintc_data *irq_data_get_avec_data(struct irq_data *data)
+{
+ return data->parent_data->chip_data;
+}
+
+static int redirect_table_alloc(int node, u32 nr_irqs)
+{
+ struct redirect_table *ird_table = &redirect_descs[node].ird_table;
+ int index, order = 0;
+
+ if (nr_irqs > 1) {
+ nr_irqs = __roundup_pow_of_two(nr_irqs);
+ order = ilog2(nr_irqs);
+ }
+
+ guard(raw_spinlock_irqsave)(&ird_table->lock);
+
+ index = bitmap_find_free_region(ird_table->bitmap, IRD_ENTRIES, order);
+ if (index < 0) {
+ pr_err("No redirect entry to use\n");
+ return -EINVAL;
+ }
+
+ return index;
+}
+
+static void redirect_table_free(struct redirect_item *item)
+{
+ struct redirect_table *ird_table = &item->irde->ird_table;
+ struct redirect_entry *entry = item_get_entry(item);
+
+ memset(entry, 0, sizeof(*entry));
+
+ scoped_guard(raw_spinlock_irq, &ird_table->lock)
+ clear_bit(item->index, ird_table->bitmap);
+
+ kfree(item->gpid);
+
+ irde_invalidate_entry(item);
+}
+
+static inline void redirect_domain_prepare_entry(struct redirect_item *item,
+ struct avecintc_data *adata)
+{
+ struct redirect_entry *entry = item_get_entry(item);
+
+ item->gpid->en = 1;
+ item->gpid->dstcpu = adata->cpu;
+ item->gpid->irqnum = adata->vec;
+
+ entry->lo.valid = 1;
+ entry->lo.vector = 0xff;
+ entry->lo.gpid = ((unsigned long)item->gpid & GPID_ADDR_MASK) >> GPID_ADDR_SHIFT;
+}
+
+static void redirect_free_resources(struct irq_domain *domain,
+ unsigned int virq, unsigned int nr_irqs)
+{
+ for (int i = 0; i < nr_irqs; i++) {
+ struct irq_data *irq_data = irq_domain_get_irq_data(domain, virq + i);
+
+ if (irq_data && irq_data->chip_data) {
+ struct redirect_item *item = irq_data->chip_data;
+
+ redirect_table_free(item);
+ kfree(item);
+ }
+ }
+}
+
+#ifdef CONFIG_SMP
+static int redirect_set_affinity(struct irq_data *data, const struct cpumask *dest, bool force)
+{
+ struct avecintc_data *adata = irq_data_get_avec_data(data);
+ struct redirect_item *item = data->chip_data;
+ int ret;
+
+ ret = irq_chip_set_affinity_parent(data, dest, force);
+ switch (ret) {
+ case IRQ_SET_MASK_OK:
+ break;
+ case IRQ_SET_MASK_OK_DONE:
+ return ret;
+ default:
+ pr_err("IRDE: set_affinity error %d\n", ret);
+ return ret;
+ }
+
+ redirect_domain_prepare_entry(item, adata);
+ irde_invalidate_entry(item);
+ avecintc_sync(adata);
+
+ return IRQ_SET_MASK_OK;
+}
+#endif
+
+static void redirect_compose_msi_msg(struct irq_data *d, struct msi_msg *msg)
+{
+ struct redirect_item *item = irq_data_get_irq_chip_data(d);
+
+ msg->address_hi = 0x0;
+ msg->address_lo = (msi_base_addr | 1 << 2);
+ msg->data = item->index;
+}
+
+static struct irq_chip loongarch_redirect_chip = {
+ .name = "REDIRECT",
+ .irq_ack = irq_chip_ack_parent,
+ .irq_mask = irq_chip_mask_parent,
+ .irq_unmask = irq_chip_unmask_parent,
+#ifdef CONFIG_SMP
+ .irq_set_affinity = redirect_set_affinity,
+#endif
+ .irq_compose_msi_msg = redirect_compose_msi_msg,
+};
+
+static int redirect_domain_alloc(struct irq_domain *domain, unsigned int virq,
+ unsigned int nr_irqs, void *arg)
+{
+ msi_alloc_info_t *info = arg;
+ int ret, i, node, index;
+
+ node = dev_to_node(info->desc->dev);
+
+ ret = irq_domain_alloc_irqs_parent(domain, virq, nr_irqs, arg);
+ if (ret < 0)
+ return ret;
+
+ index = redirect_table_alloc(node, nr_irqs);
+ if (index < 0) {
+ pr_err("Alloc redirect table entry failed\n");
+ return -EINVAL;
+ }
+
+ for (i = 0; i < nr_irqs; i++) {
+ struct irq_data *irq_data = irq_domain_get_irq_data(domain, virq + i);
+ struct redirect_item *item;
+
+ item = kzalloc(sizeof(*item), GFP_KERNEL);
+ if (!item) {
+ pr_err("Alloc redirect descriptor failed\n");
+ goto out_free_resources;
+ }
+ item->irde = &redirect_descs[node];
+
+ /*
+ * Only bits 47:6 of the GPID are passed to the controller,
+ * 64-byte alignment must be guarantee and make kzalloc can
+ * align to the respective size.
+ */
+ static_assert(sizeof(*item->gpid) == 64);
+ item->gpid = kzalloc_node(sizeof(*item->gpid), GFP_KERNEL, node);
+ if (!item->gpid) {
+ pr_err("Alloc redirect GPID failed\n");
+ goto out_free_resources;
+ }
+ item->index = index + i;
+
+ irq_data->chip_data = item;
+ irq_data->chip = &loongarch_redirect_chip;
+
+ redirect_domain_prepare_entry(item, irq_data_get_avec_data(irq_data));
+ }
+
+ return 0;
+
+out_free_resources:
+ redirect_free_resources(domain, virq, nr_irqs);
+ irq_domain_free_irqs_common(domain, virq, nr_irqs);
+
+ return -ENOMEM;
+}
+
+static void redirect_domain_free(struct irq_domain *domain, unsigned int virq, unsigned int nr_irqs)
+{
+ redirect_free_resources(domain, virq, nr_irqs);
+ return irq_domain_free_irqs_common(domain, virq, nr_irqs);
+}
+
+static const struct irq_domain_ops redirect_domain_ops = {
+ .alloc = redirect_domain_alloc,
+ .free = redirect_domain_free,
+ .select = msi_lib_irq_domain_select,
+};
+
+static int redirect_table_init(struct redirect_desc *irde)
+{
+ struct redirect_table *ird_table = &irde->ird_table;
+ unsigned long *bitmap;
+ struct folio *folio;
+
+ folio = __folio_alloc_node(GFP_KERNEL | __GFP_ZERO, IRD_TABLE_PAGE_ORDER, irde->node);
+ if (!folio) {
+ pr_err("Node [%d] redirect table alloc pages failed!\n", irde->node);
+ return -ENOMEM;
+ }
+ ird_table->table = folio_address(folio);
+
+ bitmap = bitmap_zalloc(IRD_ENTRIES, GFP_KERNEL);
+ if (!bitmap) {
+ pr_err("Node [%d] redirect table bitmap alloc pages failed!\n", irde->node);
+ folio_put(folio);
+ ird_table->table = NULL;
+ return -ENOMEM;
+ }
+ ird_table->bitmap = bitmap;
+
+ raw_spin_lock_init(&ird_table->lock);
+
+ return 0;
+}
+
+static int redirect_queue_init(struct redirect_desc *irde)
+{
+ struct redirect_queue *inv_queue = &irde->inv_queue;
+ struct folio *folio;
+
+ folio = __folio_alloc_node(GFP_KERNEL | __GFP_ZERO, INV_QUEUE_PAGE_ORDER, irde->node);
+ if (!folio) {
+ pr_err("Node [%d] invalid queue alloc pages failed!\n", irde->node);
+ return -ENOMEM;
+ }
+
+ inv_queue->cmd_base = folio_address(folio);
+ inv_queue->head = 0;
+ inv_queue->tail = 0;
+ raw_spin_lock_init(&inv_queue->lock);
+
+ return 0;
+}
+
+static void redirect_irde_cfg(struct redirect_desc *irde)
+{
+ redirect_write_reg64(irde->node, CFG_DISABLE_IDLE, LOONGARCH_IOCSR_REDIRECT_CFG);
+ redirect_write_reg64(irde->node, __pa(irde->ird_table.table), LOONGARCH_IOCSR_REDIRECT_TBR);
+ redirect_write_reg32(irde->node, 0, LOONGARCH_IOCSR_REDIRECT_CQH);
+ redirect_write_reg32(irde->node, 0, LOONGARCH_IOCSR_REDIRECT_CQT);
+ redirect_write_reg64(irde->node, ((unsigned long)irde->inv_queue.cmd_base & CQB_ADDR_MASK) |
+ CQB_SIZE_MASK, LOONGARCH_IOCSR_REDIRECT_CQB);
+}
+
+static void __init redirect_irde_free(struct redirect_desc *irde)
+{
+ struct redirect_table *ird_table = &redirect_descs->ird_table;
+ struct redirect_queue *inv_queue = &redirect_descs->inv_queue;
+
+ if (ird_table->table) {
+ folio_put(virt_to_folio(ird_table->table));
+ ird_table->table = NULL;
+ }
+
+ if (ird_table->bitmap) {
+ bitmap_free(ird_table->bitmap);
+ ird_table->bitmap = NULL;
+ }
+
+ if (inv_queue->cmd_base) {
+ folio_put(virt_to_folio(inv_queue->cmd_base));
+ inv_queue->cmd_base = NULL;
+ }
+}
+
+static int __init redirect_irde_init(int node)
+{
+ struct redirect_desc *irde = &redirect_descs[node];
+ int ret;
+
+ irde->node = node;
+
+ ret = redirect_table_init(irde);
+ if (ret)
+ return ret;
+
+ ret = redirect_queue_init(irde);
+ if (ret) {
+ redirect_irde_free(irde);
+ return ret;
+ }
+
+ redirect_irde_cfg(irde);
+
+ return 0;
+}
+
+static int __init pch_msi_parse_madt(union acpi_subtable_headers *header, const unsigned long end)
+{
+ struct acpi_madt_msi_pic *pchmsi_entry = (struct acpi_madt_msi_pic *)header;
+
+ msi_base_addr = pchmsi_entry->msg_address - AVEC_MSG_OFFSET;
+
+ return pch_msi_acpi_init_avec(redirect_domain);
+}
+
+static int __init acpi_cascade_irqdomain_init(void)
+{
+ return acpi_table_parse_madt(ACPI_MADT_TYPE_MSI_PIC, pch_msi_parse_madt, 1);
+}
+
+int __init redirect_acpi_init(struct irq_domain *parent)
+{
+ struct fwnode_handle *fwnode;
+ int ret = -EINVAL, node;
+
+ fwnode = irq_domain_alloc_named_fwnode("redirect");
+ if (!fwnode) {
+ pr_err("Unable to alloc redirect domain handle\n");
+ goto fail;
+ }
+
+ redirect_domain = irq_domain_create_hierarchy(parent, 0, IRD_ENTRIES, fwnode,
+ &redirect_domain_ops, redirect_descs);
+ if (!redirect_domain) {
+ pr_err("Unable to alloc redirect domain\n");
+ goto out_free_fwnode;
+ }
+
+ for_each_node_mask(node, node_possible_map) {
+ ret = redirect_irde_init(node);
+ if (ret)
+ goto out_clear_irde;
+ }
+
+ ret = acpi_cascade_irqdomain_init();
+ if (ret < 0) {
+ pr_err("Failed to cascade IRQ domain, ret=%d\n", ret);
+ goto out_clear_irde;
+ }
+
+ pr_info("init succeeded\n");
+
+ return 0;
+
+out_clear_irde:
+ for_each_node_mask(node, node_possible_map) {
+ redirect_irde_free(&redirect_descs[node]);
+ }
+ irq_domain_remove(redirect_domain);
+out_free_fwnode:
+ irq_domain_free_fwnode(fwnode);
+fail:
+ return ret;
+}
diff --git a/drivers/irqchip/irq-loongson.h b/drivers/irqchip/irq-loongson.h
index 11fa138d1f44..dd37cd7f453d 100644
--- a/drivers/irqchip/irq-loongson.h
+++ b/drivers/irqchip/irq-loongson.h
@@ -6,6 +6,17 @@
#ifndef _DRIVERS_IRQCHIP_IRQ_LOONGSON_H
#define _DRIVERS_IRQCHIP_IRQ_LOONGSON_H
+#define AVEC_MSG_OFFSET 0x100000
+
+struct avecintc_data {
+ struct list_head entry;
+ unsigned int cpu;
+ unsigned int vec;
+ unsigned int prev_cpu;
+ unsigned int prev_vec;
+ unsigned int moving;
+};
+
int find_pch_pic(u32 gsi);
int liointc_acpi_init(struct irq_domain *parent,
@@ -14,6 +25,8 @@ int eiointc_acpi_init(struct irq_domain *parent,
struct acpi_madt_eio_pic *acpi_eiointc);
int avecintc_acpi_init(struct irq_domain *parent);
+int redirect_acpi_init(struct irq_domain *parent);
+
int htvec_acpi_init(struct irq_domain *parent,
struct acpi_madt_ht_pic *acpi_htvec);
int pch_lpc_acpi_init(struct irq_domain *parent,
@@ -24,4 +37,6 @@ int pch_msi_acpi_init(struct irq_domain *parent,
struct acpi_madt_msi_pic *acpi_pchmsi);
int pch_msi_acpi_init_avec(struct irq_domain *parent);
+void avecintc_sync(struct avecintc_data *adata);
+
#endif /* _DRIVERS_IRQCHIP_IRQ_LOONGSON_H */
diff --git a/drivers/irqchip/irq-meson-gpio.c b/drivers/irqchip/irq-meson-gpio.c
index 74a376ef452e..91a9c337fe6d 100644
--- a/drivers/irqchip/irq-meson-gpio.c
+++ b/drivers/irqchip/irq-meson-gpio.c
@@ -27,6 +27,10 @@
/* use for A1 like chips */
#define REG_PIN_A1_SEL 0x04
+/* use for A9 like chips */
+#define REG_A9_AO_POL 0x00
+#define REG_A9_AO_EDGE 0x30
+
/*
* Note: The S905X3 datasheet reports that BOTH_EDGE is controlled by
* bits 24 to 31. Tests on the actual HW show that these bits are
@@ -53,6 +57,8 @@ static void meson_a1_gpio_irq_sel_pin(struct meson_gpio_irq_controller *ctl,
static void meson_a1_gpio_irq_init(struct meson_gpio_irq_controller *ctl);
static int meson8_gpio_irq_set_type(struct meson_gpio_irq_controller *ctl,
unsigned int type, u32 *channel_hwirq);
+static int meson_a9_ao_gpio_irq_set_type(struct meson_gpio_irq_controller *ctl,
+ unsigned int type, u32 *channel_hwirq);
static int meson_s4_gpio_irq_set_type(struct meson_gpio_irq_controller *ctl,
unsigned int type, u32 *channel_hwirq);
@@ -116,6 +122,18 @@ struct meson_gpio_irq_params {
.pin_sel_mask = 0xff, \
.nr_channels = 2, \
+#define INIT_MESON_A9_AO_COMMON_DATA(irqs) \
+ INIT_MESON_COMMON(irqs, meson_a1_gpio_irq_init, \
+ meson_a1_gpio_irq_sel_pin, \
+ meson_a9_ao_gpio_irq_set_type) \
+ .support_edge_both = true, \
+ .edge_both_offset = 0, \
+ .edge_single_offset = 0, \
+ .edge_pol_reg = 0x2c, \
+ .pol_low_offset = 0, \
+ .pin_sel_mask = 0xff, \
+ .nr_channels = 20, \
+
#define INIT_MESON_S4_COMMON_DATA(irqs) \
INIT_MESON_COMMON(irqs, meson_a1_gpio_irq_init, \
meson_a1_gpio_irq_sel_pin, \
@@ -170,6 +188,14 @@ static const struct meson_gpio_irq_params a5_params = {
INIT_MESON_S4_COMMON_DATA(99)
};
+static const struct meson_gpio_irq_params a9_params = {
+ INIT_MESON_S4_COMMON_DATA(96)
+};
+
+static const struct meson_gpio_irq_params a9_ao_params = {
+ INIT_MESON_A9_AO_COMMON_DATA(39)
+};
+
static const struct meson_gpio_irq_params s4_params = {
INIT_MESON_S4_COMMON_DATA(82)
};
@@ -203,6 +229,8 @@ static const struct of_device_id meson_irq_gpio_matches[] __maybe_unused = {
{ .compatible = "amlogic,a4-gpio-ao-intc", .data = &a4_ao_params },
{ .compatible = "amlogic,a4-gpio-intc", .data = &a4_params },
{ .compatible = "amlogic,a5-gpio-intc", .data = &a5_params },
+ { .compatible = "amlogic,a9-gpio-ao-intc", .data = &a9_ao_params },
+ { .compatible = "amlogic,a9-gpio-intc", .data = &a9_params },
{ .compatible = "amlogic,s6-gpio-intc", .data = &s6_params },
{ .compatible = "amlogic,s7-gpio-intc", .data = &s7_params },
{ .compatible = "amlogic,s7d-gpio-intc", .data = &s7_params },
@@ -375,6 +403,55 @@ static int meson8_gpio_irq_set_type(struct meson_gpio_irq_controller *ctl,
return 0;
}
+/*
+ * gpio irq relative registers for a9_ao
+ * -PADCTRL_GPIO_IRQ_CTRL0
+ * bit[31]: enable/disable all the irq lines
+ * bit[0-19]: polarity trigger
+ *
+ * -PADCTRL_GPIO_IRQ_CTRL[X]
+ * bit[0-5]: 6 bits to choose gpio source for irq line 2*[X] - 2
+ * bit[16-21]:6 bits to choose gpio source for irq line 2*[X] - 1
+ * where X = 1-10
+ *
+ * -PADCTRL_GPIO_IRQ_CTRL[11]
+ * bit[0-19]: both edge trigger
+ *
+ * -PADCTRL_GPIO_IRQ_CTRL[12]
+ * bit[0-19]: single edge trigger
+ */
+static int meson_a9_ao_gpio_irq_set_type(struct meson_gpio_irq_controller *ctl,
+ unsigned int type, u32 *channel_hwirq)
+{
+ const struct meson_gpio_irq_params *params = ctl->params;
+ unsigned int idx;
+ u32 val;
+
+ idx = meson_gpio_irq_get_channel_idx(ctl, channel_hwirq);
+
+ type &= IRQ_TYPE_SENSE_MASK;
+
+ meson_gpio_irq_update_bits(ctl, params->edge_pol_reg, BIT(idx), 0);
+
+ if (type == IRQ_TYPE_EDGE_BOTH) {
+ val = BIT(ctl->params->edge_both_offset + idx);
+ meson_gpio_irq_update_bits(ctl, params->edge_pol_reg, val, val);
+ return 0;
+ }
+
+ val = 0;
+ if (type & (IRQ_TYPE_LEVEL_LOW | IRQ_TYPE_EDGE_FALLING))
+ val = BIT(idx);
+ meson_gpio_irq_update_bits(ctl, REG_A9_AO_POL, BIT(idx), val);
+
+ val = 0;
+ if (type & (IRQ_TYPE_EDGE_RISING | IRQ_TYPE_EDGE_FALLING))
+ val = BIT(idx);
+ meson_gpio_irq_update_bits(ctl, REG_A9_AO_EDGE, BIT(idx), val);
+
+ return 0;
+};
+
/*
* gpio irq relative registers for s4
* -PADCTRL_GPIO_IRQ_CTRL0
diff --git a/drivers/irqchip/irq-realtek-rtl.c b/drivers/irqchip/irq-realtek-rtl.c
index 942c1f8c363d..2ae3be7fa633 100644
--- a/drivers/irqchip/irq-realtek-rtl.c
+++ b/drivers/irqchip/irq-realtek-rtl.c
@@ -23,10 +23,10 @@
#define RTL_ICTL_NUM_INPUTS 32
-#define REG(x) (realtek_ictl_base + x)
+#define REG(cpu, x) (realtek_ictl_base[cpu] + x)
static DEFINE_RAW_SPINLOCK(irq_lock);
-static void __iomem *realtek_ictl_base;
+static void __iomem *realtek_ictl_base[NR_CPUS];
/*
* IRR0-IRR3 store 4 bits per interrupt, but Realtek uses inverted numbering,
@@ -37,10 +37,29 @@ static void __iomem *realtek_ictl_base;
#define IRR_OFFSET(idx) (4 * (3 - (idx * 4) / 32))
#define IRR_SHIFT(idx) ((idx * 4) % 32)
-static void write_irr(void __iomem *irr0, int idx, u32 value)
+static inline void enable_gimr(unsigned int cpu, unsigned int hw_irq)
{
- unsigned int offset = IRR_OFFSET(idx);
- unsigned int shift = IRR_SHIFT(idx);
+ u32 gimr;
+
+ gimr = readl(REG(cpu, RTL_ICTL_GIMR));
+ gimr |= BIT(hw_irq);
+ writel(gimr, REG(cpu, RTL_ICTL_GIMR));
+}
+
+static inline void disable_gimr(unsigned int cpu, unsigned int hw_irq)
+{
+ u32 gimr;
+
+ gimr = readl(REG(cpu, RTL_ICTL_GIMR));
+ gimr &= ~BIT(hw_irq);
+ writel(gimr, REG(cpu, RTL_ICTL_GIMR));
+}
+
+static void write_irr(unsigned int cpu, int hw_irq, u32 value)
+{
+ void __iomem *irr0 = REG(cpu, RTL_ICTL_IRR0);
+ unsigned int offset = IRR_OFFSET(hw_irq);
+ unsigned int shift = IRR_SHIFT(hw_irq);
u32 irr;
irr = readl(irr0 + offset) & ~(0xf << shift);
@@ -50,47 +69,51 @@ static void write_irr(void __iomem *irr0, int idx, u32 value)
static void realtek_ictl_unmask_irq(struct irq_data *i)
{
- unsigned long flags;
- u32 value;
+ unsigned int cpu;
- raw_spin_lock_irqsave(&irq_lock, flags);
+ guard(raw_spinlock)(&irq_lock);
+ for_each_cpu(cpu, irq_data_get_effective_affinity_mask(i))
+ enable_gimr(cpu, i->hwirq);
+}
- value = readl(REG(RTL_ICTL_GIMR));
- value |= BIT(i->hwirq);
- writel(value, REG(RTL_ICTL_GIMR));
+static void realtek_ictl_mask_irq(struct irq_data *i)
+{
+ unsigned int cpu;
- raw_spin_unlock_irqrestore(&irq_lock, flags);
+ guard(raw_spinlock)(&irq_lock);
+ for_each_cpu(cpu, irq_data_get_effective_affinity_mask(i))
+ disable_gimr(cpu, i->hwirq);
}
-static void realtek_ictl_mask_irq(struct irq_data *i)
+static int realtek_ictl_irq_affinity(struct irq_data *i, const struct cpumask *dest, bool force)
{
- unsigned long flags;
- u32 value;
+ if (!irqd_irq_masked(i))
+ realtek_ictl_mask_irq(i);
- raw_spin_lock_irqsave(&irq_lock, flags);
+ irq_data_update_effective_affinity(i, dest);
- value = readl(REG(RTL_ICTL_GIMR));
- value &= ~BIT(i->hwirq);
- writel(value, REG(RTL_ICTL_GIMR));
+ if (!irqd_irq_masked(i))
+ realtek_ictl_unmask_irq(i);
- raw_spin_unlock_irqrestore(&irq_lock, flags);
+ return IRQ_SET_MASK_OK;
}
static struct irq_chip realtek_ictl_irq = {
- .name = "realtek-rtl-intc",
- .irq_mask = realtek_ictl_mask_irq,
- .irq_unmask = realtek_ictl_unmask_irq,
+ .name = "realtek-rtl-intc",
+ .irq_mask = realtek_ictl_mask_irq,
+ .irq_unmask = realtek_ictl_unmask_irq,
+ .irq_set_affinity = realtek_ictl_irq_affinity,
};
static int intc_map(struct irq_domain *d, unsigned int irq, irq_hw_number_t hw)
{
- unsigned long flags;
+ unsigned int cpu;
irq_set_chip_and_handler(irq, &realtek_ictl_irq, handle_level_irq);
- raw_spin_lock_irqsave(&irq_lock, flags);
- write_irr(REG(RTL_ICTL_IRR0), hw, 1);
- raw_spin_unlock_irqrestore(&irq_lock, flags);
+ guard(raw_spinlock_irqsave)(&irq_lock);
+ for_each_present_cpu(cpu)
+ write_irr(cpu, hw, 1);
return 0;
}
@@ -103,12 +126,13 @@ static const struct irq_domain_ops irq_domain_ops = {
static void realtek_irq_dispatch(struct irq_desc *desc)
{
struct irq_chip *chip = irq_desc_get_chip(desc);
+ unsigned int cpu = smp_processor_id();
struct irq_domain *domain;
unsigned long pending;
unsigned int soc_int;
chained_irq_enter(chip, desc);
- pending = readl(REG(RTL_ICTL_GIMR)) & readl(REG(RTL_ICTL_GISR));
+ pending = readl(REG(cpu, RTL_ICTL_GIMR)) & readl(REG(cpu, RTL_ICTL_GISR));
if (unlikely(!pending)) {
spurious_interrupt();
@@ -116,7 +140,7 @@ static void realtek_irq_dispatch(struct irq_desc *desc)
}
domain = irq_desc_get_handler_data(desc);
- for_each_set_bit(soc_int, &pending, 32)
+ for_each_set_bit(soc_int, &pending, RTL_ICTL_NUM_INPUTS)
generic_handle_domain_irq(domain, soc_int);
out:
@@ -127,17 +151,19 @@ static int __init realtek_rtl_of_init(struct device_node *node, struct device_no
{
struct of_phandle_args oirq;
struct irq_domain *domain;
- unsigned int soc_irq;
- int parent_irq;
-
- realtek_ictl_base = of_iomap(node, 0);
- if (!realtek_ictl_base)
- return -ENXIO;
-
- /* Disable all cascaded interrupts and clear routing */
- writel(0, REG(RTL_ICTL_GIMR));
- for (soc_irq = 0; soc_irq < RTL_ICTL_NUM_INPUTS; soc_irq++)
- write_irr(REG(RTL_ICTL_IRR0), soc_irq, 0);
+ int cpu, parent_irq;
+
+ for_each_present_cpu(cpu) {
+ realtek_ictl_base[cpu] = of_iomap(node, cpu);
+ if (!realtek_ictl_base[cpu])
+ return -ENXIO;
+
+ /* Disable all cascaded interrupts and clear routing */
+ for (unsigned int hw_irq = 0; hw_irq < RTL_ICTL_NUM_INPUTS; hw_irq++) {
+ disable_gimr(cpu, hw_irq);
+ write_irr(cpu, hw_irq, 0);
+ }
+ }
if (WARN_ON(!of_irq_count(node))) {
/*
diff --git a/drivers/irqchip/irq-renesas-rzt2h.c b/drivers/irqchip/irq-renesas-rzt2h.c
index ecb69da55508..e06264add3cc 100644
--- a/drivers/irqchip/irq-renesas-rzt2h.c
+++ b/drivers/irqchip/irq-renesas-rzt2h.c
@@ -2,6 +2,7 @@
#include <linux/bitfield.h>
#include <linux/err.h>
+#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/irqchip.h>
#include <linux/irqchip/irq-renesas-rzt2h.h>
@@ -30,16 +31,44 @@
RZT2H_ICU_IRQ_S_COUNT)
#define RZT2H_ICU_SEI_COUNT 1
+#define RZT2H_ICU_CA55_ERR_START (RZT2H_ICU_SEI_START + \
+ RZT2H_ICU_SEI_COUNT)
+#define RZT2H_ICU_CA55_ERR_COUNT 2
+
+#define RZT2H_ICU_CR52_ERR_START (RZT2H_ICU_CA55_ERR_START + \
+ RZT2H_ICU_CA55_ERR_COUNT)
+#define RZT2H_ICU_CR52_ERR_COUNT 4
+
+#define RZT2H_ICU_PERI_ERR_START (RZT2H_ICU_CR52_ERR_START + \
+ RZT2H_ICU_CR52_ERR_COUNT)
+#define RZT2H_ICU_PERI_ERR_COUNT 2
+
+#define RZT2H_ICU_DSMIF_ERR_START (RZT2H_ICU_PERI_ERR_START + \
+ RZT2H_ICU_PERI_ERR_COUNT)
+#define RZT2H_ICU_DSMIF_ERR_COUNT 2
+
+#define RZT2H_ICU_ENCIF_ERR_START (RZT2H_ICU_DSMIF_ERR_START + \
+ RZT2H_ICU_DSMIF_ERR_COUNT)
+#define RZT2H_ICU_ENCIF_ERR_COUNT 2
+
#define RZT2H_ICU_NUM_IRQ (RZT2H_ICU_INTCPU_NS_COUNT + \
RZT2H_ICU_INTCPU_S_COUNT + \
RZT2H_ICU_IRQ_NS_COUNT + \
RZT2H_ICU_IRQ_S_COUNT + \
- RZT2H_ICU_SEI_COUNT)
+ RZT2H_ICU_SEI_COUNT + \
+ RZT2H_ICU_CA55_ERR_COUNT + \
+ RZT2H_ICU_CR52_ERR_COUNT + \
+ RZT2H_ICU_PERI_ERR_COUNT + \
+ RZT2H_ICU_DSMIF_ERR_COUNT + \
+ RZT2H_ICU_ENCIF_ERR_COUNT)
#define RZT2H_ICU_IRQ_IN_RANGE(n, type) \
((n) >= RZT2H_ICU_##type##_START && \
(n) < RZT2H_ICU_##type##_START + RZT2H_ICU_##type##_COUNT)
+#define RZT2H_ICU_SWINT 0x0
+#define RZT2H_ICU_SWINT_IC_MASK(i) BIT(i)
+
#define RZT2H_ICU_PORTNF_MD 0xc
#define RZT2H_ICU_PORTNF_MDi_MASK(i) (GENMASK(1, 0) << ((i) * 2))
#define RZT2H_ICU_PORTNF_MDi_PREP(i, val) (FIELD_PREP(GENMASK(1, 0), val) << ((i) * 2))
@@ -49,6 +78,29 @@
#define RZT2H_ICU_MD_RISING_EDGE 0b10
#define RZT2H_ICU_MD_BOTH_EDGES 0b11
+#define RZT2H_ICU_CA55ERR_E0MSK 0x50
+#define RZT2H_ICU_CA55ERR_CLR 0x60
+#define RZT2H_ICU_CA55ERR_STAT 0x64
+#define RZT2H_ICU_CA55ERR_MASK GENMASK(12, 0)
+
+#define RZT2H_ICU_PERIERR_E0MSKn(n) (0x98 + 0x4 * (n))
+#define RZT2H_ICU_PERIERR_CLRn(n) (0xc8 + 0x4 * (n))
+#define RZT2H_ICU_PERIERR_STAT 0xd4
+#define RZT2H_ICU_PERIERR_NUM 3
+#define RZT2H_ICU_PERIERR_MASK GENMASK(31, 0)
+
+#define RZT2H_ICU_DSMIFERR_E0MSKn(n) (0xe0 + 0x4 * (n))
+#define RZT2H_ICU_DSMIFERR_CLRn(n) (0x1a0 + 0x4 * (n))
+#define RZT2H_ICU_DSMIFERR_STAT 0x1d0
+#define RZT2H_ICU_DSMIFERR_NUM 12
+#define RZT2H_ICU_DSMIFERR_MASK GENMASK(31, 0)
+
+#define RZT2H_ICU_ENCIFERR_E0MSKn(n) (0x200 + 0x4 * (n))
+#define RZT2H_ICU_ENCIFERR_CLRn(n) (0x250 + 0x4 * (n))
+#define RZT2H_ICU_ENCIFERR_STAT 0x264
+#define RZT2H_ICU_ENCIFERR_NUM 5
+#define RZT2H_ICU_ENCIFERR_MASK GENMASK(31, 0)
+
#define RZT2H_ICU_DMACn_RSSELi(n, i) (0x7d0 + 0x18 * (n) + 0x4 * (i))
#define RZT2H_ICU_DMAC_REQ_SELx_MASK(x) (GENMASK(9, 0) << ((x) * 10))
#define RZT2H_ICU_DMAC_REQ_SELx_PREP(x, val) (FIELD_PREP(GENMASK(9, 0), val) << ((x) * 10))
@@ -99,6 +151,12 @@ static inline int rzt2h_icu_irq_to_offset(struct irq_data *d, void __iomem **bas
} else if (RZT2H_ICU_IRQ_IN_RANGE(hwirq, IRQ_S) || RZT2H_ICU_IRQ_IN_RANGE(hwirq, SEI)) {
*offset = hwirq - RZT2H_ICU_IRQ_S_START;
*base = priv->base_s;
+ } else if (RZT2H_ICU_IRQ_IN_RANGE(hwirq, INTCPU_NS)) {
+ *offset = hwirq - RZT2H_ICU_INTCPU_NS_START;
+ *base = priv->base_ns;
+ } else if (RZT2H_ICU_IRQ_IN_RANGE(hwirq, INTCPU_S)) {
+ *offset = hwirq - RZT2H_ICU_INTCPU_S_START;
+ *base = priv->base_s;
} else {
return -EINVAL;
}
@@ -164,6 +222,28 @@ static int rzt2h_icu_set_type(struct irq_data *d, unsigned int type)
return irq_chip_set_type_parent(d, IRQ_TYPE_EDGE_RISING);
}
+static int rzt2h_icu_intcpu_set_irqchip_state(struct irq_data *d, enum irqchip_irq_state which,
+ bool state)
+{
+ unsigned int offset;
+ void __iomem *base;
+ int ret;
+
+ if (which != IRQCHIP_STATE_PENDING)
+ return irq_chip_set_parent_state(d, which, state);
+
+ if (!state)
+ return 0;
+
+ ret = rzt2h_icu_irq_to_offset(d, &base, &offset);
+ if (ret)
+ return ret;
+
+ writel_relaxed(RZT2H_ICU_SWINT_IC_MASK(offset), base + RZT2H_ICU_SWINT);
+
+ return 0;
+}
+
static const struct irq_chip rzt2h_icu_chip = {
.name = "rzt2h-icu",
.irq_mask = irq_chip_mask_parent,
@@ -180,10 +260,27 @@ static const struct irq_chip rzt2h_icu_chip = {
IRQCHIP_SKIP_SET_WAKE,
};
+static const struct irq_chip rzt2h_icu_intcpu_chip = {
+ .name = "rzt2h-icu",
+ .irq_mask = irq_chip_mask_parent,
+ .irq_unmask = irq_chip_unmask_parent,
+ .irq_eoi = irq_chip_eoi_parent,
+ .irq_set_type = irq_chip_set_type_parent,
+ .irq_set_wake = irq_chip_set_wake_parent,
+ .irq_set_affinity = irq_chip_set_affinity_parent,
+ .irq_retrigger = irq_chip_retrigger_hierarchy,
+ .irq_get_irqchip_state = irq_chip_get_parent_state,
+ .irq_set_irqchip_state = rzt2h_icu_intcpu_set_irqchip_state,
+ .flags = IRQCHIP_MASK_ON_SUSPEND |
+ IRQCHIP_SET_TYPE_MASKED |
+ IRQCHIP_SKIP_SET_WAKE,
+};
+
static int rzt2h_icu_alloc(struct irq_domain *domain, unsigned int virq, unsigned int nr_irqs,
void *arg)
{
struct rzt2h_icu_priv *priv = domain->host_data;
+ const struct irq_chip *chip;
irq_hw_number_t hwirq;
unsigned int type;
int ret;
@@ -192,7 +289,12 @@ static int rzt2h_icu_alloc(struct irq_domain *domain, unsigned int virq, unsigne
if (ret)
return ret;
- ret = irq_domain_set_hwirq_and_chip(domain, virq, hwirq, &rzt2h_icu_chip, NULL);
+ if (RZT2H_ICU_IRQ_IN_RANGE(hwirq, INTCPU_NS) || RZT2H_ICU_IRQ_IN_RANGE(hwirq, INTCPU_S))
+ chip = &rzt2h_icu_intcpu_chip;
+ else
+ chip = &rzt2h_icu_chip;
+
+ ret = irq_domain_set_hwirq_and_chip(domain, virq, hwirq, chip, NULL);
if (ret)
return ret;
@@ -222,6 +324,155 @@ static int rzt2h_icu_parse_interrupts(struct rzt2h_icu_priv *priv, struct device
return 0;
}
+static irqreturn_t rzt2h_icu_intcpu_irq(int irq, void *data)
+{
+ unsigned int intcpu = (uintptr_t)data;
+
+ pr_info("INTCPU%u software interrupt\n", intcpu);
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t rzt2h_icu_err_irq(struct rzt2h_icu_priv *priv, const char *name,
+ unsigned int num, u32 stat_base, u32 clr_base)
+{
+ bool handled = false;
+
+ for (unsigned int n = 0; n < num; n++) {
+ u32 stat = readl(priv->base_ns + stat_base + n * 0x4);
+
+ if (!stat)
+ continue;
+
+ handled = true;
+
+ pr_err("rzt2h-icu: %s error n=%u status=0x%08x\n", name, n, stat);
+
+ writel_relaxed(stat, priv->base_ns + clr_base + n * 0x4);
+ }
+
+ return handled ? IRQ_HANDLED : IRQ_NONE;
+}
+
+static irqreturn_t rzt2h_icu_ca55_err_irq(int irq, void *data)
+{
+ return rzt2h_icu_err_irq(data, "CA55", 1, RZT2H_ICU_CA55ERR_STAT, RZT2H_ICU_CA55ERR_CLR);
+}
+
+static irqreturn_t rzt2h_icu_peri_err_irq(int irq, void *data)
+{
+ return rzt2h_icu_err_irq(data, "peripheral", RZT2H_ICU_PERIERR_NUM, RZT2H_ICU_PERIERR_STAT,
+ RZT2H_ICU_PERIERR_CLRn(0));
+}
+
+static irqreturn_t rzt2h_icu_dsmif_err_irq(int irq, void *data)
+{
+ return rzt2h_icu_err_irq(data, "DSMIF", RZT2H_ICU_DSMIFERR_NUM, RZT2H_ICU_DSMIFERR_STAT,
+ RZT2H_ICU_DSMIFERR_CLRn(0));
+}
+
+static irqreturn_t rzt2h_icu_encif_err_irq(int irq, void *data)
+{
+ return rzt2h_icu_err_irq(data, "ENCIF", RZT2H_ICU_ENCIFERR_NUM, RZT2H_ICU_ENCIFERR_STAT,
+ RZT2H_ICU_ENCIFERR_CLRn(0));
+}
+
+static int rzt2h_icu_request_irqs(struct platform_device *pdev, struct irq_domain *irq_domain,
+ unsigned int start, unsigned int count, irq_handler_t handler,
+ void *data)
+{
+ struct device *dev = &pdev->dev;
+ unsigned int offset, virq;
+ struct irq_fwspec fwspec;
+ int ret;
+
+ for (offset = start; offset < start + count; offset++) {
+ fwspec.fwnode = irq_domain->fwnode;
+ fwspec.param_count = 2;
+ fwspec.param[0] = offset;
+ fwspec.param[1] = IRQ_TYPE_EDGE_RISING;
+
+ virq = irq_create_fwspec_mapping(&fwspec);
+ if (!virq)
+ return dev_err_probe(dev, -EINVAL, "Failed to create IRQ %u mapping\n", offset);
+
+ ret = devm_request_irq(dev, virq, handler, 0, dev_name(dev),
+ data ?: (void *)(uintptr_t)offset);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to request IRQ %u\n", offset);
+ }
+
+ return 0;
+}
+
+static int rzt2h_icu_setup_irqs(struct platform_device *pdev, struct irq_domain *irq_domain)
+{
+ struct rzt2h_icu_priv *priv = platform_get_drvdata(pdev);
+ unsigned int n;
+ int ret;
+
+ if (IS_ENABLED(CONFIG_GENERIC_IRQ_INJECTION)) {
+ ret = rzt2h_icu_request_irqs(pdev, irq_domain, RZT2H_ICU_INTCPU_NS_START,
+ RZT2H_ICU_INTCPU_NS_COUNT, rzt2h_icu_intcpu_irq, NULL);
+ if (ret)
+ return ret;
+
+ ret = rzt2h_icu_request_irqs(pdev, irq_domain, RZT2H_ICU_INTCPU_S_START,
+ RZT2H_ICU_INTCPU_S_COUNT, rzt2h_icu_intcpu_irq, NULL);
+ if (ret)
+ return ret;
+ }
+
+ /*
+ * There are two error interrupts and two error masks that can be used
+ * separately for each error type. It would not be very useful to
+ * receive two interrupts for the same error, so use only the first one.
+ */
+
+ ret = rzt2h_icu_request_irqs(pdev, irq_domain, RZT2H_ICU_CA55_ERR_START, 1,
+ rzt2h_icu_ca55_err_irq, priv);
+ if (ret)
+ return ret;
+
+ ret = rzt2h_icu_request_irqs(pdev, irq_domain, RZT2H_ICU_PERI_ERR_START, 1,
+ rzt2h_icu_peri_err_irq, priv);
+ if (ret)
+ return ret;
+
+ ret = rzt2h_icu_request_irqs(pdev, irq_domain, RZT2H_ICU_DSMIF_ERR_START, 1,
+ rzt2h_icu_dsmif_err_irq, priv);
+ if (ret)
+ return ret;
+
+ ret = rzt2h_icu_request_irqs(pdev, irq_domain, RZT2H_ICU_ENCIF_ERR_START, 1,
+ rzt2h_icu_encif_err_irq, priv);
+ if (ret)
+ return ret;
+
+ /* Clear and unmask CA55 error events */
+ writel_relaxed(RZT2H_ICU_CA55ERR_MASK, priv->base_ns + RZT2H_ICU_CA55ERR_CLR);
+ writel_relaxed(0, priv->base_ns + RZT2H_ICU_CA55ERR_E0MSK);
+
+ /* Clear and unmask peripheral error events */
+ for (n = 0; n < RZT2H_ICU_PERIERR_NUM; n++) {
+ writel_relaxed(RZT2H_ICU_PERIERR_MASK, priv->base_ns + RZT2H_ICU_PERIERR_CLRn(n));
+ writel_relaxed(0, priv->base_ns + RZT2H_ICU_PERIERR_E0MSKn(n));
+ }
+
+ /* Clear and unmask DSMIF error events */
+ for (n = 0; n < RZT2H_ICU_DSMIFERR_NUM; n++) {
+ writel_relaxed(RZT2H_ICU_DSMIFERR_MASK, priv->base_ns + RZT2H_ICU_DSMIFERR_CLRn(n));
+ writel_relaxed(0, priv->base_ns + RZT2H_ICU_DSMIFERR_E0MSKn(n));
+ }
+
+ /* Clear and unmask ENCIF error events */
+ for (n = 0; n < RZT2H_ICU_ENCIFERR_NUM; n++) {
+ writel_relaxed(RZT2H_ICU_ENCIFERR_MASK, priv->base_ns + RZT2H_ICU_ENCIFERR_CLRn(n));
+ writel_relaxed(0, priv->base_ns + RZT2H_ICU_ENCIFERR_E0MSKn(n));
+ }
+
+ return 0;
+}
+
static int rzt2h_icu_init(struct platform_device *pdev, struct device_node *parent)
{
struct irq_domain *irq_domain, *parent_domain;
@@ -265,11 +516,20 @@ static int rzt2h_icu_init(struct platform_device *pdev, struct device_node *pare
irq_domain = irq_domain_create_hierarchy(parent_domain, 0, RZT2H_ICU_NUM_IRQ,
dev_fwnode(dev), &rzt2h_icu_domain_ops, priv);
if (!irq_domain) {
- pm_runtime_put_sync(dev);
- return -ENOMEM;
+ ret = -ENOMEM;
+ goto err_pm_put;
}
+ ret = rzt2h_icu_setup_irqs(pdev, irq_domain);
+ if (ret)
+ goto err_irq_domain_free;
return 0;
+
+err_irq_domain_free:
+ irq_domain_remove(irq_domain);
+err_pm_put:
+ pm_runtime_put_sync(dev);
+ return ret;
}
IRQCHIP_PLATFORM_DRIVER_BEGIN(rzt2h_icu)
diff --git a/drivers/irqchip/irq-starfive-jh8100-intc.c b/drivers/irqchip/irq-starfive-jh8100-intc.c
deleted file mode 100644
index bb62ef363d0b..000000000000
--- a/drivers/irqchip/irq-starfive-jh8100-intc.c
+++ /dev/null
@@ -1,207 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0
-/*
- * StarFive JH8100 External Interrupt Controller driver
- *
- * Copyright (C) 2023 StarFive Technology Co., Ltd.
- *
- * Author: Changhuang Liang <changhuang.liang@xxxxxxxxxxxxxxxx>
- */
-
-#define pr_fmt(fmt) "irq-starfive-jh8100: " fmt
-
-#include <linux/bitops.h>
-#include <linux/clk.h>
-#include <linux/irq.h>
-#include <linux/irqchip.h>
-#include <linux/irqchip/chained_irq.h>
-#include <linux/irqdomain.h>
-#include <linux/of_address.h>
-#include <linux/of_irq.h>
-#include <linux/reset.h>
-#include <linux/spinlock.h>
-
-#define STARFIVE_INTC_SRC0_CLEAR 0x10
-#define STARFIVE_INTC_SRC0_MASK 0x14
-#define STARFIVE_INTC_SRC0_INT 0x1c
-
-#define STARFIVE_INTC_SRC_IRQ_NUM 32
-
-struct starfive_irq_chip {
- void __iomem *base;
- struct irq_domain *domain;
- raw_spinlock_t lock;
-};
-
-static void starfive_intc_bit_set(struct starfive_irq_chip *irqc,
- u32 reg, u32 bit_mask)
-{
- u32 value;
-
- value = ioread32(irqc->base + reg);
- value |= bit_mask;
- iowrite32(value, irqc->base + reg);
-}
-
-static void starfive_intc_bit_clear(struct starfive_irq_chip *irqc,
- u32 reg, u32 bit_mask)
-{
- u32 value;
-
- value = ioread32(irqc->base + reg);
- value &= ~bit_mask;
- iowrite32(value, irqc->base + reg);
-}
-
-static void starfive_intc_unmask(struct irq_data *d)
-{
- struct starfive_irq_chip *irqc = irq_data_get_irq_chip_data(d);
-
- raw_spin_lock(&irqc->lock);
- starfive_intc_bit_clear(irqc, STARFIVE_INTC_SRC0_MASK, BIT(d->hwirq));
- raw_spin_unlock(&irqc->lock);
-}
-
-static void starfive_intc_mask(struct irq_data *d)
-{
- struct starfive_irq_chip *irqc = irq_data_get_irq_chip_data(d);
-
- raw_spin_lock(&irqc->lock);
- starfive_intc_bit_set(irqc, STARFIVE_INTC_SRC0_MASK, BIT(d->hwirq));
- raw_spin_unlock(&irqc->lock);
-}
-
-static struct irq_chip intc_dev = {
- .name = "StarFive JH8100 INTC",
- .irq_unmask = starfive_intc_unmask,
- .irq_mask = starfive_intc_mask,
-};
-
-static int starfive_intc_map(struct irq_domain *d, unsigned int irq,
- irq_hw_number_t hwirq)
-{
- irq_domain_set_info(d, irq, hwirq, &intc_dev, d->host_data,
- handle_level_irq, NULL, NULL);
-
- return 0;
-}
-
-static const struct irq_domain_ops starfive_intc_domain_ops = {
- .xlate = irq_domain_xlate_onecell,
- .map = starfive_intc_map,
-};
-
-static void starfive_intc_irq_handler(struct irq_desc *desc)
-{
- struct starfive_irq_chip *irqc = irq_data_get_irq_handler_data(&desc->irq_data);
- struct irq_chip *chip = irq_desc_get_chip(desc);
- unsigned long value;
- int hwirq;
-
- chained_irq_enter(chip, desc);
-
- value = ioread32(irqc->base + STARFIVE_INTC_SRC0_INT);
- while (value) {
- hwirq = ffs(value) - 1;
-
- generic_handle_domain_irq(irqc->domain, hwirq);
-
- starfive_intc_bit_set(irqc, STARFIVE_INTC_SRC0_CLEAR, BIT(hwirq));
- starfive_intc_bit_clear(irqc, STARFIVE_INTC_SRC0_CLEAR, BIT(hwirq));
-
- __clear_bit(hwirq, &value);
- }
-
- chained_irq_exit(chip, desc);
-}
-
-static int starfive_intc_probe(struct platform_device *pdev, struct device_node *parent)
-{
- struct device_node *intc = pdev->dev.of_node;
- struct starfive_irq_chip *irqc;
- struct reset_control *rst;
- struct clk *clk;
- int parent_irq;
- int ret;
-
- irqc = kzalloc_obj(*irqc);
- if (!irqc)
- return -ENOMEM;
-
- irqc->base = of_iomap(intc, 0);
- if (!irqc->base) {
- pr_err("Unable to map registers\n");
- ret = -ENXIO;
- goto err_free;
- }
-
- rst = of_reset_control_get_exclusive(intc, NULL);
- if (IS_ERR(rst)) {
- pr_err("Unable to get reset control %pe\n", rst);
- ret = PTR_ERR(rst);
- goto err_unmap;
- }
-
- clk = of_clk_get(intc, 0);
- if (IS_ERR(clk)) {
- pr_err("Unable to get clock %pe\n", clk);
- ret = PTR_ERR(clk);
- goto err_reset_put;
- }
-
- ret = reset_control_deassert(rst);
- if (ret)
- goto err_clk_put;
-
- ret = clk_prepare_enable(clk);
- if (ret)
- goto err_reset_assert;
-
- raw_spin_lock_init(&irqc->lock);
-
- irqc->domain = irq_domain_create_linear(of_fwnode_handle(intc), STARFIVE_INTC_SRC_IRQ_NUM,
- &starfive_intc_domain_ops, irqc);
- if (!irqc->domain) {
- pr_err("Unable to create IRQ domain\n");
- ret = -EINVAL;
- goto err_clk_disable;
- }
-
- parent_irq = of_irq_get(intc, 0);
- if (parent_irq < 0) {
- pr_err("Failed to get main IRQ: %d\n", parent_irq);
- ret = parent_irq;
- goto err_remove_domain;
- }
-
- irq_set_chained_handler_and_data(parent_irq, starfive_intc_irq_handler,
- irqc);
-
- pr_info("Interrupt controller register, nr_irqs %d\n",
- STARFIVE_INTC_SRC_IRQ_NUM);
-
- return 0;
-
-err_remove_domain:
- irq_domain_remove(irqc->domain);
-err_clk_disable:
- clk_disable_unprepare(clk);
-err_reset_assert:
- reset_control_assert(rst);
-err_clk_put:
- clk_put(clk);
-err_reset_put:
- reset_control_put(rst);
-err_unmap:
- iounmap(irqc->base);
-err_free:
- kfree(irqc);
- return ret;
-}
-
-IRQCHIP_PLATFORM_DRIVER_BEGIN(starfive_intc)
-IRQCHIP_MATCH("starfive,jh8100-intc", starfive_intc_probe)
-IRQCHIP_PLATFORM_DRIVER_END(starfive_intc)
-
-MODULE_DESCRIPTION("StarFive JH8100 External Interrupt Controller");
-MODULE_LICENSE("GPL");
-MODULE_AUTHOR("Changhuang Liang <changhuang.liang@xxxxxxxxxxxxxxxx>");
diff --git a/drivers/irqchip/irq-starfive-jhb100-intc.c b/drivers/irqchip/irq-starfive-jhb100-intc.c
new file mode 100644
index 000000000000..838885b02f34
--- /dev/null
+++ b/drivers/irqchip/irq-starfive-jhb100-intc.c
@@ -0,0 +1,254 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * StarFive JHB100 External Interrupt Controller driver
+ *
+ * Copyright (C) 2023 StarFive Technology Co., Ltd.
+ *
+ * Author: Changhuang Liang <changhuang.liang@xxxxxxxxxxxxxxxx>
+ */
+
+#include <linux/bitops.h>
+#include <linux/cleanup.h>
+#include <linux/clk.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/irqchip.h>
+#include <linux/irqchip/chained_irq.h>
+#include <linux/irqdomain.h>
+#include <linux/of_irq.h>
+#include <linux/platform_device.h>
+#include <linux/reset.h>
+#include <linux/spinlock.h>
+
+#define STARFIVE_INTC_SRC_TYPE(n) (0x04 + ((n) * 0x20))
+#define STARFIVE_INTC_SRC_CLEAR(n) (0x10 + ((n) * 0x20))
+#define STARFIVE_INTC_SRC_MASK(n) (0x14 + ((n) * 0x20))
+#define STARFIVE_INTC_SRC_INT(n) (0x1c + ((n) * 0x20))
+
+#define STARFIVE_INTC_TRIGGER_MASK 0x3
+#define STARFIVE_INTC_TRIGGER_HIGH 0
+#define STARFIVE_INTC_TRIGGER_LOW 1
+#define STARFIVE_INTC_TRIGGER_POSEDGE 2
+#define STARFIVE_INTC_TRIGGER_NEGEDGE 3
+
+#define STARFIVE_INTC_NUM 2
+#define STARFIVE_INTC_SRC_IRQ_NUM 32
+#define STARFIVE_INTC_TYPE_NUM 16
+
+struct starfive_irq_chip {
+ void __iomem *base;
+ struct irq_domain *domain;
+ raw_spinlock_t lock;
+};
+
+static void starfive_intc_mod(struct starfive_irq_chip *irqc, u32 reg, u32 mask, u32 data)
+{
+ u32 value;
+
+ value = ioread32(irqc->base + reg) & ~mask;
+ data &= mask;
+ data |= value;
+ iowrite32(data, irqc->base + reg);
+}
+
+static void starfive_intc_bit_set(struct starfive_irq_chip *irqc,
+ u32 reg, u32 bit_mask)
+{
+ u32 value;
+
+ value = ioread32(irqc->base + reg);
+ value |= bit_mask;
+ iowrite32(value, irqc->base + reg);
+}
+
+static void starfive_intc_bit_clear(struct starfive_irq_chip *irqc,
+ u32 reg, u32 bit_mask)
+{
+ u32 value;
+
+ value = ioread32(irqc->base + reg);
+ value &= ~bit_mask;
+ iowrite32(value, irqc->base + reg);
+}
+
+static void starfive_intc_unmask(struct irq_data *d)
+{
+ struct starfive_irq_chip *irqc = irq_data_get_irq_chip_data(d);
+ int i, bitpos;
+
+ i = d->hwirq / STARFIVE_INTC_SRC_IRQ_NUM;
+ bitpos = d->hwirq % STARFIVE_INTC_SRC_IRQ_NUM;
+
+ guard(raw_spinlock)(&irqc->lock);
+ starfive_intc_bit_clear(irqc, STARFIVE_INTC_SRC_MASK(i), BIT(bitpos));
+}
+
+static void starfive_intc_mask(struct irq_data *d)
+{
+ struct starfive_irq_chip *irqc = irq_data_get_irq_chip_data(d);
+ int i, bitpos;
+
+ i = d->hwirq / STARFIVE_INTC_SRC_IRQ_NUM;
+ bitpos = d->hwirq % STARFIVE_INTC_SRC_IRQ_NUM;
+
+ guard(raw_spinlock)(&irqc->lock);
+ starfive_intc_bit_set(irqc, STARFIVE_INTC_SRC_MASK(i), BIT(bitpos));
+}
+
+static void starfive_intc_ack(struct irq_data *d)
+{
+ /* for handle_edge_irq, nothing to do */
+}
+
+static int starfive_intc_set_type(struct irq_data *d, unsigned int type)
+{
+ struct starfive_irq_chip *irqc = irq_data_get_irq_chip_data(d);
+ u32 i, bitpos, ty_pos, ty_shift, trigger, typeval;
+ irq_flow_handler_t handler;
+
+ i = d->hwirq / STARFIVE_INTC_SRC_IRQ_NUM;
+ bitpos = d->hwirq % STARFIVE_INTC_SRC_IRQ_NUM;
+ ty_pos = bitpos / STARFIVE_INTC_TYPE_NUM;
+ ty_shift = (bitpos % STARFIVE_INTC_TYPE_NUM) * 2;
+
+ switch (type) {
+ case IRQF_TRIGGER_LOW:
+ trigger = STARFIVE_INTC_TRIGGER_LOW;
+ handler = handle_level_irq;
+ break;
+ case IRQF_TRIGGER_HIGH:
+ trigger = STARFIVE_INTC_TRIGGER_HIGH;
+ handler = handle_level_irq;
+ break;
+ case IRQF_TRIGGER_FALLING:
+ trigger = STARFIVE_INTC_TRIGGER_NEGEDGE;
+ handler = handle_edge_irq;
+ break;
+ case IRQF_TRIGGER_RISING:
+ trigger = STARFIVE_INTC_TRIGGER_POSEDGE;
+ handler = handle_edge_irq;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ irq_set_handler_locked(d, handler);
+ typeval = trigger << ty_shift;
+
+ guard(raw_spinlock)(&irqc->lock);
+
+ starfive_intc_mod(irqc, STARFIVE_INTC_SRC_TYPE(i) + 4 * ty_pos,
+ STARFIVE_INTC_TRIGGER_MASK << ty_shift, typeval);
+
+ /* Once the type is updated, clear interrupt can help to reset the type value */
+ starfive_intc_bit_set(irqc, STARFIVE_INTC_SRC_CLEAR(i), BIT(bitpos));
+ starfive_intc_bit_clear(irqc, STARFIVE_INTC_SRC_CLEAR(i), BIT(bitpos));
+
+ return 0;
+}
+
+static struct irq_chip intc_dev = {
+ .name = "StarFive JHB100 INTC",
+ .irq_unmask = starfive_intc_unmask,
+ .irq_mask = starfive_intc_mask,
+ .irq_ack = starfive_intc_ack,
+ .irq_set_type = starfive_intc_set_type,
+};
+
+static int starfive_intc_map(struct irq_domain *d, unsigned int irq,
+ irq_hw_number_t hwirq)
+{
+ irq_domain_set_info(d, irq, hwirq, &intc_dev, d->host_data,
+ handle_level_irq, NULL, NULL);
+
+ return 0;
+}
+
+static const struct irq_domain_ops starfive_intc_domain_ops = {
+ .xlate = irq_domain_xlate_onecell,
+ .map = starfive_intc_map,
+};
+
+static void starfive_intc_irq_handler(struct irq_desc *desc)
+{
+ struct starfive_irq_chip *irqc = irq_data_get_irq_handler_data(&desc->irq_data);
+ struct irq_chip *chip = irq_desc_get_chip(desc);
+ unsigned long value;
+ int hwirq;
+
+ chained_irq_enter(chip, desc);
+
+ for (int i = 0; i < STARFIVE_INTC_NUM; i++) {
+ value = ioread32(irqc->base + STARFIVE_INTC_SRC_INT(i));
+ while (value) {
+ hwirq = ffs(value) - 1;
+
+ generic_handle_domain_irq(irqc->domain,
+ hwirq + i * STARFIVE_INTC_SRC_IRQ_NUM);
+
+ starfive_intc_bit_set(irqc, STARFIVE_INTC_SRC_CLEAR(i), BIT(hwirq));
+ starfive_intc_bit_clear(irqc, STARFIVE_INTC_SRC_CLEAR(i), BIT(hwirq));
+
+ __clear_bit(hwirq, &value);
+ }
+ }
+
+ chained_irq_exit(chip, desc);
+}
+
+static int starfive_intc_probe(struct platform_device *pdev, struct device_node *parent)
+{
+ struct device_node *intc = pdev->dev.of_node;
+ struct reset_control *rst;
+ struct clk *clk;
+ int parent_irq;
+
+ struct starfive_irq_chip *irqc __free(kfree) = kzalloc_obj(*irqc);
+ if (!irqc)
+ return -ENOMEM;
+
+ irqc->base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(irqc->base))
+ return dev_err_probe(&pdev->dev, PTR_ERR(irqc->base), "unable to map registers\n");
+
+ rst = devm_reset_control_get_optional_exclusive_deasserted(&pdev->dev, NULL);
+ if (IS_ERR(rst))
+ return dev_err_probe(&pdev->dev, PTR_ERR(rst),
+ "Unable to get and deassert reset control\n");
+
+ clk = devm_clk_get_optional_enabled(&pdev->dev, NULL);
+ if (IS_ERR(clk))
+ return dev_err_probe(&pdev->dev, PTR_ERR(clk), "Unable to get and enable clock\n");
+
+
+ raw_spin_lock_init(&irqc->lock);
+
+ irqc->domain = irq_domain_create_linear(of_fwnode_handle(intc),
+ STARFIVE_INTC_SRC_IRQ_NUM * STARFIVE_INTC_NUM,
+ &starfive_intc_domain_ops, irqc);
+ if (!irqc->domain)
+ return dev_err_probe(&pdev->dev, -EINVAL, "Unable to create IRQ domain\n");
+
+ parent_irq = of_irq_get(intc, 0);
+ if (parent_irq < 0) {
+ irq_domain_remove(irqc->domain);
+ return dev_err_probe(&pdev->dev, parent_irq, "Failed to get main IRQ\n");
+ }
+
+ irq_set_chained_handler_and_data(parent_irq, starfive_intc_irq_handler,
+ irqc);
+
+ dev_info(&pdev->dev, "Interrupt controller register, nr_irqs %d\n",
+ STARFIVE_INTC_SRC_IRQ_NUM * STARFIVE_INTC_NUM);
+
+ retain_and_null_ptr(irqc);
+ return 0;
+}
+
+IRQCHIP_PLATFORM_DRIVER_BEGIN(starfive_intc)
+IRQCHIP_MATCH("starfive,jhb100-intc", starfive_intc_probe)
+IRQCHIP_PLATFORM_DRIVER_END(starfive_intc)
+
+MODULE_DESCRIPTION("StarFive JHB100 External Interrupt Controller");
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Changhuang Liang <changhuang.liang@xxxxxxxxxxxxxxxx>");
diff --git a/drivers/irqchip/qcom-pdc.c b/drivers/irqchip/qcom-pdc.c
index 32b77fa93f73..2014dbb0bc43 100644
--- a/drivers/irqchip/qcom-pdc.c
+++ b/drivers/irqchip/qcom-pdc.c
@@ -3,6 +3,7 @@
* Copyright (c) 2017-2019, The Linux Foundation. All rights reserved.
*/
+#include <linux/bitfield.h>
#include <linux/err.h>
#include <linux/init.h>
#include <linux/interrupt.h>
@@ -21,22 +22,30 @@
#include <linux/types.h>
#define PDC_MAX_GPIO_IRQS 256
-#define PDC_DRV_OFFSET 0x10000
+#define PDC_DRV_SIZE 0x10000
/* Valid only on HW version < 3.2 */
#define IRQ_ENABLE_BANK 0x10
#define IRQ_ENABLE_BANK_MAX (IRQ_ENABLE_BANK + BITS_TO_BYTES(PDC_MAX_GPIO_IRQS))
+#define IRQ_ENABLE_BANK_INDEX_MASK GENMASK(31, 5)
+#define IRQ_ENABLE_BANK_BIT_MASK GENMASK(4, 0)
#define IRQ_i_CFG 0x110
/* Valid only on HW version >= 3.2 */
#define IRQ_i_CFG_IRQ_ENABLE 3
-#define IRQ_i_CFG_TYPE_MASK GENMASK(2, 0)
+#define IRQ_i_CFG_TYPE_MASK GENMASK(2, 0)
-#define PDC_VERSION_REG 0x1000
+#define PDC_VERSION_REG 0x1000
+#define PDC_VERSION_MAJOR GENMASK(23, 16)
+#define PDC_VERSION_MINOR GENMASK(15, 8)
+#define PDC_VERSION_STEP GENMASK(7, 0)
+#define PDC_VERSION(maj, min, step) (FIELD_PREP(PDC_VERSION_MAJOR, (maj)) | \
+ FIELD_PREP(PDC_VERSION_MINOR, (min)) | \
+ FIELD_PREP(PDC_VERSION_STEP, (step)))
/* Notable PDC versions */
-#define PDC_VERSION_3_2 0x30200
+#define PDC_VERSION_3_2 PDC_VERSION(3, 2, 0)
struct pdc_pin_region {
u32 pin_base;
@@ -97,28 +106,37 @@ static void pdc_x1e_irq_enable_write(u32 bank, u32 enable)
pdc_base_reg_write(base, IRQ_ENABLE_BANK, bank, enable);
}
-static void __pdc_enable_intr(int pin_out, bool on)
+static void pdc_enable_intr_bank(int pin_out, bool on)
{
unsigned long enable;
+ u32 index, mask;
- if (pdc_version < PDC_VERSION_3_2) {
- u32 index, mask;
+ index = FIELD_GET(IRQ_ENABLE_BANK_INDEX_MASK, pin_out);
+ mask = FIELD_GET(IRQ_ENABLE_BANK_BIT_MASK, pin_out);
- index = pin_out / 32;
- mask = pin_out % 32;
+ enable = pdc_reg_read(IRQ_ENABLE_BANK, index);
+ __assign_bit(mask, &enable, on);
- enable = pdc_reg_read(IRQ_ENABLE_BANK, index);
- __assign_bit(mask, &enable, on);
+ if (pdc_x1e_quirk)
+ pdc_x1e_irq_enable_write(index, enable);
+ else
+ pdc_reg_write(IRQ_ENABLE_BANK, index, enable);
+}
- if (pdc_x1e_quirk)
- pdc_x1e_irq_enable_write(index, enable);
- else
- pdc_reg_write(IRQ_ENABLE_BANK, index, enable);
- } else {
- enable = pdc_reg_read(IRQ_i_CFG, pin_out);
- __assign_bit(IRQ_i_CFG_IRQ_ENABLE, &enable, on);
- pdc_reg_write(IRQ_i_CFG, pin_out, enable);
- }
+static void pdc_enable_intr_cfg(int pin_out, bool on)
+{
+ unsigned long enable = pdc_reg_read(IRQ_i_CFG, pin_out);
+
+ __assign_bit(IRQ_i_CFG_IRQ_ENABLE, &enable, on);
+ pdc_reg_write(IRQ_i_CFG, pin_out, enable);
+}
+
+static void __pdc_enable_intr(int pin_out, bool on)
+{
+ if (pdc_version < PDC_VERSION_3_2)
+ pdc_enable_intr_bank(pin_out, on);
+ else
+ pdc_enable_intr_cfg(pin_out, on);
}
static void pdc_enable_intr(struct irq_data *d, bool on)
@@ -348,7 +366,6 @@ static int pdc_setup_pin_mapping(struct device_node *np)
return 0;
}
-#define QCOM_PDC_SIZE 0x30000
static int qcom_pdc_probe(struct platform_device *pdev, struct device_node *parent)
{
@@ -362,7 +379,7 @@ static int qcom_pdc_probe(struct platform_device *pdev, struct device_node *pare
if (of_address_to_resource(node, 0, &res))
return -EINVAL;
- res_size = max_t(resource_size_t, resource_size(&res), QCOM_PDC_SIZE);
+ res_size = max_t(resource_size_t, resource_size(&res), PDC_DRV_SIZE);
if (res_size > resource_size(&res))
pr_warn("%pOF: invalid reg size, please fix DT\n", node);
@@ -375,7 +392,7 @@ static int qcom_pdc_probe(struct platform_device *pdev, struct device_node *pare
* region with the expected offset to preserve support for old DTs.
*/
if (of_device_is_compatible(node, "qcom,x1e80100-pdc")) {
- pdc_prev_base = ioremap(res.start - PDC_DRV_OFFSET, IRQ_ENABLE_BANK_MAX);
+ pdc_prev_base = ioremap(res.start - PDC_DRV_SIZE, IRQ_ENABLE_BANK_MAX);
if (!pdc_prev_base) {
pr_err("%pOF: unable to map previous PDC DRV region\n", node);
return -ENXIO;
diff --git a/include/linux/irqchip/arm-gic-v3.h b/include/linux/irqchip/arm-gic-v3.h
index 0225121f3013..ea5fd2374ebe 100644
--- a/include/linux/irqchip/arm-gic-v3.h
+++ b/include/linux/irqchip/arm-gic-v3.h
@@ -604,7 +604,7 @@
#include <asm/arch_gicv3.h>
-#ifndef __ASSEMBLY__
+#ifndef __ASSEMBLER__
/*
* We need a value to serve as a irq-type for LPIs. Choose one that will
diff --git a/include/linux/irqchip/arm-gic.h b/include/linux/irqchip/arm-gic.h
index d45fa19f9e47..849386dc5ec8 100644
--- a/include/linux/irqchip/arm-gic.h
+++ b/include/linux/irqchip/arm-gic.h
@@ -131,7 +131,7 @@
#define GICV_PMR_PRIORITY_SHIFT 3
#define GICV_PMR_PRIORITY_MASK (0x1f << GICV_PMR_PRIORITY_SHIFT)
-#ifndef __ASSEMBLY__
+#ifndef __ASSEMBLER__
#include <linux/irqdomain.h>
@@ -162,5 +162,5 @@ int gic_get_cpu_id(unsigned int cpu);
void gic_migrate_target(unsigned int new_cpu_id);
unsigned long gic_get_sgir_physaddr(void);
-#endif /* __ASSEMBLY */
+#endif /* __ASSEMBLER__ */
#endif