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
 -----BEGIN PGP SIGNATURE-----
 
 iQJEBAABCgAuFiEEQp8+kY+LLUocC4bMphj1TA10mKEFAmotiUMQHHRnbHhAa2Vy
 bmVsLm9yZwAKCRCmGPVMDXSYoVI1D/9hTno8fq6LtgB/YbY81m6Ulf6uHTiLzwkl
 bRd/62h99W/moP+teVapIbh00swIP7gc2tcS9JJ1Jh8rXcr+ePe5305MV0JvyywP
 Kiyv8GFLoviWKsekieDGnRJeX630toB4VonOKJHY47q6xBqwVspf5vHlK6jCKWbB
 QdLHTWJ2dbrwTu8vDRSA3J2u3/cZco9UxABrT0m/JMyDrJSIft9tkTNIrHVjfQyO
 OeDhZ4qTCGkvY6yOFQ4T+2e2s4D+Uz4FGoUo8q2Usrmt/c2fCqdrkVhIH6mIRzN0
 tC183K+eqhEGmoQ6/HjQfHZqULimHxkD4To6jZLjJ26L2aHjW/9yGIjX4JYVpFn9
 nWRaWcx9Bc16uauIHdPclHMJr9sDNGuBGNeHiPC0XcVzj8kjSnkR/N9axQ9FPM4j
 xoqrVC576A5oe/nMqfdVmVFbTDApgS05kHpinCe+Y4jZPZGpzPvULJiIfEwQbw+M
 C3xXSGiy7dxG0S84tx03AvNvewtw9UFwXLxLftHIYQ6arCul/m8eRpErjneQhcnf
 sbH5Hgkk4V37QsLwn1w51yuqeg+N/SOnLnkld93sXJhPEzaPenwbiteIKPsargng
 9DR9DVDtskAnESj/UNHhSuaQvbRHyYh3IYbudC2iqAGCOkp3+Qzd9UKPJ/mbJHe3
 DMAl9mbZLw==
 =zJc2
 -----END PGP SIGNATURE-----

Merge tag 'irq-drivers-2026-06-13' of gitolite.kernel.org:pub/scm/linux/kernel/git/tip/tip

Pull interrupt chip driver updates from Thomas Gleixner:

 - 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

* tag 'irq-drivers-2026-06-13' of gitolite.kernel.org:pub/scm/linux/kernel/git/tip/tip: (32 commits)
  irqchip/irq-realtek-rtl: Add multicore support
  irqchip/irq-realtek-rtl: Add/simplify register helpers
  irqchip/loongarch-ir: Add IR (interrupt redirection) irqchip support
  irqchip/loongarch-avec: Return IRQ_SET_MASK_OK_DONE when keep affinity
  irqchip/loongarch-avec: Prepare for interrupt redirection support
  Docs/LoongArch: Add advanced extended IRQ model
  irqchip/qcom-pdc: Use FIELD_GET() to extract bank index and bit position
  irqchip/qcom-pdc: Add PDC_VERSION() macro to describe version register fields
  irqchip/qcom-pdc: Tighten ioremap clamp to single DRV region size
  irqchip/qcom-pdc: Split __pdc_enable_intr() into per-version helpers
  irqchip/exynos-combiner: Remove useless spinlock
  irqchip/renesas-rzt2h: Add error interrupts support
  irqchip/renesas-rzt2h: Add software-triggered interrupts support
  irqchip/gic-v4: Don't advertise VLPIs if no ITS is probed
  irqchip/gic-v3-its: Use FIELD_MODIFY()
  irqchip/econet-en751221: Support MIPS 34Kc VEIC mode
  dt-bindings: interrupt-controller: econet: Add CPU interrupt mapping
  irqchip/meson-gpio: Add support for Amlogic A9 SoCs
  dt-bindings: interrupt-controller: Add support for Amlogic A9 SoCs
  irqchip/meson-gpio: Use the correct register in meson_s4_gpio_irq_set_type()
  ...
This commit is contained in:
Linus Torvalds 2026-06-15 13:25:32 +05:30
commit 857ae5a445
32 changed files with 3276 additions and 572 deletions

View File

@ -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
========================

View File

@ -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

View File

@ -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@aspeedtech.com>
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>;
};
};

View File

@ -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@aspeedtech.com>
- Andrew Jeffery <andrew@codeconstruct.com.au>
properties:
compatible:
enum:
- aspeed,ast2700-intc0
- aspeed,ast2700-intc1
reg:
maxItems: 1
interrupt-controller: true
'#interrupt-cells':
const: 1
description: Single cell encoding the INTC local interrupt number (hwirq).
aspeed,interrupt-ranges:
description: |
Describes how ranges of controller output pins are routed to a parent
interrupt controller.
Each range entry is encoded as:
<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>;
};

View File

@ -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>;
};
...

View File

@ -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>;

View File

@ -174,6 +174,40 @@ CPU串口UARTs中断发送到LIOINTCPCH-MSI中断发送到AVECINTC
| Devices |
+---------+
高级扩展IRQ模型 (带重定向)
==========================
在这种模型里面IPIInter-Processor Interrupt和CPU本地时钟中断直接发送到CPUINTC
CPU串口UARTs中断发送到LIOINTCPCH-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相关的定义
==============

View File

@ -25598,11 +25598,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@starfivetech.com>
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@infradead.org>

View File

@ -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 */

View File

@ -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

View File

@ -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.

View File

@ -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

View File

@ -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)

View File

@ -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);

View File

@ -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");

View File

@ -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)

View File

@ -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)

View File

@ -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;
}

View File

@ -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

View File

@ -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;

View File

@ -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;
}

View File

@ -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);
}

View File

@ -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;
}

View File

@ -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 */

View File

@ -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

View File

@ -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);
value = readl(REG(RTL_ICTL_GIMR));
value |= BIT(i->hwirq);
writel(value, REG(RTL_ICTL_GIMR));
raw_spin_unlock_irqrestore(&irq_lock, flags);
guard(raw_spinlock)(&irq_lock);
for_each_cpu(cpu, irq_data_get_effective_affinity_mask(i))
enable_gimr(cpu, i->hwirq);
}
static void realtek_ictl_mask_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))
disable_gimr(cpu, i->hwirq);
}
value = readl(REG(RTL_ICTL_GIMR));
value &= ~BIT(i->hwirq);
writel(value, REG(RTL_ICTL_GIMR));
static int realtek_ictl_irq_affinity(struct irq_data *i, const struct cpumask *dest, bool force)
{
if (!irqd_irq_masked(i))
realtek_ictl_mask_irq(i);
raw_spin_unlock_irqrestore(&irq_lock, flags);
irq_data_update_effective_affinity(i, dest);
if (!irqd_irq_masked(i))
realtek_ictl_unmask_irq(i);
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;
int cpu, parent_irq;
realtek_ictl_base = of_iomap(node, 0);
if (!realtek_ictl_base)
return -ENXIO;
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 */
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);
/* 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))) {
/*

View File

@ -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)

View File

@ -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@starfivetech.com>
*/
#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@starfivetech.com>");

View File

@ -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@starfivetech.com>
*/
#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@starfivetech.com>");

View File

@ -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;

View File

@ -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

View File

@ -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