Merge branch 'tracking-armlt-tc2-pm' into lsk-3.10-vexpress

Conflicts:
	arch/arm/mach-vexpress/Makefile
This commit is contained in:
Jon Medhurst
2013-07-17 12:02:16 +01:00
11 changed files with 568 additions and 8 deletions

View File

@@ -15,7 +15,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>;

View File

@@ -262,6 +262,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;

View File

@@ -4,6 +4,7 @@
#include <asm/assembler.h>
#include <asm/glue-cache.h>
#include <asm/glue-proc.h>
#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)
bic r0, #0xff000000
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

View File

@@ -67,4 +67,12 @@ config ARCH_VEXPRESS_DCSCB
This is needed to provide CPU and cluster power management
on RTSM implementing big.LITTLE.
config ARCH_VEXPRESS_TC2
bool "TC2 cluster management"
depends on MCPM
select VEXPRESS_SPC
select ARM_CCI
help
Support for CPU and cluster power management on TC2.
endmenu

View File

@@ -8,5 +8,7 @@ obj-y := v2m.o
obj-$(CONFIG_ARCH_VEXPRESS_CA9X4) += ct-ca9x4.o
obj-$(CONFIG_ARCH_VEXPRESS_DCSCB) += dcscb.o dcscb_setup.o
CFLAGS_REMOVE_dcscb.o = -pg
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

View File

@@ -0,0 +1,258 @@
/*
* 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 <linux/init.h>
#include <linux/kernel.h>
#include <linux/spinlock.h>
#include <linux/errno.h>
#include <linux/irqchip/arm-gic.h>
#include <asm/mcpm.h>
#include <asm/proc-fns.h>
#include <asm/cacheflush.h>
#include <asm/cputype.h>
#include <asm/cp15.h>
#include <mach/motherboard.h>
#include <linux/vexpress.h>
#include <linux/arm-cci.h>
/*
* 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_resume_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_down(u64 residency)
{
unsigned int mpidr, cpu, cluster;
bool last_man = false, skip_wfi = false;
mpidr = read_cpuid_mpidr();
cpu = MPIDR_AFFINITY_LEVEL(mpidr, 0);
cluster = MPIDR_AFFINITY_LEVEL(mpidr, 1);
pr_debug("%s: cpu %u cluster %u\n", __func__, cpu, cluster);
BUG_ON(cluster >= 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] &&
(!residency || residency > 5000)) {
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();
gic_cpu_if_down();
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));
cci_disable_port_by_cpu(mpidr);
/*
* 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_power_down(void)
{
tc2_pm_down(0);
}
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_resume_reg(cluster, cpu,
virt_to_phys(tc2_resume));
tc2_pm_down(residency);
}
static void tc2_pm_powered_up(void)
{
unsigned int mpidr, cpu, cluster;
unsigned long flags;
mpidr = read_cpuid_mpidr();
cpu = MPIDR_AFFINITY_LEVEL(mpidr, 0);
cluster = MPIDR_AFFINITY_LEVEL(mpidr, 1);
pr_debug("%s: cpu %u cluster %u\n", __func__, cpu, cluster);
BUG_ON(cluster >= 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_resume_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,
.suspend = tc2_pm_suspend,
.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);

View File

@@ -0,0 +1,68 @@
/*
* 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 <linux/linkage.h>
#include <asm/mcpm.h>
#define SPC_PHYS_BASE 0x7FFF0000
#define SPC_WAKE_INT_STAT 0xb2c
#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
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()
* already restores that.
*/
ENTRY(tc2_pm_power_up_setup)
cmp r0, #0
beq 2f
b cci_enable_port_for_self
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)

View File

@@ -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_ARCH_KIRKWOOD) += cpuidle-kirkwood.o

View File

@@ -0,0 +1,183 @@
/*
* big.LITTLE CPU idle driver.
*
* Copyright (C) 2012 ARM Ltd.
* Author: Lorenzo Pieralisi <lorenzo.pieralisi@arm.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/arm-cci.h>
#include <linux/bitmap.h>
#include <linux/cpuidle.h>
#include <linux/cpu_pm.h>
#include <linux/clockchips.h>
#include <linux/debugfs.h>
#include <linux/hrtimer.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/tick.h>
#include <linux/vexpress.h>
#include <asm/mcpm.h>
#include <asm/cpuidle.h>
#include <asm/cputype.h>
#include <asm/idmap.h>
#include <asm/proc-fns.h>
#include <asm/suspend.h>
#include <linux/of.h>
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;
mcpm_set_entry_vector(cpu, cluster, cpu_resume);
mcpm_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();
mcpm_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;
}
device_initcall(bl_idle_init);

View File

@@ -453,6 +453,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

View File

@@ -67,6 +67,8 @@ 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)
{