Merge branch 'cpuidle/biglittle' into next/drivers
authorOlof Johansson <olof@lixom.net>
Wed, 28 Aug 2013 18:29:18 +0000 (11:29 -0700)
committerOlof Johansson <olof@lixom.net>
Wed, 28 Aug 2013 18:29:18 +0000 (11:29 -0700)
From Lorenzo Pieralisi:
This patch series contains:

- GIC driver update to add a method to disable the GIC CPU IF
- TC2 MCPM update to add GIC CPU disabling to suspend method
- TC2 CPU idle big.LITTLE driver

* cpuidle/biglittle:
  cpuidle: big.LITTLE: vexpress-TC2 CPU idle driver
  ARM: vexpress: tc2: disable GIC CPU IF in tc2_pm_suspend
  drivers: irq-chip: irq-gic: introduce gic_cpu_if_down()
  ARM: vexpress/TC2: implement PM suspend method
  ARM: vexpress/TC2: basic PM support
  ARM: vexpress: Add SCC to V2P-CA15_A7's device tree
  ARM: vexpress/TC2: add Serial Power Controller (SPC) support
  ARM: vexpress/dcscb: fix cache disabling sequences

Signed-off-by: Olof Johansson <olof@lixom.net>
14 files changed:
Documentation/devicetree/bindings/arm/vexpress-scc.txt [new file with mode: 0644]
MAINTAINERS
arch/arm/boot/dts/vexpress-v2p-ca15_a7.dts
arch/arm/mach-vexpress/Kconfig
arch/arm/mach-vexpress/Makefile
arch/arm/mach-vexpress/dcscb.c
arch/arm/mach-vexpress/spc.c [new file with mode: 0644]
arch/arm/mach-vexpress/spc.h [new file with mode: 0644]
arch/arm/mach-vexpress/tc2_pm.c [new file with mode: 0644]
drivers/cpuidle/Kconfig
drivers/cpuidle/Makefile
drivers/cpuidle/cpuidle-big_little.c [new file with mode: 0644]
drivers/irqchip/irq-gic.c
include/linux/irqchip/arm-gic.h

diff --git a/Documentation/devicetree/bindings/arm/vexpress-scc.txt b/Documentation/devicetree/bindings/arm/vexpress-scc.txt
new file mode 100644 (file)
index 0000000..ae5043e
--- /dev/null
@@ -0,0 +1,33 @@
+ARM Versatile Express Serial Configuration Controller
+-----------------------------------------------------
+
+Test chips for ARM Versatile Express platform implement SCC (Serial
+Configuration Controller) interface, used to set initial conditions
+for the test chip.
+
+In some cases its registers are also mapped in normal address space
+and can be used to obtain runtime information about the chip internals
+(like silicon temperature sensors) and as interface to other subsystems
+like platform configuration control and power management.
+
+Required properties:
+
+- compatible value: "arm,vexpress-scc,<model>", "arm,vexpress-scc";
+                   where <model> is the full tile model name (as used
+                   in the tile's Technical Reference Manual),
+                   eg. for Coretile Express A15x2 A7x3 (V2P-CA15_A7):
+       compatible = "arm,vexpress-scc,v2p-ca15_a7", "arm,vexpress-scc";
+
+Optional properties:
+
+- reg: when the SCC is memory mapped, physical address and size of the
+       registers window
+- interrupts: when the SCC can generate a system-level interrupt
+
+Example:
+
+       scc@7fff0000 {
+               compatible = "arm,vexpress-scc,v2p-ca15_a7", "arm,vexpress-scc";
+               reg = <0 0x7fff0000 0 0x1000>;
+               interrupts = <0 95 4>;
+       };
index 229c66b..a0001ef 100644 (file)
@@ -2268,6 +2268,15 @@ F:       drivers/cpufreq/arm_big_little.h
 F:     drivers/cpufreq/arm_big_little.c
 F:     drivers/cpufreq/arm_big_little_dt.c
 
+CPUIDLE DRIVER - ARM BIG LITTLE
+M:      Lorenzo Pieralisi <lorenzo.pieralisi@arm.com>
+M:      Daniel Lezcano <daniel.lezcano@linaro.org>
+L:      linux-pm@vger.kernel.org
+L:      linux-arm-kernel@lists.infradead.org
+T:      git://git.kernel.org/pub/scm/linux/kernel/git/rafael/linux-pm.git
+S:      Maintained
+F:      drivers/cpuidle/cpuidle-big_little.c
+
 CPUIDLE DRIVERS
 M:     Rafael J. Wysocki <rjw@sisk.pl>
 M:     Daniel Lezcano <daniel.lezcano@linaro.org>
index d2803be..759b0cd 100644 (file)
                clock-names = "apb_pclk";
        };
 
+        scc@7fff0000 {
+               compatible = "arm,vexpress-scc,v2p-ca15_a7", "arm,vexpress-scc";
+               reg = <0 0x7fff0000 0 0x1000>;
+               interrupts = <0 95 4>;
+        };
+
        timer {
                compatible = "arm,armv7-timer";
                interrupts = <1 13 0xf08>,
index b8bbabe..c700e62 100644 (file)
@@ -66,4 +66,12 @@ config ARCH_VEXPRESS_DCSCB
          This is needed to provide CPU and cluster power management
          on RTSM implementing big.LITTLE.
 
+config ARCH_VEXPRESS_TC2_PM
+       bool "Versatile Express TC2 power management"
+       depends on MCPM
+       select ARM_CCI
+       help
+         Support for CPU and cluster power management on Versatile Express
+         with a TC2 (A15x2 A7x3) big.LITTLE core tile.
+
 endmenu
index 48ba89a..36ea824 100644 (file)
@@ -7,5 +7,6 @@ ccflags-$(CONFIG_ARCH_MULTIPLATFORM) := -I$(srctree)/$(src)/include \
 obj-y                                  := v2m.o
 obj-$(CONFIG_ARCH_VEXPRESS_CA9X4)      += ct-ca9x4.o
 obj-$(CONFIG_ARCH_VEXPRESS_DCSCB)      += dcscb.o      dcscb_setup.o
+obj-$(CONFIG_ARCH_VEXPRESS_TC2_PM)     += tc2_pm.o spc.o
 obj-$(CONFIG_SMP)                      += platsmp.o
 obj-$(CONFIG_HOTPLUG_CPU)              += hotplug.o
index 16d57a8..85fffa7 100644 (file)
@@ -136,14 +136,29 @@ static void dcscb_power_down(void)
                /*
                 * Flush all cache levels for this cluster.
                 *
-                * A15/A7 can hit in the cache with SCTLR.C=0, so we don't need
-                * a preliminary flush here for those CPUs.  At least, that's
-                * the theory -- without the extra flush, Linux explodes on
-                * RTSM (to be investigated).
+                * To do so we do:
+                * - Clear the SCTLR.C bit to prevent further cache allocations
+                * - Flush the whole cache
+                * - Clear the ACTLR "SMP" bit to disable local coherency
+                *
+                * Let's do it in the safest possible way i.e. with
+                * no memory access within the following sequence
+                * including to the stack.
                 */
-               flush_cache_all();
-               set_cr(get_cr() & ~CR_C);
-               flush_cache_all();
+               asm volatile(
+               "mrc    p15, 0, r0, c1, c0, 0   @ get CR \n\t"
+               "bic    r0, r0, #"__stringify(CR_C)" \n\t"
+               "mcr    p15, 0, r0, c1, c0, 0   @ set CR \n\t"
+               "isb    \n\t"
+               "bl     v7_flush_dcache_all \n\t"
+               "clrex  \n\t"
+               "mrc    p15, 0, r0, c1, c0, 1   @ get AUXCR \n\t"
+               "bic    r0, r0, #(1 << 6)       @ disable local coherency \n\t"
+               "mcr    p15, 0, r0, c1, c0, 1   @ set AUXCR \n\t"
+               "isb    \n\t"
+               "dsb    "
+               : : : "r0","r1","r2","r3","r4","r5","r6","r7",
+                     "r9","r10","r11","lr","memory");
 
                /*
                 * This is a harmless no-op.  On platforms with a real
@@ -152,9 +167,6 @@ static void dcscb_power_down(void)
                 */
                outer_flush_all();
 
-               /* Disable local coherency by clearing the ACTLR "SMP" bit: */
-               set_auxcr(get_auxcr() & ~(1 << 6));
-
                /*
                 * Disable cluster-level coherency by masking
                 * incoming snoops and DVM messages:
@@ -167,18 +179,22 @@ static void dcscb_power_down(void)
 
                /*
                 * Flush the local CPU cache.
-                *
-                * A15/A7 can hit in the cache with SCTLR.C=0, so we don't need
-                * a preliminary flush here for those CPUs.  At least, that's
-                * the theory -- without the extra flush, Linux explodes on
-                * RTSM (to be investigated).
+                * Let's do it in the safest possible way as above.
                 */
-               flush_cache_louis();
-               set_cr(get_cr() & ~CR_C);
-               flush_cache_louis();
-
-               /* Disable local coherency by clearing the ACTLR "SMP" bit: */
-               set_auxcr(get_auxcr() & ~(1 << 6));
+               asm volatile(
+               "mrc    p15, 0, r0, c1, c0, 0   @ get CR \n\t"
+               "bic    r0, r0, #"__stringify(CR_C)" \n\t"
+               "mcr    p15, 0, r0, c1, c0, 0   @ set CR \n\t"
+               "isb    \n\t"
+               "bl     v7_flush_dcache_louis \n\t"
+               "clrex  \n\t"
+               "mrc    p15, 0, r0, c1, c0, 1   @ get AUXCR \n\t"
+               "bic    r0, r0, #(1 << 6)       @ disable local coherency \n\t"
+               "mcr    p15, 0, r0, c1, c0, 1   @ set AUXCR \n\t"
+               "isb    \n\t"
+               "dsb    "
+               : : : "r0","r1","r2","r3","r4","r5","r6","r7",
+                     "r9","r10","r11","lr","memory");
        }
 
        __mcpm_cpu_down(cpu, cluster);
diff --git a/arch/arm/mach-vexpress/spc.c b/arch/arm/mach-vexpress/spc.c
new file mode 100644 (file)
index 0000000..eefb029
--- /dev/null
@@ -0,0 +1,180 @@
+/*
+ * Versatile Express Serial Power Controller (SPC) support
+ *
+ * Copyright (C) 2013 ARM Ltd.
+ *
+ * Authors: Sudeep KarkadaNagesha <sudeep.karkadanagesha@arm.com>
+ *          Achin Gupta           <achin.gupta@arm.com>
+ *          Lorenzo Pieralisi     <lorenzo.pieralisi@arm.com>
+ *
+ * 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.
+ *
+ * This program is distributed "as is" WITHOUT ANY WARRANTY of any
+ * kind, whether express or implied; without even the implied warranty
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/slab.h>
+
+#include <asm/cacheflush.h>
+
+#define SPCLOG "vexpress-spc: "
+
+/* SPC wake-up IRQs status and mask */
+#define WAKE_INT_MASK          0x24
+#define WAKE_INT_RAW           0x28
+#define WAKE_INT_STAT          0x2c
+/* SPC power down registers */
+#define A15_PWRDN_EN           0x30
+#define A7_PWRDN_EN            0x34
+/* SPC per-CPU mailboxes */
+#define A15_BX_ADDR0           0x68
+#define A7_BX_ADDR0            0x78
+
+/* wake-up interrupt masks */
+#define GBL_WAKEUP_INT_MSK     (0x3 << 10)
+
+/* TC2 static dual-cluster configuration */
+#define MAX_CLUSTERS           2
+
+struct ve_spc_drvdata {
+       void __iomem *baseaddr;
+       /*
+        * A15s cluster identifier
+        * It corresponds to A15 processors MPIDR[15:8] bitfield
+        */
+       u32 a15_clusid;
+};
+
+static struct ve_spc_drvdata *info;
+
+static inline bool cluster_is_a15(u32 cluster)
+{
+       return cluster == info->a15_clusid;
+}
+
+/**
+ * ve_spc_global_wakeup_irq()
+ *
+ * Function to set/clear global wakeup IRQs. Not protected by locking since
+ * it might be used in code paths where normal cacheable locks are not
+ * working. Locking must be provided by the caller to ensure atomicity.
+ *
+ * @set: if true, global wake-up IRQs are set, if false they are cleared
+ */
+void ve_spc_global_wakeup_irq(bool set)
+{
+       u32 reg;
+
+       reg = readl_relaxed(info->baseaddr + WAKE_INT_MASK);
+
+       if (set)
+               reg |= GBL_WAKEUP_INT_MSK;
+       else
+               reg &= ~GBL_WAKEUP_INT_MSK;
+
+       writel_relaxed(reg, info->baseaddr + WAKE_INT_MASK);
+}
+
+/**
+ * ve_spc_cpu_wakeup_irq()
+ *
+ * Function to set/clear per-CPU wake-up IRQs. Not protected by locking since
+ * it might be used in code paths where normal cacheable locks are not
+ * working. Locking must be provided by the caller to ensure atomicity.
+ *
+ * @cluster: mpidr[15:8] bitfield describing cluster affinity level
+ * @cpu: mpidr[7:0] bitfield describing cpu affinity level
+ * @set: if true, wake-up IRQs are set, if false they are cleared
+ */
+void ve_spc_cpu_wakeup_irq(u32 cluster, u32 cpu, bool set)
+{
+       u32 mask, reg;
+
+       if (cluster >= MAX_CLUSTERS)
+               return;
+
+       mask = 1 << cpu;
+
+       if (!cluster_is_a15(cluster))
+               mask <<= 4;
+
+       reg = readl_relaxed(info->baseaddr + WAKE_INT_MASK);
+
+       if (set)
+               reg |= mask;
+       else
+               reg &= ~mask;
+
+       writel_relaxed(reg, info->baseaddr + WAKE_INT_MASK);
+}
+
+/**
+ * ve_spc_set_resume_addr() - set the jump address used for warm boot
+ *
+ * @cluster: mpidr[15:8] bitfield describing cluster affinity level
+ * @cpu: mpidr[7:0] bitfield describing cpu affinity level
+ * @addr: physical resume address
+ */
+void ve_spc_set_resume_addr(u32 cluster, u32 cpu, u32 addr)
+{
+       void __iomem *baseaddr;
+
+       if (cluster >= MAX_CLUSTERS)
+               return;
+
+       if (cluster_is_a15(cluster))
+               baseaddr = info->baseaddr + A15_BX_ADDR0 + (cpu << 2);
+       else
+               baseaddr = info->baseaddr + A7_BX_ADDR0 + (cpu << 2);
+
+       writel_relaxed(addr, baseaddr);
+}
+
+/**
+ * ve_spc_powerdown()
+ *
+ * Function to enable/disable cluster powerdown. Not protected by locking
+ * since it might be used in code paths where normal cacheable locks are not
+ * working. Locking must be provided by the caller to ensure atomicity.
+ *
+ * @cluster: mpidr[15:8] bitfield describing cluster affinity level
+ * @enable: if true enables powerdown, if false disables it
+ */
+void ve_spc_powerdown(u32 cluster, bool enable)
+{
+       u32 pwdrn_reg;
+
+       if (cluster >= MAX_CLUSTERS)
+               return;
+
+       pwdrn_reg = cluster_is_a15(cluster) ? A15_PWRDN_EN : A7_PWRDN_EN;
+       writel_relaxed(enable, info->baseaddr + pwdrn_reg);
+}
+
+int __init ve_spc_init(void __iomem *baseaddr, u32 a15_clusid)
+{
+       info = kzalloc(sizeof(*info), GFP_KERNEL);
+       if (!info) {
+               pr_err(SPCLOG "unable to allocate mem\n");
+               return -ENOMEM;
+       }
+
+       info->baseaddr = baseaddr;
+       info->a15_clusid = a15_clusid;
+
+       /*
+        * Multi-cluster systems may need this data when non-coherent, during
+        * cluster power-up/power-down. Make sure driver info reaches main
+        * memory.
+        */
+       sync_cache_w(info);
+       sync_cache_w(&info);
+
+       return 0;
+}
diff --git a/arch/arm/mach-vexpress/spc.h b/arch/arm/mach-vexpress/spc.h
new file mode 100644 (file)
index 0000000..5f7e4a4
--- /dev/null
@@ -0,0 +1,24 @@
+/*
+ * 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * Copyright (C) 2012 ARM Limited
+ */
+
+
+#ifndef __SPC_H_
+#define __SPC_H_
+
+int __init ve_spc_init(void __iomem *base, u32 a15_clusid);
+void ve_spc_global_wakeup_irq(bool set);
+void ve_spc_cpu_wakeup_irq(u32 cluster, u32 cpu, bool set);
+void ve_spc_set_resume_addr(u32 cluster, u32 cpu, u32 addr);
+void ve_spc_powerdown(u32 cluster, bool enable);
+
+#endif
diff --git a/arch/arm/mach-vexpress/tc2_pm.c b/arch/arm/mach-vexpress/tc2_pm.c
new file mode 100644 (file)
index 0000000..68adb40
--- /dev/null
@@ -0,0 +1,346 @@
+/*
+ * arch/arm/mach-vexpress/tc2_pm.c - TC2 power management support
+ *
+ * Created by: Nicolas Pitre, October 2012
+ * Copyright:  (C) 2012-2013  Linaro Limited
+ *
+ * Some portions of this file were originally written by Achin Gupta
+ * Copyright:   (C) 2012  ARM Limited
+ *
+ * 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/init.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/of_address.h>
+#include <linux/spinlock.h>
+#include <linux/errno.h>
+#include <linux/irqchip/arm-gic.h>
+
+#include <asm/mcpm.h>
+#include <asm/proc-fns.h>
+#include <asm/cacheflush.h>
+#include <asm/cputype.h>
+#include <asm/cp15.h>
+
+#include <linux/arm-cci.h>
+
+#include "spc.h"
+
+/* SCC conf registers */
+#define A15_CONF               0x400
+#define A7_CONF                        0x500
+#define SYS_INFO               0x700
+#define SPC_BASE               0xb00
+
+/*
+ * We can't use regular spinlocks. In the switcher case, it is possible
+ * for an outbound CPU to call power_down() after its inbound counterpart
+ * is already live using the same logical CPU number which trips lockdep
+ * debugging.
+ */
+static arch_spinlock_t tc2_pm_lock = __ARCH_SPIN_LOCK_UNLOCKED;
+
+#define TC2_CLUSTERS                   2
+#define TC2_MAX_CPUS_PER_CLUSTER       3
+
+static unsigned int tc2_nr_cpus[TC2_CLUSTERS];
+
+/* Keep per-cpu usage count to cope with unordered up/down requests */
+static int tc2_pm_use_count[TC2_MAX_CPUS_PER_CLUSTER][TC2_CLUSTERS];
+
+#define tc2_cluster_unused(cluster) \
+       (!tc2_pm_use_count[0][cluster] && \
+        !tc2_pm_use_count[1][cluster] && \
+        !tc2_pm_use_count[2][cluster])
+
+static int tc2_pm_power_up(unsigned int cpu, unsigned int cluster)
+{
+       pr_debug("%s: cpu %u cluster %u\n", __func__, cpu, cluster);
+       if (cluster >= TC2_CLUSTERS || cpu >= tc2_nr_cpus[cluster])
+               return -EINVAL;
+
+       /*
+        * Since this is called with IRQs enabled, and no arch_spin_lock_irq
+        * variant exists, we need to disable IRQs manually here.
+        */
+       local_irq_disable();
+       arch_spin_lock(&tc2_pm_lock);
+
+       if (tc2_cluster_unused(cluster))
+               ve_spc_powerdown(cluster, false);
+
+       tc2_pm_use_count[cpu][cluster]++;
+       if (tc2_pm_use_count[cpu][cluster] == 1) {
+               ve_spc_set_resume_addr(cluster, cpu,
+                                      virt_to_phys(mcpm_entry_point));
+               ve_spc_cpu_wakeup_irq(cluster, cpu, true);
+       } else if (tc2_pm_use_count[cpu][cluster] != 2) {
+               /*
+                * The only possible values are:
+                * 0 = CPU down
+                * 1 = CPU (still) up
+                * 2 = CPU requested to be up before it had a chance
+                *     to actually make itself down.
+                * Any other value is a bug.
+                */
+               BUG();
+       }
+
+       arch_spin_unlock(&tc2_pm_lock);
+       local_irq_enable();
+
+       return 0;
+}
+
+static void tc2_pm_down(u64 residency)
+{
+       unsigned int mpidr, cpu, cluster;
+       bool last_man = false, skip_wfi = false;
+
+       mpidr = read_cpuid_mpidr();
+       cpu = MPIDR_AFFINITY_LEVEL(mpidr, 0);
+       cluster = MPIDR_AFFINITY_LEVEL(mpidr, 1);
+
+       pr_debug("%s: cpu %u cluster %u\n", __func__, cpu, cluster);
+       BUG_ON(cluster >= TC2_CLUSTERS || cpu >= TC2_MAX_CPUS_PER_CLUSTER);
+
+       __mcpm_cpu_going_down(cpu, cluster);
+
+       arch_spin_lock(&tc2_pm_lock);
+       BUG_ON(__mcpm_cluster_state(cluster) != CLUSTER_UP);
+       tc2_pm_use_count[cpu][cluster]--;
+       if (tc2_pm_use_count[cpu][cluster] == 0) {
+               ve_spc_cpu_wakeup_irq(cluster, cpu, true);
+               if (tc2_cluster_unused(cluster)) {
+                       ve_spc_powerdown(cluster, true);
+                       ve_spc_global_wakeup_irq(true);
+                       last_man = true;
+               }
+       } else if (tc2_pm_use_count[cpu][cluster] == 1) {
+               /*
+                * A power_up request went ahead of us.
+                * Even if we do not want to shut this CPU down,
+                * the caller expects a certain state as if the WFI
+                * was aborted.  So let's continue with cache cleaning.
+                */
+               skip_wfi = true;
+       } else
+               BUG();
+
+       if (last_man && __mcpm_outbound_enter_critical(cpu, cluster)) {
+               arch_spin_unlock(&tc2_pm_lock);
+
+               if (read_cpuid_part_number() == ARM_CPU_PART_CORTEX_A15) {
+                       /*
+                        * On the Cortex-A15 we need to disable
+                        * L2 prefetching before flushing the cache.
+                        */
+                       asm volatile(
+                       "mcr    p15, 1, %0, c15, c0, 3 \n\t"
+                       "isb    \n\t"
+                       "dsb    "
+                       : : "r" (0x400) );
+               }
+
+               /*
+                * We need to disable and flush the whole (L1 and L2) cache.
+                * Let's do it in the safest possible way i.e. with
+                * no memory access within the following sequence
+                * including the stack.
+                */
+               asm volatile(
+               "mrc    p15, 0, r0, c1, c0, 0   @ get CR \n\t"
+               "bic    r0, r0, #"__stringify(CR_C)" \n\t"
+               "mcr    p15, 0, r0, c1, c0, 0   @ set CR \n\t"
+               "isb    \n\t"
+               "bl     v7_flush_dcache_all \n\t"
+               "clrex  \n\t"
+               "mrc    p15, 0, r0, c1, c0, 1   @ get AUXCR \n\t"
+               "bic    r0, r0, #(1 << 6)       @ disable local coherency \n\t"
+               "mcr    p15, 0, r0, c1, c0, 1   @ set AUXCR \n\t"
+               "isb    \n\t"
+               "dsb    "
+               : : : "r0","r1","r2","r3","r4","r5","r6","r7",
+                     "r9","r10","r11","lr","memory");
+
+               cci_disable_port_by_cpu(mpidr);
+
+               __mcpm_outbound_leave_critical(cluster, CLUSTER_DOWN);
+       } else {
+               /*
+                * If last man then undo any setup done previously.
+                */
+               if (last_man) {
+                       ve_spc_powerdown(cluster, false);
+                       ve_spc_global_wakeup_irq(false);
+               }
+
+               arch_spin_unlock(&tc2_pm_lock);
+
+               /*
+                * We need to disable and flush only the L1 cache.
+                * Let's do it in the safest possible way as above.
+                */
+               asm volatile(
+               "mrc    p15, 0, r0, c1, c0, 0   @ get CR \n\t"
+               "bic    r0, r0, #"__stringify(CR_C)" \n\t"
+               "mcr    p15, 0, r0, c1, c0, 0   @ set CR \n\t"
+               "isb    \n\t"
+               "bl     v7_flush_dcache_louis \n\t"
+               "clrex  \n\t"
+               "mrc    p15, 0, r0, c1, c0, 1   @ get AUXCR \n\t"
+               "bic    r0, r0, #(1 << 6)       @ disable local coherency \n\t"
+               "mcr    p15, 0, r0, c1, c0, 1   @ set AUXCR \n\t"
+               "isb    \n\t"
+               "dsb    "
+               : : : "r0","r1","r2","r3","r4","r5","r6","r7",
+                     "r9","r10","r11","lr","memory");
+       }
+
+       __mcpm_cpu_down(cpu, cluster);
+
+       /* Now we are prepared for power-down, do it: */
+       if (!skip_wfi)
+               wfi();
+
+       /* Not dead at this point?  Let our caller cope. */
+}
+
+static void tc2_pm_power_down(void)
+{
+       tc2_pm_down(0);
+}
+
+static void tc2_pm_suspend(u64 residency)
+{
+       unsigned int mpidr, cpu, cluster;
+
+       mpidr = read_cpuid_mpidr();
+       cpu = MPIDR_AFFINITY_LEVEL(mpidr, 0);
+       cluster = MPIDR_AFFINITY_LEVEL(mpidr, 1);
+       ve_spc_set_resume_addr(cluster, cpu, virt_to_phys(mcpm_entry_point));
+       gic_cpu_if_down();
+       tc2_pm_down(residency);
+}
+
+static void tc2_pm_powered_up(void)
+{
+       unsigned int mpidr, cpu, cluster;
+       unsigned long flags;
+
+       mpidr = read_cpuid_mpidr();
+       cpu = MPIDR_AFFINITY_LEVEL(mpidr, 0);
+       cluster = MPIDR_AFFINITY_LEVEL(mpidr, 1);
+
+       pr_debug("%s: cpu %u cluster %u\n", __func__, cpu, cluster);
+       BUG_ON(cluster >= TC2_CLUSTERS || cpu >= TC2_MAX_CPUS_PER_CLUSTER);
+
+       local_irq_save(flags);
+       arch_spin_lock(&tc2_pm_lock);
+
+       if (tc2_cluster_unused(cluster)) {
+               ve_spc_powerdown(cluster, false);
+               ve_spc_global_wakeup_irq(false);
+       }
+
+       if (!tc2_pm_use_count[cpu][cluster])
+               tc2_pm_use_count[cpu][cluster] = 1;
+
+       ve_spc_cpu_wakeup_irq(cluster, cpu, false);
+       ve_spc_set_resume_addr(cluster, cpu, 0);
+
+       arch_spin_unlock(&tc2_pm_lock);
+       local_irq_restore(flags);
+}
+
+static const struct mcpm_platform_ops tc2_pm_power_ops = {
+       .power_up       = tc2_pm_power_up,
+       .power_down     = tc2_pm_power_down,
+       .suspend        = tc2_pm_suspend,
+       .powered_up     = tc2_pm_powered_up,
+};
+
+static bool __init tc2_pm_usage_count_init(void)
+{
+       unsigned int mpidr, cpu, cluster;
+
+       mpidr = read_cpuid_mpidr();
+       cpu = MPIDR_AFFINITY_LEVEL(mpidr, 0);
+       cluster = MPIDR_AFFINITY_LEVEL(mpidr, 1);
+
+       pr_debug("%s: cpu %u cluster %u\n", __func__, cpu, cluster);
+       if (cluster >= TC2_CLUSTERS || cpu >= tc2_nr_cpus[cluster]) {
+               pr_err("%s: boot CPU is out of bound!\n", __func__);
+               return false;
+       }
+       tc2_pm_use_count[cpu][cluster] = 1;
+       return true;
+}
+
+/*
+ * Enable cluster-level coherency, in preparation for turning on the MMU.
+ */
+static void __naked tc2_pm_power_up_setup(unsigned int affinity_level)
+{
+       asm volatile (" \n"
+"      cmp     r0, #1 \n"
+"      bxne    lr \n"
+"      b       cci_enable_port_for_self ");
+}
+
+static int __init tc2_pm_init(void)
+{
+       int ret;
+       void __iomem *scc;
+       u32 a15_cluster_id, a7_cluster_id, sys_info;
+       struct device_node *np;
+
+       /*
+        * The power management-related features are hidden behind
+        * SCC registers. We need to extract runtime information like
+        * cluster ids and number of CPUs really available in clusters.
+        */
+       np = of_find_compatible_node(NULL, NULL,
+                       "arm,vexpress-scc,v2p-ca15_a7");
+       scc = of_iomap(np, 0);
+       if (!scc)
+               return -ENODEV;
+
+       a15_cluster_id = readl_relaxed(scc + A15_CONF) & 0xf;
+       a7_cluster_id = readl_relaxed(scc + A7_CONF) & 0xf;
+       if (a15_cluster_id >= TC2_CLUSTERS || a7_cluster_id >= TC2_CLUSTERS)
+               return -EINVAL;
+
+       sys_info = readl_relaxed(scc + SYS_INFO);
+       tc2_nr_cpus[a15_cluster_id] = (sys_info >> 16) & 0xf;
+       tc2_nr_cpus[a7_cluster_id] = (sys_info >> 20) & 0xf;
+
+       /*
+        * A subset of the SCC registers is also used to communicate
+        * with the SPC (power controller). We need to be able to
+        * drive it very early in the boot process to power up
+        * processors, so we initialize the SPC driver here.
+        */
+       ret = ve_spc_init(scc + SPC_BASE, a15_cluster_id);
+       if (ret)
+               return ret;
+
+       if (!cci_probed())
+               return -ENODEV;
+
+       if (!tc2_pm_usage_count_init())
+               return -EINVAL;
+
+       ret = mcpm_platform_register(&tc2_pm_power_ops);
+       if (!ret) {
+               mcpm_sync_init(tc2_pm_power_up_setup);
+               pr_info("TC2 power management initialized\n");
+       }
+       return ret;
+}
+
+early_initcall(tc2_pm_init);
index 0e2cd5c..0f86587 100644 (file)
@@ -42,6 +42,16 @@ config CPU_IDLE_ZYNQ
        help
          Select this to enable cpuidle on Xilinx Zynq processors.
 
+config CPU_IDLE_BIG_LITTLE
+       bool "Support for ARM big.LITTLE processors"
+       depends on ARCH_VEXPRESS_TC2_PM
+       select ARM_CPU_SUSPEND
+       select CPU_IDLE_MULTIPLE_DRIVERS
+       help
+         Select this option to enable CPU idle driver for big.LITTLE based
+         ARM systems. Driver manages CPUs coordination through MCPM and
+         define different C-states for little and big cores through the
+         multiple CPU idle drivers infrastructure.
 endif
 
 config ARCH_NEEDS_CPU_IDLE_COUPLED
index 8767a7b..3b6445c 100644 (file)
@@ -8,3 +8,4 @@ obj-$(CONFIG_ARCH_NEEDS_CPU_IDLE_COUPLED) += coupled.o
 obj-$(CONFIG_CPU_IDLE_CALXEDA) += cpuidle-calxeda.o
 obj-$(CONFIG_ARCH_KIRKWOOD) += cpuidle-kirkwood.o
 obj-$(CONFIG_CPU_IDLE_ZYNQ) += cpuidle-zynq.o
+obj-$(CONFIG_CPU_IDLE_BIG_LITTLE) += cpuidle-big_little.o
diff --git a/drivers/cpuidle/cpuidle-big_little.c b/drivers/cpuidle/cpuidle-big_little.c
new file mode 100644 (file)
index 0000000..b45fc62
--- /dev/null
@@ -0,0 +1,209 @@
+/*
+ * Copyright (c) 2013 ARM/Linaro
+ *
+ * Authors: Daniel Lezcano <daniel.lezcano@linaro.org>
+ *          Lorenzo Pieralisi <lorenzo.pieralisi@arm.com>
+ *          Nicolas Pitre <nicolas.pitre@linaro.org>
+ *
+ * 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.
+ *
+ * Maintainer: Lorenzo Pieralisi <lorenzo.pieralisi@arm.com>
+ * Maintainer: Daniel Lezcano <daniel.lezcano@linaro.org>
+ */
+#include <linux/cpuidle.h>
+#include <linux/cpu_pm.h>
+#include <linux/slab.h>
+#include <linux/of.h>
+
+#include <asm/cpu.h>
+#include <asm/cputype.h>
+#include <asm/cpuidle.h>
+#include <asm/mcpm.h>
+#include <asm/smp_plat.h>
+#include <asm/suspend.h>
+
+static int bl_enter_powerdown(struct cpuidle_device *dev,
+                             struct cpuidle_driver *drv, int idx);
+
+/*
+ * NB: Owing to current menu governor behaviour big and LITTLE
+ * index 1 states have to define exit_latency and target_residency for
+ * cluster state since, when all CPUs in a cluster hit it, the cluster
+ * can be shutdown. This means that when a single CPU enters this state
+ * the exit_latency and target_residency values are somewhat overkill.
+ * There is no notion of cluster states in the menu governor, so CPUs
+ * have to define CPU states where possibly the cluster will be shutdown
+ * depending on the state of other CPUs. idle states entry and exit happen
+ * at random times; however the cluster state provides target_residency
+ * values as if all CPUs in a cluster enter the state at once; this is
+ * somewhat optimistic and behaviour should be fixed either in the governor
+ * or in the MCPM back-ends.
+ * To make this driver 100% generic the number of states and the exit_latency
+ * target_residency values must be obtained from device tree bindings.
+ *
+ * exit_latency: refers to the TC2 vexpress test chip and depends on the
+ * current cluster operating point. It is the time it takes to get the CPU
+ * up and running when the CPU is powered up on cluster wake-up from shutdown.
+ * Current values for big and LITTLE clusters are provided for clusters
+ * running at default operating points.
+ *
+ * target_residency: it is the minimum amount of time the cluster has
+ * to be down to break even in terms of power consumption. cluster
+ * shutdown has inherent dynamic power costs (L2 writebacks to DRAM
+ * being the main factor) that depend on the current operating points.
+ * The current values for both clusters are provided for a CPU whose half
+ * of L2 lines are dirty and require cleaning to DRAM, and takes into
+ * account leakage static power values related to the vexpress TC2 testchip.
+ */
+static struct cpuidle_driver bl_idle_little_driver = {
+       .name = "little_idle",
+       .owner = THIS_MODULE,
+       .states[0] = ARM_CPUIDLE_WFI_STATE,
+       .states[1] = {
+               .enter                  = bl_enter_powerdown,
+               .exit_latency           = 700,
+               .target_residency       = 2500,
+               .flags                  = CPUIDLE_FLAG_TIME_VALID |
+                                         CPUIDLE_FLAG_TIMER_STOP,
+               .name                   = "C1",
+               .desc                   = "ARM little-cluster power down",
+       },
+       .state_count = 2,
+};
+
+static struct cpuidle_driver bl_idle_big_driver = {
+       .name = "big_idle",
+       .owner = THIS_MODULE,
+       .states[0] = ARM_CPUIDLE_WFI_STATE,
+       .states[1] = {
+               .enter                  = bl_enter_powerdown,
+               .exit_latency           = 500,
+               .target_residency       = 2000,
+               .flags                  = CPUIDLE_FLAG_TIME_VALID |
+                                         CPUIDLE_FLAG_TIMER_STOP,
+               .name                   = "C1",
+               .desc                   = "ARM big-cluster power down",
+       },
+       .state_count = 2,
+};
+
+/*
+ * notrace prevents trace shims from getting inserted where they
+ * should not. Global jumps and ldrex/strex must not be inserted
+ * in power down sequences where caches and MMU may be turned off.
+ */
+static int notrace bl_powerdown_finisher(unsigned long arg)
+{
+       /* MCPM works with HW CPU identifiers */
+       unsigned int mpidr = read_cpuid_mpidr();
+       unsigned int cluster = MPIDR_AFFINITY_LEVEL(mpidr, 1);
+       unsigned int cpu = MPIDR_AFFINITY_LEVEL(mpidr, 0);
+
+       mcpm_set_entry_vector(cpu, cluster, cpu_resume);
+
+       /*
+        * Residency value passed to mcpm_cpu_suspend back-end
+        * has to be given clear semantics. Set to 0 as a
+        * temporary value.
+        */
+       mcpm_cpu_suspend(0);
+
+       /* return value != 0 means failure */
+       return 1;
+}
+
+/**
+ * bl_enter_powerdown - Programs CPU to enter the specified state
+ * @dev: cpuidle device
+ * @drv: The target state to be programmed
+ * @idx: state index
+ *
+ * Called from the CPUidle framework to program the device to the
+ * specified target state selected by the governor.
+ */
+static int bl_enter_powerdown(struct cpuidle_device *dev,
+                               struct cpuidle_driver *drv, int idx)
+{
+       cpu_pm_enter();
+
+       cpu_suspend(0, bl_powerdown_finisher);
+
+       /* signals the MCPM core that CPU is out of low power state */
+       mcpm_cpu_powered_up();
+
+       cpu_pm_exit();
+
+       return idx;
+}
+
+static int __init bl_idle_driver_init(struct cpuidle_driver *drv, int cpu_id)
+{
+       struct cpuinfo_arm *cpu_info;
+       struct cpumask *cpumask;
+       unsigned long cpuid;
+       int cpu;
+
+       cpumask = kzalloc(cpumask_size(), GFP_KERNEL);
+       if (!cpumask)
+               return -ENOMEM;
+
+       for_each_possible_cpu(cpu) {
+               cpu_info = &per_cpu(cpu_data, cpu);
+               cpuid = is_smp() ? cpu_info->cpuid : read_cpuid_id();
+
+               /* read cpu id part number */
+               if ((cpuid & 0xFFF0) == cpu_id)
+                       cpumask_set_cpu(cpu, cpumask);
+       }
+
+       drv->cpumask = cpumask;
+
+       return 0;
+}
+
+static int __init bl_idle_init(void)
+{
+       int ret;
+
+       /*
+        * Initialize the driver just for a compliant set of machines
+        */
+       if (!of_machine_is_compatible("arm,vexpress,v2p-ca15_a7"))
+               return -ENODEV;
+       /*
+        * For now the differentiation between little and big cores
+        * is based on the part number. A7 cores are considered little
+        * cores, A15 are considered big cores. This distinction may
+        * evolve in the future with a more generic matching approach.
+        */
+       ret = bl_idle_driver_init(&bl_idle_little_driver,
+                                 ARM_CPU_PART_CORTEX_A7);
+       if (ret)
+               return ret;
+
+       ret = bl_idle_driver_init(&bl_idle_big_driver, ARM_CPU_PART_CORTEX_A15);
+       if (ret)
+               goto out_uninit_little;
+
+       ret = cpuidle_register(&bl_idle_little_driver, NULL);
+       if (ret)
+               goto out_uninit_big;
+
+       ret = cpuidle_register(&bl_idle_big_driver, NULL);
+       if (ret)
+               goto out_unregister_little;
+
+       return 0;
+
+out_unregister_little:
+       cpuidle_unregister(&bl_idle_little_driver);
+out_uninit_big:
+       kfree(bl_idle_big_driver.cpumask);
+out_uninit_little:
+       kfree(bl_idle_little_driver.cpumask);
+
+       return ret;
+}
+device_initcall(bl_idle_init);
index ee7c503..d0e9480 100644 (file)
@@ -453,6 +453,12 @@ static void gic_cpu_init(struct gic_chip_data *gic)
        writel_relaxed(1, base + GIC_CPU_CTRL);
 }
 
+void gic_cpu_if_down(void)
+{
+       void __iomem *cpu_base = gic_data_cpu_base(&gic_data[0]);
+       writel_relaxed(0, cpu_base + GIC_CPU_CTRL);
+}
+
 #ifdef CONFIG_CPU_PM
 /*
  * Saves the GIC distributor registers during suspend or idle.  Must be called
index 3e203eb..0e5d9ec 100644 (file)
@@ -66,6 +66,7 @@ extern struct irq_chip gic_arch_extn;
 void gic_init_bases(unsigned int, int, void __iomem *, void __iomem *,
                    u32 offset, struct device_node *);
 void gic_cascade_irq(unsigned int gic_nr, unsigned int irq);
+void gic_cpu_if_down(void);
 
 static inline void gic_init(unsigned int nr, int start,
                            void __iomem *dist , void __iomem *cpu)