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:
Carlos Llamas committed 2026-07-23 14:59:12 +00:00
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
========================
@@ -38,6 +38,8 @@ properties:
- amlogic,a4-gpio-intc
- amlogic,a4-gpio-ao-intc
- amlogic,a5-gpio-intc
- amlogic,a9-gpio-intc
- amlogic,a9-gpio-ao-intc
- amlogic,c3-gpio-intc
- amlogic,s6-gpio-intc
- amlogic,s7-gpio-intc
@@ -56,7 +58,7 @@ properties:
amlogic,channel-interrupts:
description: Array with the upstream hwirq numbers
minItems: 2
maxItems: 12
maxItems: 20
$ref: /schemas/types.yaml#/definitions/uint32-array
required:
@@ -76,9 +78,20 @@ then:
amlogic,channel-interrupts:
maxItems: 2
else:
properties:
amlogic,channel-interrupts:
minItems: 8
if:
properties:
compatible:
contains:
const: amlogic,a9-gpio-ao-intc
then:
properties:
amlogic,channel-interrupts:
minItems: 20
else:
properties:
amlogic,channel-interrupts:
minItems: 8
maxItems: 12
additionalProperties: false
@@ -1,90 +0,0 @@
# SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause
%YAML 1.2
---
$id: http://devicetree.org/schemas/interrupt-controller/aspeed,ast2700-intc.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
title: Aspeed AST2700 Interrupt Controller
description:
This interrupt controller hardware is second level interrupt controller that
is hooked to a parent interrupt controller. It's useful to combine multiple
interrupt sources into 1 interrupt to parent interrupt controller.
maintainers:
- Kevin Chen <kevin_chen@aspeedtech.com>
properties:
compatible:
enum:
- aspeed,ast2700-intc-ic
reg:
maxItems: 1
interrupt-controller: true
'#interrupt-cells':
const: 1
description:
The first cell is the IRQ number, the second cell is the trigger
type as defined in interrupt.txt in this directory.
interrupts:
minItems: 1
maxItems: 10
description: |
Depend to which INTC0 or INTC1 used.
INTC0 and INTC1 are two kinds of interrupt controller with enable and raw
status registers for use.
INTC0 is used to assert GIC if interrupt in INTC1 asserted.
INTC1 is used to assert INTC0 if interrupt of modules asserted.
+-----+ +-------+ +---------+---module0
| GIC |---| INTC0 |--+--| INTC1_0 |---module2
| | | | | | |---...
+-----+ +-------+ | +---------+---module31
|
| +---------+---module0
+---| INTC1_1 |---module2
| | |---...
| +---------+---module31
...
| +---------+---module0
+---| INTC1_5 |---module2
| |---...
+---------+---module31
required:
- compatible
- reg
- interrupt-controller
- '#interrupt-cells'
- interrupts
additionalProperties: false
examples:
- |
#include <dt-bindings/interrupt-controller/arm-gic.h>
bus {
#address-cells = <2>;
#size-cells = <2>;
interrupt-controller@12101b00 {
compatible = "aspeed,ast2700-intc-ic";
reg = <0 0x12101b00 0 0x10>;
#interrupt-cells = <1>;
interrupt-controller;
interrupts = <GIC_SPI 192 IRQ_TYPE_LEVEL_HIGH>,
<GIC_SPI 193 IRQ_TYPE_LEVEL_HIGH>,
<GIC_SPI 194 IRQ_TYPE_LEVEL_HIGH>,
<GIC_SPI 195 IRQ_TYPE_LEVEL_HIGH>,
<GIC_SPI 196 IRQ_TYPE_LEVEL_HIGH>,
<GIC_SPI 197 IRQ_TYPE_LEVEL_HIGH>,
<GIC_SPI 198 IRQ_TYPE_LEVEL_HIGH>,
<GIC_SPI 199 IRQ_TYPE_LEVEL_HIGH>,
<GIC_SPI 200 IRQ_TYPE_LEVEL_HIGH>,
<GIC_SPI 201 IRQ_TYPE_LEVEL_HIGH>;
};
};
@@ -0,0 +1,188 @@
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
%YAML 1.2
---
$id: http://devicetree.org/schemas/interrupt-controller/aspeed,ast2700-interrupt.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
title: ASPEED AST2700 Interrupt Controllers (INTC0/INTC1)
description: |
The ASPEED AST2700 SoC integrates two interrupt controller designs:
- INTC0: Primary controller that routes interrupt sources to upstream,
processor-specific interrupt controllers
- INTC1: Secondary controller whose interrupt outputs feed into INTC0
The SoC contains four processors to which interrupts can be routed:
- PSP: Primary Service Processor (Cortex-A35)
- SSP: Secondary Service Processor (Cortex-M4)
- TSP: Tertiary Service Processor (Cortex-M4)
- BMCU: Boot MCU (a RISC-V microcontroller)
The following diagram illustrates the overall architecture of the
ASPEED AST2700 interrupt controllers:
+-----------+ +-----------+
| INTC0 | | INTC1(0) |
+-----------+ +-----------+
| Router | +-----------+ | Router |
| out int | +Peripheral + | out int |
+-----------+ | 0 0 <-+Controllers+ | INTM | +-----------+
|PSP GIC <-|---+ . . | +-----------+ | . . <-+Peripheral +
+-----------+ | . . | | . . | +Controllers+
+-----------+ | . . | | . . | +-----------+
|SSP NVIC <-|---+ . . <----------------+ . . |
+-----------+ | . . | | . . |
+-----------+ | . . <-------- | . . |
|TSP NVIC <-|---+ . . | | ----+ . . |
+-----------+ | . . | | | | O P |
| . . | | | +-----------+
| . . <---- | --------------------
| . . | | | +-----------+ |
| M N | | ---------+ INTC1(1) | |
+-----------+ | +-----------+ |
| . |
| +-----------+ |
-------------+ INTC1(N) | |
+-----------+ |
+--------------+ |
+ BMCU APLIC <-+---------------------------------------------
+--------------+
INTC0 supports:
- 128 local peripheral interrupt inputs
- Fan-in from up to three INTC1 instances via banked interrupt lines (INTM)
- Local peripheral interrupt outputs
- Merged interrupt outputs
- Software interrupt outputs (SWINT)
- Configurable interrupt routes targeting the PSP, SSP, and TSP
INTC1 supports:
- 192 local peripheral interrupt inputs
- Banked interrupt outputs (INTM, 5 x 6 banks x 32 interrupts per bank)
- Configurable interrupt routes targeting the PSP, SSP, TSP, and BMCU
One INTC1 instance is always present, on the SoC's IO die. A further two
instances may be attached to the SoC's one INTC0 instance via LTPI (LVDS
Tunneling Protocol & Interface).
Interrupt numbering model
-------------------------
The binding uses a controller-local numbering model. Peripheral device
nodes use the INTCx local interrupt number (hwirq) in their 'interrupts' or
'interrupts-extended' properties.
For AST2700, INTC0 exposes the following (inclusive) input ranges:
- 000..479: Independent interrupts
- 480..489: INTM0-INTM9
- 490..499: INTM10-INTM19
- 500..509: INTM20-INTM29
- 510..519: INTM30-INTM39
- 520..529: INTM40-INTM49
INTC0's (inclusive) output ranges are as follows:
- 000..127: 1:1 local peripheral interrupt output to PSP
- 144..151: Software interrupts from the SSP output to PSP
- 152..159: Software interrupts from the TSP output to PSP
- 192..201: INTM0-INTM9 banked outputs to PSP
- 208..217: INTM30-INTM39 banked outputs to PSP
- 224..233: INTM40-INTM49 banked outputs to PSP
- 256..383: 1:1 local peripheral interrupt output to SSP
- 384..393: INTM10-INTM19 banked outputs to SSP
- 400..407: Software interrupts from the PSP output to SSP
- 408..415: Software interrupts from the TSP output to SSP
- 426..553: 1:1 local peripheral interrupt output to TSP
- 554..563: INTM20-INTM29 banked outputs to TSP
- 570..577: Software interrupts from the PSP output to TSP
- 578..585: Software interrupts from the SSP output to TSP
Inputs and outputs for INTC1 instances are context-dependent. However, for the
first instance of INTC1, the (inclusive) output ranges are:
- 00..05: INTM0-INTM5
- 10..15: INTM10-INTM15
- 20..25: INTM20-INTM25
- 30..35: INTM30-INTM35
- 40..45: INTM40-INTM45
- 50..50: BootMCU
maintainers:
- Ryan Chen <ryan_chen@aspeedtech.com>
- Andrew Jeffery <andrew@codeconstruct.com.au>
properties:
compatible:
enum:
- aspeed,ast2700-intc0
- aspeed,ast2700-intc1
reg:
maxItems: 1
interrupt-controller: true
'#interrupt-cells':
const: 1
description: Single cell encoding the INTC local interrupt number (hwirq).
aspeed,interrupt-ranges:
description: |
Describes how ranges of controller output pins are routed to a parent
interrupt controller.
Each range entry is encoded as:
<out count phandle parent-specifier...>
where:
- out: First controller interrupt output index in the range.
- count: Number of consecutive controller interrupt outputs and parent
interrupt inputs in this range.
- phandle: Phandle to the parent interrupt controller node.
- parent-specifier: Interrupt specifier, as defined by the parent
interrupt controller binding.
$ref: /schemas/types.yaml#/definitions/uint32-array
minItems: 3
items:
description: Range descriptors with a parent interrupt specifier.
required:
- compatible
- reg
- interrupt-controller
- '#interrupt-cells'
- aspeed,interrupt-ranges
additionalProperties: false
examples:
- |
#include <dt-bindings/interrupt-controller/arm-gic.h>
interrupt-controller@12100000 {
compatible = "aspeed,ast2700-intc0";
reg = <0x12100000 0x3b00>;
interrupt-parent = <&gic>;
interrupt-controller;
#interrupt-cells = <1>;
aspeed,interrupt-ranges =
<0 128 &gic GIC_SPI 0 IRQ_TYPE_LEVEL_HIGH>,
<144 8 &gic GIC_SPI 144 IRQ_TYPE_LEVEL_HIGH>,
<152 8 &gic GIC_SPI 152 IRQ_TYPE_LEVEL_HIGH>,
<192 10 &gic GIC_SPI 192 IRQ_TYPE_LEVEL_HIGH>,
<208 10 &gic GIC_SPI 208 IRQ_TYPE_LEVEL_HIGH>,
<224 10 &gic GIC_SPI 224 IRQ_TYPE_LEVEL_HIGH>,
<256 128 &ssp_nvic 0 0>,
<384 10 &ssp_nvic 160 0>,
<400 8 &ssp_nvic 144 0>,
<408 8 &ssp_nvic 152 0>,
<426 128 &tsp_nvic 0 0>,
<554 10 &tsp_nvic 160 0>,
<570 8 &tsp_nvic 144 0>,
<578 8 &tsp_nvic 152 0>;
};
@@ -52,6 +52,25 @@ properties:
- description: primary per-CPU IRQ
- description: shadow IRQ number
econet,cpu-interrupt-map:
$ref: /schemas/types.yaml#/definitions/uint32-matrix
description:
When running in VEIC mode, the hardware re-routes interrupts from the
CPU interrupt controller core to the "external" interrupt controller
(this device). It then prioritizes them and sends them back to the CPU
along with its own interrupts. The CPU hardware handles interrupts using
a special dispatch table (the normal interrupt handler is not invoked).
In this interrupt controller, the CPU interrupts are renumbered as they
are merged with this controller's own hardware interrupts.
This is the inverse of an interrupt-map, mapping which interrupts from
this controller must be routed back to the CPU interrupt domain for
correct handling there.
items:
items:
- description: The interrupt number as received in this controller
- description: The interrupt number to be dispatched on the CPU intc
required:
- compatible
- reg
@@ -74,5 +93,6 @@ examples:
interrupts = <2>;
econet,shadow-interrupts = <7 2>, <8 3>, <13 12>, <30 29>;
econet,cpu-interrupt-map = <7 0>, <8 1>;
};
...
@@ -1,13 +1,13 @@
# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
%YAML 1.2
---
$id: http://devicetree.org/schemas/interrupt-controller/starfive,jh8100-intc.yaml#
$id: http://devicetree.org/schemas/interrupt-controller/starfive,jhb100-intc.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
title: StarFive External Interrupt Controller
description:
StarFive SoC JH8100 contain a external interrupt controller. It can be used
StarFive SoC JHB100 contain a external interrupt controller. It can be used
to handle high-level input interrupt signals. It also send the output
interrupt signal to RISC-V PLIC.
@@ -16,19 +16,11 @@ maintainers:
properties:
compatible:
const: starfive,jh8100-intc
const: starfive,jhb100-intc
reg:
maxItems: 1
clocks:
description: APB clock for the interrupt controller
maxItems: 1
resets:
description: APB reset for the interrupt controller
maxItems: 1
interrupts:
maxItems: 1
@@ -40,8 +32,6 @@ properties:
required:
- compatible
- reg
- clocks
- resets
- interrupts
- interrupt-controller
- "#interrupt-cells"
@@ -51,10 +41,8 @@ additionalProperties: false
examples:
- |
interrupt-controller@12260000 {
compatible = "starfive,jh8100-intc";
compatible = "starfive,jhb100-intc";
reg = <0x12260000 0x10000>;
clocks = <&syscrg_ne 76>;
resets = <&syscrg_ne 13>;
interrupts = <45>;
interrupt-controller;
#interrupt-cells = <1>;
@@ -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
View File
@@ -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>
+4 -4
View File
@@ -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;
}
+2 -2
View File
@@ -7,7 +7,7 @@
#ifndef __ASM_ARCH_GICV3_H
#define __ASM_ARCH_GICV3_H
#ifndef __ASSEMBLY__
#ifndef __ASSEMBLER__
#include <linux/io.h>
#include <linux/io-64-nonatomic-lo-hi.h>
@@ -257,5 +257,5 @@ static inline bool gic_has_relaxed_pmr_sync(void)
return false;
}
#endif /* !__ASSEMBLY__ */
#endif /* !__ASSEMBLER__ */
#endif /* !__ASM_ARCH_GICV3_H */
+1 -2
View File
@@ -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));
-21
View File
@@ -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
+2 -3
View File
@@ -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);
+1 -1
View File
@@ -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]);
+1 -2
View File
@@ -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]);
+1 -1
View File
@@ -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));
+6 -6
View File
@@ -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;
}
+2 -2
View File
@@ -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;
}
+2 -2
View File
@@ -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;
+1 -1
View File
@@ -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;
+1 -1
View File
@@ -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);
+1 -1
View File
@@ -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;
}
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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];
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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);
}
+64 -45
View File
@@ -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
-4
View File
@@ -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[];
-3
View File
@@ -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);
+3 -3
View File
@@ -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);
+1 -3
View File
@@ -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);
}
+1 -1
View File
@@ -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;
+1 -1
View File
@@ -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();
+1 -1
View File
@@ -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();
+2 -6
View File
@@ -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;
+1 -1
View File
@@ -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();
+2 -2
View File
@@ -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);
+1 -1
View File
@@ -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
View File
@@ -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();
+1 -1
View File
@@ -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);
}
+1 -1
View File
@@ -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);
+2 -2
View File
@@ -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);
+3 -3
View File
@@ -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
View File
@@ -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);
}
+1 -1
View File
@@ -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();
+1 -1
View File
@@ -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
View File
@@ -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;
}
+1 -1
View File
@@ -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;
}
+1 -1
View File
@@ -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;
}
+40 -2
View File
@@ -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;
+31
View File
@@ -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
+33 -22
View File
@@ -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);
+8 -3
View File
@@ -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);
+13 -11
View File
@@ -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);
+2 -3
View File
@@ -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
+1 -1
View File
@@ -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,
},
+68 -19
View File
@@ -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);
+104 -18
View File
@@ -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[] = {
+1 -1
View File
@@ -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.
*
+217
View File
@@ -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) {
+5
View File
@@ -0,0 +1,5 @@
CONFIG_KUNIT=y
CONFIG_OF=y
CONFIG_COMPILE_TEST=y
CONFIG_ASPEED_AST2700_INTC=y
CONFIG_ASPEED_AST2700_INTC_TEST=y
+29 -6
View File
@@ -110,6 +110,29 @@ config AL_FIC
help
Support Amazon's Annapurna Labs Fabric Interrupt Controller.
config ASPEED_AST2700_INTC
bool "ASPEED AST2700 Interrupt Controller support"
depends on OF
depends on ARCH_ASPEED || COMPILE_TEST
select IRQ_DOMAIN_HIERARCHY
help
Enable support for the ASPEED AST2700 interrupt controller.
This driver handles interrupt, routing and merged interrupt
sources to upstream parent interrupt controllers.
If unsure, say N.
config ASPEED_AST2700_INTC_TEST
bool "Tests for the ASPEED AST2700 Interrupt Controller"
depends on ASPEED_AST2700_INTC && KUNIT=y
default KUNIT_ALL_TESTS
help
Enable KUnit tests for AST2700 INTC route resolution.
The tests exercise error handling and route selection paths.
This option is intended for test builds.
If unsure, say N.
config ATMEL_AIC_IRQ
bool
select GENERIC_IRQ_CHIP
@@ -476,7 +499,7 @@ config STM32_EXTI
select GENERIC_IRQ_CHIP
config QCOM_IRQ_COMBINER
bool "QCOM IRQ combiner support"
bool "Qualcomm IRQ combiner support"
depends on ARCH_QCOM && ACPI
select IRQ_DOMAIN_HIERARCHY
help
@@ -509,7 +532,7 @@ config GOLDFISH_PIC
for Goldfish based virtual platforms.
config QCOM_PDC
tristate "QCOM PDC"
tristate "Qualcomm PDC"
depends on ARCH_QCOM
select IRQ_DOMAIN_HIERARCHY
help
@@ -517,7 +540,7 @@ config QCOM_PDC
IRQs for Qualcomm Technologies Inc (QTI) mobile chips.
config QCOM_MPM
tristate "QCOM MPM"
tristate "Qualcomm MPM"
depends on ARCH_QCOM
depends on MAILBOX
select IRQ_DOMAIN_HIERARCHY
@@ -654,13 +677,13 @@ config SIFIVE_PLIC
select IRQ_DOMAIN_HIERARCHY
select GENERIC_IRQ_EFFECTIVE_AFF_MASK if SMP
config STARFIVE_JH8100_INTC
bool "StarFive JH8100 External Interrupt Controller"
config STARFIVE_JHB100_INTC
bool "StarFive JHB100 External Interrupt Controller"
depends on ARCH_STARFIVE || COMPILE_TEST
default ARCH_STARFIVE
select IRQ_DOMAIN_HIERARCHY
help
This enables support for the INTC chip found in StarFive JH8100
This enables support for the INTC chip found in StarFive JHB100
SoC.
If you don't know what to do here, say Y.
+4 -3
View File
@@ -89,8 +89,9 @@ obj-$(CONFIG_MVEBU_PIC) += irq-mvebu-pic.o
obj-$(CONFIG_MVEBU_SEI) += irq-mvebu-sei.o
obj-$(CONFIG_LS_EXTIRQ) += irq-ls-extirq.o
obj-$(CONFIG_LS_SCFG_MSI) += irq-ls-scfg-msi.o
obj-$(CONFIG_ASPEED_AST2700_INTC) += irq-ast2700.o irq-ast2700-intc0.o irq-ast2700-intc1.o
obj-$(CONFIG_ASPEED_AST2700_INTC_TEST) += irq-ast2700-intc0-test.o
obj-$(CONFIG_ARCH_ASPEED) += irq-aspeed-vic.o irq-aspeed-i2c-ic.o irq-aspeed-scu-ic.o
obj-$(CONFIG_ARCH_ASPEED) += irq-aspeed-intc.o
obj-$(CONFIG_STM32MP_EXTI) += irq-stm32mp-exti.o
obj-$(CONFIG_STM32_EXTI) += irq-stm32-exti.o
obj-$(CONFIG_QCOM_IRQ_COMBINER) += qcom-irq-combiner.o
@@ -108,7 +109,7 @@ obj-$(CONFIG_RISCV_APLIC_MSI) += irq-riscv-aplic-msi.o
obj-$(CONFIG_RISCV_IMSIC) += irq-riscv-imsic-state.o irq-riscv-imsic-early.o irq-riscv-imsic-platform.o
obj-$(CONFIG_RISCV_RPMI_SYSMSI) += irq-riscv-rpmi-sysmsi.o
obj-$(CONFIG_SIFIVE_PLIC) += irq-sifive-plic.o
obj-$(CONFIG_STARFIVE_JH8100_INTC) += irq-starfive-jh8100-intc.o
obj-$(CONFIG_STARFIVE_JHB100_INTC) += irq-starfive-jhb100-intc.o
obj-$(CONFIG_ACLINT_SSWI) += irq-aclint-sswi.o
obj-$(CONFIG_IMX_IRQSTEER) += irq-imx-irqsteer.o
obj-$(CONFIG_IMX_INTMUX) += irq-imx-intmux.o
@@ -119,7 +120,7 @@ obj-$(CONFIG_LS1X_IRQ) += irq-ls1x.o
obj-$(CONFIG_TI_SCI_INTR_IRQCHIP) += irq-ti-sci-intr.o
obj-$(CONFIG_TI_SCI_INTA_IRQCHIP) += irq-ti-sci-inta.o
obj-$(CONFIG_TI_PRUSS_INTC) += irq-pruss-intc.o
obj-$(CONFIG_IRQ_LOONGARCH_CPU) += irq-loongarch-cpu.o irq-loongarch-avec.o
obj-$(CONFIG_IRQ_LOONGARCH_CPU) += irq-loongarch-cpu.o irq-loongarch-avec.o irq-loongarch-ir.o
obj-$(CONFIG_LOONGSON_LIOINTC) += irq-loongson-liointc.o
obj-$(CONFIG_LOONGSON_EIOINTC) += irq-loongson-eiointc.o
obj-$(CONFIG_LOONGSON_HTPIC) += irq-loongson-htpic.o
-4
View File
@@ -24,8 +24,6 @@
#define IRQ_IN_COMBINER 8
static DEFINE_RAW_SPINLOCK(irq_controller_lock);
struct combiner_chip_data {
unsigned int hwirq_offset;
unsigned int irq_mask;
@@ -72,9 +70,7 @@ static void combiner_handle_cascade_irq(struct irq_desc *desc)
chained_irq_enter(chip, desc);
raw_spin_lock(&irq_controller_lock);
status = readl_relaxed(chip_data->base + COMBINER_INT_STATUS);
raw_spin_unlock(&irq_controller_lock);
status &= chip_data->irq_mask;
if (status == 0)
-139
View File
@@ -1,139 +0,0 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Aspeed Interrupt Controller.
*
* Copyright (C) 2023 ASPEED Technology Inc.
*/
#include <linux/bitops.h>
#include <linux/irq.h>
#include <linux/irqchip.h>
#include <linux/irqchip/chained_irq.h>
#include <linux/irqdomain.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
#include <linux/io.h>
#include <linux/spinlock.h>
#define INTC_INT_ENABLE_REG 0x00
#define INTC_INT_STATUS_REG 0x04
#define INTC_IRQS_PER_WORD 32
struct aspeed_intc_ic {
void __iomem *base;
raw_spinlock_t gic_lock;
raw_spinlock_t intc_lock;
struct irq_domain *irq_domain;
};
static void aspeed_intc_ic_irq_handler(struct irq_desc *desc)
{
struct aspeed_intc_ic *intc_ic = irq_desc_get_handler_data(desc);
struct irq_chip *chip = irq_desc_get_chip(desc);
chained_irq_enter(chip, desc);
scoped_guard(raw_spinlock, &intc_ic->gic_lock) {
unsigned long bit, status;
status = readl(intc_ic->base + INTC_INT_STATUS_REG);
for_each_set_bit(bit, &status, INTC_IRQS_PER_WORD) {
generic_handle_domain_irq(intc_ic->irq_domain, bit);
writel(BIT(bit), intc_ic->base + INTC_INT_STATUS_REG);
}
}
chained_irq_exit(chip, desc);
}
static void aspeed_intc_irq_mask(struct irq_data *data)
{
struct aspeed_intc_ic *intc_ic = irq_data_get_irq_chip_data(data);
unsigned int mask = readl(intc_ic->base + INTC_INT_ENABLE_REG) & ~BIT(data->hwirq);
guard(raw_spinlock)(&intc_ic->intc_lock);
writel(mask, intc_ic->base + INTC_INT_ENABLE_REG);
}
static void aspeed_intc_irq_unmask(struct irq_data *data)
{
struct aspeed_intc_ic *intc_ic = irq_data_get_irq_chip_data(data);
unsigned int unmask = readl(intc_ic->base + INTC_INT_ENABLE_REG) | BIT(data->hwirq);
guard(raw_spinlock)(&intc_ic->intc_lock);
writel(unmask, intc_ic->base + INTC_INT_ENABLE_REG);
}
static struct irq_chip aspeed_intc_chip = {
.name = "ASPEED INTC",
.irq_mask = aspeed_intc_irq_mask,
.irq_unmask = aspeed_intc_irq_unmask,
};
static int aspeed_intc_ic_map_irq_domain(struct irq_domain *domain, unsigned int irq,
irq_hw_number_t hwirq)
{
irq_set_chip_and_handler(irq, &aspeed_intc_chip, handle_level_irq);
irq_set_chip_data(irq, domain->host_data);
return 0;
}
static const struct irq_domain_ops aspeed_intc_ic_irq_domain_ops = {
.map = aspeed_intc_ic_map_irq_domain,
};
static int __init aspeed_intc_ic_of_init(struct device_node *node,
struct device_node *parent)
{
struct aspeed_intc_ic *intc_ic;
int irq, i, ret = 0;
intc_ic = kzalloc_obj(*intc_ic);
if (!intc_ic)
return -ENOMEM;
intc_ic->base = of_iomap(node, 0);
if (!intc_ic->base) {
pr_err("Failed to iomap intc_ic base\n");
ret = -ENOMEM;
goto err_free_ic;
}
writel(0xffffffff, intc_ic->base + INTC_INT_STATUS_REG);
writel(0x0, intc_ic->base + INTC_INT_ENABLE_REG);
intc_ic->irq_domain = irq_domain_create_linear(of_fwnode_handle(node), INTC_IRQS_PER_WORD,
&aspeed_intc_ic_irq_domain_ops, intc_ic);
if (!intc_ic->irq_domain) {
ret = -ENOMEM;
goto err_iounmap;
}
raw_spin_lock_init(&intc_ic->gic_lock);
raw_spin_lock_init(&intc_ic->intc_lock);
/* Check all the irq numbers valid. If not, unmaps all the base and frees the data. */
for (i = 0; i < of_irq_count(node); i++) {
irq = irq_of_parse_and_map(node, i);
if (!irq) {
pr_err("Failed to get irq number\n");
ret = -EINVAL;
goto err_iounmap;
}
}
for (i = 0; i < of_irq_count(node); i++) {
irq = irq_of_parse_and_map(node, i);
irq_set_chained_handler_and_data(irq, aspeed_intc_ic_irq_handler, intc_ic);
}
return 0;
err_iounmap:
iounmap(intc_ic->base);
err_free_ic:
kfree(intc_ic);
return ret;
}
IRQCHIP_DECLARE(ast2700_intc_ic, "aspeed,ast2700-intc-ic", aspeed_intc_ic_of_init);
+473
View File
@@ -0,0 +1,473 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2026 Code Construct
*/
#include <kunit/test.h>
#include "irq-ast2700.h"
static void aspeed_intc0_resolve_route_bad_args(struct kunit *test)
{
static const struct aspeed_intc_interrupt_range c1ranges[] = { 0 };
static const u32 c1outs[] = { 0 };
struct aspeed_intc_interrupt_range resolved;
const struct irq_domain c0domain = { 0 };
int rc;
rc = aspeed_intc0_resolve_route(NULL, 0, c1outs, 0, c1ranges, NULL);
KUNIT_EXPECT_EQ(test, rc, -EINVAL);
rc = aspeed_intc0_resolve_route(&c0domain, 0, c1outs,
ARRAY_SIZE(c1ranges), c1ranges,
&resolved);
KUNIT_EXPECT_EQ(test, rc, -ENOENT);
rc = aspeed_intc0_resolve_route(&c0domain, ARRAY_SIZE(c1outs), c1outs,
0, c1ranges, &resolved);
KUNIT_EXPECT_EQ(test, rc, -ENOENT);
}
static int gicv3_fwnode_read_string_array(const struct fwnode_handle *fwnode,
const char *propname, const char **val, size_t nval)
{
if (!propname)
return -EINVAL;
if (!val)
return 1;
if (WARN_ON(nval != 1))
return -EOVERFLOW;
*val = "arm,gic-v3";
return 1;
}
static const struct fwnode_operations arm_gicv3_fwnode_ops = {
.property_read_string_array = gicv3_fwnode_read_string_array,
};
static void aspeed_intc_resolve_route_invalid_c0domain(struct kunit *test)
{
struct device_node intc0_node = {
.fwnode = { .ops = &arm_gicv3_fwnode_ops },
};
const struct irq_domain c0domain = { .fwnode = &intc0_node.fwnode };
static const struct aspeed_intc_interrupt_range c1ranges[] = { 0 };
static const u32 c1outs[] = { 0 };
struct aspeed_intc_interrupt_range resolved;
int rc;
rc = aspeed_intc0_resolve_route(&c0domain, ARRAY_SIZE(c1outs), c1outs,
ARRAY_SIZE(c1ranges), c1ranges,
&resolved);
KUNIT_EXPECT_NE(test, rc, 0);
}
static int
aspeed_intc0_fwnode_read_string_array(const struct fwnode_handle *fwnode_handle,
const char *propname, const char **val,
size_t nval)
{
if (!propname)
return -EINVAL;
if (!val)
return 1;
if (WARN_ON(nval != 1))
return -EOVERFLOW;
*val = "aspeed,ast2700-intc0";
return nval;
}
static const struct fwnode_operations intc0_fwnode_ops = {
.property_read_string_array = aspeed_intc0_fwnode_read_string_array,
};
static void
aspeed_intc0_resolve_route_c1i1o1c0i1o1_connected(struct kunit *test)
{
struct device_node intc0_node = {
.fwnode = { .ops = &intc0_fwnode_ops },
};
struct aspeed_intc_interrupt_range c1ranges[] = {
{
.start = 0,
.count = 1,
.upstream = {
.fwnode = &intc0_node.fwnode,
.param_count = 1,
.param = { 128 }
}
}
};
static const u32 c1outs[] = { 0 };
struct aspeed_intc_interrupt_range resolved;
struct aspeed_intc_interrupt_range intc0_ranges[] = {
{
.start = 128,
.count = 1,
.upstream = {
.fwnode = NULL,
.param_count = 0,
.param = { 0 },
}
}
};
struct aspeed_intc0 intc0 = {
.ranges = { .ranges = intc0_ranges, .nranges = ARRAY_SIZE(intc0_ranges), }
};
const struct irq_domain c0domain = {
.host_data = &intc0,
.fwnode = &intc0_node.fwnode
};
int rc;
rc = aspeed_intc0_resolve_route(&c0domain, ARRAY_SIZE(c1outs), c1outs,
ARRAY_SIZE(c1ranges), c1ranges,
&resolved);
KUNIT_EXPECT_EQ(test, rc, 0);
KUNIT_EXPECT_EQ(test, resolved.start, 0);
KUNIT_EXPECT_EQ(test, resolved.count, 1);
KUNIT_EXPECT_EQ(test, resolved.upstream.param[0], 128);
}
static void
aspeed_intc0_resolve_route_c1i1o1c0i1o1_disconnected(struct kunit *test)
{
struct device_node intc0_node = {
.fwnode = { .ops = &intc0_fwnode_ops },
};
struct aspeed_intc_interrupt_range c1ranges[] = {
{
.start = 0,
.count = 1,
.upstream = {
.fwnode = &intc0_node.fwnode,
.param_count = 1,
.param = { 128 }
}
}
};
static const u32 c1outs[] = { 0 };
struct aspeed_intc_interrupt_range resolved;
struct aspeed_intc_interrupt_range intc0_ranges[] = {
{
.start = 129,
.count = 1,
.upstream = {
.fwnode = NULL,
.param_count = 0,
.param = { 0 },
}
}
};
struct aspeed_intc0 intc0 = {
.ranges = {
.ranges = intc0_ranges,
.nranges = ARRAY_SIZE(intc0_ranges),
}
};
const struct irq_domain c0domain = {
.host_data = &intc0,
.fwnode = &intc0_node.fwnode
};
int rc;
rc = aspeed_intc0_resolve_route(&c0domain, ARRAY_SIZE(c1outs), c1outs,
ARRAY_SIZE(c1ranges), c1ranges,
&resolved);
KUNIT_EXPECT_NE(test, rc, 0);
}
static void aspeed_intc0_resolve_route_c1i1o1mc0i1o1(struct kunit *test)
{
struct device_node intc0_node = {
.fwnode = { .ops = &intc0_fwnode_ops },
};
struct aspeed_intc_interrupt_range c1ranges[] = {
{
.start = 0,
.count = 1,
.upstream = {
.fwnode = &intc0_node.fwnode,
.param_count = 1,
.param = { 480 }
}
}
};
static const u32 c1outs[] = { 0 };
struct aspeed_intc_interrupt_range resolved;
struct aspeed_intc_interrupt_range intc0_ranges[] = {
{
.start = 192,
.count = 1,
.upstream = {
.fwnode = NULL,
.param_count = 0,
.param = { 0 },
}
}
};
struct aspeed_intc0 intc0 = {
.ranges = {
.ranges = intc0_ranges,
.nranges = ARRAY_SIZE(intc0_ranges),
}
};
const struct irq_domain c0domain = {
.host_data = &intc0,
.fwnode = &intc0_node.fwnode
};
int rc;
rc = aspeed_intc0_resolve_route(&c0domain, ARRAY_SIZE(c1outs), c1outs,
ARRAY_SIZE(c1ranges), c1ranges,
&resolved);
KUNIT_EXPECT_EQ(test, rc, 0);
KUNIT_EXPECT_EQ(test, resolved.start, 0);
KUNIT_EXPECT_EQ(test, resolved.count, 1);
KUNIT_EXPECT_EQ(test, resolved.upstream.param[0], 480);
}
static void aspeed_intc0_resolve_route_c1i2o2mc0i1o1(struct kunit *test)
{
struct device_node intc0_node = {
.fwnode = { .ops = &intc0_fwnode_ops },
};
struct aspeed_intc_interrupt_range c1ranges[] = {
{
.start = 0,
.count = 1,
.upstream = {
.fwnode = &intc0_node.fwnode,
.param_count = 1,
.param = { 480 }
}
},
{
.start = 1,
.count = 1,
.upstream = {
.fwnode = &intc0_node.fwnode,
.param_count = 1,
.param = { 510 }
}
}
};
static const u32 c1outs[] = { 1 };
struct aspeed_intc_interrupt_range resolved;
static struct aspeed_intc_interrupt_range intc0_ranges[] = {
{
.start = 208,
.count = 1,
.upstream = {
.fwnode = NULL,
.param_count = 0,
.param = { 0 },
}
}
};
struct aspeed_intc0 intc0 = {
.ranges = {
.ranges = intc0_ranges,
.nranges = ARRAY_SIZE(intc0_ranges),
}
};
const struct irq_domain c0domain = {
.host_data = &intc0,
.fwnode = &intc0_node.fwnode
};
int rc;
rc = aspeed_intc0_resolve_route(&c0domain, ARRAY_SIZE(c1outs), c1outs,
ARRAY_SIZE(c1ranges), c1ranges,
&resolved);
KUNIT_EXPECT_EQ(test, rc, 0);
KUNIT_EXPECT_EQ(test, resolved.start, 1);
KUNIT_EXPECT_EQ(test, resolved.count, 1);
KUNIT_EXPECT_EQ(test, resolved.upstream.param[0], 510);
}
static void aspeed_intc0_resolve_route_c1i1o1mc0i2o1(struct kunit *test)
{
struct device_node intc0_node = {
.fwnode = { .ops = &intc0_fwnode_ops },
};
struct aspeed_intc_interrupt_range c1ranges[] = {
{
.start = 0,
.count = 1,
.upstream = {
.fwnode = &intc0_node.fwnode,
.param_count = 1,
.param = { 510 }
}
},
};
static const u32 c1outs[] = { 0 };
struct aspeed_intc_interrupt_range resolved;
static struct aspeed_intc_interrupt_range intc0_ranges[] = {
{
.start = 192,
.count = 1,
.upstream = {
.fwnode = NULL,
.param_count = 0,
.param = {0},
}
},
{
.start = 208,
.count = 1,
.upstream = {
.fwnode = NULL,
.param_count = 0,
.param = {0},
}
}
};
struct aspeed_intc0 intc0 = {
.ranges = {
.ranges = intc0_ranges,
.nranges = ARRAY_SIZE(intc0_ranges),
}
};
const struct irq_domain c0domain = {
.host_data = &intc0,
.fwnode = &intc0_node.fwnode
};
int rc;
rc = aspeed_intc0_resolve_route(&c0domain, ARRAY_SIZE(c1outs), c1outs,
ARRAY_SIZE(c1ranges), c1ranges,
&resolved);
KUNIT_EXPECT_EQ(test, rc, 0);
KUNIT_EXPECT_EQ(test, resolved.start, 0);
KUNIT_EXPECT_EQ(test, resolved.count, 1);
KUNIT_EXPECT_EQ(test, resolved.upstream.param[0], 510);
}
static void aspeed_intc0_resolve_route_c1i1o2mc0i1o1_invalid(struct kunit *test)
{
struct device_node intc0_node = {
.fwnode = { .ops = &intc0_fwnode_ops },
};
struct aspeed_intc_interrupt_range c1ranges[] = {
{
.start = 0,
.count = 1,
.upstream = {
.fwnode = &intc0_node.fwnode,
.param_count = 1,
.param = { 480 }
}
}
};
static const u32 c1outs[] = {
AST2700_INTC_INVALID_ROUTE, 0
};
struct aspeed_intc_interrupt_range resolved;
struct aspeed_intc_interrupt_range intc0_ranges[] = {
{
.start = 192,
.count = 1,
.upstream = {
.fwnode = NULL,
.param_count = 0,
.param = { 0 },
}
}
};
struct aspeed_intc0 intc0 = {
.ranges = {
.ranges = intc0_ranges,
.nranges = ARRAY_SIZE(intc0_ranges),
}
};
const struct irq_domain c0domain = {
.host_data = &intc0,
.fwnode = &intc0_node.fwnode
};
int rc;
rc = aspeed_intc0_resolve_route(&c0domain, ARRAY_SIZE(c1outs), c1outs,
ARRAY_SIZE(c1ranges), c1ranges,
&resolved);
KUNIT_EXPECT_EQ(test, rc, 1);
KUNIT_EXPECT_EQ(test, resolved.start, 0);
KUNIT_EXPECT_EQ(test, resolved.count, 1);
KUNIT_EXPECT_EQ(test, resolved.upstream.param[0], 480);
}
static void
aspeed_intc0_resolve_route_c1i1o1mc0i1o1_bad_range_upstream(struct kunit *test)
{
struct device_node intc0_node = {
.fwnode = { .ops = &intc0_fwnode_ops },
};
struct aspeed_intc_interrupt_range c1ranges[] = {
{
.start = 0,
.count = 1,
.upstream = {
.fwnode = &intc0_node.fwnode,
.param_count = 0,
.param = { 0 }
}
}
};
static const u32 c1outs[] = { 0 };
struct aspeed_intc_interrupt_range resolved;
struct aspeed_intc_interrupt_range intc0_ranges[] = {
{
.start = 0,
.count = 0,
.upstream = {
.fwnode = NULL,
.param_count = 0,
.param = { 0 },
}
}
};
struct aspeed_intc0 intc0 = {
.ranges = {
.ranges = intc0_ranges,
.nranges = ARRAY_SIZE(intc0_ranges),
}
};
const struct irq_domain c0domain = {
.host_data = &intc0,
.fwnode = &intc0_node.fwnode
};
int rc;
rc = aspeed_intc0_resolve_route(&c0domain, ARRAY_SIZE(c1outs), c1outs,
ARRAY_SIZE(c1ranges), c1ranges,
&resolved);
KUNIT_EXPECT_NE(test, rc, 0);
}
static struct kunit_case ast2700_intc0_test_cases[] = {
KUNIT_CASE(aspeed_intc0_resolve_route_bad_args),
KUNIT_CASE(aspeed_intc_resolve_route_invalid_c0domain),
KUNIT_CASE(aspeed_intc0_resolve_route_c1i1o1c0i1o1_connected),
KUNIT_CASE(aspeed_intc0_resolve_route_c1i1o1c0i1o1_disconnected),
KUNIT_CASE(aspeed_intc0_resolve_route_c1i1o1mc0i1o1),
KUNIT_CASE(aspeed_intc0_resolve_route_c1i2o2mc0i1o1),
KUNIT_CASE(aspeed_intc0_resolve_route_c1i1o1mc0i2o1),
KUNIT_CASE(aspeed_intc0_resolve_route_c1i1o2mc0i1o1_invalid),
KUNIT_CASE(aspeed_intc0_resolve_route_c1i1o1mc0i1o1_bad_range_upstream),
{},
};
static struct kunit_suite ast2700_intc0_test_suite = {
.name = "ast2700-intc0",
.test_cases = ast2700_intc0_test_cases,
};
kunit_test_suite(ast2700_intc0_test_suite);
MODULE_LICENSE("GPL");
+582
View File
@@ -0,0 +1,582 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Aspeed AST2700 Interrupt Controller.
*
* Copyright (C) 2026 ASPEED Technology Inc.
*/
#include <linux/bitops.h>
#include <linux/device.h>
#include <linux/err.h>
#include <linux/fwnode.h>
#include <linux/io.h>
#include <linux/irq.h>
#include <linux/irqchip.h>
#include <linux/irqchip/chained_irq.h>
#include <linux/irqdomain.h>
#include <linux/kconfig.h>
#include <linux/of.h>
#include <linux/of_irq.h>
#include <linux/overflow.h>
#include <linux/property.h>
#include <linux/spinlock.h>
#include "irq-ast2700.h"
#define INT_NUM 480
#define INTM_NUM 50
#define SWINT_NUM 16
#define INTM_BASE (INT_NUM)
#define SWINT_BASE (INT_NUM + INTM_NUM)
#define INT0_NUM (INT_NUM + INTM_NUM + SWINT_NUM)
#define INTC0_IN_NUM 480
#define INTC0_ROUTE_NUM 5
#define INTC0_INTM_NUM 50
#define INTC0_ROUTE_BITS 3
#define GIC_P2P_SPI_END 128
#define INTC0_SWINT_OUT_BASE 144
#define INTC0_SWINT_IER 0x10
#define INTC0_SWINT_ISR 0x14
#define INTC0_INTBANKX_IER 0x1000
#define INTC0_INTBANK_SIZE 0x100
#define INTC0_INTBANK_GROUPS 11
#define INTC0_INTBANKS_PER_GRP 3
#define INTC0_INTMX_IER 0x1b00
#define INTC0_INTMX_ISR 0x1b04
#define INTC0_INTMX_BANK_SIZE 0x10
#define INTC0_INTM_BANK_NUM 3
#define INTC0_IRQS_PER_BANK 32
#define INTM_IRQS_PER_BANK 10
#define INTC0_SEL_BASE 0x200
#define INTC0_SEL_BANK_SIZE 0x4
#define INTC0_SEL_ROUTE_SIZE 0x100
static void aspeed_swint_irq_mask(struct irq_data *data)
{
struct aspeed_intc0 *intc0 = irq_data_get_irq_chip_data(data);
int bit = data->hwirq - SWINT_BASE;
u32 ier;
guard(raw_spinlock)(&intc0->intc_lock);
ier = readl(intc0->base + INTC0_SWINT_IER) & ~BIT(bit);
writel(ier, intc0->base + INTC0_SWINT_IER);
irq_chip_mask_parent(data);
}
static void aspeed_swint_irq_unmask(struct irq_data *data)
{
struct aspeed_intc0 *intc0 = irq_data_get_irq_chip_data(data);
int bit = data->hwirq - SWINT_BASE;
u32 ier;
guard(raw_spinlock)(&intc0->intc_lock);
ier = readl(intc0->base + INTC0_SWINT_IER) | BIT(bit);
writel(ier, intc0->base + INTC0_SWINT_IER);
irq_chip_unmask_parent(data);
}
static void aspeed_swint_irq_eoi(struct irq_data *data)
{
struct aspeed_intc0 *intc0 = irq_data_get_irq_chip_data(data);
int bit = data->hwirq - SWINT_BASE;
writel(BIT(bit), intc0->base + INTC0_SWINT_ISR);
irq_chip_eoi_parent(data);
}
static struct irq_chip aspeed_swint_chip = {
.name = "ast2700-swint",
.irq_eoi = aspeed_swint_irq_eoi,
.irq_mask = aspeed_swint_irq_mask,
.irq_unmask = aspeed_swint_irq_unmask,
.irq_set_affinity = irq_chip_set_affinity_parent,
.flags = IRQCHIP_SET_TYPE_MASKED,
};
static void aspeed_intc0_irq_mask(struct irq_data *data)
{
struct aspeed_intc0 *intc0 = irq_data_get_irq_chip_data(data);
int bank = (data->hwirq - INTM_BASE) / INTM_IRQS_PER_BANK;
int bit = (data->hwirq - INTM_BASE) % INTM_IRQS_PER_BANK;
u32 ier;
guard(raw_spinlock)(&intc0->intc_lock);
ier = readl(intc0->base + INTC0_INTMX_IER + bank * INTC0_INTMX_BANK_SIZE) & ~BIT(bit);
writel(ier, intc0->base + INTC0_INTMX_IER + bank * INTC0_INTMX_BANK_SIZE);
irq_chip_mask_parent(data);
}
static void aspeed_intc0_irq_unmask(struct irq_data *data)
{
struct aspeed_intc0 *intc0 = irq_data_get_irq_chip_data(data);
int bank = (data->hwirq - INTM_BASE) / INTM_IRQS_PER_BANK;
int bit = (data->hwirq - INTM_BASE) % INTM_IRQS_PER_BANK;
u32 ier;
guard(raw_spinlock)(&intc0->intc_lock);
ier = readl(intc0->base + INTC0_INTMX_IER + bank * INTC0_INTMX_BANK_SIZE) | BIT(bit);
writel(ier, intc0->base + INTC0_INTMX_IER + bank * INTC0_INTMX_BANK_SIZE);
irq_chip_unmask_parent(data);
}
static void aspeed_intc0_irq_eoi(struct irq_data *data)
{
struct aspeed_intc0 *intc0 = irq_data_get_irq_chip_data(data);
int bank = (data->hwirq - INTM_BASE) / INTM_IRQS_PER_BANK;
int bit = (data->hwirq - INTM_BASE) % INTM_IRQS_PER_BANK;
writel(BIT(bit), intc0->base + INTC0_INTMX_ISR + bank * INTC0_INTMX_BANK_SIZE);
irq_chip_eoi_parent(data);
}
static struct irq_chip aspeed_intm_chip = {
.name = "ast2700-intmerge",
.irq_eoi = aspeed_intc0_irq_eoi,
.irq_mask = aspeed_intc0_irq_mask,
.irq_unmask = aspeed_intc0_irq_unmask,
.irq_set_affinity = irq_chip_set_affinity_parent,
.flags = IRQCHIP_SET_TYPE_MASKED,
};
static struct irq_chip linear_intr_irq_chip = {
.name = "ast2700-int",
.irq_eoi = irq_chip_eoi_parent,
.irq_mask = irq_chip_mask_parent,
.irq_unmask = irq_chip_unmask_parent,
.irq_set_affinity = irq_chip_set_affinity_parent,
.flags = IRQCHIP_SET_TYPE_MASKED,
};
static const u32 aspeed_intc0_routes[INTC0_IN_NUM / INTC0_IRQS_PER_BANK][INTC0_ROUTE_NUM] = {
{ 0, 256, 426, AST2700_INTC_INVALID_ROUTE, AST2700_INTC_INVALID_ROUTE },
{ 32, 288, 458, AST2700_INTC_INVALID_ROUTE, AST2700_INTC_INVALID_ROUTE },
{ 64, 320, 490, AST2700_INTC_INVALID_ROUTE, AST2700_INTC_INVALID_ROUTE },
{ 96, 352, 522, AST2700_INTC_INVALID_ROUTE, AST2700_INTC_INVALID_ROUTE },
{ 128, 384, 554, 160, 176 },
{ 129, 385, 555, 161, 177 },
{ 130, 386, 556, 162, 178 },
{ 131, 387, 557, 163, 179 },
{ 132, 388, 558, 164, 180 },
{ 133, 544, 714, 165, 181 },
{ 134, 545, 715, 166, 182 },
{ 135, 546, 706, 167, 183 },
{ 136, 547, 707, 168, 184 },
{ 137, 548, 708, 169, 185 },
{ 138, 549, 709, 170, 186 },
};
static const u32 aspeed_intc0_intm_routes[INTC0_INTM_NUM / INTM_IRQS_PER_BANK] = {
192, 416, 586, 208, 224
};
static int resolve_input_from_child_ranges(const struct aspeed_intc0 *intc0,
const struct aspeed_intc_interrupt_range *range,
u32 outpin, u32 *input)
{
u32 offset, base;
if (!in_range32(outpin, range->start, range->count))
return -ENOENT;
if (range->upstream.param_count == 0)
return -EINVAL;
base = range->upstream.param[ASPEED_INTC_RANGES_BASE];
offset = outpin - range->start;
if (check_add_overflow(base, offset, input)) {
dev_warn(intc0->dev, "%s: Arithmetic overflow for input derivation: %u + %u\n",
__func__, base, offset);
return -EINVAL;
}
return 0;
}
static int resolve_parent_range_for_output(const struct aspeed_intc0 *intc0,
const struct fwnode_handle *parent, u32 output,
struct aspeed_intc_interrupt_range *resolved)
{
for (size_t i = 0; i < intc0->ranges.nranges; i++) {
struct aspeed_intc_interrupt_range range = intc0->ranges.ranges[i];
if (!in_range32(output, range.start, range.count))
continue;
if (range.upstream.fwnode != parent)
continue;
if (resolved) {
resolved->start = output;
resolved->count = 1;
resolved->upstream = range.upstream;
resolved->upstream.param[ASPEED_INTC_RANGES_COUNT] +=
output - range.start;
}
return 0;
}
return -ENOENT;
}
static int resolve_parent_route_for_input(const struct aspeed_intc0 *intc0,
const struct fwnode_handle *parent, u32 input,
struct aspeed_intc_interrupt_range *resolved)
{
int rc = -ENOENT;
u32 c0o;
if (input < INT_NUM) {
static_assert(INTC0_ROUTE_NUM < INT_MAX, "Broken cast");
for (size_t i = 0; rc == -ENOENT && i < INTC0_ROUTE_NUM; i++) {
c0o = aspeed_intc0_routes[input / INTC0_IRQS_PER_BANK][i];
if (c0o == AST2700_INTC_INVALID_ROUTE)
continue;
if (input < GIC_P2P_SPI_END)
c0o += input % INTC0_IRQS_PER_BANK;
rc = resolve_parent_range_for_output(intc0, parent, c0o, resolved);
if (!rc)
return (int)i;
}
} else if (input < (INT_NUM + INTM_NUM)) {
c0o = aspeed_intc0_intm_routes[(input - INT_NUM) / INTM_IRQS_PER_BANK];
c0o += ((input - INT_NUM) % INTM_IRQS_PER_BANK);
return resolve_parent_range_for_output(intc0, parent, c0o, resolved);
} else if (input < (INT_NUM + INTM_NUM + SWINT_NUM)) {
c0o = input - SWINT_BASE + INTC0_SWINT_OUT_BASE;
return resolve_parent_range_for_output(intc0, parent, c0o, resolved);
} else {
return -ENOENT;
}
return rc;
}
/**
* aspeed_intc0_resolve_route - Determine the necessary interrupt output at intc1
* @c0domain: The pointer to intc0's irq_domain
* @nc1outs: The number of valid intc1 outputs available for the input
* @c1outs: The array of available intc1 output indices for the input
* @nc1ranges: The number of interrupt range entries for intc1
* @c1ranges: The array of configured intc1 interrupt ranges
* @resolved: The fully resolved range entry after applying the resolution
* algorithm
*
* Returns: The intc1 route index associated with the intc1 output identified in
* @resolved on success. Otherwise, a negative errno value.
*
* The AST2700 interrupt architecture allows any peripheral interrupt source
* to be routed to one of up to four processors running in the SoC. A processor
* binding a driver for a peripheral that requests an interrupt is (without
* further design and effort) the destination for the requested interrupt.
*
* Routing a peripheral interrupt to its destination processor requires
* coordination between INTC0 on the CPU die and one or more INTC1 instances.
* At least one INTC1 instance exists in the SoC on the IO-die, however up
* to two more instances may be integrated via LTPI (LVDS Tunneling Protocol
* & Interface).
*
* Between the multiple destinations, various route constraints, and the
* devicetree binding design, some information that's needed at INTC1 instances
* to route inbound interrupts correctly to the destination processor is only
* available at INTC0.
*
* aspeed_intc0_resolve_route() is to be invoked by INTC1 driver instances to
* perform the route resolution. The implementation in INTC0 allows INTC0 to
* encapsulate the information used to perform route selection, and provides it
* with an opportunity to apply policy as part of the selection process. Such
* policy may, for instance, choose to de-prioritise some interrupts destined
* for the PSP (Primary Service Processor) GIC.
*/
int aspeed_intc0_resolve_route(const struct irq_domain *c0domain, size_t nc1outs,
const u32 *c1outs, size_t nc1ranges,
const struct aspeed_intc_interrupt_range *c1ranges,
struct aspeed_intc_interrupt_range *resolved)
{
struct fwnode_handle *parent_fwnode;
struct aspeed_intc0 *intc0;
int ret;
if (!c0domain || !resolved)
return -EINVAL;
if (nc1outs > INT_MAX)
return -EINVAL;
if (nc1outs == 0 || nc1ranges == 0)
return -ENOENT;
if (!IS_ENABLED(CONFIG_ASPEED_AST2700_INTC_TEST) &&
!fwnode_device_is_compatible(c0domain->fwnode, "aspeed,ast2700-intc0"))
return -ENODEV;
intc0 = c0domain->host_data;
if (!intc0)
return -EINVAL;
parent_fwnode = of_fwnode_handle(intc0->parent);
for (size_t i = 0; i < nc1outs; i++) {
u32 c1o = c1outs[i];
if (c1o == AST2700_INTC_INVALID_ROUTE)
continue;
for (size_t j = 0; j < nc1ranges; j++) {
struct aspeed_intc_interrupt_range c1r = c1ranges[j];
u32 input;
/*
* Range match for intc1 output pin
*
* Assume a failed match is still a match for the purpose of testing,
* saves a bunch of mess in the test fixtures
*/
if (!(c0domain == c1r.domain ||
IS_ENABLED(CONFIG_ASPEED_AST2700_INTC_TEST)))
continue;
ret = resolve_input_from_child_ranges(intc0, &c1r, c1o, &input);
if (ret)
continue;
/*
* INTC1 should never request routes for peripheral interrupt sources
* directly attached to INTC0.
*/
if (input < GIC_P2P_SPI_END)
continue;
ret = resolve_parent_route_for_input(intc0, parent_fwnode, input, NULL);
if (ret < 0)
continue;
/* Route resolution succeeded */
resolved->start = c1o;
resolved->count = 1;
resolved->upstream = c1r.upstream;
resolved->upstream.param[ASPEED_INTC_RANGES_BASE] = input;
/* Cast protected by prior test against nc1outs */
return (int)i;
}
}
return -ENOENT;
}
static int aspeed_intc0_irq_domain_map(struct irq_domain *domain,
unsigned int irq, irq_hw_number_t hwirq)
{
if (hwirq < GIC_P2P_SPI_END)
irq_set_chip_and_handler(irq, &linear_intr_irq_chip, handle_level_irq);
else if (hwirq < INTM_BASE)
return -EINVAL;
else if (hwirq < SWINT_BASE)
irq_set_chip_and_handler(irq, &aspeed_intm_chip, handle_level_irq);
else if (hwirq < INT0_NUM)
irq_set_chip_and_handler(irq, &aspeed_swint_chip, handle_level_irq);
else
return -EINVAL;
irq_set_chip_data(irq, domain->host_data);
return 0;
}
static int aspeed_intc0_irq_domain_translate(struct irq_domain *domain,
struct irq_fwspec *fwspec,
unsigned long *hwirq,
unsigned int *type)
{
if (fwspec->param_count != 1)
return -EINVAL;
*hwirq = fwspec->param[0];
*type = IRQ_TYPE_NONE;
return 0;
}
static int aspeed_intc0_irq_domain_alloc(struct irq_domain *domain,
unsigned int virq,
unsigned int nr_irqs, void *data)
{
struct aspeed_intc0 *intc0 = domain->host_data;
struct aspeed_intc_interrupt_range resolved;
struct irq_fwspec *fwspec = data;
struct irq_fwspec parent_fwspec;
struct irq_chip *chip;
unsigned long hwirq;
unsigned int type;
int ret;
ret = aspeed_intc0_irq_domain_translate(domain, fwspec, &hwirq, &type);
if (ret)
return ret;
if (hwirq >= GIC_P2P_SPI_END && hwirq < INT_NUM)
return -EINVAL;
if (hwirq < INTM_BASE)
chip = &linear_intr_irq_chip;
else if (hwirq < SWINT_BASE)
chip = &aspeed_intm_chip;
else
chip = &aspeed_swint_chip;
ret = resolve_parent_route_for_input(intc0, domain->parent->fwnode,
(u32)hwirq, &resolved);
if (ret)
return ret;
parent_fwspec = resolved.upstream;
ret = irq_domain_alloc_irqs_parent(domain, virq, nr_irqs,
&parent_fwspec);
if (ret)
return ret;
for (int i = 0; i < nr_irqs; ++i, ++hwirq, ++virq) {
ret = irq_domain_set_hwirq_and_chip(domain, virq, hwirq, chip,
domain->host_data);
if (ret)
return ret;
}
return 0;
}
static int aspeed_intc0_irq_domain_activate(struct irq_domain *domain,
struct irq_data *data, bool reserve)
{
struct aspeed_intc0 *intc0 = irq_data_get_irq_chip_data(data);
unsigned long hwirq = data->hwirq;
int route, bank, bit;
u32 mask;
if (hwirq >= INT0_NUM)
return -EINVAL;
if (in_range32(hwirq, INTM_BASE, INTM_NUM + SWINT_NUM))
return 0;
bank = hwirq / INTC0_IRQS_PER_BANK;
bit = hwirq % INTC0_IRQS_PER_BANK;
mask = BIT(bit);
route = resolve_parent_route_for_input(intc0, intc0->local->parent->fwnode,
hwirq, NULL);
if (route < 0)
return route;
guard(raw_spinlock)(&intc0->intc_lock);
for (int i = 0; i < INTC0_ROUTE_BITS; i++) {
void __iomem *sel = intc0->base + INTC0_SEL_BASE +
(bank * INTC0_SEL_BANK_SIZE) +
(INTC0_SEL_ROUTE_SIZE * i);
u32 reg = readl(sel);
if (route & BIT(i))
reg |= mask;
else
reg &= ~mask;
writel(reg, sel);
if (readl(sel) != reg)
return -EACCES;
}
return 0;
}
static const struct irq_domain_ops aspeed_intc0_irq_domain_ops = {
.translate = aspeed_intc0_irq_domain_translate,
.activate = aspeed_intc0_irq_domain_activate,
.alloc = aspeed_intc0_irq_domain_alloc,
.free = irq_domain_free_irqs_common,
.map = aspeed_intc0_irq_domain_map,
};
static void aspeed_intc0_disable_swint(struct aspeed_intc0 *intc0)
{
writel(0, intc0->base + INTC0_SWINT_IER);
}
static void aspeed_intc0_disable_intbank(struct aspeed_intc0 *intc0)
{
for (int i = 0; i < INTC0_INTBANK_GROUPS; i++) {
for (int j = 0; j < INTC0_INTBANKS_PER_GRP; j++) {
u32 base = INTC0_INTBANKX_IER +
(INTC0_INTBANK_SIZE * i) +
(INTC0_INTMX_BANK_SIZE * j);
writel(0, intc0->base + base);
}
}
}
static void aspeed_intc0_disable_intm(struct aspeed_intc0 *intc0)
{
for (int i = 0; i < INTC0_INTM_BANK_NUM; i++)
writel(0, intc0->base + INTC0_INTMX_IER + (INTC0_INTMX_BANK_SIZE * i));
}
static int aspeed_intc0_probe(struct platform_device *pdev,
struct device_node *parent)
{
struct device_node *node = pdev->dev.of_node;
struct irq_domain *parent_domain;
struct aspeed_intc0 *intc0;
int ret;
if (!parent) {
pr_err("missing parent interrupt node\n");
return -ENODEV;
}
intc0 = devm_kzalloc(&pdev->dev, sizeof(*intc0), GFP_KERNEL);
if (!intc0)
return -ENOMEM;
intc0->dev = &pdev->dev;
intc0->parent = parent;
intc0->base = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(intc0->base))
return PTR_ERR(intc0->base);
aspeed_intc0_disable_swint(intc0);
aspeed_intc0_disable_intbank(intc0);
aspeed_intc0_disable_intm(intc0);
raw_spin_lock_init(&intc0->intc_lock);
parent_domain = irq_find_host(parent);
if (!parent_domain) {
pr_err("unable to obtain parent domain\n");
return -ENODEV;
}
if (!of_device_is_compatible(parent, "arm,gic-v3"))
return -ENODEV;
intc0->local = irq_domain_create_hierarchy(parent_domain, 0, INT0_NUM,
of_fwnode_handle(node),
&aspeed_intc0_irq_domain_ops,
intc0);
if (!intc0->local)
return -ENOMEM;
ret = aspeed_intc_populate_ranges(&pdev->dev, &intc0->ranges);
if (ret < 0) {
irq_domain_remove(intc0->local);
return ret;
}
return 0;
}
IRQCHIP_PLATFORM_DRIVER_BEGIN(ast2700_intc0)
IRQCHIP_MATCH("aspeed,ast2700-intc0", aspeed_intc0_probe)
IRQCHIP_PLATFORM_DRIVER_END(ast2700_intc0)
+280
View File
@@ -0,0 +1,280 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Aspeed AST2700 Interrupt Controller.
*
* Copyright (C) 2026 ASPEED Technology Inc.
*/
#include <linux/bitops.h>
#include <linux/device.h>
#include <linux/io.h>
#include <linux/irq.h>
#include <linux/irqchip.h>
#include <linux/irqchip/chained_irq.h>
#include <linux/irqdomain.h>
#include <linux/of.h>
#include <linux/of_irq.h>
#include <linux/spinlock.h>
#include "irq-ast2700.h"
#define INTC1_IER 0x100
#define INTC1_ISR 0x104
#define INTC1_BANK_SIZE 0x10
#define INTC1_SEL_BASE 0x80
#define INTC1_SEL_BANK_SIZE 0x4
#define INTC1_SEL_ROUTE_SIZE 0x20
#define INTC1_IRQS_PER_BANK 32
#define INTC1_BANK_NUM 6
#define INTC1_ROUTE_NUM 7
#define INTC1_IN_NUM 192
#define INTC1_BOOTMCU_ROUTE 6
#define INTC1_ROUTE_SELECTOR_BITS 3
#define INTC1_ROUTE_IRQS_PER_GROUP 32
#define INTC1_ROUTE_SHIFT 5
struct aspeed_intc1 {
struct device *dev;
void __iomem *base;
raw_spinlock_t intc_lock;
struct irq_domain *local;
struct irq_domain *upstream;
struct aspeed_intc_interrupt_ranges ranges;
};
static void aspeed_intc1_disable_int(struct aspeed_intc1 *intc1)
{
for (int i = 0; i < INTC1_BANK_NUM; i++)
writel(0, intc1->base + INTC1_IER + (INTC1_BANK_SIZE * i));
}
static void aspeed_intc1_irq_handler(struct irq_desc *desc)
{
struct aspeed_intc1 *intc1 = irq_desc_get_handler_data(desc);
struct irq_chip *chip = irq_desc_get_chip(desc);
unsigned long bit, status;
chained_irq_enter(chip, desc);
for (int bank = 0; bank < INTC1_BANK_NUM; bank++) {
status = readl(intc1->base + INTC1_ISR + (INTC1_BANK_SIZE * bank));
if (!status)
continue;
for_each_set_bit(bit, &status, INTC1_IRQS_PER_BANK) {
generic_handle_domain_irq(intc1->local, (bank * INTC1_IRQS_PER_BANK) + bit);
writel(BIT(bit), intc1->base + INTC1_ISR + (INTC1_BANK_SIZE * bank));
}
}
chained_irq_exit(chip, desc);
}
static void aspeed_intc1_irq_mask(struct irq_data *data)
{
struct aspeed_intc1 *intc1 = irq_data_get_irq_chip_data(data);
int bank = data->hwirq / INTC1_IRQS_PER_BANK;
int bit = data->hwirq % INTC1_IRQS_PER_BANK;
u32 ier;
guard(raw_spinlock)(&intc1->intc_lock);
ier = readl(intc1->base + INTC1_IER + (INTC1_BANK_SIZE * bank)) & ~BIT(bit);
writel(ier, intc1->base + INTC1_IER + (INTC1_BANK_SIZE * bank));
}
static void aspeed_intc1_irq_unmask(struct irq_data *data)
{
struct aspeed_intc1 *intc1 = irq_data_get_irq_chip_data(data);
int bank = data->hwirq / INTC1_IRQS_PER_BANK;
int bit = data->hwirq % INTC1_IRQS_PER_BANK;
u32 ier;
guard(raw_spinlock)(&intc1->intc_lock);
ier = readl(intc1->base + INTC1_IER + (INTC1_BANK_SIZE * bank)) | BIT(bit);
writel(ier, intc1->base + INTC1_IER + (INTC1_BANK_SIZE * bank));
}
static struct irq_chip aspeed_intc_chip = {
.name = "ASPEED INTC1",
.irq_mask = aspeed_intc1_irq_mask,
.irq_unmask = aspeed_intc1_irq_unmask,
};
static int aspeed_intc1_irq_domain_translate(struct irq_domain *domain,
struct irq_fwspec *fwspec,
unsigned long *hwirq,
unsigned int *type)
{
if (fwspec->param_count != 1)
return -EINVAL;
*hwirq = fwspec->param[0];
*type = IRQ_TYPE_LEVEL_HIGH;
return 0;
}
static int aspeed_intc1_map_irq_domain(struct irq_domain *domain,
unsigned int irq,
irq_hw_number_t hwirq)
{
irq_domain_set_info(domain, irq, hwirq, &aspeed_intc_chip,
domain->host_data, handle_level_irq, NULL, NULL);
return 0;
}
/*
* In-bound interrupts are progressively merged into one out-bound interrupt in
* groups of 32. Apply this fact to compress the route table in corresponding
* groups of 32.
*/
static const u32
aspeed_intc1_routes[INTC1_IN_NUM / INTC1_ROUTE_IRQS_PER_GROUP][INTC1_ROUTE_NUM] = {
{ 0, AST2700_INTC_INVALID_ROUTE, 10, 20, 30, 40, 50 },
{ 1, AST2700_INTC_INVALID_ROUTE, 11, 21, 31, 41, 50 },
{ 2, AST2700_INTC_INVALID_ROUTE, 12, 22, 32, 42, 50 },
{ 3, AST2700_INTC_INVALID_ROUTE, 13, 23, 33, 43, 50 },
{ 4, AST2700_INTC_INVALID_ROUTE, 14, 24, 34, 44, 50 },
{ 5, AST2700_INTC_INVALID_ROUTE, 15, 25, 35, 45, 50 },
};
static int aspeed_intc1_irq_domain_activate(struct irq_domain *domain,
struct irq_data *data, bool reserve)
{
struct aspeed_intc1 *intc1 = irq_data_get_irq_chip_data(data);
struct aspeed_intc_interrupt_range resolved;
int rc, bank, bit;
u32 mask;
if (WARN_ON_ONCE((data->hwirq >> INTC1_ROUTE_SHIFT) >= ARRAY_SIZE(aspeed_intc1_routes)))
return -EINVAL;
/*
* outpin may be an error if the upstream is the BootMCU APLIC node, or
* anything except a valid intc0 driver instance
*/
rc = aspeed_intc0_resolve_route(intc1->upstream, INTC1_ROUTE_NUM,
aspeed_intc1_routes[data->hwirq >> INTC1_ROUTE_SHIFT],
intc1->ranges.nranges,
intc1->ranges.ranges, &resolved);
if (rc < 0) {
if (!fwnode_device_is_compatible(intc1->upstream->fwnode, "riscv,aplic")) {
dev_warn(intc1->dev,
"Failed to resolve interrupt route for hwirq %lu in domain %s\n",
data->hwirq, domain->name);
return rc;
}
rc = INTC1_BOOTMCU_ROUTE;
}
bank = data->hwirq / INTC1_IRQS_PER_BANK;
bit = data->hwirq % INTC1_IRQS_PER_BANK;
mask = BIT(bit);
guard(raw_spinlock)(&intc1->intc_lock);
for (int i = 0; i < INTC1_ROUTE_SELECTOR_BITS; i++) {
void __iomem *sel = intc1->base + INTC1_SEL_BASE +
(bank * INTC1_SEL_BANK_SIZE) +
(INTC1_SEL_ROUTE_SIZE * i);
u32 reg = readl(sel);
if (rc & BIT(i))
reg |= mask;
else
reg &= ~mask;
writel(reg, sel);
if (readl(sel) != reg)
return -EACCES;
}
return 0;
}
static const struct irq_domain_ops aspeed_intc1_irq_domain_ops = {
.map = aspeed_intc1_map_irq_domain,
.translate = aspeed_intc1_irq_domain_translate,
.activate = aspeed_intc1_irq_domain_activate,
};
static void aspeed_intc1_request_interrupts(struct aspeed_intc1 *intc1)
{
for (unsigned int i = 0; i < intc1->ranges.nranges; i++) {
struct aspeed_intc_interrupt_range *r =
&intc1->ranges.ranges[i];
if (intc1->upstream != r->domain)
continue;
for (u32 k = 0; k < r->count; k++) {
struct of_phandle_args parent_irq;
int irq;
parent_irq.np = to_of_node(r->upstream.fwnode);
parent_irq.args_count = 1;
parent_irq.args[0] =
intc1->ranges.ranges[i].upstream.param[ASPEED_INTC_RANGES_BASE] + k;
irq = irq_create_of_mapping(&parent_irq);
if (!irq)
continue;
irq_set_chained_handler_and_data(irq,
aspeed_intc1_irq_handler, intc1);
}
}
}
static int aspeed_intc1_probe(struct platform_device *pdev,
struct device_node *parent)
{
struct device_node *node = pdev->dev.of_node;
struct aspeed_intc1 *intc1;
struct irq_domain *host;
int ret;
if (!parent) {
dev_err(&pdev->dev, "missing parent interrupt node\n");
return -ENODEV;
}
if (!of_device_is_compatible(parent, "aspeed,ast2700-intc0"))
return -ENODEV;
host = irq_find_host(parent);
if (!host)
return -ENODEV;
intc1 = devm_kzalloc(&pdev->dev, sizeof(*intc1), GFP_KERNEL);
if (!intc1)
return -ENOMEM;
intc1->dev = &pdev->dev;
intc1->upstream = host;
intc1->base = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(intc1->base))
return PTR_ERR(intc1->base);
aspeed_intc1_disable_int(intc1);
raw_spin_lock_init(&intc1->intc_lock);
intc1->local = irq_domain_create_linear(of_fwnode_handle(node),
INTC1_BANK_NUM * INTC1_IRQS_PER_BANK,
&aspeed_intc1_irq_domain_ops, intc1);
if (!intc1->local)
return -ENOMEM;
ret = aspeed_intc_populate_ranges(&pdev->dev, &intc1->ranges);
if (ret < 0) {
irq_domain_remove(intc1->local);
return ret;
}
aspeed_intc1_request_interrupts(intc1);
return 0;
}
IRQCHIP_PLATFORM_DRIVER_BEGIN(ast2700_intc1)
IRQCHIP_MATCH("aspeed,ast2700-intc1", aspeed_intc1_probe)
IRQCHIP_PLATFORM_DRIVER_END(ast2700_intc1)
+107
View File
@@ -0,0 +1,107 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Aspeed AST2700 Interrupt Controller.
*
* Copyright (C) 2026 ASPEED Technology Inc.
*/
#include "irq-ast2700.h"
#define ASPEED_INTC_RANGE_FIXED_CELLS 3U
#define ASPEED_INTC_RANGE_OFF_START 0U
#define ASPEED_INTC_RANGE_OFF_COUNT 1U
#define ASPEED_INTC_RANGE_OFF_PHANDLE 2U
/**
* aspeed_intc_populate_ranges
* @dev: Device owning the interrupt controller node.
* @ranges: Destination for parsed range descriptors.
*
* Return: 0 on success, negative errno on error.
*/
int aspeed_intc_populate_ranges(struct device *dev,
struct aspeed_intc_interrupt_ranges *ranges)
{
struct aspeed_intc_interrupt_range *arr;
const __be32 *pvs, *pve;
struct device_node *dn;
int len;
if (!dev || !ranges)
return -EINVAL;
dn = dev->of_node;
pvs = of_get_property(dn, "aspeed,interrupt-ranges", &len);
if (!pvs)
return -EINVAL;
if (len % sizeof(__be32))
return -EINVAL;
/* Over-estimate the range entry count for now */
ranges->ranges = devm_kmalloc_array(dev,
len / (ASPEED_INTC_RANGE_FIXED_CELLS * sizeof(__be32)),
sizeof(*ranges->ranges),
GFP_KERNEL);
if (!ranges->ranges)
return -ENOMEM;
pve = pvs + (len / sizeof(__be32));
for (unsigned int i = 0; pve - pvs >= ASPEED_INTC_RANGE_FIXED_CELLS; i++) {
struct aspeed_intc_interrupt_range *r;
struct device_node *target;
u32 target_cells;
target = of_find_node_by_phandle(be32_to_cpu(pvs[ASPEED_INTC_RANGE_OFF_PHANDLE]));
if (!target)
return -EINVAL;
if (of_property_read_u32(target, "#interrupt-cells",
&target_cells)) {
of_node_put(target);
return -EINVAL;
}
if (!target_cells || target_cells > IRQ_DOMAIN_IRQ_SPEC_PARAMS) {
of_node_put(target);
return -EINVAL;
}
if (pve - pvs < ASPEED_INTC_RANGE_FIXED_CELLS + target_cells) {
of_node_put(target);
return -EINVAL;
}
r = &ranges->ranges[i];
r->start = be32_to_cpu(pvs[ASPEED_INTC_RANGE_OFF_START]);
r->count = be32_to_cpu(pvs[ASPEED_INTC_RANGE_OFF_COUNT]);
{
struct of_phandle_args args = {
.np = target,
.args_count = target_cells,
};
for (u32 j = 0; j < target_cells; j++)
args.args[j] = be32_to_cpu(pvs[ASPEED_INTC_RANGE_FIXED_CELLS + j]);
of_phandle_args_to_fwspec(target, args.args,
args.args_count,
&r->upstream);
}
of_node_put(target);
r->domain = irq_find_matching_fwspec(&r->upstream, DOMAIN_BUS_ANY);
pvs += ASPEED_INTC_RANGE_FIXED_CELLS + target_cells;
ranges->nranges++;
}
/* Re-fit the range array now we know the entry count */
arr = devm_krealloc_array(dev, ranges->ranges, ranges->nranges,
sizeof(*ranges->ranges), GFP_KERNEL);
if (!arr)
return -ENOMEM;
ranges->ranges = arr;
return 0;
}
+48
View File
@@ -0,0 +1,48 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Aspeed AST2700 Interrupt Controller.
*
* Copyright (C) 2026 ASPEED Technology Inc.
*/
#ifndef DRIVERS_IRQCHIP_AST2700
#define DRIVERS_IRQCHIP_AST2700
#include <linux/device.h>
#include <linux/irqdomain.h>
#define AST2700_INTC_INVALID_ROUTE (~0U)
#define ASPEED_INTC_RANGES_BASE 0U
#define ASPEED_INTC_RANGES_COUNT 1U
struct aspeed_intc_interrupt_range {
u32 start;
u32 count;
struct irq_fwspec upstream;
struct irq_domain *domain;
};
struct aspeed_intc_interrupt_ranges {
struct aspeed_intc_interrupt_range *ranges;
unsigned int nranges;
};
struct aspeed_intc0 {
struct device *dev;
void __iomem *base;
raw_spinlock_t intc_lock;
struct irq_domain *local;
struct device_node *parent;
struct aspeed_intc_interrupt_ranges ranges;
};
int aspeed_intc_populate_ranges(struct device *dev,
struct aspeed_intc_interrupt_ranges *ranges);
int aspeed_intc0_resolve_route(const struct irq_domain *c0domain,
size_t nc1outs,
const u32 *c1outs,
size_t nc1ranges,
const struct aspeed_intc_interrupt_range *c1ranges,
struct aspeed_intc_interrupt_range *resolved);
#endif
+178 -8
View File
@@ -30,6 +30,8 @@
#include <linux/irqchip.h>
#include <linux/irqchip/chained_irq.h>
#include <asm/setup.h>
#define IRQ_COUNT 40
#define NOT_PERCPU 0xff
@@ -41,15 +43,19 @@
#define REG_PENDING1 0x54
/**
* @membase: Base address of the interrupt controller registers
* @interrupt_shadows: Array of all interrupts, for each value,
* - NOT_PERCPU: This interrupt is not per-cpu, so it has no shadow
* - IS_SHADOW: This interrupt is a shadow of another per-cpu interrupt
* - else: This is a per-cpu interrupt whose shadow is the value
* @membase: Base address of the interrupt controller registers
* @domain: The irq_domain for direct dispatch
* @ipi_domain: The irq_domain for inter-process dispatch
* @interrupt_shadows: Array of all interrupts, for each value,
* - NOT_PERCPU: This interrupt is not per-cpu, so it has no shadow
* - IS_SHADOW: This interrupt is a shadow of another per-cpu interrupt
* - else: This is a per-cpu interrupt whose shadow is the value
*/
static struct {
void __iomem *membase;
u8 interrupt_shadows[IRQ_COUNT];
void __iomem *membase;
struct irq_domain *domain;
struct irq_domain *ipi_domain;
u8 interrupt_shadows[IRQ_COUNT];
} econet_intc __ro_after_init;
static DEFINE_RAW_SPINLOCK(irq_lock);
@@ -150,6 +156,56 @@ static void econet_intc_from_parent(struct irq_desc *desc)
chained_irq_exit(chip, desc);
}
/*
* When in VEIC mode, the CPU jumps to a handler in the vector table.
* The only way to know which interrupt is being triggered is from the vector table offset that
* has been jumped to. Reading REG_PENDING(0|1) will tell you which interrupts are currently
* pending in the intc, but that will not tell you which one the intc wants you to process
* right now. And if you are not processing the exact interrupt that the intc wants you to be
* processing, you might be on the wrong VPE. You can't tell which VPE any given REG_PENDING
* interrupt is intended for (shadow IRQ numbers are for masking only, they never flag as
* pending).
*
* Consequently, this little ritual of generating n handler functions and registering one per
* interrupt is unavoidable.
*/
#define X(irq) \
static void econet_irq_dispatch ## irq (void) \
{ \
do_domain_IRQ(econet_intc.domain, irq); \
}
X(0) X(1) X(2) X(3) X(4) X(5) X(6) X(7) X(8) X(9)
X(10) X(11) X(12) X(13) X(14) X(15) X(16) X(17) X(18) X(19)
X(20) X(21) X(22) X(23) X(24) X(25) X(26) X(27) X(28) X(29)
X(30) X(31) X(32) X(33) X(34) X(35) X(36) X(37) X(38) X(39)
#undef X
#define X(irq) econet_irq_dispatch ## irq,
static void (* const econet_irq_dispatchers[])(void) = {
X(0) X(1) X(2) X(3) X(4) X(5) X(6) X(7) X(8) X(9)
X(10) X(11) X(12) X(13) X(14) X(15) X(16) X(17) X(18) X(19)
X(20) X(21) X(22) X(23) X(24) X(25) X(26) X(27) X(28) X(29)
X(30) X(31) X(32) X(33) X(34) X(35) X(36) X(37) X(38) X(39)
};
/* Likewise, we do the same for the 2 IPI IRQs so that we can route them back */
static void econet_cpu_dispatch0(void)
{
do_domain_IRQ(econet_intc.ipi_domain, 0);
}
static void econet_cpu_dispatch1(void)
{
do_domain_IRQ(econet_intc.ipi_domain, 1);
}
static void (* const econet_cpu_dispatchers[])(void) = {
econet_cpu_dispatch0,
econet_cpu_dispatch1,
};
static const struct irq_chip econet_irq_chip;
static int econet_intc_map(struct irq_domain *d, u32 irq, irq_hw_number_t hwirq)
@@ -174,6 +230,10 @@ static int econet_intc_map(struct irq_domain *d, u32 irq, irq_hw_number_t hwirq)
}
irq_set_chip_data(irq, NULL);
if (cpu_has_veic)
set_vi_handler(hwirq + 1, econet_irq_dispatchers[hwirq]);
return 0;
}
@@ -249,6 +309,100 @@ static int __init get_shadow_interrupts(struct device_node *node)
return 0;
}
/**
* econet_cpu_init() - configure routing of CPU interrupts to the correct domain.
* @node: The devicetree node of this interrupt controller.
*
* Interrupts that originate from the CPU are unconditionally unmasked here and are re-routed back
* to the IPI irq_domain in the CPU intc. Masking still takes place but the CPU intc is in charge
* of it, using the mask bits of the c0_status register.
*
* Note that because IP2 ... IP7 are repurposed as Interrupt Priority Level, only the two IPI
* interrupts are actually supported.
*/
static int __init econet_cpu_init(struct device_node *node)
{
const char *field = "econet,cpu-interrupt-map";
struct device_node *parent_intc;
int map_size;
u32 mask;
map_size = of_property_count_u32_elems(node, field);
if (map_size <= 0) {
return 0;
} else if (map_size % 2) {
pr_err("%pOF: %s count is odd, ignoring\n", node, field);
return 0;
}
u32 *maps __free(kfree) = kmalloc_array(map_size, sizeof(u32), GFP_KERNEL);
if (!maps)
return -ENOMEM;
if (of_property_read_u32_array(node, field, maps, map_size)) {
pr_err("%pOF: Failed to read %s\n", node, field);
return -EINVAL;
}
/* Validation */
for (int i = 0; i < map_size; i += 2) {
u32 receive = maps[i];
u32 dispatch = maps[i + 1];
u8 shadow;
if (receive >= IRQ_COUNT) {
pr_err("%pOF: Entry %d:%d in %s (%u) is out of bounds\n",
node, i, 0, field, receive);
return -EINVAL;
}
shadow = econet_intc.interrupt_shadows[receive];
if (shadow != NOT_PERCPU && shadow >= IRQ_COUNT) {
pr_err("%pOF: Entry %d:%d in %s (%u) has invalid shadow (%d)\n",
node, i, 0, field, receive, shadow);
return -EINVAL;
}
if (dispatch >= ARRAY_SIZE(econet_cpu_dispatchers)) {
pr_err("%pOF: Entry %d:%d in %s (%u) is out of bounds only IPI interrupts are supported\n",
node, i, 1, field, dispatch);
return -EINVAL;
}
}
parent_intc = of_irq_find_parent(node);
if (!parent_intc) {
pr_err("%pOF: Failed to find parent %s\n", node, "IRQ device");
return -ENODEV;
}
econet_intc.ipi_domain = irq_find_matching_host(parent_intc, DOMAIN_BUS_IPI);
if (!econet_intc.ipi_domain) {
pr_err("%pOF: Failed to find parent %s\n", node, "IPI domain");
return -ENODEV;
}
mask = 0;
for (int i = 0; i < map_size; i += 2) {
u32 receive = maps[i];
u32 dispatch = maps[i + 1];
u8 shadow;
set_vi_handler(receive + 1, econet_cpu_dispatchers[dispatch]);
mask |= BIT(receive);
shadow = econet_intc.interrupt_shadows[receive];
if (shadow != NOT_PERCPU)
mask |= BIT(shadow);
}
econet_wreg(REG_MASK0, mask, mask);
return 0;
}
static int __init econet_intc_of_init(struct device_node *node, struct device_node *parent)
{
struct irq_domain *domain;
@@ -294,7 +448,23 @@ static int __init econet_intc_of_init(struct device_node *node, struct device_no
goto err_unmap;
}
irq_set_chained_handler_and_data(irq, econet_intc_from_parent, domain);
/*
* 34K Manual (MD00534) Section 6.3.1.3 rev 1.13 page 136:
* In VEIC mode, IP2 ... IP7 are repurposed as Interrupt Priority Level. The controller
* will filter incoming interrupts whose priority is lower than the IPL number. Therefore
* we must not set any of these bits. We avoid setting IP2 by not actually chaining this
* intc to the CPU intc.
*/
if (cpu_has_veic) {
ret = econet_cpu_init(node);
if (ret)
return ret;
} else {
irq_set_chained_handler_and_data(irq, econet_intc_from_parent, domain);
}
econet_intc.domain = domain;
return 0;
+5 -8
View File
@@ -4784,8 +4784,7 @@ static bool __maybe_unused its_enable_quirk_cavium_22375(void *data)
struct its_node *its = data;
/* erratum 22375: only alloc 8MB table size (20 bits) */
its->typer &= ~GITS_TYPER_DEVBITS;
its->typer |= FIELD_PREP(GITS_TYPER_DEVBITS, 20 - 1);
FIELD_MODIFY(GITS_TYPER_DEVBITS, &its->typer, 20 - 1);
its->flags |= ITS_FLAGS_WORKAROUND_CAVIUM_22375;
return true;
@@ -4805,8 +4804,7 @@ static bool __maybe_unused its_enable_quirk_qdf2400_e0065(void *data)
struct its_node *its = data;
/* On QDF2400, the size of the ITE is 16Bytes */
its->typer &= ~GITS_TYPER_ITT_ENTRY_SIZE;
its->typer |= FIELD_PREP(GITS_TYPER_ITT_ENTRY_SIZE, 16 - 1);
FIELD_MODIFY(GITS_TYPER_ITT_ENTRY_SIZE, &its->typer, 16 - 1);
return true;
}
@@ -4840,10 +4838,8 @@ static bool __maybe_unused its_enable_quirk_socionext_synquacer(void *data)
its->get_msi_base = its_irq_get_msi_base_pre_its;
ids = ilog2(pre_its_window[1]) - 2;
if (device_ids(its) > ids) {
its->typer &= ~GITS_TYPER_DEVBITS;
its->typer |= FIELD_PREP(GITS_TYPER_DEVBITS, ids - 1);
}
if (device_ids(its) > ids)
FIELD_MODIFY(GITS_TYPER_DEVBITS, &its->typer, ids - 1);
/* the pre-ITS breaks isolation, so disable MSI remapping */
its->msi_domain_flags &= ~IRQ_DOMAIN_FLAG_ISOLATED_MSI;
@@ -5837,6 +5833,7 @@ int __init its_init(struct fwnode_handle *handle, struct rdists *rdists,
its_acpi_probe();
if (list_empty(&its_nodes)) {
rdists->has_vlpis = false;
pr_warn("ITS: No ITS available, not enabling LPIs\n");
return -ENXIO;
}
+7 -13
View File
@@ -24,7 +24,6 @@
#define VECTORS_PER_REG 64
#define IRR_VECTOR_MASK 0xffUL
#define IRR_INVALID_MASK 0x80000000UL
#define AVEC_MSG_OFFSET 0x100000
#ifdef CONFIG_SMP
struct pending_list {
@@ -47,15 +46,6 @@ struct avecintc_chip {
static struct avecintc_chip loongarch_avec;
struct avecintc_data {
struct list_head entry;
unsigned int cpu;
unsigned int vec;
unsigned int prev_cpu;
unsigned int prev_vec;
unsigned int moving;
};
static inline void avecintc_enable(void)
{
#ifdef CONFIG_MACH_LOONGSON64
@@ -87,7 +77,7 @@ static inline void pending_list_init(int cpu)
INIT_LIST_HEAD(&plist->head);
}
static void avecintc_sync(struct avecintc_data *adata)
void avecintc_sync(struct avecintc_data *adata)
{
struct pending_list *plist;
@@ -111,7 +101,7 @@ static int avecintc_set_affinity(struct irq_data *data, const struct cpumask *de
return -EBUSY;
if (cpu_online(adata->cpu) && cpumask_test_cpu(adata->cpu, dest))
return 0;
return IRQ_SET_MASK_OK_DONE;
cpumask_and(&intersect_mask, dest, cpu_online_mask);
@@ -123,7 +113,8 @@ static int avecintc_set_affinity(struct irq_data *data, const struct cpumask *de
adata->cpu = cpu;
adata->vec = vector;
per_cpu_ptr(irq_map, adata->cpu)[adata->vec] = irq_data_to_desc(data);
avecintc_sync(adata);
if (!cpu_has_redirectint)
avecintc_sync(adata);
}
irq_data_update_effective_affinity(data, cpumask_of(cpu));
@@ -415,6 +406,9 @@ static int __init pch_msi_parse_madt(union acpi_subtable_headers *header,
static inline int __init acpi_cascade_irqdomain_init(void)
{
if (cpu_has_redirectint)
return redirect_acpi_init(loongarch_avec.domain);
return acpi_table_parse_madt(ACPI_MADT_TYPE_MSI_PIC, pch_msi_parse_madt, 1);
}
+537
View File
@@ -0,0 +1,537 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2024-2026 Loongson Technologies, Inc.
*/
#define pr_fmt(fmt) "redirect: " fmt
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/io-64-nonatomic-lo-hi.h>
#include <linux/irq.h>
#include <linux/irqchip.h>
#include <linux/irqchip/irq-msi-lib.h>
#include <linux/irqdomain.h>
#include <linux/kernel.h>
#include <linux/msi.h>
#include <linux/spinlock.h>
#include <asm/irq.h>
#include <asm/loongarch.h>
#include <asm/loongson.h>
#include <asm/numa.h>
#include <asm/setup.h>
#include "irq-loongson.h"
#define LOONGARCH_IOCSR_REDIRECT_CFG 0x15e0
#define LOONGARCH_IOCSR_REDIRECT_TBR 0x15e8 /* IRT BASE REG */
#define LOONGARCH_IOCSR_REDIRECT_CQB 0x15f0 /* IRT CACHE QUEUE BASE */
#define LOONGARCH_IOCSR_REDIRECT_CQH 0x15f8 /* IRT CACHE QUEUE HEAD, 32bit */
#define LOONGARCH_IOCSR_REDIRECT_CQT 0x15fc /* IRT CACHE QUEUE TAIL, 32bit */
#define CQB_ADDR_MASK GENMASK_U64(47, 12)
#define CQB_SIZE_MASK 0xf
#define GPID_ADDR_MASK GENMASK_U64(47, 6)
#define GPID_ADDR_SHIFT 6
#define INVALID_INDEX 0
#define CFG_DISABLE_IDLE 2
#define MAX_IR_ENGINES 16
struct redirect_entry {
struct {
u64 valid : 1,
res1 : 5,
gpid : 42,
res2 : 8,
vector : 8;
} lo;
u64 hi;
};
#define IRD_ENTRY_SIZE sizeof(struct redirect_entry)
#define IRD_ENTRIES SZ_64K
#define IRD_TABLE_PAGE_ORDER get_order(IRD_ENTRIES * IRD_ENTRY_SIZE)
struct redirect_cmd {
union {
u64 cmd_info;
struct {
u64 res1 : 4,
type : 1,
need_notice : 1,
pad1 : 2,
index : 16,
pad2 : 40;
} index;
};
u64 notice_addr;
};
#define IRD_CMD_SIZE sizeof(struct redirect_cmd)
#define INV_QUEUE_SIZE SZ_4K
#define INV_QUEUE_PAGE_ORDER get_order(INV_QUEUE_SIZE * IRD_CMD_SIZE)
struct redirect_gpid {
u64 pir[4]; /* Pending interrupt requested */
u8 en : 1, /* Doorbell */
res1 : 7;
u8 irqnum;
u16 res2;
u32 dstcpu;
u32 rsvd[6];
};
struct redirect_table {
struct redirect_entry *table;
unsigned long *bitmap;
raw_spinlock_t lock;
};
struct redirect_queue {
struct redirect_cmd *cmd_base;
int head;
int tail;
raw_spinlock_t lock;
};
struct redirect_desc {
struct redirect_table ird_table;
struct redirect_queue inv_queue;
int node;
};
struct redirect_item {
int index;
struct redirect_desc *irde;
struct redirect_gpid *gpid;
};
static struct irq_domain *redirect_domain;
static struct redirect_desc redirect_descs[MAX_IR_ENGINES];
static phys_addr_t msi_base_addr;
static phys_addr_t redirect_reg_base = LOONGSON_REG_BASE;
#ifdef CONFIG_32BIT
#define REDIRECT_REG(reg, node) \
((void __iomem *)(IO_BASE | redirect_reg_base | (reg)))
#else
#define REDIRECT_REG(reg, node) \
((void __iomem *)(IO_BASE | redirect_reg_base | (u64)(node) << NODE_ADDRSPACE_SHIFT | (reg)))
#endif
static inline u32 redirect_read_reg32(u32 node, u32 reg)
{
return readl(REDIRECT_REG(reg, node));
}
static inline void redirect_write_reg32(u32 node, u32 val, u32 reg)
{
writel(val, REDIRECT_REG(reg, node));
}
static inline void redirect_write_reg64(u32 node, u64 val, u32 reg)
{
writeq(val, REDIRECT_REG(reg, node));
}
static inline struct redirect_entry *item_get_entry(struct redirect_item *item)
{
return item->irde->ird_table.table + item->index;
}
static inline bool invalid_queue_is_full(int node, u32 *tail)
{
u32 head = redirect_read_reg32(node, LOONGARCH_IOCSR_REDIRECT_CQH);
*tail = redirect_read_reg32(node, LOONGARCH_IOCSR_REDIRECT_CQT);
return head == ((*tail + 1) % INV_QUEUE_SIZE);
}
static void invalid_enqueue(struct redirect_item *item, struct redirect_cmd *cmd)
{
struct redirect_queue *inv_queue = &item->irde->inv_queue;
u32 tail;
guard(raw_spinlock_irqsave)(&inv_queue->lock);
while (invalid_queue_is_full(item->irde->node, &tail))
cpu_relax();
memcpy(&inv_queue->cmd_base[tail], cmd, sizeof(*cmd));
redirect_write_reg32(item->irde->node, (tail + 1) % INV_QUEUE_SIZE, LOONGARCH_IOCSR_REDIRECT_CQT);
}
static void irde_invalidate_entry(struct redirect_item *item)
{
struct redirect_cmd cmd;
u64 raddr = 0;
cmd.cmd_info = 0;
cmd.index.type = INVALID_INDEX;
cmd.index.need_notice = 1;
cmd.index.index = item->index;
cmd.notice_addr = (u64)(__pa(&raddr));
invalid_enqueue(item, &cmd);
/*
* The CPU needs to wait here for cmd to complete, and it determines this
* by checking whether the invalidation queue has already written a valid value
* to cmd.notice_addr.
*/
while (!raddr)
cpu_relax();
}
static inline struct avecintc_data *irq_data_get_avec_data(struct irq_data *data)
{
return data->parent_data->chip_data;
}
static int redirect_table_alloc(int node, u32 nr_irqs)
{
struct redirect_table *ird_table = &redirect_descs[node].ird_table;
int index, order = 0;
if (nr_irqs > 1) {
nr_irqs = __roundup_pow_of_two(nr_irqs);
order = ilog2(nr_irqs);
}
guard(raw_spinlock_irqsave)(&ird_table->lock);
index = bitmap_find_free_region(ird_table->bitmap, IRD_ENTRIES, order);
if (index < 0) {
pr_err("No redirect entry to use\n");
return -EINVAL;
}
return index;
}
static void redirect_table_free(struct redirect_item *item)
{
struct redirect_table *ird_table = &item->irde->ird_table;
struct redirect_entry *entry = item_get_entry(item);
memset(entry, 0, sizeof(*entry));
scoped_guard(raw_spinlock_irq, &ird_table->lock)
clear_bit(item->index, ird_table->bitmap);
kfree(item->gpid);
irde_invalidate_entry(item);
}
static inline void redirect_domain_prepare_entry(struct redirect_item *item,
struct avecintc_data *adata)
{
struct redirect_entry *entry = item_get_entry(item);
item->gpid->en = 1;
item->gpid->dstcpu = adata->cpu;
item->gpid->irqnum = adata->vec;
entry->lo.valid = 1;
entry->lo.vector = 0xff;
entry->lo.gpid = ((unsigned long)item->gpid & GPID_ADDR_MASK) >> GPID_ADDR_SHIFT;
}
static void redirect_free_resources(struct irq_domain *domain,
unsigned int virq, unsigned int nr_irqs)
{
for (int i = 0; i < nr_irqs; i++) {
struct irq_data *irq_data = irq_domain_get_irq_data(domain, virq + i);
if (irq_data && irq_data->chip_data) {
struct redirect_item *item = irq_data->chip_data;
redirect_table_free(item);
kfree(item);
}
}
}
#ifdef CONFIG_SMP
static int redirect_set_affinity(struct irq_data *data, const struct cpumask *dest, bool force)
{
struct avecintc_data *adata = irq_data_get_avec_data(data);
struct redirect_item *item = data->chip_data;
int ret;
ret = irq_chip_set_affinity_parent(data, dest, force);
switch (ret) {
case IRQ_SET_MASK_OK:
break;
case IRQ_SET_MASK_OK_DONE:
return ret;
default:
pr_err("IRDE: set_affinity error %d\n", ret);
return ret;
}
redirect_domain_prepare_entry(item, adata);
irde_invalidate_entry(item);
avecintc_sync(adata);
return IRQ_SET_MASK_OK;
}
#endif
static void redirect_compose_msi_msg(struct irq_data *d, struct msi_msg *msg)
{
struct redirect_item *item = irq_data_get_irq_chip_data(d);
msg->address_hi = 0x0;
msg->address_lo = (msi_base_addr | 1 << 2);
msg->data = item->index;
}
static struct irq_chip loongarch_redirect_chip = {
.name = "REDIRECT",
.irq_ack = irq_chip_ack_parent,
.irq_mask = irq_chip_mask_parent,
.irq_unmask = irq_chip_unmask_parent,
#ifdef CONFIG_SMP
.irq_set_affinity = redirect_set_affinity,
#endif
.irq_compose_msi_msg = redirect_compose_msi_msg,
};
static int redirect_domain_alloc(struct irq_domain *domain, unsigned int virq,
unsigned int nr_irqs, void *arg)
{
msi_alloc_info_t *info = arg;
int ret, i, node, index;
node = dev_to_node(info->desc->dev);
ret = irq_domain_alloc_irqs_parent(domain, virq, nr_irqs, arg);
if (ret < 0)
return ret;
index = redirect_table_alloc(node, nr_irqs);
if (index < 0) {
pr_err("Alloc redirect table entry failed\n");
return -EINVAL;
}
for (i = 0; i < nr_irqs; i++) {
struct irq_data *irq_data = irq_domain_get_irq_data(domain, virq + i);
struct redirect_item *item;
item = kzalloc(sizeof(*item), GFP_KERNEL);
if (!item) {
pr_err("Alloc redirect descriptor failed\n");
goto out_free_resources;
}
item->irde = &redirect_descs[node];
/*
* Only bits 47:6 of the GPID are passed to the controller,
* 64-byte alignment must be guarantee and make kzalloc can
* align to the respective size.
*/
static_assert(sizeof(*item->gpid) == 64);
item->gpid = kzalloc_node(sizeof(*item->gpid), GFP_KERNEL, node);
if (!item->gpid) {
pr_err("Alloc redirect GPID failed\n");
goto out_free_resources;
}
item->index = index + i;
irq_data->chip_data = item;
irq_data->chip = &loongarch_redirect_chip;
redirect_domain_prepare_entry(item, irq_data_get_avec_data(irq_data));
}
return 0;
out_free_resources:
redirect_free_resources(domain, virq, nr_irqs);
irq_domain_free_irqs_common(domain, virq, nr_irqs);
return -ENOMEM;
}
static void redirect_domain_free(struct irq_domain *domain, unsigned int virq, unsigned int nr_irqs)
{
redirect_free_resources(domain, virq, nr_irqs);
return irq_domain_free_irqs_common(domain, virq, nr_irqs);
}
static const struct irq_domain_ops redirect_domain_ops = {
.alloc = redirect_domain_alloc,
.free = redirect_domain_free,
.select = msi_lib_irq_domain_select,
};
static int redirect_table_init(struct redirect_desc *irde)
{
struct redirect_table *ird_table = &irde->ird_table;
unsigned long *bitmap;
struct folio *folio;
folio = __folio_alloc_node(GFP_KERNEL | __GFP_ZERO, IRD_TABLE_PAGE_ORDER, irde->node);
if (!folio) {
pr_err("Node [%d] redirect table alloc pages failed!\n", irde->node);
return -ENOMEM;
}
ird_table->table = folio_address(folio);
bitmap = bitmap_zalloc(IRD_ENTRIES, GFP_KERNEL);
if (!bitmap) {
pr_err("Node [%d] redirect table bitmap alloc pages failed!\n", irde->node);
folio_put(folio);
ird_table->table = NULL;
return -ENOMEM;
}
ird_table->bitmap = bitmap;
raw_spin_lock_init(&ird_table->lock);
return 0;
}
static int redirect_queue_init(struct redirect_desc *irde)
{
struct redirect_queue *inv_queue = &irde->inv_queue;
struct folio *folio;
folio = __folio_alloc_node(GFP_KERNEL | __GFP_ZERO, INV_QUEUE_PAGE_ORDER, irde->node);
if (!folio) {
pr_err("Node [%d] invalid queue alloc pages failed!\n", irde->node);
return -ENOMEM;
}
inv_queue->cmd_base = folio_address(folio);
inv_queue->head = 0;
inv_queue->tail = 0;
raw_spin_lock_init(&inv_queue->lock);
return 0;
}
static void redirect_irde_cfg(struct redirect_desc *irde)
{
redirect_write_reg64(irde->node, CFG_DISABLE_IDLE, LOONGARCH_IOCSR_REDIRECT_CFG);
redirect_write_reg64(irde->node, __pa(irde->ird_table.table), LOONGARCH_IOCSR_REDIRECT_TBR);
redirect_write_reg32(irde->node, 0, LOONGARCH_IOCSR_REDIRECT_CQH);
redirect_write_reg32(irde->node, 0, LOONGARCH_IOCSR_REDIRECT_CQT);
redirect_write_reg64(irde->node, ((unsigned long)irde->inv_queue.cmd_base & CQB_ADDR_MASK) |
CQB_SIZE_MASK, LOONGARCH_IOCSR_REDIRECT_CQB);
}
static void __init redirect_irde_free(struct redirect_desc *irde)
{
struct redirect_table *ird_table = &redirect_descs->ird_table;
struct redirect_queue *inv_queue = &redirect_descs->inv_queue;
if (ird_table->table) {
folio_put(virt_to_folio(ird_table->table));
ird_table->table = NULL;
}
if (ird_table->bitmap) {
bitmap_free(ird_table->bitmap);
ird_table->bitmap = NULL;
}
if (inv_queue->cmd_base) {
folio_put(virt_to_folio(inv_queue->cmd_base));
inv_queue->cmd_base = NULL;
}
}
static int __init redirect_irde_init(int node)
{
struct redirect_desc *irde = &redirect_descs[node];
int ret;
irde->node = node;
ret = redirect_table_init(irde);
if (ret)
return ret;
ret = redirect_queue_init(irde);
if (ret) {
redirect_irde_free(irde);
return ret;
}
redirect_irde_cfg(irde);
return 0;
}
static int __init pch_msi_parse_madt(union acpi_subtable_headers *header, const unsigned long end)
{
struct acpi_madt_msi_pic *pchmsi_entry = (struct acpi_madt_msi_pic *)header;
msi_base_addr = pchmsi_entry->msg_address - AVEC_MSG_OFFSET;
return pch_msi_acpi_init_avec(redirect_domain);
}
static int __init acpi_cascade_irqdomain_init(void)
{
return acpi_table_parse_madt(ACPI_MADT_TYPE_MSI_PIC, pch_msi_parse_madt, 1);
}
int __init redirect_acpi_init(struct irq_domain *parent)
{
struct fwnode_handle *fwnode;
int ret = -EINVAL, node;
fwnode = irq_domain_alloc_named_fwnode("redirect");
if (!fwnode) {
pr_err("Unable to alloc redirect domain handle\n");
goto fail;
}
redirect_domain = irq_domain_create_hierarchy(parent, 0, IRD_ENTRIES, fwnode,
&redirect_domain_ops, redirect_descs);
if (!redirect_domain) {
pr_err("Unable to alloc redirect domain\n");
goto out_free_fwnode;
}
for_each_node_mask(node, node_possible_map) {
ret = redirect_irde_init(node);
if (ret)
goto out_clear_irde;
}
ret = acpi_cascade_irqdomain_init();
if (ret < 0) {
pr_err("Failed to cascade IRQ domain, ret=%d\n", ret);
goto out_clear_irde;
}
pr_info("init succeeded\n");
return 0;
out_clear_irde:
for_each_node_mask(node, node_possible_map) {
redirect_irde_free(&redirect_descs[node]);
}
irq_domain_remove(redirect_domain);
out_free_fwnode:
irq_domain_free_fwnode(fwnode);
fail:
return ret;
}
+15
View File
@@ -6,6 +6,17 @@
#ifndef _DRIVERS_IRQCHIP_IRQ_LOONGSON_H
#define _DRIVERS_IRQCHIP_IRQ_LOONGSON_H
#define AVEC_MSG_OFFSET 0x100000
struct avecintc_data {
struct list_head entry;
unsigned int cpu;
unsigned int vec;
unsigned int prev_cpu;
unsigned int prev_vec;
unsigned int moving;
};
int find_pch_pic(u32 gsi);
int liointc_acpi_init(struct irq_domain *parent,
@@ -14,6 +25,8 @@ int eiointc_acpi_init(struct irq_domain *parent,
struct acpi_madt_eio_pic *acpi_eiointc);
int avecintc_acpi_init(struct irq_domain *parent);
int redirect_acpi_init(struct irq_domain *parent);
int htvec_acpi_init(struct irq_domain *parent,
struct acpi_madt_ht_pic *acpi_htvec);
int pch_lpc_acpi_init(struct irq_domain *parent,
@@ -24,4 +37,6 @@ int pch_msi_acpi_init(struct irq_domain *parent,
struct acpi_madt_msi_pic *acpi_pchmsi);
int pch_msi_acpi_init_avec(struct irq_domain *parent);
void avecintc_sync(struct avecintc_data *adata);
#endif /* _DRIVERS_IRQCHIP_IRQ_LOONGSON_H */
+77
View File
@@ -27,6 +27,10 @@
/* use for A1 like chips */
#define REG_PIN_A1_SEL 0x04
/* use for A9 like chips */
#define REG_A9_AO_POL 0x00
#define REG_A9_AO_EDGE 0x30
/*
* Note: The S905X3 datasheet reports that BOTH_EDGE is controlled by
* bits 24 to 31. Tests on the actual HW show that these bits are
@@ -53,6 +57,8 @@ static void meson_a1_gpio_irq_sel_pin(struct meson_gpio_irq_controller *ctl,
static void meson_a1_gpio_irq_init(struct meson_gpio_irq_controller *ctl);
static int meson8_gpio_irq_set_type(struct meson_gpio_irq_controller *ctl,
unsigned int type, u32 *channel_hwirq);
static int meson_a9_ao_gpio_irq_set_type(struct meson_gpio_irq_controller *ctl,
unsigned int type, u32 *channel_hwirq);
static int meson_s4_gpio_irq_set_type(struct meson_gpio_irq_controller *ctl,
unsigned int type, u32 *channel_hwirq);
@@ -116,6 +122,18 @@ struct meson_gpio_irq_params {
.pin_sel_mask = 0xff, \
.nr_channels = 2, \
#define INIT_MESON_A9_AO_COMMON_DATA(irqs) \
INIT_MESON_COMMON(irqs, meson_a1_gpio_irq_init, \
meson_a1_gpio_irq_sel_pin, \
meson_a9_ao_gpio_irq_set_type) \
.support_edge_both = true, \
.edge_both_offset = 0, \
.edge_single_offset = 0, \
.edge_pol_reg = 0x2c, \
.pol_low_offset = 0, \
.pin_sel_mask = 0xff, \
.nr_channels = 20, \
#define INIT_MESON_S4_COMMON_DATA(irqs) \
INIT_MESON_COMMON(irqs, meson_a1_gpio_irq_init, \
meson_a1_gpio_irq_sel_pin, \
@@ -170,6 +188,14 @@ static const struct meson_gpio_irq_params a5_params = {
INIT_MESON_S4_COMMON_DATA(99)
};
static const struct meson_gpio_irq_params a9_params = {
INIT_MESON_S4_COMMON_DATA(96)
};
static const struct meson_gpio_irq_params a9_ao_params = {
INIT_MESON_A9_AO_COMMON_DATA(39)
};
static const struct meson_gpio_irq_params s4_params = {
INIT_MESON_S4_COMMON_DATA(82)
};
@@ -203,6 +229,8 @@ static const struct of_device_id meson_irq_gpio_matches[] __maybe_unused = {
{ .compatible = "amlogic,a4-gpio-ao-intc", .data = &a4_ao_params },
{ .compatible = "amlogic,a4-gpio-intc", .data = &a4_params },
{ .compatible = "amlogic,a5-gpio-intc", .data = &a5_params },
{ .compatible = "amlogic,a9-gpio-ao-intc", .data = &a9_ao_params },
{ .compatible = "amlogic,a9-gpio-intc", .data = &a9_params },
{ .compatible = "amlogic,s6-gpio-intc", .data = &s6_params },
{ .compatible = "amlogic,s7-gpio-intc", .data = &s7_params },
{ .compatible = "amlogic,s7d-gpio-intc", .data = &s7_params },
@@ -375,6 +403,55 @@ static int meson8_gpio_irq_set_type(struct meson_gpio_irq_controller *ctl,
return 0;
}
/*
* gpio irq relative registers for a9_ao
* -PADCTRL_GPIO_IRQ_CTRL0
* bit[31]: enable/disable all the irq lines
* bit[0-19]: polarity trigger
*
* -PADCTRL_GPIO_IRQ_CTRL[X]
* bit[0-5]: 6 bits to choose gpio source for irq line 2*[X] - 2
* bit[16-21]:6 bits to choose gpio source for irq line 2*[X] - 1
* where X = 1-10
*
* -PADCTRL_GPIO_IRQ_CTRL[11]
* bit[0-19]: both edge trigger
*
* -PADCTRL_GPIO_IRQ_CTRL[12]
* bit[0-19]: single edge trigger
*/
static int meson_a9_ao_gpio_irq_set_type(struct meson_gpio_irq_controller *ctl,
unsigned int type, u32 *channel_hwirq)
{
const struct meson_gpio_irq_params *params = ctl->params;
unsigned int idx;
u32 val;
idx = meson_gpio_irq_get_channel_idx(ctl, channel_hwirq);
type &= IRQ_TYPE_SENSE_MASK;
meson_gpio_irq_update_bits(ctl, params->edge_pol_reg, BIT(idx), 0);
if (type == IRQ_TYPE_EDGE_BOTH) {
val = BIT(ctl->params->edge_both_offset + idx);
meson_gpio_irq_update_bits(ctl, params->edge_pol_reg, val, val);
return 0;
}
val = 0;
if (type & (IRQ_TYPE_LEVEL_LOW | IRQ_TYPE_EDGE_FALLING))
val = BIT(idx);
meson_gpio_irq_update_bits(ctl, REG_A9_AO_POL, BIT(idx), val);
val = 0;
if (type & (IRQ_TYPE_EDGE_RISING | IRQ_TYPE_EDGE_FALLING))
val = BIT(idx);
meson_gpio_irq_update_bits(ctl, REG_A9_AO_EDGE, BIT(idx), val);
return 0;
};
/*
* gpio irq relative registers for s4
* -PADCTRL_GPIO_IRQ_CTRL0
+65 -39
View File
@@ -23,10 +23,10 @@
#define RTL_ICTL_NUM_INPUTS 32
#define REG(x) (realtek_ictl_base + x)
#define REG(cpu, x) (realtek_ictl_base[cpu] + x)
static DEFINE_RAW_SPINLOCK(irq_lock);
static void __iomem *realtek_ictl_base;
static void __iomem *realtek_ictl_base[NR_CPUS];
/*
* IRR0-IRR3 store 4 bits per interrupt, but Realtek uses inverted numbering,
@@ -37,10 +37,29 @@ static void __iomem *realtek_ictl_base;
#define IRR_OFFSET(idx) (4 * (3 - (idx * 4) / 32))
#define IRR_SHIFT(idx) ((idx * 4) % 32)
static void write_irr(void __iomem *irr0, int idx, u32 value)
static inline void enable_gimr(unsigned int cpu, unsigned int hw_irq)
{
unsigned int offset = IRR_OFFSET(idx);
unsigned int shift = IRR_SHIFT(idx);
u32 gimr;
gimr = readl(REG(cpu, RTL_ICTL_GIMR));
gimr |= BIT(hw_irq);
writel(gimr, REG(cpu, RTL_ICTL_GIMR));
}
static inline void disable_gimr(unsigned int cpu, unsigned int hw_irq)
{
u32 gimr;
gimr = readl(REG(cpu, RTL_ICTL_GIMR));
gimr &= ~BIT(hw_irq);
writel(gimr, REG(cpu, RTL_ICTL_GIMR));
}
static void write_irr(unsigned int cpu, int hw_irq, u32 value)
{
void __iomem *irr0 = REG(cpu, RTL_ICTL_IRR0);
unsigned int offset = IRR_OFFSET(hw_irq);
unsigned int shift = IRR_SHIFT(hw_irq);
u32 irr;
irr = readl(irr0 + offset) & ~(0xf << shift);
@@ -50,47 +69,51 @@ static void write_irr(void __iomem *irr0, int idx, u32 value)
static void realtek_ictl_unmask_irq(struct irq_data *i)
{
unsigned long flags;
u32 value;
unsigned int cpu;
raw_spin_lock_irqsave(&irq_lock, flags);
value = readl(REG(RTL_ICTL_GIMR));
value |= BIT(i->hwirq);
writel(value, REG(RTL_ICTL_GIMR));
raw_spin_unlock_irqrestore(&irq_lock, flags);
guard(raw_spinlock)(&irq_lock);
for_each_cpu(cpu, irq_data_get_effective_affinity_mask(i))
enable_gimr(cpu, i->hwirq);
}
static void realtek_ictl_mask_irq(struct irq_data *i)
{
unsigned long flags;
u32 value;
unsigned int cpu;
raw_spin_lock_irqsave(&irq_lock, flags);
guard(raw_spinlock)(&irq_lock);
for_each_cpu(cpu, irq_data_get_effective_affinity_mask(i))
disable_gimr(cpu, i->hwirq);
}
value = readl(REG(RTL_ICTL_GIMR));
value &= ~BIT(i->hwirq);
writel(value, REG(RTL_ICTL_GIMR));
static int realtek_ictl_irq_affinity(struct irq_data *i, const struct cpumask *dest, bool force)
{
if (!irqd_irq_masked(i))
realtek_ictl_mask_irq(i);
raw_spin_unlock_irqrestore(&irq_lock, flags);
irq_data_update_effective_affinity(i, dest);
if (!irqd_irq_masked(i))
realtek_ictl_unmask_irq(i);
return IRQ_SET_MASK_OK;
}
static struct irq_chip realtek_ictl_irq = {
.name = "realtek-rtl-intc",
.irq_mask = realtek_ictl_mask_irq,
.irq_unmask = realtek_ictl_unmask_irq,
.name = "realtek-rtl-intc",
.irq_mask = realtek_ictl_mask_irq,
.irq_unmask = realtek_ictl_unmask_irq,
.irq_set_affinity = realtek_ictl_irq_affinity,
};
static int intc_map(struct irq_domain *d, unsigned int irq, irq_hw_number_t hw)
{
unsigned long flags;
unsigned int cpu;
irq_set_chip_and_handler(irq, &realtek_ictl_irq, handle_level_irq);
raw_spin_lock_irqsave(&irq_lock, flags);
write_irr(REG(RTL_ICTL_IRR0), hw, 1);
raw_spin_unlock_irqrestore(&irq_lock, flags);
guard(raw_spinlock_irqsave)(&irq_lock);
for_each_present_cpu(cpu)
write_irr(cpu, hw, 1);
return 0;
}
@@ -103,12 +126,13 @@ static const struct irq_domain_ops irq_domain_ops = {
static void realtek_irq_dispatch(struct irq_desc *desc)
{
struct irq_chip *chip = irq_desc_get_chip(desc);
unsigned int cpu = smp_processor_id();
struct irq_domain *domain;
unsigned long pending;
unsigned int soc_int;
chained_irq_enter(chip, desc);
pending = readl(REG(RTL_ICTL_GIMR)) & readl(REG(RTL_ICTL_GISR));
pending = readl(REG(cpu, RTL_ICTL_GIMR)) & readl(REG(cpu, RTL_ICTL_GISR));
if (unlikely(!pending)) {
spurious_interrupt();
@@ -116,7 +140,7 @@ static void realtek_irq_dispatch(struct irq_desc *desc)
}
domain = irq_desc_get_handler_data(desc);
for_each_set_bit(soc_int, &pending, 32)
for_each_set_bit(soc_int, &pending, RTL_ICTL_NUM_INPUTS)
generic_handle_domain_irq(domain, soc_int);
out:
@@ -127,17 +151,19 @@ static int __init realtek_rtl_of_init(struct device_node *node, struct device_no
{
struct of_phandle_args oirq;
struct irq_domain *domain;
unsigned int soc_irq;
int parent_irq;
int cpu, parent_irq;
realtek_ictl_base = of_iomap(node, 0);
if (!realtek_ictl_base)
return -ENXIO;
for_each_present_cpu(cpu) {
realtek_ictl_base[cpu] = of_iomap(node, cpu);
if (!realtek_ictl_base[cpu])
return -ENXIO;
/* Disable all cascaded interrupts and clear routing */
writel(0, REG(RTL_ICTL_GIMR));
for (soc_irq = 0; soc_irq < RTL_ICTL_NUM_INPUTS; soc_irq++)
write_irr(REG(RTL_ICTL_IRR0), soc_irq, 0);
/* Disable all cascaded interrupts and clear routing */
for (unsigned int hw_irq = 0; hw_irq < RTL_ICTL_NUM_INPUTS; hw_irq++) {
disable_gimr(cpu, hw_irq);
write_irr(cpu, hw_irq, 0);
}
}
if (WARN_ON(!of_irq_count(node))) {
/*
+264 -4
View File
@@ -2,6 +2,7 @@
#include <linux/bitfield.h>
#include <linux/err.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/irqchip.h>
#include <linux/irqchip/irq-renesas-rzt2h.h>
@@ -30,16 +31,44 @@
RZT2H_ICU_IRQ_S_COUNT)
#define RZT2H_ICU_SEI_COUNT 1
#define RZT2H_ICU_CA55_ERR_START (RZT2H_ICU_SEI_START + \
RZT2H_ICU_SEI_COUNT)
#define RZT2H_ICU_CA55_ERR_COUNT 2
#define RZT2H_ICU_CR52_ERR_START (RZT2H_ICU_CA55_ERR_START + \
RZT2H_ICU_CA55_ERR_COUNT)
#define RZT2H_ICU_CR52_ERR_COUNT 4
#define RZT2H_ICU_PERI_ERR_START (RZT2H_ICU_CR52_ERR_START + \
RZT2H_ICU_CR52_ERR_COUNT)
#define RZT2H_ICU_PERI_ERR_COUNT 2
#define RZT2H_ICU_DSMIF_ERR_START (RZT2H_ICU_PERI_ERR_START + \
RZT2H_ICU_PERI_ERR_COUNT)
#define RZT2H_ICU_DSMIF_ERR_COUNT 2
#define RZT2H_ICU_ENCIF_ERR_START (RZT2H_ICU_DSMIF_ERR_START + \
RZT2H_ICU_DSMIF_ERR_COUNT)
#define RZT2H_ICU_ENCIF_ERR_COUNT 2
#define RZT2H_ICU_NUM_IRQ (RZT2H_ICU_INTCPU_NS_COUNT + \
RZT2H_ICU_INTCPU_S_COUNT + \
RZT2H_ICU_IRQ_NS_COUNT + \
RZT2H_ICU_IRQ_S_COUNT + \
RZT2H_ICU_SEI_COUNT)
RZT2H_ICU_SEI_COUNT + \
RZT2H_ICU_CA55_ERR_COUNT + \
RZT2H_ICU_CR52_ERR_COUNT + \
RZT2H_ICU_PERI_ERR_COUNT + \
RZT2H_ICU_DSMIF_ERR_COUNT + \
RZT2H_ICU_ENCIF_ERR_COUNT)
#define RZT2H_ICU_IRQ_IN_RANGE(n, type) \
((n) >= RZT2H_ICU_##type##_START && \
(n) < RZT2H_ICU_##type##_START + RZT2H_ICU_##type##_COUNT)
#define RZT2H_ICU_SWINT 0x0
#define RZT2H_ICU_SWINT_IC_MASK(i) BIT(i)
#define RZT2H_ICU_PORTNF_MD 0xc
#define RZT2H_ICU_PORTNF_MDi_MASK(i) (GENMASK(1, 0) << ((i) * 2))
#define RZT2H_ICU_PORTNF_MDi_PREP(i, val) (FIELD_PREP(GENMASK(1, 0), val) << ((i) * 2))
@@ -49,6 +78,29 @@
#define RZT2H_ICU_MD_RISING_EDGE 0b10
#define RZT2H_ICU_MD_BOTH_EDGES 0b11
#define RZT2H_ICU_CA55ERR_E0MSK 0x50
#define RZT2H_ICU_CA55ERR_CLR 0x60
#define RZT2H_ICU_CA55ERR_STAT 0x64
#define RZT2H_ICU_CA55ERR_MASK GENMASK(12, 0)
#define RZT2H_ICU_PERIERR_E0MSKn(n) (0x98 + 0x4 * (n))
#define RZT2H_ICU_PERIERR_CLRn(n) (0xc8 + 0x4 * (n))
#define RZT2H_ICU_PERIERR_STAT 0xd4
#define RZT2H_ICU_PERIERR_NUM 3
#define RZT2H_ICU_PERIERR_MASK GENMASK(31, 0)
#define RZT2H_ICU_DSMIFERR_E0MSKn(n) (0xe0 + 0x4 * (n))
#define RZT2H_ICU_DSMIFERR_CLRn(n) (0x1a0 + 0x4 * (n))
#define RZT2H_ICU_DSMIFERR_STAT 0x1d0
#define RZT2H_ICU_DSMIFERR_NUM 12
#define RZT2H_ICU_DSMIFERR_MASK GENMASK(31, 0)
#define RZT2H_ICU_ENCIFERR_E0MSKn(n) (0x200 + 0x4 * (n))
#define RZT2H_ICU_ENCIFERR_CLRn(n) (0x250 + 0x4 * (n))
#define RZT2H_ICU_ENCIFERR_STAT 0x264
#define RZT2H_ICU_ENCIFERR_NUM 5
#define RZT2H_ICU_ENCIFERR_MASK GENMASK(31, 0)
#define RZT2H_ICU_DMACn_RSSELi(n, i) (0x7d0 + 0x18 * (n) + 0x4 * (i))
#define RZT2H_ICU_DMAC_REQ_SELx_MASK(x) (GENMASK(9, 0) << ((x) * 10))
#define RZT2H_ICU_DMAC_REQ_SELx_PREP(x, val) (FIELD_PREP(GENMASK(9, 0), val) << ((x) * 10))
@@ -99,6 +151,12 @@ static inline int rzt2h_icu_irq_to_offset(struct irq_data *d, void __iomem **bas
} else if (RZT2H_ICU_IRQ_IN_RANGE(hwirq, IRQ_S) || RZT2H_ICU_IRQ_IN_RANGE(hwirq, SEI)) {
*offset = hwirq - RZT2H_ICU_IRQ_S_START;
*base = priv->base_s;
} else if (RZT2H_ICU_IRQ_IN_RANGE(hwirq, INTCPU_NS)) {
*offset = hwirq - RZT2H_ICU_INTCPU_NS_START;
*base = priv->base_ns;
} else if (RZT2H_ICU_IRQ_IN_RANGE(hwirq, INTCPU_S)) {
*offset = hwirq - RZT2H_ICU_INTCPU_S_START;
*base = priv->base_s;
} else {
return -EINVAL;
}
@@ -164,6 +222,28 @@ static int rzt2h_icu_set_type(struct irq_data *d, unsigned int type)
return irq_chip_set_type_parent(d, IRQ_TYPE_EDGE_RISING);
}
static int rzt2h_icu_intcpu_set_irqchip_state(struct irq_data *d, enum irqchip_irq_state which,
bool state)
{
unsigned int offset;
void __iomem *base;
int ret;
if (which != IRQCHIP_STATE_PENDING)
return irq_chip_set_parent_state(d, which, state);
if (!state)
return 0;
ret = rzt2h_icu_irq_to_offset(d, &base, &offset);
if (ret)
return ret;
writel_relaxed(RZT2H_ICU_SWINT_IC_MASK(offset), base + RZT2H_ICU_SWINT);
return 0;
}
static const struct irq_chip rzt2h_icu_chip = {
.name = "rzt2h-icu",
.irq_mask = irq_chip_mask_parent,
@@ -180,10 +260,27 @@ static const struct irq_chip rzt2h_icu_chip = {
IRQCHIP_SKIP_SET_WAKE,
};
static const struct irq_chip rzt2h_icu_intcpu_chip = {
.name = "rzt2h-icu",
.irq_mask = irq_chip_mask_parent,
.irq_unmask = irq_chip_unmask_parent,
.irq_eoi = irq_chip_eoi_parent,
.irq_set_type = irq_chip_set_type_parent,
.irq_set_wake = irq_chip_set_wake_parent,
.irq_set_affinity = irq_chip_set_affinity_parent,
.irq_retrigger = irq_chip_retrigger_hierarchy,
.irq_get_irqchip_state = irq_chip_get_parent_state,
.irq_set_irqchip_state = rzt2h_icu_intcpu_set_irqchip_state,
.flags = IRQCHIP_MASK_ON_SUSPEND |
IRQCHIP_SET_TYPE_MASKED |
IRQCHIP_SKIP_SET_WAKE,
};
static int rzt2h_icu_alloc(struct irq_domain *domain, unsigned int virq, unsigned int nr_irqs,
void *arg)
{
struct rzt2h_icu_priv *priv = domain->host_data;
const struct irq_chip *chip;
irq_hw_number_t hwirq;
unsigned int type;
int ret;
@@ -192,7 +289,12 @@ static int rzt2h_icu_alloc(struct irq_domain *domain, unsigned int virq, unsigne
if (ret)
return ret;
ret = irq_domain_set_hwirq_and_chip(domain, virq, hwirq, &rzt2h_icu_chip, NULL);
if (RZT2H_ICU_IRQ_IN_RANGE(hwirq, INTCPU_NS) || RZT2H_ICU_IRQ_IN_RANGE(hwirq, INTCPU_S))
chip = &rzt2h_icu_intcpu_chip;
else
chip = &rzt2h_icu_chip;
ret = irq_domain_set_hwirq_and_chip(domain, virq, hwirq, chip, NULL);
if (ret)
return ret;
@@ -222,6 +324,155 @@ static int rzt2h_icu_parse_interrupts(struct rzt2h_icu_priv *priv, struct device
return 0;
}
static irqreturn_t rzt2h_icu_intcpu_irq(int irq, void *data)
{
unsigned int intcpu = (uintptr_t)data;
pr_info("INTCPU%u software interrupt\n", intcpu);
return IRQ_HANDLED;
}
static irqreturn_t rzt2h_icu_err_irq(struct rzt2h_icu_priv *priv, const char *name,
unsigned int num, u32 stat_base, u32 clr_base)
{
bool handled = false;
for (unsigned int n = 0; n < num; n++) {
u32 stat = readl(priv->base_ns + stat_base + n * 0x4);
if (!stat)
continue;
handled = true;
pr_err("rzt2h-icu: %s error n=%u status=0x%08x\n", name, n, stat);
writel_relaxed(stat, priv->base_ns + clr_base + n * 0x4);
}
return handled ? IRQ_HANDLED : IRQ_NONE;
}
static irqreturn_t rzt2h_icu_ca55_err_irq(int irq, void *data)
{
return rzt2h_icu_err_irq(data, "CA55", 1, RZT2H_ICU_CA55ERR_STAT, RZT2H_ICU_CA55ERR_CLR);
}
static irqreturn_t rzt2h_icu_peri_err_irq(int irq, void *data)
{
return rzt2h_icu_err_irq(data, "peripheral", RZT2H_ICU_PERIERR_NUM, RZT2H_ICU_PERIERR_STAT,
RZT2H_ICU_PERIERR_CLRn(0));
}
static irqreturn_t rzt2h_icu_dsmif_err_irq(int irq, void *data)
{
return rzt2h_icu_err_irq(data, "DSMIF", RZT2H_ICU_DSMIFERR_NUM, RZT2H_ICU_DSMIFERR_STAT,
RZT2H_ICU_DSMIFERR_CLRn(0));
}
static irqreturn_t rzt2h_icu_encif_err_irq(int irq, void *data)
{
return rzt2h_icu_err_irq(data, "ENCIF", RZT2H_ICU_ENCIFERR_NUM, RZT2H_ICU_ENCIFERR_STAT,
RZT2H_ICU_ENCIFERR_CLRn(0));
}
static int rzt2h_icu_request_irqs(struct platform_device *pdev, struct irq_domain *irq_domain,
unsigned int start, unsigned int count, irq_handler_t handler,
void *data)
{
struct device *dev = &pdev->dev;
unsigned int offset, virq;
struct irq_fwspec fwspec;
int ret;
for (offset = start; offset < start + count; offset++) {
fwspec.fwnode = irq_domain->fwnode;
fwspec.param_count = 2;
fwspec.param[0] = offset;
fwspec.param[1] = IRQ_TYPE_EDGE_RISING;
virq = irq_create_fwspec_mapping(&fwspec);
if (!virq)
return dev_err_probe(dev, -EINVAL, "Failed to create IRQ %u mapping\n", offset);
ret = devm_request_irq(dev, virq, handler, 0, dev_name(dev),
data ?: (void *)(uintptr_t)offset);
if (ret)
return dev_err_probe(dev, ret, "Failed to request IRQ %u\n", offset);
}
return 0;
}
static int rzt2h_icu_setup_irqs(struct platform_device *pdev, struct irq_domain *irq_domain)
{
struct rzt2h_icu_priv *priv = platform_get_drvdata(pdev);
unsigned int n;
int ret;
if (IS_ENABLED(CONFIG_GENERIC_IRQ_INJECTION)) {
ret = rzt2h_icu_request_irqs(pdev, irq_domain, RZT2H_ICU_INTCPU_NS_START,
RZT2H_ICU_INTCPU_NS_COUNT, rzt2h_icu_intcpu_irq, NULL);
if (ret)
return ret;
ret = rzt2h_icu_request_irqs(pdev, irq_domain, RZT2H_ICU_INTCPU_S_START,
RZT2H_ICU_INTCPU_S_COUNT, rzt2h_icu_intcpu_irq, NULL);
if (ret)
return ret;
}
/*
* There are two error interrupts and two error masks that can be used
* separately for each error type. It would not be very useful to
* receive two interrupts for the same error, so use only the first one.
*/
ret = rzt2h_icu_request_irqs(pdev, irq_domain, RZT2H_ICU_CA55_ERR_START, 1,
rzt2h_icu_ca55_err_irq, priv);
if (ret)
return ret;
ret = rzt2h_icu_request_irqs(pdev, irq_domain, RZT2H_ICU_PERI_ERR_START, 1,
rzt2h_icu_peri_err_irq, priv);
if (ret)
return ret;
ret = rzt2h_icu_request_irqs(pdev, irq_domain, RZT2H_ICU_DSMIF_ERR_START, 1,
rzt2h_icu_dsmif_err_irq, priv);
if (ret)
return ret;
ret = rzt2h_icu_request_irqs(pdev, irq_domain, RZT2H_ICU_ENCIF_ERR_START, 1,
rzt2h_icu_encif_err_irq, priv);
if (ret)
return ret;
/* Clear and unmask CA55 error events */
writel_relaxed(RZT2H_ICU_CA55ERR_MASK, priv->base_ns + RZT2H_ICU_CA55ERR_CLR);
writel_relaxed(0, priv->base_ns + RZT2H_ICU_CA55ERR_E0MSK);
/* Clear and unmask peripheral error events */
for (n = 0; n < RZT2H_ICU_PERIERR_NUM; n++) {
writel_relaxed(RZT2H_ICU_PERIERR_MASK, priv->base_ns + RZT2H_ICU_PERIERR_CLRn(n));
writel_relaxed(0, priv->base_ns + RZT2H_ICU_PERIERR_E0MSKn(n));
}
/* Clear and unmask DSMIF error events */
for (n = 0; n < RZT2H_ICU_DSMIFERR_NUM; n++) {
writel_relaxed(RZT2H_ICU_DSMIFERR_MASK, priv->base_ns + RZT2H_ICU_DSMIFERR_CLRn(n));
writel_relaxed(0, priv->base_ns + RZT2H_ICU_DSMIFERR_E0MSKn(n));
}
/* Clear and unmask ENCIF error events */
for (n = 0; n < RZT2H_ICU_ENCIFERR_NUM; n++) {
writel_relaxed(RZT2H_ICU_ENCIFERR_MASK, priv->base_ns + RZT2H_ICU_ENCIFERR_CLRn(n));
writel_relaxed(0, priv->base_ns + RZT2H_ICU_ENCIFERR_E0MSKn(n));
}
return 0;
}
static int rzt2h_icu_init(struct platform_device *pdev, struct device_node *parent)
{
struct irq_domain *irq_domain, *parent_domain;
@@ -265,11 +516,20 @@ static int rzt2h_icu_init(struct platform_device *pdev, struct device_node *pare
irq_domain = irq_domain_create_hierarchy(parent_domain, 0, RZT2H_ICU_NUM_IRQ,
dev_fwnode(dev), &rzt2h_icu_domain_ops, priv);
if (!irq_domain) {
pm_runtime_put_sync(dev);
return -ENOMEM;
ret = -ENOMEM;
goto err_pm_put;
}
ret = rzt2h_icu_setup_irqs(pdev, irq_domain);
if (ret)
goto err_irq_domain_free;
return 0;
err_irq_domain_free:
irq_domain_remove(irq_domain);
err_pm_put:
pm_runtime_put_sync(dev);
return ret;
}
IRQCHIP_PLATFORM_DRIVER_BEGIN(rzt2h_icu)
-207
View File
@@ -1,207 +0,0 @@
// SPDX-License-Identifier: GPL-2.0
/*
* StarFive JH8100 External Interrupt Controller driver
*
* Copyright (C) 2023 StarFive Technology Co., Ltd.
*
* Author: Changhuang Liang <changhuang.liang@starfivetech.com>
*/
#define pr_fmt(fmt) "irq-starfive-jh8100: " fmt
#include <linux/bitops.h>
#include <linux/clk.h>
#include <linux/irq.h>
#include <linux/irqchip.h>
#include <linux/irqchip/chained_irq.h>
#include <linux/irqdomain.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
#include <linux/reset.h>
#include <linux/spinlock.h>
#define STARFIVE_INTC_SRC0_CLEAR 0x10
#define STARFIVE_INTC_SRC0_MASK 0x14
#define STARFIVE_INTC_SRC0_INT 0x1c
#define STARFIVE_INTC_SRC_IRQ_NUM 32
struct starfive_irq_chip {
void __iomem *base;
struct irq_domain *domain;
raw_spinlock_t lock;
};
static void starfive_intc_bit_set(struct starfive_irq_chip *irqc,
u32 reg, u32 bit_mask)
{
u32 value;
value = ioread32(irqc->base + reg);
value |= bit_mask;
iowrite32(value, irqc->base + reg);
}
static void starfive_intc_bit_clear(struct starfive_irq_chip *irqc,
u32 reg, u32 bit_mask)
{
u32 value;
value = ioread32(irqc->base + reg);
value &= ~bit_mask;
iowrite32(value, irqc->base + reg);
}
static void starfive_intc_unmask(struct irq_data *d)
{
struct starfive_irq_chip *irqc = irq_data_get_irq_chip_data(d);
raw_spin_lock(&irqc->lock);
starfive_intc_bit_clear(irqc, STARFIVE_INTC_SRC0_MASK, BIT(d->hwirq));
raw_spin_unlock(&irqc->lock);
}
static void starfive_intc_mask(struct irq_data *d)
{
struct starfive_irq_chip *irqc = irq_data_get_irq_chip_data(d);
raw_spin_lock(&irqc->lock);
starfive_intc_bit_set(irqc, STARFIVE_INTC_SRC0_MASK, BIT(d->hwirq));
raw_spin_unlock(&irqc->lock);
}
static struct irq_chip intc_dev = {
.name = "StarFive JH8100 INTC",
.irq_unmask = starfive_intc_unmask,
.irq_mask = starfive_intc_mask,
};
static int starfive_intc_map(struct irq_domain *d, unsigned int irq,
irq_hw_number_t hwirq)
{
irq_domain_set_info(d, irq, hwirq, &intc_dev, d->host_data,
handle_level_irq, NULL, NULL);
return 0;
}
static const struct irq_domain_ops starfive_intc_domain_ops = {
.xlate = irq_domain_xlate_onecell,
.map = starfive_intc_map,
};
static void starfive_intc_irq_handler(struct irq_desc *desc)
{
struct starfive_irq_chip *irqc = irq_data_get_irq_handler_data(&desc->irq_data);
struct irq_chip *chip = irq_desc_get_chip(desc);
unsigned long value;
int hwirq;
chained_irq_enter(chip, desc);
value = ioread32(irqc->base + STARFIVE_INTC_SRC0_INT);
while (value) {
hwirq = ffs(value) - 1;
generic_handle_domain_irq(irqc->domain, hwirq);
starfive_intc_bit_set(irqc, STARFIVE_INTC_SRC0_CLEAR, BIT(hwirq));
starfive_intc_bit_clear(irqc, STARFIVE_INTC_SRC0_CLEAR, BIT(hwirq));
__clear_bit(hwirq, &value);
}
chained_irq_exit(chip, desc);
}
static int starfive_intc_probe(struct platform_device *pdev, struct device_node *parent)
{
struct device_node *intc = pdev->dev.of_node;
struct starfive_irq_chip *irqc;
struct reset_control *rst;
struct clk *clk;
int parent_irq;
int ret;
irqc = kzalloc_obj(*irqc);
if (!irqc)
return -ENOMEM;
irqc->base = of_iomap(intc, 0);
if (!irqc->base) {
pr_err("Unable to map registers\n");
ret = -ENXIO;
goto err_free;
}
rst = of_reset_control_get_exclusive(intc, NULL);
if (IS_ERR(rst)) {
pr_err("Unable to get reset control %pe\n", rst);
ret = PTR_ERR(rst);
goto err_unmap;
}
clk = of_clk_get(intc, 0);
if (IS_ERR(clk)) {
pr_err("Unable to get clock %pe\n", clk);
ret = PTR_ERR(clk);
goto err_reset_put;
}
ret = reset_control_deassert(rst);
if (ret)
goto err_clk_put;
ret = clk_prepare_enable(clk);
if (ret)
goto err_reset_assert;
raw_spin_lock_init(&irqc->lock);
irqc->domain = irq_domain_create_linear(of_fwnode_handle(intc), STARFIVE_INTC_SRC_IRQ_NUM,
&starfive_intc_domain_ops, irqc);
if (!irqc->domain) {
pr_err("Unable to create IRQ domain\n");
ret = -EINVAL;
goto err_clk_disable;
}
parent_irq = of_irq_get(intc, 0);
if (parent_irq < 0) {
pr_err("Failed to get main IRQ: %d\n", parent_irq);
ret = parent_irq;
goto err_remove_domain;
}
irq_set_chained_handler_and_data(parent_irq, starfive_intc_irq_handler,
irqc);
pr_info("Interrupt controller register, nr_irqs %d\n",
STARFIVE_INTC_SRC_IRQ_NUM);
return 0;
err_remove_domain:
irq_domain_remove(irqc->domain);
err_clk_disable:
clk_disable_unprepare(clk);
err_reset_assert:
reset_control_assert(rst);
err_clk_put:
clk_put(clk);
err_reset_put:
reset_control_put(rst);
err_unmap:
iounmap(irqc->base);
err_free:
kfree(irqc);
return ret;
}
IRQCHIP_PLATFORM_DRIVER_BEGIN(starfive_intc)
IRQCHIP_MATCH("starfive,jh8100-intc", starfive_intc_probe)
IRQCHIP_PLATFORM_DRIVER_END(starfive_intc)
MODULE_DESCRIPTION("StarFive JH8100 External Interrupt Controller");
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Changhuang Liang <changhuang.liang@starfivetech.com>");
+254
View File
@@ -0,0 +1,254 @@
// SPDX-License-Identifier: GPL-2.0
/*
* StarFive JHB100 External Interrupt Controller driver
*
* Copyright (C) 2023 StarFive Technology Co., Ltd.
*
* Author: Changhuang Liang <changhuang.liang@starfivetech.com>
*/
#include <linux/bitops.h>
#include <linux/cleanup.h>
#include <linux/clk.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/irqchip.h>
#include <linux/irqchip/chained_irq.h>
#include <linux/irqdomain.h>
#include <linux/of_irq.h>
#include <linux/platform_device.h>
#include <linux/reset.h>
#include <linux/spinlock.h>
#define STARFIVE_INTC_SRC_TYPE(n) (0x04 + ((n) * 0x20))
#define STARFIVE_INTC_SRC_CLEAR(n) (0x10 + ((n) * 0x20))
#define STARFIVE_INTC_SRC_MASK(n) (0x14 + ((n) * 0x20))
#define STARFIVE_INTC_SRC_INT(n) (0x1c + ((n) * 0x20))
#define STARFIVE_INTC_TRIGGER_MASK 0x3
#define STARFIVE_INTC_TRIGGER_HIGH 0
#define STARFIVE_INTC_TRIGGER_LOW 1
#define STARFIVE_INTC_TRIGGER_POSEDGE 2
#define STARFIVE_INTC_TRIGGER_NEGEDGE 3
#define STARFIVE_INTC_NUM 2
#define STARFIVE_INTC_SRC_IRQ_NUM 32
#define STARFIVE_INTC_TYPE_NUM 16
struct starfive_irq_chip {
void __iomem *base;
struct irq_domain *domain;
raw_spinlock_t lock;
};
static void starfive_intc_mod(struct starfive_irq_chip *irqc, u32 reg, u32 mask, u32 data)
{
u32 value;
value = ioread32(irqc->base + reg) & ~mask;
data &= mask;
data |= value;
iowrite32(data, irqc->base + reg);
}
static void starfive_intc_bit_set(struct starfive_irq_chip *irqc,
u32 reg, u32 bit_mask)
{
u32 value;
value = ioread32(irqc->base + reg);
value |= bit_mask;
iowrite32(value, irqc->base + reg);
}
static void starfive_intc_bit_clear(struct starfive_irq_chip *irqc,
u32 reg, u32 bit_mask)
{
u32 value;
value = ioread32(irqc->base + reg);
value &= ~bit_mask;
iowrite32(value, irqc->base + reg);
}
static void starfive_intc_unmask(struct irq_data *d)
{
struct starfive_irq_chip *irqc = irq_data_get_irq_chip_data(d);
int i, bitpos;
i = d->hwirq / STARFIVE_INTC_SRC_IRQ_NUM;
bitpos = d->hwirq % STARFIVE_INTC_SRC_IRQ_NUM;
guard(raw_spinlock)(&irqc->lock);
starfive_intc_bit_clear(irqc, STARFIVE_INTC_SRC_MASK(i), BIT(bitpos));
}
static void starfive_intc_mask(struct irq_data *d)
{
struct starfive_irq_chip *irqc = irq_data_get_irq_chip_data(d);
int i, bitpos;
i = d->hwirq / STARFIVE_INTC_SRC_IRQ_NUM;
bitpos = d->hwirq % STARFIVE_INTC_SRC_IRQ_NUM;
guard(raw_spinlock)(&irqc->lock);
starfive_intc_bit_set(irqc, STARFIVE_INTC_SRC_MASK(i), BIT(bitpos));
}
static void starfive_intc_ack(struct irq_data *d)
{
/* for handle_edge_irq, nothing to do */
}
static int starfive_intc_set_type(struct irq_data *d, unsigned int type)
{
struct starfive_irq_chip *irqc = irq_data_get_irq_chip_data(d);
u32 i, bitpos, ty_pos, ty_shift, trigger, typeval;
irq_flow_handler_t handler;
i = d->hwirq / STARFIVE_INTC_SRC_IRQ_NUM;
bitpos = d->hwirq % STARFIVE_INTC_SRC_IRQ_NUM;
ty_pos = bitpos / STARFIVE_INTC_TYPE_NUM;
ty_shift = (bitpos % STARFIVE_INTC_TYPE_NUM) * 2;
switch (type) {
case IRQF_TRIGGER_LOW:
trigger = STARFIVE_INTC_TRIGGER_LOW;
handler = handle_level_irq;
break;
case IRQF_TRIGGER_HIGH:
trigger = STARFIVE_INTC_TRIGGER_HIGH;
handler = handle_level_irq;
break;
case IRQF_TRIGGER_FALLING:
trigger = STARFIVE_INTC_TRIGGER_NEGEDGE;
handler = handle_edge_irq;
break;
case IRQF_TRIGGER_RISING:
trigger = STARFIVE_INTC_TRIGGER_POSEDGE;
handler = handle_edge_irq;
break;
default:
return -EINVAL;
}
irq_set_handler_locked(d, handler);
typeval = trigger << ty_shift;
guard(raw_spinlock)(&irqc->lock);
starfive_intc_mod(irqc, STARFIVE_INTC_SRC_TYPE(i) + 4 * ty_pos,
STARFIVE_INTC_TRIGGER_MASK << ty_shift, typeval);
/* Once the type is updated, clear interrupt can help to reset the type value */
starfive_intc_bit_set(irqc, STARFIVE_INTC_SRC_CLEAR(i), BIT(bitpos));
starfive_intc_bit_clear(irqc, STARFIVE_INTC_SRC_CLEAR(i), BIT(bitpos));
return 0;
}
static struct irq_chip intc_dev = {
.name = "StarFive JHB100 INTC",
.irq_unmask = starfive_intc_unmask,
.irq_mask = starfive_intc_mask,
.irq_ack = starfive_intc_ack,
.irq_set_type = starfive_intc_set_type,
};
static int starfive_intc_map(struct irq_domain *d, unsigned int irq,
irq_hw_number_t hwirq)
{
irq_domain_set_info(d, irq, hwirq, &intc_dev, d->host_data,
handle_level_irq, NULL, NULL);
return 0;
}
static const struct irq_domain_ops starfive_intc_domain_ops = {
.xlate = irq_domain_xlate_onecell,
.map = starfive_intc_map,
};
static void starfive_intc_irq_handler(struct irq_desc *desc)
{
struct starfive_irq_chip *irqc = irq_data_get_irq_handler_data(&desc->irq_data);
struct irq_chip *chip = irq_desc_get_chip(desc);
unsigned long value;
int hwirq;
chained_irq_enter(chip, desc);
for (int i = 0; i < STARFIVE_INTC_NUM; i++) {
value = ioread32(irqc->base + STARFIVE_INTC_SRC_INT(i));
while (value) {
hwirq = ffs(value) - 1;
generic_handle_domain_irq(irqc->domain,
hwirq + i * STARFIVE_INTC_SRC_IRQ_NUM);
starfive_intc_bit_set(irqc, STARFIVE_INTC_SRC_CLEAR(i), BIT(hwirq));
starfive_intc_bit_clear(irqc, STARFIVE_INTC_SRC_CLEAR(i), BIT(hwirq));
__clear_bit(hwirq, &value);
}
}
chained_irq_exit(chip, desc);
}
static int starfive_intc_probe(struct platform_device *pdev, struct device_node *parent)
{
struct device_node *intc = pdev->dev.of_node;
struct reset_control *rst;
struct clk *clk;
int parent_irq;
struct starfive_irq_chip *irqc __free(kfree) = kzalloc_obj(*irqc);
if (!irqc)
return -ENOMEM;
irqc->base = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(irqc->base))
return dev_err_probe(&pdev->dev, PTR_ERR(irqc->base), "unable to map registers\n");
rst = devm_reset_control_get_optional_exclusive_deasserted(&pdev->dev, NULL);
if (IS_ERR(rst))
return dev_err_probe(&pdev->dev, PTR_ERR(rst),
"Unable to get and deassert reset control\n");
clk = devm_clk_get_optional_enabled(&pdev->dev, NULL);
if (IS_ERR(clk))
return dev_err_probe(&pdev->dev, PTR_ERR(clk), "Unable to get and enable clock\n");
raw_spin_lock_init(&irqc->lock);
irqc->domain = irq_domain_create_linear(of_fwnode_handle(intc),
STARFIVE_INTC_SRC_IRQ_NUM * STARFIVE_INTC_NUM,
&starfive_intc_domain_ops, irqc);
if (!irqc->domain)
return dev_err_probe(&pdev->dev, -EINVAL, "Unable to create IRQ domain\n");
parent_irq = of_irq_get(intc, 0);
if (parent_irq < 0) {
irq_domain_remove(irqc->domain);
return dev_err_probe(&pdev->dev, parent_irq, "Failed to get main IRQ\n");
}
irq_set_chained_handler_and_data(parent_irq, starfive_intc_irq_handler,
irqc);
dev_info(&pdev->dev, "Interrupt controller register, nr_irqs %d\n",
STARFIVE_INTC_SRC_IRQ_NUM * STARFIVE_INTC_NUM);
retain_and_null_ptr(irqc);
return 0;
}
IRQCHIP_PLATFORM_DRIVER_BEGIN(starfive_intc)
IRQCHIP_MATCH("starfive,jhb100-intc", starfive_intc_probe)
IRQCHIP_PLATFORM_DRIVER_END(starfive_intc)
MODULE_DESCRIPTION("StarFive JHB100 External Interrupt Controller");
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Changhuang Liang <changhuang.liang@starfivetech.com>");
+40 -23
View File
@@ -3,6 +3,7 @@
* Copyright (c) 2017-2019, The Linux Foundation. All rights reserved.
*/
#include <linux/bitfield.h>
#include <linux/err.h>
#include <linux/init.h>
#include <linux/interrupt.h>
@@ -21,22 +22,30 @@
#include <linux/types.h>
#define PDC_MAX_GPIO_IRQS 256
#define PDC_DRV_OFFSET 0x10000
#define PDC_DRV_SIZE 0x10000
/* Valid only on HW version < 3.2 */
#define IRQ_ENABLE_BANK 0x10
#define IRQ_ENABLE_BANK_MAX (IRQ_ENABLE_BANK + BITS_TO_BYTES(PDC_MAX_GPIO_IRQS))
#define IRQ_ENABLE_BANK_INDEX_MASK GENMASK(31, 5)
#define IRQ_ENABLE_BANK_BIT_MASK GENMASK(4, 0)
#define IRQ_i_CFG 0x110
/* Valid only on HW version >= 3.2 */
#define IRQ_i_CFG_IRQ_ENABLE 3
#define IRQ_i_CFG_TYPE_MASK GENMASK(2, 0)
#define IRQ_i_CFG_TYPE_MASK GENMASK(2, 0)
#define PDC_VERSION_REG 0x1000
#define PDC_VERSION_REG 0x1000
#define PDC_VERSION_MAJOR GENMASK(23, 16)
#define PDC_VERSION_MINOR GENMASK(15, 8)
#define PDC_VERSION_STEP GENMASK(7, 0)
#define PDC_VERSION(maj, min, step) (FIELD_PREP(PDC_VERSION_MAJOR, (maj)) | \
FIELD_PREP(PDC_VERSION_MINOR, (min)) | \
FIELD_PREP(PDC_VERSION_STEP, (step)))
/* Notable PDC versions */
#define PDC_VERSION_3_2 0x30200
#define PDC_VERSION_3_2 PDC_VERSION(3, 2, 0)
struct pdc_pin_region {
u32 pin_base;
@@ -97,28 +106,37 @@ static void pdc_x1e_irq_enable_write(u32 bank, u32 enable)
pdc_base_reg_write(base, IRQ_ENABLE_BANK, bank, enable);
}
static void __pdc_enable_intr(int pin_out, bool on)
static void pdc_enable_intr_bank(int pin_out, bool on)
{
unsigned long enable;
u32 index, mask;
if (pdc_version < PDC_VERSION_3_2) {
u32 index, mask;
index = FIELD_GET(IRQ_ENABLE_BANK_INDEX_MASK, pin_out);
mask = FIELD_GET(IRQ_ENABLE_BANK_BIT_MASK, pin_out);
index = pin_out / 32;
mask = pin_out % 32;
enable = pdc_reg_read(IRQ_ENABLE_BANK, index);
__assign_bit(mask, &enable, on);
enable = pdc_reg_read(IRQ_ENABLE_BANK, index);
__assign_bit(mask, &enable, on);
if (pdc_x1e_quirk)
pdc_x1e_irq_enable_write(index, enable);
else
pdc_reg_write(IRQ_ENABLE_BANK, index, enable);
}
if (pdc_x1e_quirk)
pdc_x1e_irq_enable_write(index, enable);
else
pdc_reg_write(IRQ_ENABLE_BANK, index, enable);
} else {
enable = pdc_reg_read(IRQ_i_CFG, pin_out);
__assign_bit(IRQ_i_CFG_IRQ_ENABLE, &enable, on);
pdc_reg_write(IRQ_i_CFG, pin_out, enable);
}
static void pdc_enable_intr_cfg(int pin_out, bool on)
{
unsigned long enable = pdc_reg_read(IRQ_i_CFG, pin_out);
__assign_bit(IRQ_i_CFG_IRQ_ENABLE, &enable, on);
pdc_reg_write(IRQ_i_CFG, pin_out, enable);
}
static void __pdc_enable_intr(int pin_out, bool on)
{
if (pdc_version < PDC_VERSION_3_2)
pdc_enable_intr_bank(pin_out, on);
else
pdc_enable_intr_cfg(pin_out, on);
}
static void pdc_enable_intr(struct irq_data *d, bool on)
@@ -348,7 +366,6 @@ static int pdc_setup_pin_mapping(struct device_node *np)
return 0;
}
#define QCOM_PDC_SIZE 0x30000
static int qcom_pdc_probe(struct platform_device *pdev, struct device_node *parent)
{
@@ -362,7 +379,7 @@ static int qcom_pdc_probe(struct platform_device *pdev, struct device_node *pare
if (of_address_to_resource(node, 0, &res))
return -EINVAL;
res_size = max_t(resource_size_t, resource_size(&res), QCOM_PDC_SIZE);
res_size = max_t(resource_size_t, resource_size(&res), PDC_DRV_SIZE);
if (res_size > resource_size(&res))
pr_warn("%pOF: invalid reg size, please fix DT\n", node);
@@ -375,7 +392,7 @@ static int qcom_pdc_probe(struct platform_device *pdev, struct device_node *pare
* region with the expected offset to preserve support for old DTs.
*/
if (of_device_is_compatible(node, "qcom,x1e80100-pdc")) {
pdc_prev_base = ioremap(res.start - PDC_DRV_OFFSET, IRQ_ENABLE_BANK_MAX);
pdc_prev_base = ioremap(res.start - PDC_DRV_SIZE, IRQ_ENABLE_BANK_MAX);
if (!pdc_prev_base) {
pr_err("%pOF: unable to map previous PDC DRV region\n", node);
return -ENXIO;
+3 -1
View File
@@ -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
-4
View File
@@ -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));
+15
View File
@@ -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);
+4 -3
View File
@@ -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) { }
+1
View File
@@ -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);
+1
View File
@@ -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 \
+1 -1
View File
@@ -604,7 +604,7 @@
#include <asm/arch_gicv3.h>
#ifndef __ASSEMBLY__
#ifndef __ASSEMBLER__
/*
* We need a value to serve as a irq-type for LPIs. Choose one that will
+2 -2
View File
@@ -131,7 +131,7 @@
#define GICV_PMR_PRIORITY_SHIFT 3
#define GICV_PMR_PRIORITY_MASK (0x1f << GICV_PMR_PRIORITY_SHIFT)
#ifndef __ASSEMBLY__
#ifndef __ASSEMBLER__
#include <linux/irqdomain.h>
@@ -162,5 +162,5 @@ int gic_get_cpu_id(unsigned int cpu);
void gic_migrate_target(unsigned int new_cpu_id);
unsigned long gic_get_sgir_physaddr(void);
#endif /* __ASSEMBLY */
#endif /* __ASSEMBLER__ */
#endif
+5 -3
View File
@@ -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
View File
@@ -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
View File
@@ -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);
+44
View File
@@ -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
View File
@@ -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