mirror of
https://github.com/torvalds/linux.git
synced 2026-07-27 17:47:41 +02:00
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:
commit
857ae5a445
|
|
@ -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
|
||||
========================
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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>;
|
||||
};
|
||||
};
|
||||
|
|
@ -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>;
|
||||
};
|
||||
|
|
@ -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>;
|
||||
};
|
||||
...
|
||||
|
|
|
|||
|
|
@ -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>;
|
||||
|
|
@ -174,6 +174,40 @@ CPU串口(UARTs)中断发送到LIOINTC,PCH-MSI中断发送到AVECINTC,
|
|||
| Devices |
|
||||
+---------+
|
||||
|
||||
高级扩展IRQ模型 (带重定向)
|
||||
==========================
|
||||
|
||||
在这种模型里面,IPI(Inter-Processor Interrupt)和CPU本地时钟中断直接发送到CPUINTC,
|
||||
CPU串口(UARTs)中断发送到LIOINTC,PCH-MSI中断首先发送到REDIRECT模块,完成重定向后发
|
||||
送到AVECINTC,而后通过AVECINTC直接送达CPUINTC,而其他所有设备的中断则分别发送到所连
|
||||
接的PCH-PIC/PCH-LPC,然后由EIOINTC统一收集,再直接到达CPUINTC::
|
||||
|
||||
+-----+ +-----------------------+ +-------+
|
||||
| IPI | --> | CPUINTC | <-- | Timer |
|
||||
+-----+ +-----------------------+ +-------+
|
||||
^ ^ ^
|
||||
| | |
|
||||
| +----------+ |
|
||||
+---------+ | AVECINTC | +---------+ +-------+
|
||||
| EIOINTC | +----------+ | LIOINTC | <-- | UARTs |
|
||||
+---------+ | REDIRECT | +---------+ +-------+
|
||||
^ +----------+
|
||||
| ^
|
||||
| |
|
||||
+---------+ +---------+
|
||||
| PCH-PIC | | PCH-MSI |
|
||||
+---------+ +---------+
|
||||
^ ^ ^
|
||||
| | |
|
||||
+---------+ +---------+ +---------+
|
||||
| Devices | | PCH-LPC | | Devices |
|
||||
+---------+ +---------+ +---------+
|
||||
^
|
||||
|
|
||||
+---------+
|
||||
| Devices |
|
||||
+---------+
|
||||
|
||||
ACPI相关的定义
|
||||
==============
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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 */
|
||||
|
|
|
|||
5
drivers/irqchip/.kunitconfig
Normal file
5
drivers/irqchip/.kunitconfig
Normal 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
|
||||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
473
drivers/irqchip/irq-ast2700-intc0-test.c
Normal file
473
drivers/irqchip/irq-ast2700-intc0-test.c
Normal 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");
|
||||
582
drivers/irqchip/irq-ast2700-intc0.c
Normal file
582
drivers/irqchip/irq-ast2700-intc0.c
Normal 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)
|
||||
280
drivers/irqchip/irq-ast2700-intc1.c
Normal file
280
drivers/irqchip/irq-ast2700-intc1.c
Normal 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)
|
||||
107
drivers/irqchip/irq-ast2700.c
Normal file
107
drivers/irqchip/irq-ast2700.c
Normal 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;
|
||||
}
|
||||
48
drivers/irqchip/irq-ast2700.h
Normal file
48
drivers/irqchip/irq-ast2700.h
Normal 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
|
||||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
|||
537
drivers/irqchip/irq-loongarch-ir.c
Normal file
537
drivers/irqchip/irq-loongarch-ir.c
Normal 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;
|
||||
}
|
||||
|
|
@ -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 */
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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))) {
|
||||
/*
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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>");
|
||||
254
drivers/irqchip/irq-starfive-jhb100-intc.c
Normal file
254
drivers/irqchip/irq-starfive-jhb100-intc.c
Normal 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>");
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user