Merge f488790059 ("Merge tag 'modules-6.11-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/mcgrof/linux") into android-mainline
Steps on the way to v6.11-rc1 Signed-off-by: Lee Jones <joneslee@google.com> Change-Id: I926d90229b2088665d9953d1d4fecd98552c48e7
This commit is contained in:
@@ -47,6 +47,14 @@ Description:
|
||||
disabled when the feature is used. See
|
||||
Documentation/livepatch/livepatch.rst for more information.
|
||||
|
||||
What: /sys/kernel/livepatch/<patch>/replace
|
||||
Date: Jun 2024
|
||||
KernelVersion: 6.11.0
|
||||
Contact: live-patching@vger.kernel.org
|
||||
Description:
|
||||
An attribute which indicates whether the patch supports
|
||||
atomic-replace.
|
||||
|
||||
What: /sys/kernel/livepatch/<patch>/<object>
|
||||
Date: Nov 2014
|
||||
KernelVersion: 3.19.0
|
||||
|
||||
@@ -410,6 +410,8 @@ which are used in the generic IRQ layer.
|
||||
.. kernel-doc:: include/linux/interrupt.h
|
||||
:internal:
|
||||
|
||||
.. kernel-doc:: include/linux/irqdomain.h
|
||||
|
||||
Public Functions Provided
|
||||
=========================
|
||||
|
||||
|
||||
@@ -57,6 +57,9 @@ properties:
|
||||
last value used.
|
||||
$ref: /schemas/types.yaml#/definitions/uint32
|
||||
|
||||
settle-time-us:
|
||||
description: Delay to wait before doing any transfer when a new bus gets selected.
|
||||
|
||||
allOf:
|
||||
- $ref: i2c-mux.yaml
|
||||
|
||||
|
||||
@@ -31,6 +31,8 @@ properties:
|
||||
- qcom,sm6350-cci
|
||||
- qcom,sm8250-cci
|
||||
- qcom,sm8450-cci
|
||||
- qcom,sm8550-cci
|
||||
- qcom,sm8650-cci
|
||||
- const: qcom,msm8996-cci # CCI v2
|
||||
|
||||
"#address-cells":
|
||||
@@ -195,6 +197,24 @@ allOf:
|
||||
- const: cpas_ahb
|
||||
- const: cci
|
||||
|
||||
- if:
|
||||
properties:
|
||||
compatible:
|
||||
contains:
|
||||
enum:
|
||||
- qcom,sm8550-cci
|
||||
- qcom,sm8650-cci
|
||||
then:
|
||||
properties:
|
||||
clocks:
|
||||
minItems: 3
|
||||
maxItems: 3
|
||||
clock-names:
|
||||
items:
|
||||
- const: camnoc_axi
|
||||
- const: cpas_ahb
|
||||
- const: cci
|
||||
|
||||
additionalProperties: false
|
||||
|
||||
examples:
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
|
||||
%YAML 1.2
|
||||
---
|
||||
$id: http://devicetree.org/schemas/interrupt-controller/microchip,lan966x-oic.yaml#
|
||||
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
|
||||
title: Microchip LAN966x outband interrupt controller
|
||||
|
||||
maintainers:
|
||||
- Herve Codina <herve.codina@bootlin.com>
|
||||
|
||||
allOf:
|
||||
- $ref: /schemas/interrupt-controller.yaml#
|
||||
|
||||
description: |
|
||||
The Microchip LAN966x outband interrupt controller (OIC) maps the internal
|
||||
interrupt sources of the LAN966x device to an external interrupt.
|
||||
When the LAN966x device is used as a PCI device, the external interrupt is
|
||||
routed to the PCI interrupt.
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
const: microchip,lan966x-oic
|
||||
|
||||
'#interrupt-cells':
|
||||
const: 2
|
||||
|
||||
interrupt-controller: true
|
||||
|
||||
reg:
|
||||
maxItems: 1
|
||||
|
||||
interrupts:
|
||||
maxItems: 1
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- '#interrupt-cells'
|
||||
- interrupt-controller
|
||||
- interrupts
|
||||
- reg
|
||||
|
||||
additionalProperties: false
|
||||
|
||||
examples:
|
||||
- |
|
||||
interrupt-controller@e00c0120 {
|
||||
compatible = "microchip,lan966x-oic";
|
||||
reg = <0xe00c0120 0x190>;
|
||||
#interrupt-cells = <2>;
|
||||
interrupt-controller;
|
||||
interrupts = <0>;
|
||||
interrupt-parent = <&intc>;
|
||||
};
|
||||
...
|
||||
@@ -21,13 +21,16 @@ description: |
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
items:
|
||||
- enum:
|
||||
- renesas,r9a07g043u-irqc # RZ/G2UL
|
||||
- renesas,r9a07g044-irqc # RZ/G2{L,LC}
|
||||
- renesas,r9a07g054-irqc # RZ/V2L
|
||||
- renesas,r9a08g045-irqc # RZ/G3S
|
||||
- const: renesas,rzg2l-irqc
|
||||
oneOf:
|
||||
- items:
|
||||
- enum:
|
||||
- renesas,r9a07g043u-irqc # RZ/G2UL
|
||||
- renesas,r9a07g044-irqc # RZ/G2{L,LC}
|
||||
- renesas,r9a07g054-irqc # RZ/V2L
|
||||
- renesas,r9a08g045-irqc # RZ/G3S
|
||||
- const: renesas,rzg2l-irqc
|
||||
|
||||
- const: renesas,r9a07g043f-irqc # RZ/Five
|
||||
|
||||
'#interrupt-cells':
|
||||
description: The first cell should contain a macro RZG2L_{NMI,IRQX} included in the
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
|
||||
%YAML 1.2
|
||||
---
|
||||
$id: http://devicetree.org/schemas/mailbox/mediatek,gce-props.yaml#
|
||||
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
|
||||
title: MediaTek Global Command Engine Common Properties
|
||||
|
||||
maintainers:
|
||||
- Houlong Wei <houlong.wei@mediatek.com>
|
||||
|
||||
description:
|
||||
The Global Command Engine (GCE) is an instruction based, multi-threaded,
|
||||
single-core command dispatcher for MediaTek hardware. The Command Queue
|
||||
(CMDQ) mailbox driver is a driver for GCE, implemented using the Linux
|
||||
mailbox framework. It is used to receive messages from mailbox consumers
|
||||
and configure GCE to execute the specified instruction set in the message.
|
||||
We use mediatek,gce-mailbox.yaml to define the properties for CMDQ mailbox
|
||||
driver. A device driver that uses the CMDQ driver to configure its hardware
|
||||
registers is a mailbox consumer. The mailbox consumer can request a mailbox
|
||||
channel corresponding to a GCE hardware thread to send a message, specifying
|
||||
that the GCE thread to configure its hardware. The mailbox provider can also
|
||||
reserve a mailbox channel to configure GCE hardware register by the specific
|
||||
GCE thread. This binding defines the common GCE properties for both mailbox
|
||||
provider and consumers.
|
||||
|
||||
properties:
|
||||
mediatek,gce-events:
|
||||
description:
|
||||
GCE has an event table in SRAM, consisting of 1024 event IDs (0~1023).
|
||||
Each event ID has a boolean event value with the default value 0.
|
||||
The property mediatek,gce-events is used to obtain the event IDs.
|
||||
Some gce-events are hardware-bound and cannot be changed by software.
|
||||
For instance, in MT8195, when VDO0_MUTEX is stream done, VDO_MUTEX will
|
||||
send an event signal to GCE, setting the value of event ID 597 to 1.
|
||||
Similarly, in MT8188, the value of event ID 574 will be set to 1 when
|
||||
VOD0_MUTEX is stream done.
|
||||
On the other hand, some gce-events are not hardware-bound and can be
|
||||
changed by software. For example, in MT8188, we can set the value of
|
||||
event ID 855, which is not bound to any hardware, to 1 when the driver
|
||||
in the secure world completes a task. However, in MT8195, event ID 855
|
||||
is already bound to VDEC_LAT1, so we need to select another event ID to
|
||||
achieve the same purpose. This event ID can be any ID that is not bound
|
||||
to any hardware and is not yet used in any software driver.
|
||||
To determine if the event ID is bound to the hardware or used by a
|
||||
software driver, refer to the GCE header
|
||||
include/dt-bindings/gce/<chip>-gce.h of each chip.
|
||||
$ref: /schemas/types.yaml#/definitions/uint32-array
|
||||
minItems: 1
|
||||
maxItems: 32
|
||||
|
||||
additionalProperties: true
|
||||
@@ -0,0 +1,49 @@
|
||||
# SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause
|
||||
%YAML 1.2
|
||||
---
|
||||
$id: http://devicetree.org/schemas/mailbox/qcom,cpucp-mbox.yaml#
|
||||
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
|
||||
title: Qualcomm Technologies, Inc. CPUCP Mailbox Controller
|
||||
|
||||
maintainers:
|
||||
- Sibi Sankar <quic_sibis@quicinc.com>
|
||||
|
||||
description:
|
||||
The CPUSS Control Processor (CPUCP) mailbox controller enables communication
|
||||
between AP and CPUCP by acting as a doorbell between them.
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
items:
|
||||
- const: qcom,x1e80100-cpucp-mbox
|
||||
|
||||
reg:
|
||||
items:
|
||||
- description: CPUCP rx register region
|
||||
- description: CPUCP tx register region
|
||||
|
||||
interrupts:
|
||||
maxItems: 1
|
||||
|
||||
"#mbox-cells":
|
||||
const: 1
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- reg
|
||||
- interrupts
|
||||
- "#mbox-cells"
|
||||
|
||||
additionalProperties: false
|
||||
|
||||
examples:
|
||||
- |
|
||||
#include <dt-bindings/interrupt-controller/arm-gic.h>
|
||||
|
||||
mailbox@17430000 {
|
||||
compatible = "qcom,x1e80100-cpucp-mbox";
|
||||
reg = <0x17430000 0x10000>, <0x18830000 0x10000>;
|
||||
interrupts = <GIC_SPI 28 IRQ_TYPE_LEVEL_HIGH>;
|
||||
#mbox-cells = <1>;
|
||||
};
|
||||
@@ -0,0 +1,58 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
|
||||
%YAML 1.2
|
||||
---
|
||||
$id: http://devicetree.org/schemas/power/supply/maxim,max17201.yaml#
|
||||
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
|
||||
title: Maxim MAX17201 fuel gauge
|
||||
|
||||
maintainers:
|
||||
- Dimitri Fedrau <dima.fedrau@gmail.com>
|
||||
|
||||
allOf:
|
||||
- $ref: power-supply.yaml#
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
oneOf:
|
||||
- const: maxim,max17201
|
||||
- items:
|
||||
- enum:
|
||||
- maxim,max17205
|
||||
- const: maxim,max17201
|
||||
|
||||
reg:
|
||||
items:
|
||||
- description: ModelGauge m5 registers
|
||||
- description: Nonvolatile registers
|
||||
|
||||
reg-names:
|
||||
items:
|
||||
- const: m5
|
||||
- const: nvmem
|
||||
|
||||
interrupts:
|
||||
maxItems: 1
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- reg
|
||||
- reg-names
|
||||
|
||||
unevaluatedProperties: false
|
||||
|
||||
examples:
|
||||
- |
|
||||
#include <dt-bindings/interrupt-controller/irq.h>
|
||||
i2c {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
fuel-gauge@36 {
|
||||
compatible = "maxim,max17201";
|
||||
reg = <0x36>, <0xb>;
|
||||
reg-names = "m5", "nvmem";
|
||||
interrupt-parent = <&gpio0>;
|
||||
interrupts = <31 IRQ_TYPE_LEVEL_LOW>;
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,160 @@
|
||||
# SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause
|
||||
%YAML 1.2
|
||||
---
|
||||
$id: http://devicetree.org/schemas/remoteproc/qcom,sa8775p-pas.yaml#
|
||||
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
|
||||
title: Qualcomm SA8775p Peripheral Authentication Service
|
||||
|
||||
maintainers:
|
||||
- Bartosz Golaszewski <bartosz.golaszewski@linaro.org>
|
||||
|
||||
description:
|
||||
Qualcomm SA8775p SoC Peripheral Authentication Service loads and boots firmware
|
||||
on the Qualcomm DSP Hexagon cores.
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
enum:
|
||||
- qcom,sa8775p-adsp-pas
|
||||
- qcom,sa8775p-cdsp0-pas
|
||||
- qcom,sa8775p-cdsp1-pas
|
||||
- qcom,sa8775p-gpdsp0-pas
|
||||
- qcom,sa8775p-gpdsp1-pas
|
||||
|
||||
reg:
|
||||
maxItems: 1
|
||||
|
||||
clocks:
|
||||
items:
|
||||
- description: XO clock
|
||||
|
||||
clock-names:
|
||||
items:
|
||||
- const: xo
|
||||
|
||||
qcom,qmp:
|
||||
$ref: /schemas/types.yaml#/definitions/phandle
|
||||
description: Reference to the AOSS side-channel message RAM.
|
||||
|
||||
firmware-name:
|
||||
$ref: /schemas/types.yaml#/definitions/string-array
|
||||
items:
|
||||
- description: Firmware name of the Hexagon core
|
||||
|
||||
memory-region:
|
||||
items:
|
||||
- description: Memory region for main Firmware authentication
|
||||
|
||||
interrupts:
|
||||
maxItems: 5
|
||||
|
||||
interrupt-names:
|
||||
maxItems: 5
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- reg
|
||||
- memory-region
|
||||
|
||||
allOf:
|
||||
- $ref: /schemas/remoteproc/qcom,pas-common.yaml#
|
||||
|
||||
- if:
|
||||
properties:
|
||||
compatible:
|
||||
enum:
|
||||
- qcom,sa8775p-adsp-pas
|
||||
then:
|
||||
properties:
|
||||
power-domains:
|
||||
items:
|
||||
- description: LCX power domain
|
||||
- description: LMX power domain
|
||||
power-domain-names:
|
||||
items:
|
||||
- const: lcx
|
||||
- const: lmx
|
||||
|
||||
- if:
|
||||
properties:
|
||||
compatible:
|
||||
enum:
|
||||
- qcom,sa8775p-cdsp0-pas
|
||||
- qcom,sa8775p-cdsp1-pas
|
||||
then:
|
||||
properties:
|
||||
power-domains:
|
||||
items:
|
||||
- description: CX power domain
|
||||
- description: MXC power domain
|
||||
- description: NSP0 power domain
|
||||
power-domain-names:
|
||||
items:
|
||||
- const: cx
|
||||
- const: mxc
|
||||
- const: nsp
|
||||
|
||||
- if:
|
||||
properties:
|
||||
compatible:
|
||||
enum:
|
||||
- qcom,sa8775p-gpdsp0-pas
|
||||
- qcom,sa8775p-gpdsp1-pas
|
||||
then:
|
||||
properties:
|
||||
power-domains:
|
||||
items:
|
||||
- description: CX power domain
|
||||
- description: MXC power domain
|
||||
power-domain-names:
|
||||
items:
|
||||
- const: cx
|
||||
- const: mxc
|
||||
|
||||
unevaluatedProperties: false
|
||||
|
||||
examples:
|
||||
- |
|
||||
#include <dt-bindings/clock/qcom,rpmh.h>
|
||||
#include <dt-bindings/interconnect/qcom,sa8775p-rpmh.h>
|
||||
#include <dt-bindings/interrupt-controller/irq.h>
|
||||
#include <dt-bindings/mailbox/qcom-ipcc.h>
|
||||
#include <dt-bindings/power/qcom,rpmhpd.h>
|
||||
|
||||
remoteproc@30000000 {
|
||||
compatible = "qcom,sa8775p-adsp-pas";
|
||||
reg = <0x30000000 0x100>;
|
||||
|
||||
interrupts-extended = <&pdc 6 IRQ_TYPE_EDGE_RISING>,
|
||||
<&smp2p_adsp_in 0 IRQ_TYPE_EDGE_RISING>,
|
||||
<&smp2p_adsp_in 2 IRQ_TYPE_EDGE_RISING>,
|
||||
<&smp2p_adsp_in 1 IRQ_TYPE_EDGE_RISING>,
|
||||
<&smp2p_adsp_in 3 IRQ_TYPE_EDGE_RISING>;
|
||||
interrupt-names = "wdog", "fatal", "ready", "handover", "stop-ack";
|
||||
|
||||
clocks = <&rpmhcc RPMH_CXO_CLK>;
|
||||
clock-names = "xo";
|
||||
|
||||
power-domains = <&rpmhpd RPMHPD_LCX>, <&rpmhpd RPMHPD_LMX>;
|
||||
power-domain-names = "lcx", "lmx";
|
||||
|
||||
interconnects = <&lpass_ag_noc MASTER_LPASS_PROC 0 &mc_virt SLAVE_EBI1 0>;
|
||||
|
||||
memory-region = <&pil_adsp_mem>;
|
||||
|
||||
qcom,qmp = <&aoss_qmp>;
|
||||
|
||||
qcom,smem-states = <&smp2p_adsp_out 0>;
|
||||
qcom,smem-state-names = "stop";
|
||||
|
||||
glink-edge {
|
||||
interrupts-extended = <&ipcc IPCC_CLIENT_LPASS
|
||||
IPCC_MPROC_SIGNAL_GLINK_QMP
|
||||
IRQ_TYPE_EDGE_RISING>;
|
||||
mboxes = <&ipcc IPCC_CLIENT_LPASS IPCC_MPROC_SIGNAL_GLINK_QMP>;
|
||||
|
||||
label = "lpass";
|
||||
qcom,remote-pid = <2>;
|
||||
};
|
||||
};
|
||||
@@ -12,7 +12,7 @@
|
||||
| arm64: | ok |
|
||||
| csky: | TODO |
|
||||
| hexagon: | TODO |
|
||||
| loongarch: | TODO |
|
||||
| loongarch: | ok |
|
||||
| m68k: | TODO |
|
||||
| microblaze: | TODO |
|
||||
| mips: | TODO |
|
||||
|
||||
+13
@@ -14949,6 +14949,12 @@ L: netdev@vger.kernel.org
|
||||
S: Maintained
|
||||
F: drivers/net/ethernet/microchip/lan966x/*
|
||||
|
||||
MICROCHIP LAN966X OIC DRIVER
|
||||
M: Herve Codina <herve.codina@bootlin.com>
|
||||
S: Maintained
|
||||
F: Documentation/devicetree/bindings/interrupt-controller/microchip,lan966x-oic.yaml
|
||||
F: drivers/irqchip/irq-lan966x-oic.c
|
||||
|
||||
MICROCHIP LCDFB DRIVER
|
||||
M: Nicolas Ferre <nicolas.ferre@microchip.com>
|
||||
L: linux-fbdev@vger.kernel.org
|
||||
@@ -18750,6 +18756,13 @@ S: Maintained
|
||||
F: Documentation/devicetree/bindings/power/avs/qcom,cpr.yaml
|
||||
F: drivers/pmdomain/qcom/cpr.c
|
||||
|
||||
QUALCOMM CPUCP MAILBOX DRIVER
|
||||
M: Sibi Sankar <quic_sibis@quicinc.com>
|
||||
L: linux-arm-msm@vger.kernel.org
|
||||
S: Supported
|
||||
F: Documentation/devicetree/bindings/mailbox/qcom,cpucp-mbox.yaml
|
||||
F: drivers/mailbox/qcom-cpucp-mbox.c
|
||||
|
||||
QUALCOMM CPUFREQ DRIVER MSM8996/APQ8096
|
||||
M: Ilia Lin <ilia.lin@kernel.org>
|
||||
L: linux-pm@vger.kernel.org
|
||||
|
||||
@@ -11,7 +11,7 @@ menuconfig ARCH_STM32
|
||||
select CLKSRC_STM32
|
||||
select PINCTRL
|
||||
select RESET_CONTROLLER
|
||||
select STM32_EXTI
|
||||
select STM32_EXTI if ARM_SINGLE_ARMV7M
|
||||
select STM32_FIREWALL
|
||||
help
|
||||
Support for STMicroelectronics STM32 processors.
|
||||
|
||||
@@ -309,7 +309,6 @@ config ARCH_STM32
|
||||
select GPIOLIB
|
||||
select PINCTRL
|
||||
select PINCTRL_STM32MP257
|
||||
select STM32_EXTI
|
||||
select ARM_SMC_MBOX
|
||||
select ARM_SCMI_PROTOCOL
|
||||
select REGULATOR
|
||||
|
||||
@@ -16,12 +16,14 @@ config LOONGARCH
|
||||
select ARCH_HAS_ACPI_TABLE_UPGRADE if ACPI
|
||||
select ARCH_HAS_CPU_FINALIZE_INIT
|
||||
select ARCH_HAS_CURRENT_STACK_POINTER
|
||||
select ARCH_HAS_DEBUG_VM_PGTABLE
|
||||
select ARCH_HAS_FAST_MULTIPLIER
|
||||
select ARCH_HAS_FORTIFY_SOURCE
|
||||
select ARCH_HAS_KCOV
|
||||
select ARCH_HAS_KERNEL_FPU_SUPPORT if CPU_HAS_FPU
|
||||
select ARCH_HAS_NMI_SAFE_THIS_CPU_OPS
|
||||
select ARCH_HAS_NON_OVERLAPPING_ADDRESS_SPACE
|
||||
select ARCH_HAS_PTE_DEVMAP
|
||||
select ARCH_HAS_PTE_SPECIAL
|
||||
select ARCH_HAS_TICK_BROADCAST if GENERIC_CLOCKEVENTS_BROADCAST
|
||||
select ARCH_INLINE_READ_LOCK if !PREEMPTION
|
||||
@@ -106,6 +108,7 @@ config LOONGARCH
|
||||
select HAVE_ARCH_KFENCE
|
||||
select HAVE_ARCH_KGDB if PERF_EVENTS
|
||||
select HAVE_ARCH_MMAP_RND_BITS if MMU
|
||||
select HAVE_ARCH_RANDOMIZE_KSTACK_OFFSET
|
||||
select HAVE_ARCH_SECCOMP
|
||||
select HAVE_ARCH_SECCOMP_FILTER
|
||||
select HAVE_ARCH_TRACEHOOK
|
||||
@@ -607,6 +610,7 @@ config ARCH_HAS_GENERIC_CRASHKERNEL_RESERVATION
|
||||
|
||||
config RELOCATABLE
|
||||
bool "Relocatable kernel"
|
||||
select ARCH_HAS_RELR
|
||||
help
|
||||
This builds the kernel as a Position Independent Executable (PIE),
|
||||
which retains all relocation metadata required, so as to relocate
|
||||
@@ -710,6 +714,7 @@ config ARCH_HIBERNATION_POSSIBLE
|
||||
|
||||
source "kernel/power/Kconfig"
|
||||
source "drivers/acpi/Kconfig"
|
||||
source "drivers/cpufreq/Kconfig"
|
||||
|
||||
endmenu
|
||||
|
||||
|
||||
@@ -105,7 +105,8 @@ KBUILD_CFLAGS += -fno-jump-tables
|
||||
endif
|
||||
|
||||
KBUILD_RUSTFLAGS += --target=loongarch64-unknown-none-softfloat
|
||||
KBUILD_RUSTFLAGS_MODULE += -Crelocation-model=pic
|
||||
KBUILD_RUSTFLAGS_KERNEL += -Zdirect-access-external-data=yes
|
||||
KBUILD_RUSTFLAGS_MODULE += -Zdirect-access-external-data=no
|
||||
|
||||
ifeq ($(CONFIG_RELOCATABLE),y)
|
||||
KBUILD_CFLAGS_KERNEL += -fPIE
|
||||
|
||||
@@ -37,6 +37,10 @@ extern unsigned long vm_map_base;
|
||||
#define UNCACHE_BASE CSR_DMW0_BASE
|
||||
#endif
|
||||
|
||||
#ifndef WRITECOMBINE_BASE
|
||||
#define WRITECOMBINE_BASE CSR_DMW2_BASE
|
||||
#endif
|
||||
|
||||
#define DMW_PABITS 48
|
||||
#define TO_PHYS_MASK ((1ULL << DMW_PABITS) - 1)
|
||||
|
||||
|
||||
@@ -609,6 +609,7 @@
|
||||
lu32i.d \reg, 0
|
||||
lu52i.d \reg, \reg, 0
|
||||
.pushsection ".la_abs", "aw", %progbits
|
||||
.p2align 3
|
||||
.dword 766b
|
||||
.dword \sym
|
||||
.popsection
|
||||
|
||||
@@ -12,11 +12,12 @@
|
||||
extern void ack_bad_irq(unsigned int irq);
|
||||
#define ack_bad_irq ack_bad_irq
|
||||
|
||||
#define NR_IPI 2
|
||||
#define NR_IPI 3
|
||||
|
||||
enum ipi_msg_type {
|
||||
IPI_RESCHEDULE,
|
||||
IPI_CALL_FUNCTION,
|
||||
IPI_IRQ_WORK,
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
|
||||
@@ -532,6 +532,9 @@ static inline void emit_##NAME(union loongarch_instruction *insn, \
|
||||
|
||||
DEF_EMIT_REG0I15_FORMAT(break, break_op)
|
||||
|
||||
/* like emit_break(imm) but returns a constant expression */
|
||||
#define __emit_break(imm) ((u32)((imm) | (break_op << 15)))
|
||||
|
||||
#define DEF_EMIT_REG0I26_FORMAT(NAME, OP) \
|
||||
static inline void emit_##NAME(union loongarch_instruction *insn, \
|
||||
int offset) \
|
||||
|
||||
@@ -25,10 +25,16 @@ extern void __init early_iounmap(void __iomem *addr, unsigned long size);
|
||||
static inline void __iomem *ioremap_prot(phys_addr_t offset, unsigned long size,
|
||||
unsigned long prot_val)
|
||||
{
|
||||
if (prot_val & _CACHE_CC)
|
||||
switch (prot_val & _CACHE_MASK) {
|
||||
case _CACHE_CC:
|
||||
return (void __iomem *)(unsigned long)(CACHE_BASE + offset);
|
||||
else
|
||||
case _CACHE_SUC:
|
||||
return (void __iomem *)(unsigned long)(UNCACHE_BASE + offset);
|
||||
case _CACHE_WUC:
|
||||
return (void __iomem *)(unsigned long)(WRITECOMBINE_BASE + offset);
|
||||
default:
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
#define ioremap(offset, size) \
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
#ifndef _ASM_LOONGARCH_IRQ_WORK_H
|
||||
#define _ASM_LOONGARCH_IRQ_WORK_H
|
||||
|
||||
static inline bool arch_irq_work_has_interrupt(void)
|
||||
{
|
||||
return IS_ENABLED(CONFIG_SMP);
|
||||
}
|
||||
|
||||
#endif /* _ASM_LOONGARCH_IRQ_WORK_H */
|
||||
@@ -878,7 +878,7 @@
|
||||
#define LOONGARCH_CSR_DMWIN2 0x182 /* 64 direct map win2: MEM */
|
||||
#define LOONGARCH_CSR_DMWIN3 0x183 /* 64 direct map win3: MEM */
|
||||
|
||||
/* Direct Map window 0/1 */
|
||||
/* Direct Map window 0/1/2/3 */
|
||||
#define CSR_DMW0_PLV0 _CONST64_(1 << 0)
|
||||
#define CSR_DMW0_VSEG _CONST64_(0x8000)
|
||||
#define CSR_DMW0_BASE (CSR_DMW0_VSEG << DMW_PABITS)
|
||||
@@ -890,6 +890,14 @@
|
||||
#define CSR_DMW1_BASE (CSR_DMW1_VSEG << DMW_PABITS)
|
||||
#define CSR_DMW1_INIT (CSR_DMW1_BASE | CSR_DMW1_MAT | CSR_DMW1_PLV0)
|
||||
|
||||
#define CSR_DMW2_PLV0 _CONST64_(1 << 0)
|
||||
#define CSR_DMW2_MAT _CONST64_(2 << 4)
|
||||
#define CSR_DMW2_VSEG _CONST64_(0xa000)
|
||||
#define CSR_DMW2_BASE (CSR_DMW2_VSEG << DMW_PABITS)
|
||||
#define CSR_DMW2_INIT (CSR_DMW2_BASE | CSR_DMW2_MAT | CSR_DMW2_PLV0)
|
||||
|
||||
#define CSR_DMW3_INIT 0x0
|
||||
|
||||
/* Performance Counter registers */
|
||||
#define LOONGARCH_CSR_PERFCTRL0 0x200 /* 32 perf event 0 config */
|
||||
#define LOONGARCH_CSR_PERFCNTR0 0x201 /* 64 perf event 0 count value */
|
||||
@@ -1054,11 +1062,14 @@
|
||||
#define LOONGARCH_IOCSR_NODECNT 0x408
|
||||
|
||||
#define LOONGARCH_IOCSR_MISC_FUNC 0x420
|
||||
#define IOCSR_MISC_FUNC_SOFT_INT BIT_ULL(10)
|
||||
#define IOCSR_MISC_FUNC_TIMER_RESET BIT_ULL(21)
|
||||
#define IOCSR_MISC_FUNC_EXT_IOI_EN BIT_ULL(48)
|
||||
|
||||
#define LOONGARCH_IOCSR_CPUTEMP 0x428
|
||||
|
||||
#define LOONGARCH_IOCSR_SMCMBX 0x51c
|
||||
|
||||
/* PerCore CSR, only accessible by local cores */
|
||||
#define LOONGARCH_IOCSR_IPI_STATUS 0x1000
|
||||
#define LOONGARCH_IOCSR_IPI_EN 0x1004
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
#define _PAGE_PFN_SHIFT 12
|
||||
#define _PAGE_SWP_EXCLUSIVE_SHIFT 23
|
||||
#define _PAGE_PFN_END_SHIFT 48
|
||||
#define _PAGE_DEVMAP_SHIFT 59
|
||||
#define _PAGE_PRESENT_INVALID_SHIFT 60
|
||||
#define _PAGE_NO_READ_SHIFT 61
|
||||
#define _PAGE_NO_EXEC_SHIFT 62
|
||||
@@ -35,6 +36,7 @@
|
||||
#define _PAGE_MODIFIED (_ULCAST_(1) << _PAGE_MODIFIED_SHIFT)
|
||||
#define _PAGE_PROTNONE (_ULCAST_(1) << _PAGE_PROTNONE_SHIFT)
|
||||
#define _PAGE_SPECIAL (_ULCAST_(1) << _PAGE_SPECIAL_SHIFT)
|
||||
#define _PAGE_DEVMAP (_ULCAST_(1) << _PAGE_DEVMAP_SHIFT)
|
||||
|
||||
/* We borrow bit 23 to store the exclusive marker in swap PTEs. */
|
||||
#define _PAGE_SWP_EXCLUSIVE (_ULCAST_(1) << _PAGE_SWP_EXCLUSIVE_SHIFT)
|
||||
@@ -74,8 +76,8 @@
|
||||
#define __READABLE (_PAGE_VALID)
|
||||
#define __WRITEABLE (_PAGE_DIRTY | _PAGE_WRITE)
|
||||
|
||||
#define _PAGE_CHG_MASK (_PAGE_MODIFIED | _PAGE_SPECIAL | _PFN_MASK | _CACHE_MASK | _PAGE_PLV)
|
||||
#define _HPAGE_CHG_MASK (_PAGE_MODIFIED | _PAGE_SPECIAL | _PFN_MASK | _CACHE_MASK | _PAGE_PLV | _PAGE_HUGE)
|
||||
#define _PAGE_CHG_MASK (_PAGE_MODIFIED | _PAGE_SPECIAL | _PAGE_DEVMAP | _PFN_MASK | _CACHE_MASK | _PAGE_PLV)
|
||||
#define _HPAGE_CHG_MASK (_PAGE_MODIFIED | _PAGE_SPECIAL | _PAGE_DEVMAP | _PFN_MASK | _CACHE_MASK | _PAGE_PLV | _PAGE_HUGE)
|
||||
|
||||
#define PAGE_NONE __pgprot(_PAGE_PROTNONE | _PAGE_NO_READ | \
|
||||
_PAGE_USER | _CACHE_CC)
|
||||
|
||||
@@ -424,6 +424,9 @@ static inline int pte_special(pte_t pte) { return pte_val(pte) & _PAGE_SPECIAL;
|
||||
static inline pte_t pte_mkspecial(pte_t pte) { pte_val(pte) |= _PAGE_SPECIAL; return pte; }
|
||||
#endif /* CONFIG_ARCH_HAS_PTE_SPECIAL */
|
||||
|
||||
static inline int pte_devmap(pte_t pte) { return !!(pte_val(pte) & _PAGE_DEVMAP); }
|
||||
static inline pte_t pte_mkdevmap(pte_t pte) { pte_val(pte) |= _PAGE_DEVMAP; return pte; }
|
||||
|
||||
#define pte_accessible pte_accessible
|
||||
static inline unsigned long pte_accessible(struct mm_struct *mm, pte_t a)
|
||||
{
|
||||
@@ -558,6 +561,17 @@ static inline pmd_t pmd_mkyoung(pmd_t pmd)
|
||||
return pmd;
|
||||
}
|
||||
|
||||
static inline int pmd_devmap(pmd_t pmd)
|
||||
{
|
||||
return !!(pmd_val(pmd) & _PAGE_DEVMAP);
|
||||
}
|
||||
|
||||
static inline pmd_t pmd_mkdevmap(pmd_t pmd)
|
||||
{
|
||||
pmd_val(pmd) |= _PAGE_DEVMAP;
|
||||
return pmd;
|
||||
}
|
||||
|
||||
static inline struct page *pmd_page(pmd_t pmd)
|
||||
{
|
||||
if (pmd_trans_huge(pmd))
|
||||
@@ -613,6 +627,11 @@ static inline long pmd_protnone(pmd_t pmd)
|
||||
#define pmd_leaf(pmd) ((pmd_val(pmd) & _PAGE_HUGE) != 0)
|
||||
#define pud_leaf(pud) ((pud_val(pud) & _PAGE_HUGE) != 0)
|
||||
|
||||
#ifdef CONFIG_TRANSPARENT_HUGEPAGE
|
||||
#define pud_devmap(pud) (0)
|
||||
#define pgd_devmap(pgd) (0)
|
||||
#endif /* CONFIG_TRANSPARENT_HUGEPAGE */
|
||||
|
||||
/*
|
||||
* We provide our own get_unmapped area to cope with the virtual aliasing
|
||||
* constraints placed on us by the cache architecture.
|
||||
|
||||
@@ -34,6 +34,11 @@ extern long __la_abs_end;
|
||||
extern long __rela_dyn_begin;
|
||||
extern long __rela_dyn_end;
|
||||
|
||||
#ifdef CONFIG_RELR
|
||||
extern long __relr_dyn_begin;
|
||||
extern long __relr_dyn_end;
|
||||
#endif
|
||||
|
||||
extern unsigned long __init relocate_kernel(void);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -69,9 +69,11 @@ extern int __cpu_logical_map[NR_CPUS];
|
||||
#define ACTION_BOOT_CPU 0
|
||||
#define ACTION_RESCHEDULE 1
|
||||
#define ACTION_CALL_FUNCTION 2
|
||||
#define ACTION_IRQ_WORK 3
|
||||
#define SMP_BOOT_CPU BIT(ACTION_BOOT_CPU)
|
||||
#define SMP_RESCHEDULE BIT(ACTION_RESCHEDULE)
|
||||
#define SMP_CALL_FUNCTION BIT(ACTION_CALL_FUNCTION)
|
||||
#define SMP_IRQ_WORK BIT(ACTION_IRQ_WORK)
|
||||
|
||||
struct secondary_data {
|
||||
unsigned long stack;
|
||||
|
||||
@@ -38,6 +38,17 @@
|
||||
cfi_restore \reg \offset \docfi
|
||||
.endm
|
||||
|
||||
.macro SETUP_DMWINS temp
|
||||
li.d \temp, CSR_DMW0_INIT # WUC, PLV0, 0x8000 xxxx xxxx xxxx
|
||||
csrwr \temp, LOONGARCH_CSR_DMWIN0
|
||||
li.d \temp, CSR_DMW1_INIT # CAC, PLV0, 0x9000 xxxx xxxx xxxx
|
||||
csrwr \temp, LOONGARCH_CSR_DMWIN1
|
||||
li.d \temp, CSR_DMW2_INIT # WUC, PLV0, 0xa000 xxxx xxxx xxxx
|
||||
csrwr \temp, LOONGARCH_CSR_DMWIN2
|
||||
li.d \temp, CSR_DMW3_INIT # 0x0, unused
|
||||
csrwr \temp, LOONGARCH_CSR_DMWIN3
|
||||
.endm
|
||||
|
||||
/* Jump to the runtime virtual address. */
|
||||
.macro JUMP_VIRT_ADDR temp1 temp2
|
||||
li.d \temp1, CACHE_BASE
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
#include <uapi/asm/unistd.h>
|
||||
|
||||
#define __ARCH_WANT_NEW_STAT
|
||||
#define __ARCH_WANT_SYS_CLONE
|
||||
|
||||
#define NR_syscalls (__NR_syscalls)
|
||||
|
||||
@@ -9,10 +9,10 @@ typedef u32 uprobe_opcode_t;
|
||||
#define MAX_UINSN_BYTES 8
|
||||
#define UPROBE_XOL_SLOT_BYTES MAX_UINSN_BYTES
|
||||
|
||||
#define UPROBE_SWBP_INSN larch_insn_gen_break(BRK_UPROBE_BP)
|
||||
#define UPROBE_SWBP_INSN __emit_break(BRK_UPROBE_BP)
|
||||
#define UPROBE_SWBP_INSN_SIZE LOONGARCH_INSN_SIZE
|
||||
|
||||
#define UPROBE_XOLBP_INSN larch_insn_gen_break(BRK_UPROBE_XOLBP)
|
||||
#define UPROBE_XOLBP_INSN __emit_break(BRK_UPROBE_XOLBP)
|
||||
|
||||
struct arch_uprobe {
|
||||
unsigned long resume_era;
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
# No special ABIs on loongarch so far
|
||||
syscall_abis_64 +=
|
||||
syscall_abis_64 += newstat
|
||||
|
||||
@@ -57,15 +57,22 @@ void __iomem *acpi_os_ioremap(acpi_physical_address phys, acpi_size size)
|
||||
return ioremap_cache(phys, size);
|
||||
}
|
||||
|
||||
static int cpu_enumerated = 0;
|
||||
|
||||
#ifdef CONFIG_SMP
|
||||
static int set_processor_mask(u32 id, u32 flags)
|
||||
{
|
||||
|
||||
int nr_cpus;
|
||||
int cpu, cpuid = id;
|
||||
|
||||
if (num_processors >= nr_cpu_ids) {
|
||||
pr_warn(PREFIX "nr_cpus/possible_cpus limit of %i reached."
|
||||
" processor 0x%x ignored.\n", nr_cpu_ids, cpuid);
|
||||
if (!cpu_enumerated)
|
||||
nr_cpus = NR_CPUS;
|
||||
else
|
||||
nr_cpus = nr_cpu_ids;
|
||||
|
||||
if (num_processors >= nr_cpus) {
|
||||
pr_warn(PREFIX "nr_cpus limit of %i reached."
|
||||
" processor 0x%x ignored.\n", nr_cpus, cpuid);
|
||||
|
||||
return -ENODEV;
|
||||
|
||||
@@ -73,11 +80,13 @@ static int set_processor_mask(u32 id, u32 flags)
|
||||
if (cpuid == loongson_sysconf.boot_cpu_id)
|
||||
cpu = 0;
|
||||
else
|
||||
cpu = cpumask_next_zero(-1, cpu_present_mask);
|
||||
cpu = find_first_zero_bit(cpumask_bits(cpu_present_mask), NR_CPUS);
|
||||
|
||||
if (!cpu_enumerated)
|
||||
set_cpu_possible(cpu, true);
|
||||
|
||||
if (flags & ACPI_MADT_ENABLED) {
|
||||
num_processors++;
|
||||
set_cpu_possible(cpu, true);
|
||||
set_cpu_present(cpu, true);
|
||||
__cpu_number_map[cpuid] = cpu;
|
||||
__cpu_logical_map[cpu] = cpuid;
|
||||
@@ -138,6 +147,7 @@ static void __init acpi_process_madt(void)
|
||||
acpi_table_parse_madt(ACPI_MADT_TYPE_EIO_PIC,
|
||||
acpi_parse_eio_master, MAX_IO_PICS);
|
||||
|
||||
cpu_enumerated = 1;
|
||||
loongson_sysconf.nr_cpus = num_processors;
|
||||
}
|
||||
|
||||
|
||||
@@ -44,11 +44,7 @@ SYM_DATA(kernel_fsize, .long _kernel_fsize);
|
||||
SYM_CODE_START(kernel_entry) # kernel entry point
|
||||
|
||||
/* Config direct window and set PG */
|
||||
li.d t0, CSR_DMW0_INIT # UC, PLV0, 0x8000 xxxx xxxx xxxx
|
||||
csrwr t0, LOONGARCH_CSR_DMWIN0
|
||||
li.d t0, CSR_DMW1_INIT # CA, PLV0, 0x9000 xxxx xxxx xxxx
|
||||
csrwr t0, LOONGARCH_CSR_DMWIN1
|
||||
|
||||
SETUP_DMWINS t0
|
||||
JUMP_VIRT_ADDR t0, t1
|
||||
|
||||
/* Enable PG */
|
||||
@@ -124,11 +120,8 @@ SYM_CODE_END(kernel_entry)
|
||||
* function after setting up the stack and tp registers.
|
||||
*/
|
||||
SYM_CODE_START(smpboot_entry)
|
||||
li.d t0, CSR_DMW0_INIT # UC, PLV0
|
||||
csrwr t0, LOONGARCH_CSR_DMWIN0
|
||||
li.d t0, CSR_DMW1_INIT # CA, PLV0
|
||||
csrwr t0, LOONGARCH_CSR_DMWIN1
|
||||
|
||||
SETUP_DMWINS t0
|
||||
JUMP_VIRT_ADDR t0, t1
|
||||
|
||||
#ifdef CONFIG_PAGE_SIZE_4KB
|
||||
|
||||
@@ -221,7 +221,7 @@ static int hw_breakpoint_control(struct perf_event *bp,
|
||||
}
|
||||
enable = csr_read64(LOONGARCH_CSR_CRMD);
|
||||
csr_write64(CSR_CRMD_WE | enable, LOONGARCH_CSR_CRMD);
|
||||
if (bp->hw.target)
|
||||
if (bp->hw.target && test_tsk_thread_flag(bp->hw.target, TIF_LOAD_WATCH))
|
||||
regs->csr_prmd |= CSR_PRMD_PWE;
|
||||
break;
|
||||
case HW_BREAKPOINT_UNINSTALL:
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
#include <linux/preempt.h>
|
||||
#include <asm/break.h>
|
||||
|
||||
#define KPROBE_BP_INSN larch_insn_gen_break(BRK_KPROBE_BP)
|
||||
#define KPROBE_SSTEPBP_INSN larch_insn_gen_break(BRK_KPROBE_SSTEPBP)
|
||||
#define KPROBE_BP_INSN __emit_break(BRK_KPROBE_BP)
|
||||
#define KPROBE_SSTEPBP_INSN __emit_break(BRK_KPROBE_SSTEPBP)
|
||||
|
||||
DEFINE_PER_CPU(struct kprobe *, current_kprobe);
|
||||
DEFINE_PER_CPU(struct kprobe_ctlblk, kprobe_ctlblk);
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#include <linux/export.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/irq_work.h>
|
||||
#include <linux/jump_label.h>
|
||||
#include <linux/kvm_para.h>
|
||||
#include <linux/reboot.h>
|
||||
@@ -128,6 +129,11 @@ static irqreturn_t pv_ipi_interrupt(int irq, void *dev)
|
||||
info->ipi_irqs[IPI_CALL_FUNCTION]++;
|
||||
}
|
||||
|
||||
if (action & SMP_IRQ_WORK) {
|
||||
irq_work_run();
|
||||
info->ipi_irqs[IPI_IRQ_WORK]++;
|
||||
}
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
|
||||
@@ -589,6 +589,7 @@ static int ptrace_hbp_set_ctrl(unsigned int note_type,
|
||||
struct perf_event *bp;
|
||||
struct perf_event_attr attr;
|
||||
struct arch_hw_breakpoint_ctrl ctrl;
|
||||
struct thread_info *ti = task_thread_info(tsk);
|
||||
|
||||
bp = ptrace_hbp_get_initialised_bp(note_type, tsk, idx);
|
||||
if (IS_ERR(bp))
|
||||
@@ -613,8 +614,10 @@ static int ptrace_hbp_set_ctrl(unsigned int note_type,
|
||||
if (err)
|
||||
return err;
|
||||
attr.disabled = 0;
|
||||
set_ti_thread_flag(ti, TIF_LOAD_WATCH);
|
||||
} else {
|
||||
attr.disabled = 1;
|
||||
clear_ti_thread_flag(ti, TIF_LOAD_WATCH);
|
||||
}
|
||||
|
||||
return modify_user_hw_breakpoint(bp, &attr);
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#include <asm/bootinfo.h>
|
||||
#include <asm/early_ioremap.h>
|
||||
#include <asm/inst.h>
|
||||
#include <asm/io.h>
|
||||
#include <asm/sections.h>
|
||||
#include <asm/setup.h>
|
||||
|
||||
@@ -34,11 +35,27 @@ static inline void __init relocate_relative(void)
|
||||
if (rela->r_info != R_LARCH_RELATIVE)
|
||||
continue;
|
||||
|
||||
if (relocated_addr >= VMLINUX_LOAD_ADDRESS)
|
||||
relocated_addr = (Elf64_Addr)RELOCATED(relocated_addr);
|
||||
|
||||
relocated_addr = (Elf64_Addr)RELOCATED(relocated_addr);
|
||||
*(Elf64_Addr *)RELOCATED(addr) = relocated_addr;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_RELR
|
||||
u64 *addr = NULL;
|
||||
u64 *relr = (u64 *)&__relr_dyn_begin;
|
||||
u64 *relr_end = (u64 *)&__relr_dyn_end;
|
||||
|
||||
for ( ; relr < relr_end; relr++) {
|
||||
if ((*relr & 1) == 0) {
|
||||
addr = (u64 *)(*relr + reloc_offset);
|
||||
*addr++ += reloc_offset;
|
||||
} else {
|
||||
for (u64 *p = addr, r = *relr >> 1; r; p++, r >>= 1)
|
||||
if (r & 1)
|
||||
*p += reloc_offset;
|
||||
addr += 63;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
static inline void __init relocate_absolute(long random_offset)
|
||||
@@ -123,6 +140,32 @@ static inline __init bool kaslr_disabled(void)
|
||||
if (str == boot_command_line || (str > boot_command_line && *(str - 1) == ' '))
|
||||
return true;
|
||||
|
||||
#ifdef CONFIG_HIBERNATION
|
||||
str = strstr(builtin_cmdline, "nohibernate");
|
||||
if (str == builtin_cmdline || (str > builtin_cmdline && *(str - 1) == ' '))
|
||||
return false;
|
||||
|
||||
str = strstr(boot_command_line, "nohibernate");
|
||||
if (str == boot_command_line || (str > boot_command_line && *(str - 1) == ' '))
|
||||
return false;
|
||||
|
||||
str = strstr(builtin_cmdline, "noresume");
|
||||
if (str == builtin_cmdline || (str > builtin_cmdline && *(str - 1) == ' '))
|
||||
return false;
|
||||
|
||||
str = strstr(boot_command_line, "noresume");
|
||||
if (str == boot_command_line || (str > boot_command_line && *(str - 1) == ' '))
|
||||
return false;
|
||||
|
||||
str = strstr(builtin_cmdline, "resume=");
|
||||
if (str == builtin_cmdline || (str > builtin_cmdline && *(str - 1) == ' '))
|
||||
return true;
|
||||
|
||||
str = strstr(boot_command_line, "resume=");
|
||||
if (str == boot_command_line || (str > boot_command_line && *(str - 1) == ' '))
|
||||
return true;
|
||||
#endif
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -170,7 +213,7 @@ unsigned long __init relocate_kernel(void)
|
||||
unsigned long kernel_length;
|
||||
unsigned long random_offset = 0;
|
||||
void *location_new = _text; /* Default to original kernel start */
|
||||
char *cmdline = early_ioremap(fw_arg1, COMMAND_LINE_SIZE); /* Boot command line is passed in fw_arg1 */
|
||||
char *cmdline = early_memremap_ro(fw_arg1, COMMAND_LINE_SIZE); /* Boot command line is passed in fw_arg1 */
|
||||
|
||||
strscpy(boot_command_line, cmdline, COMMAND_LINE_SIZE);
|
||||
|
||||
@@ -182,6 +225,7 @@ unsigned long __init relocate_kernel(void)
|
||||
random_offset = (unsigned long)location_new - (unsigned long)(_text);
|
||||
#endif
|
||||
reloc_offset = (unsigned long)_text - VMLINUX_LOAD_ADDRESS;
|
||||
early_memunmap(cmdline, COMMAND_LINE_SIZE);
|
||||
|
||||
if (random_offset) {
|
||||
kernel_length = (long)(_end) - (long)(_text);
|
||||
|
||||
@@ -576,8 +576,10 @@ static void __init prefill_possible_map(void)
|
||||
|
||||
for (i = 0; i < possible; i++)
|
||||
set_cpu_possible(i, true);
|
||||
for (; i < NR_CPUS; i++)
|
||||
for (; i < NR_CPUS; i++) {
|
||||
set_cpu_present(i, false);
|
||||
set_cpu_possible(i, false);
|
||||
}
|
||||
|
||||
set_nr_cpu_ids(possible);
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#include <linux/cpumask.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/irq_work.h>
|
||||
#include <linux/profile.h>
|
||||
#include <linux/seq_file.h>
|
||||
#include <linux/smp.h>
|
||||
@@ -70,6 +71,7 @@ static DEFINE_PER_CPU(int, cpu_state);
|
||||
static const char *ipi_types[NR_IPI] __tracepoint_string = {
|
||||
[IPI_RESCHEDULE] = "Rescheduling interrupts",
|
||||
[IPI_CALL_FUNCTION] = "Function call interrupts",
|
||||
[IPI_IRQ_WORK] = "IRQ work interrupts",
|
||||
};
|
||||
|
||||
void show_ipi_list(struct seq_file *p, int prec)
|
||||
@@ -217,6 +219,13 @@ void arch_smp_send_reschedule(int cpu)
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(arch_smp_send_reschedule);
|
||||
|
||||
#ifdef CONFIG_IRQ_WORK
|
||||
void arch_irq_work_raise(void)
|
||||
{
|
||||
mp_ops.send_ipi_single(smp_processor_id(), ACTION_IRQ_WORK);
|
||||
}
|
||||
#endif
|
||||
|
||||
static irqreturn_t loongson_ipi_interrupt(int irq, void *dev)
|
||||
{
|
||||
unsigned int action;
|
||||
@@ -234,6 +243,11 @@ static irqreturn_t loongson_ipi_interrupt(int irq, void *dev)
|
||||
per_cpu(irq_stat, cpu).ipi_irqs[IPI_CALL_FUNCTION]++;
|
||||
}
|
||||
|
||||
if (action & SMP_IRQ_WORK) {
|
||||
irq_work_run();
|
||||
per_cpu(irq_stat, cpu).ipi_irqs[IPI_IRQ_WORK]++;
|
||||
}
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
@@ -271,11 +285,10 @@ static void __init fdt_smp_setup(void)
|
||||
if (cpuid >= nr_cpu_ids)
|
||||
continue;
|
||||
|
||||
if (cpuid == loongson_sysconf.boot_cpu_id) {
|
||||
if (cpuid == loongson_sysconf.boot_cpu_id)
|
||||
cpu = 0;
|
||||
} else {
|
||||
cpu = cpumask_next_zero(-1, cpu_present_mask);
|
||||
}
|
||||
else
|
||||
cpu = find_first_zero_bit(cpumask_bits(cpu_present_mask), NR_CPUS);
|
||||
|
||||
num_processors++;
|
||||
set_cpu_possible(cpu, true);
|
||||
|
||||
@@ -9,11 +9,14 @@
|
||||
#include <linux/entry-common.h>
|
||||
#include <linux/errno.h>
|
||||
#include <linux/linkage.h>
|
||||
#include <linux/objtool.h>
|
||||
#include <linux/randomize_kstack.h>
|
||||
#include <linux/syscalls.h>
|
||||
#include <linux/unistd.h>
|
||||
|
||||
#include <asm/asm.h>
|
||||
#include <asm/exception.h>
|
||||
#include <asm/loongarch.h>
|
||||
#include <asm/signal.h>
|
||||
#include <asm/switch_to.h>
|
||||
#include <asm-generic/syscalls.h>
|
||||
@@ -39,7 +42,7 @@ void *sys_call_table[__NR_syscalls] = {
|
||||
typedef long (*sys_call_fn)(unsigned long, unsigned long,
|
||||
unsigned long, unsigned long, unsigned long, unsigned long);
|
||||
|
||||
void noinstr do_syscall(struct pt_regs *regs)
|
||||
void noinstr __no_stack_protector do_syscall(struct pt_regs *regs)
|
||||
{
|
||||
unsigned long nr;
|
||||
sys_call_fn syscall_fn;
|
||||
@@ -55,11 +58,28 @@ void noinstr do_syscall(struct pt_regs *regs)
|
||||
|
||||
nr = syscall_enter_from_user_mode(regs, nr);
|
||||
|
||||
add_random_kstack_offset();
|
||||
|
||||
if (nr < NR_syscalls) {
|
||||
syscall_fn = sys_call_table[nr];
|
||||
regs->regs[4] = syscall_fn(regs->orig_a0, regs->regs[5], regs->regs[6],
|
||||
regs->regs[7], regs->regs[8], regs->regs[9]);
|
||||
}
|
||||
|
||||
/*
|
||||
* This value will get limited by KSTACK_OFFSET_MAX(), which is 10
|
||||
* bits. The actual entropy will be further reduced by the compiler
|
||||
* when applying stack alignment constraints: 16-bytes (i.e. 4-bits)
|
||||
* aligned, which will remove the 4 low bits from any entropy chosen
|
||||
* here.
|
||||
*
|
||||
* The resulting 6 bits of entropy is seen in SP[9:4].
|
||||
*/
|
||||
choose_random_kstack_offset(drdtime());
|
||||
|
||||
syscall_exit_to_user_mode(regs);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_RANDOMIZE_KSTACK_OFFSET
|
||||
STACK_FRAME_NON_STANDARD(do_syscall);
|
||||
#endif
|
||||
|
||||
@@ -113,6 +113,14 @@ SECTIONS
|
||||
__rela_dyn_end = .;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_RELR
|
||||
.relr.dyn : ALIGN(8) {
|
||||
__relr_dyn_begin = .;
|
||||
*(.relr.dyn)
|
||||
__relr_dyn_end = .;
|
||||
}
|
||||
#endif
|
||||
|
||||
.data.rel : { *(.data.rel*) }
|
||||
|
||||
#ifdef CONFIG_RELOCATABLE
|
||||
|
||||
@@ -34,22 +34,49 @@ void enable_pci_wakeup(void)
|
||||
acpi_write_bit_register(ACPI_BITREG_PCIEXP_WAKE_DISABLE, 0);
|
||||
}
|
||||
|
||||
static struct platform_device loongson3_cpufreq_device = {
|
||||
.name = "loongson3_cpufreq",
|
||||
.id = -1,
|
||||
};
|
||||
|
||||
static int __init loongson_cpufreq_init(void)
|
||||
{
|
||||
if (!cpu_has_scalefreq)
|
||||
return -ENODEV;
|
||||
|
||||
return platform_device_register(&loongson3_cpufreq_device);
|
||||
}
|
||||
|
||||
arch_initcall(loongson_cpufreq_init);
|
||||
|
||||
static void default_suspend_addr(void)
|
||||
{
|
||||
acpi_enter_sleep_state(ACPI_STATE_S3);
|
||||
}
|
||||
|
||||
static int __init loongson3_acpi_suspend_init(void)
|
||||
{
|
||||
#ifdef CONFIG_ACPI
|
||||
acpi_status status;
|
||||
uint64_t suspend_addr = 0;
|
||||
|
||||
if (acpi_disabled || acpi_gbl_reduced_hardware)
|
||||
if (acpi_disabled)
|
||||
return 0;
|
||||
|
||||
if (!acpi_gbl_reduced_hardware)
|
||||
acpi_write_bit_register(ACPI_BITREG_SCI_ENABLE, 1);
|
||||
|
||||
if (!acpi_sleep_state_supported(ACPI_STATE_S3))
|
||||
return 0;
|
||||
|
||||
acpi_write_bit_register(ACPI_BITREG_SCI_ENABLE, 1);
|
||||
status = acpi_evaluate_integer(NULL, "\\SADR", NULL, &suspend_addr);
|
||||
if (ACPI_FAILURE(status) || !suspend_addr) {
|
||||
pr_err("ACPI S3 is not support!\n");
|
||||
return -1;
|
||||
pr_info("ACPI S3 supported with hardware register default\n");
|
||||
loongson_sysconf.suspend_addr = (u64)default_suspend_addr;
|
||||
} else {
|
||||
pr_info("ACPI S3 supported with Loongson ACPI SADR extension\n");
|
||||
loongson_sysconf.suspend_addr = (u64)phys_to_virt(PHYSADDR(suspend_addr));
|
||||
}
|
||||
loongson_sysconf.suspend_addr = (u64)phys_to_virt(PHYSADDR(suspend_addr));
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -66,18 +66,14 @@ SYM_FUNC_START(loongarch_suspend_enter)
|
||||
la.pcrel a0, loongarch_wakeup_start
|
||||
la.pcrel t0, loongarch_suspend_addr
|
||||
ld.d t0, t0, 0
|
||||
jirl a0, t0, 0 /* Call BIOS's STR sleep routine */
|
||||
jirl ra, t0, 0 /* Call BIOS's STR sleep routine */
|
||||
|
||||
/*
|
||||
* This is where we return upon wakeup.
|
||||
* Reload all of the registers and return.
|
||||
*/
|
||||
SYM_INNER_LABEL(loongarch_wakeup_start, SYM_L_GLOBAL)
|
||||
li.d t0, CSR_DMW0_INIT # UC, PLV0
|
||||
csrwr t0, LOONGARCH_CSR_DMWIN0
|
||||
li.d t0, CSR_DMW1_INIT # CA, PLV0
|
||||
csrwr t0, LOONGARCH_CSR_DMWIN1
|
||||
|
||||
SETUP_DMWINS t0
|
||||
JUMP_VIRT_ADDR t0, t1
|
||||
|
||||
/* Enable PG */
|
||||
|
||||
@@ -988,6 +988,11 @@ static struct resource virt_platform_resource = {
|
||||
|
||||
static int __init um_pci_init(void)
|
||||
{
|
||||
struct irq_domain_info inner_domain_info = {
|
||||
.size = MAX_MSI_VECTORS,
|
||||
.hwirq_max = MAX_MSI_VECTORS,
|
||||
.ops = &um_pci_inner_domain_ops,
|
||||
};
|
||||
int err, i;
|
||||
|
||||
WARN_ON(logic_iomem_add_region(&virt_cfgspace_resource,
|
||||
@@ -1017,11 +1022,10 @@ static int __init um_pci_init(void)
|
||||
goto free;
|
||||
}
|
||||
|
||||
um_pci_inner_domain = __irq_domain_add(um_pci_fwnode, MAX_MSI_VECTORS,
|
||||
MAX_MSI_VECTORS, 0,
|
||||
&um_pci_inner_domain_ops, NULL);
|
||||
if (!um_pci_inner_domain) {
|
||||
err = -ENOMEM;
|
||||
inner_domain_info.fwnode = um_pci_fwnode;
|
||||
um_pci_inner_domain = irq_domain_instantiate(&inner_domain_info);
|
||||
if (IS_ERR(um_pci_inner_domain)) {
|
||||
err = PTR_ERR(um_pci_inner_domain);
|
||||
goto free;
|
||||
}
|
||||
|
||||
@@ -1058,7 +1062,7 @@ static int __init um_pci_init(void)
|
||||
goto free;
|
||||
return 0;
|
||||
free:
|
||||
if (um_pci_inner_domain)
|
||||
if (!IS_ERR_OR_NULL(um_pci_inner_domain))
|
||||
irq_domain_remove(um_pci_inner_domain);
|
||||
if (um_pci_fwnode)
|
||||
irq_domain_free_fwnode(um_pci_fwnode);
|
||||
|
||||
@@ -216,6 +216,21 @@ phys_cpuid_t __init acpi_map_madt_entry(u32 acpi_id)
|
||||
return rv;
|
||||
}
|
||||
|
||||
int __init acpi_get_madt_revision(void)
|
||||
{
|
||||
struct acpi_table_header *madt = NULL;
|
||||
int revision;
|
||||
|
||||
if (ACPI_FAILURE(acpi_get_table(ACPI_SIG_MADT, 0, &madt)))
|
||||
return -EINVAL;
|
||||
|
||||
revision = madt->revision;
|
||||
|
||||
acpi_put_table(madt);
|
||||
|
||||
return revision;
|
||||
}
|
||||
|
||||
static phys_cpuid_t map_mat_entry(acpi_handle handle, int type, u32 acpi_id)
|
||||
{
|
||||
struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };
|
||||
|
||||
+3
-347
@@ -4,346 +4,12 @@
|
||||
*
|
||||
* Copyright (C) 2015 ARM Limited, All Rights Reserved.
|
||||
* Author: Marc Zyngier <marc.zyngier@arm.com>
|
||||
* Copyright (C) 2022 Linutronix GmbH
|
||||
*/
|
||||
|
||||
#include <linux/device.h>
|
||||
#include <linux/idr.h>
|
||||
#include <linux/irq.h>
|
||||
#include <linux/irqdomain.h>
|
||||
#include <linux/msi.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
/* Begin of removal area. Once everything is converted over. Cleanup the includes too! */
|
||||
|
||||
#define DEV_ID_SHIFT 21
|
||||
#define MAX_DEV_MSIS (1 << (32 - DEV_ID_SHIFT))
|
||||
|
||||
/*
|
||||
* Internal data structure containing a (made up, but unique) devid
|
||||
* and the callback to write the MSI message.
|
||||
*/
|
||||
struct platform_msi_priv_data {
|
||||
struct device *dev;
|
||||
void *host_data;
|
||||
msi_alloc_info_t arg;
|
||||
irq_write_msi_msg_t write_msg;
|
||||
int devid;
|
||||
};
|
||||
|
||||
/* The devid allocator */
|
||||
static DEFINE_IDA(platform_msi_devid_ida);
|
||||
|
||||
#ifdef GENERIC_MSI_DOMAIN_OPS
|
||||
/*
|
||||
* Convert an msi_desc to a globaly unique identifier (per-device
|
||||
* devid + msi_desc position in the msi_list).
|
||||
*/
|
||||
static irq_hw_number_t platform_msi_calc_hwirq(struct msi_desc *desc)
|
||||
{
|
||||
u32 devid = desc->dev->msi.data->platform_data->devid;
|
||||
|
||||
return (devid << (32 - DEV_ID_SHIFT)) | desc->msi_index;
|
||||
}
|
||||
|
||||
static void platform_msi_set_desc(msi_alloc_info_t *arg, struct msi_desc *desc)
|
||||
{
|
||||
arg->desc = desc;
|
||||
arg->hwirq = platform_msi_calc_hwirq(desc);
|
||||
}
|
||||
|
||||
static int platform_msi_init(struct irq_domain *domain,
|
||||
struct msi_domain_info *info,
|
||||
unsigned int virq, irq_hw_number_t hwirq,
|
||||
msi_alloc_info_t *arg)
|
||||
{
|
||||
return irq_domain_set_hwirq_and_chip(domain, virq, hwirq,
|
||||
info->chip, info->chip_data);
|
||||
}
|
||||
|
||||
static void platform_msi_set_proxy_dev(msi_alloc_info_t *arg)
|
||||
{
|
||||
arg->flags |= MSI_ALLOC_FLAGS_PROXY_DEVICE;
|
||||
}
|
||||
#else
|
||||
#define platform_msi_set_desc NULL
|
||||
#define platform_msi_init NULL
|
||||
#define platform_msi_set_proxy_dev(x) do {} while(0)
|
||||
#endif
|
||||
|
||||
static void platform_msi_update_dom_ops(struct msi_domain_info *info)
|
||||
{
|
||||
struct msi_domain_ops *ops = info->ops;
|
||||
|
||||
BUG_ON(!ops);
|
||||
|
||||
if (ops->msi_init == NULL)
|
||||
ops->msi_init = platform_msi_init;
|
||||
if (ops->set_desc == NULL)
|
||||
ops->set_desc = platform_msi_set_desc;
|
||||
}
|
||||
|
||||
static void platform_msi_write_msg(struct irq_data *data, struct msi_msg *msg)
|
||||
{
|
||||
struct msi_desc *desc = irq_data_get_msi_desc(data);
|
||||
|
||||
desc->dev->msi.data->platform_data->write_msg(desc, msg);
|
||||
}
|
||||
|
||||
static void platform_msi_update_chip_ops(struct msi_domain_info *info)
|
||||
{
|
||||
struct irq_chip *chip = info->chip;
|
||||
|
||||
BUG_ON(!chip);
|
||||
if (!chip->irq_mask)
|
||||
chip->irq_mask = irq_chip_mask_parent;
|
||||
if (!chip->irq_unmask)
|
||||
chip->irq_unmask = irq_chip_unmask_parent;
|
||||
if (!chip->irq_eoi)
|
||||
chip->irq_eoi = irq_chip_eoi_parent;
|
||||
if (!chip->irq_set_affinity)
|
||||
chip->irq_set_affinity = msi_domain_set_affinity;
|
||||
if (!chip->irq_write_msi_msg)
|
||||
chip->irq_write_msi_msg = platform_msi_write_msg;
|
||||
if (WARN_ON((info->flags & MSI_FLAG_LEVEL_CAPABLE) &&
|
||||
!(chip->flags & IRQCHIP_SUPPORTS_LEVEL_MSI)))
|
||||
info->flags &= ~MSI_FLAG_LEVEL_CAPABLE;
|
||||
}
|
||||
|
||||
/**
|
||||
* platform_msi_create_irq_domain - Create a platform MSI interrupt domain
|
||||
* @fwnode: Optional fwnode of the interrupt controller
|
||||
* @info: MSI domain info
|
||||
* @parent: Parent irq domain
|
||||
*
|
||||
* Updates the domain and chip ops and creates a platform MSI
|
||||
* interrupt domain.
|
||||
*
|
||||
* Returns:
|
||||
* A domain pointer or NULL in case of failure.
|
||||
*/
|
||||
struct irq_domain *platform_msi_create_irq_domain(struct fwnode_handle *fwnode,
|
||||
struct msi_domain_info *info,
|
||||
struct irq_domain *parent)
|
||||
{
|
||||
struct irq_domain *domain;
|
||||
|
||||
if (info->flags & MSI_FLAG_USE_DEF_DOM_OPS)
|
||||
platform_msi_update_dom_ops(info);
|
||||
if (info->flags & MSI_FLAG_USE_DEF_CHIP_OPS)
|
||||
platform_msi_update_chip_ops(info);
|
||||
info->flags |= MSI_FLAG_DEV_SYSFS | MSI_FLAG_ALLOC_SIMPLE_MSI_DESCS |
|
||||
MSI_FLAG_FREE_MSI_DESCS;
|
||||
|
||||
domain = msi_create_irq_domain(fwnode, info, parent);
|
||||
if (domain)
|
||||
irq_domain_update_bus_token(domain, DOMAIN_BUS_PLATFORM_MSI);
|
||||
|
||||
return domain;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(platform_msi_create_irq_domain);
|
||||
|
||||
static int platform_msi_alloc_priv_data(struct device *dev, unsigned int nvec,
|
||||
irq_write_msi_msg_t write_msi_msg)
|
||||
{
|
||||
struct platform_msi_priv_data *datap;
|
||||
int err;
|
||||
|
||||
/*
|
||||
* Limit the number of interrupts to 2048 per device. Should we
|
||||
* need to bump this up, DEV_ID_SHIFT should be adjusted
|
||||
* accordingly (which would impact the max number of MSI
|
||||
* capable devices).
|
||||
*/
|
||||
if (!dev->msi.domain || !write_msi_msg || !nvec || nvec > MAX_DEV_MSIS)
|
||||
return -EINVAL;
|
||||
|
||||
if (dev->msi.domain->bus_token != DOMAIN_BUS_PLATFORM_MSI) {
|
||||
dev_err(dev, "Incompatible msi_domain, giving up\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
err = msi_setup_device_data(dev);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
/* Already initialized? */
|
||||
if (dev->msi.data->platform_data)
|
||||
return -EBUSY;
|
||||
|
||||
datap = kzalloc(sizeof(*datap), GFP_KERNEL);
|
||||
if (!datap)
|
||||
return -ENOMEM;
|
||||
|
||||
datap->devid = ida_alloc_max(&platform_msi_devid_ida,
|
||||
(1 << DEV_ID_SHIFT) - 1, GFP_KERNEL);
|
||||
if (datap->devid < 0) {
|
||||
err = datap->devid;
|
||||
kfree(datap);
|
||||
return err;
|
||||
}
|
||||
|
||||
datap->write_msg = write_msi_msg;
|
||||
datap->dev = dev;
|
||||
dev->msi.data->platform_data = datap;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void platform_msi_free_priv_data(struct device *dev)
|
||||
{
|
||||
struct platform_msi_priv_data *data = dev->msi.data->platform_data;
|
||||
|
||||
dev->msi.data->platform_data = NULL;
|
||||
ida_free(&platform_msi_devid_ida, data->devid);
|
||||
kfree(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* platform_msi_domain_alloc_irqs - Allocate MSI interrupts for @dev
|
||||
* @dev: The device for which to allocate interrupts
|
||||
* @nvec: The number of interrupts to allocate
|
||||
* @write_msi_msg: Callback to write an interrupt message for @dev
|
||||
*
|
||||
* Returns:
|
||||
* Zero for success, or an error code in case of failure
|
||||
*/
|
||||
static int platform_msi_domain_alloc_irqs(struct device *dev, unsigned int nvec,
|
||||
irq_write_msi_msg_t write_msi_msg)
|
||||
{
|
||||
int err;
|
||||
|
||||
err = platform_msi_alloc_priv_data(dev, nvec, write_msi_msg);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
err = msi_domain_alloc_irqs_range(dev, MSI_DEFAULT_DOMAIN, 0, nvec - 1);
|
||||
if (err)
|
||||
platform_msi_free_priv_data(dev);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
/**
|
||||
* platform_msi_get_host_data - Query the private data associated with
|
||||
* a platform-msi domain
|
||||
* @domain: The platform-msi domain
|
||||
*
|
||||
* Return: The private data provided when calling
|
||||
* platform_msi_create_device_domain().
|
||||
*/
|
||||
void *platform_msi_get_host_data(struct irq_domain *domain)
|
||||
{
|
||||
struct platform_msi_priv_data *data = domain->host_data;
|
||||
|
||||
return data->host_data;
|
||||
}
|
||||
|
||||
static struct lock_class_key platform_device_msi_lock_class;
|
||||
|
||||
/**
|
||||
* __platform_msi_create_device_domain - Create a platform-msi device domain
|
||||
*
|
||||
* @dev: The device generating the MSIs
|
||||
* @nvec: The number of MSIs that need to be allocated
|
||||
* @is_tree: flag to indicate tree hierarchy
|
||||
* @write_msi_msg: Callback to write an interrupt message for @dev
|
||||
* @ops: The hierarchy domain operations to use
|
||||
* @host_data: Private data associated to this domain
|
||||
*
|
||||
* Return: An irqdomain for @nvec interrupts on success, NULL in case of error.
|
||||
*
|
||||
* This is for interrupt domains which stack on a platform-msi domain
|
||||
* created by platform_msi_create_irq_domain(). @dev->msi.domain points to
|
||||
* that platform-msi domain which is the parent for the new domain.
|
||||
*/
|
||||
struct irq_domain *
|
||||
__platform_msi_create_device_domain(struct device *dev,
|
||||
unsigned int nvec,
|
||||
bool is_tree,
|
||||
irq_write_msi_msg_t write_msi_msg,
|
||||
const struct irq_domain_ops *ops,
|
||||
void *host_data)
|
||||
{
|
||||
struct platform_msi_priv_data *data;
|
||||
struct irq_domain *domain;
|
||||
int err;
|
||||
|
||||
err = platform_msi_alloc_priv_data(dev, nvec, write_msi_msg);
|
||||
if (err)
|
||||
return NULL;
|
||||
|
||||
/*
|
||||
* Use a separate lock class for the MSI descriptor mutex on
|
||||
* platform MSI device domains because the descriptor mutex nests
|
||||
* into the domain mutex. See alloc/free below.
|
||||
*/
|
||||
lockdep_set_class(&dev->msi.data->mutex, &platform_device_msi_lock_class);
|
||||
|
||||
data = dev->msi.data->platform_data;
|
||||
data->host_data = host_data;
|
||||
domain = irq_domain_create_hierarchy(dev->msi.domain, 0,
|
||||
is_tree ? 0 : nvec,
|
||||
dev->fwnode, ops, data);
|
||||
if (!domain)
|
||||
goto free_priv;
|
||||
|
||||
platform_msi_set_proxy_dev(&data->arg);
|
||||
err = msi_domain_prepare_irqs(domain->parent, dev, nvec, &data->arg);
|
||||
if (err)
|
||||
goto free_domain;
|
||||
|
||||
return domain;
|
||||
|
||||
free_domain:
|
||||
irq_domain_remove(domain);
|
||||
free_priv:
|
||||
platform_msi_free_priv_data(dev);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* platform_msi_device_domain_free - Free interrupts associated with a platform-msi
|
||||
* device domain
|
||||
*
|
||||
* @domain: The platform-msi device domain
|
||||
* @virq: The base irq from which to perform the free operation
|
||||
* @nr_irqs: How many interrupts to free from @virq
|
||||
*/
|
||||
void platform_msi_device_domain_free(struct irq_domain *domain, unsigned int virq,
|
||||
unsigned int nr_irqs)
|
||||
{
|
||||
struct platform_msi_priv_data *data = domain->host_data;
|
||||
|
||||
msi_lock_descs(data->dev);
|
||||
msi_domain_depopulate_descs(data->dev, virq, nr_irqs);
|
||||
irq_domain_free_irqs_common(domain, virq, nr_irqs);
|
||||
msi_free_msi_descs_range(data->dev, virq, virq + nr_irqs - 1);
|
||||
msi_unlock_descs(data->dev);
|
||||
}
|
||||
|
||||
/**
|
||||
* platform_msi_device_domain_alloc - Allocate interrupts associated with
|
||||
* a platform-msi device domain
|
||||
*
|
||||
* @domain: The platform-msi device domain
|
||||
* @virq: The base irq from which to perform the allocate operation
|
||||
* @nr_irqs: How many interrupts to allocate from @virq
|
||||
*
|
||||
* Return 0 on success, or an error code on failure. Must be called
|
||||
* with irq_domain_mutex held (which can only be done as part of a
|
||||
* top-level interrupt allocation).
|
||||
*/
|
||||
int platform_msi_device_domain_alloc(struct irq_domain *domain, unsigned int virq,
|
||||
unsigned int nr_irqs)
|
||||
{
|
||||
struct platform_msi_priv_data *data = domain->host_data;
|
||||
struct device *dev = data->dev;
|
||||
|
||||
return msi_domain_populate_irqs(domain->parent, dev, virq, nr_irqs, &data->arg);
|
||||
}
|
||||
|
||||
/* End of removal area */
|
||||
|
||||
/* Real per device domain interfaces */
|
||||
|
||||
/*
|
||||
* This indirection can go when platform_device_msi_init_and_alloc_irqs()
|
||||
@@ -357,7 +23,7 @@ static void platform_msi_write_msi_msg(struct irq_data *d, struct msi_msg *msg)
|
||||
cb(irq_data_get_msi_desc(d), msg);
|
||||
}
|
||||
|
||||
static void platform_msi_set_desc_byindex(msi_alloc_info_t *arg, struct msi_desc *desc)
|
||||
static void platform_msi_set_desc(msi_alloc_info_t *arg, struct msi_desc *desc)
|
||||
{
|
||||
arg->desc = desc;
|
||||
arg->hwirq = desc->msi_index;
|
||||
@@ -373,7 +39,7 @@ static const struct msi_domain_template platform_msi_template = {
|
||||
},
|
||||
|
||||
.ops = {
|
||||
.set_desc = platform_msi_set_desc_byindex,
|
||||
.set_desc = platform_msi_set_desc,
|
||||
},
|
||||
|
||||
.info = {
|
||||
@@ -408,10 +74,6 @@ int platform_device_msi_init_and_alloc_irqs(struct device *dev, unsigned int nve
|
||||
if (!domain || !write_msi_msg)
|
||||
return -EINVAL;
|
||||
|
||||
/* Migration support. Will go away once everything is converted */
|
||||
if (!irq_domain_is_msi_parent(domain))
|
||||
return platform_msi_domain_alloc_irqs(dev, nvec, write_msi_msg);
|
||||
|
||||
/*
|
||||
* @write_msi_msg is stored in the resulting msi_domain_info::data.
|
||||
* The underlying domain creation mechanism will assign that
|
||||
@@ -432,12 +94,6 @@ EXPORT_SYMBOL_GPL(platform_device_msi_init_and_alloc_irqs);
|
||||
*/
|
||||
void platform_device_msi_free_irqs_all(struct device *dev)
|
||||
{
|
||||
struct irq_domain *domain = dev->msi.domain;
|
||||
|
||||
msi_domain_free_irqs_all(dev, MSI_DEFAULT_DOMAIN);
|
||||
|
||||
/* Migration support. Will go away once everything is converted */
|
||||
if (!irq_domain_is_msi_parent(domain))
|
||||
platform_msi_free_priv_data(dev);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(platform_device_msi_free_irqs_all);
|
||||
|
||||
@@ -74,6 +74,8 @@ efi_status_t efi_boot_kernel(void *handle, efi_loaded_image_t *image,
|
||||
/* Config Direct Mapping */
|
||||
csr_write64(CSR_DMW0_INIT, LOONGARCH_CSR_DMWIN0);
|
||||
csr_write64(CSR_DMW1_INIT, LOONGARCH_CSR_DMWIN1);
|
||||
csr_write64(CSR_DMW2_INIT, LOONGARCH_CSR_DMWIN2);
|
||||
csr_write64(CSR_DMW3_INIT, LOONGARCH_CSR_DMWIN3);
|
||||
|
||||
real_kernel_entry = (void *)kernel_entry_address(kernel_addr, image);
|
||||
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
#include <linux/clk.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/gpio.h>
|
||||
#include <linux/if_ether.h>
|
||||
#include <linux/if_arp.h>
|
||||
#include <linux/if_phonet.h>
|
||||
|
||||
@@ -196,6 +196,7 @@ config I2C_ISMT
|
||||
config I2C_PIIX4
|
||||
tristate "Intel PIIX4 and compatible (ATI/AMD/Serverworks/Broadcom/SMSC)"
|
||||
depends on PCI && HAS_IOPORT
|
||||
select I2C_SMBUS
|
||||
help
|
||||
If you say yes to this option, support will be included for the Intel
|
||||
PIIX4 family of mainboard I2C interfaces. Specifically, the following
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
#include <linux/stddef.h>
|
||||
#include <linux/ioport.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/i2c-smbus.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/dmi.h>
|
||||
#include <linux/acpi.h>
|
||||
@@ -982,6 +983,14 @@ static int piix4_add_adapter(struct pci_dev *dev, unsigned short smba,
|
||||
return retval;
|
||||
}
|
||||
|
||||
/*
|
||||
* The AUX bus can not be probed as on some platforms it reports all
|
||||
* devices present and all reads return "0".
|
||||
* This would allow the ee1004 to be probed incorrectly.
|
||||
*/
|
||||
if (port == 0)
|
||||
i2c_register_spd(adap);
|
||||
|
||||
*padap = adap;
|
||||
return 0;
|
||||
}
|
||||
|
||||
+4
-11
@@ -352,18 +352,11 @@ void i2c_register_spd(struct i2c_adapter *adap)
|
||||
return;
|
||||
|
||||
/*
|
||||
* If we're a child adapter on a muxed segment, then limit slots to 8,
|
||||
* as this is the max number of SPD EEPROMs that can be addressed per bus.
|
||||
* The max number of SPD EEPROMs that can be addressed per bus is 8.
|
||||
* If more slots are present either muxed or multiple busses are
|
||||
* necessary or the additional slots are ignored.
|
||||
*/
|
||||
if (i2c_parent_is_i2c_adapter(adap)) {
|
||||
slot_count = 8;
|
||||
} else {
|
||||
if (slot_count > 8) {
|
||||
dev_warn(&adap->dev,
|
||||
"More than 8 memory slots on a single bus, contact i801 maintainer to add missing mux config\n");
|
||||
return;
|
||||
}
|
||||
}
|
||||
slot_count = min(slot_count, 8);
|
||||
|
||||
/*
|
||||
* Memory types could be found at section 7.18.2 (Memory Device — Type), table 78
|
||||
|
||||
@@ -5,16 +5,17 @@
|
||||
* Peter Korsgaard <peter.korsgaard@barco.com>
|
||||
*/
|
||||
|
||||
#include <linux/bits.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/gpio/consumer.h>
|
||||
#include <linux/gpio/driver.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/i2c-mux.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/overflow.h>
|
||||
#include <linux/platform_data/i2c-mux-gpio.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/bits.h>
|
||||
#include <linux/gpio/consumer.h>
|
||||
#include <linux/gpio/driver.h>
|
||||
|
||||
struct gpiomux {
|
||||
struct i2c_mux_gpio_platform_data data;
|
||||
@@ -37,6 +38,9 @@ static int i2c_mux_gpio_select(struct i2c_mux_core *muxc, u32 chan)
|
||||
|
||||
i2c_mux_gpio_set(mux, chan);
|
||||
|
||||
if (mux->data.settle_time)
|
||||
fsleep(mux->data.settle_time);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -116,6 +120,8 @@ static int i2c_mux_gpio_probe_fw(struct gpiomux *mux,
|
||||
if (device_property_read_u32(dev, "idle-state", &mux->data.idle))
|
||||
mux->data.idle = I2C_MUX_GPIO_NO_IDLE;
|
||||
|
||||
device_property_read_u32(dev, "settle-time-us", &mux->data.settle_time);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ config ARM_GIC_V2M
|
||||
bool
|
||||
depends on PCI
|
||||
select ARM_GIC
|
||||
select IRQ_MSI_LIB
|
||||
select PCI_MSI
|
||||
|
||||
config GIC_NON_BANKED
|
||||
@@ -41,6 +42,7 @@ config ARM_GIC_V3
|
||||
config ARM_GIC_V3_ITS
|
||||
bool
|
||||
select GENERIC_MSI_IRQ
|
||||
select IRQ_MSI_LIB
|
||||
default ARM_GIC_V3
|
||||
|
||||
config ARM_GIC_V3_ITS_PCI
|
||||
@@ -74,6 +76,9 @@ config ARM_VIC_NR
|
||||
The maximum number of VICs available in the system, for
|
||||
power management.
|
||||
|
||||
config IRQ_MSI_LIB
|
||||
bool
|
||||
|
||||
config ARMADA_370_XP_IRQ
|
||||
bool
|
||||
select GENERIC_IRQ_CHIP
|
||||
@@ -169,6 +174,18 @@ config IXP4XX_IRQ
|
||||
select IRQ_DOMAIN
|
||||
select SPARSE_IRQ
|
||||
|
||||
config LAN966X_OIC
|
||||
tristate "Microchip LAN966x OIC Support"
|
||||
select GENERIC_IRQ_CHIP
|
||||
select IRQ_DOMAIN
|
||||
help
|
||||
Enable support for the LAN966x Outbound Interrupt Controller.
|
||||
This controller is present on the Microchip LAN966x PCI device and
|
||||
maps the internal interrupts sources to PCIe interrupt.
|
||||
|
||||
To compile this driver as a module, choose M here: the module
|
||||
will be called irq-lan966x-oic.
|
||||
|
||||
config MADERA_IRQ
|
||||
tristate
|
||||
|
||||
@@ -366,6 +383,7 @@ config MSCC_OCELOT_IRQ
|
||||
select GENERIC_IRQ_CHIP
|
||||
|
||||
config MVEBU_GICP
|
||||
select IRQ_MSI_LIB
|
||||
bool
|
||||
|
||||
config MVEBU_ICU
|
||||
@@ -373,6 +391,7 @@ config MVEBU_ICU
|
||||
|
||||
config MVEBU_ODMI
|
||||
bool
|
||||
select IRQ_MSI_LIB
|
||||
select GENERIC_MSI_IRQ
|
||||
|
||||
config MVEBU_PIC
|
||||
@@ -392,6 +411,15 @@ config LS_SCFG_MSI
|
||||
config PARTITION_PERCPU
|
||||
bool
|
||||
|
||||
config STM32MP_EXTI
|
||||
tristate "STM32MP extended interrupts and event controller"
|
||||
depends on (ARCH_STM32 && !ARM_SINGLE_ARMV7M) || COMPILE_TEST
|
||||
default y
|
||||
select IRQ_DOMAIN_HIERARCHY
|
||||
select GENERIC_IRQ_CHIP
|
||||
help
|
||||
Support STM32MP EXTI (extended interrupts and event) controller.
|
||||
|
||||
config STM32_EXTI
|
||||
bool
|
||||
select IRQ_DOMAIN
|
||||
@@ -487,6 +515,7 @@ config IMX_MU_MSI
|
||||
select IRQ_DOMAIN
|
||||
select IRQ_DOMAIN_HIERARCHY
|
||||
select GENERIC_MSI_IRQ
|
||||
select IRQ_MSI_LIB
|
||||
help
|
||||
Provide a driver for the i.MX Messaging Unit block used as a
|
||||
CPU-to-CPU MSI controller. This requires a specially crafted DT
|
||||
|
||||
@@ -29,10 +29,10 @@ obj-$(CONFIG_ARCH_SPEAR3XX) += spear-shirq.o
|
||||
obj-$(CONFIG_ARM_GIC) += irq-gic.o irq-gic-common.o
|
||||
obj-$(CONFIG_ARM_GIC_PM) += irq-gic-pm.o
|
||||
obj-$(CONFIG_ARCH_REALVIEW) += irq-gic-realview.o
|
||||
obj-$(CONFIG_IRQ_MSI_LIB) += irq-msi-lib.o
|
||||
obj-$(CONFIG_ARM_GIC_V2M) += irq-gic-v2m.o
|
||||
obj-$(CONFIG_ARM_GIC_V3) += irq-gic-v3.o irq-gic-v3-mbi.o irq-gic-common.o
|
||||
obj-$(CONFIG_ARM_GIC_V3_ITS) += irq-gic-v3-its.o irq-gic-v3-its-platform-msi.o irq-gic-v4.o
|
||||
obj-$(CONFIG_ARM_GIC_V3_ITS_PCI) += irq-gic-v3-its-pci-msi.o
|
||||
obj-$(CONFIG_ARM_GIC_V3_ITS) += irq-gic-v3-its.o irq-gic-v4.o irq-gic-v3-its-msi-parent.o
|
||||
obj-$(CONFIG_ARM_GIC_V3_ITS_FSL_MC) += irq-gic-v3-its-fsl-mc-msi.o
|
||||
obj-$(CONFIG_PARTITION_PERCPU) += irq-partition-percpu.o
|
||||
obj-$(CONFIG_HISILICON_IRQ_MBIGEN) += irq-mbigen.o
|
||||
@@ -84,6 +84,7 @@ 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_ARCH_ASPEED) += irq-aspeed-vic.o irq-aspeed-i2c-ic.o irq-aspeed-scu-ic.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
|
||||
obj-$(CONFIG_IRQ_UNIPHIER_AIDET) += irq-uniphier-aidet.o
|
||||
@@ -104,6 +105,7 @@ obj-$(CONFIG_IMX_IRQSTEER) += irq-imx-irqsteer.o
|
||||
obj-$(CONFIG_IMX_INTMUX) += irq-imx-intmux.o
|
||||
obj-$(CONFIG_IMX_MU_MSI) += irq-imx-mu-msi.o
|
||||
obj-$(CONFIG_MADERA_IRQ) += irq-madera.o
|
||||
obj-$(CONFIG_LAN966X_OIC) += irq-lan966x-oic.o
|
||||
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
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* warranty of any kind, whether express or implied.
|
||||
*/
|
||||
|
||||
#include <linux/bits.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/init.h>
|
||||
@@ -29,6 +30,7 @@
|
||||
#include <linux/slab.h>
|
||||
#include <linux/syscore_ops.h>
|
||||
#include <linux/msi.h>
|
||||
#include <linux/types.h>
|
||||
#include <asm/mach/arch.h>
|
||||
#include <asm/exception.h>
|
||||
#include <asm/smp_plat.h>
|
||||
@@ -135,6 +137,7 @@
|
||||
|
||||
#define ARMADA_370_XP_MAX_PER_CPU_IRQS (28)
|
||||
|
||||
/* IPI and MSI interrupt definitions for IPI platforms */
|
||||
#define IPI_DOORBELL_START (0)
|
||||
#define IPI_DOORBELL_END (8)
|
||||
#define IPI_DOORBELL_MASK 0xFF
|
||||
@@ -143,6 +146,14 @@
|
||||
#define PCI_MSI_DOORBELL_END (32)
|
||||
#define PCI_MSI_DOORBELL_MASK 0xFFFF0000
|
||||
|
||||
/* MSI interrupt definitions for non-IPI platforms */
|
||||
#define PCI_MSI_FULL_DOORBELL_START 0
|
||||
#define PCI_MSI_FULL_DOORBELL_NR 32
|
||||
#define PCI_MSI_FULL_DOORBELL_END 32
|
||||
#define PCI_MSI_FULL_DOORBELL_MASK GENMASK(31, 0)
|
||||
#define PCI_MSI_FULL_DOORBELL_SRC0_MASK GENMASK(15, 0)
|
||||
#define PCI_MSI_FULL_DOORBELL_SRC1_MASK GENMASK(31, 16)
|
||||
|
||||
static void __iomem *per_cpu_int_base;
|
||||
static void __iomem *main_int_base;
|
||||
static struct irq_domain *armada_370_xp_mpic_domain;
|
||||
@@ -151,11 +162,46 @@ static int parent_irq;
|
||||
#ifdef CONFIG_PCI_MSI
|
||||
static struct irq_domain *armada_370_xp_msi_domain;
|
||||
static struct irq_domain *armada_370_xp_msi_inner_domain;
|
||||
static DECLARE_BITMAP(msi_used, PCI_MSI_DOORBELL_NR);
|
||||
static DECLARE_BITMAP(msi_used, PCI_MSI_FULL_DOORBELL_NR);
|
||||
static DEFINE_MUTEX(msi_used_lock);
|
||||
static phys_addr_t msi_doorbell_addr;
|
||||
#endif
|
||||
|
||||
static inline bool is_ipi_available(void)
|
||||
{
|
||||
/*
|
||||
* We distinguish IPI availability in the IC by the IC not having a
|
||||
* parent irq defined. If a parent irq is defined, there is a parent
|
||||
* interrupt controller (e.g. GIC) that takes care of inter-processor
|
||||
* interrupts.
|
||||
*/
|
||||
return parent_irq <= 0;
|
||||
}
|
||||
|
||||
static inline u32 msi_doorbell_mask(void)
|
||||
{
|
||||
return is_ipi_available() ? PCI_MSI_DOORBELL_MASK :
|
||||
PCI_MSI_FULL_DOORBELL_MASK;
|
||||
}
|
||||
|
||||
static inline unsigned int msi_doorbell_start(void)
|
||||
{
|
||||
return is_ipi_available() ? PCI_MSI_DOORBELL_START :
|
||||
PCI_MSI_FULL_DOORBELL_START;
|
||||
}
|
||||
|
||||
static inline unsigned int msi_doorbell_size(void)
|
||||
{
|
||||
return is_ipi_available() ? PCI_MSI_DOORBELL_NR :
|
||||
PCI_MSI_FULL_DOORBELL_NR;
|
||||
}
|
||||
|
||||
static inline unsigned int msi_doorbell_end(void)
|
||||
{
|
||||
return is_ipi_available() ? PCI_MSI_DOORBELL_END :
|
||||
PCI_MSI_FULL_DOORBELL_END;
|
||||
}
|
||||
|
||||
static inline bool is_percpu_irq(irq_hw_number_t irq)
|
||||
{
|
||||
if (irq <= ARMADA_370_XP_MAX_PER_CPU_IRQS)
|
||||
@@ -213,7 +259,7 @@ static void armada_370_xp_compose_msi_msg(struct irq_data *data, struct msi_msg
|
||||
|
||||
msg->address_lo = lower_32_bits(msi_doorbell_addr);
|
||||
msg->address_hi = upper_32_bits(msi_doorbell_addr);
|
||||
msg->data = BIT(cpu + 8) | (data->hwirq + PCI_MSI_DOORBELL_START);
|
||||
msg->data = BIT(cpu + 8) | (data->hwirq + msi_doorbell_start());
|
||||
}
|
||||
|
||||
static int armada_370_xp_msi_set_affinity(struct irq_data *irq_data,
|
||||
@@ -246,7 +292,7 @@ static int armada_370_xp_msi_alloc(struct irq_domain *domain, unsigned int virq,
|
||||
int hwirq, i;
|
||||
|
||||
mutex_lock(&msi_used_lock);
|
||||
hwirq = bitmap_find_free_region(msi_used, PCI_MSI_DOORBELL_NR,
|
||||
hwirq = bitmap_find_free_region(msi_used, msi_doorbell_size(),
|
||||
order_base_2(nr_irqs));
|
||||
mutex_unlock(&msi_used_lock);
|
||||
|
||||
@@ -283,9 +329,10 @@ static void armada_370_xp_msi_reenable_percpu(void)
|
||||
u32 reg;
|
||||
|
||||
/* Enable MSI doorbell mask and combined cpu local interrupt */
|
||||
reg = readl(per_cpu_int_base + ARMADA_370_XP_IN_DRBEL_MSK_OFFS)
|
||||
| PCI_MSI_DOORBELL_MASK;
|
||||
reg = readl(per_cpu_int_base + ARMADA_370_XP_IN_DRBEL_MSK_OFFS);
|
||||
reg |= msi_doorbell_mask();
|
||||
writel(reg, per_cpu_int_base + ARMADA_370_XP_IN_DRBEL_MSK_OFFS);
|
||||
|
||||
/* Unmask local doorbell interrupt */
|
||||
writel(1, per_cpu_int_base + ARMADA_370_XP_INT_CLEAR_MASK_OFFS);
|
||||
}
|
||||
@@ -297,7 +344,7 @@ static int armada_370_xp_msi_init(struct device_node *node,
|
||||
ARMADA_370_XP_SW_TRIG_INT_OFFS;
|
||||
|
||||
armada_370_xp_msi_inner_domain =
|
||||
irq_domain_add_linear(NULL, PCI_MSI_DOORBELL_NR,
|
||||
irq_domain_add_linear(NULL, msi_doorbell_size(),
|
||||
&armada_370_xp_msi_domain_ops, NULL);
|
||||
if (!armada_370_xp_msi_inner_domain)
|
||||
return -ENOMEM;
|
||||
@@ -313,6 +360,10 @@ static int armada_370_xp_msi_init(struct device_node *node,
|
||||
|
||||
armada_370_xp_msi_reenable_percpu();
|
||||
|
||||
/* Unmask low 16 MSI irqs on non-IPI platforms */
|
||||
if (!is_ipi_available())
|
||||
writel(0, per_cpu_int_base + ARMADA_370_XP_INT_CLEAR_MASK_OFFS);
|
||||
|
||||
return 0;
|
||||
}
|
||||
#else
|
||||
@@ -461,24 +512,18 @@ static __init void armada_xp_ipi_init(struct device_node *node)
|
||||
set_smp_ipi_range(base_ipi, IPI_DOORBELL_END);
|
||||
}
|
||||
|
||||
static DEFINE_RAW_SPINLOCK(irq_controller_lock);
|
||||
|
||||
static int armada_xp_set_affinity(struct irq_data *d,
|
||||
const struct cpumask *mask_val, bool force)
|
||||
{
|
||||
irq_hw_number_t hwirq = irqd_to_hwirq(d);
|
||||
unsigned long reg, mask;
|
||||
int cpu;
|
||||
|
||||
/* Select a single core from the affinity mask which is online */
|
||||
cpu = cpumask_any_and(mask_val, cpu_online_mask);
|
||||
mask = 1UL << cpu_logical_map(cpu);
|
||||
|
||||
raw_spin_lock(&irq_controller_lock);
|
||||
reg = readl(main_int_base + ARMADA_370_XP_INT_SOURCE_CTL(hwirq));
|
||||
reg = (reg & (~ARMADA_370_XP_INT_SOURCE_CPU_MASK)) | mask;
|
||||
writel(reg, main_int_base + ARMADA_370_XP_INT_SOURCE_CTL(hwirq));
|
||||
raw_spin_unlock(&irq_controller_lock);
|
||||
atomic_io_modify(main_int_base + ARMADA_370_XP_INT_SOURCE_CTL(hwirq),
|
||||
ARMADA_370_XP_INT_SOURCE_CPU_MASK,
|
||||
BIT(cpu_logical_map(cpu)));
|
||||
|
||||
irq_data_update_effective_affinity(d, cpumask_of(cpu));
|
||||
|
||||
@@ -496,6 +541,9 @@ static void armada_xp_mpic_smp_cpu_init(void)
|
||||
for (i = 0; i < nr_irqs; i++)
|
||||
writel(i, per_cpu_int_base + ARMADA_370_XP_INT_SET_MASK_OFFS);
|
||||
|
||||
if (!is_ipi_available())
|
||||
return;
|
||||
|
||||
/* Disable all IPIs */
|
||||
writel(0, per_cpu_int_base + ARMADA_370_XP_IN_DRBEL_MSK_OFFS);
|
||||
|
||||
@@ -527,7 +575,8 @@ static void armada_xp_mpic_reenable_percpu(void)
|
||||
armada_370_xp_irq_unmask(data);
|
||||
}
|
||||
|
||||
ipi_resume();
|
||||
if (is_ipi_available())
|
||||
ipi_resume();
|
||||
|
||||
armada_370_xp_msi_reenable_percpu();
|
||||
}
|
||||
@@ -566,6 +615,10 @@ static struct irq_chip armada_370_xp_irq_chip = {
|
||||
static int armada_370_xp_mpic_irq_map(struct irq_domain *h,
|
||||
unsigned int virq, irq_hw_number_t hw)
|
||||
{
|
||||
/* IRQs 0 and 1 cannot be mapped, they are handled internally */
|
||||
if (hw <= 1)
|
||||
return -EINVAL;
|
||||
|
||||
armada_370_xp_irq_mask(irq_get_irq_data(virq));
|
||||
if (!is_percpu_irq(hw))
|
||||
writel(hw, per_cpu_int_base +
|
||||
@@ -599,20 +652,20 @@ static void armada_370_xp_handle_msi_irq(struct pt_regs *regs, bool is_chained)
|
||||
u32 msimask, msinr;
|
||||
|
||||
msimask = readl_relaxed(per_cpu_int_base +
|
||||
ARMADA_370_XP_IN_DRBEL_CAUSE_OFFS)
|
||||
& PCI_MSI_DOORBELL_MASK;
|
||||
ARMADA_370_XP_IN_DRBEL_CAUSE_OFFS);
|
||||
msimask &= msi_doorbell_mask();
|
||||
|
||||
writel(~msimask, per_cpu_int_base +
|
||||
ARMADA_370_XP_IN_DRBEL_CAUSE_OFFS);
|
||||
|
||||
for (msinr = PCI_MSI_DOORBELL_START;
|
||||
msinr < PCI_MSI_DOORBELL_END; msinr++) {
|
||||
for (msinr = msi_doorbell_start();
|
||||
msinr < msi_doorbell_end(); msinr++) {
|
||||
unsigned int irq;
|
||||
|
||||
if (!(msimask & BIT(msinr)))
|
||||
continue;
|
||||
|
||||
irq = msinr - PCI_MSI_DOORBELL_START;
|
||||
irq = msinr - msi_doorbell_start();
|
||||
|
||||
generic_handle_domain_irq(armada_370_xp_msi_inner_domain, irq);
|
||||
}
|
||||
@@ -641,7 +694,7 @@ static void armada_370_xp_mpic_handle_cascade_irq(struct irq_desc *desc)
|
||||
if (!(irqsrc & ARMADA_370_XP_INT_IRQ_FIQ_MASK(cpuid)))
|
||||
continue;
|
||||
|
||||
if (irqn == 1) {
|
||||
if (irqn == 0 || irqn == 1) {
|
||||
armada_370_xp_handle_msi_irq(NULL, true);
|
||||
continue;
|
||||
}
|
||||
@@ -702,6 +755,7 @@ static int armada_370_xp_mpic_suspend(void)
|
||||
|
||||
static void armada_370_xp_mpic_resume(void)
|
||||
{
|
||||
bool src0, src1;
|
||||
int nirqs;
|
||||
irq_hw_number_t irq;
|
||||
|
||||
@@ -741,12 +795,22 @@ static void armada_370_xp_mpic_resume(void)
|
||||
/* Reconfigure doorbells for IPIs and MSIs */
|
||||
writel(doorbell_mask_reg,
|
||||
per_cpu_int_base + ARMADA_370_XP_IN_DRBEL_MSK_OFFS);
|
||||
if (doorbell_mask_reg & IPI_DOORBELL_MASK)
|
||||
|
||||
if (is_ipi_available()) {
|
||||
src0 = doorbell_mask_reg & IPI_DOORBELL_MASK;
|
||||
src1 = doorbell_mask_reg & PCI_MSI_DOORBELL_MASK;
|
||||
} else {
|
||||
src0 = doorbell_mask_reg & PCI_MSI_FULL_DOORBELL_SRC0_MASK;
|
||||
src1 = doorbell_mask_reg & PCI_MSI_FULL_DOORBELL_SRC1_MASK;
|
||||
}
|
||||
|
||||
if (src0)
|
||||
writel(0, per_cpu_int_base + ARMADA_370_XP_INT_CLEAR_MASK_OFFS);
|
||||
if (doorbell_mask_reg & PCI_MSI_DOORBELL_MASK)
|
||||
if (src1)
|
||||
writel(1, per_cpu_int_base + ARMADA_370_XP_INT_CLEAR_MASK_OFFS);
|
||||
|
||||
ipi_resume();
|
||||
if (is_ipi_available())
|
||||
ipi_resume();
|
||||
}
|
||||
|
||||
static struct syscore_ops armada_370_xp_mpic_syscore_ops = {
|
||||
@@ -791,13 +855,18 @@ static int __init armada_370_xp_mpic_of_init(struct device_node *node,
|
||||
BUG_ON(!armada_370_xp_mpic_domain);
|
||||
irq_domain_update_bus_token(armada_370_xp_mpic_domain, DOMAIN_BUS_WIRED);
|
||||
|
||||
/*
|
||||
* Initialize parent_irq before calling any other functions, since it is
|
||||
* used to distinguish between IPI and non-IPI platforms.
|
||||
*/
|
||||
parent_irq = irq_of_parse_and_map(node, 0);
|
||||
|
||||
/* Setup for the boot CPU */
|
||||
armada_xp_mpic_perf_init();
|
||||
armada_xp_mpic_smp_cpu_init();
|
||||
|
||||
armada_370_xp_msi_init(node, main_int_res.start);
|
||||
|
||||
parent_irq = irq_of_parse_and_map(node, 0);
|
||||
if (parent_irq <= 0) {
|
||||
irq_set_default_host(armada_370_xp_mpic_domain);
|
||||
set_handle_irq(armada_370_xp_handle_irq);
|
||||
|
||||
@@ -102,7 +102,9 @@ static void armctrl_unmask_irq(struct irq_data *d)
|
||||
static struct irq_chip armctrl_chip = {
|
||||
.name = "ARMCTRL-level",
|
||||
.irq_mask = armctrl_mask_irq,
|
||||
.irq_unmask = armctrl_unmask_irq
|
||||
.irq_unmask = armctrl_unmask_irq,
|
||||
.flags = IRQCHIP_MASK_ON_SUSPEND |
|
||||
IRQCHIP_SKIP_SET_WAKE,
|
||||
};
|
||||
|
||||
static int armctrl_xlate(struct irq_domain *d, struct device_node *ctrlr,
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
#include <linux/of.h>
|
||||
#include <linux/irqdomain.h>
|
||||
#include <linux/msi.h>
|
||||
#include <linux/irqchip/arm-gic-common.h>
|
||||
|
||||
struct gic_quirk {
|
||||
@@ -28,6 +29,8 @@ void gic_enable_quirks(u32 iidr, const struct gic_quirk *quirks,
|
||||
void gic_enable_of_quirks(const struct device_node *np,
|
||||
const struct gic_quirk *quirks, void *data);
|
||||
|
||||
extern const struct msi_parent_ops gic_v3_its_msi_parent_ops;
|
||||
|
||||
#define RDIST_FLAGS_PROPBASE_NEEDS_FLUSHING (1 << 0)
|
||||
#define RDIST_FLAGS_RD_TABLES_PREALLOCATED (1 << 1)
|
||||
#define RDIST_FLAGS_FORCE_NON_SHAREABLE (1 << 2)
|
||||
|
||||
@@ -26,6 +26,8 @@
|
||||
#include <linux/irqchip/arm-gic.h>
|
||||
#include <linux/irqchip/arm-gic-common.h>
|
||||
|
||||
#include "irq-msi-lib.h"
|
||||
|
||||
/*
|
||||
* MSI_TYPER:
|
||||
* [31:26] Reserved
|
||||
@@ -72,31 +74,6 @@ struct v2m_data {
|
||||
u32 flags; /* v2m flags for specific implementation */
|
||||
};
|
||||
|
||||
static void gicv2m_mask_msi_irq(struct irq_data *d)
|
||||
{
|
||||
pci_msi_mask_irq(d);
|
||||
irq_chip_mask_parent(d);
|
||||
}
|
||||
|
||||
static void gicv2m_unmask_msi_irq(struct irq_data *d)
|
||||
{
|
||||
pci_msi_unmask_irq(d);
|
||||
irq_chip_unmask_parent(d);
|
||||
}
|
||||
|
||||
static struct irq_chip gicv2m_msi_irq_chip = {
|
||||
.name = "MSI",
|
||||
.irq_mask = gicv2m_mask_msi_irq,
|
||||
.irq_unmask = gicv2m_unmask_msi_irq,
|
||||
.irq_eoi = irq_chip_eoi_parent,
|
||||
};
|
||||
|
||||
static struct msi_domain_info gicv2m_msi_domain_info = {
|
||||
.flags = (MSI_FLAG_USE_DEF_DOM_OPS | MSI_FLAG_USE_DEF_CHIP_OPS |
|
||||
MSI_FLAG_PCI_MSIX | MSI_FLAG_MULTI_PCI_MSI),
|
||||
.chip = &gicv2m_msi_irq_chip,
|
||||
};
|
||||
|
||||
static phys_addr_t gicv2m_get_msi_addr(struct v2m_data *v2m, int hwirq)
|
||||
{
|
||||
if (v2m->flags & GICV2M_GRAVITON_ADDRESS_ONLY)
|
||||
@@ -230,6 +207,7 @@ static void gicv2m_irq_domain_free(struct irq_domain *domain,
|
||||
}
|
||||
|
||||
static const struct irq_domain_ops gicv2m_domain_ops = {
|
||||
.select = msi_lib_irq_domain_select,
|
||||
.alloc = gicv2m_irq_domain_alloc,
|
||||
.free = gicv2m_irq_domain_free,
|
||||
};
|
||||
@@ -250,19 +228,6 @@ static bool is_msi_spi_valid(u32 base, u32 num)
|
||||
return true;
|
||||
}
|
||||
|
||||
static struct irq_chip gicv2m_pmsi_irq_chip = {
|
||||
.name = "pMSI",
|
||||
};
|
||||
|
||||
static struct msi_domain_ops gicv2m_pmsi_ops = {
|
||||
};
|
||||
|
||||
static struct msi_domain_info gicv2m_pmsi_domain_info = {
|
||||
.flags = (MSI_FLAG_USE_DEF_DOM_OPS | MSI_FLAG_USE_DEF_CHIP_OPS),
|
||||
.ops = &gicv2m_pmsi_ops,
|
||||
.chip = &gicv2m_pmsi_irq_chip,
|
||||
};
|
||||
|
||||
static void __init gicv2m_teardown(void)
|
||||
{
|
||||
struct v2m_data *v2m, *tmp;
|
||||
@@ -278,9 +243,27 @@ static void __init gicv2m_teardown(void)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#define GICV2M_MSI_FLAGS_REQUIRED (MSI_FLAG_USE_DEF_DOM_OPS | \
|
||||
MSI_FLAG_USE_DEF_CHIP_OPS | \
|
||||
MSI_FLAG_PCI_MSI_MASK_PARENT)
|
||||
|
||||
#define GICV2M_MSI_FLAGS_SUPPORTED (MSI_GENERIC_FLAGS_MASK | \
|
||||
MSI_FLAG_PCI_MSIX | \
|
||||
MSI_FLAG_MULTI_PCI_MSI)
|
||||
|
||||
static struct msi_parent_ops gicv2m_msi_parent_ops = {
|
||||
.supported_flags = GICV2M_MSI_FLAGS_SUPPORTED,
|
||||
.required_flags = GICV2M_MSI_FLAGS_REQUIRED,
|
||||
.bus_select_token = DOMAIN_BUS_NEXUS,
|
||||
.bus_select_mask = MATCH_PCI_MSI | MATCH_PLATFORM_MSI,
|
||||
.prefix = "GICv2m-",
|
||||
.init_dev_msi_info = msi_lib_init_dev_msi_info,
|
||||
};
|
||||
|
||||
static __init int gicv2m_allocate_domains(struct irq_domain *parent)
|
||||
{
|
||||
struct irq_domain *inner_domain, *pci_domain, *plat_domain;
|
||||
struct irq_domain *inner_domain;
|
||||
struct v2m_data *v2m;
|
||||
|
||||
v2m = list_first_entry_or_null(&v2m_nodes, struct v2m_data, entry);
|
||||
@@ -295,22 +278,8 @@ static __init int gicv2m_allocate_domains(struct irq_domain *parent)
|
||||
}
|
||||
|
||||
irq_domain_update_bus_token(inner_domain, DOMAIN_BUS_NEXUS);
|
||||
pci_domain = pci_msi_create_irq_domain(v2m->fwnode,
|
||||
&gicv2m_msi_domain_info,
|
||||
inner_domain);
|
||||
plat_domain = platform_msi_create_irq_domain(v2m->fwnode,
|
||||
&gicv2m_pmsi_domain_info,
|
||||
inner_domain);
|
||||
if (!pci_domain || !plat_domain) {
|
||||
pr_err("Failed to create MSI domains\n");
|
||||
if (plat_domain)
|
||||
irq_domain_remove(plat_domain);
|
||||
if (pci_domain)
|
||||
irq_domain_remove(pci_domain);
|
||||
irq_domain_remove(inner_domain);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
inner_domain->flags |= IRQ_DOMAIN_FLAG_MSI_PARENT;
|
||||
inner_domain->msi_parent_ops = &gicv2m_msi_parent_ops;
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -511,7 +480,7 @@ acpi_parse_madt_msi(union acpi_subtable_headers *header,
|
||||
pr_info("applying Amazon Graviton quirk\n");
|
||||
res.end = res.start + SZ_8K - 1;
|
||||
flags |= GICV2M_GRAVITON_ADDRESS_ONLY;
|
||||
gicv2m_msi_domain_info.flags &= ~MSI_FLAG_MULTI_PCI_MSI;
|
||||
gicv2m_msi_parent_ops.supported_flags &= ~MSI_FLAG_MULTI_PCI_MSI;
|
||||
}
|
||||
|
||||
if (m->flags & ACPI_MADT_OVERRIDE_SPI_VALUES) {
|
||||
|
||||
@@ -0,0 +1,210 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
// Copyright (C) 2013-2015 ARM Limited, All Rights Reserved.
|
||||
// Author: Marc Zyngier <marc.zyngier@arm.com>
|
||||
// Copyright (C) 2022 Linutronix GmbH
|
||||
// Copyright (C) 2022 Intel
|
||||
|
||||
#include <linux/acpi_iort.h>
|
||||
#include <linux/pci.h>
|
||||
|
||||
#include "irq-gic-common.h"
|
||||
#include "irq-msi-lib.h"
|
||||
|
||||
#define ITS_MSI_FLAGS_REQUIRED (MSI_FLAG_USE_DEF_DOM_OPS | \
|
||||
MSI_FLAG_USE_DEF_CHIP_OPS | \
|
||||
MSI_FLAG_PCI_MSI_MASK_PARENT)
|
||||
|
||||
#define ITS_MSI_FLAGS_SUPPORTED (MSI_GENERIC_FLAGS_MASK | \
|
||||
MSI_FLAG_PCI_MSIX | \
|
||||
MSI_FLAG_MULTI_PCI_MSI)
|
||||
|
||||
#ifdef CONFIG_PCI_MSI
|
||||
static int its_pci_msi_vec_count(struct pci_dev *pdev, void *data)
|
||||
{
|
||||
int msi, msix, *count = data;
|
||||
|
||||
msi = max(pci_msi_vec_count(pdev), 0);
|
||||
msix = max(pci_msix_vec_count(pdev), 0);
|
||||
*count += max(msi, msix);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int its_get_pci_alias(struct pci_dev *pdev, u16 alias, void *data)
|
||||
{
|
||||
struct pci_dev **alias_dev = data;
|
||||
|
||||
*alias_dev = pdev;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int its_pci_msi_prepare(struct irq_domain *domain, struct device *dev,
|
||||
int nvec, msi_alloc_info_t *info)
|
||||
{
|
||||
struct pci_dev *pdev, *alias_dev;
|
||||
struct msi_domain_info *msi_info;
|
||||
int alias_count = 0, minnvec = 1;
|
||||
|
||||
if (!dev_is_pci(dev))
|
||||
return -EINVAL;
|
||||
|
||||
pdev = to_pci_dev(dev);
|
||||
/*
|
||||
* If pdev is downstream of any aliasing bridges, take an upper
|
||||
* bound of how many other vectors could map to the same DevID.
|
||||
* Also tell the ITS that the signalling will come from a proxy
|
||||
* device, and that special allocation rules apply.
|
||||
*/
|
||||
pci_for_each_dma_alias(pdev, its_get_pci_alias, &alias_dev);
|
||||
if (alias_dev != pdev) {
|
||||
if (alias_dev->subordinate)
|
||||
pci_walk_bus(alias_dev->subordinate,
|
||||
its_pci_msi_vec_count, &alias_count);
|
||||
info->flags |= MSI_ALLOC_FLAGS_PROXY_DEVICE;
|
||||
}
|
||||
|
||||
/* ITS specific DeviceID, as the core ITS ignores dev. */
|
||||
info->scratchpad[0].ul = pci_msi_domain_get_msi_rid(domain->parent, pdev);
|
||||
|
||||
/*
|
||||
* @domain->msi_domain_info->hwsize contains the size of the
|
||||
* MSI[-X] domain, but vector allocation happens one by one. This
|
||||
* needs some thought when MSI comes into play as the size of MSI
|
||||
* might be unknown at domain creation time and therefore set to
|
||||
* MSI_MAX_INDEX.
|
||||
*/
|
||||
msi_info = msi_get_domain_info(domain);
|
||||
if (msi_info->hwsize > nvec)
|
||||
nvec = msi_info->hwsize;
|
||||
|
||||
/*
|
||||
* Always allocate a power of 2, and special case device 0 for
|
||||
* broken systems where the DevID is not wired (and all devices
|
||||
* appear as DevID 0). For that reason, we generously allocate a
|
||||
* minimum of 32 MSIs for DevID 0. If you want more because all
|
||||
* your devices are aliasing to DevID 0, consider fixing your HW.
|
||||
*/
|
||||
nvec = max(nvec, alias_count);
|
||||
if (!info->scratchpad[0].ul)
|
||||
minnvec = 32;
|
||||
nvec = max_t(int, minnvec, roundup_pow_of_two(nvec));
|
||||
|
||||
msi_info = msi_get_domain_info(domain->parent);
|
||||
return msi_info->ops->msi_prepare(domain->parent, dev, nvec, info);
|
||||
}
|
||||
#else /* CONFIG_PCI_MSI */
|
||||
#define its_pci_msi_prepare NULL
|
||||
#endif /* !CONFIG_PCI_MSI */
|
||||
|
||||
static int of_pmsi_get_dev_id(struct irq_domain *domain, struct device *dev,
|
||||
u32 *dev_id)
|
||||
{
|
||||
int ret, index = 0;
|
||||
|
||||
/* Suck the DeviceID out of the msi-parent property */
|
||||
do {
|
||||
struct of_phandle_args args;
|
||||
|
||||
ret = of_parse_phandle_with_args(dev->of_node,
|
||||
"msi-parent", "#msi-cells",
|
||||
index, &args);
|
||||
if (args.np == irq_domain_get_of_node(domain)) {
|
||||
if (WARN_ON(args.args_count != 1))
|
||||
return -EINVAL;
|
||||
*dev_id = args.args[0];
|
||||
break;
|
||||
}
|
||||
index++;
|
||||
} while (!ret);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int __weak iort_pmsi_get_dev_id(struct device *dev, u32 *dev_id)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int its_pmsi_prepare(struct irq_domain *domain, struct device *dev,
|
||||
int nvec, msi_alloc_info_t *info)
|
||||
{
|
||||
struct msi_domain_info *msi_info;
|
||||
u32 dev_id;
|
||||
int ret;
|
||||
|
||||
if (dev->of_node)
|
||||
ret = of_pmsi_get_dev_id(domain->parent, dev, &dev_id);
|
||||
else
|
||||
ret = iort_pmsi_get_dev_id(dev, &dev_id);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* ITS specific DeviceID, as the core ITS ignores dev. */
|
||||
info->scratchpad[0].ul = dev_id;
|
||||
|
||||
/*
|
||||
* @domain->msi_domain_info->hwsize contains the size of the device
|
||||
* domain, but vector allocation happens one by one.
|
||||
*/
|
||||
msi_info = msi_get_domain_info(domain);
|
||||
if (msi_info->hwsize > nvec)
|
||||
nvec = msi_info->hwsize;
|
||||
|
||||
/* Allocate at least 32 MSIs, and always as a power of 2 */
|
||||
nvec = max_t(int, 32, roundup_pow_of_two(nvec));
|
||||
|
||||
msi_info = msi_get_domain_info(domain->parent);
|
||||
return msi_info->ops->msi_prepare(domain->parent,
|
||||
dev, nvec, info);
|
||||
}
|
||||
|
||||
static bool its_init_dev_msi_info(struct device *dev, struct irq_domain *domain,
|
||||
struct irq_domain *real_parent, struct msi_domain_info *info)
|
||||
{
|
||||
if (!msi_lib_init_dev_msi_info(dev, domain, real_parent, info))
|
||||
return false;
|
||||
|
||||
switch(info->bus_token) {
|
||||
case DOMAIN_BUS_PCI_DEVICE_MSI:
|
||||
case DOMAIN_BUS_PCI_DEVICE_MSIX:
|
||||
/*
|
||||
* FIXME: This probably should be done after a (not yet
|
||||
* existing) post domain creation callback once to make
|
||||
* support for dynamic post-enable MSI-X allocations
|
||||
* work without having to reevaluate the domain size
|
||||
* over and over. It is known already at allocation
|
||||
* time via info->hwsize.
|
||||
*
|
||||
* That should work perfectly fine for MSI/MSI-X but needs
|
||||
* some thoughts for purely software managed MSI domains
|
||||
* where the index space is only limited artificially via
|
||||
* %MSI_MAX_INDEX.
|
||||
*/
|
||||
info->ops->msi_prepare = its_pci_msi_prepare;
|
||||
break;
|
||||
case DOMAIN_BUS_DEVICE_MSI:
|
||||
case DOMAIN_BUS_WIRED_TO_MSI:
|
||||
/*
|
||||
* FIXME: See the above PCI prepare comment. The domain
|
||||
* size is also known at domain creation time.
|
||||
*/
|
||||
info->ops->msi_prepare = its_pmsi_prepare;
|
||||
break;
|
||||
default:
|
||||
/* Confused. How did the lib return true? */
|
||||
WARN_ON_ONCE(1);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
const struct msi_parent_ops gic_v3_its_msi_parent_ops = {
|
||||
.supported_flags = ITS_MSI_FLAGS_SUPPORTED,
|
||||
.required_flags = ITS_MSI_FLAGS_REQUIRED,
|
||||
.bus_select_token = DOMAIN_BUS_NEXUS,
|
||||
.bus_select_mask = MATCH_PCI_MSI | MATCH_PLATFORM_MSI,
|
||||
.prefix = "ITS-",
|
||||
.init_dev_msi_info = its_init_dev_msi_info,
|
||||
};
|
||||
@@ -1,202 +0,0 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Copyright (C) 2013-2015 ARM Limited, All Rights Reserved.
|
||||
* Author: Marc Zyngier <marc.zyngier@arm.com>
|
||||
*/
|
||||
|
||||
#include <linux/acpi_iort.h>
|
||||
#include <linux/pci.h>
|
||||
#include <linux/msi.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_irq.h>
|
||||
#include <linux/of_pci.h>
|
||||
|
||||
static void its_mask_msi_irq(struct irq_data *d)
|
||||
{
|
||||
pci_msi_mask_irq(d);
|
||||
irq_chip_mask_parent(d);
|
||||
}
|
||||
|
||||
static void its_unmask_msi_irq(struct irq_data *d)
|
||||
{
|
||||
pci_msi_unmask_irq(d);
|
||||
irq_chip_unmask_parent(d);
|
||||
}
|
||||
|
||||
static struct irq_chip its_msi_irq_chip = {
|
||||
.name = "ITS-MSI",
|
||||
.irq_unmask = its_unmask_msi_irq,
|
||||
.irq_mask = its_mask_msi_irq,
|
||||
.irq_eoi = irq_chip_eoi_parent,
|
||||
};
|
||||
|
||||
static int its_pci_msi_vec_count(struct pci_dev *pdev, void *data)
|
||||
{
|
||||
int msi, msix, *count = data;
|
||||
|
||||
msi = max(pci_msi_vec_count(pdev), 0);
|
||||
msix = max(pci_msix_vec_count(pdev), 0);
|
||||
*count += max(msi, msix);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int its_get_pci_alias(struct pci_dev *pdev, u16 alias, void *data)
|
||||
{
|
||||
struct pci_dev **alias_dev = data;
|
||||
|
||||
*alias_dev = pdev;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int its_pci_msi_prepare(struct irq_domain *domain, struct device *dev,
|
||||
int nvec, msi_alloc_info_t *info)
|
||||
{
|
||||
struct pci_dev *pdev, *alias_dev;
|
||||
struct msi_domain_info *msi_info;
|
||||
int alias_count = 0, minnvec = 1;
|
||||
|
||||
if (!dev_is_pci(dev))
|
||||
return -EINVAL;
|
||||
|
||||
msi_info = msi_get_domain_info(domain->parent);
|
||||
|
||||
pdev = to_pci_dev(dev);
|
||||
/*
|
||||
* If pdev is downstream of any aliasing bridges, take an upper
|
||||
* bound of how many other vectors could map to the same DevID.
|
||||
* Also tell the ITS that the signalling will come from a proxy
|
||||
* device, and that special allocation rules apply.
|
||||
*/
|
||||
pci_for_each_dma_alias(pdev, its_get_pci_alias, &alias_dev);
|
||||
if (alias_dev != pdev) {
|
||||
if (alias_dev->subordinate)
|
||||
pci_walk_bus(alias_dev->subordinate,
|
||||
its_pci_msi_vec_count, &alias_count);
|
||||
info->flags |= MSI_ALLOC_FLAGS_PROXY_DEVICE;
|
||||
}
|
||||
|
||||
/* ITS specific DeviceID, as the core ITS ignores dev. */
|
||||
info->scratchpad[0].ul = pci_msi_domain_get_msi_rid(domain, pdev);
|
||||
|
||||
/*
|
||||
* Always allocate a power of 2, and special case device 0 for
|
||||
* broken systems where the DevID is not wired (and all devices
|
||||
* appear as DevID 0). For that reason, we generously allocate a
|
||||
* minimum of 32 MSIs for DevID 0. If you want more because all
|
||||
* your devices are aliasing to DevID 0, consider fixing your HW.
|
||||
*/
|
||||
nvec = max(nvec, alias_count);
|
||||
if (!info->scratchpad[0].ul)
|
||||
minnvec = 32;
|
||||
nvec = max_t(int, minnvec, roundup_pow_of_two(nvec));
|
||||
return msi_info->ops->msi_prepare(domain->parent, dev, nvec, info);
|
||||
}
|
||||
|
||||
static struct msi_domain_ops its_pci_msi_ops = {
|
||||
.msi_prepare = its_pci_msi_prepare,
|
||||
};
|
||||
|
||||
static struct msi_domain_info its_pci_msi_domain_info = {
|
||||
.flags = (MSI_FLAG_USE_DEF_DOM_OPS | MSI_FLAG_USE_DEF_CHIP_OPS |
|
||||
MSI_FLAG_MULTI_PCI_MSI | MSI_FLAG_PCI_MSIX),
|
||||
.ops = &its_pci_msi_ops,
|
||||
.chip = &its_msi_irq_chip,
|
||||
};
|
||||
|
||||
static struct of_device_id its_device_id[] = {
|
||||
{ .compatible = "arm,gic-v3-its", },
|
||||
{},
|
||||
};
|
||||
|
||||
static int __init its_pci_msi_init_one(struct fwnode_handle *handle,
|
||||
const char *name)
|
||||
{
|
||||
struct irq_domain *parent;
|
||||
|
||||
parent = irq_find_matching_fwnode(handle, DOMAIN_BUS_NEXUS);
|
||||
if (!parent || !msi_get_domain_info(parent)) {
|
||||
pr_err("%s: Unable to locate ITS domain\n", name);
|
||||
return -ENXIO;
|
||||
}
|
||||
|
||||
if (!pci_msi_create_irq_domain(handle, &its_pci_msi_domain_info,
|
||||
parent)) {
|
||||
pr_err("%s: Unable to create PCI domain\n", name);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __init its_pci_of_msi_init(void)
|
||||
{
|
||||
struct device_node *np;
|
||||
|
||||
for (np = of_find_matching_node(NULL, its_device_id); np;
|
||||
np = of_find_matching_node(np, its_device_id)) {
|
||||
if (!of_device_is_available(np))
|
||||
continue;
|
||||
if (!of_property_read_bool(np, "msi-controller"))
|
||||
continue;
|
||||
|
||||
if (its_pci_msi_init_one(of_node_to_fwnode(np), np->full_name))
|
||||
continue;
|
||||
|
||||
pr_info("PCI/MSI: %pOF domain created\n", np);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_ACPI
|
||||
|
||||
static int __init
|
||||
its_pci_msi_parse_madt(union acpi_subtable_headers *header,
|
||||
const unsigned long end)
|
||||
{
|
||||
struct acpi_madt_generic_translator *its_entry;
|
||||
struct fwnode_handle *dom_handle;
|
||||
const char *node_name;
|
||||
int err = -ENXIO;
|
||||
|
||||
its_entry = (struct acpi_madt_generic_translator *)header;
|
||||
node_name = kasprintf(GFP_KERNEL, "ITS@0x%lx",
|
||||
(long)its_entry->base_address);
|
||||
dom_handle = iort_find_domain_token(its_entry->translation_id);
|
||||
if (!dom_handle) {
|
||||
pr_err("%s: Unable to locate ITS domain handle\n", node_name);
|
||||
goto out;
|
||||
}
|
||||
|
||||
err = its_pci_msi_init_one(dom_handle, node_name);
|
||||
if (!err)
|
||||
pr_info("PCI/MSI: %s domain created\n", node_name);
|
||||
|
||||
out:
|
||||
kfree(node_name);
|
||||
return err;
|
||||
}
|
||||
|
||||
static int __init its_pci_acpi_msi_init(void)
|
||||
{
|
||||
acpi_table_parse_madt(ACPI_MADT_TYPE_GENERIC_TRANSLATOR,
|
||||
its_pci_msi_parse_madt, 0);
|
||||
return 0;
|
||||
}
|
||||
#else
|
||||
static int __init its_pci_acpi_msi_init(void)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
static int __init its_pci_msi_init(void)
|
||||
{
|
||||
its_pci_of_msi_init();
|
||||
its_pci_acpi_msi_init();
|
||||
|
||||
return 0;
|
||||
}
|
||||
early_initcall(its_pci_msi_init);
|
||||
@@ -1,163 +0,0 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Copyright (C) 2013-2015 ARM Limited, All Rights Reserved.
|
||||
* Author: Marc Zyngier <marc.zyngier@arm.com>
|
||||
*/
|
||||
|
||||
#include <linux/acpi_iort.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/msi.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_irq.h>
|
||||
|
||||
static struct irq_chip its_pmsi_irq_chip = {
|
||||
.name = "ITS-pMSI",
|
||||
};
|
||||
|
||||
static int of_pmsi_get_dev_id(struct irq_domain *domain, struct device *dev,
|
||||
u32 *dev_id)
|
||||
{
|
||||
int ret, index = 0;
|
||||
|
||||
/* Suck the DeviceID out of the msi-parent property */
|
||||
do {
|
||||
struct of_phandle_args args;
|
||||
|
||||
ret = of_parse_phandle_with_args(dev->of_node,
|
||||
"msi-parent", "#msi-cells",
|
||||
index, &args);
|
||||
if (args.np == irq_domain_get_of_node(domain)) {
|
||||
if (WARN_ON(args.args_count != 1))
|
||||
return -EINVAL;
|
||||
*dev_id = args.args[0];
|
||||
break;
|
||||
}
|
||||
index++;
|
||||
} while (!ret);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int __weak iort_pmsi_get_dev_id(struct device *dev, u32 *dev_id)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int its_pmsi_prepare(struct irq_domain *domain, struct device *dev,
|
||||
int nvec, msi_alloc_info_t *info)
|
||||
{
|
||||
struct msi_domain_info *msi_info;
|
||||
u32 dev_id;
|
||||
int ret;
|
||||
|
||||
msi_info = msi_get_domain_info(domain->parent);
|
||||
|
||||
if (dev->of_node)
|
||||
ret = of_pmsi_get_dev_id(domain, dev, &dev_id);
|
||||
else
|
||||
ret = iort_pmsi_get_dev_id(dev, &dev_id);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* ITS specific DeviceID, as the core ITS ignores dev. */
|
||||
info->scratchpad[0].ul = dev_id;
|
||||
|
||||
/* Allocate at least 32 MSIs, and always as a power of 2 */
|
||||
nvec = max_t(int, 32, roundup_pow_of_two(nvec));
|
||||
return msi_info->ops->msi_prepare(domain->parent,
|
||||
dev, nvec, info);
|
||||
}
|
||||
|
||||
static struct msi_domain_ops its_pmsi_ops = {
|
||||
.msi_prepare = its_pmsi_prepare,
|
||||
};
|
||||
|
||||
static struct msi_domain_info its_pmsi_domain_info = {
|
||||
.flags = (MSI_FLAG_USE_DEF_DOM_OPS | MSI_FLAG_USE_DEF_CHIP_OPS),
|
||||
.ops = &its_pmsi_ops,
|
||||
.chip = &its_pmsi_irq_chip,
|
||||
};
|
||||
|
||||
static const struct of_device_id its_device_id[] = {
|
||||
{ .compatible = "arm,gic-v3-its", },
|
||||
{},
|
||||
};
|
||||
|
||||
static int __init its_pmsi_init_one(struct fwnode_handle *fwnode,
|
||||
const char *name)
|
||||
{
|
||||
struct irq_domain *parent;
|
||||
|
||||
parent = irq_find_matching_fwnode(fwnode, DOMAIN_BUS_NEXUS);
|
||||
if (!parent || !msi_get_domain_info(parent)) {
|
||||
pr_err("%s: unable to locate ITS domain\n", name);
|
||||
return -ENXIO;
|
||||
}
|
||||
|
||||
if (!platform_msi_create_irq_domain(fwnode, &its_pmsi_domain_info,
|
||||
parent)) {
|
||||
pr_err("%s: unable to create platform domain\n", name);
|
||||
return -ENXIO;
|
||||
}
|
||||
|
||||
pr_info("Platform MSI: %s domain created\n", name);
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_ACPI
|
||||
static int __init
|
||||
its_pmsi_parse_madt(union acpi_subtable_headers *header,
|
||||
const unsigned long end)
|
||||
{
|
||||
struct acpi_madt_generic_translator *its_entry;
|
||||
struct fwnode_handle *domain_handle;
|
||||
const char *node_name;
|
||||
int err = -ENXIO;
|
||||
|
||||
its_entry = (struct acpi_madt_generic_translator *)header;
|
||||
node_name = kasprintf(GFP_KERNEL, "ITS@0x%lx",
|
||||
(long)its_entry->base_address);
|
||||
domain_handle = iort_find_domain_token(its_entry->translation_id);
|
||||
if (!domain_handle) {
|
||||
pr_err("%s: Unable to locate ITS domain handle\n", node_name);
|
||||
goto out;
|
||||
}
|
||||
|
||||
err = its_pmsi_init_one(domain_handle, node_name);
|
||||
|
||||
out:
|
||||
kfree(node_name);
|
||||
return err;
|
||||
}
|
||||
|
||||
static void __init its_pmsi_acpi_init(void)
|
||||
{
|
||||
acpi_table_parse_madt(ACPI_MADT_TYPE_GENERIC_TRANSLATOR,
|
||||
its_pmsi_parse_madt, 0);
|
||||
}
|
||||
#else
|
||||
static inline void its_pmsi_acpi_init(void) { }
|
||||
#endif
|
||||
|
||||
static void __init its_pmsi_of_init(void)
|
||||
{
|
||||
struct device_node *np;
|
||||
|
||||
for (np = of_find_matching_node(NULL, its_device_id); np;
|
||||
np = of_find_matching_node(np, its_device_id)) {
|
||||
if (!of_device_is_available(np))
|
||||
continue;
|
||||
if (!of_property_read_bool(np, "msi-controller"))
|
||||
continue;
|
||||
|
||||
its_pmsi_init_one(of_node_to_fwnode(np), np->full_name);
|
||||
}
|
||||
}
|
||||
|
||||
static int __init its_pmsi_init(void)
|
||||
{
|
||||
its_pmsi_of_init();
|
||||
its_pmsi_acpi_init();
|
||||
return 0;
|
||||
}
|
||||
early_initcall(its_pmsi_init);
|
||||
@@ -38,6 +38,7 @@
|
||||
#include <asm/exception.h>
|
||||
|
||||
#include "irq-gic-common.h"
|
||||
#include "irq-msi-lib.h"
|
||||
|
||||
#define ITS_FLAGS_CMDQ_NEEDS_FLUSHING (1ULL << 0)
|
||||
#define ITS_FLAGS_WORKAROUND_CAVIUM_22375 (1ULL << 1)
|
||||
@@ -1317,7 +1318,6 @@ static void its_send_vmovp(struct its_vpe *vpe)
|
||||
{
|
||||
struct its_cmd_desc desc = {};
|
||||
struct its_node *its;
|
||||
unsigned long flags;
|
||||
int col_id = vpe->col_idx;
|
||||
|
||||
desc.its_vmovp_cmd.vpe = vpe;
|
||||
@@ -1329,6 +1329,12 @@ static void its_send_vmovp(struct its_vpe *vpe)
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* Protect against concurrent updates of the mapping state on
|
||||
* individual VMs.
|
||||
*/
|
||||
guard(raw_spinlock_irqsave)(&vpe->its_vm->vmapp_lock);
|
||||
|
||||
/*
|
||||
* Yet another marvel of the architecture. If using the
|
||||
* its_list "feature", we need to make sure that all ITSs
|
||||
@@ -1337,8 +1343,7 @@ static void its_send_vmovp(struct its_vpe *vpe)
|
||||
*
|
||||
* Wall <-- Head.
|
||||
*/
|
||||
raw_spin_lock_irqsave(&vmovp_lock, flags);
|
||||
|
||||
guard(raw_spinlock)(&vmovp_lock);
|
||||
desc.its_vmovp_cmd.seq_num = vmovp_seq_num++;
|
||||
desc.its_vmovp_cmd.its_list = get_its_list(vpe->its_vm);
|
||||
|
||||
@@ -1353,8 +1358,6 @@ static void its_send_vmovp(struct its_vpe *vpe)
|
||||
desc.its_vmovp_cmd.col = &its->collections[col_id];
|
||||
its_send_single_vcommand(its, its_build_vmovp_cmd, &desc);
|
||||
}
|
||||
|
||||
raw_spin_unlock_irqrestore(&vmovp_lock, flags);
|
||||
}
|
||||
|
||||
static void its_send_vinvall(struct its_node *its, struct its_vpe *vpe)
|
||||
@@ -1791,12 +1794,10 @@ static bool gic_requires_eager_mapping(void)
|
||||
|
||||
static void its_map_vm(struct its_node *its, struct its_vm *vm)
|
||||
{
|
||||
unsigned long flags;
|
||||
|
||||
if (gic_requires_eager_mapping())
|
||||
return;
|
||||
|
||||
raw_spin_lock_irqsave(&vmovp_lock, flags);
|
||||
guard(raw_spinlock_irqsave)(&vm->vmapp_lock);
|
||||
|
||||
/*
|
||||
* If the VM wasn't mapped yet, iterate over the vpes and get
|
||||
@@ -1809,37 +1810,31 @@ static void its_map_vm(struct its_node *its, struct its_vm *vm)
|
||||
|
||||
for (i = 0; i < vm->nr_vpes; i++) {
|
||||
struct its_vpe *vpe = vm->vpes[i];
|
||||
struct irq_data *d = irq_get_irq_data(vpe->irq);
|
||||
|
||||
/* Map the VPE to the first possible CPU */
|
||||
vpe->col_idx = cpumask_first(cpu_online_mask);
|
||||
its_send_vmapp(its, vpe, true);
|
||||
scoped_guard(raw_spinlock, &vpe->vpe_lock)
|
||||
its_send_vmapp(its, vpe, true);
|
||||
|
||||
its_send_vinvall(its, vpe);
|
||||
irq_data_update_effective_affinity(d, cpumask_of(vpe->col_idx));
|
||||
}
|
||||
}
|
||||
|
||||
raw_spin_unlock_irqrestore(&vmovp_lock, flags);
|
||||
}
|
||||
|
||||
static void its_unmap_vm(struct its_node *its, struct its_vm *vm)
|
||||
{
|
||||
unsigned long flags;
|
||||
|
||||
/* Not using the ITS list? Everything is always mapped. */
|
||||
if (gic_requires_eager_mapping())
|
||||
return;
|
||||
|
||||
raw_spin_lock_irqsave(&vmovp_lock, flags);
|
||||
guard(raw_spinlock_irqsave)(&vm->vmapp_lock);
|
||||
|
||||
if (!--vm->vlpi_count[its->list_nr]) {
|
||||
int i;
|
||||
|
||||
for (i = 0; i < vm->nr_vpes; i++)
|
||||
for (i = 0; i < vm->nr_vpes; i++) {
|
||||
guard(raw_spinlock)(&vm->vpes[i]->vpe_lock);
|
||||
its_send_vmapp(its, vm->vpes[i], false);
|
||||
}
|
||||
}
|
||||
|
||||
raw_spin_unlock_irqrestore(&vmovp_lock, flags);
|
||||
}
|
||||
|
||||
static int its_vlpi_map(struct irq_data *d, struct its_cmd_info *info)
|
||||
@@ -3688,6 +3683,7 @@ static void its_irq_domain_free(struct irq_domain *domain, unsigned int virq,
|
||||
}
|
||||
|
||||
static const struct irq_domain_ops its_domain_ops = {
|
||||
.select = msi_lib_irq_domain_select,
|
||||
.alloc = its_irq_domain_alloc,
|
||||
.free = its_irq_domain_free,
|
||||
.activate = its_irq_domain_activate,
|
||||
@@ -3926,6 +3922,8 @@ static void its_vpe_invall(struct its_vpe *vpe)
|
||||
{
|
||||
struct its_node *its;
|
||||
|
||||
guard(raw_spinlock_irqsave)(&vpe->its_vm->vmapp_lock);
|
||||
|
||||
list_for_each_entry(its, &its_nodes, entry) {
|
||||
if (!is_v4(its))
|
||||
continue;
|
||||
@@ -4531,6 +4529,7 @@ static int its_vpe_irq_domain_alloc(struct irq_domain *domain, unsigned int virq
|
||||
vm->db_lpi_base = base;
|
||||
vm->nr_db_lpis = nr_ids;
|
||||
vm->vprop_page = vprop_page;
|
||||
raw_spin_lock_init(&vm->vmapp_lock);
|
||||
|
||||
if (gic_rdists->has_rvpeid)
|
||||
irqchip = &its_vpe_4_1_irq_chip;
|
||||
@@ -4562,6 +4561,10 @@ static int its_vpe_irq_domain_activate(struct irq_domain *domain,
|
||||
struct its_vpe *vpe = irq_data_get_irq_chip_data(d);
|
||||
struct its_node *its;
|
||||
|
||||
/* Map the VPE to the first possible CPU */
|
||||
vpe->col_idx = cpumask_first(cpu_online_mask);
|
||||
irq_data_update_effective_affinity(d, cpumask_of(vpe->col_idx));
|
||||
|
||||
/*
|
||||
* If we use the list map, we issue VMAPP on demand... Unless
|
||||
* we're on a GICv4.1 and we eagerly map the VPE on all ITSs
|
||||
@@ -4570,9 +4573,6 @@ static int its_vpe_irq_domain_activate(struct irq_domain *domain,
|
||||
if (!gic_requires_eager_mapping())
|
||||
return 0;
|
||||
|
||||
/* Map the VPE to the first possible CPU */
|
||||
vpe->col_idx = cpumask_first(cpu_online_mask);
|
||||
|
||||
list_for_each_entry(its, &its_nodes, entry) {
|
||||
if (!is_v4(its))
|
||||
continue;
|
||||
@@ -4581,8 +4581,6 @@ static int its_vpe_irq_domain_activate(struct irq_domain *domain,
|
||||
its_send_vinvall(its, vpe);
|
||||
}
|
||||
|
||||
irq_data_update_effective_affinity(d, cpumask_of(vpe->col_idx));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -4993,6 +4991,9 @@ static int its_init_domain(struct its_node *its)
|
||||
|
||||
irq_domain_update_bus_token(inner_domain, DOMAIN_BUS_NEXUS);
|
||||
|
||||
inner_domain->msi_parent_ops = &gic_v3_its_msi_parent_ops;
|
||||
inner_domain->flags |= IRQ_DOMAIN_FLAG_MSI_PARENT;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -5580,6 +5581,10 @@ static int __init gic_acpi_parse_madt_its(union acpi_subtable_headers *header,
|
||||
goto node_err;
|
||||
}
|
||||
|
||||
if (acpi_get_madt_revision() >= 7 &&
|
||||
(its_entry->flags & ACPI_MADT_ITS_NON_COHERENT))
|
||||
its->flags |= ITS_FLAGS_FORCE_NON_SHAREABLE;
|
||||
|
||||
err = its_probe_one(its);
|
||||
if (!err)
|
||||
return 0;
|
||||
|
||||
@@ -18,6 +18,8 @@
|
||||
|
||||
#include <linux/irqchip/arm-gic-v3.h>
|
||||
|
||||
#include "irq-msi-lib.h"
|
||||
|
||||
struct mbi_range {
|
||||
u32 spi_start;
|
||||
u32 nr_spis;
|
||||
@@ -138,6 +140,7 @@ static void mbi_irq_domain_free(struct irq_domain *domain,
|
||||
}
|
||||
|
||||
static const struct irq_domain_ops mbi_domain_ops = {
|
||||
.select = msi_lib_irq_domain_select,
|
||||
.alloc = mbi_irq_domain_alloc,
|
||||
.free = mbi_irq_domain_free,
|
||||
};
|
||||
@@ -151,54 +154,6 @@ static void mbi_compose_msi_msg(struct irq_data *data, struct msi_msg *msg)
|
||||
iommu_dma_compose_msi_msg(irq_data_get_msi_desc(data), msg);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PCI_MSI
|
||||
/* PCI-specific irqchip */
|
||||
static void mbi_mask_msi_irq(struct irq_data *d)
|
||||
{
|
||||
pci_msi_mask_irq(d);
|
||||
irq_chip_mask_parent(d);
|
||||
}
|
||||
|
||||
static void mbi_unmask_msi_irq(struct irq_data *d)
|
||||
{
|
||||
pci_msi_unmask_irq(d);
|
||||
irq_chip_unmask_parent(d);
|
||||
}
|
||||
|
||||
static struct irq_chip mbi_msi_irq_chip = {
|
||||
.name = "MSI",
|
||||
.irq_mask = mbi_mask_msi_irq,
|
||||
.irq_unmask = mbi_unmask_msi_irq,
|
||||
.irq_eoi = irq_chip_eoi_parent,
|
||||
.irq_compose_msi_msg = mbi_compose_msi_msg,
|
||||
};
|
||||
|
||||
static struct msi_domain_info mbi_msi_domain_info = {
|
||||
.flags = (MSI_FLAG_USE_DEF_DOM_OPS | MSI_FLAG_USE_DEF_CHIP_OPS |
|
||||
MSI_FLAG_PCI_MSIX | MSI_FLAG_MULTI_PCI_MSI),
|
||||
.chip = &mbi_msi_irq_chip,
|
||||
};
|
||||
|
||||
static int mbi_allocate_pci_domain(struct irq_domain *nexus_domain,
|
||||
struct irq_domain **pci_domain)
|
||||
{
|
||||
*pci_domain = pci_msi_create_irq_domain(nexus_domain->parent->fwnode,
|
||||
&mbi_msi_domain_info,
|
||||
nexus_domain);
|
||||
if (!*pci_domain)
|
||||
return -ENOMEM;
|
||||
|
||||
return 0;
|
||||
}
|
||||
#else
|
||||
static int mbi_allocate_pci_domain(struct irq_domain *nexus_domain,
|
||||
struct irq_domain **pci_domain)
|
||||
{
|
||||
*pci_domain = NULL;
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
static void mbi_compose_mbi_msg(struct irq_data *data, struct msi_msg *msg)
|
||||
{
|
||||
mbi_compose_msi_msg(data, msg);
|
||||
@@ -210,28 +165,51 @@ static void mbi_compose_mbi_msg(struct irq_data *data, struct msi_msg *msg)
|
||||
iommu_dma_compose_msi_msg(irq_data_get_msi_desc(data), &msg[1]);
|
||||
}
|
||||
|
||||
/* Platform-MSI specific irqchip */
|
||||
static struct irq_chip mbi_pmsi_irq_chip = {
|
||||
.name = "pMSI",
|
||||
.irq_set_type = irq_chip_set_type_parent,
|
||||
.irq_compose_msi_msg = mbi_compose_mbi_msg,
|
||||
.flags = IRQCHIP_SUPPORTS_LEVEL_MSI,
|
||||
};
|
||||
|
||||
static struct msi_domain_ops mbi_pmsi_ops = {
|
||||
};
|
||||
|
||||
static struct msi_domain_info mbi_pmsi_domain_info = {
|
||||
.flags = (MSI_FLAG_USE_DEF_DOM_OPS | MSI_FLAG_USE_DEF_CHIP_OPS |
|
||||
MSI_FLAG_LEVEL_CAPABLE),
|
||||
.ops = &mbi_pmsi_ops,
|
||||
.chip = &mbi_pmsi_irq_chip,
|
||||
};
|
||||
|
||||
static int mbi_allocate_domains(struct irq_domain *parent)
|
||||
static bool mbi_init_dev_msi_info(struct device *dev, struct irq_domain *domain,
|
||||
struct irq_domain *real_parent, struct msi_domain_info *info)
|
||||
{
|
||||
struct irq_domain *nexus_domain, *pci_domain, *plat_domain;
|
||||
int err;
|
||||
if (!msi_lib_init_dev_msi_info(dev, domain, real_parent, info))
|
||||
return false;
|
||||
|
||||
switch (info->bus_token) {
|
||||
case DOMAIN_BUS_PCI_DEVICE_MSI:
|
||||
case DOMAIN_BUS_PCI_DEVICE_MSIX:
|
||||
info->chip->irq_compose_msi_msg = mbi_compose_msi_msg;
|
||||
return true;
|
||||
|
||||
case DOMAIN_BUS_DEVICE_MSI:
|
||||
info->chip->irq_compose_msi_msg = mbi_compose_mbi_msg;
|
||||
info->chip->irq_set_type = irq_chip_set_type_parent;
|
||||
info->chip->flags |= IRQCHIP_SUPPORTS_LEVEL_MSI;
|
||||
info->flags |= MSI_FLAG_LEVEL_CAPABLE;
|
||||
return true;
|
||||
|
||||
default:
|
||||
WARN_ON_ONCE(1);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
#define MBI_MSI_FLAGS_REQUIRED (MSI_FLAG_USE_DEF_DOM_OPS | \
|
||||
MSI_FLAG_USE_DEF_CHIP_OPS | \
|
||||
MSI_FLAG_PCI_MSI_MASK_PARENT)
|
||||
|
||||
#define MBI_MSI_FLAGS_SUPPORTED (MSI_GENERIC_FLAGS_MASK | \
|
||||
MSI_FLAG_PCI_MSIX | \
|
||||
MSI_FLAG_MULTI_PCI_MSI)
|
||||
|
||||
static const struct msi_parent_ops gic_v3_mbi_msi_parent_ops = {
|
||||
.supported_flags = MBI_MSI_FLAGS_SUPPORTED,
|
||||
.required_flags = MBI_MSI_FLAGS_REQUIRED,
|
||||
.bus_select_token = DOMAIN_BUS_NEXUS,
|
||||
.bus_select_mask = MATCH_PCI_MSI | MATCH_PLATFORM_MSI,
|
||||
.prefix = "MBI-",
|
||||
.init_dev_msi_info = mbi_init_dev_msi_info,
|
||||
};
|
||||
|
||||
static int mbi_allocate_domain(struct irq_domain *parent)
|
||||
{
|
||||
struct irq_domain *nexus_domain;
|
||||
|
||||
nexus_domain = irq_domain_create_hierarchy(parent, 0, 0, parent->fwnode,
|
||||
&mbi_domain_ops, NULL);
|
||||
@@ -239,22 +217,8 @@ static int mbi_allocate_domains(struct irq_domain *parent)
|
||||
return -ENOMEM;
|
||||
|
||||
irq_domain_update_bus_token(nexus_domain, DOMAIN_BUS_NEXUS);
|
||||
|
||||
err = mbi_allocate_pci_domain(nexus_domain, &pci_domain);
|
||||
|
||||
plat_domain = platform_msi_create_irq_domain(parent->fwnode,
|
||||
&mbi_pmsi_domain_info,
|
||||
nexus_domain);
|
||||
|
||||
if (err || !plat_domain) {
|
||||
if (plat_domain)
|
||||
irq_domain_remove(plat_domain);
|
||||
if (pci_domain)
|
||||
irq_domain_remove(pci_domain);
|
||||
irq_domain_remove(nexus_domain);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
nexus_domain->flags |= IRQ_DOMAIN_FLAG_MSI_PARENT;
|
||||
nexus_domain->msi_parent_ops = &gic_v3_mbi_msi_parent_ops;
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -317,7 +281,7 @@ int __init mbi_init(struct fwnode_handle *fwnode, struct irq_domain *parent)
|
||||
|
||||
pr_info("Using MBI frame %pa\n", &mbi_phys_base);
|
||||
|
||||
ret = mbi_allocate_domains(parent);
|
||||
ret = mbi_allocate_domain(parent);
|
||||
if (ret)
|
||||
goto err_free_mbi;
|
||||
|
||||
|
||||
@@ -2211,11 +2211,10 @@ out_put_node:
|
||||
of_node_put(parts_node);
|
||||
}
|
||||
|
||||
static void __init gic_of_setup_kvm_info(struct device_node *node)
|
||||
static void __init gic_of_setup_kvm_info(struct device_node *node, u32 nr_redist_regions)
|
||||
{
|
||||
int ret;
|
||||
struct resource r;
|
||||
u32 gicv_idx;
|
||||
|
||||
gic_v3_kvm_info.type = GIC_V3;
|
||||
|
||||
@@ -2223,12 +2222,8 @@ static void __init gic_of_setup_kvm_info(struct device_node *node)
|
||||
if (!gic_v3_kvm_info.maint_irq)
|
||||
return;
|
||||
|
||||
if (of_property_read_u32(node, "#redistributor-regions",
|
||||
&gicv_idx))
|
||||
gicv_idx = 1;
|
||||
|
||||
gicv_idx += 3; /* Also skip GICD, GICC, GICH */
|
||||
ret = of_address_to_resource(node, gicv_idx, &r);
|
||||
/* Also skip GICD, GICC, GICH */
|
||||
ret = of_address_to_resource(node, nr_redist_regions + 3, &r);
|
||||
if (!ret)
|
||||
gic_v3_kvm_info.vcpu = r;
|
||||
|
||||
@@ -2318,7 +2313,7 @@ static int __init gic_of_init(struct device_node *node, struct device_node *pare
|
||||
gic_populate_ppi_partitions(node);
|
||||
|
||||
if (static_branch_likely(&supports_deactivate_key))
|
||||
gic_of_setup_kvm_info(node);
|
||||
gic_of_setup_kvm_info(node, nr_redist_regions);
|
||||
return 0;
|
||||
|
||||
out_unmap_rdist:
|
||||
@@ -2370,6 +2365,11 @@ gic_acpi_parse_madt_redist(union acpi_subtable_headers *header,
|
||||
pr_err("Couldn't map GICR region @%llx\n", redist->base_address);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
if (acpi_get_madt_revision() >= 7 &&
|
||||
(redist->flags & ACPI_MADT_GICR_NON_COHERENT))
|
||||
gic_data.rdists.flags |= RDIST_FLAGS_FORCE_NON_SHAREABLE;
|
||||
|
||||
gic_request_region(redist->base_address, redist->length, "GICR");
|
||||
|
||||
gic_acpi_register_redist(redist->base_address, redist_base);
|
||||
@@ -2410,6 +2410,10 @@ gic_acpi_parse_madt_gicc(union acpi_subtable_headers *header,
|
||||
return -ENOMEM;
|
||||
gic_request_region(gicc->gicr_base_address, size, "GICR");
|
||||
|
||||
if (acpi_get_madt_revision() >= 7 &&
|
||||
(gicc->flags & ACPI_MADT_GICC_NON_COHERENT))
|
||||
gic_data.rdists.flags |= RDIST_FLAGS_FORCE_NON_SHAREABLE;
|
||||
|
||||
gic_acpi_register_redist(gicc->gicr_base_address, redist_base);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -36,6 +36,7 @@ struct irqsteer_data {
|
||||
int channel;
|
||||
struct irq_domain *domain;
|
||||
u32 *saved_reg;
|
||||
struct device *dev;
|
||||
};
|
||||
|
||||
static int imx_irqsteer_get_reg_index(struct irqsteer_data *data,
|
||||
@@ -72,10 +73,26 @@ static void imx_irqsteer_irq_mask(struct irq_data *d)
|
||||
raw_spin_unlock_irqrestore(&data->lock, flags);
|
||||
}
|
||||
|
||||
static void imx_irqsteer_irq_bus_lock(struct irq_data *d)
|
||||
{
|
||||
struct irqsteer_data *data = d->chip_data;
|
||||
|
||||
pm_runtime_get_sync(data->dev);
|
||||
}
|
||||
|
||||
static void imx_irqsteer_irq_bus_sync_unlock(struct irq_data *d)
|
||||
{
|
||||
struct irqsteer_data *data = d->chip_data;
|
||||
|
||||
pm_runtime_put_autosuspend(data->dev);
|
||||
}
|
||||
|
||||
static const struct irq_chip imx_irqsteer_irq_chip = {
|
||||
.name = "irqsteer",
|
||||
.irq_mask = imx_irqsteer_irq_mask,
|
||||
.irq_unmask = imx_irqsteer_irq_unmask,
|
||||
.name = "irqsteer",
|
||||
.irq_mask = imx_irqsteer_irq_mask,
|
||||
.irq_unmask = imx_irqsteer_irq_unmask,
|
||||
.irq_bus_lock = imx_irqsteer_irq_bus_lock,
|
||||
.irq_bus_sync_unlock = imx_irqsteer_irq_bus_sync_unlock,
|
||||
};
|
||||
|
||||
static int imx_irqsteer_irq_map(struct irq_domain *h, unsigned int irq,
|
||||
@@ -150,6 +167,7 @@ static int imx_irqsteer_probe(struct platform_device *pdev)
|
||||
if (!data)
|
||||
return -ENOMEM;
|
||||
|
||||
data->dev = &pdev->dev;
|
||||
data->regs = devm_platform_ioremap_resource(pdev, 0);
|
||||
if (IS_ERR(data->regs)) {
|
||||
dev_err(&pdev->dev, "failed to initialize reg\n");
|
||||
|
||||
@@ -24,6 +24,8 @@
|
||||
#include <linux/pm_domain.h>
|
||||
#include <linux/spinlock.h>
|
||||
|
||||
#include "irq-msi-lib.h"
|
||||
|
||||
#define IMX_MU_CHANS 4
|
||||
|
||||
enum imx_mu_xcr {
|
||||
@@ -114,20 +116,6 @@ static void imx_mu_msi_parent_ack_irq(struct irq_data *data)
|
||||
imx_mu_read(msi_data, msi_data->cfg->xRR + data->hwirq * 4);
|
||||
}
|
||||
|
||||
static struct irq_chip imx_mu_msi_irq_chip = {
|
||||
.name = "MU-MSI",
|
||||
.irq_ack = irq_chip_ack_parent,
|
||||
};
|
||||
|
||||
static struct msi_domain_ops imx_mu_msi_irq_ops = {
|
||||
};
|
||||
|
||||
static struct msi_domain_info imx_mu_msi_domain_info = {
|
||||
.flags = (MSI_FLAG_USE_DEF_DOM_OPS | MSI_FLAG_USE_DEF_CHIP_OPS),
|
||||
.ops = &imx_mu_msi_irq_ops,
|
||||
.chip = &imx_mu_msi_irq_chip,
|
||||
};
|
||||
|
||||
static void imx_mu_msi_parent_compose_msg(struct irq_data *data,
|
||||
struct msi_msg *msg)
|
||||
{
|
||||
@@ -195,6 +183,7 @@ static void imx_mu_msi_domain_irq_free(struct irq_domain *domain,
|
||||
}
|
||||
|
||||
static const struct irq_domain_ops imx_mu_msi_domain_ops = {
|
||||
.select = msi_lib_irq_domain_select,
|
||||
.alloc = imx_mu_msi_domain_irq_alloc,
|
||||
.free = imx_mu_msi_domain_irq_free,
|
||||
};
|
||||
@@ -216,35 +205,38 @@ static void imx_mu_msi_irq_handler(struct irq_desc *desc)
|
||||
chained_irq_exit(chip, desc);
|
||||
}
|
||||
|
||||
#define IMX_MU_MSI_FLAGS_REQUIRED (MSI_FLAG_USE_DEF_DOM_OPS | \
|
||||
MSI_FLAG_USE_DEF_CHIP_OPS | \
|
||||
MSI_FLAG_PARENT_PM_DEV)
|
||||
|
||||
#define IMX_MU_MSI_FLAGS_SUPPORTED (MSI_GENERIC_FLAGS_MASK)
|
||||
|
||||
static const struct msi_parent_ops imx_mu_msi_parent_ops = {
|
||||
.supported_flags = IMX_MU_MSI_FLAGS_SUPPORTED,
|
||||
.required_flags = IMX_MU_MSI_FLAGS_REQUIRED,
|
||||
.bus_select_token = DOMAIN_BUS_NEXUS,
|
||||
.bus_select_mask = MATCH_PLATFORM_MSI,
|
||||
.prefix = "MU-MSI-",
|
||||
.init_dev_msi_info = msi_lib_init_dev_msi_info,
|
||||
};
|
||||
|
||||
static int imx_mu_msi_domains_init(struct imx_mu_msi *msi_data, struct device *dev)
|
||||
{
|
||||
struct fwnode_handle *fwnodes = dev_fwnode(dev);
|
||||
struct irq_domain *parent;
|
||||
|
||||
/* Initialize MSI domain parent */
|
||||
parent = irq_domain_create_linear(fwnodes,
|
||||
IMX_MU_CHANS,
|
||||
&imx_mu_msi_domain_ops,
|
||||
msi_data);
|
||||
parent = irq_domain_create_linear(fwnodes, IMX_MU_CHANS,
|
||||
&imx_mu_msi_domain_ops, msi_data);
|
||||
if (!parent) {
|
||||
dev_err(dev, "failed to create IRQ domain\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
irq_domain_update_bus_token(parent, DOMAIN_BUS_NEXUS);
|
||||
|
||||
msi_data->msi_domain = platform_msi_create_irq_domain(fwnodes,
|
||||
&imx_mu_msi_domain_info,
|
||||
parent);
|
||||
|
||||
if (!msi_data->msi_domain) {
|
||||
dev_err(dev, "failed to create MSI domain\n");
|
||||
irq_domain_remove(parent);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
irq_domain_set_pm_device(msi_data->msi_domain, dev);
|
||||
|
||||
parent->dev = parent->pm_dev = dev;
|
||||
parent->flags |= IRQ_DOMAIN_FLAG_MSI_PARENT;
|
||||
parent->msi_parent_ops = &imx_mu_msi_parent_ops;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,278 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Driver for the Microchip LAN966x outbound interrupt controller
|
||||
*
|
||||
* Copyright (c) 2024 Technology Inc. and its subsidiaries.
|
||||
*
|
||||
* Authors:
|
||||
* Horatiu Vultur <horatiu.vultur@microchip.com>
|
||||
* Clément Léger <clement.leger@bootlin.com>
|
||||
* Herve Codina <herve.codina@bootlin.com>
|
||||
*/
|
||||
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/irqchip/chained_irq.h>
|
||||
#include <linux/irqchip.h>
|
||||
#include <linux/irq.h>
|
||||
#include <linux/mod_devicetable.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
struct lan966x_oic_chip_regs {
|
||||
int reg_off_ena_set;
|
||||
int reg_off_ena_clr;
|
||||
int reg_off_sticky;
|
||||
int reg_off_ident;
|
||||
int reg_off_map;
|
||||
};
|
||||
|
||||
struct lan966x_oic_data {
|
||||
void __iomem *regs;
|
||||
int irq;
|
||||
};
|
||||
|
||||
#define LAN966X_OIC_NR_IRQ 86
|
||||
|
||||
/* Interrupt sticky status */
|
||||
#define LAN966X_OIC_INTR_STICKY 0x30
|
||||
#define LAN966X_OIC_INTR_STICKY1 0x34
|
||||
#define LAN966X_OIC_INTR_STICKY2 0x38
|
||||
|
||||
/* Interrupt enable */
|
||||
#define LAN966X_OIC_INTR_ENA 0x48
|
||||
#define LAN966X_OIC_INTR_ENA1 0x4c
|
||||
#define LAN966X_OIC_INTR_ENA2 0x50
|
||||
|
||||
/* Atomic clear of interrupt enable */
|
||||
#define LAN966X_OIC_INTR_ENA_CLR 0x54
|
||||
#define LAN966X_OIC_INTR_ENA_CLR1 0x58
|
||||
#define LAN966X_OIC_INTR_ENA_CLR2 0x5c
|
||||
|
||||
/* Atomic set of interrupt */
|
||||
#define LAN966X_OIC_INTR_ENA_SET 0x60
|
||||
#define LAN966X_OIC_INTR_ENA_SET1 0x64
|
||||
#define LAN966X_OIC_INTR_ENA_SET2 0x68
|
||||
|
||||
/* Mapping of source to destination interrupts (_n = 0..8) */
|
||||
#define LAN966X_OIC_DST_INTR_MAP(_n) (0x78 + (_n) * 4)
|
||||
#define LAN966X_OIC_DST_INTR_MAP1(_n) (0x9c + (_n) * 4)
|
||||
#define LAN966X_OIC_DST_INTR_MAP2(_n) (0xc0 + (_n) * 4)
|
||||
|
||||
/* Currently active interrupt sources per destination (_n = 0..8) */
|
||||
#define LAN966X_OIC_DST_INTR_IDENT(_n) (0xe4 + (_n) * 4)
|
||||
#define LAN966X_OIC_DST_INTR_IDENT1(_n) (0x108 + (_n) * 4)
|
||||
#define LAN966X_OIC_DST_INTR_IDENT2(_n) (0x12c + (_n) * 4)
|
||||
|
||||
static unsigned int lan966x_oic_irq_startup(struct irq_data *data)
|
||||
{
|
||||
struct irq_chip_generic *gc = irq_data_get_irq_chip_data(data);
|
||||
struct irq_chip_type *ct = irq_data_get_chip_type(data);
|
||||
struct lan966x_oic_chip_regs *chip_regs = gc->private;
|
||||
u32 map;
|
||||
|
||||
irq_gc_lock(gc);
|
||||
|
||||
/* Map the source interrupt to the destination */
|
||||
map = irq_reg_readl(gc, chip_regs->reg_off_map);
|
||||
map |= data->mask;
|
||||
irq_reg_writel(gc, map, chip_regs->reg_off_map);
|
||||
|
||||
irq_gc_unlock(gc);
|
||||
|
||||
ct->chip.irq_ack(data);
|
||||
ct->chip.irq_unmask(data);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void lan966x_oic_irq_shutdown(struct irq_data *data)
|
||||
{
|
||||
struct irq_chip_generic *gc = irq_data_get_irq_chip_data(data);
|
||||
struct irq_chip_type *ct = irq_data_get_chip_type(data);
|
||||
struct lan966x_oic_chip_regs *chip_regs = gc->private;
|
||||
u32 map;
|
||||
|
||||
ct->chip.irq_mask(data);
|
||||
|
||||
irq_gc_lock(gc);
|
||||
|
||||
/* Unmap the interrupt */
|
||||
map = irq_reg_readl(gc, chip_regs->reg_off_map);
|
||||
map &= ~data->mask;
|
||||
irq_reg_writel(gc, map, chip_regs->reg_off_map);
|
||||
|
||||
irq_gc_unlock(gc);
|
||||
}
|
||||
|
||||
static int lan966x_oic_irq_set_type(struct irq_data *data,
|
||||
unsigned int flow_type)
|
||||
{
|
||||
if (flow_type != IRQ_TYPE_LEVEL_HIGH) {
|
||||
pr_err("lan966x oic doesn't support flow type %d\n", flow_type);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void lan966x_oic_irq_handler_domain(struct irq_domain *d, u32 first_irq)
|
||||
{
|
||||
struct irq_chip_generic *gc = irq_get_domain_generic_chip(d, first_irq);
|
||||
struct lan966x_oic_chip_regs *chip_regs = gc->private;
|
||||
unsigned long ident;
|
||||
unsigned int hwirq;
|
||||
|
||||
ident = irq_reg_readl(gc, chip_regs->reg_off_ident);
|
||||
if (!ident)
|
||||
return;
|
||||
|
||||
for_each_set_bit(hwirq, &ident, 32)
|
||||
generic_handle_domain_irq(d, hwirq + first_irq);
|
||||
}
|
||||
|
||||
static void lan966x_oic_irq_handler(struct irq_desc *desc)
|
||||
{
|
||||
struct irq_domain *d = irq_desc_get_handler_data(desc);
|
||||
struct irq_chip *chip = irq_desc_get_chip(desc);
|
||||
|
||||
chained_irq_enter(chip, desc);
|
||||
lan966x_oic_irq_handler_domain(d, 0);
|
||||
lan966x_oic_irq_handler_domain(d, 32);
|
||||
lan966x_oic_irq_handler_domain(d, 64);
|
||||
chained_irq_exit(chip, desc);
|
||||
}
|
||||
|
||||
static struct lan966x_oic_chip_regs lan966x_oic_chip_regs[3] = {
|
||||
{
|
||||
.reg_off_ena_set = LAN966X_OIC_INTR_ENA_SET,
|
||||
.reg_off_ena_clr = LAN966X_OIC_INTR_ENA_CLR,
|
||||
.reg_off_sticky = LAN966X_OIC_INTR_STICKY,
|
||||
.reg_off_ident = LAN966X_OIC_DST_INTR_IDENT(0),
|
||||
.reg_off_map = LAN966X_OIC_DST_INTR_MAP(0),
|
||||
}, {
|
||||
.reg_off_ena_set = LAN966X_OIC_INTR_ENA_SET1,
|
||||
.reg_off_ena_clr = LAN966X_OIC_INTR_ENA_CLR1,
|
||||
.reg_off_sticky = LAN966X_OIC_INTR_STICKY1,
|
||||
.reg_off_ident = LAN966X_OIC_DST_INTR_IDENT1(0),
|
||||
.reg_off_map = LAN966X_OIC_DST_INTR_MAP1(0),
|
||||
}, {
|
||||
.reg_off_ena_set = LAN966X_OIC_INTR_ENA_SET2,
|
||||
.reg_off_ena_clr = LAN966X_OIC_INTR_ENA_CLR2,
|
||||
.reg_off_sticky = LAN966X_OIC_INTR_STICKY2,
|
||||
.reg_off_ident = LAN966X_OIC_DST_INTR_IDENT2(0),
|
||||
.reg_off_map = LAN966X_OIC_DST_INTR_MAP2(0),
|
||||
}
|
||||
};
|
||||
|
||||
static int lan966x_oic_chip_init(struct irq_chip_generic *gc)
|
||||
{
|
||||
struct lan966x_oic_data *lan966x_oic = gc->domain->host_data;
|
||||
struct lan966x_oic_chip_regs *chip_regs;
|
||||
|
||||
chip_regs = &lan966x_oic_chip_regs[gc->irq_base / 32];
|
||||
|
||||
gc->reg_base = lan966x_oic->regs;
|
||||
gc->chip_types[0].regs.enable = chip_regs->reg_off_ena_set;
|
||||
gc->chip_types[0].regs.disable = chip_regs->reg_off_ena_clr;
|
||||
gc->chip_types[0].regs.ack = chip_regs->reg_off_sticky;
|
||||
gc->chip_types[0].chip.irq_startup = lan966x_oic_irq_startup;
|
||||
gc->chip_types[0].chip.irq_shutdown = lan966x_oic_irq_shutdown;
|
||||
gc->chip_types[0].chip.irq_set_type = lan966x_oic_irq_set_type;
|
||||
gc->chip_types[0].chip.irq_mask = irq_gc_mask_disable_reg;
|
||||
gc->chip_types[0].chip.irq_unmask = irq_gc_unmask_enable_reg;
|
||||
gc->chip_types[0].chip.irq_ack = irq_gc_ack_set_bit;
|
||||
gc->private = chip_regs;
|
||||
|
||||
/* Disable all interrupts handled by this chip */
|
||||
irq_reg_writel(gc, ~0U, chip_regs->reg_off_ena_clr);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void lan966x_oic_chip_exit(struct irq_chip_generic *gc)
|
||||
{
|
||||
/* Disable and ack all interrupts handled by this chip */
|
||||
irq_reg_writel(gc, ~0U, gc->chip_types[0].regs.disable);
|
||||
irq_reg_writel(gc, ~0U, gc->chip_types[0].regs.ack);
|
||||
}
|
||||
|
||||
static int lan966x_oic_domain_init(struct irq_domain *d)
|
||||
{
|
||||
struct lan966x_oic_data *lan966x_oic = d->host_data;
|
||||
|
||||
irq_set_chained_handler_and_data(lan966x_oic->irq, lan966x_oic_irq_handler, d);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void lan966x_oic_domain_exit(struct irq_domain *d)
|
||||
{
|
||||
struct lan966x_oic_data *lan966x_oic = d->host_data;
|
||||
|
||||
irq_set_chained_handler_and_data(lan966x_oic->irq, NULL, NULL);
|
||||
}
|
||||
|
||||
static int lan966x_oic_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct irq_domain_chip_generic_info dgc_info = {
|
||||
.name = "lan966x-oic",
|
||||
.handler = handle_level_irq,
|
||||
.irqs_per_chip = 32,
|
||||
.num_ct = 1,
|
||||
.init = lan966x_oic_chip_init,
|
||||
.exit = lan966x_oic_chip_exit,
|
||||
};
|
||||
struct irq_domain_info d_info = {
|
||||
.fwnode = of_node_to_fwnode(pdev->dev.of_node),
|
||||
.domain_flags = IRQ_DOMAIN_FLAG_DESTROY_GC,
|
||||
.size = LAN966X_OIC_NR_IRQ,
|
||||
.hwirq_max = LAN966X_OIC_NR_IRQ,
|
||||
.ops = &irq_generic_chip_ops,
|
||||
.dgc_info = &dgc_info,
|
||||
.init = lan966x_oic_domain_init,
|
||||
.exit = lan966x_oic_domain_exit,
|
||||
};
|
||||
struct lan966x_oic_data *lan966x_oic;
|
||||
struct device *dev = &pdev->dev;
|
||||
struct irq_domain *domain;
|
||||
|
||||
lan966x_oic = devm_kmalloc(dev, sizeof(*lan966x_oic), GFP_KERNEL);
|
||||
if (!lan966x_oic)
|
||||
return -ENOMEM;
|
||||
|
||||
lan966x_oic->regs = devm_platform_ioremap_resource(pdev, 0);
|
||||
if (IS_ERR(lan966x_oic->regs))
|
||||
return dev_err_probe(dev, PTR_ERR(lan966x_oic->regs),
|
||||
"failed to map resource\n");
|
||||
|
||||
lan966x_oic->irq = platform_get_irq(pdev, 0);
|
||||
if (lan966x_oic->irq < 0)
|
||||
return dev_err_probe(dev, lan966x_oic->irq, "failed to get the IRQ\n");
|
||||
|
||||
d_info.host_data = lan966x_oic;
|
||||
domain = devm_irq_domain_instantiate(dev, &d_info);
|
||||
if (IS_ERR(domain))
|
||||
return dev_err_probe(dev, PTR_ERR(domain),
|
||||
"failed to instantiate the IRQ domain\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct of_device_id lan966x_oic_of_match[] = {
|
||||
{ .compatible = "microchip,lan966x-oic" },
|
||||
{} /* sentinel */
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, lan966x_oic_of_match);
|
||||
|
||||
static struct platform_driver lan966x_oic_driver = {
|
||||
.probe = lan966x_oic_probe,
|
||||
.driver = {
|
||||
.name = "lan966x-oic",
|
||||
.of_match_table = lan966x_oic_of_match,
|
||||
},
|
||||
};
|
||||
module_platform_driver(lan966x_oic_driver);
|
||||
|
||||
MODULE_AUTHOR("Herve Codina <herve.codina@bootlin.com>");
|
||||
MODULE_DESCRIPTION("Microchip LAN966x OIC driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
@@ -135,24 +135,14 @@ static int mbigen_set_type(struct irq_data *data, unsigned int type)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct irq_chip mbigen_irq_chip = {
|
||||
.name = "mbigen-v2",
|
||||
.irq_mask = irq_chip_mask_parent,
|
||||
.irq_unmask = irq_chip_unmask_parent,
|
||||
.irq_eoi = mbigen_eoi_irq,
|
||||
.irq_set_type = mbigen_set_type,
|
||||
.irq_set_affinity = irq_chip_set_affinity_parent,
|
||||
};
|
||||
|
||||
static void mbigen_write_msg(struct msi_desc *desc, struct msi_msg *msg)
|
||||
static void mbigen_write_msi_msg(struct irq_data *d, struct msi_msg *msg)
|
||||
{
|
||||
struct irq_data *d = irq_get_irq_data(desc->irq);
|
||||
void __iomem *base = d->chip_data;
|
||||
u32 val;
|
||||
|
||||
if (!msg->address_lo && !msg->address_hi)
|
||||
return;
|
||||
|
||||
|
||||
base += get_mbigen_vec_reg(d->hwirq);
|
||||
val = readl_relaxed(base);
|
||||
|
||||
@@ -165,10 +155,8 @@ static void mbigen_write_msg(struct msi_desc *desc, struct msi_msg *msg)
|
||||
writel_relaxed(val, base);
|
||||
}
|
||||
|
||||
static int mbigen_domain_translate(struct irq_domain *d,
|
||||
struct irq_fwspec *fwspec,
|
||||
unsigned long *hwirq,
|
||||
unsigned int *type)
|
||||
static int mbigen_domain_translate(struct irq_domain *d, struct irq_fwspec *fwspec,
|
||||
unsigned long *hwirq, unsigned int *type)
|
||||
{
|
||||
if (is_of_node(fwspec->fwnode) || is_acpi_device_node(fwspec->fwnode)) {
|
||||
if (fwspec->param_count != 2)
|
||||
@@ -192,51 +180,48 @@ static int mbigen_domain_translate(struct irq_domain *d,
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static int mbigen_irq_domain_alloc(struct irq_domain *domain,
|
||||
unsigned int virq,
|
||||
unsigned int nr_irqs,
|
||||
void *args)
|
||||
static void mbigen_domain_set_desc(msi_alloc_info_t *arg, struct msi_desc *desc)
|
||||
{
|
||||
struct irq_fwspec *fwspec = args;
|
||||
irq_hw_number_t hwirq;
|
||||
unsigned int type;
|
||||
struct mbigen_device *mgn_chip;
|
||||
int i, err;
|
||||
|
||||
err = mbigen_domain_translate(domain, fwspec, &hwirq, &type);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
err = platform_msi_device_domain_alloc(domain, virq, nr_irqs);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
mgn_chip = platform_msi_get_host_data(domain);
|
||||
|
||||
for (i = 0; i < nr_irqs; i++)
|
||||
irq_domain_set_hwirq_and_chip(domain, virq + i, hwirq + i,
|
||||
&mbigen_irq_chip, mgn_chip->base);
|
||||
|
||||
return 0;
|
||||
arg->desc = desc;
|
||||
arg->hwirq = (u32)desc->data.icookie.value;
|
||||
}
|
||||
|
||||
static void mbigen_irq_domain_free(struct irq_domain *domain, unsigned int virq,
|
||||
unsigned int nr_irqs)
|
||||
{
|
||||
platform_msi_device_domain_free(domain, virq, nr_irqs);
|
||||
}
|
||||
static const struct msi_domain_template mbigen_msi_template = {
|
||||
.chip = {
|
||||
.name = "mbigen-v2",
|
||||
.irq_mask = irq_chip_mask_parent,
|
||||
.irq_unmask = irq_chip_unmask_parent,
|
||||
.irq_eoi = mbigen_eoi_irq,
|
||||
.irq_set_type = mbigen_set_type,
|
||||
.irq_write_msi_msg = mbigen_write_msi_msg,
|
||||
},
|
||||
|
||||
static const struct irq_domain_ops mbigen_domain_ops = {
|
||||
.translate = mbigen_domain_translate,
|
||||
.alloc = mbigen_irq_domain_alloc,
|
||||
.free = mbigen_irq_domain_free,
|
||||
.ops = {
|
||||
.set_desc = mbigen_domain_set_desc,
|
||||
.msi_translate = mbigen_domain_translate,
|
||||
},
|
||||
|
||||
.info = {
|
||||
.bus_token = DOMAIN_BUS_WIRED_TO_MSI,
|
||||
.flags = MSI_FLAG_USE_DEV_FWNODE,
|
||||
},
|
||||
};
|
||||
|
||||
static bool mbigen_create_device_domain(struct device *dev, unsigned int size,
|
||||
struct mbigen_device *mgn_chip)
|
||||
{
|
||||
if (WARN_ON_ONCE(!dev->msi.domain))
|
||||
return false;
|
||||
|
||||
return msi_create_device_irq_domain(dev, MSI_DEFAULT_DOMAIN,
|
||||
&mbigen_msi_template, size,
|
||||
NULL, mgn_chip->base);
|
||||
}
|
||||
|
||||
static int mbigen_of_create_domain(struct platform_device *pdev,
|
||||
struct mbigen_device *mgn_chip)
|
||||
{
|
||||
struct platform_device *child;
|
||||
struct irq_domain *domain;
|
||||
struct device_node *np;
|
||||
u32 num_pins;
|
||||
int ret = 0;
|
||||
@@ -258,11 +243,7 @@ static int mbigen_of_create_domain(struct platform_device *pdev,
|
||||
break;
|
||||
}
|
||||
|
||||
domain = platform_msi_create_device_domain(&child->dev, num_pins,
|
||||
mbigen_write_msg,
|
||||
&mbigen_domain_ops,
|
||||
mgn_chip);
|
||||
if (!domain) {
|
||||
if (!mbigen_create_device_domain(&child->dev, num_pins, mgn_chip)) {
|
||||
ret = -ENOMEM;
|
||||
break;
|
||||
}
|
||||
@@ -284,7 +265,6 @@ MODULE_DEVICE_TABLE(acpi, mbigen_acpi_match);
|
||||
static int mbigen_acpi_create_domain(struct platform_device *pdev,
|
||||
struct mbigen_device *mgn_chip)
|
||||
{
|
||||
struct irq_domain *domain;
|
||||
u32 num_pins = 0;
|
||||
int ret;
|
||||
|
||||
@@ -315,11 +295,7 @@ static int mbigen_acpi_create_domain(struct platform_device *pdev,
|
||||
if (ret || num_pins == 0)
|
||||
return -EINVAL;
|
||||
|
||||
domain = platform_msi_create_device_domain(&pdev->dev, num_pins,
|
||||
mbigen_write_msg,
|
||||
&mbigen_domain_ops,
|
||||
mgn_chip);
|
||||
if (!domain)
|
||||
if (!mbigen_create_device_domain(&pdev->dev, num_pins, mgn_chip))
|
||||
return -ENOMEM;
|
||||
|
||||
return 0;
|
||||
|
||||
@@ -608,5 +608,6 @@ IRQCHIP_MATCH("amlogic,meson-gpio-intc", meson_gpio_irq_of_init)
|
||||
IRQCHIP_PLATFORM_DRIVER_END(meson_gpio_intc)
|
||||
|
||||
MODULE_AUTHOR("Jerome Brunet <jbrunet@baylibre.com>");
|
||||
MODULE_DESCRIPTION("Meson GPIO Interrupt Multiplexer driver");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
MODULE_ALIAS("platform:meson-gpio-intc");
|
||||
|
||||
@@ -0,0 +1,140 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
// Copyright (C) 2022 Linutronix GmbH
|
||||
// Copyright (C) 2022 Intel
|
||||
|
||||
#include <linux/export.h>
|
||||
|
||||
#include "irq-msi-lib.h"
|
||||
|
||||
/**
|
||||
* msi_lib_init_dev_msi_info - Domain info setup for MSI domains
|
||||
* @dev: The device for which the domain is created for
|
||||
* @domain: The domain providing this callback
|
||||
* @real_parent: The real parent domain of the domain to be initialized
|
||||
* which might be a domain built on top of @domain or
|
||||
* @domain itself
|
||||
* @info: The domain info for the domain to be initialize
|
||||
*
|
||||
* This function is to be used for all types of MSI domains above the root
|
||||
* parent domain and any intermediates. The topmost parent domain specific
|
||||
* functionality is determined via @real_parent.
|
||||
*
|
||||
* All intermediate domains between the root and the device domain must
|
||||
* have either msi_parent_ops.init_dev_msi_info = msi_parent_init_dev_msi_info
|
||||
* or invoke it down the line.
|
||||
*/
|
||||
bool msi_lib_init_dev_msi_info(struct device *dev, struct irq_domain *domain,
|
||||
struct irq_domain *real_parent,
|
||||
struct msi_domain_info *info)
|
||||
{
|
||||
const struct msi_parent_ops *pops = real_parent->msi_parent_ops;
|
||||
u32 required_flags;
|
||||
|
||||
/* Parent ops available? */
|
||||
if (WARN_ON_ONCE(!pops))
|
||||
return false;
|
||||
|
||||
/*
|
||||
* MSI parent domain specific settings. For now there is only the
|
||||
* root parent domain, e.g. NEXUS, acting as a MSI parent, but it is
|
||||
* possible to stack MSI parents. See x86 vector -> irq remapping
|
||||
*/
|
||||
if (domain->bus_token == pops->bus_select_token) {
|
||||
if (WARN_ON_ONCE(domain != real_parent))
|
||||
return false;
|
||||
} else {
|
||||
WARN_ON_ONCE(1);
|
||||
return false;
|
||||
}
|
||||
|
||||
required_flags = pops->required_flags;
|
||||
|
||||
/* Is the target domain bus token supported? */
|
||||
switch(info->bus_token) {
|
||||
case DOMAIN_BUS_PCI_DEVICE_MSI:
|
||||
case DOMAIN_BUS_PCI_DEVICE_MSIX:
|
||||
if (WARN_ON_ONCE(!IS_ENABLED(CONFIG_PCI_MSI)))
|
||||
return false;
|
||||
|
||||
break;
|
||||
case DOMAIN_BUS_DEVICE_MSI:
|
||||
/*
|
||||
* Per device MSI should never have any MSI feature bits
|
||||
* set. It's sole purpose is to create a dumb interrupt
|
||||
* chip which has a device specific irq_write_msi_msg()
|
||||
* callback.
|
||||
*/
|
||||
if (WARN_ON_ONCE(info->flags))
|
||||
return false;
|
||||
|
||||
/* Core managed MSI descriptors */
|
||||
info->flags = MSI_FLAG_ALLOC_SIMPLE_MSI_DESCS | MSI_FLAG_FREE_MSI_DESCS;
|
||||
fallthrough;
|
||||
case DOMAIN_BUS_WIRED_TO_MSI:
|
||||
/* Remove PCI specific flags */
|
||||
required_flags &= ~MSI_FLAG_PCI_MSI_MASK_PARENT;
|
||||
break;
|
||||
default:
|
||||
/*
|
||||
* This should never be reached. See
|
||||
* msi_lib_irq_domain_select()
|
||||
*/
|
||||
WARN_ON_ONCE(1);
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* Mask out the domain specific MSI feature flags which are not
|
||||
* supported by the real parent.
|
||||
*/
|
||||
info->flags &= pops->supported_flags;
|
||||
/* Enforce the required flags */
|
||||
info->flags |= required_flags;
|
||||
|
||||
/* Chip updates for all child bus types */
|
||||
if (!info->chip->irq_eoi)
|
||||
info->chip->irq_eoi = irq_chip_eoi_parent;
|
||||
if (!info->chip->irq_ack)
|
||||
info->chip->irq_ack = irq_chip_ack_parent;
|
||||
|
||||
/*
|
||||
* The device MSI domain can never have a set affinity callback. It
|
||||
* always has to rely on the parent domain to handle affinity
|
||||
* settings. The device MSI domain just has to write the resulting
|
||||
* MSI message into the hardware which is the whole purpose of the
|
||||
* device MSI domain aside of mask/unmask which is provided e.g. by
|
||||
* PCI/MSI device domains.
|
||||
*/
|
||||
info->chip->irq_set_affinity = msi_domain_set_affinity;
|
||||
return true;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(msi_lib_init_dev_msi_info);
|
||||
|
||||
/**
|
||||
* msi_lib_irq_domain_select - Shared select function for NEXUS domains
|
||||
* @d: Pointer to the irq domain on which select is invoked
|
||||
* @fwspec: Firmware spec describing what is searched
|
||||
* @bus_token: The bus token for which a matching irq domain is looked up
|
||||
*
|
||||
* Returns: %0 if @d is not what is being looked for
|
||||
*
|
||||
* %1 if @d is either the domain which is directly searched for or
|
||||
* if @d is providing the parent MSI domain for the functionality
|
||||
* requested with @bus_token.
|
||||
*/
|
||||
int msi_lib_irq_domain_select(struct irq_domain *d, struct irq_fwspec *fwspec,
|
||||
enum irq_domain_bus_token bus_token)
|
||||
{
|
||||
const struct msi_parent_ops *ops = d->msi_parent_ops;
|
||||
u32 busmask = BIT(bus_token);
|
||||
|
||||
if (fwspec->fwnode != d->fwnode || fwspec->param_count != 0)
|
||||
return 0;
|
||||
|
||||
/* Handle pure domain searches */
|
||||
if (bus_token == ops->bus_select_token)
|
||||
return 1;
|
||||
|
||||
return ops && !!(ops->bus_select_mask & busmask);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(msi_lib_irq_domain_select);
|
||||
@@ -0,0 +1,27 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
// Copyright (C) 2022 Linutronix GmbH
|
||||
// Copyright (C) 2022 Intel
|
||||
|
||||
#ifndef _DRIVERS_IRQCHIP_IRQ_MSI_LIB_H
|
||||
#define _DRIVERS_IRQCHIP_IRQ_MSI_LIB_H
|
||||
|
||||
#include <linux/bits.h>
|
||||
#include <linux/irqdomain.h>
|
||||
#include <linux/msi.h>
|
||||
|
||||
#ifdef CONFIG_PCI_MSI
|
||||
#define MATCH_PCI_MSI BIT(DOMAIN_BUS_PCI_MSI)
|
||||
#else
|
||||
#define MATCH_PCI_MSI (0)
|
||||
#endif
|
||||
|
||||
#define MATCH_PLATFORM_MSI BIT(DOMAIN_BUS_PLATFORM_MSI)
|
||||
|
||||
int msi_lib_irq_domain_select(struct irq_domain *d, struct irq_fwspec *fwspec,
|
||||
enum irq_domain_bus_token bus_token);
|
||||
|
||||
bool msi_lib_init_dev_msi_info(struct device *dev, struct irq_domain *domain,
|
||||
struct irq_domain *real_parent,
|
||||
struct msi_domain_info *info);
|
||||
|
||||
#endif /* _DRIVERS_IRQCHIP_IRQ_MSI_LIB_H */
|
||||
@@ -17,6 +17,8 @@
|
||||
#include <linux/of_platform.h>
|
||||
#include <linux/platform_device.h>
|
||||
|
||||
#include "irq-msi-lib.h"
|
||||
|
||||
#include <dt-bindings/interrupt-controller/arm-gic.h>
|
||||
|
||||
#define GICP_SETSPI_NSR_OFFSET 0x0
|
||||
@@ -145,32 +147,32 @@ static void gicp_irq_domain_free(struct irq_domain *domain,
|
||||
}
|
||||
|
||||
static const struct irq_domain_ops gicp_domain_ops = {
|
||||
.select = msi_lib_irq_domain_select,
|
||||
.alloc = gicp_irq_domain_alloc,
|
||||
.free = gicp_irq_domain_free,
|
||||
};
|
||||
|
||||
static struct irq_chip gicp_msi_irq_chip = {
|
||||
.name = "GICP",
|
||||
.irq_set_type = irq_chip_set_type_parent,
|
||||
.flags = IRQCHIP_SUPPORTS_LEVEL_MSI,
|
||||
};
|
||||
#define GICP_MSI_FLAGS_REQUIRED (MSI_FLAG_USE_DEF_DOM_OPS | \
|
||||
MSI_FLAG_USE_DEF_CHIP_OPS)
|
||||
|
||||
static struct msi_domain_ops gicp_msi_ops = {
|
||||
};
|
||||
#define GICP_MSI_FLAGS_SUPPORTED (MSI_GENERIC_FLAGS_MASK | \
|
||||
MSI_FLAG_LEVEL_CAPABLE)
|
||||
|
||||
static struct msi_domain_info gicp_msi_domain_info = {
|
||||
.flags = (MSI_FLAG_USE_DEF_DOM_OPS | MSI_FLAG_USE_DEF_CHIP_OPS |
|
||||
MSI_FLAG_LEVEL_CAPABLE),
|
||||
.ops = &gicp_msi_ops,
|
||||
.chip = &gicp_msi_irq_chip,
|
||||
static const struct msi_parent_ops gicp_msi_parent_ops = {
|
||||
.supported_flags = GICP_MSI_FLAGS_SUPPORTED,
|
||||
.required_flags = GICP_MSI_FLAGS_REQUIRED,
|
||||
.bus_select_token = DOMAIN_BUS_GENERIC_MSI,
|
||||
.bus_select_mask = MATCH_PLATFORM_MSI,
|
||||
.prefix = "GICP-",
|
||||
.init_dev_msi_info = msi_lib_init_dev_msi_info,
|
||||
};
|
||||
|
||||
static int mvebu_gicp_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct mvebu_gicp *gicp;
|
||||
struct irq_domain *inner_domain, *plat_domain, *parent_domain;
|
||||
struct irq_domain *inner_domain, *parent_domain;
|
||||
struct device_node *node = pdev->dev.of_node;
|
||||
struct device_node *irq_parent_dn;
|
||||
struct mvebu_gicp *gicp;
|
||||
int ret, i;
|
||||
|
||||
gicp = devm_kzalloc(&pdev->dev, sizeof(*gicp), GFP_KERNEL);
|
||||
@@ -234,17 +236,9 @@ static int mvebu_gicp_probe(struct platform_device *pdev)
|
||||
if (!inner_domain)
|
||||
return -ENOMEM;
|
||||
|
||||
|
||||
plat_domain = platform_msi_create_irq_domain(of_node_to_fwnode(node),
|
||||
&gicp_msi_domain_info,
|
||||
inner_domain);
|
||||
if (!plat_domain) {
|
||||
irq_domain_remove(inner_domain);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
platform_set_drvdata(pdev, gicp);
|
||||
|
||||
irq_domain_update_bus_token(inner_domain, DOMAIN_BUS_GENERIC_MSI);
|
||||
inner_domain->flags |= IRQ_DOMAIN_FLAG_MSI_PARENT;
|
||||
inner_domain->msi_parent_ops = &gicp_msi_parent_ops;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
+121
-162
@@ -20,6 +20,8 @@
|
||||
#include <linux/of_platform.h>
|
||||
#include <linux/platform_device.h>
|
||||
|
||||
#include "irq-msi-lib.h"
|
||||
|
||||
#include <dt-bindings/interrupt-controller/mvebu-icu.h>
|
||||
|
||||
/* ICU registers */
|
||||
@@ -60,99 +62,13 @@ struct mvebu_icu_msi_data {
|
||||
const struct mvebu_icu_subset_data *subset_data;
|
||||
};
|
||||
|
||||
struct mvebu_icu_irq_data {
|
||||
struct mvebu_icu *icu;
|
||||
unsigned int icu_group;
|
||||
unsigned int type;
|
||||
};
|
||||
|
||||
static DEFINE_STATIC_KEY_FALSE(legacy_bindings);
|
||||
|
||||
static void mvebu_icu_init(struct mvebu_icu *icu,
|
||||
struct mvebu_icu_msi_data *msi_data,
|
||||
struct msi_msg *msg)
|
||||
{
|
||||
const struct mvebu_icu_subset_data *subset = msi_data->subset_data;
|
||||
|
||||
if (atomic_cmpxchg(&msi_data->initialized, false, true))
|
||||
return;
|
||||
|
||||
/* Set 'SET' ICU SPI message address in AP */
|
||||
writel_relaxed(msg[0].address_hi, icu->base + subset->offset_set_ah);
|
||||
writel_relaxed(msg[0].address_lo, icu->base + subset->offset_set_al);
|
||||
|
||||
if (subset->icu_group != ICU_GRP_NSR)
|
||||
return;
|
||||
|
||||
/* Set 'CLEAR' ICU SPI message address in AP (level-MSI only) */
|
||||
writel_relaxed(msg[1].address_hi, icu->base + subset->offset_clr_ah);
|
||||
writel_relaxed(msg[1].address_lo, icu->base + subset->offset_clr_al);
|
||||
}
|
||||
|
||||
static void mvebu_icu_write_msg(struct msi_desc *desc, struct msi_msg *msg)
|
||||
{
|
||||
struct irq_data *d = irq_get_irq_data(desc->irq);
|
||||
struct mvebu_icu_msi_data *msi_data = platform_msi_get_host_data(d->domain);
|
||||
struct mvebu_icu_irq_data *icu_irqd = d->chip_data;
|
||||
struct mvebu_icu *icu = icu_irqd->icu;
|
||||
unsigned int icu_int;
|
||||
|
||||
if (msg->address_lo || msg->address_hi) {
|
||||
/* One off initialization per domain */
|
||||
mvebu_icu_init(icu, msi_data, msg);
|
||||
/* Configure the ICU with irq number & type */
|
||||
icu_int = msg->data | ICU_INT_ENABLE;
|
||||
if (icu_irqd->type & IRQ_TYPE_EDGE_RISING)
|
||||
icu_int |= ICU_IS_EDGE;
|
||||
icu_int |= icu_irqd->icu_group << ICU_GROUP_SHIFT;
|
||||
} else {
|
||||
/* De-configure the ICU */
|
||||
icu_int = 0;
|
||||
}
|
||||
|
||||
writel_relaxed(icu_int, icu->base + ICU_INT_CFG(d->hwirq));
|
||||
|
||||
/*
|
||||
* The SATA unit has 2 ports, and a dedicated ICU entry per
|
||||
* port. The ahci sata driver supports only one irq interrupt
|
||||
* per SATA unit. To solve this conflict, we configure the 2
|
||||
* SATA wired interrupts in the south bridge into 1 GIC
|
||||
* interrupt in the north bridge. Even if only a single port
|
||||
* is enabled, if sata node is enabled, both interrupts are
|
||||
* configured (regardless of which port is actually in use).
|
||||
*/
|
||||
if (d->hwirq == ICU_SATA0_ICU_ID || d->hwirq == ICU_SATA1_ICU_ID) {
|
||||
writel_relaxed(icu_int,
|
||||
icu->base + ICU_INT_CFG(ICU_SATA0_ICU_ID));
|
||||
writel_relaxed(icu_int,
|
||||
icu->base + ICU_INT_CFG(ICU_SATA1_ICU_ID));
|
||||
}
|
||||
}
|
||||
|
||||
static struct irq_chip mvebu_icu_nsr_chip = {
|
||||
.name = "ICU-NSR",
|
||||
.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_affinity = irq_chip_set_affinity_parent,
|
||||
};
|
||||
|
||||
static struct irq_chip mvebu_icu_sei_chip = {
|
||||
.name = "ICU-SEI",
|
||||
.irq_ack = irq_chip_ack_parent,
|
||||
.irq_mask = irq_chip_mask_parent,
|
||||
.irq_unmask = irq_chip_unmask_parent,
|
||||
.irq_set_type = irq_chip_set_type_parent,
|
||||
.irq_set_affinity = irq_chip_set_affinity_parent,
|
||||
};
|
||||
|
||||
static int
|
||||
mvebu_icu_irq_domain_translate(struct irq_domain *d, struct irq_fwspec *fwspec,
|
||||
static int mvebu_icu_translate(struct irq_domain *d, struct irq_fwspec *fwspec,
|
||||
unsigned long *hwirq, unsigned int *type)
|
||||
{
|
||||
unsigned int param_count = static_branch_unlikely(&legacy_bindings) ? 3 : 2;
|
||||
struct mvebu_icu_msi_data *msi_data = platform_msi_get_host_data(d);
|
||||
struct mvebu_icu_msi_data *msi_data = d->host_data;
|
||||
struct mvebu_icu *icu = msi_data->icu;
|
||||
|
||||
/* Check the count of the parameters in dt */
|
||||
@@ -192,81 +108,126 @@ mvebu_icu_irq_domain_translate(struct irq_domain *d, struct irq_fwspec *fwspec,
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
mvebu_icu_irq_domain_alloc(struct irq_domain *domain, unsigned int virq,
|
||||
unsigned int nr_irqs, void *args)
|
||||
static void mvebu_icu_init(struct mvebu_icu *icu,
|
||||
struct mvebu_icu_msi_data *msi_data,
|
||||
struct msi_msg *msg)
|
||||
{
|
||||
int err;
|
||||
unsigned long hwirq;
|
||||
struct irq_fwspec *fwspec = args;
|
||||
struct mvebu_icu_msi_data *msi_data = platform_msi_get_host_data(domain);
|
||||
const struct mvebu_icu_subset_data *subset = msi_data->subset_data;
|
||||
|
||||
if (atomic_cmpxchg(&msi_data->initialized, false, true))
|
||||
return;
|
||||
|
||||
/* Set 'SET' ICU SPI message address in AP */
|
||||
writel_relaxed(msg[0].address_hi, icu->base + subset->offset_set_ah);
|
||||
writel_relaxed(msg[0].address_lo, icu->base + subset->offset_set_al);
|
||||
|
||||
if (subset->icu_group != ICU_GRP_NSR)
|
||||
return;
|
||||
|
||||
/* Set 'CLEAR' ICU SPI message address in AP (level-MSI only) */
|
||||
writel_relaxed(msg[1].address_hi, icu->base + subset->offset_clr_ah);
|
||||
writel_relaxed(msg[1].address_lo, icu->base + subset->offset_clr_al);
|
||||
}
|
||||
|
||||
static int mvebu_icu_msi_init(struct irq_domain *domain, struct msi_domain_info *info,
|
||||
unsigned int virq, irq_hw_number_t hwirq, msi_alloc_info_t *arg)
|
||||
{
|
||||
irq_domain_set_hwirq_and_chip(domain, virq, hwirq, info->chip, info->chip_data);
|
||||
return irq_set_irqchip_state(virq, IRQCHIP_STATE_PENDING, false);
|
||||
}
|
||||
|
||||
static void mvebu_icu_set_desc(msi_alloc_info_t *arg, struct msi_desc *desc)
|
||||
{
|
||||
arg->desc = desc;
|
||||
arg->hwirq = (u32)desc->data.icookie.value;
|
||||
}
|
||||
|
||||
static void mvebu_icu_write_msi_msg(struct irq_data *d, struct msi_msg *msg)
|
||||
{
|
||||
struct mvebu_icu_msi_data *msi_data = d->chip_data;
|
||||
unsigned int icu_group = msi_data->subset_data->icu_group;
|
||||
struct msi_desc *desc = irq_data_get_msi_desc(d);
|
||||
struct mvebu_icu *icu = msi_data->icu;
|
||||
struct mvebu_icu_irq_data *icu_irqd;
|
||||
struct irq_chip *chip = &mvebu_icu_nsr_chip;
|
||||
unsigned int type;
|
||||
u32 icu_int;
|
||||
|
||||
icu_irqd = kmalloc(sizeof(*icu_irqd), GFP_KERNEL);
|
||||
if (!icu_irqd)
|
||||
return -ENOMEM;
|
||||
|
||||
err = mvebu_icu_irq_domain_translate(domain, fwspec, &hwirq,
|
||||
&icu_irqd->type);
|
||||
if (err) {
|
||||
dev_err(icu->dev, "failed to translate ICU parameters\n");
|
||||
goto free_irqd;
|
||||
if (msg->address_lo || msg->address_hi) {
|
||||
/* One off initialization per domain */
|
||||
mvebu_icu_init(icu, msi_data, msg);
|
||||
/* Configure the ICU with irq number & type */
|
||||
icu_int = msg->data | ICU_INT_ENABLE;
|
||||
type = (unsigned int)(desc->data.icookie.value >> 32);
|
||||
if (type & IRQ_TYPE_EDGE_RISING)
|
||||
icu_int |= ICU_IS_EDGE;
|
||||
icu_int |= icu_group << ICU_GROUP_SHIFT;
|
||||
} else {
|
||||
/* De-configure the ICU */
|
||||
icu_int = 0;
|
||||
}
|
||||
|
||||
if (static_branch_unlikely(&legacy_bindings))
|
||||
icu_irqd->icu_group = fwspec->param[0];
|
||||
else
|
||||
icu_irqd->icu_group = msi_data->subset_data->icu_group;
|
||||
icu_irqd->icu = icu;
|
||||
writel_relaxed(icu_int, icu->base + ICU_INT_CFG(d->hwirq));
|
||||
|
||||
err = platform_msi_device_domain_alloc(domain, virq, nr_irqs);
|
||||
if (err) {
|
||||
dev_err(icu->dev, "failed to allocate ICU interrupt in parent domain\n");
|
||||
goto free_irqd;
|
||||
/*
|
||||
* The SATA unit has 2 ports, and a dedicated ICU entry per
|
||||
* port. The ahci sata driver supports only one irq interrupt
|
||||
* per SATA unit. To solve this conflict, we configure the 2
|
||||
* SATA wired interrupts in the south bridge into 1 GIC
|
||||
* interrupt in the north bridge. Even if only a single port
|
||||
* is enabled, if sata node is enabled, both interrupts are
|
||||
* configured (regardless of which port is actually in use).
|
||||
*/
|
||||
if (d->hwirq == ICU_SATA0_ICU_ID || d->hwirq == ICU_SATA1_ICU_ID) {
|
||||
writel_relaxed(icu_int, icu->base + ICU_INT_CFG(ICU_SATA0_ICU_ID));
|
||||
writel_relaxed(icu_int, icu->base + ICU_INT_CFG(ICU_SATA1_ICU_ID));
|
||||
}
|
||||
|
||||
/* Make sure there is no interrupt left pending by the firmware */
|
||||
err = irq_set_irqchip_state(virq, IRQCHIP_STATE_PENDING, false);
|
||||
if (err)
|
||||
goto free_msi;
|
||||
|
||||
if (icu_irqd->icu_group == ICU_GRP_SEI)
|
||||
chip = &mvebu_icu_sei_chip;
|
||||
|
||||
err = irq_domain_set_hwirq_and_chip(domain, virq, hwirq,
|
||||
chip, icu_irqd);
|
||||
if (err) {
|
||||
dev_err(icu->dev, "failed to set the data to IRQ domain\n");
|
||||
goto free_msi;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
free_msi:
|
||||
platform_msi_device_domain_free(domain, virq, nr_irqs);
|
||||
free_irqd:
|
||||
kfree(icu_irqd);
|
||||
return err;
|
||||
}
|
||||
|
||||
static void
|
||||
mvebu_icu_irq_domain_free(struct irq_domain *domain, unsigned int virq,
|
||||
unsigned int nr_irqs)
|
||||
{
|
||||
struct irq_data *d = irq_get_irq_data(virq);
|
||||
struct mvebu_icu_irq_data *icu_irqd = d->chip_data;
|
||||
static const struct msi_domain_template mvebu_icu_nsr_msi_template = {
|
||||
.chip = {
|
||||
.name = "ICU-NSR",
|
||||
.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_write_msi_msg = mvebu_icu_write_msi_msg,
|
||||
.flags = IRQCHIP_SUPPORTS_LEVEL_MSI,
|
||||
},
|
||||
|
||||
kfree(icu_irqd);
|
||||
.ops = {
|
||||
.msi_translate = mvebu_icu_translate,
|
||||
.msi_init = mvebu_icu_msi_init,
|
||||
.set_desc = mvebu_icu_set_desc,
|
||||
},
|
||||
|
||||
platform_msi_device_domain_free(domain, virq, nr_irqs);
|
||||
}
|
||||
.info = {
|
||||
.bus_token = DOMAIN_BUS_WIRED_TO_MSI,
|
||||
.flags = MSI_FLAG_LEVEL_CAPABLE |
|
||||
MSI_FLAG_USE_DEV_FWNODE,
|
||||
},
|
||||
};
|
||||
|
||||
static const struct irq_domain_ops mvebu_icu_domain_ops = {
|
||||
.translate = mvebu_icu_irq_domain_translate,
|
||||
.alloc = mvebu_icu_irq_domain_alloc,
|
||||
.free = mvebu_icu_irq_domain_free,
|
||||
static const struct msi_domain_template mvebu_icu_sei_msi_template = {
|
||||
.chip = {
|
||||
.name = "ICU-SEI",
|
||||
.irq_mask = irq_chip_mask_parent,
|
||||
.irq_unmask = irq_chip_unmask_parent,
|
||||
.irq_ack = irq_chip_ack_parent,
|
||||
.irq_set_type = irq_chip_set_type_parent,
|
||||
.irq_write_msi_msg = mvebu_icu_write_msi_msg,
|
||||
.flags = IRQCHIP_SUPPORTS_LEVEL_MSI,
|
||||
},
|
||||
|
||||
.ops = {
|
||||
.msi_translate = mvebu_icu_translate,
|
||||
.msi_init = mvebu_icu_msi_init,
|
||||
.set_desc = mvebu_icu_set_desc,
|
||||
},
|
||||
|
||||
.info = {
|
||||
.bus_token = DOMAIN_BUS_WIRED_TO_MSI,
|
||||
.flags = MSI_FLAG_LEVEL_CAPABLE |
|
||||
MSI_FLAG_USE_DEV_FWNODE,
|
||||
},
|
||||
};
|
||||
|
||||
static const struct mvebu_icu_subset_data mvebu_icu_nsr_subset_data = {
|
||||
@@ -297,10 +258,10 @@ static const struct of_device_id mvebu_icu_subset_of_match[] = {
|
||||
|
||||
static int mvebu_icu_subset_probe(struct platform_device *pdev)
|
||||
{
|
||||
const struct msi_domain_template *tmpl;
|
||||
struct mvebu_icu_msi_data *msi_data;
|
||||
struct device_node *msi_parent_dn;
|
||||
struct device *dev = &pdev->dev;
|
||||
struct irq_domain *irq_domain;
|
||||
bool sei;
|
||||
|
||||
msi_data = devm_kzalloc(dev, sizeof(*msi_data), GFP_KERNEL);
|
||||
if (!msi_data)
|
||||
@@ -314,20 +275,18 @@ static int mvebu_icu_subset_probe(struct platform_device *pdev)
|
||||
msi_data->subset_data = of_device_get_match_data(dev);
|
||||
}
|
||||
|
||||
dev->msi.domain = of_msi_get_domain(dev, dev->of_node,
|
||||
DOMAIN_BUS_PLATFORM_MSI);
|
||||
dev->msi.domain = of_msi_get_domain(dev, dev->of_node, DOMAIN_BUS_PLATFORM_MSI);
|
||||
if (!dev->msi.domain)
|
||||
return -EPROBE_DEFER;
|
||||
|
||||
msi_parent_dn = irq_domain_get_of_node(dev->msi.domain);
|
||||
if (!msi_parent_dn)
|
||||
if (!irq_domain_get_of_node(dev->msi.domain))
|
||||
return -ENODEV;
|
||||
|
||||
irq_domain = platform_msi_create_device_tree_domain(dev, ICU_MAX_IRQS,
|
||||
mvebu_icu_write_msg,
|
||||
&mvebu_icu_domain_ops,
|
||||
msi_data);
|
||||
if (!irq_domain) {
|
||||
sei = msi_data->subset_data->icu_group == ICU_GRP_SEI;
|
||||
tmpl = sei ? &mvebu_icu_sei_msi_template : &mvebu_icu_nsr_msi_template;
|
||||
|
||||
if (!msi_create_device_irq_domain(dev, MSI_DEFAULT_DOMAIN, tmpl,
|
||||
ICU_MAX_IRQS, NULL, msi_data)) {
|
||||
dev_err(dev, "Failed to create ICU MSI domain\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
@@ -17,6 +17,9 @@
|
||||
#include <linux/msi.h>
|
||||
#include <linux/of_address.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
#include "irq-msi-lib.h"
|
||||
|
||||
#include <dt-bindings/interrupt-controller/arm-gic.h>
|
||||
|
||||
#define GICP_ODMIN_SET 0x40
|
||||
@@ -141,27 +144,29 @@ static void odmi_irq_domain_free(struct irq_domain *domain,
|
||||
}
|
||||
|
||||
static const struct irq_domain_ops odmi_domain_ops = {
|
||||
.select = msi_lib_irq_domain_select,
|
||||
.alloc = odmi_irq_domain_alloc,
|
||||
.free = odmi_irq_domain_free,
|
||||
};
|
||||
|
||||
static struct irq_chip odmi_msi_irq_chip = {
|
||||
.name = "ODMI",
|
||||
};
|
||||
#define ODMI_MSI_FLAGS_REQUIRED (MSI_FLAG_USE_DEF_DOM_OPS | \
|
||||
MSI_FLAG_USE_DEF_CHIP_OPS)
|
||||
|
||||
static struct msi_domain_ops odmi_msi_ops = {
|
||||
};
|
||||
#define ODMI_MSI_FLAGS_SUPPORTED (MSI_GENERIC_FLAGS_MASK)
|
||||
|
||||
static struct msi_domain_info odmi_msi_domain_info = {
|
||||
.flags = (MSI_FLAG_USE_DEF_DOM_OPS | MSI_FLAG_USE_DEF_CHIP_OPS),
|
||||
.ops = &odmi_msi_ops,
|
||||
.chip = &odmi_msi_irq_chip,
|
||||
static const struct msi_parent_ops odmi_msi_parent_ops = {
|
||||
.supported_flags = ODMI_MSI_FLAGS_SUPPORTED,
|
||||
.required_flags = ODMI_MSI_FLAGS_REQUIRED,
|
||||
.bus_select_token = DOMAIN_BUS_GENERIC_MSI,
|
||||
.bus_select_mask = MATCH_PLATFORM_MSI,
|
||||
.prefix = "ODMI-",
|
||||
.init_dev_msi_info = msi_lib_init_dev_msi_info,
|
||||
};
|
||||
|
||||
static int __init mvebu_odmi_init(struct device_node *node,
|
||||
struct device_node *parent)
|
||||
{
|
||||
struct irq_domain *parent_domain, *inner_domain, *plat_domain;
|
||||
struct irq_domain *parent_domain, *inner_domain;
|
||||
int ret, i;
|
||||
|
||||
if (of_property_read_u32(node, "marvell,odmi-frames", &odmis_count))
|
||||
@@ -208,18 +213,12 @@ static int __init mvebu_odmi_init(struct device_node *node,
|
||||
goto err_unmap;
|
||||
}
|
||||
|
||||
plat_domain = platform_msi_create_irq_domain(of_node_to_fwnode(node),
|
||||
&odmi_msi_domain_info,
|
||||
inner_domain);
|
||||
if (!plat_domain) {
|
||||
ret = -ENOMEM;
|
||||
goto err_remove_inner;
|
||||
}
|
||||
irq_domain_update_bus_token(inner_domain, DOMAIN_BUS_GENERIC_MSI);
|
||||
inner_domain->flags |= IRQ_DOMAIN_FLAG_MSI_PARENT;
|
||||
inner_domain->msi_parent_ops = &odmi_msi_parent_ops;
|
||||
|
||||
return 0;
|
||||
|
||||
err_remove_inner:
|
||||
irq_domain_remove(inner_domain);
|
||||
err_unmap:
|
||||
for (i = 0; i < odmis_count; i++) {
|
||||
struct odmi_data *odmi = &odmis[i];
|
||||
|
||||
@@ -193,6 +193,7 @@ module_platform_driver(mvebu_pic_driver);
|
||||
|
||||
MODULE_AUTHOR("Yehuda Yitschak <yehuday@marvell.com>");
|
||||
MODULE_AUTHOR("Thomas Petazzoni <thomas.petazzoni@free-electrons.com>");
|
||||
MODULE_DESCRIPTION("Marvell Armada 7K/8K PIC driver");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
MODULE_ALIAS("platform:mvebu_pic");
|
||||
|
||||
|
||||
@@ -14,6 +14,8 @@
|
||||
#include <linux/of_irq.h>
|
||||
#include <linux/of_platform.h>
|
||||
|
||||
#include "irq-msi-lib.h"
|
||||
|
||||
/* Cause register */
|
||||
#define GICP_SECR(idx) (0x0 + ((idx) * 0x4))
|
||||
/* Mask register */
|
||||
@@ -190,6 +192,7 @@ static void mvebu_sei_domain_free(struct irq_domain *domain, unsigned int virq,
|
||||
}
|
||||
|
||||
static const struct irq_domain_ops mvebu_sei_domain_ops = {
|
||||
.select = msi_lib_irq_domain_select,
|
||||
.alloc = mvebu_sei_domain_alloc,
|
||||
.free = mvebu_sei_domain_free,
|
||||
};
|
||||
@@ -307,21 +310,6 @@ static const struct irq_domain_ops mvebu_sei_cp_domain_ops = {
|
||||
.free = mvebu_sei_cp_domain_free,
|
||||
};
|
||||
|
||||
static struct irq_chip mvebu_sei_msi_irq_chip = {
|
||||
.name = "SEI pMSI",
|
||||
.irq_ack = irq_chip_ack_parent,
|
||||
.irq_set_type = irq_chip_set_type_parent,
|
||||
};
|
||||
|
||||
static struct msi_domain_ops mvebu_sei_msi_ops = {
|
||||
};
|
||||
|
||||
static struct msi_domain_info mvebu_sei_msi_domain_info = {
|
||||
.flags = MSI_FLAG_USE_DEF_DOM_OPS | MSI_FLAG_USE_DEF_CHIP_OPS,
|
||||
.ops = &mvebu_sei_msi_ops,
|
||||
.chip = &mvebu_sei_msi_irq_chip,
|
||||
};
|
||||
|
||||
static void mvebu_sei_handle_cascade_irq(struct irq_desc *desc)
|
||||
{
|
||||
struct mvebu_sei *sei = irq_desc_get_handler_data(desc);
|
||||
@@ -360,10 +348,23 @@ static void mvebu_sei_reset(struct mvebu_sei *sei)
|
||||
}
|
||||
}
|
||||
|
||||
#define SEI_MSI_FLAGS_REQUIRED (MSI_FLAG_USE_DEF_DOM_OPS | \
|
||||
MSI_FLAG_USE_DEF_CHIP_OPS)
|
||||
|
||||
#define SEI_MSI_FLAGS_SUPPORTED (MSI_GENERIC_FLAGS_MASK)
|
||||
|
||||
static const struct msi_parent_ops sei_msi_parent_ops = {
|
||||
.supported_flags = SEI_MSI_FLAGS_SUPPORTED,
|
||||
.required_flags = SEI_MSI_FLAGS_REQUIRED,
|
||||
.bus_select_mask = MATCH_PLATFORM_MSI,
|
||||
.bus_select_token = DOMAIN_BUS_GENERIC_MSI,
|
||||
.prefix = "SEI-",
|
||||
.init_dev_msi_info = msi_lib_init_dev_msi_info,
|
||||
};
|
||||
|
||||
static int mvebu_sei_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device_node *node = pdev->dev.of_node;
|
||||
struct irq_domain *plat_domain;
|
||||
struct mvebu_sei *sei;
|
||||
u32 parent_irq;
|
||||
int ret;
|
||||
@@ -440,33 +441,20 @@ static int mvebu_sei_probe(struct platform_device *pdev)
|
||||
}
|
||||
|
||||
irq_domain_update_bus_token(sei->cp_domain, DOMAIN_BUS_GENERIC_MSI);
|
||||
|
||||
plat_domain = platform_msi_create_irq_domain(of_node_to_fwnode(node),
|
||||
&mvebu_sei_msi_domain_info,
|
||||
sei->cp_domain);
|
||||
if (!plat_domain) {
|
||||
pr_err("Failed to create CPs MSI domain\n");
|
||||
ret = -ENOMEM;
|
||||
goto remove_cp_domain;
|
||||
}
|
||||
sei->cp_domain->flags |= IRQ_DOMAIN_FLAG_MSI_PARENT;
|
||||
sei->cp_domain->msi_parent_ops = &sei_msi_parent_ops;
|
||||
|
||||
mvebu_sei_reset(sei);
|
||||
|
||||
irq_set_chained_handler_and_data(parent_irq,
|
||||
mvebu_sei_handle_cascade_irq,
|
||||
sei);
|
||||
|
||||
irq_set_chained_handler_and_data(parent_irq, mvebu_sei_handle_cascade_irq, sei);
|
||||
return 0;
|
||||
|
||||
remove_cp_domain:
|
||||
irq_domain_remove(sei->cp_domain);
|
||||
remove_ap_domain:
|
||||
irq_domain_remove(sei->ap_domain);
|
||||
remove_sei_domain:
|
||||
irq_domain_remove(sei->sei_domain);
|
||||
dispose_irq:
|
||||
irq_dispose_mapping(parent_irq);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
@@ -37,6 +37,8 @@
|
||||
#define TSSEL_SHIFT(n) (8 * (n))
|
||||
#define TSSEL_MASK GENMASK(7, 0)
|
||||
#define IRQ_MASK 0x3
|
||||
#define IMSK 0x10010
|
||||
#define TMSK 0x10020
|
||||
|
||||
#define TSSR_OFFSET(n) ((n) % 4)
|
||||
#define TSSR_INDEX(n) ((n) / 4)
|
||||
@@ -69,12 +71,14 @@ struct rzg2l_irqc_reg_cache {
|
||||
/**
|
||||
* struct rzg2l_irqc_priv - IRQ controller private data structure
|
||||
* @base: Controller's base address
|
||||
* @irqchip: Pointer to struct irq_chip
|
||||
* @fwspec: IRQ firmware specific data
|
||||
* @lock: Lock to serialize access to hardware registers
|
||||
* @cache: Registers cache for suspend/resume
|
||||
*/
|
||||
static struct rzg2l_irqc_priv {
|
||||
void __iomem *base;
|
||||
const struct irq_chip *irqchip;
|
||||
struct irq_fwspec fwspec[IRQC_NUM_IRQ];
|
||||
raw_spinlock_t lock;
|
||||
struct rzg2l_irqc_reg_cache cache;
|
||||
@@ -138,6 +142,111 @@ static void rzg2l_irqc_eoi(struct irq_data *d)
|
||||
irq_chip_eoi_parent(d);
|
||||
}
|
||||
|
||||
static void rzfive_irqc_mask_irq_interrupt(struct rzg2l_irqc_priv *priv,
|
||||
unsigned int hwirq)
|
||||
{
|
||||
u32 bit = BIT(hwirq - IRQC_IRQ_START);
|
||||
|
||||
writel_relaxed(readl_relaxed(priv->base + IMSK) | bit, priv->base + IMSK);
|
||||
}
|
||||
|
||||
static void rzfive_irqc_unmask_irq_interrupt(struct rzg2l_irqc_priv *priv,
|
||||
unsigned int hwirq)
|
||||
{
|
||||
u32 bit = BIT(hwirq - IRQC_IRQ_START);
|
||||
|
||||
writel_relaxed(readl_relaxed(priv->base + IMSK) & ~bit, priv->base + IMSK);
|
||||
}
|
||||
|
||||
static void rzfive_irqc_mask_tint_interrupt(struct rzg2l_irqc_priv *priv,
|
||||
unsigned int hwirq)
|
||||
{
|
||||
u32 bit = BIT(hwirq - IRQC_TINT_START);
|
||||
|
||||
writel_relaxed(readl_relaxed(priv->base + TMSK) | bit, priv->base + TMSK);
|
||||
}
|
||||
|
||||
static void rzfive_irqc_unmask_tint_interrupt(struct rzg2l_irqc_priv *priv,
|
||||
unsigned int hwirq)
|
||||
{
|
||||
u32 bit = BIT(hwirq - IRQC_TINT_START);
|
||||
|
||||
writel_relaxed(readl_relaxed(priv->base + TMSK) & ~bit, priv->base + TMSK);
|
||||
}
|
||||
|
||||
static void rzfive_irqc_mask(struct irq_data *d)
|
||||
{
|
||||
struct rzg2l_irqc_priv *priv = irq_data_to_priv(d);
|
||||
unsigned int hwirq = irqd_to_hwirq(d);
|
||||
|
||||
raw_spin_lock(&priv->lock);
|
||||
if (hwirq >= IRQC_IRQ_START && hwirq <= IRQC_IRQ_COUNT)
|
||||
rzfive_irqc_mask_irq_interrupt(priv, hwirq);
|
||||
else if (hwirq >= IRQC_TINT_START && hwirq < IRQC_NUM_IRQ)
|
||||
rzfive_irqc_mask_tint_interrupt(priv, hwirq);
|
||||
raw_spin_unlock(&priv->lock);
|
||||
irq_chip_mask_parent(d);
|
||||
}
|
||||
|
||||
static void rzfive_irqc_unmask(struct irq_data *d)
|
||||
{
|
||||
struct rzg2l_irqc_priv *priv = irq_data_to_priv(d);
|
||||
unsigned int hwirq = irqd_to_hwirq(d);
|
||||
|
||||
raw_spin_lock(&priv->lock);
|
||||
if (hwirq >= IRQC_IRQ_START && hwirq <= IRQC_IRQ_COUNT)
|
||||
rzfive_irqc_unmask_irq_interrupt(priv, hwirq);
|
||||
else if (hwirq >= IRQC_TINT_START && hwirq < IRQC_NUM_IRQ)
|
||||
rzfive_irqc_unmask_tint_interrupt(priv, hwirq);
|
||||
raw_spin_unlock(&priv->lock);
|
||||
irq_chip_unmask_parent(d);
|
||||
}
|
||||
|
||||
static void rzfive_tint_irq_endisable(struct irq_data *d, bool enable)
|
||||
{
|
||||
struct rzg2l_irqc_priv *priv = irq_data_to_priv(d);
|
||||
unsigned int hwirq = irqd_to_hwirq(d);
|
||||
|
||||
if (hwirq >= IRQC_TINT_START && hwirq < IRQC_NUM_IRQ) {
|
||||
u32 offset = hwirq - IRQC_TINT_START;
|
||||
u32 tssr_offset = TSSR_OFFSET(offset);
|
||||
u8 tssr_index = TSSR_INDEX(offset);
|
||||
u32 reg;
|
||||
|
||||
raw_spin_lock(&priv->lock);
|
||||
if (enable)
|
||||
rzfive_irqc_unmask_tint_interrupt(priv, hwirq);
|
||||
else
|
||||
rzfive_irqc_mask_tint_interrupt(priv, hwirq);
|
||||
reg = readl_relaxed(priv->base + TSSR(tssr_index));
|
||||
if (enable)
|
||||
reg |= TIEN << TSSEL_SHIFT(tssr_offset);
|
||||
else
|
||||
reg &= ~(TIEN << TSSEL_SHIFT(tssr_offset));
|
||||
writel_relaxed(reg, priv->base + TSSR(tssr_index));
|
||||
raw_spin_unlock(&priv->lock);
|
||||
} else {
|
||||
raw_spin_lock(&priv->lock);
|
||||
if (enable)
|
||||
rzfive_irqc_unmask_irq_interrupt(priv, hwirq);
|
||||
else
|
||||
rzfive_irqc_mask_irq_interrupt(priv, hwirq);
|
||||
raw_spin_unlock(&priv->lock);
|
||||
}
|
||||
}
|
||||
|
||||
static void rzfive_irqc_irq_disable(struct irq_data *d)
|
||||
{
|
||||
irq_chip_disable_parent(d);
|
||||
rzfive_tint_irq_endisable(d, false);
|
||||
}
|
||||
|
||||
static void rzfive_irqc_irq_enable(struct irq_data *d)
|
||||
{
|
||||
rzfive_tint_irq_endisable(d, true);
|
||||
irq_chip_enable_parent(d);
|
||||
}
|
||||
|
||||
static void rzg2l_tint_irq_endisable(struct irq_data *d, bool enable)
|
||||
{
|
||||
unsigned int hw_irq = irqd_to_hwirq(d);
|
||||
@@ -162,8 +271,8 @@ static void rzg2l_tint_irq_endisable(struct irq_data *d, bool enable)
|
||||
|
||||
static void rzg2l_irqc_irq_disable(struct irq_data *d)
|
||||
{
|
||||
rzg2l_tint_irq_endisable(d, false);
|
||||
irq_chip_disable_parent(d);
|
||||
rzg2l_tint_irq_endisable(d, false);
|
||||
}
|
||||
|
||||
static void rzg2l_irqc_irq_enable(struct irq_data *d)
|
||||
@@ -321,7 +430,7 @@ static struct syscore_ops rzg2l_irqc_syscore_ops = {
|
||||
.resume = rzg2l_irqc_irq_resume,
|
||||
};
|
||||
|
||||
static const struct irq_chip irqc_chip = {
|
||||
static const struct irq_chip rzg2l_irqc_chip = {
|
||||
.name = "rzg2l-irqc",
|
||||
.irq_eoi = rzg2l_irqc_eoi,
|
||||
.irq_mask = irq_chip_mask_parent,
|
||||
@@ -338,6 +447,23 @@ static const struct irq_chip irqc_chip = {
|
||||
IRQCHIP_SKIP_SET_WAKE,
|
||||
};
|
||||
|
||||
static const struct irq_chip rzfive_irqc_chip = {
|
||||
.name = "rzfive-irqc",
|
||||
.irq_eoi = rzg2l_irqc_eoi,
|
||||
.irq_mask = rzfive_irqc_mask,
|
||||
.irq_unmask = rzfive_irqc_unmask,
|
||||
.irq_disable = rzfive_irqc_irq_disable,
|
||||
.irq_enable = rzfive_irqc_irq_enable,
|
||||
.irq_get_irqchip_state = irq_chip_get_parent_state,
|
||||
.irq_set_irqchip_state = irq_chip_set_parent_state,
|
||||
.irq_retrigger = irq_chip_retrigger_hierarchy,
|
||||
.irq_set_type = rzg2l_irqc_set_type,
|
||||
.irq_set_affinity = irq_chip_set_affinity_parent,
|
||||
.flags = IRQCHIP_MASK_ON_SUSPEND |
|
||||
IRQCHIP_SET_TYPE_MASKED |
|
||||
IRQCHIP_SKIP_SET_WAKE,
|
||||
};
|
||||
|
||||
static int rzg2l_irqc_alloc(struct irq_domain *domain, unsigned int virq,
|
||||
unsigned int nr_irqs, void *arg)
|
||||
{
|
||||
@@ -369,7 +495,7 @@ static int rzg2l_irqc_alloc(struct irq_domain *domain, unsigned int virq,
|
||||
if (hwirq > (IRQC_NUM_IRQ - 1))
|
||||
return -EINVAL;
|
||||
|
||||
ret = irq_domain_set_hwirq_and_chip(domain, virq, hwirq, &irqc_chip,
|
||||
ret = irq_domain_set_hwirq_and_chip(domain, virq, hwirq, priv->irqchip,
|
||||
(void *)(uintptr_t)tint);
|
||||
if (ret)
|
||||
return ret;
|
||||
@@ -401,7 +527,8 @@ static int rzg2l_irqc_parse_interrupts(struct rzg2l_irqc_priv *priv,
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rzg2l_irqc_init(struct device_node *node, struct device_node *parent)
|
||||
static int rzg2l_irqc_common_init(struct device_node *node, struct device_node *parent,
|
||||
const struct irq_chip *irq_chip)
|
||||
{
|
||||
struct irq_domain *irq_domain, *parent_domain;
|
||||
struct platform_device *pdev;
|
||||
@@ -422,6 +549,8 @@ static int rzg2l_irqc_init(struct device_node *node, struct device_node *parent)
|
||||
if (!rzg2l_irqc_data)
|
||||
return -ENOMEM;
|
||||
|
||||
rzg2l_irqc_data->irqchip = irq_chip;
|
||||
|
||||
rzg2l_irqc_data->base = devm_of_iomap(&pdev->dev, pdev->dev.of_node, 0, NULL);
|
||||
if (IS_ERR(rzg2l_irqc_data->base))
|
||||
return PTR_ERR(rzg2l_irqc_data->base);
|
||||
@@ -472,8 +601,21 @@ pm_disable:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int __init rzg2l_irqc_init(struct device_node *node,
|
||||
struct device_node *parent)
|
||||
{
|
||||
return rzg2l_irqc_common_init(node, parent, &rzg2l_irqc_chip);
|
||||
}
|
||||
|
||||
static int __init rzfive_irqc_init(struct device_node *node,
|
||||
struct device_node *parent)
|
||||
{
|
||||
return rzg2l_irqc_common_init(node, parent, &rzfive_irqc_chip);
|
||||
}
|
||||
|
||||
IRQCHIP_PLATFORM_DRIVER_BEGIN(rzg2l_irqc)
|
||||
IRQCHIP_MATCH("renesas,rzg2l-irqc", rzg2l_irqc_init)
|
||||
IRQCHIP_MATCH("renesas,r9a07g043f-irqc", rzfive_irqc_init)
|
||||
IRQCHIP_PLATFORM_DRIVER_END(rzg2l_irqc)
|
||||
MODULE_AUTHOR("Lad Prabhakar <prabhakar.mahadev-lad.rj@bp.renesas.com>");
|
||||
MODULE_DESCRIPTION("Renesas RZ/G2L IRQC Driver");
|
||||
|
||||
@@ -127,6 +127,7 @@ static void aplic_init_hw_irqs(struct aplic_priv *priv)
|
||||
|
||||
int aplic_setup_priv(struct aplic_priv *priv, struct device *dev, void __iomem *regs)
|
||||
{
|
||||
struct device_node *np = to_of_node(dev->fwnode);
|
||||
struct of_phandle_args parent;
|
||||
int rc;
|
||||
|
||||
@@ -134,7 +135,7 @@ int aplic_setup_priv(struct aplic_priv *priv, struct device *dev, void __iomem *
|
||||
* Currently, only OF fwnode is supported so extend this
|
||||
* function for ACPI support.
|
||||
*/
|
||||
if (!is_of_node(dev->fwnode))
|
||||
if (!np)
|
||||
return -EINVAL;
|
||||
|
||||
/* Save device pointer and register base */
|
||||
@@ -142,8 +143,7 @@ int aplic_setup_priv(struct aplic_priv *priv, struct device *dev, void __iomem *
|
||||
priv->regs = regs;
|
||||
|
||||
/* Find out number of interrupt sources */
|
||||
rc = of_property_read_u32(to_of_node(dev->fwnode), "riscv,num-sources",
|
||||
&priv->nr_irqs);
|
||||
rc = of_property_read_u32(np, "riscv,num-sources", &priv->nr_irqs);
|
||||
if (rc) {
|
||||
dev_err(dev, "failed to get number of interrupt sources\n");
|
||||
return rc;
|
||||
@@ -155,8 +155,8 @@ int aplic_setup_priv(struct aplic_priv *priv, struct device *dev, void __iomem *
|
||||
* If "msi-parent" property is present then we ignore the
|
||||
* APLIC IDCs which forces the APLIC driver to use MSI mode.
|
||||
*/
|
||||
if (!of_property_present(to_of_node(dev->fwnode), "msi-parent")) {
|
||||
while (!of_irq_parse_one(to_of_node(dev->fwnode), priv->nr_idcs, &parent))
|
||||
if (!of_property_present(np, "msi-parent")) {
|
||||
while (!of_irq_parse_one(np, priv->nr_idcs, &parent))
|
||||
priv->nr_idcs++;
|
||||
}
|
||||
|
||||
@@ -184,8 +184,7 @@ static int aplic_probe(struct platform_device *pdev)
|
||||
* If msi-parent property is present then setup APLIC MSI
|
||||
* mode otherwise setup APLIC direct mode.
|
||||
*/
|
||||
if (is_of_node(dev->fwnode))
|
||||
msi_mode = of_property_present(to_of_node(dev->fwnode), "msi-parent");
|
||||
msi_mode = of_property_present(to_of_node(dev->fwnode), "msi-parent");
|
||||
if (msi_mode)
|
||||
rc = aplic_msi_setup(dev, regs);
|
||||
else
|
||||
|
||||
@@ -26,7 +26,7 @@ static unsigned int riscv_intc_nr_irqs __ro_after_init = BITS_PER_LONG;
|
||||
static unsigned int riscv_intc_custom_base __ro_after_init = BITS_PER_LONG;
|
||||
static unsigned int riscv_intc_custom_nr_irqs __ro_after_init;
|
||||
|
||||
static asmlinkage void riscv_intc_irq(struct pt_regs *regs)
|
||||
static void riscv_intc_irq(struct pt_regs *regs)
|
||||
{
|
||||
unsigned long cause = regs->cause & ~CAUSE_IRQ_FLAG;
|
||||
|
||||
@@ -34,7 +34,7 @@ static asmlinkage void riscv_intc_irq(struct pt_regs *regs)
|
||||
pr_warn_ratelimited("Failed to handle interrupt (cause: %ld)\n", cause);
|
||||
}
|
||||
|
||||
static asmlinkage void riscv_intc_aia_irq(struct pt_regs *regs)
|
||||
static void riscv_intc_aia_irq(struct pt_regs *regs)
|
||||
{
|
||||
unsigned long topi;
|
||||
|
||||
|
||||
@@ -1,45 +1,22 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Copyright (C) Maxime Coquelin 2015
|
||||
* Copyright (C) STMicroelectronics 2017
|
||||
* Copyright (C) STMicroelectronics 2017-2024
|
||||
* Author: Maxime Coquelin <mcoquelin.stm32@gmail.com>
|
||||
*/
|
||||
|
||||
#include <linux/bitops.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/hwspinlock.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/irq.h>
|
||||
#include <linux/irqchip.h>
|
||||
#include <linux/irqchip/chained_irq.h>
|
||||
#include <linux/irqdomain.h>
|
||||
#include <linux/mod_devicetable.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of_address.h>
|
||||
#include <linux/of_irq.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/pm.h>
|
||||
|
||||
#include <dt-bindings/interrupt-controller/arm-gic.h>
|
||||
|
||||
#define IRQS_PER_BANK 32
|
||||
|
||||
#define HWSPNLCK_TIMEOUT 1000 /* usec */
|
||||
|
||||
#define EXTI_EnCIDCFGR(n) (0x180 + (n) * 4)
|
||||
#define EXTI_HWCFGR1 0x3f0
|
||||
|
||||
/* Register: EXTI_EnCIDCFGR(n) */
|
||||
#define EXTI_CIDCFGR_CFEN_MASK BIT(0)
|
||||
#define EXTI_CIDCFGR_CID_MASK GENMASK(6, 4)
|
||||
#define EXTI_CIDCFGR_CID_SHIFT 4
|
||||
|
||||
/* Register: EXTI_HWCFGR1 */
|
||||
#define EXTI_HWCFGR1_CIDWIDTH_MASK GENMASK(27, 24)
|
||||
|
||||
#define EXTI_CID1 1
|
||||
|
||||
struct stm32_exti_bank {
|
||||
u32 imr_ofst;
|
||||
u32 emr_ofst;
|
||||
@@ -47,13 +24,8 @@ struct stm32_exti_bank {
|
||||
u32 ftsr_ofst;
|
||||
u32 swier_ofst;
|
||||
u32 rpr_ofst;
|
||||
u32 fpr_ofst;
|
||||
u32 trg_ofst;
|
||||
u32 seccfgr_ofst;
|
||||
};
|
||||
|
||||
#define UNDEF_REG ~0
|
||||
|
||||
struct stm32_exti_drv_data {
|
||||
const struct stm32_exti_bank **exti_banks;
|
||||
const u8 *desc_irqs;
|
||||
@@ -63,7 +35,6 @@ struct stm32_exti_drv_data {
|
||||
struct stm32_exti_chip_data {
|
||||
struct stm32_exti_host_data *host_data;
|
||||
const struct stm32_exti_bank *reg_bank;
|
||||
struct raw_spinlock rlock;
|
||||
u32 wake_active;
|
||||
u32 mask_cache;
|
||||
u32 rtsr_cache;
|
||||
@@ -76,8 +47,6 @@ struct stm32_exti_host_data {
|
||||
struct device *dev;
|
||||
struct stm32_exti_chip_data *chips_data;
|
||||
const struct stm32_exti_drv_data *drv_data;
|
||||
struct hwspinlock *hwlock;
|
||||
bool dt_has_irqs_desc; /* skip internal desc_irqs array and get it from DT */
|
||||
};
|
||||
|
||||
static const struct stm32_exti_bank stm32f4xx_exti_b1 = {
|
||||
@@ -87,9 +56,6 @@ static const struct stm32_exti_bank stm32f4xx_exti_b1 = {
|
||||
.ftsr_ofst = 0x0C,
|
||||
.swier_ofst = 0x10,
|
||||
.rpr_ofst = 0x14,
|
||||
.fpr_ofst = UNDEF_REG,
|
||||
.trg_ofst = UNDEF_REG,
|
||||
.seccfgr_ofst = UNDEF_REG,
|
||||
};
|
||||
|
||||
static const struct stm32_exti_bank *stm32f4xx_exti_banks[] = {
|
||||
@@ -108,9 +74,6 @@ static const struct stm32_exti_bank stm32h7xx_exti_b1 = {
|
||||
.ftsr_ofst = 0x04,
|
||||
.swier_ofst = 0x08,
|
||||
.rpr_ofst = 0x88,
|
||||
.fpr_ofst = UNDEF_REG,
|
||||
.trg_ofst = UNDEF_REG,
|
||||
.seccfgr_ofst = UNDEF_REG,
|
||||
};
|
||||
|
||||
static const struct stm32_exti_bank stm32h7xx_exti_b2 = {
|
||||
@@ -120,9 +83,6 @@ static const struct stm32_exti_bank stm32h7xx_exti_b2 = {
|
||||
.ftsr_ofst = 0x24,
|
||||
.swier_ofst = 0x28,
|
||||
.rpr_ofst = 0x98,
|
||||
.fpr_ofst = UNDEF_REG,
|
||||
.trg_ofst = UNDEF_REG,
|
||||
.seccfgr_ofst = UNDEF_REG,
|
||||
};
|
||||
|
||||
static const struct stm32_exti_bank stm32h7xx_exti_b3 = {
|
||||
@@ -132,9 +92,6 @@ static const struct stm32_exti_bank stm32h7xx_exti_b3 = {
|
||||
.ftsr_ofst = 0x44,
|
||||
.swier_ofst = 0x48,
|
||||
.rpr_ofst = 0xA8,
|
||||
.fpr_ofst = UNDEF_REG,
|
||||
.trg_ofst = UNDEF_REG,
|
||||
.seccfgr_ofst = UNDEF_REG,
|
||||
};
|
||||
|
||||
static const struct stm32_exti_bank *stm32h7xx_exti_banks[] = {
|
||||
@@ -148,183 +105,12 @@ static const struct stm32_exti_drv_data stm32h7xx_drv_data = {
|
||||
.bank_nr = ARRAY_SIZE(stm32h7xx_exti_banks),
|
||||
};
|
||||
|
||||
static const struct stm32_exti_bank stm32mp1_exti_b1 = {
|
||||
.imr_ofst = 0x80,
|
||||
.emr_ofst = UNDEF_REG,
|
||||
.rtsr_ofst = 0x00,
|
||||
.ftsr_ofst = 0x04,
|
||||
.swier_ofst = 0x08,
|
||||
.rpr_ofst = 0x0C,
|
||||
.fpr_ofst = 0x10,
|
||||
.trg_ofst = 0x3EC,
|
||||
.seccfgr_ofst = 0x14,
|
||||
};
|
||||
|
||||
static const struct stm32_exti_bank stm32mp1_exti_b2 = {
|
||||
.imr_ofst = 0x90,
|
||||
.emr_ofst = UNDEF_REG,
|
||||
.rtsr_ofst = 0x20,
|
||||
.ftsr_ofst = 0x24,
|
||||
.swier_ofst = 0x28,
|
||||
.rpr_ofst = 0x2C,
|
||||
.fpr_ofst = 0x30,
|
||||
.trg_ofst = 0x3E8,
|
||||
.seccfgr_ofst = 0x34,
|
||||
};
|
||||
|
||||
static const struct stm32_exti_bank stm32mp1_exti_b3 = {
|
||||
.imr_ofst = 0xA0,
|
||||
.emr_ofst = UNDEF_REG,
|
||||
.rtsr_ofst = 0x40,
|
||||
.ftsr_ofst = 0x44,
|
||||
.swier_ofst = 0x48,
|
||||
.rpr_ofst = 0x4C,
|
||||
.fpr_ofst = 0x50,
|
||||
.trg_ofst = 0x3E4,
|
||||
.seccfgr_ofst = 0x54,
|
||||
};
|
||||
|
||||
static const struct stm32_exti_bank *stm32mp1_exti_banks[] = {
|
||||
&stm32mp1_exti_b1,
|
||||
&stm32mp1_exti_b2,
|
||||
&stm32mp1_exti_b3,
|
||||
};
|
||||
|
||||
static struct irq_chip stm32_exti_h_chip;
|
||||
static struct irq_chip stm32_exti_h_chip_direct;
|
||||
|
||||
#define EXTI_INVALID_IRQ U8_MAX
|
||||
#define STM32MP1_DESC_IRQ_SIZE (ARRAY_SIZE(stm32mp1_exti_banks) * IRQS_PER_BANK)
|
||||
|
||||
/*
|
||||
* Use some intentionally tricky logic here to initialize the whole array to
|
||||
* EXTI_INVALID_IRQ, but then override certain fields, requiring us to indicate
|
||||
* that we "know" that there are overrides in this structure, and we'll need to
|
||||
* disable that warning from W=1 builds.
|
||||
*/
|
||||
__diag_push();
|
||||
__diag_ignore_all("-Woverride-init",
|
||||
"logic to initialize all and then override some is OK");
|
||||
|
||||
static const u8 stm32mp1_desc_irq[] = {
|
||||
/* default value */
|
||||
[0 ... (STM32MP1_DESC_IRQ_SIZE - 1)] = EXTI_INVALID_IRQ,
|
||||
|
||||
[0] = 6,
|
||||
[1] = 7,
|
||||
[2] = 8,
|
||||
[3] = 9,
|
||||
[4] = 10,
|
||||
[5] = 23,
|
||||
[6] = 64,
|
||||
[7] = 65,
|
||||
[8] = 66,
|
||||
[9] = 67,
|
||||
[10] = 40,
|
||||
[11] = 42,
|
||||
[12] = 76,
|
||||
[13] = 77,
|
||||
[14] = 121,
|
||||
[15] = 127,
|
||||
[16] = 1,
|
||||
[19] = 3,
|
||||
[21] = 31,
|
||||
[22] = 33,
|
||||
[23] = 72,
|
||||
[24] = 95,
|
||||
[25] = 107,
|
||||
[26] = 37,
|
||||
[27] = 38,
|
||||
[28] = 39,
|
||||
[29] = 71,
|
||||
[30] = 52,
|
||||
[31] = 53,
|
||||
[32] = 82,
|
||||
[33] = 83,
|
||||
[46] = 151,
|
||||
[47] = 93,
|
||||
[48] = 138,
|
||||
[50] = 139,
|
||||
[52] = 140,
|
||||
[53] = 141,
|
||||
[54] = 135,
|
||||
[61] = 100,
|
||||
[65] = 144,
|
||||
[68] = 143,
|
||||
[70] = 62,
|
||||
[73] = 129,
|
||||
};
|
||||
|
||||
static const u8 stm32mp13_desc_irq[] = {
|
||||
/* default value */
|
||||
[0 ... (STM32MP1_DESC_IRQ_SIZE - 1)] = EXTI_INVALID_IRQ,
|
||||
|
||||
[0] = 6,
|
||||
[1] = 7,
|
||||
[2] = 8,
|
||||
[3] = 9,
|
||||
[4] = 10,
|
||||
[5] = 24,
|
||||
[6] = 65,
|
||||
[7] = 66,
|
||||
[8] = 67,
|
||||
[9] = 68,
|
||||
[10] = 41,
|
||||
[11] = 43,
|
||||
[12] = 77,
|
||||
[13] = 78,
|
||||
[14] = 106,
|
||||
[15] = 109,
|
||||
[16] = 1,
|
||||
[19] = 3,
|
||||
[21] = 32,
|
||||
[22] = 34,
|
||||
[23] = 73,
|
||||
[24] = 93,
|
||||
[25] = 114,
|
||||
[26] = 38,
|
||||
[27] = 39,
|
||||
[28] = 40,
|
||||
[29] = 72,
|
||||
[30] = 53,
|
||||
[31] = 54,
|
||||
[32] = 83,
|
||||
[33] = 84,
|
||||
[44] = 96,
|
||||
[47] = 92,
|
||||
[48] = 116,
|
||||
[50] = 117,
|
||||
[52] = 118,
|
||||
[53] = 119,
|
||||
[68] = 63,
|
||||
[70] = 98,
|
||||
};
|
||||
|
||||
__diag_pop();
|
||||
|
||||
static const struct stm32_exti_drv_data stm32mp1_drv_data = {
|
||||
.exti_banks = stm32mp1_exti_banks,
|
||||
.bank_nr = ARRAY_SIZE(stm32mp1_exti_banks),
|
||||
.desc_irqs = stm32mp1_desc_irq,
|
||||
};
|
||||
|
||||
static const struct stm32_exti_drv_data stm32mp13_drv_data = {
|
||||
.exti_banks = stm32mp1_exti_banks,
|
||||
.bank_nr = ARRAY_SIZE(stm32mp1_exti_banks),
|
||||
.desc_irqs = stm32mp13_desc_irq,
|
||||
};
|
||||
|
||||
static unsigned long stm32_exti_pending(struct irq_chip_generic *gc)
|
||||
{
|
||||
struct stm32_exti_chip_data *chip_data = gc->private;
|
||||
const struct stm32_exti_bank *stm32_bank = chip_data->reg_bank;
|
||||
unsigned long pending;
|
||||
|
||||
pending = irq_reg_readl(gc, stm32_bank->rpr_ofst);
|
||||
if (stm32_bank->fpr_ofst != UNDEF_REG)
|
||||
pending |= irq_reg_readl(gc, stm32_bank->fpr_ofst);
|
||||
|
||||
return pending;
|
||||
return irq_reg_readl(gc, stm32_bank->rpr_ofst);
|
||||
}
|
||||
|
||||
static void stm32_irq_handler(struct irq_desc *desc)
|
||||
@@ -380,33 +166,21 @@ static int stm32_irq_set_type(struct irq_data *d, unsigned int type)
|
||||
struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d);
|
||||
struct stm32_exti_chip_data *chip_data = gc->private;
|
||||
const struct stm32_exti_bank *stm32_bank = chip_data->reg_bank;
|
||||
struct hwspinlock *hwlock = chip_data->host_data->hwlock;
|
||||
u32 rtsr, ftsr;
|
||||
int err;
|
||||
|
||||
irq_gc_lock(gc);
|
||||
|
||||
if (hwlock) {
|
||||
err = hwspin_lock_timeout_in_atomic(hwlock, HWSPNLCK_TIMEOUT);
|
||||
if (err) {
|
||||
pr_err("%s can't get hwspinlock (%d)\n", __func__, err);
|
||||
goto unlock;
|
||||
}
|
||||
}
|
||||
|
||||
rtsr = irq_reg_readl(gc, stm32_bank->rtsr_ofst);
|
||||
ftsr = irq_reg_readl(gc, stm32_bank->ftsr_ofst);
|
||||
|
||||
err = stm32_exti_set_type(d, type, &rtsr, &ftsr);
|
||||
if (err)
|
||||
goto unspinlock;
|
||||
goto unlock;
|
||||
|
||||
irq_reg_writel(gc, rtsr, stm32_bank->rtsr_ofst);
|
||||
irq_reg_writel(gc, ftsr, stm32_bank->ftsr_ofst);
|
||||
|
||||
unspinlock:
|
||||
if (hwlock)
|
||||
hwspin_unlock_in_atomic(hwlock);
|
||||
unlock:
|
||||
irq_gc_unlock(gc);
|
||||
|
||||
@@ -494,287 +268,10 @@ static void stm32_irq_ack(struct irq_data *d)
|
||||
irq_gc_lock(gc);
|
||||
|
||||
irq_reg_writel(gc, d->mask, stm32_bank->rpr_ofst);
|
||||
if (stm32_bank->fpr_ofst != UNDEF_REG)
|
||||
irq_reg_writel(gc, d->mask, stm32_bank->fpr_ofst);
|
||||
|
||||
irq_gc_unlock(gc);
|
||||
}
|
||||
|
||||
/* directly set the target bit without reading first. */
|
||||
static inline void stm32_exti_write_bit(struct irq_data *d, u32 reg)
|
||||
{
|
||||
struct stm32_exti_chip_data *chip_data = irq_data_get_irq_chip_data(d);
|
||||
void __iomem *base = chip_data->host_data->base;
|
||||
u32 val = BIT(d->hwirq % IRQS_PER_BANK);
|
||||
|
||||
writel_relaxed(val, base + reg);
|
||||
}
|
||||
|
||||
static inline u32 stm32_exti_set_bit(struct irq_data *d, u32 reg)
|
||||
{
|
||||
struct stm32_exti_chip_data *chip_data = irq_data_get_irq_chip_data(d);
|
||||
void __iomem *base = chip_data->host_data->base;
|
||||
u32 val;
|
||||
|
||||
val = readl_relaxed(base + reg);
|
||||
val |= BIT(d->hwirq % IRQS_PER_BANK);
|
||||
writel_relaxed(val, base + reg);
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
static inline u32 stm32_exti_clr_bit(struct irq_data *d, u32 reg)
|
||||
{
|
||||
struct stm32_exti_chip_data *chip_data = irq_data_get_irq_chip_data(d);
|
||||
void __iomem *base = chip_data->host_data->base;
|
||||
u32 val;
|
||||
|
||||
val = readl_relaxed(base + reg);
|
||||
val &= ~BIT(d->hwirq % IRQS_PER_BANK);
|
||||
writel_relaxed(val, base + reg);
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
static void stm32_exti_h_eoi(struct irq_data *d)
|
||||
{
|
||||
struct stm32_exti_chip_data *chip_data = irq_data_get_irq_chip_data(d);
|
||||
const struct stm32_exti_bank *stm32_bank = chip_data->reg_bank;
|
||||
|
||||
raw_spin_lock(&chip_data->rlock);
|
||||
|
||||
stm32_exti_write_bit(d, stm32_bank->rpr_ofst);
|
||||
if (stm32_bank->fpr_ofst != UNDEF_REG)
|
||||
stm32_exti_write_bit(d, stm32_bank->fpr_ofst);
|
||||
|
||||
raw_spin_unlock(&chip_data->rlock);
|
||||
|
||||
if (d->parent_data->chip)
|
||||
irq_chip_eoi_parent(d);
|
||||
}
|
||||
|
||||
static void stm32_exti_h_mask(struct irq_data *d)
|
||||
{
|
||||
struct stm32_exti_chip_data *chip_data = irq_data_get_irq_chip_data(d);
|
||||
const struct stm32_exti_bank *stm32_bank = chip_data->reg_bank;
|
||||
|
||||
raw_spin_lock(&chip_data->rlock);
|
||||
chip_data->mask_cache = stm32_exti_clr_bit(d, stm32_bank->imr_ofst);
|
||||
raw_spin_unlock(&chip_data->rlock);
|
||||
|
||||
if (d->parent_data->chip)
|
||||
irq_chip_mask_parent(d);
|
||||
}
|
||||
|
||||
static void stm32_exti_h_unmask(struct irq_data *d)
|
||||
{
|
||||
struct stm32_exti_chip_data *chip_data = irq_data_get_irq_chip_data(d);
|
||||
const struct stm32_exti_bank *stm32_bank = chip_data->reg_bank;
|
||||
|
||||
raw_spin_lock(&chip_data->rlock);
|
||||
chip_data->mask_cache = stm32_exti_set_bit(d, stm32_bank->imr_ofst);
|
||||
raw_spin_unlock(&chip_data->rlock);
|
||||
|
||||
if (d->parent_data->chip)
|
||||
irq_chip_unmask_parent(d);
|
||||
}
|
||||
|
||||
static int stm32_exti_h_set_type(struct irq_data *d, unsigned int type)
|
||||
{
|
||||
struct stm32_exti_chip_data *chip_data = irq_data_get_irq_chip_data(d);
|
||||
const struct stm32_exti_bank *stm32_bank = chip_data->reg_bank;
|
||||
struct hwspinlock *hwlock = chip_data->host_data->hwlock;
|
||||
void __iomem *base = chip_data->host_data->base;
|
||||
u32 rtsr, ftsr;
|
||||
int err;
|
||||
|
||||
raw_spin_lock(&chip_data->rlock);
|
||||
|
||||
if (hwlock) {
|
||||
err = hwspin_lock_timeout_in_atomic(hwlock, HWSPNLCK_TIMEOUT);
|
||||
if (err) {
|
||||
pr_err("%s can't get hwspinlock (%d)\n", __func__, err);
|
||||
goto unlock;
|
||||
}
|
||||
}
|
||||
|
||||
rtsr = readl_relaxed(base + stm32_bank->rtsr_ofst);
|
||||
ftsr = readl_relaxed(base + stm32_bank->ftsr_ofst);
|
||||
|
||||
err = stm32_exti_set_type(d, type, &rtsr, &ftsr);
|
||||
if (err)
|
||||
goto unspinlock;
|
||||
|
||||
writel_relaxed(rtsr, base + stm32_bank->rtsr_ofst);
|
||||
writel_relaxed(ftsr, base + stm32_bank->ftsr_ofst);
|
||||
|
||||
unspinlock:
|
||||
if (hwlock)
|
||||
hwspin_unlock_in_atomic(hwlock);
|
||||
unlock:
|
||||
raw_spin_unlock(&chip_data->rlock);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int stm32_exti_h_set_wake(struct irq_data *d, unsigned int on)
|
||||
{
|
||||
struct stm32_exti_chip_data *chip_data = irq_data_get_irq_chip_data(d);
|
||||
u32 mask = BIT(d->hwirq % IRQS_PER_BANK);
|
||||
|
||||
raw_spin_lock(&chip_data->rlock);
|
||||
|
||||
if (on)
|
||||
chip_data->wake_active |= mask;
|
||||
else
|
||||
chip_data->wake_active &= ~mask;
|
||||
|
||||
raw_spin_unlock(&chip_data->rlock);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int stm32_exti_h_set_affinity(struct irq_data *d,
|
||||
const struct cpumask *dest, bool force)
|
||||
{
|
||||
if (d->parent_data->chip)
|
||||
return irq_chip_set_affinity_parent(d, dest, force);
|
||||
|
||||
return IRQ_SET_MASK_OK_DONE;
|
||||
}
|
||||
|
||||
static int stm32_exti_h_suspend(struct device *dev)
|
||||
{
|
||||
struct stm32_exti_host_data *host_data = dev_get_drvdata(dev);
|
||||
struct stm32_exti_chip_data *chip_data;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < host_data->drv_data->bank_nr; i++) {
|
||||
chip_data = &host_data->chips_data[i];
|
||||
stm32_chip_suspend(chip_data, chip_data->wake_active);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int stm32_exti_h_resume(struct device *dev)
|
||||
{
|
||||
struct stm32_exti_host_data *host_data = dev_get_drvdata(dev);
|
||||
struct stm32_exti_chip_data *chip_data;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < host_data->drv_data->bank_nr; i++) {
|
||||
chip_data = &host_data->chips_data[i];
|
||||
stm32_chip_resume(chip_data, chip_data->mask_cache);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int stm32_exti_h_retrigger(struct irq_data *d)
|
||||
{
|
||||
struct stm32_exti_chip_data *chip_data = irq_data_get_irq_chip_data(d);
|
||||
const struct stm32_exti_bank *stm32_bank = chip_data->reg_bank;
|
||||
void __iomem *base = chip_data->host_data->base;
|
||||
u32 mask = BIT(d->hwirq % IRQS_PER_BANK);
|
||||
|
||||
writel_relaxed(mask, base + stm32_bank->swier_ofst);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct irq_chip stm32_exti_h_chip = {
|
||||
.name = "stm32-exti-h",
|
||||
.irq_eoi = stm32_exti_h_eoi,
|
||||
.irq_mask = stm32_exti_h_mask,
|
||||
.irq_unmask = stm32_exti_h_unmask,
|
||||
.irq_retrigger = stm32_exti_h_retrigger,
|
||||
.irq_set_type = stm32_exti_h_set_type,
|
||||
.irq_set_wake = stm32_exti_h_set_wake,
|
||||
.flags = IRQCHIP_MASK_ON_SUSPEND,
|
||||
.irq_set_affinity = IS_ENABLED(CONFIG_SMP) ? stm32_exti_h_set_affinity : NULL,
|
||||
};
|
||||
|
||||
static struct irq_chip stm32_exti_h_chip_direct = {
|
||||
.name = "stm32-exti-h-direct",
|
||||
.irq_eoi = irq_chip_eoi_parent,
|
||||
.irq_ack = irq_chip_ack_parent,
|
||||
.irq_mask = stm32_exti_h_mask,
|
||||
.irq_unmask = stm32_exti_h_unmask,
|
||||
.irq_retrigger = irq_chip_retrigger_hierarchy,
|
||||
.irq_set_type = irq_chip_set_type_parent,
|
||||
.irq_set_wake = stm32_exti_h_set_wake,
|
||||
.flags = IRQCHIP_MASK_ON_SUSPEND,
|
||||
.irq_set_affinity = IS_ENABLED(CONFIG_SMP) ? irq_chip_set_affinity_parent : NULL,
|
||||
};
|
||||
|
||||
static int stm32_exti_h_domain_alloc(struct irq_domain *dm,
|
||||
unsigned int virq,
|
||||
unsigned int nr_irqs, void *data)
|
||||
{
|
||||
struct stm32_exti_host_data *host_data = dm->host_data;
|
||||
struct stm32_exti_chip_data *chip_data;
|
||||
u8 desc_irq;
|
||||
struct irq_fwspec *fwspec = data;
|
||||
struct irq_fwspec p_fwspec;
|
||||
irq_hw_number_t hwirq;
|
||||
int bank;
|
||||
u32 event_trg;
|
||||
struct irq_chip *chip;
|
||||
|
||||
hwirq = fwspec->param[0];
|
||||
if (hwirq >= host_data->drv_data->bank_nr * IRQS_PER_BANK)
|
||||
return -EINVAL;
|
||||
|
||||
bank = hwirq / IRQS_PER_BANK;
|
||||
chip_data = &host_data->chips_data[bank];
|
||||
|
||||
/* Check if event is reserved (Secure) */
|
||||
if (chip_data->event_reserved & BIT(hwirq % IRQS_PER_BANK)) {
|
||||
dev_err(host_data->dev, "event %lu is reserved, secure\n", hwirq);
|
||||
return -EPERM;
|
||||
}
|
||||
|
||||
event_trg = readl_relaxed(host_data->base + chip_data->reg_bank->trg_ofst);
|
||||
chip = (event_trg & BIT(hwirq % IRQS_PER_BANK)) ?
|
||||
&stm32_exti_h_chip : &stm32_exti_h_chip_direct;
|
||||
|
||||
irq_domain_set_hwirq_and_chip(dm, virq, hwirq, chip, chip_data);
|
||||
|
||||
if (host_data->dt_has_irqs_desc) {
|
||||
struct of_phandle_args out_irq;
|
||||
int ret;
|
||||
|
||||
ret = of_irq_parse_one(host_data->dev->of_node, hwirq, &out_irq);
|
||||
if (ret)
|
||||
return ret;
|
||||
/* we only support one parent, so far */
|
||||
if (of_node_to_fwnode(out_irq.np) != dm->parent->fwnode)
|
||||
return -EINVAL;
|
||||
|
||||
of_phandle_args_to_fwspec(out_irq.np, out_irq.args,
|
||||
out_irq.args_count, &p_fwspec);
|
||||
|
||||
return irq_domain_alloc_irqs_parent(dm, virq, 1, &p_fwspec);
|
||||
}
|
||||
|
||||
if (!host_data->drv_data->desc_irqs)
|
||||
return -EINVAL;
|
||||
|
||||
desc_irq = host_data->drv_data->desc_irqs[hwirq];
|
||||
if (desc_irq != EXTI_INVALID_IRQ) {
|
||||
p_fwspec.fwnode = dm->parent->fwnode;
|
||||
p_fwspec.param_count = 3;
|
||||
p_fwspec.param[0] = GIC_SPI;
|
||||
p_fwspec.param[1] = desc_irq;
|
||||
p_fwspec.param[2] = IRQ_TYPE_LEVEL_HIGH;
|
||||
|
||||
return irq_domain_alloc_irqs_parent(dm, virq, 1, &p_fwspec);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct
|
||||
stm32_exti_host_data *stm32_exti_host_init(const struct stm32_exti_drv_data *dd,
|
||||
struct device_node *node)
|
||||
@@ -822,19 +319,12 @@ stm32_exti_chip_data *stm32_exti_chip_init(struct stm32_exti_host_data *h_data,
|
||||
chip_data->host_data = h_data;
|
||||
chip_data->reg_bank = stm32_bank;
|
||||
|
||||
raw_spin_lock_init(&chip_data->rlock);
|
||||
|
||||
/*
|
||||
* This IP has no reset, so after hot reboot we should
|
||||
* clear registers to avoid residue
|
||||
*/
|
||||
writel_relaxed(0, base + stm32_bank->imr_ofst);
|
||||
if (stm32_bank->emr_ofst != UNDEF_REG)
|
||||
writel_relaxed(0, base + stm32_bank->emr_ofst);
|
||||
|
||||
/* reserve Secure events */
|
||||
if (stm32_bank->seccfgr_ofst != UNDEF_REG)
|
||||
chip_data->event_reserved = readl_relaxed(base + stm32_bank->seccfgr_ofst);
|
||||
writel_relaxed(0, base + stm32_bank->emr_ofst);
|
||||
|
||||
pr_info("%pOF: bank%d\n", node, bank_idx);
|
||||
|
||||
@@ -914,158 +404,6 @@ out_unmap:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct irq_domain_ops stm32_exti_h_domain_ops = {
|
||||
.alloc = stm32_exti_h_domain_alloc,
|
||||
.free = irq_domain_free_irqs_common,
|
||||
.xlate = irq_domain_xlate_twocell,
|
||||
};
|
||||
|
||||
static void stm32_exti_check_rif(struct stm32_exti_host_data *host_data)
|
||||
{
|
||||
unsigned int bank, i, event;
|
||||
u32 cid, cidcfgr, hwcfgr1;
|
||||
|
||||
/* quit on CID not supported */
|
||||
hwcfgr1 = readl_relaxed(host_data->base + EXTI_HWCFGR1);
|
||||
if ((hwcfgr1 & EXTI_HWCFGR1_CIDWIDTH_MASK) == 0)
|
||||
return;
|
||||
|
||||
for (bank = 0; bank < host_data->drv_data->bank_nr; bank++) {
|
||||
for (i = 0; i < IRQS_PER_BANK; i++) {
|
||||
event = bank * IRQS_PER_BANK + i;
|
||||
cidcfgr = readl_relaxed(host_data->base + EXTI_EnCIDCFGR(event));
|
||||
cid = (cidcfgr & EXTI_CIDCFGR_CID_MASK) >> EXTI_CIDCFGR_CID_SHIFT;
|
||||
if ((cidcfgr & EXTI_CIDCFGR_CFEN_MASK) && cid != EXTI_CID1)
|
||||
host_data->chips_data[bank].event_reserved |= BIT(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void stm32_exti_remove_irq(void *data)
|
||||
{
|
||||
struct irq_domain *domain = data;
|
||||
|
||||
irq_domain_remove(domain);
|
||||
}
|
||||
|
||||
static int stm32_exti_probe(struct platform_device *pdev)
|
||||
{
|
||||
int ret, i;
|
||||
struct device *dev = &pdev->dev;
|
||||
struct device_node *np = dev->of_node;
|
||||
struct irq_domain *parent_domain, *domain;
|
||||
struct stm32_exti_host_data *host_data;
|
||||
const struct stm32_exti_drv_data *drv_data;
|
||||
|
||||
host_data = devm_kzalloc(dev, sizeof(*host_data), GFP_KERNEL);
|
||||
if (!host_data)
|
||||
return -ENOMEM;
|
||||
|
||||
dev_set_drvdata(dev, host_data);
|
||||
host_data->dev = dev;
|
||||
|
||||
/* check for optional hwspinlock which may be not available yet */
|
||||
ret = of_hwspin_lock_get_id(np, 0);
|
||||
if (ret == -EPROBE_DEFER)
|
||||
/* hwspinlock framework not yet ready */
|
||||
return ret;
|
||||
|
||||
if (ret >= 0) {
|
||||
host_data->hwlock = devm_hwspin_lock_request_specific(dev, ret);
|
||||
if (!host_data->hwlock) {
|
||||
dev_err(dev, "Failed to request hwspinlock\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
} else if (ret != -ENOENT) {
|
||||
/* note: ENOENT is a valid case (means 'no hwspinlock') */
|
||||
dev_err(dev, "Failed to get hwspinlock\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* initialize host_data */
|
||||
drv_data = of_device_get_match_data(dev);
|
||||
if (!drv_data) {
|
||||
dev_err(dev, "no of match data\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
host_data->drv_data = drv_data;
|
||||
|
||||
host_data->chips_data = devm_kcalloc(dev, drv_data->bank_nr,
|
||||
sizeof(*host_data->chips_data),
|
||||
GFP_KERNEL);
|
||||
if (!host_data->chips_data)
|
||||
return -ENOMEM;
|
||||
|
||||
host_data->base = devm_platform_ioremap_resource(pdev, 0);
|
||||
if (IS_ERR(host_data->base))
|
||||
return PTR_ERR(host_data->base);
|
||||
|
||||
for (i = 0; i < drv_data->bank_nr; i++)
|
||||
stm32_exti_chip_init(host_data, i, np);
|
||||
|
||||
stm32_exti_check_rif(host_data);
|
||||
|
||||
parent_domain = irq_find_host(of_irq_find_parent(np));
|
||||
if (!parent_domain) {
|
||||
dev_err(dev, "GIC interrupt-parent not found\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
domain = irq_domain_add_hierarchy(parent_domain, 0,
|
||||
drv_data->bank_nr * IRQS_PER_BANK,
|
||||
np, &stm32_exti_h_domain_ops,
|
||||
host_data);
|
||||
|
||||
if (!domain) {
|
||||
dev_err(dev, "Could not register exti domain\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
ret = devm_add_action_or_reset(dev, stm32_exti_remove_irq, domain);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (of_property_read_bool(np, "interrupts-extended"))
|
||||
host_data->dt_has_irqs_desc = true;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* platform driver only for MP1 */
|
||||
static const struct of_device_id stm32_exti_ids[] = {
|
||||
{ .compatible = "st,stm32mp1-exti", .data = &stm32mp1_drv_data},
|
||||
{ .compatible = "st,stm32mp13-exti", .data = &stm32mp13_drv_data},
|
||||
{},
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, stm32_exti_ids);
|
||||
|
||||
static const struct dev_pm_ops stm32_exti_dev_pm_ops = {
|
||||
NOIRQ_SYSTEM_SLEEP_PM_OPS(stm32_exti_h_suspend, stm32_exti_h_resume)
|
||||
};
|
||||
|
||||
static struct platform_driver stm32_exti_driver = {
|
||||
.probe = stm32_exti_probe,
|
||||
.driver = {
|
||||
.name = "stm32_exti",
|
||||
.of_match_table = stm32_exti_ids,
|
||||
.pm = &stm32_exti_dev_pm_ops,
|
||||
},
|
||||
};
|
||||
|
||||
static int __init stm32_exti_arch_init(void)
|
||||
{
|
||||
return platform_driver_register(&stm32_exti_driver);
|
||||
}
|
||||
|
||||
static void __exit stm32_exti_arch_exit(void)
|
||||
{
|
||||
return platform_driver_unregister(&stm32_exti_driver);
|
||||
}
|
||||
|
||||
arch_initcall(stm32_exti_arch_init);
|
||||
module_exit(stm32_exti_arch_exit);
|
||||
|
||||
/* no platform driver for F4 and H7 */
|
||||
static int __init stm32f4_exti_of_init(struct device_node *np,
|
||||
struct device_node *parent)
|
||||
{
|
||||
|
||||
@@ -0,0 +1,729 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Copyright (C) Maxime Coquelin 2015
|
||||
* Copyright (C) STMicroelectronics 2017-2024
|
||||
* Author: Maxime Coquelin <mcoquelin.stm32@gmail.com>
|
||||
*/
|
||||
|
||||
#include <linux/bitops.h>
|
||||
#include <linux/hwspinlock.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/irq.h>
|
||||
#include <linux/irqchip.h>
|
||||
#include <linux/irqdomain.h>
|
||||
#include <linux/mod_devicetable.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of_address.h>
|
||||
#include <linux/of_irq.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/pm.h>
|
||||
|
||||
#include <dt-bindings/interrupt-controller/arm-gic.h>
|
||||
|
||||
#define IRQS_PER_BANK 32
|
||||
|
||||
#define HWSPNLCK_TIMEOUT 1000 /* usec */
|
||||
|
||||
#define EXTI_EnCIDCFGR(n) (0x180 + (n) * 4)
|
||||
#define EXTI_HWCFGR1 0x3f0
|
||||
|
||||
/* Register: EXTI_EnCIDCFGR(n) */
|
||||
#define EXTI_CIDCFGR_CFEN_MASK BIT(0)
|
||||
#define EXTI_CIDCFGR_CID_MASK GENMASK(6, 4)
|
||||
#define EXTI_CIDCFGR_CID_SHIFT 4
|
||||
|
||||
/* Register: EXTI_HWCFGR1 */
|
||||
#define EXTI_HWCFGR1_CIDWIDTH_MASK GENMASK(27, 24)
|
||||
|
||||
#define EXTI_CID1 1
|
||||
|
||||
struct stm32mp_exti_bank {
|
||||
u32 imr_ofst;
|
||||
u32 rtsr_ofst;
|
||||
u32 ftsr_ofst;
|
||||
u32 swier_ofst;
|
||||
u32 rpr_ofst;
|
||||
u32 fpr_ofst;
|
||||
u32 trg_ofst;
|
||||
u32 seccfgr_ofst;
|
||||
};
|
||||
|
||||
struct stm32mp_exti_drv_data {
|
||||
const struct stm32mp_exti_bank **exti_banks;
|
||||
const u8 *desc_irqs;
|
||||
u32 bank_nr;
|
||||
};
|
||||
|
||||
struct stm32mp_exti_chip_data {
|
||||
struct stm32mp_exti_host_data *host_data;
|
||||
const struct stm32mp_exti_bank *reg_bank;
|
||||
struct raw_spinlock rlock;
|
||||
u32 wake_active;
|
||||
u32 mask_cache;
|
||||
u32 rtsr_cache;
|
||||
u32 ftsr_cache;
|
||||
u32 event_reserved;
|
||||
};
|
||||
|
||||
struct stm32mp_exti_host_data {
|
||||
void __iomem *base;
|
||||
struct device *dev;
|
||||
struct stm32mp_exti_chip_data *chips_data;
|
||||
const struct stm32mp_exti_drv_data *drv_data;
|
||||
struct hwspinlock *hwlock;
|
||||
/* skip internal desc_irqs array and get it from DT */
|
||||
bool dt_has_irqs_desc;
|
||||
};
|
||||
|
||||
static const struct stm32mp_exti_bank stm32mp_exti_b1 = {
|
||||
.imr_ofst = 0x80,
|
||||
.rtsr_ofst = 0x00,
|
||||
.ftsr_ofst = 0x04,
|
||||
.swier_ofst = 0x08,
|
||||
.rpr_ofst = 0x0C,
|
||||
.fpr_ofst = 0x10,
|
||||
.trg_ofst = 0x3EC,
|
||||
.seccfgr_ofst = 0x14,
|
||||
};
|
||||
|
||||
static const struct stm32mp_exti_bank stm32mp_exti_b2 = {
|
||||
.imr_ofst = 0x90,
|
||||
.rtsr_ofst = 0x20,
|
||||
.ftsr_ofst = 0x24,
|
||||
.swier_ofst = 0x28,
|
||||
.rpr_ofst = 0x2C,
|
||||
.fpr_ofst = 0x30,
|
||||
.trg_ofst = 0x3E8,
|
||||
.seccfgr_ofst = 0x34,
|
||||
};
|
||||
|
||||
static const struct stm32mp_exti_bank stm32mp_exti_b3 = {
|
||||
.imr_ofst = 0xA0,
|
||||
.rtsr_ofst = 0x40,
|
||||
.ftsr_ofst = 0x44,
|
||||
.swier_ofst = 0x48,
|
||||
.rpr_ofst = 0x4C,
|
||||
.fpr_ofst = 0x50,
|
||||
.trg_ofst = 0x3E4,
|
||||
.seccfgr_ofst = 0x54,
|
||||
};
|
||||
|
||||
static const struct stm32mp_exti_bank *stm32mp_exti_banks[] = {
|
||||
&stm32mp_exti_b1,
|
||||
&stm32mp_exti_b2,
|
||||
&stm32mp_exti_b3,
|
||||
};
|
||||
|
||||
static struct irq_chip stm32mp_exti_chip;
|
||||
static struct irq_chip stm32mp_exti_chip_direct;
|
||||
|
||||
#define EXTI_INVALID_IRQ U8_MAX
|
||||
#define STM32MP_DESC_IRQ_SIZE (ARRAY_SIZE(stm32mp_exti_banks) * IRQS_PER_BANK)
|
||||
|
||||
/*
|
||||
* Use some intentionally tricky logic here to initialize the whole array to
|
||||
* EXTI_INVALID_IRQ, but then override certain fields, requiring us to indicate
|
||||
* that we "know" that there are overrides in this structure, and we'll need to
|
||||
* disable that warning from W=1 builds.
|
||||
*/
|
||||
__diag_push();
|
||||
__diag_ignore_all("-Woverride-init",
|
||||
"logic to initialize all and then override some is OK");
|
||||
|
||||
static const u8 stm32mp1_desc_irq[] = {
|
||||
/* default value */
|
||||
[0 ... (STM32MP_DESC_IRQ_SIZE - 1)] = EXTI_INVALID_IRQ,
|
||||
|
||||
[0] = 6,
|
||||
[1] = 7,
|
||||
[2] = 8,
|
||||
[3] = 9,
|
||||
[4] = 10,
|
||||
[5] = 23,
|
||||
[6] = 64,
|
||||
[7] = 65,
|
||||
[8] = 66,
|
||||
[9] = 67,
|
||||
[10] = 40,
|
||||
[11] = 42,
|
||||
[12] = 76,
|
||||
[13] = 77,
|
||||
[14] = 121,
|
||||
[15] = 127,
|
||||
[16] = 1,
|
||||
[19] = 3,
|
||||
[21] = 31,
|
||||
[22] = 33,
|
||||
[23] = 72,
|
||||
[24] = 95,
|
||||
[25] = 107,
|
||||
[26] = 37,
|
||||
[27] = 38,
|
||||
[28] = 39,
|
||||
[29] = 71,
|
||||
[30] = 52,
|
||||
[31] = 53,
|
||||
[32] = 82,
|
||||
[33] = 83,
|
||||
[46] = 151,
|
||||
[47] = 93,
|
||||
[48] = 138,
|
||||
[50] = 139,
|
||||
[52] = 140,
|
||||
[53] = 141,
|
||||
[54] = 135,
|
||||
[61] = 100,
|
||||
[65] = 144,
|
||||
[68] = 143,
|
||||
[70] = 62,
|
||||
[73] = 129,
|
||||
};
|
||||
|
||||
static const u8 stm32mp13_desc_irq[] = {
|
||||
/* default value */
|
||||
[0 ... (STM32MP_DESC_IRQ_SIZE - 1)] = EXTI_INVALID_IRQ,
|
||||
|
||||
[0] = 6,
|
||||
[1] = 7,
|
||||
[2] = 8,
|
||||
[3] = 9,
|
||||
[4] = 10,
|
||||
[5] = 24,
|
||||
[6] = 65,
|
||||
[7] = 66,
|
||||
[8] = 67,
|
||||
[9] = 68,
|
||||
[10] = 41,
|
||||
[11] = 43,
|
||||
[12] = 77,
|
||||
[13] = 78,
|
||||
[14] = 106,
|
||||
[15] = 109,
|
||||
[16] = 1,
|
||||
[19] = 3,
|
||||
[21] = 32,
|
||||
[22] = 34,
|
||||
[23] = 73,
|
||||
[24] = 93,
|
||||
[25] = 114,
|
||||
[26] = 38,
|
||||
[27] = 39,
|
||||
[28] = 40,
|
||||
[29] = 72,
|
||||
[30] = 53,
|
||||
[31] = 54,
|
||||
[32] = 83,
|
||||
[33] = 84,
|
||||
[44] = 96,
|
||||
[47] = 92,
|
||||
[48] = 116,
|
||||
[50] = 117,
|
||||
[52] = 118,
|
||||
[53] = 119,
|
||||
[68] = 63,
|
||||
[70] = 98,
|
||||
};
|
||||
|
||||
__diag_pop();
|
||||
|
||||
static const struct stm32mp_exti_drv_data stm32mp1_drv_data = {
|
||||
.exti_banks = stm32mp_exti_banks,
|
||||
.bank_nr = ARRAY_SIZE(stm32mp_exti_banks),
|
||||
.desc_irqs = stm32mp1_desc_irq,
|
||||
};
|
||||
|
||||
static const struct stm32mp_exti_drv_data stm32mp13_drv_data = {
|
||||
.exti_banks = stm32mp_exti_banks,
|
||||
.bank_nr = ARRAY_SIZE(stm32mp_exti_banks),
|
||||
.desc_irqs = stm32mp13_desc_irq,
|
||||
};
|
||||
|
||||
static int stm32mp_exti_convert_type(struct irq_data *d, unsigned int type, u32 *rtsr, u32 *ftsr)
|
||||
{
|
||||
u32 mask = BIT(d->hwirq % IRQS_PER_BANK);
|
||||
|
||||
switch (type) {
|
||||
case IRQ_TYPE_EDGE_RISING:
|
||||
*rtsr |= mask;
|
||||
*ftsr &= ~mask;
|
||||
break;
|
||||
case IRQ_TYPE_EDGE_FALLING:
|
||||
*rtsr &= ~mask;
|
||||
*ftsr |= mask;
|
||||
break;
|
||||
case IRQ_TYPE_EDGE_BOTH:
|
||||
*rtsr |= mask;
|
||||
*ftsr |= mask;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void stm32mp_chip_suspend(struct stm32mp_exti_chip_data *chip_data, u32 wake_active)
|
||||
{
|
||||
const struct stm32mp_exti_bank *bank = chip_data->reg_bank;
|
||||
void __iomem *base = chip_data->host_data->base;
|
||||
|
||||
/* save rtsr, ftsr registers */
|
||||
chip_data->rtsr_cache = readl_relaxed(base + bank->rtsr_ofst);
|
||||
chip_data->ftsr_cache = readl_relaxed(base + bank->ftsr_ofst);
|
||||
|
||||
writel_relaxed(wake_active, base + bank->imr_ofst);
|
||||
}
|
||||
|
||||
static void stm32mp_chip_resume(struct stm32mp_exti_chip_data *chip_data, u32 mask_cache)
|
||||
{
|
||||
const struct stm32mp_exti_bank *bank = chip_data->reg_bank;
|
||||
void __iomem *base = chip_data->host_data->base;
|
||||
|
||||
/* restore rtsr, ftsr, registers */
|
||||
writel_relaxed(chip_data->rtsr_cache, base + bank->rtsr_ofst);
|
||||
writel_relaxed(chip_data->ftsr_cache, base + bank->ftsr_ofst);
|
||||
|
||||
writel_relaxed(mask_cache, base + bank->imr_ofst);
|
||||
}
|
||||
|
||||
/* directly set the target bit without reading first. */
|
||||
static inline void stm32mp_exti_write_bit(struct irq_data *d, u32 reg)
|
||||
{
|
||||
struct stm32mp_exti_chip_data *chip_data = irq_data_get_irq_chip_data(d);
|
||||
void __iomem *base = chip_data->host_data->base;
|
||||
u32 val = BIT(d->hwirq % IRQS_PER_BANK);
|
||||
|
||||
writel_relaxed(val, base + reg);
|
||||
}
|
||||
|
||||
static inline u32 stm32mp_exti_set_bit(struct irq_data *d, u32 reg)
|
||||
{
|
||||
struct stm32mp_exti_chip_data *chip_data = irq_data_get_irq_chip_data(d);
|
||||
void __iomem *base = chip_data->host_data->base;
|
||||
u32 val;
|
||||
|
||||
val = readl_relaxed(base + reg);
|
||||
val |= BIT(d->hwirq % IRQS_PER_BANK);
|
||||
writel_relaxed(val, base + reg);
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
static inline u32 stm32mp_exti_clr_bit(struct irq_data *d, u32 reg)
|
||||
{
|
||||
struct stm32mp_exti_chip_data *chip_data = irq_data_get_irq_chip_data(d);
|
||||
void __iomem *base = chip_data->host_data->base;
|
||||
u32 val;
|
||||
|
||||
val = readl_relaxed(base + reg);
|
||||
val &= ~BIT(d->hwirq % IRQS_PER_BANK);
|
||||
writel_relaxed(val, base + reg);
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
static void stm32mp_exti_eoi(struct irq_data *d)
|
||||
{
|
||||
struct stm32mp_exti_chip_data *chip_data = irq_data_get_irq_chip_data(d);
|
||||
const struct stm32mp_exti_bank *bank = chip_data->reg_bank;
|
||||
|
||||
raw_spin_lock(&chip_data->rlock);
|
||||
|
||||
stm32mp_exti_write_bit(d, bank->rpr_ofst);
|
||||
stm32mp_exti_write_bit(d, bank->fpr_ofst);
|
||||
|
||||
raw_spin_unlock(&chip_data->rlock);
|
||||
|
||||
if (d->parent_data->chip)
|
||||
irq_chip_eoi_parent(d);
|
||||
}
|
||||
|
||||
static void stm32mp_exti_mask(struct irq_data *d)
|
||||
{
|
||||
struct stm32mp_exti_chip_data *chip_data = irq_data_get_irq_chip_data(d);
|
||||
const struct stm32mp_exti_bank *bank = chip_data->reg_bank;
|
||||
|
||||
raw_spin_lock(&chip_data->rlock);
|
||||
chip_data->mask_cache = stm32mp_exti_clr_bit(d, bank->imr_ofst);
|
||||
raw_spin_unlock(&chip_data->rlock);
|
||||
|
||||
if (d->parent_data->chip)
|
||||
irq_chip_mask_parent(d);
|
||||
}
|
||||
|
||||
static void stm32mp_exti_unmask(struct irq_data *d)
|
||||
{
|
||||
struct stm32mp_exti_chip_data *chip_data = irq_data_get_irq_chip_data(d);
|
||||
const struct stm32mp_exti_bank *bank = chip_data->reg_bank;
|
||||
|
||||
raw_spin_lock(&chip_data->rlock);
|
||||
chip_data->mask_cache = stm32mp_exti_set_bit(d, bank->imr_ofst);
|
||||
raw_spin_unlock(&chip_data->rlock);
|
||||
|
||||
if (d->parent_data->chip)
|
||||
irq_chip_unmask_parent(d);
|
||||
}
|
||||
|
||||
static int stm32mp_exti_set_type(struct irq_data *d, unsigned int type)
|
||||
{
|
||||
struct stm32mp_exti_chip_data *chip_data = irq_data_get_irq_chip_data(d);
|
||||
const struct stm32mp_exti_bank *bank = chip_data->reg_bank;
|
||||
struct hwspinlock *hwlock = chip_data->host_data->hwlock;
|
||||
void __iomem *base = chip_data->host_data->base;
|
||||
u32 rtsr, ftsr;
|
||||
int err;
|
||||
|
||||
raw_spin_lock(&chip_data->rlock);
|
||||
|
||||
if (hwlock) {
|
||||
err = hwspin_lock_timeout_in_atomic(hwlock, HWSPNLCK_TIMEOUT);
|
||||
if (err) {
|
||||
pr_err("%s can't get hwspinlock (%d)\n", __func__, err);
|
||||
goto unlock;
|
||||
}
|
||||
}
|
||||
|
||||
rtsr = readl_relaxed(base + bank->rtsr_ofst);
|
||||
ftsr = readl_relaxed(base + bank->ftsr_ofst);
|
||||
|
||||
err = stm32mp_exti_convert_type(d, type, &rtsr, &ftsr);
|
||||
if (!err) {
|
||||
writel_relaxed(rtsr, base + bank->rtsr_ofst);
|
||||
writel_relaxed(ftsr, base + bank->ftsr_ofst);
|
||||
}
|
||||
|
||||
if (hwlock)
|
||||
hwspin_unlock_in_atomic(hwlock);
|
||||
unlock:
|
||||
raw_spin_unlock(&chip_data->rlock);
|
||||
return err;
|
||||
}
|
||||
|
||||
static int stm32mp_exti_set_wake(struct irq_data *d, unsigned int on)
|
||||
{
|
||||
struct stm32mp_exti_chip_data *chip_data = irq_data_get_irq_chip_data(d);
|
||||
u32 mask = BIT(d->hwirq % IRQS_PER_BANK);
|
||||
|
||||
raw_spin_lock(&chip_data->rlock);
|
||||
|
||||
if (on)
|
||||
chip_data->wake_active |= mask;
|
||||
else
|
||||
chip_data->wake_active &= ~mask;
|
||||
|
||||
raw_spin_unlock(&chip_data->rlock);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int stm32mp_exti_set_affinity(struct irq_data *d, const struct cpumask *dest, bool force)
|
||||
{
|
||||
if (d->parent_data->chip)
|
||||
return irq_chip_set_affinity_parent(d, dest, force);
|
||||
|
||||
return IRQ_SET_MASK_OK_DONE;
|
||||
}
|
||||
|
||||
static int stm32mp_exti_suspend(struct device *dev)
|
||||
{
|
||||
struct stm32mp_exti_host_data *host_data = dev_get_drvdata(dev);
|
||||
struct stm32mp_exti_chip_data *chip_data;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < host_data->drv_data->bank_nr; i++) {
|
||||
chip_data = &host_data->chips_data[i];
|
||||
stm32mp_chip_suspend(chip_data, chip_data->wake_active);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int stm32mp_exti_resume(struct device *dev)
|
||||
{
|
||||
struct stm32mp_exti_host_data *host_data = dev_get_drvdata(dev);
|
||||
struct stm32mp_exti_chip_data *chip_data;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < host_data->drv_data->bank_nr; i++) {
|
||||
chip_data = &host_data->chips_data[i];
|
||||
stm32mp_chip_resume(chip_data, chip_data->mask_cache);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int stm32mp_exti_retrigger(struct irq_data *d)
|
||||
{
|
||||
struct stm32mp_exti_chip_data *chip_data = irq_data_get_irq_chip_data(d);
|
||||
const struct stm32mp_exti_bank *bank = chip_data->reg_bank;
|
||||
void __iomem *base = chip_data->host_data->base;
|
||||
u32 mask = BIT(d->hwirq % IRQS_PER_BANK);
|
||||
|
||||
writel_relaxed(mask, base + bank->swier_ofst);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct irq_chip stm32mp_exti_chip = {
|
||||
.name = "stm32mp-exti",
|
||||
.irq_eoi = stm32mp_exti_eoi,
|
||||
.irq_mask = stm32mp_exti_mask,
|
||||
.irq_unmask = stm32mp_exti_unmask,
|
||||
.irq_retrigger = stm32mp_exti_retrigger,
|
||||
.irq_set_type = stm32mp_exti_set_type,
|
||||
.irq_set_wake = stm32mp_exti_set_wake,
|
||||
.flags = IRQCHIP_MASK_ON_SUSPEND,
|
||||
.irq_set_affinity = IS_ENABLED(CONFIG_SMP) ? stm32mp_exti_set_affinity : NULL,
|
||||
};
|
||||
|
||||
static struct irq_chip stm32mp_exti_chip_direct = {
|
||||
.name = "stm32mp-exti-direct",
|
||||
.irq_eoi = irq_chip_eoi_parent,
|
||||
.irq_ack = irq_chip_ack_parent,
|
||||
.irq_mask = stm32mp_exti_mask,
|
||||
.irq_unmask = stm32mp_exti_unmask,
|
||||
.irq_retrigger = irq_chip_retrigger_hierarchy,
|
||||
.irq_set_type = irq_chip_set_type_parent,
|
||||
.irq_set_wake = stm32mp_exti_set_wake,
|
||||
.flags = IRQCHIP_MASK_ON_SUSPEND,
|
||||
.irq_set_affinity = IS_ENABLED(CONFIG_SMP) ? irq_chip_set_affinity_parent : NULL,
|
||||
};
|
||||
|
||||
static int stm32mp_exti_domain_alloc(struct irq_domain *dm,
|
||||
unsigned int virq,
|
||||
unsigned int nr_irqs, void *data)
|
||||
{
|
||||
struct stm32mp_exti_host_data *host_data = dm->host_data;
|
||||
struct stm32mp_exti_chip_data *chip_data;
|
||||
struct irq_fwspec *fwspec = data;
|
||||
struct irq_fwspec p_fwspec;
|
||||
irq_hw_number_t hwirq;
|
||||
struct irq_chip *chip;
|
||||
u32 event_trg;
|
||||
u8 desc_irq;
|
||||
int bank;
|
||||
|
||||
hwirq = fwspec->param[0];
|
||||
if (hwirq >= host_data->drv_data->bank_nr * IRQS_PER_BANK)
|
||||
return -EINVAL;
|
||||
|
||||
bank = hwirq / IRQS_PER_BANK;
|
||||
chip_data = &host_data->chips_data[bank];
|
||||
|
||||
/* Check if event is reserved (Secure) */
|
||||
if (chip_data->event_reserved & BIT(hwirq % IRQS_PER_BANK)) {
|
||||
dev_err(host_data->dev, "event %lu is reserved, secure\n", hwirq);
|
||||
return -EPERM;
|
||||
}
|
||||
|
||||
event_trg = readl_relaxed(host_data->base + chip_data->reg_bank->trg_ofst);
|
||||
chip = (event_trg & BIT(hwirq % IRQS_PER_BANK)) ?
|
||||
&stm32mp_exti_chip : &stm32mp_exti_chip_direct;
|
||||
|
||||
irq_domain_set_hwirq_and_chip(dm, virq, hwirq, chip, chip_data);
|
||||
|
||||
if (host_data->dt_has_irqs_desc) {
|
||||
struct of_phandle_args out_irq;
|
||||
int ret;
|
||||
|
||||
ret = of_irq_parse_one(host_data->dev->of_node, hwirq, &out_irq);
|
||||
if (ret)
|
||||
return ret;
|
||||
/* we only support one parent, so far */
|
||||
if (of_node_to_fwnode(out_irq.np) != dm->parent->fwnode)
|
||||
return -EINVAL;
|
||||
|
||||
of_phandle_args_to_fwspec(out_irq.np, out_irq.args,
|
||||
out_irq.args_count, &p_fwspec);
|
||||
|
||||
return irq_domain_alloc_irqs_parent(dm, virq, 1, &p_fwspec);
|
||||
}
|
||||
|
||||
if (!host_data->drv_data->desc_irqs)
|
||||
return -EINVAL;
|
||||
|
||||
desc_irq = host_data->drv_data->desc_irqs[hwirq];
|
||||
if (desc_irq != EXTI_INVALID_IRQ) {
|
||||
p_fwspec.fwnode = dm->parent->fwnode;
|
||||
p_fwspec.param_count = 3;
|
||||
p_fwspec.param[0] = GIC_SPI;
|
||||
p_fwspec.param[1] = desc_irq;
|
||||
p_fwspec.param[2] = IRQ_TYPE_LEVEL_HIGH;
|
||||
|
||||
return irq_domain_alloc_irqs_parent(dm, virq, 1, &p_fwspec);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct stm32mp_exti_chip_data *stm32mp_exti_chip_init(struct stm32mp_exti_host_data *h_data,
|
||||
u32 bank_idx, struct device_node *node)
|
||||
{
|
||||
struct stm32mp_exti_chip_data *chip_data;
|
||||
const struct stm32mp_exti_bank *bank;
|
||||
void __iomem *base = h_data->base;
|
||||
|
||||
bank = h_data->drv_data->exti_banks[bank_idx];
|
||||
chip_data = &h_data->chips_data[bank_idx];
|
||||
chip_data->host_data = h_data;
|
||||
chip_data->reg_bank = bank;
|
||||
|
||||
raw_spin_lock_init(&chip_data->rlock);
|
||||
|
||||
/*
|
||||
* This IP has no reset, so after hot reboot we should
|
||||
* clear registers to avoid residue
|
||||
*/
|
||||
writel_relaxed(0, base + bank->imr_ofst);
|
||||
|
||||
/* reserve Secure events */
|
||||
chip_data->event_reserved = readl_relaxed(base + bank->seccfgr_ofst);
|
||||
|
||||
pr_info("%pOF: bank%d\n", node, bank_idx);
|
||||
|
||||
return chip_data;
|
||||
}
|
||||
|
||||
static const struct irq_domain_ops stm32mp_exti_domain_ops = {
|
||||
.alloc = stm32mp_exti_domain_alloc,
|
||||
.free = irq_domain_free_irqs_common,
|
||||
.xlate = irq_domain_xlate_twocell,
|
||||
};
|
||||
|
||||
static void stm32mp_exti_check_rif(struct stm32mp_exti_host_data *host_data)
|
||||
{
|
||||
unsigned int bank, i, event;
|
||||
u32 cid, cidcfgr, hwcfgr1;
|
||||
|
||||
/* quit on CID not supported */
|
||||
hwcfgr1 = readl_relaxed(host_data->base + EXTI_HWCFGR1);
|
||||
if ((hwcfgr1 & EXTI_HWCFGR1_CIDWIDTH_MASK) == 0)
|
||||
return;
|
||||
|
||||
for (bank = 0; bank < host_data->drv_data->bank_nr; bank++) {
|
||||
for (i = 0; i < IRQS_PER_BANK; i++) {
|
||||
event = bank * IRQS_PER_BANK + i;
|
||||
cidcfgr = readl_relaxed(host_data->base + EXTI_EnCIDCFGR(event));
|
||||
cid = (cidcfgr & EXTI_CIDCFGR_CID_MASK) >> EXTI_CIDCFGR_CID_SHIFT;
|
||||
if ((cidcfgr & EXTI_CIDCFGR_CFEN_MASK) && cid != EXTI_CID1)
|
||||
host_data->chips_data[bank].event_reserved |= BIT(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void stm32mp_exti_remove_irq(void *data)
|
||||
{
|
||||
struct irq_domain *domain = data;
|
||||
|
||||
irq_domain_remove(domain);
|
||||
}
|
||||
|
||||
static int stm32mp_exti_probe(struct platform_device *pdev)
|
||||
{
|
||||
const struct stm32mp_exti_drv_data *drv_data;
|
||||
struct irq_domain *parent_domain, *domain;
|
||||
struct stm32mp_exti_host_data *host_data;
|
||||
struct device *dev = &pdev->dev;
|
||||
struct device_node *np = dev->of_node;
|
||||
int ret, i;
|
||||
|
||||
host_data = devm_kzalloc(dev, sizeof(*host_data), GFP_KERNEL);
|
||||
if (!host_data)
|
||||
return -ENOMEM;
|
||||
|
||||
dev_set_drvdata(dev, host_data);
|
||||
host_data->dev = dev;
|
||||
|
||||
/* check for optional hwspinlock which may be not available yet */
|
||||
ret = of_hwspin_lock_get_id(np, 0);
|
||||
if (ret == -EPROBE_DEFER)
|
||||
/* hwspinlock framework not yet ready */
|
||||
return ret;
|
||||
|
||||
if (ret >= 0) {
|
||||
host_data->hwlock = devm_hwspin_lock_request_specific(dev, ret);
|
||||
if (!host_data->hwlock) {
|
||||
dev_err(dev, "Failed to request hwspinlock\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
} else if (ret != -ENOENT) {
|
||||
/* note: ENOENT is a valid case (means 'no hwspinlock') */
|
||||
dev_err(dev, "Failed to get hwspinlock\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* initialize host_data */
|
||||
drv_data = of_device_get_match_data(dev);
|
||||
if (!drv_data) {
|
||||
dev_err(dev, "no of match data\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
host_data->drv_data = drv_data;
|
||||
|
||||
host_data->chips_data = devm_kcalloc(dev, drv_data->bank_nr,
|
||||
sizeof(*host_data->chips_data),
|
||||
GFP_KERNEL);
|
||||
if (!host_data->chips_data)
|
||||
return -ENOMEM;
|
||||
|
||||
host_data->base = devm_platform_ioremap_resource(pdev, 0);
|
||||
if (IS_ERR(host_data->base))
|
||||
return PTR_ERR(host_data->base);
|
||||
|
||||
for (i = 0; i < drv_data->bank_nr; i++)
|
||||
stm32mp_exti_chip_init(host_data, i, np);
|
||||
|
||||
stm32mp_exti_check_rif(host_data);
|
||||
|
||||
parent_domain = irq_find_host(of_irq_find_parent(np));
|
||||
if (!parent_domain) {
|
||||
dev_err(dev, "GIC interrupt-parent not found\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
domain = irq_domain_add_hierarchy(parent_domain, 0,
|
||||
drv_data->bank_nr * IRQS_PER_BANK,
|
||||
np, &stm32mp_exti_domain_ops,
|
||||
host_data);
|
||||
|
||||
if (!domain) {
|
||||
dev_err(dev, "Could not register exti domain\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
ret = devm_add_action_or_reset(dev, stm32mp_exti_remove_irq, domain);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (of_property_read_bool(np, "interrupts-extended"))
|
||||
host_data->dt_has_irqs_desc = true;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct of_device_id stm32mp_exti_ids[] = {
|
||||
{ .compatible = "st,stm32mp1-exti", .data = &stm32mp1_drv_data},
|
||||
{ .compatible = "st,stm32mp13-exti", .data = &stm32mp13_drv_data},
|
||||
{},
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, stm32mp_exti_ids);
|
||||
|
||||
static const struct dev_pm_ops stm32mp_exti_dev_pm_ops = {
|
||||
NOIRQ_SYSTEM_SLEEP_PM_OPS(stm32mp_exti_suspend, stm32mp_exti_resume)
|
||||
};
|
||||
|
||||
static struct platform_driver stm32mp_exti_driver = {
|
||||
.probe = stm32mp_exti_probe,
|
||||
.driver = {
|
||||
.name = "stm32mp_exti",
|
||||
.of_match_table = stm32mp_exti_ids,
|
||||
.pm = &stm32mp_exti_dev_pm_ops,
|
||||
},
|
||||
};
|
||||
|
||||
module_platform_driver(stm32mp_exti_driver);
|
||||
|
||||
MODULE_AUTHOR("Maxime Coquelin <mcoquelin.stm32@gmail.com>");
|
||||
MODULE_DESCRIPTION("STM32MP EXTI driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
@@ -163,5 +163,6 @@ static struct platform_driver ts4800_ic_driver = {
|
||||
module_platform_driver(ts4800_ic_driver);
|
||||
|
||||
MODULE_AUTHOR("Damien Riegel <damien.riegel@savoirfairelinux.com>");
|
||||
MODULE_DESCRIPTION("Multiplexed-IRQs driver for TS-4800's FPGA");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
MODULE_ALIAS("platform:ts4800_irqc");
|
||||
|
||||
@@ -276,6 +276,14 @@ config SPRD_MBOX
|
||||
to send message between application processors and MCU. Say Y here if
|
||||
you want to build the Spreatrum mailbox controller driver.
|
||||
|
||||
config QCOM_CPUCP_MBOX
|
||||
tristate "Qualcomm Technologies, Inc. CPUCP mailbox driver"
|
||||
depends on (ARCH_QCOM || COMPILE_TEST) && 64BIT
|
||||
help
|
||||
Qualcomm Technologies, Inc. CPUSS Control Processor (CPUCP) mailbox
|
||||
controller driver enables communication between AP and CPUCP. Say
|
||||
Y here if you want to build this driver.
|
||||
|
||||
config QCOM_IPCC
|
||||
tristate "Qualcomm Technologies, Inc. IPCC driver"
|
||||
depends on ARCH_QCOM || COMPILE_TEST
|
||||
|
||||
@@ -61,4 +61,6 @@ obj-$(CONFIG_SUN6I_MSGBOX) += sun6i-msgbox.o
|
||||
|
||||
obj-$(CONFIG_SPRD_MBOX) += sprd-mailbox.o
|
||||
|
||||
obj-$(CONFIG_QCOM_CPUCP_MBOX) += qcom-cpucp-mbox.o
|
||||
|
||||
obj-$(CONFIG_QCOM_IPCC) += qcom-ipcc.o
|
||||
|
||||
@@ -158,10 +158,6 @@ enum pdc_hw {
|
||||
PDC_HW /* PDC/MDE hardware (i.e. Northstar 2, Pegasus) */
|
||||
};
|
||||
|
||||
struct pdc_dma_map {
|
||||
void *ctx; /* opaque context associated with frame */
|
||||
};
|
||||
|
||||
/* dma descriptor */
|
||||
struct dma64dd {
|
||||
u32 ctrl1; /* misc control bits */
|
||||
|
||||
@@ -225,6 +225,8 @@ static int imx_mu_generic_tx(struct imx_mu_priv *priv,
|
||||
void *data)
|
||||
{
|
||||
u32 *arg = data;
|
||||
u32 val;
|
||||
int ret;
|
||||
|
||||
switch (cp->type) {
|
||||
case IMX_MU_TYPE_TX:
|
||||
@@ -236,7 +238,13 @@ static int imx_mu_generic_tx(struct imx_mu_priv *priv,
|
||||
queue_work(system_bh_wq, &cp->txdb_work);
|
||||
break;
|
||||
case IMX_MU_TYPE_TXDB_V2:
|
||||
imx_mu_xcr_rmw(priv, IMX_MU_GCR, IMX_MU_xCR_GIRn(priv->dcfg->type, cp->idx), 0);
|
||||
imx_mu_write(priv, IMX_MU_xCR_GIRn(priv->dcfg->type, cp->idx),
|
||||
priv->dcfg->xCR[IMX_MU_GCR]);
|
||||
ret = readl_poll_timeout(priv->base + priv->dcfg->xCR[IMX_MU_GCR], val,
|
||||
!(val & IMX_MU_xCR_GIRn(priv->dcfg->type, cp->idx)),
|
||||
0, 1000);
|
||||
if (ret)
|
||||
dev_warn_ratelimited(priv->dev, "channel type: %d failure\n", cp->type);
|
||||
break;
|
||||
default:
|
||||
dev_warn_ratelimited(priv->dev, "Send data on wrong channel type: %d\n", cp->type);
|
||||
|
||||
@@ -22,7 +22,6 @@
|
||||
|
||||
#define CMDQ_OP_CODE_MASK (0xff << CMDQ_OP_CODE_SHIFT)
|
||||
#define CMDQ_NUM_CMD(t) (t->cmd_buf_size / CMDQ_INST_SIZE)
|
||||
#define CMDQ_GCE_NUM_MAX (2)
|
||||
|
||||
#define CMDQ_CURR_IRQ_STATUS 0x10
|
||||
#define CMDQ_SYNC_TOKEN_UPDATE 0x68
|
||||
@@ -81,7 +80,7 @@ struct cmdq {
|
||||
u32 irq_mask;
|
||||
const struct gce_plat *pdata;
|
||||
struct cmdq_thread *thread;
|
||||
struct clk_bulk_data clocks[CMDQ_GCE_NUM_MAX];
|
||||
struct clk_bulk_data *clocks;
|
||||
bool suspended;
|
||||
};
|
||||
|
||||
@@ -578,16 +577,64 @@ static struct mbox_chan *cmdq_xlate(struct mbox_controller *mbox,
|
||||
return &mbox->chans[ind];
|
||||
}
|
||||
|
||||
static int cmdq_get_clocks(struct device *dev, struct cmdq *cmdq)
|
||||
{
|
||||
static const char * const gce_name = "gce";
|
||||
struct device_node *node, *parent = dev->of_node->parent;
|
||||
struct clk_bulk_data *clks;
|
||||
|
||||
cmdq->clocks = devm_kcalloc(dev, cmdq->pdata->gce_num,
|
||||
sizeof(cmdq->clocks), GFP_KERNEL);
|
||||
if (!cmdq->clocks)
|
||||
return -ENOMEM;
|
||||
|
||||
if (cmdq->pdata->gce_num == 1) {
|
||||
clks = &cmdq->clocks[0];
|
||||
|
||||
clks->id = gce_name;
|
||||
clks->clk = devm_clk_get(dev, NULL);
|
||||
if (IS_ERR(clks->clk))
|
||||
return dev_err_probe(dev, PTR_ERR(clks->clk),
|
||||
"failed to get gce clock\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* If there is more than one GCE, get the clocks for the others too,
|
||||
* as the clock of the main GCE must be enabled for additional IPs
|
||||
* to be reachable.
|
||||
*/
|
||||
for_each_child_of_node(parent, node) {
|
||||
int alias_id = of_alias_get_id(node, gce_name);
|
||||
|
||||
if (alias_id < 0 || alias_id >= cmdq->pdata->gce_num)
|
||||
continue;
|
||||
|
||||
clks = &cmdq->clocks[alias_id];
|
||||
|
||||
clks->id = devm_kasprintf(dev, GFP_KERNEL, "gce%d", alias_id);
|
||||
if (!clks->id) {
|
||||
of_node_put(node);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
clks->clk = of_clk_get(node, 0);
|
||||
if (IS_ERR(clks->clk)) {
|
||||
of_node_put(node);
|
||||
return dev_err_probe(dev, PTR_ERR(clks->clk),
|
||||
"failed to get gce%d clock\n", alias_id);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int cmdq_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
struct cmdq *cmdq;
|
||||
int err, i;
|
||||
struct device_node *phandle = dev->of_node;
|
||||
struct device_node *node;
|
||||
int alias_id = 0;
|
||||
static const char * const clk_name = "gce";
|
||||
static const char * const clk_names[] = { "gce0", "gce1" };
|
||||
|
||||
cmdq = devm_kzalloc(dev, sizeof(*cmdq), GFP_KERNEL);
|
||||
if (!cmdq)
|
||||
@@ -612,29 +659,9 @@ static int cmdq_probe(struct platform_device *pdev)
|
||||
dev_dbg(dev, "cmdq device: addr:0x%p, va:0x%p, irq:%d\n",
|
||||
dev, cmdq->base, cmdq->irq);
|
||||
|
||||
if (cmdq->pdata->gce_num > 1) {
|
||||
for_each_child_of_node(phandle->parent, node) {
|
||||
alias_id = of_alias_get_id(node, clk_name);
|
||||
if (alias_id >= 0 && alias_id < cmdq->pdata->gce_num) {
|
||||
cmdq->clocks[alias_id].id = clk_names[alias_id];
|
||||
cmdq->clocks[alias_id].clk = of_clk_get(node, 0);
|
||||
if (IS_ERR(cmdq->clocks[alias_id].clk)) {
|
||||
of_node_put(node);
|
||||
return dev_err_probe(dev,
|
||||
PTR_ERR(cmdq->clocks[alias_id].clk),
|
||||
"failed to get gce clk: %d\n",
|
||||
alias_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
cmdq->clocks[alias_id].id = clk_name;
|
||||
cmdq->clocks[alias_id].clk = devm_clk_get(&pdev->dev, clk_name);
|
||||
if (IS_ERR(cmdq->clocks[alias_id].clk)) {
|
||||
return dev_err_probe(dev, PTR_ERR(cmdq->clocks[alias_id].clk),
|
||||
"failed to get gce clk\n");
|
||||
}
|
||||
}
|
||||
err = cmdq_get_clocks(dev, cmdq);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
cmdq->mbox.dev = dev;
|
||||
cmdq->mbox.chans = devm_kcalloc(dev, cmdq->pdata->thread_nr,
|
||||
@@ -662,12 +689,6 @@ static int cmdq_probe(struct platform_device *pdev)
|
||||
cmdq->mbox.chans[i].con_priv = (void *)&cmdq->thread[i];
|
||||
}
|
||||
|
||||
err = devm_mbox_controller_register(dev, &cmdq->mbox);
|
||||
if (err < 0) {
|
||||
dev_err(dev, "failed to register mailbox: %d\n", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
platform_set_drvdata(pdev, cmdq);
|
||||
|
||||
WARN_ON(clk_bulk_prepare(cmdq->pdata->gce_num, cmdq->clocks));
|
||||
@@ -695,6 +716,12 @@ static int cmdq_probe(struct platform_device *pdev)
|
||||
pm_runtime_set_autosuspend_delay(dev, CMDQ_MBOX_AUTOSUSPEND_DELAY_MS);
|
||||
pm_runtime_use_autosuspend(dev);
|
||||
|
||||
err = devm_mbox_controller_register(dev, &cmdq->mbox);
|
||||
if (err < 0) {
|
||||
dev_err(dev, "failed to register mailbox: %d\n", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -790,4 +817,5 @@ static void __exit cmdq_drv_exit(void)
|
||||
subsys_initcall(cmdq_drv_init);
|
||||
module_exit(cmdq_drv_exit);
|
||||
|
||||
MODULE_DESCRIPTION("Mediatek Command Queue(CMDQ) Mailbox driver");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
|
||||
@@ -230,7 +230,8 @@ static int omap_mbox_startup(struct omap_mbox *mbox)
|
||||
int ret = 0;
|
||||
|
||||
ret = request_threaded_irq(mbox->irq, NULL, mbox_interrupt,
|
||||
IRQF_ONESHOT, mbox->name, mbox);
|
||||
IRQF_SHARED | IRQF_ONESHOT, mbox->name,
|
||||
mbox);
|
||||
if (unlikely(ret)) {
|
||||
pr_err("failed to register mailbox interrupt:%d\n", ret);
|
||||
return ret;
|
||||
|
||||
@@ -0,0 +1,187 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Copyright (c) 2024, Qualcomm Innovation Center, Inc. All rights reserved.
|
||||
*/
|
||||
|
||||
#include <linux/bitops.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/irq.h>
|
||||
#include <linux/irqdomain.h>
|
||||
#include <linux/mailbox_controller.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
|
||||
#define APSS_CPUCP_IPC_CHAN_SUPPORTED 3
|
||||
#define APSS_CPUCP_MBOX_CMD_OFF 0x4
|
||||
|
||||
/* Tx Registers */
|
||||
#define APSS_CPUCP_TX_MBOX_CMD(i) (0x100 + ((i) * 8))
|
||||
|
||||
/* Rx Registers */
|
||||
#define APSS_CPUCP_RX_MBOX_CMD(i) (0x100 + ((i) * 8))
|
||||
#define APSS_CPUCP_RX_MBOX_MAP 0x4000
|
||||
#define APSS_CPUCP_RX_MBOX_STAT 0x4400
|
||||
#define APSS_CPUCP_RX_MBOX_CLEAR 0x4800
|
||||
#define APSS_CPUCP_RX_MBOX_EN 0x4c00
|
||||
#define APSS_CPUCP_RX_MBOX_CMD_MASK GENMASK_ULL(63, 0)
|
||||
|
||||
/**
|
||||
* struct qcom_cpucp_mbox - Holder for the mailbox driver
|
||||
* @chans: The mailbox channel
|
||||
* @mbox: The mailbox controller
|
||||
* @tx_base: Base address of the CPUCP tx registers
|
||||
* @rx_base: Base address of the CPUCP rx registers
|
||||
*/
|
||||
struct qcom_cpucp_mbox {
|
||||
struct mbox_chan chans[APSS_CPUCP_IPC_CHAN_SUPPORTED];
|
||||
struct mbox_controller mbox;
|
||||
void __iomem *tx_base;
|
||||
void __iomem *rx_base;
|
||||
};
|
||||
|
||||
static inline int channel_number(struct mbox_chan *chan)
|
||||
{
|
||||
return chan - chan->mbox->chans;
|
||||
}
|
||||
|
||||
static irqreturn_t qcom_cpucp_mbox_irq_fn(int irq, void *data)
|
||||
{
|
||||
struct qcom_cpucp_mbox *cpucp = data;
|
||||
u64 status;
|
||||
int i;
|
||||
|
||||
status = readq(cpucp->rx_base + APSS_CPUCP_RX_MBOX_STAT);
|
||||
|
||||
for_each_set_bit(i, (unsigned long *)&status, APSS_CPUCP_IPC_CHAN_SUPPORTED) {
|
||||
u32 val = readl(cpucp->rx_base + APSS_CPUCP_RX_MBOX_CMD(i) + APSS_CPUCP_MBOX_CMD_OFF);
|
||||
struct mbox_chan *chan = &cpucp->chans[i];
|
||||
unsigned long flags;
|
||||
|
||||
/* Provide mutual exclusion with changes to chan->cl */
|
||||
spin_lock_irqsave(&chan->lock, flags);
|
||||
if (chan->cl)
|
||||
mbox_chan_received_data(chan, &val);
|
||||
writeq(BIT(i), cpucp->rx_base + APSS_CPUCP_RX_MBOX_CLEAR);
|
||||
spin_unlock_irqrestore(&chan->lock, flags);
|
||||
}
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static int qcom_cpucp_mbox_startup(struct mbox_chan *chan)
|
||||
{
|
||||
struct qcom_cpucp_mbox *cpucp = container_of(chan->mbox, struct qcom_cpucp_mbox, mbox);
|
||||
unsigned long chan_id = channel_number(chan);
|
||||
u64 val;
|
||||
|
||||
val = readq(cpucp->rx_base + APSS_CPUCP_RX_MBOX_EN);
|
||||
val |= BIT(chan_id);
|
||||
writeq(val, cpucp->rx_base + APSS_CPUCP_RX_MBOX_EN);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void qcom_cpucp_mbox_shutdown(struct mbox_chan *chan)
|
||||
{
|
||||
struct qcom_cpucp_mbox *cpucp = container_of(chan->mbox, struct qcom_cpucp_mbox, mbox);
|
||||
unsigned long chan_id = channel_number(chan);
|
||||
u64 val;
|
||||
|
||||
val = readq(cpucp->rx_base + APSS_CPUCP_RX_MBOX_EN);
|
||||
val &= ~BIT(chan_id);
|
||||
writeq(val, cpucp->rx_base + APSS_CPUCP_RX_MBOX_EN);
|
||||
}
|
||||
|
||||
static int qcom_cpucp_mbox_send_data(struct mbox_chan *chan, void *data)
|
||||
{
|
||||
struct qcom_cpucp_mbox *cpucp = container_of(chan->mbox, struct qcom_cpucp_mbox, mbox);
|
||||
unsigned long chan_id = channel_number(chan);
|
||||
u32 *val = data;
|
||||
|
||||
writel(*val, cpucp->tx_base + APSS_CPUCP_TX_MBOX_CMD(chan_id) + APSS_CPUCP_MBOX_CMD_OFF);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct mbox_chan_ops qcom_cpucp_mbox_chan_ops = {
|
||||
.startup = qcom_cpucp_mbox_startup,
|
||||
.send_data = qcom_cpucp_mbox_send_data,
|
||||
.shutdown = qcom_cpucp_mbox_shutdown
|
||||
};
|
||||
|
||||
static int qcom_cpucp_mbox_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
struct qcom_cpucp_mbox *cpucp;
|
||||
struct mbox_controller *mbox;
|
||||
int irq, ret;
|
||||
|
||||
cpucp = devm_kzalloc(dev, sizeof(*cpucp), GFP_KERNEL);
|
||||
if (!cpucp)
|
||||
return -ENOMEM;
|
||||
|
||||
cpucp->rx_base = devm_of_iomap(dev, dev->of_node, 0, NULL);
|
||||
if (IS_ERR(cpucp->rx_base))
|
||||
return PTR_ERR(cpucp->rx_base);
|
||||
|
||||
cpucp->tx_base = devm_of_iomap(dev, dev->of_node, 1, NULL);
|
||||
if (IS_ERR(cpucp->tx_base))
|
||||
return PTR_ERR(cpucp->tx_base);
|
||||
|
||||
writeq(0, cpucp->rx_base + APSS_CPUCP_RX_MBOX_EN);
|
||||
writeq(0, cpucp->rx_base + APSS_CPUCP_RX_MBOX_CLEAR);
|
||||
writeq(0, cpucp->rx_base + APSS_CPUCP_RX_MBOX_MAP);
|
||||
|
||||
irq = platform_get_irq(pdev, 0);
|
||||
if (irq < 0)
|
||||
return irq;
|
||||
|
||||
ret = devm_request_irq(dev, irq, qcom_cpucp_mbox_irq_fn,
|
||||
IRQF_TRIGGER_HIGH, "apss_cpucp_mbox", cpucp);
|
||||
if (ret < 0)
|
||||
return dev_err_probe(dev, ret, "Failed to register irq: %d\n", irq);
|
||||
|
||||
writeq(APSS_CPUCP_RX_MBOX_CMD_MASK, cpucp->rx_base + APSS_CPUCP_RX_MBOX_MAP);
|
||||
|
||||
mbox = &cpucp->mbox;
|
||||
mbox->dev = dev;
|
||||
mbox->num_chans = APSS_CPUCP_IPC_CHAN_SUPPORTED;
|
||||
mbox->chans = cpucp->chans;
|
||||
mbox->ops = &qcom_cpucp_mbox_chan_ops;
|
||||
|
||||
ret = devm_mbox_controller_register(dev, mbox);
|
||||
if (ret)
|
||||
return dev_err_probe(dev, ret, "Failed to create mailbox\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct of_device_id qcom_cpucp_mbox_of_match[] = {
|
||||
{ .compatible = "qcom,x1e80100-cpucp-mbox" },
|
||||
{}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, qcom_cpucp_mbox_of_match);
|
||||
|
||||
static struct platform_driver qcom_cpucp_mbox_driver = {
|
||||
.probe = qcom_cpucp_mbox_probe,
|
||||
.driver = {
|
||||
.name = "qcom_cpucp_mbox",
|
||||
.of_match_table = qcom_cpucp_mbox_of_match,
|
||||
},
|
||||
};
|
||||
|
||||
static int __init qcom_cpucp_mbox_init(void)
|
||||
{
|
||||
return platform_driver_register(&qcom_cpucp_mbox_driver);
|
||||
}
|
||||
core_initcall(qcom_cpucp_mbox_init);
|
||||
|
||||
static void __exit qcom_cpucp_mbox_exit(void)
|
||||
{
|
||||
platform_driver_unregister(&qcom_cpucp_mbox_driver);
|
||||
}
|
||||
module_exit(qcom_cpucp_mbox_exit);
|
||||
|
||||
MODULE_DESCRIPTION("QTI CPUCP MBOX Driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
@@ -64,6 +64,13 @@
|
||||
|
||||
#define MAX_SGI 16
|
||||
|
||||
/*
|
||||
* Module parameters
|
||||
*/
|
||||
static int tx_poll_period = 5;
|
||||
module_param_named(tx_poll_period, tx_poll_period, int, 0644);
|
||||
MODULE_PARM_DESC(tx_poll_period, "Poll period waiting for ack after send.");
|
||||
|
||||
/**
|
||||
* struct zynqmp_ipi_mchan - Description of a Xilinx ZynqMP IPI mailbox channel
|
||||
* @is_opened: indicate if the IPI channel is opened
|
||||
@@ -537,7 +544,7 @@ static int zynqmp_ipi_mbox_probe(struct zynqmp_ipi_mbox *ipi_mbox,
|
||||
mbox->num_chans = 2;
|
||||
mbox->txdone_irq = false;
|
||||
mbox->txdone_poll = true;
|
||||
mbox->txpoll_period = 5;
|
||||
mbox->txpoll_period = tx_poll_period;
|
||||
mbox->of_xlate = zynqmp_ipi_of_xlate;
|
||||
chans = devm_kzalloc(mdev, 2 * sizeof(*chans), GFP_KERNEL);
|
||||
if (!chans)
|
||||
|
||||
@@ -148,17 +148,35 @@ static void pci_device_domain_set_desc(msi_alloc_info_t *arg, struct msi_desc *d
|
||||
arg->hwirq = desc->msi_index;
|
||||
}
|
||||
|
||||
static __always_inline void cond_mask_parent(struct irq_data *data)
|
||||
{
|
||||
struct msi_domain_info *info = data->domain->host_data;
|
||||
|
||||
if (unlikely(info->flags & MSI_FLAG_PCI_MSI_MASK_PARENT))
|
||||
irq_chip_mask_parent(data);
|
||||
}
|
||||
|
||||
static __always_inline void cond_unmask_parent(struct irq_data *data)
|
||||
{
|
||||
struct msi_domain_info *info = data->domain->host_data;
|
||||
|
||||
if (unlikely(info->flags & MSI_FLAG_PCI_MSI_MASK_PARENT))
|
||||
irq_chip_unmask_parent(data);
|
||||
}
|
||||
|
||||
static void pci_irq_mask_msi(struct irq_data *data)
|
||||
{
|
||||
struct msi_desc *desc = irq_data_get_msi_desc(data);
|
||||
|
||||
pci_msi_mask(desc, BIT(data->irq - desc->irq));
|
||||
cond_mask_parent(data);
|
||||
}
|
||||
|
||||
static void pci_irq_unmask_msi(struct irq_data *data)
|
||||
{
|
||||
struct msi_desc *desc = irq_data_get_msi_desc(data);
|
||||
|
||||
cond_unmask_parent(data);
|
||||
pci_msi_unmask(desc, BIT(data->irq - desc->irq));
|
||||
}
|
||||
|
||||
@@ -195,10 +213,12 @@ static const struct msi_domain_template pci_msi_template = {
|
||||
static void pci_irq_mask_msix(struct irq_data *data)
|
||||
{
|
||||
pci_msix_mask(irq_data_get_msi_desc(data));
|
||||
cond_mask_parent(data);
|
||||
}
|
||||
|
||||
static void pci_irq_unmask_msix(struct irq_data *data)
|
||||
{
|
||||
cond_unmask_parent(data);
|
||||
pci_msix_unmask(irq_data_get_msi_desc(data));
|
||||
}
|
||||
|
||||
|
||||
@@ -456,7 +456,6 @@ struct platform_driver bcm63xx_pcmcia_driver = {
|
||||
.remove_new = bcm63xx_drv_pcmcia_remove,
|
||||
.driver = {
|
||||
.name = "bcm63xx_pcmcia",
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
#include "i82092aa.h"
|
||||
#include "i82365.h"
|
||||
|
||||
MODULE_DESCRIPTION("Driver for Intel I82092AA PCI-PCMCIA bridge");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
||||
/* PCI core routines */
|
||||
|
||||
@@ -1342,5 +1342,6 @@ static void __exit exit_i82365(void)
|
||||
|
||||
module_init(init_i82365);
|
||||
module_exit(exit_i82365);
|
||||
MODULE_DESCRIPTION("Driver for Intel 82365 and compatible PC Card controllers");
|
||||
MODULE_LICENSE("Dual MPL/GPL");
|
||||
/*====================================================================*/
|
||||
|
||||
@@ -119,4 +119,5 @@ int max1600_configure(struct max1600 *m, unsigned int vcc, unsigned int vpp)
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(max1600_configure);
|
||||
|
||||
MODULE_DESCRIPTION("MAX1600 PCMCIA power switch library");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
|
||||
@@ -66,5 +66,6 @@ EXPORT_SYMBOL(pccard_static_ops);
|
||||
|
||||
|
||||
MODULE_AUTHOR("David A. Hinds, Dominik Brodowski");
|
||||
MODULE_DESCRIPTION("PCMCIA resource management routines");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_ALIAS("rsrc_nonstatic");
|
||||
|
||||
@@ -638,11 +638,11 @@ static int yenta_search_one_res(struct resource *root, struct resource *res,
|
||||
start = PCIBIOS_MIN_CARDBUS_IO;
|
||||
end = ~0U;
|
||||
} else {
|
||||
unsigned long avail = root->end - root->start;
|
||||
unsigned long avail = resource_size(root);
|
||||
int i;
|
||||
size = BRIDGE_MEM_MAX;
|
||||
if (size > avail/8) {
|
||||
size = (avail+1)/8;
|
||||
if (size > (avail - 1) / 8) {
|
||||
size = avail / 8;
|
||||
/* round size down to next power of 2 */
|
||||
i = 0;
|
||||
while ((size /= 2) != 0)
|
||||
@@ -1452,4 +1452,5 @@ static struct pci_driver yenta_cardbus_driver = {
|
||||
|
||||
module_pci_driver(yenta_cardbus_driver);
|
||||
|
||||
MODULE_DESCRIPTION("Driver for CardBus yenta-compatible bridges");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
||||
@@ -106,4 +106,5 @@ static struct pci_driver piix4_poweroff_driver = {
|
||||
|
||||
module_pci_driver(piix4_poweroff_driver);
|
||||
MODULE_AUTHOR("Paul Burton <paul.burton@mips.com>");
|
||||
MODULE_DESCRIPTION("Intel PIIX4 power-off driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
||||
@@ -167,6 +167,15 @@ config BATTERY_LEGO_EV3
|
||||
help
|
||||
Say Y here to enable support for the LEGO MINDSTORMS EV3 battery.
|
||||
|
||||
config BATTERY_LENOVO_YOGA_C630
|
||||
tristate "Lenovo Yoga C630 battery"
|
||||
depends on EC_LENOVO_YOGA_C630
|
||||
help
|
||||
This driver enables battery support on the Lenovo Yoga C630 laptop.
|
||||
|
||||
To compile the driver as a module, choose M here: the module will be
|
||||
called lenovo_yoga_c630_battery.
|
||||
|
||||
config BATTERY_PMU
|
||||
tristate "Apple PMU battery"
|
||||
depends on PPC32 && ADB_PMU
|
||||
@@ -402,6 +411,18 @@ config BATTERY_MAX17042
|
||||
|
||||
Driver can be build as a module (max17042_battery).
|
||||
|
||||
config BATTERY_MAX1720X
|
||||
tristate "Maxim MAX17201/MAX17205 Fuel Gauge"
|
||||
depends on I2C
|
||||
select REGMAP_I2C
|
||||
help
|
||||
MAX1720x is a family of fuel-gauge systems for lithium-ion (Li+)
|
||||
batteries in handheld and portable equipment. MAX17201 are
|
||||
configured to operate with a single lithium cell, the MAX17205
|
||||
can operate with multiple cells.
|
||||
|
||||
Say Y to include support for the MAX17201/MAX17205 Fuel Gauges.
|
||||
|
||||
config BATTERY_MAX1721X
|
||||
tristate "MAX17211/MAX17215 standalone gas-gauge"
|
||||
depends on W1
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user