ARM: imx6: convert GPC to stacked domains
authorMarc Zyngier <marc.zyngier@arm.com>
Mon, 23 Feb 2015 17:45:18 +0000 (17:45 +0000)
committerShawn Guo <shawn.guo@linaro.org>
Mon, 30 Mar 2015 08:42:15 +0000 (16:42 +0800)
IMX6 has been (ab)using the gic_arch_extn to provide
wakeup from suspend, and it makes a lot of sense to convert
this code to use stacked domains instead.

This patch does just this, updating the DT files to actually
reflect what the HW provides.

BIG FAT WARNING: because the DTs were so far lying by not
exposing the fact that the GPC block is actually the first
interrupt controller in the chain, kernels with this patch
applied wont have any suspend-resume facility when booted
with old DTs, and old kernels with updated DTs won't even boot.

Tested-by: Stefan Agner <stefan@agner.ch>
Acked-by: Stefan Agner <stefan@agner.ch>
Signed-off-by: Marc Zyngier <marc.zyngier@arm.com>
Signed-off-by: Shawn Guo <shawn.guo@linaro.org>
arch/arm/boot/dts/imx6qdl.dtsi
arch/arm/boot/dts/imx6sl.dtsi
arch/arm/boot/dts/imx6sx.dtsi
arch/arm/mach-imx/common.h
arch/arm/mach-imx/gpc.c
arch/arm/mach-imx/mach-imx6q.c
arch/arm/mach-imx/mach-imx6sl.c
arch/arm/mach-imx/mach-imx6sx.c
arch/arm/mach-imx/pm-imx6.c

index 1b6f380e7eaad6e9c30ec5cd81e787dafed6e91c..da09dc4568143f873acbc9d68b1b29aa304505ca 100644 (file)
@@ -53,6 +53,7 @@
                interrupt-controller;
                reg = <0x00a01000 0x1000>,
                      <0x00a00100 0x100>;
+               interrupt-parent = <&intc>;
        };
 
        clocks {
@@ -82,7 +83,7 @@
                #address-cells = <1>;
                #size-cells = <1>;
                compatible = "simple-bus";
-               interrupt-parent = <&intc>;
+               interrupt-parent = <&gpc>;
                ranges;
 
                dma_apbh: dma-apbh@00110000 {
                        compatible = "arm,cortex-a9-twd-timer";
                        reg = <0x00a00600 0x20>;
                        interrupts = <1 13 0xf01>;
+                       interrupt-parent = <&intc>;
                        clocks = <&clks IMX6QDL_CLK_TWD>;
                };
 
                        gpc: gpc@020dc000 {
                                compatible = "fsl,imx6q-gpc";
                                reg = <0x020dc000 0x4000>;
+                               interrupt-controller;
+                               #interrupt-cells = <3>;
                                interrupts = <0 89 IRQ_TYPE_LEVEL_HIGH>,
                                             <0 90 IRQ_TYPE_LEVEL_HIGH>;
+                               interrupt-parent = <&intc>;
                        };
 
                        gpr: iomuxc-gpr@020e0000 {
index 36ab8e054cee0a8ff5fcc1fb6938dc1d62a1e09f..0d0962bf37c4fbd7c2784ba21d44709aed6178c0 100644 (file)
@@ -72,6 +72,7 @@
                interrupt-controller;
                reg = <0x00a01000 0x1000>,
                      <0x00a00100 0x100>;
+               interrupt-parent = <&intc>;
        };
 
        clocks {
@@ -95,7 +96,7 @@
                #address-cells = <1>;
                #size-cells = <1>;
                compatible = "simple-bus";
-               interrupt-parent = <&intc>;
+               interrupt-parent = <&gpc>;
                ranges;
 
                ocram: sram@00900000 {
                        gpc: gpc@020dc000 {
                                compatible = "fsl,imx6sl-gpc", "fsl,imx6q-gpc";
                                reg = <0x020dc000 0x4000>;
+                               interrupt-controller;
+                               #interrupt-cells = <3>;
                                interrupts = <0 89 IRQ_TYPE_LEVEL_HIGH>;
+                               interrupt-parent = <&intc>;
                        };
 
                        gpr: iomuxc-gpr@020e0000 {
index 7a24fee1e7aecf7bc16df8c241e42a5ba5e5e561..dabaf89a5dd98b600b1d02a706c86c2a7a9ed058 100644 (file)
@@ -88,6 +88,7 @@
                interrupt-controller;
                reg = <0x00a01000 0x1000>,
                      <0x00a00100 0x100>;
+               interrupt-parent = <&intc>;
        };
 
        clocks {
                #address-cells = <1>;
                #size-cells = <1>;
                compatible = "simple-bus";
-               interrupt-parent = <&intc>;
+               interrupt-parent = <&gpc>;
                ranges;
 
                pmu {
                        gpc: gpc@020dc000 {
                                compatible = "fsl,imx6sx-gpc", "fsl,imx6q-gpc";
                                reg = <0x020dc000 0x4000>;
+                               interrupt-controller;
+                               #interrupt-cells = <3>;
                                interrupts = <GIC_SPI 89 IRQ_TYPE_LEVEL_HIGH>;
+                               interrupt-parent = <&intc>;
                        };
 
                        iomuxc: iomuxc@020e0000 {
index 771ecfe96c14cb07806a6dc0ce8365da051b58de..2fbdc283bc996625c0876004461e4b2c65a404a0 100644 (file)
@@ -101,7 +101,6 @@ static inline void imx_scu_map_io(void) {}
 static inline void imx_smp_prepare(void) {}
 #endif
 void imx_src_init(void);
-void imx_gpc_init(void);
 void imx_gpc_pre_suspend(bool arm_power_off);
 void imx_gpc_post_resume(void);
 void imx_gpc_mask_all(void);
index 029f59ce2712a00332b6cc0b969417b445aa80e0..6f1f77ed0c715fc986d46af5f7d97745c44b6d1d 100644 (file)
@@ -36,6 +36,7 @@
 #define GPC_PGC_SW_SHIFT       0x0
 
 #define IMR_NUM                        4
+#define GPC_MAX_IRQS           (IMR_NUM * 32)
 
 #define GPU_VPU_PUP_REQ                BIT(1)
 #define GPU_VPU_PDN_REQ                BIT(0)
@@ -99,17 +100,17 @@ void imx_gpc_post_resume(void)
 
 static int imx_gpc_irq_set_wake(struct irq_data *d, unsigned int on)
 {
-       unsigned int idx = d->hwirq / 32 - 1;
+       unsigned int idx = d->hwirq / 32;
        u32 mask;
 
-       /* Sanity check for SPI irq */
-       if (d->hwirq < 32)
-               return -EINVAL;
-
        mask = 1 << d->hwirq % 32;
        gpc_wake_irqs[idx] = on ? gpc_wake_irqs[idx] | mask :
                                  gpc_wake_irqs[idx] & ~mask;
 
+       /*
+        * Do *not* call into the parent, as the GIC doesn't have any
+        * wake-up facility...
+        */
        return 0;
 }
 
@@ -139,7 +140,7 @@ void imx_gpc_hwirq_unmask(unsigned int hwirq)
        void __iomem *reg;
        u32 val;
 
-       reg = gpc_base + GPC_IMR1 + (hwirq / 32 - 1) * 4;
+       reg = gpc_base + GPC_IMR1 + hwirq / 32 * 4;
        val = readl_relaxed(reg);
        val &= ~(1 << hwirq % 32);
        writel_relaxed(val, reg);
@@ -150,7 +151,7 @@ void imx_gpc_hwirq_mask(unsigned int hwirq)
        void __iomem *reg;
        u32 val;
 
-       reg = gpc_base + GPC_IMR1 + (hwirq / 32 - 1) * 4;
+       reg = gpc_base + GPC_IMR1 + hwirq / 32 * 4;
        val = readl_relaxed(reg);
        val |= 1 << (hwirq % 32);
        writel_relaxed(val, reg);
@@ -158,41 +159,119 @@ void imx_gpc_hwirq_mask(unsigned int hwirq)
 
 static void imx_gpc_irq_unmask(struct irq_data *d)
 {
-       /* Sanity check for SPI irq */
-       if (d->hwirq < 32)
-               return;
-
        imx_gpc_hwirq_unmask(d->hwirq);
+       irq_chip_unmask_parent(d);
 }
 
 static void imx_gpc_irq_mask(struct irq_data *d)
 {
-       /* Sanity check for SPI irq */
-       if (d->hwirq < 32)
-               return;
-
        imx_gpc_hwirq_mask(d->hwirq);
+       irq_chip_mask_parent(d);
 }
 
-void __init imx_gpc_init(void)
+static struct irq_chip imx_gpc_chip = {
+       .name           = "GPC",
+       .irq_eoi        = irq_chip_eoi_parent,
+       .irq_mask       = imx_gpc_irq_mask,
+       .irq_unmask     = imx_gpc_irq_unmask,
+       .irq_retrigger  = irq_chip_retrigger_hierarchy,
+       .irq_set_wake   = imx_gpc_irq_set_wake,
+};
+
+static int imx_gpc_domain_xlate(struct irq_domain *domain,
+                               struct device_node *controller,
+                               const u32 *intspec,
+                               unsigned int intsize,
+                               unsigned long *out_hwirq,
+                               unsigned int *out_type)
 {
-       struct device_node *np;
+       if (domain->of_node != controller)
+               return -EINVAL; /* Shouldn't happen, really... */
+       if (intsize != 3)
+               return -EINVAL; /* Not GIC compliant */
+       if (intspec[0] != 0)
+               return -EINVAL; /* No PPI should point to this domain */
+
+       *out_hwirq = intspec[1];
+       *out_type = intspec[2];
+       return 0;
+}
+
+static int imx_gpc_domain_alloc(struct irq_domain *domain,
+                                 unsigned int irq,
+                                 unsigned int nr_irqs, void *data)
+{
+       struct of_phandle_args *args = data;
+       struct of_phandle_args parent_args;
+       irq_hw_number_t hwirq;
        int i;
 
-       np = of_find_compatible_node(NULL, NULL, "fsl,imx6q-gpc");
-       gpc_base = of_iomap(np, 0);
-       WARN_ON(!gpc_base);
+       if (args->args_count != 3)
+               return -EINVAL; /* Not GIC compliant */
+       if (args->args[0] != 0)
+               return -EINVAL; /* No PPI should point to this domain */
+
+       hwirq = args->args[1];
+       if (hwirq >= GPC_MAX_IRQS)
+               return -EINVAL; /* Can't deal with this */
+
+       for (i = 0; i < nr_irqs; i++)
+               irq_domain_set_hwirq_and_chip(domain, irq + i, hwirq + i,
+                                             &imx_gpc_chip, NULL);
+
+       parent_args = *args;
+       parent_args.np = domain->parent->of_node;
+       return irq_domain_alloc_irqs_parent(domain, irq, nr_irqs, &parent_args);
+}
+
+static struct irq_domain_ops imx_gpc_domain_ops = {
+       .xlate  = imx_gpc_domain_xlate,
+       .alloc  = imx_gpc_domain_alloc,
+       .free   = irq_domain_free_irqs_common,
+};
+
+static int __init imx_gpc_init(struct device_node *node,
+                              struct device_node *parent)
+{
+       struct irq_domain *parent_domain, *domain;
+       int i;
+
+       if (!parent) {
+               pr_err("%s: no parent, giving up\n", node->full_name);
+               return -ENODEV;
+       }
+
+       parent_domain = irq_find_host(parent);
+       if (!parent_domain) {
+               pr_err("%s: unable to obtain parent domain\n", node->full_name);
+               return -ENXIO;
+       }
+
+       gpc_base = of_iomap(node, 0);
+       if (WARN_ON(!gpc_base))
+               return -ENOMEM;
+
+       domain = irq_domain_add_hierarchy(parent_domain, 0, GPC_MAX_IRQS,
+                                         node, &imx_gpc_domain_ops,
+                                         NULL);
+       if (!domain) {
+               iounmap(gpc_base);
+               return -ENOMEM;
+       }
 
        /* Initially mask all interrupts */
        for (i = 0; i < IMR_NUM; i++)
                writel_relaxed(~0, gpc_base + GPC_IMR1 + i * 4);
 
-       /* Register GPC as the secondary interrupt controller behind GIC */
-       gic_arch_extn.irq_mask = imx_gpc_irq_mask;
-       gic_arch_extn.irq_unmask = imx_gpc_irq_unmask;
-       gic_arch_extn.irq_set_wake = imx_gpc_irq_set_wake;
+       return 0;
 }
 
+/*
+ * We cannot use the IRQCHIP_DECLARE macro that lives in
+ * drivers/irqchip, so we're forced to roll our own. Not very nice.
+ */
+OF_DECLARE_2(irqchip, imx_gpc, "fsl,imx6q-gpc", imx_gpc_init);
+
 #ifdef CONFIG_PM_GENERIC_DOMAINS
 
 static void _imx6q_pm_pu_power_off(struct generic_pm_domain *genpd)
index 4ad6e473cf83ab82e3a769ddcae8956762f47be9..6fc2b7e89c6b20de7e430b9b5c8c0479760db442 100644 (file)
@@ -390,7 +390,6 @@ static void __init imx6q_init_irq(void)
        imx_init_revision_from_anatop();
        imx_init_l2cache();
        imx_src_init();
-       imx_gpc_init();
        irqchip_init();
 }
 
index 24bfaaf944c8459f4e2947b1d855ce37cb8dd9b7..d39c274910c5c23afc03b99479d7c4b64c94440d 100644 (file)
@@ -64,7 +64,6 @@ static void __init imx6sl_init_irq(void)
        imx_init_revision_from_anatop();
        imx_init_l2cache();
        imx_src_init();
-       imx_gpc_init();
        irqchip_init();
 }
 
index 66988eb6a3a4dc5ac520bd2a19421e76eaaf28fd..8595f9ea30a0e2b0ceed1d820f897dd40b0b14d6 100644 (file)
@@ -84,7 +84,6 @@ static void __init imx6sx_init_irq(void)
        imx_init_revision_from_anatop();
        imx_init_l2cache();
        imx_src_init();
-       imx_gpc_init();
        irqchip_init();
 }
 
index 46fd695203c70a22fdc4389b39161d1e6ef5a5b8..6a7c6fc780cce686650ea684d2965c30de3746df 100644 (file)
@@ -310,10 +310,12 @@ int imx6q_set_lpm(enum mxc_cpu_pwr_mode mode)
         *    Low-Power mode.
         * 3) Software should mask IRQ #32 right after CCM Low-Power mode
         *    is set (set bits 0-1 of CCM_CLPCR).
+        *
+        * Note that IRQ #32 is GIC SPI #0.
         */
-       imx_gpc_hwirq_unmask(32);
+       imx_gpc_hwirq_unmask(0);
        writel_relaxed(val, ccm_base + CLPCR);
-       imx_gpc_hwirq_mask(32);
+       imx_gpc_hwirq_mask(0);
 
        return 0;
 }