]> git.kernelconcepts.de Git - karo-tx-linux.git/commitdiff
Merge tag 'irqchip-4.13' of git://git.kernel.org/pub/scm/linux/kernel/git/maz/arm...
authorThomas Gleixner <tglx@linutronix.de>
Fri, 23 Jun 2017 12:26:29 +0000 (14:26 +0200)
committerThomas Gleixner <tglx@linutronix.de>
Fri, 23 Jun 2017 12:26:29 +0000 (14:26 +0200)
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

25 files changed:
Documentation/devicetree/bindings/interrupt-controller/allwinner,sunxi-nmi.txt
Documentation/devicetree/bindings/interrupt-controller/aspeed,ast2400-i2c-ic.txt [new file with mode: 0644]
Documentation/devicetree/bindings/interrupt-controller/aspeed,ast2400-vic.txt
Documentation/devicetree/bindings/interrupt-controller/marvell,gicp.txt [new file with mode: 0644]
Documentation/devicetree/bindings/interrupt-controller/marvell,icu.txt [new file with mode: 0644]
drivers/irqchip/Kconfig
drivers/irqchip/Makefile
drivers/irqchip/irq-armada-370-xp.c
drivers/irqchip/irq-aspeed-i2c-ic.c [new file with mode: 0644]
drivers/irqchip/irq-aspeed-vic.c
drivers/irqchip/irq-gic-v3-its-pci-msi.c
drivers/irqchip/irq-gic-v3-its-platform-msi.c
drivers/irqchip/irq-gic-v3-its.c
drivers/irqchip/irq-i8259.c
drivers/irqchip/irq-imx-gpcv2.c
drivers/irqchip/irq-mbigen.c
drivers/irqchip/irq-mips-gic.c
drivers/irqchip/irq-mvebu-gicp.c [new file with mode: 0644]
drivers/irqchip/irq-mvebu-gicp.h [new file with mode: 0644]
drivers/irqchip/irq-mvebu-icu.c [new file with mode: 0644]
drivers/irqchip/irq-renesas-h8300h.c
drivers/irqchip/irq-renesas-h8s.c
drivers/irqchip/irq-sunxi-nmi.c
drivers/irqchip/qcom-irq-combiner.c
include/dt-bindings/interrupt-controller/mvebu-icu.h [new file with mode: 0644]

index 81cd3692405e5fc7c74ab2ea6aca0d9d942c0969..4ae553eb333d00c749c75b0ee385f02f92b8bd42 100644 (file)
@@ -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 (file)
index 0000000..033cc82
--- /dev/null
@@ -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;
+};
index 6c6e85324b9db0cec116af86d710db1ca94161b9..e3fea0758d2535cb0598337941a7a995a275651f 100644 (file)
@@ -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 (file)
index 0000000..64a00ce
--- /dev/null
@@ -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 (file)
index 0000000..aa8bf2e
--- /dev/null
@@ -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>;
+};
index 478f8ace266418d73e357c7eb09ba882818afb90..676232a94f9fdc08963ea13640ba6de88cb8c2c3 100644 (file)
@@ -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
index b64c59b838a02d10c4d88c3035355683fd80d942..e88d856cc09cc8707776d9c359aaa2eea96563a3 100644 (file)
@@ -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
index 5e16d042f281097b8cc287ca484edf90095004ee..b207b2c3aa5558efc0e4542e0b42309b681daf26 100644 (file)
 #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 (file)
index 0000000..815b88d
--- /dev/null
@@ -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);
index d24451d5bf8ab1f9921bc4da2b1bd2ffe5e4b09b..03ba477ea0d0c4d69b7e5facd16e538ee5ffc1c7 100644 (file)
@@ -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);
index aee1c60d7ab5689cc9cb550e666fb21b5db035a6..77931214d954ddd35d3ab05e4079275cb00e688c 100644 (file)
@@ -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 = {
index 9e9dda33eb17460bd661c2a992a06594752bffb5..249240d9a4259eb72b7afb68c2e2723a72e7c9c5 100644 (file)
@@ -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",     },
        {},
 };
index 0590165412771887629ab5be0471f7a39680c289..68932873eebc0c3d14caa00b048d0c961e765a98 100644 (file)
@@ -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();
 }
index 1aec12c6d9ac83cb4338d4c0c44a78b83c1e01d8..7aafbb091b67cc1f6c8897b92d07697226e2c866 100644 (file)
@@ -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,
 };
index 9463f3557e82f410880dbc2ee84351c37449f244..bb36f572e3223f61f4c62d5f2c1b39223d551f07 100644 (file)
@@ -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,
index 31d6b5a582d28a2b4fdd0d0c0f0bf9e6113a9e4d..567b29c476081056232f13eed15a27c8d8d4fa64 100644 (file)
@@ -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,
index d3a6dc800e3c7decfb595dacc92dedece37c3f57..98f12ed4841cbefa02635cd5066fffa336c6d47a 100644 (file)
@@ -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 (file)
index 0000000..45358ac
--- /dev/null
@@ -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 (file)
index 0000000..98535e8
--- /dev/null
@@ -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 (file)
index 0000000..e18c48d
--- /dev/null
@@ -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);
index c378768d75b333866778f0d2e63567a1d249cc4a..b8327590ae521e62c7555c26ae8e37b62c168a28 100644 (file)
@@ -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,
 };
index af8c6c61c8243445f803ad1be0f63e09124564ca..71d8139be26cd43f18582b6f5d5186a0c578494c 100644 (file)
@@ -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,
 };
index 668730c5cb66f3bf4b06846ea4daf4b97dd3675d..a412b5d5d0facab6e063d567f0dc06d063044073 100644 (file)
 
 #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)
 {
index 22655869834435900773448735d3b1403aca4b66..6aa3ea4792148d057b9b5d766acd0a7dd5813979 100644 (file)
@@ -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 (file)
index 0000000..8249558
--- /dev/null
@@ -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