From d776d6be2e051f26d04e5ad565d4acb482f1cfa5 Mon Sep 17 00:00:00 2001 From: Lorenzo Pieralisi Date: Fri, 4 May 2012 17:20:44 +0100 Subject: ARM: kernel: fix MPIDR cpu_{suspend}/{resume} usage The current version of cpu_{suspend}/{resume} relies on the 8 LSBs of the MPIDR register to index the context pointer saved and restored on CPU shutdown. This approach breaks as soon as platforms with populated MPIDR affinity levels 1 and 2 are deployed, since the MPIDR cannot be considered a linear index anymore. There are multiple solutions to this problem, each with pros and cons. This patch changes cpu_{suspend}/{resume} so that the CPU logical id is used to retrieve an index into the context pointers array. Performance is impacted on both save and restore paths. On save path the CPU logical id has to be retrieved from thread_info; since caches are on, the performance hit should be neglectable. In the resume code path the MMU is off and so are the caches. The patch adds a trivial for loop that polls the cpu_logical_map array scanning the present MPIDRs and retrieves the actual CPU logical index. Since everything runs out of strongly ordered memory the perfomance hit in the resume code path must be measured and thought over; it worsens as the number of CPUs increases since it is a linear search (but can be improved). On the up side, the logical index approach is by far the easiest solution in terms of coding and make dynamic changes to the cpu mapping trivial at run-time. Any change to the cpu_logical_map (ie in-kernel switcher) at run time must be cleaned from the caches since this data has to be retrieved with the MMU off, when caches are not searched. Tested on TC2 and fast models. --- arch/arm/kernel/sleep.S | 32 ++++++++++++++++++++++++++------ 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/arch/arm/kernel/sleep.S b/arch/arm/kernel/sleep.S index 987dcf33415..c8952daf410 100644 --- a/arch/arm/kernel/sleep.S +++ b/arch/arm/kernel/sleep.S @@ -4,6 +4,7 @@ #include #include #include +#include "entry-header.S" .text /* @@ -30,9 +31,8 @@ ENTRY(__cpu_suspend) mov r2, r5 @ virtual SP ldr r3, =sleep_save_sp #ifdef CONFIG_SMP - ALT_SMP(mrc p15, 0, lr, c0, c0, 5) - ALT_UP(mov lr, #0) - and lr, lr, #15 + get_thread_info r5 + ldr lr, [r5, #TI_CPU] @ cpu logical index add r3, r3, lr, lsl #2 #endif bl __cpu_suspend_save @@ -82,10 +82,13 @@ ENDPROC(cpu_resume_after_mmu) .align ENTRY(cpu_resume) #ifdef CONFIG_SMP + mov r1, #0 @ fall-back logical index for UP + ALT_SMP(mrc p15, 0, r0, c0, c0, 5) + ALT_UP_B(1f) + bfc r0, #24, #8 + bl cpu_logical_index @ return logical index in r1 +1: adr r0, sleep_save_sp - ALT_SMP(mrc p15, 0, r1, c0, c0, 5) - ALT_UP(mov r1, #0) - and r1, r1, #15 ldr r0, [r0, r1, lsl #2] @ stack phys addr #else ldr r0, sleep_save_sp @ stack phys addr @@ -102,3 +105,20 @@ sleep_save_sp: .rept CONFIG_NR_CPUS .long 0 @ preserve stack phys ptr here .endr + +#ifdef CONFIG_SMP +cpu_logical_index: + adr r3, cpu_map_ptr + ldr r2, [r3] + add r3, r3, r2 @ virt_to_phys(__cpu_logical_map) + mov r1, #0 +1: + ldr r2, [r3, r1, lsl #2] + cmp r2, r0 + moveq pc, lr + add r1, r1, #1 + b 1b + +cpu_map_ptr: + .long __cpu_logical_map - . +#endif -- cgit v1.2.3 From bb7cfd97181f3f536c50360eb6d8a405d6a664b3 Mon Sep 17 00:00:00 2001 From: Jon Medhurst Date: Wed, 29 Aug 2012 09:16:44 +0100 Subject: ARM: kernel: Fix compilation of sleep.S on ARMv6 The patch "ARM: kernel: fix MPIDR cpu_{suspend}/{resume} usage" uses the BFC assembler instruction but this isn't available on ARMv6 CPUs, which breaks compilation when building kernels which support both SMP and ARMv6, e.g. omap2plus_defconifg. Fix this by using a BIC instruction instead. Signed-off-by: Jon Medhurst --- arch/arm/kernel/sleep.S | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/arm/kernel/sleep.S b/arch/arm/kernel/sleep.S index c8952daf410..b5c1e636ed8 100644 --- a/arch/arm/kernel/sleep.S +++ b/arch/arm/kernel/sleep.S @@ -85,7 +85,7 @@ ENTRY(cpu_resume) mov r1, #0 @ fall-back logical index for UP ALT_SMP(mrc p15, 0, r0, c0, c0, 5) ALT_UP_B(1f) - bfc r0, #24, #8 + bic r0, #0xff000000 bl cpu_logical_index @ return logical index in r1 1: adr r0, sleep_save_sp -- cgit v1.2.3 From d24bfc093eb0482b390a7098839574b80c79fe4f Mon Sep 17 00:00:00 2001 From: Lorenzo Pieralisi Date: Fri, 13 Jul 2012 12:06:26 +0100 Subject: ARM: vexpress: add TC2 CPU idle PM TC2 test-chip integrates power management circuitry and firmware that allows to remove voltage from both (A7 and A15) clusters when they are idle or more generically when the system is forced into shutdown mode. All CPUs in a cluster share the same voltage source so they cannot be shutdown independently. In order to take advantage of TC2 power management capabilities this patch implements a multi-cluster aware CPU idle implementation. It is based on coupled C-state concept provided by this code: http://lists.infradead.org/pipermail/linux-arm-kernel/2012-April/097084.html CPUs that are part of the same cluster are coupled using the mask provided by the MPIDR at boot. Once all CPUs hit the coupled barrier the primary CPU in the cluster (the one with MPIDR[7:0] == 0) waits for secondaries to clean their L1 and enter wfi. Then it cleans all cache levels, exits cluster coherency and starts the procedure to shutdown the respective cluster. All wake-up IRQ sources are enabled by default. Deep shutdown states for clusters are not enabled by default. To enabled them: A15 cluster echo 0 > /sys/kernel/debug/idle_debug/enable_idle A7 cluster echo 1 > /sys/kernel/debug/idle_debug/enable_idle Tested thoroughly using lookbusy to modulate system load and trigger idle states entry/exit. --- arch/arm/mach-vexpress/Kconfig | 14 ++ arch/arm/mach-vexpress/Makefile | 1 + arch/arm/mach-vexpress/cpuidle-tc2.c | 288 +++++++++++++++++++++++++++++++++++ arch/arm/mach-vexpress/hotplug-asm.S | 28 ++++ arch/arm/mach-vexpress/tc2-sleep.S | 76 +++++++++ 5 files changed, 407 insertions(+) create mode 100644 arch/arm/mach-vexpress/cpuidle-tc2.c create mode 100644 arch/arm/mach-vexpress/hotplug-asm.S create mode 100644 arch/arm/mach-vexpress/tc2-sleep.S diff --git a/arch/arm/mach-vexpress/Kconfig b/arch/arm/mach-vexpress/Kconfig index 52d315b792c..2dec389c58b 100644 --- a/arch/arm/mach-vexpress/Kconfig +++ b/arch/arm/mach-vexpress/Kconfig @@ -49,6 +49,20 @@ config ARCH_VEXPRESS_CORTEX_A5_A9_ERRATA build a working kernel, you must also enable relevant core tile support or Flattened Device Tree based support options. +config ARCH_VEXPRESS_TC2_PM + bool "Power Management Support for TC2 test-chip (EXPERIMENTAL)" + depends on CPU_IDLE && PM + select ARM_CPU_SUSPEND + select ARCH_NEEDS_CPU_IDLE_COUPLED + select ARM_SPC + select ARM_CCI + help + Provides code that enables CPU idle power management on the + TC2 testchip. It enables the CPU idle driver so that the kernel + can enter cluster power down states provided by the power + controller. Code is built on top of coupled C-state idle code + since all CPUs need to be idle to enter cluster shutdown. + config ARCH_VEXPRESS_CA9X4 bool "Versatile Express Cortex-A9x4 tile" diff --git a/arch/arm/mach-vexpress/Makefile b/arch/arm/mach-vexpress/Makefile index 80b64971fbd..03d9029dbd6 100644 --- a/arch/arm/mach-vexpress/Makefile +++ b/arch/arm/mach-vexpress/Makefile @@ -8,3 +8,4 @@ obj-y := v2m.o reset.o obj-$(CONFIG_ARCH_VEXPRESS_CA9X4) += ct-ca9x4.o obj-$(CONFIG_SMP) += platsmp.o obj-$(CONFIG_HOTPLUG_CPU) += hotplug.o +obj-$(CONFIG_ARCH_VEXPRESS_TC2_PM) += cpuidle-tc2.o hotplug-asm.o tc2-sleep.o diff --git a/arch/arm/mach-vexpress/cpuidle-tc2.c b/arch/arm/mach-vexpress/cpuidle-tc2.c new file mode 100644 index 00000000000..de66243a7ef --- /dev/null +++ b/arch/arm/mach-vexpress/cpuidle-tc2.c @@ -0,0 +1,288 @@ +/* + * TC2 CPU idle driver. + * + * Copyright (C) 2012 ARM Ltd. + * Author: 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +static int tc2_cpuidle_simple_enter(struct cpuidle_device *dev, + struct cpuidle_driver *drv, int index) +{ + ktime_t time_start, time_end; + s64 diff; + + time_start = ktime_get(); + + cpu_do_idle(); + + time_end = ktime_get(); + + local_irq_enable(); + + diff = ktime_to_us(ktime_sub(time_end, time_start)); + if (diff > INT_MAX) + diff = INT_MAX; + + dev->last_residency = (int) diff; + + return index; +} + +static int tc2_enter_coupled(struct cpuidle_device *dev, + struct cpuidle_driver *drv, int idx); + +static struct cpuidle_state tc2_cpuidle_set[] __initdata = { + [0] = { + .enter = tc2_cpuidle_simple_enter, + .exit_latency = 1, + .target_residency = 1, + .power_usage = UINT_MAX, + .flags = CPUIDLE_FLAG_TIME_VALID, + .name = "WFI", + .desc = "ARM WFI", + }, + [1] = { + .enter = tc2_enter_coupled, + .exit_latency = 300, + .target_residency = 1000, + .flags = CPUIDLE_FLAG_TIME_VALID | + CPUIDLE_FLAG_COUPLED, + .name = "C1", + .desc = "ARM power down", + }, +}; + +struct cpuidle_driver tc2_idle_driver = { + .name = "tc2_idle", + .owner = THIS_MODULE, + .safe_state_index = 0 +}; + +static DEFINE_PER_CPU(struct cpuidle_device, tc2_idle_dev); + +#define NR_CLUSTERS 2 +static cpumask_t cluster_mask = CPU_MASK_NONE; + +extern void disable_clean_inv_dcache(int); +static atomic_t abort_barrier[NR_CLUSTERS]; + +extern void tc2_cpu_resume(void); +extern void disable_snoops(void); + +static int notrace tc2_coupled_finisher(unsigned long arg) +{ + unsigned int mpidr = read_cpuid_mpidr(); + unsigned int cpu = smp_processor_id(); + unsigned int cluster = (mpidr >> 8) & 0xf; + unsigned int weight = cpumask_weight(topology_core_cpumask(cpu)); + u8 wfi_weight = 0; + + cpuidle_coupled_parallel_barrier((struct cpuidle_device *)arg, + &abort_barrier[cluster]); + if (mpidr & 0xf) { + disable_clean_inv_dcache(0); + wfi(); + /* not reached */ + } + + while (wfi_weight != (weight - 1)) { + wfi_weight = vexpress_spc_wfi_cpustat(cluster); + wfi_weight = hweight8(wfi_weight); + } + + vexpress_spc_powerdown_enable(cluster, 1); + disable_clean_inv_dcache(1); + disable_cci(cluster); + disable_snoops(); + return 1; +} + +/* + * tc2_enter_coupled - 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 tc2_enter_coupled(struct cpuidle_device *dev, + struct cpuidle_driver *drv, int idx) +{ + struct timespec ts_preidle, ts_postidle, ts_idle; + int ret; + int cluster = (read_cpuid_mpidr() >> 8) & 0xf; + /* Used to keep track of the total time in idle */ + getnstimeofday(&ts_preidle); + + if (!cpu_isset(cluster, cluster_mask)) { + cpuidle_coupled_parallel_barrier(dev, + &abort_barrier[cluster]); + goto shallow_out; + } + + BUG_ON(!irqs_disabled()); + + cpu_pm_enter(); + + clockevents_notify(CLOCK_EVT_NOTIFY_BROADCAST_ENTER, &dev->cpu); + + ret = cpu_suspend((unsigned long) dev, tc2_coupled_finisher); + + if (ret) + BUG(); + + clockevents_notify(CLOCK_EVT_NOTIFY_BROADCAST_EXIT, &dev->cpu); + + cpu_pm_exit(); + +shallow_out: + getnstimeofday(&ts_postidle); + ts_idle = timespec_sub(ts_postidle, ts_preidle); + + dev->last_residency = ts_idle.tv_nsec / NSEC_PER_USEC + + ts_idle.tv_sec * USEC_PER_SEC; + return idx; +} + +static int idle_mask_show(struct seq_file *f, void *p) +{ + char buf[256]; + bitmap_scnlistprintf(buf, 256, cpumask_bits(&cluster_mask), + NR_CLUSTERS); + + seq_printf(f, "%s\n", buf); + + return 0; +} + +static int idle_mask_open(struct inode *inode, struct file *file) +{ + return single_open(file, idle_mask_show, inode->i_private); +} + +static const struct file_operations cpuidle_fops = { + .open = idle_mask_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static int idle_debug_set(void *data, u64 val) +{ + if (val >= (unsigned)NR_CLUSTERS && val != 0xff) { + pr_warning("Wrong parameter passed\n"); + return -EINVAL; + } + cpuidle_pause_and_lock(); + if (val == 0xff) + cpumask_clear(&cluster_mask); + else + cpumask_set_cpu(val, &cluster_mask); + + cpuidle_resume_and_unlock(); + return 0; +} +DEFINE_SIMPLE_ATTRIBUTE(idle_debug_fops, NULL, idle_debug_set, "%llu\n"); + +/* + * tc2_idle_init + * + * Registers the TC2 specific cpuidle driver with the cpuidle + * framework with the valid set of states. + */ +int __init tc2_idle_init(void) +{ + struct cpuidle_device *dev; + int i, cpu_id; + struct dentry *idle_debug, *file_debug; + struct cpuidle_driver *drv = &tc2_idle_driver; + + drv->state_count = (sizeof(tc2_cpuidle_set) / + sizeof(struct cpuidle_state)); + + for (i = 0; i < drv->state_count; i++) { + memcpy(&drv->states[i], &tc2_cpuidle_set[i], + sizeof(struct cpuidle_state)); + } + + cpuidle_register_driver(drv); + + for_each_cpu(cpu_id, cpu_online_mask) { + pr_err("CPUidle for CPU%d registered\n", cpu_id); + dev = &per_cpu(tc2_idle_dev, cpu_id); + dev->cpu = cpu_id; + dev->safe_state_index = 0; + + cpumask_copy(&dev->coupled_cpus, + topology_core_cpumask(cpu_id)); + dev->state_count = drv->state_count; + + if (cpuidle_register_device(dev)) { + printk(KERN_ERR "%s: Cpuidle register device failed\n", + __func__); + return -EIO; + } + } + + idle_debug = debugfs_create_dir("idle_debug", NULL); + + if (IS_ERR_OR_NULL(idle_debug)) { + printk(KERN_INFO "Error in creating idle debugfs directory\n"); + return 0; + } + + file_debug = debugfs_create_file("enable_idle", S_IRUGO | S_IWGRP, + idle_debug, NULL, &idle_debug_fops); + + if (IS_ERR_OR_NULL(file_debug)) { + printk(KERN_INFO "Error in creating enable_idle file\n"); + return 0; + } + + file_debug = debugfs_create_file("enable_mask", S_IRUGO | S_IWGRP, + idle_debug, NULL, &cpuidle_fops); + + if (IS_ERR_OR_NULL(file_debug)) + printk(KERN_INFO "Error in creating enable_mask file\n"); + + /* enable all wake-up IRQs by default */ + vexpress_spc_set_wake_intr(0x7ff); + vexpress_flags_set(virt_to_phys(tc2_cpu_resume)); + + /* + * Enable idle by default for all possible clusters. + * This must be done after all other setup to prevent the + * possibility of clusters being powered down before they + * are fully configured. + */ + for (i = 0; i < NR_CLUSTERS; i++) + cpumask_set_cpu(i, &cluster_mask); + + return 0; +} + +late_initcall(tc2_idle_init); diff --git a/arch/arm/mach-vexpress/hotplug-asm.S b/arch/arm/mach-vexpress/hotplug-asm.S new file mode 100644 index 00000000000..f63472edcc4 --- /dev/null +++ b/arch/arm/mach-vexpress/hotplug-asm.S @@ -0,0 +1,28 @@ +#include +#include + + .text +ENTRY(disable_clean_inv_dcache) + ARM( stmfd sp!, {r4-r5, r7, r9-r11, lr} ) + THUMB( stmfd sp!, {r4-r7, r9-r11, lr} ) + + mrc p15, 0, r3, c1, c0, 0 + bic r3, #4 @ clear C bit + mcr p15, 0, r3, c1, c0, 0 + dsb + isb + mov r12, r0 + cmp r12, #0 + bleq v7_flush_dcache_louis + cmp r12, #0 + blne v7_flush_dcache_all + clrex + mrc p15, 0, r3, c1, c0, 1 + bic r3, #0x40 @ clear SMP bit + mcr p15, 0, r3, c1, c0, 1 + isb + dsb + ARM( ldmfd sp!, {r4-r5, r7, r9-r11, lr} ) + THUMB( ldmfd sp!, {r4-r7, r9-r11, lr} ) + mov pc, lr +ENDPROC(disable_clean_inv_dcache) diff --git a/arch/arm/mach-vexpress/tc2-sleep.S b/arch/arm/mach-vexpress/tc2-sleep.S new file mode 100644 index 00000000000..9bf8348fcc8 --- /dev/null +++ b/arch/arm/mach-vexpress/tc2-sleep.S @@ -0,0 +1,76 @@ +#include + +#define SPC_PHYS_BASE 0x7FFF0000 +#define A15_CONF 0x400 + +ENTRY(tc2_cpu_resume) + mrc p15, 0, r0, c0, c0, 5 + ands r0, r0, #0xff00 + ldr r1, =SPC_PHYS_BASE + mov r2, #A15_CONF + add r1, r1, r2 + ldr r1, [r1] + and r1, r1, #0x7 + cmp r1, r0, lsr #8 + adr r0, value + addne r0, r0, #16 + ldmia r0, {r1, r2, r3, r4} @ CCI address, SCC snoop control & val + mvn r3, r3 @ undo actions done at shutdown + ldr r0, [r2] + and r5, r0, r3 + str r5, [r2] + mov r0, #3 @ enable CCI for the cluster + str r0, [r1] + adr r1, cci_ctrl + ldr r1, [r1] +loop: + ldr r0, [r1] + ands r0, r0, #1 + bne loop + mov r0, #0 @ disable power down enable + str r0, [r4] + b cpu_resume +ENDPROC(tc2_cpu_resume) + +ENTRY(disable_snoops) + mrc p15, 0, r0, c0, c0, 5 + ands r0, r0, #0xff00 + ldr r1, scc_ptr + ldr r1, [r1] + mov r2, #A15_CONF + add r1, r1, r2 + ldr r1, [r1] + and r1, r1, #0x7 + cmp r1, r0, lsr #8 + adr r0, vvalue + addne r0, r0, #8 + ldmia r0, {r2, r3} @ CCI address, SCC snoop control & val + ldr r1, scc_ptr + ldr r1, [r1] + add r2, r1, r2 + ldr r0, [r2] + orr r0, r0, r3 + dsb + isb + str r0, [r2] + wfi +ENDPROC(disable_snoops) + +cci_ctrl: + .long 0x2c09000c +value: + .long 0x2c094000 + .long 0x7fff0404 + .long 0x180 + .long 0x7fff0b30 + .long 0x2c095000 + .long 0x7fff0504 + .long 0x2000 + .long 0x7fff0b34 +vvalue: + .long 0x404 + .long 0x180 + .long 0x504 + .long 0x2000 +scc_ptr: + .long vscc -- cgit v1.2.3 From 31d74d48627957a76ae9fa2484ffe0d61e16c105 Mon Sep 17 00:00:00 2001 From: Jon Medhurst Date: Wed, 25 Jul 2012 15:13:46 +0100 Subject: ARM: vexpress: Make cpuidle check for presence of SPC driver The cpuidle code requires SPC hardware, so check for its presence before initialising. This enables the cpuidle code to safely exist in kernels run on hardware without SPC support. Signed-off-by: Jon Medhurst --- arch/arm/mach-vexpress/cpuidle-tc2.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/arch/arm/mach-vexpress/cpuidle-tc2.c b/arch/arm/mach-vexpress/cpuidle-tc2.c index de66243a7ef..3b73d4a5dc5 100644 --- a/arch/arm/mach-vexpress/cpuidle-tc2.c +++ b/arch/arm/mach-vexpress/cpuidle-tc2.c @@ -221,6 +221,11 @@ int __init tc2_idle_init(void) struct dentry *idle_debug, *file_debug; struct cpuidle_driver *drv = &tc2_idle_driver; + if (!vexpress_spc_check_loaded()) { + pr_info("TC2 CPUidle not registered because no SPC found\n"); + return -ENODEV; + } + drv->state_count = (sizeof(tc2_cpuidle_set) / sizeof(struct cpuidle_state)); -- cgit v1.2.3 From eedf05bea83c12b323719560bfa42c10491d9b4e Mon Sep 17 00:00:00 2001 From: Nicolas Pitre Date: Wed, 13 Jun 2012 09:19:05 -0400 Subject: ARM: b.L: assume aliasing I-cache To deal with the I-cache discrepancy between Cortex-A15 and Cortex-A7, let's assume aliasing I-cache in both cases. Note: this might need to be refined i.e. detect a big.LITTLE system somehow by probing all CPUs not only the boot one. Signed-off-by: Nicolas Pitre --- arch/arm/kernel/setup.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/arch/arm/kernel/setup.c b/arch/arm/kernel/setup.c index 234e339196c..f44e249154a 100644 --- a/arch/arm/kernel/setup.c +++ b/arch/arm/kernel/setup.c @@ -260,6 +260,19 @@ static int cpu_has_aliasing_icache(unsigned int arch) int aliasing_icache; unsigned int id_reg, num_sets, line_size; +#ifdef CONFIG_BIG_LITTLE + /* + * We expect a combination of Cortex-A15 and Cortex-A7 cores. + * A7 = VIPT aliasing I-cache + * A15 = PIPT (non-aliasing) I-cache + * To cater for this discrepancy, let's assume aliasing I-cache + * all the time. This means unneeded extra work on the A15 but + * only ptrace is affected which is not performance critical. + */ + if ((read_cpuid_id() & 0xff0ffff0) == 0x410fc0f0) + return 1; +#endif + /* PIPT caches never alias. */ if (icache_is_pipt()) return 0; -- cgit v1.2.3 From 5215cc6c44ce1b882b1e63d7ff2e76234b943417 Mon Sep 17 00:00:00 2001 From: Nicolas Pitre Date: Fri, 19 Oct 2012 20:48:50 -0400 Subject: ARM: TC2: basic PM support Signed-off-by: Nicolas Pitre --- arch/arm/mach-vexpress/Kconfig | 8 ++ arch/arm/mach-vexpress/Makefile | 1 + arch/arm/mach-vexpress/tc2_pm.c | 234 ++++++++++++++++++++++++++++++++++ arch/arm/mach-vexpress/tc2_pm_setup.S | 88 +++++++++++++ 4 files changed, 331 insertions(+) create mode 100644 arch/arm/mach-vexpress/tc2_pm.c create mode 100644 arch/arm/mach-vexpress/tc2_pm_setup.S diff --git a/arch/arm/mach-vexpress/Kconfig b/arch/arm/mach-vexpress/Kconfig index 2dec389c58b..5a99485138c 100644 --- a/arch/arm/mach-vexpress/Kconfig +++ b/arch/arm/mach-vexpress/Kconfig @@ -66,4 +66,12 @@ config ARCH_VEXPRESS_TC2_PM config ARCH_VEXPRESS_CA9X4 bool "Versatile Express Cortex-A9x4 tile" +config ARCH_VEXPRESS_TC2 + bool "TC2 cluster management" + depends on MCPM + select ARM_SPC + select ARM_CCI + help + Support for CPU and cluster power management on TC2. + endmenu diff --git a/arch/arm/mach-vexpress/Makefile b/arch/arm/mach-vexpress/Makefile index 03d9029dbd6..e3948d02d4f 100644 --- a/arch/arm/mach-vexpress/Makefile +++ b/arch/arm/mach-vexpress/Makefile @@ -6,6 +6,7 @@ ccflags-$(CONFIG_ARCH_MULTIPLATFORM) := -I$(srctree)/$(src)/include \ obj-y := v2m.o reset.o obj-$(CONFIG_ARCH_VEXPRESS_CA9X4) += ct-ca9x4.o +obj-$(CONFIG_ARCH_VEXPRESS_TC2) += tc2_pm.o tc2_pm_setup.o obj-$(CONFIG_SMP) += platsmp.o obj-$(CONFIG_HOTPLUG_CPU) += hotplug.o obj-$(CONFIG_ARCH_VEXPRESS_TC2_PM) += cpuidle-tc2.o hotplug-asm.o tc2-sleep.o diff --git a/arch/arm/mach-vexpress/tc2_pm.c b/arch/arm/mach-vexpress/tc2_pm.c new file mode 100644 index 00000000000..4be2d73cb38 --- /dev/null +++ b/arch/arm/mach-vexpress/tc2_pm.c @@ -0,0 +1,234 @@ +/* + * arch/arm/mach-vexpress/tc2_pm.c - TC2 power management support + * + * Created by: Nicolas Pitre, October 2012 + * Copyright: (C) 2012 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 + +/* + * 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; + +static int tc2_pm_use_count[3][2]; + +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 >= 2 || cpu >= vexpress_spc_get_nb_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_pm_use_count[0][cluster] && + !tc2_pm_use_count[1][cluster] && + !tc2_pm_use_count[2][cluster]) + vexpress_spc_powerdown_enable(cluster, 0); + + tc2_pm_use_count[cpu][cluster]++; + if (tc2_pm_use_count[cpu][cluster] == 1) { + vexpress_spc_write_bxaddr_reg(cluster, cpu, + virt_to_phys(mcpm_entry_point)); + vexpress_spc_set_cpu_wakeup_irq(cpu, cluster, 1); + } 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 >= 2 || cpu >= vexpress_spc_get_nb_cpus(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) { + vexpress_spc_set_cpu_wakeup_irq(cpu, cluster, 1); + if (!tc2_pm_use_count[0][cluster] && + !tc2_pm_use_count[1][cluster] && + !tc2_pm_use_count[2][cluster]) { + vexpress_spc_powerdown_enable(cluster, 1); + vexpress_spc_set_global_wakeup_intr(1); + 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); + + set_cr(get_cr() & ~CR_C); + flush_cache_all(); + asm volatile ("clrex"); + set_auxcr(get_auxcr() & ~(1 << 6)); + + disable_cci(cluster); + + /* + * Ensure that both C & I bits are disabled in the SCTLR + * before disabling ACE snoops. This ensures that no + * coherency traffic will originate from this cpu after + * ACE snoops are turned off. + */ + cpu_proc_fin(); + + __mcpm_outbound_leave_critical(cluster, CLUSTER_DOWN); + } else { + /* + * If last man then undo any setup done previously. + */ + if (last_man) { + vexpress_spc_powerdown_enable(cluster, 0); + vexpress_spc_set_global_wakeup_intr(0); + } + + arch_spin_unlock(&tc2_pm_lock); + + set_cr(get_cr() & ~CR_C); + flush_cache_louis(); + asm volatile ("clrex"); + set_auxcr(get_auxcr() & ~(1 << 6)); + } + + __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 >= 2 || cpu >= vexpress_spc_get_nb_cpus(cluster)); + + local_irq_save(flags); + arch_spin_lock(&tc2_pm_lock); + + if (!tc2_pm_use_count[0][cluster] && + !tc2_pm_use_count[1][cluster] && + !tc2_pm_use_count[2][cluster]) { + vexpress_spc_powerdown_enable(cluster, 0); + vexpress_spc_set_global_wakeup_intr(0); + } + + if (!tc2_pm_use_count[cpu][cluster]) + tc2_pm_use_count[cpu][cluster] = 1; + + vexpress_spc_set_cpu_wakeup_irq(cpu, cluster, 0); + vexpress_spc_write_bxaddr_reg(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 void __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); + BUG_ON(cpu >= 3 || cluster >= 2); + tc2_pm_use_count[cpu][cluster] = 1; +} + +extern void tc2_pm_power_up_setup(unsigned int affinity_level); + +static int __init tc2_pm_init(void) +{ + int ret; + + if (!vexpress_spc_check_loaded()) + return -ENODEV; + + tc2_pm_usage_count_init(); + + ret = mcpm_platform_register(&tc2_pm_power_ops); + if (!ret) + ret = mcpm_sync_init(tc2_pm_power_up_setup); + if (!ret) + pr_info("TC2 power management initialized\n"); + return ret; +} + +early_initcall(tc2_pm_init); diff --git a/arch/arm/mach-vexpress/tc2_pm_setup.S b/arch/arm/mach-vexpress/tc2_pm_setup.S new file mode 100644 index 00000000000..046890e5ff8 --- /dev/null +++ b/arch/arm/mach-vexpress/tc2_pm_setup.S @@ -0,0 +1,88 @@ +/* + * arch/arm/include/asm/tc2_pm_setup.S + * + * Created by: Nicolas Pitre, October 2012 + ( (based on dcscb_setup.S by Dave Martin) + * Copyright: (C) 2012 Linaro 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 + + +#define SPC_PHYS_BASE 0x7FFF0000 + +#define SNOOP_CTL_A15 0x404 +#define SNOOP_CTL_A7 0x504 + +#define A15_SNOOP_MASK (0x3 << 7) +#define A7_SNOOP_MASK (0x1 << 13) + +#define A15_BX_ADDR0 0xB68 + + +#define CCI_PHYS_BASE 0x2c090000 + +#define SLAVE_SNOOPCTL_OFFSET 0 +#define SNOOPCTL_SNOOP_ENABLE (1 << 0) +#define SNOOPCTL_DVM_ENABLE (1 << 1) + +#define CCI_STATUS_OFFSET 0xc +#define STATUS_CHANGE_PENDING (1 << 0) + +#define CCI_SLAVE_OFFSET(n) (0x1000 + 0x1000 * (n)) +#define CCI_SLAVE_A15 3 +#define CCI_SLAVE_A7 4 +#define CCI_A15_OFFSET CCI_SLAVE_OFFSET(CCI_SLAVE_A15) +#define CCI_A7_OFFSET CCI_SLAVE_OFFSET(CCI_SLAVE_A7) + + +/* + * Enable cluster-level coherency, in preparation for turning on the MMU. + * The ACTLR SMP bit does not need to be set here, because cpu_resume() + * already restores that. + */ + +ENTRY(tc2_pm_power_up_setup) + + cmp r0, #0 + beq 2f + + @ Enable CCI snoops + mrc p15, 0, r0, c0, c0, 5 @ MPIDR + ubfx r0, r0, #8, #4 @ cluster + ldr r3, =CCI_PHYS_BASE + CCI_A15_OFFSET + cmp r0, #0 @ A15 cluster? + addne r3, r3, #CCI_A7_OFFSET - CCI_A15_OFFSET + + @ r3 now points to the correct CCI slave register block + ldr r0, [r3, #SLAVE_SNOOPCTL_OFFSET] + orr r0, r0, #SNOOPCTL_SNOOP_ENABLE | SNOOPCTL_DVM_ENABLE + str r0, [r3, #SLAVE_SNOOPCTL_OFFSET] @ enable CCI snoops + + @ Wait for snoop control change to complete: + ldr r3, =CCI_PHYS_BASE +1: ldr r0, [r3, #CCI_STATUS_OFFSET] + tst r0, #STATUS_CHANGE_PENDING + bne 1b + + bx lr + +2: @ Clear the BX addr register + ldr r3, =SPC_PHYS_BASE + A15_BX_ADDR0 + mrc p15, 0, r0, c0, c0, 5 @ MPIDR + ubfx r1, r0, #8, #4 @ cluster + ubfx r0, r0, #0, #4 @ cpu + add r3, r3, r1, lsl #4 + mov r1, #0 + str r1, [r3, r0, lsl #2] + dsb + + bx lr + +ENDPROC(tc2_pm_power_up_setup) -- cgit v1.2.3 From 0943c1754d68ed11f10775b30d69ebe440813882 Mon Sep 17 00:00:00 2001 From: Nicolas Pitre Date: Tue, 19 Mar 2013 23:59:04 -0400 Subject: gic: introduce gic_cpu_if_down() This should be queued right before 'Revert "ARM: common: add GIC bybass disable on GIC CPU IF save function"'. Signed-off-by: Nicolas Pitre --- drivers/irqchip/irq-gic.c | 6 ++++++ include/linux/irqchip/arm-gic.h | 2 ++ 2 files changed, 8 insertions(+) diff --git a/drivers/irqchip/irq-gic.c b/drivers/irqchip/irq-gic.c index fc6aebf1e4b..3be382f18c2 100644 --- a/drivers/irqchip/irq-gic.c +++ b/drivers/irqchip/irq-gic.c @@ -452,6 +452,12 @@ static void __cpuinit 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 3fd8e4290a1..2639f614a00 100644 --- a/include/linux/irqchip/arm-gic.h +++ b/include/linux/irqchip/arm-gic.h @@ -68,6 +68,8 @@ void gic_init_bases(unsigned int, int, void __iomem *, void __iomem *, void gic_secondary_init(unsigned int); 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) { -- cgit v1.2.3 From 970395d745e01a4e9429228a2b2ed5f240628539 Mon Sep 17 00:00:00 2001 From: Lorenzo Pieralisi Date: Tue, 5 Feb 2013 11:09:16 +0000 Subject: ARM: TC2: disable GIC CPU IF on power down On TC2 testchip the GIC CPU IF must be disabled before powering down a core since a pending IRQ might cause wfi completion and the processor would exit wfi state while power controller is taking action to reset or power up the CPU upon IRQ reception. This patch adds code that disables the GIC CPU IF in TC2 specific power API methods. Signed-off-by: Lorenzo Pieralisi --- arch/arm/mach-vexpress/tc2_pm.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/arch/arm/mach-vexpress/tc2_pm.c b/arch/arm/mach-vexpress/tc2_pm.c index 4be2d73cb38..8be975e4b31 100644 --- a/arch/arm/mach-vexpress/tc2_pm.c +++ b/arch/arm/mach-vexpress/tc2_pm.c @@ -16,6 +16,7 @@ #include #include #include +#include #include #include @@ -116,6 +117,8 @@ static void tc2_pm_power_down(void) } else BUG(); + gic_cpu_if_down(); + if (last_man && __mcpm_outbound_enter_critical(cpu, cluster)) { arch_spin_unlock(&tc2_pm_lock); -- cgit v1.2.3 From 17f705be668f23cd5efcab837515f4c97ba0b3fa Mon Sep 17 00:00:00 2001 From: Nicolas Pitre Date: Mon, 10 Dec 2012 00:22:06 -0500 Subject: ARM: vexpress/tc2: implement PM suspend method This is simplistic for the moment as the expected residency is used to prevent the shutting down of L2 and the cluster if the residency for the last man is lower than 5 ms. To make this right, the residency end time for each CPU would need to be recorded and taken into account. On a suspend, the firmware mailbox address has to be set prior entering low power mode. Signed-off-by: Nicolas Pitre --- arch/arm/mach-vexpress/Kconfig | 25 +++---- arch/arm/mach-vexpress/Makefile | 2 +- arch/arm/mach-vexpress/cpuidle-tc2.c | 141 +++++------------------------------ arch/arm/mach-vexpress/hotplug-asm.S | 28 ------- arch/arm/mach-vexpress/tc2-sleep.S | 76 ------------------- arch/arm/mach-vexpress/tc2_pm.c | 23 +++++- 6 files changed, 50 insertions(+), 245 deletions(-) delete mode 100644 arch/arm/mach-vexpress/hotplug-asm.S delete mode 100644 arch/arm/mach-vexpress/tc2-sleep.S diff --git a/arch/arm/mach-vexpress/Kconfig b/arch/arm/mach-vexpress/Kconfig index 5a99485138c..bca54a33598 100644 --- a/arch/arm/mach-vexpress/Kconfig +++ b/arch/arm/mach-vexpress/Kconfig @@ -49,20 +49,6 @@ config ARCH_VEXPRESS_CORTEX_A5_A9_ERRATA build a working kernel, you must also enable relevant core tile support or Flattened Device Tree based support options. -config ARCH_VEXPRESS_TC2_PM - bool "Power Management Support for TC2 test-chip (EXPERIMENTAL)" - depends on CPU_IDLE && PM - select ARM_CPU_SUSPEND - select ARCH_NEEDS_CPU_IDLE_COUPLED - select ARM_SPC - select ARM_CCI - help - Provides code that enables CPU idle power management on the - TC2 testchip. It enables the CPU idle driver so that the kernel - can enter cluster power down states provided by the power - controller. Code is built on top of coupled C-state idle code - since all CPUs need to be idle to enter cluster shutdown. - config ARCH_VEXPRESS_CA9X4 bool "Versatile Express Cortex-A9x4 tile" @@ -74,4 +60,15 @@ config ARCH_VEXPRESS_TC2 help Support for CPU and cluster power management on TC2. +config VEXPRESS_TC2_CPUIDLE + bool "cpuidle support for TC2 test-chip (EXPERIMENTAL)" + depends on CPU_IDLE && PM && ARCH_VEXPRESS_TC2 + select ARM_CPU_SUSPEND + select ARM_SPC + help + Provides code that enables CPU idle power management on the + TC2 testchip. It enables the CPU idle driver so that the kernel + can enter cluster power down states provided by the power + controller. + endmenu diff --git a/arch/arm/mach-vexpress/Makefile b/arch/arm/mach-vexpress/Makefile index e3948d02d4f..50a57510d76 100644 --- a/arch/arm/mach-vexpress/Makefile +++ b/arch/arm/mach-vexpress/Makefile @@ -9,4 +9,4 @@ obj-$(CONFIG_ARCH_VEXPRESS_CA9X4) += ct-ca9x4.o obj-$(CONFIG_ARCH_VEXPRESS_TC2) += tc2_pm.o tc2_pm_setup.o obj-$(CONFIG_SMP) += platsmp.o obj-$(CONFIG_HOTPLUG_CPU) += hotplug.o -obj-$(CONFIG_ARCH_VEXPRESS_TC2_PM) += cpuidle-tc2.o hotplug-asm.o tc2-sleep.o +obj-$(CONFIG_VEXPRESS_TC2_CPUIDLE) += cpuidle-tc2.o diff --git a/arch/arm/mach-vexpress/cpuidle-tc2.c b/arch/arm/mach-vexpress/cpuidle-tc2.c index 3b73d4a5dc5..0a983dd531b 100644 --- a/arch/arm/mach-vexpress/cpuidle-tc2.c +++ b/arch/arm/mach-vexpress/cpuidle-tc2.c @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include @@ -51,7 +52,7 @@ static int tc2_cpuidle_simple_enter(struct cpuidle_device *dev, return index; } -static int tc2_enter_coupled(struct cpuidle_device *dev, +static int tc2_enter_powerdown(struct cpuidle_device *dev, struct cpuidle_driver *drv, int idx); static struct cpuidle_state tc2_cpuidle_set[] __initdata = { @@ -65,11 +66,10 @@ static struct cpuidle_state tc2_cpuidle_set[] __initdata = { .desc = "ARM WFI", }, [1] = { - .enter = tc2_enter_coupled, + .enter = tc2_enter_powerdown, .exit_latency = 300, .target_residency = 1000, - .flags = CPUIDLE_FLAG_TIME_VALID | - CPUIDLE_FLAG_COUPLED, + .flags = CPUIDLE_FLAG_TIME_VALID, .name = "C1", .desc = "ARM power down", }, @@ -83,45 +83,21 @@ struct cpuidle_driver tc2_idle_driver = { static DEFINE_PER_CPU(struct cpuidle_device, tc2_idle_dev); -#define NR_CLUSTERS 2 -static cpumask_t cluster_mask = CPU_MASK_NONE; - -extern void disable_clean_inv_dcache(int); -static atomic_t abort_barrier[NR_CLUSTERS]; - -extern void tc2_cpu_resume(void); -extern void disable_snoops(void); - -static int notrace tc2_coupled_finisher(unsigned long arg) +static int notrace tc2_powerdown_finisher(unsigned long arg) { unsigned int mpidr = read_cpuid_mpidr(); - unsigned int cpu = smp_processor_id(); unsigned int cluster = (mpidr >> 8) & 0xf; - unsigned int weight = cpumask_weight(topology_core_cpumask(cpu)); - u8 wfi_weight = 0; - - cpuidle_coupled_parallel_barrier((struct cpuidle_device *)arg, - &abort_barrier[cluster]); - if (mpidr & 0xf) { - disable_clean_inv_dcache(0); - wfi(); - /* not reached */ - } - - while (wfi_weight != (weight - 1)) { - wfi_weight = vexpress_spc_wfi_cpustat(cluster); - wfi_weight = hweight8(wfi_weight); - } + unsigned int cpu = mpidr & 0xf; - vexpress_spc_powerdown_enable(cluster, 1); - disable_clean_inv_dcache(1); - disable_cci(cluster); - disable_snoops(); + bL_set_entry_vector(cpu, cluster, cpu_resume); + vexpress_spc_write_bxaddr_reg(cluster, cpu, + virt_to_phys(bL_entry_point)); + bL_cpu_power_down(); return 1; } /* - * tc2_enter_coupled - Programs CPU to enter the specified state + * tc2_enter_powerdown - Programs CPU to enter the specified state * @dev: cpuidle device * @drv: The target state to be programmed * @idx: state index @@ -129,38 +105,33 @@ static int notrace tc2_coupled_finisher(unsigned long arg) * Called from the CPUidle framework to program the device to the * specified target state selected by the governor. */ -static int tc2_enter_coupled(struct cpuidle_device *dev, +static int tc2_enter_powerdown(struct cpuidle_device *dev, struct cpuidle_driver *drv, int idx) { struct timespec ts_preidle, ts_postidle, ts_idle; int ret; - int cluster = (read_cpuid_mpidr() >> 8) & 0xf; + /* Used to keep track of the total time in idle */ getnstimeofday(&ts_preidle); - if (!cpu_isset(cluster, cluster_mask)) { - cpuidle_coupled_parallel_barrier(dev, - &abort_barrier[cluster]); - goto shallow_out; - } - BUG_ON(!irqs_disabled()); cpu_pm_enter(); clockevents_notify(CLOCK_EVT_NOTIFY_BROADCAST_ENTER, &dev->cpu); - ret = cpu_suspend((unsigned long) dev, tc2_coupled_finisher); - + ret = cpu_suspend((unsigned long) dev, tc2_powerdown_finisher); if (ret) BUG(); + bL_cpu_powered_up(); + clockevents_notify(CLOCK_EVT_NOTIFY_BROADCAST_EXIT, &dev->cpu); cpu_pm_exit(); -shallow_out: getnstimeofday(&ts_postidle); + local_irq_enable(); ts_idle = timespec_sub(ts_postidle, ts_preidle); dev->last_residency = ts_idle.tv_nsec / NSEC_PER_USEC + @@ -168,46 +139,6 @@ shallow_out: return idx; } -static int idle_mask_show(struct seq_file *f, void *p) -{ - char buf[256]; - bitmap_scnlistprintf(buf, 256, cpumask_bits(&cluster_mask), - NR_CLUSTERS); - - seq_printf(f, "%s\n", buf); - - return 0; -} - -static int idle_mask_open(struct inode *inode, struct file *file) -{ - return single_open(file, idle_mask_show, inode->i_private); -} - -static const struct file_operations cpuidle_fops = { - .open = idle_mask_open, - .read = seq_read, - .llseek = seq_lseek, - .release = single_release, -}; - -static int idle_debug_set(void *data, u64 val) -{ - if (val >= (unsigned)NR_CLUSTERS && val != 0xff) { - pr_warning("Wrong parameter passed\n"); - return -EINVAL; - } - cpuidle_pause_and_lock(); - if (val == 0xff) - cpumask_clear(&cluster_mask); - else - cpumask_set_cpu(val, &cluster_mask); - - cpuidle_resume_and_unlock(); - return 0; -} -DEFINE_SIMPLE_ATTRIBUTE(idle_debug_fops, NULL, idle_debug_set, "%llu\n"); - /* * tc2_idle_init * @@ -218,7 +149,6 @@ int __init tc2_idle_init(void) { struct cpuidle_device *dev; int i, cpu_id; - struct dentry *idle_debug, *file_debug; struct cpuidle_driver *drv = &tc2_idle_driver; if (!vexpress_spc_check_loaded()) { @@ -240,10 +170,7 @@ int __init tc2_idle_init(void) pr_err("CPUidle for CPU%d registered\n", cpu_id); dev = &per_cpu(tc2_idle_dev, cpu_id); dev->cpu = cpu_id; - dev->safe_state_index = 0; - cpumask_copy(&dev->coupled_cpus, - topology_core_cpumask(cpu_id)); dev->state_count = drv->state_count; if (cpuidle_register_device(dev)) { @@ -253,40 +180,6 @@ int __init tc2_idle_init(void) } } - idle_debug = debugfs_create_dir("idle_debug", NULL); - - if (IS_ERR_OR_NULL(idle_debug)) { - printk(KERN_INFO "Error in creating idle debugfs directory\n"); - return 0; - } - - file_debug = debugfs_create_file("enable_idle", S_IRUGO | S_IWGRP, - idle_debug, NULL, &idle_debug_fops); - - if (IS_ERR_OR_NULL(file_debug)) { - printk(KERN_INFO "Error in creating enable_idle file\n"); - return 0; - } - - file_debug = debugfs_create_file("enable_mask", S_IRUGO | S_IWGRP, - idle_debug, NULL, &cpuidle_fops); - - if (IS_ERR_OR_NULL(file_debug)) - printk(KERN_INFO "Error in creating enable_mask file\n"); - - /* enable all wake-up IRQs by default */ - vexpress_spc_set_wake_intr(0x7ff); - vexpress_flags_set(virt_to_phys(tc2_cpu_resume)); - - /* - * Enable idle by default for all possible clusters. - * This must be done after all other setup to prevent the - * possibility of clusters being powered down before they - * are fully configured. - */ - for (i = 0; i < NR_CLUSTERS; i++) - cpumask_set_cpu(i, &cluster_mask); - return 0; } diff --git a/arch/arm/mach-vexpress/hotplug-asm.S b/arch/arm/mach-vexpress/hotplug-asm.S deleted file mode 100644 index f63472edcc4..00000000000 --- a/arch/arm/mach-vexpress/hotplug-asm.S +++ /dev/null @@ -1,28 +0,0 @@ -#include -#include - - .text -ENTRY(disable_clean_inv_dcache) - ARM( stmfd sp!, {r4-r5, r7, r9-r11, lr} ) - THUMB( stmfd sp!, {r4-r7, r9-r11, lr} ) - - mrc p15, 0, r3, c1, c0, 0 - bic r3, #4 @ clear C bit - mcr p15, 0, r3, c1, c0, 0 - dsb - isb - mov r12, r0 - cmp r12, #0 - bleq v7_flush_dcache_louis - cmp r12, #0 - blne v7_flush_dcache_all - clrex - mrc p15, 0, r3, c1, c0, 1 - bic r3, #0x40 @ clear SMP bit - mcr p15, 0, r3, c1, c0, 1 - isb - dsb - ARM( ldmfd sp!, {r4-r5, r7, r9-r11, lr} ) - THUMB( ldmfd sp!, {r4-r7, r9-r11, lr} ) - mov pc, lr -ENDPROC(disable_clean_inv_dcache) diff --git a/arch/arm/mach-vexpress/tc2-sleep.S b/arch/arm/mach-vexpress/tc2-sleep.S deleted file mode 100644 index 9bf8348fcc8..00000000000 --- a/arch/arm/mach-vexpress/tc2-sleep.S +++ /dev/null @@ -1,76 +0,0 @@ -#include - -#define SPC_PHYS_BASE 0x7FFF0000 -#define A15_CONF 0x400 - -ENTRY(tc2_cpu_resume) - mrc p15, 0, r0, c0, c0, 5 - ands r0, r0, #0xff00 - ldr r1, =SPC_PHYS_BASE - mov r2, #A15_CONF - add r1, r1, r2 - ldr r1, [r1] - and r1, r1, #0x7 - cmp r1, r0, lsr #8 - adr r0, value - addne r0, r0, #16 - ldmia r0, {r1, r2, r3, r4} @ CCI address, SCC snoop control & val - mvn r3, r3 @ undo actions done at shutdown - ldr r0, [r2] - and r5, r0, r3 - str r5, [r2] - mov r0, #3 @ enable CCI for the cluster - str r0, [r1] - adr r1, cci_ctrl - ldr r1, [r1] -loop: - ldr r0, [r1] - ands r0, r0, #1 - bne loop - mov r0, #0 @ disable power down enable - str r0, [r4] - b cpu_resume -ENDPROC(tc2_cpu_resume) - -ENTRY(disable_snoops) - mrc p15, 0, r0, c0, c0, 5 - ands r0, r0, #0xff00 - ldr r1, scc_ptr - ldr r1, [r1] - mov r2, #A15_CONF - add r1, r1, r2 - ldr r1, [r1] - and r1, r1, #0x7 - cmp r1, r0, lsr #8 - adr r0, vvalue - addne r0, r0, #8 - ldmia r0, {r2, r3} @ CCI address, SCC snoop control & val - ldr r1, scc_ptr - ldr r1, [r1] - add r2, r1, r2 - ldr r0, [r2] - orr r0, r0, r3 - dsb - isb - str r0, [r2] - wfi -ENDPROC(disable_snoops) - -cci_ctrl: - .long 0x2c09000c -value: - .long 0x2c094000 - .long 0x7fff0404 - .long 0x180 - .long 0x7fff0b30 - .long 0x2c095000 - .long 0x7fff0504 - .long 0x2000 - .long 0x7fff0b34 -vvalue: - .long 0x404 - .long 0x180 - .long 0x504 - .long 0x2000 -scc_ptr: - .long vscc diff --git a/arch/arm/mach-vexpress/tc2_pm.c b/arch/arm/mach-vexpress/tc2_pm.c index 8be975e4b31..a5e18df801d 100644 --- a/arch/arm/mach-vexpress/tc2_pm.c +++ b/arch/arm/mach-vexpress/tc2_pm.c @@ -80,7 +80,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; @@ -101,7 +101,8 @@ static void tc2_pm_power_down(void) vexpress_spc_set_cpu_wakeup_irq(cpu, cluster, 1); if (!tc2_pm_use_count[0][cluster] && !tc2_pm_use_count[1][cluster] && - !tc2_pm_use_count[2][cluster]) { + !tc2_pm_use_count[2][cluster] && + (!residency || residency > 5000)) { vexpress_spc_powerdown_enable(cluster, 1); vexpress_spc_set_global_wakeup_intr(1); last_man = true; @@ -164,6 +165,23 @@ 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); + vexpress_spc_write_bxaddr_reg(cluster, cpu, + virt_to_phys(mcpm_entry_point)); + tc2_pm_down(residency); +} + static void tc2_pm_powered_up(void) { unsigned int mpidr, cpu, cluster; @@ -199,6 +217,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, }; -- cgit v1.2.3 From 99ada4af139f13f75c3d9621aa216937e4c92c84 Mon Sep 17 00:00:00 2001 From: Dave Martin Date: Wed, 20 Feb 2013 17:34:20 +0000 Subject: ARM: mcpm: Make all mcpm functions notrace The functions in mcpm_entry.c are mostly intended for use during scary cache and coherency disabling sequences, or do other things which confuse trace ... like powering a CPU down and not returning. Similarly for the backend code. For simplicity, this patch just makes whole files notrace. There should be more than enough traceable points on the paths to these functions, but we can be more fine-grained later if there is a need for it. Signed-off-by: Dave Martin --- arch/arm/mach-vexpress/Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/arm/mach-vexpress/Makefile b/arch/arm/mach-vexpress/Makefile index 50a57510d76..83741a9ca73 100644 --- a/arch/arm/mach-vexpress/Makefile +++ b/arch/arm/mach-vexpress/Makefile @@ -7,6 +7,7 @@ ccflags-$(CONFIG_ARCH_MULTIPLATFORM) := -I$(srctree)/$(src)/include \ obj-y := v2m.o reset.o obj-$(CONFIG_ARCH_VEXPRESS_CA9X4) += ct-ca9x4.o obj-$(CONFIG_ARCH_VEXPRESS_TC2) += tc2_pm.o tc2_pm_setup.o +CFLAGS_REMOVE_tc2_pm.o = -pg obj-$(CONFIG_SMP) += platsmp.o obj-$(CONFIG_HOTPLUG_CPU) += hotplug.o obj-$(CONFIG_VEXPRESS_TC2_CPUIDLE) += cpuidle-tc2.o -- cgit v1.2.3 From dde8cbcdaa29e3b05f33305ed7e55242c8cc9c84 Mon Sep 17 00:00:00 2001 From: Nicolas Pitre Date: Mon, 10 Dec 2012 00:36:26 -0500 Subject: ARM: vexpress/tc2: clean up the cpuidle driver Use the bL_cpu_suspend method instead of bL_cpu_power_down. This allows for the driver to become usable on non SPC based platform such as RTSM if vexpress_spc_check_loaded() is removed. Signed-off-by: Nicolas Pitre --- arch/arm/mach-vexpress/cpuidle-tc2.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/arch/arm/mach-vexpress/cpuidle-tc2.c b/arch/arm/mach-vexpress/cpuidle-tc2.c index 0a983dd531b..1f1efc4936f 100644 --- a/arch/arm/mach-vexpress/cpuidle-tc2.c +++ b/arch/arm/mach-vexpress/cpuidle-tc2.c @@ -90,9 +90,7 @@ static int notrace tc2_powerdown_finisher(unsigned long arg) unsigned int cpu = mpidr & 0xf; bL_set_entry_vector(cpu, cluster, cpu_resume); - vexpress_spc_write_bxaddr_reg(cluster, cpu, - virt_to_phys(bL_entry_point)); - bL_cpu_power_down(); + bL_cpu_suspend(0); /* 0 should be replaced with better value here */ return 1; } -- cgit v1.2.3 From eb6ca80ed72ce952769cbb51cc6403c80f88ad3f Mon Sep 17 00:00:00 2001 From: mark hambleton Date: Fri, 18 Jan 2013 14:16:19 +0000 Subject: Use dts compatible node to init cpuidle-tc2 Change the init code for cpuidle-tc2 to check for a compatible node in the devicetree of "arm,generic" in preparation for moving it to driver/cpuidle. Rename functions / variable from tc2_ to bl_. Signed-off-by: mark hambleton --- arch/arm/boot/dts/vexpress-v2p-ca15_a7.dts | 2 +- arch/arm/mach-vexpress/Kconfig | 11 -- arch/arm/mach-vexpress/Makefile | 1 - arch/arm/mach-vexpress/cpuidle-tc2.c | 184 ----------------------------- drivers/cpuidle/Makefile | 2 +- drivers/cpuidle/arm_big_little.c | 183 ++++++++++++++++++++++++++++ 6 files changed, 185 insertions(+), 198 deletions(-) delete mode 100644 arch/arm/mach-vexpress/cpuidle-tc2.c create mode 100644 drivers/cpuidle/arm_big_little.c diff --git a/arch/arm/boot/dts/vexpress-v2p-ca15_a7.dts b/arch/arm/boot/dts/vexpress-v2p-ca15_a7.dts index dfe371ec274..972a56bc1d3 100644 --- a/arch/arm/boot/dts/vexpress-v2p-ca15_a7.dts +++ b/arch/arm/boot/dts/vexpress-v2p-ca15_a7.dts @@ -13,7 +13,7 @@ model = "V2P-CA15_CA7"; arm,hbi = <0x249>; arm,vexpress,site = <0xf>; - compatible = "arm,vexpress,v2p-ca15_a7", "arm,vexpress"; + compatible = "arm,vexpress,v2p-ca15_a7", "arm,vexpress", "arm,generic"; interrupt-parent = <&gic>; #address-cells = <2>; #size-cells = <2>; diff --git a/arch/arm/mach-vexpress/Kconfig b/arch/arm/mach-vexpress/Kconfig index bca54a33598..8d997ee05b6 100644 --- a/arch/arm/mach-vexpress/Kconfig +++ b/arch/arm/mach-vexpress/Kconfig @@ -60,15 +60,4 @@ config ARCH_VEXPRESS_TC2 help Support for CPU and cluster power management on TC2. -config VEXPRESS_TC2_CPUIDLE - bool "cpuidle support for TC2 test-chip (EXPERIMENTAL)" - depends on CPU_IDLE && PM && ARCH_VEXPRESS_TC2 - select ARM_CPU_SUSPEND - select ARM_SPC - help - Provides code that enables CPU idle power management on the - TC2 testchip. It enables the CPU idle driver so that the kernel - can enter cluster power down states provided by the power - controller. - endmenu diff --git a/arch/arm/mach-vexpress/Makefile b/arch/arm/mach-vexpress/Makefile index 83741a9ca73..a95282125eb 100644 --- a/arch/arm/mach-vexpress/Makefile +++ b/arch/arm/mach-vexpress/Makefile @@ -10,4 +10,3 @@ obj-$(CONFIG_ARCH_VEXPRESS_TC2) += tc2_pm.o tc2_pm_setup.o CFLAGS_REMOVE_tc2_pm.o = -pg obj-$(CONFIG_SMP) += platsmp.o obj-$(CONFIG_HOTPLUG_CPU) += hotplug.o -obj-$(CONFIG_VEXPRESS_TC2_CPUIDLE) += cpuidle-tc2.o diff --git a/arch/arm/mach-vexpress/cpuidle-tc2.c b/arch/arm/mach-vexpress/cpuidle-tc2.c deleted file mode 100644 index 1f1efc4936f..00000000000 --- a/arch/arm/mach-vexpress/cpuidle-tc2.c +++ /dev/null @@ -1,184 +0,0 @@ -/* - * TC2 CPU idle driver. - * - * Copyright (C) 2012 ARM Ltd. - * Author: 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. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -static int tc2_cpuidle_simple_enter(struct cpuidle_device *dev, - struct cpuidle_driver *drv, int index) -{ - ktime_t time_start, time_end; - s64 diff; - - time_start = ktime_get(); - - cpu_do_idle(); - - time_end = ktime_get(); - - local_irq_enable(); - - diff = ktime_to_us(ktime_sub(time_end, time_start)); - if (diff > INT_MAX) - diff = INT_MAX; - - dev->last_residency = (int) diff; - - return index; -} - -static int tc2_enter_powerdown(struct cpuidle_device *dev, - struct cpuidle_driver *drv, int idx); - -static struct cpuidle_state tc2_cpuidle_set[] __initdata = { - [0] = { - .enter = tc2_cpuidle_simple_enter, - .exit_latency = 1, - .target_residency = 1, - .power_usage = UINT_MAX, - .flags = CPUIDLE_FLAG_TIME_VALID, - .name = "WFI", - .desc = "ARM WFI", - }, - [1] = { - .enter = tc2_enter_powerdown, - .exit_latency = 300, - .target_residency = 1000, - .flags = CPUIDLE_FLAG_TIME_VALID, - .name = "C1", - .desc = "ARM power down", - }, -}; - -struct cpuidle_driver tc2_idle_driver = { - .name = "tc2_idle", - .owner = THIS_MODULE, - .safe_state_index = 0 -}; - -static DEFINE_PER_CPU(struct cpuidle_device, tc2_idle_dev); - -static int notrace tc2_powerdown_finisher(unsigned long arg) -{ - unsigned int mpidr = read_cpuid_mpidr(); - unsigned int cluster = (mpidr >> 8) & 0xf; - unsigned int cpu = mpidr & 0xf; - - bL_set_entry_vector(cpu, cluster, cpu_resume); - bL_cpu_suspend(0); /* 0 should be replaced with better value here */ - return 1; -} - -/* - * tc2_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 tc2_enter_powerdown(struct cpuidle_device *dev, - struct cpuidle_driver *drv, int idx) -{ - struct timespec ts_preidle, ts_postidle, ts_idle; - int ret; - - /* Used to keep track of the total time in idle */ - getnstimeofday(&ts_preidle); - - BUG_ON(!irqs_disabled()); - - cpu_pm_enter(); - - clockevents_notify(CLOCK_EVT_NOTIFY_BROADCAST_ENTER, &dev->cpu); - - ret = cpu_suspend((unsigned long) dev, tc2_powerdown_finisher); - if (ret) - BUG(); - - bL_cpu_powered_up(); - - clockevents_notify(CLOCK_EVT_NOTIFY_BROADCAST_EXIT, &dev->cpu); - - cpu_pm_exit(); - - getnstimeofday(&ts_postidle); - local_irq_enable(); - ts_idle = timespec_sub(ts_postidle, ts_preidle); - - dev->last_residency = ts_idle.tv_nsec / NSEC_PER_USEC + - ts_idle.tv_sec * USEC_PER_SEC; - return idx; -} - -/* - * tc2_idle_init - * - * Registers the TC2 specific cpuidle driver with the cpuidle - * framework with the valid set of states. - */ -int __init tc2_idle_init(void) -{ - struct cpuidle_device *dev; - int i, cpu_id; - struct cpuidle_driver *drv = &tc2_idle_driver; - - if (!vexpress_spc_check_loaded()) { - pr_info("TC2 CPUidle not registered because no SPC found\n"); - return -ENODEV; - } - - drv->state_count = (sizeof(tc2_cpuidle_set) / - sizeof(struct cpuidle_state)); - - for (i = 0; i < drv->state_count; i++) { - memcpy(&drv->states[i], &tc2_cpuidle_set[i], - sizeof(struct cpuidle_state)); - } - - cpuidle_register_driver(drv); - - for_each_cpu(cpu_id, cpu_online_mask) { - pr_err("CPUidle for CPU%d registered\n", cpu_id); - dev = &per_cpu(tc2_idle_dev, cpu_id); - dev->cpu = cpu_id; - - dev->state_count = drv->state_count; - - if (cpuidle_register_device(dev)) { - printk(KERN_ERR "%s: Cpuidle register device failed\n", - __func__); - return -EIO; - } - } - - return 0; -} - -late_initcall(tc2_idle_init); diff --git a/drivers/cpuidle/Makefile b/drivers/cpuidle/Makefile index 24c6e7d945e..6a7e6a9beff 100644 --- a/drivers/cpuidle/Makefile +++ b/drivers/cpuidle/Makefile @@ -4,6 +4,6 @@ obj-y += cpuidle.o driver.o governor.o sysfs.o governors/ obj-$(CONFIG_ARCH_NEEDS_CPU_IDLE_COUPLED) += coupled.o - +obj-$(CONFIG_BIG_LITTLE) += arm_big_little.o obj-$(CONFIG_CPU_IDLE_CALXEDA) += cpuidle-calxeda.o obj-$(CONFIG_CPU_IDLE_KIRKWOOD) += cpuidle-kirkwood.o diff --git a/drivers/cpuidle/arm_big_little.c b/drivers/cpuidle/arm_big_little.c new file mode 100644 index 00000000000..b97ebe018b6 --- /dev/null +++ b/drivers/cpuidle/arm_big_little.c @@ -0,0 +1,183 @@ +/* + * big.LITTLE CPU idle driver. + * + * Copyright (C) 2012 ARM Ltd. + * Author: 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static int bl_cpuidle_simple_enter(struct cpuidle_device *dev, + struct cpuidle_driver *drv, int index) +{ + ktime_t time_start, time_end; + s64 diff; + + time_start = ktime_get(); + + cpu_do_idle(); + + time_end = ktime_get(); + + local_irq_enable(); + + diff = ktime_to_us(ktime_sub(time_end, time_start)); + if (diff > INT_MAX) + diff = INT_MAX; + + dev->last_residency = (int) diff; + + return index; +} + +static int bl_enter_powerdown(struct cpuidle_device *dev, + struct cpuidle_driver *drv, int idx); + +static struct cpuidle_state bl_cpuidle_set[] __initdata = { + [0] = { + .enter = bl_cpuidle_simple_enter, + .exit_latency = 1, + .target_residency = 1, + .power_usage = UINT_MAX, + .flags = CPUIDLE_FLAG_TIME_VALID, + .name = "WFI", + .desc = "ARM WFI", + }, + [1] = { + .enter = bl_enter_powerdown, + .exit_latency = 300, + .target_residency = 1000, + .flags = CPUIDLE_FLAG_TIME_VALID, + .name = "C1", + .desc = "ARM power down", + }, +}; + +struct cpuidle_driver bl_idle_driver = { + .name = "bl_idle", + .owner = THIS_MODULE, + .safe_state_index = 0 +}; + +static DEFINE_PER_CPU(struct cpuidle_device, bl_idle_dev); + +static int notrace bl_powerdown_finisher(unsigned long arg) +{ + unsigned int mpidr = read_cpuid_mpidr(); + unsigned int cluster = (mpidr >> 8) & 0xf; + unsigned int cpu = mpidr & 0xf; + + bL_set_entry_vector(cpu, cluster, cpu_resume); + bL_cpu_suspend(0); /* 0 should be replaced with better value here */ + 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) +{ + struct timespec ts_preidle, ts_postidle, ts_idle; + int ret; + + /* Used to keep track of the total time in idle */ + getnstimeofday(&ts_preidle); + + BUG_ON(!irqs_disabled()); + + cpu_pm_enter(); + + clockevents_notify(CLOCK_EVT_NOTIFY_BROADCAST_ENTER, &dev->cpu); + + ret = cpu_suspend((unsigned long) dev, bl_powerdown_finisher); + if (ret) + BUG(); + + bL_cpu_powered_up(); + + clockevents_notify(CLOCK_EVT_NOTIFY_BROADCAST_EXIT, &dev->cpu); + + cpu_pm_exit(); + + getnstimeofday(&ts_postidle); + local_irq_enable(); + ts_idle = timespec_sub(ts_postidle, ts_preidle); + + dev->last_residency = ts_idle.tv_nsec / NSEC_PER_USEC + + ts_idle.tv_sec * USEC_PER_SEC; + return idx; +} + +/* + * bl_idle_init + * + * Registers the bl specific cpuidle driver with the cpuidle + * framework with the valid set of states. + */ +int __init bl_idle_init(void) +{ + struct cpuidle_device *dev; + int i, cpu_id; + struct cpuidle_driver *drv = &bl_idle_driver; + + if (!of_find_compatible_node(NULL, NULL, "arm,generic")) { + pr_info("%s: No compatible node found\n", __func__); + return -ENODEV; + } + + drv->state_count = (sizeof(bl_cpuidle_set) / + sizeof(struct cpuidle_state)); + + for (i = 0; i < drv->state_count; i++) { + memcpy(&drv->states[i], &bl_cpuidle_set[i], + sizeof(struct cpuidle_state)); + } + + cpuidle_register_driver(drv); + + for_each_cpu(cpu_id, cpu_online_mask) { + pr_err("CPUidle for CPU%d registered\n", cpu_id); + dev = &per_cpu(bl_idle_dev, cpu_id); + dev->cpu = cpu_id; + + dev->state_count = drv->state_count; + + if (cpuidle_register_device(dev)) { + printk(KERN_ERR "%s: Cpuidle register device failed\n", + __func__); + return -EIO; + } + } + + return 0; +} + +late_initcall(bl_idle_init); -- cgit v1.2.3 From c98a7217394f6a96c2681c6e6089360bb29bd6d1 Mon Sep 17 00:00:00 2001 From: Nicolas Pitre Date: Tue, 19 Mar 2013 15:59:24 -0400 Subject: cpuidle: arm_big_little: fixup for MCPM The low-level layer is now called "mcpm". --- drivers/cpuidle/arm_big_little.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/cpuidle/arm_big_little.c b/drivers/cpuidle/arm_big_little.c index b97ebe018b6..a430800d4a7 100644 --- a/drivers/cpuidle/arm_big_little.c +++ b/drivers/cpuidle/arm_big_little.c @@ -20,7 +20,7 @@ #include #include #include -#include +#include #include #include #include @@ -88,8 +88,8 @@ static int notrace bl_powerdown_finisher(unsigned long arg) unsigned int cluster = (mpidr >> 8) & 0xf; unsigned int cpu = mpidr & 0xf; - bL_set_entry_vector(cpu, cluster, cpu_resume); - bL_cpu_suspend(0); /* 0 should be replaced with better value here */ + mcpm_set_entry_vector(cpu, cluster, cpu_resume); + mcpm_cpu_suspend(0); /* 0 should be replaced with better value here */ return 1; } @@ -121,7 +121,7 @@ static int bl_enter_powerdown(struct cpuidle_device *dev, if (ret) BUG(); - bL_cpu_powered_up(); + mcpm_cpu_powered_up(); clockevents_notify(CLOCK_EVT_NOTIFY_BROADCAST_EXIT, &dev->cpu); -- cgit v1.2.3 From 892404cc9460bdc9156ec0ca3ac27c6f20539213 Mon Sep 17 00:00:00 2001 From: Lorenzo Pieralisi Date: Thu, 14 Mar 2013 14:07:20 +0000 Subject: ARM: TC2: reset CPUs spuriously woken up on cluster power up On TC2, all CPUs in a cluster are woken up when an IRQ event triggers for a CPU in a cluster in shutdown state. This patch puts spuriously woken CPUs back in reset by checking the pending IRQ status in the SPC wake-up interrupt status register; if the CPU has no pending IRQ routed to it, the core reexecutes wfi and it is put in reset by FW straight away. Tested-by: Viresh Kumar Signed-off-by: Lorenzo Pieralisi --- arch/arm/mach-vexpress/tc2_pm.c | 4 +++- arch/arm/mach-vexpress/tc2_pm_setup.S | 14 ++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/arch/arm/mach-vexpress/tc2_pm.c b/arch/arm/mach-vexpress/tc2_pm.c index a5e18df801d..54757301ca5 100644 --- a/arch/arm/mach-vexpress/tc2_pm.c +++ b/arch/arm/mach-vexpress/tc2_pm.c @@ -172,13 +172,15 @@ static void tc2_pm_power_down(void) static void tc2_pm_suspend(u64 residency) { + extern void tc2_resume(void); unsigned int mpidr, cpu, cluster; mpidr = read_cpuid_mpidr(); cpu = MPIDR_AFFINITY_LEVEL(mpidr, 0); cluster = MPIDR_AFFINITY_LEVEL(mpidr, 1); vexpress_spc_write_bxaddr_reg(cluster, cpu, - virt_to_phys(mcpm_entry_point)); + virt_to_phys(tc2_resume)); + tc2_pm_down(residency); } diff --git a/arch/arm/mach-vexpress/tc2_pm_setup.S b/arch/arm/mach-vexpress/tc2_pm_setup.S index 046890e5ff8..4728f83731a 100644 --- a/arch/arm/mach-vexpress/tc2_pm_setup.S +++ b/arch/arm/mach-vexpress/tc2_pm_setup.S @@ -16,6 +16,7 @@ #define SPC_PHYS_BASE 0x7FFF0000 +#define SPC_WAKE_INT_STAT 0xb2c #define SNOOP_CTL_A15 0x404 #define SNOOP_CTL_A7 0x504 @@ -42,6 +43,19 @@ #define CCI_A7_OFFSET CCI_SLAVE_OFFSET(CCI_SLAVE_A7) +ENTRY(tc2_resume) + mrc p15, 0, r0, c0, c0, 5 + ubfx r1, r0, #0, #4 @ r1 = cpu + ubfx r2, r0, #8, #4 @ r2 = cluster + add r1, r1, r2, lsl #2 @ r1 = index of CPU in WAKE_INT_STAT + ldr r3, =SPC_PHYS_BASE + SPC_WAKE_INT_STAT + ldr r3, [r3] + lsr r3, r1 + tst r3, #1 + wfieq @ if no pending IRQ reenters wfi + b mcpm_entry_point +ENDPROC(tc2_resume) + /* * Enable cluster-level coherency, in preparation for turning on the MMU. * The ACTLR SMP bit does not need to be set here, because cpu_resume() -- cgit v1.2.3