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