aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorThomas Gleixner <tglx@linutronix.de>2017-06-23 14:26:29 +0200
committerThomas Gleixner <tglx@linutronix.de>2017-06-23 14:26:29 +0200
commit8d9d51b62e8558bbc11c6b978acad001f9ea7a42 (patch)
tree8ca3c44dd7401d0bcc42f21a93876715e2be5f4b
parentgenirq/irqdomain: Remove auto-recursive hierarchy support (diff)
parentirqchip/gic-v3-its: Don't assume GICv3 hardware supports 16bit INTID (diff)
downloadlinux-dev-8d9d51b62e8558bbc11c6b978acad001f9ea7a42.tar.xz
linux-dev-8d9d51b62e8558bbc11c6b978acad001f9ea7a42.zip
Merge tag 'irqchip-4.13' of git://git.kernel.org/pub/scm/linux/kernel/git/maz/arm-platforms into irq/core
Pull irqchip updates for v4.13 from Marc Zyngier - support for the new Marvell wire-to-MSI bridge - support for the Aspeed I2C irqchip - Armada XP370 per-cpu interrupt fixes - GICv3 ITS ACPI NUMA support - sunxi-nmi cleanup and updates for new platform support - various GICv3 ITS cleanups and fixes - some constifying in various places
-rw-r--r--Documentation/devicetree/bindings/interrupt-controller/allwinner,sunxi-nmi.txt7
-rw-r--r--Documentation/devicetree/bindings/interrupt-controller/aspeed,ast2400-i2c-ic.txt25
-rw-r--r--Documentation/devicetree/bindings/interrupt-controller/aspeed,ast2400-vic.txt9
-rw-r--r--Documentation/devicetree/bindings/interrupt-controller/marvell,gicp.txt27
-rw-r--r--Documentation/devicetree/bindings/interrupt-controller/marvell,icu.txt51
-rw-r--r--drivers/irqchip/Kconfig6
-rw-r--r--drivers/irqchip/Makefile4
-rw-r--r--drivers/irqchip/irq-armada-370-xp.c146
-rw-r--r--drivers/irqchip/irq-aspeed-i2c-ic.c115
-rw-r--r--drivers/irqchip/irq-aspeed-vic.c5
-rw-r--r--drivers/irqchip/irq-gic-v3-its-pci-msi.c35
-rw-r--r--drivers/irqchip/irq-gic-v3-its-platform-msi.c2
-rw-r--r--drivers/irqchip/irq-gic-v3-its.c111
-rw-r--r--drivers/irqchip/irq-i8259.c2
-rw-r--r--drivers/irqchip/irq-imx-gpcv2.c2
-rw-r--r--drivers/irqchip/irq-mbigen.c2
-rw-r--r--drivers/irqchip/irq-mips-gic.c2
-rw-r--r--drivers/irqchip/irq-mvebu-gicp.c279
-rw-r--r--drivers/irqchip/irq-mvebu-gicp.h11
-rw-r--r--drivers/irqchip/irq-mvebu-icu.c289
-rw-r--r--drivers/irqchip/irq-renesas-h8300h.c2
-rw-r--r--drivers/irqchip/irq-renesas-h8s.c2
-rw-r--r--drivers/irqchip/irq-sunxi-nmi.c68
-rw-r--r--drivers/irqchip/qcom-irq-combiner.c7
-rw-r--r--include/dt-bindings/interrupt-controller/mvebu-icu.h15
25 files changed, 1134 insertions, 90 deletions
diff --git a/Documentation/devicetree/bindings/interrupt-controller/allwinner,sunxi-nmi.txt b/Documentation/devicetree/bindings/interrupt-controller/allwinner,sunxi-nmi.txt
index 81cd3692405e..4ae553eb333d 100644
--- a/Documentation/devicetree/bindings/interrupt-controller/allwinner,sunxi-nmi.txt
+++ b/Documentation/devicetree/bindings/interrupt-controller/allwinner,sunxi-nmi.txt
@@ -3,8 +3,11 @@ Allwinner Sunxi NMI Controller
Required properties:
-- compatible : should be "allwinner,sun7i-a20-sc-nmi" or
- "allwinner,sun6i-a31-sc-nmi" or "allwinner,sun9i-a80-nmi"
+- compatible : should be one of the following:
+ - "allwinner,sun7i-a20-sc-nmi"
+ - "allwinner,sun6i-a31-sc-nmi" (deprecated)
+ - "allwinner,sun6i-a31-r-intc"
+ - "allwinner,sun9i-a80-nmi"
- reg : Specifies base physical address and size of the registers.
- interrupt-controller : Identifies the node as an interrupt controller
- #interrupt-cells : Specifies the number of cells needed to encode an
diff --git a/Documentation/devicetree/bindings/interrupt-controller/aspeed,ast2400-i2c-ic.txt b/Documentation/devicetree/bindings/interrupt-controller/aspeed,ast2400-i2c-ic.txt
new file mode 100644
index 000000000000..033cc82e5684
--- /dev/null
+++ b/Documentation/devicetree/bindings/interrupt-controller/aspeed,ast2400-i2c-ic.txt
@@ -0,0 +1,25 @@
+Device tree configuration for the I2C Interrupt Controller on the AST24XX and
+AST25XX SoCs.
+
+Required Properties:
+- #address-cells : should be 1
+- #size-cells : should be 1
+- #interrupt-cells : should be 1
+- compatible : should be "aspeed,ast2400-i2c-ic"
+ or "aspeed,ast2500-i2c-ic"
+- reg : address start and range of controller
+- interrupts : interrupt number
+- interrupt-controller : denotes that the controller receives and fires
+ new interrupts for child busses
+
+Example:
+
+i2c_ic: interrupt-controller@0 {
+ #address-cells = <1>;
+ #size-cells = <1>;
+ #interrupt-cells = <1>;
+ compatible = "aspeed,ast2400-i2c-ic";
+ reg = <0x0 0x40>;
+ interrupts = <12>;
+ interrupt-controller;
+};
diff --git a/Documentation/devicetree/bindings/interrupt-controller/aspeed,ast2400-vic.txt b/Documentation/devicetree/bindings/interrupt-controller/aspeed,ast2400-vic.txt
index 6c6e85324b9d..e3fea0758d25 100644
--- a/Documentation/devicetree/bindings/interrupt-controller/aspeed,ast2400-vic.txt
+++ b/Documentation/devicetree/bindings/interrupt-controller/aspeed,ast2400-vic.txt
@@ -1,12 +1,13 @@
Aspeed Vectored Interrupt Controller
-These bindings are for the Aspeed AST2400 interrupt controller register layout.
-The SoC has an legacy register layout, but this driver does not support that
-mode of operation.
+These bindings are for the Aspeed interrupt controller. The AST2400 and
+AST2500 SoC families include a legacy register layout before a re-designed
+layout, but the bindings do not prescribe the use of one or the other.
Required properties:
-- compatible : should be "aspeed,ast2400-vic".
+- compatible : "aspeed,ast2400-vic"
+ "aspeed,ast2500-vic"
- interrupt-controller : Identifies the node as an interrupt controller
- #interrupt-cells : Specifies the number of cells needed to encode an
diff --git a/Documentation/devicetree/bindings/interrupt-controller/marvell,gicp.txt b/Documentation/devicetree/bindings/interrupt-controller/marvell,gicp.txt
new file mode 100644
index 000000000000..64a00ceb7da4
--- /dev/null
+++ b/Documentation/devicetree/bindings/interrupt-controller/marvell,gicp.txt
@@ -0,0 +1,27 @@
+Marvell GICP Controller
+-----------------------
+
+GICP is a Marvell extension of the GIC that allows to trigger GIC SPI
+interrupts by doing a memory transaction. It is used by the ICU
+located in the Marvell CP110 to turn wired interrupts inside the CP
+into GIC SPI interrupts.
+
+Required properties:
+
+- compatible: Must be "marvell,ap806-gicp"
+
+- reg: Must be the address and size of the GICP SPI registers
+
+- marvell,spi-ranges: tuples of GIC SPI interrupts ranges available
+ for this GICP
+
+- msi-controller: indicates that this is an MSI controller
+
+Example:
+
+gicp_spi: gicp-spi@3f0040 {
+ compatible = "marvell,ap806-gicp";
+ reg = <0x3f0040 0x10>;
+ marvell,spi-ranges = <64 64>, <288 64>;
+ msi-controller;
+};
diff --git a/Documentation/devicetree/bindings/interrupt-controller/marvell,icu.txt b/Documentation/devicetree/bindings/interrupt-controller/marvell,icu.txt
new file mode 100644
index 000000000000..aa8bf2ec8905
--- /dev/null
+++ b/Documentation/devicetree/bindings/interrupt-controller/marvell,icu.txt
@@ -0,0 +1,51 @@
+Marvell ICU Interrupt Controller
+--------------------------------
+
+The Marvell ICU (Interrupt Consolidation Unit) controller is
+responsible for collecting all wired-interrupt sources in the CP and
+communicating them to the GIC in the AP, the unit translates interrupt
+requests on input wires to MSG memory mapped transactions to the GIC.
+
+Required properties:
+
+- compatible: Should be "marvell,cp110-icu"
+
+- reg: Should contain ICU registers location and length.
+
+- #interrupt-cells: Specifies the number of cells needed to encode an
+ interrupt source. The value shall be 3.
+
+ The 1st cell is the group type of the ICU interrupt. Possible group
+ types are:
+
+ ICU_GRP_NSR (0x0) : Shared peripheral interrupt, non-secure
+ ICU_GRP_SR (0x1) : Shared peripheral interrupt, secure
+ ICU_GRP_SEI (0x4) : System error interrupt
+ ICU_GRP_REI (0x5) : RAM error interrupt
+
+ The 2nd cell is the index of the interrupt in the ICU unit.
+
+ The 3rd cell is the type of the interrupt. See arm,gic.txt for
+ details.
+
+- interrupt-controller: Identifies the node as an interrupt
+ controller.
+
+- msi-parent: Should point to the GICP controller, the GIC extension
+ that allows to trigger interrupts using MSG memory mapped
+ transactions.
+
+Example:
+
+icu: interrupt-controller@1e0000 {
+ compatible = "marvell,cp110-icu";
+ reg = <0x1e0000 0x10>;
+ #interrupt-cells = <3>;
+ interrupt-controller;
+ msi-parent = <&gicp>;
+};
+
+usb3h0: usb3@500000 {
+ interrupt-parent = <&icu>;
+ interrupts = <ICU_GRP_NSR 106 IRQ_TYPE_LEVEL_HIGH>;
+};
diff --git a/drivers/irqchip/Kconfig b/drivers/irqchip/Kconfig
index 478f8ace2664..676232a94f9f 100644
--- a/drivers/irqchip/Kconfig
+++ b/drivers/irqchip/Kconfig
@@ -268,6 +268,12 @@ config IRQ_MXS
select IRQ_DOMAIN
select STMP_DEVICE
+config MVEBU_GICP
+ bool
+
+config MVEBU_ICU
+ bool
+
config MVEBU_ODMI
bool
select GENERIC_MSI_IRQ_DOMAIN
diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile
index b64c59b838a0..e88d856cc09c 100644
--- a/drivers/irqchip/Makefile
+++ b/drivers/irqchip/Makefile
@@ -69,10 +69,12 @@ obj-$(CONFIG_ARCH_SA1100) += irq-sa11x0.o
obj-$(CONFIG_INGENIC_IRQ) += irq-ingenic.o
obj-$(CONFIG_IMX_GPCV2) += irq-imx-gpcv2.o
obj-$(CONFIG_PIC32_EVIC) += irq-pic32-evic.o
+obj-$(CONFIG_MVEBU_GICP) += irq-mvebu-gicp.o
+obj-$(CONFIG_MVEBU_ICU) += irq-mvebu-icu.o
obj-$(CONFIG_MVEBU_ODMI) += irq-mvebu-odmi.o
obj-$(CONFIG_MVEBU_PIC) += irq-mvebu-pic.o
obj-$(CONFIG_LS_SCFG_MSI) += irq-ls-scfg-msi.o
obj-$(CONFIG_EZNPS_GIC) += irq-eznps.o
-obj-$(CONFIG_ARCH_ASPEED) += irq-aspeed-vic.o
+obj-$(CONFIG_ARCH_ASPEED) += irq-aspeed-vic.o irq-aspeed-i2c-ic.o
obj-$(CONFIG_STM32_EXTI) += irq-stm32-exti.o
obj-$(CONFIG_QCOM_IRQ_COMBINER) += qcom-irq-combiner.o
diff --git a/drivers/irqchip/irq-armada-370-xp.c b/drivers/irqchip/irq-armada-370-xp.c
index 5e16d042f281..b207b2c3aa55 100644
--- a/drivers/irqchip/irq-armada-370-xp.c
+++ b/drivers/irqchip/irq-armada-370-xp.c
@@ -34,25 +34,104 @@
#include <asm/smp_plat.h>
#include <asm/mach/irq.h>
-/* Interrupt Controller Registers Map */
-#define ARMADA_370_XP_INT_SET_MASK_OFFS (0x48)
-#define ARMADA_370_XP_INT_CLEAR_MASK_OFFS (0x4C)
-#define ARMADA_370_XP_INT_FABRIC_MASK_OFFS (0x54)
-#define ARMADA_370_XP_INT_CAUSE_PERF(cpu) (1 << cpu)
+/*
+ * Overall diagram of the Armada XP interrupt controller:
+ *
+ * To CPU 0 To CPU 1
+ *
+ * /\ /\
+ * || ||
+ * +---------------+ +---------------+
+ * | | | |
+ * | per-CPU | | per-CPU |
+ * | mask/unmask | | mask/unmask |
+ * | CPU0 | | CPU1 |
+ * | | | |
+ * +---------------+ +---------------+
+ * /\ /\
+ * || ||
+ * \\_______________________//
+ * ||
+ * +-------------------+
+ * | |
+ * | Global interrupt |
+ * | mask/unmask |
+ * | |
+ * +-------------------+
+ * /\
+ * ||
+ * interrupt from
+ * device
+ *
+ * The "global interrupt mask/unmask" is modified using the
+ * ARMADA_370_XP_INT_SET_ENABLE_OFFS and
+ * ARMADA_370_XP_INT_CLEAR_ENABLE_OFFS registers, which are relative
+ * to "main_int_base".
+ *
+ * The "per-CPU mask/unmask" is modified using the
+ * ARMADA_370_XP_INT_SET_MASK_OFFS and
+ * ARMADA_370_XP_INT_CLEAR_MASK_OFFS registers, which are relative to
+ * "per_cpu_int_base". This base address points to a special address,
+ * which automatically accesses the registers of the current CPU.
+ *
+ * The per-CPU mask/unmask can also be adjusted using the global
+ * per-interrupt ARMADA_370_XP_INT_SOURCE_CTL register, which we use
+ * to configure interrupt affinity.
+ *
+ * Due to this model, all interrupts need to be mask/unmasked at two
+ * different levels: at the global level and at the per-CPU level.
+ *
+ * This driver takes the following approach to deal with this:
+ *
+ * - For global interrupts:
+ *
+ * At ->map() time, a global interrupt is unmasked at the per-CPU
+ * mask/unmask level. It is therefore unmasked at this level for
+ * the current CPU, running the ->map() code. This allows to have
+ * the interrupt unmasked at this level in non-SMP
+ * configurations. In SMP configurations, the ->set_affinity()
+ * callback is called, which using the
+ * ARMADA_370_XP_INT_SOURCE_CTL() readjusts the per-CPU mask/unmask
+ * for the interrupt.
+ *
+ * The ->mask() and ->unmask() operations only mask/unmask the
+ * interrupt at the "global" level.
+ *
+ * So, a global interrupt is enabled at the per-CPU level as soon
+ * as it is mapped. At run time, the masking/unmasking takes place
+ * at the global level.
+ *
+ * - For per-CPU interrupts
+ *
+ * At ->map() time, a per-CPU interrupt is unmasked at the global
+ * mask/unmask level.
+ *
+ * The ->mask() and ->unmask() operations mask/unmask the interrupt
+ * at the per-CPU level.
+ *
+ * So, a per-CPU interrupt is enabled at the global level as soon
+ * as it is mapped. At run time, the masking/unmasking takes place
+ * at the per-CPU level.
+ */
+/* Registers relative to main_int_base */
#define ARMADA_370_XP_INT_CONTROL (0x00)
+#define ARMADA_370_XP_SW_TRIG_INT_OFFS (0x04)
#define ARMADA_370_XP_INT_SET_ENABLE_OFFS (0x30)
#define ARMADA_370_XP_INT_CLEAR_ENABLE_OFFS (0x34)
#define ARMADA_370_XP_INT_SOURCE_CTL(irq) (0x100 + irq*4)
#define ARMADA_370_XP_INT_SOURCE_CPU_MASK 0xF
#define ARMADA_370_XP_INT_IRQ_FIQ_MASK(cpuid) ((BIT(0) | BIT(8)) << cpuid)
-#define ARMADA_370_XP_CPU_INTACK_OFFS (0x44)
+/* Registers relative to per_cpu_int_base */
+#define ARMADA_370_XP_IN_DRBEL_CAUSE_OFFS (0x08)
+#define ARMADA_370_XP_IN_DRBEL_MSK_OFFS (0x0c)
#define ARMADA_375_PPI_CAUSE (0x10)
-
-#define ARMADA_370_XP_SW_TRIG_INT_OFFS (0x4)
-#define ARMADA_370_XP_IN_DRBEL_MSK_OFFS (0xc)
-#define ARMADA_370_XP_IN_DRBEL_CAUSE_OFFS (0x8)
+#define ARMADA_370_XP_CPU_INTACK_OFFS (0x44)
+#define ARMADA_370_XP_INT_SET_MASK_OFFS (0x48)
+#define ARMADA_370_XP_INT_CLEAR_MASK_OFFS (0x4C)
+#define ARMADA_370_XP_INT_FABRIC_MASK_OFFS (0x54)
+#define ARMADA_370_XP_INT_CAUSE_PERF(cpu) (1 << cpu)
#define ARMADA_370_XP_MAX_PER_CPU_IRQS (28)
@@ -281,13 +360,11 @@ static int armada_370_xp_mpic_irq_map(struct irq_domain *h,
irq_set_percpu_devid(virq);
irq_set_chip_and_handler(virq, &armada_370_xp_irq_chip,
handle_percpu_devid_irq);
-
} else {
irq_set_chip_and_handler(virq, &armada_370_xp_irq_chip,
handle_level_irq);
}
irq_set_probe(virq);
- irq_clear_status_flags(virq, IRQ_NOAUTOEN);
return 0;
}
@@ -345,16 +422,40 @@ static void armada_mpic_send_doorbell(const struct cpumask *mask,
ARMADA_370_XP_SW_TRIG_INT_OFFS);
}
+static void armada_xp_mpic_reenable_percpu(void)
+{
+ unsigned int irq;
+
+ /* Re-enable per-CPU interrupts that were enabled before suspend */
+ for (irq = 0; irq < ARMADA_370_XP_MAX_PER_CPU_IRQS; irq++) {
+ struct irq_data *data;
+ int virq;
+
+ virq = irq_linear_revmap(armada_370_xp_mpic_domain, irq);
+ if (virq == 0)
+ continue;
+
+ data = irq_get_irq_data(virq);
+
+ if (!irq_percpu_is_enabled(virq))
+ continue;
+
+ armada_370_xp_irq_unmask(data);
+ }
+}
+
static int armada_xp_mpic_starting_cpu(unsigned int cpu)
{
armada_xp_mpic_perf_init();
armada_xp_mpic_smp_cpu_init();
+ armada_xp_mpic_reenable_percpu();
return 0;
}
static int mpic_cascaded_starting_cpu(unsigned int cpu)
{
armada_xp_mpic_perf_init();
+ armada_xp_mpic_reenable_percpu();
enable_percpu_irq(parent_irq, IRQ_TYPE_NONE);
return 0;
}
@@ -502,16 +603,27 @@ static void armada_370_xp_mpic_resume(void)
if (virq == 0)
continue;
- if (!is_percpu_irq(irq))
+ data = irq_get_irq_data(virq);
+
+ if (!is_percpu_irq(irq)) {
+ /* Non per-CPU interrupts */
writel(irq, per_cpu_int_base +
ARMADA_370_XP_INT_CLEAR_MASK_OFFS);
- else
+ if (!irqd_irq_disabled(data))
+ armada_370_xp_irq_unmask(data);
+ } else {
+ /* Per-CPU interrupts */
writel(irq, main_int_base +
ARMADA_370_XP_INT_SET_ENABLE_OFFS);
- data = irq_get_irq_data(virq);
- if (!irqd_irq_disabled(data))
- armada_370_xp_irq_unmask(data);
+ /*
+ * Re-enable on the current CPU,
+ * armada_xp_mpic_reenable_percpu() will take
+ * care of secondary CPUs when they come up.
+ */
+ if (irq_percpu_is_enabled(virq))
+ armada_370_xp_irq_unmask(data);
+ }
}
/* Reconfigure doorbells for IPIs and MSIs */
diff --git a/drivers/irqchip/irq-aspeed-i2c-ic.c b/drivers/irqchip/irq-aspeed-i2c-ic.c
new file mode 100644
index 000000000000..815b88dd18f2
--- /dev/null
+++ b/drivers/irqchip/irq-aspeed-i2c-ic.c
@@ -0,0 +1,115 @@
+/*
+ * Aspeed 24XX/25XX I2C Interrupt Controller.
+ *
+ * Copyright (C) 2012-2017 ASPEED Technology Inc.
+ * Copyright 2017 IBM Corporation
+ * Copyright 2017 Google, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/irq.h>
+#include <linux/irqchip.h>
+#include <linux/irqchip/chained_irq.h>
+#include <linux/irqdomain.h>
+#include <linux/of_address.h>
+#include <linux/of_irq.h>
+#include <linux/io.h>
+
+
+#define ASPEED_I2C_IC_NUM_BUS 14
+
+struct aspeed_i2c_ic {
+ void __iomem *base;
+ int parent_irq;
+ struct irq_domain *irq_domain;
+};
+
+/*
+ * The aspeed chip provides a single hardware interrupt for all of the I2C
+ * busses, so we use a dummy interrupt chip to translate this single interrupt
+ * into multiple interrupts, each associated with a single I2C bus.
+ */
+static void aspeed_i2c_ic_irq_handler(struct irq_desc *desc)
+{
+ struct aspeed_i2c_ic *i2c_ic = irq_desc_get_handler_data(desc);
+ struct irq_chip *chip = irq_desc_get_chip(desc);
+ unsigned long bit, status;
+ unsigned int bus_irq;
+
+ chained_irq_enter(chip, desc);
+ status = readl(i2c_ic->base);
+ for_each_set_bit(bit, &status, ASPEED_I2C_IC_NUM_BUS) {
+ bus_irq = irq_find_mapping(i2c_ic->irq_domain, bit);
+ generic_handle_irq(bus_irq);
+ }
+ chained_irq_exit(chip, desc);
+}
+
+/*
+ * Set simple handler and mark IRQ as valid. Nothing interesting to do here
+ * since we are using a dummy interrupt chip.
+ */
+static int aspeed_i2c_ic_map_irq_domain(struct irq_domain *domain,
+ unsigned int irq, irq_hw_number_t hwirq)
+{
+ irq_set_chip_and_handler(irq, &dummy_irq_chip, handle_simple_irq);
+ irq_set_chip_data(irq, domain->host_data);
+
+ return 0;
+}
+
+static const struct irq_domain_ops aspeed_i2c_ic_irq_domain_ops = {
+ .map = aspeed_i2c_ic_map_irq_domain,
+};
+
+static int __init aspeed_i2c_ic_of_init(struct device_node *node,
+ struct device_node *parent)
+{
+ struct aspeed_i2c_ic *i2c_ic;
+ int ret = 0;
+
+ i2c_ic = kzalloc(sizeof(*i2c_ic), GFP_KERNEL);
+ if (!i2c_ic)
+ return -ENOMEM;
+
+ i2c_ic->base = of_iomap(node, 0);
+ if (IS_ERR(i2c_ic->base)) {
+ ret = PTR_ERR(i2c_ic->base);
+ goto err_free_ic;
+ }
+
+ i2c_ic->parent_irq = irq_of_parse_and_map(node, 0);
+ if (i2c_ic->parent_irq < 0) {
+ ret = i2c_ic->parent_irq;
+ goto err_iounmap;
+ }
+
+ i2c_ic->irq_domain = irq_domain_add_linear(node, ASPEED_I2C_IC_NUM_BUS,
+ &aspeed_i2c_ic_irq_domain_ops,
+ NULL);
+ if (!i2c_ic->irq_domain) {
+ ret = -ENOMEM;
+ goto err_iounmap;
+ }
+
+ i2c_ic->irq_domain->name = "aspeed-i2c-domain";
+
+ irq_set_chained_handler_and_data(i2c_ic->parent_irq,
+ aspeed_i2c_ic_irq_handler, i2c_ic);
+
+ pr_info("i2c controller registered, irq %d\n", i2c_ic->parent_irq);
+
+ return 0;
+
+err_iounmap:
+ iounmap(i2c_ic->base);
+err_free_ic:
+ kfree(i2c_ic);
+ return ret;
+}
+
+IRQCHIP_DECLARE(ast2400_i2c_ic, "aspeed,ast2400-i2c-ic", aspeed_i2c_ic_of_init);
+IRQCHIP_DECLARE(ast2500_i2c_ic, "aspeed,ast2500-i2c-ic", aspeed_i2c_ic_of_init);
diff --git a/drivers/irqchip/irq-aspeed-vic.c b/drivers/irqchip/irq-aspeed-vic.c
index d24451d5bf8a..03ba477ea0d0 100644
--- a/drivers/irqchip/irq-aspeed-vic.c
+++ b/drivers/irqchip/irq-aspeed-vic.c
@@ -186,7 +186,7 @@ static int avic_map(struct irq_domain *d, unsigned int irq,
return 0;
}
-static struct irq_domain_ops avic_dom_ops = {
+static const struct irq_domain_ops avic_dom_ops = {
.map = avic_map,
.xlate = irq_domain_xlate_onetwocell,
};
@@ -227,4 +227,5 @@ static int __init avic_of_init(struct device_node *node,
return 0;
}
-IRQCHIP_DECLARE(aspeed_new_vic, "aspeed,ast2400-vic", avic_of_init);
+IRQCHIP_DECLARE(ast2400_vic, "aspeed,ast2400-vic", avic_of_init);
+IRQCHIP_DECLARE(ast2500_vic, "aspeed,ast2500-vic", avic_of_init);
diff --git a/drivers/irqchip/irq-gic-v3-its-pci-msi.c b/drivers/irqchip/irq-gic-v3-its-pci-msi.c
index aee1c60d7ab5..77931214d954 100644
--- a/drivers/irqchip/irq-gic-v3-its-pci-msi.c
+++ b/drivers/irqchip/irq-gic-v3-its-pci-msi.c
@@ -41,27 +41,22 @@ static struct irq_chip its_msi_irq_chip = {
.irq_write_msi_msg = pci_msi_domain_write_msg,
};
-struct its_pci_alias {
- struct pci_dev *pdev;
- u32 count;
-};
-
-static int its_pci_msi_vec_count(struct pci_dev *pdev)
+static int its_pci_msi_vec_count(struct pci_dev *pdev, void *data)
{
- int msi, msix;
+ int msi, msix, *count = data;
msi = max(pci_msi_vec_count(pdev), 0);
msix = max(pci_msix_vec_count(pdev), 0);
+ *count += max(msi, msix);
- return max(msi, msix);
+ return 0;
}
static int its_get_pci_alias(struct pci_dev *pdev, u16 alias, void *data)
{
- struct its_pci_alias *dev_alias = data;
+ struct pci_dev **alias_dev = data;
- if (pdev != dev_alias->pdev)
- dev_alias->count += its_pci_msi_vec_count(pdev);
+ *alias_dev = pdev;
return 0;
}
@@ -69,9 +64,9 @@ static int its_get_pci_alias(struct pci_dev *pdev, u16 alias, void *data)
static int its_pci_msi_prepare(struct irq_domain *domain, struct device *dev,
int nvec, msi_alloc_info_t *info)
{
- struct pci_dev *pdev;
- struct its_pci_alias dev_alias;
+ struct pci_dev *pdev, *alias_dev;
struct msi_domain_info *msi_info;
+ int alias_count = 0;
if (!dev_is_pci(dev))
return -EINVAL;
@@ -79,16 +74,20 @@ static int its_pci_msi_prepare(struct irq_domain *domain, struct device *dev,
msi_info = msi_get_domain_info(domain->parent);
pdev = to_pci_dev(dev);
- dev_alias.pdev = pdev;
- dev_alias.count = nvec;
-
- pci_for_each_dma_alias(pdev, its_get_pci_alias, &dev_alias);
+ /*
+ * If pdev is downstream of any aliasing bridges, take an upper
+ * bound of how many other vectors could map to the same DevID.
+ */
+ pci_for_each_dma_alias(pdev, its_get_pci_alias, &alias_dev);
+ if (alias_dev != pdev && alias_dev->subordinate)
+ pci_walk_bus(alias_dev->subordinate, its_pci_msi_vec_count,
+ &alias_count);
/* ITS specific DeviceID, as the core ITS ignores dev. */
info->scratchpad[0].ul = pci_msi_domain_get_msi_rid(domain, pdev);
return msi_info->ops->msi_prepare(domain->parent,
- dev, dev_alias.count, info);
+ dev, max(nvec, alias_count), info);
}
static struct msi_domain_ops its_pci_msi_ops = {
diff --git a/drivers/irqchip/irq-gic-v3-its-platform-msi.c b/drivers/irqchip/irq-gic-v3-its-platform-msi.c
index 9e9dda33eb17..249240d9a425 100644
--- a/drivers/irqchip/irq-gic-v3-its-platform-msi.c
+++ b/drivers/irqchip/irq-gic-v3-its-platform-msi.c
@@ -86,7 +86,7 @@ static struct msi_domain_info its_pmsi_domain_info = {
.chip = &its_pmsi_irq_chip,
};
-static struct of_device_id its_device_id[] = {
+static const struct of_device_id its_device_id[] = {
{ .compatible = "arm,gic-v3-its", },
{},
};
diff --git a/drivers/irqchip/irq-gic-v3-its.c b/drivers/irqchip/irq-gic-v3-its.c
index 059016541277..68932873eebc 100644
--- a/drivers/irqchip/irq-gic-v3-its.c
+++ b/drivers/irqchip/irq-gic-v3-its.c
@@ -644,9 +644,12 @@ static int its_set_affinity(struct irq_data *d, const struct cpumask *mask_val,
if (cpu >= nr_cpu_ids)
return -EINVAL;
- target_col = &its_dev->its->collections[cpu];
- its_send_movi(its_dev, target_col, id);
- its_dev->event_map.col_map[id] = cpu;
+ /* don't set the affinity when the target cpu is same as current one */
+ if (cpu != its_dev->event_map.col_map[id]) {
+ target_col = &its_dev->its->collections[cpu];
+ its_send_movi(its_dev, target_col, id);
+ its_dev->event_map.col_map[id] = cpu;
+ }
return IRQ_SET_MASK_OK_DONE;
}
@@ -688,9 +691,11 @@ static struct irq_chip its_irq_chip = {
*/
#define IRQS_PER_CHUNK_SHIFT 5
#define IRQS_PER_CHUNK (1 << IRQS_PER_CHUNK_SHIFT)
+#define ITS_MAX_LPI_NRBITS 16 /* 64K LPIs */
static unsigned long *lpi_bitmap;
static u32 lpi_chunks;
+static u32 lpi_id_bits;
static DEFINE_SPINLOCK(lpi_lock);
static int its_lpi_to_chunk(int lpi)
@@ -786,17 +791,13 @@ static void its_lpi_free(struct event_lpi_map *map)
}
/*
- * We allocate 64kB for PROPBASE. That gives us at most 64K LPIs to
+ * We allocate memory for PROPBASE to cover 2 ^ lpi_id_bits LPIs to
* deal with (one configuration byte per interrupt). PENDBASE has to
* be 64kB aligned (one bit per LPI, plus 8192 bits for SPI/PPI/SGI).
*/
-#define LPI_PROPBASE_SZ SZ_64K
-#define LPI_PENDBASE_SZ (LPI_PROPBASE_SZ / 8 + SZ_1K)
-
-/*
- * This is how many bits of ID we need, including the useless ones.
- */
-#define LPI_NRBITS ilog2(LPI_PROPBASE_SZ + SZ_8K)
+#define LPI_NRBITS lpi_id_bits
+#define LPI_PROPBASE_SZ ALIGN(BIT(LPI_NRBITS), SZ_64K)
+#define LPI_PENDBASE_SZ ALIGN(BIT(LPI_NRBITS) / 8, SZ_64K)
#define LPI_PROP_DEFAULT_PRIO 0xa0
@@ -804,6 +805,7 @@ static int __init its_alloc_lpi_tables(void)
{
phys_addr_t paddr;
+ lpi_id_bits = min_t(u32, gic_rdists->id_bits, ITS_MAX_LPI_NRBITS);
gic_rdists->prop_page = alloc_pages(GFP_NOWAIT,
get_order(LPI_PROPBASE_SZ));
if (!gic_rdists->prop_page) {
@@ -822,7 +824,7 @@ static int __init its_alloc_lpi_tables(void)
/* Make sure the GIC will observe the written configuration */
gic_flush_dcache_to_poc(page_address(gic_rdists->prop_page), LPI_PROPBASE_SZ);
- return 0;
+ return its_lpi_init(lpi_id_bits);
}
static const char *its_base_type_string[] = {
@@ -1097,7 +1099,7 @@ static void its_cpu_init_lpis(void)
* hence the 'max(LPI_PENDBASE_SZ, SZ_64K)' below.
*/
pend_page = alloc_pages(GFP_NOWAIT | __GFP_ZERO,
- get_order(max(LPI_PENDBASE_SZ, SZ_64K)));
+ get_order(max_t(u32, LPI_PENDBASE_SZ, SZ_64K)));
if (!pend_page) {
pr_err("Failed to allocate PENDBASE for CPU%d\n",
smp_processor_id());
@@ -1801,7 +1803,7 @@ int its_cpu_init(void)
return 0;
}
-static struct of_device_id its_device_id[] = {
+static const struct of_device_id its_device_id[] = {
{ .compatible = "arm,gic-v3-its", },
{},
};
@@ -1833,6 +1835,78 @@ static int __init its_of_probe(struct device_node *node)
#define ACPI_GICV3_ITS_MEM_SIZE (SZ_128K)
+#if defined(CONFIG_ACPI_NUMA) && (ACPI_CA_VERSION >= 0x20170531)
+struct its_srat_map {
+ /* numa node id */
+ u32 numa_node;
+ /* GIC ITS ID */
+ u32 its_id;
+};
+
+static struct its_srat_map its_srat_maps[MAX_NUMNODES] __initdata;
+static int its_in_srat __initdata;
+
+static int __init acpi_get_its_numa_node(u32 its_id)
+{
+ int i;
+
+ for (i = 0; i < its_in_srat; i++) {
+ if (its_id == its_srat_maps[i].its_id)
+ return its_srat_maps[i].numa_node;
+ }
+ return NUMA_NO_NODE;
+}
+
+static int __init gic_acpi_parse_srat_its(struct acpi_subtable_header *header,
+ const unsigned long end)
+{
+ int node;
+ struct acpi_srat_gic_its_affinity *its_affinity;
+
+ its_affinity = (struct acpi_srat_gic_its_affinity *)header;
+ if (!its_affinity)
+ return -EINVAL;
+
+ if (its_affinity->header.length < sizeof(*its_affinity)) {
+ pr_err("SRAT: Invalid header length %d in ITS affinity\n",
+ its_affinity->header.length);
+ return -EINVAL;
+ }
+
+ if (its_in_srat >= MAX_NUMNODES) {
+ pr_err("SRAT: ITS affinity exceeding max count[%d]\n",
+ MAX_NUMNODES);
+ return -EINVAL;
+ }
+
+ node = acpi_map_pxm_to_node(its_affinity->proximity_domain);
+
+ if (node == NUMA_NO_NODE || node >= MAX_NUMNODES) {
+ pr_err("SRAT: Invalid NUMA node %d in ITS affinity\n", node);
+ return 0;
+ }
+
+ its_srat_maps[its_in_srat].numa_node = node;
+ its_srat_maps[its_in_srat].its_id = its_affinity->its_id;
+ its_in_srat++;
+ pr_info("SRAT: PXM %d -> ITS %d -> Node %d\n",
+ its_affinity->proximity_domain, its_affinity->its_id, node);
+
+ return 0;
+}
+
+static void __init acpi_table_parse_srat_its(void)
+{
+ acpi_table_parse_entries(ACPI_SIG_SRAT,
+ sizeof(struct acpi_table_srat),
+ ACPI_SRAT_TYPE_GIC_ITS_AFFINITY,
+ gic_acpi_parse_srat_its, 0);
+}
+#else
+static void __init acpi_table_parse_srat_its(void) { }
+static int __init acpi_get_its_numa_node(u32 its_id) { return NUMA_NO_NODE; }
+#endif
+
static int __init gic_acpi_parse_madt_its(struct acpi_subtable_header *header,
const unsigned long end)
{
@@ -1861,7 +1935,8 @@ static int __init gic_acpi_parse_madt_its(struct acpi_subtable_header *header,
goto dom_err;
}
- err = its_probe_one(&res, dom_handle, NUMA_NO_NODE);
+ err = its_probe_one(&res, dom_handle,
+ acpi_get_its_numa_node(its_entry->translation_id));
if (!err)
return 0;
@@ -1873,6 +1948,7 @@ dom_err:
static void __init its_acpi_probe(void)
{
+ acpi_table_parse_srat_its();
acpi_table_parse_madt(ACPI_MADT_TYPE_GENERIC_TRANSLATOR,
gic_acpi_parse_madt_its, 0);
}
@@ -1898,8 +1974,5 @@ int __init its_init(struct fwnode_handle *handle, struct rdists *rdists,
}
gic_rdists = rdists;
- its_alloc_lpi_tables();
- its_lpi_init(rdists->id_bits);
-
- return 0;
+ return its_alloc_lpi_tables();
}
diff --git a/drivers/irqchip/irq-i8259.c b/drivers/irqchip/irq-i8259.c
index 1aec12c6d9ac..7aafbb091b67 100644
--- a/drivers/irqchip/irq-i8259.c
+++ b/drivers/irqchip/irq-i8259.c
@@ -307,7 +307,7 @@ static int i8259A_irq_domain_map(struct irq_domain *d, unsigned int virq,
return 0;
}
-static struct irq_domain_ops i8259A_ops = {
+static const struct irq_domain_ops i8259A_ops = {
.map = i8259A_irq_domain_map,
.xlate = irq_domain_xlate_onecell,
};
diff --git a/drivers/irqchip/irq-imx-gpcv2.c b/drivers/irqchip/irq-imx-gpcv2.c
index 9463f3557e82..bb36f572e322 100644
--- a/drivers/irqchip/irq-imx-gpcv2.c
+++ b/drivers/irqchip/irq-imx-gpcv2.c
@@ -200,7 +200,7 @@ static int imx_gpcv2_domain_alloc(struct irq_domain *domain,
&parent_fwspec);
}
-static struct irq_domain_ops gpcv2_irqchip_data_domain_ops = {
+static const struct irq_domain_ops gpcv2_irqchip_data_domain_ops = {
.translate = imx_gpcv2_domain_translate,
.alloc = imx_gpcv2_domain_alloc,
.free = irq_domain_free_irqs_common,
diff --git a/drivers/irqchip/irq-mbigen.c b/drivers/irqchip/irq-mbigen.c
index 31d6b5a582d2..567b29c47608 100644
--- a/drivers/irqchip/irq-mbigen.c
+++ b/drivers/irqchip/irq-mbigen.c
@@ -228,7 +228,7 @@ static int mbigen_irq_domain_alloc(struct irq_domain *domain,
return 0;
}
-static struct irq_domain_ops mbigen_domain_ops = {
+static const struct irq_domain_ops mbigen_domain_ops = {
.translate = mbigen_domain_translate,
.alloc = mbigen_irq_domain_alloc,
.free = irq_domain_free_irqs_common,
diff --git a/drivers/irqchip/irq-mips-gic.c b/drivers/irqchip/irq-mips-gic.c
index d3a6dc800e3c..98f12ed4841c 100644
--- a/drivers/irqchip/irq-mips-gic.c
+++ b/drivers/irqchip/irq-mips-gic.c
@@ -874,7 +874,7 @@ int gic_ipi_domain_match(struct irq_domain *d, struct device_node *node,
}
}
-static struct irq_domain_ops gic_ipi_domain_ops = {
+static const struct irq_domain_ops gic_ipi_domain_ops = {
.xlate = gic_ipi_domain_xlate,
.alloc = gic_ipi_domain_alloc,
.free = gic_ipi_domain_free,
diff --git a/drivers/irqchip/irq-mvebu-gicp.c b/drivers/irqchip/irq-mvebu-gicp.c
new file mode 100644
index 000000000000..45358ac9bb1d
--- /dev/null
+++ b/drivers/irqchip/irq-mvebu-gicp.c
@@ -0,0 +1,279 @@
+/*
+ * Copyright (C) 2017 Marvell
+ *
+ * Thomas Petazzoni <thomas.petazzoni@free-electrons.com>
+ *
+ * This file is licensed under the terms of the GNU General Public
+ * License version 2. This program is licensed "as is" without any
+ * warranty of any kind, whether express or implied.
+ */
+
+#include <linux/io.h>
+#include <linux/irq.h>
+#include <linux/irqdomain.h>
+#include <linux/msi.h>
+#include <linux/of.h>
+#include <linux/of_irq.h>
+#include <linux/of_platform.h>
+#include <linux/platform_device.h>
+
+#include <dt-bindings/interrupt-controller/arm-gic.h>
+
+#include "irq-mvebu-gicp.h"
+
+#define GICP_SETSPI_NSR_OFFSET 0x0
+#define GICP_CLRSPI_NSR_OFFSET 0x8
+
+struct mvebu_gicp_spi_range {
+ unsigned int start;
+ unsigned int count;
+};
+
+struct mvebu_gicp {
+ struct mvebu_gicp_spi_range *spi_ranges;
+ unsigned int spi_ranges_cnt;
+ unsigned int spi_cnt;
+ unsigned long *spi_bitmap;
+ spinlock_t spi_lock;
+ struct resource *res;
+ struct device *dev;
+};
+
+static int gicp_idx_to_spi(struct mvebu_gicp *gicp, int idx)
+{
+ int i;
+
+ for (i = 0; i < gicp->spi_ranges_cnt; i++) {
+ struct mvebu_gicp_spi_range *r = &gicp->spi_ranges[i];
+
+ if (idx < r->count)
+ return r->start + idx;
+
+ idx -= r->count;
+ }
+
+ return -EINVAL;
+}
+
+int mvebu_gicp_get_doorbells(struct device_node *dn, phys_addr_t *setspi,
+ phys_addr_t *clrspi)
+{
+ struct platform_device *pdev;
+ struct mvebu_gicp *gicp;
+
+ pdev = of_find_device_by_node(dn);
+ if (!pdev)
+ return -ENODEV;
+
+ gicp = platform_get_drvdata(pdev);
+ if (!gicp)
+ return -ENODEV;
+
+ *setspi = gicp->res->start + GICP_SETSPI_NSR_OFFSET;
+ *clrspi = gicp->res->start + GICP_CLRSPI_NSR_OFFSET;
+
+ return 0;
+}
+
+static void gicp_compose_msi_msg(struct irq_data *data, struct msi_msg *msg)
+{
+ struct mvebu_gicp *gicp = data->chip_data;
+ phys_addr_t setspi = gicp->res->start + GICP_SETSPI_NSR_OFFSET;
+
+ msg->data = data->hwirq;
+ msg->address_lo = lower_32_bits(setspi);
+ msg->address_hi = upper_32_bits(setspi);
+}
+
+static struct irq_chip gicp_irq_chip = {
+ .name = "GICP",
+ .irq_mask = irq_chip_mask_parent,
+ .irq_unmask = irq_chip_unmask_parent,
+ .irq_eoi = irq_chip_eoi_parent,
+ .irq_set_affinity = irq_chip_set_affinity_parent,
+ .irq_set_type = irq_chip_set_type_parent,
+ .irq_compose_msi_msg = gicp_compose_msi_msg,
+};
+
+static int gicp_irq_domain_alloc(struct irq_domain *domain, unsigned int virq,
+ unsigned int nr_irqs, void *args)
+{
+ struct mvebu_gicp *gicp = domain->host_data;
+ struct irq_fwspec fwspec;
+ unsigned int hwirq;
+ int ret;
+
+ spin_lock(&gicp->spi_lock);
+ hwirq = find_first_zero_bit(gicp->spi_bitmap, gicp->spi_cnt);
+ if (hwirq == gicp->spi_cnt) {
+ spin_unlock(&gicp->spi_lock);
+ return -ENOSPC;
+ }
+ __set_bit(hwirq, gicp->spi_bitmap);
+ spin_unlock(&gicp->spi_lock);
+
+ fwspec.fwnode = domain->parent->fwnode;
+ fwspec.param_count = 3;
+ fwspec.param[0] = GIC_SPI;
+ fwspec.param[1] = gicp_idx_to_spi(gicp, hwirq) - 32;
+ /*
+ * Assume edge rising for now, it will be properly set when
+ * ->set_type() is called
+ */
+ fwspec.param[2] = IRQ_TYPE_EDGE_RISING;
+
+ ret = irq_domain_alloc_irqs_parent(domain, virq, 1, &fwspec);
+ if (ret) {
+ dev_err(gicp->dev, "Cannot allocate parent IRQ\n");
+ goto free_hwirq;
+ }
+
+ ret = irq_domain_set_hwirq_and_chip(domain, virq, hwirq,
+ &gicp_irq_chip, gicp);
+ if (ret)
+ goto free_irqs_parent;
+
+ return 0;
+
+free_irqs_parent:
+ irq_domain_free_irqs_parent(domain, virq, nr_irqs);
+free_hwirq:
+ spin_lock(&gicp->spi_lock);
+ __clear_bit(hwirq, gicp->spi_bitmap);
+ spin_unlock(&gicp->spi_lock);
+ return ret;
+}
+
+static void gicp_irq_domain_free(struct irq_domain *domain,
+ unsigned int virq, unsigned int nr_irqs)
+{
+ struct mvebu_gicp *gicp = domain->host_data;
+ struct irq_data *d = irq_domain_get_irq_data(domain, virq);
+
+ if (d->hwirq >= gicp->spi_cnt) {
+ dev_err(gicp->dev, "Invalid hwirq %lu\n", d->hwirq);
+ return;
+ }
+
+ irq_domain_free_irqs_parent(domain, virq, nr_irqs);
+
+ spin_lock(&gicp->spi_lock);
+ __clear_bit(d->hwirq, gicp->spi_bitmap);
+ spin_unlock(&gicp->spi_lock);
+}
+
+static const struct irq_domain_ops gicp_domain_ops = {
+ .alloc = gicp_irq_domain_alloc,
+ .free = gicp_irq_domain_free,
+};
+
+static struct irq_chip gicp_msi_irq_chip = {
+ .name = "GICP",
+ .irq_set_type = irq_chip_set_type_parent,
+};
+
+static struct msi_domain_ops gicp_msi_ops = {
+};
+
+static struct msi_domain_info gicp_msi_domain_info = {
+ .flags = (MSI_FLAG_USE_DEF_DOM_OPS | MSI_FLAG_USE_DEF_CHIP_OPS),
+ .ops = &gicp_msi_ops,
+ .chip = &gicp_msi_irq_chip,
+};
+
+static int mvebu_gicp_probe(struct platform_device *pdev)
+{
+ struct mvebu_gicp *gicp;
+ struct irq_domain *inner_domain, *plat_domain, *parent_domain;
+ struct device_node *node = pdev->dev.of_node;
+ struct device_node *irq_parent_dn;
+ int ret, i;
+
+ gicp = devm_kzalloc(&pdev->dev, sizeof(*gicp), GFP_KERNEL);
+ if (!gicp)
+ return -ENOMEM;
+
+ gicp->dev = &pdev->dev;
+
+ gicp->res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!gicp->res)
+ return -ENODEV;
+
+ ret = of_property_count_u32_elems(node, "marvell,spi-ranges");
+ if (ret < 0)
+ return ret;
+
+ gicp->spi_ranges_cnt = ret / 2;
+
+ gicp->spi_ranges =
+ devm_kzalloc(&pdev->dev,
+ gicp->spi_ranges_cnt *
+ sizeof(struct mvebu_gicp_spi_range),
+ GFP_KERNEL);
+ if (!gicp->spi_ranges)
+ return -ENOMEM;
+
+ for (i = 0; i < gicp->spi_ranges_cnt; i++) {
+ of_property_read_u32_index(node, "marvell,spi-ranges",
+ i * 2,
+ &gicp->spi_ranges[i].start);
+
+ of_property_read_u32_index(node, "marvell,spi-ranges",
+ i * 2 + 1,
+ &gicp->spi_ranges[i].count);
+
+ gicp->spi_cnt += gicp->spi_ranges[i].count;
+ }
+
+ gicp->spi_bitmap = devm_kzalloc(&pdev->dev,
+ BITS_TO_LONGS(gicp->spi_cnt),
+ GFP_KERNEL);
+ if (!gicp->spi_bitmap)
+ return -ENOMEM;
+
+ irq_parent_dn = of_irq_find_parent(node);
+ if (!irq_parent_dn) {
+ dev_err(&pdev->dev, "failed to find parent IRQ node\n");
+ return -ENODEV;
+ }
+
+ parent_domain = irq_find_host(irq_parent_dn);
+ if (!parent_domain) {
+ dev_err(&pdev->dev, "failed to find parent IRQ domain\n");
+ return -ENODEV;
+ }
+
+ inner_domain = irq_domain_create_hierarchy(parent_domain, 0,
+ gicp->spi_cnt,
+ of_node_to_fwnode(node),
+ &gicp_domain_ops, gicp);
+ if (!inner_domain)
+ return -ENOMEM;
+
+
+ plat_domain = platform_msi_create_irq_domain(of_node_to_fwnode(node),
+ &gicp_msi_domain_info,
+ inner_domain);
+ if (!plat_domain) {
+ irq_domain_remove(inner_domain);
+ return -ENOMEM;
+ }
+
+ platform_set_drvdata(pdev, gicp);
+
+ return 0;
+}
+
+static const struct of_device_id mvebu_gicp_of_match[] = {
+ { .compatible = "marvell,ap806-gicp", },
+ {},
+};
+
+static struct platform_driver mvebu_gicp_driver = {
+ .probe = mvebu_gicp_probe,
+ .driver = {
+ .name = "mvebu-gicp",
+ .of_match_table = mvebu_gicp_of_match,
+ },
+};
+builtin_platform_driver(mvebu_gicp_driver);
diff --git a/drivers/irqchip/irq-mvebu-gicp.h b/drivers/irqchip/irq-mvebu-gicp.h
new file mode 100644
index 000000000000..98535e886ea5
--- /dev/null
+++ b/drivers/irqchip/irq-mvebu-gicp.h
@@ -0,0 +1,11 @@
+#ifndef __MVEBU_GICP_H__
+#define __MVEBU_GICP_H__
+
+#include <linux/types.h>
+
+struct device_node;
+
+int mvebu_gicp_get_doorbells(struct device_node *dn, phys_addr_t *setspi,
+ phys_addr_t *clrspi);
+
+#endif /* __MVEBU_GICP_H__ */
diff --git a/drivers/irqchip/irq-mvebu-icu.c b/drivers/irqchip/irq-mvebu-icu.c
new file mode 100644
index 000000000000..e18c48d3a92e
--- /dev/null
+++ b/drivers/irqchip/irq-mvebu-icu.c
@@ -0,0 +1,289 @@
+/*
+ * Copyright (C) 2017 Marvell
+ *
+ * Hanna Hawa <hannah@marvell.com>
+ * Thomas Petazzoni <thomas.petazzoni@free-electrons.com>
+ *
+ * This file is licensed under the terms of the GNU General Public
+ * License version 2. This program is licensed "as is" without any
+ * warranty of any kind, whether express or implied.
+ */
+
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/irqchip.h>
+#include <linux/irqdomain.h>
+#include <linux/kernel.h>
+#include <linux/msi.h>
+#include <linux/of_irq.h>
+#include <linux/of_platform.h>
+#include <linux/platform_device.h>
+
+#include <dt-bindings/interrupt-controller/mvebu-icu.h>
+
+#include "irq-mvebu-gicp.h"
+
+/* ICU registers */
+#define ICU_SETSPI_NSR_AL 0x10
+#define ICU_SETSPI_NSR_AH 0x14
+#define ICU_CLRSPI_NSR_AL 0x18
+#define ICU_CLRSPI_NSR_AH 0x1c
+#define ICU_INT_CFG(x) (0x100 + 4 * (x))
+#define ICU_INT_ENABLE BIT(24)
+#define ICU_IS_EDGE BIT(28)
+#define ICU_GROUP_SHIFT 29
+
+/* ICU definitions */
+#define ICU_MAX_IRQS 207
+#define ICU_SATA0_ICU_ID 109
+#define ICU_SATA1_ICU_ID 107
+
+struct mvebu_icu {
+ struct irq_chip irq_chip;
+ void __iomem *base;
+ struct irq_domain *domain;
+ struct device *dev;
+};
+
+struct mvebu_icu_irq_data {
+ struct mvebu_icu *icu;
+ unsigned int icu_group;
+ unsigned int type;
+};
+
+static void mvebu_icu_write_msg(struct msi_desc *desc, struct msi_msg *msg)
+{
+ struct irq_data *d = irq_get_irq_data(desc->irq);
+ struct mvebu_icu_irq_data *icu_irqd = d->chip_data;
+ struct mvebu_icu *icu = icu_irqd->icu;
+ unsigned int icu_int;
+
+ if (msg->address_lo || msg->address_hi) {
+ /* Configure the ICU with irq number & type */
+ icu_int = msg->data | ICU_INT_ENABLE;
+ if (icu_irqd->type & IRQ_TYPE_EDGE_RISING)
+ icu_int |= ICU_IS_EDGE;
+ icu_int |= icu_irqd->icu_group << ICU_GROUP_SHIFT;
+ } else {
+ /* De-configure the ICU */
+ icu_int = 0;
+ }
+
+ writel_relaxed(icu_int, icu->base + ICU_INT_CFG(d->hwirq));
+
+ /*
+ * The SATA unit has 2 ports, and a dedicated ICU entry per
+ * port. The ahci sata driver supports only one irq interrupt
+ * per SATA unit. To solve this conflict, we configure the 2
+ * SATA wired interrupts in the south bridge into 1 GIC
+ * interrupt in the north bridge. Even if only a single port
+ * is enabled, if sata node is enabled, both interrupts are
+ * configured (regardless of which port is actually in use).
+ */
+ if (d->hwirq == ICU_SATA0_ICU_ID || d->hwirq == ICU_SATA1_ICU_ID) {
+ writel_relaxed(icu_int,
+ icu->base + ICU_INT_CFG(ICU_SATA0_ICU_ID));
+ writel_relaxed(icu_int,
+ icu->base + ICU_INT_CFG(ICU_SATA1_ICU_ID));
+ }
+}
+
+static int
+mvebu_icu_irq_domain_translate(struct irq_domain *d, struct irq_fwspec *fwspec,
+ unsigned long *hwirq, unsigned int *type)
+{
+ struct mvebu_icu *icu = d->host_data;
+ unsigned int icu_group;
+
+ /* Check the count of the parameters in dt */
+ if (WARN_ON(fwspec->param_count < 3)) {
+ dev_err(icu->dev, "wrong ICU parameter count %d\n",
+ fwspec->param_count);
+ return -EINVAL;
+ }
+
+ /* Only ICU group type is handled */
+ icu_group = fwspec->param[0];
+ if (icu_group != ICU_GRP_NSR && icu_group != ICU_GRP_SR &&
+ icu_group != ICU_GRP_SEI && icu_group != ICU_GRP_REI) {
+ dev_err(icu->dev, "wrong ICU group type %x\n", icu_group);
+ return -EINVAL;
+ }
+
+ *hwirq = fwspec->param[1];
+ if (*hwirq >= ICU_MAX_IRQS) {
+ dev_err(icu->dev, "invalid interrupt number %ld\n", *hwirq);
+ return -EINVAL;
+ }
+
+ /* Mask the type to prevent wrong DT configuration */
+ *type = fwspec->param[2] & IRQ_TYPE_SENSE_MASK;
+
+ return 0;
+}
+
+static int
+mvebu_icu_irq_domain_alloc(struct irq_domain *domain, unsigned int virq,
+ unsigned int nr_irqs, void *args)
+{
+ int err;
+ unsigned long hwirq;
+ struct irq_fwspec *fwspec = args;
+ struct mvebu_icu *icu = platform_msi_get_host_data(domain);
+ struct mvebu_icu_irq_data *icu_irqd;
+
+ icu_irqd = kmalloc(sizeof(*icu_irqd), GFP_KERNEL);
+ if (!icu_irqd)
+ return -ENOMEM;
+
+ err = mvebu_icu_irq_domain_translate(domain, fwspec, &hwirq,
+ &icu_irqd->type);
+ if (err) {
+ dev_err(icu->dev, "failed to translate ICU parameters\n");
+ goto free_irqd;
+ }
+
+ icu_irqd->icu_group = fwspec->param[0];
+ icu_irqd->icu = icu;
+
+ err = platform_msi_domain_alloc(domain, virq, nr_irqs);
+ if (err) {
+ dev_err(icu->dev, "failed to allocate ICU interrupt in parent domain\n");
+ goto free_irqd;
+ }
+
+ /* Make sure there is no interrupt left pending by the firmware */
+ err = irq_set_irqchip_state(virq, IRQCHIP_STATE_PENDING, false);
+ if (err)
+ goto free_msi;
+
+ err = irq_domain_set_hwirq_and_chip(domain, virq, hwirq,
+ &icu->irq_chip, icu_irqd);
+ if (err) {
+ dev_err(icu->dev, "failed to set the data to IRQ domain\n");
+ goto free_msi;
+ }
+
+ return 0;
+
+free_msi:
+ platform_msi_domain_free(domain, virq, nr_irqs);
+free_irqd:
+ kfree(icu_irqd);
+ return err;
+}
+
+static void
+mvebu_icu_irq_domain_free(struct irq_domain *domain, unsigned int virq,
+ unsigned int nr_irqs)
+{
+ struct irq_data *d = irq_get_irq_data(virq);
+ struct mvebu_icu_irq_data *icu_irqd = d->chip_data;
+
+ kfree(icu_irqd);
+
+ platform_msi_domain_free(domain, virq, nr_irqs);
+}
+
+static const struct irq_domain_ops mvebu_icu_domain_ops = {
+ .translate = mvebu_icu_irq_domain_translate,
+ .alloc = mvebu_icu_irq_domain_alloc,
+ .free = mvebu_icu_irq_domain_free,
+};
+
+static int mvebu_icu_probe(struct platform_device *pdev)
+{
+ struct mvebu_icu *icu;
+ struct device_node *node = pdev->dev.of_node;
+ struct device_node *gicp_dn;
+ struct resource *res;
+ phys_addr_t setspi, clrspi;
+ u32 i, icu_int;
+ int ret;
+
+ icu = devm_kzalloc(&pdev->dev, sizeof(struct mvebu_icu),
+ GFP_KERNEL);
+ if (!icu)
+ return -ENOMEM;
+
+ icu->dev = &pdev->dev;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ icu->base = devm_ioremap_resource(&pdev->dev, res);
+ if (IS_ERR(icu->base)) {
+ dev_err(&pdev->dev, "Failed to map icu base address.\n");
+ return PTR_ERR(icu->base);
+ }
+
+ icu->irq_chip.name = devm_kasprintf(&pdev->dev, GFP_KERNEL,
+ "ICU.%x",
+ (unsigned int)res->start);
+ if (!icu->irq_chip.name)
+ return -ENOMEM;
+
+ icu->irq_chip.irq_mask = irq_chip_mask_parent;
+ icu->irq_chip.irq_unmask = irq_chip_unmask_parent;
+ icu->irq_chip.irq_eoi = irq_chip_eoi_parent;
+ icu->irq_chip.irq_set_type = irq_chip_set_type_parent;
+#ifdef CONFIG_SMP
+ icu->irq_chip.irq_set_affinity = irq_chip_set_affinity_parent;
+#endif
+
+ /*
+ * We're probed after MSI domains have been resolved, so force
+ * resolution here.
+ */
+ pdev->dev.msi_domain = of_msi_get_domain(&pdev->dev, node,
+ DOMAIN_BUS_PLATFORM_MSI);
+ if (!pdev->dev.msi_domain)
+ return -EPROBE_DEFER;
+
+ gicp_dn = irq_domain_get_of_node(pdev->dev.msi_domain);
+ if (!gicp_dn)
+ return -ENODEV;
+
+ ret = mvebu_gicp_get_doorbells(gicp_dn, &setspi, &clrspi);
+ if (ret)
+ return ret;
+
+ /* Set Clear/Set ICU SPI message address in AP */
+ writel_relaxed(upper_32_bits(setspi), icu->base + ICU_SETSPI_NSR_AH);
+ writel_relaxed(lower_32_bits(setspi), icu->base + ICU_SETSPI_NSR_AL);
+ writel_relaxed(upper_32_bits(clrspi), icu->base + ICU_CLRSPI_NSR_AH);
+ writel_relaxed(lower_32_bits(clrspi), icu->base + ICU_CLRSPI_NSR_AL);
+
+ /*
+ * Clean all ICU interrupts with type SPI_NSR, required to
+ * avoid unpredictable SPI assignments done by firmware.
+ */
+ for (i = 0 ; i < ICU_MAX_IRQS ; i++) {
+ icu_int = readl(icu->base + ICU_INT_CFG(i));
+ if ((icu_int >> ICU_GROUP_SHIFT) == ICU_GRP_NSR)
+ writel_relaxed(0x0, icu->base + ICU_INT_CFG(i));
+ }
+
+ icu->domain =
+ platform_msi_create_device_domain(&pdev->dev, ICU_MAX_IRQS,
+ mvebu_icu_write_msg,
+ &mvebu_icu_domain_ops, icu);
+ if (!icu->domain) {
+ dev_err(&pdev->dev, "Failed to create ICU domain\n");
+ return -ENOMEM;
+ }
+
+ return 0;
+}
+
+static const struct of_device_id mvebu_icu_of_match[] = {
+ { .compatible = "marvell,cp110-icu", },
+ {},
+};
+
+static struct platform_driver mvebu_icu_driver = {
+ .probe = mvebu_icu_probe,
+ .driver = {
+ .name = "mvebu-icu",
+ .of_match_table = mvebu_icu_of_match,
+ },
+};
+builtin_platform_driver(mvebu_icu_driver);
diff --git a/drivers/irqchip/irq-renesas-h8300h.c b/drivers/irqchip/irq-renesas-h8300h.c
index c378768d75b3..b8327590ae52 100644
--- a/drivers/irqchip/irq-renesas-h8300h.c
+++ b/drivers/irqchip/irq-renesas-h8300h.c
@@ -67,7 +67,7 @@ static int irq_map(struct irq_domain *h, unsigned int virq,
return 0;
}
-static struct irq_domain_ops irq_ops = {
+static const struct irq_domain_ops irq_ops = {
.map = irq_map,
.xlate = irq_domain_xlate_onecell,
};
diff --git a/drivers/irqchip/irq-renesas-h8s.c b/drivers/irqchip/irq-renesas-h8s.c
index af8c6c61c824..71d8139be26c 100644
--- a/drivers/irqchip/irq-renesas-h8s.c
+++ b/drivers/irqchip/irq-renesas-h8s.c
@@ -73,7 +73,7 @@ static __init int irq_map(struct irq_domain *h, unsigned int virq,
return 0;
}
-static struct irq_domain_ops irq_ops = {
+static const struct irq_domain_ops irq_ops = {
.map = irq_map,
.xlate = irq_domain_xlate_onecell,
};
diff --git a/drivers/irqchip/irq-sunxi-nmi.c b/drivers/irqchip/irq-sunxi-nmi.c
index 668730c5cb66..a412b5d5d0fa 100644
--- a/drivers/irqchip/irq-sunxi-nmi.c
+++ b/drivers/irqchip/irq-sunxi-nmi.c
@@ -25,6 +25,29 @@
#define SUNXI_NMI_SRC_TYPE_MASK 0x00000003
+#define SUNXI_NMI_IRQ_BIT BIT(0)
+
+#define SUN6I_R_INTC_CTRL 0x0c
+#define SUN6I_R_INTC_PENDING 0x10
+#define SUN6I_R_INTC_ENABLE 0x40
+
+/*
+ * For deprecated sun6i-a31-sc-nmi compatible.
+ * Registers are offset by 0x0c.
+ */
+#define SUN6I_R_INTC_NMI_OFFSET 0x0c
+#define SUN6I_NMI_CTRL (SUN6I_R_INTC_CTRL - SUN6I_R_INTC_NMI_OFFSET)
+#define SUN6I_NMI_PENDING (SUN6I_R_INTC_PENDING - SUN6I_R_INTC_NMI_OFFSET)
+#define SUN6I_NMI_ENABLE (SUN6I_R_INTC_ENABLE - SUN6I_R_INTC_NMI_OFFSET)
+
+#define SUN7I_NMI_CTRL 0x00
+#define SUN7I_NMI_PENDING 0x04
+#define SUN7I_NMI_ENABLE 0x08
+
+#define SUN9I_NMI_CTRL 0x00
+#define SUN9I_NMI_ENABLE 0x04
+#define SUN9I_NMI_PENDING 0x08
+
enum {
SUNXI_SRC_TYPE_LEVEL_LOW = 0,
SUNXI_SRC_TYPE_EDGE_FALLING,
@@ -38,22 +61,28 @@ struct sunxi_sc_nmi_reg_offs {
u32 enable;
};
-static struct sunxi_sc_nmi_reg_offs sun7i_reg_offs = {
- .ctrl = 0x00,
- .pend = 0x04,
- .enable = 0x08,
+static const struct sunxi_sc_nmi_reg_offs sun6i_r_intc_reg_offs __initconst = {
+ .ctrl = SUN6I_R_INTC_CTRL,
+ .pend = SUN6I_R_INTC_PENDING,
+ .enable = SUN6I_R_INTC_ENABLE,
};
-static struct sunxi_sc_nmi_reg_offs sun6i_reg_offs = {
- .ctrl = 0x00,
- .pend = 0x04,
- .enable = 0x34,
+static const struct sunxi_sc_nmi_reg_offs sun6i_reg_offs __initconst = {
+ .ctrl = SUN6I_NMI_CTRL,
+ .pend = SUN6I_NMI_PENDING,
+ .enable = SUN6I_NMI_ENABLE,
};
-static struct sunxi_sc_nmi_reg_offs sun9i_reg_offs = {
- .ctrl = 0x00,
- .pend = 0x08,
- .enable = 0x04,
+static const struct sunxi_sc_nmi_reg_offs sun7i_reg_offs __initconst = {
+ .ctrl = SUN7I_NMI_CTRL,
+ .pend = SUN7I_NMI_PENDING,
+ .enable = SUN7I_NMI_ENABLE,
+};
+
+static const struct sunxi_sc_nmi_reg_offs sun9i_reg_offs __initconst = {
+ .ctrl = SUN9I_NMI_CTRL,
+ .pend = SUN9I_NMI_PENDING,
+ .enable = SUN9I_NMI_ENABLE,
};
static inline void sunxi_sc_nmi_write(struct irq_chip_generic *gc, u32 off,
@@ -128,7 +157,7 @@ static int sunxi_sc_nmi_set_type(struct irq_data *data, unsigned int flow_type)
}
static int __init sunxi_sc_nmi_irq_init(struct device_node *node,
- struct sunxi_sc_nmi_reg_offs *reg_offs)
+ const struct sunxi_sc_nmi_reg_offs *reg_offs)
{
struct irq_domain *domain;
struct irq_chip_generic *gc;
@@ -187,8 +216,11 @@ static int __init sunxi_sc_nmi_irq_init(struct device_node *node,
gc->chip_types[1].regs.type = reg_offs->ctrl;
gc->chip_types[1].handler = handle_edge_irq;
+ /* Disable any active interrupts */
sunxi_sc_nmi_write(gc, reg_offs->enable, 0);
- sunxi_sc_nmi_write(gc, reg_offs->pend, 0x1);
+
+ /* Clear any pending NMI interrupts */
+ sunxi_sc_nmi_write(gc, reg_offs->pend, SUNXI_NMI_IRQ_BIT);
irq_set_chained_handler_and_data(irq, sunxi_sc_nmi_handle_irq, domain);
@@ -200,6 +232,14 @@ fail_irqd_remove:
return ret;
}
+static int __init sun6i_r_intc_irq_init(struct device_node *node,
+ struct device_node *parent)
+{
+ return sunxi_sc_nmi_irq_init(node, &sun6i_r_intc_reg_offs);
+}
+IRQCHIP_DECLARE(sun6i_r_intc, "allwinner,sun6i-a31-r-intc",
+ sun6i_r_intc_irq_init);
+
static int __init sun6i_sc_nmi_irq_init(struct device_node *node,
struct device_node *parent)
{
diff --git a/drivers/irqchip/qcom-irq-combiner.c b/drivers/irqchip/qcom-irq-combiner.c
index 226558698344..6aa3ea479214 100644
--- a/drivers/irqchip/qcom-irq-combiner.c
+++ b/drivers/irqchip/qcom-irq-combiner.c
@@ -288,9 +288,4 @@ static struct platform_driver qcom_irq_combiner_probe = {
},
.probe = combiner_probe,
};
-
-static int __init register_qcom_irq_combiner(void)
-{
- return platform_driver_register(&qcom_irq_combiner_probe);
-}
-device_initcall(register_qcom_irq_combiner);
+builtin_platform_driver(qcom_irq_combiner_probe);
diff --git a/include/dt-bindings/interrupt-controller/mvebu-icu.h b/include/dt-bindings/interrupt-controller/mvebu-icu.h
new file mode 100644
index 000000000000..8249558545c7
--- /dev/null
+++ b/include/dt-bindings/interrupt-controller/mvebu-icu.h
@@ -0,0 +1,15 @@
+/*
+ * This header provides constants for the MVEBU ICU driver.
+ */
+
+#ifndef _DT_BINDINGS_INTERRUPT_CONTROLLER_MVEBU_ICU_H
+#define _DT_BINDINGS_INTERRUPT_CONTROLLER_MVEBU_ICU_H
+
+/* interrupt specifier cell 0 */
+
+#define ICU_GRP_NSR 0x0
+#define ICU_GRP_SR 0x1
+#define ICU_GRP_SEI 0x4
+#define ICU_GRP_REI 0x5
+
+#endif