From e8f9bb1bd6bb93fff773345cc54c42585e0e3ece Mon Sep 17 00:00:00 2001 From: Nicolas Pitre Date: Tue, 16 Jul 2013 20:59:53 -0400 Subject: [PATCH 1/8] ARM: vexpress/dcscb: fix cache disabling sequences Unlike real A15/A7's, the RTSM simulation doesn't appear to hit the cache when the CTRL.C bit is cleared. Let's ensure there is no memory access within the disable and flush cache sequence, including to the stack. Signed-off-by: Nicolas Pitre --- arch/arm/mach-vexpress/dcscb.c | 58 ++++++++++++++++++++++------------ 1 file changed, 37 insertions(+), 21 deletions(-) diff --git a/arch/arm/mach-vexpress/dcscb.c b/arch/arm/mach-vexpress/dcscb.c index 16d57a8a9d5a..85fffa702f5b 100644 --- a/arch/arm/mach-vexpress/dcscb.c +++ b/arch/arm/mach-vexpress/dcscb.c @@ -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); From 63819cb10342bbf3556f6b59ac4daacbf84833bf Mon Sep 17 00:00:00 2001 From: Lorenzo Pieralisi Date: Tue, 16 Jul 2013 17:05:43 +0100 Subject: [PATCH 2/8] ARM: vexpress/TC2: add Serial Power Controller (SPC) support The TC2 versatile express core tile integrates a logic block that provides the interface between the dual cluster test-chip and the M3 microcontroller that carries out power management. The logic block, called Serial Power Controller (SPC), contains several memory mapped registers to control among other things low-power states, wake-up irqs and per-CPU jump addresses registers. This patch provides a driver that enables run-time control of features implemented by the SPC power management control logic with an API to be used by different subsystem drivers on top. The SPC control logic is required to be programmed very early in the boot process to reset secondary CPUs on the TC2 testchip, set-up jump addresses and wake-up IRQs for power management. Hence, waiting for core changes to be made in the device core code to enable early registration of platform devices, the driver puts in place an early init scheme that allows kernel drivers to initialize the SPC driver directly from the components requiring it, if their initialization routine is called before this driver init function during the boot process. Device tree bindings documentation for the SPC component is also provided. Cc: Olof Johansson Cc: Amit Kucheria Cc: Jon Medhurst Signed-off-by: Achin Gupta Signed-off-by: Lorenzo Pieralisi Signed-off-by: Sudeep KarkadaNagesha Acked-by: Pawel Moll [ np: moved from drivers/mfd/ to drivers/platform/vexpress/ ] Signed-off-by: Nicolas Pitre [ PM: moved again to arch/arm/mach-vexpress, requested by Olof ] [ PM: removed useless printk, from Olof ] [ PM: made the driver SPC-only ] Signed-off-by: Pawel Moll --- arch/arm/mach-vexpress/spc.c | 180 +++++++++++++++++++++++++++++++++++ arch/arm/mach-vexpress/spc.h | 24 +++++ 2 files changed, 204 insertions(+) create mode 100644 arch/arm/mach-vexpress/spc.c create mode 100644 arch/arm/mach-vexpress/spc.h diff --git a/arch/arm/mach-vexpress/spc.c b/arch/arm/mach-vexpress/spc.c new file mode 100644 index 000000000000..eefb029197ca --- /dev/null +++ b/arch/arm/mach-vexpress/spc.c @@ -0,0 +1,180 @@ +/* + * Versatile Express Serial Power Controller (SPC) support + * + * Copyright (C) 2013 ARM Ltd. + * + * Authors: Sudeep KarkadaNagesha + * Achin Gupta + * Lorenzo Pieralisi + * + * 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 +#include +#include + +#include + +#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 index 000000000000..5f7e4a446a17 --- /dev/null +++ b/arch/arm/mach-vexpress/spc.h @@ -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 From ceca0e1c3930c942cb849620ebe8fdafb55b5cda Mon Sep 17 00:00:00 2001 From: Pawel Moll Date: Wed, 7 Aug 2013 12:50:26 +0100 Subject: [PATCH 3/8] ARM: vexpress: Add SCC to V2P-CA15_A7's device tree SCC (Serial Configuration Controller) is used to set initial conditions for the test chip (TC2). Its registers are also mapped in normal address space and used to obtain runtime information and for power management. Signed-off-by: Pawel Moll --- .../devicetree/bindings/arm/vexpress-scc.txt | 33 +++++++++++++++++++ arch/arm/boot/dts/vexpress-v2p-ca15_a7.dts | 6 ++++ 2 files changed, 39 insertions(+) create mode 100644 Documentation/devicetree/bindings/arm/vexpress-scc.txt diff --git a/Documentation/devicetree/bindings/arm/vexpress-scc.txt b/Documentation/devicetree/bindings/arm/vexpress-scc.txt new file mode 100644 index 000000000000..ae5043e42e5d --- /dev/null +++ b/Documentation/devicetree/bindings/arm/vexpress-scc.txt @@ -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,", "arm,vexpress-scc"; + where 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>; + }; diff --git a/arch/arm/boot/dts/vexpress-v2p-ca15_a7.dts b/arch/arm/boot/dts/vexpress-v2p-ca15_a7.dts index d2803be4e1a8..759b0cd20013 100644 --- a/arch/arm/boot/dts/vexpress-v2p-ca15_a7.dts +++ b/arch/arm/boot/dts/vexpress-v2p-ca15_a7.dts @@ -125,6 +125,12 @@ 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>, From 11b277eabe7005f5c6f2c200b1e26a237badb114 Mon Sep 17 00:00:00 2001 From: Nicolas Pitre Date: Tue, 6 Aug 2013 19:10:08 +0100 Subject: [PATCH 4/8] ARM: vexpress/TC2: basic PM support This is the MCPM backend for the Virtual Express A15x2 A7x3 CoreTile aka TC2. This provides cluster management for SMP secondary boot and CPU hotplug. Signed-off-by: Nicolas Pitre Acked-by: Pawel Moll Reviewed-by: Lorenzo Pieralisi [PM: made it drive SCC registers directly and provide base for SPC] Signed-off-by: Pawel Moll --- arch/arm/mach-vexpress/Kconfig | 8 + arch/arm/mach-vexpress/Makefile | 1 + arch/arm/mach-vexpress/tc2_pm.c | 327 ++++++++++++++++++++++++++++++++ 3 files changed, 336 insertions(+) create mode 100644 arch/arm/mach-vexpress/tc2_pm.c diff --git a/arch/arm/mach-vexpress/Kconfig b/arch/arm/mach-vexpress/Kconfig index b8bbabec6310..c700e623f9d8 100644 --- a/arch/arm/mach-vexpress/Kconfig +++ b/arch/arm/mach-vexpress/Kconfig @@ -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 diff --git a/arch/arm/mach-vexpress/Makefile b/arch/arm/mach-vexpress/Makefile index 48ba89a8149f..36ea8247123a 100644 --- a/arch/arm/mach-vexpress/Makefile +++ b/arch/arm/mach-vexpress/Makefile @@ -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 diff --git a/arch/arm/mach-vexpress/tc2_pm.c b/arch/arm/mach-vexpress/tc2_pm.c new file mode 100644 index 000000000000..cdc606816cdd --- /dev/null +++ b/arch/arm/mach-vexpress/tc2_pm.c @@ -0,0 +1,327 @@ +/* + * 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 +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +#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_power_down(void) +{ + 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_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, + .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); From e607b0f985f5277324e3fdce5bb462ef4eac4bc9 Mon Sep 17 00:00:00 2001 From: Nicolas Pitre Date: Mon, 10 Dec 2012 00:22:06 -0500 Subject: [PATCH 5/8] ARM: vexpress/TC2: implement PM suspend method Similar to power_down(), except that for a suspend, the firmware mailbox address has to be set prior entering low power mode. The residency argument is not used yet, so the last man always shuts down the cluster for now. Signed-off-by: Nicolas Pitre Acked-by: Pawel Moll --- arch/arm/mach-vexpress/tc2_pm.c | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/arch/arm/mach-vexpress/tc2_pm.c b/arch/arm/mach-vexpress/tc2_pm.c index cdc606816cdd..ddd97dd4e9b7 100644 --- a/arch/arm/mach-vexpress/tc2_pm.c +++ b/arch/arm/mach-vexpress/tc2_pm.c @@ -95,7 +95,7 @@ static int tc2_pm_power_up(unsigned int cpu, unsigned int cluster) return 0; } -static void tc2_pm_power_down(void) +static void tc2_pm_down(u64 residency) { unsigned int mpidr, cpu, cluster; bool last_man = false, skip_wfi = false; @@ -209,6 +209,22 @@ static void tc2_pm_power_down(void) /* 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)); + tc2_pm_down(residency); +} + static void tc2_pm_powered_up(void) { unsigned int mpidr, cpu, cluster; @@ -242,6 +258,7 @@ static void tc2_pm_powered_up(void) 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, }; From 10d9eb8a17cfb697967928bde06f3e7e530b03ac Mon Sep 17 00:00:00 2001 From: Nicolas Pitre Date: Tue, 19 Mar 2013 23:59:04 -0400 Subject: [PATCH 6/8] drivers: irq-chip: irq-gic: introduce gic_cpu_if_down() When processors are about to hit low power states, the assertion of standbywfi signal, triggered by the wfi instruction, is essential to entering low power modes. If an IRQ is pending on the processor at the time wfi is issued, the wfi instruction completes and the processor restarts execution without asserting the standbywfi signal. Depending on the platform power controller HW this behaviour can be acceptable or not; if this behaviour must be prevented software should be provided with a way to disable the routing of interrupts to the core IRQ pins. On systems where raw GIC distributor interrupts are connected to the power controller as wake-up events (hence the power controller still senses IRQs and can wake up cores upon IRQ pending), the GIC CPU interface can be disabled on power down, so that the GIC CPU IF output is gated and wfi cannot complete, thereby preventing the standbywfi issue. This patch adds a simple function to the GIC driver that allows to disable the GIC CPU IF from power down procedures. Signed-off-by: Nicolas Pitre Signed-off-by: Lorenzo Pieralisi [rewrote commit log] Signed-off-by: Olof Johansson --- drivers/irqchip/irq-gic.c | 6 ++++++ include/linux/irqchip/arm-gic.h | 1 + 2 files changed, 7 insertions(+) diff --git a/drivers/irqchip/irq-gic.c b/drivers/irqchip/irq-gic.c index ee7c50312066..d0e948084eaf 100644 --- a/drivers/irqchip/irq-gic.c +++ b/drivers/irqchip/irq-gic.c @@ -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 diff --git a/include/linux/irqchip/arm-gic.h b/include/linux/irqchip/arm-gic.h index 3e203eb23cc7..0e5d9ecdb2b6 100644 --- a/include/linux/irqchip/arm-gic.h +++ b/include/linux/irqchip/arm-gic.h @@ -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) From 9ee2ee0f0576fc070b02e5d31a2c4d09c39f439a Mon Sep 17 00:00:00 2001 From: Lorenzo Pieralisi Date: Wed, 24 Jul 2013 12:05:01 +0100 Subject: [PATCH 7/8] ARM: vexpress: tc2: disable GIC CPU IF in tc2_pm_suspend To prevent cores from exiting wfi when they are about to be shut down the GIC CPU IF must be disabled so that the GIC CPU IF IRQ output line is not asserted to the cores. wfi completion must be prevented since, in absence of coordinating HW logic, if the power controller receives a standbywfi signal but in the meantime the processor restarts executing owing to a pending IRQ, the core might be reset when running in a non-quiescent state (eg with pending load/store transactions) Raw GIC distributor IRQ signals are routed to the power controller, that is capable of taking core out of reset on pending IRQs even if their GIC CPU IF is disabled, thus keeping the normal wfi behaviour. GIC CPU IF is restored upon CPU wake-up by the respective MCPM API consumers (ie CPU idle driver and suspend to RAM thread). Signed-off-by: Lorenzo Pieralisi Signed-off-by: Olof Johansson --- arch/arm/mach-vexpress/tc2_pm.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/arch/arm/mach-vexpress/tc2_pm.c b/arch/arm/mach-vexpress/tc2_pm.c index ddd97dd4e9b7..68adb40d2c71 100644 --- a/arch/arm/mach-vexpress/tc2_pm.c +++ b/arch/arm/mach-vexpress/tc2_pm.c @@ -18,6 +18,7 @@ #include #include #include +#include #include #include @@ -222,6 +223,7 @@ static void tc2_pm_suspend(u64 residency) 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); } From 14d2c34cfa0026ba3916f5d5b2f1ad433beeef5a Mon Sep 17 00:00:00 2001 From: Lorenzo Pieralisi Date: Wed, 12 Jun 2013 17:40:01 +0200 Subject: [PATCH 8/8] cpuidle: big.LITTLE: vexpress-TC2 CPU idle driver The big.LITTLE architecture is composed of two clusters of cpus. One cluster contains less powerful but more energy efficient processors and the other cluster groups the powerful but energy-intensive cpus. The TC2 testchip implements two clusters of CPUs (A7 and A15 clusters in a big.LITTLE configuration) connected through a CCI interconnect that manages coherency of their respective L2 caches and intercluster distributed virtual memory messages (DVM). TC2 testchip integrates a power controller that manages cores resets, wake-up IRQs and cluster low-power states. Power states are managed at cluster level, which means that voltage is removed from a cluster iff all cores in a cluster are in a wfi state. Single cores can enter a reset state which is identical to wfi in terms of power consumption but simplifies the way cluster states are entered. This patch provides a multiple driver CPU idle implementation for TC2 which paves the way for a generic big.LITTLE idle driver for all upcoming big.LITTLE based systems on chip. The driver relies on the MCPM infrastructure to coordinate and manage core power states; in particular MCPM allows to suspend specific cores and hides the CPUs coordination required to shut-down clusters of CPUs. Power down sequences for the respective clusters are implemented in the MCPM TC2 backend, with all code needed to clean caches and exit coherency. The multiple driver CPU idle infrastructure allows to define different C-states for big and little cores, determined at boot by checking the part id of the possible CPUs and initializing the respective logical masks in the big and little drivers. Current big.little systems are composed of A7 and A15 clusters, as implemented in TC2, but in the future that may change and the driver will have evolve to retrieve what is a 'big' cpu and what is a 'little' cpu in order to build the correct topology. Cc: Kevin Hilman Cc: Amit Kucheria Cc: Olof Johansson Cc: Nicolas Pitre Cc: Rafael J. Wysocki Signed-off-by: Daniel Lezcano Signed-off-by: Lorenzo Pieralisi Signed-off-by: Olof Johansson --- MAINTAINERS | 9 ++ drivers/cpuidle/Kconfig | 10 ++ drivers/cpuidle/Makefile | 1 + drivers/cpuidle/cpuidle-big_little.c | 209 +++++++++++++++++++++++++++ 4 files changed, 229 insertions(+) create mode 100644 drivers/cpuidle/cpuidle-big_little.c diff --git a/MAINTAINERS b/MAINTAINERS index bf61e04291ab..01f1b3decaf8 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -2263,6 +2263,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 +M: Daniel Lezcano +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 M: Daniel Lezcano diff --git a/drivers/cpuidle/Kconfig b/drivers/cpuidle/Kconfig index 0e2cd5cab4d0..0f8658773be3 100644 --- a/drivers/cpuidle/Kconfig +++ b/drivers/cpuidle/Kconfig @@ -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 diff --git a/drivers/cpuidle/Makefile b/drivers/cpuidle/Makefile index 8767a7b3eb91..3b6445c106df 100644 --- a/drivers/cpuidle/Makefile +++ b/drivers/cpuidle/Makefile @@ -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 index 000000000000..b45fc6249041 --- /dev/null +++ b/drivers/cpuidle/cpuidle-big_little.c @@ -0,0 +1,209 @@ +/* + * Copyright (c) 2013 ARM/Linaro + * + * Authors: Daniel Lezcano + * Lorenzo Pieralisi + * Nicolas Pitre + * + * 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 + * Maintainer: Daniel Lezcano + */ +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +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);