Merge branch 'irq-core-for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip
Pull irq updates from Thomas Gleixner: "The irq departement provides the usual mixed bag: Core: - Further improvements to the irq timings code which aims to predict the next interrupt for power state selection to achieve better latency/power balance - Add interrupt statistics to the core NMI handlers - The usual small fixes and cleanups Drivers: - Support for Renesas RZ/A1, Annapurna Labs FIC, Meson-G12A SoC and Amazon Gravition AMR/GIC interrupt controllers. - Rework of the Renesas INTC controller driver - ACPI support for Socionext SoCs - Enhancements to the CSKY interrupt controller - The usual small fixes and cleanups" * 'irq-core-for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip: (39 commits) irq/irqdomain: Fix comment typo genirq: Update irq stats from NMI handlers irqchip/gic-pm: Remove PM_CLK dependency irqchip/al-fic: Introduce Amazon's Annapurna Labs Fabric Interrupt Controller Driver dt-bindings: interrupt-controller: Add Amazon's Annapurna Labs FIC softirq: Use __this_cpu_write() in takeover_tasklets() irqchip/mbigen: Stop printing kernel addresses irqchip/gic: Add dependency for ARM_GIC_MAX_NR genirq/affinity: Remove unused argument from [__]irq_build_affinity_masks() genirq/timings: Add selftest for next event computation genirq/timings: Add selftest for irqs circular buffer genirq/timings: Add selftest for circular array genirq/timings: Encapsulate storing function genirq/timings: Encapsulate timings push genirq/timings: Optimize the period detection speed genirq/timings: Fix timings buffer inspection genirq/timings: Fix next event index function irqchip/qcom: Use struct_size() in devm_kzalloc() irqchip/irq-csky-mpintc: Remove unnecessary loop in interrupt handler dt-bindings: interrupt-controller: Update csky mpintc ...
This commit is contained in:
commit
2a1ccd3142
|
@ -0,0 +1,29 @@
|
||||||
|
Amazon's Annapurna Labs Fabric Interrupt Controller
|
||||||
|
|
||||||
|
Required properties:
|
||||||
|
|
||||||
|
- compatible: should be "amazon,al-fic"
|
||||||
|
- reg: physical base address and size of the registers
|
||||||
|
- interrupt-controller: identifies the node as an interrupt controller
|
||||||
|
- #interrupt-cells: must be 2.
|
||||||
|
First cell defines the index of the interrupt within the controller.
|
||||||
|
Second cell is used to specify the trigger type and must be one of the
|
||||||
|
following:
|
||||||
|
- bits[3:0] trigger type and level flags
|
||||||
|
1 = low-to-high edge triggered
|
||||||
|
4 = active high level-sensitive
|
||||||
|
- interrupt-parent: specifies the parent interrupt controller.
|
||||||
|
- interrupts: describes which input line in the interrupt parent, this
|
||||||
|
fic's output is connected to. This field property depends on the parent's
|
||||||
|
binding
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
amazon_fic: interrupt-controller@0xfd8a8500 {
|
||||||
|
compatible = "amazon,al-fic";
|
||||||
|
interrupt-controller;
|
||||||
|
#interrupt-cells = <2>;
|
||||||
|
reg = <0x0 0xfd8a8500 0x0 0x1000>;
|
||||||
|
interrupt-parent = <&gic>;
|
||||||
|
interrupts = <GIC_SPI 0x0 IRQ_TYPE_LEVEL_HIGH>;
|
||||||
|
};
|
|
@ -15,6 +15,7 @@ Required properties:
|
||||||
"amlogic,meson-gxbb-gpio-intc" for GXBB SoCs (S905) or
|
"amlogic,meson-gxbb-gpio-intc" for GXBB SoCs (S905) or
|
||||||
"amlogic,meson-gxl-gpio-intc" for GXL SoCs (S905X, S912)
|
"amlogic,meson-gxl-gpio-intc" for GXL SoCs (S905X, S912)
|
||||||
"amlogic,meson-axg-gpio-intc" for AXG SoCs (A113D, A113X)
|
"amlogic,meson-axg-gpio-intc" for AXG SoCs (A113D, A113X)
|
||||||
|
"amlogic,meson-g12a-gpio-intc" for G12A SoCs (S905D2, S905X2, S905Y2)
|
||||||
- reg : Specifies base physical address and size of the registers.
|
- reg : Specifies base physical address and size of the registers.
|
||||||
- interrupt-controller : Identifies the node as an interrupt controller.
|
- interrupt-controller : Identifies the node as an interrupt controller.
|
||||||
- #interrupt-cells : Specifies the number of cells needed to encode an
|
- #interrupt-cells : Specifies the number of cells needed to encode an
|
||||||
|
|
|
@ -6,11 +6,16 @@ C-SKY Multi-processors Interrupt Controller is designed for ck807/ck810/ck860
|
||||||
SMP soc, and it also could be used in non-SMP system.
|
SMP soc, and it also could be used in non-SMP system.
|
||||||
|
|
||||||
Interrupt number definition:
|
Interrupt number definition:
|
||||||
|
|
||||||
0-15 : software irq, and we use 15 as our IPI_IRQ.
|
0-15 : software irq, and we use 15 as our IPI_IRQ.
|
||||||
16-31 : private irq, and we use 16 as the co-processor timer.
|
16-31 : private irq, and we use 16 as the co-processor timer.
|
||||||
31-1024: common irq for soc ip.
|
31-1024: common irq for soc ip.
|
||||||
|
|
||||||
|
Interrupt triger mode: (Defined in dt-bindings/interrupt-controller/irq.h)
|
||||||
|
IRQ_TYPE_LEVEL_HIGH (default)
|
||||||
|
IRQ_TYPE_LEVEL_LOW
|
||||||
|
IRQ_TYPE_EDGE_RISING
|
||||||
|
IRQ_TYPE_EDGE_FALLING
|
||||||
|
|
||||||
=============================
|
=============================
|
||||||
intc node bindings definition
|
intc node bindings definition
|
||||||
=============================
|
=============================
|
||||||
|
@ -26,15 +31,22 @@ intc node bindings definition
|
||||||
- #interrupt-cells
|
- #interrupt-cells
|
||||||
Usage: required
|
Usage: required
|
||||||
Value type: <u32>
|
Value type: <u32>
|
||||||
Definition: must be <1>
|
Definition: <2>
|
||||||
- interrupt-controller:
|
- interrupt-controller:
|
||||||
Usage: required
|
Usage: required
|
||||||
|
|
||||||
Examples:
|
Examples: ("interrupts = <irq_num IRQ_TYPE_XXX>")
|
||||||
---------
|
---------
|
||||||
|
#include <dt-bindings/interrupt-controller/irq.h>
|
||||||
|
|
||||||
intc: interrupt-controller {
|
intc: interrupt-controller {
|
||||||
compatible = "csky,mpintc";
|
compatible = "csky,mpintc";
|
||||||
#interrupt-cells = <1>;
|
#interrupt-cells = <2>;
|
||||||
interrupt-controller;
|
interrupt-controller;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
device: device-example {
|
||||||
|
...
|
||||||
|
interrupts = <34 IRQ_TYPE_EDGE_RISING>;
|
||||||
|
interrupt-parent = <&intc>;
|
||||||
|
};
|
||||||
|
|
|
@ -0,0 +1,43 @@
|
||||||
|
DT bindings for the Renesas RZ/A1 Interrupt Controller
|
||||||
|
|
||||||
|
The RZ/A1 Interrupt Controller is a front-end for the GIC found on Renesas
|
||||||
|
RZ/A1 and RZ/A2 SoCs:
|
||||||
|
- IRQ sense select for 8 external interrupts, 1:1-mapped to 8 GIC SPI
|
||||||
|
interrupts,
|
||||||
|
- NMI edge select.
|
||||||
|
|
||||||
|
Required properties:
|
||||||
|
- compatible: Must be "renesas,<soctype>-irqc", and "renesas,rza1-irqc" as
|
||||||
|
fallback.
|
||||||
|
Examples with soctypes are:
|
||||||
|
- "renesas,r7s72100-irqc" (RZ/A1H)
|
||||||
|
- "renesas,r7s9210-irqc" (RZ/A2M)
|
||||||
|
- #interrupt-cells: Must be 2 (an interrupt index and flags, as defined
|
||||||
|
in interrupts.txt in this directory)
|
||||||
|
- #address-cells: Must be zero
|
||||||
|
- interrupt-controller: Marks the device as an interrupt controller
|
||||||
|
- reg: Base address and length of the memory resource used by the interrupt
|
||||||
|
controller
|
||||||
|
- interrupt-map: Specifies the mapping from external interrupts to GIC
|
||||||
|
interrupts
|
||||||
|
- interrupt-map-mask: Must be <7 0>
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
irqc: interrupt-controller@fcfef800 {
|
||||||
|
compatible = "renesas,r7s72100-irqc", "renesas,rza1-irqc";
|
||||||
|
#interrupt-cells = <2>;
|
||||||
|
#address-cells = <0>;
|
||||||
|
interrupt-controller;
|
||||||
|
reg = <0xfcfef800 0x6>;
|
||||||
|
interrupt-map =
|
||||||
|
<0 0 &gic GIC_SPI 0 IRQ_TYPE_LEVEL_HIGH>,
|
||||||
|
<1 0 &gic GIC_SPI 1 IRQ_TYPE_LEVEL_HIGH>,
|
||||||
|
<2 0 &gic GIC_SPI 2 IRQ_TYPE_LEVEL_HIGH>,
|
||||||
|
<3 0 &gic GIC_SPI 3 IRQ_TYPE_LEVEL_HIGH>,
|
||||||
|
<4 0 &gic GIC_SPI 4 IRQ_TYPE_LEVEL_HIGH>,
|
||||||
|
<5 0 &gic GIC_SPI 5 IRQ_TYPE_LEVEL_HIGH>,
|
||||||
|
<6 0 &gic GIC_SPI 6 IRQ_TYPE_LEVEL_HIGH>,
|
||||||
|
<7 0 &gic GIC_SPI 7 IRQ_TYPE_LEVEL_HIGH>;
|
||||||
|
interrupt-map-mask = <7 0>;
|
||||||
|
};
|
|
@ -1306,6 +1306,12 @@ S: Maintained
|
||||||
F: Documentation/devicetree/bindings/interrupt-controller/arm,vic.txt
|
F: Documentation/devicetree/bindings/interrupt-controller/arm,vic.txt
|
||||||
F: drivers/irqchip/irq-vic.c
|
F: drivers/irqchip/irq-vic.c
|
||||||
|
|
||||||
|
AMAZON ANNAPURNA LABS FIC DRIVER
|
||||||
|
M: Talel Shenhar <talel@amazon.com>
|
||||||
|
S: Maintained
|
||||||
|
F: Documentation/devicetree/bindings/interrupt-controller/amazon,al-fic.txt
|
||||||
|
F: drivers/irqchip/irq-al-fic.c
|
||||||
|
|
||||||
ARM SMMU DRIVERS
|
ARM SMMU DRIVERS
|
||||||
M: Will Deacon <will@kernel.org>
|
M: Will Deacon <will@kernel.org>
|
||||||
R: Robin Murphy <robin.murphy@arm.com>
|
R: Robin Murphy <robin.murphy@arm.com>
|
||||||
|
|
|
@ -292,3 +292,29 @@ void __init acpi_set_irq_model(enum acpi_irq_model_id model,
|
||||||
acpi_irq_model = model;
|
acpi_irq_model = model;
|
||||||
acpi_gsi_domain_id = fwnode;
|
acpi_gsi_domain_id = fwnode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* acpi_irq_create_hierarchy - Create a hierarchical IRQ domain with the default
|
||||||
|
* GSI domain as its parent.
|
||||||
|
* @flags: Irq domain flags associated with the domain
|
||||||
|
* @size: Size of the domain.
|
||||||
|
* @fwnode: Optional fwnode of the interrupt controller
|
||||||
|
* @ops: Pointer to the interrupt domain callbacks
|
||||||
|
* @host_data: Controller private data pointer
|
||||||
|
*/
|
||||||
|
struct irq_domain *acpi_irq_create_hierarchy(unsigned int flags,
|
||||||
|
unsigned int size,
|
||||||
|
struct fwnode_handle *fwnode,
|
||||||
|
const struct irq_domain_ops *ops,
|
||||||
|
void *host_data)
|
||||||
|
{
|
||||||
|
struct irq_domain *d = irq_find_matching_fwnode(acpi_gsi_domain_id,
|
||||||
|
DOMAIN_BUS_ANY);
|
||||||
|
|
||||||
|
if (!d)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
return irq_domain_create_hierarchy(d, flags, size, fwnode, ops,
|
||||||
|
host_data);
|
||||||
|
}
|
||||||
|
EXPORT_SYMBOL_GPL(acpi_irq_create_hierarchy);
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
* Copyright (C) 2015 Linaro Ltd.
|
* Copyright (C) 2015 Linaro Ltd.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include <linux/acpi.h>
|
||||||
#include <linux/io.h>
|
#include <linux/io.h>
|
||||||
#include <linux/init.h>
|
#include <linux/init.h>
|
||||||
#include <linux/clk.h>
|
#include <linux/clk.h>
|
||||||
|
@ -19,6 +20,8 @@
|
||||||
#include <linux/spinlock.h>
|
#include <linux/spinlock.h>
|
||||||
#include <linux/slab.h>
|
#include <linux/slab.h>
|
||||||
|
|
||||||
|
#include "gpiolib.h"
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Only first 8bits of a register correspond to each pin,
|
* Only first 8bits of a register correspond to each pin,
|
||||||
* so there are 4 registers for 32 pins.
|
* so there are 4 registers for 32 pins.
|
||||||
|
@ -135,6 +138,20 @@ static void mb86s70_gpio_set(struct gpio_chip *gc, unsigned gpio, int value)
|
||||||
spin_unlock_irqrestore(&gchip->lock, flags);
|
spin_unlock_irqrestore(&gchip->lock, flags);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int mb86s70_gpio_to_irq(struct gpio_chip *gc, unsigned int offset)
|
||||||
|
{
|
||||||
|
int irq, index;
|
||||||
|
|
||||||
|
for (index = 0;; index++) {
|
||||||
|
irq = platform_get_irq(to_platform_device(gc->parent), index);
|
||||||
|
if (irq <= 0)
|
||||||
|
break;
|
||||||
|
if (irq_get_irq_data(irq)->hwirq == offset)
|
||||||
|
return irq;
|
||||||
|
}
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
static int mb86s70_gpio_probe(struct platform_device *pdev)
|
static int mb86s70_gpio_probe(struct platform_device *pdev)
|
||||||
{
|
{
|
||||||
struct mb86s70_gpio_chip *gchip;
|
struct mb86s70_gpio_chip *gchip;
|
||||||
|
@ -150,13 +167,15 @@ static int mb86s70_gpio_probe(struct platform_device *pdev)
|
||||||
if (IS_ERR(gchip->base))
|
if (IS_ERR(gchip->base))
|
||||||
return PTR_ERR(gchip->base);
|
return PTR_ERR(gchip->base);
|
||||||
|
|
||||||
gchip->clk = devm_clk_get(&pdev->dev, NULL);
|
if (!has_acpi_companion(&pdev->dev)) {
|
||||||
if (IS_ERR(gchip->clk))
|
gchip->clk = devm_clk_get(&pdev->dev, NULL);
|
||||||
return PTR_ERR(gchip->clk);
|
if (IS_ERR(gchip->clk))
|
||||||
|
return PTR_ERR(gchip->clk);
|
||||||
|
|
||||||
ret = clk_prepare_enable(gchip->clk);
|
ret = clk_prepare_enable(gchip->clk);
|
||||||
if (ret)
|
if (ret)
|
||||||
return ret;
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
spin_lock_init(&gchip->lock);
|
spin_lock_init(&gchip->lock);
|
||||||
|
|
||||||
|
@ -172,19 +191,28 @@ static int mb86s70_gpio_probe(struct platform_device *pdev)
|
||||||
gchip->gc.parent = &pdev->dev;
|
gchip->gc.parent = &pdev->dev;
|
||||||
gchip->gc.base = -1;
|
gchip->gc.base = -1;
|
||||||
|
|
||||||
|
if (has_acpi_companion(&pdev->dev))
|
||||||
|
gchip->gc.to_irq = mb86s70_gpio_to_irq;
|
||||||
|
|
||||||
ret = gpiochip_add_data(&gchip->gc, gchip);
|
ret = gpiochip_add_data(&gchip->gc, gchip);
|
||||||
if (ret) {
|
if (ret) {
|
||||||
dev_err(&pdev->dev, "couldn't register gpio driver\n");
|
dev_err(&pdev->dev, "couldn't register gpio driver\n");
|
||||||
clk_disable_unprepare(gchip->clk);
|
clk_disable_unprepare(gchip->clk);
|
||||||
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
return ret;
|
if (has_acpi_companion(&pdev->dev))
|
||||||
|
acpi_gpiochip_request_interrupts(&gchip->gc);
|
||||||
|
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int mb86s70_gpio_remove(struct platform_device *pdev)
|
static int mb86s70_gpio_remove(struct platform_device *pdev)
|
||||||
{
|
{
|
||||||
struct mb86s70_gpio_chip *gchip = platform_get_drvdata(pdev);
|
struct mb86s70_gpio_chip *gchip = platform_get_drvdata(pdev);
|
||||||
|
|
||||||
|
if (has_acpi_companion(&pdev->dev))
|
||||||
|
acpi_gpiochip_free_interrupts(&gchip->gc);
|
||||||
gpiochip_remove(&gchip->gc);
|
gpiochip_remove(&gchip->gc);
|
||||||
clk_disable_unprepare(gchip->clk);
|
clk_disable_unprepare(gchip->clk);
|
||||||
|
|
||||||
|
@ -197,10 +225,19 @@ static const struct of_device_id mb86s70_gpio_dt_ids[] = {
|
||||||
};
|
};
|
||||||
MODULE_DEVICE_TABLE(of, mb86s70_gpio_dt_ids);
|
MODULE_DEVICE_TABLE(of, mb86s70_gpio_dt_ids);
|
||||||
|
|
||||||
|
#ifdef CONFIG_ACPI
|
||||||
|
static const struct acpi_device_id mb86s70_gpio_acpi_ids[] = {
|
||||||
|
{ "SCX0007" },
|
||||||
|
{ /* sentinel */ }
|
||||||
|
};
|
||||||
|
MODULE_DEVICE_TABLE(acpi, mb86s70_gpio_acpi_ids);
|
||||||
|
#endif
|
||||||
|
|
||||||
static struct platform_driver mb86s70_gpio_driver = {
|
static struct platform_driver mb86s70_gpio_driver = {
|
||||||
.driver = {
|
.driver = {
|
||||||
.name = "mb86s70-gpio",
|
.name = "mb86s70-gpio",
|
||||||
.of_match_table = mb86s70_gpio_dt_ids,
|
.of_match_table = mb86s70_gpio_dt_ids,
|
||||||
|
.acpi_match_table = ACPI_PTR(mb86s70_gpio_acpi_ids),
|
||||||
},
|
},
|
||||||
.probe = mb86s70_gpio_probe,
|
.probe = mb86s70_gpio_probe,
|
||||||
.remove = mb86s70_gpio_remove,
|
.remove = mb86s70_gpio_remove,
|
||||||
|
|
|
@ -15,10 +15,10 @@ config ARM_GIC_PM
|
||||||
bool
|
bool
|
||||||
depends on PM
|
depends on PM
|
||||||
select ARM_GIC
|
select ARM_GIC
|
||||||
select PM_CLK
|
|
||||||
|
|
||||||
config ARM_GIC_MAX_NR
|
config ARM_GIC_MAX_NR
|
||||||
int
|
int
|
||||||
|
depends on ARM_GIC
|
||||||
default 2 if ARCH_REALVIEW
|
default 2 if ARCH_REALVIEW
|
||||||
default 1
|
default 1
|
||||||
|
|
||||||
|
@ -87,6 +87,14 @@ config ALPINE_MSI
|
||||||
select PCI_MSI
|
select PCI_MSI
|
||||||
select GENERIC_IRQ_CHIP
|
select GENERIC_IRQ_CHIP
|
||||||
|
|
||||||
|
config AL_FIC
|
||||||
|
bool "Amazon's Annapurna Labs Fabric Interrupt Controller"
|
||||||
|
depends on OF || COMPILE_TEST
|
||||||
|
select GENERIC_IRQ_CHIP
|
||||||
|
select IRQ_DOMAIN
|
||||||
|
help
|
||||||
|
Support Amazon's Annapurna Labs Fabric Interrupt Controller.
|
||||||
|
|
||||||
config ATMEL_AIC_IRQ
|
config ATMEL_AIC_IRQ
|
||||||
bool
|
bool
|
||||||
select GENERIC_IRQ_CHIP
|
select GENERIC_IRQ_CHIP
|
||||||
|
@ -217,13 +225,26 @@ config RDA_INTC
|
||||||
select IRQ_DOMAIN
|
select IRQ_DOMAIN
|
||||||
|
|
||||||
config RENESAS_INTC_IRQPIN
|
config RENESAS_INTC_IRQPIN
|
||||||
bool
|
bool "Renesas INTC External IRQ Pin Support" if COMPILE_TEST
|
||||||
select IRQ_DOMAIN
|
select IRQ_DOMAIN
|
||||||
|
help
|
||||||
|
Enable support for the Renesas Interrupt Controller for external
|
||||||
|
interrupt pins, as found on SH/R-Mobile and R-Car Gen1 SoCs.
|
||||||
|
|
||||||
config RENESAS_IRQC
|
config RENESAS_IRQC
|
||||||
bool
|
bool "Renesas R-Mobile APE6 and R-Car IRQC support" if COMPILE_TEST
|
||||||
select GENERIC_IRQ_CHIP
|
select GENERIC_IRQ_CHIP
|
||||||
select IRQ_DOMAIN
|
select IRQ_DOMAIN
|
||||||
|
help
|
||||||
|
Enable support for the Renesas Interrupt Controller for external
|
||||||
|
devices, as found on R-Mobile APE6, R-Car Gen2, and R-Car Gen3 SoCs.
|
||||||
|
|
||||||
|
config RENESAS_RZA1_IRQC
|
||||||
|
bool "Renesas RZ/A1 IRQC support" if COMPILE_TEST
|
||||||
|
select IRQ_DOMAIN_HIERARCHY
|
||||||
|
help
|
||||||
|
Enable support for the Renesas RZ/A1 Interrupt Controller, to use up
|
||||||
|
to 8 external interrupts with configurable sense select.
|
||||||
|
|
||||||
config ST_IRQCHIP
|
config ST_IRQCHIP
|
||||||
bool
|
bool
|
||||||
|
@ -299,8 +320,11 @@ config RENESAS_H8300H_INTC
|
||||||
select IRQ_DOMAIN
|
select IRQ_DOMAIN
|
||||||
|
|
||||||
config RENESAS_H8S_INTC
|
config RENESAS_H8S_INTC
|
||||||
bool
|
bool "Renesas H8S Interrupt Controller Support" if COMPILE_TEST
|
||||||
select IRQ_DOMAIN
|
select IRQ_DOMAIN
|
||||||
|
help
|
||||||
|
Enable support for the Renesas H8/300 Interrupt Controller, as found
|
||||||
|
on Renesas H8S SoCs.
|
||||||
|
|
||||||
config IMX_GPCV2
|
config IMX_GPCV2
|
||||||
bool
|
bool
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
# SPDX-License-Identifier: GPL-2.0
|
# SPDX-License-Identifier: GPL-2.0
|
||||||
obj-$(CONFIG_IRQCHIP) += irqchip.o
|
obj-$(CONFIG_IRQCHIP) += irqchip.o
|
||||||
|
|
||||||
|
obj-$(CONFIG_AL_FIC) += irq-al-fic.o
|
||||||
obj-$(CONFIG_ALPINE_MSI) += irq-alpine-msi.o
|
obj-$(CONFIG_ALPINE_MSI) += irq-alpine-msi.o
|
||||||
obj-$(CONFIG_ATH79) += irq-ath79-cpu.o
|
obj-$(CONFIG_ATH79) += irq-ath79-cpu.o
|
||||||
obj-$(CONFIG_ATH79) += irq-ath79-misc.o
|
obj-$(CONFIG_ATH79) += irq-ath79-misc.o
|
||||||
|
@ -49,6 +50,7 @@ obj-$(CONFIG_JCORE_AIC) += irq-jcore-aic.o
|
||||||
obj-$(CONFIG_RDA_INTC) += irq-rda-intc.o
|
obj-$(CONFIG_RDA_INTC) += irq-rda-intc.o
|
||||||
obj-$(CONFIG_RENESAS_INTC_IRQPIN) += irq-renesas-intc-irqpin.o
|
obj-$(CONFIG_RENESAS_INTC_IRQPIN) += irq-renesas-intc-irqpin.o
|
||||||
obj-$(CONFIG_RENESAS_IRQC) += irq-renesas-irqc.o
|
obj-$(CONFIG_RENESAS_IRQC) += irq-renesas-irqc.o
|
||||||
|
obj-$(CONFIG_RENESAS_RZA1_IRQC) += irq-renesas-rza1.o
|
||||||
obj-$(CONFIG_VERSATILE_FPGA_IRQ) += irq-versatile-fpga.o
|
obj-$(CONFIG_VERSATILE_FPGA_IRQ) += irq-versatile-fpga.o
|
||||||
obj-$(CONFIG_ARCH_NSPIRE) += irq-zevio.o
|
obj-$(CONFIG_ARCH_NSPIRE) += irq-zevio.o
|
||||||
obj-$(CONFIG_ARCH_VT8500) += irq-vt8500.o
|
obj-$(CONFIG_ARCH_VT8500) += irq-vt8500.o
|
||||||
|
|
|
@ -0,0 +1,278 @@
|
||||||
|
// SPDX-License-Identifier: GPL-2.0
|
||||||
|
/*
|
||||||
|
* Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <linux/bitfield.h>
|
||||||
|
#include <linux/irq.h>
|
||||||
|
#include <linux/irqchip.h>
|
||||||
|
#include <linux/irqchip/chained_irq.h>
|
||||||
|
#include <linux/irqdomain.h>
|
||||||
|
#include <linux/module.h>
|
||||||
|
#include <linux/of.h>
|
||||||
|
#include <linux/of_address.h>
|
||||||
|
#include <linux/of_irq.h>
|
||||||
|
|
||||||
|
/* FIC Registers */
|
||||||
|
#define AL_FIC_CAUSE 0x00
|
||||||
|
#define AL_FIC_MASK 0x10
|
||||||
|
#define AL_FIC_CONTROL 0x28
|
||||||
|
|
||||||
|
#define CONTROL_TRIGGER_RISING BIT(3)
|
||||||
|
#define CONTROL_MASK_MSI_X BIT(5)
|
||||||
|
|
||||||
|
#define NR_FIC_IRQS 32
|
||||||
|
|
||||||
|
MODULE_AUTHOR("Talel Shenhar");
|
||||||
|
MODULE_DESCRIPTION("Amazon's Annapurna Labs Interrupt Controller Driver");
|
||||||
|
MODULE_LICENSE("GPL v2");
|
||||||
|
|
||||||
|
enum al_fic_state {
|
||||||
|
AL_FIC_UNCONFIGURED = 0,
|
||||||
|
AL_FIC_CONFIGURED_LEVEL,
|
||||||
|
AL_FIC_CONFIGURED_RISING_EDGE,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct al_fic {
|
||||||
|
void __iomem *base;
|
||||||
|
struct irq_domain *domain;
|
||||||
|
const char *name;
|
||||||
|
unsigned int parent_irq;
|
||||||
|
enum al_fic_state state;
|
||||||
|
};
|
||||||
|
|
||||||
|
static void al_fic_set_trigger(struct al_fic *fic,
|
||||||
|
struct irq_chip_generic *gc,
|
||||||
|
enum al_fic_state new_state)
|
||||||
|
{
|
||||||
|
irq_flow_handler_t handler;
|
||||||
|
u32 control = readl_relaxed(fic->base + AL_FIC_CONTROL);
|
||||||
|
|
||||||
|
if (new_state == AL_FIC_CONFIGURED_LEVEL) {
|
||||||
|
handler = handle_level_irq;
|
||||||
|
control &= ~CONTROL_TRIGGER_RISING;
|
||||||
|
} else {
|
||||||
|
handler = handle_edge_irq;
|
||||||
|
control |= CONTROL_TRIGGER_RISING;
|
||||||
|
}
|
||||||
|
gc->chip_types->handler = handler;
|
||||||
|
fic->state = new_state;
|
||||||
|
writel_relaxed(control, fic->base + AL_FIC_CONTROL);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int al_fic_irq_set_type(struct irq_data *data, unsigned int flow_type)
|
||||||
|
{
|
||||||
|
struct irq_chip_generic *gc = irq_data_get_irq_chip_data(data);
|
||||||
|
struct al_fic *fic = gc->private;
|
||||||
|
enum al_fic_state new_state;
|
||||||
|
int ret = 0;
|
||||||
|
|
||||||
|
irq_gc_lock(gc);
|
||||||
|
|
||||||
|
if (((flow_type & IRQ_TYPE_SENSE_MASK) != IRQ_TYPE_LEVEL_HIGH) &&
|
||||||
|
((flow_type & IRQ_TYPE_SENSE_MASK) != IRQ_TYPE_EDGE_RISING)) {
|
||||||
|
pr_debug("fic doesn't support flow type %d\n", flow_type);
|
||||||
|
ret = -EINVAL;
|
||||||
|
goto err;
|
||||||
|
}
|
||||||
|
|
||||||
|
new_state = (flow_type & IRQ_TYPE_LEVEL_HIGH) ?
|
||||||
|
AL_FIC_CONFIGURED_LEVEL : AL_FIC_CONFIGURED_RISING_EDGE;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* A given FIC instance can be either all level or all edge triggered.
|
||||||
|
* This is generally fixed depending on what pieces of HW it's wired up
|
||||||
|
* to.
|
||||||
|
*
|
||||||
|
* We configure it based on the sensitivity of the first source
|
||||||
|
* being setup, and reject any subsequent attempt at configuring it in a
|
||||||
|
* different way.
|
||||||
|
*/
|
||||||
|
if (fic->state == AL_FIC_UNCONFIGURED) {
|
||||||
|
al_fic_set_trigger(fic, gc, new_state);
|
||||||
|
} else if (fic->state != new_state) {
|
||||||
|
pr_debug("fic %s state already configured to %d\n",
|
||||||
|
fic->name, fic->state);
|
||||||
|
ret = -EINVAL;
|
||||||
|
goto err;
|
||||||
|
}
|
||||||
|
|
||||||
|
err:
|
||||||
|
irq_gc_unlock(gc);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void al_fic_irq_handler(struct irq_desc *desc)
|
||||||
|
{
|
||||||
|
struct al_fic *fic = irq_desc_get_handler_data(desc);
|
||||||
|
struct irq_domain *domain = fic->domain;
|
||||||
|
struct irq_chip *irqchip = irq_desc_get_chip(desc);
|
||||||
|
struct irq_chip_generic *gc = irq_get_domain_generic_chip(domain, 0);
|
||||||
|
unsigned long pending;
|
||||||
|
unsigned int irq;
|
||||||
|
u32 hwirq;
|
||||||
|
|
||||||
|
chained_irq_enter(irqchip, desc);
|
||||||
|
|
||||||
|
pending = readl_relaxed(fic->base + AL_FIC_CAUSE);
|
||||||
|
pending &= ~gc->mask_cache;
|
||||||
|
|
||||||
|
for_each_set_bit(hwirq, &pending, NR_FIC_IRQS) {
|
||||||
|
irq = irq_find_mapping(domain, hwirq);
|
||||||
|
generic_handle_irq(irq);
|
||||||
|
}
|
||||||
|
|
||||||
|
chained_irq_exit(irqchip, desc);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int al_fic_register(struct device_node *node,
|
||||||
|
struct al_fic *fic)
|
||||||
|
{
|
||||||
|
struct irq_chip_generic *gc;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
fic->domain = irq_domain_add_linear(node,
|
||||||
|
NR_FIC_IRQS,
|
||||||
|
&irq_generic_chip_ops,
|
||||||
|
fic);
|
||||||
|
if (!fic->domain) {
|
||||||
|
pr_err("fail to add irq domain\n");
|
||||||
|
return -ENOMEM;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = irq_alloc_domain_generic_chips(fic->domain,
|
||||||
|
NR_FIC_IRQS,
|
||||||
|
1, fic->name,
|
||||||
|
handle_level_irq,
|
||||||
|
0, 0, IRQ_GC_INIT_MASK_CACHE);
|
||||||
|
if (ret) {
|
||||||
|
pr_err("fail to allocate generic chip (%d)\n", ret);
|
||||||
|
goto err_domain_remove;
|
||||||
|
}
|
||||||
|
|
||||||
|
gc = irq_get_domain_generic_chip(fic->domain, 0);
|
||||||
|
gc->reg_base = fic->base;
|
||||||
|
gc->chip_types->regs.mask = AL_FIC_MASK;
|
||||||
|
gc->chip_types->regs.ack = AL_FIC_CAUSE;
|
||||||
|
gc->chip_types->chip.irq_mask = irq_gc_mask_set_bit;
|
||||||
|
gc->chip_types->chip.irq_unmask = irq_gc_mask_clr_bit;
|
||||||
|
gc->chip_types->chip.irq_ack = irq_gc_ack_clr_bit;
|
||||||
|
gc->chip_types->chip.irq_set_type = al_fic_irq_set_type;
|
||||||
|
gc->chip_types->chip.flags = IRQCHIP_SKIP_SET_WAKE;
|
||||||
|
gc->private = fic;
|
||||||
|
|
||||||
|
irq_set_chained_handler_and_data(fic->parent_irq,
|
||||||
|
al_fic_irq_handler,
|
||||||
|
fic);
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
err_domain_remove:
|
||||||
|
irq_domain_remove(fic->domain);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* al_fic_wire_init() - initialize and configure fic in wire mode
|
||||||
|
* @of_node: optional pointer to interrupt controller's device tree node.
|
||||||
|
* @base: mmio to fic register
|
||||||
|
* @name: name of the fic
|
||||||
|
* @parent_irq: interrupt of parent
|
||||||
|
*
|
||||||
|
* This API will configure the fic hardware to to work in wire mode.
|
||||||
|
* In wire mode, fic hardware is generating a wire ("wired") interrupt.
|
||||||
|
* Interrupt can be generated based on positive edge or level - configuration is
|
||||||
|
* to be determined based on connected hardware to this fic.
|
||||||
|
*/
|
||||||
|
static struct al_fic *al_fic_wire_init(struct device_node *node,
|
||||||
|
void __iomem *base,
|
||||||
|
const char *name,
|
||||||
|
unsigned int parent_irq)
|
||||||
|
{
|
||||||
|
struct al_fic *fic;
|
||||||
|
int ret;
|
||||||
|
u32 control = CONTROL_MASK_MSI_X;
|
||||||
|
|
||||||
|
fic = kzalloc(sizeof(*fic), GFP_KERNEL);
|
||||||
|
if (!fic)
|
||||||
|
return ERR_PTR(-ENOMEM);
|
||||||
|
|
||||||
|
fic->base = base;
|
||||||
|
fic->parent_irq = parent_irq;
|
||||||
|
fic->name = name;
|
||||||
|
|
||||||
|
/* mask out all interrupts */
|
||||||
|
writel_relaxed(0xFFFFFFFF, fic->base + AL_FIC_MASK);
|
||||||
|
|
||||||
|
/* clear any pending interrupt */
|
||||||
|
writel_relaxed(0, fic->base + AL_FIC_CAUSE);
|
||||||
|
|
||||||
|
writel_relaxed(control, fic->base + AL_FIC_CONTROL);
|
||||||
|
|
||||||
|
ret = al_fic_register(node, fic);
|
||||||
|
if (ret) {
|
||||||
|
pr_err("fail to register irqchip\n");
|
||||||
|
goto err_free;
|
||||||
|
}
|
||||||
|
|
||||||
|
pr_debug("%s initialized successfully in Legacy mode (parent-irq=%u)\n",
|
||||||
|
fic->name, parent_irq);
|
||||||
|
|
||||||
|
return fic;
|
||||||
|
|
||||||
|
err_free:
|
||||||
|
kfree(fic);
|
||||||
|
return ERR_PTR(ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int __init al_fic_init_dt(struct device_node *node,
|
||||||
|
struct device_node *parent)
|
||||||
|
{
|
||||||
|
int ret;
|
||||||
|
void __iomem *base;
|
||||||
|
unsigned int parent_irq;
|
||||||
|
struct al_fic *fic;
|
||||||
|
|
||||||
|
if (!parent) {
|
||||||
|
pr_err("%s: unsupported - device require a parent\n",
|
||||||
|
node->name);
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
base = of_iomap(node, 0);
|
||||||
|
if (!base) {
|
||||||
|
pr_err("%s: fail to map memory\n", node->name);
|
||||||
|
return -ENOMEM;
|
||||||
|
}
|
||||||
|
|
||||||
|
parent_irq = irq_of_parse_and_map(node, 0);
|
||||||
|
if (!parent_irq) {
|
||||||
|
pr_err("%s: fail to map irq\n", node->name);
|
||||||
|
ret = -EINVAL;
|
||||||
|
goto err_unmap;
|
||||||
|
}
|
||||||
|
|
||||||
|
fic = al_fic_wire_init(node,
|
||||||
|
base,
|
||||||
|
node->name,
|
||||||
|
parent_irq);
|
||||||
|
if (IS_ERR(fic)) {
|
||||||
|
pr_err("%s: fail to initialize irqchip (%lu)\n",
|
||||||
|
node->name,
|
||||||
|
PTR_ERR(fic));
|
||||||
|
ret = PTR_ERR(fic);
|
||||||
|
goto err_irq_dispose;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
err_irq_dispose:
|
||||||
|
irq_dispose_mapping(parent_irq);
|
||||||
|
err_unmap:
|
||||||
|
iounmap(base);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
IRQCHIP_DECLARE(al_fic, "amazon,al-fic", al_fic_init_dt);
|
|
@ -32,8 +32,8 @@ static void __iomem *INTCL_base;
|
||||||
#define INTCG_CIDSTR 0x1000
|
#define INTCG_CIDSTR 0x1000
|
||||||
|
|
||||||
#define INTCL_PICTLR 0x0
|
#define INTCL_PICTLR 0x0
|
||||||
|
#define INTCL_CFGR 0x14
|
||||||
#define INTCL_SIGR 0x60
|
#define INTCL_SIGR 0x60
|
||||||
#define INTCL_HPPIR 0x68
|
|
||||||
#define INTCL_RDYIR 0x6c
|
#define INTCL_RDYIR 0x6c
|
||||||
#define INTCL_SENR 0xa0
|
#define INTCL_SENR 0xa0
|
||||||
#define INTCL_CENR 0xa4
|
#define INTCL_CENR 0xa4
|
||||||
|
@ -41,21 +41,49 @@ static void __iomem *INTCL_base;
|
||||||
|
|
||||||
static DEFINE_PER_CPU(void __iomem *, intcl_reg);
|
static DEFINE_PER_CPU(void __iomem *, intcl_reg);
|
||||||
|
|
||||||
|
static unsigned long *__trigger;
|
||||||
|
|
||||||
|
#define IRQ_OFFSET(irq) ((irq < COMM_IRQ_BASE) ? irq : (irq - COMM_IRQ_BASE))
|
||||||
|
|
||||||
|
#define TRIG_BYTE_OFFSET(i) ((((i) * 2) / 32) * 4)
|
||||||
|
#define TRIG_BIT_OFFSET(i) (((i) * 2) % 32)
|
||||||
|
|
||||||
|
#define TRIG_VAL(trigger, irq) (trigger << TRIG_BIT_OFFSET(IRQ_OFFSET(irq)))
|
||||||
|
#define TRIG_VAL_MSK(irq) (~(3 << TRIG_BIT_OFFSET(IRQ_OFFSET(irq))))
|
||||||
|
|
||||||
|
#define TRIG_BASE(irq) \
|
||||||
|
(TRIG_BYTE_OFFSET(IRQ_OFFSET(irq)) + ((irq < COMM_IRQ_BASE) ? \
|
||||||
|
(this_cpu_read(intcl_reg) + INTCL_CFGR) : (INTCG_base + INTCG_CICFGR)))
|
||||||
|
|
||||||
|
static DEFINE_SPINLOCK(setup_lock);
|
||||||
|
static void setup_trigger(unsigned long irq, unsigned long trigger)
|
||||||
|
{
|
||||||
|
unsigned int tmp;
|
||||||
|
|
||||||
|
spin_lock(&setup_lock);
|
||||||
|
|
||||||
|
/* setup trigger */
|
||||||
|
tmp = readl_relaxed(TRIG_BASE(irq)) & TRIG_VAL_MSK(irq);
|
||||||
|
|
||||||
|
writel_relaxed(tmp | TRIG_VAL(trigger, irq), TRIG_BASE(irq));
|
||||||
|
|
||||||
|
spin_unlock(&setup_lock);
|
||||||
|
}
|
||||||
|
|
||||||
static void csky_mpintc_handler(struct pt_regs *regs)
|
static void csky_mpintc_handler(struct pt_regs *regs)
|
||||||
{
|
{
|
||||||
void __iomem *reg_base = this_cpu_read(intcl_reg);
|
void __iomem *reg_base = this_cpu_read(intcl_reg);
|
||||||
|
|
||||||
do {
|
handle_domain_irq(root_domain,
|
||||||
handle_domain_irq(root_domain,
|
readl_relaxed(reg_base + INTCL_RDYIR), regs);
|
||||||
readl_relaxed(reg_base + INTCL_RDYIR),
|
|
||||||
regs);
|
|
||||||
} while (readl_relaxed(reg_base + INTCL_HPPIR) & BIT(31));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void csky_mpintc_enable(struct irq_data *d)
|
static void csky_mpintc_enable(struct irq_data *d)
|
||||||
{
|
{
|
||||||
void __iomem *reg_base = this_cpu_read(intcl_reg);
|
void __iomem *reg_base = this_cpu_read(intcl_reg);
|
||||||
|
|
||||||
|
setup_trigger(d->hwirq, __trigger[d->hwirq]);
|
||||||
|
|
||||||
writel_relaxed(d->hwirq, reg_base + INTCL_SENR);
|
writel_relaxed(d->hwirq, reg_base + INTCL_SENR);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -73,6 +101,28 @@ static void csky_mpintc_eoi(struct irq_data *d)
|
||||||
writel_relaxed(d->hwirq, reg_base + INTCL_CACR);
|
writel_relaxed(d->hwirq, reg_base + INTCL_CACR);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int csky_mpintc_set_type(struct irq_data *d, unsigned int type)
|
||||||
|
{
|
||||||
|
switch (type & IRQ_TYPE_SENSE_MASK) {
|
||||||
|
case IRQ_TYPE_LEVEL_HIGH:
|
||||||
|
__trigger[d->hwirq] = 0;
|
||||||
|
break;
|
||||||
|
case IRQ_TYPE_LEVEL_LOW:
|
||||||
|
__trigger[d->hwirq] = 1;
|
||||||
|
break;
|
||||||
|
case IRQ_TYPE_EDGE_RISING:
|
||||||
|
__trigger[d->hwirq] = 2;
|
||||||
|
break;
|
||||||
|
case IRQ_TYPE_EDGE_FALLING:
|
||||||
|
__trigger[d->hwirq] = 3;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
#ifdef CONFIG_SMP
|
#ifdef CONFIG_SMP
|
||||||
static int csky_irq_set_affinity(struct irq_data *d,
|
static int csky_irq_set_affinity(struct irq_data *d,
|
||||||
const struct cpumask *mask_val,
|
const struct cpumask *mask_val,
|
||||||
|
@ -116,6 +166,7 @@ static struct irq_chip csky_irq_chip = {
|
||||||
.irq_eoi = csky_mpintc_eoi,
|
.irq_eoi = csky_mpintc_eoi,
|
||||||
.irq_enable = csky_mpintc_enable,
|
.irq_enable = csky_mpintc_enable,
|
||||||
.irq_disable = csky_mpintc_disable,
|
.irq_disable = csky_mpintc_disable,
|
||||||
|
.irq_set_type = csky_mpintc_set_type,
|
||||||
#ifdef CONFIG_SMP
|
#ifdef CONFIG_SMP
|
||||||
.irq_set_affinity = csky_irq_set_affinity,
|
.irq_set_affinity = csky_irq_set_affinity,
|
||||||
#endif
|
#endif
|
||||||
|
@ -136,9 +187,26 @@ static int csky_irqdomain_map(struct irq_domain *d, unsigned int irq,
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int csky_irq_domain_xlate_cells(struct irq_domain *d,
|
||||||
|
struct device_node *ctrlr, const u32 *intspec,
|
||||||
|
unsigned int intsize, unsigned long *out_hwirq,
|
||||||
|
unsigned int *out_type)
|
||||||
|
{
|
||||||
|
if (WARN_ON(intsize < 1))
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
*out_hwirq = intspec[0];
|
||||||
|
if (intsize > 1)
|
||||||
|
*out_type = intspec[1] & IRQ_TYPE_SENSE_MASK;
|
||||||
|
else
|
||||||
|
*out_type = IRQ_TYPE_LEVEL_HIGH;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
static const struct irq_domain_ops csky_irqdomain_ops = {
|
static const struct irq_domain_ops csky_irqdomain_ops = {
|
||||||
.map = csky_irqdomain_map,
|
.map = csky_irqdomain_map,
|
||||||
.xlate = irq_domain_xlate_onecell,
|
.xlate = csky_irq_domain_xlate_cells,
|
||||||
};
|
};
|
||||||
|
|
||||||
#ifdef CONFIG_SMP
|
#ifdef CONFIG_SMP
|
||||||
|
@ -172,6 +240,10 @@ csky_mpintc_init(struct device_node *node, struct device_node *parent)
|
||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
nr_irq = INTC_IRQS;
|
nr_irq = INTC_IRQS;
|
||||||
|
|
||||||
|
__trigger = kcalloc(nr_irq, sizeof(unsigned long), GFP_KERNEL);
|
||||||
|
if (__trigger == NULL)
|
||||||
|
return -ENXIO;
|
||||||
|
|
||||||
if (INTCG_base == NULL) {
|
if (INTCG_base == NULL) {
|
||||||
INTCG_base = ioremap(mfcr("cr<31, 14>"),
|
INTCG_base = ioremap(mfcr("cr<31, 14>"),
|
||||||
INTCL_SIZE*nr_cpu_ids + INTCG_SIZE);
|
INTCL_SIZE*nr_cpu_ids + INTCG_SIZE);
|
||||||
|
|
|
@ -53,6 +53,7 @@
|
||||||
|
|
||||||
/* List of flags for specific v2m implementation */
|
/* List of flags for specific v2m implementation */
|
||||||
#define GICV2M_NEEDS_SPI_OFFSET 0x00000001
|
#define GICV2M_NEEDS_SPI_OFFSET 0x00000001
|
||||||
|
#define GICV2M_GRAVITON_ADDRESS_ONLY 0x00000002
|
||||||
|
|
||||||
static LIST_HEAD(v2m_nodes);
|
static LIST_HEAD(v2m_nodes);
|
||||||
static DEFINE_SPINLOCK(v2m_lock);
|
static DEFINE_SPINLOCK(v2m_lock);
|
||||||
|
@ -95,15 +96,26 @@ static struct msi_domain_info gicv2m_msi_domain_info = {
|
||||||
.chip = &gicv2m_msi_irq_chip,
|
.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)
|
||||||
|
return v2m->res.start | ((hwirq - 32) << 3);
|
||||||
|
else
|
||||||
|
return v2m->res.start + V2M_MSI_SETSPI_NS;
|
||||||
|
}
|
||||||
|
|
||||||
static void gicv2m_compose_msi_msg(struct irq_data *data, struct msi_msg *msg)
|
static void gicv2m_compose_msi_msg(struct irq_data *data, struct msi_msg *msg)
|
||||||
{
|
{
|
||||||
struct v2m_data *v2m = irq_data_get_irq_chip_data(data);
|
struct v2m_data *v2m = irq_data_get_irq_chip_data(data);
|
||||||
phys_addr_t addr = v2m->res.start + V2M_MSI_SETSPI_NS;
|
phys_addr_t addr = gicv2m_get_msi_addr(v2m, data->hwirq);
|
||||||
|
|
||||||
msg->address_hi = upper_32_bits(addr);
|
msg->address_hi = upper_32_bits(addr);
|
||||||
msg->address_lo = lower_32_bits(addr);
|
msg->address_lo = lower_32_bits(addr);
|
||||||
msg->data = data->hwirq;
|
|
||||||
|
|
||||||
|
if (v2m->flags & GICV2M_GRAVITON_ADDRESS_ONLY)
|
||||||
|
msg->data = 0;
|
||||||
|
else
|
||||||
|
msg->data = data->hwirq;
|
||||||
if (v2m->flags & GICV2M_NEEDS_SPI_OFFSET)
|
if (v2m->flags & GICV2M_NEEDS_SPI_OFFSET)
|
||||||
msg->data -= v2m->spi_offset;
|
msg->data -= v2m->spi_offset;
|
||||||
|
|
||||||
|
@ -185,7 +197,7 @@ static int gicv2m_irq_domain_alloc(struct irq_domain *domain, unsigned int virq,
|
||||||
hwirq = v2m->spi_start + offset;
|
hwirq = v2m->spi_start + offset;
|
||||||
|
|
||||||
err = iommu_dma_prepare_msi(info->desc,
|
err = iommu_dma_prepare_msi(info->desc,
|
||||||
v2m->res.start + V2M_MSI_SETSPI_NS);
|
gicv2m_get_msi_addr(v2m, hwirq));
|
||||||
if (err)
|
if (err)
|
||||||
return err;
|
return err;
|
||||||
|
|
||||||
|
@ -304,7 +316,7 @@ static int gicv2m_allocate_domains(struct irq_domain *parent)
|
||||||
|
|
||||||
static int __init gicv2m_init_one(struct fwnode_handle *fwnode,
|
static int __init gicv2m_init_one(struct fwnode_handle *fwnode,
|
||||||
u32 spi_start, u32 nr_spis,
|
u32 spi_start, u32 nr_spis,
|
||||||
struct resource *res)
|
struct resource *res, u32 flags)
|
||||||
{
|
{
|
||||||
int ret;
|
int ret;
|
||||||
struct v2m_data *v2m;
|
struct v2m_data *v2m;
|
||||||
|
@ -317,6 +329,7 @@ static int __init gicv2m_init_one(struct fwnode_handle *fwnode,
|
||||||
|
|
||||||
INIT_LIST_HEAD(&v2m->entry);
|
INIT_LIST_HEAD(&v2m->entry);
|
||||||
v2m->fwnode = fwnode;
|
v2m->fwnode = fwnode;
|
||||||
|
v2m->flags = flags;
|
||||||
|
|
||||||
memcpy(&v2m->res, res, sizeof(struct resource));
|
memcpy(&v2m->res, res, sizeof(struct resource));
|
||||||
|
|
||||||
|
@ -331,7 +344,14 @@ static int __init gicv2m_init_one(struct fwnode_handle *fwnode,
|
||||||
v2m->spi_start = spi_start;
|
v2m->spi_start = spi_start;
|
||||||
v2m->nr_spis = nr_spis;
|
v2m->nr_spis = nr_spis;
|
||||||
} else {
|
} else {
|
||||||
u32 typer = readl_relaxed(v2m->base + V2M_MSI_TYPER);
|
u32 typer;
|
||||||
|
|
||||||
|
/* Graviton should always have explicit spi_start/nr_spis */
|
||||||
|
if (v2m->flags & GICV2M_GRAVITON_ADDRESS_ONLY) {
|
||||||
|
ret = -EINVAL;
|
||||||
|
goto err_iounmap;
|
||||||
|
}
|
||||||
|
typer = readl_relaxed(v2m->base + V2M_MSI_TYPER);
|
||||||
|
|
||||||
v2m->spi_start = V2M_MSI_TYPER_BASE_SPI(typer);
|
v2m->spi_start = V2M_MSI_TYPER_BASE_SPI(typer);
|
||||||
v2m->nr_spis = V2M_MSI_TYPER_NUM_SPI(typer);
|
v2m->nr_spis = V2M_MSI_TYPER_NUM_SPI(typer);
|
||||||
|
@ -352,18 +372,21 @@ static int __init gicv2m_init_one(struct fwnode_handle *fwnode,
|
||||||
*
|
*
|
||||||
* Broadom NS2 GICv2m implementation has an erratum where the MSI data
|
* Broadom NS2 GICv2m implementation has an erratum where the MSI data
|
||||||
* is 'spi_number - 32'
|
* is 'spi_number - 32'
|
||||||
|
*
|
||||||
|
* Reading that register fails on the Graviton implementation
|
||||||
*/
|
*/
|
||||||
switch (readl_relaxed(v2m->base + V2M_MSI_IIDR)) {
|
if (!(v2m->flags & GICV2M_GRAVITON_ADDRESS_ONLY)) {
|
||||||
case XGENE_GICV2M_MSI_IIDR:
|
switch (readl_relaxed(v2m->base + V2M_MSI_IIDR)) {
|
||||||
v2m->flags |= GICV2M_NEEDS_SPI_OFFSET;
|
case XGENE_GICV2M_MSI_IIDR:
|
||||||
v2m->spi_offset = v2m->spi_start;
|
v2m->flags |= GICV2M_NEEDS_SPI_OFFSET;
|
||||||
break;
|
v2m->spi_offset = v2m->spi_start;
|
||||||
case BCM_NS2_GICV2M_MSI_IIDR:
|
break;
|
||||||
v2m->flags |= GICV2M_NEEDS_SPI_OFFSET;
|
case BCM_NS2_GICV2M_MSI_IIDR:
|
||||||
v2m->spi_offset = 32;
|
v2m->flags |= GICV2M_NEEDS_SPI_OFFSET;
|
||||||
break;
|
v2m->spi_offset = 32;
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
v2m->bm = kcalloc(BITS_TO_LONGS(v2m->nr_spis), sizeof(long),
|
v2m->bm = kcalloc(BITS_TO_LONGS(v2m->nr_spis), sizeof(long),
|
||||||
GFP_KERNEL);
|
GFP_KERNEL);
|
||||||
if (!v2m->bm) {
|
if (!v2m->bm) {
|
||||||
|
@ -416,7 +439,8 @@ static int __init gicv2m_of_init(struct fwnode_handle *parent_handle,
|
||||||
pr_info("DT overriding V2M MSI_TYPER (base:%u, num:%u)\n",
|
pr_info("DT overriding V2M MSI_TYPER (base:%u, num:%u)\n",
|
||||||
spi_start, nr_spis);
|
spi_start, nr_spis);
|
||||||
|
|
||||||
ret = gicv2m_init_one(&child->fwnode, spi_start, nr_spis, &res);
|
ret = gicv2m_init_one(&child->fwnode, spi_start, nr_spis,
|
||||||
|
&res, 0);
|
||||||
if (ret) {
|
if (ret) {
|
||||||
of_node_put(child);
|
of_node_put(child);
|
||||||
break;
|
break;
|
||||||
|
@ -448,6 +472,25 @@ static struct fwnode_handle *gicv2m_get_fwnode(struct device *dev)
|
||||||
return data->fwnode;
|
return data->fwnode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool acpi_check_amazon_graviton_quirks(void)
|
||||||
|
{
|
||||||
|
static struct acpi_table_madt *madt;
|
||||||
|
acpi_status status;
|
||||||
|
bool rc = false;
|
||||||
|
|
||||||
|
#define ACPI_AMZN_OEM_ID "AMAZON"
|
||||||
|
|
||||||
|
status = acpi_get_table(ACPI_SIG_MADT, 0,
|
||||||
|
(struct acpi_table_header **)&madt);
|
||||||
|
|
||||||
|
if (ACPI_FAILURE(status) || !madt)
|
||||||
|
return rc;
|
||||||
|
rc = !memcmp(madt->header.oem_id, ACPI_AMZN_OEM_ID, ACPI_OEM_ID_SIZE);
|
||||||
|
acpi_put_table((struct acpi_table_header *)madt);
|
||||||
|
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
static int __init
|
static int __init
|
||||||
acpi_parse_madt_msi(union acpi_subtable_headers *header,
|
acpi_parse_madt_msi(union acpi_subtable_headers *header,
|
||||||
const unsigned long end)
|
const unsigned long end)
|
||||||
|
@ -457,6 +500,7 @@ acpi_parse_madt_msi(union acpi_subtable_headers *header,
|
||||||
u32 spi_start = 0, nr_spis = 0;
|
u32 spi_start = 0, nr_spis = 0;
|
||||||
struct acpi_madt_generic_msi_frame *m;
|
struct acpi_madt_generic_msi_frame *m;
|
||||||
struct fwnode_handle *fwnode;
|
struct fwnode_handle *fwnode;
|
||||||
|
u32 flags = 0;
|
||||||
|
|
||||||
m = (struct acpi_madt_generic_msi_frame *)header;
|
m = (struct acpi_madt_generic_msi_frame *)header;
|
||||||
if (BAD_MADT_ENTRY(m, end))
|
if (BAD_MADT_ENTRY(m, end))
|
||||||
|
@ -466,6 +510,13 @@ acpi_parse_madt_msi(union acpi_subtable_headers *header,
|
||||||
res.end = m->base_address + SZ_4K - 1;
|
res.end = m->base_address + SZ_4K - 1;
|
||||||
res.flags = IORESOURCE_MEM;
|
res.flags = IORESOURCE_MEM;
|
||||||
|
|
||||||
|
if (acpi_check_amazon_graviton_quirks()) {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
if (m->flags & ACPI_MADT_OVERRIDE_SPI_VALUES) {
|
if (m->flags & ACPI_MADT_OVERRIDE_SPI_VALUES) {
|
||||||
spi_start = m->spi_base;
|
spi_start = m->spi_base;
|
||||||
nr_spis = m->spi_count;
|
nr_spis = m->spi_count;
|
||||||
|
@ -480,7 +531,7 @@ acpi_parse_madt_msi(union acpi_subtable_headers *header,
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
}
|
}
|
||||||
|
|
||||||
ret = gicv2m_init_one(fwnode, spi_start, nr_spis, &res);
|
ret = gicv2m_init_one(fwnode, spi_start, nr_spis, &res, flags);
|
||||||
if (ret)
|
if (ret)
|
||||||
irq_domain_free_fwnode(fwnode);
|
irq_domain_free_fwnode(fwnode);
|
||||||
|
|
||||||
|
|
|
@ -1339,6 +1339,9 @@ static int __init gic_init_bases(void __iomem *dist_base,
|
||||||
if (gic_dist_supports_lpis()) {
|
if (gic_dist_supports_lpis()) {
|
||||||
its_init(handle, &gic_data.rdists, gic_data.domain);
|
its_init(handle, &gic_data.rdists, gic_data.domain);
|
||||||
its_cpu_init();
|
its_cpu_init();
|
||||||
|
} else {
|
||||||
|
if (IS_ENABLED(CONFIG_ARM_GIC_V2M))
|
||||||
|
gicv2m_init(handle, gic_data.domain);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (gic_prio_masking_enabled()) {
|
if (gic_prio_masking_enabled()) {
|
||||||
|
|
|
@ -344,8 +344,7 @@ static int mbigen_device_probe(struct platform_device *pdev)
|
||||||
err = -EINVAL;
|
err = -EINVAL;
|
||||||
|
|
||||||
if (err) {
|
if (err) {
|
||||||
dev_err(&pdev->dev, "Failed to create mbi-gen@%p irqdomain",
|
dev_err(&pdev->dev, "Failed to create mbi-gen irqdomain\n");
|
||||||
mgn_chip->base);
|
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -60,6 +60,7 @@ static const struct of_device_id meson_irq_gpio_matches[] = {
|
||||||
{ .compatible = "amlogic,meson-gxbb-gpio-intc", .data = &gxbb_params },
|
{ .compatible = "amlogic,meson-gxbb-gpio-intc", .data = &gxbb_params },
|
||||||
{ .compatible = "amlogic,meson-gxl-gpio-intc", .data = &gxl_params },
|
{ .compatible = "amlogic,meson-gxl-gpio-intc", .data = &gxl_params },
|
||||||
{ .compatible = "amlogic,meson-axg-gpio-intc", .data = &axg_params },
|
{ .compatible = "amlogic,meson-axg-gpio-intc", .data = &axg_params },
|
||||||
|
{ .compatible = "amlogic,meson-g12a-gpio-intc", .data = &axg_params },
|
||||||
{ }
|
{ }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -508,7 +508,8 @@ static int intc_irqpin_probe(struct platform_device *pdev)
|
||||||
}
|
}
|
||||||
|
|
||||||
irq_chip = &p->irq_chip;
|
irq_chip = &p->irq_chip;
|
||||||
irq_chip->name = name;
|
irq_chip->name = "intc-irqpin";
|
||||||
|
irq_chip->parent_device = dev;
|
||||||
irq_chip->irq_mask = disable_fn;
|
irq_chip->irq_mask = disable_fn;
|
||||||
irq_chip->irq_unmask = enable_fn;
|
irq_chip->irq_unmask = enable_fn;
|
||||||
irq_chip->irq_set_type = intc_irqpin_irq_set_type;
|
irq_chip->irq_set_type = intc_irqpin_irq_set_type;
|
||||||
|
|
|
@ -7,7 +7,6 @@
|
||||||
|
|
||||||
#include <linux/init.h>
|
#include <linux/init.h>
|
||||||
#include <linux/platform_device.h>
|
#include <linux/platform_device.h>
|
||||||
#include <linux/spinlock.h>
|
|
||||||
#include <linux/interrupt.h>
|
#include <linux/interrupt.h>
|
||||||
#include <linux/ioport.h>
|
#include <linux/ioport.h>
|
||||||
#include <linux/io.h>
|
#include <linux/io.h>
|
||||||
|
@ -48,7 +47,7 @@ struct irqc_priv {
|
||||||
void __iomem *cpu_int_base;
|
void __iomem *cpu_int_base;
|
||||||
struct irqc_irq irq[IRQC_IRQ_MAX];
|
struct irqc_irq irq[IRQC_IRQ_MAX];
|
||||||
unsigned int number_of_irqs;
|
unsigned int number_of_irqs;
|
||||||
struct platform_device *pdev;
|
struct device *dev;
|
||||||
struct irq_chip_generic *gc;
|
struct irq_chip_generic *gc;
|
||||||
struct irq_domain *irq_domain;
|
struct irq_domain *irq_domain;
|
||||||
atomic_t wakeup_path;
|
atomic_t wakeup_path;
|
||||||
|
@ -61,8 +60,7 @@ static struct irqc_priv *irq_data_to_priv(struct irq_data *data)
|
||||||
|
|
||||||
static void irqc_dbg(struct irqc_irq *i, char *str)
|
static void irqc_dbg(struct irqc_irq *i, char *str)
|
||||||
{
|
{
|
||||||
dev_dbg(&i->p->pdev->dev, "%s (%d:%d)\n",
|
dev_dbg(i->p->dev, "%s (%d:%d)\n", str, i->requested_irq, i->hw_irq);
|
||||||
str, i->requested_irq, i->hw_irq);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static unsigned char irqc_sense[IRQ_TYPE_SENSE_MASK + 1] = {
|
static unsigned char irqc_sense[IRQ_TYPE_SENSE_MASK + 1] = {
|
||||||
|
@ -125,33 +123,22 @@ static irqreturn_t irqc_irq_handler(int irq, void *dev_id)
|
||||||
|
|
||||||
static int irqc_probe(struct platform_device *pdev)
|
static int irqc_probe(struct platform_device *pdev)
|
||||||
{
|
{
|
||||||
|
struct device *dev = &pdev->dev;
|
||||||
|
const char *name = dev_name(dev);
|
||||||
struct irqc_priv *p;
|
struct irqc_priv *p;
|
||||||
struct resource *io;
|
|
||||||
struct resource *irq;
|
struct resource *irq;
|
||||||
const char *name = dev_name(&pdev->dev);
|
|
||||||
int ret;
|
int ret;
|
||||||
int k;
|
int k;
|
||||||
|
|
||||||
p = kzalloc(sizeof(*p), GFP_KERNEL);
|
p = devm_kzalloc(dev, sizeof(*p), GFP_KERNEL);
|
||||||
if (!p) {
|
if (!p)
|
||||||
dev_err(&pdev->dev, "failed to allocate driver data\n");
|
return -ENOMEM;
|
||||||
ret = -ENOMEM;
|
|
||||||
goto err0;
|
|
||||||
}
|
|
||||||
|
|
||||||
p->pdev = pdev;
|
p->dev = dev;
|
||||||
platform_set_drvdata(pdev, p);
|
platform_set_drvdata(pdev, p);
|
||||||
|
|
||||||
pm_runtime_enable(&pdev->dev);
|
pm_runtime_enable(dev);
|
||||||
pm_runtime_get_sync(&pdev->dev);
|
pm_runtime_get_sync(dev);
|
||||||
|
|
||||||
/* get hold of manadatory IOMEM */
|
|
||||||
io = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
|
||||||
if (!io) {
|
|
||||||
dev_err(&pdev->dev, "not enough IOMEM resources\n");
|
|
||||||
ret = -EINVAL;
|
|
||||||
goto err1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* allow any number of IRQs between 1 and IRQC_IRQ_MAX */
|
/* allow any number of IRQs between 1 and IRQC_IRQ_MAX */
|
||||||
for (k = 0; k < IRQC_IRQ_MAX; k++) {
|
for (k = 0; k < IRQC_IRQ_MAX; k++) {
|
||||||
|
@ -166,42 +153,41 @@ static int irqc_probe(struct platform_device *pdev)
|
||||||
|
|
||||||
p->number_of_irqs = k;
|
p->number_of_irqs = k;
|
||||||
if (p->number_of_irqs < 1) {
|
if (p->number_of_irqs < 1) {
|
||||||
dev_err(&pdev->dev, "not enough IRQ resources\n");
|
dev_err(dev, "not enough IRQ resources\n");
|
||||||
ret = -EINVAL;
|
ret = -EINVAL;
|
||||||
goto err1;
|
goto err_runtime_pm_disable;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ioremap IOMEM and setup read/write callbacks */
|
/* ioremap IOMEM and setup read/write callbacks */
|
||||||
p->iomem = ioremap_nocache(io->start, resource_size(io));
|
p->iomem = devm_platform_ioremap_resource(pdev, 0);
|
||||||
if (!p->iomem) {
|
if (IS_ERR(p->iomem)) {
|
||||||
dev_err(&pdev->dev, "failed to remap IOMEM\n");
|
ret = PTR_ERR(p->iomem);
|
||||||
ret = -ENXIO;
|
goto err_runtime_pm_disable;
|
||||||
goto err2;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
p->cpu_int_base = p->iomem + IRQC_INT_CPU_BASE(0); /* SYS-SPI */
|
p->cpu_int_base = p->iomem + IRQC_INT_CPU_BASE(0); /* SYS-SPI */
|
||||||
|
|
||||||
p->irq_domain = irq_domain_add_linear(pdev->dev.of_node,
|
p->irq_domain = irq_domain_add_linear(dev->of_node, p->number_of_irqs,
|
||||||
p->number_of_irqs,
|
|
||||||
&irq_generic_chip_ops, p);
|
&irq_generic_chip_ops, p);
|
||||||
if (!p->irq_domain) {
|
if (!p->irq_domain) {
|
||||||
ret = -ENXIO;
|
ret = -ENXIO;
|
||||||
dev_err(&pdev->dev, "cannot initialize irq domain\n");
|
dev_err(dev, "cannot initialize irq domain\n");
|
||||||
goto err2;
|
goto err_runtime_pm_disable;
|
||||||
}
|
}
|
||||||
|
|
||||||
ret = irq_alloc_domain_generic_chips(p->irq_domain, p->number_of_irqs,
|
ret = irq_alloc_domain_generic_chips(p->irq_domain, p->number_of_irqs,
|
||||||
1, name, handle_level_irq,
|
1, "irqc", handle_level_irq,
|
||||||
0, 0, IRQ_GC_INIT_NESTED_LOCK);
|
0, 0, IRQ_GC_INIT_NESTED_LOCK);
|
||||||
if (ret) {
|
if (ret) {
|
||||||
dev_err(&pdev->dev, "cannot allocate generic chip\n");
|
dev_err(dev, "cannot allocate generic chip\n");
|
||||||
goto err3;
|
goto err_remove_domain;
|
||||||
}
|
}
|
||||||
|
|
||||||
p->gc = irq_get_domain_generic_chip(p->irq_domain, 0);
|
p->gc = irq_get_domain_generic_chip(p->irq_domain, 0);
|
||||||
p->gc->reg_base = p->cpu_int_base;
|
p->gc->reg_base = p->cpu_int_base;
|
||||||
p->gc->chip_types[0].regs.enable = IRQC_EN_SET;
|
p->gc->chip_types[0].regs.enable = IRQC_EN_SET;
|
||||||
p->gc->chip_types[0].regs.disable = IRQC_EN_STS;
|
p->gc->chip_types[0].regs.disable = IRQC_EN_STS;
|
||||||
|
p->gc->chip_types[0].chip.parent_device = dev;
|
||||||
p->gc->chip_types[0].chip.irq_mask = irq_gc_mask_disable_reg;
|
p->gc->chip_types[0].chip.irq_mask = irq_gc_mask_disable_reg;
|
||||||
p->gc->chip_types[0].chip.irq_unmask = irq_gc_unmask_enable_reg;
|
p->gc->chip_types[0].chip.irq_unmask = irq_gc_unmask_enable_reg;
|
||||||
p->gc->chip_types[0].chip.irq_set_type = irqc_irq_set_type;
|
p->gc->chip_types[0].chip.irq_set_type = irqc_irq_set_type;
|
||||||
|
@ -210,46 +196,33 @@ static int irqc_probe(struct platform_device *pdev)
|
||||||
|
|
||||||
/* request interrupts one by one */
|
/* request interrupts one by one */
|
||||||
for (k = 0; k < p->number_of_irqs; k++) {
|
for (k = 0; k < p->number_of_irqs; k++) {
|
||||||
if (request_irq(p->irq[k].requested_irq, irqc_irq_handler,
|
if (devm_request_irq(dev, p->irq[k].requested_irq,
|
||||||
0, name, &p->irq[k])) {
|
irqc_irq_handler, 0, name, &p->irq[k])) {
|
||||||
dev_err(&pdev->dev, "failed to request IRQ\n");
|
dev_err(dev, "failed to request IRQ\n");
|
||||||
ret = -ENOENT;
|
ret = -ENOENT;
|
||||||
goto err4;
|
goto err_remove_domain;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dev_info(&pdev->dev, "driving %d irqs\n", p->number_of_irqs);
|
dev_info(dev, "driving %d irqs\n", p->number_of_irqs);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
err4:
|
|
||||||
while (--k >= 0)
|
|
||||||
free_irq(p->irq[k].requested_irq, &p->irq[k]);
|
|
||||||
|
|
||||||
err3:
|
err_remove_domain:
|
||||||
irq_domain_remove(p->irq_domain);
|
irq_domain_remove(p->irq_domain);
|
||||||
err2:
|
err_runtime_pm_disable:
|
||||||
iounmap(p->iomem);
|
pm_runtime_put(dev);
|
||||||
err1:
|
pm_runtime_disable(dev);
|
||||||
pm_runtime_put(&pdev->dev);
|
|
||||||
pm_runtime_disable(&pdev->dev);
|
|
||||||
kfree(p);
|
|
||||||
err0:
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int irqc_remove(struct platform_device *pdev)
|
static int irqc_remove(struct platform_device *pdev)
|
||||||
{
|
{
|
||||||
struct irqc_priv *p = platform_get_drvdata(pdev);
|
struct irqc_priv *p = platform_get_drvdata(pdev);
|
||||||
int k;
|
|
||||||
|
|
||||||
for (k = 0; k < p->number_of_irqs; k++)
|
|
||||||
free_irq(p->irq[k].requested_irq, &p->irq[k]);
|
|
||||||
|
|
||||||
irq_domain_remove(p->irq_domain);
|
irq_domain_remove(p->irq_domain);
|
||||||
iounmap(p->iomem);
|
|
||||||
pm_runtime_put(&pdev->dev);
|
pm_runtime_put(&pdev->dev);
|
||||||
pm_runtime_disable(&pdev->dev);
|
pm_runtime_disable(&pdev->dev);
|
||||||
kfree(p);
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,283 @@
|
||||||
|
// SPDX-License-Identifier: GPL-2.0
|
||||||
|
/*
|
||||||
|
* Renesas RZ/A1 IRQC Driver
|
||||||
|
*
|
||||||
|
* Copyright (C) 2019 Glider bvba
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <linux/err.h>
|
||||||
|
#include <linux/init.h>
|
||||||
|
#include <linux/interrupt.h>
|
||||||
|
#include <linux/io.h>
|
||||||
|
#include <linux/irqdomain.h>
|
||||||
|
#include <linux/irq.h>
|
||||||
|
#include <linux/module.h>
|
||||||
|
#include <linux/of_irq.h>
|
||||||
|
#include <linux/platform_device.h>
|
||||||
|
#include <linux/slab.h>
|
||||||
|
|
||||||
|
#include <dt-bindings/interrupt-controller/arm-gic.h>
|
||||||
|
|
||||||
|
#define IRQC_NUM_IRQ 8
|
||||||
|
|
||||||
|
#define ICR0 0 /* Interrupt Control Register 0 */
|
||||||
|
|
||||||
|
#define ICR0_NMIL BIT(15) /* NMI Input Level (0=low, 1=high) */
|
||||||
|
#define ICR0_NMIE BIT(8) /* Edge Select (0=falling, 1=rising) */
|
||||||
|
#define ICR0_NMIF BIT(1) /* NMI Interrupt Request */
|
||||||
|
|
||||||
|
#define ICR1 2 /* Interrupt Control Register 1 */
|
||||||
|
|
||||||
|
#define ICR1_IRQS(n, sense) ((sense) << ((n) * 2)) /* IRQ Sense Select */
|
||||||
|
#define ICR1_IRQS_LEVEL_LOW 0
|
||||||
|
#define ICR1_IRQS_EDGE_FALLING 1
|
||||||
|
#define ICR1_IRQS_EDGE_RISING 2
|
||||||
|
#define ICR1_IRQS_EDGE_BOTH 3
|
||||||
|
#define ICR1_IRQS_MASK(n) ICR1_IRQS((n), 3)
|
||||||
|
|
||||||
|
#define IRQRR 4 /* IRQ Interrupt Request Register */
|
||||||
|
|
||||||
|
|
||||||
|
struct rza1_irqc_priv {
|
||||||
|
struct device *dev;
|
||||||
|
void __iomem *base;
|
||||||
|
struct irq_chip chip;
|
||||||
|
struct irq_domain *irq_domain;
|
||||||
|
struct of_phandle_args map[IRQC_NUM_IRQ];
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct rza1_irqc_priv *irq_data_to_priv(struct irq_data *data)
|
||||||
|
{
|
||||||
|
return data->domain->host_data;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void rza1_irqc_eoi(struct irq_data *d)
|
||||||
|
{
|
||||||
|
struct rza1_irqc_priv *priv = irq_data_to_priv(d);
|
||||||
|
u16 bit = BIT(irqd_to_hwirq(d));
|
||||||
|
u16 tmp;
|
||||||
|
|
||||||
|
tmp = readw_relaxed(priv->base + IRQRR);
|
||||||
|
if (tmp & bit)
|
||||||
|
writew_relaxed(GENMASK(IRQC_NUM_IRQ - 1, 0) & ~bit,
|
||||||
|
priv->base + IRQRR);
|
||||||
|
|
||||||
|
irq_chip_eoi_parent(d);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int rza1_irqc_set_type(struct irq_data *d, unsigned int type)
|
||||||
|
{
|
||||||
|
struct rza1_irqc_priv *priv = irq_data_to_priv(d);
|
||||||
|
unsigned int hw_irq = irqd_to_hwirq(d);
|
||||||
|
u16 sense, tmp;
|
||||||
|
|
||||||
|
switch (type & IRQ_TYPE_SENSE_MASK) {
|
||||||
|
case IRQ_TYPE_LEVEL_LOW:
|
||||||
|
sense = ICR1_IRQS_LEVEL_LOW;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case IRQ_TYPE_EDGE_FALLING:
|
||||||
|
sense = ICR1_IRQS_EDGE_FALLING;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case IRQ_TYPE_EDGE_RISING:
|
||||||
|
sense = ICR1_IRQS_EDGE_RISING;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case IRQ_TYPE_EDGE_BOTH:
|
||||||
|
sense = ICR1_IRQS_EDGE_BOTH;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
tmp = readw_relaxed(priv->base + ICR1);
|
||||||
|
tmp &= ~ICR1_IRQS_MASK(hw_irq);
|
||||||
|
tmp |= ICR1_IRQS(hw_irq, sense);
|
||||||
|
writew_relaxed(tmp, priv->base + ICR1);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int rza1_irqc_alloc(struct irq_domain *domain, unsigned int virq,
|
||||||
|
unsigned int nr_irqs, void *arg)
|
||||||
|
{
|
||||||
|
struct rza1_irqc_priv *priv = domain->host_data;
|
||||||
|
struct irq_fwspec *fwspec = arg;
|
||||||
|
unsigned int hwirq = fwspec->param[0];
|
||||||
|
struct irq_fwspec spec;
|
||||||
|
unsigned int i;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
ret = irq_domain_set_hwirq_and_chip(domain, virq, hwirq, &priv->chip,
|
||||||
|
priv);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
spec.fwnode = &priv->dev->of_node->fwnode;
|
||||||
|
spec.param_count = priv->map[hwirq].args_count;
|
||||||
|
for (i = 0; i < spec.param_count; i++)
|
||||||
|
spec.param[i] = priv->map[hwirq].args[i];
|
||||||
|
|
||||||
|
return irq_domain_alloc_irqs_parent(domain, virq, nr_irqs, &spec);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int rza1_irqc_translate(struct irq_domain *domain,
|
||||||
|
struct irq_fwspec *fwspec, unsigned long *hwirq,
|
||||||
|
unsigned int *type)
|
||||||
|
{
|
||||||
|
if (fwspec->param_count != 2 || fwspec->param[0] >= IRQC_NUM_IRQ)
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
*hwirq = fwspec->param[0];
|
||||||
|
*type = fwspec->param[1];
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct irq_domain_ops rza1_irqc_domain_ops = {
|
||||||
|
.alloc = rza1_irqc_alloc,
|
||||||
|
.translate = rza1_irqc_translate,
|
||||||
|
};
|
||||||
|
|
||||||
|
static int rza1_irqc_parse_map(struct rza1_irqc_priv *priv,
|
||||||
|
struct device_node *gic_node)
|
||||||
|
{
|
||||||
|
unsigned int imaplen, i, j, ret;
|
||||||
|
struct device *dev = priv->dev;
|
||||||
|
struct device_node *ipar;
|
||||||
|
const __be32 *imap;
|
||||||
|
u32 intsize;
|
||||||
|
|
||||||
|
imap = of_get_property(dev->of_node, "interrupt-map", &imaplen);
|
||||||
|
if (!imap)
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
for (i = 0; i < IRQC_NUM_IRQ; i++) {
|
||||||
|
if (imaplen < 3)
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
/* Check interrupt number, ignore sense */
|
||||||
|
if (be32_to_cpup(imap) != i)
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
ipar = of_find_node_by_phandle(be32_to_cpup(imap + 2));
|
||||||
|
if (ipar != gic_node) {
|
||||||
|
of_node_put(ipar);
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
imap += 3;
|
||||||
|
imaplen -= 3;
|
||||||
|
|
||||||
|
ret = of_property_read_u32(ipar, "#interrupt-cells", &intsize);
|
||||||
|
of_node_put(ipar);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
if (imaplen < intsize)
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
priv->map[i].args_count = intsize;
|
||||||
|
for (j = 0; j < intsize; j++)
|
||||||
|
priv->map[i].args[j] = be32_to_cpup(imap++);
|
||||||
|
|
||||||
|
imaplen -= intsize;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int rza1_irqc_probe(struct platform_device *pdev)
|
||||||
|
{
|
||||||
|
struct device *dev = &pdev->dev;
|
||||||
|
struct device_node *np = dev->of_node;
|
||||||
|
struct irq_domain *parent = NULL;
|
||||||
|
struct device_node *gic_node;
|
||||||
|
struct rza1_irqc_priv *priv;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
|
||||||
|
if (!priv)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
platform_set_drvdata(pdev, priv);
|
||||||
|
priv->dev = dev;
|
||||||
|
|
||||||
|
priv->base = devm_platform_ioremap_resource(pdev, 0);
|
||||||
|
if (IS_ERR(priv->base))
|
||||||
|
return PTR_ERR(priv->base);
|
||||||
|
|
||||||
|
gic_node = of_irq_find_parent(np);
|
||||||
|
if (gic_node) {
|
||||||
|
parent = irq_find_host(gic_node);
|
||||||
|
of_node_put(gic_node);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!parent) {
|
||||||
|
dev_err(dev, "cannot find parent domain\n");
|
||||||
|
return -ENODEV;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = rza1_irqc_parse_map(priv, gic_node);
|
||||||
|
if (ret) {
|
||||||
|
dev_err(dev, "cannot parse %s: %d\n", "interrupt-map", ret);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
priv->chip.name = "rza1-irqc",
|
||||||
|
priv->chip.irq_mask = irq_chip_mask_parent,
|
||||||
|
priv->chip.irq_unmask = irq_chip_unmask_parent,
|
||||||
|
priv->chip.irq_eoi = rza1_irqc_eoi,
|
||||||
|
priv->chip.irq_retrigger = irq_chip_retrigger_hierarchy,
|
||||||
|
priv->chip.irq_set_type = rza1_irqc_set_type,
|
||||||
|
priv->chip.flags = IRQCHIP_MASK_ON_SUSPEND | IRQCHIP_SKIP_SET_WAKE;
|
||||||
|
|
||||||
|
priv->irq_domain = irq_domain_add_hierarchy(parent, 0, IRQC_NUM_IRQ,
|
||||||
|
np, &rza1_irqc_domain_ops,
|
||||||
|
priv);
|
||||||
|
if (!priv->irq_domain) {
|
||||||
|
dev_err(dev, "cannot initialize irq domain\n");
|
||||||
|
return -ENOMEM;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int rza1_irqc_remove(struct platform_device *pdev)
|
||||||
|
{
|
||||||
|
struct rza1_irqc_priv *priv = platform_get_drvdata(pdev);
|
||||||
|
|
||||||
|
irq_domain_remove(priv->irq_domain);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct of_device_id rza1_irqc_dt_ids[] = {
|
||||||
|
{ .compatible = "renesas,rza1-irqc" },
|
||||||
|
{},
|
||||||
|
};
|
||||||
|
MODULE_DEVICE_TABLE(of, rza1_irqc_dt_ids);
|
||||||
|
|
||||||
|
static struct platform_driver rza1_irqc_device_driver = {
|
||||||
|
.probe = rza1_irqc_probe,
|
||||||
|
.remove = rza1_irqc_remove,
|
||||||
|
.driver = {
|
||||||
|
.name = "renesas_rza1_irqc",
|
||||||
|
.of_match_table = rza1_irqc_dt_ids,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static int __init rza1_irqc_init(void)
|
||||||
|
{
|
||||||
|
return platform_driver_register(&rza1_irqc_device_driver);
|
||||||
|
}
|
||||||
|
postcore_initcall(rza1_irqc_init);
|
||||||
|
|
||||||
|
static void __exit rza1_irqc_exit(void)
|
||||||
|
{
|
||||||
|
platform_driver_unregister(&rza1_irqc_device_driver);
|
||||||
|
}
|
||||||
|
module_exit(rza1_irqc_exit);
|
||||||
|
|
||||||
|
MODULE_AUTHOR("Geert Uytterhoeven <geert+renesas@glider.be>");
|
||||||
|
MODULE_DESCRIPTION("Renesas RZ/A1 IRQC Driver");
|
||||||
|
MODULE_LICENSE("GPL v2");
|
|
@ -2,7 +2,7 @@
|
||||||
/*
|
/*
|
||||||
* Driver for Socionext External Interrupt Unit (EXIU)
|
* Driver for Socionext External Interrupt Unit (EXIU)
|
||||||
*
|
*
|
||||||
* Copyright (c) 2017 Linaro, Ltd. <ard.biesheuvel@linaro.org>
|
* Copyright (c) 2017-2019 Linaro, Ltd. <ard.biesheuvel@linaro.org>
|
||||||
*
|
*
|
||||||
* Based on irq-tegra.c:
|
* Based on irq-tegra.c:
|
||||||
* Copyright (C) 2011 Google, Inc.
|
* Copyright (C) 2011 Google, Inc.
|
||||||
|
@ -17,6 +17,7 @@
|
||||||
#include <linux/of.h>
|
#include <linux/of.h>
|
||||||
#include <linux/of_address.h>
|
#include <linux/of_address.h>
|
||||||
#include <linux/of_irq.h>
|
#include <linux/of_irq.h>
|
||||||
|
#include <linux/platform_device.h>
|
||||||
|
|
||||||
#include <dt-bindings/interrupt-controller/arm-gic.h>
|
#include <dt-bindings/interrupt-controller/arm-gic.h>
|
||||||
|
|
||||||
|
@ -131,9 +132,13 @@ static int exiu_domain_translate(struct irq_domain *domain,
|
||||||
|
|
||||||
*hwirq = fwspec->param[1] - info->spi_base;
|
*hwirq = fwspec->param[1] - info->spi_base;
|
||||||
*type = fwspec->param[2] & IRQ_TYPE_SENSE_MASK;
|
*type = fwspec->param[2] & IRQ_TYPE_SENSE_MASK;
|
||||||
return 0;
|
} else {
|
||||||
|
if (fwspec->param_count != 2)
|
||||||
|
return -EINVAL;
|
||||||
|
*hwirq = fwspec->param[0];
|
||||||
|
*type = fwspec->param[2] & IRQ_TYPE_SENSE_MASK;
|
||||||
}
|
}
|
||||||
return -EINVAL;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int exiu_domain_alloc(struct irq_domain *dom, unsigned int virq,
|
static int exiu_domain_alloc(struct irq_domain *dom, unsigned int virq,
|
||||||
|
@ -144,16 +149,21 @@ static int exiu_domain_alloc(struct irq_domain *dom, unsigned int virq,
|
||||||
struct exiu_irq_data *info = dom->host_data;
|
struct exiu_irq_data *info = dom->host_data;
|
||||||
irq_hw_number_t hwirq;
|
irq_hw_number_t hwirq;
|
||||||
|
|
||||||
if (fwspec->param_count != 3)
|
parent_fwspec = *fwspec;
|
||||||
return -EINVAL; /* Not GIC compliant */
|
if (is_of_node(dom->parent->fwnode)) {
|
||||||
if (fwspec->param[0] != GIC_SPI)
|
if (fwspec->param_count != 3)
|
||||||
return -EINVAL; /* No PPI should point to this domain */
|
return -EINVAL; /* Not GIC compliant */
|
||||||
|
if (fwspec->param[0] != GIC_SPI)
|
||||||
|
return -EINVAL; /* No PPI should point to this domain */
|
||||||
|
|
||||||
|
hwirq = fwspec->param[1] - info->spi_base;
|
||||||
|
} else {
|
||||||
|
hwirq = fwspec->param[0];
|
||||||
|
parent_fwspec.param[0] = hwirq + info->spi_base + 32;
|
||||||
|
}
|
||||||
WARN_ON(nr_irqs != 1);
|
WARN_ON(nr_irqs != 1);
|
||||||
hwirq = fwspec->param[1] - info->spi_base;
|
|
||||||
irq_domain_set_hwirq_and_chip(dom, virq, hwirq, &exiu_irq_chip, info);
|
irq_domain_set_hwirq_and_chip(dom, virq, hwirq, &exiu_irq_chip, info);
|
||||||
|
|
||||||
parent_fwspec = *fwspec;
|
|
||||||
parent_fwspec.fwnode = dom->parent->fwnode;
|
parent_fwspec.fwnode = dom->parent->fwnode;
|
||||||
return irq_domain_alloc_irqs_parent(dom, virq, nr_irqs, &parent_fwspec);
|
return irq_domain_alloc_irqs_parent(dom, virq, nr_irqs, &parent_fwspec);
|
||||||
}
|
}
|
||||||
|
@ -164,12 +174,45 @@ static const struct irq_domain_ops exiu_domain_ops = {
|
||||||
.free = irq_domain_free_irqs_common,
|
.free = irq_domain_free_irqs_common,
|
||||||
};
|
};
|
||||||
|
|
||||||
static int __init exiu_init(struct device_node *node,
|
static struct exiu_irq_data *exiu_init(const struct fwnode_handle *fwnode,
|
||||||
struct device_node *parent)
|
struct resource *res)
|
||||||
|
{
|
||||||
|
struct exiu_irq_data *data;
|
||||||
|
int err;
|
||||||
|
|
||||||
|
data = kzalloc(sizeof(*data), GFP_KERNEL);
|
||||||
|
if (!data)
|
||||||
|
return ERR_PTR(-ENOMEM);
|
||||||
|
|
||||||
|
if (fwnode_property_read_u32_array(fwnode, "socionext,spi-base",
|
||||||
|
&data->spi_base, 1)) {
|
||||||
|
err = -ENODEV;
|
||||||
|
goto out_free;
|
||||||
|
}
|
||||||
|
|
||||||
|
data->base = ioremap(res->start, resource_size(res));
|
||||||
|
if (!data->base) {
|
||||||
|
err = -ENODEV;
|
||||||
|
goto out_free;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* clear and mask all interrupts */
|
||||||
|
writel_relaxed(0xFFFFFFFF, data->base + EIREQCLR);
|
||||||
|
writel_relaxed(0xFFFFFFFF, data->base + EIMASK);
|
||||||
|
|
||||||
|
return data;
|
||||||
|
|
||||||
|
out_free:
|
||||||
|
kfree(data);
|
||||||
|
return ERR_PTR(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int __init exiu_dt_init(struct device_node *node,
|
||||||
|
struct device_node *parent)
|
||||||
{
|
{
|
||||||
struct irq_domain *parent_domain, *domain;
|
struct irq_domain *parent_domain, *domain;
|
||||||
struct exiu_irq_data *data;
|
struct exiu_irq_data *data;
|
||||||
int err;
|
struct resource res;
|
||||||
|
|
||||||
if (!parent) {
|
if (!parent) {
|
||||||
pr_err("%pOF: no parent, giving up\n", node);
|
pr_err("%pOF: no parent, giving up\n", node);
|
||||||
|
@ -182,31 +225,19 @@ static int __init exiu_init(struct device_node *node,
|
||||||
return -ENXIO;
|
return -ENXIO;
|
||||||
}
|
}
|
||||||
|
|
||||||
data = kzalloc(sizeof(*data), GFP_KERNEL);
|
if (of_address_to_resource(node, 0, &res)) {
|
||||||
if (!data)
|
pr_err("%pOF: failed to parse memory resource\n", node);
|
||||||
return -ENOMEM;
|
return -ENXIO;
|
||||||
|
|
||||||
if (of_property_read_u32(node, "socionext,spi-base", &data->spi_base)) {
|
|
||||||
pr_err("%pOF: failed to parse 'spi-base' property\n", node);
|
|
||||||
err = -ENODEV;
|
|
||||||
goto out_free;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
data->base = of_iomap(node, 0);
|
data = exiu_init(of_node_to_fwnode(node), &res);
|
||||||
if (!data->base) {
|
if (IS_ERR(data))
|
||||||
err = -ENODEV;
|
return PTR_ERR(data);
|
||||||
goto out_free;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* clear and mask all interrupts */
|
|
||||||
writel_relaxed(0xFFFFFFFF, data->base + EIREQCLR);
|
|
||||||
writel_relaxed(0xFFFFFFFF, data->base + EIMASK);
|
|
||||||
|
|
||||||
domain = irq_domain_add_hierarchy(parent_domain, 0, NUM_IRQS, node,
|
domain = irq_domain_add_hierarchy(parent_domain, 0, NUM_IRQS, node,
|
||||||
&exiu_domain_ops, data);
|
&exiu_domain_ops, data);
|
||||||
if (!domain) {
|
if (!domain) {
|
||||||
pr_err("%pOF: failed to allocate domain\n", node);
|
pr_err("%pOF: failed to allocate domain\n", node);
|
||||||
err = -ENOMEM;
|
|
||||||
goto out_unmap;
|
goto out_unmap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -217,8 +248,57 @@ static int __init exiu_init(struct device_node *node,
|
||||||
|
|
||||||
out_unmap:
|
out_unmap:
|
||||||
iounmap(data->base);
|
iounmap(data->base);
|
||||||
out_free:
|
|
||||||
kfree(data);
|
kfree(data);
|
||||||
return err;
|
return -ENOMEM;
|
||||||
}
|
}
|
||||||
IRQCHIP_DECLARE(exiu, "socionext,synquacer-exiu", exiu_init);
|
IRQCHIP_DECLARE(exiu, "socionext,synquacer-exiu", exiu_dt_init);
|
||||||
|
|
||||||
|
#ifdef CONFIG_ACPI
|
||||||
|
static int exiu_acpi_probe(struct platform_device *pdev)
|
||||||
|
{
|
||||||
|
struct irq_domain *domain;
|
||||||
|
struct exiu_irq_data *data;
|
||||||
|
struct resource *res;
|
||||||
|
|
||||||
|
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||||
|
if (!res) {
|
||||||
|
dev_err(&pdev->dev, "failed to parse memory resource\n");
|
||||||
|
return -ENXIO;
|
||||||
|
}
|
||||||
|
|
||||||
|
data = exiu_init(dev_fwnode(&pdev->dev), res);
|
||||||
|
if (IS_ERR(data))
|
||||||
|
return PTR_ERR(data);
|
||||||
|
|
||||||
|
domain = acpi_irq_create_hierarchy(0, NUM_IRQS, dev_fwnode(&pdev->dev),
|
||||||
|
&exiu_domain_ops, data);
|
||||||
|
if (!domain) {
|
||||||
|
dev_err(&pdev->dev, "failed to create IRQ domain\n");
|
||||||
|
goto out_unmap;
|
||||||
|
}
|
||||||
|
|
||||||
|
dev_info(&pdev->dev, "%d interrupts forwarded\n", NUM_IRQS);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
out_unmap:
|
||||||
|
iounmap(data->base);
|
||||||
|
kfree(data);
|
||||||
|
return -ENOMEM;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct acpi_device_id exiu_acpi_ids[] = {
|
||||||
|
{ "SCX0008" },
|
||||||
|
{ /* sentinel */ }
|
||||||
|
};
|
||||||
|
MODULE_DEVICE_TABLE(acpi, exiu_acpi_ids);
|
||||||
|
|
||||||
|
static struct platform_driver exiu_driver = {
|
||||||
|
.driver = {
|
||||||
|
.name = "exiu",
|
||||||
|
.acpi_match_table = exiu_acpi_ids,
|
||||||
|
},
|
||||||
|
.probe = exiu_acpi_probe,
|
||||||
|
};
|
||||||
|
builtin_platform_driver(exiu_driver);
|
||||||
|
#endif
|
||||||
|
|
|
@ -229,7 +229,6 @@ static int get_registers(struct platform_device *pdev, struct combiner *comb)
|
||||||
static int __init combiner_probe(struct platform_device *pdev)
|
static int __init combiner_probe(struct platform_device *pdev)
|
||||||
{
|
{
|
||||||
struct combiner *combiner;
|
struct combiner *combiner;
|
||||||
size_t alloc_sz;
|
|
||||||
int nregs;
|
int nregs;
|
||||||
int err;
|
int err;
|
||||||
|
|
||||||
|
@ -239,8 +238,8 @@ static int __init combiner_probe(struct platform_device *pdev)
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
}
|
}
|
||||||
|
|
||||||
alloc_sz = sizeof(*combiner) + sizeof(struct combiner_reg) * nregs;
|
combiner = devm_kzalloc(&pdev->dev, struct_size(combiner, regs, nregs),
|
||||||
combiner = devm_kzalloc(&pdev->dev, alloc_sz, GFP_KERNEL);
|
GFP_KERNEL);
|
||||||
if (!combiner)
|
if (!combiner)
|
||||||
return -ENOMEM;
|
return -ENOMEM;
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
|
|
||||||
#include <linux/errno.h>
|
#include <linux/errno.h>
|
||||||
#include <linux/ioport.h> /* for struct resource */
|
#include <linux/ioport.h> /* for struct resource */
|
||||||
|
#include <linux/irqdomain.h>
|
||||||
#include <linux/resource_ext.h>
|
#include <linux/resource_ext.h>
|
||||||
#include <linux/device.h>
|
#include <linux/device.h>
|
||||||
#include <linux/property.h>
|
#include <linux/property.h>
|
||||||
|
@ -314,6 +315,12 @@ int acpi_isa_irq_to_gsi (unsigned isa_irq, u32 *gsi);
|
||||||
void acpi_set_irq_model(enum acpi_irq_model_id model,
|
void acpi_set_irq_model(enum acpi_irq_model_id model,
|
||||||
struct fwnode_handle *fwnode);
|
struct fwnode_handle *fwnode);
|
||||||
|
|
||||||
|
struct irq_domain *acpi_irq_create_hierarchy(unsigned int flags,
|
||||||
|
unsigned int size,
|
||||||
|
struct fwnode_handle *fwnode,
|
||||||
|
const struct irq_domain_ops *ops,
|
||||||
|
void *host_data);
|
||||||
|
|
||||||
#ifdef CONFIG_X86_IO_APIC
|
#ifdef CONFIG_X86_IO_APIC
|
||||||
extern int acpi_get_override_irq(u32 gsi, int *trigger, int *polarity);
|
extern int acpi_get_override_irq(u32 gsi, int *trigger, int *polarity);
|
||||||
#else
|
#else
|
||||||
|
|
|
@ -36,4 +36,9 @@ struct gic_kvm_info {
|
||||||
|
|
||||||
const struct gic_kvm_info *gic_get_kvm_info(void);
|
const struct gic_kvm_info *gic_get_kvm_info(void);
|
||||||
|
|
||||||
|
struct irq_domain;
|
||||||
|
struct fwnode_handle;
|
||||||
|
int gicv2m_init(struct fwnode_handle *parent_handle,
|
||||||
|
struct irq_domain *parent);
|
||||||
|
|
||||||
#endif /* __LINUX_IRQCHIP_ARM_GIC_COMMON_H */
|
#endif /* __LINUX_IRQCHIP_ARM_GIC_COMMON_H */
|
||||||
|
|
|
@ -157,9 +157,6 @@ int gic_of_init_child(struct device *dev, struct gic_chip_data **gic, int irq);
|
||||||
*/
|
*/
|
||||||
void gic_init(void __iomem *dist , void __iomem *cpu);
|
void gic_init(void __iomem *dist , void __iomem *cpu);
|
||||||
|
|
||||||
int gicv2m_init(struct fwnode_handle *parent_handle,
|
|
||||||
struct irq_domain *parent);
|
|
||||||
|
|
||||||
void gic_send_sgi(unsigned int cpu_id, unsigned int irq);
|
void gic_send_sgi(unsigned int cpu_id, unsigned int irq);
|
||||||
int gic_get_cpu_id(unsigned int cpu);
|
int gic_get_cpu_id(unsigned int cpu);
|
||||||
void gic_migrate_target(unsigned int new_cpu_id);
|
void gic_migrate_target(unsigned int new_cpu_id);
|
||||||
|
|
|
@ -2,6 +2,9 @@
|
||||||
|
|
||||||
obj-y := irqdesc.o handle.o manage.o spurious.o resend.o chip.o dummychip.o devres.o
|
obj-y := irqdesc.o handle.o manage.o spurious.o resend.o chip.o dummychip.o devres.o
|
||||||
obj-$(CONFIG_IRQ_TIMINGS) += timings.o
|
obj-$(CONFIG_IRQ_TIMINGS) += timings.o
|
||||||
|
ifeq ($(CONFIG_TEST_IRQ_TIMINGS),y)
|
||||||
|
CFLAGS_timings.o += -DDEBUG
|
||||||
|
endif
|
||||||
obj-$(CONFIG_GENERIC_IRQ_CHIP) += generic-chip.o
|
obj-$(CONFIG_GENERIC_IRQ_CHIP) += generic-chip.o
|
||||||
obj-$(CONFIG_GENERIC_IRQ_PROBE) += autoprobe.o
|
obj-$(CONFIG_GENERIC_IRQ_PROBE) += autoprobe.o
|
||||||
obj-$(CONFIG_IRQ_DOMAIN) += irqdomain.o
|
obj-$(CONFIG_IRQ_DOMAIN) += irqdomain.o
|
||||||
|
|
|
@ -94,8 +94,7 @@ static int get_nodes_in_cpumask(cpumask_var_t *node_to_cpumask,
|
||||||
return nodes;
|
return nodes;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int __irq_build_affinity_masks(const struct irq_affinity *affd,
|
static int __irq_build_affinity_masks(unsigned int startvec,
|
||||||
unsigned int startvec,
|
|
||||||
unsigned int numvecs,
|
unsigned int numvecs,
|
||||||
unsigned int firstvec,
|
unsigned int firstvec,
|
||||||
cpumask_var_t *node_to_cpumask,
|
cpumask_var_t *node_to_cpumask,
|
||||||
|
@ -171,8 +170,7 @@ static int __irq_build_affinity_masks(const struct irq_affinity *affd,
|
||||||
* 1) spread present CPU on these vectors
|
* 1) spread present CPU on these vectors
|
||||||
* 2) spread other possible CPUs on these vectors
|
* 2) spread other possible CPUs on these vectors
|
||||||
*/
|
*/
|
||||||
static int irq_build_affinity_masks(const struct irq_affinity *affd,
|
static int irq_build_affinity_masks(unsigned int startvec, unsigned int numvecs,
|
||||||
unsigned int startvec, unsigned int numvecs,
|
|
||||||
unsigned int firstvec,
|
unsigned int firstvec,
|
||||||
struct irq_affinity_desc *masks)
|
struct irq_affinity_desc *masks)
|
||||||
{
|
{
|
||||||
|
@ -197,7 +195,7 @@ static int irq_build_affinity_masks(const struct irq_affinity *affd,
|
||||||
build_node_to_cpumask(node_to_cpumask);
|
build_node_to_cpumask(node_to_cpumask);
|
||||||
|
|
||||||
/* Spread on present CPUs starting from affd->pre_vectors */
|
/* Spread on present CPUs starting from affd->pre_vectors */
|
||||||
nr_present = __irq_build_affinity_masks(affd, curvec, numvecs,
|
nr_present = __irq_build_affinity_masks(curvec, numvecs,
|
||||||
firstvec, node_to_cpumask,
|
firstvec, node_to_cpumask,
|
||||||
cpu_present_mask, nmsk, masks);
|
cpu_present_mask, nmsk, masks);
|
||||||
|
|
||||||
|
@ -212,7 +210,7 @@ static int irq_build_affinity_masks(const struct irq_affinity *affd,
|
||||||
else
|
else
|
||||||
curvec = firstvec + nr_present;
|
curvec = firstvec + nr_present;
|
||||||
cpumask_andnot(npresmsk, cpu_possible_mask, cpu_present_mask);
|
cpumask_andnot(npresmsk, cpu_possible_mask, cpu_present_mask);
|
||||||
nr_others = __irq_build_affinity_masks(affd, curvec, numvecs,
|
nr_others = __irq_build_affinity_masks(curvec, numvecs,
|
||||||
firstvec, node_to_cpumask,
|
firstvec, node_to_cpumask,
|
||||||
npresmsk, nmsk, masks);
|
npresmsk, nmsk, masks);
|
||||||
put_online_cpus();
|
put_online_cpus();
|
||||||
|
@ -295,7 +293,7 @@ irq_create_affinity_masks(unsigned int nvecs, struct irq_affinity *affd)
|
||||||
unsigned int this_vecs = affd->set_size[i];
|
unsigned int this_vecs = affd->set_size[i];
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
ret = irq_build_affinity_masks(affd, curvec, this_vecs,
|
ret = irq_build_affinity_masks(curvec, this_vecs,
|
||||||
curvec, masks);
|
curvec, masks);
|
||||||
if (ret) {
|
if (ret) {
|
||||||
kfree(masks);
|
kfree(masks);
|
||||||
|
|
|
@ -748,6 +748,8 @@ void handle_fasteoi_nmi(struct irq_desc *desc)
|
||||||
unsigned int irq = irq_desc_get_irq(desc);
|
unsigned int irq = irq_desc_get_irq(desc);
|
||||||
irqreturn_t res;
|
irqreturn_t res;
|
||||||
|
|
||||||
|
__kstat_incr_irqs_this_cpu(desc);
|
||||||
|
|
||||||
trace_irq_handler_entry(irq, action);
|
trace_irq_handler_entry(irq, action);
|
||||||
/*
|
/*
|
||||||
* NMIs cannot be shared, there is only one action.
|
* NMIs cannot be shared, there is only one action.
|
||||||
|
@ -962,6 +964,8 @@ void handle_percpu_devid_fasteoi_nmi(struct irq_desc *desc)
|
||||||
unsigned int irq = irq_desc_get_irq(desc);
|
unsigned int irq = irq_desc_get_irq(desc);
|
||||||
irqreturn_t res;
|
irqreturn_t res;
|
||||||
|
|
||||||
|
__kstat_incr_irqs_this_cpu(desc);
|
||||||
|
|
||||||
trace_irq_handler_entry(irq, action);
|
trace_irq_handler_entry(irq, action);
|
||||||
res = action->handler(irq, raw_cpu_ptr(action->percpu_dev_id));
|
res = action->handler(irq, raw_cpu_ptr(action->percpu_dev_id));
|
||||||
trace_irq_handler_exit(irq, action, res);
|
trace_irq_handler_exit(irq, action, res);
|
||||||
|
|
|
@ -354,6 +354,16 @@ static inline int irq_timing_decode(u64 value, u64 *timestamp)
|
||||||
return value & U16_MAX;
|
return value & U16_MAX;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static __always_inline void irq_timings_push(u64 ts, int irq)
|
||||||
|
{
|
||||||
|
struct irq_timings *timings = this_cpu_ptr(&irq_timings);
|
||||||
|
|
||||||
|
timings->values[timings->count & IRQ_TIMINGS_MASK] =
|
||||||
|
irq_timing_encode(ts, irq);
|
||||||
|
|
||||||
|
timings->count++;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* The function record_irq_time is only called in one place in the
|
* The function record_irq_time is only called in one place in the
|
||||||
* interrupts handler. We want this function always inline so the code
|
* interrupts handler. We want this function always inline so the code
|
||||||
|
@ -367,15 +377,8 @@ static __always_inline void record_irq_time(struct irq_desc *desc)
|
||||||
if (!static_branch_likely(&irq_timing_enabled))
|
if (!static_branch_likely(&irq_timing_enabled))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (desc->istate & IRQS_TIMINGS) {
|
if (desc->istate & IRQS_TIMINGS)
|
||||||
struct irq_timings *timings = this_cpu_ptr(&irq_timings);
|
irq_timings_push(local_clock(), irq_desc_get_irq(desc));
|
||||||
|
|
||||||
timings->values[timings->count & IRQ_TIMINGS_MASK] =
|
|
||||||
irq_timing_encode(local_clock(),
|
|
||||||
irq_desc_get_irq(desc));
|
|
||||||
|
|
||||||
timings->count++;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
static inline void irq_remove_timings(struct irq_desc *desc) {}
|
static inline void irq_remove_timings(struct irq_desc *desc) {}
|
||||||
|
|
|
@ -950,6 +950,11 @@ unsigned int kstat_irqs_cpu(unsigned int irq, int cpu)
|
||||||
*per_cpu_ptr(desc->kstat_irqs, cpu) : 0;
|
*per_cpu_ptr(desc->kstat_irqs, cpu) : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool irq_is_nmi(struct irq_desc *desc)
|
||||||
|
{
|
||||||
|
return desc->istate & IRQS_NMI;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* kstat_irqs - Get the statistics for an interrupt
|
* kstat_irqs - Get the statistics for an interrupt
|
||||||
* @irq: The interrupt number
|
* @irq: The interrupt number
|
||||||
|
@ -967,7 +972,8 @@ unsigned int kstat_irqs(unsigned int irq)
|
||||||
if (!desc || !desc->kstat_irqs)
|
if (!desc || !desc->kstat_irqs)
|
||||||
return 0;
|
return 0;
|
||||||
if (!irq_settings_is_per_cpu_devid(desc) &&
|
if (!irq_settings_is_per_cpu_devid(desc) &&
|
||||||
!irq_settings_is_per_cpu(desc))
|
!irq_settings_is_per_cpu(desc) &&
|
||||||
|
!irq_is_nmi(desc))
|
||||||
return desc->tot_count;
|
return desc->tot_count;
|
||||||
|
|
||||||
for_each_possible_cpu(cpu)
|
for_each_possible_cpu(cpu)
|
||||||
|
|
|
@ -123,7 +123,7 @@ EXPORT_SYMBOL_GPL(irq_domain_free_fwnode);
|
||||||
* @ops: domain callbacks
|
* @ops: domain callbacks
|
||||||
* @host_data: Controller private data pointer
|
* @host_data: Controller private data pointer
|
||||||
*
|
*
|
||||||
* Allocates and initialize and irq_domain structure.
|
* Allocates and initializes an irq_domain structure.
|
||||||
* Returns pointer to IRQ domain, or NULL on failure.
|
* Returns pointer to IRQ domain, or NULL on failure.
|
||||||
*/
|
*/
|
||||||
struct irq_domain *__irq_domain_add(struct fwnode_handle *fwnode, int size,
|
struct irq_domain *__irq_domain_add(struct fwnode_handle *fwnode, int size,
|
||||||
|
@ -139,7 +139,7 @@ struct irq_domain *__irq_domain_add(struct fwnode_handle *fwnode, int size,
|
||||||
|
|
||||||
domain = kzalloc_node(sizeof(*domain) + (sizeof(unsigned int) * size),
|
domain = kzalloc_node(sizeof(*domain) + (sizeof(unsigned int) * size),
|
||||||
GFP_KERNEL, of_node_to_nid(of_node));
|
GFP_KERNEL, of_node_to_nid(of_node));
|
||||||
if (WARN_ON(!domain))
|
if (!domain)
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
if (fwnode && is_fwnode_irqchip(fwnode)) {
|
if (fwnode && is_fwnode_irqchip(fwnode)) {
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
// SPDX-License-Identifier: GPL-2.0
|
// SPDX-License-Identifier: GPL-2.0
|
||||||
// Copyright (C) 2016, Linaro Ltd - Daniel Lezcano <daniel.lezcano@linaro.org>
|
// Copyright (C) 2016, Linaro Ltd - Daniel Lezcano <daniel.lezcano@linaro.org>
|
||||||
|
#define pr_fmt(fmt) "irq_timings: " fmt
|
||||||
|
|
||||||
#include <linux/kernel.h>
|
#include <linux/kernel.h>
|
||||||
#include <linux/percpu.h>
|
#include <linux/percpu.h>
|
||||||
#include <linux/slab.h>
|
#include <linux/slab.h>
|
||||||
#include <linux/static_key.h>
|
#include <linux/static_key.h>
|
||||||
|
#include <linux/init.h>
|
||||||
#include <linux/interrupt.h>
|
#include <linux/interrupt.h>
|
||||||
#include <linux/idr.h>
|
#include <linux/idr.h>
|
||||||
#include <linux/irq.h>
|
#include <linux/irq.h>
|
||||||
|
@ -261,12 +263,29 @@ void irq_timings_disable(void)
|
||||||
#define EMA_ALPHA_VAL 64
|
#define EMA_ALPHA_VAL 64
|
||||||
#define EMA_ALPHA_SHIFT 7
|
#define EMA_ALPHA_SHIFT 7
|
||||||
|
|
||||||
#define PREDICTION_PERIOD_MIN 2
|
#define PREDICTION_PERIOD_MIN 3
|
||||||
#define PREDICTION_PERIOD_MAX 5
|
#define PREDICTION_PERIOD_MAX 5
|
||||||
#define PREDICTION_FACTOR 4
|
#define PREDICTION_FACTOR 4
|
||||||
#define PREDICTION_MAX 10 /* 2 ^ PREDICTION_MAX useconds */
|
#define PREDICTION_MAX 10 /* 2 ^ PREDICTION_MAX useconds */
|
||||||
#define PREDICTION_BUFFER_SIZE 16 /* slots for EMAs, hardly more than 16 */
|
#define PREDICTION_BUFFER_SIZE 16 /* slots for EMAs, hardly more than 16 */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Number of elements in the circular buffer: If it happens it was
|
||||||
|
* flushed before, then the number of elements could be smaller than
|
||||||
|
* IRQ_TIMINGS_SIZE, so the count is used, otherwise the array size is
|
||||||
|
* used as we wrapped. The index begins from zero when we did not
|
||||||
|
* wrap. That could be done in a nicer way with the proper circular
|
||||||
|
* array structure type but with the cost of extra computation in the
|
||||||
|
* interrupt handler hot path. We choose efficiency.
|
||||||
|
*/
|
||||||
|
#define for_each_irqts(i, irqts) \
|
||||||
|
for (i = irqts->count < IRQ_TIMINGS_SIZE ? \
|
||||||
|
0 : irqts->count & IRQ_TIMINGS_MASK, \
|
||||||
|
irqts->count = min(IRQ_TIMINGS_SIZE, \
|
||||||
|
irqts->count); \
|
||||||
|
irqts->count > 0; irqts->count--, \
|
||||||
|
i = (i + 1) & IRQ_TIMINGS_MASK)
|
||||||
|
|
||||||
struct irqt_stat {
|
struct irqt_stat {
|
||||||
u64 last_ts;
|
u64 last_ts;
|
||||||
u64 ema_time[PREDICTION_BUFFER_SIZE];
|
u64 ema_time[PREDICTION_BUFFER_SIZE];
|
||||||
|
@ -297,7 +316,16 @@ static u64 irq_timings_ema_new(u64 value, u64 ema_old)
|
||||||
|
|
||||||
static int irq_timings_next_event_index(int *buffer, size_t len, int period_max)
|
static int irq_timings_next_event_index(int *buffer, size_t len, int period_max)
|
||||||
{
|
{
|
||||||
int i;
|
int period;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Move the beginning pointer to the end minus the max period x 3.
|
||||||
|
* We are at the point we can begin searching the pattern
|
||||||
|
*/
|
||||||
|
buffer = &buffer[len - (period_max * 3)];
|
||||||
|
|
||||||
|
/* Adjust the length to the maximum allowed period x 3 */
|
||||||
|
len = period_max * 3;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* The buffer contains the suite of intervals, in a ilog2
|
* The buffer contains the suite of intervals, in a ilog2
|
||||||
|
@ -306,21 +334,45 @@ static int irq_timings_next_event_index(int *buffer, size_t len, int period_max)
|
||||||
* period beginning at the end of the buffer. We do that for
|
* period beginning at the end of the buffer. We do that for
|
||||||
* each suffix.
|
* each suffix.
|
||||||
*/
|
*/
|
||||||
for (i = period_max; i >= PREDICTION_PERIOD_MIN ; i--) {
|
for (period = period_max; period >= PREDICTION_PERIOD_MIN; period--) {
|
||||||
|
|
||||||
int *begin = &buffer[len - (i * 3)];
|
/*
|
||||||
int *ptr = begin;
|
* The first comparison always succeed because the
|
||||||
|
* suffix is deduced from the first n-period bytes of
|
||||||
|
* the buffer and we compare the initial suffix with
|
||||||
|
* itself, so we can skip the first iteration.
|
||||||
|
*/
|
||||||
|
int idx = period;
|
||||||
|
size_t size = period;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* We look if the suite with period 'i' repeat
|
* We look if the suite with period 'i' repeat
|
||||||
* itself. If it is truncated at the end, as it
|
* itself. If it is truncated at the end, as it
|
||||||
* repeats we can use the period to find out the next
|
* repeats we can use the period to find out the next
|
||||||
* element.
|
* element with the modulo.
|
||||||
*/
|
*/
|
||||||
while (!memcmp(ptr, begin, i * sizeof(*ptr))) {
|
while (!memcmp(buffer, &buffer[idx], size * sizeof(int))) {
|
||||||
ptr += i;
|
|
||||||
if (ptr >= &buffer[len])
|
/*
|
||||||
return begin[((i * 3) % i)];
|
* Move the index in a period basis
|
||||||
|
*/
|
||||||
|
idx += size;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If this condition is reached, all previous
|
||||||
|
* memcmp were successful, so the period is
|
||||||
|
* found.
|
||||||
|
*/
|
||||||
|
if (idx == len)
|
||||||
|
return buffer[len % period];
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If the remaining elements to compare are
|
||||||
|
* smaller than the period, readjust the size
|
||||||
|
* of the comparison for the last iteration.
|
||||||
|
*/
|
||||||
|
if (len - idx < period)
|
||||||
|
size = len - idx;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -380,11 +432,43 @@ static u64 __irq_timings_next_event(struct irqt_stat *irqs, int irq, u64 now)
|
||||||
return irqs->last_ts + irqs->ema_time[index];
|
return irqs->last_ts + irqs->ema_time[index];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static __always_inline int irq_timings_interval_index(u64 interval)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* The PREDICTION_FACTOR increase the interval size for the
|
||||||
|
* array of exponential average.
|
||||||
|
*/
|
||||||
|
u64 interval_us = (interval >> 10) / PREDICTION_FACTOR;
|
||||||
|
|
||||||
|
return likely(interval_us) ? ilog2(interval_us) : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static __always_inline void __irq_timings_store(int irq, struct irqt_stat *irqs,
|
||||||
|
u64 interval)
|
||||||
|
{
|
||||||
|
int index;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Get the index in the ema table for this interrupt.
|
||||||
|
*/
|
||||||
|
index = irq_timings_interval_index(interval);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Store the index as an element of the pattern in another
|
||||||
|
* circular array.
|
||||||
|
*/
|
||||||
|
irqs->circ_timings[irqs->count & IRQ_TIMINGS_MASK] = index;
|
||||||
|
|
||||||
|
irqs->ema_time[index] = irq_timings_ema_new(interval,
|
||||||
|
irqs->ema_time[index]);
|
||||||
|
|
||||||
|
irqs->count++;
|
||||||
|
}
|
||||||
|
|
||||||
static inline void irq_timings_store(int irq, struct irqt_stat *irqs, u64 ts)
|
static inline void irq_timings_store(int irq, struct irqt_stat *irqs, u64 ts)
|
||||||
{
|
{
|
||||||
u64 old_ts = irqs->last_ts;
|
u64 old_ts = irqs->last_ts;
|
||||||
u64 interval;
|
u64 interval;
|
||||||
int index;
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* The timestamps are absolute time values, we need to compute
|
* The timestamps are absolute time values, we need to compute
|
||||||
|
@ -415,24 +499,7 @@ static inline void irq_timings_store(int irq, struct irqt_stat *irqs, u64 ts)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
__irq_timings_store(irq, irqs, interval);
|
||||||
* Get the index in the ema table for this interrupt. The
|
|
||||||
* PREDICTION_FACTOR increase the interval size for the array
|
|
||||||
* of exponential average.
|
|
||||||
*/
|
|
||||||
index = likely(interval) ?
|
|
||||||
ilog2((interval >> 10) / PREDICTION_FACTOR) : 0;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Store the index as an element of the pattern in another
|
|
||||||
* circular array.
|
|
||||||
*/
|
|
||||||
irqs->circ_timings[irqs->count & IRQ_TIMINGS_MASK] = index;
|
|
||||||
|
|
||||||
irqs->ema_time[index] = irq_timings_ema_new(interval,
|
|
||||||
irqs->ema_time[index]);
|
|
||||||
|
|
||||||
irqs->count++;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -493,11 +560,7 @@ u64 irq_timings_next_event(u64 now)
|
||||||
* model while decrementing the counter because we consume the
|
* model while decrementing the counter because we consume the
|
||||||
* data from our circular buffer.
|
* data from our circular buffer.
|
||||||
*/
|
*/
|
||||||
|
for_each_irqts(i, irqts) {
|
||||||
i = (irqts->count & IRQ_TIMINGS_MASK) - 1;
|
|
||||||
irqts->count = min(IRQ_TIMINGS_SIZE, irqts->count);
|
|
||||||
|
|
||||||
for (; irqts->count > 0; irqts->count--, i = (i + 1) & IRQ_TIMINGS_MASK) {
|
|
||||||
irq = irq_timing_decode(irqts->values[i], &ts);
|
irq = irq_timing_decode(irqts->values[i], &ts);
|
||||||
s = idr_find(&irqt_stats, irq);
|
s = idr_find(&irqt_stats, irq);
|
||||||
if (s)
|
if (s)
|
||||||
|
@ -564,3 +627,325 @@ int irq_timings_alloc(int irq)
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef CONFIG_TEST_IRQ_TIMINGS
|
||||||
|
struct timings_intervals {
|
||||||
|
u64 *intervals;
|
||||||
|
size_t count;
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Intervals are given in nanosecond base
|
||||||
|
*/
|
||||||
|
static u64 intervals0[] __initdata = {
|
||||||
|
10000, 50000, 200000, 500000,
|
||||||
|
10000, 50000, 200000, 500000,
|
||||||
|
10000, 50000, 200000, 500000,
|
||||||
|
10000, 50000, 200000, 500000,
|
||||||
|
10000, 50000, 200000, 500000,
|
||||||
|
10000, 50000, 200000, 500000,
|
||||||
|
10000, 50000, 200000, 500000,
|
||||||
|
10000, 50000, 200000, 500000,
|
||||||
|
10000, 50000, 200000,
|
||||||
|
};
|
||||||
|
|
||||||
|
static u64 intervals1[] __initdata = {
|
||||||
|
223947000, 1240000, 1384000, 1386000, 1386000,
|
||||||
|
217416000, 1236000, 1384000, 1386000, 1387000,
|
||||||
|
214719000, 1241000, 1386000, 1387000, 1384000,
|
||||||
|
213696000, 1234000, 1384000, 1386000, 1388000,
|
||||||
|
219904000, 1240000, 1385000, 1389000, 1385000,
|
||||||
|
212240000, 1240000, 1386000, 1386000, 1386000,
|
||||||
|
214415000, 1236000, 1384000, 1386000, 1387000,
|
||||||
|
214276000, 1234000,
|
||||||
|
};
|
||||||
|
|
||||||
|
static u64 intervals2[] __initdata = {
|
||||||
|
4000, 3000, 5000, 100000,
|
||||||
|
3000, 3000, 5000, 117000,
|
||||||
|
4000, 4000, 5000, 112000,
|
||||||
|
4000, 3000, 4000, 110000,
|
||||||
|
3000, 5000, 3000, 117000,
|
||||||
|
4000, 4000, 5000, 112000,
|
||||||
|
4000, 3000, 4000, 110000,
|
||||||
|
3000, 4000, 5000, 112000,
|
||||||
|
4000,
|
||||||
|
};
|
||||||
|
|
||||||
|
static u64 intervals3[] __initdata = {
|
||||||
|
1385000, 212240000, 1240000,
|
||||||
|
1386000, 214415000, 1236000,
|
||||||
|
1384000, 214276000, 1234000,
|
||||||
|
1386000, 214415000, 1236000,
|
||||||
|
1385000, 212240000, 1240000,
|
||||||
|
1386000, 214415000, 1236000,
|
||||||
|
1384000, 214276000, 1234000,
|
||||||
|
1386000, 214415000, 1236000,
|
||||||
|
1385000, 212240000, 1240000,
|
||||||
|
};
|
||||||
|
|
||||||
|
static u64 intervals4[] __initdata = {
|
||||||
|
10000, 50000, 10000, 50000,
|
||||||
|
10000, 50000, 10000, 50000,
|
||||||
|
10000, 50000, 10000, 50000,
|
||||||
|
10000, 50000, 10000, 50000,
|
||||||
|
10000, 50000, 10000, 50000,
|
||||||
|
10000, 50000, 10000, 50000,
|
||||||
|
10000, 50000, 10000, 50000,
|
||||||
|
10000, 50000, 10000, 50000,
|
||||||
|
10000,
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct timings_intervals tis[] __initdata = {
|
||||||
|
{ intervals0, ARRAY_SIZE(intervals0) },
|
||||||
|
{ intervals1, ARRAY_SIZE(intervals1) },
|
||||||
|
{ intervals2, ARRAY_SIZE(intervals2) },
|
||||||
|
{ intervals3, ARRAY_SIZE(intervals3) },
|
||||||
|
{ intervals4, ARRAY_SIZE(intervals4) },
|
||||||
|
};
|
||||||
|
|
||||||
|
static int __init irq_timings_test_next_index(struct timings_intervals *ti)
|
||||||
|
{
|
||||||
|
int _buffer[IRQ_TIMINGS_SIZE];
|
||||||
|
int buffer[IRQ_TIMINGS_SIZE];
|
||||||
|
int index, start, i, count, period_max;
|
||||||
|
|
||||||
|
count = ti->count - 1;
|
||||||
|
|
||||||
|
period_max = count > (3 * PREDICTION_PERIOD_MAX) ?
|
||||||
|
PREDICTION_PERIOD_MAX : count / 3;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Inject all values except the last one which will be used
|
||||||
|
* to compare with the next index result.
|
||||||
|
*/
|
||||||
|
pr_debug("index suite: ");
|
||||||
|
|
||||||
|
for (i = 0; i < count; i++) {
|
||||||
|
index = irq_timings_interval_index(ti->intervals[i]);
|
||||||
|
_buffer[i & IRQ_TIMINGS_MASK] = index;
|
||||||
|
pr_cont("%d ", index);
|
||||||
|
}
|
||||||
|
|
||||||
|
start = count < IRQ_TIMINGS_SIZE ? 0 :
|
||||||
|
count & IRQ_TIMINGS_MASK;
|
||||||
|
|
||||||
|
count = min_t(int, count, IRQ_TIMINGS_SIZE);
|
||||||
|
|
||||||
|
for (i = 0; i < count; i++) {
|
||||||
|
int index = (start + i) & IRQ_TIMINGS_MASK;
|
||||||
|
buffer[i] = _buffer[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
index = irq_timings_next_event_index(buffer, count, period_max);
|
||||||
|
i = irq_timings_interval_index(ti->intervals[ti->count - 1]);
|
||||||
|
|
||||||
|
if (index != i) {
|
||||||
|
pr_err("Expected (%d) and computed (%d) next indexes differ\n",
|
||||||
|
i, index);
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int __init irq_timings_next_index_selftest(void)
|
||||||
|
{
|
||||||
|
int i, ret;
|
||||||
|
|
||||||
|
for (i = 0; i < ARRAY_SIZE(tis); i++) {
|
||||||
|
|
||||||
|
pr_info("---> Injecting intervals number #%d (count=%zd)\n",
|
||||||
|
i, tis[i].count);
|
||||||
|
|
||||||
|
ret = irq_timings_test_next_index(&tis[i]);
|
||||||
|
if (ret)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int __init irq_timings_test_irqs(struct timings_intervals *ti)
|
||||||
|
{
|
||||||
|
struct irqt_stat __percpu *s;
|
||||||
|
struct irqt_stat *irqs;
|
||||||
|
int i, index, ret, irq = 0xACE5;
|
||||||
|
|
||||||
|
ret = irq_timings_alloc(irq);
|
||||||
|
if (ret) {
|
||||||
|
pr_err("Failed to allocate irq timings\n");
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
s = idr_find(&irqt_stats, irq);
|
||||||
|
if (!s) {
|
||||||
|
ret = -EIDRM;
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
irqs = this_cpu_ptr(s);
|
||||||
|
|
||||||
|
for (i = 0; i < ti->count; i++) {
|
||||||
|
|
||||||
|
index = irq_timings_interval_index(ti->intervals[i]);
|
||||||
|
pr_debug("%d: interval=%llu ema_index=%d\n",
|
||||||
|
i, ti->intervals[i], index);
|
||||||
|
|
||||||
|
__irq_timings_store(irq, irqs, ti->intervals[i]);
|
||||||
|
if (irqs->circ_timings[i & IRQ_TIMINGS_MASK] != index) {
|
||||||
|
pr_err("Failed to store in the circular buffer\n");
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (irqs->count != ti->count) {
|
||||||
|
pr_err("Count differs\n");
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = 0;
|
||||||
|
out:
|
||||||
|
irq_timings_free(irq);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int __init irq_timings_irqs_selftest(void)
|
||||||
|
{
|
||||||
|
int i, ret;
|
||||||
|
|
||||||
|
for (i = 0; i < ARRAY_SIZE(tis); i++) {
|
||||||
|
pr_info("---> Injecting intervals number #%d (count=%zd)\n",
|
||||||
|
i, tis[i].count);
|
||||||
|
ret = irq_timings_test_irqs(&tis[i]);
|
||||||
|
if (ret)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int __init irq_timings_test_irqts(struct irq_timings *irqts,
|
||||||
|
unsigned count)
|
||||||
|
{
|
||||||
|
int start = count >= IRQ_TIMINGS_SIZE ? count - IRQ_TIMINGS_SIZE : 0;
|
||||||
|
int i, irq, oirq = 0xBEEF;
|
||||||
|
u64 ots = 0xDEAD, ts;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Fill the circular buffer by using the dedicated function.
|
||||||
|
*/
|
||||||
|
for (i = 0; i < count; i++) {
|
||||||
|
pr_debug("%d: index=%d, ts=%llX irq=%X\n",
|
||||||
|
i, i & IRQ_TIMINGS_MASK, ots + i, oirq + i);
|
||||||
|
|
||||||
|
irq_timings_push(ots + i, oirq + i);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Compute the first elements values after the index wrapped
|
||||||
|
* up or not.
|
||||||
|
*/
|
||||||
|
ots += start;
|
||||||
|
oirq += start;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Test the circular buffer count is correct.
|
||||||
|
*/
|
||||||
|
pr_debug("---> Checking timings array count (%d) is right\n", count);
|
||||||
|
if (WARN_ON(irqts->count != count))
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Test the macro allowing to browse all the irqts.
|
||||||
|
*/
|
||||||
|
pr_debug("---> Checking the for_each_irqts() macro\n");
|
||||||
|
for_each_irqts(i, irqts) {
|
||||||
|
|
||||||
|
irq = irq_timing_decode(irqts->values[i], &ts);
|
||||||
|
|
||||||
|
pr_debug("index=%d, ts=%llX / %llX, irq=%X / %X\n",
|
||||||
|
i, ts, ots, irq, oirq);
|
||||||
|
|
||||||
|
if (WARN_ON(ts != ots || irq != oirq))
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
ots++; oirq++;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The circular buffer should have be flushed when browsed
|
||||||
|
* with for_each_irqts
|
||||||
|
*/
|
||||||
|
pr_debug("---> Checking timings array is empty after browsing it\n");
|
||||||
|
if (WARN_ON(irqts->count))
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int __init irq_timings_irqts_selftest(void)
|
||||||
|
{
|
||||||
|
struct irq_timings *irqts = this_cpu_ptr(&irq_timings);
|
||||||
|
int i, ret;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Test the circular buffer with different number of
|
||||||
|
* elements. The purpose is to test at the limits (empty, half
|
||||||
|
* full, full, wrapped with the cursor at the boundaries,
|
||||||
|
* wrapped several times, etc ...
|
||||||
|
*/
|
||||||
|
int count[] = { 0,
|
||||||
|
IRQ_TIMINGS_SIZE >> 1,
|
||||||
|
IRQ_TIMINGS_SIZE,
|
||||||
|
IRQ_TIMINGS_SIZE + (IRQ_TIMINGS_SIZE >> 1),
|
||||||
|
2 * IRQ_TIMINGS_SIZE,
|
||||||
|
(2 * IRQ_TIMINGS_SIZE) + 3,
|
||||||
|
};
|
||||||
|
|
||||||
|
for (i = 0; i < ARRAY_SIZE(count); i++) {
|
||||||
|
|
||||||
|
pr_info("---> Checking the timings with %d/%d values\n",
|
||||||
|
count[i], IRQ_TIMINGS_SIZE);
|
||||||
|
|
||||||
|
ret = irq_timings_test_irqts(irqts, count[i]);
|
||||||
|
if (ret)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int __init irq_timings_selftest(void)
|
||||||
|
{
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
pr_info("------------------- selftest start -----------------\n");
|
||||||
|
|
||||||
|
/*
|
||||||
|
* At this point, we don't except any subsystem to use the irq
|
||||||
|
* timings but us, so it should not be enabled.
|
||||||
|
*/
|
||||||
|
if (static_branch_unlikely(&irq_timing_enabled)) {
|
||||||
|
pr_warn("irq timings already initialized, skipping selftest\n");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = irq_timings_irqts_selftest();
|
||||||
|
if (ret)
|
||||||
|
goto out;
|
||||||
|
|
||||||
|
ret = irq_timings_irqs_selftest();
|
||||||
|
if (ret)
|
||||||
|
goto out;
|
||||||
|
|
||||||
|
ret = irq_timings_next_index_selftest();
|
||||||
|
out:
|
||||||
|
pr_info("---------- selftest end with %s -----------\n",
|
||||||
|
ret ? "failure" : "success");
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
early_initcall(irq_timings_selftest);
|
||||||
|
#endif
|
||||||
|
|
|
@ -649,7 +649,7 @@ static int takeover_tasklets(unsigned int cpu)
|
||||||
/* Find end, append list for that CPU. */
|
/* Find end, append list for that CPU. */
|
||||||
if (&per_cpu(tasklet_vec, cpu).head != per_cpu(tasklet_vec, cpu).tail) {
|
if (&per_cpu(tasklet_vec, cpu).head != per_cpu(tasklet_vec, cpu).tail) {
|
||||||
*__this_cpu_read(tasklet_vec.tail) = per_cpu(tasklet_vec, cpu).head;
|
*__this_cpu_read(tasklet_vec.tail) = per_cpu(tasklet_vec, cpu).head;
|
||||||
this_cpu_write(tasklet_vec.tail, per_cpu(tasklet_vec, cpu).tail);
|
__this_cpu_write(tasklet_vec.tail, per_cpu(tasklet_vec, cpu).tail);
|
||||||
per_cpu(tasklet_vec, cpu).head = NULL;
|
per_cpu(tasklet_vec, cpu).head = NULL;
|
||||||
per_cpu(tasklet_vec, cpu).tail = &per_cpu(tasklet_vec, cpu).head;
|
per_cpu(tasklet_vec, cpu).tail = &per_cpu(tasklet_vec, cpu).head;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1870,6 +1870,14 @@ config TEST_PARMAN
|
||||||
|
|
||||||
If unsure, say N.
|
If unsure, say N.
|
||||||
|
|
||||||
|
config TEST_IRQ_TIMINGS
|
||||||
|
bool "IRQ timings selftest"
|
||||||
|
depends on IRQ_TIMINGS
|
||||||
|
help
|
||||||
|
Enable this option to test the irq timings code on boot.
|
||||||
|
|
||||||
|
If unsure, say N.
|
||||||
|
|
||||||
config TEST_LKM
|
config TEST_LKM
|
||||||
tristate "Test module loading with 'hello world' module"
|
tristate "Test module loading with 'hello world' module"
|
||||||
depends on m
|
depends on m
|
||||||
|
|
Loading…
Reference in New Issue