Merge f20e2fdaae ("Merge tag 'timers-clocksource-2026-06-13' of gitolite.kernel.org:pub/scm/linux/kernel/git/tip/tip") into android-mainline
Steps on the way to v7.2-rc1 Change-Id: I9c90ece98c5f7d66f2f2e799484e220224e327bb Signed-off-by: Carlos Llamas <cmllamas@google.com>
This commit is contained in:
commit
c3601310a9
109 files changed
+4538
-1223
No files matched your search
@@ -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
|
||||
========================
|
||||
|
||||
|
||||
+17
-4
@@ -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>;
|
||||
};
|
||||
...
|
||||
+4
-16
@@ -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>;
|
||||
@@ -15,9 +15,13 @@ properties:
|
||||
oneOf:
|
||||
- const: allwinner,sun5i-a13-hstimer
|
||||
- const: allwinner,sun7i-a20-hstimer
|
||||
- const: allwinner,sun20i-d1-hstimer
|
||||
- items:
|
||||
- const: allwinner,sun6i-a31-hstimer
|
||||
- const: allwinner,sun7i-a20-hstimer
|
||||
- items:
|
||||
- const: allwinner,sun50i-h616-hstimer
|
||||
- const: allwinner,sun20i-d1-hstimer
|
||||
|
||||
reg:
|
||||
maxItems: 1
|
||||
@@ -45,7 +49,10 @@ required:
|
||||
if:
|
||||
properties:
|
||||
compatible:
|
||||
const: allwinner,sun5i-a13-hstimer
|
||||
anyOf:
|
||||
- const: allwinner,sun5i-a13-hstimer
|
||||
- contains:
|
||||
const: allwinner,sun20i-d1-hstimer
|
||||
|
||||
then:
|
||||
properties:
|
||||
|
||||
@@ -10,13 +10,8 @@ maintainers:
|
||||
- Marc Zyngier <marc.zyngier@arm.com>
|
||||
- Mark Rutland <mark.rutland@arm.com>
|
||||
description: |+
|
||||
ARM cores may have a per-core architected timer, which provides per-cpu timers,
|
||||
or a memory mapped architected timer, which provides up to 8 frames with a
|
||||
physical and optional virtual timer per frame.
|
||||
|
||||
The per-core architected timer is attached to a GIC to deliver its
|
||||
per-processor interrupts via PPIs. The memory mapped timer is attached to a GIC
|
||||
to deliver its interrupts via SPIs.
|
||||
The per-core architected timer is expected to deliver per-CPU interrupts
|
||||
(commonly to a GIC to deliver its per-processor interrupts as PPIs).
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
@@ -33,13 +28,13 @@ properties:
|
||||
- const: arm,armv7-timer
|
||||
|
||||
interrupts:
|
||||
minItems: 1
|
||||
minItems: 2
|
||||
items:
|
||||
- description: secure timer irq
|
||||
- description: non-secure timer irq
|
||||
- description: virtual timer irq
|
||||
- description: hypervisor timer irq
|
||||
- description: hypervisor virtual timer irq
|
||||
- description: EL1 secure physical timer irq, if EL3 is implemented
|
||||
- description: EL1 non-secure physical timer irq
|
||||
- description: EL1 virtual timer irq
|
||||
- description: EL2 physical timer irq, if EL2 is implemented
|
||||
- description: EL2 virtual timer irq, if FEAT_VHE is implemented
|
||||
|
||||
interrupt-names:
|
||||
oneOf:
|
||||
|
||||
@@ -14,6 +14,7 @@ properties:
|
||||
oneOf:
|
||||
- const: fsl,imx1-gpt
|
||||
- const: fsl,imx21-gpt
|
||||
- const: fsl,imx25-epit
|
||||
- items:
|
||||
- const: fsl,imx27-gpt
|
||||
- const: fsl,imx21-gpt
|
||||
|
||||
@@ -112,6 +112,8 @@ properties:
|
||||
- renesas,r9a07g043-mtu3 # RZ/{G2UL,Five}
|
||||
- renesas,r9a07g044-mtu3 # RZ/G2{L,LC}
|
||||
- renesas,r9a07g054-mtu3 # RZ/V2L
|
||||
- renesas,r9a09g077-mtu3 # RZ/T2H
|
||||
- renesas,r9a09g087-mtu3 # RZ/N2H
|
||||
- const: renesas,rz-mtu3
|
||||
|
||||
reg:
|
||||
@@ -162,7 +164,6 @@ properties:
|
||||
- description: MTU8.TGRC input capture/compare match
|
||||
- description: MTU8.TGRD input capture/compare match
|
||||
- description: MTU8.TCNT overflow
|
||||
- description: MTU8.TCNT underflow
|
||||
|
||||
interrupt-names:
|
||||
items:
|
||||
@@ -209,7 +210,6 @@ properties:
|
||||
- const: tgic8
|
||||
- const: tgid8
|
||||
- const: tciv8
|
||||
- const: tciu8
|
||||
|
||||
clocks:
|
||||
maxItems: 1
|
||||
@@ -233,7 +233,22 @@ required:
|
||||
- interrupt-names
|
||||
- clocks
|
||||
- power-domains
|
||||
- resets
|
||||
|
||||
allOf:
|
||||
- if:
|
||||
properties:
|
||||
compatible:
|
||||
contains:
|
||||
enum:
|
||||
- renesas,r9a07g043-mtu3
|
||||
- renesas,r9a07g044-mtu3
|
||||
- renesas,r9a07g054-mtu3
|
||||
then:
|
||||
required:
|
||||
- resets
|
||||
else:
|
||||
properties:
|
||||
resets: false
|
||||
|
||||
additionalProperties: false
|
||||
|
||||
@@ -287,8 +302,7 @@ examples:
|
||||
<GIC_SPI 209 IRQ_TYPE_EDGE_RISING>,
|
||||
<GIC_SPI 210 IRQ_TYPE_EDGE_RISING>,
|
||||
<GIC_SPI 211 IRQ_TYPE_EDGE_RISING>,
|
||||
<GIC_SPI 212 IRQ_TYPE_EDGE_RISING>,
|
||||
<GIC_SPI 213 IRQ_TYPE_EDGE_RISING>;
|
||||
<GIC_SPI 212 IRQ_TYPE_EDGE_RISING>;
|
||||
interrupt-names = "tgia0", "tgib0", "tgic0", "tgid0", "tciv0", "tgie0",
|
||||
"tgif0",
|
||||
"tgia1", "tgib1", "tciv1", "tciu1",
|
||||
@@ -298,7 +312,7 @@ examples:
|
||||
"tgiu5", "tgiv5", "tgiw5",
|
||||
"tgia6", "tgib6", "tgic6", "tgid6", "tciv6",
|
||||
"tgia7", "tgib7", "tgic7", "tgid7", "tciv7",
|
||||
"tgia8", "tgib8", "tgic8", "tgid8", "tciv8", "tciu8";
|
||||
"tgia8", "tgib8", "tgic8", "tgid8", "tciv8";
|
||||
clocks = <&cpg CPG_MOD R9A07G044_MTU_X_MCK_MTU3>;
|
||||
power-domains = <&cpg>;
|
||||
resets = <&cpg R9A07G044_MTU_X_PRESET_MTU3>;
|
||||
|
||||
@@ -38,6 +38,7 @@ properties:
|
||||
- starfive,jh7100-clint # StarFive JH7100
|
||||
- starfive,jh7110-clint # StarFive JH7110
|
||||
- starfive,jh8100-clint # StarFive JH8100
|
||||
- starfive,jhb100-clint # StarFive JHB100
|
||||
- tenstorrent,blackhole-clint # Tenstorrent Blackhole
|
||||
- const: sifive,clint0 # SiFive CLINT v0 IP block
|
||||
- items:
|
||||
@@ -72,22 +73,6 @@ properties:
|
||||
minItems: 1
|
||||
maxItems: 4095
|
||||
|
||||
sifive,fine-ctr-bits:
|
||||
maximum: 15
|
||||
description: The width in bits of the fine counter.
|
||||
|
||||
if:
|
||||
properties:
|
||||
compatible:
|
||||
contains:
|
||||
const: sifive,clint2
|
||||
then:
|
||||
required:
|
||||
- sifive,fine-ctr-bits
|
||||
else:
|
||||
properties:
|
||||
sifive,fine-ctr-bits: false
|
||||
|
||||
additionalProperties: false
|
||||
|
||||
required:
|
||||
|
||||
@@ -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相关的定义
|
||||
==============
|
||||
|
||||
|
||||
+4
-3
@@ -6677,6 +6677,7 @@ P: Documentation/process/maintainer-tip.rst
|
||||
T: git git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip.git smp/core
|
||||
F: include/linux/cpu.h
|
||||
F: include/linux/cpuhotplug.h
|
||||
F: include/linux/cpuhplock.h
|
||||
F: include/linux/smpboot.h
|
||||
F: kernel/cpu.c
|
||||
F: kernel/smpboot.*
|
||||
@@ -25606,11 +25607,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>
|
||||
|
||||
@@ -72,16 +72,16 @@ int arch_show_interrupts(struct seq_file *p, int prec)
|
||||
int j;
|
||||
|
||||
#ifdef CONFIG_SMP
|
||||
seq_puts(p, "IPI: ");
|
||||
seq_puts(p, " IPI: ");
|
||||
for_each_online_cpu(j)
|
||||
seq_printf(p, "%10lu ", cpu_data[j].ipi_count);
|
||||
seq_putc(p, '\n');
|
||||
#endif
|
||||
seq_puts(p, "PMI: ");
|
||||
seq_puts(p, " PMI: ");
|
||||
for_each_online_cpu(j)
|
||||
seq_printf(p, "%10lu ", per_cpu(irq_pmi_count, j));
|
||||
seq_puts(p, " Performance Monitoring\n");
|
||||
seq_printf(p, "ERR: %10lu\n", irq_err_count);
|
||||
seq_puts(p, " Performance Monitoring\n");
|
||||
seq_printf(p, " ERR: %10lu\n", irq_err_count);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -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 */
|
||||
@@ -555,8 +555,7 @@ void show_ipi_list(struct seq_file *p, int prec)
|
||||
if (!ipi_desc[i])
|
||||
continue;
|
||||
|
||||
seq_printf(p, "%*s%u:%s", prec - 1, "IPI", i,
|
||||
prec >= 4 ? " " : "");
|
||||
seq_printf(p, "%*s%u:", prec - 1, "IPI", i);
|
||||
|
||||
for_each_online_cpu(cpu)
|
||||
seq_printf(p, "%10u ", irq_desc_kstat_cpu(ipi_desc[i], cpu));
|
||||
|
||||
@@ -227,27 +227,6 @@ config SOC_VF610
|
||||
help
|
||||
This enables support for Freescale Vybrid VF610 processor.
|
||||
|
||||
choice
|
||||
prompt "Clocksource for scheduler clock"
|
||||
depends on SOC_VF610
|
||||
default VF_USE_ARM_GLOBAL_TIMER
|
||||
|
||||
config VF_USE_ARM_GLOBAL_TIMER
|
||||
bool "Use ARM Global Timer"
|
||||
depends on ARCH_MULTI_V7
|
||||
select ARM_GLOBAL_TIMER
|
||||
select CLKSRC_ARM_GLOBAL_TIMER_SCHED_CLOCK
|
||||
help
|
||||
Use the ARM Global Timer as clocksource
|
||||
|
||||
config VF_USE_PIT_TIMER
|
||||
bool "Use PIT timer"
|
||||
select NXP_PIT_TIMER
|
||||
help
|
||||
Use SoC Periodic Interrupt Timer (PIT) as clocksource
|
||||
|
||||
endchoice
|
||||
|
||||
endif
|
||||
|
||||
endif
|
||||
@@ -839,11 +839,10 @@ int arch_show_interrupts(struct seq_file *p, int prec)
|
||||
unsigned int cpu, i;
|
||||
|
||||
for (i = 0; i < MAX_IPI; i++) {
|
||||
seq_printf(p, "%*s%u:%s", prec - 1, "IPI", i,
|
||||
prec >= 4 ? " " : "");
|
||||
seq_printf(p, "%*s%u: ", prec - 1, "IPI", i);
|
||||
for_each_online_cpu(cpu)
|
||||
seq_printf(p, "%10u ", irq_desc_kstat_cpu(get_ipi_desc(cpu, i), cpu));
|
||||
seq_printf(p, " %s\n", ipi_types[i]);
|
||||
seq_printf(p, " %s\n", ipi_types[i]);
|
||||
}
|
||||
|
||||
seq_printf(p, "%*s: %10lu\n", prec, "Err", irq_err_count);
|
||||
|
||||
@@ -88,7 +88,7 @@ void show_ipi_list(struct seq_file *p, int prec)
|
||||
unsigned int cpu, i;
|
||||
|
||||
for (i = 0; i < NR_IPI; i++) {
|
||||
seq_printf(p, "%*s%u:%s", prec - 1, "IPI", i, prec >= 4 ? " " : "");
|
||||
seq_printf(p, "%*s%u:", prec - 1, "IPI", i);
|
||||
for_each_online_cpu(cpu)
|
||||
seq_put_decimal_ull_width(p, " ", per_cpu(irq_stat, cpu).ipi_irqs[i], 10);
|
||||
seq_printf(p, " LoongArch %d %s\n", i + 1, ipi_types[i]);
|
||||
|
||||
@@ -226,8 +226,7 @@ void show_ipi_stats(struct seq_file *p, int prec)
|
||||
unsigned int cpu, i;
|
||||
|
||||
for (i = 0; i < IPI_MAX; i++) {
|
||||
seq_printf(p, "%*s%u:%s", prec - 1, "IPI", i,
|
||||
prec >= 4 ? " " : "");
|
||||
seq_printf(p, "%*s%u:", prec - 1, "IPI", i);
|
||||
for_each_online_cpu(cpu)
|
||||
seq_printf(p, "%10u ", irq_desc_kstat_cpu(ipi_desc[i], cpu));
|
||||
seq_printf(p, " %s\n", ipi_names[i]);
|
||||
|
||||
@@ -46,7 +46,7 @@ int arch_show_interrupts(struct seq_file *p, int prec)
|
||||
seq_printf(p, "%*s:", prec, "NMI");
|
||||
for_each_online_cpu(j)
|
||||
seq_put_decimal_ull_width(p, " ", per_cpu(irq_stat.__nmi_count, j), 10);
|
||||
seq_printf(p, " Non-maskable interrupts\n");
|
||||
seq_printf(p, " Non-maskable interrupts\n");
|
||||
|
||||
seq_printf(p, "%*s: %10u\n", prec, "ERR", atomic_read(&irq_err_count));
|
||||
|
||||
|
||||
@@ -199,19 +199,19 @@ int arch_show_interrupts(struct seq_file *p, int prec)
|
||||
int j;
|
||||
|
||||
#ifdef CONFIG_SMP
|
||||
seq_printf(p, "RES:");
|
||||
seq_printf(p, "%*s:", prec, "RES");
|
||||
for_each_online_cpu(j)
|
||||
seq_put_decimal_ull_width(p, " ", cpu_data(j).irq_resched_count, 10);
|
||||
seq_printf(p, " IPI rescheduling interrupts\n");
|
||||
seq_printf(p, "CAL:");
|
||||
seq_printf(p, " IPI rescheduling interrupts\n");
|
||||
seq_printf(p, "%*s:", prec, "CAL");
|
||||
for_each_online_cpu(j)
|
||||
seq_put_decimal_ull_width(p, " ", cpu_data(j).irq_call_count, 10);
|
||||
seq_printf(p, " IPI function call interrupts\n");
|
||||
seq_printf(p, " IPI function call interrupts\n");
|
||||
#endif
|
||||
seq_printf(p, "NMI:");
|
||||
seq_printf(p, "%*s:", prec, "NMI");
|
||||
for_each_online_cpu(j)
|
||||
seq_put_decimal_ull_width(p, " ", cpu_data(j).counter, 10);
|
||||
seq_printf(p, " Non-maskable interrupts\n");
|
||||
seq_printf(p, " Non-maskable interrupts\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -303,10 +303,10 @@ int arch_show_interrupts(struct seq_file *p, int prec)
|
||||
{
|
||||
int j;
|
||||
|
||||
seq_printf(p, "NMI:");
|
||||
seq_printf(p, "%*s:", prec, "NMI");
|
||||
for_each_online_cpu(j)
|
||||
seq_put_decimal_ull_width(p, " ", cpu_data(j).__nmi_count, 10);
|
||||
seq_printf(p, " Non-maskable interrupts\n");
|
||||
seq_printf(p, " Non-maskable interrupts\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -716,12 +716,12 @@ int arch_show_interrupts(struct seq_file *p, int prec)
|
||||
seq_printf(p, "%*s: ", prec, "RES");
|
||||
for_each_online_cpu(cpu)
|
||||
seq_printf(p, "%10u ", irq_stats(cpu)->irq_resched_count);
|
||||
seq_puts(p, " Rescheduling interrupts\n");
|
||||
seq_puts(p, " Rescheduling interrupts\n");
|
||||
|
||||
seq_printf(p, "%*s: ", prec, "CAL");
|
||||
for_each_online_cpu(cpu)
|
||||
seq_printf(p, "%10u ", irq_stats(cpu)->irq_call_count);
|
||||
seq_puts(p, " Function call interrupts\n");
|
||||
seq_puts(p, " Function call interrupts\n");
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
|
||||
@@ -1032,7 +1032,7 @@ static int amd_pmu_v2_handle_irq(struct pt_regs *regs)
|
||||
* Unmasking the LVTPC is not required as the Mask (M) bit of the LVT
|
||||
* PMI entry is not set by the local APIC when a PMC overflow occurs
|
||||
*/
|
||||
inc_irq_stat(apic_perf_irqs);
|
||||
inc_perf_irq_stat();
|
||||
|
||||
done:
|
||||
cpuc->enabled = pmu_enabled;
|
||||
|
||||
@@ -1600,7 +1600,7 @@ perf_ibs_nmi_handler(unsigned int cmd, struct pt_regs *regs)
|
||||
handled += perf_ibs_handle_irq(&perf_ibs_op, regs);
|
||||
|
||||
if (handled)
|
||||
inc_irq_stat(apic_perf_irqs);
|
||||
inc_perf_irq_stat();
|
||||
|
||||
perf_sample_event_took(sched_clock() - stamp);
|
||||
|
||||
|
||||
@@ -1753,7 +1753,7 @@ int x86_pmu_handle_irq(struct pt_regs *regs)
|
||||
}
|
||||
|
||||
if (handled)
|
||||
inc_irq_stat(apic_perf_irqs);
|
||||
inc_perf_irq_stat();
|
||||
|
||||
return handled;
|
||||
}
|
||||
|
||||
@@ -3531,7 +3531,7 @@ static int handle_pmi_common(struct pt_regs *regs, u64 status)
|
||||
int bit;
|
||||
int handled = 0;
|
||||
|
||||
inc_irq_stat(apic_perf_irqs);
|
||||
inc_perf_irq_stat();
|
||||
|
||||
/*
|
||||
* Ignore a range of extra bits in status that do not indicate
|
||||
|
||||
@@ -238,7 +238,7 @@ again:
|
||||
goto done;
|
||||
}
|
||||
|
||||
inc_irq_stat(apic_perf_irqs);
|
||||
inc_perf_irq_stat();
|
||||
|
||||
for_each_set_bit(bit, (unsigned long *)&status, X86_PMC_IDX_MAX) {
|
||||
struct perf_event *event = cpuc->events[bit];
|
||||
|
||||
@@ -1077,7 +1077,7 @@ static int p4_pmu_handle_irq(struct pt_regs *regs)
|
||||
}
|
||||
|
||||
if (handled)
|
||||
inc_irq_stat(apic_perf_irqs);
|
||||
inc_perf_irq_stat();
|
||||
|
||||
/*
|
||||
* When dealing with the unmasking of the LVTPC on P4 perf hw, it has
|
||||
|
||||
@@ -373,7 +373,7 @@ again:
|
||||
else
|
||||
zhaoxin_pmu_ack_status(status);
|
||||
|
||||
inc_irq_stat(apic_perf_irqs);
|
||||
inc_perf_irq_stat();
|
||||
|
||||
/*
|
||||
* CondChgd bit 63 doesn't mean any overflow status. Ignore
|
||||
|
||||
@@ -219,7 +219,7 @@ static inline bool hv_reenlightenment_available(void)
|
||||
DEFINE_IDTENTRY_SYSVEC(sysvec_hyperv_reenlightenment)
|
||||
{
|
||||
apic_eoi();
|
||||
inc_irq_stat(irq_hv_reenlightenment_count);
|
||||
inc_irq_stat(HYPERV_REENLIGHTENMENT);
|
||||
schedule_delayed_work(&hv_reenlightenment_work, HZ/10);
|
||||
}
|
||||
|
||||
|
||||
@@ -4,51 +4,64 @@
|
||||
|
||||
#include <linux/threads.h>
|
||||
|
||||
enum irq_stat_counts {
|
||||
IRQ_COUNT_NMI,
|
||||
#ifdef CONFIG_X86_LOCAL_APIC
|
||||
IRQ_COUNT_APIC_TIMER,
|
||||
IRQ_COUNT_SPURIOUS,
|
||||
IRQ_COUNT_APIC_PERF,
|
||||
IRQ_COUNT_IRQ_WORK,
|
||||
IRQ_COUNT_ICR_READ_RETRY,
|
||||
IRQ_COUNT_X86_PLATFORM_IPI,
|
||||
#endif
|
||||
#ifdef CONFIG_SMP
|
||||
IRQ_COUNT_RESCHEDULE,
|
||||
IRQ_COUNT_CALL_FUNCTION,
|
||||
#endif
|
||||
IRQ_COUNT_TLB,
|
||||
#ifdef CONFIG_X86_THERMAL_VECTOR
|
||||
IRQ_COUNT_THERMAL_APIC,
|
||||
#endif
|
||||
#ifdef CONFIG_X86_MCE_THRESHOLD
|
||||
IRQ_COUNT_THRESHOLD_APIC,
|
||||
#endif
|
||||
#ifdef CONFIG_X86_MCE_AMD
|
||||
IRQ_COUNT_DEFERRED_ERROR,
|
||||
#endif
|
||||
#ifdef CONFIG_X86_MCE
|
||||
IRQ_COUNT_MCE_EXCEPTION,
|
||||
IRQ_COUNT_MCE_POLL,
|
||||
#endif
|
||||
#ifdef CONFIG_X86_HV_CALLBACK_VECTOR
|
||||
IRQ_COUNT_HYPERVISOR_CALLBACK,
|
||||
#endif
|
||||
#if IS_ENABLED(CONFIG_HYPERV)
|
||||
IRQ_COUNT_HYPERV_REENLIGHTENMENT,
|
||||
IRQ_COUNT_HYPERV_STIMER0,
|
||||
#endif
|
||||
#if IS_ENABLED(CONFIG_KVM)
|
||||
IRQ_COUNT_POSTED_INTR,
|
||||
IRQ_COUNT_POSTED_INTR_NESTED,
|
||||
IRQ_COUNT_POSTED_INTR_WAKEUP,
|
||||
#endif
|
||||
#ifdef CONFIG_GUEST_PERF_EVENTS
|
||||
IRQ_COUNT_PERF_GUEST_MEDIATED_PMI,
|
||||
#endif
|
||||
#ifdef CONFIG_X86_POSTED_MSI
|
||||
IRQ_COUNT_POSTED_MSI_NOTIFICATION,
|
||||
#endif
|
||||
IRQ_COUNT_PIC_APIC_ERROR,
|
||||
#ifdef CONFIG_X86_IO_APIC
|
||||
IRQ_COUNT_IOAPIC_MISROUTED,
|
||||
#endif
|
||||
IRQ_COUNT_MAX,
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
#if IS_ENABLED(CONFIG_CPU_MITIGATIONS) && IS_ENABLED(CONFIG_KVM_INTEL)
|
||||
u8 kvm_cpu_l1tf_flush_l1d;
|
||||
#endif
|
||||
unsigned int __nmi_count; /* arch dependent */
|
||||
#ifdef CONFIG_X86_LOCAL_APIC
|
||||
unsigned int apic_timer_irqs; /* arch dependent */
|
||||
unsigned int irq_spurious_count;
|
||||
unsigned int icr_read_retry_count;
|
||||
#endif
|
||||
#if IS_ENABLED(CONFIG_KVM)
|
||||
unsigned int kvm_posted_intr_ipis;
|
||||
unsigned int kvm_posted_intr_wakeup_ipis;
|
||||
unsigned int kvm_posted_intr_nested_ipis;
|
||||
#endif
|
||||
#ifdef CONFIG_GUEST_PERF_EVENTS
|
||||
unsigned int perf_guest_mediated_pmis;
|
||||
#endif
|
||||
unsigned int x86_platform_ipis; /* arch dependent */
|
||||
unsigned int apic_perf_irqs;
|
||||
unsigned int apic_irq_work_irqs;
|
||||
#ifdef CONFIG_SMP
|
||||
unsigned int irq_resched_count;
|
||||
unsigned int irq_call_count;
|
||||
#endif
|
||||
unsigned int irq_tlb_count;
|
||||
#ifdef CONFIG_X86_THERMAL_VECTOR
|
||||
unsigned int irq_thermal_count;
|
||||
#endif
|
||||
#ifdef CONFIG_X86_MCE_THRESHOLD
|
||||
unsigned int irq_threshold_count;
|
||||
#endif
|
||||
#ifdef CONFIG_X86_MCE_AMD
|
||||
unsigned int irq_deferred_error_count;
|
||||
#endif
|
||||
#ifdef CONFIG_X86_HV_CALLBACK_VECTOR
|
||||
unsigned int irq_hv_callback_count;
|
||||
#endif
|
||||
#if IS_ENABLED(CONFIG_HYPERV)
|
||||
unsigned int irq_hv_reenlightenment_count;
|
||||
unsigned int hyperv_stimer0_count;
|
||||
#endif
|
||||
#ifdef CONFIG_X86_POSTED_MSI
|
||||
unsigned int posted_msi_notification_count;
|
||||
#endif
|
||||
unsigned int counts[IRQ_COUNT_MAX];
|
||||
} ____cacheline_aligned irq_cpustat_t;
|
||||
|
||||
DECLARE_PER_CPU_SHARED_ALIGNED(irq_cpustat_t, irq_stat);
|
||||
@@ -58,15 +71,21 @@ DECLARE_PER_CPU_ALIGNED(struct pi_desc, posted_msi_pi_desc);
|
||||
#endif
|
||||
#define __ARCH_IRQ_STAT
|
||||
|
||||
#define inc_irq_stat(member) this_cpu_inc(irq_stat.member)
|
||||
#define inc_irq_stat(index) this_cpu_inc(irq_stat.counts[IRQ_COUNT_##index])
|
||||
void irq_stat_inc_and_enable(enum irq_stat_counts which);
|
||||
|
||||
#ifdef CONFIG_X86_LOCAL_APIC
|
||||
#define inc_perf_irq_stat() inc_irq_stat(APIC_PERF)
|
||||
#else
|
||||
#define inc_perf_irq_stat() do { } while (0)
|
||||
#endif
|
||||
|
||||
extern void ack_bad_irq(unsigned int irq);
|
||||
|
||||
#ifdef CONFIG_PROC_FS
|
||||
extern u64 arch_irq_stat_cpu(unsigned int cpu);
|
||||
#define arch_irq_stat_cpu arch_irq_stat_cpu
|
||||
|
||||
extern u64 arch_irq_stat(void);
|
||||
#define arch_irq_stat arch_irq_stat
|
||||
#endif
|
||||
|
||||
DECLARE_PER_CPU_CACHE_HOT(u16, __softirq_pending);
|
||||
#define local_softirq_pending_ref __softirq_pending
|
||||
|
||||
@@ -110,10 +110,6 @@ static inline void lock_vector_lock(void) {}
|
||||
static inline void unlock_vector_lock(void) {}
|
||||
#endif
|
||||
|
||||
/* Statistics */
|
||||
extern atomic_t irq_err_count;
|
||||
extern atomic_t irq_mis_count;
|
||||
|
||||
extern void elcr_set_level_irq(unsigned int irq);
|
||||
|
||||
extern char irq_entries_start[];
|
||||
|
||||
@@ -291,9 +291,6 @@ bool mce_is_memory_error(struct mce *m);
|
||||
bool mce_is_correctable(struct mce *m);
|
||||
bool mce_usable_address(struct mce *m);
|
||||
|
||||
DECLARE_PER_CPU(unsigned, mce_exception_count);
|
||||
DECLARE_PER_CPU(unsigned, mce_poll_count);
|
||||
|
||||
typedef DECLARE_BITMAP(mce_banks_t, MAX_NR_BANKS);
|
||||
DECLARE_PER_CPU(mce_banks_t, mce_poll_banks);
|
||||
|
||||
|
||||
@@ -1045,7 +1045,7 @@ static void local_apic_timer_interrupt(void)
|
||||
/*
|
||||
* the NMI deadlock-detector uses this.
|
||||
*/
|
||||
inc_irq_stat(apic_timer_irqs);
|
||||
inc_irq_stat(APIC_TIMER);
|
||||
|
||||
evt->event_handler(evt);
|
||||
}
|
||||
@@ -2114,7 +2114,7 @@ static noinline void handle_spurious_interrupt(u8 vector)
|
||||
|
||||
trace_spurious_apic_entry(vector);
|
||||
|
||||
inc_irq_stat(irq_spurious_count);
|
||||
irq_stat_inc_and_enable(IRQ_COUNT_SPURIOUS);
|
||||
|
||||
/*
|
||||
* If this is a spurious interrupt then do not acknowledge
|
||||
@@ -2186,7 +2186,7 @@ DEFINE_IDTENTRY_SYSVEC(sysvec_error_interrupt)
|
||||
apic_write(APIC_ESR, 0);
|
||||
v = apic_read(APIC_ESR);
|
||||
apic_eoi();
|
||||
atomic_inc(&irq_err_count);
|
||||
irq_stat_inc_and_enable(IRQ_COUNT_PIC_APIC_ERROR);
|
||||
|
||||
apic_pr_debug("APIC error on CPU%d: %02x", smp_processor_id(), v);
|
||||
|
||||
|
||||
@@ -1575,8 +1575,6 @@ static unsigned int startup_ioapic_irq(struct irq_data *data)
|
||||
return was_pending;
|
||||
}
|
||||
|
||||
atomic_t irq_mis_count;
|
||||
|
||||
#ifdef CONFIG_GENERIC_PENDING_IRQ
|
||||
static bool io_apic_level_ack_pending(struct mp_chip_data *data)
|
||||
{
|
||||
@@ -1713,7 +1711,7 @@ static void ioapic_ack_level(struct irq_data *irq_data)
|
||||
* at the cpu.
|
||||
*/
|
||||
if (!(v & (1 << (i & 0x1f)))) {
|
||||
atomic_inc(&irq_mis_count);
|
||||
irq_stat_inc_and_enable(IRQ_COUNT_IOAPIC_MISROUTED);
|
||||
eoi_ioapic_pin(cfg->vector, irq_data->chip_data);
|
||||
}
|
||||
|
||||
|
||||
@@ -120,7 +120,7 @@ u32 apic_mem_wait_icr_idle_timeout(void)
|
||||
for (cnt = 0; cnt < 1000; cnt++) {
|
||||
if (!(apic_read(APIC_ICR) & APIC_ICR_BUSY))
|
||||
return 0;
|
||||
inc_irq_stat(icr_read_retry_count);
|
||||
irq_stat_inc_and_enable(IRQ_COUNT_ICR_READ_RETRY);
|
||||
udelay(100);
|
||||
}
|
||||
return APIC_ICR_BUSY;
|
||||
|
||||
@@ -52,7 +52,7 @@ DEFINE_IDTENTRY_SYSVEC(sysvec_acrn_hv_callback)
|
||||
* HYPERVISOR_CALLBACK_VECTOR.
|
||||
*/
|
||||
apic_eoi();
|
||||
inc_irq_stat(irq_hv_callback_count);
|
||||
inc_irq_stat(HYPERVISOR_CALLBACK);
|
||||
|
||||
if (acrn_intr_handler)
|
||||
acrn_intr_handler();
|
||||
|
||||
@@ -850,7 +850,7 @@ bool amd_mce_usable_address(struct mce *m)
|
||||
DEFINE_IDTENTRY_SYSVEC(sysvec_deferred_error)
|
||||
{
|
||||
trace_deferred_error_apic_entry(DEFERRED_ERROR_VECTOR);
|
||||
inc_irq_stat(irq_deferred_error_count);
|
||||
inc_irq_stat(DEFERRED_ERROR);
|
||||
deferred_error_int_vector();
|
||||
trace_deferred_error_apic_exit(DEFERRED_ERROR_VECTOR);
|
||||
apic_eoi();
|
||||
|
||||
@@ -67,8 +67,6 @@ static DEFINE_MUTEX(mce_sysfs_mutex);
|
||||
|
||||
#define SPINUNIT 100 /* 100ns */
|
||||
|
||||
DEFINE_PER_CPU(unsigned, mce_exception_count);
|
||||
|
||||
DEFINE_PER_CPU_READ_MOSTLY(unsigned int, mce_num_banks);
|
||||
|
||||
DEFINE_PER_CPU_READ_MOSTLY(struct mce_bank[MAX_NR_BANKS], mce_banks_array);
|
||||
@@ -693,8 +691,6 @@ static noinstr void mce_read_aux(struct mce_hw_err *err, int i)
|
||||
}
|
||||
}
|
||||
|
||||
DEFINE_PER_CPU(unsigned, mce_poll_count);
|
||||
|
||||
/*
|
||||
* We have three scenarios for checking for Deferred errors:
|
||||
*
|
||||
@@ -797,7 +793,7 @@ void machine_check_poll(enum mcp_flags flags, mce_banks_t *b)
|
||||
struct mce *m;
|
||||
int i;
|
||||
|
||||
this_cpu_inc(mce_poll_count);
|
||||
inc_irq_stat(MCE_POLL);
|
||||
|
||||
mce_gather_info(&err, NULL);
|
||||
m = &err.m;
|
||||
@@ -1572,7 +1568,7 @@ noinstr void do_machine_check(struct pt_regs *regs)
|
||||
*/
|
||||
lmce = 1;
|
||||
|
||||
this_cpu_inc(mce_exception_count);
|
||||
inc_irq_stat(MCE_EXCEPTION);
|
||||
|
||||
mce_gather_info(&err, regs);
|
||||
m = &err.m;
|
||||
|
||||
@@ -37,7 +37,7 @@ void (*mce_threshold_vector)(void) = default_threshold_interrupt;
|
||||
DEFINE_IDTENTRY_SYSVEC(sysvec_threshold)
|
||||
{
|
||||
trace_threshold_apic_entry(THRESHOLD_APIC_VECTOR);
|
||||
inc_irq_stat(irq_threshold_count);
|
||||
inc_irq_stat(THRESHOLD_APIC);
|
||||
mce_threshold_vector();
|
||||
trace_threshold_apic_exit(THRESHOLD_APIC_VECTOR);
|
||||
apic_eoi();
|
||||
|
||||
@@ -154,7 +154,7 @@ DEFINE_IDTENTRY_SYSVEC(sysvec_hyperv_callback)
|
||||
{
|
||||
struct pt_regs *old_regs = set_irq_regs(regs);
|
||||
|
||||
inc_irq_stat(irq_hv_callback_count);
|
||||
inc_irq_stat(HYPERVISOR_CALLBACK);
|
||||
if (mshv_handler)
|
||||
mshv_handler();
|
||||
|
||||
@@ -193,7 +193,7 @@ DEFINE_IDTENTRY_SYSVEC(sysvec_hyperv_stimer0)
|
||||
{
|
||||
struct pt_regs *old_regs = set_irq_regs(regs);
|
||||
|
||||
inc_irq_stat(hyperv_stimer0_count);
|
||||
inc_irq_stat(HYPERV_STIMER0);
|
||||
if (hv_stimer0_handler)
|
||||
hv_stimer0_handler();
|
||||
add_interrupt_randomness(HYPERV_STIMER0_VECTOR);
|
||||
|
||||
@@ -214,7 +214,7 @@ spurious_8259A_irq:
|
||||
"spurious 8259A interrupt: IRQ%d.\n", irq);
|
||||
spurious_irq_mask |= irqmask;
|
||||
}
|
||||
atomic_inc(&irq_err_count);
|
||||
irq_stat_inc_and_enable(IRQ_COUNT_PIC_APIC_ERROR);
|
||||
/*
|
||||
* Theoretically we do not have to handle this IRQ,
|
||||
* but in Linux this does not cause problems and is
|
||||
|
||||
+130
-183
@@ -39,8 +39,6 @@ EXPORT_PER_CPU_SYMBOL(__softirq_pending);
|
||||
|
||||
DEFINE_PER_CPU_CACHE_HOT(struct irq_stack *, hardirq_stack_ptr);
|
||||
|
||||
atomic_t irq_err_count;
|
||||
|
||||
/*
|
||||
* 'what should we do if we get a hw irq event on an illegal vector'.
|
||||
* each architecture has to answer this themselves.
|
||||
@@ -62,150 +60,131 @@ void ack_bad_irq(unsigned int irq)
|
||||
apic_eoi();
|
||||
}
|
||||
|
||||
#define irq_stats(x) (&per_cpu(irq_stat, x))
|
||||
struct irq_stat_info {
|
||||
unsigned int skip_vector;
|
||||
const char *symbol;
|
||||
const char *text;
|
||||
};
|
||||
|
||||
#define DEFAULT_SUPPRESSED_VECTOR UINT_MAX
|
||||
|
||||
#define ISS(idx, sym, txt) [IRQ_COUNT_##idx] = { .symbol = sym, .text = txt }
|
||||
|
||||
#define ITS(idx, sym, txt) [IRQ_COUNT_##idx] = \
|
||||
{ .skip_vector = idx## _VECTOR, .symbol = sym, .text = txt }
|
||||
|
||||
#define IDS(idx, sym, txt) [IRQ_COUNT_##idx] = \
|
||||
{ .skip_vector = DEFAULT_SUPPRESSED_VECTOR, .symbol = sym, .text = txt }
|
||||
|
||||
static const struct irq_stat_info irq_stat_info[IRQ_COUNT_MAX] = {
|
||||
ISS(NMI, "NMI", " Non-maskable interrupts\n"),
|
||||
#ifdef CONFIG_X86_LOCAL_APIC
|
||||
ISS(APIC_TIMER, "LOC", " Local timer interrupts\n"),
|
||||
IDS(SPURIOUS, "SPU", " Spurious interrupts\n"),
|
||||
ISS(APIC_PERF, "PMI", " Performance monitoring interrupts\n"),
|
||||
ISS(IRQ_WORK, "IWI", " IRQ work interrupts\n"),
|
||||
IDS(ICR_READ_RETRY, "RTR", " APIC ICR read retries\n"),
|
||||
ISS(X86_PLATFORM_IPI, "PLT", " Platform interrupts\n"),
|
||||
#endif
|
||||
#ifdef CONFIG_SMP
|
||||
ISS(RESCHEDULE, "RES", " Rescheduling interrupts\n"),
|
||||
ISS(CALL_FUNCTION, "CAL", " Function call interrupts\n"),
|
||||
#endif
|
||||
ISS(TLB, "TLB", " TLB shootdowns\n"),
|
||||
#ifdef CONFIG_X86_THERMAL_VECTOR
|
||||
ISS(THERMAL_APIC, "TRM", " Thermal event interrupts\n"),
|
||||
#endif
|
||||
#ifdef CONFIG_X86_MCE_THRESHOLD
|
||||
ISS(THRESHOLD_APIC, "THR", " Threshold APIC interrupts\n"),
|
||||
#endif
|
||||
#ifdef CONFIG_X86_MCE_AMD
|
||||
ISS(DEFERRED_ERROR, "DFR", " Deferred Error APIC interrupts\n"),
|
||||
#endif
|
||||
#ifdef CONFIG_X86_MCE
|
||||
ISS(MCE_EXCEPTION, "MCE", " Machine check exceptions\n"),
|
||||
ISS(MCE_POLL, "MCP", " Machine check polls\n"),
|
||||
#endif
|
||||
#ifdef CONFIG_X86_HV_CALLBACK_VECTOR
|
||||
ITS(HYPERVISOR_CALLBACK, "HYP", " Hypervisor callback interrupts\n"),
|
||||
#endif
|
||||
#if IS_ENABLED(CONFIG_HYPERV)
|
||||
ITS(HYPERV_REENLIGHTENMENT, "HRE", " Hyper-V reenlightenment interrupts\n"),
|
||||
ITS(HYPERV_STIMER0, "HVS", " Hyper-V stimer0 interrupts\n"),
|
||||
#endif
|
||||
#if IS_ENABLED(CONFIG_KVM)
|
||||
ITS(POSTED_INTR, "PIN", " Posted-interrupt notification event\n"),
|
||||
ITS(POSTED_INTR_NESTED, "NPI", " Nested posted-interrupt event\n"),
|
||||
ITS(POSTED_INTR_WAKEUP, "PIW", " Posted-interrupt wakeup event\n"),
|
||||
#endif
|
||||
#ifdef CONFIG_GUEST_PERF_EVENTS
|
||||
ISS(PERF_GUEST_MEDIATED_PMI, "VPMI", " Perf Guest Mediated PMI\n"),
|
||||
#endif
|
||||
#ifdef CONFIG_X86_POSTED_MSI
|
||||
ISS(POSTED_MSI_NOTIFICATION, "PMN", " Posted MSI notification event\n"),
|
||||
#endif
|
||||
IDS(PIC_APIC_ERROR, "ERR", " PIC/APIC error interrupts\n"),
|
||||
#ifdef CONFIG_X86_IO_APIC
|
||||
IDS(IOAPIC_MISROUTED, "MIS", " Misrouted IO/APIC interrupts\n"),
|
||||
#endif
|
||||
};
|
||||
|
||||
static DECLARE_BITMAP(irq_stat_count_show, IRQ_COUNT_MAX) __read_mostly;
|
||||
|
||||
static int __init irq_init_stats(void)
|
||||
{
|
||||
const struct irq_stat_info *info = irq_stat_info;
|
||||
|
||||
for (unsigned int i = 0; i < ARRAY_SIZE(irq_stat_info); i++, info++) {
|
||||
if (!info->skip_vector || (info->skip_vector != DEFAULT_SUPPRESSED_VECTOR &&
|
||||
test_bit(info->skip_vector, system_vectors)))
|
||||
set_bit(i, irq_stat_count_show);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_X86_LOCAL_APIC
|
||||
if (!x86_platform_ipi_callback)
|
||||
clear_bit(IRQ_COUNT_X86_PLATFORM_IPI, irq_stat_count_show);
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_X86_POSTED_MSI
|
||||
if (!posted_msi_enabled())
|
||||
clear_bit(IRQ_COUNT_POSTED_MSI_NOTIFICATION, irq_stat_count_show);
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_X86_MCE_AMD
|
||||
if (boot_cpu_data.x86_vendor != X86_VENDOR_AMD &&
|
||||
boot_cpu_data.x86_vendor != X86_VENDOR_HYGON)
|
||||
clear_bit(IRQ_COUNT_DEFERRED_ERROR, irq_stat_count_show);
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
late_initcall(irq_init_stats);
|
||||
|
||||
/*
|
||||
* Used for default disabled counters to increment the stats and to enable the
|
||||
* entry for /proc/interrupts output.
|
||||
*/
|
||||
void irq_stat_inc_and_enable(enum irq_stat_counts which)
|
||||
{
|
||||
this_cpu_inc(irq_stat.counts[which]);
|
||||
set_bit(which, irq_stat_count_show);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PROC_FS
|
||||
/*
|
||||
* /proc/interrupts printing for arch specific interrupts
|
||||
*/
|
||||
int arch_show_interrupts(struct seq_file *p, int prec)
|
||||
{
|
||||
int j;
|
||||
const struct irq_stat_info *info = irq_stat_info;
|
||||
|
||||
seq_printf(p, "%*s: ", prec, "NMI");
|
||||
for_each_online_cpu(j)
|
||||
seq_printf(p, "%10u ", irq_stats(j)->__nmi_count);
|
||||
seq_puts(p, " Non-maskable interrupts\n");
|
||||
#ifdef CONFIG_X86_LOCAL_APIC
|
||||
seq_printf(p, "%*s: ", prec, "LOC");
|
||||
for_each_online_cpu(j)
|
||||
seq_printf(p, "%10u ", irq_stats(j)->apic_timer_irqs);
|
||||
seq_puts(p, " Local timer interrupts\n");
|
||||
for (unsigned int i = 0; i < ARRAY_SIZE(irq_stat_info); i++, info++) {
|
||||
if (!test_bit(i, irq_stat_count_show))
|
||||
continue;
|
||||
|
||||
seq_printf(p, "%*s: ", prec, "SPU");
|
||||
for_each_online_cpu(j)
|
||||
seq_printf(p, "%10u ", irq_stats(j)->irq_spurious_count);
|
||||
seq_puts(p, " Spurious interrupts\n");
|
||||
seq_printf(p, "%*s: ", prec, "PMI");
|
||||
for_each_online_cpu(j)
|
||||
seq_printf(p, "%10u ", irq_stats(j)->apic_perf_irqs);
|
||||
seq_puts(p, " Performance monitoring interrupts\n");
|
||||
seq_printf(p, "%*s: ", prec, "IWI");
|
||||
for_each_online_cpu(j)
|
||||
seq_printf(p, "%10u ", irq_stats(j)->apic_irq_work_irqs);
|
||||
seq_puts(p, " IRQ work interrupts\n");
|
||||
seq_printf(p, "%*s: ", prec, "RTR");
|
||||
for_each_online_cpu(j)
|
||||
seq_printf(p, "%10u ", irq_stats(j)->icr_read_retry_count);
|
||||
seq_puts(p, " APIC ICR read retries\n");
|
||||
if (x86_platform_ipi_callback) {
|
||||
seq_printf(p, "%*s: ", prec, "PLT");
|
||||
for_each_online_cpu(j)
|
||||
seq_printf(p, "%10u ", irq_stats(j)->x86_platform_ipis);
|
||||
seq_puts(p, " Platform interrupts\n");
|
||||
seq_printf(p, "%*s:", prec, info->symbol);
|
||||
irq_proc_emit_counts(p, &irq_stat.counts[i]);
|
||||
seq_puts(p, info->text);
|
||||
}
|
||||
#endif
|
||||
#ifdef CONFIG_SMP
|
||||
seq_printf(p, "%*s: ", prec, "RES");
|
||||
for_each_online_cpu(j)
|
||||
seq_printf(p, "%10u ", irq_stats(j)->irq_resched_count);
|
||||
seq_puts(p, " Rescheduling interrupts\n");
|
||||
seq_printf(p, "%*s: ", prec, "CAL");
|
||||
for_each_online_cpu(j)
|
||||
seq_printf(p, "%10u ", irq_stats(j)->irq_call_count);
|
||||
seq_puts(p, " Function call interrupts\n");
|
||||
seq_printf(p, "%*s: ", prec, "TLB");
|
||||
for_each_online_cpu(j)
|
||||
seq_printf(p, "%10u ", irq_stats(j)->irq_tlb_count);
|
||||
seq_puts(p, " TLB shootdowns\n");
|
||||
#endif
|
||||
#ifdef CONFIG_X86_THERMAL_VECTOR
|
||||
seq_printf(p, "%*s: ", prec, "TRM");
|
||||
for_each_online_cpu(j)
|
||||
seq_printf(p, "%10u ", irq_stats(j)->irq_thermal_count);
|
||||
seq_puts(p, " Thermal event interrupts\n");
|
||||
#endif
|
||||
#ifdef CONFIG_X86_MCE_THRESHOLD
|
||||
seq_printf(p, "%*s: ", prec, "THR");
|
||||
for_each_online_cpu(j)
|
||||
seq_printf(p, "%10u ", irq_stats(j)->irq_threshold_count);
|
||||
seq_puts(p, " Threshold APIC interrupts\n");
|
||||
#endif
|
||||
#ifdef CONFIG_X86_MCE_AMD
|
||||
seq_printf(p, "%*s: ", prec, "DFR");
|
||||
for_each_online_cpu(j)
|
||||
seq_printf(p, "%10u ", irq_stats(j)->irq_deferred_error_count);
|
||||
seq_puts(p, " Deferred Error APIC interrupts\n");
|
||||
#endif
|
||||
#ifdef CONFIG_X86_MCE
|
||||
seq_printf(p, "%*s: ", prec, "MCE");
|
||||
for_each_online_cpu(j)
|
||||
seq_printf(p, "%10u ", per_cpu(mce_exception_count, j));
|
||||
seq_puts(p, " Machine check exceptions\n");
|
||||
seq_printf(p, "%*s: ", prec, "MCP");
|
||||
for_each_online_cpu(j)
|
||||
seq_printf(p, "%10u ", per_cpu(mce_poll_count, j));
|
||||
seq_puts(p, " Machine check polls\n");
|
||||
#endif
|
||||
#ifdef CONFIG_X86_HV_CALLBACK_VECTOR
|
||||
if (test_bit(HYPERVISOR_CALLBACK_VECTOR, system_vectors)) {
|
||||
seq_printf(p, "%*s: ", prec, "HYP");
|
||||
for_each_online_cpu(j)
|
||||
seq_printf(p, "%10u ",
|
||||
irq_stats(j)->irq_hv_callback_count);
|
||||
seq_puts(p, " Hypervisor callback interrupts\n");
|
||||
}
|
||||
#endif
|
||||
#if IS_ENABLED(CONFIG_HYPERV)
|
||||
if (test_bit(HYPERV_REENLIGHTENMENT_VECTOR, system_vectors)) {
|
||||
seq_printf(p, "%*s: ", prec, "HRE");
|
||||
for_each_online_cpu(j)
|
||||
seq_printf(p, "%10u ",
|
||||
irq_stats(j)->irq_hv_reenlightenment_count);
|
||||
seq_puts(p, " Hyper-V reenlightenment interrupts\n");
|
||||
}
|
||||
if (test_bit(HYPERV_STIMER0_VECTOR, system_vectors)) {
|
||||
seq_printf(p, "%*s: ", prec, "HVS");
|
||||
for_each_online_cpu(j)
|
||||
seq_printf(p, "%10u ",
|
||||
irq_stats(j)->hyperv_stimer0_count);
|
||||
seq_puts(p, " Hyper-V stimer0 interrupts\n");
|
||||
}
|
||||
#endif
|
||||
seq_printf(p, "%*s: %10u\n", prec, "ERR", atomic_read(&irq_err_count));
|
||||
#if defined(CONFIG_X86_IO_APIC)
|
||||
seq_printf(p, "%*s: %10u\n", prec, "MIS", atomic_read(&irq_mis_count));
|
||||
#endif
|
||||
#if IS_ENABLED(CONFIG_KVM)
|
||||
seq_printf(p, "%*s: ", prec, "PIN");
|
||||
for_each_online_cpu(j)
|
||||
seq_printf(p, "%10u ", irq_stats(j)->kvm_posted_intr_ipis);
|
||||
seq_puts(p, " Posted-interrupt notification event\n");
|
||||
|
||||
seq_printf(p, "%*s: ", prec, "NPI");
|
||||
for_each_online_cpu(j)
|
||||
seq_printf(p, "%10u ",
|
||||
irq_stats(j)->kvm_posted_intr_nested_ipis);
|
||||
seq_puts(p, " Nested posted-interrupt event\n");
|
||||
|
||||
seq_printf(p, "%*s: ", prec, "PIW");
|
||||
for_each_online_cpu(j)
|
||||
seq_printf(p, "%10u ",
|
||||
irq_stats(j)->kvm_posted_intr_wakeup_ipis);
|
||||
seq_puts(p, " Posted-interrupt wakeup event\n");
|
||||
#endif
|
||||
#ifdef CONFIG_GUEST_PERF_EVENTS
|
||||
seq_printf(p, "%*s: ", prec, "VPMI");
|
||||
for_each_online_cpu(j)
|
||||
seq_printf(p, "%10u ",
|
||||
irq_stats(j)->perf_guest_mediated_pmis);
|
||||
seq_puts(p, " Perf Guest Mediated PMI\n");
|
||||
#endif
|
||||
#ifdef CONFIG_X86_POSTED_MSI
|
||||
seq_printf(p, "%*s: ", prec, "PMN");
|
||||
for_each_online_cpu(j)
|
||||
seq_printf(p, "%10u ",
|
||||
irq_stats(j)->posted_msi_notification_count);
|
||||
seq_puts(p, " Posted MSI notification event\n");
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -214,46 +193,14 @@ int arch_show_interrupts(struct seq_file *p, int prec)
|
||||
*/
|
||||
u64 arch_irq_stat_cpu(unsigned int cpu)
|
||||
{
|
||||
u64 sum = irq_stats(cpu)->__nmi_count;
|
||||
irq_cpustat_t *p = per_cpu_ptr(&irq_stat, cpu);
|
||||
u64 sum = 0;
|
||||
|
||||
#ifdef CONFIG_X86_LOCAL_APIC
|
||||
sum += irq_stats(cpu)->apic_timer_irqs;
|
||||
sum += irq_stats(cpu)->irq_spurious_count;
|
||||
sum += irq_stats(cpu)->apic_perf_irqs;
|
||||
sum += irq_stats(cpu)->apic_irq_work_irqs;
|
||||
sum += irq_stats(cpu)->icr_read_retry_count;
|
||||
if (x86_platform_ipi_callback)
|
||||
sum += irq_stats(cpu)->x86_platform_ipis;
|
||||
#endif
|
||||
#ifdef CONFIG_SMP
|
||||
sum += irq_stats(cpu)->irq_resched_count;
|
||||
sum += irq_stats(cpu)->irq_call_count;
|
||||
#endif
|
||||
#ifdef CONFIG_X86_THERMAL_VECTOR
|
||||
sum += irq_stats(cpu)->irq_thermal_count;
|
||||
#endif
|
||||
#ifdef CONFIG_X86_MCE_THRESHOLD
|
||||
sum += irq_stats(cpu)->irq_threshold_count;
|
||||
#endif
|
||||
#ifdef CONFIG_X86_HV_CALLBACK_VECTOR
|
||||
sum += irq_stats(cpu)->irq_hv_callback_count;
|
||||
#endif
|
||||
#if IS_ENABLED(CONFIG_HYPERV)
|
||||
sum += irq_stats(cpu)->irq_hv_reenlightenment_count;
|
||||
sum += irq_stats(cpu)->hyperv_stimer0_count;
|
||||
#endif
|
||||
#ifdef CONFIG_X86_MCE
|
||||
sum += per_cpu(mce_exception_count, cpu);
|
||||
sum += per_cpu(mce_poll_count, cpu);
|
||||
#endif
|
||||
return sum;
|
||||
}
|
||||
|
||||
u64 arch_irq_stat(void)
|
||||
{
|
||||
u64 sum = atomic_read(&irq_err_count);
|
||||
for (unsigned int i = 0; i < ARRAY_SIZE(irq_stat_info); i++)
|
||||
sum += p->counts[i];
|
||||
return sum;
|
||||
}
|
||||
#endif /* CONFIG_PROC_FS */
|
||||
|
||||
static __always_inline void handle_irq(struct irq_desc *desc,
|
||||
struct pt_regs *regs)
|
||||
@@ -338,7 +285,7 @@ DEFINE_IDTENTRY_IRQ(common_interrupt)
|
||||
|
||||
#ifdef CONFIG_X86_LOCAL_APIC
|
||||
/* Function pointer for generic interrupt vector handling */
|
||||
void (*x86_platform_ipi_callback)(void) = NULL;
|
||||
void (*x86_platform_ipi_callback)(void) __ro_after_init = NULL;
|
||||
/*
|
||||
* Handler for X86_PLATFORM_IPI_VECTOR.
|
||||
*/
|
||||
@@ -348,7 +295,7 @@ DEFINE_IDTENTRY_SYSVEC(sysvec_x86_platform_ipi)
|
||||
|
||||
apic_eoi();
|
||||
trace_x86_platform_ipi_entry(X86_PLATFORM_IPI_VECTOR);
|
||||
inc_irq_stat(x86_platform_ipis);
|
||||
inc_irq_stat(X86_PLATFORM_IPI);
|
||||
if (x86_platform_ipi_callback)
|
||||
x86_platform_ipi_callback();
|
||||
trace_x86_platform_ipi_exit(X86_PLATFORM_IPI_VECTOR);
|
||||
@@ -363,7 +310,7 @@ DEFINE_IDTENTRY_SYSVEC(sysvec_x86_platform_ipi)
|
||||
DEFINE_IDTENTRY_SYSVEC(sysvec_perf_guest_mediated_pmi_handler)
|
||||
{
|
||||
apic_eoi();
|
||||
inc_irq_stat(perf_guest_mediated_pmis);
|
||||
inc_irq_stat(PERF_GUEST_MEDIATED_PMI);
|
||||
perf_guest_handle_mediated_pmi();
|
||||
}
|
||||
#endif
|
||||
@@ -389,7 +336,7 @@ EXPORT_SYMBOL_FOR_KVM(kvm_set_posted_intr_wakeup_handler);
|
||||
DEFINE_IDTENTRY_SYSVEC_SIMPLE(sysvec_kvm_posted_intr_ipi)
|
||||
{
|
||||
apic_eoi();
|
||||
inc_irq_stat(kvm_posted_intr_ipis);
|
||||
inc_irq_stat(POSTED_INTR);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -398,7 +345,7 @@ DEFINE_IDTENTRY_SYSVEC_SIMPLE(sysvec_kvm_posted_intr_ipi)
|
||||
DEFINE_IDTENTRY_SYSVEC(sysvec_kvm_posted_intr_wakeup_ipi)
|
||||
{
|
||||
apic_eoi();
|
||||
inc_irq_stat(kvm_posted_intr_wakeup_ipis);
|
||||
inc_irq_stat(POSTED_INTR_WAKEUP);
|
||||
kvm_posted_intr_wakeup_handler();
|
||||
}
|
||||
|
||||
@@ -408,7 +355,7 @@ DEFINE_IDTENTRY_SYSVEC(sysvec_kvm_posted_intr_wakeup_ipi)
|
||||
DEFINE_IDTENTRY_SYSVEC_SIMPLE(sysvec_kvm_posted_intr_nested_ipi)
|
||||
{
|
||||
apic_eoi();
|
||||
inc_irq_stat(kvm_posted_intr_nested_ipis);
|
||||
inc_irq_stat(POSTED_INTR_NESTED);
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -482,7 +429,7 @@ DEFINE_IDTENTRY_SYSVEC(sysvec_posted_msi_notification)
|
||||
|
||||
/* Mark the handler active for intel_ack_posted_msi_irq() */
|
||||
__this_cpu_write(posted_msi_handler_active, true);
|
||||
inc_irq_stat(posted_msi_notification_count);
|
||||
inc_irq_stat(POSTED_MSI_NOTIFICATION);
|
||||
irq_enter();
|
||||
|
||||
/*
|
||||
@@ -577,7 +524,7 @@ static void smp_thermal_vector(void)
|
||||
DEFINE_IDTENTRY_SYSVEC(sysvec_thermal)
|
||||
{
|
||||
trace_thermal_apic_entry(THERMAL_APIC_VECTOR);
|
||||
inc_irq_stat(irq_thermal_count);
|
||||
inc_irq_stat(THERMAL_APIC);
|
||||
smp_thermal_vector();
|
||||
trace_thermal_apic_exit(THERMAL_APIC_VECTOR);
|
||||
apic_eoi();
|
||||
|
||||
@@ -18,7 +18,7 @@ DEFINE_IDTENTRY_SYSVEC(sysvec_irq_work)
|
||||
{
|
||||
apic_eoi();
|
||||
trace_irq_work_entry(IRQ_WORK_VECTOR);
|
||||
inc_irq_stat(apic_irq_work_irqs);
|
||||
inc_irq_stat(IRQ_WORK);
|
||||
irq_work_run();
|
||||
trace_irq_work_exit(IRQ_WORK_VECTOR);
|
||||
}
|
||||
|
||||
@@ -304,7 +304,7 @@ DEFINE_IDTENTRY_SYSVEC(sysvec_kvm_asyncpf_interrupt)
|
||||
|
||||
apic_eoi();
|
||||
|
||||
inc_irq_stat(irq_hv_callback_count);
|
||||
inc_irq_stat(HYPERVISOR_CALLBACK);
|
||||
|
||||
if (__this_cpu_read(async_pf_enabled)) {
|
||||
token = __this_cpu_read(apf_reason.token);
|
||||
|
||||
@@ -576,7 +576,7 @@ nmi_restart:
|
||||
|
||||
irq_state = irqentry_nmi_enter(regs);
|
||||
|
||||
inc_irq_stat(__nmi_count);
|
||||
inc_irq_stat(NMI);
|
||||
|
||||
if (IS_ENABLED(CONFIG_NMI_CHECK_CPU) && ignore_nmis) {
|
||||
WRITE_ONCE(nsp->idt_ignored, nsp->idt_ignored + 1);
|
||||
@@ -724,7 +724,7 @@ DEFINE_FREDENTRY_NMI(exc_nmi)
|
||||
|
||||
irq_state = irqentry_nmi_enter(regs);
|
||||
|
||||
inc_irq_stat(__nmi_count);
|
||||
inc_irq_stat(NMI);
|
||||
default_do_nmi(regs);
|
||||
|
||||
irqentry_nmi_exit(regs, irq_state);
|
||||
|
||||
@@ -250,7 +250,7 @@ DEFINE_IDTENTRY_SYSVEC_SIMPLE(sysvec_reschedule_ipi)
|
||||
{
|
||||
apic_eoi();
|
||||
trace_reschedule_entry(RESCHEDULE_VECTOR);
|
||||
inc_irq_stat(irq_resched_count);
|
||||
inc_irq_stat(RESCHEDULE);
|
||||
scheduler_ipi();
|
||||
trace_reschedule_exit(RESCHEDULE_VECTOR);
|
||||
}
|
||||
@@ -259,7 +259,7 @@ DEFINE_IDTENTRY_SYSVEC(sysvec_call_function)
|
||||
{
|
||||
apic_eoi();
|
||||
trace_call_function_entry(CALL_FUNCTION_VECTOR);
|
||||
inc_irq_stat(irq_call_count);
|
||||
inc_irq_stat(CALL_FUNCTION);
|
||||
generic_smp_call_function_interrupt();
|
||||
trace_call_function_exit(CALL_FUNCTION_VECTOR);
|
||||
}
|
||||
@@ -268,7 +268,7 @@ DEFINE_IDTENTRY_SYSVEC(sysvec_call_function_single)
|
||||
{
|
||||
apic_eoi();
|
||||
trace_call_function_single_entry(CALL_FUNCTION_SINGLE_VECTOR);
|
||||
inc_irq_stat(irq_call_count);
|
||||
inc_irq_stat(CALL_FUNCTION);
|
||||
generic_smp_call_function_single_interrupt();
|
||||
trace_call_function_single_exit(CALL_FUNCTION_SINGLE_VECTOR);
|
||||
}
|
||||
|
||||
+1
-1
@@ -1123,7 +1123,7 @@ static void flush_tlb_func(void *info)
|
||||
VM_WARN_ON(!irqs_disabled());
|
||||
|
||||
if (!local) {
|
||||
inc_irq_stat(irq_tlb_count);
|
||||
inc_irq_stat(TLB);
|
||||
count_vm_tlb_event(NR_TLB_REMOTE_FLUSH_RECEIVED);
|
||||
}
|
||||
|
||||
|
||||
@@ -125,7 +125,7 @@ DEFINE_IDTENTRY_SYSVEC(sysvec_xen_hvm_callback)
|
||||
if (xen_percpu_upcall)
|
||||
apic_eoi();
|
||||
|
||||
inc_irq_stat(irq_hv_callback_count);
|
||||
inc_irq_stat(HYPERVISOR_CALLBACK);
|
||||
|
||||
xen_evtchn_do_upcall();
|
||||
|
||||
|
||||
@@ -722,7 +722,7 @@ static void __xen_pv_evtchn_do_upcall(struct pt_regs *regs)
|
||||
{
|
||||
struct pt_regs *old_regs = set_irq_regs(regs);
|
||||
|
||||
inc_irq_stat(irq_hv_callback_count);
|
||||
inc_irq_stat(HYPERVISOR_CALLBACK);
|
||||
|
||||
xen_evtchn_do_upcall();
|
||||
|
||||
|
||||
+3
-3
@@ -23,7 +23,7 @@ static irqreturn_t xen_call_function_single_interrupt(int irq, void *dev_id);
|
||||
*/
|
||||
static irqreturn_t xen_reschedule_interrupt(int irq, void *dev_id)
|
||||
{
|
||||
inc_irq_stat(irq_resched_count);
|
||||
inc_irq_stat(RESCHEDULE);
|
||||
scheduler_ipi();
|
||||
|
||||
return IRQ_HANDLED;
|
||||
@@ -254,7 +254,7 @@ void xen_send_IPI_allbutself(int vector)
|
||||
static irqreturn_t xen_call_function_interrupt(int irq, void *dev_id)
|
||||
{
|
||||
generic_smp_call_function_interrupt();
|
||||
inc_irq_stat(irq_call_count);
|
||||
inc_irq_stat(CALL_FUNCTION);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
@@ -262,7 +262,7 @@ static irqreturn_t xen_call_function_interrupt(int irq, void *dev_id)
|
||||
static irqreturn_t xen_call_function_single_interrupt(int irq, void *dev_id)
|
||||
{
|
||||
generic_smp_call_function_single_interrupt();
|
||||
inc_irq_stat(irq_call_count);
|
||||
inc_irq_stat(CALL_FUNCTION);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
@@ -400,7 +400,7 @@ static void xen_pv_stop_other_cpus(int wait)
|
||||
static irqreturn_t xen_irq_work_interrupt(int irq, void *dev_id)
|
||||
{
|
||||
irq_work_run();
|
||||
inc_irq_stat(apic_irq_work_irqs);
|
||||
inc_irq_stat(IRQ_WORK);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
@@ -59,7 +59,7 @@ int arch_show_interrupts(struct seq_file *p, int prec)
|
||||
seq_printf(p, "%*s:", prec, "NMI");
|
||||
for_each_online_cpu(cpu)
|
||||
seq_printf(p, " %10lu", per_cpu(nmi_count, cpu));
|
||||
seq_puts(p, " Non-maskable interrupts\n");
|
||||
seq_puts(p, " Non-maskable interrupts\n");
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -34,14 +34,33 @@ struct acpi_gtdt_descriptor {
|
||||
void *platform_timer;
|
||||
};
|
||||
|
||||
struct gtdt_v3 {
|
||||
struct acpi_table_gtdt gtdt_v2;
|
||||
struct acpi_gtdt_el2 el2_vtimer;
|
||||
};
|
||||
|
||||
static struct acpi_gtdt_descriptor acpi_gtdt_desc __initdata;
|
||||
|
||||
static __init struct acpi_gtdt_el2 *gtdt_to_el2_vtimer(struct acpi_table_gtdt *gtdt)
|
||||
{
|
||||
if (gtdt->header.revision < 3)
|
||||
return NULL;
|
||||
|
||||
return &container_of(gtdt, struct gtdt_v3, gtdt_v2)->el2_vtimer;
|
||||
}
|
||||
|
||||
static __init bool platform_timer_valid(void *platform_timer)
|
||||
{
|
||||
struct acpi_gtdt_header *gh = platform_timer;
|
||||
void *platform_timer_begin;
|
||||
|
||||
return (platform_timer >= (void *)(acpi_gtdt_desc.gtdt + 1) &&
|
||||
platform_timer < acpi_gtdt_desc.gtdt_end &&
|
||||
if (acpi_gtdt_desc.gtdt->header.revision >= 3)
|
||||
platform_timer_begin = container_of(acpi_gtdt_desc.gtdt, struct gtdt_v3, gtdt_v2) + 1;
|
||||
else
|
||||
platform_timer_begin = acpi_gtdt_desc.gtdt + 1;
|
||||
|
||||
return (platform_timer >= platform_timer_begin &&
|
||||
platform_timer + sizeof(*gh) <= acpi_gtdt_desc.gtdt_end &&
|
||||
gh->length != 0 &&
|
||||
platform_timer + gh->length <= acpi_gtdt_desc.gtdt_end);
|
||||
}
|
||||
@@ -101,6 +120,7 @@ static int __init map_gt_gsi(u32 interrupt, u32 flags)
|
||||
int __init acpi_gtdt_map_ppi(int type)
|
||||
{
|
||||
struct acpi_table_gtdt *gtdt = acpi_gtdt_desc.gtdt;
|
||||
struct acpi_gtdt_el2 *el2_vtimer = gtdt_to_el2_vtimer(gtdt);
|
||||
|
||||
switch (type) {
|
||||
case ARCH_TIMER_PHYS_NONSECURE_PPI:
|
||||
@@ -113,6 +133,12 @@ int __init acpi_gtdt_map_ppi(int type)
|
||||
case ARCH_TIMER_HYP_PPI:
|
||||
return map_gt_gsi(gtdt->non_secure_el2_interrupt,
|
||||
gtdt->non_secure_el2_flags);
|
||||
case ARCH_TIMER_HYP_VIRT_PPI:
|
||||
if (el2_vtimer && el2_vtimer->virtual_el2_timer_gsiv)
|
||||
return map_gt_gsi(el2_vtimer->virtual_el2_timer_gsiv,
|
||||
el2_vtimer->virtual_el2_timer_flags);
|
||||
|
||||
return 0;
|
||||
default:
|
||||
pr_err("Failed to map timer interrupt: invalid type.\n");
|
||||
}
|
||||
@@ -130,6 +156,7 @@ int __init acpi_gtdt_map_ppi(int type)
|
||||
bool __init acpi_gtdt_c3stop(int type)
|
||||
{
|
||||
struct acpi_table_gtdt *gtdt = acpi_gtdt_desc.gtdt;
|
||||
struct acpi_gtdt_el2 *el2_vtimer = gtdt_to_el2_vtimer(gtdt);
|
||||
|
||||
switch (type) {
|
||||
case ARCH_TIMER_PHYS_NONSECURE_PPI:
|
||||
@@ -141,6 +168,10 @@ bool __init acpi_gtdt_c3stop(int type)
|
||||
case ARCH_TIMER_HYP_PPI:
|
||||
return !(gtdt->non_secure_el2_flags & ACPI_GTDT_ALWAYS_ON);
|
||||
|
||||
case ARCH_TIMER_HYP_VIRT_PPI:
|
||||
return el2_vtimer && el2_vtimer->virtual_el2_timer_gsiv &&
|
||||
!(el2_vtimer->virtual_el2_timer_flags & ACPI_GTDT_ALWAYS_ON);
|
||||
|
||||
default:
|
||||
pr_err("Failed to get c3stop info: invalid type.\n");
|
||||
}
|
||||
@@ -166,6 +197,13 @@ int __init acpi_gtdt_init(struct acpi_table_header *table,
|
||||
u32 cnt = 0;
|
||||
|
||||
gtdt = container_of(table, struct acpi_table_gtdt, header);
|
||||
|
||||
if ((gtdt->header.revision >= 3 && gtdt->header.length < sizeof(struct gtdt_v3)) ||
|
||||
(gtdt->header.revision == 2 && gtdt->header.length < sizeof(*gtdt))) {
|
||||
pr_err(FW_BUG "GTDT with invalid size %d\n", gtdt->header.length);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
acpi_gtdt_desc.gtdt = gtdt;
|
||||
acpi_gtdt_desc.gtdt_end = (void *)table + table->length;
|
||||
acpi_gtdt_desc.platform_timer = NULL;
|
||||
|
||||
@@ -793,4 +793,35 @@ config RTK_SYSTIMER
|
||||
this option only when building for a Realtek platform or for compilation
|
||||
testing.
|
||||
|
||||
choice
|
||||
prompt "NXP clocksource for scheduler clock"
|
||||
depends on SOC_VF610 || ARCH_S32
|
||||
# Default to Global Timer for Vybrid (32-bit)
|
||||
default VF_USE_ARM_GLOBAL_TIMER if SOC_VF610
|
||||
# Default to None for S32 (64-bit)
|
||||
default VF_TIMER_NONE if ARCH_S32
|
||||
|
||||
config VF_USE_ARM_GLOBAL_TIMER
|
||||
bool "Use NXP Vybrid Global Timer"
|
||||
depends on ARCH_MULTI_V7 && SOC_VF610
|
||||
select ARM_GLOBAL_TIMER
|
||||
select CLKSRC_ARM_GLOBAL_TIMER_SCHED_CLOCK
|
||||
help
|
||||
Use the NXP Vybrid Global Timer as clocksource.
|
||||
|
||||
config VF_USE_PIT_TIMER
|
||||
bool "Use NXP PIT timer"
|
||||
select NXP_PIT_TIMER
|
||||
help
|
||||
Use NXP Periodic Interrupt Timer (PIT) as clocksource.
|
||||
|
||||
config VF_TIMER_NONE
|
||||
bool "None (Use standard Arch Timer)"
|
||||
depends on ARCH_S32
|
||||
help
|
||||
Do not use any specific NXP timer driver. Use the standard
|
||||
ARM Architected Timer instead.
|
||||
|
||||
endchoice
|
||||
|
||||
endmenu
|
||||
@@ -688,6 +688,7 @@ static void __arch_timer_setup(struct clock_event_device *clk)
|
||||
clk->irq = arch_timer_ppi[arch_timer_uses_ppi];
|
||||
switch (arch_timer_uses_ppi) {
|
||||
case ARCH_TIMER_VIRT_PPI:
|
||||
case ARCH_TIMER_HYP_VIRT_PPI:
|
||||
clk->set_state_shutdown = arch_timer_shutdown_virt;
|
||||
clk->set_state_oneshot_stopped = arch_timer_shutdown_virt;
|
||||
sne = erratum_handler(set_next_event_virt);
|
||||
@@ -879,7 +880,7 @@ static void __init arch_timer_banner(void)
|
||||
pr_info("cp15 timer running at %lu.%02luMHz (%s).\n",
|
||||
(unsigned long)arch_timer_rate / 1000000,
|
||||
(unsigned long)(arch_timer_rate / 10000) % 100,
|
||||
(arch_timer_uses_ppi == ARCH_TIMER_VIRT_PPI) ? "virt" : "phys");
|
||||
arch_timer_ppi_names[arch_timer_uses_ppi]);
|
||||
}
|
||||
|
||||
u32 arch_timer_get_rate(void)
|
||||
@@ -912,7 +913,8 @@ static void __init arch_counter_register(void)
|
||||
int width;
|
||||
|
||||
if ((IS_ENABLED(CONFIG_ARM64) && !is_hyp_mode_available()) ||
|
||||
arch_timer_uses_ppi == ARCH_TIMER_VIRT_PPI) {
|
||||
arch_timer_uses_ppi == ARCH_TIMER_VIRT_PPI ||
|
||||
arch_timer_uses_ppi == ARCH_TIMER_HYP_VIRT_PPI) {
|
||||
if (arch_timer_counter_has_wa()) {
|
||||
rd = arch_counter_get_cntvct_stable;
|
||||
scr = raw_counter_get_cntvct_stable;
|
||||
@@ -1023,6 +1025,7 @@ static int __init arch_timer_register(void)
|
||||
ppi = arch_timer_ppi[arch_timer_uses_ppi];
|
||||
switch (arch_timer_uses_ppi) {
|
||||
case ARCH_TIMER_VIRT_PPI:
|
||||
case ARCH_TIMER_HYP_VIRT_PPI:
|
||||
err = request_percpu_irq(ppi, arch_timer_handler_virt,
|
||||
"arch_timer", arch_timer_evt);
|
||||
break;
|
||||
@@ -1090,25 +1093,34 @@ static int __init arch_timer_common_init(void)
|
||||
/**
|
||||
* arch_timer_select_ppi() - Select suitable PPI for the current system.
|
||||
*
|
||||
* If HYP mode is available, we know that the physical timer
|
||||
* has been configured to be accessible from PL1. Use it, so
|
||||
* that a guest can use the virtual timer instead.
|
||||
* On AArch32, if HYP mode is available, we know that the physical
|
||||
* timer has been configured to be accessible from PL1. Use it, so
|
||||
* that a guest can use the virtual timer instead (though KVM host
|
||||
* support has long been removed).
|
||||
*
|
||||
* On ARMv8.1 with VH extensions, the kernel runs in HYP. VHE
|
||||
* accesses to CNTP_*_EL1 registers are silently redirected to
|
||||
* their CNTHP_*_EL2 counterparts, and use a different PPI
|
||||
* number.
|
||||
* On ARMv8.1 with FEAT_VHE, the kernel runs in EL2. Accesses to
|
||||
* CNTV_*_EL1 registers are silently redirected to their CNTHV_*_EL2
|
||||
* counterparts, and the timer uses a different PPI number. Similar
|
||||
* thing happen when using the EL2 physical timer. Note that a bunch
|
||||
* of DTs out there omit the virtual EL2 timer, so fallback gracefully
|
||||
* on the physical timer.
|
||||
*
|
||||
* Without VHE, if no interrupt provided for virtual timer, we'll have
|
||||
* to stick to the physical timer. It'd better be accessible...
|
||||
*
|
||||
* If no interrupt provided for virtual timer, we'll have to
|
||||
* stick to the physical timer. It'd better be accessible...
|
||||
* For arm64 we never use the secure interrupt.
|
||||
*
|
||||
* Return: a suitable PPI type for the current system.
|
||||
*/
|
||||
static enum arch_timer_ppi_nr __init arch_timer_select_ppi(void)
|
||||
{
|
||||
if (is_kernel_in_hyp_mode())
|
||||
if (is_kernel_in_hyp_mode()) {
|
||||
if (arch_timer_ppi[ARCH_TIMER_HYP_VIRT_PPI])
|
||||
return ARCH_TIMER_HYP_VIRT_PPI;
|
||||
|
||||
pr_warn_once(FW_BUG "VHE-capable CPU without EL2 virtual timer interrupt\n");
|
||||
return ARCH_TIMER_HYP_PPI;
|
||||
}
|
||||
|
||||
if (!is_hyp_mode_available() && arch_timer_ppi[ARCH_TIMER_VIRT_PPI])
|
||||
return ARCH_TIMER_VIRT_PPI;
|
||||
@@ -1200,14 +1212,9 @@ static int __init arch_timer_acpi_init(struct acpi_table_header *table)
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
arch_timer_ppi[ARCH_TIMER_PHYS_NONSECURE_PPI] =
|
||||
acpi_gtdt_map_ppi(ARCH_TIMER_PHYS_NONSECURE_PPI);
|
||||
|
||||
arch_timer_ppi[ARCH_TIMER_VIRT_PPI] =
|
||||
acpi_gtdt_map_ppi(ARCH_TIMER_VIRT_PPI);
|
||||
|
||||
arch_timer_ppi[ARCH_TIMER_HYP_PPI] =
|
||||
acpi_gtdt_map_ppi(ARCH_TIMER_HYP_PPI);
|
||||
/* The GTDT parser can't be bothered with the secure timer */
|
||||
for (int i = ARCH_TIMER_PHYS_NONSECURE_PPI; i < ARCH_TIMER_MAX_TIMER_PPI; i++)
|
||||
arch_timer_ppi[i] = acpi_gtdt_map_ppi(i);
|
||||
|
||||
arch_timer_populate_kvm_info();
|
||||
|
||||
@@ -1253,10 +1260,14 @@ int kvm_arch_ptp_get_crosststamp(u64 *cycle, struct timespec64 *ts,
|
||||
if (!IS_ENABLED(CONFIG_HAVE_ARM_SMCCC_DISCOVERY))
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
if (arch_timer_uses_ppi == ARCH_TIMER_VIRT_PPI)
|
||||
switch (arch_timer_uses_ppi) {
|
||||
case ARCH_TIMER_VIRT_PPI:
|
||||
case ARCH_TIMER_HYP_VIRT_PPI:
|
||||
ptp_counter = KVM_PTP_VIRT_COUNTER;
|
||||
else
|
||||
break;
|
||||
default:
|
||||
ptp_counter = KVM_PTP_PHYS_COUNTER;
|
||||
}
|
||||
|
||||
arm_smccc_1_1_invoke(ARM_SMCCC_VENDOR_HYP_KVM_PTP_FUNC_ID,
|
||||
ptp_counter, &hvc_res);
|
||||
|
||||
@@ -21,21 +21,25 @@ u64 clocksource_mmio_readl_up(struct clocksource *c)
|
||||
{
|
||||
return (u64)readl_relaxed(to_mmio_clksrc(c)->reg);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(clocksource_mmio_readl_up);
|
||||
|
||||
u64 clocksource_mmio_readl_down(struct clocksource *c)
|
||||
{
|
||||
return ~(u64)readl_relaxed(to_mmio_clksrc(c)->reg) & c->mask;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(clocksource_mmio_readl_down);
|
||||
|
||||
u64 clocksource_mmio_readw_up(struct clocksource *c)
|
||||
{
|
||||
return (u64)readw_relaxed(to_mmio_clksrc(c)->reg);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(clocksource_mmio_readw_up);
|
||||
|
||||
u64 clocksource_mmio_readw_down(struct clocksource *c)
|
||||
{
|
||||
return ~(u64)readw_relaxed(to_mmio_clksrc(c)->reg) & c->mask;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(clocksource_mmio_readw_down);
|
||||
|
||||
/**
|
||||
* clocksource_mmio_init - Initialize a simple mmio based clocksource
|
||||
@@ -46,9 +50,9 @@ u64 clocksource_mmio_readw_down(struct clocksource *c)
|
||||
* @bits: Number of valid bits
|
||||
* @read: One of clocksource_mmio_read*() above
|
||||
*/
|
||||
int __init clocksource_mmio_init(void __iomem *base, const char *name,
|
||||
unsigned long hz, int rating, unsigned bits,
|
||||
u64 (*read)(struct clocksource *))
|
||||
int clocksource_mmio_init(void __iomem *base, const char *name,
|
||||
unsigned long hz, int rating, unsigned bits,
|
||||
u64 (*read)(struct clocksource *))
|
||||
{
|
||||
struct clocksource_mmio *cs;
|
||||
|
||||
@@ -68,3 +72,4 @@ int __init clocksource_mmio_init(void __iomem *base, const char *name,
|
||||
|
||||
return clocksource_register_hz(&cs->clksrc, hz);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(clocksource_mmio_init);
|
||||
@@ -19,7 +19,7 @@
|
||||
*
|
||||
* Free the irq resource
|
||||
*/
|
||||
static __init void timer_of_irq_exit(struct of_timer_irq *of_irq)
|
||||
static void timer_of_irq_exit(struct of_timer_irq *of_irq)
|
||||
{
|
||||
struct timer_of *to = container_of(of_irq, struct timer_of, of_irq);
|
||||
|
||||
@@ -41,8 +41,8 @@ static __init void timer_of_irq_exit(struct of_timer_irq *of_irq)
|
||||
*
|
||||
* Returns 0 on success, < 0 otherwise
|
||||
*/
|
||||
static __init int timer_of_irq_init(struct device_node *np,
|
||||
struct of_timer_irq *of_irq)
|
||||
static int timer_of_irq_init(struct device_node *np,
|
||||
struct of_timer_irq *of_irq)
|
||||
{
|
||||
int ret;
|
||||
struct timer_of *to = container_of(of_irq, struct timer_of, of_irq);
|
||||
@@ -82,7 +82,7 @@ static __init int timer_of_irq_init(struct device_node *np,
|
||||
*
|
||||
* Disables and releases the refcount on the clk
|
||||
*/
|
||||
static __init void timer_of_clk_exit(struct of_timer_clk *of_clk)
|
||||
static void timer_of_clk_exit(struct of_timer_clk *of_clk)
|
||||
{
|
||||
of_clk->rate = 0;
|
||||
clk_disable_unprepare(of_clk->clk);
|
||||
@@ -98,8 +98,8 @@ static __init void timer_of_clk_exit(struct of_timer_clk *of_clk)
|
||||
*
|
||||
* Returns 0 on success, < 0 otherwise
|
||||
*/
|
||||
static __init int timer_of_clk_init(struct device_node *np,
|
||||
struct of_timer_clk *of_clk)
|
||||
static int timer_of_clk_init(struct device_node *np,
|
||||
struct of_timer_clk *of_clk)
|
||||
{
|
||||
int ret;
|
||||
|
||||
@@ -137,13 +137,13 @@ out_clk_put:
|
||||
goto out;
|
||||
}
|
||||
|
||||
static __init void timer_of_base_exit(struct of_timer_base *of_base)
|
||||
static void timer_of_base_exit(struct of_timer_base *of_base)
|
||||
{
|
||||
iounmap(of_base->base);
|
||||
}
|
||||
|
||||
static __init int timer_of_base_init(struct device_node *np,
|
||||
struct of_timer_base *of_base)
|
||||
static int timer_of_base_init(struct device_node *np,
|
||||
struct of_timer_base *of_base)
|
||||
{
|
||||
of_base->base = of_base->name ?
|
||||
of_io_request_and_map(np, of_base->index, of_base->name) :
|
||||
@@ -156,7 +156,7 @@ static __init int timer_of_base_init(struct device_node *np,
|
||||
return 0;
|
||||
}
|
||||
|
||||
int __init timer_of_init(struct device_node *np, struct timer_of *to)
|
||||
int timer_of_init(struct device_node *np, struct timer_of *to)
|
||||
{
|
||||
int ret = -EINVAL;
|
||||
int flags = 0;
|
||||
@@ -200,6 +200,7 @@ out_fail:
|
||||
timer_of_base_exit(&to->of_base);
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(timer_of_init);
|
||||
|
||||
/**
|
||||
* timer_of_cleanup - release timer_of resources
|
||||
@@ -208,7 +209,7 @@ out_fail:
|
||||
* Release the resources that has been used in timer_of_init().
|
||||
* This function should be called in init error cases
|
||||
*/
|
||||
void __init timer_of_cleanup(struct timer_of *to)
|
||||
void timer_of_cleanup(struct timer_of *to)
|
||||
{
|
||||
if (to->flags & TIMER_OF_IRQ)
|
||||
timer_of_irq_exit(&to->of_irq);
|
||||
@@ -219,3 +220,4 @@ void __init timer_of_cleanup(struct timer_of *to)
|
||||
if (to->flags & TIMER_OF_BASE)
|
||||
timer_of_base_exit(&to->of_base);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(timer_of_cleanup);
|
||||
@@ -65,9 +65,8 @@ static inline unsigned long timer_of_period(struct timer_of *to)
|
||||
return to->of_clk.period;
|
||||
}
|
||||
|
||||
extern int __init timer_of_init(struct device_node *np,
|
||||
struct timer_of *to);
|
||||
int timer_of_init(struct device_node *np, struct timer_of *to);
|
||||
|
||||
extern void __init timer_of_cleanup(struct timer_of *to);
|
||||
void timer_of_cleanup(struct timer_of *to);
|
||||
|
||||
#endif
|
||||
@@ -225,7 +225,7 @@ static int rttm_enable_clocksource(struct clocksource *cs)
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct rttm_cs rttm_cs = {
|
||||
static struct rttm_cs rttm_cs = {
|
||||
.to = {
|
||||
.flags = TIMER_OF_BASE | TIMER_OF_CLOCK,
|
||||
},
|
||||
|
||||
@@ -18,21 +18,30 @@
|
||||
#include <linux/slab.h>
|
||||
#include <linux/platform_device.h>
|
||||
|
||||
#define TIMER_IRQ_EN_REG 0x00
|
||||
#define TIMER_IRQ_EN_REG 0x00
|
||||
#define TIMER_IRQ_EN(val) BIT(val)
|
||||
#define TIMER_IRQ_ST_REG 0x04
|
||||
#define TIMER_CTL_REG(val) (0x20 * (val) + 0x10)
|
||||
#define TIMER_IRQ_ST_REG 0x04
|
||||
#define TIMER_CTL_REG(val, offset) (0x20 * (val) + 0x10 + (offset))
|
||||
#define TIMER_CTL_ENABLE BIT(0)
|
||||
#define TIMER_CTL_RELOAD BIT(1)
|
||||
#define TIMER_CTL_CLK_PRES(val) (((val) & 0x7) << 4)
|
||||
#define TIMER_CTL_ONESHOT BIT(7)
|
||||
#define TIMER_INTVAL_LO_REG(val) (0x20 * (val) + 0x14)
|
||||
#define TIMER_INTVAL_HI_REG(val) (0x20 * (val) + 0x18)
|
||||
#define TIMER_CNTVAL_LO_REG(val) (0x20 * (val) + 0x1c)
|
||||
#define TIMER_CNTVAL_HI_REG(val) (0x20 * (val) + 0x20)
|
||||
#define TIMER_INTVAL_LO_REG(val, offset) (0x20 * (val) + 0x14 + (offset))
|
||||
#define TIMER_INTVAL_HI_REG(val, offset) (0x20 * (val) + 0x18 + (offset))
|
||||
#define TIMER_CNTVAL_LO_REG(val, offset) (0x20 * (val) + 0x1c + (offset))
|
||||
#define TIMER_CNTVAL_HI_REG(val, offset) (0x20 * (val) + 0x20 + (offset))
|
||||
|
||||
#define TIMER_SYNC_TICKS 3
|
||||
|
||||
/**
|
||||
* struct sunxi_timer_quirks - Differences between SoC variants.
|
||||
*
|
||||
* @from_ctl_base_offset: offset applied from ctl register onwards
|
||||
*/
|
||||
struct sunxi_timer_quirks {
|
||||
u32 from_ctl_base_offset;
|
||||
};
|
||||
|
||||
struct sun5i_timer {
|
||||
void __iomem *base;
|
||||
struct clk *clk;
|
||||
@@ -40,6 +49,7 @@ struct sun5i_timer {
|
||||
u32 ticks_per_jiffy;
|
||||
struct clocksource clksrc;
|
||||
struct clock_event_device clkevt;
|
||||
const struct sunxi_timer_quirks *quirks;
|
||||
};
|
||||
|
||||
#define nb_to_sun5i_timer(x) \
|
||||
@@ -57,28 +67,36 @@ struct sun5i_timer {
|
||||
*/
|
||||
static void sun5i_clkevt_sync(struct sun5i_timer *ce)
|
||||
{
|
||||
u32 old = readl(ce->base + TIMER_CNTVAL_LO_REG(1));
|
||||
u32 offset = ce->quirks->from_ctl_base_offset;
|
||||
u32 old = readl(ce->base + TIMER_CNTVAL_LO_REG(1, offset));
|
||||
|
||||
while ((old - readl(ce->base + TIMER_CNTVAL_LO_REG(1))) < TIMER_SYNC_TICKS)
|
||||
while ((old - readl(ce->base + TIMER_CNTVAL_LO_REG(1, offset))) <
|
||||
TIMER_SYNC_TICKS)
|
||||
cpu_relax();
|
||||
}
|
||||
|
||||
static void sun5i_clkevt_time_stop(struct sun5i_timer *ce, u8 timer)
|
||||
{
|
||||
u32 val = readl(ce->base + TIMER_CTL_REG(timer));
|
||||
writel(val & ~TIMER_CTL_ENABLE, ce->base + TIMER_CTL_REG(timer));
|
||||
u32 offset = ce->quirks->from_ctl_base_offset;
|
||||
u32 val = readl(ce->base + TIMER_CTL_REG(timer, offset));
|
||||
|
||||
writel(val & ~TIMER_CTL_ENABLE,
|
||||
ce->base + TIMER_CTL_REG(timer, offset));
|
||||
|
||||
sun5i_clkevt_sync(ce);
|
||||
}
|
||||
|
||||
static void sun5i_clkevt_time_setup(struct sun5i_timer *ce, u8 timer, u32 delay)
|
||||
{
|
||||
writel(delay, ce->base + TIMER_INTVAL_LO_REG(timer));
|
||||
u32 offset = ce->quirks->from_ctl_base_offset;
|
||||
|
||||
writel(delay, ce->base + TIMER_INTVAL_LO_REG(timer, offset));
|
||||
}
|
||||
|
||||
static void sun5i_clkevt_time_start(struct sun5i_timer *ce, u8 timer, bool periodic)
|
||||
{
|
||||
u32 val = readl(ce->base + TIMER_CTL_REG(timer));
|
||||
u32 offset = ce->quirks->from_ctl_base_offset;
|
||||
u32 val = readl(ce->base + TIMER_CTL_REG(timer, offset));
|
||||
|
||||
if (periodic)
|
||||
val &= ~TIMER_CTL_ONESHOT;
|
||||
@@ -86,7 +104,7 @@ static void sun5i_clkevt_time_start(struct sun5i_timer *ce, u8 timer, bool perio
|
||||
val |= TIMER_CTL_ONESHOT;
|
||||
|
||||
writel(val | TIMER_CTL_ENABLE | TIMER_CTL_RELOAD,
|
||||
ce->base + TIMER_CTL_REG(timer));
|
||||
ce->base + TIMER_CTL_REG(timer, offset));
|
||||
}
|
||||
|
||||
static int sun5i_clkevt_shutdown(struct clock_event_device *clkevt)
|
||||
@@ -141,8 +159,9 @@ static irqreturn_t sun5i_timer_interrupt(int irq, void *dev_id)
|
||||
static u64 sun5i_clksrc_read(struct clocksource *clksrc)
|
||||
{
|
||||
struct sun5i_timer *cs = clksrc_to_sun5i_timer(clksrc);
|
||||
u32 offset = cs->quirks->from_ctl_base_offset;
|
||||
|
||||
return ~readl(cs->base + TIMER_CNTVAL_LO_REG(1));
|
||||
return ~readl(cs->base + TIMER_CNTVAL_LO_REG(1, offset));
|
||||
}
|
||||
|
||||
static int sun5i_rate_cb(struct notifier_block *nb,
|
||||
@@ -173,12 +192,13 @@ static int sun5i_setup_clocksource(struct platform_device *pdev,
|
||||
unsigned long rate)
|
||||
{
|
||||
struct sun5i_timer *cs = platform_get_drvdata(pdev);
|
||||
u32 offset = cs->quirks->from_ctl_base_offset;
|
||||
void __iomem *base = cs->base;
|
||||
int ret;
|
||||
|
||||
writel(~0, base + TIMER_INTVAL_LO_REG(1));
|
||||
writel(~0, base + TIMER_INTVAL_LO_REG(1, offset));
|
||||
writel(TIMER_CTL_ENABLE | TIMER_CTL_RELOAD,
|
||||
base + TIMER_CTL_REG(1));
|
||||
base + TIMER_CTL_REG(1, offset));
|
||||
|
||||
cs->clksrc.name = pdev->dev.of_node->name;
|
||||
cs->clksrc.rating = 340;
|
||||
@@ -237,6 +257,7 @@ static int sun5i_setup_clockevent(struct platform_device *pdev,
|
||||
|
||||
static int sun5i_timer_probe(struct platform_device *pdev)
|
||||
{
|
||||
const struct sunxi_timer_quirks *quirks;
|
||||
struct device *dev = &pdev->dev;
|
||||
struct sun5i_timer *st;
|
||||
struct reset_control *rstc;
|
||||
@@ -273,11 +294,18 @@ static int sun5i_timer_probe(struct platform_device *pdev)
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
quirks = of_device_get_match_data(&pdev->dev);
|
||||
if (!quirks) {
|
||||
dev_err(&pdev->dev, "Failed to determine the quirks to use\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
st->base = timer_base;
|
||||
st->ticks_per_jiffy = DIV_ROUND_UP(rate, HZ);
|
||||
st->clk = clk;
|
||||
st->clk_rate_cb.notifier_call = sun5i_rate_cb;
|
||||
st->clk_rate_cb.next = NULL;
|
||||
st->quirks = quirks;
|
||||
|
||||
ret = devm_clk_notifier_register(dev, clk, &st->clk_rate_cb);
|
||||
if (ret) {
|
||||
@@ -286,6 +314,9 @@ static int sun5i_timer_probe(struct platform_device *pdev)
|
||||
}
|
||||
|
||||
rstc = devm_reset_control_get_optional_exclusive(dev, NULL);
|
||||
if (IS_ERR(rstc))
|
||||
return dev_err_probe(dev, PTR_ERR(rstc),
|
||||
"failed to get reset\n");
|
||||
if (rstc)
|
||||
reset_control_deassert(rstc);
|
||||
|
||||
@@ -311,9 +342,27 @@ static void sun5i_timer_remove(struct platform_device *pdev)
|
||||
clocksource_unregister(&st->clksrc);
|
||||
}
|
||||
|
||||
static const struct sunxi_timer_quirks sun5i_sun7i_hstimer_quirks = {
|
||||
.from_ctl_base_offset = 0x0,
|
||||
};
|
||||
|
||||
static const struct sunxi_timer_quirks sun20i_d1_hstimer_quirks = {
|
||||
.from_ctl_base_offset = 0x10,
|
||||
};
|
||||
|
||||
static const struct of_device_id sun5i_timer_of_match[] = {
|
||||
{ .compatible = "allwinner,sun5i-a13-hstimer" },
|
||||
{ .compatible = "allwinner,sun7i-a20-hstimer" },
|
||||
{
|
||||
.compatible = "allwinner,sun5i-a13-hstimer",
|
||||
.data = &sun5i_sun7i_hstimer_quirks,
|
||||
},
|
||||
{
|
||||
.compatible = "allwinner,sun7i-a20-hstimer",
|
||||
.data = &sun5i_sun7i_hstimer_quirks,
|
||||
},
|
||||
{
|
||||
.compatible = "allwinner,sun20i-d1-hstimer",
|
||||
.data = &sun20i_d1_hstimer_quirks,
|
||||
},
|
||||
{},
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, sun5i_timer_of_match);
|
||||
|
||||
@@ -57,6 +57,15 @@
|
||||
#define WDTUR 0x00c
|
||||
#define WDTUR_UNLOCK_PATTERN 0x0000c45a
|
||||
|
||||
#define TEGRA186_KERNEL_WDT_TIMEOUT 120
|
||||
|
||||
/* WDT security configuration registers */
|
||||
#define WDTSCR(x) (0xf02c + (x) * 4)
|
||||
#define WDTSCR_SEC_WEN BIT(28)
|
||||
#define WDTSCR_SEC_REN BIT(27)
|
||||
#define WDTSCR_SEC_G1W BIT(9)
|
||||
#define WDTSCR_SEC_G1R BIT(1)
|
||||
|
||||
struct tegra186_timer_soc {
|
||||
unsigned int num_timers;
|
||||
unsigned int num_wdts;
|
||||
@@ -75,6 +84,7 @@ struct tegra186_wdt {
|
||||
void __iomem *regs;
|
||||
unsigned int index;
|
||||
bool locked;
|
||||
bool is_kernel_wdt;
|
||||
|
||||
struct tegra186_tmr *tmr;
|
||||
};
|
||||
@@ -89,7 +99,7 @@ struct tegra186_timer {
|
||||
struct device *dev;
|
||||
void __iomem *regs;
|
||||
|
||||
struct tegra186_wdt *wdt;
|
||||
struct tegra186_wdt **wdts;
|
||||
struct clocksource usec;
|
||||
struct clocksource tsc;
|
||||
struct clocksource osc;
|
||||
@@ -149,7 +159,8 @@ static void tegra186_wdt_enable(struct tegra186_wdt *wdt)
|
||||
u32 value;
|
||||
|
||||
/* unmask hardware IRQ, this may have been lost across powergate */
|
||||
value = TKEIE_WDT_MASK(wdt->index, 1);
|
||||
value = readl(tegra->regs + TKEIE(wdt->tmr->hwirq));
|
||||
value |= TKEIE_WDT_MASK(wdt->index, 1);
|
||||
writel(value, tegra->regs + TKEIE(wdt->tmr->hwirq));
|
||||
|
||||
/* clear interrupt */
|
||||
@@ -174,6 +185,10 @@ static void tegra186_wdt_enable(struct tegra186_wdt *wdt)
|
||||
value &= ~WDTCR_PERIOD_MASK;
|
||||
value |= WDTCR_PERIOD(1);
|
||||
|
||||
/* enable local interrupt for kernel watchdog */
|
||||
if (wdt->is_kernel_wdt)
|
||||
value |= WDTCR_LOCAL_INT_ENABLE;
|
||||
|
||||
/* enable system POR reset */
|
||||
value |= WDTCR_SYSTEM_POR_RESET_ENABLE;
|
||||
|
||||
@@ -211,6 +226,16 @@ static int tegra186_wdt_ping(struct watchdog_device *wdd)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static irqreturn_t tegra186_wdt_irq(int irq, void *data)
|
||||
{
|
||||
struct tegra186_wdt *wdt = data;
|
||||
|
||||
tegra186_wdt_disable(wdt);
|
||||
tegra186_wdt_enable(wdt);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static int tegra186_wdt_set_timeout(struct watchdog_device *wdd,
|
||||
unsigned int timeout)
|
||||
{
|
||||
@@ -297,6 +322,23 @@ static const struct watchdog_ops tegra186_wdt_ops = {
|
||||
.get_timeleft = tegra186_wdt_get_timeleft,
|
||||
};
|
||||
|
||||
static bool tegra186_wdt_is_accessible(struct tegra186_timer *tegra, unsigned int index)
|
||||
{
|
||||
u32 value;
|
||||
|
||||
value = readl_relaxed(tegra->regs + WDTSCR(index));
|
||||
|
||||
/* Check OS write access if write blocking is enabled. */
|
||||
if ((value & WDTSCR_SEC_WEN) && !(value & WDTSCR_SEC_G1W))
|
||||
return false;
|
||||
|
||||
/* Check OS read access if read blocking is enabled. */
|
||||
if ((value & WDTSCR_SEC_REN) && !(value & WDTSCR_SEC_G1R))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static struct tegra186_wdt *tegra186_wdt_create(struct tegra186_timer *tegra,
|
||||
unsigned int index)
|
||||
{
|
||||
@@ -336,10 +378,6 @@ static struct tegra186_wdt *tegra186_wdt_create(struct tegra186_timer *tegra,
|
||||
if (err < 0)
|
||||
return ERR_PTR(err);
|
||||
|
||||
err = devm_watchdog_register_device(tegra->dev, &wdt->base);
|
||||
if (err < 0)
|
||||
return ERR_PTR(err);
|
||||
|
||||
return wdt;
|
||||
}
|
||||
|
||||
@@ -421,8 +459,11 @@ static int tegra186_timer_usec_init(struct tegra186_timer *tegra)
|
||||
|
||||
static int tegra186_timer_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct tegra186_wdt *kernel_wdt = NULL;
|
||||
struct device *dev = &pdev->dev;
|
||||
struct tegra186_timer *tegra;
|
||||
unsigned int i;
|
||||
int irq;
|
||||
int err;
|
||||
|
||||
tegra = devm_kzalloc(dev, sizeof(*tegra), GFP_KERNEL);
|
||||
@@ -441,12 +482,33 @@ static int tegra186_timer_probe(struct platform_device *pdev)
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
/* create a watchdog using a preconfigured timer */
|
||||
tegra->wdt = tegra186_wdt_create(tegra, 0);
|
||||
if (IS_ERR(tegra->wdt)) {
|
||||
err = PTR_ERR(tegra->wdt);
|
||||
dev_err(dev, "failed to create WDT: %d\n", err);
|
||||
return err;
|
||||
irq = err;
|
||||
|
||||
tegra->wdts = devm_kcalloc(dev, tegra->soc->num_wdts, sizeof(*tegra->wdts), GFP_KERNEL);
|
||||
if (!tegra->wdts)
|
||||
return -ENOMEM;
|
||||
|
||||
for (i = 0; i < tegra->soc->num_wdts; i++) {
|
||||
if (!tegra186_wdt_is_accessible(tegra, i)) {
|
||||
dev_warn(dev, "WDT%u is not accessible\n", i);
|
||||
continue;
|
||||
}
|
||||
|
||||
tegra->wdts[i] = tegra186_wdt_create(tegra, i);
|
||||
if (IS_ERR(tegra->wdts[i]))
|
||||
return dev_err_probe(dev, PTR_ERR(tegra->wdts[i]),
|
||||
"failed to create WDT%u\n", i);
|
||||
|
||||
/* Reserve the first accessible WDT for the Kernel. */
|
||||
if (!kernel_wdt) {
|
||||
kernel_wdt = tegra->wdts[i];
|
||||
kernel_wdt->is_kernel_wdt = true;
|
||||
} else {
|
||||
err = devm_watchdog_register_device(dev, &tegra->wdts[i]->base);
|
||||
if (err < 0)
|
||||
return dev_err_probe(dev, err,
|
||||
"failed to register WDT%u\n", i);
|
||||
}
|
||||
}
|
||||
|
||||
err = tegra186_timer_tsc_init(tegra);
|
||||
@@ -467,8 +529,22 @@ static int tegra186_timer_probe(struct platform_device *pdev)
|
||||
goto unregister_osc;
|
||||
}
|
||||
|
||||
if (kernel_wdt) {
|
||||
err = devm_request_irq(dev, irq, tegra186_wdt_irq, 0,
|
||||
dev_name(dev), kernel_wdt);
|
||||
if (err < 0) {
|
||||
dev_err(dev, "failed to request kernel WDT IRQ: %d\n", err);
|
||||
goto unregister_usec;
|
||||
}
|
||||
|
||||
tegra186_wdt_set_timeout(&kernel_wdt->base, TEGRA186_KERNEL_WDT_TIMEOUT);
|
||||
tegra186_wdt_enable(kernel_wdt);
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
unregister_usec:
|
||||
clocksource_unregister(&tegra->usec);
|
||||
unregister_osc:
|
||||
clocksource_unregister(&tegra->osc);
|
||||
unregister_tsc:
|
||||
@@ -488,9 +564,14 @@ static void tegra186_timer_remove(struct platform_device *pdev)
|
||||
static int __maybe_unused tegra186_timer_suspend(struct device *dev)
|
||||
{
|
||||
struct tegra186_timer *tegra = dev_get_drvdata(dev);
|
||||
unsigned int i;
|
||||
|
||||
if (watchdog_active(&tegra->wdt->base))
|
||||
tegra186_wdt_disable(tegra->wdt);
|
||||
for (i = 0; i < tegra->soc->num_wdts; i++) {
|
||||
struct tegra186_wdt *wdt = tegra->wdts[i];
|
||||
|
||||
if (wdt && (wdt->is_kernel_wdt || watchdog_active(&wdt->base)))
|
||||
tegra186_wdt_disable(wdt);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -498,9 +579,14 @@ static int __maybe_unused tegra186_timer_suspend(struct device *dev)
|
||||
static int __maybe_unused tegra186_timer_resume(struct device *dev)
|
||||
{
|
||||
struct tegra186_timer *tegra = dev_get_drvdata(dev);
|
||||
unsigned int i;
|
||||
|
||||
if (watchdog_active(&tegra->wdt->base))
|
||||
tegra186_wdt_enable(tegra->wdt);
|
||||
for (i = 0; i < tegra->soc->num_wdts; i++) {
|
||||
struct tegra186_wdt *wdt = tegra->wdts[i];
|
||||
|
||||
if (wdt && (wdt->is_kernel_wdt || watchdog_active(&wdt->base)))
|
||||
tegra186_wdt_enable(wdt);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -510,12 +596,12 @@ static SIMPLE_DEV_PM_OPS(tegra186_timer_pm_ops, tegra186_timer_suspend,
|
||||
|
||||
static const struct tegra186_timer_soc tegra186_timer = {
|
||||
.num_timers = 10,
|
||||
.num_wdts = 3,
|
||||
.num_wdts = 2,
|
||||
};
|
||||
|
||||
static const struct tegra186_timer_soc tegra234_timer = {
|
||||
.num_timers = 16,
|
||||
.num_wdts = 3,
|
||||
.num_wdts = 2,
|
||||
};
|
||||
|
||||
static const struct of_device_id tegra186_timer_of_match[] = {
|
||||
|
||||
@@ -226,7 +226,7 @@ static bool __init dmtimer_is_preferred(struct device_node *np)
|
||||
* Some omap3 boards with unreliable oscillator must not use the counter_32k
|
||||
* or dmtimer1 with 32 KiHz source. Additionally, the boards with unreliable
|
||||
* oscillator should really set counter_32k as disabled, and delete dmtimer1
|
||||
* ti,always-on property, but let's not count on it. For these quirky cases,
|
||||
* ti,timer-alwon property, but let's not count on it. For these quirky cases,
|
||||
* we prefer using the always-on secure dmtimer12 with the internal 32 KiHz
|
||||
* clock as the clocksource, and any available dmtimer as clockevent.
|
||||
*
|
||||
|
||||
@@ -20,8 +20,11 @@
|
||||
|
||||
#include <linux/clk.h>
|
||||
#include <linux/clk-provider.h>
|
||||
#include <linux/clocksource.h>
|
||||
#include <linux/clockchips.h>
|
||||
#include <linux/cpu_pm.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/err.h>
|
||||
@@ -29,6 +32,7 @@
|
||||
#include <linux/of.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/platform_data/dmtimer-omap.h>
|
||||
#include <linux/sched_clock.h>
|
||||
|
||||
#include <clocksource/timer-ti-dm.h>
|
||||
#include <linux/delay.h>
|
||||
@@ -148,6 +152,21 @@ static u32 omap_reserved_systimers;
|
||||
static LIST_HEAD(omap_timer_list);
|
||||
static DEFINE_SPINLOCK(dm_timer_lock);
|
||||
|
||||
struct dmtimer_clocksource {
|
||||
struct clocksource dev;
|
||||
struct dmtimer *timer;
|
||||
unsigned int loadval;
|
||||
};
|
||||
|
||||
struct omap_dm_timer_clockevent {
|
||||
struct clock_event_device dev;
|
||||
struct dmtimer *timer;
|
||||
u32 period;
|
||||
};
|
||||
|
||||
static bool omap_dm_timer_clockevent_setup;
|
||||
static void __iomem *omap_dm_timer_sched_clock_counter;
|
||||
|
||||
enum {
|
||||
REQUEST_ANY = 0,
|
||||
REQUEST_BY_ID,
|
||||
@@ -1185,6 +1204,192 @@ static const struct dev_pm_ops omap_dm_timer_pm_ops = {
|
||||
|
||||
static const struct of_device_id omap_timer_match[];
|
||||
|
||||
static struct dmtimer_clocksource *omap_dm_timer_to_clocksource(struct clocksource *cs)
|
||||
{
|
||||
return container_of(cs, struct dmtimer_clocksource, dev);
|
||||
}
|
||||
|
||||
static u64 omap_dm_timer_read_cycles(struct clocksource *cs)
|
||||
{
|
||||
struct dmtimer_clocksource *clksrc = omap_dm_timer_to_clocksource(cs);
|
||||
struct dmtimer *timer = clksrc->timer;
|
||||
|
||||
return (u64)__omap_dm_timer_read_counter(timer);
|
||||
}
|
||||
|
||||
static u64 notrace omap_dm_timer_read_sched_clock(void)
|
||||
{
|
||||
/* Posted mode is not active here, so we can read directly */
|
||||
return readl_relaxed(omap_dm_timer_sched_clock_counter);
|
||||
}
|
||||
|
||||
static void omap_dm_timer_clocksource_suspend(struct clocksource *cs)
|
||||
{
|
||||
struct dmtimer_clocksource *clksrc = omap_dm_timer_to_clocksource(cs);
|
||||
struct dmtimer *timer = clksrc->timer;
|
||||
|
||||
clksrc->loadval = __omap_dm_timer_read_counter(timer);
|
||||
__omap_dm_timer_stop(timer);
|
||||
}
|
||||
|
||||
static void omap_dm_timer_clocksource_resume(struct clocksource *cs)
|
||||
{
|
||||
struct dmtimer_clocksource *clksrc = omap_dm_timer_to_clocksource(cs);
|
||||
struct dmtimer *timer = clksrc->timer;
|
||||
|
||||
dmtimer_write(timer, OMAP_TIMER_COUNTER_REG, clksrc->loadval);
|
||||
dmtimer_write(timer, OMAP_TIMER_CTRL_REG, OMAP_TIMER_CTRL_ST | OMAP_TIMER_CTRL_AR);
|
||||
}
|
||||
|
||||
static void omap_dm_timer_clocksource_unregister(void *data)
|
||||
{
|
||||
struct clocksource *cs = data;
|
||||
|
||||
clocksource_unregister(cs);
|
||||
}
|
||||
|
||||
static int omap_dm_timer_setup_clocksource(struct dmtimer *timer)
|
||||
{
|
||||
struct device *dev = &timer->pdev->dev;
|
||||
struct dmtimer_clocksource *clksrc;
|
||||
int err;
|
||||
|
||||
__omap_dm_timer_init_regs(timer);
|
||||
|
||||
timer->reserved = 1;
|
||||
|
||||
clksrc = devm_kzalloc(dev, sizeof(*clksrc), GFP_KERNEL);
|
||||
if (!clksrc)
|
||||
return -ENOMEM;
|
||||
|
||||
clksrc->timer = timer;
|
||||
|
||||
clksrc->dev.name = "omap_dm_timer";
|
||||
clksrc->dev.rating = 300;
|
||||
clksrc->dev.read = omap_dm_timer_read_cycles;
|
||||
clksrc->dev.mask = CLOCKSOURCE_MASK(32);
|
||||
clksrc->dev.flags = CLOCK_SOURCE_IS_CONTINUOUS;
|
||||
clksrc->dev.suspend = omap_dm_timer_clocksource_suspend;
|
||||
clksrc->dev.resume = omap_dm_timer_clocksource_resume;
|
||||
|
||||
dmtimer_write(timer, OMAP_TIMER_COUNTER_REG, 0);
|
||||
dmtimer_write(timer, OMAP_TIMER_LOAD_REG, 0);
|
||||
dmtimer_write(timer, OMAP_TIMER_CTRL_REG, OMAP_TIMER_CTRL_ST | OMAP_TIMER_CTRL_AR);
|
||||
|
||||
omap_dm_timer_sched_clock_counter = timer->func_base + _OMAP_TIMER_COUNTER_OFFSET;
|
||||
sched_clock_register(omap_dm_timer_read_sched_clock, 32, timer->fclk_rate);
|
||||
|
||||
err = clocksource_register_hz(&clksrc->dev, timer->fclk_rate);
|
||||
if (err)
|
||||
return dev_err_probe(dev, err, "Could not register as clocksource\n");
|
||||
|
||||
err = devm_add_action_or_reset(dev, omap_dm_timer_clocksource_unregister, &clksrc->dev);
|
||||
if (err)
|
||||
return dev_err_probe(dev, err, "Could not register clocksource_unregister action\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct omap_dm_timer_clockevent *to_dm_timer_clockevent(struct clock_event_device *evt)
|
||||
{
|
||||
return container_of(evt, struct omap_dm_timer_clockevent, dev);
|
||||
}
|
||||
|
||||
static int omap_dm_timer_evt_set_next_event(unsigned long cycles,
|
||||
struct clock_event_device *evt)
|
||||
{
|
||||
struct omap_dm_timer_clockevent *clkevt = to_dm_timer_clockevent(evt);
|
||||
struct dmtimer *timer = clkevt->timer;
|
||||
|
||||
dmtimer_write(timer, OMAP_TIMER_COUNTER_REG, 0xffffffff - cycles);
|
||||
dmtimer_write(timer, OMAP_TIMER_CTRL_REG, OMAP_TIMER_CTRL_ST);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int omap_dm_timer_evt_shutdown(struct clock_event_device *evt)
|
||||
{
|
||||
struct omap_dm_timer_clockevent *clkevt = to_dm_timer_clockevent(evt);
|
||||
struct dmtimer *timer = clkevt->timer;
|
||||
|
||||
__omap_dm_timer_stop(timer);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int omap_dm_timer_evt_set_periodic(struct clock_event_device *evt)
|
||||
{
|
||||
struct omap_dm_timer_clockevent *clkevt = to_dm_timer_clockevent(evt);
|
||||
struct dmtimer *timer = clkevt->timer;
|
||||
|
||||
omap_dm_timer_evt_shutdown(evt);
|
||||
|
||||
omap_dm_timer_set_load(&timer->cookie, clkevt->period);
|
||||
dmtimer_write(timer, OMAP_TIMER_COUNTER_REG, clkevt->period);
|
||||
dmtimer_write(timer, OMAP_TIMER_CTRL_REG,
|
||||
OMAP_TIMER_CTRL_AR | OMAP_TIMER_CTRL_ST);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static irqreturn_t omap_dm_timer_evt_interrupt(int irq, void *dev_id)
|
||||
{
|
||||
struct omap_dm_timer_clockevent *clkevt = dev_id;
|
||||
struct dmtimer *timer = clkevt->timer;
|
||||
|
||||
__omap_dm_timer_write_status(timer, OMAP_TIMER_INT_OVERFLOW);
|
||||
|
||||
clkevt->dev.event_handler(&clkevt->dev);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static int omap_dm_timer_setup_clockevent(struct dmtimer *timer)
|
||||
{
|
||||
struct device *dev = &timer->pdev->dev;
|
||||
struct omap_dm_timer_clockevent *clkevt;
|
||||
int ret;
|
||||
|
||||
clkevt = devm_kzalloc(dev, sizeof(*clkevt), GFP_KERNEL);
|
||||
if (!clkevt)
|
||||
return -ENOMEM;
|
||||
|
||||
timer->reserved = 1;
|
||||
clkevt->timer = timer;
|
||||
|
||||
clkevt->dev.name = "omap_dm_timer";
|
||||
clkevt->dev.features = CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT;
|
||||
clkevt->dev.rating = 300;
|
||||
clkevt->dev.set_next_event = omap_dm_timer_evt_set_next_event;
|
||||
clkevt->dev.set_state_shutdown = omap_dm_timer_evt_shutdown;
|
||||
clkevt->dev.set_state_periodic = omap_dm_timer_evt_set_periodic;
|
||||
clkevt->dev.set_state_oneshot = omap_dm_timer_evt_shutdown;
|
||||
clkevt->dev.set_state_oneshot_stopped = omap_dm_timer_evt_shutdown;
|
||||
clkevt->dev.tick_resume = omap_dm_timer_evt_shutdown;
|
||||
clkevt->dev.cpumask = cpu_possible_mask;
|
||||
clkevt->period = 0xffffffff - DIV_ROUND_CLOSEST(timer->fclk_rate, HZ);
|
||||
|
||||
__omap_dm_timer_init_regs(timer);
|
||||
__omap_dm_timer_stop(timer);
|
||||
__omap_dm_timer_enable_posted(timer);
|
||||
|
||||
ret = devm_request_irq(dev, timer->irq, omap_dm_timer_evt_interrupt,
|
||||
IRQF_TIMER, "omap_dm_timer_clockevent", clkevt);
|
||||
if (ret) {
|
||||
dev_err(dev, "Failed to request interrupt: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
__omap_dm_timer_int_enable(timer, OMAP_TIMER_INT_OVERFLOW);
|
||||
|
||||
clockevents_config_and_register(&clkevt->dev, timer->fclk_rate,
|
||||
3,
|
||||
0xffffffff);
|
||||
|
||||
omap_dm_timer_clockevent_setup = true;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* omap_dm_timer_probe - probe function called for every registered device
|
||||
* @pdev: pointer to current timer platform device
|
||||
@@ -1272,6 +1477,18 @@ static int omap_dm_timer_probe(struct platform_device *pdev)
|
||||
|
||||
timer->pdev = pdev;
|
||||
|
||||
if (timer->capability & OMAP_TIMER_ALWON && !IS_ERR_OR_NULL(timer->fclk)) {
|
||||
if (!omap_dm_timer_sched_clock_counter) {
|
||||
ret = omap_dm_timer_setup_clocksource(timer);
|
||||
if (ret)
|
||||
return ret;
|
||||
} else if (!omap_dm_timer_clockevent_setup) {
|
||||
ret = omap_dm_timer_setup_clockevent(timer);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
pm_runtime_enable(dev);
|
||||
|
||||
if (!timer->reserved) {
|
||||
|
||||
@@ -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
|
||||
+29
-6
@@ -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);
|
||||
@@ -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");
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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>");
|
||||
@@ -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>");
|
||||
+40
-23
@@ -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;
|
||||
|
||||
+3
-1
@@ -16,7 +16,9 @@ proc-y += cmdline.o
|
||||
proc-y += consoles.o
|
||||
proc-y += cpuinfo.o
|
||||
proc-y += devices.o
|
||||
proc-y += interrupts.o
|
||||
ifneq ($(CONFIG_GENERIC_IRQ_SHOW),y)
|
||||
proc-y += interrupts.o
|
||||
endif
|
||||
proc-y += loadavg.o
|
||||
proc-y += meminfo.o
|
||||
proc-y += stat.o
|
||||
|
||||
@@ -18,9 +18,6 @@
|
||||
#ifndef arch_irq_stat_cpu
|
||||
#define arch_irq_stat_cpu(cpu) 0
|
||||
#endif
|
||||
#ifndef arch_irq_stat
|
||||
#define arch_irq_stat() 0
|
||||
#endif
|
||||
|
||||
u64 get_idle_time(struct kernel_cpustat *kcs, int cpu)
|
||||
{
|
||||
@@ -122,7 +119,6 @@ static int show_stat(struct seq_file *p, void *v)
|
||||
sum_softirq += softirq_stat;
|
||||
}
|
||||
}
|
||||
sum += arch_irq_stat();
|
||||
|
||||
seq_put_decimal_ull(p, "cpu ", nsec_to_clock_t(user));
|
||||
seq_put_decimal_ull(p, " ", nsec_to_clock_t(nice));
|
||||
|
||||
@@ -236,6 +236,9 @@ clocks_calc_mult_shift(u32 *mult, u32 *shift, u32 from, u32 to, u32 minsec);
|
||||
*/
|
||||
extern int
|
||||
__clocksource_register_scale(struct clocksource *cs, u32 scale, u32 freq);
|
||||
extern int
|
||||
__devm_clocksource_register_scale(struct device *dev, struct clocksource *cs,
|
||||
u32 scale, u32 freq);
|
||||
extern void
|
||||
__clocksource_update_freq_scale(struct clocksource *cs, u32 scale, u32 freq);
|
||||
|
||||
@@ -258,6 +261,18 @@ static inline int clocksource_register_khz(struct clocksource *cs, u32 khz)
|
||||
return __clocksource_register_scale(cs, 1000, khz);
|
||||
}
|
||||
|
||||
static inline int devm_clocksource_register_hz(struct device *dev,
|
||||
struct clocksource *cs, u32 hz)
|
||||
{
|
||||
return __devm_clocksource_register_scale(dev, cs, 1, hz);
|
||||
}
|
||||
|
||||
static inline int devm_clocksource_register_khz(struct device *dev,
|
||||
struct clocksource *cs, u32 khz)
|
||||
{
|
||||
return __devm_clocksource_register_scale(dev, cs, 1000, khz);
|
||||
}
|
||||
|
||||
static inline void __clocksource_update_freq_hz(struct clocksource *cs, u32 hz)
|
||||
{
|
||||
__clocksource_update_freq_scale(cs, 1, hz);
|
||||
|
||||
@@ -12,9 +12,6 @@
|
||||
|
||||
struct device;
|
||||
|
||||
extern int lockdep_is_cpus_held(void);
|
||||
extern int lockdep_is_cpus_write_held(void);
|
||||
|
||||
#ifdef CONFIG_HOTPLUG_CPU
|
||||
void cpus_write_lock(void);
|
||||
void cpus_write_unlock(void);
|
||||
@@ -22,6 +19,8 @@ void cpus_read_lock(void);
|
||||
void cpus_read_unlock(void);
|
||||
int cpus_read_trylock(void);
|
||||
void lockdep_assert_cpus_held(void);
|
||||
int lockdep_is_cpus_held(void);
|
||||
int lockdep_is_cpus_write_held(void);
|
||||
void cpu_hotplug_disable_offlining(void);
|
||||
void cpu_hotplug_disable(void);
|
||||
void cpu_hotplug_enable(void);
|
||||
@@ -38,6 +37,8 @@ static inline void cpus_read_lock(void) { }
|
||||
static inline void cpus_read_unlock(void) { }
|
||||
static inline int cpus_read_trylock(void) { return true; }
|
||||
static inline void lockdep_assert_cpus_held(void) { }
|
||||
static inline int lockdep_is_cpus_held(void) { return 1; }
|
||||
static inline int lockdep_is_cpus_write_held(void) { return 1; }
|
||||
static inline void cpu_hotplug_disable_offlining(void) { }
|
||||
static inline void cpu_hotplug_disable(void) { }
|
||||
static inline void cpu_hotplug_enable(void) { }
|
||||
|
||||
@@ -885,6 +885,7 @@ static inline void init_irq_proc(void)
|
||||
struct seq_file;
|
||||
int show_interrupts(struct seq_file *p, void *v);
|
||||
int arch_show_interrupts(struct seq_file *p, int prec);
|
||||
void irq_proc_emit_counts(struct seq_file *p, unsigned int __percpu *cnts);
|
||||
|
||||
extern int early_irq_init(void);
|
||||
extern int arch_probe_nr_irqs(void);
|
||||
|
||||
@@ -103,6 +103,7 @@ enum {
|
||||
IRQ_DISABLE_UNLAZY = (1 << 19),
|
||||
IRQ_HIDDEN = (1 << 20),
|
||||
IRQ_NO_DEBUG = (1 << 21),
|
||||
IRQ_RESERVED = (1 << 22),
|
||||
};
|
||||
|
||||
#define IRQF_MODIFY_MASK \
|
||||
|
||||
@@ -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
|
||||
@@ -52,8 +52,8 @@ struct irq_redirect {
|
||||
* @depth: disable-depth, for nested irq_disable() calls
|
||||
* @wake_depth: enable depth, for multiple irq_set_irq_wake() callers
|
||||
* @tot_count: stats field for non-percpu irqs
|
||||
* @irq_count: stats field to detect stalled irqs
|
||||
* @last_unhandled: aging timer for unhandled count
|
||||
* @irq_count: stats field to detect stalled irqs
|
||||
* @irqs_unhandled: stats field for spurious unhandled interrupts
|
||||
* @threads_handled: stats field for deferred spurious detection of threaded handlers
|
||||
* @threads_handled_last: comparator field for deferred spurious detection of threaded handlers
|
||||
@@ -70,6 +70,7 @@ struct irq_redirect {
|
||||
* IRQF_NO_SUSPEND set
|
||||
* @force_resume_depth: number of irqactions on a irq descriptor with
|
||||
* IRQF_FORCE_RESUME set
|
||||
* @refcnt: Reference count mainly for /proc/interrupts
|
||||
* @rcu: rcu head for delayed free
|
||||
* @kobj: kobject used to represent this struct in sysfs
|
||||
* @request_mutex: mutex to protect request/free before locking desc->lock
|
||||
@@ -87,9 +88,9 @@ struct irq_desc {
|
||||
unsigned int core_internal_state__do_not_mess_with_it;
|
||||
unsigned int depth; /* nested irq disables */
|
||||
unsigned int wake_depth; /* nested wake enables */
|
||||
unsigned int tot_count;
|
||||
unsigned int irq_count; /* For detecting broken IRQs */
|
||||
unsigned long tot_count;
|
||||
unsigned long last_unhandled; /* Aging timer for unhandled count */
|
||||
unsigned int irq_count; /* For detecting broken IRQs */
|
||||
unsigned int irqs_unhandled;
|
||||
atomic_t threads_handled;
|
||||
int threads_handled_last;
|
||||
@@ -119,6 +120,7 @@ struct irq_desc {
|
||||
struct dentry *debugfs_file;
|
||||
const char *dev_name;
|
||||
#endif
|
||||
rcuref_t refcnt;
|
||||
#ifdef CONFIG_SPARSE_IRQ
|
||||
struct rcu_head rcu;
|
||||
struct kobject kobj;
|
||||
|
||||
+1
-1
@@ -444,7 +444,7 @@ struct msi_domain_info;
|
||||
*
|
||||
* @domain_alloc_irqs, @domain_free_irqs can be used to override the
|
||||
* default allocation/free functions (__msi_domain_alloc/free_irqs). This
|
||||
* is initially for a wrapper around XENs seperate MSI universe which can't
|
||||
* is initially for a wrapper around XEN's separate MSI universe which can't
|
||||
* be wrapped into the regular irq domains concepts by mere mortals. This
|
||||
* allows to universally use msi_domain_alloc/free_irqs without having to
|
||||
* special case XEN all over the place.
|
||||
|
||||
+6
-2
@@ -49,9 +49,11 @@ int irq_set_chip(unsigned int irq, const struct irq_chip *chip)
|
||||
scoped_irqdesc->irq_data.chip = (struct irq_chip *)(chip ?: &no_irq_chip);
|
||||
ret = 0;
|
||||
}
|
||||
/* For !CONFIG_SPARSE_IRQ make the irq show up in allocated_irqs. */
|
||||
if (!ret)
|
||||
if (!ret) {
|
||||
/* For !CONFIG_SPARSE_IRQ make the irq show up in allocated_irqs. */
|
||||
irq_mark_irq(irq);
|
||||
irq_proc_update_chip(chip);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL(irq_set_chip);
|
||||
@@ -1027,6 +1029,7 @@ __irq_do_set_handler(struct irq_desc *desc, irq_flow_handler_t handle,
|
||||
WARN_ON(irq_chip_pm_get(irq_desc_get_irq_data(desc)));
|
||||
irq_activate_and_startup(desc, IRQ_RESEND);
|
||||
}
|
||||
irq_proc_update_valid(desc);
|
||||
}
|
||||
|
||||
void __irq_set_handler(unsigned int irq, irq_flow_handler_t handle, int is_chained,
|
||||
@@ -1087,6 +1090,7 @@ void irq_modify_status(unsigned int irq, unsigned long clr, unsigned long set)
|
||||
trigger = tmp;
|
||||
|
||||
irqd_set(&desc->irq_data, trigger);
|
||||
irq_proc_update_valid(desc);
|
||||
}
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(irq_modify_status);
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
#ifndef _KERNEL_IRQ_DEBUGFS_H
|
||||
#define _KERNEL_IRQ_DEBUGFS_H
|
||||
|
||||
#ifdef CONFIG_GENERIC_IRQ_DEBUGFS
|
||||
#include <linux/debugfs.h>
|
||||
|
||||
struct irq_bit_descr {
|
||||
unsigned int mask;
|
||||
char *name;
|
||||
};
|
||||
|
||||
#define BIT_MASK_DESCR(m) { .mask = m, .name = #m }
|
||||
|
||||
void irq_debug_show_bits(struct seq_file *m, int ind, unsigned int state,
|
||||
const struct irq_bit_descr *sd, int size);
|
||||
|
||||
void irq_add_debugfs_entry(unsigned int irq, struct irq_desc *desc);
|
||||
static inline void irq_remove_debugfs_entry(struct irq_desc *desc)
|
||||
{
|
||||
debugfs_remove(desc->debugfs_file);
|
||||
kfree(desc->dev_name);
|
||||
}
|
||||
void irq_debugfs_copy_devname(int irq, struct device *dev);
|
||||
# ifdef CONFIG_IRQ_DOMAIN
|
||||
void irq_domain_debugfs_init(struct dentry *root);
|
||||
# else
|
||||
static inline void irq_domain_debugfs_init(struct dentry *root)
|
||||
{
|
||||
}
|
||||
# endif
|
||||
#else /* CONFIG_GENERIC_IRQ_DEBUGFS */
|
||||
static inline void irq_add_debugfs_entry(unsigned int irq, struct irq_desc *d)
|
||||
{
|
||||
}
|
||||
static inline void irq_remove_debugfs_entry(struct irq_desc *d)
|
||||
{
|
||||
}
|
||||
static inline void irq_debugfs_copy_devname(int irq, struct device *dev)
|
||||
{
|
||||
}
|
||||
#endif /* CONFIG_GENERIC_IRQ_DEBUGFS */
|
||||
|
||||
#endif
|
||||
+24
-40
@@ -9,8 +9,12 @@
|
||||
#include <linux/irqdesc.h>
|
||||
#include <linux/kernel_stat.h>
|
||||
#include <linux/pm_runtime.h>
|
||||
#include <linux/rcuref.h>
|
||||
#include <linux/sched/clock.h>
|
||||
|
||||
#include "debugfs.h"
|
||||
#include "proc.h"
|
||||
|
||||
#ifdef CONFIG_SPARSE_IRQ
|
||||
# define MAX_SPARSE_IRQS INT_MAX
|
||||
#else
|
||||
@@ -21,6 +25,7 @@
|
||||
|
||||
extern bool noirqdebug;
|
||||
extern int irq_poll_cpu;
|
||||
extern unsigned int total_nr_irqs;
|
||||
|
||||
extern struct irqaction chained_action;
|
||||
|
||||
@@ -100,9 +105,23 @@ extern void unmask_irq(struct irq_desc *desc);
|
||||
extern void unmask_threaded_irq(struct irq_desc *desc);
|
||||
|
||||
#ifdef CONFIG_SPARSE_IRQ
|
||||
static inline void irq_mark_irq(unsigned int irq) { }
|
||||
static __always_inline void irq_mark_irq(unsigned int irq) { }
|
||||
void irq_desc_free_rcu(struct irq_desc *desc);
|
||||
|
||||
static __always_inline bool irq_desc_get_ref(struct irq_desc *desc)
|
||||
{
|
||||
return rcuref_get(&desc->refcnt);
|
||||
}
|
||||
|
||||
static __always_inline void irq_desc_put_ref(struct irq_desc *desc)
|
||||
{
|
||||
if (rcuref_put(&desc->refcnt))
|
||||
irq_desc_free_rcu(desc);
|
||||
}
|
||||
#else
|
||||
extern void irq_mark_irq(unsigned int irq);
|
||||
static __always_inline bool irq_desc_get_ref(struct irq_desc *desc) { return true; }
|
||||
static __always_inline void irq_desc_put_ref(struct irq_desc *desc) { }
|
||||
#endif
|
||||
|
||||
irqreturn_t __handle_irq_event_percpu(struct irq_desc *desc);
|
||||
@@ -122,6 +141,7 @@ extern void register_irq_proc(unsigned int irq, struct irq_desc *desc);
|
||||
extern void unregister_irq_proc(unsigned int irq, struct irq_desc *desc);
|
||||
extern void register_handler_proc(unsigned int irq, struct irqaction *action);
|
||||
extern void unregister_handler_proc(unsigned int irq, struct irqaction *action);
|
||||
void irq_proc_update_valid(struct irq_desc *desc);
|
||||
#else
|
||||
static inline void register_irq_proc(unsigned int irq, struct irq_desc *desc) { }
|
||||
static inline void unregister_irq_proc(unsigned int irq, struct irq_desc *desc) { }
|
||||
@@ -129,8 +149,11 @@ static inline void register_handler_proc(unsigned int irq,
|
||||
struct irqaction *action) { }
|
||||
static inline void unregister_handler_proc(unsigned int irq,
|
||||
struct irqaction *action) { }
|
||||
static inline void irq_proc_update_valid(struct irq_desc *desc) { }
|
||||
#endif
|
||||
|
||||
struct irq_desc *irq_find_desc_at_or_after(unsigned int offset);
|
||||
|
||||
extern bool irq_can_set_affinity_usr(unsigned int irq);
|
||||
|
||||
extern int irq_do_set_affinity(struct irq_data *data,
|
||||
@@ -372,42 +395,3 @@ static inline struct irq_data *irqd_get_parent_data(struct irq_data *irqd)
|
||||
return NULL;
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef CONFIG_GENERIC_IRQ_DEBUGFS
|
||||
#include <linux/debugfs.h>
|
||||
|
||||
struct irq_bit_descr {
|
||||
unsigned int mask;
|
||||
char *name;
|
||||
};
|
||||
|
||||
#define BIT_MASK_DESCR(m) { .mask = m, .name = #m }
|
||||
|
||||
void irq_debug_show_bits(struct seq_file *m, int ind, unsigned int state,
|
||||
const struct irq_bit_descr *sd, int size);
|
||||
|
||||
void irq_add_debugfs_entry(unsigned int irq, struct irq_desc *desc);
|
||||
static inline void irq_remove_debugfs_entry(struct irq_desc *desc)
|
||||
{
|
||||
debugfs_remove(desc->debugfs_file);
|
||||
kfree(desc->dev_name);
|
||||
}
|
||||
void irq_debugfs_copy_devname(int irq, struct device *dev);
|
||||
# ifdef CONFIG_IRQ_DOMAIN
|
||||
void irq_domain_debugfs_init(struct dentry *root);
|
||||
# else
|
||||
static inline void irq_domain_debugfs_init(struct dentry *root)
|
||||
{
|
||||
}
|
||||
# endif
|
||||
#else /* CONFIG_GENERIC_IRQ_DEBUGFS */
|
||||
static inline void irq_add_debugfs_entry(unsigned int irq, struct irq_desc *d)
|
||||
{
|
||||
}
|
||||
static inline void irq_remove_debugfs_entry(struct irq_desc *d)
|
||||
{
|
||||
}
|
||||
static inline void irq_debugfs_copy_devname(int irq, struct device *dev)
|
||||
{
|
||||
}
|
||||
#endif /* CONFIG_GENERIC_IRQ_DEBUGFS */
|
||||
Loaded 100 of 109 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user